feat(M02): 집계 항목 복사본을 구조물마다 — templates/structures/{구조물 id} · 옛 항목 폴더 옮겨 받기 · 재적용(덮임 알림) · 개인에 저장 · 프로젝트에서 새 항목 · 지정 바꿈 · 삭제 때 고친 것은 _지난 (PLAN 26-2b)

- 같은 항목을 고른 구조물 둘도 도면 · 면적 · 재료 · 자재조합 · 일위대가 · 입력연결 · 입력값 · 고른자재 · 단가를 따로 가짐 · 폴더 = structure_id 또는 pipe@측점
- 옛 templates/items/{층}_{주인}_{키} 는 읽을 때 그 항목을 쓰는 구조물마다 복사(고친 값 유지) · 안 쓰이고 고친 것은 structures/_지난
- 길: 미리 가져오기 {structure, layer, owner, key} · POST …/{폴더}/reapply(refresh 옛 이름) · …/save-as {to, 경로, 이름} · …/items/new {structure, 경로, 이름, 단위} · 회사 없음 409
- manifest 원본에 경로(26-1 · 경로 칸 우선 · 옛 종류 · 분류는 건너뛰며 받음) · 원본이 바뀌거나 지워져도 복사본 · 계산 그대로(목록 마스터바뀜 · 원본없음)
- 채움(M02_Table_Fill)은 구조물 복사본의 열 정의를 읽음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt
This commit is contained in:
2026-09-30 07:45:20 +09:00
co-authored by Claude Opus 5.5
parent bf21676ef5
commit fbb214cd94
8 changed files with 683 additions and 151 deletions
+15 -4
View File
@@ -54,13 +54,24 @@ def row_of(document: Any, key: str) -> dict[str, Any] | None:
return None
def item_name(item: dict[str, Any]) -> str:
"""층 SS 줄의 보이는 이름(종류 · 분류1~3) — 없으면 LookupError."""
def row_path(row: dict[str, Any]) -> list[str]:
"""SS 줄의 경로(26-1) — `경로` 칸이 있으면 그것 · 옛 줄은 종류 · 분류1~3(빈 분류는 건너뜀)."""
if isinstance(row.get("경로"), list):
return [str(p) for p in row["경로"] if str(p or "").strip()]
return [str(row[k]) for k in ("종류", "분류1", "분류2", "분류3") if row.get(k)]
def item_path(item: dict[str, Any]) -> list[str]:
"""층 SS 줄의 경로 — 없으면 LookupError."""
row = row_of(read_json(layer_root(item) / layers.COMBO_FILE), str(item.get("key")))
if row is None:
raise LookupError(f"집계 항목 {item.get('key')} 이 {item.get('layer')} 층에 없음")
parts = (row.get(k) for k in ("종류", "분류1", "분류2", "분류3"))
return " ".join(str(p) for p in parts if p) or str(row["키"])
return row_path(row) or [str(row["키"])]
def item_name(item: dict[str, Any]) -> str:
"""층 SS 줄의 보이는 이름(경로를 이은 글) — 없으면 LookupError."""
return " ".join(item_path(item))
def version_of(content: Any) -> str: