"""공종 마스터 — 부모 공종의 모양 `parent_mode` (명세 2장 · 2026-09-13 축 C Ⓐ). 아래 절이 있는 공종(부모)은 두 모양이고 규칙이 다르다. choose_one 갈래 고르기형 — 잎 하나를 고른다. **부모에 금액을 붙이지 않는다.** (기본) sum_steps 단계 합산형 — 내역 한 줄이 곧 부모이고, 일위대가는 **잎들의 합**으로 조립한다. ⚠ **자동 판정 금지** — 표 모양으로 짐작하면 조용히 틀린다. 합산형은 아래에 **사람이 적은 것**만. ⚠ 가중치(`weight`)도 사람이 적는다 — 원문 절 제목이 준 비율만(「발파 10%」·「깎기(90%)」). """ from __future__ import annotations from typing import Any PARENT_CHOOSE_ONE = "choose_one" PARENT_SUM_STEPS = "sum_steps" #: 단계 합산형 부모 — (잎 코드, 가중치) 차례와 근거. ⚠ 여기 없는 부모는 전부 `choose_one`. SUM_STEPS: dict[str, dict[str, Any]] = { "FP-09-04": { "steps": (("FP-09-04-01", "1"), ("FP-09-04-02", "1")), "basis": ( "품셈 9-4 암절취 = 9-4-1 암파쇄 + 9-4-2 집토 — 같은 ㎥ 를 깨고 모음" "(지식DB 토공_수량 「암절취 (암파쇄ㆍ집토) | 9-4」)" ), }, "FP-09-05": { "steps": (("FP-09-05-01", "0.1"), ("FP-09-05-02", "0.9"), ("FP-09-05-03", "1")), "basis": ( "품셈 9-5 발파암 — 절 제목 「9-5-1 육상, 암석 절취(발파 10%)」·「9-5-2 깎기(90%)」·" "「9-5-3 집토」(지식DB 토공_수량 「발파암 (절취 10 %ㆍ깎기 90 %ㆍ집토) | 9-5」)" ), }, "FP-12-38": { "steps": (("FP-12-38-02", "1"), ("FP-12-38-03", "1")), "basis": ( "품셈 12-38 유로폼 = 12-38-2 사용수량(자재) + 12-38-3 설치 및 해체(품) — " "12-38-1 사용횟수는 금액 단계가 아니라 사용수량의 잔존율 조건" ), }, } def apply_parent_modes(nodes: list[dict[str, Any]]) -> None: """부모 공종에 `parent_mode`(합산형이면 `steps`·`steps_basis` 까지)를 단다 — 자리에서. ⚠ 선언한 잎이 그 부모의 자식이 아니면 멈춘다 — 판이 바뀌어 절 번호가 옮겨 간 자리다. """ children: dict[str, list[str]] = {} for node in nodes: if node.get("parent_code"): children.setdefault(node["parent_code"], []).append(node["work_item_code"]) for node in nodes: code = node["work_item_code"] if code not in children: continue declared = SUM_STEPS.get(code) if declared is None: node["parent_mode"] = PARENT_CHOOSE_ONE continue strays = [step for step, _ in declared["steps"] if step not in children[code]] if strays: raise ValueError(f"{code} 합산 단계가 자식이 아닙니다: {strays}") node["parent_mode"] = PARENT_SUM_STEPS node["steps"] = [{"code": step, "weight": weight} for step, weight in declared["steps"]] node["steps_basis"] = declared["basis"] missing = [code for code in SUM_STEPS if code not in children] if missing: raise ValueError(f"합산형으로 적은 부모가 마스터에 없습니다: {missing}")