From dd23c9d04f73a194303435af04306aa66c4a0251 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 10:37:50 +0900 Subject: [PATCH] =?UTF-8?q?feat(B09):=20=EA=B8=B0=EA=B3=84=EB=A7=8C=20?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20=EA=B3=B5=EC=A2=85=EC=9D=B4=20=EA=B3=B5?= =?UTF-8?q?=EC=8B=9D=EB=A7=8C=EC=9C=BC=EB=A1=9C=20=EC=84=9C=EA=B2=8C=20?= =?UTF-8?q?=ED=95=A8=20=E2=80=94=20=EC=B8=B5=EB=94=B0=EA=B8=B0=20=EC=82=B4?= =?UTF-8?q?=EC=95=84=EB=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「미판정 77건」을 훑다 나온 것 — 그중 일부는 **미판정이 아니라 시공능력 공식표**였음 ``` F0287 9-18 층따기 굴착기 0.7㎥ · K 0.9 · f 1/1.3 · E 0.7 · Cm 22 F0328 11-4 쇄석·혼합석 부설 같은 모양 ``` - ⚠ **자원 줄이 하나도 없으면 조립 반복문이 그 공종을 아예 안 보고 있었음** — 표에 인력 줄이 없는 기계 전용 공종이 그 자리. 공식이 온전하면 세우게 고침 - 층따기 **1,550.9원/㎡** · 쇄석 부설 **3,549.5원/㎡** 로 섬 (일위대가 203 → 206) - 내역서(2f940d8a) 합계 **2,149,464 → 6,252,172원** — 층따기 2,645.21㎡ × 1,550.9 = 4,102,708 이 실제로 붙음 - ⚠ 짝 시험 — 자원도 공식도 없으면 **여전히 안 섬**(0 원 일위대가 금지). 그 문이 넓으면 빈 제목이 무더기로 서고 화면이 「단가가 있다」고 거짓말함 검증: pytest 215 통과(신규 2) Co-Authored-By: Claude Opus 5 (1M context) --- B09_Estimation/B09_Estimation_UnitPrice.py | 30 +++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/B09_Estimation/B09_Estimation_UnitPrice.py b/B09_Estimation/B09_Estimation_UnitPrice.py index 41943249..043e61c4 100644 --- a/B09_Estimation/B09_Estimation_UnitPrice.py +++ b/B09_Estimation/B09_Estimation_UnitPrice.py @@ -333,6 +333,19 @@ def build_unit_prices(axis: AxisResult | None = None) -> UnitPriceBuild: for row in axis.rows: by_item.setdefault((row.work_item_code, getattr(row, "variant", "")), []).append(row) + # ⚠ **자원 줄이 하나도 없어도 공식이 온전하면 세운다.** 기계만 쓰는 공종(층따기 9-18 · + # 쇄석 부설 11-4)은 표에 인력 줄이 없어 자원 축이 비고, 그러면 아래 반복문이 그 공종을 + # 아예 안 본다 — 「미판정」으로 남아 있던 것이 실은 **시공능력 공식표**였다 + # (2026-09-08 미판정 77건을 훑다 발견). + for node in master.get("work_items", []): + code = node.get("work_item_code") + if not code or (code, "") in by_item: + continue + for table in node.get("tables", []): + if isinstance(extract_cycle_factors(code, table), CycleFactors): + by_item[(code, "")] = [] + break + for (work_item_code, variant), rows in sorted(by_item.items()): # 갈래 키는 **내부 공백을 지운 것**, 화면 문구는 **원문 그대로** # (2026-09-08 두 창 합의). 원문이 「보 통」·「보 통」으로 들쭉날쭉해 @@ -349,7 +362,8 @@ def build_unit_prices(axis: AxisResult | None = None) -> UnitPriceBuild: for row in rows ] attachable = [(row, ref) for row, ref in attachable if ref in build.book.titles] - if not attachable: + if not attachable and not _has_full_formula(master, work_item_code): + # 붙을 상세도 없고 공식도 없으면 **제목도 안 세운다**(0 원 일위대가 금지). build.skipped.append(work_item_code) continue @@ -416,6 +430,20 @@ def build_unit_prices(axis: AxisResult | None = None) -> UnitPriceBuild: return build +def _has_full_formula(master: dict, work_item_code: str) -> bool: + """그 공종에 **온전한 시공능력 공식**이 있는가 (기계만 쓰는 공종용).""" + node = next( + (w for w in master.get("work_items", []) if w.get("work_item_code") == work_item_code), + None, + ) + if node is None: + return False + return any( + isinstance(extract_cycle_factors(work_item_code, table), CycleFactors) + for table in node.get("tables", []) + ) + + def _share_of(row) -> Decimal: """그 줄이 차지하는 몫(0~1). 배분율이 없으면 1 — 종전과 같다.""" ratio = getattr(row, "group_ratio_pct", None)