Files
group_xinghuo_jinrong/docs/项目框架设计/演示SOP-风控模块.md
T
GaoYiYuan_0626 323d98bdc2 docs: C4~C6 文档落账同步(勾 TODO/MEMORY/交接文档 + 演示 SOP A-12 + 02-redis-keys C6 联动)
- TODO/MEMORY/交接文档:C4/C5/C6 全部标记完成,基线更新为 482 绿(406+30+17+17+12)
- 演示 SOP 补 A-12 代理人行为链走查步骤(debug 头透传 actor + cron 扫描脚本)
- 02-redis-keys.md §2.6 C6 联动清单(代理人维度独立出单线/推送/去重口径)
2026-09-07 19:57:52 +08:00

9.8 KiB
Raw Blame History

演示 SOP · 风控监测 Agent(B9b)

版本:v1.1 · 2026-09-07 · 配套 PRD v1.0 §8 验收表 + 开发计划 B9b 核查单 适用:演示机/本机(app_env=development,debug 鉴权头可用;生产部署禁用,见核查单⑤) 首次走查记录:2026-09-07,A-1A-5/A-7/A-9(A-6 归 M3 对话线、A-8 由单测矩阵 + P2-8 手工 SQL 对照覆盖)+ 核查单①⑥ 全部通过(11/11 PASS)


1. 前置条件

  • 本机 bootstrap ①~⑤ 已执行(.env、依赖、双库、种子)——见 docs/memory/FLOW.md §0; 已就位机器勿重跑,只按本 SOP §2 重灌即可。
  • redis 包已安装(requirements 内;B9a 曾发现环境漏装,重装环境时留意)。
  • 本机 MySQL 可达、Redis 服务在跑(订阅演示需要;不可达时推送降级仅日志,走查其余项不受影响)。

2. 演示链路重灌(每次演示/走查前执行)

目的:清空上一轮演示/走查残留(trade/预警/审计/L3),使验收断言从干净状态出发。

脚本化方式(推荐,自动化执行不卡交互)——Git Bash:

export MYSQL_PWD=<密码>
MYSQL="C:/Program Files/MySQL/MySQL Server 8.0/bin/mysql.exe"   # mysql.exe 不在 PATH(FLOW §0 坑)
"$MYSQL" -h 127.0.0.1 -u root -e "DROP DATABASE IF EXISTS jinrong_core; DROP DATABASE IF EXISTS jinrong_agent;"
for f in scripts/core/00-create-database.sql scripts/core/01-ddl.sql scripts/core/02-seed-base.sql \
         scripts/core/03-seed-customers.sql scripts/core/04-seed-holdings.sql scripts/core/05-seed-trades.sql \
         scripts/core/06-seed-nav.sql "docs/项目框架设计/表设计/01-mysql-共用底座.sql" \
         "docs/项目框架设计/表设计/02-mysql-agent专用.sql" scripts/agent/seed-aml-list.sql \
         scripts/demo/prepare_risk_demo.sql; do
  "$MYSQL" -h 127.0.0.1 -u root --default-character-set=utf8mb4 < "$f"
done
python scripts/sync/sync_advisor_rel.py     # 归属同步(28 行)

交互方式:.\scripts\core\reset.ps1(PowerShell,交互输密码;-SkipNeo4j 可跳过 Neo4j 同步)。 注意:reset.ps1 只重建 jinrong_core,且 agent 建表 SQL 是裸 CREATE TABLE—— 交互方式需先手动 DROP DATABASE IF EXISTS jinrong_agent; 再跑后 4 个 SQL,否则 ERROR 1050(表已存在)中止且残留未清。脚本化方式已含双库 DROP,可照抄。 (两种方式均不含 Neo4j 同步;风控演示不依赖 Neo4j,需要时手动跑 scripts/sync/sync_neo4j.py。)

重灌完成标志:seed-aml-list.sql 输出 8 条名单;prepare_risk_demo.sql 输出 7 行测评 (6 行测评 90 天前 / expires_at 剩余 275 天,CUST-1004 保持过期演示 FM-03);sync_advisor_rel 输出 upserted 33 rows(33 客户口径,AL-01 起)。

3. 启动与入口

uvicorn app.main:app --reload          # Swagger: http://127.0.0.1:8000/docs
python scripts/demo/subscribe_alerts.py   # 另开终端:订阅预警推送(Ctrl+C 退出)

debug 鉴权头(dev 兜底通道,T-01 已接 JWT):X-Debug-Role + X-Debug-Actor。 走查角色:risk_officer(STAFF-90001) / compliance(STAFF-40001) / customer(CUST-1001/CUST-1002) / advisor(STAFF-10087,名下无 CUST-3001) / risk_demo(STAFF-DEMO,simulate 白名单)。

JWT 通道(T-01,可选演示):python scripts/dev/issue_dev_token.py --sub STAFF-30001 --roles risk_officer 签发后请求带 Authorization: Bearer <token> + X-Agent-Type: risk(JWT 通道强制 X-Agent-Type 交叉校验;debug 头与 JWT 不可同时生效时 JWT 优先)。

4. 验收走查(PRD §8 · A-6 归 M3 对话线)

Swagger 逐条执行;预期响应与库内核验点如下(trade_id 以实际响应为准)。

