From 446426e99750fa3736b1d462b4f6927611ad1ead Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 9 Sep 2026 11:19:57 +0900 Subject: [PATCH] =?UTF-8?q?feat(B09):=20=EC=9B=90=EB=AC=B8=EC=97=90?= =?UTF-8?q?=EB=8A=94=20=EC=9E=88=EB=8A=94=EB=8D=B0=20=EB=8B=A8=EA=B0=80?= =?UTF-8?q?=EC=97=90=20=EB=AA=BB=20=EC=8B=A4=EB=A6=B0=20=EB=AA=AB=EC=9D=84?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=EC=9D=B4=20=EB=A7=90=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「반쪽 단가」 표시가 여태 **이름을 카탈로그에서 못 찾은 줄**만 잡고 있었음. 그물 밖에 둘이 더 있었고, 둘 다 **금액이 서 있는 줄**이라 표시가 없으면 완성된 값으로 읽혔음. - 기초잡석 12-25 — 원문에 「운반 | 덤프트럭(15ton)」 줄이 있는데 우리 단가에 없음. ⚠ 사유를 「원문이 값을 안 줌」으로 뭉뚱그리지 않음 — 그 값은 품셈 10-12(덤프운반)가 **운반거리로** 내는 것이라 **거리가 정해지면 풀림**. 「영영 막힘」과 「곧 풀림」은 사용자가 보는 뜻이 다름 - 기초잡석 소할 — 원문이 「**브레이커 사용할 때 제외**」라 한 줄인데 지금은 무조건 붙음. ⚠ 조건부 열두 줄 중 **이것만 금액이 서 있음**(다른 열한 줄은 붙는 쪽, 이것은 빠지는 쪽) - 규준틀 11-2·11-3 — 지금 값은 **인력 품만**. 손율은 원문에 있음(비탈 50% · 수평 80%) ⚠ 앞 보고를 정정함 — 규준틀은 「표시가 아무것도 없다」고 했으나 **B08 인계가 이미 「재료량은 [주]④ 설계수량에 따른다라 미확보」를 싣고 있었음.** 화면 문구 뒤쪽이 잘려 안 보였던 것임. 그래서 그 말은 되풀이하지 않고 **B09 쪽에서만 아는 것**만 보탬. 「잘린 자리를 사유가 없는 자리로 읽지 말 것」을 주석에 남김 검증: pytest 300 통과(신규 5) Co-Authored-By: Claude Opus 5 (1M context) --- .../B09_Estimation_BillOfQuantities_Rows.py | 9 +++ B09_Estimation/B09_Estimation_KnownGaps.py | 67 +++++++++++++++++++ .../B09_Estimation_UnitPrice_View.py | 4 ++ 3 files changed, 80 insertions(+) create mode 100644 B09_Estimation/B09_Estimation_KnownGaps.py diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py index ed02f559..fbf2c438 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py @@ -314,6 +314,12 @@ def _leaf_row( if pending: row.note = " / ".join(part for part in (row.note, pending) if part) + # ⚠ **원문에는 있는데 단가에 못 실린 몫**도 같은 자리에서 말한다. 금액이 서 있는 줄이라 + # 표시가 없으면 완성된 값으로 읽힌다(규준틀 둘이 인력만으로 492만원이었다). + gap = known_gap_note(node.code) + if gap: + row.note = " / ".join(part for part in (row.note, gap) if part) + # 쓰인 차례를 기억한다 — 실무 참조번호(「단산 46」)가 그 차례다. if price_code not in result.used_unit_prices: result.used_unit_prices.append(price_code) @@ -350,6 +356,9 @@ _PENDING_FORMULA: dict[str, str] = { } +from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note # noqa: E402 + + def pending_formula_note(code: str | None) -> str: """그 공종의 수량 산식이 확정 대기인가 — 맞으면 실을 문구.""" if not code: diff --git a/B09_Estimation/B09_Estimation_KnownGaps.py b/B09_Estimation/B09_Estimation_KnownGaps.py new file mode 100644 index 00000000..ac0f36b2 --- /dev/null +++ b/B09_Estimation/B09_Estimation_KnownGaps.py @@ -0,0 +1,67 @@ +"""B09 원가계산 — **원문에는 있는데 단가에 못 실린 몫** (2026-09-09). + +「반쪽 단가」 표시는 여태 **이름을 카탈로그에서 못 찾은 줄**만 잡았다. 그물 밖에 둘이 더 있다. + + ㉠ 표에 줄은 있는데 **값을 못 적는** 것 — 기초잡석 「운반 | 덤프트럭(15ton)」 + ㉡ 표에 아예 없고 **[주]가 별도라 한** 것 — 규준틀 목재·표지판 + +둘 다 **금액이 서 있는 줄**이라 표시가 없으면 **완성된 값으로 읽힌다**(2026-09-09 실측: +규준틀 둘이 4,921,267원인데 인력만의 값이었다). + +⚠ **사유를 뭉뚱그리지 않는다.** 「원문이 값을 안 줌」은 **영영 막힌 것**으로 읽히고, +「거리 미정」·「설계수량 대기」는 **곧 풀릴 것**으로 읽힌다. 사용자가 보는 뜻이 다르다. + +⚠ **여기 적는 것은 원문에 있는 말뿐이다.** 값을 만들지 않는다 — 무엇이 왜 빠졌는지만 적는다. +⚠ **마스터가 [주]를 싣게 되면 이 표는 지운다** — 두 곳에 같은 말을 두면 한쪽만 고쳐진다. +""" + +from __future__ import annotations + +#: 공종코드 → (짧은 딱지, 사유 한 줄). 접두사로 맞춘다(갈래가 붙어도 걸리게). +KNOWN_GAPS: dict[str, tuple[str, str]] = { + # ⚠ 규준틀 둘은 **B08 인계가 이미 「재료량은 [주]④ 설계수량에 따른다라 미확보」**를 싣는다. + # 그러니 그 말을 되풀이하지 않고 **B09 쪽에서만 아는 것**만 보탠다 — + # ㉠ 지금 선 값이 **인력 품만**이라는 것 ㉡ 손율은 원문에 이미 있다는 것. + # (2026-09-09: 처음엔 「표시가 아무것도 없다」고 봤는데, 인계 사유가 화면 문구 뒤쪽에 + # 잘려 안 보였던 것이다. 잘린 자리를 사유가 없는 자리로 읽지 말 것.) + "FP-11-02": ( + "인력 품만", + "ⓘ 지금 값은 **인력 품만**입니다. 재료가 서면 손율이 함께 걸립니다 — " + "품셈 11-2 [주]③ 「목재의 손율은 1개소 사용당 50%」.", + ), + "FP-11-03": ( + "인력 품만", + "ⓘ 지금 값은 **인력 품만**입니다. 재료가 서면 손율이 함께 걸립니다 — " + "품셈 11-3 [주]③ 「목재의 손율은 1개소 사용당 80%」.", + ), + "FP-12-25": ( + "운반거리 미정", + "⚠ 이 값에는 **운반 몫이 빠져 있습니다** — 품셈 12-25 는 「운반 | 덤프트럭(15ton)」 줄을 " + "두었으나 시간을 적지 않았습니다. 그 값은 품셈 10-12(덤프운반)가 **운반거리로** 냅니다" + "(같은 15ton 장비). 거리가 정해지면 사토 운반·덤프 운반과 **함께** 섭니다.", + ), +} + +#: 조건이 서면 **빠져야 하는** 줄 — 지금은 무조건 붙어 있다. +#: ⚠ 다른 조건부 줄들(계획서 9-9)과 달리 **이것은 금액이 서 있는 줄**이다. +CONDITIONAL_INCLUDED: dict[str, str] = { + "FP-12-25": ( + "ⓘ 소할(할석공 0.06인)은 품셈 12-25 가 「**브레이커 사용할 때 제외**」라 적은 줄입니다 — " + "브레이커 갈래가 서면 이 몫이 빠져야 합니다. 지금은 붙어 있습니다." + ), +} + + +def known_gap_note(code: str | None) -> str: + """그 공종에 **원문에는 있는데 못 실린 몫**이 있으면 사유 한 줄.""" + if not code: + return "" + plain = str(code).split("#")[0] + parts = [] + for prefix, (_label, note) in KNOWN_GAPS.items(): + if plain.startswith(prefix): + parts.append(note) + for prefix, note in CONDITIONAL_INCLUDED.items(): + if plain.startswith(prefix): + parts.append(note) + return " / ".join(parts) diff --git a/B09_Estimation/B09_Estimation_UnitPrice_View.py b/B09_Estimation/B09_Estimation_UnitPrice_View.py index b3cd3790..200999ec 100644 --- a/B09_Estimation/B09_Estimation_UnitPrice_View.py +++ b/B09_Estimation/B09_Estimation_UnitPrice_View.py @@ -167,6 +167,8 @@ def detail_of(build: UnitPriceBuild, code: str) -> dict: money = build.book.resolve(code) # 코드에서 공종을 도로 뽑는다 — 「B-FP-09-11-01#갈래」의 갈래는 떼고 본다. work_item_code = code[2:].split("#")[0] if code.startswith("B-") else "" + from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note + unattached = list(build.unattached.get(work_item_code, [])) rows: list[dict] = [] for detail in build.book.details.get(code, []): @@ -261,6 +263,8 @@ def detail_of(build: UnitPriceBuild, code: str) -> dict: # ⚠ **표에 있는데 못 붙은 줄** — 이 단가가 일부만으로 섰다는 뜻이다. # 안 보이면 조용히 싼 단가가 내역서에 그대로 든다. "unattached": unattached, + # ⚠ 원문에는 있는데 못 실린 몫 — 이름을 못 찾은 줄과 **다른 갈래**다. + "known_gap_note": known_gap_note(work_item_code), "unattached_note": ( f"⚠ 품셈 표에 있는 {len(unattached)}줄이 아직 안 붙었습니다 — " f"{', '.join(unattached[:4])}"