diff --git a/B08_Quantity/B08_Quantity_Engine_Formwork.py b/B08_Quantity/B08_Quantity_Engine_Formwork.py index 65c80556..bc4bc0d1 100644 --- a/B08_Quantity/B08_Quantity_Engine_Formwork.py +++ b/B08_Quantity/B08_Quantity_Engine_Formwork.py @@ -105,15 +105,17 @@ def annotate( component["reuse_note"] = NOTE_REUSE_MISSING continue count = entry.get("reuse_count") + # 근거 — 대개 1-7-1 분류 · 그 공종 표가 직접 적었으면 그 표(집수정 12-15 · 2026-09-14). + basis = str(entry.get("basis") or "품셈 1-7-1") for component in targets: component["reuse_count"] = count component["reuse_note"] = ( - f"품셈 1-7-1 {count}회 — 「{entry.get('matched_example')}」" + f"{basis} {count}회 — 「{entry.get('matched_example')}」" if count else NOTE_NOT_APPLICABLE ) if count: - notes.append(f"{structure.get('name') or type_id} 거푸집 {count}회 (품셈 1-7-1)") + notes.append(f"{structure.get('name') or type_id} 거푸집 {count}회 ({basis})") else: missing.append(type_id) return notes, sorted(set(missing)) diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py b/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py index 4d8fa446..822d67b7 100644 --- a/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py +++ b/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py @@ -382,6 +382,30 @@ def composite_quantities( entry["not_ready"] = True entry["why"] = why missing.append({"code": spec.get("code"), "reason": why}) + if suffix == "formwork_reuse": + # 12-4 사용횟수 갈래 — B08 이 성분에 단 횟수(`Formwork.annotate`) 그대로(한 벌 · 09-14). + counts = { + component.get("reuse_count") + for component in structure.get("components") or [] + if str(component.get("name") or "").strip() in found + } + count = next(iter(counts)) if len(counts) == 1 else None + if isinstance(count, int) and count > 0: + entry["kind"] = f"{count}회" + entry["kind_basis"] = next( + ( + str(component.get("reuse_note") or "") + for component in structure.get("components") or [] + if str(component.get("name") or "").strip() in found + ), + "", + ) + entry["code"] = f"{spec.get('code')}#{count}회" + elif found: + why = "거푸집 사용횟수가 없거나 둘 이상이라 12-4 갈래를 못 고름" + entry["not_ready"] = True + entry["why"] = why + missing.append({"code": spec.get("code"), "reason": why}) if suffix == "rebar_complexity": # 갈래는 원문이 정한다 — 화면·인계에 이름과 근거를 함께 실어 사람이 검증하게 한다. complexity, why = rebar_complexity( diff --git a/resources/data_formwork/formwork_reuse_2026-01-01.json b/resources/data_formwork/formwork_reuse_2026-01-01.json index a8cc0429..ebc76622 100644 --- a/resources/data_formwork/formwork_reuse_2026-01-01.json +++ b/resources/data_formwork/formwork_reuse_2026-01-01.json @@ -67,9 +67,10 @@ }, { "type_id": "pipe_inlet_basin", - "reuse_count": 6, - "matched_example": "보호공 기초", - "note": "관보호공 집수정 — 원문 6회 줄의 「호안 및 보호공의 기초」에 해당. ⚠ 벽체까지 6회로 볼지는 확인 필요" + "reuse_count": 4, + "matched_example": "거푸집 합판4회", + "note": "그 공종 표가 직접 적은 「거푸집 | 합판4회」 가 정본 — 1-7-1 분류(「호안 및 보호공의 기초」 6회)보다 위(2026-09-14 브레인 ㉯ ②). 앞서 6회로 봤음.", + "basis": "품셈 12-15 집수정 표" }, { "type_id": "ford_pavement", diff --git a/resources/data_work_item_mapping/work_item_mapping_2026-01-01.json b/resources/data_work_item_mapping/work_item_mapping_2026-01-01.json index 037592ad..785214d5 100644 --- a/resources/data_work_item_mapping/work_item_mapping_2026-01-01.json +++ b/resources/data_work_item_mapping/work_item_mapping_2026-01-01.json @@ -346,7 +346,8 @@ "unit": "㎡", "from_components": [ "합판거푸집" - ] + ], + "kind_suffix": "formwork_reuse" }, { "code": "FP-12-38", diff --git a/resources/tester/test_b08_formwork_reuse_link.py b/resources/tester/test_b08_formwork_reuse_link.py new file mode 100644 index 00000000..0e6a4d3c --- /dev/null +++ b/resources/tester/test_b08_formwork_reuse_link.py @@ -0,0 +1,45 @@ +"""거푸집 사용횟수 → 12-4 갈래 잇기 · 집수정 4회 — 2026-09-14 브레인 ㉯ ①②. + +① 옹벽 조립 조각 「FP-12-04 합판거푸집」 에 꼬리가 없어 12-4 사용횟수 갈래(#1~6회)를 못 고름 → + B08 이 성분에 단 사용횟수(`reuse_count`, 품셈 1-7-1 옹벽 3회)를 꼬리로 붙임(값은 한 벌). +② 집수정은 **12-15 표가 직접 적은 「거푸집 합판4회」 가 정본** — 1-7-1 분류로 본 6회는 걷음. + 화면(자재 표 · 인계)과 표가 같은 값이어야 함(한 벌이 두 값으로 갈리지 않게). +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from B08_Quantity.B08_Quantity_Engine_Formwork import FORMWORK_NAMES # noqa: E402 +from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff # noqa: E402 +from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table # noqa: E402 + +WALL = {"structure_id": "w1", "type_id": "retaining_wall", "start_m": 100.0, "end_m": 110.0, + "options": {"form": "반중력식", "height_m": 2.0, "length_m": 10.0}} # fmt: skip +BASIN = {"structure_id": "b1", "type_id": "pipe_inlet_basin", "start_m": 50.0, "end_m": 50.0, + "options": {"inlet_basin_form": "□형(기본형)", "inlet_basin_material": "콘크리트", + "pipe_diameter_mm": "800"}} # fmt: skip + + +def test_옹벽_조각_합판거푸집이_사용횟수_갈래로_이어져_단가가_섬() -> None: + from B09_Estimation.B09_Estimation_UnitPrice import cached_build + + unit = build_table([WALL], {"retaining_wall": "옹벽"}) + row = next( + r for r in build_handoff(unit_quantity_table=unit)["work_items"] if r["composite_parts"] + ) + part = next(p for p in row["composite_parts"] if str(p["code"]).startswith("FP-12-04")) + assert part["code"] == "FP-12-04#3회" and not part.get("not_ready"), part + assert "B-FP-12-04#3회" in cached_build().book.titles + + +def test_집수정_거푸집은_12_15_표의_4회_화면과_인계가_같은_값() -> None: + unit = build_table([BASIN], {"pipe_inlet_basin": "집수정"}) + forms = [c for s in unit["structures"] for c in s["components"] if c["name"] in FORMWORK_NAMES] + assert forms and all(c["reuse_count"] == 4 for c in forms), forms + assert all("12-15" in c["reuse_note"] for c in forms), forms