fix(b09): 기계 수송비가 조용히 0 으로 빠지던 자리 — 거리(인근 시·군·구청 → 현장)가 비어 중기운반(10-4) 줄이 안 설 때 사유를 그 공종에 붙임(아래 층 문구 그대로 · 금액은 안 지어냄) · 줄이 서면 사유 안 붙음 · 조사에서 모든 프로젝트가 수송비 0 이고 사유도 없던 것을 확인(브레인)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
2026-09-16 15:41:58 +09:00
co-authored by Claude Opus 5
parent b6fb372543
commit 3c4b915f7e
2 changed files with 47 additions and 0 deletions
@@ -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)
@@ -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 # 줄이 섰으면 공종 사유는 안 붙음