Files
Aislo/common_util/common_util_summary_item.py
T
eomsangdonandClaude Opus 5.5 760e43c8df feat(M02): 항목 키 체계 · 품번 새 모양(PLAN 26-4a)
- 항목 키 = 마스터 SS + 6자리 · 회사 SC{회사}- + 6자리 · 개인 SP{사용자}- + 6자리(장부 key_head · 층 폴더로 가름) · 한 번 주면 안 바뀜 · 지운 키 다시 안 씀 · 층 사이 복사 · 개인에 저장 · 새 항목은 그 층 새 키 + 본뜬 곳
- 회사 · 개인 층의 옛 SS 키는 장부를 맞출 때(처음 읽을 때 한 번) 새 모양으로 옮김(숫자 여섯 그대로 · 겹치면 다음 번호) · 옛 키는 줄 옛키 에 남김 · 옛 키로도 찾음(store.find_combo · 나무 고침 · 입력 목록 · 항목 복사 · M02_Item_Source.row_of)
- summary_item 키 검사 — SC · SP 는 그 층 · 머리 번호 = owner 일 때만 · 옛 SS 는 어느 층이든 받음
- 품번(도번 SD · 면적번 SA · 재료번 SM) 새 일련 = 붙임표 + 6자리(SD-000001) — 옛 8자리(제목행 자리) · 12장 옛 6자리(SD010101)와 모양이 달라 안 겹침 · 옛 번호는 그대로 유효 · 새 모양끼리 셈 · 지운 번호 안 씀
- 시험 test_m02_item_keys 7(층마다 키 모양 · 안 바뀜 · 다시 안 씀 · 복사 본뜬 곳 · 옛 SS 옮김 · 옛 키로 찾음 · 겹친 숫자 · 참조 키 모양 · 120 형제 깊이 6 번호 · 층마다 번호 따로 · 키대장 안 겹침) · 번호 · 키 시험 기대값을 새 모양으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL
2026-09-30 09:04:39 +09:00

84 lines
3.7 KiB
Python

"""구조물 집계표 항목 참조 `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']}"