Files
eomsangdonandClaude Opus 5.5 48848bb287 refactor(785-7): 옛 맞춤 층 Python 지움 · 서버 새 길로 정리 · 낡은 시험 정리
- 지움(git rm) 10 파일 3,374 줄: M02_Hopyo_Finger · Ours · Option · Fit · Combine · Verdict · Line · SkLink · SkPatch · Basis
- Match: 흐름 몫(candidates · name_cands · fix_typo · clause · b_order · _logic_specs · _line_mod · units_of · _state · view · guess · picks · chain) 지움 · 이름 도구 몫 · line_key 남김
- Words: conds · cond_score · close · cosine · jacc 지움 / Shares: sections 지움(trade · eq_key 남김)
- 옮김: Finger.kind_of → Fixed.kind_of · SkLink(sk_line · kept · left · LEFT · WHY) → Sangeun · SkPatch 기계 몫(MACH_IN · MACHINE · SOLO · _mach_logics) → Units.mach_logics
- Sangeun: 옛 /sangeun 몸(build · _sure · Maker.basis · judge · plan · make) 지움 · Kids: first · proposal · order 지움 · Build: build() · unlinked() 지움(모두 고정 Builder · build_all · _order · _origins 남김)
- Router: 옛 길 지움(GET hopyo/{키}/{번} · guess · hopyo-picks · hopyo-build · hopyo-unlinked · hopyo/{키}/sangeun) · GET hopyo/{키} = Judge.view(1차 줄 판정 줄상태 · 만든 · 블록 풀이 셈)
- Marks: 칠 = Judge.judged 만 · Forget: 맞춤 캐시 지움 줄 걷음 · Ledger: Ours.version 판 칸 걷음 · Item: Kids.first 걷음
- Row(덧): 단산 줄 = 장부 짝이 있어도 늘 「2차 필요」(장부 = 1차 후보 힌트만)

시험 정리(까닭 = 지운 길 · 모듈만 시험함):
- 지움 11 파일: test_m02_hopyo_finger · fit · golden · option · ours · basis · seam · flow · draft_calc · test_635 · test_628(지문 · 순위 · 옵션 거꾸로 · 맡은 절 · 옛 확인 흐름)
- 함수 지움: /sangeun · /hopyo-build · /hopyo-unlinked · hopyo-picks · 옛 줄 맞춤 보기를 부르던 것(572 · 718_1 · 729 · 739_1 · 777_1 · 778_5 · 780_1 · labor_national · 705_3 · m02_hopyo_match · 746_1)
- test_621 S2(지문 해시) 지움 · S1 남김 · test_m02_hopyo_build = 장부 길 시험만(PUT 으로 심음)
- 새 길로: 779_2(줄상태 = 1차 판정 · 칠 셈 전 None) · grid · stmate(hopyo-judge 줄 판) · 778_1 · 778_2(Units.mach_logics) · 642_6(Marks 원문) · 642_3 장부 짝 단산 줄 2차 지킴

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

196 lines
8.9 KiB
Python

