Files
Aislo/M02_MasterTemplete/M02_Library_Flat.py
T
eomsangdonandClaude Opus 5.5 6893599fa9 feat(740-1): 일위대가 안 일위대가 = 사본 블록(원본 연결 끊음 · 하위 호표 끝수 그대로) · 초기화 이음 기계 다시 받음
- SC 안 SC = 안쪽 SC 몸 통째를 그 줄 참조 꼴 {층: 사본, 주인: 몸 지문, 키: 본 SC, 몸} 에 담음(Labor_Ref.copy_ref · read_ref 가 몸을 돌려줌 → 셈 · 알약 · B09 하위 대가 블록 · 지문 = 옛 참조 줄과 같은 길) · 엑셀 대조 거창 같음 14 그대로 · 같은 기초 겹침도 블록 안(400 없음)
- 저장 · 읽기 · 옮기기 · 찾아 넣기 모두 사본 블록 · 자기 자신을 담은 줄 = 안 펼침(저장 422 그대로)
- 단가산출에 SC 넣기 = 사본 블록 줄도 더한줄로 펼침(바깥 × 안 수량)
- 초기화 = 사본 몸 안까지 · 이음 기계 고정 줄(736-3) 단가 = M01 기계 한 시간 값 다시 받음
- 옮기기 = 쓰는 곳 검사 걷음(같은 자리 한 줄이라 안 줄 알약 차례 그대로) · 실 storage 보기만 = 1/3 SC-000002 사본 블록 · 전후 1,446,078 같음
- 낡은 시험 고침(까닭 = 하위 호표 참조가 사본 블록으로): m02_labor_ref(순환 대신 사본 · 복사 딸림 없음) · 733-2(딸림 재사용 = 구조물 조합 복사) · 735-1(쓰는 곳 = 구조물 조합 둘) · hopyo_build · 707-7(하위 호표 줄 = 사본 블록 · 본뜬)

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

