Files
Mutual_Fund/service/advisor/suitability.py
T

43 lines
1.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""适当性校验(工作台本地桩实现,契约与投顾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": ""}