Files
Aislo/resources/tester/test_719_1_sk_freeze.py
T
eomsangdonandClaude Opus 5.5 b54950d28b feat(719-1): 프로젝트 사본 SK 풀어 굳힘 — 고친 줄 = 사본 단가.json 로직 줄 · 굳힌 키 GX9##### (SK 이름표 해시 · 같은 SK = 같은 키) · B09 서고 manifest 원본판
- 새 M02_Item_Sangeun: 참조사본 SK → M02_Sangeun_Calc.resolve 고친 줄 → 로직_단가산출.json 줄 · 사본 SK = 바탕 굳힌 키 · 덮개 0 · 원본 · 바탕판 · 짝잃음 근거
- M02_Item_Source: labor_doc_rows 가 굳힘 · price_bundle 이 굳힌 줄과 그 줄이 닿는 마스터 줄 · 고른 자재 담음
- M02_Item_Prices: 수집기 줄 하나 길(row) 떼어 둠 · 요소 없는 줄(참조 · 고정 더한줄) 건너뜀
- B09 Bill_Library: 서고 사본 manifest 원본판 = 일위대가.json 지문(source_version)
- 시험 test_719_1_sk_freeze 4(임시 storage)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:34:29 +09:00

121 lines
6.0 KiB
Python

"""719-1 프로젝트 사본 — M02 단가산출(SK) 풀어 굳힘(`M02_Item_Sangeun` · `M02_Item_Source` ·
`M02_Item_Prices` · `B09_Estimation_Bill_Library`) — 임시 storage(견본 층 · 실제 M01 읽기만).
구조물 사본 · B09 서고 사본 셈 = M02 층 SK 셈 · 굳힌 줄은 사본 `단가.json` 로직 줄 · 원본 SK 를
지워도 같은 값 · 같은 SK = 같은 굳힌 키(다시 복사 · SK 고친 뒤도) · 원본 층 지문 그대로 ·
서고 manifest 원본판 = SK 바뀌면 다름.
"""
# ruff: noqa: F811 — 임시 층 fixture `world` 를 들여 씀
import hashlib
import json
import pytest
from B09_Estimation import B09_Estimation_Bill_Library as library
from M02_MasterTemplete import M02_Item_Prices as prices
from M02_MasterTemplete import M02_Item_Sangeun as sk
from M02_MasterTemplete import M02_Item_Source as source
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from resources.tester.test_715_sangeun_store import PCT, _sk
from resources.tester.test_m02_labor_ref import MINE, world # noqa: F401 — 같은 임시 층
COVER = {"labor:LB000036#0": {"qty": 0.5}}
ADD = [{"종류": "인력", "요소": "LB000002", "수량": 0.1}]
def _print(folder):
"""층 폴더 지문 — 파일 이름 · 바이트."""
out = hashlib.sha256()
for path in sorted(p for p in folder.rglob("*") if p.is_file()):
out.update(str(path.relative_to(folder)).encode() + path.read_bytes())
return out.hexdigest()
def _top(ref, qty=2):
with store.at(ref["folder"]):
doc = labor.clean({"열": "rv", "담은로직": [{"참조": ref["ref"], "수량": {"손": qty}}]})
return doc, labor.calc(doc, {})["줄"][0]
def _mine(world):
key = _sk(world["personal"], 바탕={"로직": PCT}, 덮개=COVER, 더한줄=ADD)
return key, {"folder": world["personal"], "ref": {**MINE, "키": key}}
def test_구조물_사본_굳힌_줄_값_같음_원본_지워도(world):
key, ref = _mine(world)
doc, live = _top(ref)
assert not live["까닭"] and live["기준"]["계"] > 0
before = _print(world["personal"])
rows = source.labor_doc_rows(doc)
bundle = source.price_bundle(rows, world["personal"])
assert _print(world["personal"]) == before # 원본 층 안 건드림
rid = refs.ref_id(ref["ref"])
frozen = rows["참조사본"][rid]
fk = frozen["바탕"]["로직"]
assert fk == sk.frozen_key(rid, set()) and fk.startswith("GX9")
assert frozen["덮개"] == {} and frozen["더한줄"] == [] and frozen["뺀줄"] == []
assert frozen["원본"]["덮개"]["labor:LB000036#0"]["qty"] == 0.5 # 고친 근거
assert [r["키"] for r in bundle[sk.FILE]["줄"]] == [fk]
json.dumps(rows["참조사본"], ensure_ascii=False) # 사본 파일로 쓸 수 있는 꼴(Decimal 없음)
(world["personal"] / "sangeun" / f"{key}.json").unlink() # 원본이 사라져도 사본만 봄
got = prices.calc(rows, json.loads(json.dumps(bundle, ensure_ascii=False)), {}, {}, None)
line = got["줄"][0]
assert not line["까닭"]
for part in ("노무비", "재료비", "경비", "계"):
assert line["기준"][part] == pytest.approx(live["기준"][part], rel=1e-9)
assert line["반영"]["계"] == pytest.approx(live["반영"]["계"], rel=1e-9)
def test_같은_SK_같은_굳힌_키_다른_SK_다른_키(world):
key, ref = _mine(world)
doc, _ = _top(ref)
rid = refs.ref_id(ref["ref"])
first = source.labor_doc_rows(doc)["참조사본"][rid]["바탕"]["로직"]
with store.at(world["personal"]): # SK 를 고쳐도 굳힌 키 그대로(B09 덮개 짝 지킴)
now = store.read("sangeun", key)
store.write("sangeun", key, now["판"], {**now["문서"], "덮개": {}})
again = source.labor_doc_rows(doc)["참조사본"][rid]
assert again["바탕"]["로직"] == first and again["원본"]["덮개"] == {}
other = refs.ref_id({"층": "company", "주인": "7", "키": key}) # 같은 번호 다른 층
assert sk.frozen_key(other, set()) != first
taken = {first}
assert sk.frozen_key(rid, taken) != first and len(taken) == 2 # 겹치면 다음 번호
def test_B09_서고_사본_값_같음_원본판(world, tmp_path):
_, ref = _mine(world)
with store.at(world["personal"]):
sc = refs.create(
"precombo",
{"이름": "조합", "단위": "㎡", "담은로직": [{"참조": ref["ref"], "수량": {"손": 2}}]},
)["이름"]
_, live = _top({"folder": world["personal"], "ref": {**MINE, "키": sc}}, 1)
before = _print(world["personal"])
root = tmp_path / "proj"
made = library.import_copy(root, "precombo", "personal", "7/42", sc)
assert made["만듦"] and _print(world["personal"]) == before
folder = library.library_dir(root) / made["폴더"]
read = {p.name: json.loads(p.read_text(encoding="utf-8")) for p in folder.glob("*.json")}
got = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, None)["줄"][0]
assert not got["까닭"]
assert got["기준"]["계"] == pytest.approx(live["기준"]["계"], rel=1e-9)
mark = read["manifest.json"]["원본판"]
built = library._build("precombo", "personal", "7/42", sc)[0]
assert library.source_version(built) == mark # 다시 모아도 같은 판
again = library.import_copy(root, "precombo", "personal", "7/42", sc)
assert again == {"폴더": made["폴더"], "만듦": False}
with store.at(world["personal"]): # SK 고침 → 원본판 다름
now = store.read("sangeun", ref["ref"]["키"])
store.write("sangeun", ref["ref"]["키"], now["판"], {**now["문서"], "덮개": {}})
assert library.source_version(library._build("precombo", "personal", "7/42", sc)[0]) != mark
def test_SK_없는_사본_그대로():
"""SK 없는 참조사본 = 받은 묶음 그대로(기존 사본 · 공사 금액 전후 같음)."""
copies = {"x": {"종류": "사전조합", "담은로직": []}}
assert sk.freeze(copies) is copies and sk.rows(copies) == [] and sk.materials(copies) == []