- 역전개(서버 단독) = 설계내역서 수량 × 호표 구성(겹 호표 끝까지 · 깊이 한도) → 자원 총수량 · 내역 자재 줄 그대로 - 끝수 = 엑셀 자리 — 총수량 ROUND 2 · 금액 ROUND(수량 × 단가, 0) · 중기 비목마다 ROUND · 단가 없으면 금액 빈칸 - 호표 구성 = hopyo_lines 자리(145-1 · 2 가 들어오면 채움) · GET 응답 summary · 화면 탭 넷 + 목록표 이동 - 시험 = test_145_5_b09_summary(엑셀 중기 42.95 시간 5,411,700 · 건축목공 2,222,201 · 각재 280,370 · 못 5,321 · 구역화물 678,345) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
125 lines
5.2 KiB
Python
125 lines
5.2 KiB
Python
"""B09 집계표 넷(PLAN 145-5) — 재료비 · 노무비 · 경비 수량금액집계표 · 중기시간금액집계표
|
|
(서버 단독).
|
|
|
|
역전개 = 설계내역서 항목 수량 × 호표 구성(산근 · 대가 줄 · 겹 호표 끝까지) → 자원마다 총수량.
|
|
호표 구성 = `hopyo_lines` — 145-1 · 2(산근 · 대가)가 `{호표 키: {"lines": [{kind, key, qty}]}}`
|
|
(호표 1 단위당 · kind = labor · material · expense · machine · hopyo)로 줌 · 그전엔 빈 묶음
|
|
4 자재대 · 관급자재대 자재 줄 = 그 자재 그대로(호표 없음)
|
|
끝수 = 엑셀 자리 그대로 — 총수량 ROUND 2 · 금액 = ROUND(수량 × 단가, 0)(여기만 반올림) ·
|
|
중기 = 노무 · 재료 · 경비 각 ROUND(시간 × 시간당, 0) · 합계 = 셋 합.
|
|
단가 = 목록표(노무비 · 재료비 · 경비 · 중기목록표) — 없으면 금액 빈칸(수량은 둠).
|
|
결과는 GET 때 셈(저장 안 함) · 조작 중엔 다시 안 셈([저장] 뒤 서버 정본).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from B09_Estimation.B09_Estimation_Calc_Base import num, round_half
|
|
|
|
#: 겹 호표 깊이 한도(엑셀 최대 대가 → 대가 → 산근 → 중기 = 4) — 돌고 도는 구성 막음
|
|
MAX_DEPTH = 8
|
|
KINDS = ("material", "labor", "expense", "machine")
|
|
|
|
|
|
def expand(
|
|
lines_of: dict[str, Any], key: str, qty: float, out: dict[str, dict[str, float]], depth: int = 0
|
|
) -> None:
|
|
"""호표 하나 × 수량 → 자원 총수량에 더함(겹 호표는 끝까지 · 깊이 한도 넘으면 멈춤)."""
|
|
if depth > MAX_DEPTH:
|
|
return
|
|
for line in (lines_of.get(key) or {}).get("lines") or []:
|
|
q = num(line.get("qty"))
|
|
if q is None:
|
|
continue
|
|
kind = line.get("kind")
|
|
if kind == "hopyo":
|
|
expand(lines_of, str(line.get("key")), qty * q, out, depth + 1)
|
|
elif kind in KINDS:
|
|
bucket = out.setdefault(kind, {})
|
|
bucket[str(line.get("key"))] = bucket.get(str(line.get("key")), 0.0) + qty * q
|
|
|
|
|
|
def totals(sheets: dict[str, Any], lines_of: dict[str, Any]) -> dict[str, dict[str, float]]:
|
|
"""설계내역서 항목 → 자원 총수량(내역 차례 · 관급 포함)."""
|
|
out: dict[str, dict[str, float]] = {}
|
|
for row in (sheets.get("bill") or {}).get("rows") or []:
|
|
qty = num(row.get("qty"))
|
|
ref = row.get("ref") or {}
|
|
if row.get("kind") != "item" or qty is None or not ref.get("key"):
|
|
continue
|
|
if ref.get("tab") == "material":
|
|
bucket = out.setdefault("material", {})
|
|
bucket[ref["key"]] = bucket.get(ref["key"], 0.0) + qty
|
|
else:
|
|
expand(lines_of, str(ref["key"]), qty, out)
|
|
return out
|
|
|
|
|
|
def _list(rows: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
|
return {row["key"]: row for row in rows}
|
|
|
|
|
|
def _resource_sheet(used: dict[str, float], listed: dict[str, dict[str, Any]], unit_default: str):
|
|
rows = []
|
|
for no, (key, qty) in enumerate(used.items(), 1):
|
|
src = listed.get(key) or {}
|
|
q = round_half(qty, 2)
|
|
price = num(src.get("price"))
|
|
rows.append(
|
|
{
|
|
"no": no,
|
|
"key": key,
|
|
"name": src.get("name") or key,
|
|
"spec": src.get("spec") or "",
|
|
"unit": src.get("unit") or unit_default,
|
|
"qty": q,
|
|
"price": price,
|
|
"amount": round_half(q * price, 0) if price is not None else None,
|
|
"note": "" if src else "목록표에 없음",
|
|
}
|
|
)
|
|
return rows
|
|
|
|
|
|
def _machine_sheet(used: dict[str, float], listed: dict[str, dict[str, Any]]):
|
|
rows = []
|
|
for no, (key, qty) in enumerate(used.items(), 1):
|
|
src = listed.get(key) or {}
|
|
q = round_half(qty, 2)
|
|
parts = {}
|
|
for part in ("labor", "material", "expense"):
|
|
unit = num(src.get(part))
|
|
parts[part] = round_half(q * unit, 0) if unit is not None else None
|
|
known = all(v is not None for v in parts.values())
|
|
rows.append(
|
|
{
|
|
"no": no,
|
|
"key": key,
|
|
"name": src.get("name") or key,
|
|
"spec": src.get("spec") or "",
|
|
"unit": "시간",
|
|
"qty": q,
|
|
**parts,
|
|
"sum": parts["labor"] + parts["material"] + parts["expense"] if known else None,
|
|
"note": "" if src else "목록표에 없음",
|
|
}
|
|
)
|
|
return rows
|
|
|
|
|
|
def compute_summary(sheets: dict[str, Any], lines_of: dict[str, Any]) -> dict[str, Any]:
|
|
"""집계표 넷 — `sheets` = 짝 셈 결과(바탕 + 설계내역서)."""
|
|
used = totals(sheets, lines_of)
|
|
return {
|
|
"sum_material": _resource_sheet(used.get("material", {}), _list(sheets["material"]), ""),
|
|
"sum_labor": _resource_sheet(used.get("labor", {}), _list(sheets["labor"]), "인"),
|
|
"sum_expense": _resource_sheet(used.get("expense", {}), _list(sheets["expense"]), ""),
|
|
"sum_machine": _machine_sheet(used.get("machine", {}), _list(sheets["machine"])),
|
|
}
|
|
|
|
|
|
def hopyo_lines(project_root: Any) -> dict[str, Any]:
|
|
"""호표 구성(1 단위당 자원 · 겹 호표) — 145-1 · 2 가 들어오면 채움 · 그전엔 빈 묶음."""
|
|
return {}
|