feat(B08): 연결된 사본 원천 함수 · 집계표 사본 있는 칸만 · 조합 이름 · 구조물도 사본에서(143-1)

- M02_Item_Snapshot.linked_snapshots — 지정 ∩ 같은 항목 사본 {구조물 id: 항목 · 폴더 · 부분 · 경로} · B08 · B09 공용
- 집계표 서버 — 잇기 목록을 사본으로 거름 · 조합 모두 없는 칸은 안 셈 · 세월교 날 잇기 · 관 몫 본체 잇기 · 「조합 사본 없는 시설 n곳」 알림 · 응답 linked
- 화면 — 조합 이름 = 사본 경로(층 목록 GET 없음) · 측점 · 조합 열 = 사본 있는 것만 · 구조물도 모달 = 사본만(없으면 사본 없음)
- 시험 — test_98 · 120 · 131 픽스처 잇기 · test_61_2 사본만 · 새 test_143_1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
2026-10-09 14:51:50 +09:00
co-authored by Claude Opus 5.5
parent c0cf0ab90a
commit b40a129cb5
11 changed files with 421 additions and 128 deletions
+27
View File
@@ -304,6 +304,33 @@ def complete_old(folder: Path) -> bool:
return True
#: 「연결된 사본」 부분 이름 → 파일(있는 것만 `부분` 에 담음)
LINKED_PARTS = (("도면", "도면.json"), ("면적", "면적.json"), ("일위대가", LABOR_FILE))
def linked_snapshots(project_root: str | Path) -> dict[str, dict[str, Any]]:
"""연결된 사본 — 지정 ∩ 같은 항목 사본 `{구조물 id: {항목, 폴더, 부분, 경로}}`(PLAN 143-1).
구조물 id = `assignments` 키(관 지점 `pipe@측점` · 옵션 그룹 `{id}__{그룹}`) · 미지정 · 사본 없음 ·
다른 항목 사본은 안 담음(M02 `assign_items` 규칙) · 원본 층과 대조 안 함(빠른 읽기 · 쓰기 없음) ·
`경로` = manifest 원본 경로(가져온 때 M02 구분 · 이름) · B08 · B09 공용.
"""
base = structures_dir(project_root)
found: dict[str, dict[str, Any]] = {}
for owner, item in assignments(project_root).items():
folder = base / folder_name(owner)
manifest = read_json(folder / MANIFEST_NAME)
if not same_item(manifest, item):
continue
found[owner] = {
"항목": item,
"폴더": folder.name,
"부분": [part for part, name in LINKED_PARTS if (folder / name).is_file()],
"경로": list(manifest["원본"].get("경로") or []),
}
return found
def read_snapshot(project_root: str | Path, owner_id: str) -> dict[str, Any] | None:
"""그 구조물 복사본의 `집계.json`(`{줄, 열}`) — 없으면 None(옛 항목 폴더는 먼저 옮겨 받음)."""
if items_dir(project_root).is_dir():