- 까닭: SK 셈이 바탕 로직 재료 줄에 일위대가 기본(별도계상 · 162-1)을 써 drop_apart 가 줄 · 재료비에서 뺌 - M02_Sangeun_Calc.resolve · run 에 line 인자 — SK = 바탕 재료 별도계상 빈 칸을 직접계산으로(고른 「별도계상」 만 밖) - 일위대가 덮개 줄(Labor_Patch.run · Item_Sangeun._freeze_lines)은 line=True — 일위대가 기본 그대로 - 테스트1(GC000211) 재료비 1,404.81 → 36,525.06 · 계 = M01 124,419.85 · 기존 SK 17 중 GC000211 바탕 3 만 바뀜 - 새 지킴 시험 test_747_1_sk_material Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
163 lines
7.5 KiB
Python
163 lines
7.5 KiB
Python
"""프로젝트 사본 — M02 단가산출(SK) 풀어 굳힘(PLAN 719-1 · 요구서 내역12 원칙 3 · 4장 14).
|
|
|
|
사본을 만들 때(구조물 사본 `M02_Item_Source.build_source` · B09 서고 사본 `Bill_Library`) 굳힌
|
|
참조사본 안 SK 문서마다 고친 줄(`M02_Sangeun_Calc.resolve` = 바탕 사슬 + 덮개 · 더한줄 · 뺀줄)을
|
|
풀어 사본 `단가.json` 로직 줄 하나로 굳힘 — 사본 SK 문서는 그 줄만 바탕으로 가리킴(덮개 0 ·
|
|
연결 끊김). 셈은 그대로 `M02_Sangeun_Calc` 한 벌(사본 색인 = 사본 `단가.json`).
|
|
|
|
굳힌 키 = SK 참조 이름표(`ref_id` = 층 · 주인 · 키)에서 정해지는 `GX9#####`(해시 · 겹치면
|
|
이름표 차례로 다음 번호) — 같은 SK = 같은 굳힌 키([업데이트] 때 B09 덮개 cover · cid · 입력값 ·
|
|
고른자재가 짝을 안 잃음). 원본 층 문서는 읽기만.
|
|
사본 SK 문서 = {종류 · 키 · 이름 · 단위 · 구분 · 상세구분, 바탕 {로직: 굳힌 키}, 입력값(사슬 합),
|
|
열린입력, 켜쌓기율, 고름 · 자재표시(굳힌 줄 cid), 덮개 {}, 더한줄 [], 뺀줄 [],
|
|
굳힌줄(= 단가.json 줄), 원본(원래 SK 문서 · 「M02 에서 고침」 근거), 정체(굳힌 줄 차례 → 원본
|
|
cid · 더한줄 = `add:#k`), 바탕판, 짝잃음}.
|
|
단가산출 속 일위대가(SC) · SK 더한줄 = 참조 줄 그대로 → 사본 셈이 굳힌 참조사본으로 펼침.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
from typing import Any
|
|
|
|
from M02_MasterTemplete import M02_Labor_Ref as refs
|
|
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
|
|
|
#: 사본 `단가.json` 안 굳힌 SK 줄 파일(`master_formula.Master` 가 그룹 「로직」 으로 읽음)
|
|
FILE = "로직_단가산출.json"
|
|
ROW = "굳힌줄"
|
|
_HEAD, _LOW, _SPAN = "GX", 900000, 99999 # GX900000 ~ GX999998(999999 = M01 저장 전 초안)
|
|
|
|
|
|
def frozen_key(rid: str, taken: set[str]) -> str:
|
|
"""SK 이름표 → 굳힌 로직 키 — 해시 자리 · 이미 쓴 키면 다음 번호(제자리 `taken` 에 더함)."""
|
|
at = int(hashlib.sha256(rid.encode("utf-8")).hexdigest(), 16) % _SPAN
|
|
while (key := f"{_HEAD}{_LOW + at:06d}") in taken:
|
|
at = (at + 1) % _SPAN
|
|
taken.add(key)
|
|
return key
|
|
|
|
|
|
def is_sangeun(doc: Any) -> bool:
|
|
return isinstance(doc, dict) and doc.get("종류") == sd.TITLE
|
|
|
|
|
|
def freeze(copies: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
|
"""참조사본 `{ref_id: 문서}` → SK 를 풀어 굳힌 참조사본(새 묶음 · 받은 것 안 고침) ·
|
|
못 푼 SK(없는 바탕 · 순환) = 그대로(사본 셈이 까닭을 냄)."""
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_Item_Prices as prices
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
|
|
|
sks = sorted(rid for rid, doc in copies.items() if is_sangeun(doc))
|
|
if not sks and not any(_patched(doc) for doc in copies.values()):
|
|
return copies
|
|
whole = m01.loaded()[1]
|
|
|
|
def read(ref: dict) -> dict | None:
|
|
return copies.get(refs.ref_id(ref))
|
|
|
|
out, taken = dict(copies), set()
|
|
for rid in sks:
|
|
doc = copies[rid]
|
|
try:
|
|
got = calc.resolve(doc, whole, read, (rid,))
|
|
except sd.DocError:
|
|
continue
|
|
key = frozen_key(rid, taken)
|
|
row = prices.to_json({**got["줄"], "키": key})
|
|
ids = sd.line_ids(row)
|
|
frozen = {k: doc.get(k) for k in ("종류", "키", "이름", "단위", "구분", "상세구분")}
|
|
frozen |= {
|
|
"바탕": {"로직": key},
|
|
"입력값": prices.to_json(got["입력값"]),
|
|
"열린입력": list(doc.get("열린입력") or []),
|
|
"고름": {ids[int(n)]: mt for n, mt in got["고름"].items()},
|
|
"자재표시": {ids[int(n)]: one for n, one in got["자재표시"].items()},
|
|
"덮개": {},
|
|
"더한줄": [],
|
|
"뺀줄": [],
|
|
ROW: row,
|
|
"원본": doc,
|
|
"정체": list(got["정체"]), # 굳힌 줄 차례 → 원본 cid(B09 덮개 · 내 자료집 · 719-2)
|
|
"바탕판": got["바탕판"],
|
|
"짝잃음": got["짝잃음"],
|
|
}
|
|
if got["켜쌓기율"] is not None:
|
|
frozen["켜쌓기율"] = prices.to_json(got["켜쌓기율"])
|
|
out[rid] = frozen
|
|
for rid in sorted(rid for rid, doc in copies.items() if _patched(doc)):
|
|
out[rid] = _freeze_lines(rid, copies[rid], whole, read, taken)
|
|
return out
|
|
|
|
|
|
def _patched(doc: Any) -> list[int]:
|
|
"""일위대가(SC) 사본의 덮개 줄(로직 줄 + `패치` · 740-1 SK 넣기 꼴) 차례."""
|
|
if not isinstance(doc, dict) or doc.get("종류") != refs.TITLE["precombo"]:
|
|
return []
|
|
lines = doc.get("담은로직") or []
|
|
return [
|
|
n for n, x in enumerate(lines) if isinstance(x, dict) and x.get("로직") and x.get("패치")
|
|
]
|
|
|
|
|
|
def _freeze_lines(rid: str, doc: dict, whole: Any, read: Any, taken: set[str]) -> dict:
|
|
"""SC 사본 덮개 줄마다 고친 줄을 풀어 굳힘(740-1 · B09 대가 · 사본 셈이 덮개를 읽게) —
|
|
줄은 그대로 두고 덧칸 `굳힌 {키, 줄, 입력값, 켜쌓기율, 고름, 자재표시, 정체}`(고름 ·
|
|
자재표시 = 굳힌 줄 차례) · 못 푼 줄 = 그대로(사본 셈이 옛 길)."""
|
|
from M02_MasterTemplete import M02_Item_Prices as prices
|
|
from M02_MasterTemplete import M02_Labor_Patch as patch
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
|
from M02_MasterTemplete.M02_Labor_Expand import Expand
|
|
|
|
lines = list(doc.get("담은로직") or [])
|
|
for n in _patched(doc):
|
|
one = lines[n]
|
|
try:
|
|
doc_ = sd.clean(patch.as_doc(one, whole), None, whole)
|
|
got = calc.resolve(doc_, whole, read, (rid,), line=True) # 일위대가 기본 별도계상
|
|
row = {**got["줄"], "호표": [dict(x) for x in got["줄"]["호표"]]}
|
|
calc._bundles(row, whole, Expand(whole, read), (rid,)) # 고정 더한줄 = 값묶음
|
|
except sd.DocError:
|
|
continue
|
|
key = frozen_key(f"{rid}#{n}", taken)
|
|
fz = {"키": key, "줄": prices.to_json({**row, "키": key}), "정체": list(got["정체"])}
|
|
fz |= {
|
|
"입력값": prices.to_json(got["입력값"]),
|
|
"고름": got["고름"],
|
|
"자재표시": got["자재표시"],
|
|
}
|
|
if got["켜쌓기율"] is not None:
|
|
fz["켜쌓기율"] = prices.to_json(got["켜쌓기율"])
|
|
lines[n] = {**one, FROZEN: fz}
|
|
return {**doc, "담은로직": lines}
|
|
|
|
|
|
FROZEN = "굳힌"
|
|
|
|
|
|
def rows(copies: dict[str, Any]) -> list[dict[str, Any]]:
|
|
"""굳힌 SK 줄 + SC 덮개 줄 굳힌 줄(740-1) — 사본 `단가.json` `FILE` 에 넣음(이름표 차례)."""
|
|
out = [doc[ROW] for _, doc in sorted(copies.items()) if is_sangeun(doc) and ROW in doc]
|
|
for _, doc in sorted(copies.items()):
|
|
out += [
|
|
x[FROZEN]["줄"]
|
|
for x in doc.get("담은로직") or []
|
|
if isinstance(x, dict) and x.get(FROZEN)
|
|
]
|
|
return out
|
|
|
|
|
|
def materials(copies: dict[str, Any]) -> list[str]:
|
|
"""굳힌 SK 고른 자재 키 — 단가 묶음에 담음."""
|
|
out = [
|
|
str(mt)
|
|
for doc in copies.values()
|
|
if is_sangeun(doc)
|
|
for mt in (doc.get("고름") or {}).values()
|
|
]
|
|
for doc in copies.values():
|
|
lines = [x for x in doc.get("담은로직") or [] if isinstance(x, dict) and x.get(FROZEN)]
|
|
out += [str(mt) for x in lines for mt in (x[FROZEN].get("고름") or {}).values()]
|
|
return out
|