feat(B08): 구조물 집계표 열마다 사본 주인별 값 owners(145-3 이음)
- 열 owners = {사본 주인 id: 그 주인이 낸 값} — 주인 id = linked_snapshots 키(pipe@측점 · 구조물 id · {id}__{그룹})
- 칸을 낸 잇기 사본이 먼저 · 없으면 시설 본체 · owners 합 = 열 계 · 기존 키 · 화면 그대로
- 새 test_145_3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
@@ -22,6 +22,9 @@
|
||||
열 id 접두 `pipe:` = 이 집계표(횡단배수) 이름공간 · 같은 측점 박스 + 관은 종류로 갈라 셈.
|
||||
머리 글 = M02 정의 이름(`구조물정의.json` types.name · 종류.{종류}.이름 — 사용자가 M02 에서 고침) ·
|
||||
열마다 `combos` = 그 값을 낸 컨테이너에 이은 조합 id(`{층}|{주인}|{키}` · 95-3 모달 · 맨 윗 머리).
|
||||
열마다 `owners` = {사본 주인 id: 그 주인이 낸 값}(145-3) — 주인 id = `linked_snapshots` 키
|
||||
(`pipe@측점` · 구조물 id · `{id}__{그룹}`) · 칸을 낸 잇기 사본이 먼저 · 없으면 시설 본체 ·
|
||||
합 = 열 계.
|
||||
설계 = B08 토공과 같은 길(확정 노선 먼저) · 관 · 컨테이너 줄 = M02 채움 `collect_items` 재사용.
|
||||
`pipes` = 관마다 길이(커플링 열 · 측점) — 좌측 패널 1본 길이를 바꾸면 화면이 같은 식으로 바로 다시 셈
|
||||
(짝 = `couplings` ↔ `B08_Quantity_Options_Calc.ts` `couplings` · `recountCouplings` · PLAN 122).
|
||||
@@ -111,6 +114,21 @@ def _combo(item: Any) -> str | None:
|
||||
return f"{item.get('layer')}|{item.get('owner') or ''}|{item['key']}"
|
||||
|
||||
|
||||
class _Copy(str):
|
||||
"""잇기 조합 id + 그 사본 주인 id(`linked_snapshots` 키 · `{구조물 id}__{그룹}`) — 145-3."""
|
||||
|
||||
copy: str
|
||||
|
||||
|
||||
def _linked(combo: str | None, copy: str) -> str | None:
|
||||
"""잇기 조합에 사본 주인 id 를 붙임 — 본체 조합(그냥 글)은 시설 id 가 주인."""
|
||||
if combo is None:
|
||||
return None
|
||||
out = _Copy(combo)
|
||||
out.copy = copy
|
||||
return out
|
||||
|
||||
|
||||
def one_pipe_length(project_root: str | Path) -> float | None:
|
||||
"""1본 길이(m) — 프로젝트 층 구조물집계표 변수만(시스템 층으로 안 채움 · 143-2) · 빈칸 None."""
|
||||
found = layers.read_template(layers.project_dir(project_root), "table", TABLE_NAME)
|
||||
@@ -193,14 +211,19 @@ class _Sheet:
|
||||
return
|
||||
cid = f"pipe:{key}"
|
||||
column = self.columns.setdefault(
|
||||
cid, {"id": cid, "group": group, "label": label, "combos": [], "_sort": sort}
|
||||
cid,
|
||||
{"id": cid, "group": group, "label": label, "combos": [], "owners": {}, "_sort": sort},
|
||||
)
|
||||
for combo in combos:
|
||||
if combo and combo not in column["combos"]:
|
||||
column["combos"].append(combo)
|
||||
column["combos"].append(str(combo))
|
||||
row = self.rows.setdefault(round(chainage * 100), {"chainage_m": chainage, "values": {}})
|
||||
if value is not None: # 열은 세움 — 놓였는데 값이 빈칸인 것이 보여야 함
|
||||
row["values"][cid] = round((row["values"].get(cid) or 0.0) + value, 3)
|
||||
# 사본 주인별 값(145-3 · 설계내역서가 대가 호표에 이음) — 잇기 사본이 먼저 · 없으면 본체
|
||||
owner = next((c.copy for c in combos if isinstance(c, _Copy)), self.owner)
|
||||
owners = column["owners"]
|
||||
owners[owner] = round(owners.get(owner, 0.0) + value, 3)
|
||||
|
||||
def result(self) -> dict[str, Any]:
|
||||
columns = sorted(self.columns.values(), key=lambda column: column["_sort"])
|
||||
@@ -472,7 +495,11 @@ def _drain(
|
||||
return
|
||||
# 필수 컨테이너 잇기 — 정의 그룹에 없는 것(세월교)은 저장된 잇기에서
|
||||
required = {"box_culvert": BOX_LINKS, "ford_bridge": FORD_LINKS}.get(type_id, PAVE_LINKS)
|
||||
combos = {part: links.get(link) or _combo(group.get(link)) for part, link in required.items()}
|
||||
combos = {
|
||||
part: links.get(link)
|
||||
or _linked(_combo(group.get(link)), f"{sheet.owner}{GROUP_JOIN}{link}")
|
||||
for part, link in required.items()
|
||||
}
|
||||
if type_id == "ford_pavement":
|
||||
_pavement(sheet, at, base, combos, names)
|
||||
return
|
||||
@@ -527,7 +554,7 @@ def pipe_summary(
|
||||
# 거름 한 곳 — 사본 없는 잇기는 조합 없음으로(그 칸만 안 셈 · 143-1)
|
||||
combo = _combo(item.get("summary_item")) if item["복사본"] in linked else None
|
||||
if link:
|
||||
entry["links"][link] = combo
|
||||
entry["links"][link] = _linked(combo, item["복사본"])
|
||||
else:
|
||||
entry["base"] = {**item, "summary_item": item.get("summary_item") if combo else None}
|
||||
|
||||
@@ -552,7 +579,9 @@ def pipe_summary(
|
||||
}
|
||||
_drain(sheet, at, type_id, base, options, links, group, kinds, names, box_length)
|
||||
body_link = BOX_LINKS["body"] if type_id == "box_culvert" else FORD_LINKS["body"]
|
||||
body = links.get(body_link) or _combo(group.get(body_link))
|
||||
body = links.get(body_link) or _linked(
|
||||
_combo(group.get(body_link)), f"{owner}{GROUP_JOIN}{body_link}"
|
||||
)
|
||||
counted = bool(base["summary_item"] or body) # 본체 칸을 셈(사본 있음)
|
||||
no_box += type_id == "box_culvert" and counted and not box_length
|
||||
if type_id == "ford_bridge":
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""145-3 이음 — B08 구조물 집계표 열마다 `owners` = {사본 주인 id: 그 주인이 낸 값}.
|
||||
|
||||
주인 id = `linked_snapshots` 키(`pipe@측점` · 구조물 id · `{id}__{그룹}`) · 열 owners 합 = 열 계 ·
|
||||
칸을 낸 잇기 사본이 먼저(없으면 시설 본체).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from test_120_b08_drain_summary import out # noqa: F401 — 픽스처(관 밖 횡단배수 견본)
|
||||
from test_143_1_b08_linked_snapshots import _run as _run_143
|
||||
from test_143_1_b08_linked_snapshots import root # noqa: F401 — 픽스처(실제 사본 폴더)
|
||||
|
||||
from M02_MasterTemplete import M02_Item_Snapshot as snapshots
|
||||
|
||||
|
||||
def _check_sums(result: dict, keys: set[str]) -> None:
|
||||
for column in result["columns"]:
|
||||
owners = column["owners"]
|
||||
assert set(owners) <= keys, column["id"]
|
||||
assert sum(owners.values()) == pytest.approx(result["total"].get(column["id"], 0.0))
|
||||
|
||||
|
||||
def test_owners_합_열_계_주인은_사본_키(out): # noqa: F811
|
||||
cols = {c["id"]: c for c in out["columns"]}
|
||||
_check_sums(out, set(out["linked"]))
|
||||
# 본체 칸 = 본체 잇기 사본(gboxbody) · 날개벽 = 유입 잇기 · 흙막이 = 유출 잇기
|
||||
assert cols["pipe:box|body|2.0×2.0"]["owners"] == {"pipe@300.0__gboxbody": 9.6}
|
||||
assert cols["pipe:box_culvert|wing|inlet"]["owners"] == {"pipe@300.0__gboxin": 1.0}
|
||||
assert cols["pipe:revet|돌쌓기(메)|2.0"]["owners"] == {"pipe@300.0__gboxout": 10.0}
|
||||
# 세월교 월류 폭 = 저장된 잇기(gford) · 관 공은 본체(사본 키 = 관 지점 id)
|
||||
assert cols["pipe:ford|1000|2|width"]["owners"] == {"pipe@400.0__gford": 12.0}
|
||||
assert cols["pipe:len|800"]["owners"] == {"pipe@300.0": 18.0}
|
||||
# B05 구조물 = 구조물 id · 그룹 잇기 = {id}__{그룹}
|
||||
assert cols["pipe:open_ditch"]["owners"] == {"s-ditch": 1.0}
|
||||
assert cols["pipe:cross_underdrain"]["owners"] == {"s-ud__gcrossud": 1.0}
|
||||
|
||||
|
||||
def test_실제_사본_폴더_키와_같다(root): # noqa: F811
|
||||
result = _run_143(root)
|
||||
linked = set(snapshots.linked_snapshots(root))
|
||||
_check_sums(result, linked)
|
||||
cols = {c["id"]: c for c in result["columns"]}
|
||||
assert cols["pipe:len|800"]["owners"] == {"pipe@100.0": 16.0}
|
||||
assert cols["pipe:len|1000"]["owners"] == {"pipe@500.0__gford": 18.0}
|
||||
Reference in New Issue
Block a user