From 3c4b915f7e4db3468602c9fd7fca4080054d1fb4 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 16 Sep 2026 15:41:58 +0900 Subject: [PATCH] =?UTF-8?q?fix(b09):=20=EA=B8=B0=EA=B3=84=20=EC=88=98?= =?UTF-8?q?=EC=86=A1=EB=B9=84=EA=B0=80=20=EC=A1=B0=EC=9A=A9=ED=9E=88=200?= =?UTF-8?q?=20=EC=9C=BC=EB=A1=9C=20=EB=B9=A0=EC=A7=80=EB=8D=98=20=EC=9E=90?= =?UTF-8?q?=EB=A6=AC=20=E2=80=94=20=EA=B1=B0=EB=A6=AC(=EC=9D=B8=EA=B7=BC?= =?UTF-8?q?=20=EC=8B=9C=C2=B7=EA=B5=B0=C2=B7=EA=B5=AC=EC=B2=AD=20=E2=86=92?= =?UTF-8?q?=20=ED=98=84=EC=9E=A5)=EA=B0=80=20=EB=B9=84=EC=96=B4=20?= =?UTF-8?q?=EC=A4=91=EA=B8=B0=EC=9A=B4=EB=B0=98(10-4)=20=EC=A4=84=EC=9D=B4?= =?UTF-8?q?=20=EC=95=88=20=EC=84=A4=20=EB=95=8C=20=EC=82=AC=EC=9C=A0?= =?UTF-8?q?=EB=A5=BC=20=EA=B7=B8=20=EA=B3=B5=EC=A2=85=EC=97=90=20=EB=B6=99?= =?UTF-8?q?=EC=9E=84(=EC=95=84=EB=9E=98=20=EC=B8=B5=20=EB=AC=B8=EA=B5=AC?= =?UTF-8?q?=20=EA=B7=B8=EB=8C=80=EB=A1=9C=20=C2=B7=20=EA=B8=88=EC=95=A1?= =?UTF-8?q?=EC=9D=80=20=EC=95=88=20=EC=A7=80=EC=96=B4=EB=83=84)=20=C2=B7?= =?UTF-8?q?=20=EC=A4=84=EC=9D=B4=20=EC=84=9C=EB=A9=B4=20=EC=82=AC=EC=9C=A0?= =?UTF-8?q?=20=EC=95=88=20=EB=B6=99=EC=9D=8C=20=C2=B7=20=EC=A1=B0=EC=82=AC?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=AA=A8=EB=93=A0=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=EA=B0=80=20=EC=88=98=EC=86=A1=EB=B9=84=200?= =?UTF-8?q?=20=EC=9D=B4=EA=B3=A0=20=EC=82=AC=EC=9C=A0=EB=8F=84=20=EC=97=86?= =?UTF-8?q?=EB=8D=98=20=EA=B2=83=EC=9D=84=20=ED=99=95=EC=9D=B8(=EB=B8=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B8)?= 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 | 8 ++++ resources/tester/test_b09_transport_reason.py | 39 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 resources/tester/test_b09_transport_reason.py diff --git a/B09_Estimation/B09_Estimation_UnitPrice.py b/B09_Estimation/B09_Estimation_UnitPrice.py index 5005265c..e025561f 100644 --- a/B09_Estimation/B09_Estimation_UnitPrice.py +++ b/B09_Estimation/B09_Estimation_UnitPrice.py @@ -1100,6 +1100,7 @@ def build_unit_prices( attach_work_items_ax(build) # 기계 수송비 — 기계경비의 셋째 몫(손료·운전경비·**수송비**). 거리가 있어야 선다. + from B09_Estimation.B09_Estimation_Transport import WORK_ITEM_CODE as TRANSPORT_CODE from B09_Estimation.B09_Estimation_Transport import attach_transport build.transport_notes = attach_transport( @@ -1108,6 +1109,13 @@ def build_unit_prices( distance_km=transport_distance_km, road_key=transport_road, ) + # ⚠ 줄이 하나도 안 서면 **사유를 그 공종에 붙인다** — 안 그러면 수송비가 조용히 0 으로 빠진다 + # (2026-09-16 조사: 거리가 비어 모든 프로젝트에서 안 서고 있었고 사유도 안 떴다). + # 문구는 아래 층 것 **그대로** 쓴다 — 여기서 새로 짓지 않는다(까닭은 그대로 올림). + if build.transport_notes and not any( + code.startswith(f"B-{TRANSPORT_CODE}") for code in build.book.titles + ): + build.component_gaps[TRANSPORT_CODE] = build.transport_notes[0] # 덤프 운반 — 운반거리(B08 유토곡선·사토장)마다 한 벌(산림품셈 10-12 「2. 운반」). attach_dump_hauls(build, master, dump_haul_m) diff --git a/resources/tester/test_b09_transport_reason.py b/resources/tester/test_b09_transport_reason.py new file mode 100644 index 00000000..24c47929 --- /dev/null +++ b/resources/tester/test_b09_transport_reason.py @@ -0,0 +1,39 @@ +"""B09 기계 수송비 — 거리가 비면 **사유가 붙은 빈 줄**이 선다(2026-09-16 브레인 · 사용자 지시). + +기계경비는 손료 + 운전경비 + **수송비**(건설품셈 8-1-6의 1)인데, 수송비는 거리(인근 시·군·구청 +소재지 → 현장)가 설계 입력이라 안 넣으면 줄이 안 섰다. ⚠ 그때 **사유도 안 떠서** 모든 프로젝트에서 +수송비가 조용히 0 이었다(2026-09-16 조사) — 「조용히 빠짐」은 우리가 내내 막아 온 자리다. +⇒ 금액은 지어내지 않는다. **사유만** 세운다 — 문구는 아래 층(`B09_Estimation_Transport`) 것 그대로. +""" + +from __future__ import annotations + +from decimal import Decimal + +from B09_Estimation.B09_Estimation_Transport import WORK_ITEM_CODE +from B09_Estimation.B09_Estimation_UnitPrice import cached_build + + +def test_거리가_비면_사유가_서고_금액은_안_섬() -> None: + build = cached_build() + assert f"B-{WORK_ITEM_CODE}" not in build.book.titles # 금액을 지어내지 않음 + gap = build.component_gaps.get(WORK_ITEM_CODE) or "" + assert "거리" in gap and "산출 조건" in gap + assert gap in build.transport_notes # 아래 층 문구 그대로 — 새로 짓지 않음 + + +def test_거리를_넣으면_줄이_서고_사유는_사라짐() -> None: + build = cached_build(transport_distance_km="12", transport_road="paved") + codes = [c for c in build.book.titles if c.startswith(f"B-{WORK_ITEM_CODE}#")] + assert codes, "거리를 넣었는데 수송비 줄이 안 섬" + assert WORK_ITEM_CODE not in build.component_gaps + detail = build.book.details[codes[0]][0] + assert detail.quantity > Decimal(0) + + +def test_한_갈래만_서면_사유는_그_갈래에만_남음() -> None: + """고속4차선은 원문에 트레일러 속도가 「-」 — 덤프 줄은 서고 트레일러만 사유.""" + build = cached_build(transport_distance_km="12", transport_road="expressway_4") + assert [c for c in build.book.titles if c.startswith(f"B-{WORK_ITEM_CODE}#")] + assert build.transport_notes and any("원문에 없습니다" in n for n in build.transport_notes) + assert WORK_ITEM_CODE not in build.component_gaps # 줄이 섰으면 공종 사유는 안 붙음