"""B09 원가계산 — 일위대가표 **호표 번호** (PLAN 12장 · 실무 `일위대가목록표`). 실무 내역서는 일위대가를 「제 N 호표」로 부르고, 목록표가 번호마다 명칭·규격·성분 금액을 보인다. ⚠ **번호는 저장하지 않음**(2026-09-14 브레인 판정) — 내역이 바뀌면 번호도 바뀌므로 낼 때마다 매김. 단산 번호(`B09_Estimation_PriceBasis`)와 같은 규칙: **내역에 처음 쓰인 차례**. ⚠ 내역 줄이 부르지 않고 **다른 일위대가 안에서만** 불리는 호표(찰쌓기 안의 모르타르 배합 따위)는 부르는 호표 **바로 뒤**에 번호가 붙음(줄 차례대로 깊이 우선). ⚠ 구조물도 호표(`B-AX-ST-…#키`)는 단가표 제목이 아니라 B08 일위대가 표로 섬 — 금액은 그 표 값, 안의 하위 호표 번호는 아직 안 매김(구조물도 탭이 그 표를 보임). """ from __future__ import annotations from dataclasses import dataclass, field from typing import Any from B09_Estimation.B09_Estimation_PriceBook import Money3, PriceBook, PriceKind STRUCTURE_SOURCE = "structure" @dataclass class UnitPriceSheetEntry: number: int code: str name: str spec: str unit: str money: Money3 #: 「book」 단가표 제목 · 「structure」 구조물도 일위대가 표. source: str = "book" unconfirmed: int = 0 @property def label(self) -> str: return f"제 {self.number} 호표" def as_dict(self) -> dict[str, Any]: return { "number": self.number, "label": self.label, "code": self.code, "name": self.name, "spec": self.spec, "unit": self.unit, "material_krw": str(self.money.material), "labor_krw": str(self.money.labor), "expense_krw": str(self.money.expense), "total_krw": str(self.money.total), "source": self.source, "unconfirmed": self.unconfirmed, } @dataclass class UnitPriceSheet: entries: list[UnitPriceSheetEntry] = field(default_factory=list) def as_dict(self) -> dict[str, Any]: return {"entries": [entry.as_dict() for entry in self.entries]} def _nested(book: PriceBook, code: str, seen: tuple[str, ...]) -> list[str]: """호표 안에서 부르는 일위대가 — 줄 차례대로 깊이 우선.""" found: list[str] = [] for detail in book.details.get(code, []): ref = detail.ref_code title = book.titles.get(ref) if title is None or title.kind is not PriceKind.UNIT_PRICE or ref in seen: continue found.append(ref) found.extend(_nested(book, ref, (*seen, ref))) return found def build_unit_price_sheet( rows: list[Any], book: PriceBook, structure_prices: dict[str, dict[str, Any]] | None = None, ) -> UnitPriceSheet: """내역 줄(`BillRow`) 차례 → 호표 번호 한 벌. 같은 호표를 두 줄이 불러도 한 번만.""" sheet = UnitPriceSheet() numbered: set[str] = set() def add(code: str, **values: Any) -> None: numbered.add(code) sheet.entries.append( UnitPriceSheetEntry(number=len(sheet.entries) + 1, code=code, **values) ) for row in rows: code = row.price_code if not code or code in numbered: continue structure = (structure_prices or {}).get(code) if structure is not None: add( code, name=row.name, spec=row.spec, unit=row.unit, money=structure["money"], source=STRUCTURE_SOURCE, unconfirmed=int(structure.get("unconfirmed") or 0), ) continue if code not in book.titles: continue for ref in (code, *_nested(book, code, (code,))): if ref in numbered: continue title = book.title(ref) add(ref, name=title.name, spec=title.spec, unit=title.unit, money=book.resolve(ref)) return sheet