From 490cab5e2c86f8f80f629dfb7343e01ee2f6ddac Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 15 Sep 2026 05:18:16 +0900 Subject: [PATCH] =?UTF-8?q?fix(b09):=20=EC=82=AC=EC=9C=A0=20=EC=97=86?= =?UTF-8?q?=EC=9D=B4=20=EB=B2=84=EB=A0=A4=EC=A7=80=EB=8D=98=20=EC=9E=8E=20?= =?UTF-8?q?=EA=B3=B5=EC=A2=85=2049=20=EC=97=90=20=EC=82=AC=EC=9C=A0=20?= =?UTF-8?q?=E2=80=94=20=ED=91=9C=20=EB=B2=88=ED=98=B8=C2=B7=EB=AA=A8?= =?UTF-8?q?=EC=96=91(=EC=9E=91=EC=97=85=EB=9F=89=C2=B7=EC=86=8C=EC=9A=94?= =?UTF-8?q?=EB=9F=89=C2=B7=ED=98=95=EC=8B=9D=20=EB=AF=B8=EC=A0=95)=C2=B7?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=EB=8F=84=20=EC=95=88=20=EC=84=AC=EC=9D=84?= =?UTF-8?q?=20=EC=84=B1=EB=B6=84=20=EB=AF=B8=ED=99=95=EB=B3=B4=EB=A1=9C(?= =?UTF-8?q?=EC=9D=BD=EB=8A=94=20=EA=B8=B8=EC=9D=80=20=EC=95=88=20=EB=A7=8C?= =?UTF-8?q?=EB=93=A6=20=C2=B7=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=ED=95=B4=EB=8B=B9=200)?= 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 --- B09_Estimation/B09_Estimation_UnitPrice.py | 4 ++ B09_Estimation/B09_Estimation_UnreadTables.py | 42 ++++++++++++++ resources/tester/test_b09_unread_tables.py | 55 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 B09_Estimation/B09_Estimation_UnreadTables.py create mode 100644 resources/tester/test_b09_unread_tables.py diff --git a/B09_Estimation/B09_Estimation_UnitPrice.py b/B09_Estimation/B09_Estimation_UnitPrice.py index 1f8442c6..496693b3 100644 --- a/B09_Estimation/B09_Estimation_UnitPrice.py +++ b/B09_Estimation/B09_Estimation_UnitPrice.py @@ -1134,6 +1134,10 @@ def build_unit_prices( from B09_Estimation.B09_Estimation_RockLoss import attach_rock_loss attach_rock_loss(build, master) + # 제목도 사유도 없는 잎 공종 — 버린 표 번호·모양을 사유로(2026-09-15 · 모든 공종 모듈이 돈 뒤). + from B09_Estimation.B09_Estimation_UnreadTables import mark_unread_tables + + mark_unread_tables(build, master) build.labor_reliability = _labor_reliability_in_use(build.book) # ③ 할증 포함 재료량을 준 공종에 자재가 재료비로 붙지 않았는가(명세 6장 · ㉠). from B09_Estimation.B09_Estimation_Guards import check_materials_before_surcharge diff --git a/B09_Estimation/B09_Estimation_UnreadTables.py b/B09_Estimation/B09_Estimation_UnreadTables.py new file mode 100644 index 00000000..57ee2184 --- /dev/null +++ b/B09_Estimation/B09_Estimation_UnreadTables.py @@ -0,0 +1,42 @@ +"""B09 원가계산 — **사유 없이 버려지는 표에 사유를 남긴다** (2026-09-15 브레인 「버릴 때는 사유」). + +일할 표(작업량·소요량·형식 미정)를 가진 잎 공종인데 제목도 사유도 없는 것 — 표를 읽는 길이 없어 한 줄도 +안 서고 어디에도 안 남음(잰 수 49 · ㉯ 가르기에서 먼저 6). 내역서가 「일위대가가 아직 없습니다」 만 띄워 +무엇을 손볼지 모름 → 표 번호·모양을 `component_gaps` 에 적음(내역서 「성분 미확보 — …」 로 섬). +⚠ 읽는 길을 만드는 것이 아님 — 그 표가 풀리면 제목이 서서 이 사유는 저절로 안 붙음. +""" + +from __future__ import annotations + +from typing import Any + +#: 마스터 표 형식 → 사유에 쓰는 이름. 여기 없는 형식(참고·계수)은 일할 표가 아님. +FORM_LABELS = { + "productivity": "작업량 표", + "requirement": "소요량 표", + "undetermined": "형식 미정 표(모양 판정 안 됨)", +} +UNREAD = "표를 읽는 길이 없어 한 줄도 안 섬 — {tables}" + + +def mark_unread_tables(build: Any, master: dict[str, Any]) -> None: + """제목·사유가 둘 다 없는 잎 공종에 표 번호·모양 사유를 단다.""" + from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note + + nodes = master.get("work_items", []) + parents = {node.get("parent_code") for node in nodes} + titled = {code[2:].split("#")[0] for code in build.book.titles if code.startswith("B-")} + for node in nodes: + code = node["work_item_code"] + tables = [t for t in node.get("tables") or [] if t.get("pum_form") in FORM_LABELS] + if not tables or code in titled or code in parents: + continue + if ( + build.unattached.get(code) + or build.component_gaps.get(code) + or code in build.basis_missing + or known_gap_note(code) + ): + continue + listed = " · ".join(f"{t.get('pum_table_id')} {FORM_LABELS[t['pum_form']]}" for t in tables) + build.component_gaps[code] = UNREAD.format(tables=listed) diff --git a/resources/tester/test_b09_unread_tables.py b/resources/tester/test_b09_unread_tables.py new file mode 100644 index 00000000..f4f246bc --- /dev/null +++ b/resources/tester/test_b09_unread_tables.py @@ -0,0 +1,55 @@ +"""사유 없이 버려지는 표 — 2026-09-15 브레인 차례 ① (㉯ 가르기에서 6 · 전체로 재면 49). + +일할 표(작업량·소요량·형식 미정)를 가진 잎 공종인데 **제목도 사유도 없는** 것 — 표를 읽는 길이 없어 +한 줄도 안 서고 `unattached`·`component_gaps`·밑수·[주] 사유 어디에도 안 남아, 내역서엔 「일위대가가 아직 +없습니다」 만 뜸(무엇을 손볼지 모름). 예: 3-1 경계표시 F0075 「0.2 | 보통인부」(값이 이름 앞) · +6-4-1 덩굴걷기 F0158 · 7-3 아키야윈치 F0174 · 10-13-1 드론운반 F0322(형식 미정). +⇒ 읽는 길을 만드는 것은 뒤(예취기 한 모양 4 …) — 여기서는 **버린 표를 사유로 남김**(버릴 때는 사유). +""" + +from __future__ import annotations + +from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note +from B09_Estimation.B09_Estimation_ResourceAxis import load_work_item_master +from B09_Estimation.B09_Estimation_UnitPrice import cached_build + +WORK_FORMS = {"productivity", "requirement", "undetermined"} + + +def test_제목_없는_잎_공종은_모두_사유가_있음() -> None: + build = cached_build() + master = load_work_item_master() + parents = {n.get("parent_code") for n in master["work_items"]} + titled = {t[2:].split("#")[0] for t in build.book.titles if t.startswith("B-")} + silent = [ + n["work_item_code"] + for n in master["work_items"] + if n["work_item_code"] not in titled + and n["work_item_code"] not in parents + and any(t.get("pum_form") in WORK_FORMS for t in n.get("tables") or []) + and not ( + build.unattached.get(n["work_item_code"]) + or build.component_gaps.get(n["work_item_code"]) + or n["work_item_code"] in build.basis_missing + or known_gap_note(n["work_item_code"]) + ) + ] + assert silent == [], silent + + +def test_사유는_표_번호와_모양을_가리킴() -> None: + gaps = cached_build().component_gaps + for code, table in ( + ("FP-03-01", "F0075"), + ("FP-06-04-01", "F0158"), + ("FP-07-03", "F0174"), + ("FP-03-08", "F0082"), + ): + assert table in gaps[code] and "한 줄도" in gaps[code], (code, gaps.get(code)) + assert "F0322" in gaps["FP-10-13-01"] and "형식 미정" in gaps["FP-10-13-01"] + + +def test_부모와_제목_선_공종에는_안_붙음() -> None: + build = cached_build() + assert "FP-04-01" not in build.component_gaps # choose_one 부모 — 잎이 섬 + assert "FP-05-24-01" not in build.component_gaps # 제목 선 공종