From b23db36c783815e357a30c1f32a2ff7e603ef179 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 19:05:55 +0900 Subject: [PATCH] =?UTF-8?q?feat(b08):=20=EC=98=B9=EB=B2=BD=20=EC=A1=B0?= =?UTF-8?q?=EB=A6=BD=20=EC=A1=B0=EA=B0=81=20=ED=95=A9=ED=8C=90=EA=B1=B0?= =?UTF-8?q?=ED=91=B8=EC=A7=91=EC=9D=B4=20=EC=82=AC=EC=9A=A9=ED=9A=9F?= =?UTF-8?q?=EC=88=98=20=EA=B0=88=EB=9E=98=EB=A1=9C(=E3=80=8CFP-12-04#3?= =?UTF-8?q?=ED=9A=8C=E3=80=8D=20=C2=B7=20=EC=84=B1=EB=B6=84=EC=9D=98=20reu?= =?UTF-8?q?se=5Fcount=20=EA=B7=B8=EB=8C=80=EB=A1=9C)=20=C2=B7=20=EC=A7=91?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B1=B0=ED=91=B8=EC=A7=91=206=ED=9A=8C?= =?UTF-8?q?=20=E2=86=92=204=ED=9A=8C(12-15=20=ED=91=9C=20=E3=80=8C?= =?UTF-8?q?=ED=95=A9=ED=8C=904=ED=9A=8C=E3=80=8D=20=EC=A0=95=EB=B3=B8=20?= =?UTF-8?q?=C2=B7=20=EC=82=AC=EC=9C=A0=20=EB=AC=B8=EA=B5=AC=EB=8F=84=20?= =?UTF-8?q?=EA=B7=B8=20=ED=91=9C)=20=E2=80=94=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=9E=90=EC=9E=AC=20=ED=91=9C=C2=B7=EC=9D=B8=EA=B3=84=20?= =?UTF-8?q?=EC=84=B1=EB=B6=84=20=ED=95=9C=20=EB=B2=8C(=EB=B8=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B8=20=E3=89=AF=20=E2=91=A0=E2=91=A1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn --- B08_Quantity/B08_Quantity_Engine_Formwork.py | 6 ++- .../B08_Quantity_Engine_Handoff_Mapping.py | 24 ++++++++++ .../formwork_reuse_2026-01-01.json | 7 +-- .../work_item_mapping_2026-01-01.json | 3 +- .../tester/test_b08_formwork_reuse_link.py | 45 +++++++++++++++++++ 5 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 resources/tester/test_b08_formwork_reuse_link.py 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