"""옛 로직 사본 변수표 새로 쓰기 — 한 번 돌리는 도구(PLAN 800-5 · 요구 660-1 · 659-1). 옛 사본(795-5 이전에 찍힌 것) 줄은 변수표(이름 · 기호 · 아래 · 위)가 비었거나 한글 그대로 → 수식 영역에 한글 기호가 보임. 지금 M01 줄의 변수표로 그 줄 변수만 채움: · 사본 줄에 이미 정한 기호(영문 · 그리스 · 사용자가 고친 것)가 있는 칸 = 그대로 · 기호가 비었거나 열쇠 그대로(한글)인 칸 = M01 칸(이름 · 기호 · 아래 · 위)으로 · M01 에 그 줄 · 그 변수가 없으면 건드리지 않음(= 목록 「못닿음」) 금액 · 셈 = 변수표는 보이기만 → 전후 금액 대조(같을 때만 씀) · 사본 `판`은 줄 지문으로 다시 셈 (M01 줄과 같아지면 `M01새판` 표지도 걷힘). 되돌릴 길 = 쓰기 전 파일 그대로 `--backup` 폴더에(`M02_Copy_Migrate --restore` 로 덮음). 프로젝트 층 문서는 건드리지 않음(굳힌 사본). ./venv/Scripts/python.exe -m M02_MasterTemplete.M02_Copy_Symbols # 보기만 ./venv/Scripts/python.exe -m M02_MasterTemplete.M02_Copy_Symbols --apply --backup tmp/800_5 """ from __future__ import annotations import json import shutil import sys from pathlib import Path from typing import Any from common_util.common_util_json import atomic_write_json from M02_MasterTemplete import M02_Copy_Migrate as mig from M02_MasterTemplete import M02_Sangeun_Copy as skc FIELD = "변수표" # master_equation.FIELD 와 같은 칸 LOOK = ("이름", "기호", "아래", "위") def _open(entry: Any, name: str) -> bool: """사본 칸이 채울 자리 — 없음 · 기호 없음 · 열쇠 그대로(한글 기본).""" sign = str((entry or {}).get("기호") or "") return not sign or sign == name def fill(copy: dict, whole: Any) -> tuple[dict, int, int]: """(새 사본, 채운 칸 수, 못 닿은 변수 수) — 줄 `변수표`만 고치고 `판` 다시 셈.""" rows, done, lost = [], 0, 0 for row in copy["로직"]: key = str(row.get("키")) live = whole.index.get(key[:2], {}).get(key) or {} src = live.get(FIELD) if isinstance(live.get(FIELD), dict) else {} mine = dict(row.get(FIELD) or {}) for part in ("입력", "중간"): for spec in row.get(part) or []: name = str(spec.get("이름")) new = (src or {}).get(name) if not _open(mine.get(name), name): continue if not isinstance(new, dict) or not new.get("기호"): lost += 1 continue mine[name] = {**(mine.get(name) or {}), **{k: new[k] for k in LOOK if k in new}} done += 1 rows.append({**row, FIELD: mine} if mine != (row.get(FIELD) or {}) else row) return {**copy, "로직": rows, "판": skc._fp(rows)}, done, lost def check(kind: str, path: Path, fill_fn=None) -> dict[str, Any]: """문서 하나 — {상태: 있음 | 채울것없음 | 같음 | 다름 | 못셈, 채움, 못닿음, 사본?} · `fill_fn` = 다른 고침(801-5 단위 글).""" from M02_MasterTemplete import M02_MasterTemplete_Store as store doc = json.loads(path.read_bytes()) if not skc.has(doc): return {"상태": "사본없음"} try: with store.at(path.parent.parent): live = skc.whole_() new, done, lost = (fill_fn or fill)(doc[skc.KEY], live) if not done: return {"상태": "채울것없음", "못닿음": lost} clean = mig._clean(kind, doc, path.stem) before = mig._money(kind, clean, skc.over({skc.KEY: doc[skc.KEY]}, live)) after = mig._money(kind, clean, skc.over({skc.KEY: new}, live)) except Exception as e: # noqa: BLE001 — 못 셈 = 목록(통째로 멈추지 않음) return {"상태": "못셈", "까닭": str(e) or type(e).__name__} row = {"상태": "같음" if before == after else "다름", "채움": done, "못닿음": lost} return {**row, "사본": new} if before == after else {**row, "전": before, "후": after} def run(apply: bool = False, backup: Path | None = None, fill_fn=None) -> list[dict[str, Any]]: """마스터 · 회사 · 개인 층 문서마다 {어디, 종류, 키, 상태, 씀, …} · `apply` = 같음만 씀.""" if apply and backup is None: raise ValueError("--apply 는 --backup 폴더가 있어야 함") out = [] for where_, kind, path, project in mig._files(): if project: continue got = check(kind, path, fill_fn) copy = got.pop("사본", None) row = {"어디": where_, "종류": kind, "키": path.stem, **got, "씀": False} if apply and copy: keep = mig._backup_path(path, backup) keep.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(path, keep) atomic_write_json(path, {**json.loads(path.read_bytes()), skc.KEY: copy}) row["씀"] = True out.append(row) return out if __name__ == "__main__": # 브레인 지시 뒤만 --apply args = sys.argv[1:] at = Path(args[args.index("--backup") + 1]) if "--backup" in args else None rows = run("--apply" in args, at) for one in rows: if one["상태"] not in ("사본없음", "채울것없음", "같음"): print(json.dumps(one, ensure_ascii=False, default=str)) count: dict[str, int] = {} for one in rows: count[one["상태"]] = count.get(one["상태"], 0) + 1 print(json.dumps(count, ensure_ascii=False), "채움", sum(r.get("채움", 0) for r in rows))