fix(B09): 내역 구조물 줄 ref = 대가 블록 — 집계표 역전개 · 이동 이음 · 한 바퀴 시험(145-2)

- daega_price / daegaPrice 에 블록 key · split_by_block(파이썬 · TS) = 대가 단가가 있으면 ref {tab: dg, key}
- 한 바퀴 시험 = 설계내역서 → 대가 → 산근(145-1) → 중기 → 집계표(145-5) · 연결 없음 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QND7Co43B24zK2P937rdxt
This commit is contained in:
2026-10-09 15:33:44 +09:00
co-authored by Claude Opus 5.5
parent 163275048c
commit 3bd20530bf
5 changed files with 56 additions and 0 deletions
@@ -126,6 +126,8 @@ def split_by_block(item: dict[str, Any], price_of: PriceOf) -> list[tuple[dict,
"note": " · ".join(t for t in (tag, item.get("note") or "") if t),
"owners": {o: item["owners"][o] for o in group["owners"]},
}
if group["price"] and group["price"].get("key"): # 대가 블록(145-2) — 이동 · 집계표 역전개
part["ref"] = {"tab": "dg", "key": group["price"]["key"]}
out.append((part, group["price"]))
return out
@@ -119,6 +119,8 @@ export function splitByBlock(item: Row, priceOf: PriceOf): Array<[Row, Row | nul
note: [tag, item.note || ""].filter((t) => t).join(" · "),
owners: Object.fromEntries(group.owners.map((o: string) => [o, item.owners[o]])),
};
// 대가 블록(145-2) — 이동 · 집계표 역전개
if (group.price?.key) part.ref = { tab: "dg", key: group.price.key };
out.push([part, group.price]);
}
return out;
@@ -193,5 +193,6 @@ def daega_price(sheets: dict[str, Any], owner: str) -> dict[str, Any] | None:
return None
return {
"no": f"대가{row['no']}",
"key": bid,
**{k: row[k] for k in ("labor", "material", "expense", "sum")},
}
@@ -176,6 +176,7 @@ export function daegaPrice(sheets: Row, owner: string): Row | null {
if (!row) return null;
return {
no: `대가${row.no}`,
key: bid,
labor: row.labor,
material: row.material,
expense: row.expense,
@@ -124,6 +124,7 @@ def test_엑셀_대가_블록_검산():
} # fmt: skip
assert daega_price(out, "pipe@100.0") == {
"no": "대가1",
"key": "DG14",
"labor": 77755,
"material": 9519,
"expense": 14248,
@@ -362,3 +363,52 @@ def test_집계표_이음_꼴():
]
assert {"kind": "machine", "key": "X00044", "qty": 0.0416} in lines["DG2"]["lines"]
assert all(ln["kind"] != "pct" for ln in lines["DG2"]["lines"]) # % 줄 = 자원 아님
def test_한_바퀴_내역_대가_산근_중기_집계표(tmp_path):
"""이음 끝 확인 — 설계내역서 구조물 줄(B08 열 owners) → 대가 블록마다 나눔 → 대가 줄 →
산근(145-1 · 승격 줄) → 중기 → 집계표(145-5) 역전개 · 짝 셈 한 벌 = Router.compute_sheets."""
from B09_Estimation.B09_Estimation_Calc_Base import rounddown
from B09_Estimation.B09_Estimation_Router import compute_sheets
from B09_Estimation.B09_Estimation_Summary import compute_summary, hopyo_lines
root = _project(tmp_path / "loop", [(100, "SS000901"), (400, "SS000902")])
_snap(root, "pipe@100.0", "SS000901", LINES, _values(STONE))
promoted = [
{"로직": "GF000119", "승격": True, "수량": {"손": 1}}, # 노체포설 = 산근 · 중기 줄
{"참조": {"층": "system", "주인": None, "키": "SC-T"}, "수량": {"손": 4}},
]
_snap(root, "pipe@400.0", "SS000902", promoted, _values(STONE), SC)
inputs = prices.build_inputs(None, linked_resource_keys(root))
inputs["daega"] = daega_inputs(root)
inputs["hopyo"] = {}
inputs["bill"] = {
"items": [
{"key": "st:revet", "section": "2.1", "name": "돌기슭막이", "spec": "", "unit": "m",
"qty": 10, "ref": {"tab": "dg_list", "key": "revet"}, "note": "",
"owners": {"pipe@100.0": 4, "pipe@400.0": 6}},
]
} # fmt: skip
sheets = compute_sheets(root, inputs, {})
owners = sheets["daega_owners"]
rows = [r for r in sheets["bill"]["rows"] if r.get("kind") == "item"]
assert [r["ref"] for r in rows] == [
{"tab": "dg", "key": owners["pipe@100.0"]},
{"tab": "dg", "key": owners["pipe@400.0"]},
]
for row in rows:
block = _block(sheets, row["ref"]["key"])
assert row["price_labor"] == block["labor"] and block["sum"] is not None
assert row["labor"] == rounddown(row["qty"] * block["labor"], 0)
# 산근 이음 — 대가 줄 키 = 145-1 산근 키 · 단가가 들어옴
combo = _block(sheets, owners["pipe@400.0"])
sg_line = combo["lines"][0]
sg_row = next(r for r in sheets["sg_list"] if r["key"] == sg_line["key"])
assert sg_line["kind"] == "sangeun" and sg_line["prices"]["labor"] == sg_row["labor"]
# 집계표 역전개 — 대가 · 사전조합 · 품셈 · 산근 끝까지
summary = compute_summary(sheets, hopyo_lines(sheets))
assert summary["unlinked"] == 0
labor = {r["key"]: r["qty"] for r in summary["sum_labor"]}
assert labor["LB000033"] == 25.6 # 4 × 2 × 0.32 + 6 × (4 × 3 × 0.32)
machine = {r["key"]: r["qty"] for r in summary["sum_machine"]}
assert machine.get("EQ000020", 0) > 0.08 # 품셈 4 × 0.5 × 0.0412 + 산근 6 × (중기 시간)