Files
Aislo/M02_MasterTemplete/M02_Sangeun_Calc.py
T
eomsangdonandClaude Opus 5.5 08ad680188 feat(728-2): SK 단위 환산 — 기초 기준단위 제안 · 바꾸면 배 · 계수 칸 · 둘째 기초 수량 처음값 · 돈 아닌 기초 뺌
- 서버 Sangeun_Calc: 배 = 바탕 기준단위 → SK 단위(master_unit.swap) · 성격 다름 = 문서 환산 {계수 | 단위중량} · 없으면 셈 · 저장 막음(까닭 글) · 「식」 · 빈 단위 = 옛 셈 그대로(일위대가 줄 모드)
- 화면 셈 답 줄 · 계 = SK 1 단위 몫(per_unit · 화면배) · 머리 띠 「100본 당」 · 「1 본 당」
- 서버 Sangeun_Doc: 환산 칸 검사(계수 양수 · 단위중량 종별 · 형상)
- B09 Labor_Source: SK 참조 배 = 같은 ratio_of(옛 바탕 앞 수 대신 · 기존 문서 같은 값)
- 화면 Sangeun_Detail: SK 단위 처음 값 = 기초 기준단위(「100본」) · 둘째 기초 수량 처음값 = 바탕 기준단위 ÷ 그 기초 기준단위(swapUnit) · 계 칸 원/SK 단위
- 새 Sangeun_Unit: 근거 글 · 환산 계수 칸 · 단위중량 표(CC000337) 고르기
- Cover_Model: 화면 품 × 화면배 = 문서 품(덮개 · 더한줄 · 세부) · 율 그대로
- Find_Pick: 돈 아닌 기초(결과단위 「원」 아님 21 개) 목록에서 뺌
- 낡은 시험 test_715_sangeun_engine(바탕 원/주 + SK ㎡) · test_727(바탕 원/㎡ + SK ㎥) = 성격 다른 단위가 이제 계수 없으면 막혀 SK 단위를 바탕 단위로 고침(금액 같음)
- 새 시험 test_728_2_sk_unit(100본 → 본 1,538.89 · ㎡ 44,791.32 · 계수 · 단위중량 · 식 · 둘째 기초 · 돈 아닌 기초 · 고침 꼴)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:52:05 +09:00

