fix(818-1): M02 왼쪽 단가산출 · 일위대가 칸 빈 화면 — 층 목록 「쓰는 곳 수」 셈이 SK 마다 모든 층 파일 통째 읽음
- 원인: 서버 층 목록(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 <noreply@anthropic.com>
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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"])
|
||||
|
||||
Reference in New Issue
Block a user