From f5484e8906a41b051f4cb24fc436b7430d56d545 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 11 Nov 2026 22:12:41 +0900 Subject: [PATCH] =?UTF-8?q?fix(818-1):=20M02=20=EC=99=BC=EC=AA=BD=20?= =?UTF-8?q?=EB=8B=A8=EA=B0=80=EC=82=B0=EC=B6=9C=20=C2=B7=20=EC=9D=BC?= =?UTF-8?q?=EC=9C=84=EB=8C=80=EA=B0=80=20=EC=B9=B8=20=EB=B9=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=E2=80=94=20=EC=B8=B5=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=E3=80=8C=EC=93=B0=EB=8A=94=20=EA=B3=B3=20=EC=88=98=E3=80=8D=20?= =?UTF-8?q?=EC=85=88=EC=9D=B4=20SK=20=EB=A7=88=EB=8B=A4=20=EB=AA=A8?= =?UTF-8?q?=EB=93=A0=20=EC=B8=B5=20=ED=8C=8C=EC=9D=BC=20=ED=86=B5=EC=A7=B8?= =?UTF-8?q?=20=EC=9D=BD=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 원인: 서버 층 목록(GET /m02/layers/{층}/templates)이 단가산출 줄마다 모든 층 labor · precombo · sangeun 파일(로직 사본으로 SK 파일 5 MB · 합 340 MB)을 다시 읽음 → 응답 4 분 넘음 → 화면 목록이 「불러오는 중」 빈 칸 - 고침: 파일마다 (이름 · 참조 이름표) 를 판(고친 때 · 크기)으로 기억 · 목록 한 번에 파일 찾기 한 번 - 응답 마스터 7.2 → 3.4 초 · 개인 8.5 → 4.6 초(처음 → 다음) · 쓰는 곳 수 · 기준금액 전후 같음 - 지킴 시험: 파일을 고치면 쓰는 곳 다시 읽음 Co-Authored-By: Claude Opus 5.5 --- M02_MasterTemplete/M02_Labor_Ref.py | 61 +++++++++++++++++++------- resources/tester/test_m02_labor_ref.py | 11 +++++ 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/M02_MasterTemplete/M02_Labor_Ref.py b/M02_MasterTemplete/M02_Labor_Ref.py index 6cb971e55..6afc85727 100644 --- a/M02_MasterTemplete/M02_Labor_Ref.py +++ b/M02_MasterTemplete/M02_Labor_Ref.py @@ -363,7 +363,9 @@ def create(kind: str, doc: dict[str, Any]) -> dict[str, Any]: raise _bad("새 키를 못 잡음 — 다시 누름", 409) -def summary(folder: Path, kind: str, key: str, expand: Expand | None = None) -> dict[str, Any]: +def summary( + folder: Path, kind: str, key: str, expand: Expand | None = None, files: UseFiles | None = None +) -> dict[str, Any]: """층 목록 한 줄에 붙일 것 — `{제목, 단위, 구분, 상세구분, 연결}`(51-12) · `expand` 면 1 단위 기준 계(`기준금액` · 51-4).""" try: @@ -392,8 +394,8 @@ def summary(folder: Path, kind: str, key: str, expand: Expand | None = None) -> except Exception: # noqa: BLE001 — 없는 로직 = 키만(셈 때 까닭) pass out["고친줄수"] = sum(len(doc.get(k) or ()) for k in ("덮개", "더한줄", "뺀줄")) - # ponytail: SK 마다 모든 층 훑음(users) — 목록이 느려지면 층 훑기 한 번 셈으로 - out["쓰는곳수"] = len(users({"층": layer, "주인": owner, "키": key})) + # SK 마다 모든 층 훑음(users) — 파일 읽기는 판 기억(`_ids_of`)이라 처음 한 번만 무거움 + out["쓰는곳수"] = len(users({"층": layer, "주인": owner, "키": key}, files)) return out @@ -403,29 +405,58 @@ def summaries(folder: Path, rows: list[dict[str, Any]]) -> None: from M02_MasterTemplete.M02_Labor_Expand import Expand expand = Expand(m01.loaded()[1]) + files = use_files() if any(row["종류"] == "sangeun" for row in rows) else None for row in rows: if row["종류"] in layers.LIBRARY_KINDS: - row.update(summary(folder, row["종류"], row["이름"], expand)) + row.update(summary(folder, row["종류"], row["이름"], expand, files)) # ── 쓰는 곳 · 지우기 ────────────────────────────────────────────────── -def users(ref: Ref) -> list[str]: +UseFiles = list[tuple[str, str, Path]] + + +def use_files() -> UseFiles: + """쓰는 곳을 찾을 파일 — (무엇, 어디, 파일) · 목록 한 번에 한 번 찾음(818-1).""" + # 「일위대가」 = 사전조합 새 이름(내역 14 확정 1) · 구조물 조합 일위대가 = 「구조물 조합」 + kinds = (("labor", "구조물 조합"), ("precombo", "일위대가"), ("sangeun", "단가산출")) + return [(what, at, path) for kind, what in kinds for at, path in layers.layer_files(kind)] + + +def users(ref: Ref, files: UseFiles | None = None) -> list[str]: """`ref` 를 담은 일위대가 · 사전조합 · 단가산출(바탕 · 더한줄) — 「어디 이름」 목록(모든 층 훑음 · 프로젝트 뺌).""" rid, out = ref_id(ref), [] - # 「일위대가」 = 사전조합 새 이름(내역 14 확정 1) · 구조물 조합 일위대가 = 「구조물 조합」 - for kind, what in (("labor", "구조물 조합"), ("precombo", "일위대가"), ("sangeun", "단가산출")): - for at, path in layers.layer_files(kind): - try: - doc = json.loads(path.read_bytes()) - except (OSError, ValueError): - continue - if any(ref_id(one) == rid for one in refs_in(doc)): - name = doc.get("일위대가번") or doc.get("이름") or path.stem - out.append(f"{what} {at} 「{name}」") + for what, at, path in use_files() if files is None else files: + got = _ids_of(path) + if got and rid in got[1]: + out.append(f"{what} {at} 「{got[0]}」") return out +_IDS: dict[Path, tuple[tuple[int, int], tuple[str, frozenset[str]]]] = {} + + +def _ids_of(path: Path) -> tuple[str, frozenset[str]] | None: + """파일 하나의 (이름, 참조 이름표들) — 판(고친 때 · 크기)이 같으면 기억한 것(818-1 · 로직 사본으로 + SK 파일이 MB 단위라 목록 줄마다 모든 층을 다시 읽으면 분 단위).""" + try: + st = path.stat() + stamp = (st.st_mtime_ns, st.st_size) + if (hit := _IDS.get(path)) and hit[0] == stamp: + return hit[1] + doc = json.loads(path.read_bytes()) + except (OSError, ValueError): + return None + name = ( + (doc.get("일위대가번") or doc.get("이름") or path.stem) + if isinstance(doc, dict) + else path.stem + ) + got = (str(name), frozenset(ref_id(one) for one in refs_in(doc))) + _IDS[path] = (stamp, got) + return got + + def refuse_used(folder: Path, kind: str, key: str) -> None: """쓰는 곳이 있으면 409 + 이름(서고 종류만).""" if kind not in PREFIX: diff --git a/resources/tester/test_m02_labor_ref.py b/resources/tester/test_m02_labor_ref.py index 7c14351ce..ee77ac30f 100644 --- a/resources/tester/test_m02_labor_ref.py +++ b/resources/tester/test_m02_labor_ref.py @@ -319,6 +319,17 @@ def test_쓰는_곳이_있으면_지우기_409_없으면_지움(world): assert not (world["system"] / "precombo" / f"{sc}.json").exists() +def test_쓰는_곳_기억은_파일을_고치면_다시_읽음_818_1(world): + sc = _combo(world["system"], [{"로직": TEN}]) + ref = {**SYS, "키": sc} + with store.at(world["personal"]): + store.write("labor", "x1", "", {"열": "x1", "담은로직": [{"참조": ref}], "자재": []}) + assert len(refs.users(ref)) == 1 and refs.users(ref) == refs.users(ref, refs.use_files()) + ver = store.read("labor", "x1")["판"] + store.write("labor", "x1", ver, {"열": "x1", "담은로직": [], "자재": []}) + assert refs.users(ref) == [] + + def test_복사는_못_읽는_참조를_같이_복사_본뜬(world): inner = _combo(world["personal"], [{"로직": TEN}], "안") sys_sp = _promote(world["system"])