- 새 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>
121 lines
6.0 KiB
Python
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) == []
|