fix(B09 통합 점검 · 703-2 × 703-3): 사전조합에서 복사한 일위대가 — [업데이트] 뒤 금액 0 · [저장] 때 원본 사본 지움 막음

- 흠: 사전조합 연결 줄을 「복사해 새 일위대가」 → [바꿈] 하면 복사본 원본 열쇠가 `C:참조…` 꼴인데 쓰는 사본 셈(used_folders)이 `L:` 만 봄
  → [업데이트] 수요에서 원본 사본 노임 줄이 빠져 책에서 지워짐(금액 40,490 → 0 · 빈 줄 3) · [저장] 정리가 원본 사본 폴더를 지울 수 있음
- 고침: 복사본의 복사본(`K:`)은 맨 처음 원본까지 따라감 · `C:참조` = 그 참조를 담은 사본 폴더도 지킴(프로젝트 자리 줄 때)
- 고친 파일: B09_Estimation/B09_Estimation_Bill_Library.py · B09_Estimation/B09_Estimation_Router.py · resources/tester/test_703_b09_daega_copy.py
- 확인: 시험 test_703 · 703_1 · 703_3 · 204_2 · 251_1 29 통과 · 검증_sub6 [업데이트] 뒤 측구파기(토사) 40,490 그대로 · [저장] 뒤 서버 161,960 = 화면 · 사본 폴더 남음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 17:20:45 +09:00
co-authored by Claude Opus 5.5
parent e22fd23c6c
commit ebd33de9c9
3 changed files with 54 additions and 13 deletions
+31 -12
View File
@@ -340,24 +340,43 @@ def copies(project_root: str | Path) -> list[tuple[Path, dict[str, Any]]]:
return out
def used_folders(user: dict[str, Any]) -> set[str]:
def used_folders(user: dict[str, Any], project_root: str | Path | None = None) -> set[str]:
"""설계내역서 덮개 줄 · 거리이정표 운반 연결(251-5)이 잇는 서고 사본 폴더 +
일위대가 복사본(703-2 `@copy`)의 원본 사본."""
copies = [
str(lines["@copy"].get("from") or "")
for lines in (user.get("daega_cover") or {}).values()
일위대가 복사본(703-2 `@copy`)의 원본 사본 — 복사본의 복사본(`K:`)은 맨 처음 원본까지 ·
사전조합 원본(`C:참조`) = 그 참조를 담은 사본 폴더(`project_root` 줄 때)."""
covers = user.get("daega_cover") or {}
def origin(src: str) -> str:
seen: set[str] = set()
while src.startswith("K:") and src not in seen:
seen.add(src)
src = str(((covers.get(src) or {}).get("@copy") or {}).get("from") or "")
return src
srcs = {
origin(str(lines["@copy"].get("from") or ""))
for lines in covers.values()
if isinstance(lines, dict) and isinstance(lines.get("@copy"), dict)
]
return {
str(ref["lib"])
for row in ref_rows(user)
if isinstance(ref := row.get("ref"), dict) and ref.get("tab") == "dg" and ref.get("lib")
} | {src[2:] for src in copies if src.startswith("L:")}
}
pre = {src[2:] for src in srcs if src.startswith("C:")}
return (
{
str(ref["lib"])
for row in ref_rows(user)
if isinstance(ref := row.get("ref"), dict) and ref.get("tab") == "dg" and ref.get("lib")
}
| {src[2:] for src in srcs if src.startswith("L:")}
| {
folder.name
for folder, manifest in (copies(project_root) if pre and project_root else [])
if refs.ref_id((manifest.get("원본") or {}).get("참조") or {}) in pre
}
)
def prune(project_root: str | Path, user: dict[str, Any]) -> list[str]:
"""[저장] 뒤 — 줄이 안 잇는 사본 폴더를 지움(다시 고르면 새로 들여옴) · 지운 이름."""
keep = used_folders(user)
keep = used_folders(user, project_root)
base = library_dir(project_root)
gone = []
for child in sorted(base.iterdir()) if base.is_dir() else []:
+1 -1
View File
@@ -210,7 +210,7 @@ async def _demand(root: Path, draft: dict[str, Any] | None = None) -> list[str]:
"""단가 수요 키 — 연결된 사본 + 설계내역서 손 줄 로직(166-2) +
거리이정표 운반 연결(251-5) · draft = 화면 캐시(296 · 서고 사본 = 그 줄이 잇는 것만)."""
user = await asyncio.to_thread(calc_user, root, draft) # 운반 값 사본 먼저(263-2)
keep = None if draft is None else library.used_folders(user)
keep = None if draft is None else library.used_folders(user, root)
keys = await asyncio.to_thread(linked_resource_keys, root, keep)
hand = await asyncio.to_thread(sangeun.hand_resource_keys, user)
hand = [*hand, *added_keys(user.get("daega_cover"))] # 705-2 붙인 줄 이음
@@ -96,6 +96,28 @@ def test_서고_사본_원본_지킴():
assert library.used_folders(user) == {"combo@system@-@SS1"}
def test_사전조합_원본_복사본_사본_지킴(tmp_path):
"""통합 점검 — 사전조합(`C:참조`) 복사본 · 복사본의 복사본 = 원본 사본 폴더 지킴(업데이트 수요 · 저장 정리)."""
base = library.library_dir(tmp_path)
for name, ref in (
("precombo@personal@1-3@SC-000001", {"층": "personal", "주인": "1/3", "키": "SC-000001"}),
("precombo@personal@1-3@SC-000002", {"층": "personal", "주인": "1/3", "키": "SC-000002"}),
):
(base / name).mkdir(parents=True)
manifest = {"종류": "precombo", "원본": {"참조": ref}}
(base / name / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
src = "C:참조|personal|1/3|SC-000001"
user = {
"daega_cover": {
"K:aa": {"@copy": {"from": src, "name": "복사"}},
"K:bb": {"@copy": {"from": "K:aa", "name": "복사의 복사"}},
}
}
assert library.used_folders(user) == set() # 폴더 모름 = 옛 꼴
assert library.used_folders(user, tmp_path) == {"precombo@personal@1-3@SC-000001"}
assert library.prune(tmp_path, user) == ["precombo@personal@1-3@SC-000002"]
# ── 내 자료집(개인 층 사전조합 · 임시 storage) ─────────────────────────────