Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Book.py
T
eomsangdonandClaude Opus 5.5 b5114ec17f feat(M02): 집계 항목 폴더 구조 서버 — 경로 · 일련번호 · 나무 고침 길(PLAN 26-2a)
- 항목 경로 = 폴더 이름 배열(깊이 자유) · 집계표 열 머리(빈 칸 건너뜀)가 곧 경로 · 열에 경로를 같이 적음 · 머리 줄 수(층)는 가장 깊은 경로에 맞춤(넷 밑으로는 안 줄임) · 빈 폴더는 집계표 폴더 칸(M02_MasterTemplete_Tree)
- 표 저장과 나무 고침 한 줄기 — 나무 고침은 집계표 문서를 고쳐 같은 집계표 저장(store.write)을 탐(번호 · SS 장부 · 세 파일 · 입력 연결 파일 지우기가 함께)
- 나무 고침 길 POST /api/m02/layers/{layer}/tree(add-folder · add-item · rename · move · delete · 판 409 · 422 · 404 · 층 권한) · 읽기 GET 같은 길 {판, 폴더, items}
- 번호 = 새 항목은 자리 뜻 없는 일련번호(있던 번호 · 지운 번호 가장 큰 수 + 1) · 있던 번호 그대로
- SS 장부 = M02_MasterTemplete_Book 로 떼어 냄(Store 543 → 478) · 줄에 경로 · 이름 · 옛 종류 · 분류1~3 = 경로 앞 넷 · 경로 없는 옛 줄은 읽을 때 채움 · 옮기는 함수 migrate_paths
- 목록 길 /api/m02/items 줄에 경로 · 이름(경로 이음) · 자재 조합 · 갈라 내기 이름도 경로 이음
- 마스터 집계표 · 장부를 경로 모양으로 옮김(51 항목 · 키 · 번호 그대로 · 빈 글 머리는 null)
- 시험 test_m02_tree 5(옛 51 항목 · 옮기기 두 번 같음 · 깊이 1 · 2 · 5 · 이름 · 옮기기 · 지우기 · 마지막 항목 · 409 · 403 · 표 저장 한 줄기) · 번호 시험을 일련번호 규칙으로

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

91 lines
4.2 KiB
Python

"""M02 구조물집계표조합(SS 장부) — 구조물 열마다 줄 하나 · 경로 옮겨 받기(PLAN 26-2a).
제어용 장부 · 관리자는 집계표 화면(또는 나무)으로 고침 · 서버가 집계표 [저장] 때 맞춤 ·
키 = SS + 6자리 · 한 번 준 키는 그 열에 남음 · 지운 열의 키는 다시 안 씀(`다음` 이 기억).
줄 = `{키, 열, 경로, 이름, 종류, 분류1~3(경로 앞 넷 — 옛 코드가 읽음), 단위, 일위대가, 자재조합,
도번, 면적번, 재료번, 바인딩, 펼침}`. `M02_MasterTemplete_Store.py` 가 커서 떼어 냄 — 저장소는
`store.sync_combos` · `store.read_combos` 이름으로 이 파일을 부름.
"""
from __future__ import annotations
import json
from typing import Any
from common_util.common_util_json import atomic_write_json
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_MasterTemplete_Tree as tree
HEAD = "SS"
NOTE = (
"구조물집계표 구조물 열(꼴=수)마다 한 줄 · 서버가 집계표 [저장] 때 맞춤(손으로 고치지 않음) · "
"키 SS 는 한 번 주면 안 바뀌고 지운 열의 키는 다시 안 씀 · 경로 = 폴더 이름 배열"
)
def _row(key: str, col: dict[str, Any], numbers: dict[str, str]) -> dict[str, Any]:
path = tree.path_of(col)
return {
"키": key,
"열": col["id"],
"경로": path,
"이름": tree.name_of(path),
**tree.old_fields(path),
"단위": col.get("단위"),
"일위대가": col.get("일위대가"),
"자재조합": col.get("자재조합"),
**{field: numbers.get(field, "") for _, field, _, _ in store.PARTS},
"바인딩": col.get("바인딩"),
"펼침": col.get("펼침"),
}
def sync() -> dict[str, Any]:
"""장부를 집계표에 맞춤 — 새 열 = 새 SS 키 · 지운 열 = 줄 지움 · 고친 열 = 칸 갱신 ·
줄 차례 = 집계표 열 차례. 바뀐 것이 없으면 파일을 안 씀."""
path = store.root() / store.COMBO_FILE
with store._LOCK:
old = json.loads(path.read_bytes()) if path.is_file() else {}
keys = {row["열"]: row["키"] for row in old.get("줄", [])}
taken = [int(k[len(HEAD) :]) for k in keys.values()]
after = max(old.get("다음", 1), max(taken, default=0) + 1)
numbers = store.structure_numbers()
rows = []
for col in store._structure_columns():
if col["id"] not in keys:
keys[col["id"]] = f"{HEAD}{after:06d}"
after += 1
rows.append(_row(keys[col["id"]], col, numbers.get(col["id"], {})))
doc = {"그룹": "구조물집계표조합", "판": 1, "비고": NOTE, "다음": after}
if old.get("지운번호"):
doc["지운번호"] = old["지운번호"] # 지운 열의 도번 8자리 — 다시 안 줌
doc["줄"] = rows
if doc != old:
atomic_write_json(path, doc)
return doc
def read() -> dict[str, Any]:
"""장부 — 파일이 아직 없으면 지금 열 전부로 처음 한 번 만듦 · `경로` 가 없던 옛 줄은 읽을 때
종류 · 분류1~3 에서 채움(빈 분류 건너뜀 · 파일은 다음 [저장] 때 새 모양)."""
path = store.root() / store.COMBO_FILE
doc = json.loads(path.read_bytes()) if path.is_file() else sync()
for row in doc.get("줄") or []:
if "경로" not in row:
row["경로"] = tree.row_path(row)
row["이름"] = tree.name_of(row["경로"])
return doc
def migrate_paths() -> bool:
"""옛 집계표 · 장부 → 경로 모양(열 `경로` · 머리 줄 수 · 장부 `경로` · `이름`) — 한 번 · 두 번
돌려도 같음. 바뀐 것이 없으면 파일을 안 씀 · 바꿨으면 참. 지금 층(`store.at`) 기준."""
got = store.read("table", store.STRUCTURE_TABLE)
if tree.normalize_table(got["문서"]) != got["문서"]:
store.write("table", store.STRUCTURE_TABLE, got["판"], got["문서"])
return True
path = store.root() / store.COMBO_FILE
before = path.read_bytes() if path.is_file() else b""
sync()
return (path.read_bytes() if path.is_file() else b"") != before