Files
Aislo/resources/tester/test_b09_transport.py
eomsangdonandClaude Opus 5 fdc30ecf17 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>
2026-09-09 22:23:06 +09:00

117 lines
5.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""기계 수송비 — 기계경비의 셋째 몫 (2026-09-09 밤).
기계경비 = 기계손료 + 운전경비 + **수송비**(건설품셈 8-1-6의 1)인데 수송비가 통째로 빠져
있었다. 수송비를 내는 공종 `FP-10-04` 중기운반은 표가 「미판정」이라 **단가가 안 서고** 있었다.
⚠ 겨누는 것 여섯
① **거리가 없으면 한 줄도 안 선다** — 임의 거리로 금액이 조용히 서면 안 됨
② 도로 구분도 마찬가지 — 속도가 도로로 갈림
③ 사이클이 원문 그대로 — 트레일러 t1=20·t3=20·t4=0.42 / 트럭 t1=10·t3=10·t4=5
④ 운반시간은 **왕복** · 회전율 N = 60×0.9/㎝
⑤ 원문이 「-」로 둔 칸(트레일러 · 고속4차선)은 **지어내지 않고 사유로 냄**
⑥ 그 차량들의 운전경비가 실제로 읽혀 있음 — 앞서 표가 페이지에서 잘려 통째로 버려졌음
"""
from __future__ import annotations
import sys
from decimal import Decimal
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from B09_Estimation.B09_Estimation_MachineOperating import ( # noqa: E402
expand_codes,
load_operating_records,
)
from B09_Estimation.B09_Estimation_Transport import ( # noqa: E402
TRANSPORT_VARIANTS,
cycle_minutes,
hours_per_trip,
parse_distance_km,
road_class,
)
from B09_Estimation.B09_Estimation_UnitPrice import build_unit_prices # noqa: E402
_TRAILER = next(v for v in TRANSPORT_VARIANTS if v["key"] == "트레일러20ton")
_TRUCK = next(v for v in TRANSPORT_VARIANTS if v["key"] == "트럭10.5ton")
def test_거리가_없으면_한_줄도_안_선다() -> None:
build = build_unit_prices()
assert not [code for code in build.book.titles if code.startswith("B-FP-10-04")]
assert any("거리" in note for note in build.transport_notes)
def test_도로를_안_고르면_안_선다() -> None:
build = build_unit_prices(transport_distance_km=Decimal(12))
assert not [code for code in build.book.titles if code.startswith("B-FP-10-04")]
assert any("도로" in note for note in build.transport_notes)
def test_사이클이_원문_그대로다() -> None:
"""③④ 트레일러 12㎞ 사리도로(양호) — 20 + (24÷20)×60 + 20 + 0.42 = 112.42분."""
road = road_class("gravel_good")
assert road is not None
minutes = cycle_minutes(_TRAILER, Decimal(12), road)
assert minutes == Decimal(20) + Decimal("72") + Decimal(20) + Decimal("0.42")
# N = 60×0.9/㎝ 의 역수가 회당 시간이다.
hours = hours_per_trip(_TRAILER, Decimal(12), road)
assert hours == minutes / (Decimal(60) * Decimal("0.9"))
def test_트럭은_트럭_사이클을_쓴다() -> None:
road = road_class("gravel_good")
assert road is not None
minutes = cycle_minutes(_TRUCK, Decimal(12), road)
# 트럭은 사리도로(양호) 25㎞/hr — 10 + (24÷25)×60 + 10 + 5
assert minutes == Decimal(10) + Decimal("57.6") + Decimal(10) + Decimal(5)
def test_원문이_비운_칸은_지어내지_않는다() -> None:
"""⑤ 트레일러는 고속4차선 값이 원문에 「-」다."""
road = road_class("expressway_4")
assert road is not None and road["trailer"] is None
with pytest.raises(ValueError) as caught:
cycle_minutes(_TRAILER, Decimal(12), road)
assert "원문에 없습니다" in str(caught.value)
def test_거리_칸은_비우면_없음이다() -> None:
assert parse_distance_km("") is None
assert parse_distance_km(" ") is None
assert parse_distance_km("0") is None
assert parse_distance_km("12.5") == Decimal("12.5")
with pytest.raises(ValueError):
parse_distance_km("멀다")
def test_거리를_넣으면_회당_단가가_선다() -> None:
build = build_unit_prices(transport_distance_km=Decimal(12), transport_road="gravel_good")
assert build.transport_notes == []
for variant in TRANSPORT_VARIANTS:
code = f"B-FP-10-04#{variant['key']}"
title = build.book.titles[code]
assert title.unit == "회"
money = build.book.resolve(code)
assert money.total > 0
# 손료(경비)·연료(재료)·운전사(노무) 셋이 다 들어야 수송비다.
assert money.material > 0 and money.labor > 0 and money.expense > 0
def test_수송_차량_운전경비가_읽힌다() -> None:
"""⑥ 표가 페이지에서 잘려 앞자리를 못 이어받아 39 기종이 통째로 버려지고 있었다."""
codes = {record.machine_code for record in load_operating_records().records}
assert "2702-0020" in codes # 트럭 트랙터 및 평판트레일러 20ton
assert "0602-0105" in codes # 덤프트럭 10.5ton
def test_앞자리_이어받기는_이어받을_것이_있을_때만() -> None:
"""⚠ 넓히면 없는 코드를 지어낸다 — 이어받을 앞자리가 없으면 그대로 버린다."""
assert expand_codes(["0080", "0100"]) == []
assert expand_codes(["0080", "0100"], "2101") == ["2101-0080", "2101-0100"]
assert expand_codes(["2101-0010", "0015"]) == ["2101-0010", "2101-0015"]