"""일위대가 · 사전조합 줄 덮개(PLAN 715-3 · 요구서 내역12 4장 16 · 4-1 확정 5). 줄 칸 `패치` = 단가산출(SK) 문서 꼴 `{덮개, 뺀줄, 더한줄}`(한 벌 · `M02_Sangeun_Doc`) — 그 줄에만 · 로직 줄(`{로직, 입력값, 고름, 자재표시, 패치}`) · 단가산출 참조 줄(`{참조: SK, 패치}`)만 받음 · 줄 `고름` · `자재표시`(호표 차례 열쇠)는 바탕 줄 차례 그대로 — 셈 때 cid 로 바꿔 뺀줄 뒤에도 제자리. 더한줄은 인력 · 재료 · 기계 · 로직 · 고정만(참조는 일위대가에 참조 줄로). 셈 = 줄을 이름 없는 SK 문서로(`as_doc`) → `M02_Sangeun_Calc` 한 벌 · 로직 줄 기준 = 결과단위 값(배는 알약 · `settle` 그대로) · 참조 줄 = 1 단위 값. [단가산출로 저장] = `as_doc` + 이름 · 단위 → 층 SK. """ from __future__ import annotations from typing import Any from M02_MasterTemplete import M02_Sangeun_Doc as sd FIELD = "패치" KEEP = ("덮개", "뺀줄", "더한줄") TEMP = "SK-000000" # 이름 없는 줄 단가산출 — 저장 안 함 def _bad(text: str): from M02_MasterTemplete import M02_MasterTemplete_Store as store return store.StoreError(400, text) def clean(one: dict, where: str, whole: Any, row: dict | None, opened: list | None = None) -> dict: """줄 칸 `패치` 정리 — 없거나 비면 `{}`(칸 안 둠) · 틀리면 400.""" raw = one.get(FIELD) if not raw: return {} if not isinstance(raw, dict): raise _bad(f"{where} · 패치는 묶음") if any("참조" in x for x in raw.get("더한줄") or [] if isinstance(x, dict)): raise _bad(f"{where} · 줄 더한줄엔 참조 못 넣음 — 일위대가에 참조 줄로") body = {k: raw.get(k) for k in KEEP} try: got = sd.clean( {"키": TEMP, "이름": "줄", "단위": "식", **body, "열린입력": opened or []}, row, whole ) except sd.DocError as e: raise _bad(f"{where} · {e}") from e out = {k: got[k] for k in KEEP if got[k]} return {FIELD: out} if out else {} def ref_clean(one: dict, ref: dict, where: str) -> dict: """단가산출 참조 줄 패치 — 그 SK 의 열린입력이면 품 배율만.""" from M02_MasterTemplete import M02_Labor_Ref as refs if not one.get(FIELD): return {} if refs.kind_of(ref["키"]) != sd.KIND: raise _bad(f"{where} · 줄 덮개는 로직 줄 · 단가산출 참조 줄만") from M01_MasterData import M01_MasterData_Store as m01 doc = refs.read_ref(ref) or {} return clean(one, where, m01.loaded()[1], None, doc.get("열린입력")) def _keyed(value: Any, ids: list[str]) -> dict: """차례 열쇠 → cid 열쇠(바탕 줄 차례).""" out = {} for n, v in (value or {}).items() if isinstance(value, dict) else (): at = int(n) if str(n).isdigit() else -1 if 0 <= at < len(ids): out[ids[at]] = v return out def as_doc(one: dict, whole: Any, name: str = "줄", unit: str = "식") -> dict[str, Any]: """일위대가 줄 → SK 문서(바탕 = 그 로직 · 그 SK) — 고름 · 자재표시는 cid 로.""" body = {k: (one.get(FIELD) or {}).get(k) or ([] if k != "덮개" else {}) for k in KEEP} head = {"키": TEMP, "이름": name, "단위": unit, **body} if "참조" in one: return {**head, "바탕": {"참조": one["참조"]}} try: ids = sd.line_ids(whole.logic(one["로직"])) except Exception: # noqa: BLE001 — 없는 로직 = 셈이 까닭 ids = [] extra = {"켜쌓기율": one["켜쌓기율"]} if one.get("켜쌓기율") is not None else {} return { **head, **extra, "바탕": {"로직": one["로직"]}, "입력값": dict(one.get("입력값") or {}), "고름": _keyed(one.get("고름"), ids), "자재표시": _keyed(one.get("자재표시"), ids), } def run(whole: Any, one: dict, read: Any = None) -> dict[str, Any]: """패치 있는 로직 줄 기준(`run_line` 꼴 · 별도계상 뺀 뒤 · 기준 = 결과단위 값).""" from M02_MasterTemplete import M02_Labor_Ref as refs from M02_MasterTemplete import M02_Sangeun_Calc as calc try: doc = sd.clean(as_doc(one, whole), None, whole) return calc.run(doc, whole, None, read or refs.read_ref) except sd.DocError as e: return {"기준": None, "까닭": str(e)} def ref(one: dict, expand: Any) -> dict[str, Any]: """참조 줄 펼침 — 패치 있는 SK 참조는 그 줄만 다시 셈(캐시 안 씀) · 그 밖 `expand` 그대로.""" from M02_MasterTemplete import M02_Sangeun_Calc as calc if not one.get(FIELD): return expand(one["참조"]) doc = as_doc(one, expand.whole) base = expand.read(one["참조"]) or {} doc = {**doc, "이름": base.get("이름") or doc["이름"], "단위": base.get("단위") or doc["단위"]} return calc.unit(doc, expand.whole, expand, ()) def to_sangeun(one: dict, name: str, unit: str, whole: Any) -> dict[str, Any]: """[단가산출로 저장] 문서 — 줄의 바탕 · 입력값 · 고름 · 덮개 → 이름 붙인 SK(키는 서버).""" if "로직" not in one and "참조" not in one: raise _bad("로직 줄 · 단가산출 참조 줄만 단가산출로 저장") doc = as_doc(one, whole, name, unit) doc.pop("키") return doc