"""B09 설계내역서 항목 짜기(PLAN 145-3) — 서버 단독 · B08 수량 → 내역 줄(수량 · 단가 자리). 공종 나무 = 엑셀 기번3 공통 꼴 고정(`B09_Estimation_Calc_Bill.SECTIONS`) · 공구 안 나눔. 토공(1.1~1.6) = B08 토공집계표 줄 짝(`EARTH` — 엑셀 항목 이름 ↔ 토공집계표 구분 · 종류 · 규격) · 단가 = 조합 밖 토공 산근 · 준비공 대가 — 원천 없음 → 빈칸 「연결 없음」(지시) 구조물(2.1~2.3) = B08 구조물 집계표 열(사본 있을 때만 · 143) · 구조물군 → 공종 짝(`STRUCTURE_SECTION`) · 커플링 열은 자재(관급자재대)라 뺌 · 단가 = 대가 호표(145-2 가 `hopyo` 로 줌 · 그전엔 빈칸) 운반(3) = 엑셀 운반공 줄 · 수량 = B08 총괄자재 짝 · 거리 · 단가 = 운반 산근(원천 없음 → 빈칸) 자재대 · 관급자재대 = 화면 짝이 대비표 「별도」 · 「관급」 표시로 짬(사용자 조작 즉시) """ from __future__ import annotations from typing import Any from B08_Quantity.B08_Quantity_Haul import bill_key, haul_groups from B08_Quantity.B08_Quantity_Material_Calc import _structure_row WHY_EARTH = "조합 밖 토공 산근 — 연결 없음" WHY_READY = "준비공 대가 — 연결 없음" WHY_HAUL = "운반 산근 — 연결 없음(운반거리 원천 없음)" WHY_LOGIC = "새 산근 — [저장] 뒤 단가(서버 셈)" WHY_STRUCTURE = "대가 호표 — 연결 없음(일위대가 145-2)" #: 토공 항목 — (공종, 엑셀 명칭, 단위, 토공집계표 (구분, 종류, 규격 | None) | None) EARTH: list[tuple[str, str, str, tuple[str, str, str | None] | None]] = [ ("1.1", "규준틀설치 종단규준틀", "개소", None), ("1.1", "규준틀설치 횡단규준틀", "개소", None), ("1.1", "제근", "㎡", None), ("1.2", "절토(토사)", "㎥", ("흙깍기", "토사", None)), ("1.2", "절토(연암)", "㎥", ("흙깍기", "연암", None)), ("1.2", "암발파", "㎥", ("흙깍기", "경암", None)), ("1.3", "구조물터파기(토사)", "㎥", ("구조물터파기", "토사", None)), ("1.3", "구조물터파기(연암)", "㎥", ("구조물터파기", "연암", None)), ("1.3", "구조물 되메우기", "㎥", ("되메우기", "", None)), ("1.4", "측구파기(토사)", "㎥", ("측구 터파기", "토사", None)), ("1.4", "측구파기(연암)", "㎥", ("측구 터파기", "연암", None)), ("1.5", "유용토운반", "㎥", ("유용토 소계", "", None)), ("1.5", "도자운반", "㎥", ("토사운반", "", "19 TON B/D")), ("1.5", "덤프운반", "㎥", ("토사운반", "", "BH+DT+BH")), ("1.6", "사토처리", "㎥", ("잔토처리", "", None)), ("1.6", "성토사면다짐", "㎡", ("성토면다짐", "", None)), ("1.6", "노면정리", "㎡", None), ] #: 구조물 집계표 열 id 머리 → 공종(그 밖 = 2.2 배수공) · 커플링 = 자재라 뺌 STRUCTURE_SECTION = {"pipe:revet|": "2.1", "pipe:chute|": "2.3"} SKIP_COLUMNS = ("pipe:cp|",) def _same(row: dict[str, Any], want: tuple[str, str, str | None]) -> bool: group, kind, spec = want same = row.get("group") == group and (row.get("kind") or "") == kind return same and (spec is None or row.get("spec") == spec) def earth_items(earthwork: dict[str, Any] | None) -> list[dict[str, Any]]: rows = (earthwork or {}).get("summary") or [] out = [] for n, (section, name, unit, want) in enumerate(EARTH, 1): source = next((r for r in rows if want and _same(r, want)), None) out.append( { "key": f"earth:{n}", "section": section, "name": name, "spec": (source or {}).get("spec") or "", "unit": unit, "qty": source.get("value") if source else None, "note": "B08 토공집계표" if source else "", "ref": None, "why": WHY_READY if section == "1.1" else WHY_EARTH, } ) return out def structure_items(summary: dict[str, Any] | None) -> list[dict[str, Any]]: columns = (summary or {}).get("columns") or [] total = (summary or {}).get("total") or {} out = [] for column in columns: cid = str(column.get("id") or "") if cid.startswith(SKIP_COLUMNS): continue section = next((s for head, s in STRUCTURE_SECTION.items() if cid.startswith(head)), "2.2") name, spec, unit = _structure_row(column) combos = [c for c in column.get("combos") or [] if c] out.append( { "key": f"st:{cid}", "section": section, "name": name, "spec": spec, "unit": unit, "qty": total.get(cid), "note": " · ".join(combos), "ref": {"tab": "dg_list", "key": cid}, # 사본 주인마다 값(B08 145-3 덧칸) — 대가 블록마다 줄을 나눌 때 씀 "owners": dict(column.get("owners") or {}), "why": WHY_STRUCTURE, } ) return out def haul_items( materials: dict[str, Any] | None, refs: dict[str, Any] | None = None, routes: list[dict[str, Any]] | None = None, ) -> list[dict[str, Any]]: """운반공 줄 — B08 운반 묶음(251-1 · 장비 둘 + 총괄자재 묶음 · 옛 키 haul:1~6 그대로) + 거리이정표 행(253-1 · `B08_Quantity_Haul.haul_rows` · 행 하나 = 줄 하나 · 둘째 행부터 수량 빈칸) · 이름 = 거리이정표 항목명 그대로(261-4 · 「운반」 안 붙임 · 비면 거래처 · 그것도 없으면 묶음 이름 · 「운반」) · 연결 = 거리이정표 단가연결(251-5 · `refs{줄 키: ref}` · 설계내역서 연결 덮개가 이김) · `dist{L1, L2}` = 그 행 거리(253-3 「운반 단가」 모달 · 없으면 None).""" rows = routes or [] by_key = {r["key"]: r for r in rows} extra: dict[str, list[dict[str, Any]]] = {} # 자동 상자 둘째 행부터 — 묶음 줄 바로 뒤 for r in rows: if r.get("haul") and r["key"] != bill_key(str(r["haul"])): extra.setdefault(str(r["haul"]), []).append(r) def line(key: str, name: str, unit: str, qty: Any, note: str) -> dict[str, Any]: ref = (refs or {}).get(key) route = by_key.get(key) or {} return { "key": key, "section": "3", "name": name, "spec": "", "unit": unit, "qty": qty, "note": note, "ref": ref, "dist": {"L1": route["L1"], "L2": route["L2"]} if route else None, "why": WHY_LOGIC if (ref or {}).get("tab") == "logic" else WHY_HAUL, } out = [] for g in haul_groups(materials): key = bill_key(g["key"]) name = (by_key.get(key) or {}).get("name") or g["name"] out.append( line(key, name, g["unit"], g["qty"], "B08 총괄자재" if g["qty"] is not None else "") ) for r in extra.get(g["key"], []): out.append(line(r["key"], r.get("name") or g["name"], "", None, "B08 거리이정표")) for r in rows: if r.get("user"): out.append(line(r["key"], r.get("name") or "운반", "", None, "B08 거리이정표")) return out def bill_items(b08: dict[str, Any]) -> dict[str, Any]: return { "items": [ *earth_items(b08.get("earthwork")), *structure_items(b08.get("summary")), *haul_items(b08.get("materials"), b08.get("haul_refs"), b08.get("haul_routes")), ] } def hopyo_prices(project_root: Any) -> dict[str, dict[str, float]]: """호표 단가 `{호표 자리 키: {labor, material, expense}}` — 145-1 · 2(산근 · 대가)가 들어오면 채움 · 그전엔 빈 묶음(구조물 단가 = 빈칸 「연결 없음」).""" return {}