Files
Mutual_Fund/service/advisor/suitability.py
T

43 lines
1.9 KiB
Python
Raw Normal View History

2026-09-12 20:42:33 +08:00
"""适当性校验(工作台本地桩实现,契约与投顾Agent 共享包保持一致)。
common_const §7 决议:公共适当性校验函数沉淀为独立共享包 `suitability`,工作台与
Agent 复用同一实现、禁止各自复制。在 Agent 侧共享包发布前,本模块提供**契约一致的
本地桩**:`check_suitability(customer_risk, product_risk) -> {ok, reason}`;
共享包就绪后仅替换 import,不改变调用方。
"""
from __future__ import annotations
2026-09-14 00:19:31 +08:00
# 风险等级 → 序号(C_n / R_n 的 n)。客户风险能力口径 C1-C5,客户画像与产品口径
# 统一为 R1-R5(历史中文等级已在数据迁移中转换,故不再收录中文键)。
2026-09-12 20:42:33 +08:00
_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": ""}