"""适当性校验(工作台本地桩实现,契约与投顾Agent 共享包保持一致)。 common_const §7 决议:公共适当性校验函数沉淀为独立共享包 `suitability`,工作台与 Agent 复用同一实现、禁止各自复制。在 Agent 侧共享包发布前,本模块提供**契约一致的 本地桩**:`check_suitability(customer_risk, product_risk) -> {ok, reason}`; 共享包就绪后仅替换 import,不改变调用方。 """ from __future__ import annotations # 风险等级 → 序号(C_n / R_n 的 n)。客户风险能力口径 C1-C5,客户画像与产品口径 # 统一为 R1-R5(历史中文等级已在数据迁移中转换,故不再收录中文键)。 _RISK_RANK = { "C1": 1, "C2": 2, "C3": 3, "C4": 4, "C5": 5, "R1": 1, "R2": 2, "R3": 3, "R4": 4, "R5": 5, } def _rank(level: str | None) -> int | None: """风险等级统一转序号;未知/空返回 None(表示无法判定)。""" if not level: return None return _RISK_RANK.get(str(level).strip()) def check_suitability(customer_risk: str | None, product_risk: str | None) -> dict: """适当性硬规则:产品风险 R 不得高于客户风险 C(R_n ≤ C_n)。 返回 {"ok": bool, "reason": str}。无法判定(缺等级/未知编码)视为不通过并说明原因, 宁可拦截不放行(合规红线,发送环节 fail-closed)。 """ customer_rank = _rank(customer_risk) product_rank = _rank(product_risk) if customer_rank is None: return {"ok": False, "reason": "客户无有效风险等级,无法进行适当性校验"} if product_rank is None: return {"ok": False, "reason": f"产品风险等级无法识别: {product_risk}"} if product_rank > customer_rank: return { "ok": False, "reason": f"产品风险等级 {product_risk} 高于客户风险等级 {customer_risk}", } return {"ok": True, "reason": ""}