500 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""M02 단가산출(SK) 셈 — 바탕 사슬 → 고친 줄 → M01 엔진 한 벌(PLAN 715-1b · 715-2).
셈 = ① 바탕(M01 로직 · 바탕 SK 의 고친 줄 · 없음) ② `M02_Sangeun_Doc.patched` ③ 더한줄 참조 ·
(SK · SC) · 고정 줄 = 먼저 셈한 1 단위 노 · 재 · 경 → `값묶음`
④ M01 색인 위에 고친 줄을 SK 키로 얹어
(M01 시험 계산 `_swap` 과 같은 꼴 · 메모리만 · 정본 · 캐시 안 고침) `labor.run_line` — 자동값 ·
켜쌓기율 · 고름 · % 덧줄을 엔진이 다시 셈. 1 단위 = 기준 ÷ 배(바탕 로직 결과단위 앞 수).
계산 자리(CLAUDE.md 5장 ③) = 서버 단독 · 화면은 셈 길(저장 안 함 · 최신 응답만).
저장(`clean_doc`) = 꼴 검사 + 참조 위 층 · 있는 문서 · 순환 422 + 서버가 `원값` · `바탕판` 적음.
둘째 기초(727-2 · 더한줄 로직) = 세부 덮개 · 뺀줄 있으면 고친 기초 줄을 엔진으로 셈한 `값묶음` 줄
(B09 굳힘도 그 값) · 화면 셈 길(`calc_doc`) = 그 줄 덧칸 `펼침`(세부 줄 × 수량) · `뺀세부` ·
`바탕판` ·
`꼬리표`(원문번호) · 답 `꼬리표` = 바탕 원문번호.
SK 단위 환산(728-2) = 배 = 바탕 기준단위(「100본」) → SK 단위(`master_unit.swap` ·
「식」 · 빈 단위 = 앞 수 뗀 바탕 단위 = 옛 셈) ·
다른 성격 = 문서 `환산` {계수 | 단위중량 {종별, 형상}} · 없으면 셈 막음(까닭) ·
화면 셈 길 줄 · 계 = SK 1 단위 몫(`per_unit`).
"""
from __future__ import annotations
import hashlib
import json
import re
from decimal import Decimal
from typing import Any
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Fixed as fixed
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_Sangeun_Doc as sd
COSTS = labor.COSTS
Ref = dict[str, Any]
class _Over:
"""M01 색인 위에 고친 줄 하나 — `logic(키)` 만 가로챔(그 밖 그대로 · 색인 안 고침)."""
def __init__(self, whole: Any, key: str, row: dict) -> None:
self._whole, self._key, self._row = whole, key, row
def logic(self, ref: str) -> dict:
return self._row if ref == self._key else self._whole.logic(ref)
def __getattr__(self, name: str) -> Any:
return getattr(self._whole, name)
def _fail(e: sd.DocError) -> store.StoreError:
return store.StoreError(e.status, str(e))
def times_of(row: dict) -> Decimal:
"""결과단위 분모 앞 수(원/10㎡ → 10) — 없으면 1."""
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
per = combo_store.mu.per(row.get("결과단위")) # 712-2 단위 한 벌
return per[0][0] if len(per) == 1 else Decimal(1)
def base_unit(row: dict) -> tuple[str, str] | None:
"""바탕 기준단위 — 「원/100본」 → (「100본」, 「본」) · 분모 없거나 여럿 = None."""
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
per = combo_store.mu.per(row.get("결과단위"))
if len(per) != 1:
return None
n, u = per[0]
return (u if n == 1 else f"{format(n, 'f')}{u}", u)
def ratio_of(doc: dict, row: dict, whole: Any) -> Decimal:
"""SK 배(728-2) — 바탕 기준단위 1 개 = SK 단위 몇 개 · 1 단위 = 기준 ÷ 배 ·
못 바꿈 = `DocError`."""
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
mu = combo_store.mu
got = base_unit(row)
if got is None:
return Decimal(1)
want = str(doc.get("단위") or "").strip()
want = (
got[1] if want in ("", "식") else want
) # 일위대가 줄 · 「식」 = 옛 셈(앞 수 뗀 바탕 단위)
how = doc.get("환산") or {}
pick = how.get("단위중량") or {}
rho = mu.unit_weight(whole, pick.get("종별"), pick.get("형상")) if pick else None
swap = mu.swap(got[0], want, rho, how.get("계수"))
if swap["배"] is None:
raise sd.DocError(f"단가산출 단위 · {swap['근거']}")
return swap["배"]
def fingerprint(row: dict) -> str:
raw = json.dumps(row, ensure_ascii=False, sort_keys=True, default=str)
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
# ── 바탕 사슬 → 고친 줄 ─────────────────────────────────────────────────
def _base_of(doc: dict, whole: Any, read: Any, trail: tuple[str, ...]) -> dict[str, Any]:
"""이 SK 가 얹힐 바탕 — {줄, 고름, 자재표시, 입력값, 켜쌓기율, 짝잃음}."""
base = doc.get("바탕")
empty = {"고름": {}, "자재표시": {}, "입력값": {}, "켜쌓기율": None, "짝잃음": []}
if not base:
return {**empty, "줄": sd.empty_row(doc)}
if "로직" in base:
try:
return {**empty, "줄": whole.logic(base["로직"])}
except Exception as e: # noqa: BLE001 — 엔진 FormulaError
raise sd.DocError(f"바탕 로직 · {e}") from e
ref = base["참조"]
rid = refs.ref_id(ref)
if rid in trail:
keys = [t.rsplit("|", 1)[-1] for t in (*trail, rid)]
raise sd.DocError("순환 — " + " → ".join(keys), 422)
if len(trail) >= refs.DEPTH:
raise sd.DocError(f"바탕 사슬이 {refs.DEPTH}단을 넘음", 422)
parent = read(ref)
if not isinstance(parent, dict):
raise sd.DocError(f"없는 바탕 「{ref.get('키')}」")
up = resolve(parent, whole, read, (*trail, rid))
lost = [{**x, "바탕": ref.get("키")} for x in up["짝잃음"]]
return {**up, "짝잃음": lost}
def resolve(
doc: dict, whole: Any, read: Any = refs.read_ref, trail: tuple = (), given: dict | None = None
) -> dict[str, Any]:
"""SK 문서 → 고친 줄 `{줄, 고름, 자재표시, 정체, 짝잃음, 입력값, 켜쌓기율, 배, 바탕판}` ·
`trail` = 지나온 참조 이름표(순환 · 깊이 422) · `given` = 공통 옵션(더한줄 로직 입력)."""
base = _base_of(doc, whole, read, trail)
common = {**base["입력값"], **(doc.get("입력값") or {}), **(given or {})}
doc, lack = _fill(doc, whole, common)
got = sd.patched(base["줄"], doc, base["고름"], base["자재표시"])
items = got["줄"]["호표"]
for at, cid in enumerate(got["정체"][: len(items)]):
hit = re.match(r"^add:#(\d+)$", cid)
if hit and int(hit[1]) in lack:
items[at] = _gap(items[at], lack[int(hit[1])])
elif hit and sd.sub_patched(doc["더한줄"][int(hit[1])]): # 고친 둘째 기초 = 셈한 값묶음
sub = sub_run(doc["더한줄"][int(hit[1])], whole)
got["짝잃음"] += [{**x, "cid": f"{cid}/{x['cid']}"} for x in sub["짝잃음"]]
items[at] = _bundled(items[at], sub)
rate = doc.get("켜쌓기율", base["켜쌓기율"])
return {
**got,
"채운더한줄": doc["더한줄"],
"짝잃음": base["짝잃음"] + got["짝잃음"],
"입력값": {**base["입력값"], **(doc.get("입력값") or {})},
"켜쌓기율": rate,
"배": ratio_of(doc, base["줄"], whole),
"바탕판": fingerprint(base["줄"]),
}
# ── 못 셈 줄 = 그 줄만 빠짐(725-1 · 725-2) ─────────────────────────────────
NO_QTY = "수량 없음" # `M02_Labor_Expand.NO_QTY` 와 같은 글(가져오기 둘레 피함)
NO_QTY_WHY = "수량 없음 — 쓰는 자리에서 연결"
def _defaults(whole: Any, code: str) -> dict:
"""로직 기본값 = M01 견본 입력(`/m01/logic/auto` 와 같은 길 · 흐름 예시값 › 옛 자동값)."""
from M01_MasterData import M01_MasterData_Store as m01
from M01_MasterData import M01_MasterData_Store_Flow as flow
try:
got = flow.auto(code) or {"값": m01.mau.values(whole, whole.logic(code))}
except Exception: # noqa: BLE001 — 없는 로직 · 자동값 못 셈 = 기본값 없음
return {}
return got.get("값") or {}
def _fill(doc: dict, whole: Any, common: dict) -> tuple[dict, dict[int, str]]:
"""더한줄 로직 입력 = 줄 값 › 공통 옵션 › 로직 기본값 — (채운 사본, {더한줄 차례: 까닭})."""
adds, lack = [], {}
for k, one in enumerate(doc.get("더한줄") or []):
if one.get("종류") != "로직":
adds.append(one)
continue
try:
names = [str(s.get("이름")) for s in whole.logic(one["로직"]).get("입력") or []]
except Exception: # noqa: BLE001 — 없는 로직 = 검사(`sd.clean`)가 먼저 막음
names = []
mine = dict(one.get("입력값") or {})
blank = [n for n in names if mine.get(n) is None or not str(mine[n]).strip()]
auto = _defaults(whole, one["로직"]) if blank else {}
for n in blank:
mine.pop(n, None)
for v in (common.get(n), auto.get(n)):
if v is not None and str(v).strip():
mine[n] = v
break
miss = [n for n in names if n not in mine]
if miss:
lack[k] = f"입력 「{miss[0]}」 없음"
adds.append({**one, "입력값": mine})
return {**doc, "더한줄": adds}, lack
def _gap(item: dict, why: str) -> dict:
"""못 셈 줄 → 0 값묶음 줄(차례 그대로 · 계에 안 듦) + `빠짐` 까닭."""
zero = dict.fromkeys(COSTS, Decimal(0))
head = {k: item.get(k) or "" for k in ("이름", "단위")}
return {**head, "종류": "로직", "요소": None, "수량": item["수량"], "값묶음": zero, "빠짐": why}
# ── 둘째 기초 세부(727-2) ─────────────────────────────────────────────────
def sub_run(one: dict, whole: Any, patched: bool = True) -> dict[str, Any]:
"""더한줄 로직 하나 = 그 기초 줄(+ 세부 덮개 · 뺀줄)을 엔진으로 — `run_line` 답 + 정체 ·
짝잃음 ·
바탕판(덮기 전 기초 지문) · 입력 = 채운 줄 값(`_fill`). `patched` False = 덮기 전."""
code = str(one["로직"])
try:
row = whole.logic(code)
except Exception as e: # noqa: BLE001 — 엔진 FormulaError
return {"기준": None, "까닭": str(e), "정체": [], "짝잃음": [], "바탕판": ""}
got = sd.patched(row, one if patched else {})
over = _Over(whole, code, {**got["줄"], "키": code})
out = labor.run_line(over, code, one.get("입력값"), None, got["고름"] or None)
return {**out, "정체": got["정체"], "짝잃음": got["짝잃음"], "바탕판": fingerprint(row)}
def _bundled(item: dict, sub: dict) -> dict:
"""고친 둘째 기초 줄 → 셈한 1 기준단위 노 · 재 · 경 `값묶음` 줄(수량 그대로) ·
못 셈 = `_gap`."""
if not sub.get("기준"):
return _gap(item, str(sub.get("까닭") or "돈 아닌 로직"))
head = {k: item.get(k) or "" for k in ("이름", "단위")}
bundle = {k: Decimal(str(sub["기준"][k])) for k in COSTS}
return {**head, "종류": "로직", "요소": None, "수량": item["수량"], "값묶음": bundle}
def tag_of(whole: Any, code: str) -> str:
"""꼬리표 = 기초 원문번호(「9-12-1」) · 없으면 키."""
try:
return str(whole.logic(code).get("원문번호") or code)
except Exception: # noqa: BLE001
return code
def _detail(one: dict, at: int, whole: Any) -> dict[str, Any]:
"""둘째 기초 세부 줄(화면) — 수량 · 금액 · 비목 = 세부 × 더한줄 수량 · cid `add:#k/세부 cid` ·
`바탕값` = 덮기 전 1 기준단위 품 · 단가 · `뺀세부` = 뺀 줄 이름."""
sub = sub_run(one, whole)
plain = sub_run(one, whole, False) if sd.sub_patched(one) else sub
was = dict(zip(plain["정체"], plain.get("줄") or []))
covers, times = one.get("덮개") or {}, Decimal(str(one.get("수량") or 0))
rows = []
for cid, line in zip(sub["정체"], sub.get("줄") or []):
mine = covers.get(cid) or {}
scaled = {
k: None if line.get(k) is None else Decimal(str(line[k])) * times
for k in ("수량", "금액")
}
parts = {
k: None if v is None else Decimal(str(v)) * times
for k, v in (line.get("비목") or {}).items()
}
rows.append(
{
**line,
**scaled,
"비목": parts,
"cid": f"add:#{at}/{cid}",
"바탕값": _was(was[cid]) if cid in was else None,
**({"덮개": [k for k in sd.COVER_KEYS if k in mine]} if mine else {}),
**({"원값": mine["원값"]} if "원값" in mine else {}),
}
)
gone = [
{"cid": f"add:#{at}/{c}", "이름": was[c].get("이름") or ""}
for c in one.get("뺀줄") or []
if c in was
]
tag = tag_of(whole, str(one["로직"]))
return {"펼침": rows, "뺀세부": gone, "바탕판": sub["바탕판"], "꼬리표": tag}
def _bundles(row: dict, whole: Any, expand: Any, trail: tuple) -> None:
"""더한줄 참조 · 고정 줄 `값묶음` = 1 단위 노 · 재 · 경 — 못 셈 줄 = `_gap`(나머지 셈 계속)."""
for at, item in enumerate(row["호표"]):
if item.get("값묶음") is not None or not ("참조" in item or "고정" in item):
continue
sub = expand(item["참조"], trail) if "참조" in item else fixed.line(whole, item["고정"])
item["이름"] = item.get("이름") or sub.get("이름") or ""
item["단위"] = item.get("단위") or sub.get("단위") or ""
if sub.get("기준") is None:
why = str(sub.get("까닭") or "")
row["호표"][at] = _gap(item, NO_QTY_WHY if NO_QTY in why else why)
continue
item["값묶음"] = {k: Decimal(str(sub["기준"][k])) for k in COSTS}
def run(
doc: dict,
whole: Any,
given: dict | None = None,
read: Any = refs.read_ref,
expand: Any = None,
trail: tuple = (),
detail: bool = False,
) -> dict[str, Any]:
"""SK 셈(저장 안 함) — `run_line` 답 + `{1단위, 배, 정체, 짝잃음, 바탕판}` · 줄마다 `cid` ·
고친 칸 `덮개`(칸 이름) · `원값`. 틀린 문서 · 순환 = `sd.DocError` ·
`detail` = 둘째 기초 세부 펼침(`_detail` · 화면 셈 길만)."""
from M02_MasterTemplete.M02_Labor_Expand import Expand
got = resolve(doc, whole, read, trail, given)
row = {**got["줄"], "호표": [dict(x) for x in got["줄"]["호표"]]}
_bundles(row, whole, expand or Expand(whole, read), trail)
head = {k: got[k] for k in ("정체", "짝잃음", "바탕판")} | {"배": got["배"]}
gaps = {at: x for at, x in enumerate(row["호표"]) if x.get("빠짐")}
key = str(doc.get("키") or "SK-000000")
over = _Over(whole, key, {**row, "키": key})
# 이 SK 줄의 입력만 — 더한줄 로직 입력은 `_fill` 이 따로 채움
names = {str(i.get("이름")) for i in row.get("입력") or []}
values = {k: v for k, v in {**got["입력값"], **(given or {})}.items() if k in names}
out = labor.run_line(over, key, values, got["켜쌓기율"], got["고름"] or None)
covers = doc.get("덮개") or {}
lines = []
for at, (cid, line) in enumerate(zip(got["정체"], out.get("줄") or [])):
mine = covers.get(cid) or {}
mark = {"덮개": [k for k in sd.COVER_KEYS if k in mine]} if mine else {}
if at in gaps: # 못 셈 줄 = 금액 — · 까닭(725)
mark |= {"단가": None, "금액": None, "비목": {}, "까닭": gaps[at]["빠짐"]}
hit = re.match(r"^add:#(\d+)$", cid)
one = got["채운더한줄"][int(hit[1])] if hit else {}
if detail and one.get("종류") == "로직" and at not in gaps:
mark |= _detail(one, int(hit[1]), whole)
lines.append(
{**line, "cid": cid, **mark, **({"원값": mine["원값"]} if "원값" in mine else {})}
)
out = labor.drop_apart({**out, "줄": lines}, got["자재표시"])
base = out.get("기준")
unit = {k: Decimal(str(v)) / got["배"] for k, v in base.items()} if base else None
gone = [
{"cid": got["정체"][at], "이름": x.get("이름") or "", "까닭": x["빠짐"]}
for at, x in gaps.items()
]
base = doc.get("바탕") or {}
tag = tag_of(whole, base["로직"]) if "로직" in base else (base.get("참조") or {}).get("키")
return {**out, **head, "1단위": unit, "빠진줄": gone, "꼬리표": tag}
def unit(doc: dict, whole: Any, expand: Any, trail: tuple) -> dict[str, Any]:
"""펼침(`Expand`) 한 칸 — `{이름, 단위, 기준(1 단위), 까닭, 줄, 짝잃음}` · 못 셈 = 까닭."""
head = {"이름": doc.get("이름"), "단위": doc.get("단위")}
try:
got = run(doc, whole, None, expand.read, expand, trail)
except sd.DocError as e:
return {**head, "기준": None, "까닭": str(e)}
gone = got.get("빠진줄") or [] # 쓰는 자리 = 빠진 줄 있으면 통째 기준 없음(부분 값 안 넘김)
why = got.get("까닭") or next((f"「{x['이름']}」 {x['까닭']}" for x in gone), None)
why = why or (None if got.get("1단위") else "돈 아닌 로직")
times = got["배"]
lines = [
{
**x,
"수량": None if x.get("수량") is None else Decimal(str(x["수량"])) / times,
"금액": None if x.get("금액") is None else Decimal(str(x["금액"])) / times,
}
for x in got.get("줄") or []
]
return {
**head,
"기준": None if gone else got["1단위"],
"까닭": why,
"줄": lines,
"짝잃음": got["짝잃음"],
}
def head_of(doc: dict) -> str:
"""머리 띠 — 「1 본 당」 · 앞 수 있는 단위 = 「100본 당」."""
unit = str(doc.get("단위") or "")
return f"{unit} 당" if unit[:1].isdigit() else f"1 {unit} 당"
def per_unit(got: dict, doc: dict) -> dict:
"""화면 셈 답 줄 · 계 = SK 1 단위 몫(728-2) — 수량 · 금액 · 비목 ÷배(율 · 단가 그대로) ·
「식」 · 빈 단위(일위대가 줄 모드) = 옛 바탕 몫 그대로."""
times = got.get("배") or Decimal(1)
if times == 1 or str(doc.get("단위") or "").strip() in ("", "식"):
return got
def cut(v: Any) -> Any:
return None if v is None else Decimal(str(v)) / times
def one(x: dict) -> dict:
out = {**x, "금액": cut(x.get("금액"))}
if "율" not in x:
out["수량"] = cut(x.get("수량"))
if x.get("비목"):
out["비목"] = {k: cut(v) for k, v in x["비목"].items()}
if x.get("펼침"):
out["펼침"] = [one(y) for y in x["펼침"]]
return out
lines = [one(x) for x in got.get("줄") or []]
goods = [one(x) for x in got.get("재료") or []]
return {**got, "줄": lines, "재료": goods, "기준": got.get("1단위"), "화면배": times}
# ── 저장 ──────────────────────────────────────────────────────────────
def _check_refs(doc: dict, here_: refs.Here) -> None:
me = {"층": here_[0], "주인": here_[1], "키": doc["키"]}
base = doc.get("바탕") or {}
if "참조" in base:
base["참조"] = refs.check_ref(base["참조"], "바탕", here_)
if refs.kind_of(base["참조"]["키"]) != sd.KIND:
raise store.StoreError(400, "바탕 참조는 단가산출(SK)만")
for at, one in enumerate(doc.get("더한줄") or [], 1):
if "참조" in one:
one["참조"] = refs.check_ref(one["참조"], f"더한줄 {at}째", here_)
for ref in refs.refs_in(doc):
if refs.ref_id(ref) == refs.ref_id(me):
raise store.StoreError(422, f"순환 — 자기 자신을 담음 「{doc['키']}」")
refs._no_loop(ref, [me, ref])
def clean_doc(doc: Any, here_: refs.Here) -> dict[str, Any]:
"""저장 꼴(`Labor_Ref.clean_doc` 가 부름) — 검사 · 참조 · 순환 · 서버가 `원값` · `바탕판`."""
from M01_MasterData import M01_MasterData_Store as m01
whole = m01.loaded()[1]
try:
rough = sd.clean(doc, None, whole)
_check_refs(rough, here_)
base = _base_of(rough, whole, refs.read_ref, ())
out = sd.clean(doc, base["줄"], whole)
out["바탕"], out["더한줄"] = rough["바탕"], rough["더한줄"]
if out["덮개"]:
bare = run({**out, "덮개": {}}, whole) # 덮기 전 값 = 원값(그때 M01)
was = {x["cid"]: x for x in bare.get("줄") or []}
for cid, one in out["덮개"].items():
if cid in was:
one["원값"] = _was(was[cid])
out["바탕판"] = fingerprint(base["줄"])
ratio_of(out, base["줄"], whole) # 단위 못 바꿈(계수 없음) = 저장 막음(728-2)
_stamp_adds(out, base, whole)
except sd.DocError as e:
raise _fail(e) from e
return out
def _stamp_adds(out: dict, base: dict, whole: Any) -> None:
"""둘째 기초 저장 칸(727-2) — `바탕판` = 그 기초 지문 · 세부 덮개 `원값` = 덮기 전
1 기준단위 값."""
filled, _ = _fill(out, whole, {**base["입력값"], **out["입력값"]})
for one, mine in zip(filled["더한줄"], out["더한줄"]):
if one.get("종류") != "로직":
continue
plain = sub_run(one, whole, False)
if plain["바탕판"]:
mine["바탕판"] = plain["바탕판"]
was = dict(zip(plain["정체"], plain.get("줄") or []))
for cid, cover in (mine.get("덮개") or {}).items():
if cid in was:
cover["원값"] = _was(was[cid])
def _was(line: dict) -> dict[str, Any]:
keep = {"qty": line.get("율", line.get("수량")), "단가": line.get("단가")}
return {k: sd.plain(Decimal(str(v))) for k, v in keep.items() if v is not None}
def calc_doc(doc: Any, given: dict | None = None) -> dict[str, Any]:
"""셈 길(`M02_Sangeun_Router`) — 지금 층 문서 · 저장 전 화면 문서 · 틀림 400 · 순환 422."""
from M01_MasterData import M01_MasterData_Store as m01
whole = m01.loaded()[1]
try:
clean = sd.clean(doc, None, whole)
_check_refs(clean, refs.here()) # 저장 전 문서도 위 층만 · 있는 문서 · 순환 422
me = refs.ref_id({"층": refs.here()[0], "주인": refs.here()[1], "키": clean["키"]})
got = run(clean, whole, given, trail=(me,), detail=True)
except sd.DocError as e:
raise _fail(e) from e
return labor._json({**per_unit(got, clean), "머리": head_of(clean)})
def from_line(line: Any, name: str, unit: str) -> dict[str, Any]:
"""[단가산출로 저장](715-3) — 일위대가 줄(바탕 · 입력값 · 고름 · 줄 덮개) → 지금 층 새 SK ·
답 = `{이름: 새 키, 줄: 그 SK 참조 줄}`(일위대가 문서는 화면 [저장] 때 바뀜)."""
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Labor_Patch as patch
if not isinstance(line, dict):
raise store.StoreError(400, "줄이 묶음 아님")
doc = patch.to_sangeun(line, name, unit, m01.loaded()[1])
made = refs.create(sd.KIND, doc)
at = refs.here()
keep = {k: line[k] for k in ("별칭", "메모", "수량") if k in line}
return {
"이름": made["이름"],
"줄": {"참조": {"층": at[0], "주인": at[1], "키": made["이름"]}, **keep},
}