"""연결고리 표(`work_item_link`)를 읽는 **한 자리** — 산림·건설 어느 품셈 품을 쓸지 **표가 정함** (2026-09-17 브레인 · 코덱스 크로스체크 ② 「연결고리 미소비」). 고시 총칙 7 「본 품셈에 명시되지 않은 품은 타 부문 표준품셈 적용 · 유사 공종은 본 품셈 우선」 — 그 규칙은 표의 `policy`(`both_precedence` · `unconfirmed_action`)에 있고 코드는 **읽기만** 함(「산림 우선」을 코드에 안 박음). 확정 · both → `both_precedence` 쪽 열쇠(지금 표는 forest) 확정 · 한쪽만 → 그 열쇠 그대로 미판정 → **안 고름** · 사유를 붙여 막음(`unconfirmed_action: do_not_link`) 표에 없음 → 제 품셈 그대로(품셈에 명시된 품) ⚠ 미판정 행 가운데 열쇠가 빈 행(여러 절 묶음)은 줄에 못 닿음 — `undecided_links()` 로 목록만 드러냄. """ from __future__ import annotations import json from dataclasses import dataclass from functools import lru_cache from pathlib import Path from typing import Any FOLDER = Path(__file__).resolve().parent.parent / "resources" / "data_work_item_link" CONFIRMED = "확정" #: 표 `policy.both_precedence` 낱말 → 그 행의 열쇠 칸 _KEY_COLUMN = {"forest": "forest_key", "construction": "const_key"} @dataclass(frozen=True) class Choice: """쓸 열쇠 · 막힌 까닭(있으면 금액을 안 세움).""" key: str | None blocked: str = "" @lru_cache(maxsize=4) def _read(path: str, _mtime_ns: int) -> dict[str, Any]: return json.loads(Path(path).read_text(encoding="utf-8")) def _doc() -> dict[str, Any]: found = sorted(FOLDER.glob("work_item_link_*.json")) if FOLDER.is_dir() else [] return _read(str(found[-1]), found[-1].stat().st_mtime_ns) if found else {} def choose(key: str | None) -> Choice: """열쇠 하나(FW-·CW-) → 실제로 쓸 열쇠. 미판정 행에 걸리면 안 고르고 막음.""" if not key: return Choice(key) doc = _doc() policy = doc.get("policy") or {} rows = [ link for link in doc.get("links") or [] if key in (link.get("forest_key"), link.get("const_key")) ] for link in rows: if link.get("status") != CONFIRMED: return Choice( None, "연결고리 미판정 — 산림·건설 어느 품을 쓸지 근거가 없어 고르지 않음" f"({link.get('evidence') or '근거 없음'})", ) for link in rows: if link.get("branch") == "both": column = _KEY_COLUMN.get(str(policy.get("both_precedence") or "")) chosen = link.get(column) if column else None if not chosen: return Choice( None, "연결고리 표에 둘 다 있는 줄의 우선 품셈이 안 적혔음 — 고르지 않음" ) return Choice(chosen) return Choice(key) def undecided_links() -> list[dict[str, str]]: """미판정 행 — 열쇠가 비어 줄에 못 닿는 행도 드러냄(조용히 안 버림).""" return [ { "branch": str(link.get("branch") or ""), "forest_key": str(link.get("forest_key") or ""), "const_key": str(link.get("const_key") or ""), "evidence": str(link.get("evidence") or ""), } for link in _doc().get("links") or [] if link.get("status") != CONFIRMED ]