"""구조물 집계표 항목 참조 `summary_item` — 모양 검사 · 스냅샷 폴더 이름 (PLAN 21-1). summary_item = {layer: "system"|"company"|"personal", owner: null|"{회사}"|"{회사}/{사용자}", key: "SS000004", name: "…"} 항목 키(PLAN 26-4a) — 마스터 `SS000004` · 회사 `SC{회사}-000001` · 개인 `SP{사용자}-000001` (머리의 번호 = owner 의 회사 · 사용자). 옛 `SS` 키는 어느 층이든 그대로 받음 — 회사 · 개인 층이 새 모양으로 옮기기 전에 적힌 기록(층 장부의 `옛키` 로 찾음). 구조물 기록(`structures.json` `StructureInstance`) · 관 지점 기록(`pipe_points.json` 점) 두 곳이 같은 칸을 씀 — 검사는 여기 한 벌. 없으면 null(옛 파일 호환). """ from __future__ import annotations import re from typing import Any SUMMARY_LAYERS = ("system", "company", "personal") _KEY = re.compile(r"^SS\d{6}$") # 마스터 · 옛 키 _LAYER_KEY = { "company": re.compile(r"^SC(\d+)-\d{6}$"), "personal": re.compile(r"^SP(\d+)-\d{6}$"), } _OWNER = { "system": None, "company": re.compile(r"^\d+$"), "personal": re.compile(r"^\d+/\d+$"), } _NAME_MAX = 200 def parse_summary_item(value: Any) -> dict[str, Any] | None: """모양이 맞으면 `{layer, owner, key, name}` 새 dict · 빈 값은 None · 틀리면 ValueError.""" if value is None: return None if not isinstance(value, dict): raise ValueError("집계 항목(summary_item)은 객체여야 합니다.") extra = set(value) - {"layer", "owner", "key", "name"} if extra: raise ValueError(f"집계 항목에 모르는 칸이 있습니다: {', '.join(sorted(extra))}") layer = value.get("layer") if layer not in SUMMARY_LAYERS: raise ValueError("집계 항목 층은 system · company · personal 중 하나입니다.") owner = value.get("owner") rule = _OWNER[layer] if rule is None: if owner is not None: raise ValueError("시스템 층 집계 항목의 owner 는 null 이어야 합니다.") elif not isinstance(owner, str) or not rule.match(owner): shape = "{회사}" if layer == "company" else "{회사}/{사용자}" raise ValueError(f"{layer} 층 집계 항목의 owner 는 「{shape}」 숫자 모양입니다.") key = value.get("key") if not isinstance(key, str) or not _key_ok(layer, owner, key): raise ValueError( "집계 항목 key 는 마스터 SS000004 · 회사 SC{회사}-000001 · " "개인 SP{사용자}-000001 모양입니다(옛 SS 키는 어느 층이든 받음)." ) name = value.get("name") if not isinstance(name, str) or not name.strip() or len(name) > _NAME_MAX: raise ValueError(f"집계 항목 name 은 1~{_NAME_MAX}자 글입니다.") return {"layer": layer, "owner": owner, "key": key, "name": name} def _key_ok(layer: str, owner: Any, key: str) -> bool: """옛 · 마스터 SS 키는 어느 층이든 · SC · SP 는 그 층에서 머리 번호가 owner 와 같을 때만.""" if _KEY.match(key): return True rule = _LAYER_KEY.get(layer) found = rule.match(key) if rule else None if not found or not isinstance(owner, str): return False return found.group(1) == (owner if layer == "company" else owner.split("/")[-1]) def snapshot_folder_name(item: dict[str, Any]) -> str: """`{layer}_{owner 슬러그}_{key}` — owner 없음 = `none` · `/` = `-`. 예: `system_none_SS000004` · `company_3_SC3-000001` · `personal_3-12_SP12-000002`. """ owner = item.get("owner") slug = str(owner).replace("/", "-") if owner else "none" return f"{item['layer']}_{slug}_{item['key']}"