- 문서 칸 `수식`(옛 `사본` 은 802-5 까지 옆에) = 값 사본(로직 · 자원 단가 줄) + 부름 닫힌 묶음 + SK 문서 · 끊음 · 따로 — `M02_Doc_Copy`(새)
· 부름 문서 = 값 줄이 지금 M01 줄과 같으면 M01 수식 문서 · 다르면 만든 때 줄 변환
- SK 금액 = 문서 셈(`M02_Doc_Sk.on_run`) — 옛 칸(덮개 · 더한줄 …)은 파생(엔진에 넣던 고친 줄 → 문서 · 덮개 없는 바탕 = M01 문서 사본) · 수식 길 고침 = 따로(옛 칸 고침 저장 409 · 재생성 = 파생으로)
- 일위대가 · SC 로직 줄 = 문서 셈(`M02_Labor_Copy.line`) · 줄 옵션 = 문서 규칙(켜쌓기율 변수 r · 고른 자재 = 요소 키 · 별도계상 = 합밖 + 뒤 「재료비의 %」 밑엔 그 줄 금액 더함)
- [단가 갱신] = 자원 단가 줄만(문서 그대로) · [재생성] = 지금 M01 문서로(고친 문서 · 끊음 버림 알림) · 빈 SK = 빈 문서 「—」
- 수식 길 GET/POST …/sangeun/{키}/doc · /preview · /save(층 쓰기 · 서버 정본 셈) — `M02_Doc_Sk_Path` · `M02_Doc_Router`(새 · main 등록)
- B09 구조물 · 서고 사본 = `수식.json` 함께 굳힘(`M02_Doc_B09` · 원본판 안 듦) · B09 셈 그대로(문서 셈 전환 = 802-6)
- 옮기기 `M02_Doc_Migrate`(보기만 · --apply --backup · --restore) · 목록 `docs/raw/요구_1104/_목록_802-4a_M02대조.xlsx` — 층 SK 15 · 일위대가 21 · SC 4 · B09 서고 사본 3 같음 · 빈 SK 2 = 옛 0 → 「—」 · 가져온 식 1 = 802-4b
- 시험 test_802_4a(12) · test_204_2 서고 사본 파일 목록에 수식.json · test_795_1 `_fake` 가 M01 수식 문서도 같이 바꿈(M01 정본 = 수식 문서 — 옛 줄만 바꾼 흉내는 문서 사본에 안 닿음) · 옛 SK 흉내 = 사본 · 수식 둘 다 뗌
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
438 lines
20 KiB
Python
438 lines
20 KiB
Python
"""M02 단가산출(SK) 숫자 넓힘(PLAN 790-2 · 요구서_652 · _분석_650-H3 E2) — 새 덮개 갈래 · 시트 답.
|
|
|
|
시험 `resources/tester/test_790_2_sangeun_const.py` · 799-2 — 화면 짝(`Const_Model.ts` · 시트 탭)은 걷음, 서버만 남음.
|
|
|
|
문서 덧칸(없으면 안 둠 — 옛 SK 그대로 · 지문 그대로):
|
|
상수덮개 {자리: {n: 값}} 자리 식의 n 번째 숫자 상수(0 부터 · 엔진 파서 차례 · 찾기 머리 · 키 ·
|
|
따옴표 글 안 셈) → 값 · 식 구조 그대로(옵션 바꾸면 나머지 따라감)
|
|
표값덮개 {자리: {n: 값}} 자리 식의 n 번째 표 찾기 값(`찾기(…).칸`) → 값 · 그 줄만 표 연결 끊김
|
|
글줄 [{자리, 글}] 셈 없는 글 줄(근거 · ※ 조건) · 자리 뒤에 찍힘("" = 맨 위)
|
|
까닭 {열쇠: 글} 덮개 · 더한줄 · 뺀줄 · 새 덮개마다(예정가격작성기준 제2조 ②)
|
|
M01판 {자리: 지문} 저장 때 서버가 바탕 줄마다 적음(`stamp` · `sd.clean` 은 버림 =
|
|
바탕판처럼) · 셈 때 받은 값과 지금 바탕이 다르면 「M01바뀜」
|
|
자리 = 바탕 줄 cid(호표 · 덧줄 `kind:키#n`) · 중간 줄 `mid:이름` · 열쇠 = 자리 · `add:#k` ·
|
|
`add:#k/세부 cid` · 식 칸 = 중간 「식」 · 호표 「수량」 · 덧줄 「식」.
|
|
셈 = `M02_Sangeun_Doc.patched` 첫머리 `formulas`(바탕 줄 식을 바꾼 사본) → 모든 셈 길(화면 · 저장
|
|
원값 · B09 · 펼침 겹침) 한 벌 · 펼침(`M02_Library_Flat._read`) = 새 덮개 SK 는 안 펼침
|
|
(옛 참조 그대로) · [초기화](`M02_Library_Flat.reset`) = `restore` 전체.
|
|
화면 셈 답(`answer`) 덧칸 `원래` {노무비, 재료비, 경비, 계}(1 단위) + `시트` {자리, 글줄, 짝잃음} ·
|
|
M01 원래 = 같은 옵션 · 덮개 · 뺀줄 · 더한줄 · 새 덮개 없는 셈.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import functools
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
|
|
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
|
from M02_MasterTemplete import M02_Sangeun_Equation as eqm
|
|
from M02_MasterTemplete import M02_Sangeun_Formula as fx
|
|
from M02_MasterTemplete import M02_Sangeun_Imported as imp
|
|
|
|
NEW = ("상수덮개", "표값덮개", "글줄", "까닭")
|
|
_SPOT = re.compile(r"^(mid:.+|(labor|material|machine|logic|pct):.*#\d+)$")
|
|
_KEY = re.compile(r"^(mid:.+|(add:#\d+/)?(labor|material|machine|logic|pct):.*#\d+|add:#\d+)$")
|
|
_ADD = re.compile(r"^add:#(\d+)(/.*)?$")
|
|
FIELD = {"중간": "식", "호표": "수량", "덧줄": "식"}
|
|
LONG = 500 # 글 · 까닭 길이 상한
|
|
MANY = 200 # 글줄 수 상한
|
|
|
|
|
|
# ── 식 속 자리 ────────────────────────────────────────────────────────
|
|
@functools.cache
|
|
def _parser() -> type:
|
|
"""엔진 파서(`master_formula._Parser`) + 숫자 · 표 찾기 글자 자리 기록(엔진과 같은 차례)."""
|
|
from M02_MasterTemplete import M02_Labor_Doc as labor
|
|
|
|
class Spots(labor.m01.cm.mf._Parser):
|
|
def __init__(self, text: str) -> None:
|
|
super().__init__(text)
|
|
self.spots: list[tuple[str, int, int]] = []
|
|
|
|
def primary(self): # noqa: ANN202
|
|
self.ws()
|
|
at = self.i
|
|
node = super().primary()
|
|
if node[0] == "num" and self.s[at : at + 1] != "(": # 「(11)」 = 안쪽 수만
|
|
self.spots.append(("num", at, self.i))
|
|
return node
|
|
|
|
def unary(self): # noqa: ANN202
|
|
self.ws()
|
|
at = self.i
|
|
node = super().unary()
|
|
inner = node
|
|
while inner[0] == "field":
|
|
inner = inner[1]
|
|
if node[0] == "field" and inner[0] == "find":
|
|
self.spots.append(("find", at, self.i))
|
|
return node
|
|
|
|
return Spots
|
|
|
|
|
|
@functools.lru_cache(maxsize=4096)
|
|
def scan(text: str) -> dict[str, list[tuple[int, int]]]:
|
|
"""식 글 → {num: [(처음,끝)], find: [(처음, 끝)]}(왼쪽부터) · 못 읽는 식 = 빈 목록."""
|
|
p = _parser()(text)
|
|
try:
|
|
p.parse()
|
|
except Exception: # noqa: BLE001 — 엔진 FormulaError · 깊은 괄호
|
|
return {"num": [], "find": []}
|
|
spots = [(t, a, a + len(text[a:b].rstrip())) for t, a, b in p.spots] # 뒤 빈칸(`.` 엿봄) 뗌
|
|
return {k: sorted((a, b) for t, a, b in spots if t == k) for k in ("num", "find")}
|
|
|
|
|
|
def _lit(value: Any) -> str:
|
|
got = sd.text(value)
|
|
return f"({got})" if got.startswith("-") else got
|
|
|
|
|
|
def swap(text: str, nums: dict, finds: dict) -> str:
|
|
"""식 글의 n 번째 숫자 · 표 찾기를 값으로(원래 식 차례 · 바꾼 표 찾기 안 숫자는 버림)."""
|
|
spots = scan(text)
|
|
cuts = [(*spots["find"][int(n)], v) for n, v in finds.items() if int(n) < len(spots["find"])]
|
|
for n, v in nums.items():
|
|
if int(n) < len(spots["num"]):
|
|
a, b = spots["num"][int(n)]
|
|
if not any(x <= a and b <= y for x, y, _ in cuts):
|
|
cuts.append((a, b, v))
|
|
for a, b, v in sorted(cuts, reverse=True):
|
|
text = text[:a] + _lit(v) + text[b:]
|
|
return text
|
|
|
|
|
|
def places(row: dict) -> dict[str, tuple[str, int]]:
|
|
"""자리 → (갈래, 차례) — 중간 `mid:이름` · 호표 · 덧줄 cid."""
|
|
out = {f"mid:{s.get('이름')}": ("중간", at) for at, s in enumerate(row.get("중간") or [])}
|
|
n = len(row.get("호표") or [])
|
|
for at, cid in enumerate(sd.line_ids(row)):
|
|
out[cid] = ("호표", at) if at < n else ("덧줄", at - n)
|
|
return out
|
|
|
|
|
|
def formulas(row: dict, doc: dict) -> dict:
|
|
"""바탕 줄 + 식 칸(792-1) · 상수 · 표값 덮개 → 식 바꾼 사본(덮개 없음 = 그 줄 그대로 · 순수)."""
|
|
return formulas_lost(row, doc)[0]
|
|
|
|
|
|
def formulas_lost(row: dict, doc: dict) -> tuple[dict, list[dict], dict]:
|
|
"""(식 바꾼 사본, 틀린 식 자리 짝잃음, 수량식 엔진 꼴 문서) — `sd.patched` 첫머리."""
|
|
row, bad, doc = fx.settle(row, doc)
|
|
return _swapped(row, doc), bad, doc
|
|
|
|
|
|
def _swapped(row: dict, doc: dict) -> dict:
|
|
nums, finds = doc.get("상수덮개") or {}, doc.get("표값덮개") or {}
|
|
if not nums and not finds:
|
|
return row
|
|
out = {**row, **{k: list(row.get(k) or []) for k in FIELD}}
|
|
for spot, (part, at) in places(row).items():
|
|
if spot in nums or spot in finds:
|
|
line = out[part][at] = dict(out[part][at])
|
|
field = FIELD[part]
|
|
line[field] = swap(str(line.get(field)), nums.get(spot) or {}, finds.get(spot) or {})
|
|
return out
|
|
|
|
|
|
def lost(row: dict, doc: dict) -> list[dict]:
|
|
"""짝 잃은 새 덮개 — 바탕에 없는 자리 · 없는 n 번째."""
|
|
where, out = places(row), []
|
|
for name, kind in (("상수덮개", "num"), ("표값덮개", "find")):
|
|
for spot, cover in (doc.get(name) or {}).items():
|
|
if spot not in where:
|
|
out.append({"cid": spot, "까닭": f"{name} — 바탕에 없는 줄"})
|
|
continue
|
|
part, at = where[spot]
|
|
have = len(scan(str((row[part][at] or {}).get(FIELD[part])))[kind])
|
|
out += [
|
|
{"cid": spot, "까닭": f"{name} — {int(n) + 1} 번째 없음"}
|
|
for n in cover
|
|
if int(n) >= have
|
|
]
|
|
return out
|
|
|
|
|
|
def no_flat(doc: Any) -> None:
|
|
"""펼침 막음 — 상수 · 표값 덮개 SK(펼친 줄에 못 옮김 · 옛 참조 그대로 = 값 그대로)."""
|
|
keys = ("상수덮개", "표값덮개", eqm.KEY) # 수식 고침(796-2b)
|
|
if isinstance(doc, dict) and (any(doc.get(k) for k in keys) or fx.has(doc)):
|
|
raise sd.DocError(
|
|
"상수 · 표값 덮개 · 식 칸 · 수식 고침 단가산출은 펼치지 않음(참조 그대로)"
|
|
)
|
|
if imp.is_imported(doc): # 가져온 식(793-2) = M01 기초 없음
|
|
raise sd.DocError("가져온 식 단가산출은 펼치지 않음(참조 그대로)")
|
|
|
|
|
|
def covers_n(doc: dict) -> int:
|
|
"""[초기화] 걷힐 고침 수 — 덮개 · 뺀줄 · 상수 · 표값 덮개 칸."""
|
|
keys = ("덮개", "뺀줄", "상수덮개", "표값덮개", "식덮개")
|
|
return sum(len(doc.get(k) or ()) for k in keys)
|
|
|
|
|
|
# ── 문서 꼴 ──────────────────────────────────────────────────────────
|
|
def _key(raw: Any, where: str, pat: re.Pattern = _SPOT) -> str:
|
|
if not isinstance(raw, str) or not pat.match(raw):
|
|
raise sd.DocError(f"{where} · 자리 꼴 아님 「{raw}」")
|
|
return raw
|
|
|
|
|
|
def _words(raw: Any, where: str) -> str:
|
|
got = str(raw or "").strip()
|
|
if len(got) > LONG:
|
|
raise sd.DocError(f"{where} · 글이 {LONG} 자를 넘음")
|
|
return got
|
|
|
|
|
|
def _covers(raw: Any, name: str) -> dict:
|
|
if raw in (None, {}):
|
|
return {}
|
|
if not isinstance(raw, dict):
|
|
raise sd.DocError(f"{name} · 묶음 아님")
|
|
out = {}
|
|
for spot, one in raw.items():
|
|
where = f"{name} 「{spot}」"
|
|
if not isinstance(one, dict):
|
|
raise sd.DocError(f"{where} · {{n: 값}} 아님")
|
|
got = {}
|
|
for n, v in one.items():
|
|
if not re.fullmatch(r"\d{1,3}", str(n)):
|
|
raise sd.DocError(f"{where} · n 은 0 부터 수 「{n}」")
|
|
got[str(int(n))] = sd.plain(sd._num(v, f"{where} {n}"))
|
|
if got:
|
|
out[_key(spot, name)] = got
|
|
return out
|
|
|
|
|
|
def clean_more(raw: Any, out: dict) -> dict:
|
|
"""`sd.clean` 끝 — 새 덮개 칸 검사 · 빈 칸은 안 둠(`M01판` · 원값은 서버 몫이라 버림)."""
|
|
raw = raw if isinstance(raw, dict) else {}
|
|
for name in ("상수덮개", "표값덮개"):
|
|
if got := _covers(raw.get(name), name):
|
|
out[name] = got
|
|
texts = raw.get("글줄") or []
|
|
if not isinstance(texts, list) or len(texts) > MANY:
|
|
raise sd.DocError(f"글줄 · {MANY} 줄까지 목록")
|
|
lines = []
|
|
for at, one in enumerate(texts, 1):
|
|
one = one if isinstance(one, dict) else {}
|
|
spot = str(one.get("자리") or "")
|
|
words = _words(one.get("글"), f"글줄 {at}째")
|
|
if words:
|
|
lines.append({"자리": spot and _key(spot, f"글줄 {at}째", _KEY), "글": words})
|
|
if lines:
|
|
out["글줄"] = lines
|
|
why = raw.get("까닭") or {}
|
|
if not isinstance(why, dict):
|
|
raise sd.DocError("까닭 · 묶음 아님")
|
|
why = {_key(k, "까닭", _KEY): _words(v, f"까닭 「{k}」") for k, v in why.items()}
|
|
if why := {k: v for k, v in why.items() if v}:
|
|
out["까닭"] = why
|
|
if isinstance(raw.get("사본"), dict): # 로직 사본(795-1) — 셈 · 저장 첫머리 settle 이 정함
|
|
out["사본"] = raw["사본"]
|
|
if isinstance(raw.get("수식"), dict): # 수식 사본(802-4a) — 저장 끝 `M02_Doc_Sk.settle` 이 정함
|
|
out["수식"] = raw["수식"]
|
|
out = eqm.clean(raw, out) # 수식 고침(796-2b)
|
|
return imp.clean(raw, fx.clean(raw, out)) # 식 칸(792-1) · 가져온 식(793-2)
|
|
|
|
|
|
def _print(one: Any) -> str:
|
|
raw = json.dumps(one, ensure_ascii=False, sort_keys=True, default=str)
|
|
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:12]
|
|
|
|
|
|
def stamp(row: dict, doc: dict | None = None) -> dict[str, Any]:
|
|
"""저장 때 서버 칸 — 바탕판(옛 칸 그대로) + M01판(자리마다 지문) · `doc` = 식 칸(792-1)을
|
|
엔진 식으로 바꿔 넣음(제자리 · 틀린 식 = `DocError`)."""
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
|
|
|
if doc is not None and fx.has(doc):
|
|
fixed = fx.settle(row, doc, strict=True)[2]
|
|
doc.update({k: fixed[k] for k in ("더한줄", *fx.FIELDS) if k in fixed})
|
|
if doc is not None and imp.is_imported(doc): # 가져온 식 검산(793-2) = 서버가 다시
|
|
doc[imp.KEY]["검산"] = imp.check(doc)
|
|
|
|
lines = [*(row.get("중간") or []), *(row.get("호표") or []), *(row.get("덧줄") or [])]
|
|
spots = [f"mid:{s.get('이름')}" for s in row.get("중간") or []] + sd.line_ids(row)
|
|
return {"바탕판": calc.fingerprint(row), "M01판": dict(zip(spots, map(_print, lines)))}
|
|
|
|
|
|
# ── 고치기 ──────────────────────────────────────
|
|
def restore(doc: dict, key: str | None = None) -> dict:
|
|
"""[M01 원래로] — 전체(None) = 덮개 · 뺀줄 · 상수 · 표값 · 식 덮개 · 그 까닭 지움(더한줄 ·
|
|
글줄 · 변수줄 · 옵션 남김) · 열쇠 하나 = 그 자리 덮개 · 뺀줄 · 새 덮개 · 까닭(세부
|
|
`add:#k/cid` = 그 더한줄 세부)."""
|
|
out = {k: v for k, v in doc.items() if k not in ("상수덮개", "표값덮개", "식덮개", "까닭")}
|
|
why = dict(doc.get("까닭") or {})
|
|
if key is None:
|
|
out |= {"덮개": {}, "뺀줄": []}
|
|
why = {k: v for k, v in why.items() if _top_add(k)}
|
|
return out | ({"까닭": why} if why else {})
|
|
hit = _ADD.match(key)
|
|
if hit and hit[2]:
|
|
k, sub = int(hit[1]), hit[2][1:]
|
|
adds = [dict(x) for x in doc.get("더한줄") or []]
|
|
if k < len(adds):
|
|
one = {n: v for n, v in adds[k].items() if n not in ("덮개", "뺀줄")}
|
|
covers = {c: v for c, v in (adds[k].get("덮개") or {}).items() if c != sub}
|
|
drop = [c for c in adds[k].get("뺀줄") or [] if c != sub]
|
|
adds[k] = one | ({"덮개": covers} if covers else {}) | ({"뺀줄": drop} if drop else {})
|
|
out["더한줄"] = adds
|
|
else:
|
|
out["덮개"] = {c: v for c, v in (doc.get("덮개") or {}).items() if c != key}
|
|
out["뺀줄"] = [c for c in doc.get("뺀줄") or [] if c != key]
|
|
for name in ("상수덮개", "표값덮개", "식덮개"):
|
|
if got := {s: v for s, v in (doc.get(name) or {}).items() if s != key}:
|
|
out[name] = got
|
|
why.pop(key, None)
|
|
return out | ({"까닭": why} if why else {})
|
|
|
|
|
|
def _top_add(key: str) -> bool:
|
|
hit = _ADD.match(key)
|
|
return bool(hit) and not hit[2]
|
|
|
|
|
|
def drop_add_keys(doc: dict, k: int) -> dict:
|
|
"""더한줄 k 를 뺀 뒤 — 까닭 · 글줄 열쇠 `add:#j`(j > k) 를 한 칸 당김 · k 는 지움."""
|
|
|
|
def move(key: str) -> str | None:
|
|
hit = _ADD.match(key)
|
|
if not hit:
|
|
return key
|
|
j = int(hit[1])
|
|
return None if j == k else f"add:#{j - 1 if j > k else j}{hit[2] or ''}"
|
|
|
|
out = dict(doc)
|
|
if "까닭" in doc:
|
|
why = {move(x): v for x, v in doc["까닭"].items() if move(x)}
|
|
out.pop("까닭")
|
|
out |= {"까닭": why} if why else {}
|
|
if "글줄" in doc:
|
|
out["글줄"] = [{**t, "자리": move(t["자리"]) or ""} for t in doc["글줄"]]
|
|
return out
|
|
|
|
|
|
def kinds(doc: dict, spot: str) -> list[str]:
|
|
"""자리의 덮개 갈래 — 품 · 단가 · 상수 · 표값 · 뺌(차례 그대로)."""
|
|
cover, out = (doc.get("덮개") or {}).get(spot) or {}, []
|
|
if "qty" in cover or "배율" in cover:
|
|
out.append("품")
|
|
if any(k in cover for k in sd.PARTS):
|
|
out.append("단가")
|
|
out += [
|
|
n
|
|
for n, k in (("상수", "상수덮개"), ("표값", "표값덮개"), ("식", "식덮개"))
|
|
if spot in (doc.get(k) or {})
|
|
]
|
|
return out + (["뺌"] if spot in (doc.get("뺀줄") or []) else [])
|
|
|
|
|
|
# ── 화면 셈 답 ────────────────────────────────────────────────────────
|
|
def answer(got: dict, clean: dict, raw: Any, given: dict | None, trail: tuple) -> dict:
|
|
"""`M02_Sangeun_Calc.calc_doc` 답 — 옛 답 + `원래` · `시트`."""
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
|
|
|
out = {**calc.views.finish(calc.per_unit(got, clean)), "머리": calc.head_of(clean)}
|
|
return calc.labor._json(imp.more(out | sheet(out, clean, raw, given, trail), clean))
|
|
|
|
|
|
def _bare(doc: dict) -> dict:
|
|
out = {k: v for k, v in doc.items() if k not in (*NEW, *fx.FIELDS, "뺀더한줄", eqm.KEY)}
|
|
return out | {"덮개": {}, "뺀줄": [], "더한줄": []}
|
|
|
|
|
|
def _mids(whole: Any, key: str, row: dict, values: dict) -> tuple[dict, dict]:
|
|
"""(중간 값, 엔진 입력) — 시험 계산과 같은 길(`M02_Sangeun_Views.text_of`) · 못 셈 = 빈."""
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
|
|
|
over = calc._Over(whole, key, {**row, "키": key})
|
|
m01 = calc.labor.m01
|
|
auto = {k: v for k, v in m01.mau.values(over, over.logic(key)).items() if v is not None}
|
|
given = {**auto, **m01._dec(values or {})}
|
|
try:
|
|
mid = calc.labor.mcut.engine(over, key, m01.cm.mf.run(over, key, given)).get("중간") or {}
|
|
except Exception: # noqa: BLE001 — 엔진 FormulaError · 셈 틀림 = 값 없음
|
|
return {}, given
|
|
return mid, given
|
|
|
|
|
|
def _finds(whole: Any, text: str, env: dict) -> list[dict]:
|
|
from M02_MasterTemplete import M02_Labor_Doc as labor
|
|
|
|
mf, out = labor.m01.cm.mf, []
|
|
for n, (a, b) in enumerate(scan(text)["find"]):
|
|
try:
|
|
value = mf.evaluate(mf.parse(text[a:b]), dict(env), whole)
|
|
except Exception: # noqa: BLE001
|
|
value = None
|
|
out.append({"n": n, "글": text[a:b], "값": value})
|
|
return out
|
|
|
|
|
|
def sheet(ans: dict, clean: dict, raw: Any, given: dict | None, trail: tuple) -> dict:
|
|
"""`원래` · `시트` — 자리마다 원래 · 지금 · 덮개 갈래 · 까닭 · M01바뀜 · 상수 · 표값."""
|
|
from M02_MasterTemplete import M02_Labor_Ref as refs
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
|
|
|
whole = calc.skc.over(clean, calc.labor.m01.loaded()[1]) # 사본(795-1) — 중간 · 표값도
|
|
try:
|
|
base = calc._base_of(clean, whole, refs.read_ref, trail)["줄"]
|
|
bare = calc.per_unit(calc.run(_bare(clean), whole, given, trail=trail), clean)
|
|
except sd.DocError:
|
|
return {"원래": None, "시트": {"자리": {}, "글줄": clean.get("글줄") or [], "짝잃음": []}}
|
|
was = {x["cid"]: x for x in bare.get("줄") or [] if x.get("cid")}
|
|
now = {x["cid"]: x for x in ans.get("줄") or [] if x.get("cid")}
|
|
key = str(clean.get("키") or "SK-000000")
|
|
values = {**(clean.get("입력값") or {}), **(given or {})}
|
|
mids0, env0 = _mids(whole, key, base, values)
|
|
mids1, _ = _mids(whole, key, formulas(base, clean), values)
|
|
stamps = (raw.get("M01판") if isinstance(raw, dict) else None) or {}
|
|
stamps = {} if imp.is_imported(clean) else stamps # 가져온 식 = M01 짝 없음(793-3b)
|
|
fresh = stamp(base)["M01판"]
|
|
why = clean.get("까닭") or {}
|
|
nums, finds = clean.get("상수덮개") or {}, clean.get("표값덮개") or {}
|
|
spots: dict[str, dict] = {}
|
|
for spot, (part, at) in places(base).items():
|
|
line = base[part][at]
|
|
text = str(line.get(FIELD[part]))
|
|
consts = [
|
|
{"n": n, "값": nums.get(spot, {}).get(str(n), v), "원값": v}
|
|
for n, v in enumerate(Decimal(text[a:b]) for a, b in scan(text)["num"])
|
|
]
|
|
tables = [
|
|
{**t, "원값": t["값"], "값": finds.get(spot, {}).get(str(t["n"]), t["값"])}
|
|
for t in _finds(whole, text, {**env0, **mids0})
|
|
]
|
|
if part == "중간":
|
|
name = str(line.get("이름"))
|
|
old, cur = mids0.get(name), mids1.get(name)
|
|
else:
|
|
old, cur = (was.get(spot) or {}).get("금액"), (now.get(spot) or {}).get("금액")
|
|
spots[spot] = {
|
|
"갈래": part,
|
|
"이름": line.get("이름") or "",
|
|
"식": text,
|
|
"상수": consts,
|
|
"표값": tables,
|
|
"원래": old,
|
|
"지금": cur,
|
|
"덮개": kinds(clean, spot),
|
|
"까닭": why.get(spot),
|
|
"M01바뀜": spot in stamps and stamps[spot] != fresh.get(spot),
|
|
}
|
|
for cid, line in now.items():
|
|
if _ADD.match(cid) and cid not in spots:
|
|
spots[cid] = {"갈래": "더한줄", "이름": line.get("이름") or "", "원래": None}
|
|
spots[cid] |= {"지금": line.get("금액"), "덮개": [], "까닭": why.get(cid)}
|
|
gone = lost(base, clean) + [
|
|
{"cid": s, "까닭": "M01 바뀜 — 바탕에 없는 줄"} for s in stamps if s not in fresh
|
|
]
|
|
gone += fx.sheet_more(spots, base, clean, mids1) # 식 칸(792-1) 덧칸 · 변수 줄 · 틀린 식
|
|
top = {"글줄": clean.get("글줄") or [], "짝잃음": gone, "이름표": fx.name_table(base, clean)}
|
|
return {"원래": bare.get("기준"), "시트": {"자리": spots, **top}}
|