fix(risk): RW-012 研判补上"≥3 倍历史均值"复核,顺带修一处除零
docs/25 P2。核实后发现**根因不在研判侧偷懒,而在快照缺数据**: - 扫描侧生成 RW-012 要求 verage > 0 且 amount >= average * 3(risk_scan_service.py:224); - 研判侧 _assess_rw012 只看了年龄、金额、非常用设备,**完全没核均值** —— 于是达不到 3 倍的交易也会被判"证据支持风险"。它自己的建议文本写着"继续核实一年期历史交易均值": 作者知道该看,只是当时确实没有数据可看。 - 原因:扫描侧写进快照的只有 product_id / age / device_id,**没有均值** (对比 RW-003 在 :124 就写了 ratio)。 **两侧一起改**: 1. 扫描侧补写 verage_amount 与 atio(用 ratio 与 RW-003 的快照风格保持一致)。 2. 研判侧据此复核: atio 缺失 → CONTINUE_REVIEW("无法复核 3 倍门槛", 不再顺着"非常用设备"定案); atio < 3 → SUSPECTED_FALSE_POSITIVE;否则走原有逻辑。 3. **顺带修一处除零**:verage == 0 时 mount < 0*3 恒为 False,会一路走到 mount / average 直接崩。改为同时挡住 verage_amount <= 0。 新增 tests/unit/service/test_risk_judgement_rw012.py(3 条):ratio 缺失时不落到风险成立、 ratio < 3 判误报并给出倍数、ratio >= 3 继续往下走。三个用例都停在登录记录判断之前, 不必构造复杂的登录证据。 ruff / mypy(136 文件) / 632 unit+contract / 29 integration 全绿。
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
"""RW-012 研判必须复核"≥ 3 倍历史均值"这条生成条件。
|
||||
|
||||
docs/25 P2:扫描侧生成预警要求 `average > 0 且 amount >= average * 3`
|
||||
(`risk_scan_service.py`),而研判侧的 `_assess_rw012` 只看了年龄、金额、非常用设备,
|
||||
**完全没核均值** —— 于是达不到 3 倍的交易也会被判"证据支持风险"。
|
||||
讽刺的是它的建议文本写着"继续核实一年期历史交易均值":作者知道该看,只是当时确实
|
||||
没有数据可看。
|
||||
|
||||
根因是快照里没有均值(原先只写 `product_id` / `age` / `device_id`)。本次两侧一起改:
|
||||
扫描侧补写 `average_amount` 与 `ratio`,研判侧据此复核。
|
||||
|
||||
这三个用例都停在登录记录判断**之前**,所以不必构造合法的登录证据。
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from app.service.risk_judgement_service import (
|
||||
VERDICT_CONTINUE_REVIEW,
|
||||
VERDICT_SUSPECTED_FALSE_POSITIVE,
|
||||
_assess_rw012,
|
||||
)
|
||||
|
||||
|
||||
def _detail(*, ratio: str | None) -> dict[str, Any]:
|
||||
"""一条已满足年龄与金额门槛的 RW-012,ratio 由用例决定(None 表示快照里没有)。"""
|
||||
snapshot: dict[str, Any] = {} if ratio is None else {"ratio": ratio}
|
||||
return {
|
||||
"customer": {"birth_date": "1950-01-01"}, # 70 岁以上
|
||||
"transaction": {"amount": "500000", "confirmed_at": "2026-09-10T04:00:00"},
|
||||
"evidence_snapshot": snapshot,
|
||||
"login_records": [],
|
||||
}
|
||||
|
||||
|
||||
def test_missing_ratio_does_not_fall_through_to_risk_supported() -> None:
|
||||
"""快照里没有均值时不能顺着"非常用设备"就定案 —— 应该继续复核。"""
|
||||
result = _assess_rw012(_detail(ratio=None))
|
||||
|
||||
assert VERDICT_CONTINUE_REVIEW in str(result)
|
||||
|
||||
|
||||
def test_ratio_below_three_is_a_false_positive() -> None:
|
||||
"""不到 3 倍均值就不满足规则的生成条件 —— 这是本次补上的判断。"""
|
||||
result = _assess_rw012(_detail(ratio="2.5"))
|
||||
|
||||
assert VERDICT_SUSPECTED_FALSE_POSITIVE in str(result)
|
||||
assert "2.50" in str(result)
|
||||
|
||||
|
||||
def test_ratio_at_or_above_three_passes_the_threshold_check() -> None:
|
||||
"""达到 3 倍时应继续往下走(本用例没有登录记录,故停在"补查登录记录")。"""
|
||||
for ratio in ("3", "6.2"):
|
||||
result = _assess_rw012(_detail(ratio=ratio))
|
||||
|
||||
assert VERDICT_CONTINUE_REVIEW in str(result), f"ratio={ratio} 不该被判成误报"
|
||||
assert VERDICT_SUSPECTED_FALSE_POSITIVE not in str(result)
|
||||
Reference in New Issue
Block a user