feat(B09): 기계 수송비를 세움 — 기계경비의 셋째 몫이 통째로 빠져 있었음

기계경비 = 기계손료 + 운전경비 + 수송비인데 수송비가 없었음. 수송비를 내는 공종
FP-10-04 중기운반은 표가 미판정이라 단가가 아예 안 섰음.

- 산림품셈 10-4 사이클 그대로 — 트레일러(20TON) t1=20·t3=20·t4=0.42 /
  트럭(10.5TON) t1=10·t3=10·t4=5 · ㎝=t1+t2+t3+t4 · N=60×0.9/㎝, 단위는 회당.
- 거리·도로 구분은 설계 입력 — 비면 줄이 안 서고 사유만 남음(임의 거리 금지).
- 원문이 「-」로 둔 칸(트레일러·고속4차선)은 지어내지 않고 사유로 냄.
- ⚠ 우리가 정한 둘을 화면 근거에 적음 — 속도를 8-1-6의 2 나 이동속도표에서 가져온 것,
  운반시간을 왕복으로 본 것.
- 곁다리: 운전경비 표가 페이지에서 잘려 앞자리를 못 이어받아 39 기종이 통째로
  버려지고 있었음(그 안에 수송 차량 2702 가 있었음). 이어받되 카탈로그에 있는
  코드일 때만 받게 함 — 운전경비 줄 92 → 158.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-09 22:23:06 +09:00
co-authored by Claude Opus 5
parent 08c0ec3134
commit fdc30ecf17
6 changed files with 473 additions and 6 deletions
@@ -64,6 +64,7 @@ from B09_Estimation.B09_Estimation_ResourceAxis import (
)
from B09_Estimation.B09_Estimation_WorkItemUnit import unit_of as work_item_unit
from B09_Estimation.B09_Estimation_Rounding import OutputPlace, round_at
from B09_Estimation.B09_Estimation_Transport import parse_distance_km
_RE_EMPHASIS = re.compile(r"\*\*(.+?)\*\*")
_ZERO = Decimal(0)
@@ -92,6 +93,8 @@ class UnitPriceBuild:
skipped: list[str] = field(default_factory=list)
#: 기계 성분이 비어 있는 채로 쓰인 기종 (연료·조종원 미확보).
incomplete_machines: list[str] = field(default_factory=list)
#: 수송비를 왜 못 세웠나 — 거리·도로 구분이 없으면 여기에 사유가 남는다(빈칸으로 안 둔다).
transport_notes: list[str] = field(default_factory=list)
#: 자원은 알아봤는데 **값을 못 읽은 줄**이 있어 단가를 못 세운 공종 — 사유 문구.
#: ⚠ 일위대가가 **아예 안 선** 경우에도 남는다 — 「일위대가 없음」과 「성분이 빠져
#: 못 세움」은 할 일이 다르므로 화면에서 갈라 보여야 한다(2026-09-08 산마루측구).
@@ -542,6 +545,8 @@ def build_unit_prices(
machine_picks: dict[str, str] | None = None,
misc_material_percent: Decimal | None = None,
fuel_region: str | None = None,
transport_distance_km: Decimal | None = None,
transport_road: str | None = None,
) -> UnitPriceBuild:
"""자원 축을 일위대가(`B`)로 조립한다.
@@ -555,6 +560,9 @@ def build_unit_prices(
사용자 확정 5차 작은 것 1 「지금은 안 넣되 숫자 넣으면 되게 열어 둘 것」 그대로다.
⚠ `fuel_region` — 유가 시도코드(품셈 8-1-7 5호). **안 주면 전국평균**이다.
⚠ `transport_*` — 기계 수송비(산림품셈 10-4 · 건설품셈 8-1-6의 2). **거리·도로 구분이
없으면 그 줄이 안 선다** — 임의 거리로 금액을 세우지 않는다.
"""
from B09_Estimation.B09_Estimation_FactorChoices import (
chosen_values,
@@ -658,6 +666,11 @@ def build_unit_prices(
# 공식표에만 나오는 기종도 사용료 층을 세운다 — 안 세우면 공식이 붙을 데가 없다.
machine_codes |= formula_machine_codes(master)
machine_codes |= {row["machine_code"] for rows in capacity_rows.values() for row in rows}
# 수송 차량(트레일러·트럭)은 어느 공종의 자원 줄도 아니다 — **거리가 들어왔을 때만** 세운다.
if transport_distance_km is not None:
from B09_Estimation.B09_Estimation_Transport import TRANSPORT_VARIANTS
machine_codes |= {variant["machine_code"] for variant in TRANSPORT_VARIANTS}
build.incomplete_machines = _add_machine_layers(build.book, machine_codes, fuel_region)
# 규격 갈래(`variant`)가 있으면 **갈래마다 따로 세운다** — 무근·철근·소형구조물은
@@ -852,6 +865,16 @@ def build_unit_prices(
covered += machine_share
if covered < Decimal(100):
build.partial_ratio[work_item_code] = covered
# 기계 수송비 — 기계경비의 셋째 몫(손료·운전경비·**수송비**). 거리가 있어야 선다.
from B09_Estimation.B09_Estimation_Transport import attach_transport
build.transport_notes = attach_transport(
build.book,
names,
distance_km=transport_distance_km,
road_key=transport_road,
)
# ⚠ **마지막에 한 번** — 조합 사용 공종의 본체 기계를 잡재료 16% 층으로 바꿔 단다
# (품셈 제8장 [주]⑤). 조립 도중에 바꾸면 어느 공종이 조합인지 아직 모른다.
build.combined_swapped = _apply_combined_misc_rate(
@@ -982,6 +1005,8 @@ def cached_build(
machine_picks: tuple[tuple[str, str], ...] = (),
misc_material_percent: str = "",
fuel_region: str = "",
transport_distance_km: str = "",
transport_road: str = "",
) -> UnitPriceBuild:
"""조립 결과를 한 번만 만든다 — 품셈 3 MB 를 요청마다 다시 읽지 않는다.
@@ -1005,6 +1030,8 @@ def cached_build(
machine_picks=machine_choices(settings),
misc_material_percent=parse_misc_material_percent(misc_material_percent),
fuel_region=fuel_region or None,
transport_distance_km=parse_distance_km(transport_distance_km),
transport_road=transport_road or None,
)