Files
Aislo/B09_Estimation/B09_Estimation_Bill_Items.py
T
eomsangdonandClaude Opus 5.5 03fdde07fb feat(B09): 설계내역서 구조물 줄 = 대가 블록마다 나눔 — B08 열 owners 잇기(145-3)
- 항목 짜기 = B08 구조물 집계표 열 owners {사본 주인 id: 값}(sub8 덧칸) 를 싣고
- 짝 셈 split_by_block / splitByBlock = 주인마다 대가 단가(price_of = 145-2 daega_price 자리) → 같은 대가 블록끼리 한 줄(수량 합 ROUND 3) · 대가 없는 주인 = 한 줄 「연결 없음」
- price_of 없으면 열 하나 그대로(단가 빈칸)
- 시험 = test_145_3_b09_bill 더함(대가17 × 20 = 2,491,880 · 대가24 × 12.5 = 2,078,274 · 거울)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
2026-10-09 15:24:46 +09:00

155 lines
6.7 KiB
Python

"""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_Material_Calc import _structure_row
WHY_EARTH = "조합 밖 토공 산근 — 연결 없음"
WHY_READY = "준비공 대가 — 연결 없음"
WHY_HAUL = "운반 산근 — 연결 없음(운반거리 원천 없음)"
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|",)
#: 운반공 — (명칭, 단위, B08 총괄자재 줄 키(합) | None)
HAUL: list[tuple[str, str, tuple[str, ...] | None]] = [
("중기운반", "대", None),
("기계운반", "대", None),
("모래운반", "㎥", ("sand",)),
("자갈운반", "㎥", ("gravel",)),
("시멘트운반", "대", ("cement",)),
("철근운반", "ton", ("rebar_d13", "rebar_d16", "rebar_d19", "rebar_d22", "rebar_d25")),
]
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) -> list[dict[str, Any]]:
rows = ((materials or {}).get("material") or {}).get("rows") or []
amount = {row.get("key"): row.get("amount") for row in rows}
out = []
for n, (name, unit, keys) in enumerate(HAUL, 1):
qty = None
if keys:
found = [amount[k] for k in keys if amount.get(k) is not None]
qty = round(sum(found), 6) if found else None
out.append(
{
"key": f"haul:{n}",
"section": "3",
"name": name,
"spec": "",
"unit": unit,
"qty": qty,
"note": "B08 총괄자재" if qty is not None else "",
"ref": None,
"why": WHY_HAUL,
}
)
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")),
]
}
def hopyo_prices(project_root: Any) -> dict[str, dict[str, float]]:
"""호표 단가 `{호표 자리 키: {labor, material, expense}}` —
145-1 · 2(산근 · 대가)가 들어오면 채움 · 그전엔 빈 묶음(구조물 단가 = 빈칸 「연결 없음」)."""
return {}