2026-09-06 15:09:13 +08:00
|
|
|
|
# 开发计划 · 风控监测 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 |
|
2026-09-06 15:37:42 +08:00
|
|
|
|
| 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③) | L3 写入 | 单测:normal→high 不降级、AML 后大额不回落 | A3 |
|
2026-09-06 15:09:13 +08:00
|
|
|
|
| B4 | `aml_service.py`(归一化+相似度匹配、scan_all)+ `engine.py`(process_trade_event 组装 + 预留客户事件钩子)+ **`scoring.py` 占位签名(FR-7 预留)** | 引擎完整 | 单测:AML 阈值边界;引擎集成冒烟 | B1、B2、B3 |
|
|
|
|
|
|
| B5 | `app/gateway/`(trade_gateway + gateway_repository 仅 INSERT core_trade)+ `api/simulate.py` 薄路由 | 网关 | 集成:convert 400、阻断不落 trade | A4、B4 |
|
2026-09-06 15:37:42 +08:00
|
|
|
|
| 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) |
|
2026-09-06 16:42:18 +08:00
|
|
|
|
| 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` 已留痕)** | 可运行应用 | `uvicorn` 启动 + `/health` + 全路由可达 | B5、B6 |
|
2026-09-06 15:09:13 +08:00
|
|
|
|
| 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 行。集成测试:A-1~A-5、A-7(状态机/compliance 403/GET 强制 aml)、A-9 越权、**trace 一致性断言** | 测试套件 + fixture | `pytest` 全绿 | B7 |
|
|
|
|
|
|
| B9a | 演示/运维脚本开发:`scripts/demo/subscribe_alerts.py`(订阅演示)+ `scripts/demo/rebuild_alerts.py`(按 trade_id 幂等重放补偿) | 2 个脚本 | 手工执行验证 | B2、B4(可与 B5~B8 并行) |
|
|
|
|
|
|
| B9b | 演示链路走查:`reset.ps1` → `prepare_risk_demo.sql` → agent 库建表 → `seed-aml-list.sql` → Swagger 逐条过 **A-1~A-5、A-7~A-9(A-6 归 M3)** | 演示 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~A-5、A-7~A-9**) | 逐任务 commit + 打 tag `risk-m2` |
|
|
|
|
|
|
| M3 | 阶段 C 完成(对话线验收 A-6) | 逐任务 commit + 打 tag `risk-m3` |
|
|
|
|
|
|
| M4 | 收尾:MEMORY/TODO 状态复核与边界文字对齐 | `docs: 风控模块验收记录` |
|
|
|
|
|
|
|
|
|
|
|
|
**Commit 规范**(自《03-团队分工》内联,该文档当前在仓库外):
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
格式: <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)
|
|
|
|
|
|
|
|
|
|
|
|
## 已知依赖风险
|
|
|
|
|
|
|
|
|
|
|
|
1. **T-05 未执行**:A2/B8 前需本机灌库(`reset.ps1` + 共用底座 SQL,FLOW §0 ③④,一次性本机操作)
|
|
|
|
|
|
2. **T-01 未做 → 假 AuthContext 过渡规范**(B6 落地,零返工):
|
|
|
|
|
|
- `AuthContext` Pydantic 模型(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 头执行,可完整验证权限矩阵
|
|
|
|
|
|
3. **Ollama 未启动**:不影响风控模块(无 Embedding 依赖)
|