- 양식 일위대가로 셀 구조물도 장은 인계 줄 하나(코드 AX-ST · 갈래 = 제원 키 · 수량 연장 m) - 양식이 품은 줄(기초잡석)은 따로 인계 안 함 — 이중계상 막음 - B09 내역이 자기 단가표로 B08 일위대가 엔진을 돌려 금액 · 못 받거나 막히면 사유 - 수동 단가도 씀 — 내역 요약에 미확정 N건 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
164 lines
6.9 KiB
Python
164 lines
6.9 KiB
Python
"""구조물도 일위대가 → B09 내역 **받는 문** (PLAN 6장 첫 일감 ② · 2026-09-13 브레인 판정).
|
|
|
|
⭐ 종전엔 구조물도가 세운 일위대가(`B-AX-ST-*`)가 인계로 **한 번도 안 나갔음** — 돌쌓기는
|
|
`FP-13-04-05` ㎡ 단가만 가고 모르터·기초잡석은 따로거나 빠졌음. 사슬의 진짜 빈칸.
|
|
|
|
한 벌로 두는 것 — 인계(B08)와 내역(B09)이 **같은 목록·같은 키**를 봄
|
|
양식 일위대가로 셀 장 → `priced_sheets` (장마다 코드 · 제원 키 · 구성원 · 품은 줄)
|
|
인계 줄 → `template_row` (코드 AX-ST · 갈래 = 제원 키 · 수량 = 연장 m)
|
|
내역 금액 → `price_sheets` (B09 단가표로 B08 일위대가 엔진을 돌림 — 코드 한 벌)
|
|
|
|
⚠ 호표 코드 `B-AX-ST-<8hex>#<키>` — 키는 **사람이 읽는 제목이 아니라** 양식 제원 칸 값을 칸 이름
|
|
차례로 이은 것(연장 L 뺌) + `normalize_variant_key`. 제목 글자가 바뀌어도 코드는 안 바뀜.
|
|
⚠ 양식 일위대가가 품은 원단위 줄(돌쌓기·모르터·기초잡석)은 **따로 인계하지 않음** — `covered`.
|
|
⚠ 금액 정본은 B09 — 여기 `price_sheets` 는 B09 가 넘긴 단가표로만 셈. 수동 단가도 입력으로 씀.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable, Iterable
|
|
from typing import Any
|
|
|
|
#: 인계 줄 갈래 축 이름 — 「구조물도 장 제원」.
|
|
VARIANT_AXIS = "structure_sheet"
|
|
|
|
|
|
def _key_value(value: Any) -> str:
|
|
if value is None:
|
|
return ""
|
|
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
return format(float(value), ".6g")
|
|
return str(value).strip()
|
|
|
|
|
|
def sheet_variant_key(template: dict[str, Any], sheet: dict[str, Any]) -> str:
|
|
"""장의 **제원 키** — 양식 칸 이름 차례로 `이름=값` 을 `|` 로 이음(연장 칸 뺌: m당)."""
|
|
values = (sheet.get("formula_sheet") or {}).get("vars") or {}
|
|
specs = template.get("vars") or {}
|
|
return "|".join(
|
|
f"{name}={_key_value(values.get(name))}"
|
|
for name in sorted(specs)
|
|
if specs[name].get("source") != "length_m"
|
|
)
|
|
|
|
|
|
def structure_price_code(code: str, key: str) -> str:
|
|
"""호표 코드 `B-AX-ST-<8hex>#<정규화 키>` — 인계·내역이 이것 하나로 만남."""
|
|
from B09_Estimation.B09_Estimation_UnitPrice import normalize_variant_key
|
|
|
|
return f"B-{code}#{normalize_variant_key(key)}"
|
|
|
|
|
|
def priced_sheets(
|
|
sheets: Iterable[dict[str, Any]],
|
|
templates: dict[str, dict[str, Any]],
|
|
row_edits: dict[str, Any] | None = None,
|
|
) -> list[dict[str, Any]]:
|
|
"""양식 일위대가로 셀 장 — 양식(고친 줄 조합 얹음)에 일위대가 줄과 코드가 있는 장만."""
|
|
from B08_Quantity.B08_Quantity_Engine_StructureUnitPrice import with_rows
|
|
|
|
found = []
|
|
for sheet in sheets:
|
|
type_id = str(sheet.get("type_id") or "")
|
|
base = templates.get(type_id)
|
|
if not base or not base.get("code") or not sheet.get("formula_sheet"):
|
|
continue
|
|
template = with_rows(base, (row_edits or {}).get(type_id))
|
|
rows = template["unit_price"]["rows"]
|
|
if not rows:
|
|
continue
|
|
names = {row.get("seq"): str(row.get("name") or "") for row in template.get("rows") or []}
|
|
found.append(
|
|
{
|
|
"type_id": type_id,
|
|
"code": str(template["code"]),
|
|
"name": str(template.get("name") or type_id),
|
|
"title": str(sheet.get("title") or ""),
|
|
"key": sheet_variant_key(template, sheet),
|
|
"structure_ids": [
|
|
str(member["structure_id"])
|
|
for member in sheet.get("members") or []
|
|
if member.get("structure_id")
|
|
],
|
|
# 일위대가가 품은 원단위 줄 이름 — 따로 인계하면 두 번 셈.
|
|
"covered": sorted(
|
|
{names[r["from_row"]] for r in rows if r.get("from_row") in names}
|
|
),
|
|
"template": template,
|
|
"sheet": sheet,
|
|
}
|
|
)
|
|
return found
|
|
|
|
|
|
def by_structure(entries: Iterable[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
|
return {sid: entry for entry in entries for sid in entry["structure_ids"]}
|
|
|
|
|
|
def template_row(row: dict[str, Any], structure: dict[str, Any], entry: dict[str, Any]) -> dict:
|
|
"""전개 인계 줄(같은 칸 29개 계약)을 **양식 일위대가 줄**로 바꿔 씀 — 코드·갈래·수량·단위만."""
|
|
length = float(structure.get("length_m") or 0.0)
|
|
in_bill = length > 0
|
|
return {
|
|
**row,
|
|
"work_item_code": entry["code"],
|
|
"name": entry["name"],
|
|
"spec": entry["title"],
|
|
"spec_detail": entry["title"],
|
|
"unit": "m",
|
|
"quantity": length,
|
|
"composite_parts": None,
|
|
"composite_not_ready": None,
|
|
"structure_kind": None,
|
|
"variant_axis": VARIANT_AXIS,
|
|
"variant_value": entry["key"],
|
|
"secondary_axes": None,
|
|
"spec_class": None,
|
|
"spec_class_basis": f"구조물도 양식 일위대가 — 품은 줄: {'·'.join(entry['covered'])}",
|
|
# 막힘은 일위대가 표가 정함(B09 가 셈) — 전개 쪽 갈래 사유는 여기 안 실음.
|
|
"blocked_kind": None if in_bill else row.get("blocked_kind"),
|
|
"blocked_reason": "" if in_bill else row.get("blocked_reason") or "연장이 0 이라 안 세움",
|
|
"in_bill": in_bill,
|
|
"in_bill_reason": "" if in_bill else "연장이 0 이라 내역에 안 세움",
|
|
}
|
|
|
|
|
|
def price_sheets(
|
|
entries: Iterable[dict[str, Any]],
|
|
book: Any,
|
|
find_variant: Callable[[str, str], str | None],
|
|
library: Iterable[dict[str, Any]],
|
|
manual_all: dict[str, Any] | None = None,
|
|
) -> dict[str, dict[str, Any]]:
|
|
"""B09 내역이 쓸 호표 금액 `{B-AX-ST-…#키: {money, blocked, unconfirmed, reasons}}`.
|
|
|
|
`money` 는 **안 자른** 단위(m)당 3분할 — 자르기는 내역 줄 자리 규칙(B09)이 함.
|
|
"""
|
|
from B08_Quantity.B08_Quantity_Engine_StructureUnitPrice import unit_price_money
|
|
|
|
prices: dict[str, dict[str, Any]] = {}
|
|
library = list(library)
|
|
for entry in entries:
|
|
assembled = unit_price_money(
|
|
entry["template"],
|
|
entry["sheet"],
|
|
book,
|
|
find_variant,
|
|
library,
|
|
(manual_all or {}).get(entry["type_id"]) or {},
|
|
)
|
|
if assembled is None:
|
|
continue
|
|
table, money = assembled
|
|
prices[structure_price_code(entry["code"], entry["key"])] = {
|
|
"money": money,
|
|
"blocked": table["blocked"],
|
|
"unconfirmed": table["unconfirmed"],
|
|
"reasons": [
|
|
f"{row['name']}: {row['reason']}"
|
|
for row in table["rows"]
|
|
if row["reason"] and not row["skipped"] and "total" not in row
|
|
],
|
|
}
|
|
return prices
|