Files
Aislo/resources/tester/test_b09_parent_steps.py
T
eomsangdonandClaude Opus 5 a81bc844fb feat(axis): 부모 공종 모양 parent_mode — 단계 합산형 조립으로 흙깎기 암절취 금액이 섬
- 마스터 부모 94곳에 parent_mode — 기본 choose_one, 합산형은 사람이 적은 셋(9-4·9-5·12-38)만 단계·가중치
- 9-4 암절취 = 암파쇄(연암·보통암·경암, 브레이커+굴착기 조합 1/Q) + 집토(굴착기 0.7 공식) 합으로 조립
- 표에 없는 암질은 9-4-1 [주]① 평균으로만 섬 · 9-5 발파암은 발파 단계(착암기 층 없음) 사유로 막힘
- B08 인계가 흙깎기 암 갈래 이름을 갈래 값으로 넘김 · 갈래 고르기형 부모를 가리키는 줄은 오류로 막음

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
2026-09-13 19:09:39 +09:00

94 lines
4.0 KiB
Python

"""부모 공종 모양 `parent_mode` (명세 2장 · 2026-09-13 축 C Ⓐ).
지키는 것
① 마스터 — 부모는 기본 choose_one, 합산형(9-4·9-5·12-38)만 사람이 적은 단계·가중치를 든다
② 흙깎기 리핑암 = 9-4 암절취 = 암파쇄(암질별 1/Q) + 집토 — 부모 제목이 잎들의 합으로 선다
③ 내역 줄의 암 갈래가 그대로 갈래 값으로 가고, 표에 없는 암질은 9-4-1 [주]① 평균으로만 선다
④ 단계 하나라도 못 서면 부모를 안 세우고 사유 · 갈래 고르기형 부모를 가리키면 오류로 막는다
"""
from __future__ import annotations
from decimal import Decimal
from functools import lru_cache
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
from B09_Estimation.B09_Estimation_BillOfQuantities import build_bill
from B09_Estimation.B09_Estimation_ResourceAxis_Sources import load_work_item_master
from B09_Estimation.B09_Estimation_UnitPrice import build_unit_prices
@lru_cache(maxsize=1)
def _build():
return build_unit_prices()
def _node(code: str) -> dict:
return next(n for n in load_work_item_master()["work_items"] if n["work_item_code"] == code)
def test_마스터_부모_모양은_기본_고르기_합산형만_단계를_든다():
assert _node("FP-10-12")["parent_mode"] == "choose_one"
rock = _node("FP-09-05")
assert rock["parent_mode"] == "sum_steps"
assert [(s["code"], s["weight"]) for s in rock["steps"]] == [
("FP-09-05-01", "0.1"),
("FP-09-05-02", "0.9"),
("FP-09-05-03", "1"),
]
assert "parent_mode" not in _node("FP-09-04-01") # 잎
def test_암절취는_암파쇄와_집토의_합으로_선다():
book = _build().book
leaf = book.details["B-FP-09-04-01#연암"]
assert {d.ref_code for d in leaf} == {"X-0201-0070#조합", "X-0230-0007"} # 조합 16 %
assert all(d.quantity == Decimal(1) / Decimal("5.0") for d in leaf)
whole = book.resolve("B-FP-09-04#연암").total
parts = book.resolve("B-FP-09-04-01#연암").total + book.resolve("B-FP-09-04-02").total
assert whole == parts > 0
assert "B-FP-09-04#평균" in book.titles # [주]① 평균
def test_단계가_못_서면_부모를_안_세우고_사유를_남긴다():
build = _build()
assert not any(code.startswith("B-FP-09-05#") for code in build.book.titles)
assert "FP-09-05-01" in build.component_gaps["FP-09-05"] # 발파 착암기 층 없음
assert "FP-12-38-02" in build.component_gaps["FP-12-38"] # 사용수량 미구현
# 깎기 표엔 기계 이름이 없음 — [주] 원문의 기종으로 섬(부모에서 물려받지 않음)
assert "[주]" in build.book.details["B-FP-09-05-02#경암"][0].note
def _bill(rock: str, method: str = "ripping"):
handoff = build_handoff(
summary_table={
"rows": [{"group": "흙깎기", "item": rock, "spec": "", "unit": "㎥", "amount": 10.0}]
},
ground_methods={rock: method},
)
return handoff["work_items"][0], build_bill(handoff, build=_build())
def test_암_갈래가_그대로_단가를_고른다():
row, bill = _bill("보통암")
assert (row["work_item_code"], row["variant_value"]) == ("FP-09-04", "보통암")
priced = next(r for r in bill.rows if not r.is_group and r.name == "흙깎기")
assert priced.unit_price_krw and "보통암" in priced.spec
def test_표에_없는_암질은_주1_평균으로만_선다():
_row, bill = _bill("풍화암")
priced = next(r for r in bill.rows if not r.is_group and r.name == "흙깎기")
assert priced.unit_price_krw and "[주]①" in priced.note
def test_발파암은_단계_사유로_막히고_고르기형_부모는_오류():
_row, bill = _bill("경암", "blasting")
assert any("단계 합산 미완" in m["reason"] for m in bill.missing)
handoff = {
"work_items": [{**_row, "work_item_code": "FP-10-12", "variant_value": None}],
"materials": [],
}
blocked = build_bill(handoff, build=_build())
assert any("갈래 고르기형 부모" in m["reason"] for m in blocked.missing)