"""㉱ (가) 암 운반량을 구성비로 가르기 — 2026-09-14 브레인 판정(구성비가 정본 · 8-1 사용자 확정). B06 토사 토글 끔 = 저장값 `ripping_rock` 은 **자리표시**(「갈라 넣는 것은 설계내역 몫」)인데 운반·사토가 그 이름을 값처럼 써서 「깎기 암은 구성비가 비어 막히는데 운반 암은 리핑암으로 금액이 섬」. ⇒ 운반표 한 곳에서 암 줄을 구성비·시공법으로 가름 · 비면 「암」 한 줄로 막음(깎기와 같은 사유). 유토곡선 거리·다짐 부피는 그대로 — 검산이 안 흔들림((나)는 뒤 차례). """ from __future__ import annotations import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from B08_Quantity.B08_Quantity_Engine_EarthworkSummary import SummaryInput # noqa: E402 from B08_Quantity.B08_Quantity_Engine_EarthworkSummary import build_table as summary # noqa: E402 from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff # noqa: E402 from B08_Quantity.B08_Quantity_Engine_Handoff_Mapping import ( # noqa: E402 NOTE_METHOD_MISSING, NOTE_ROCK_RATIO_MISSING, ) from B08_Quantity.B08_Quantity_Engine_HaulSummary import build_table, check_against_plan # noqa: E402 from B08_Quantity.B08_Quantity_Engine_RockSplit import apply_rock_split # noqa: E402 CLASSES = ["토사", "풍화암", "연암", "보통암", "경암"] PLAN = { "blocks": [ { "bands": [ {"equipment": "dozer", "haul_distance_m": 40.0, "haul_from_m": 0.0, "haul_to_m": 40.0, "ea_m3": 90.0, "rr_m3": 115.0, "br_m3": 0.0}, ] } ], "transfers": [], "hauled_m3": 205.0, "transferred_m3": 0.0, } # fmt: skip SPOIL = {"volume_m3": 23.0, "distance_m": 300.0, "by_ground_m3": {"rr_m3": 23.0}, "natural_m3_by_ground": {"rr_m3": 20.0}} # fmt: skip def _haul(ratios: dict, methods: dict) -> dict: haul = build_table(PLAN) haul["spoil"] = dict(SPOIL) apply_rock_split(haul, CLASSES, ratios, methods) return haul def _rock(haul: dict) -> list[dict]: return [r for r in haul["rows"] if r["ground"] != "토사"] def test_구성비가_비면_운반_암도_한_줄_암으로_막힘() -> None: haul = _haul({}, {}) (rock,) = _rock(haul) assert rock["ground"] == "암" and rock["natural_m3"] == pytest.approx(100.0) assert rock["blocked_reason"] == NOTE_ROCK_RATIO_MISSING items = build_handoff(haul_table=haul)["work_items"] dozer = next(r for r in items if r["name"].endswith("운반") and r["ground_class"] == "암") assert ( dozer["blocked_kind"] == "input_missing" and dozer["blocked_reason"] == NOTE_ROCK_RATIO_MISSING ) spoil = next(r for r in items if r["name"] == "사토 운반") assert ( spoil["blocked_kind"] == "input_missing" and NOTE_ROCK_RATIO_MISSING in spoil["blocked_reason"] ) def test_구성비와_시공법대로_갈리고_검산은_그대로() -> None: ratios = {"연암": 60.0, "보통암": 40.0} methods = {"연암": "ripping", "보통암": "blasting"} haul = _haul(ratios, methods) got = [(r["rock_class"], r["ground"], round(r["natural_m3"], 6)) for r in _rock(haul)] assert got == [("연암", "리핑암", 60.0), ("보통암", "발파암", 40.0)] assert check_against_plan(haul, PLAN).difference_m3 == pytest.approx(0.0) items = build_handoff(haul_table=haul)["work_items"] dozer = [r for r in items if r["haul_equipment"] == "dozer" and r["ground_class"] != "토사"] assert [(r["spec"], r["variant_value"], r["blocked_kind"]) for r in dozer] == [ ("연암 · 리핑암", "리핑암", None), ("보통암 · 발파암", "발파암", None), ] spoil = [r for r in items if r["name"] == "사토 운반"] assert [(r["variant_value"], round(r["quantity"], 3)) for r in spoil] == [ ("리핑암", 12.0), ("발파암", 8.0), ] # 깎기와 같은 몫 — 흙깎기 암 1000 이 같은 구성비로 600 · 400 rows = summary(SummaryInput(earthwork_totals={"cut_rock_volume_m3": 1000.0}, rock_classes=CLASSES, rock_ratios_pct=ratios))["rows"] # fmt: skip cut = {r["item"]: r["amount"] for r in rows if r["group"] == "흙깎기" and r["item"] != "토사"} assert cut == {"연암": 600.0, "보통암": 400.0} def test_시공법을_안_고른_갈래만_막힘() -> None: haul = _haul({"연암": 50.0, "경암": 50.0}, {"연암": "ripping"}) rows = {r["rock_class"]: r for r in _rock(haul)} assert not rows["연암"]["blocked_reason"] assert rows["경암"]["blocked_reason"] == NOTE_METHOD_MISSING