Files
Aislo/resources/tester/test_719_1_sk_freeze.py
T
eomsangdonandClaude Opus 5.5 a2da6cb069 Merge remote-tracking branch 'origin/dev' into sub_laptop_8
- 충돌 시험 셋(717-4 · 719-2 · hopyo build) = 740-1 펼침 단언 + 742-1 저장 수량 단언 합침
- 740-2 일위대가표 풍선 줄의 body(742-1 이 걷은 휘발 셈 몸) → cur

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:47:51 +09:00

132 lines
6.7 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}}]},
)["이름"]
# 742-1 = SC 안 줄 수량(손 2) 저장 → 굳힌 사본 = 저장 2 · 쓰는 쪽 알약 값(2)이 앞섬(층 · 사본 같은 값)
pill = f"{refs.ref_id({**MINE, '키': sc})}#0"
with store.at(world["personal"]):
top = labor.clean(
{"열": "rv", "담은로직": [{"참조": {**MINE, "키": sc}, "수량": {"손": 1}}]}
)
live = labor.calc(top, {pill: 2})["줄"][0]
area = {"시트": [{"id": "s", "칸": {"A1": {"값": 2}}, "연결": [{"칸": "A1", "키": pill}]}]}
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")}
bare = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, None)["줄"][0]
assert bare["기준"]["계"] == pytest.approx(
live["기준"]["계"], rel=1e-9
) # 이은 값 없음 = 저장 2
got = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, area)["줄"][0]
assert not got["까닭"] and not live["까닭"]
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"]): # 740-1 — SK 고쳐도 SC 는 펼친 사본이라 원본판 그대로
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) == []