查到价后把币种、单位、数量、单价、备注写入报价单,避免销售只看到光秃秃的金额。 Co-authored-by: Cursor <cursoragent@cursor.com>
99 lines
2.9 KiB
Python
99 lines
2.9 KiB
Python
"""
|
||
字段 / 字典合同加载。
|
||
|
||
本文件职责:从仓库 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
|