AL-08: 演示库重灌与种子核对对齐 main 基准——prepare_risk_demo.sql 改写 expires_at 口径(演示客户刷新为测评 90 天前/剩余 275 天,CUST-1004 保持过期演示 FM-03,替代 SUIT-008 evaluated_at+365 旧写法);演示 SOP 同步(§2 完成标志 33 客户口径+expires_at 剩余天数,A-1 预期改 SUIT_RISK_MISMATCH,A-2 改 FM-01 SUIT_AGE_CONFIRM 网点确认);网关阻断响应补全 main 契约字段(match_result/mismatch_type/requires_disclosure/needs_branch_confirm/rule_refs)+audit detail 加 block_response_code;集成测试 4 例旧口径断言更新(A-1 trace 一致性断机器码/A-1 audit JSON 断机器码/A-2 重写为 FM-01 语义/settings 退役断言)。SOP §2 脚本化全量重灌双库验证通过(AML 8 条/演示 7 行/sync 33 rows);全量 406 passed 0 failed 0 skipped(集成测试真库走 21 列新表+expires_at 数据恢复执行)
This commit is contained in:
@@ -141,13 +141,22 @@ def submit_trade(
|
||||
trade_id=trade_id,
|
||||
req=req,
|
||||
rule_id=result.rule_id,
|
||||
detail={"block_reason": result.block_reason, "reasons": list(result.reasons)},
|
||||
detail={
|
||||
"block_reason": result.block_reason,
|
||||
"block_response_code": result.block_response_code,
|
||||
"reasons": list(result.reasons),
|
||||
},
|
||||
)
|
||||
return {
|
||||
"blocked": True,
|
||||
"trade_id": trade_id,
|
||||
"match_result": result.match_result,
|
||||
"mismatch_type": result.mismatch_type,
|
||||
"requires_disclosure": result.requires_disclosure,
|
||||
"needs_branch_confirm": result.needs_branch_confirm,
|
||||
"block_reason": result.block_reason,
|
||||
"block_response_code": result.block_response_code,
|
||||
"rule_refs": result.rule_refs,
|
||||
"reasons": list(result.reasons),
|
||||
"advice": ADVICE,
|
||||
"notice": RECORDED_NOTICE,
|
||||
|
||||
@@ -40,7 +40,7 @@ ERROR 1050(表已存在)中止且残留未清。脚本化方式已含双库
|
||||
(两种方式均不含 Neo4j 同步;风控演示不依赖 Neo4j,需要时手动跑 `scripts/sync/sync_neo4j.py`。)
|
||||
|
||||
**重灌完成标志**:`seed-aml-list.sql` 输出 8 条名单;`prepare_risk_demo.sql` 输出 7 行测评
|
||||
(6 行 90 天、CUST-1004 保持过期);`sync_advisor_rel` 输出 upserted 28 rows。
|
||||
(6 行测评 90 天前 / expires_at 剩余 275 天,CUST-1004 保持过期演示 FM-03);`sync_advisor_rel` 输出 upserted 33 rows(33 客户口径,AL-01 起)。
|
||||
|
||||
## 3. 启动与入口
|
||||
|
||||
@@ -63,8 +63,8 @@ Swagger 逐条执行;预期响应与库内核验点如下(`trade_id` 以实
|
||||
|
||||
| # | 操作(Swagger) | 预期 |
|
||||
| --- | --- | --- |
|
||||
| A-1 | `POST /api/simulate/trade`,customer 头 CUST-1001,body:CUST-1001 / PROD-161725 / subscribe / 10000 | `blocked=true`,`block_reason` 含 SUIT-001;库核验:`core_trade` 无该 trade_id;`risk_suitability_log.is_blocked=1`;`risk_alert`(trade_id 定位)suitability/pending;audit `suitability_blocked`(agent_type=platform);订阅端收到推送且 trace 与响应头一致 |
|
||||
| A-2 | 同上,risk_demo 头,CUST-4001 / PROD-161725 / subscribe / 20000 | `blocked=true`,reasons 含 SUIT-006(≥70 按 C3)与 SUIT-003,**无 SUIT-008** |
|
||||
| A-1 | `POST /api/simulate/trade`,customer 头 CUST-1001,body:CUST-1001 / PROD-161725 / subscribe / 10000 | `blocked=true`,`block_response_code=SUIT_RISK_MISMATCH`(AL-05 起 main 契约机器码,SUIT-001 退役);库核验:`core_trade` 无该 trade_id;`risk_suitability_log.is_blocked=1`;`risk_alert`(trade_id 定位)suitability/pending;audit `suitability_blocked`(agent_type=platform);订阅端收到推送且 trace 与响应头一致 |
|
||||
| A-2 | 同上,risk_demo 头,CUST-4001 / PROD-161725 / subscribe / 20000 | `blocked=true`,`block_response_code=SUIT_AGE_CONFIRM`(AL-05 起 FM-01 网点确认替换 SUIT-006 封顶:70 岁买 R4≥R3 一律阻断待网点当面确认),`needs_branch_confirm=true` |
|
||||
| A-3 | 同上,CUST-3001 / PROD-510300 / subscribe / 500000 | `blocked=false`,`triggered_rules=["RISK-001","RISK-002"]`,预警单 score=70/pending;`core_trade` 落库;audit `trade_accepted`;**订阅端实时打印推送** |
|
||||
| A-4 | 同上,CUST-9527 / PROD-510300 / subscribe / 1000,连发 4 笔 | 第 3 笔触发 RISK-003;第 4 笔 `alert_ids` 与第 3 笔相同(并入同单),该客户 freq 类单数=1,payload.events 长度=2 |
|
||||
| A-5 | 同上,CUST-1002 / PROD-005828 / subscribe / 100 | `aml_hit=true`;aml 独立单 score=95(按本笔 trade_id 定位);L3 `monitor_tier=high`;compliance 头 `GET /api/risk/alerts` 仅见 aml 单且含该单;`core_customer.is_active` 仍=1(不冻户) |
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
-- =============================================================================
|
||||
-- 风控演示数据准备(PRD §10.2 · 每次 reset.ps1 重建全库后需重跑)
|
||||
-- 用途:刷新演示客户的风险测评日期,否则 SUIT-008(测评有效期 365 天)
|
||||
-- 会阻断所有演示交易;CUST-1004 保留过期,用于演示 SUIT-008 阻断路径。
|
||||
-- 用途:刷新演示客户的风险测评有效期,否则 FM-03(风评过期,expires_at 驱动)
|
||||
-- 会阻断所有演示交易;CUST-1004 保留过期,用于演示 FM-03 过期阻断路径。
|
||||
-- AL-08 对齐 main 契约:有效期由 core_customer_risk.expires_at 数据驱动
|
||||
-- (evaluated_at+365 天口径与 settings.risk_assessment_valid_days 已退役)。
|
||||
-- 依赖:scripts/core/reset.ps1 已执行(jinrong_core 已建库灌种子)
|
||||
-- 验收对照:docs/PRD/PRD-风控监测Agent.md §8 A-1~A-5
|
||||
-- =============================================================================
|
||||
@@ -9,12 +11,17 @@
|
||||
USE jinrong_core;
|
||||
|
||||
-- A-1 CUST-1001(C1) / A-2 CUST-4001(70岁C5) / A-3 CUST-3001(C3) / 其余为通用演示客户
|
||||
UPDATE core_customer_risk SET evaluated_at = CURDATE() - INTERVAL 90 DAY
|
||||
-- 刷新为「测评 90 天前、有效期剩余 275 天」(合计 365 天,保持原语义节奏)
|
||||
UPDATE core_customer_risk
|
||||
SET evaluated_at = CURDATE() - INTERVAL 90 DAY,
|
||||
expires_at = CURDATE() + INTERVAL 275 DAY
|
||||
WHERE customer_id IN ('CUST-1001','CUST-1002','CUST-1003','CUST-3001','CUST-4001','CUST-9527');
|
||||
|
||||
-- 校验(执行后应看到 6 行 evaluated_at 为 90 天前;CUST-1004 保持 2025-01-10 过期)
|
||||
SELECT customer_id, risk_code, evaluated_at,
|
||||
DATEDIFF(CURDATE(), evaluated_at) AS days_since_eval
|
||||
-- 校验(执行后应看到 6 行:测评 90 天前、expires_at 剩余 275 天;
|
||||
-- CUST-1004 保持种子过期日,演示 FM-03 阻断路径)
|
||||
SELECT customer_id, risk_code, evaluated_at, expires_at,
|
||||
DATEDIFF(CURDATE(), evaluated_at) AS days_since_eval,
|
||||
DATEDIFF(expires_at, CURDATE()) AS days_until_expire
|
||||
FROM core_customer_risk
|
||||
WHERE customer_id IN ('CUST-1001','CUST-1002','CUST-1003','CUST-3001','CUST-4001','CUST-9527','CUST-1004')
|
||||
ORDER BY customer_id;
|
||||
|
||||
@@ -149,7 +149,7 @@ def _assert_a4_clean(env):
|
||||
)
|
||||
|
||||
|
||||
# ---------- A-1:C1 申购 R4 → SUIT-001 阻断(含 trace 一致性) ----------
|
||||
# ---------- A-1:C1 申购 R4 → 矩阵 forbidden 阻断(含 trace 一致性) ----------
|
||||
|
||||
|
||||
def test_a1_suit001_blocked_with_trace_consistency(iclient, risk_demo_env):
|
||||
@@ -165,7 +165,8 @@ def test_a1_suit001_blocked_with_trace_consistency(iclient, risk_demo_env):
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["blocked"] is True
|
||||
assert "SUIT-001" in body["block_reason"] # C1 仅可购 R1
|
||||
# AL-05 main 契约:机器码 SUIT_RISK_MISMATCH(SUIT-001 退役)
|
||||
assert body["block_response_code"] == "SUIT_RISK_MISMATCH"
|
||||
assert r.headers["X-Trace-Id"] == trace # 中间件透传(PRD §7.5 全链路环①)
|
||||
trade_id = body["trade_id"]
|
||||
assert trade_id.startswith("TRD-TEST-")
|
||||
@@ -204,11 +205,11 @@ def test_a1_audit_input_summary_json(iclient, risk_demo_env):
|
||||
assert summary["product_id"] == "PROD-161725"
|
||||
assert summary["trade_type"] == "subscribe"
|
||||
assert summary["amount"] == "10000"
|
||||
assert "SUIT-001" in summary["block_reason"]
|
||||
assert summary["block_response_code"] == "SUIT_RISK_MISMATCH" # AL-05 main 契约
|
||||
assert isinstance(summary["reasons"], list) and summary["reasons"]
|
||||
|
||||
|
||||
# ---------- A-2:70 岁 C5 → SUIT-006 封顶 + SUIT-003,无 SUIT-008 ----------
|
||||
# ---------- A-2:70 岁 C5 买 R4 → FM-01 网点确认阻断(替换 SUIT-006 封顶) ----------
|
||||
|
||||
|
||||
def test_a2_age70_cap_suit006_no_suit008(iclient, risk_demo_env):
|
||||
@@ -222,10 +223,10 @@ def test_a2_age70_cap_suit006_no_suit008(iclient, risk_demo_env):
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["blocked"] is True
|
||||
reasons = body["reasons"]
|
||||
assert any("SUIT-006" in x for x in reasons), reasons # ≥70 按 C3 封顶
|
||||
assert any("SUIT-003" in x for x in reasons), reasons # C3 < R4 不匹配
|
||||
assert not any("SUIT-008" in x for x in reasons), reasons # 测评已刷新,不得干扰
|
||||
# AL-05 main 契约:≥70 买 R3+ 一律阻断待网点当面确认(FM-01 替换 SUIT-006 封顶)
|
||||
assert body["block_response_code"] == "SUIT_AGE_CONFIRM"
|
||||
assert body["needs_branch_confirm"] is True
|
||||
assert any("网点当面确认" in x for x in body["reasons"]), body["reasons"]
|
||||
trade_id = body["trade_id"]
|
||||
assert _core_one(env, "SELECT * FROM core_trade WHERE trade_id = :t", t=trade_id) is None
|
||||
_state["a2_trade_id"] = trade_id
|
||||
@@ -446,4 +447,5 @@ def test_missing_trace_header_generates_one(iclient):
|
||||
def test_settings_thresholds_loaded():
|
||||
"""冒烟:阈值配置与冻结规则一致(.env 未覆盖时)。"""
|
||||
assert settings.risk_large_amount == 500000
|
||||
assert settings.risk_assessment_valid_days == 365
|
||||
# risk_assessment_valid_days 已随 AL-05 退役(有效期由 core_customer_risk.expires_at 驱动)
|
||||
assert not hasattr(settings, "risk_assessment_valid_days")
|
||||
|
||||
Reference in New Issue
Block a user