Files
Aislo/resources/tester/test_b08_spoil_haul.py
T

205 lines
8.4 KiB
Python

"""사토를 실어 내는 줄 (2026-09-08).
⚠⚠ **유토곡선이 사토를 내는데 아무도 실어 내지 않고 있었다.** 운반 줄은 띠(`bands`)와
이동(`transfers`)에서만 만들어지고 사토는 잔량(`residuals`)으로 남아 어느 쪽에도 없었다 —
그래서 구조물 잔토 126.63㎥ 를 사토에 얹어도 **덤프가 하나도 안 늘었고**, 채집석 공제도
사토를 줄이는 값이라 **끝까지 금액에 안 나타났다**.
⚠ 겨누는 것 다섯
① 사토가 있으면 줄이 선다
② 거리를 안 정했으면 **막고 사유** — 임의 거리는 그대로 금액이 된다
③ 거리를 정하면 덤프 코드가 붙고 금액이 선다
④ ⚠ 자연방토는 실어 내지 않으므로 **뺀다**
⑤ 사토가 없으면 줄도 없다
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff, load_mapping # noqa: E402
from B08_Quantity.B08_Quantity_Router_Earthwork import _spoil_of # noqa: E402
def (**spoil: object) -> dict | None:
table = {"rows": [], "spoil": spoil}
rows = build_handoff(haul_table=table)["work_items"]
found = [row for row in rows if row["name"] == "사토 운반"]
return found[0] if found else None
def test_사토가_있으면_줄이_선다() -> None:
row = (volume_m3=126.63, distance_m=None)
assert row is not None and row["quantity"] == 126.63
def test_거리를_안_정했으면_막고_사유() -> None:
"""⚠ 품셈이 정하는 값이 아니다 — 임의 거리를 넣으면 그대로 금액이 된다."""
row = (volume_m3=126.63, distance_m=None)
assert row["blocked_kind"] == "input_missing"
assert row["in_bill"] is False
# ⚠ 문구가 **바뀐 자리** — 2026-09-09 사용자 확정으로 사토장이 **이미 있는 측점 위에만**
# 놓이게 되어 거리가 저절로 나온다. 그래서 「거리가 없다」가 아니라 「사토장을 안 놓았다」다.
assert "사토장을 아직 안 놓았고" in row["blocked_reason"]
def test_거리를_정하면_금액이_선다() -> None:
row = (volume_m3=126.63, distance_m=1200.0)
assert row["work_item_code"] == load_mapping().for_haul("dump_truck")["work_item_code"]
assert row["haul_distance_m"] == 1200.0
assert row["in_bill"] is True
assert row["blocked_kind"] is None
def test_자연방토는_뺀다() -> None:
"""⚠ 자연방토는 실어 내지 않는 몫이다 — 함께 세면 운반이 부푼다."""
got = _spoil_of({"haul_plan": {"spoil_m3": 200.0, "natural_spoil_m3": 50.0}}, {})
assert got["volume_m3"] == 150.0
assert got["distance_m"] is None
assert "자연방토" in got["note"]
def test_공제와_가산이_끝난_값을_그대로_쓴다() -> None:
"""⚠ 여기서 또 빼거나 더하면 두 번 셈이다 — 근거 문구에만 적는다."""
got = _spoil_of(
{
"haul_plan": {
"spoil_m3": 126.63,
"natural_spoil_m3": 0.0,
"structure_spoil_added_m3": 126.63,
"collected_stone_deducted_m3": 0.0,
}
},
{"spoil_site_distance_m": 800.0},
)
assert got["volume_m3"] == 126.63
assert got["distance_m"] == 800.0
assert "구조물 잔토" in got["note"]
def test_사토가_없으면_줄도_없다() -> None:
assert (volume_m3=0.0, distance_m=500.0) is None
def 줄들(**spoil: object) -> list[dict]:
rows = build_handoff(haul_table={"rows": [], "spoil": spoil})["work_items"]
return [row for row in rows if row["name"] == "사토 운반"]
def test_갈래마다_한_줄로_선다() -> None:
"""⚠ 덤프 단가가 토사·암으로 갈린다 — 합쳐 세우면 한쪽 단가로 다 물린다."""
rows = 줄들(
volume_m3=100.0,
distance_m=800.0,
by_ground_m3={"ea_m3": 40.0, "rr_m3": 60.0},
ground_unknown_m3=0.0,
)
assert {(row["ground_class"], row["quantity"]) for row in rows} == {
("리핑암", 60.0),
("토사", 40.0),
}
assert all(row["in_bill"] for row in rows)
def test_갈래를_못_붙인_몫은_따로_서고_막힌다() -> None:
"""⚠ 토사로 눅이면 임의 단가가 된다 — 거리가 있어도 이 줄은 막는다."""
rows = 줄들(
volume_m3=100.0,
distance_m=800.0,
by_ground_m3={"ea_m3": 70.0},
ground_unknown_m3=30.0,
)
unknown = next(row for row in rows if row["ground_class"] is None)
assert unknown["quantity"] == 30.0
assert unknown["in_bill"] is False
assert "지반 갈래를 못 붙인" in unknown["blocked_reason"]
def test_갈래가_안_오면_종전처럼_한_줄() -> None:
"""옛 저장분(갈래 없는 유토곡선 결과)에서도 줄이 사라지지 않는다."""
rows = 줄들(volume_m3=61.88, distance_m=None)
assert len(rows) == 1 and rows[0]["quantity"] == 61.88
def test_사토장_거리가_오면_그것이_이긴다() -> None:
"""⚠ 정확한 값이 이긴다 — 설정의 대체 거리보다 **사토장 측점까지 누가거리**가 앞선다."""
rows = 줄들(
volume_m3=100.0,
distance_m=800.0,
by_ground_m3={"ea_m3": 40.0, "rr_m3": 60.0},
distance_by_ground_m={"ea_m3": 250.0, "rr_m3": 410.0},
ground_unknown_m3=0.0,
)
got = {row["ground_class"]: row for row in rows}
assert got["토사"]["haul_distance_m"] == 250.0
assert got["리핑암"]["haul_distance_m"] == 410.0
assert all(row["in_bill"] for row in rows)
assert "사토장 측점까지" in got["토사"]["spec_detail"]
def test_사토장_거리가_없는_갈래는_대체값으로_서고_그_사실이_적힌다() -> None:
rows = 줄들(
volume_m3=100.0,
distance_m=800.0,
by_ground_m3={"ea_m3": 40.0, "rr_m3": 60.0},
distance_by_ground_m={"ea_m3": 250.0},
ground_unknown_m3=0.0,
)
got = {row["ground_class"]: row for row in rows}
assert got["리핑암"]["haul_distance_m"] == 800.0
assert "대체 거리" in got["리핑암"]["spec_detail"]
def test_사토도_자연상태로_선다() -> None:
"""⚠ 내역 수량은 자연상태 — 되돌린 값이 오면 그 값으로 선다(config 5-4-3)."""
rows = 줄들(
volume_m3=61.88,
distance_m=800.0,
by_ground_m3={"rr_m3": 61.88},
natural_m3_by_ground={"rr_m3": 53.809},
ground_unknown_m3=0.0,
)
assert rows[0]["quantity"] == 53.809
assert "자연상태" in rows[0]["spec_detail"]
def test_사토에도_적재_짝_줄이_서고_단가가_선다() -> None:
"""사토도 싣는 일이 따로(10-12 「1. 적재」) — 내역에 서는 운반 줄에만 붙고, 검산엔 안 셈."""
from B09_Estimation.B09_Estimation_BillOfQuantities import build_bill
from B09_Estimation.B09_Estimation_MachineProductivity_Dump import dump_haul_distances
from B09_Estimation.B09_Estimation_UnitPrice import cached_build
payload = build_handoff(
haul_table={
"rows": [],
"spoil": {
"volume_m3": 100.0,
"distance_m": 800.0,
"by_ground_m3": {"ea_m3": 70.0},
"ground_unknown_m3": 30.0,
},
}
)
rows = payload["work_items"]
loads = [row for row in rows if row["name"] == "덤프 적재"]
assert [(row["variant_value"], row["quantity"]) for row in loads] == [("토사", 70.0)]
assert loads[0]["haul_distance_m"] is None and loads[0]["haul_equipment"] == "dump_loading"
# 갈래 못 붙인 몫은 막힌 줄이라 짝 줄이 없음
assert len([row for row in rows if row["name"] == "사토 운반"]) == 2
bill = build_bill(payload, build=cached_build(dump_haul_m=dump_haul_distances(payload)))
got = {row.name: row for row in bill.rows if row.name in ("사토 운반", "덤프 적재")}
assert got["사토 운반"].price_code.startswith("B-FP-10-12-01#L800")
assert got["덤프 적재"].price_code == "B-FP-10-12-01#적재"
assert got["사토 운반"].amount_krw > 0 and got["덤프 적재"].amount_krw > 0
def test_되돌린_값이_없으면_다짐_그대로_두고_사유() -> None:
rows = 줄들(volume_m3=61.88, distance_m=800.0, by_ground_m3={"rr_m3": 61.88})
assert rows[0]["quantity"] == 61.88
assert "다짐상태 그대로" in rows[0]["spec_detail"]