- 끝냄 한 길 M02_Hopyo_Forget.finish — HP · 격자 · 맞춤셈 캐시 지움 · 만든 SC · SK 본뜬 = 출처(파일 · 때 · 누가 + 전국자재 · 복사원본)만 · SC 줄 메모 「실무 …」 걷음 · 실무수량 = 저장 수량.손 과 같을 때만 걷음(742 전 저장본은 남김 = 금액 그대로) · SK 또 = 그 HP 몫만 - 같은 엑셀 다시 올림 409 걷음(끝낸 기록 지문 안 씀 · 키 번은 그대로 이어감) - 하루 지난 HP = 목록 숨김 · 올림 때 끝냄 · 붙여넣기 만듦 뒤 실무자료 끝냄 - 마법사 떠남(다른 화면 · 페이지 닫음) = 이 화면이 쥔 파일 지움(keepalive DELETE) - 뒤 셈이 끝낸 HP 캐시를 다시 쓰지 않음(Fit._put) · 끝냄은 HP 먼저 지우고 캐시 - 옛 파일 정리 tidy(미리 보기 표 · --apply) — 옛 HP · 지문 기록 · HP 없는 문서 사본 - 본뜬 곳 칸 = 끝낸 실무자료는 「실무자료 파일」(originText) - 시험: 718-1 「끝낸 같은 엑셀 409」 → 「다시 올림 새 키」 · 705-2 붙여넣기 실무자료 「남음」 → 「지움」(요구 584-2 로 뜻 바뀜) · 372-2 화면 시험 onDeleted 꼴 바뀜 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
206 lines
9.3 KiB
Python
206 lines
9.3 KiB
Python
"""M02 일위대가 불러오기 끝냄(PLAN 743-1 · 요구 582-3 · 584-2) — 올린 엑셀은 그 불러오기 동안만.
|
|
|
|
끝냄 한 길 `finish` = [끝내기] · 화면 떠남 · 하루 지남(`Store.EXPIRE`) · 붙여넣기 만듦 뒤 ·
|
|
엑셀 보기 [삭제]:
|
|
HP · 격자 · 맞춤셈 캐시 지움 · 그 HP 로 만든 이 층 일위대가(SC) · 단가산출(SK) 엑셀 사본 걷음
|
|
(`본뜬` → 출처 `{층 「실무자료」, 파일, 때, 누가}` + 전국자재 · 복사원본 ·
|
|
SC 줄 메모 「실무 …」 걷음 · `본뜬.실무수량` = 모든 값이 그 줄 저장 `수량.손` 과 같을 때만
|
|
걷음(742-1 · 다르면 남김 = 금액 그대로)) · SK `본뜬.또` = 그 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_sc(doc) if kind == "precombo" else strip_sk(doc, hit)
|
|
if new != doc:
|
|
out.append(path.stem)
|
|
if apply:
|
|
atomic_write_json(path, new)
|
|
return out
|
|
|
|
|
|
def _caches(folder: Path, key: str) -> list[Path]:
|
|
from M02_MasterTemplete import M02_Hopyo_Fit as fit
|
|
|
|
try:
|
|
got = fit.cache_path(folder, key)
|
|
except Exception: # noqa: BLE001 — 캐시 자리 못 찾음 = 지울 것 없음
|
|
return []
|
|
return list(got.parent.glob(f"{'_'.join(got.name.split('_')[:2])}_*.json"))
|
|
|
|
|
|
def finish(folder: str | Path, key: str) -> dict[str, Any]:
|
|
"""불러오기 끝냄 — 만든 문서 사본 걷음 · HP · 격자 · 맞춤셈 캐시 지움 → `{지움, 만든}`."""
|
|
folder = Path(folder)
|
|
hopyo_store.read(folder, key) # 없으면 404
|
|
head = f"{key} "
|
|
made = forget(folder, lambda mark: mark.startswith(head))
|
|
caches = _caches(folder, key) # HP 지우기 전에 자리 셈(HP 지문)
|
|
hopyo_store.delete(folder, key) # 먼저 지움 — 뒤 셈은 HP 없으면 캐시 안 씀(`Fit._put`)
|
|
# ponytail: 뒤 셈이 HP 확인 직후 · 쓰기 직전이면 한 벌 남을 수 있음(ms 틈) — tmp 캐시라 둠
|
|
for old in caches:
|
|
old.unlink(missing_ok=True)
|
|
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:]))
|