Files
Aislo/common_util/common_util_summary_item.py
eomsangdonandClaude Opus 5.5 5892232890 feat(B05·M02): 구조물 집계 항목 칸 · 저장 스냅샷 · 지정 항목 채움 (PLAN 21-3)
- summary_item 칸: StructureInstance · pipe_points 점 기록(options 밖) — 모양 검사 한 벌 common_util_summary_item(층 system·company·personal · owner null·{회사}·{회사}/{사용자} · key SS+6자리 · name) · 없으면 null(옛 파일 호환) · 관 요청 모양 틀림 400
- 저장 스냅샷 M02_Item_Snapshot: PUT /route/structures · PUT /drainage/pipe-points 뒤 지정 항목마다 templates/items/{layer}_{owner}_{key}/ 에 집계(SS 줄 + 열 정의) · 도면 · 면적 · 재료 · 자재조합 · 일위대가(UA 줄 + 담은 로직 줄) · manifest(원본 · 판 · 적용일 · 쓰는 구조물) — 있는 폴더는 다시 안 씀(원본이 바뀌어도 프로젝트 값 그대로) · 안 쓰는 폴더 지움 · 실패해도 저장 유지
- 채움 fill_table = 지정 항목 모드: 구조물이 고른 열(묶음)에만 · 미지정 · 복사본 없음은 안 셈 + 알림 · 스냅샷 열 정의 사용 · 회사 · 개인 항목은 열id@폴더 새 열 · 자동 맞춤은 fill_document 기본으로만 남음
- 관 시설 되붙이기가 summary_item 도 옮김
- [초기화]는 templates/ 를 안 건드림 · 복제 도구는 폴더 통째 복사 — 손댈 것 없음(시험으로 지킴)
- 시험: test_structures_summary_snapshot 17 · 옛 채움 시험은 자동 맞춤 길(fill_document)로 · 층 라우트 채움 시험은 새 규칙(미지정 관 = 줄 없음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RrQ65VtGZbae2VKgYVMhmc
2026-09-29 12:00:44 +09:00

62 lines
2.6 KiB
Python

"""구조물 집계표 항목 참조 `summary_item` — 모양 검사 · 스냅샷 폴더 이름 (PLAN 21-1).
summary_item = {layer: "system"|"company"|"personal",
owner: null|"{회사}"|"{회사}/{사용자}", key: "SS000004", name: "…"}
구조물 기록(`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}$")
_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.match(key):
raise ValueError("집계 항목 key 는 SS 와 숫자 6자리입니다(예: SS000004).")
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 snapshot_folder_name(item: dict[str, Any]) -> str:
"""`{layer}_{owner 슬러그}_{key}` — owner 없음 = `none` · `/` = `-`.
예: `system_none_SS000004` · `company_3_SS000001` · `personal_3-12_SS000002`.
"""
owner = item.get("owner")
slug = str(owner).replace("/", "-") if owner else "none"
return f"{item['layer']}_{slug}_{item['key']}"