"""M02 일위대가 · 사전조합(SC) 로직 사본(PLAN 795-2 · 요구 655 · 656 · 요구서_655) — 서버 단독. SK 사본(`M02_Sangeun_Copy` · 795-1) 한 벌 — 문서 칸 `사본` 꼴 · 판 · 단가 · 할일 칸 `사본할일` (단가갱신 | 재생성) · [단가 갱신] = 자원 단가 줄만 · 자동 갱신 없음 · 지워진 M01 키 = 마지막 값. 사본에 담는 것 = 담은로직 로직 줄 · 줄 덮개(`M02_Labor_Patch`) 더한줄(로직 · 요소 · 고정 이음) · 고정 줄 이음 · 사용자 자재(MT) 가 닿는 M01 닫힌 묶음(참조 줄 = 그 문서 제 사본). 셈 = `M02_Labor_Doc.calc` 층 셈(whole 안 줌) 첫머리 `live` — M01 색인 위 사본 줄(`skc.Copy`) · 줄 덮개 = 사본 위 고친 칸(cid 열쇠 · [재생성] 뒤 다시 얹힘 · 못 얹은 칸 = 짝잃음) · 사전조합 1 단위 값(`Expand`) = 그 SC 사본(`combo`) · 프로젝트 층 · B09(단가 책 · 굳힌 참조사본) = 옛 길(795-4). 사본 정함(`settle` · 셈 · 저장 첫머리) = 받은 사본 먼저(열 복사 · 층 복사 = 원본 사본 그대로 · 판은 서버가 다시 셈) → 없으면 저장본(일위대가 = labor/<열> · SC = precombo/<키>) → [저장] 때 없으면 새로 찍음 · 사본에 없는 새 줄만 지금 M01 에서 더함 · 사본 없는 옛 문서 = 셈은 옛 길. 길(`M02_Labor_Router`) POST …/labor/{열 | SC 키}/copy/{price|regen} {doc, 수량?} → {doc(사본할일), 전, 후, 바뀐자원 | 못얹은}(저장 없음). 짝(CLAUDE.md 5장 ①) `M02_MasterTemplete_Labor_Calc.ts` = 서버 기준 값을 받아 수량 · 반영 · 합만 — 사본은 기준 값에만 닿음(짝 안 고침 · 거울 시험 `test_795_2_sc_copy`). """ from __future__ import annotations import json import re from typing import Any from M02_MasterTemplete import M02_Sangeun_Copy as skc KEY, JOB, PRICE, REGEN = skc.KEY, skc.JOB, skc.PRICE, skc.REGEN _SC = re.compile(r"^SC-\d{6}$") def _project() -> bool: from M02_MasterTemplete import M02_Labor_Ref as refs return refs.here()[0] == "project" def stamp(doc: dict) -> dict | None: """지금 M01 로 새 사본 — 닿을 것 없음(참조 줄 · 손 단가 고정 줄만) = None.""" lines = [x for x in doc.get("담은로직") or [] if isinstance(x, dict)] keys = [str(x["로직"]) for x in lines if x.get("로직")] links = [str(v) for x in lines for v in ((x.get("고정") or {}).get("이음") or {}).values() if v] for x in lines: more = skc.reach((x.get("패치") or {}).get("더한줄")) keys, links = keys + more[0], links + more[1] links += [ str(m["코드"]) for m in doc.get("자재") or [] if isinstance(m, dict) and m.get("코드") ] return skc.snap(list(dict.fromkeys(keys)), list(dict.fromkeys(links))) def _stored(doc: dict) -> dict | None: from M02_MasterTemplete import M02_MasterTemplete_Store as store key = str(doc.get("키") or doc.get("열") or "") kind = "precombo" if _SC.match(key) else "labor" try: got = json.loads(store._path(kind, key).read_bytes()) except (OSError, ValueError, store.StoreError): return None return got[KEY] if skc.has(got) else None def settle(doc: Any, save: bool = False) -> Any: """셈 · 저장 첫머리 — 서버가 정한 사본으로 갈음(할일 · 저장 때 찍음 · 새 줄 · 할일 칸 뗌).""" if not isinstance(doc, dict) or _project(): return doc job = doc.get(JOB) # 모르는 할일 = 없음 out = {k: v for k, v in doc.items() if k not in (KEY, JOB)} copy = skc.taken(doc.get(KEY)) or _stored(doc) if job == REGEN or (copy is None and save): copy = stamp(out) elif job == PRICE and copy: copy = skc.refresh(copy, skc.whole_())[0] if save and copy: copy = skc.grow(copy, stamp(out)) return {**out, KEY: copy} if copy else out def keep(raw: Any, out: dict) -> dict: """정리한 문서에 사본 · 할일 칸을 실어 보냄(꼴 검사는 `settle` 몫).""" if not isinstance(raw, dict): return out got = {k: raw[k] for k in (KEY, JOB) if raw.get(k) is not None} return {**out, **got} def live(doc: dict) -> Any: """층 셈 색인 — 사본 있는 문서 = M01 위 사본 줄 · 없거나 프로젝트 층 = 지금 M01.""" from M01_MasterData import M01_MasterData_Store as m01 whole = m01.loaded()[1] return whole if _project() else skc.over(settle(doc), whole) def combo(expand: Any, doc: dict, trail: tuple[str, ...]) -> dict[str, Any]: """사전조합 1 단위 값 — 그 SC 사본 색인으로(층 셈만 · 프로젝트 = 굳힌 단가 그대로).""" from M02_MasterTemplete import M02_Labor_Ref as refs was = expand.whole if expand.read is refs.read_ref and skc.has(doc): expand.whole = skc.over(doc, was) try: return expand._combo(doc, trail) finally: expand.whole = was def mark(ans: dict, doc: dict) -> dict: """셈 답 덧칸 `사본` {시점, 단가시점, M01새판, 지운키, 단가새값} — 사본 없음 = 칸 없음.""" return skc.mark(ans, doc) # ── [단가 갱신] · [재생성] 미리 셈(저장 없음) ────────────────────────── def _money(ans: dict) -> dict: return {k: ans.get(k) for k in ("기준합", "반영합")} def _lost(ans: dict) -> dict[str, dict]: got = {} for n, line in enumerate(ans.get("줄") or []): for x in line.get("짝잃음") or []: if isinstance(x, dict): got[f"{n}|{x.get('cid')}"] = {**x, "줄": n} return got def preview(doc: dict, job: str, given: dict | None = None) -> dict[str, Any]: """{doc(할일 얹음), 전, 후, 바뀐자원 · 못얹은} — `doc` = 정리한 문서 · 재생성 ⊃ 단가갱신.""" from M02_MasterTemplete import M02_Labor_Doc as labor new = REGEN if REGEN in (doc.get(JOB), job) else job after_doc = {**doc, JOB: new} before, after = labor.calc(doc, given), labor.calc(after_doc, given) out: dict[str, Any] = {"doc": after_doc, "전": _money(before), "후": _money(after)} if job == PRICE: copy = skc.taken(doc.get(KEY)) or _stored(doc) out["바뀐자원"] = skc.refresh(copy, skc.whole_())[1] if copy else [] else: was = _lost(before) out["못얹은"] = [ {"줄": x["줄"], "cid": x.get("cid"), "까닭": x.get("까닭") or "새 사본에 그 줄 없음"} for c, x in _lost(after).items() if c not in was ] return out