"""옵션 그룹별 집계 항목 `group_items` (PLAN 46-3 · 46-4). 못박는 것 - 칸 모양: `{그룹 id: summary_item}` · null 항목 뺌 · 빈 묶음 = None · 그룹 id · 항목이 틀리면 거절. - 구조물 기록 · 관 지점 기록 둘 다 칸을 읽고 씀(옛 파일 = 칸 없음). - [저장] 뒤 복사본: 그룹 연결마다 `{구조물 id}__{그룹 id}` 폴더(본체와 따로). - 집계표 채움: 정의된 그룹마다 한 건 더(그 그룹이 고른 열) · 미지정 그룹은 「미지정」 알림에 셈 · 정의가 사라진 그룹 연결은 표에 안 들어감(기록 · 복사본은 남음). """ from __future__ import annotations import json from pathlib import Path import pytest from B05_Profile.B05_Profile_Structures_Schema import StructureInstance from common_util.common_util_drainage_pipes import parse_pipe_points from common_util.common_util_summary_item import parse_group_items from M02_MasterTemplete import M02_Item_Snapshot as snap from M02_MasterTemplete import M02_MasterTemplete_Store as store from M02_MasterTemplete import M02_Table_Fill as fill from M02_MasterTemplete.StructOptions import M02_SO_Groups as groups from resources.tester.test_structures_summary_snapshot import ( # noqa: F401 — world 고정물 MS, PIPE, RV, _dry, _items, _pipe, _save, world, ) def _define(system: Path, monkeypatch, doc: dict) -> None: monkeypatch.setattr(store, "FOLDER", system) (system / groups.FILE).write_text(json.dumps(doc, ensure_ascii=False), encoding="utf-8") def test_칸_모양(): assert parse_group_items(None) is None assert parse_group_items({}) is None assert parse_group_items({"g1": None}) is None assert parse_group_items({"g1": MS, "g2": None}) == {"g1": MS} for bad in ({"G1": MS}, {"g1": {"layer": "x"}}, ["g1"], {"g_1": MS}): with pytest.raises(ValueError): parse_group_items(bad) def test_구조물_관_기록이_칸을_읽고_씀(): item = StructureInstance( type_id="x", placement="point", chainage_m=1, group_items={"g1": MS, "g2": None} ) assert item.group_items == {"g1": MS} assert StructureInstance(type_id="x", placement="point", chainage_m=1).group_items is None point = parse_pipe_points([{"chainage_m": 10.0, "source": "user", "group_items": {"g9": RV}}]) assert point[0].group_items == {"g9": RV} assert point[0].as_dict()["group_items"] == {"g9": RV} plain = parse_pipe_points([{"chainage_m": 10.0, "source": "user"}])[0] assert "group_items" not in plain.as_dict() def test_그룹_연결마다_복사본_폴더가_따로(world): root = world["root"] wall = {**_dry(MS), "group_items": {"g1": RV}} pipe = {**_pipe(PIPE), "group_items": {"gin": MS}} result = _save(root, [wall], [pipe]) assert result["만듦"] == ["pipe@40.0", "pipe@40.0__gin", "st0", "st0__g1"] assert snap.FOLDER_PATTERN.match("pipe@40.0__gin") manifest = json.loads((_items(root) / "st0__g1/manifest.json").read_text("utf-8")) assert manifest["원본"]["key"] == RV["key"] and manifest["구조물"] == ["st0__g1"] # 그룹 연결을 풀면 그 복사본만 지움 again = _save(root, [_dry(MS)], [pipe]) assert again["지움"] == ["st0__g1"] and again["만듦"] == [] def test_채움은_정의된_그룹마다_한_건_더(world, monkeypatch): """메쌓기 벽 = 본체(돌쌓기 ms) + 그룹 「기슭막이」(rv) — 한 구조물이 두 열에 · 길이 10.""" root, system = world["root"], world["system"] _define(system, monkeypatch, {"masonry_dry": [{"id": "g1", "이름": "기슭막이", "옵션": []}]}) linked = {**_dry(MS), "group_items": {"g1": RV, "gold": PIPE}} # gold = 정의 없음 bare = {**_dry(None), "chainage_m": 125, "start_m": 120, "end_m": 130} _save(root, [linked, bare], []) table = json.loads((system / "table/구조물집계표.json").read_text("utf-8")) doc = fill.fill_table(root, table) values = {row["id"]: row["값"] for row in doc["줄"] if not row.get("고정")} row = {key.split("|")[0]: value for key, value in values["s80.00~90.00"].items()} assert row["ms"] == 10 # 본체 = 돌쌓기 assert row["rv"] == 10 # 그룹 = 기슭막이(같은 벽 길이) assert set(values) == {"s80.00~90.00"} # 미지정 벽은 줄도 없음 # 둘째 벽: 본체 미지정 + 그룹 미지정 = 2건 · 정의 없는 gold 는 셈 밖 assert "집계 항목 미지정 2건 — 표에 안 들어감" in doc["알림"] items = fill.collect_items(root) assert [i["복사본"] for i in items] == ["st0", "st0__g1", "st1", "st1__g1"] assert items[1]["그룹"] == "기슭막이" and items[1]["summary_item"] == RV