# 操作(Swagger) 预期
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(不冻户)
A-7 POST /api/risk/alerts/{A-3的alert_id}/handle,officer 头,confirmed_suspicious → 再处置一次 → 换 compliance 头再试 200(状态 confirmed_suspicious,handler_id=STAFF-90001)→ 409 STATE_CONFLICT → 403;audit 留痕(alert_handle/alert_handled)
A-9 POST /api/risk/suitability/check:customer CUST-1002 查 CUST-3001;advisor STAFF-10087 查 CUST-3001 403 AUTH_403_NOT_OWNER / AUTH_403_NOT_ASSIGNED;audit event_type=authz, decision=forbidden 各 1 条
A-12 代理人行为链(RISK-008,C6):① POST /api/simulate/trade 用代理人 actor(debug 头 X-Debug-Role=advisor X-Debug-Actor=STAFF-10087)对同一客户发起 3 组「赎回某产品 → 2h 内申购不同产品」(trade_id 经 input_summary.trade_id 落 audit_log,actor_id 透传发起人);② 另开:advisor 查非本人客户 5 次 → AUTH_403_SCOPE(条件 B),或 10 次 → AUTH_403_NOT_OWNER(条件 C) ① 跑 python scripts/cron/agent_behavior_scan.py → 出 agent_behavior pattern 单(payload.actor_id=STAFF-10087,risk_score=75,triggered_rules=["RISK-008"],同日同代理人一张单);② 对话线 risk_officer 调 query_agent_behavior 返回该单(customers 经 mask_name 脱敏);audit agent_behavior_detected(仅 INSERT)

B9a 脚本演示(运维位):

python scripts/demo/rebuild_alerts.py <trade_id>     # 补偿重放:rebuilt → 再跑 skipped → 不存在 exit 1

A-8 映射交代:suitability_check 单元级验收不在 Swagger 走查——由单测矩阵(25 组合 + 边界, tests/test_suitability.py)与真库手工 SQL 对照(P2-8 收口:4 组演示组合 SQL 推导与函数判定一致)覆盖。

A-12 脚本演示(代理人行为链 · C6):

python scripts/cron/agent_behavior_scan.py     # 扫描 audit_log 三条件,命中出 agent_behavior 单并打印 JSON

前置:A-12 ① 的 3 组赎回+申购须由同一代理人 actor 发起(debug 头 X-Debug-Actor 透传),否则 actor==customer / SYSTEM 会被排除(条件 A 不触发);条件 B/C 的越权拒绝须 decision=forbidden 且 input_summary.code 命中对应 AUTH_403_* 族。出单后 risk:pub:alert 推送 risk_officer + risk_manager (经理仅阅知、无处置权),query_agent_behavior 工具按 agent_id 过滤、客户脱敏。

5. 核查单(B6/B7/B8 挂账收口)

# 挂账来源 检查项 结论(2026-09-07 走查)
① B6 复审 P3-7 预警类 API(GET alerts / POST handle / POST aml/scan)响应体含固定 disclaimer「本预警由系统自动生成,最终判定需经风控专员人工审核。」 ✅ 已实现并真服务验证
② B6 评审 P3-6 aml/scan 幂等:同客户当日已有 aml 单(含交易触发/已处置)不重复出单,响应 skipped_existing 指向既有单 ✅ 已实现并真服务验证
③ B6 复审遗漏⑤ 真服务手测(区别于 TestClient 等价验证):uvicorn 真库走查 A-1~A-5/A-7/A-9(A-6 归 M3、A-8 见 §4 映射交代) ✅ 本 SOP §4 即该手测
④ B6 复审观察③ analyst 角色台账 fail-closed:GET /api/risk/alerts → 403(risk:alert:read 权限扩展待 Wave 3 分析 Agent 时定矩阵) ✅ 403 保持
⑤ B7 复审 P3-3 生产/演示机误配检查:本机 .env APP_ENV=development(演示机允许 debug 头);app_env != development 时启动期拒绝 debug 依赖(单测覆盖),部署非 dev 机时必须核对 ✅ 本机=development 已确认
⑥ B8 复审 P2-1 集成测试对演示库当日状态的隐式耦合 → 已加前置断言(A-1/A-3/A-4 清洁检查 fail+提示、A-5 按 trade_id 定位、A-4/A-9 COUNT 断言加当日窗),代码随 B9b 落地 ✅ 已落地;交叉数据在场时 pytest 显式 fail 提示重灌
附 阶段 A 评审 P2-8(T-31 名下) suitability_check 真库手工 SQL 对照:4 组演示组合(SUIT-001 首因 / 70 岁封顶 / 等级匹配 / SUIT-008 过期)SQL 推导与函数判定逐项一致 ✅ 2026-09-07 收口

6. 走查后状态与注意事项

  • 走查产生的交易/预警/审计/L3 保留在演示库(演示痕迹即演示资产);再次演示前回到 §2 重灌。
  • 重灌后才能跑 pytest 集成测试:核查单⑥前置断言会对当日演示数据显式 fail(防交叉失真)。
  • B9a subscribe 窗口未开时推送不丢失告警——Redis Pub/Sub 无持久化,演示时先开订阅再发交易。
  • A-6(对话线验收)归 M3(T-01/T-03/T-07 就绪后补)。