""" 字段 / 字典合同加载。 本文件职责:从仓库 contracts/ 读 JSON;优先正式文件名,其次 PLACEHOLDER。 没读到真合同前,业务校验代码不得假装已激活字段规则(prompt/05)。 """ from __future__ import annotations import json import logging from functools import lru_cache from pathlib import Path from typing import Any, Optional logger = logging.getLogger(__name__) def contracts_dir() -> Path: """ 定位仓库 contracts 目录。 查找顺序:inquiry-agent 上一级 / 再上一级(兼容拷贝到 C:\\ytd)。 """ here = Path(__file__).resolve() for parent in here.parents: candidate = parent / "contracts" # 空目录不算:发布包曾留下空 contracts,会挡住仓库根的真合同 if candidate.is_dir() and any(candidate.glob("*.json")): return candidate # 回退:与 agent 包同级仓库根的常见布局 return here.parents[3] / "contracts" def _load_json_file(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) def resolve_contract_path(stem: str) -> Optional[Path]: """ 解析合同文件路径。 stem 例:inquiry-required-fields-v1 / tms-dictionary-projection-v2 优先 `{stem}.json`,否则 `{stem}.PLACEHOLDER.json`。 """ root = contracts_dir() formal = root / f"{stem}.json" if formal.is_file(): return formal placeholder = root / f"{stem}.PLACEHOLDER.json" if placeholder.is_file(): return placeholder return None @lru_cache(maxsize=8) def load_contract(stem: str) -> dict[str, Any]: """ 加载合同 JSON。 返回:dict;文件缺失抛 FileNotFoundError。 is_placeholder:由 meta 或文件名推断,供调用方拒绝「用占位做生产校验」。 """ path = resolve_contract_path(stem) if path is None: raise FileNotFoundError(f"合同文件不存在: {stem}(目录 {contracts_dir()})") data = _load_json_file(path) is_ph = "PLACEHOLDER" in path.name or str(data.get("schemaVersion", "")).endswith( "placeholder" ) data = dict(data) data["_meta"] = { "path": str(path), "is_placeholder": is_ph, "stem": stem, } logger.info( "已加载合同 stem=%s placeholder=%s path=%s", stem, is_ph, path.name, ) return data def require_formal_contract(stem: str) -> dict[str, Any]: """ 加载且必须为正式文件(非 PLACEHOLDER)。 骨架联调请用 load_contract;写字段校验业务前必须走本函数。 """ data = load_contract(stem) if data.get("_meta", {}).get("is_placeholder"): raise RuntimeError( f"合同仍为占位,禁止做业务校验: {stem}。" "请将正式 JSON 放到 contracts/(见 _ref/prod-pull 对照)" ) return data