12 KiB
开发计划 · 风控监测 Agent 模块
版本:v1.1(AI 评审通过:修复 P1×2——假 AuthContext 规范落 deps.py、conftest 测试前置;P2×7 顺手收敛)· 2026-09-06 · 分支
feature/risk· 负责人 E
依据:PRD v1.0 + 架构设计 v1.1
铁律:每步完成即测,测试通过才进下一步;每个任务至少一个 commit,里程碑打 tag;涉及表结构/接口协议的步骤已获 PRD 批准
任务映射:T-31 = 阶段 A(交付=suitability 服务函数+单测;check API 随 B6)· T-30 = 阶段 B · T-32 = 阶段 C(交付=对话线;台账/处置 API 随 B6)——此边界说明用于对齐docs/memory/TODO.md文字,M4 时同步修订 TODO
阶段 A · 基建与公共校验(不依赖 T-01/T-07,可立即开工)
| # | 任务 | 产出 | 验证方式 | 依赖 |
|---|---|---|---|---|
| A1 | utils/trace.py + utils/desensitize.py + settings.py 扩展(risk_* 阈值)+ .env.example 同步 |
3 个工具模块 + 配置 | 单测:脱敏 DESENS-001~004 四规则(005 资产金额归前端组件,后端不做)、trace set/get | 无 |
| A2 | core_ro.py 扩展 sum_trades_on_date(其余复用现有方法) |
Repository 方法 | 单测/手工 SQL 对照 | 前置:本机已执行 reset.ps1 灌库(FLOW §0 ③,一次性本机操作,非等待 T-05 任务) |
| A3 | repository/risk_repository.py(risk_alert / risk_suitability_log / l3 / aml_list 读写) |
Repository | 手工 SQL 对照 | agent 库建表(含 risk_aml_list) |
| A4 | service/suitability.py:SUIT-001~008 纯函数 + check 服务(落日志)+ AuthContext Pydantic 模型与 get_auth_context 工厂签名占位(实现归 B6) |
公共校验服务 + AuthContext 接口冻结 | 单测:矩阵 25 组合 + 69/70 岁 + NULL 年龄 + 364/365 天(验收 A-8) | A1、A2、A3 |
阶段 B · 事件驱动线(核心,不依赖 T-07)
| # | 任务 | 产出 | 验证方式 | 依赖 |
|---|---|---|---|---|
| B1 | service/risk/rules.py:RISK-001~005 纯函数 |
规则函数 | 单测:各规则命中/不命中 + RISK-004 窗口 + RISK-005 非连续 | A1 |
| B2 | alert_service.py:聚合去重(进程内锁 + 锁内 check-insert)+ 审计落库 + risk:pub:alert PUBLISH(同步 Redis 单例)。备注:merge 原语(append_alert_event)已下沉 repo(A3),本任务只做聚合决策与编排(评审 P2-5 口径) |
预警服务 | 单测:聚合合并、risk_score 取 max、去重追加;并发冒烟:两线程同客户同日首单 → 预警单数=1 且 events[] 含两笔 | A3 |
| B3 | profile_l3.py:get_l3 → 最高档合并 → insert_l3/update_l3。备注:非原子,并发首单需 catch IntegrityError 转更新(或复用 B2 锁)(评审 P2-7③);B3 评审后口径(P3-4 · 用户拍板 2026-09-06):L3 risk_score 一期不写(保持 NULL,归 R-05 评分模型首写),tier/tags/last_alert_id/computed_at 照 FR-7 |
L3 写入 | 单测:normal→high 不降级、AML 后大额不回落 | A3 |
| B4 | aml_service.py(归一化+相似度匹配、scan_all)+ engine.py(process_trade_event 组装 + 预留客户事件钩子)+ scoring.py 占位签名(FR-7 预留) |
引擎完整 | 单测:AML 阈值边界;引擎集成冒烟 | B1、B2、B3。B4 评审修复(2026-09-06):FR-4 payload 客户上下文(L0+近 30 天统计)补组装;core_ro 增 list_trades_range(升序)/list_active_customers;种子名单 3 条改名维持唯一演示命中;core_cash_flow 上下文一期未接(PRD「如有」),B8 前评估;真实名单数据接入时 payload/审计出口接 utils/desensitize(B9b 前核查项);复审观察项:30 天窗口口径(现 31 自然日)/申赎混合求和方向,R-05 接入前统一 |
| B5 | app/gateway/(trade_gateway + gateway_repository 仅 INSERT core_trade)+ api/simulate.py 薄路由 |
网关 | 集成:convert 400、阻断不落 trade | A4、B4 |
| B6 | app/api/deps.py:AuthContext(actor_id/roles/customer_id,字段按 JWT 手册冻结)+ get_auth_context() 工厂——dev 模式从 X-Debug-Role/X-Debug-Actor 请求头构造、app_env != development 启动时检测 debug 头直接拒绝;T-01 就绪后仅替换工厂内部为 JWT 解析,签名不变。另:api/risk.py 4 个 API(GET alerts / POST handle / POST suitability/check / POST aml/scan)+ 归属校验(含 compliance 强制 aml 过滤)。备注:依赖层须校验 handler_result 枚举(repo 不校验);本阶段顺手统一 NotFoundError 异常(utils/exceptions.py 现为占位)(评审 P2-7①②) |
鉴权依赖 + 4 个 API | Swagger 手测 + 权限矩阵(按 debug 头切换角色/身份执行 A-7/A-9 用例) | A4、B2、B4(aml/scan 依赖 scan_all) |
| B7 | main.py 集成:路由挂载 + lifespan(双 Engine 单例注入 + Redis 单例 + trace 中间件)。备注:顺手提取 utils/db.py 引擎工厂收敛 core_ro/risk_repository 双份 _default_engine(评审 P2-6);B3 挂账(B3 评审 P2-5):① _run_locked 锁原语公共化(alert_service/profile_l3 现复用私有实现)② L3 写侧 Redis 缓存 DEL 钩子(PRD §5.1 profile:l3:{customer_id} 更新时 DEL,profile_l3.upsert_profile_l3 已留痕)③ 删除 core_ro.list_trades 死代码(B4 改用 list_trades_range 后无调用方,复审 N3)④ 统一响应外壳落地(utils/response.py 现占位,simulate/risk 路由届时一并包裹,B5 评审 P2-2;错误体对齐手册 §10 error_code/message/trace_id,B6 评审 P3-2);B6 挂账(B6 复审):⑤ lifespan 启动期检测 app_env != development + debug 头依赖直接拒绝启动(现为请求时 RuntimeError,B6 评审遗漏①)⑥ 引擎工厂须覆盖 deps/simulate/risk 三处每请求 RiskRepository()/CoreReadOnlyRepository() 实例化点并 dispose(现复用 _default_engine 不释放,B6 复审 P3)⑦ handle_alert 的 update_alert_status 与 insert_audit_log 两事务非原子——统一事务或补偿记录(B6 复审 P3)⑧ input_guard_log 双写缺口(手册 P-05 要求 audit_log+input_guard_log,表归 T-03 底座,B7 接 T-02 审计中间件时统一补) |
可运行应用 | uvicorn 启动 + /health + 全路由可达 |
B5、B6 |
B7 复审(独立 AI 评审 · 2026-09-06):有条件通过 → 已闭环。 挂账①②③④⑤⑦证实落地、⑧确认随 T-02 无烂尾、红线全守住。P1-1 dispose_engines 仅 cache_clear 未真 dispose——已修(utils/db.py 手写单例字典 + 显式 Engine.dispose(),新增 tests/test_db.py 3 例,183 绿)。挂账:P2-1 L3 DEL 钩子无行为断言随 B8;P2-2 未捕获异常 500 无 trace 头回写 + 422/404/405 错误码补齐归 T-02;P3-1 update_alert_status 建议标注 deprecated(防绕过同事务审计)、P3-2 locks 降级文案中性化(可随 B8 顺手);P3-3 生产误配 app_env=development 检查归 B9b SOP;P3-4 独立 request_id(现沿用 trace_id)归 T-02。偏差留痕:成功响应不包裹外壳 = B5 P2-2「返回体不变」演进口径,M4 复核时对齐开发计划文本。
| B8 | tests/conftest.py:a) session fixture 启动校验演示数据就位(CUST-4001 测评 <365 天、risk_aml_list ≥8),缺失则中止并提示先跑 FLOW §0 ③④;b) fixture 幂等代跑 prepare_risk_demo.sql;c) teardown 按 TRD-TEST- 清 core_trade + 关联 risk_alert/risk_suitability_log/audit_log + 还原 L3 行。顺手集中 sqlite 测试 DDL 为单一事实源(B4 评审 P3-12,各测试文件手写 DDL 收敛;CURRENT_TIMESTAMP 改 localtime 或 fixture 固定时间,防 UTC/本地日界错位——B5 评审 P3-4);集成测试交易统一走 trade_id_factory 注入 TRD-TEST- 前缀(trade_gateway 已留参数,B5 评审 P3-2)。集成测试:A-1A-5、A-7(状态机/compliance 403/GET 强制 aml)、A-9 越权、trace 一致性断言;补 platform 审计 input_summary 的 JSON 解析断言(含引擎输出/阻断 reasons,B5 复审 L1);B7 复审 P2-1:补 L3 DEL 钩子行为断言(fake 记录 deletes,断言 key=B8 并行) |
| B9b | 演示链路走查:profile:l3:{customer_id} 与降级路径);P3-2 locks 降级文案中性化顺手改 | 测试套件 + fixture | pytest 全绿 | B7 |
| B9a | 演示/运维脚本开发:scripts/demo/subscribe_alerts.py(订阅演示)+ scripts/demo/rebuild_alerts.py(按 trade_id 幂等重放补偿) | 2 个脚本 | 手工执行验证 | B2、B4(可与 B5reset.ps1 → prepare_risk_demo.sql → agent 库建表 → seed-aml-list.sql → Swagger 逐条过 A-1A-5、A-7A-9(A-6 归 M3)。B6 挂账核查单:① 预警类 API 响应体含固定 disclaimer「本预警由系统自动生成,最终判定需经风控专员人工审核」(PRD §6/规则表 §5,B6 复审 P3-7)② aml/scan 幂等防护(重复扫描同命中客户重复出单,演示点击即复现,B6 评审 P3-6)③ B6 时代码以 TestClient 独立挂 router 等价验证,本次补一次真 Swagger 手测(B6 复审遗漏⑤)④ analyst 台账只读权限扩展待 Wave 3 分析 Agent 接入时定权限矩阵(手册 §5.3 有 risk:alert:read,现 fail-closed 拒绝,B6 复审观察③)⑤ 生产/演示机 app_env=development 误配检查(B7 复审 P3-3:误配时 debug 头可达且启动校验放行,列入演示 SOP) | 演示 SOP | 按 PRD §8 验收表逐条打勾 | B8、B9a |
阶段 C · 对话线(依赖 Wave 0 的 T-01 JWT / T-03 输入防护 / T-07 LangGraph)
| # | 任务 | 产出 | 验证方式 | 依赖 |
|---|---|---|---|---|
| C0 | 阶段 C 开工前置:requirements 增补 pytest-asyncio(异步 Tool 单测需要) |
依赖落地 | pytest --version 正常 |
T-07 临近 |
| C1 | service/risk/chat_tools.py:alert_query / customer_context / suitability_check / aml_lookup 四个只读 Tool |
Tool 集 | 单测:各 Tool 输出结构 + alert_id 溯源 | B6、T-07 |
| C2 | agent_service.py 注册 risk 分支 StateGraph(intent → tool → respond)+ DeepSeek + 输出规范(alert_id、仅供参考标注) |
对话闭环 | A-6:"今天有多少待审预警" | C1、T-01、T-03 |
| C3 | A-6 验收 + 边界测试(诱导处置 → 拒绝并引导 API) | 验收记录 | 手测 | C2 |
里程碑与提交策略
| 里程碑 | 内容 | 提交 |
|---|---|---|
| M1 | 阶段 A 完成(suitability 可用) | 逐任务 commit + 打 tag risk-m1 |
| M2 | 阶段 B 完成(事件线全链路,验收 A-1 |
逐任务 commit + 打 tag risk-m2 |
| M3 | 阶段 C 完成(对话线验收 A-6) | 逐任务 commit + 打 tag risk-m3 |
| M4 | 收尾:MEMORY/TODO 状态复核与边界文字对齐 | docs: 风控模块验收记录 |
Commit 规范(自《03-团队分工》内联,该文档当前在仓库外):
格式: <type>: <描述>
type: feat 新功能 / fix 修复 / docs 文档 / refactor 重构 / test 测试 / chore 辅助
粒度: 每个任务至少一个 commit(A1、A2…各自独立可回溯);里程碑完成时打 tag
示例: feat: suitability 公共校验服务(SUIT-001~008)
- 每任务完成 = 代码 + 测试通过 + 验证方式打勾 + 即时更新
docs/memory/TODO.md勾选,方可进下一任务;M4 仅做最终复核 - 阶段 B 期间若 T-01/T-02 就绪,B7 的 lifespan 立即接审计中间件(T-02)
已知依赖风险
- T-05 未执行:A2/B8 前需本机灌库(
reset.ps1+ 共用底座 SQL,FLOW §0 ③④,一次性本机操作) - T-01 未做 → 假 AuthContext 过渡规范(B6 落地,零返工):
AuthContextPydantic 模型(actor_id / roles / customer_id)与get_auth_context()工厂签名在 A4 冻结- dev 模式(
app_env=development)从X-Debug-Role/X-Debug-Actor请求头构造 AuthContext;生产环境(app_env != development)启动时检测到 debug 依赖注册直接拒绝启动 - T-01 就绪后仅替换工厂内部为 JWT 解析,接口签名与调用方零改动,无双份维护
- B8 权限类用例(A-7/A-9)通过切换 debug 头执行,可完整验证权限矩阵
- Ollama 未启动:不影响风控模块(无 Embedding 依赖)