Files
group_fqcd_jr/tests/unit/service/test_risk_judgement_rw007.py
T
张胜宇 e239eb778b docs: 品牌全量口径统一为「南方基金」+ 作废文档清理
1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富
   统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」;
   同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。
2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本),
   新增《文档规整方案与开发前待决事项-2026-09-17》。
3) 客服agent 四份交付文档首次纳入本分支。
2026-09-17 15:15:22 +08:00

118 lines
4.1 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.
"""RW-007 研判必须复核第十五条豁免额度(`docs/25` 第七节 #2)。
政策原文(`knowledge/policy/个人投资者适当性管理指南.md:334-341`):
- C3 买 R4:签署《产品风险超越投资者风险承受能力揭示书》后**可买**,但单只 R4 持仓
不超过总资产 **20%**;
- C4 买 R5:同理,上限 **10%**。
也就是说越级购买本身不是违规,**超出额度**才是。原先 `_assess_rw007` 只看留痕:
只要揭示书、二次确认、录音都齐,就判"疑似误报" —— 而这三样恰恰是豁免的**前提条件**,
把前提当成结论,于是"签了字但买超了额度"被静默放过。
数据缺失的情况单独处理:额度要靠总资产与持仓快照才能核算,本项目的画像与持仓由上游
流程写入(实测 `total_asset` 全为 0、`fin_holding` 为空),既不能默认"没超"判误报,
也不能默认"超了"判风险 —— 那会把豁免规则变成新的误报源。
"""
from typing import Any
from app.service.risk_judgement_service import (
VERDICT_CONTINUE_REVIEW,
VERDICT_RISK_SUPPORTED,
VERDICT_SUSPECTED_FALSE_POSITIVE,
_assess_rw007,
)
COMPLETE_WORK_ORDER: dict[str, Any] = {
"risk_disclosure_ack_at": "2026-09-01T00:00:00",
"second_confirmation_at": "2026-09-01T00:00:00",
"recording_reference": "REC-1",
}
REQUIRING_PRODUCT: dict[str, Any] = {
"risk_level": "R4",
"risk_disclosure_required": True,
"second_confirmation_required": True,
"recording_required": True,
}
def _detail(
snapshot: dict[str, Any],
*,
work_order: dict[str, Any] | None = None,
investor_type: str = "C3",
) -> dict[str, Any]:
"""一条 C3→R4(相差 1 级、属豁免情形)的 RW-007 详情。"""
return {
"customer": {"investor_type": investor_type},
"product": dict(REQUIRING_PRODUCT),
"work_order": dict(COMPLETE_WORK_ORDER if work_order is None else work_order),
"evidence_snapshot": snapshot,
}
def test_limit_breach_is_risk_supported_even_with_complete_trace() -> None:
"""这是本次补上的判断:留痕齐全 ≠ 豁免成立,额度超了就是违规。"""
result = _assess_rw007(
_detail({"exemption_limit": "0.20", "exemption_ratio": "0.35"})
)
assert VERDICT_RISK_SUPPORTED in str(result)
assert "35.00%" in str(result)
assert "20%" in str(result)
def test_within_limit_with_complete_trace_is_a_false_positive() -> None:
result = _assess_rw007(
_detail({"exemption_limit": "0.20", "exemption_ratio": "0.10"})
)
assert VERDICT_SUSPECTED_FALSE_POSITIVE in str(result)
def test_exact_limit_is_not_a_breach() -> None:
"""政策写的是"不超过",恰好等于上限属于合规。"""
result = _assess_rw007(
_detail({"exemption_limit": "0.20", "exemption_ratio": "0.2"})
)
assert VERDICT_SUSPECTED_FALSE_POSITIVE in str(result)
def test_missing_snapshot_data_asks_for_manual_review() -> None:
"""额度核算不出结果时,既不判误报也不判风险。"""
result = _assess_rw007(
_detail(
{
"exemption_limit": "0.20",
"exemption_ratio": None,
"exemption_data_missing": True,
}
)
)
assert VERDICT_CONTINUE_REVIEW in str(result)
assert "总资产" in str(result)
def test_missing_trace_still_outranks_the_exemption_path() -> None:
"""留痕不完整仍然直接判风险 —— 豁免的**前提**没满足,谈额度没有意义。"""
result = _assess_rw007(
_detail(
{"exemption_limit": "0.20", "exemption_ratio": "0.01"},
work_order={},
)
)
assert VERDICT_RISK_SUPPORTED in str(result)
assert "缺少交易留痕" in str(result)
def test_pairs_outside_the_exemption_table_keep_the_old_behaviour() -> None:
"""C2→R5 不属豁免情形,快照里也不会有额度字段:行为必须与改动前一致。"""
result = _assess_rw007(_detail({}, investor_type="C2"))
assert VERDICT_SUSPECTED_FALSE_POSITIVE in str(result)