From ebd33de9c97788a0661fbda197a19d5507cf9e2f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 31 Oct 2026 17:20:45 +0900 Subject: [PATCH] =?UTF-8?q?fix(B09=20=ED=86=B5=ED=95=A9=20=EC=A0=90?= =?UTF-8?q?=EA=B2=80=20=C2=B7=20703-2=20=C3=97=20703-3):=20=EC=82=AC?= =?UTF-8?q?=EC=A0=84=EC=A1=B0=ED=95=A9=EC=97=90=EC=84=9C=20=EB=B3=B5?= =?UTF-8?q?=EC=82=AC=ED=95=9C=20=EC=9D=BC=EC=9C=84=EB=8C=80=EA=B0=80=20?= =?UTF-8?q?=E2=80=94=20[=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8]=20=EB=92=A4?= =?UTF-8?q?=20=EA=B8=88=EC=95=A1=200=20=C2=B7=20[=EC=A0=80=EC=9E=A5]=20?= =?UTF-8?q?=EB=95=8C=20=EC=9B=90=EB=B3=B8=20=EC=82=AC=EB=B3=B8=20=EC=A7=80?= =?UTF-8?q?=EC=9B=80=20=EB=A7=89=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 흠: 사전조합 연결 줄을 「복사해 새 일위대가」 → [바꿈] 하면 복사본 원본 열쇠가 `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 --- B09_Estimation/B09_Estimation_Bill_Library.py | 43 +++++++++++++------ B09_Estimation/B09_Estimation_Router.py | 2 +- resources/tester/test_703_b09_daega_copy.py | 22 ++++++++++ 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/B09_Estimation/B09_Estimation_Bill_Library.py b/B09_Estimation/B09_Estimation_Bill_Library.py index 538f200ff..a53d71108 100644 --- a/B09_Estimation/B09_Estimation_Bill_Library.py +++ b/B09_Estimation/B09_Estimation_Bill_Library.py @@ -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 []: diff --git a/B09_Estimation/B09_Estimation_Router.py b/B09_Estimation/B09_Estimation_Router.py index 2e6424a3a..b414a1727 100644 --- a/B09_Estimation/B09_Estimation_Router.py +++ b/B09_Estimation/B09_Estimation_Router.py @@ -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 붙인 줄 이음 diff --git a/resources/tester/test_703_b09_daega_copy.py b/resources/tester/test_703_b09_daega_copy.py index b1f272386..c4930ef5c 100644 --- a/resources/tester/test_703_b09_daega_copy.py +++ b/resources/tester/test_703_b09_daega_copy.py @@ -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) ─────────────────────────────