"""암 운반량을 **구성비로 가르기** — ㉱ (가) (2026-09-14 브레인 판정 · 8-1 사용자 확정). B06 의 암은 **한 종류 자리표시**다 — 토사 토글을 끄면 저장값이 `ripping_rock` 이 되지만 그 뜻은 「암」이고 「갈라 넣는 것은 설계내역 몫」(`B06_Section_UI_Cross_Design.ts` 머리 주석). 그런데 운반표·사토가 그 이름(리핑암)을 **값처럼** 넘겨, 깎기 암은 구성비가 비어 막히는데 운반 암은 금액이 섰다. ⇒ 운반표를 만든 **한 곳**(`Router_Earthwork`)에서 암 줄을 흙깎기와 **같은 구성비**(`_split_by_rock`)와 갈래별 시공법으로 가른다. 운반거리 탭·토공집계 운반 줄·인계·사토가 모두 이 표를 읽어 한 값이 된다. ⚠ 유토곡선 거리·다짐 부피는 그대로(자리표시 C 로 쌓은 값) — 부피를 몫대로 나눌 뿐이라 검산이 안 흔들림. C 까지 구성비로 맞추는 것은 (나) 차례. ⚠ 구성비가 비면 「암」 한 줄로 **막는다** — 깎기와 같은 사유(지어낸 갈래로 금액을 세우지 않음). """ from __future__ import annotations from typing import Any, Iterable from B08_Quantity.B08_Quantity_Engine_EarthworkSummary import SummaryInput, _split_by_rock from B08_Quantity.B08_Quantity_Engine_Handoff_Mapping import ( BLOCKED_INPUT_MISSING, METHOD_TO_GROUND, NOTE_METHOD_MISSING, NOTE_ROCK_RATIO_MISSING, ) #: 유토곡선이 넘기는 암 이름 — 둘 다 자리표시로 본다(옛 자료 `blasting_rock` 도 「암」). ROCK_GROUNDS = ("리핑암", "발파암") #: 몫대로 나누는 수량 칸 — 운반표 줄(`HaulSummary.build_table`). SHARED_KEYS = ("volume_m3", "natural_m3", "work_m3m") def rock_shares( classes: Iterable[str], ratios_pct: dict[str, Any], methods: dict[str, str | None] ) -> list[dict[str, Any]]: """갈래별 몫 — `{rock_class, fraction, ground, blocked_reason, note}`. 흙깎기와 같은 안분.""" source = SummaryInput(rock_classes=list(classes), rock_ratios_pct=dict(ratios_pct or {})) shares = [] for name, fraction, note in _split_by_rock(1.0, source): ground = "암" if name == "암" else METHOD_TO_GROUND.get(methods.get(name) or "") if name == "암": reason = NOTE_ROCK_RATIO_MISSING else: reason = "" if ground else NOTE_METHOD_MISSING shares.append( { "rock_class": name, "fraction": fraction, "ground": ground or name, "blocked_reason": reason, "note": note, } ) return shares def split_rows( rows: Iterable[dict[str, Any]], shares: list[dict[str, Any]] ) -> list[dict[str, Any]]: """암 줄을 몫마다 한 줄로 — 토사 줄은 그대로.""" out: list[dict[str, Any]] = [] for row in rows: if row.get("ground") not in ROCK_GROUNDS: out.append(row) continue for share in shares: numbers = { key: row[key] * share["fraction"] for key in SHARED_KEYS if isinstance(row.get(key), (int, float)) } out.append( { **row, **numbers, "ground": share["ground"], "rock_class": share["rock_class"], "rock_split_note": share["note"], "blocked_kind": BLOCKED_INPUT_MISSING if share["blocked_reason"] else None, "blocked_reason": share["blocked_reason"], } ) return out def split_amounts( by_ground: dict[str, float], shares: list[dict[str, Any]] | None ) -> list[tuple[str, float, str, str | None, str]]: """갈래별 물량(사토) → `(갈래, 물량, 막힘 사유, 암 갈래, 원래 이름)`. 몫이 없으면 그대로.""" out = [] for label, amount in sorted(by_ground.items()): if shares and label in ROCK_GROUNDS: out.extend( (s["ground"], amount * s["fraction"], s["blocked_reason"], s["rock_class"], label) for s in shares ) else: out.append((label, amount, "", None, label)) return out def apply_rock_split( haul: dict[str, Any], classes: Iterable[str], ratios_pct: dict[str, Any], methods: dict[str, str | None], ) -> None: """운반표(`rows`)를 자리에서 가르고 사토가 같은 몫을 쓰게 `rock_shares` 를 싣는다.""" shares = rock_shares(classes, ratios_pct, methods) haul["rows"] = split_rows(haul.get("rows") or [], shares) haul["rock_shares"] = shares