From 857a93f08c4f9acf7e75eee1d3c81a233bf69ed4 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 9 Sep 2026 12:12:30 +0900 Subject: [PATCH] =?UTF-8?q?feat(B08):=20=EC=A7=80=EC=9E=A5=EB=AA=A9?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=EB=A5=BC=20=EB=91=90=20=EC=A4=84=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=80=EB=A6=84=20(=ED=99=95=EC=A0=95=205=EC=B0=A8=202?= =?UTF-8?q?=EB=B2=88)=20+=20=EC=9E=91=EC=97=85=20=EA=B0=88=EB=9E=98?= =?UTF-8?q?=EB=A5=BC=20=EC=A7=80=EB=B0=98=20=EA=B0=88=EB=9E=98=EB=A1=9C=20?= =?UTF-8?q?=EC=95=88=20=EC=9D=BD=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 실무(영월 1.9)가 한 면적에 두 작업을 얹음 — 뿌리뽑기 @475 · 잡관목제거 @882 가 같은 11,035㎡. ⚠ 이중계상이 아니라 서식이 그러함(사유에 적음) - 잡관목제거는 품셈에 이름이 없음(실무 D00033 별도 단가) — 공종 보류(확정 5차 3번)라 코드 없이 서고 사유가 붙음 - ⚠ 곁들여 잡은 것: `item` 칸을 **늘 지반 갈래로 읽고 있었음** — 지장목제거의 작업 갈래에 「시공법 미지정으로 공종을 못 고름」이라는 틀린 사유가 붙었음. 갈래로 읽는 공종을 GROUND_SPLIT_GROUPS 로 한정하고 나머지는 규격 칸으로 보냄 - tmp/tests/test_b08_tree_removal_split.py 신설(5건) Co-Authored-By: Claude Opus 5 (1M context) --- .../B08_Quantity_Engine_EarthworkSummary.py | 59 +++++++++++++------ .../B08_Quantity_Engine_Handoff_Mapping.py | 7 +++ .../B08_Quantity_Engine_Handoff_Rows.py | 11 +++- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/B08_Quantity/B08_Quantity_Engine_EarthworkSummary.py b/B08_Quantity/B08_Quantity_Engine_EarthworkSummary.py index 27a46e93..143ea9d8 100644 --- a/B08_Quantity/B08_Quantity_Engine_EarthworkSummary.py +++ b/B08_Quantity/B08_Quantity_Engine_EarthworkSummary.py @@ -170,24 +170,49 @@ def build_rows(source: SummaryInput) -> list[SummaryRow]: ) ) removal = slope.get("tree_removal_fill", 0.0) + slope.get("tree_removal_cut", 0.0) - rows.append( - SummaryRow( - group="지장목제거", - unit="㎡", - amount=removal * _ratio(source, "obstacle_removal"), - amount_gross=removal, - application_ratio_pct=_ratio(source, "obstacle_removal") * 100.0, - application_ratio_breakdown={ - "fill": _ratio(source, "obstacle_removal") * 100.0, - "cut": _ratio(source, "obstacle_removal") * 100.0, - }, - quantity_breakdown={ - "fill": slope.get("tree_removal_fill", 0.0) * _ratio(source, "obstacle_removal"), - "cut": slope.get("tree_removal_cut", 0.0) * _ratio(source, "obstacle_removal"), - }, - note=_ratio_note(source, "obstacle_removal", "성토면+절토면"), + # ⭐ 2026-09-09 **사용자 확정 5차 2번** — 지장목제거를 **두 줄로 가른다**(실무 서식). + # 영월 설계내역서 1.9 지장목제거가 두 줄이고 **같은 면적을 나눠 쓴다**: + # 1.9.1 뿌리뽑기(장비+인력) 11,035㎡ @475 + # 1.9.2 잡관목제거 벌목(5m미만) 11,035㎡ @882 ← 같은 11,035㎡ + # ⚠⚠ **이중계상이 아니다** — 한 면적에 **다른 두 작업**이 얹히는 것이라 실무가 그렇게 적는다. + # (같은 작업을 두 축에서 두 번 세는 것과는 다른 자리다.) + # ⚠ 잡관목제거는 **품셈에 그 이름이 없다** — 실무는 별도 단가(영월 D00033)를 씀. + # 공종 없는 줄 보류(확정 5차 3번)에 걸리므로 **코드 없이 서고 사유가 붙는다.** + for item, why in ( + ( + "뿌리뽑기", + "확정 5차 2번 — 실무가 뿌리뽑기·잡관목제거 두 줄로 가름(같은 면적을 나눠 씀 · 이중계상 아님)", + ), + ( + "잡관목제거", + "확정 5차 2번 — 같은 면적에 얹히는 다른 작업(이중계상 아님)." + " ⚠ 품셈에 그 이름이 없어 실무는 별도 단가를 씀(영월 D00033) — 공종 보류 대상", + ), + ): + rows.append( + SummaryRow( + group="지장목제거", + item=item, + unit="㎡", + amount=removal * _ratio(source, "obstacle_removal"), + amount_gross=removal, + application_ratio_pct=_ratio(source, "obstacle_removal") * 100.0, + application_ratio_breakdown={ + "fill": _ratio(source, "obstacle_removal") * 100.0, + "cut": _ratio(source, "obstacle_removal") * 100.0, + }, + quantity_breakdown={ + "fill": slope.get("tree_removal_fill", 0.0) + * _ratio(source, "obstacle_removal"), + "cut": slope.get("tree_removal_cut", 0.0) * _ratio(source, "obstacle_removal"), + }, + note=" · ".join( + part + for part in (_ratio_note(source, "obstacle_removal", "성토면+절토면"), why) + if part + ), + ) ) - ) rows.append( SummaryRow( group="층따기", spec="백호우", unit="㎡", amount=slope.get("bench_cut_fill", 0.0) diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py b/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py index d20bf41b..a99b51a3 100644 --- a/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py +++ b/B08_Quantity/B08_Quantity_Engine_Handoff_Mapping.py @@ -134,6 +134,13 @@ BLOCKED_UNIT_DATA_MISSING = "unit_data_missing" # 원단위·표준 물량 자 BLOCKED_FORMULA_MISSING = "formula_missing" # 수량 산출식 자체가 없음 #: 사면 계열 — 토공집계에 함께 실리지만 출처가 사면적이다. +#: ⚠ `item` 칸이 **지반 갈래**인 공종 — 그 밖의 공종에서 `item` 은 **작업 갈래**다 +#: (지장목제거의 「뿌리뽑기·잡관목제거」). 갈래로 읽으면 「시공법 미지정」이라는 **틀린 사유**가 +#: 붙는다(2026-09-09 실측). 정의처는 `EarthworkSummary` 이고 여기서 그대로 가져다 쓴다. +from B08_Quantity.B08_Quantity_Engine_EarthworkSummary import ( # noqa: E402 + GROUND_SPLIT_GROUPS as GROUND_SPLIT_GROUPS, +) + SLOPE_GROUPS = frozenset({"성토면다짐", "초류종자살포", "지장목제거", "층따기", "면고르기"}) #: 집계 합계 줄 — 내역 줄이 아니라 검산용이다. diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py b/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py index 6967234a..61303b3d 100644 --- a/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py +++ b/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py @@ -14,6 +14,7 @@ from B08_Quantity.B08_Quantity_Engine_Handoff_Mapping import ( BLOCKED_FORMULA_MISSING, BLOCKED_INPUT_MISSING, BLOCKED_UNIT_DATA_MISSING, + GROUND_SPLIT_GROUPS, HAUL_SUMMARY_GROUPS, METHOD_TO_GROUND, NOTE_HAUL_IN_SUMMARY, @@ -114,7 +115,12 @@ def _earthwork_rows( group = str(row.get("group") or "") if not group: continue - ground = row.get("item") or None + # ⚠ `item` 이 **지반 갈래인 공종**은 정해져 있다(흙깎기·측구터파기·구조물터파기). + # 그 밖(지장목제거의 「뿌리뽑기·잡관목제거」 같은 **작업 갈래**)을 갈래로 읽으면 + # 「시공법 미지정으로 공종을 못 고름」이라는 **틀린 사유**가 붙는다(2026-09-09 실측). + is_ground_split = group in GROUND_SPLIT_GROUPS + ground = (row.get("item") or None) if is_ground_split else None + work_kind = None if is_ground_split else (row.get("item") or None) origin = ORIGIN_SLOPE if group in SLOPE_GROUPS else ORIGIN_EARTHWORK # ⚠ 운반은 **집계에도 오르고 운반표에도 오른다** — 내역 줄은 운반표 쪽 하나뿐이다. is_subtotal = group in SUBTOTAL_GROUPS or group in HAUL_SUMMARY_GROUPS @@ -142,7 +148,8 @@ def _earthwork_rows( { "work_item_code": code, "name": group, - "spec": str(row.get("spec") or ""), + # 작업 갈래가 있으면 **규격 칸**에 적는다 — 갈래 축(`ground_class`)이 아니다. + "spec": str(work_kind or row.get("spec") or ""), "unit": unit, "quantity": amount, # 반영률 — 곱하기는 여기가 끝이다. 받는 쪽은 적기만 한다.