"""B09 원가계산 — **발파 화약류 자재**(9-5-1 · 2026-09-14 브레인 300 판정). 표 F0243 「폭약 kg 0.35 · 뇌관 개 1.0 · 비트 개 0.008」 은 **규격을 안 적음** — 자원 목록(`AR-M`) 항목은 있으나 조인 규칙(규격이 같아야 · 후보 하나여도 자동 안 고름)에 걸려 못 붙었음. ⇒ 치즐과 같은 모양: 「자재 단가」 탭에 칸이 서고 **설계자가 규격·단가를 넣으면** 표 수량으로 붙음. 안 넣으면 「규격 미정」 사유로 못 붙은 줄에 남음(임의로 규격을 고르지 않음). ⚠ 잡재료비 「주재료의 5%」(폭약 줄 비고)는 아직 안 걺 — 사유에 적음. """ from __future__ import annotations from decimal import Decimal from typing import Any #: 공종 → (표 이름, 자원 목록 코드). 표 이름은 공백을 지운 글. EXPLOSIVES: dict[str, tuple[tuple[str, str], ...]] = { "FP-09-05-01": ( ("폭약", "AR-M-6fc2930f"), ("뇌관", "AR-M-0965627d"), ("비트", "AR-M-a1a18ec4"), ), } EXPLOSIVE_MISSING = ( "{name} — 규격 미정(원문 표가 규격을 안 적음) · 「자재 단가」 탭에서 규격·단가를 넣으면 붙음" ) MISC_NOTE = "ⓘ 폭약 줄 비고 「잡재료비: 주재료의 5%」 는 아직 안 걺" #: 착암기 — 건설품셈 8-3-6 (5205) 공기압축기 손료표 [주]① 「부수물(호스포함)은 별도 계상한다」 · #: 부수물 관계표에 「래그 해머 2.7㎥/min」 · 래그해머 손료표는 고시 없음(2026-09-14 안티그래비티 · #: 원문 L2646~2683). 압축기 손료에 든다고 **정하지 않음** — 원문이 「별도」. LEG_HAMMER = "착암기2.7㎥/min" LEG_HAMMER_NO_LOSS = ( "착암기 2.7㎥/min — 공기압축기의 부수물 「래그 해머」(건설품셈 8-3-6 (5205) [주]① 「부수물은" " 별도 계상」)이라 압축기 손료에 안 듦 · 래그해머 손료표는 원문에 고시 없음 → 손료를 못 셈" ) def _table_amount(node: dict[str, Any], name: str) -> Decimal | None: """표에서 그 이름 줄의 첫 수 — 없으면 `None`.""" from B09_Estimation.B09_Estimation_ResourceAxis import parse_amount for table in node.get("tables") or []: for row in table.get("raw_row") or []: cells = [str(cell) for cell in row] names = ["".join(cell.split()) for cell in cells] if name not in names: continue index = names.index(name) value = next((parse_amount(c) for c in cells[index + 1 :] if parse_amount(c)), None) if value is not None: return value return None def attach_explosives(build: Any, nodes_by_code: dict[str, dict[str, Any]]) -> None: """칸을 세우고(`material_uses`) · 단가가 든 것은 붙이고 · 안 든 것은 사유로 갈아 끼움.""" from B09_Estimation.B09_Estimation_PriceBook import PriceDetail for code, items in EXPLOSIVES.items(): node = nodes_by_code.get(code) or {} labels = list(build.unattached.get(code) or []) for name, material in items: build.material_uses.setdefault(material, []) if code not in build.material_uses[material]: build.material_uses[material].append(code) amount = _table_amount(node, name) labels = [label for label in labels if "".join(label.split()) != name] if amount is None: continue titles = [ t for t in build.book.titles if t == f"B-{code}" or t.startswith(f"B-{code}#") ] if material in build.book.titles and titles: for title in titles: build.book.add_detail( PriceDetail(title, material, amount, note=f"{name} — 설계자 규격·단가") ) else: labels.append(EXPLOSIVE_MISSING.format(name=name)) if MISC_NOTE not in labels: labels.append(MISC_NOTE) labels = [ LEG_HAMMER_NO_LOSS if "".join(label.split()) == LEG_HAMMER else label for label in labels ] build.unattached[code] = labels