perf(826-1): M02 층 목록 요약 색인 — 자국 같으면 항목 파일 안 엶 · 판 · 요약 · 쓰는곳수 기억
- 새 M02_Layer_Index — 항목마다 자국(고친 때 · 크기) · 판 · 서고 요약 기억(메모리 + storage _derived/m02_layer_index) · 바뀐 항목만 다시 읽어 셈 - 기준금액 기댐 = 셈 코드 내용 · M01 폴더 · 층 곁 파일 자국 + 셈 때 읽은 참조 파일 자국(refs.SEEN) - 쓰는곳수 = 요청마다 쓰는 곳 파일 한 번 훑어 거꾸로 셈 · 참조 이름표 기억도 파생 저장본(ids.json) - 판 확인(저장 · 지움 409)은 그대로 파일 내용 · 목록 답 내용 · 차례 그대로(8004 바이트 대조) - 개인 층(102 줄) 두 번째 3.0 → 0.14 초 · 다시 뜬 뒤 첫 9.9 → 0.24 초 · 마스터 2.9 → 0.10 초 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
"""826-1 층 목록 요약 색인 — 답 = 다시 셈한 것 · 자국 같으면 항목 파일 안 엶 · 고침 · 지움 · 더함 ·
|
||||
참조 고침 · M01 값 바뀜 · 색인 깨짐 뒤에도 맞음 · 판 충돌(409) 그대로."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Layer_Index as index
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||||
from resources.tester.test_m02_labor_ref import COMPANY, world # noqa: F401 — 같은 임시 층
|
||||
|
||||
PCT = "GC001109" # 원/㎡ · 인력 둘 + 덧줄 %
|
||||
WAGE = "LB000001"
|
||||
LIB = ("sangeun", "precombo", "labor")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def fresh(world, monkeypatch): # noqa: F811
|
||||
"""색인 기억 비움 — 시험끼리 안 섞임."""
|
||||
monkeypatch.setattr(index, "_MEM", {})
|
||||
monkeypatch.setattr(index, "_IDS_LOADED", [])
|
||||
monkeypatch.setattr(refs, "_IDS", {})
|
||||
return world
|
||||
|
||||
|
||||
def _forget():
|
||||
index._MEM.clear()
|
||||
index._IDS_LOADED.clear()
|
||||
refs._IDS.clear()
|
||||
|
||||
|
||||
def _sk(folder, name, extra=None):
|
||||
doc = {"이름": name, "단위": "㎡", "바탕": {"로직": PCT}, "더한줄": extra or []}
|
||||
with store.at(folder):
|
||||
return refs.create("sangeun", doc)["이름"]
|
||||
|
||||
|
||||
def _wage_line(qty=1):
|
||||
return {"종류": "인력", "요소": WAGE, "이름": "보통인부", "단위": "인", "수량": qty}
|
||||
|
||||
|
||||
def _old(folder):
|
||||
rows = layers.list_templates(folder)
|
||||
refs.summaries(folder, rows)
|
||||
return rows
|
||||
|
||||
|
||||
def _new(folder):
|
||||
rows = index.list_rows(folder)
|
||||
index.summaries(folder, rows)
|
||||
return rows
|
||||
|
||||
|
||||
def _same(folder):
|
||||
got = _new(folder)
|
||||
assert json.dumps(got, ensure_ascii=False) == json.dumps(_old(folder), ensure_ascii=False)
|
||||
return got
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def reads(monkeypatch):
|
||||
"""항목 파일(서고 · 일위대가) 읽기 수."""
|
||||
seen: list[str] = []
|
||||
real = Path.read_bytes
|
||||
|
||||
def counting(self):
|
||||
if self.parent.name in LIB:
|
||||
seen.append(f"{self.parent.name}/{self.stem}")
|
||||
return real(self)
|
||||
|
||||
monkeypatch.setattr(Path, "read_bytes", counting)
|
||||
return seen
|
||||
|
||||
|
||||
def _shelf(w):
|
||||
up = _sk(w["company"], "회사 단가산출")
|
||||
mine = w["personal"]
|
||||
a = _sk(mine, "가", [_wage_line()])
|
||||
b = _sk(mine, "나", [{"참조": {**COMPANY, "키": up}, "수량": 2}])
|
||||
with store.at(mine):
|
||||
sc = refs.create(
|
||||
"precombo",
|
||||
{
|
||||
"이름": "일위대가",
|
||||
"단위": "㎡",
|
||||
"담은로직": [{"참조": {**COMPANY, "키": up}, "수량": {"손": 3}}],
|
||||
},
|
||||
)["이름"]
|
||||
return up, a, b, sc
|
||||
|
||||
|
||||
def test_same_and_no_reads_when_stamps_same(fresh, reads):
|
||||
up, a, b, sc = _shelf(fresh)
|
||||
mine = fresh["personal"]
|
||||
first = _same(mine)
|
||||
assert {r["이름"] for r in first} >= {a, b, sc} and first[0]["기준금액"] is not None
|
||||
reads.clear()
|
||||
assert _new(mine) == first and reads == [] # 메모리 기억
|
||||
_forget()
|
||||
reads.clear()
|
||||
assert _new(mine) == first and reads == [] # 디스크 파생 저장본(서버 다시 뜸)
|
||||
# 쓰는곳수 — 회사 SK 를 개인 SK 「나」 가 씀(사전조합은 사본으로 펼쳐 담음 · 740-1)
|
||||
company = _same(fresh["company"])
|
||||
assert company[0]["쓰는곳수"] == 1
|
||||
|
||||
|
||||
def _unfreeze(folder, key):
|
||||
"""옛 꼴(사본 뗌) — M01 을 지금 값으로 셈(사본 있으면 만든 때 값 그대로)."""
|
||||
doc = json.loads(layers.template_path(folder, "sangeun", key).read_bytes())
|
||||
old = {k: v for k, v in doc.items() if k not in ("사본", "수식", "M01판", "바탕판")}
|
||||
layers.write_template(folder, "sangeun", key, old)
|
||||
|
||||
|
||||
def test_edit_delete_add_and_dep_change(fresh, reads):
|
||||
up, a, b, sc = _shelf(fresh)
|
||||
mine = fresh["personal"]
|
||||
gone = _sk(mine, "지울것")
|
||||
_same(mine)
|
||||
with store.at(mine): # 고침 · 지움
|
||||
got = store.read("sangeun", a)
|
||||
store.write("sangeun", a, got["판"], {**got["문서"], "더한줄": [_wage_line(3)]})
|
||||
store.delete("sangeun", gone)
|
||||
c = _sk(mine, "다") # 더함
|
||||
reads.clear()
|
||||
rows = _new(mine)
|
||||
assert set(reads) == {f"sangeun/{a}", f"sangeun/{c}"} # 바뀐 항목만 다시 읽음
|
||||
assert rows == _old(mine)
|
||||
titles = {r["이름"]: r.get("제목") for r in rows if r["종류"] in index.layers.LIBRARY_KINDS}
|
||||
assert gone == c and titles == {
|
||||
a: "가",
|
||||
b: "나",
|
||||
c: "다",
|
||||
sc: "일위대가",
|
||||
} # 지운 키를 새 것이 받음
|
||||
reads.clear()
|
||||
was = {r["이름"]: r.get("기준금액") for r in _new(mine)}
|
||||
assert reads == []
|
||||
with store.at(fresh["company"]): # 사전조합이 담은 위 층 SK 고침 → 그 사전조합만 다시
|
||||
got = store.read("sangeun", up)
|
||||
store.write("sangeun", up, got["판"], {**got["문서"], "더한줄": [_wage_line(5)]})
|
||||
reads.clear()
|
||||
now = {r["이름"]: r.get("기준금액") for r in _new(mine)}
|
||||
assert set(reads) == {f"precombo/{sc}", f"sangeun/{up}"}
|
||||
assert now[sc] != was[sc] and {k: v for k, v in now.items() if k != sc} == {
|
||||
k: v for k, v in was.items() if k != sc
|
||||
}
|
||||
assert _new(mine) == _old(mine)
|
||||
|
||||
|
||||
def test_m01_change_recounts_amount(fresh, tmp_path, monkeypatch):
|
||||
copy = tmp_path / "m01"
|
||||
copy.mkdir()
|
||||
for path in Path(m01.FOLDER).glob("*.json"):
|
||||
shutil.copyfile(path, copy / path.name)
|
||||
monkeypatch.setattr(m01, "FOLDER", copy)
|
||||
mine = fresh["personal"]
|
||||
a = _sk(mine, "가", [_wage_line()])
|
||||
_unfreeze(mine, a)
|
||||
one = next(r for r in _same(mine) if r["이름"] == a)["기준금액"]
|
||||
assert one is not None
|
||||
wages = json.loads((copy / "인력.json").read_text(encoding="utf-8"))
|
||||
for row in wages["줄"]:
|
||||
if row.get("키") == WAGE:
|
||||
row["값"] = float(row["값"]) * 2
|
||||
(copy / "인력.json").write_text(json.dumps(wages, ensure_ascii=False), encoding="utf-8")
|
||||
two = next(r for r in _same(mine) if r["이름"] == a)["기준금액"]
|
||||
assert two != one
|
||||
|
||||
|
||||
def test_broken_index_rebuilds(fresh):
|
||||
_up, _a, _b, _sc = _shelf(fresh)
|
||||
mine = fresh["personal"]
|
||||
first = _same(mine)
|
||||
folder = index.derived_dir(layers.storage_root(), index.KIND)
|
||||
for path in folder.glob("*.json"):
|
||||
path.write_text("{깨짐", encoding="utf-8")
|
||||
_forget()
|
||||
assert _same(mine) == first
|
||||
for path in folder.glob("*.json"):
|
||||
path.write_text(json.dumps({"key": path.stem, "value": {"sangeun/x": 1}}), encoding="utf-8")
|
||||
_forget()
|
||||
assert _same(mine) == first
|
||||
shutil.rmtree(folder)
|
||||
_forget()
|
||||
assert _same(mine) == first
|
||||
|
||||
|
||||
def test_version_conflict_still_409(fresh):
|
||||
mine = fresh["personal"]
|
||||
a = _sk(mine, "가")
|
||||
listed = next(r for r in index.list_rows(mine) if r["이름"] == a)["판"]
|
||||
path = layers.template_path(mine, "sangeun", a)
|
||||
assert listed == layers.version_of(path)
|
||||
doc = json.loads(path.read_bytes())
|
||||
layers.write_template(
|
||||
mine, "sangeun", a, {**doc, "단위": "m"}, version=listed, check_version=True
|
||||
)
|
||||
with pytest.raises(layers.StaleTemplate): # 옛 판으로 또 저장
|
||||
layers.write_template(mine, "sangeun", a, doc, version=listed, check_version=True)
|
||||
fresh_ver = next(r for r in index.list_rows(mine) if r["이름"] == a)["판"]
|
||||
assert fresh_ver == layers.version_of(path) != listed
|
||||
Reference in New Issue
Block a user