"""M02 일위대가 불러오기 끝냄(PLAN 743-1 · 요구 582-3 · 584-2) — 올린 엑셀은 그 불러오기 동안만.
끝냄 한 길 `finish` = [끝내기] · 화면 떠남 · 하루 지남(`Store.EXPIRE`) · 붙여넣기 만듦 뒤 ·
엑셀 보기 [삭제]:
HP · 격자 지움(블록 풀이 저장본 = Router 가 `Judge.forget`) · 그 HP 로 만든 이 층 일위대가(SC) · 단가산출(SK) 엑셀 사본 걷음
(`본뜬` → 출처 `{층 「실무자료」, 파일, 때, 누가}` + 전국자재 · 복사원본 ·
SC 줄 메모 「실무 …」 걷음 · `본뜬.실무수량` = 모든 값이 그 줄 저장 `수량.손` 과 같을 때만
걷음(742-1 · 다르면 남김 = 금액 그대로)) · SK · SC `본뜬.또` = 그 HP 몫만 뺌 ·
문서 몸(줄 · 덮개 · 더한줄 · 별칭 · 구분)과 별칭 장부는 그대로 ·
끝낸 기록 없음(같은 엑셀 다시 올림 됨 · 키 번은 `불러옴.json` 번으로 다시 안 씀).
옛 파일 정리 `tidy`(한 번 · 층 폴더마다 · 미리 보기 = 표만) — 옛 HP(지문 없음) · 하루 지난 HP 끝냄 ·
끝낸 기록 지문 비움 · HP 가 없는 SC · SK 엑셀 사본 걷음.
`python -m M02_MasterTemplete.M02_Hopyo_Forget <층 폴더>… [--apply]`(없으면 미리 보기).
"""
from __future__ import annotations
import json
import re
import sys
from collections.abc import Callable
from datetime import datetime
from pathlib import Path
from typing import Any
from common_util.common_util_json import atomic_write_json
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
from M02_MasterTemplete import M02_Labor_Ref as refs
ORIGIN = "실무자료"
KEEP = ("층", "파일", "전국자재", "복사원본")
_MEMO = re.compile(r"^실무 ")
_HP = re.compile(r"^(HP-\d{6}) ")
# ── 사본 걷음(순수) ───────────────────────────────────────────────────
def _slim(got: dict[str, Any]) -> dict[str, Any]:
"""출처만 — 파일 · 때 · 누가(+ 전국자재 · 복사원본)."""
sure = got.get("확인") if isinstance(got.get("확인"), dict) else {}
out = {k: got[k] for k in KEEP if got.get(k) is not None}
return {**out, "층": ORIGIN, "때": sure.get("때"), "누가": sure.get("누가")}
def _hand(one: Any) -> Any:
qty = one.get("수량") if isinstance(one, dict) else None
return qty.get("손") if isinstance(qty, dict) else None
def _carried(doc: dict[str, Any], hands: dict[str, Any]) -> bool:
"""실무수량 값이 모두 그 줄 · 자재 저장 수량과 같음(742-1 저장 뒤)."""
lines = doc.get("담은로직") or []
mats = {m.get("코드"): m for m in doc.get("자재") or [] if isinstance(m, dict)}
for at, value in (hands.get("줄") or {}).items():
i = int(at) if str(at).isdigit() else -1
if not 0 <= i < len(lines) or _hand(lines[i]) != value:
return False
return all(_hand(mats.get(code)) == value for code, value in (hands.get("자재") or {}).items())
def strip_sc(doc: dict[str, Any]) -> dict[str, Any]:
"""일위대가(SC) 엑셀 사본 걷음 — 본뜬 출처만 · 줄 메모 「실무 …」 뺌."""
got = doc.get("본뜬") or {}
lines = [
{k: v for k, v in one.items() if k != "메모"}
if isinstance(one, dict) and _MEMO.match(str(one.get("메모") or ""))
else one
for one in doc.get("담은로직") or []
]
out = {**doc, "담은로직": lines, "본뜬": _slim(got)}
hands = got.get("실무수량")
if isinstance(hands, dict) and hands and not _carried(out, hands):
out["본뜬"]["실무수량"] = hands # 742 전 저장본 — 셈이 기대므로 남김
return out
def strip_sk(doc: dict[str, Any], gone: Callable[[dict], bool]) -> dict[str, Any]:
"""단가산출(SK) — 끝낸 출처(`gone(본뜬 · 또 한 칸)`) 몫만 걷음 · 다른 불러오기 몫은 그대로."""
got = doc.get("본뜬") or {}
also = [x for x in got.get("또") or [] if isinstance(x, dict) and not gone(x)]
own = _slim(got) if gone(got) else {k: v for k, v in got.items() if k != "또"}
return {**doc, "본뜬": {**own, "또": also} if also else own}
# ── 끝냄 ─────────────────────────────────────────────────────────────
def _docs(folder: Path, kind: str):
prefix = "SC" if kind == "precombo" else "SK"
for path in sorted((folder / kind).glob(f"{prefix}-*.json")):
try:
doc = json.loads(path.read_bytes())
except (OSError, ValueError):
continue
got = doc.get("본뜬") if isinstance(doc, dict) else None
if isinstance(got, dict) and got.get("층") == ORIGIN:
yield path, doc
def _here(got: dict[str, Any], here: refs.Here) -> bool:
"""출처 원본이 이 층(원본 없는 옛 것 = 이 층)."""
return not got.get("원본") or tuple(got["원본"]) == tuple(here)
def forget(folder: str | Path, gone: Callable[[str], bool], apply: bool = True) -> list[str]:
"""`gone(본뜬 키)` 인 이 층 SC · SK 엑셀 사본 걷음 → 걷은 문서 키(`apply` 거짓 = 셈만)."""
folder, out = Path(folder), []
here = refs.where(folder)
def hit(one: dict[str, Any]) -> bool:
mark = str(one.get("키") or "")
return bool(mark) and _here(one, here) and gone(mark)
for kind in ("precombo", "sangeun"):
for path, doc in _docs(folder, kind):
got = doc["본뜬"]
if not any(hit(x) for x in [got, *(got.get("또") or [])] if isinstance(x, dict)):
continue
new = strip_sk(doc, hit) if kind == "sangeun" or not hit(got) else strip_sc(doc)
if kind == "precombo" and (
also := [x for x in got.get("또") or [] if isinstance(x, dict) and not hit(x)]
):
new["본뜬"]["또"] = also # 777-1 같은 내용 SC 를 나눠 쓴 다른 불러오기 몫은 그대로
if new != doc:
out.append(path.stem)
if apply:
atomic_write_json(path, new)
return out
def finish(folder: str | Path, key: str) -> dict[str, Any]:
"""불러오기 끝냄 — 만든 문서 사본 걷음 · HP · 격자 지움 → `{지움, 만든}`."""
folder = Path(folder)
hopyo_store.read(folder, key, old=True) # 없으면 404 · 옛 판도 끝냄(776-2)
head = f"{key} "
made = forget(folder, lambda mark: mark.startswith(head))
hopyo_store.delete(folder, key)
return {"지움": key, "만든": len(made)}
def expired(folder: str | Path, now: datetime | None = None) -> list[str]:
"""하루 지난 작업 중 HP 키(`M02_Hopyo_Store.stale`)."""
out = []
for path in sorted((Path(folder) / hopyo_store.DIRNAME).glob("HP-*.json")):
doc = hopyo_store._load(path)
if isinstance(doc, dict) and doc.get("지문") and hopyo_store.stale(doc, now):
out.append(path.stem)
return out
def sweep(folder: str | Path, now: datetime | None = None) -> list[str]:
"""하루 지난 HP 끝냄 → 끝낸 키."""
for key in (got := expired(folder, now)):
finish(folder, key)
return got
# ── 옛 파일 정리(한 번) ───────────────────────────────────────────────
def tidy(folder: str | Path, apply: bool = False) -> list[dict[str, str]]:
"""옛 실무자료 정리 표 `[{할일, 대상}]` — `apply` 거짓 = 미리 보기(쓰기 없음)."""
folder = Path(folder)
home = folder / hopyo_store.DIRNAME
rows: list[dict[str, str]] = []
every = sorted(p.stem for p in home.glob("HP-*.json"))
old = [k for k in every if not (hopyo_store._load(home / f"{k}.json") or {}).get("지문")]
ended = [*old, *expired(folder)]
live = set(every) - set(ended)
for key in ended:
rows.append({"할일": "HP 끝냄(옛 · 하루 지남)", "대상": f"{hopyo_store.DIRNAME}/{key}"})
if apply:
finish(folder, key)
log = hopyo_store.done(folder)
if log["지문"]:
rows.append({"할일": f"끝낸 기록 지문 {len(log['지문'])} 비움", "대상": "불러옴.json"})
if apply:
hopyo_store._put_done(folder, {"번": log["번"]})
def gone(mark: str) -> bool:
m = _HP.match(mark)
return bool(m) and m.group(1) not in live
for key in forget(folder, gone, apply):
rows.append({"할일": "엑셀 사본 걷음(출처만)", "대상": key})
return rows
def main(argv: list[str]) -> int:
apply = "--apply" in argv
for one in [a for a in argv if a != "--apply"]:
rows = tidy(one, apply)
print(f"# {one} — {'고침' if apply else '미리 보기'} {len(rows)}")
for row in rows:
print(f"{row['할일']}\t{row['대상']}")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))