"""근거 제안 길 · 저장본(PLAN 793-3a) — 서버 단독 · 읽기만(HP 는 안 고침). 올림 = HP 에 `숨은식 {번: [G 식]}`(STmate 단가산출근거 숨은 G 열 · `hidden`) + 뒤 셈(`ensure`). 저장본 = HP 폴더 `_derived/san_basis_{키}` · 입력 지문 = HP(지문 · 판 · 올린때) + M01 역색인 지문 + 판 · 지문 다를 때만 다시 셈 · GET 은 안 지움 · [끝내기] = `forget`. 답 꼴 = `listing` {셈중, 블록 {번: 제안}, 보강 [{이름, 품셈, 까닭, 블록 [번]}]} · `block` = 제안 하나 (`M02_San_Basis.suggest` 꼴 — 단 · 꼴 · 근거후보 · 절 · 보강?). """ from __future__ import annotations import shutil import threading from pathlib import Path from typing import Any from common_util import common_util_derived_store as derived from M02_MasterTemplete import M02_Hopyo_Judge as judge from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store from M02_MasterTemplete import M02_San_Basis as basis from M02_MasterTemplete import M02_San_Basis_Check as chk from M02_MasterTemplete import M02_San_Basis_Index as bi KIND = "san_basis_" VERSION = "794-2b.1" # 제안 꼴이 바뀌면 올림 SHEET = "단가산출근거" _LOCK, _RUNNING = threading.Lock(), set() def hidden(book: dict[str, Any], sheets: dict[str, list[list[Any]]]) -> dict[str, list[str]]: """STmate 블록마다 숨은 G 열 원문 식(「_x0007_」 머리 줄만) · 그 밖 = {}.""" rows = sheets.get(SHEET) or [] if book.get("꼴") != "STmate" or not rows: return {} out = {} for blk in book.get("단산") or []: start, end = blk["행"] got = [] for r in range(start, end + 1): row = rows[r - 1] if 0 < r <= len(rows) else [] v = str(row[6]) if len(row) > 6 and row[6] is not None else "" if v.startswith("_x0007_") and v != "_x0007_": got.append(v.replace("_x0007_", "").strip()) out[str(blk["번"])] = got return out def one(book: dict[str, Any], no: int) -> dict[str, Any]: blk = next(b for b in book.get("단산") or [] if int(b["번"]) == int(no)) line, same = judge._caller(book, int(no)) g = (book.get("숨은식") or {}).get(str(no)) out = basis.suggest(blk, book.get("꼴") or "", line, same, g) return chk.edition(out, book.get("파일"), blk) # 794-2b 판 표지 def _stamp(key: str, book: dict[str, Any]) -> str: return derived.derived_key( VERSION, key, book.get("지문"), book.get("판"), book.get("올린때"), bi.stamp() ) def solved(folder: Path, key: str) -> dict[str, Any] | None: book = hopyo_store.read(folder, key) got = derived.read_derived(hopyo_store._dir(folder), KIND + key, _stamp(key, book)) return got["value"] if got else None def solve_all(folder: Path, key: str) -> dict[str, Any]: book = hopyo_store.read(folder, key) out = { str(b["번"]): one(book, int(b["번"])) for b in book.get("단산") or [] if b.get("번") is not None } derived.write_derived(hopyo_store._dir(folder), KIND + key, _stamp(key, book), out, keep=1) return out def _work(folder: Path, key: str) -> None: try: solve_all(folder, key) except Exception: # noqa: BLE001 — 뒤 셈 실패 = 다음 읽기에서 다시(파생 저장본) pass finally: with _LOCK: _RUNNING.discard(f"{folder}|{key}") def ensure(folder: Path, key: str) -> bool: """저장본 있음 = 참 · 없으면 뒤 셈 시작(같은 파일 한 번) = 거짓.""" if solved(folder, key) is not None: return True tag = f"{folder}|{key}" with _LOCK: if tag in _RUNNING: return False _RUNNING.add(tag) threading.Thread(target=_work, args=(Path(folder), key), daemon=True).start() return False def shortfall(got: dict[str, Any]) -> list[dict[str, Any]]: """없음 블록 → M01 보강 목록(794-2 근거) — 이름 · 품셈 묶음마다 블록 번.""" out: dict[tuple, dict] = {} for no, r in got.items(): if r.get("보강"): b = r["보강"] one_ = out.setdefault((b["이름"], tuple(b["품셈"])), {**b, "블록": []}) one_["블록"].append(int(no)) return sorted(out.values(), key=lambda x: (-len(x["블록"]), x["이름"])) def _others(folder: Path, key: str) -> list[dict[str, Any]]: """같은 폴더 다른 HP 저장본(셈 끝난 것만) — 판 차이 강함(794-2b) 대조.""" out = [] for r in hopyo_store.rows(folder): if r.get("키") and r["키"] != key: try: got = solved(folder, r["키"]) except Exception: # noqa: BLE001 — 못 읽는 HP = 대조에서 뺌 got = None if got: out.append(got) return out def listing(folder: Path, key: str) -> dict[str, Any]: """{셈중, 블록 {번: 제안}, 보강} — 셈 중 = 블록 {} · 뒤 셈 시작.""" got = solved(folder, key) if got is None: ensure(folder, key) return {"키": key, "셈중": True, "블록": {}, "보강": []} got = chk.strong(got, _others(folder, key)) return {"키": key, "셈중": False, "블록": got, "보강": shortfall(got)} def block(folder: Path, key: str, no: int) -> dict[str, Any]: """블록 하나 — 저장본 › 그 자리 셈.""" got = (solved(folder, key) or {}).get(str(no)) if got is not None: return chk.strong({str(no): got}, _others(folder, key))[str(no)] book = hopyo_store.read(folder, key) if not any(b.get("번") is not None and int(b["번"]) == int(no) for b in book.get("단산") or []): raise judge._bad(f"없는 블록 「단산{no}」", 404) return one(book, no) def forget(folder: Path, key: str) -> None: shutil.rmtree(derived.derived_dir(hopyo_store._dir(folder), KIND + key), ignore_errors=True)