574 lines
26 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) · 일위대가(SC) 펼침 · 초기화 · 옮기기(PLAN 740-1 · 요구 581 · 분석 579-2).
서로 독립 — 연결 고리 = M01 키만. M02 문서를 넣으면(찾아 넣기 `-flat` · 저장 · 읽기 · 층 사이
복사) 그 문서 몸을 M01 키 줄 + 그 문서 덮개 사본으로 펼쳐 담음 → 원본이 나중에 바뀌어도
넣은 쪽 금액 그대로. 줄 `본뜬 {층, 주인, 키, 이름, 때}` = 출처 정보(셈에 안 씀).
SK 에 SK 더한줄 로직 한 줄 = 원본 몸 통째(바탕 기초 · 입력값 · 덮개 · 뺀줄 · 고름 · 자재표시 ·
켜쌓기율 · 더한줄 — `M02_Sangeun_Doc.BODY` · % 덧줄까지 같은 금액) ·
수량 = 넣은 수 ÷ 배(728 환산) · 바탕 없는 SK = 그 더한줄 × 넣은 수
SK 에 SC SC 줄마다 더한줄(로직 줄 = 로직 한 줄 + 패치 몸 · 고정 · 자재 = 재료) ·
수량 = 넣은 수 × 줄 수량(저장 `수량.손` › 실무수량 › 1 · 742-1) ÷ 배(물량 알약 배)
SC 에 SK 로직 줄 하나 + 패치(= SK 몸) · 별칭 = SK 이름 · 같은 자리(안 줄 알약 키 그대로) ·
바탕 없는 SK · 단위 환산 SK = 못 넣음(값 단위가 달라짐)
SC 에 SC 사본 블록 — 안쪽 SC 몸 통째(M01 키 줄 + 덮개 · 실무수량)를 그 줄 참조 꼴
`{층: 사본, 주인: 몸 지문, 키: 본 SC, 몸}` 에 담음(`M02_Labor_Ref.copy_ref` · 원본
연결 없음 · 하위 호표 끝수 단계 · B09 하위 대가 블록 그대로 · 같은 기초 겹침도
블록 안)
바탕 SK 참조 · 패치 있는 SK 참조 줄 = 위 덮개를 아래 덮개에 겹쳐 한 벌(`compose`)
못 펼친 줄 = 저장 · 읽기 · 옮기기에선 옛 참조 그대로(값 그대로) · 넣기 길(`strict`)에선 400 까닭.
초기화(`reset`) = 덮개 · 뺀줄 · 더한줄 단가 걷음(안쪽 넣은 몸 · 사본 블록까지) · 이음 기계 고정 줄
단가 = M01 기계 한 시간 값 다시 받음(736-3) · 항목 · 수량 · 입력값 · 고름 · 자재표시 · 켜쌓기율 ·
기본정보 남김 → 값 = 연결된 M01 키 값.
옮기기(`migrate`) = 층 문서 펼쳐 다시 씀 — 전후 1 단위 금액 같을 때만(안 줄 수량 모두 1 로 대조) ·
공유 storage 는 브레인이 돌림.
"""
from __future__ import annotations
import json
import re
import time
from decimal import Decimal
from typing import Any
from M02_MasterTemplete import M02_Labor_Fixed as fixed
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_Sangeun_Doc as sd
ADD = re.compile(r"^add:#(\d+)$")
FAIL = (ArithmeticError, KeyError, TypeError, ValueError) # 못 펼침(DocError · FormulaError 포함)
EMPTY = (None, {}, [], "")
Read = Any
def _whole() -> Any:
from M01_MasterData import M01_MasterData_Store as m01
return m01.loaded()[1]
def _d(value: Any) -> Decimal:
return Decimal(str(value))
def mark(ref: refs.Ref, doc: dict) -> dict[str, Any]:
"""줄 출처 — 펼친 원본 {층, 주인, 키, 이름, 때(UTC)}."""
when = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
return {
**{k: ref.get(k) for k in ("층", "주인", "키")},
"이름": doc.get("이름") or "",
"때": when,
}
def _read(ref: refs.Ref, read: Read, trail: tuple) -> tuple[dict, tuple]:
rid = refs.ref_id(ref)
if rid in trail:
raise sd.DocError("순환 — " + " → ".join(t.rsplit("|", 1)[-1] for t in (*trail, rid)), 422)
if len(trail) >= refs.DEPTH:
raise sd.DocError(f"참조가 {refs.DEPTH}단을 넘음", 422)
got = read(ref)
if not isinstance(got, dict):
raise sd.DocError(f"없는 참조 「{ref.get('키')}」")
return got, (*trail, rid)
# ── SK ────────────────────────────────────────────────────────────────
def flat_sk(doc: dict, read: Read, whole: Any, trail: tuple = (), strict: bool = True) -> dict:
"""SK 문서 → M01 키만(더한줄 참조 펼침 · 바탕 참조 겹침) · `strict` False = 못 펼친 줄
그대로."""
adds = []
for one in doc.get("더한줄") or []:
ref = one.get("참조") if isinstance(one, dict) else None
if not isinstance(ref, dict):
adds.append(one)
continue
try:
adds += into_sk(ref, one.get("수량", 1), read, whole, trail)
except FAIL as e:
if strict:
raise sd.DocError(f"「{ref.get('키')}」 · {e}") from e
adds.append(one)
out = {**doc, "더한줄": adds}
base = doc.get("바탕") or {}
if isinstance(base.get("참조"), dict):
try:
under, path = _read(base["참조"], read, trail)
under = flat_sk(sd.clean(under, None, whole), read, whole, path)
out = compose(under, out, whole)
except FAIL:
if strict:
raise
return out
def into_sk(ref: refs.Ref, qty: Any, read: Read, whole: Any, trail: tuple = ()) -> list[dict]:
"""SK 에 넣을 더한줄 — 넣는 문서(SK · SC)를 M01 키 줄로(`qty` = 그 문서 단위 수)."""
src, path = _read(ref, read, trail)
kind, q, tag = refs.kind_of(ref.get("키")), _d(qty), mark(ref, src)
src = sd.clean(src, None, whole) if kind == "sangeun" else src
if kind == "sangeun":
return sk_adds(flat_sk(src, read, whole, path), q, whole, tag)
if kind == "precombo":
return sc_adds(flat_sc(src, read, whole, path), q, whole, tag)
raise sd.DocError("옛 단가승격은 펼치지 않음")
def filled_adds(doc: dict, whole: Any) -> list[dict]:
"""더한줄 로직 빈 입력 = 그 SK 입력값(셈 `_fill` 의 공통 옵션 몫) — 옮겨도 같은 옵션."""
common, out = doc.get("입력값") or {}, []
for one in doc.get("더한줄") or []:
if one.get("종류") == "로직":
try:
names = [str(s.get("이름")) for s in whole.logic(one["로직"]).get("입력") or []]
except Exception: # noqa: BLE001 — 없는 로직 = 검사 · 셈이 까닭
names = []
mine = dict(one.get("입력값") or {})
for n in names:
if mine.get(n) in EMPTY and common.get(n) not in EMPTY:
mine[n] = common[n]
one = {**one, "입력값": mine}
out.append(one)
return out
def _scaled(one: dict, q: Decimal, tag: dict) -> dict:
return {**one, "수량": sd.plain(_d(one.get("수량") or 0) * q), "본뜬": tag}
def sk_adds(y: dict, q: Decimal, whole: Any, tag: dict) -> list[dict]:
"""펼친 SK → 더한줄 — 바탕 기초 있으면 한 줄(몸 통째) · 없으면 그 더한줄 × q."""
from M02_MasterTemplete import M02_Sangeun_Calc as calc
adds = filled_adds(y, whole)
code = (y.get("바탕") or {}).get("로직")
if not code:
return [_scaled(one, q, tag) for one in adds]
row = whole.logic(code)
names = {str(s.get("이름")) for s in row.get("입력") or []}
unit = calc.base_unit(row)
entry: dict[str, Any] = {
"종류": "로직",
"로직": code,
"이름": y.get("이름") or code,
"단위": unit[0] if unit else "",
"수량": sd.plain(q / calc.ratio_of(y, row, whole)),
"입력값": {k: v for k, v in (y.get("입력값") or {}).items() if k in names},
}
entry |= {
k: y[k] for k in ("덮개", "뺀줄", "고름", "자재표시", "켜쌓기율") if y.get(k) not in EMPTY
}
return [{**entry, **({"더한줄": adds} if adds else {}), "본뜬": tag}]
def _hand(one: dict, kept: Any) -> Decimal:
"""SC 안 줄 수량 — 저장 `수량.손`(742-1 사용자 입력) › 실무수량 › 1."""
raw = one.get("수량")
got = raw.get("손") if isinstance(raw, dict) else None
return _d(got if got not in EMPTY else kept if kept not in EMPTY else 1)
def sc_adds(z: dict, q: Decimal, whole: Any, tag: dict) -> list[dict]:
"""펼친 SC → SK 더한줄 — 줄 수량 = q × `_hand` · 로직 줄 ÷ 물량 알약 배."""
from M02_MasterTemplete import M02_Labor_Patch as patch
from M02_MasterTemplete import M02_Sangeun_Calc as calc
hands = (z.get("본뜬") or {}).get("실무수량") or {}
lines, mats = hands.get("줄") or {}, hands.get("자재") or {}
out = []
for at, one in enumerate(z.get("담은로직") or []):
n = q * _hand(one, lines.get(str(at)))
if one.get("로직"):
row = whole.logic(one["로직"])
body = patch.as_doc(one, whole)
entry = {
"종류": "로직",
"로직": one["로직"],
"이름": one.get("별칭") or row.get("이름") or "",
}
entry |= {"수량": sd.plain(n / calc.times_of(row)), "입력값": body["입력값"]}
keep = ("덮개", "뺀줄", "고름", "자재표시", "켜쌓기율", "더한줄")
entry |= {k: body[k] for k in keep if body.get(k) not in EMPTY}
elif fixed.is_fixed(one) and not refs.is_rate(one):
entry = {
"고정": one["고정"],
"이름": one["고정"].get("이름") or "",
"수량": sd.plain(n),
}
elif refs.is_copy(one.get("참조")): # 하위 호표 사본 블록 = 그 줄들도 더한줄로
out += sc_adds(one["참조"]["몸"], n, whole, tag)
continue
else:
raise sd.DocError("비율 고정 줄 · 참조 줄은 단가산출에 못 담음")
out.append({**entry, "본뜬": tag})
for mat in z.get("자재") or []:
code = str(mat.get("코드") or "")
if not code.startswith("MT"):
raise sd.DocError(f"개인 자재 「{code}」 는 단가산출에 못 담음")
n = q * _hand(mat, mats.get(code))
one = {
"종류": "재료",
"요소": code,
"이름": mat.get("이름") or "",
"단위": mat.get("단위") or "",
}
apart = {"별도계상": True} if mat.get("별도계상") == "별도계상" else {}
out.append({**one, "수량": sd.plain(n), **apart, "본뜬": tag})
return out
# ── 겹침(바탕 SK · 패치 SK 참조) ─────────────────────────────────────────
def _merge(a: dict | None, b: dict) -> dict:
"""아래 덮개 a 위에 위 덮개 b — 품 qty 바꿈 · 배율 곱 · 단가 위 것."""
if not a:
return dict(b)
out = {k: v for x in (a, b) for k, v in x.items() if k in sd.PARTS}
if "qty" in b:
out["qty"] = b["qty"]
elif "배율" in b and "qty" in a:
out["qty"] = sd.plain(_d(a["qty"]) * _d(b["배율"]))
elif "배율" in b:
out["배율"] = sd.plain(_d(a.get("배율", 1)) * _d(b["배율"]))
else:
out |= {k: a[k] for k in ("qty", "배율") if k in a}
return out | ({"원값": a["원값"]} if "원값" in a else {})
def _cover_add(add: dict, cover: dict) -> dict:
"""위 덮개를 아래 더한줄 항목에 — 수량 · 그 비목 단가만(그 밖 = 못 겹침)."""
out = dict(add)
if "qty" in cover:
out["수량"] = cover["qty"]
elif "배율" in cover:
out["수량"] = sd.plain(_d(add.get("수량") or 0) * _d(cover["배율"]))
prices = {sd.PARTS[k]: cover[k] for k in sd.PARTS if k in cover}
if prices and (add.get("종류") not in sd.ADD_PART or set(prices) != {add.get("비목")}):
raise sd.DocError("더한줄 단가 덮개를 못 겹침")
if prices:
out["단가"] = prices[add["비목"]]
return out
def compose(under: dict, over: dict, whole: Any) -> dict:
"""펼친 바탕 SK `under` 위에 `over`(덮개 · 뺀줄 · 고름 · 자재표시 · 더한줄 · 입력값) → 한 벌 —
위 줄 정체를 아래 바탕 정체로 옮김(`patched` 의 `정체` 짝) · 짝 잃은 위 고침은 버림(값 없음)."""
base = under.get("바탕")
row = whole.logic(base["로직"]) if (base or {}).get("로직") else sd.empty_row(under)
got = sd.patched(row, under)
where = dict(zip(sd.line_ids(got["줄"]), got["정체"]))
covers = {c: dict(v) for c, v in (under.get("덮개") or {}).items()}
drop, picks = list(under.get("뺀줄") or []), dict(under.get("고름") or {})
marks, adds, gone = (
dict(under.get("자재표시") or {}),
[dict(x) for x in under.get("더한줄") or []],
set(),
)
def to(cid: str) -> tuple[str, Any] | None:
o = where.get(cid)
hit = ADD.match(o) if o else None
return None if o is None else ("add", int(hit[1])) if hit else ("base", o)
for cid, cover in (over.get("덮개") or {}).items():
if t := to(cid):
if t[0] == "base":
covers[t[1]] = _merge(covers.get(t[1]), cover)
else:
adds[t[1]] = _cover_add(adds[t[1]], cover)
for cid in over.get("뺀줄") or []:
if (t := to(cid)) and t[0] == "add":
gone.add(t[1])
elif t and t[1] not in drop:
drop.append(t[1])
for name, mine, into in (
("고름", over.get("고름"), picks),
("자재표시", over.get("자재표시"), marks),
):
for cid, value in (mine or {}).items():
if (t := to(cid)) and t[0] == "add":
raise sd.DocError(f"더한줄 {name}를 못 겹침")
if t:
into[t[1]] = value
rate = over.get("켜쌓기율", under.get("켜쌓기율"))
out = {**over, "바탕": base, "덮개": covers, "뺀줄": drop, "고름": picks, "자재표시": marks}
out["더한줄"] = [a for k, a in enumerate(adds) if k not in gone] + list(
over.get("더한줄") or []
)
out["입력값"] = {**(under.get("입력값") or {}), **(over.get("입력값") or {})}
return out | ({"켜쌓기율": rate} if rate is not None else {})
# ── SC ────────────────────────────────────────────────────────────────
def sk_line(y: dict, one: dict, whole: Any, tag: dict) -> dict:
"""펼친 SK → 일위대가 로직 줄 하나(+ 패치) — 옛 참조 줄 `one` 의 별칭 · 메모 · 수량 · 패치
이어받음."""
from M02_MasterTemplete import M02_Labor_Patch as patch
from M02_MasterTemplete import M02_Sangeun_Calc as calc
if one.get(patch.FIELD):
y = compose(y, {"입력값": {}, **one[patch.FIELD]}, whole)
code = (y.get("바탕") or {}).get("로직")
if not code:
raise sd.DocError("바탕 기초 없는 단가산출은 일위대가 줄로 못 담음 — 그 안 줄을 따로 넣음")
row = whole.logic(code)
if calc.ratio_of(y, row, whole) != calc.times_of(row):
raise sd.DocError("단위 환산 단가산출은 일위대가 줄로 못 담음(값 단위가 바뀜)")
ids = sd.line_ids(row)
names = {str(s.get("이름")) for s in row.get("입력") or []}
at = {c: str(n) for n, c in enumerate(ids)}
body = {k: y[k] for k in ("덮개", "뺀줄") if y.get(k) not in EMPTY}
body |= {"더한줄": adds} if (adds := filled_adds(y, whole)) else {}
line: dict[str, Any] = {
"로직": code,
"입력값": {k: v for k, v in (y.get("입력값") or {}).items() if k in names},
"별칭": one.get("별칭") or y.get("이름"),
"메모": one.get("메모"),
"수량": one.get("수량"),
}
for name in ("고름", "자재표시"):
if got := {at[c]: v for c, v in (y.get(name) or {}).items() if c in at}:
line[name] = got
if y.get("켜쌓기율") is not None:
line["켜쌓기율"] = y["켜쌓기율"]
return {**line, **({patch.FIELD: body} if body else {}), "본뜬": tag}
def _ids(doc: dict) -> list[Any]:
from M02_MasterTemplete import M02_Labor_Doc as labor
lines = [labor.line_id(x) for x in doc.get("담은로직") or [] if isinstance(x, dict)]
return lines + [f"자재|{m.get('코드')}" for m in doc.get("자재") or [] if isinstance(m, dict)]
def _twice(doc: dict) -> str | None:
seen: set[str] = set()
for one in _ids(doc):
key = json.dumps(one, ensure_ascii=False, sort_keys=True, default=str)
if key in seen:
return str(one)
seen.add(key)
return None
def flat_sc(doc: dict, read: Read, whole: Any, trail: tuple = (), strict: bool = True) -> dict:
"""SC 문서 → M01 키만 — SK 참조 = 로직 줄 + 패치(같은 자리) · SC 참조 = 사본 블록(같은 자리) ·
`strict` False = 못 펼친 줄 그대로(겹침 · 바탕 없음 · 환산 · 없는 참조)."""
lines = []
for at, one in enumerate(doc.get("담은로직") or []):
ref = one.get("참조") if isinstance(one, dict) else None
kind = refs.kind_of(ref.get("키")) if isinstance(ref, dict) else None
if kind not in ("sangeun", "precombo") or refs.is_copy(ref):
lines.append(one)
continue
try:
src, path = _read(ref, read, trail)
tag = mark(ref, src)
if kind == "sangeun":
y = flat_sk(sd.clean(src, None, whole), read, whole, path)
put = sk_line(y, one, whole, tag)
else:
body = flat_sc(src, read, whole, path)
put = {**one, "참조": refs.copy_ref(body, ref["키"]), "본뜬": tag}
rest = (doc.get("담은로직") or [])[at + 1 :]
trial = {"담은로직": [*lines, put, *rest], "자재": doc.get("자재") or []}
if twice := _twice(trial):
raise sd.DocError(f"같은 줄이 겹침 「{twice}」 — 펼쳐 넣지 못함")
except FAIL as e:
if strict:
raise sd.DocError(f"「{ref.get('키')}」 · {e}") from e
put = one
lines.append(put)
return {**doc, "담은로직": lines}
# ── 문 ────────────────────────────────────────────────────────────────
def flat_doc(
kind: str, doc: Any, read: Read = refs.read_ref, strict: bool = False, me: Any = None
) -> Any:
"""층 문서 하나 펼침(저장 · 읽기 · 복사 · 옮기기) — 참조 없으면 받은 그대로 · `me` = 이 문서
참조(자기 자신을 담은 줄 = 안 펼침 → 저장 검사가 422)."""
if not isinstance(doc, dict) or kind not in refs.PREFIX or not refs.refs_in(doc):
return doc
whole, trail = _whole(), (refs.ref_id(me),) if isinstance(me, dict) else ()
if kind == "sangeun":
return flat_sk(doc, read, whole, trail, strict)
return flat_sc(doc, read, whole, trail, strict)
def flat_lines(kind: str, ref: Any, qty: Any = 1, doc: Any = None) -> dict[str, Any]:
"""찾아 넣기(`-flat` 길) — `kind` 문서에 `ref` 를 넣을 줄 · 못 펼침 = StoreError 400 ·
SC = 로직 줄(SK) · 사본 블록 줄(SC) 하나 · 지금 화면 문서 `doc` 과 겹치면 400."""
from M02_MasterTemplete import M02_MasterTemplete_Store as store
at = refs.here()
try:
ref = refs.check_ref(ref, "넣을 문서", at)
whole = _whole()
if kind == "sangeun":
return {"더한줄": into_sk(ref, qty if qty not in EMPTY else 1, refs.read_ref, whole)}
got = flat_sc({"담은로직": [{"참조": ref, "수량": None}]}, refs.read_ref, whole)
host = doc if isinstance(doc, dict) else {}
trial = {"담은로직": [*(host.get("담은로직") or []), *got["담은로직"]]}
if twice := _twice({**trial, "자재": host.get("자재") or []}):
raise sd.DocError(f"이미 담은 줄과 겹침 「{twice}」")
return {"담은로직": got["담은로직"], "자재": []}
except FAIL as e:
raise store.StoreError(getattr(e, "status", 400), str(e)) from e
# ── 초기화 ────────────────────────────────────────────────────────────
def _reprice(fx: dict) -> tuple[dict, int]:
"""이음 기계 고정 줄(736-3) 단가 = M01 기계 한 시간 값 다시 받음 — (줄, 바뀐 수)."""
from M02_MasterTemplete import M02_Machine_Hourly as machine
key = (fx.get("이음") or {}).get("기계")
got = machine.hourly(str(key)) if key else {}
if not got.get("ok"):
return fx, 0
price = {k: got[k] for k in sd.PARTS.values()}
return {**fx, "단가": price}, int(price != fx.get("단가"))
def _reset_adds(adds: list) -> tuple[list, int]:
out, n = [], 0
for one in adds or []:
one = dict(one) if isinstance(one, dict) else one
if not isinstance(one, dict):
out.append(one)
continue
if "단가" in one:
one.pop("단가")
n += 1
n += len(one.pop("덮개", None) or {}) + len(one.pop("뺀줄", None) or [])
one.pop("바탕판", None)
if one.get("더한줄"):
one["더한줄"], k = _reset_adds(one["더한줄"])
n += k
if isinstance(one.get("고정"), dict):
one["고정"], k = _reprice(one["고정"])
n += k
out.append(one)
return out, n
def _reset_sc(doc: dict) -> tuple[dict, int]:
"""일위대가 초기화 — 줄 패치 · 사본 블록 몸 · 이음 기계 고정 줄."""
from M02_MasterTemplete import M02_Labor_Patch as patch
lines, n = [], 0
for one in doc.get("담은로직") or []:
if not isinstance(one, dict):
lines.append(one)
continue
ref, body = one.get("참조"), one.get(patch.FIELD)
if refs.is_copy(ref):
inner, k = _reset_sc(ref["몸"])
n += k
one = {**one, "참조": refs.copy_ref(inner, ref["키"])}
elif isinstance(one.get("고정"), dict):
fx, k = _reprice(one["고정"])
n += k
one = {**one, "고정": fx}
elif body:
adds, k = _reset_adds(body.get("더한줄") or [])
n += k + len(body.get("덮개") or {}) + len(body.get("뺀줄") or [])
keep = {x: v for x, v in body.items() if x not in ("덮개", "뺀줄", "더한줄")}
rest = {**keep, **({"더한줄": adds} if adds else {})}
one = {x: v for x, v in one.items() if x != patch.FIELD}
one = {**one, **({patch.FIELD: rest} if rest else {})}
lines.append(one)
return {**doc, "담은로직": lines}, n
def reset(kind: str, doc: dict) -> tuple[dict, int]:
"""[초기화] — (걷은 문서, 걷힌 고침 수) · 먼저 펼침(옛 참조 줄도 M01 키로)."""
doc = flat_doc(kind, doc)
if kind == "sangeun":
adds, n = _reset_adds(doc.get("더한줄") or [])
n += len(doc.get("덮개") or {}) + len(doc.get("뺀줄") or [])
return {**doc, "덮개": {}, "뺀줄": [], "더한줄": adds}, n
return _reset_sc(doc)
def reset_doc(kind: str, key: str | None, doc: Any, look: bool, version: str | None) -> dict:
"""`-reset` 길 — 미리 보기 = 걷힐 수 · 아니면 지금 층에 씀(판이 오면 그 판일 때만)."""
from M02_MasterTemplete import M02_MasterTemplete_Store as store
if doc is None:
if not key:
raise store.StoreError(400, "문서 또는 키")
found = store.read(kind, key)
doc, version = found["문서"], version or found["판"]
if not isinstance(doc, dict):
raise store.StoreError(400, "문서가 묶음 아님")
got, n = reset(kind, doc)
if look:
return {"걷힘": n, "문서": got}
name = str(got.get("키") or key or "")
if not version:
version = store.read(kind, name)["판"]
store.write(kind, name, version, got)
return {"걷힘": n, "문서": store.read(kind, name)["문서"]}
# ── 옮기기 ────────────────────────────────────────────────────────────
def _value(ref: refs.Ref, read: Read, whole: Any) -> Any:
from M02_MasterTemplete.M02_Labor_Expand import Expand, inner_pills
given = {p["키"]: 1 for p in inner_pills(ref, read)}
got = Expand(whole, read, given)(ref).get("기준")
return None if got is None else {k: round(float(v), 6) for k, v in got.items()}
def migrate(apply: bool = False) -> list[dict[str, Any]]:
"""저장소 층(마스터 · 회사 · 개인) SK · SC 의 M02 참조 줄을 펼쳐 다시 씀 — 줄마다 결과
{어디, 키, 펼침, 같음, 씀, 까닭} · `apply` False = 보기만."""
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_Template_Layers as layers
whole, out = _whole(), []
for kind in ("sangeun", "precombo"):
for where_, path in layers.layer_files(kind):
try:
doc = json.loads(path.read_bytes())
except (OSError, ValueError):
continue
if not refs.refs_in(doc):
continue
layer, owner = refs.where(path.parent.parent)
ref = {"층": layer, "주인": owner, "키": path.stem}
got = flat_doc(kind, doc)
left = [
r.get("키")
for r in refs.refs_in(got)
if refs.kind_of(r.get("키")) != "promote" and not refs.is_copy(r)
]
row = {
"어디": where_,
"키": path.stem,
"펼침": left == [],
"남은참조": left,
"씀": False,
}
def mine(r: refs.Ref, _got: dict = got, _rid: str = refs.ref_id(ref)) -> dict | None:
return _got if refs.ref_id(r) == _rid else refs.read_ref(r)
row["전"], row["후"] = _value(ref, refs.read_ref, whole), _value(ref, mine, whole)
row["같음"] = row["전"] == row["후"]
if not row["같음"]:
row["까닭"] = "전후 금액 다름 — 안 옮김"
elif apply and got != doc:
with store.at(path.parent.parent):
store.write(kind, path.stem, store.read(kind, path.stem)["판"], got)
row["씀"] = True
out.append(row)
return out
if __name__ == "__main__": # 브레인 — `python -m M02_MasterTemplete.M02_Library_Flat [--apply]`
import sys
for one in migrate("--apply" in sys.argv):
print(json.dumps(one, ensure_ascii=False, default=str))