# 风控业务第二版迁移清单 > 目标仓库:`group_fqcd_jr_rm2` > > 基线分支:`origin/qyqy_develop`,基线提交:`6516ccb` > > 开发分支:`RM2_develop` > > 迁移原则:只迁移风控业务能力,复用第二版公共底座;不复制第一版鉴权、同步数据库、私有兼容接口和演示数据。 > > 唯一业务来源:`C:\Users\Windows\PycharmProjects\python0626zsProject\nanfangjijin_demo` > > 禁止来源:`D:\项目\项目2代码\group_fqcd_jr`。该目录是被放弃的第一次迁移,只允许用于查看历史问题,不再复制任何代码。 ## 一、不可违反的边界 - 数据库以 `docs/00-新数据库基线设计.md` 和 `docs/02-数据库建表设计.md` 为准;允许新增表、新增字段和新增 Alembic 迁移,不修改、重命名或删除已有表名和字段定义。 - 已有业务代码只读:不修改、不重命名、不删除。迁移代码优先新增文件;接入主路由或 Agent 工厂时只允许追加不改变原逻辑的注册行。 - HTTP 接口以 `docs/05-接口文档.md` 为准,使用 `/api/v1`、`{data, meta}` 信封、统一错误码和调用方提供的 trace。 - 时间和日期存储统一按 UTC;对外时间使用 RFC 3339。 - 金额、价格、数量和概率使用十进制字符串;BIGINT 主键对外使用字符串。 - Controller 只调用 Service;Repository 负责数据库访问;Controller 不导入 ORM 或 SQLAlchemy Session。 - 业务 Agent 必须继承 `BaseAgent`、由 `AgentFactory` 创建,并经过鉴权、配置、记忆、意图、模型、工具、合规、审计和 Outbox。 - 风控 Agent 只读业务数据,不得确认、调查、排除、结案、升级预警或修改交易数据。 - 不把 `private_frontend`、私有登录、私有兼容路由和演示数据脚本复制到公共代码。 ## 二、旧迁移资产复用规则 | 原项目资产 | 第二版处理方式 | |---|---| | `app/model/entities.py` | 作为字段和业务关系参考;第二版优先复用 `app/model/fund.py`,缺失表再新增独立只读映射 | | `app/service/risk_service.py` | 迁移状态机、行为分和查询规则,按第二版 MVC+S 重写 | | `app/service/risk_scan_service.py` | 迁移规则算法、合并和防重,统一使用第二版 Session、UTC 和审计 | | `app/tool/rule_engine.py` | 迁移规则定义和证据生成逻辑,不迁移同步会话 | | `app/service/evidence_list_service.py` | 迁移八类证据列表查询和过滤,接入第二版只读 Repository | | `app/service/evidence_service.py` | 迁移预警证据详情聚合和脱敏 | | `app/service/evidence_archive_service.py` | 迁移文件签名、扩展名、大小和归档安全逻辑 | | `app/service/notification_service.py` | 迁移通知创建、分页和预警编号展示 | | `app/service/daily_report_service.py` | 迁移九段式日报统计和降级建议 | | `app/service/daily_report_mail_service.py` | 迁移多邮箱和邮件失败处理,接入第二版开关与 dry-run | | `app/service/agent_chat_service.py` | 作为对话行为参考,按第二版 Agent Run、工具治理和 SSE 重写 | | `app/service/agent_service.py` | 迁移研判、话术、工单摘要和日报业务内容 | | `app/prompt/agent_chat.py`、`app/prompt/daily_report.py` | 迁移业务提示词和边界,模型调用统一走第二版模型服务 | | `app/api/risk.py`、`app/api/evidence.py`、`app/api/chat.py`、`app/api/admin.py` | 作为业务接口清单参考;正式接口全部重新映射到第二版 `/api/v1` 契约 | | `app/static/index.html`、`app/static/app.js`、`app/static/style.css` | 迁移正式工作台页面,适配第二版 JWT、响应信封、分页和 Agent Run SSE | | `app/service/risk_scan_scheduler.py` | 迁移调度语义,后台执行接入第二版 Worker 和租约 | ## 三、执行阶段 ### R0 新基线核对 - [x] 从 `origin/qyqy_develop` 创建 `RM2_develop`。 - [x] 确认第二版接口文档、错误码、信封、UTC、游标分页和 Agent 接入规范。 - [x] 确认第二版已有 `app/model/fund.py`,完整映射风控使用的 `fin_*` 只读表。 - [ ] 对照 `qyqy_develop` 现有 Fund 模型确认风控查询所需字段缺口。 - [ ] 对照第二版基线确认所有风控表、字段、唯一键和主键生成方式。 - [ ] 建立第二版风控专项测试目录和测试夹具。 验收:能够回答风控数据的来源、写入点、权限校验、事务边界和审计位置,且不改数据库结构。 ### R1 风控只读模型与 Repository 状态:`[-] 进行中` - [ ] 复用 `app/model/fund.py`,不再重复映射已有 Fund 模型。 - [x] 为数据库已存在的 `sys_user`、`sys_login_record`、`biz_work_order` 新增最小只读 ORM 映射。 - [x] R1.1 模型字段专项测试通过:`3 passed`。 - [x] 新增 `RiskRepository`,统一客户范围、脱敏、分页和稳定排序。 - [x] 支持风险概览、未闭环预警列表、预警详情。 - [x] R1.2 Repository 专项测试通过:`5 passed`。 - [ ] 支持八类证据查询:客户、产品、交易、资金、持仓、登录、预警、通知。 - [ ] 所有列表遵循第二版 `{data, meta}` 和分页规范;预警队列业务限制每页 5 条,其余证据每页 10 条。 - [ ] 不向 Agent 或接口泄露非必要内部主键。 验收:Repository 在授权范围内只返回允许数据;越权、空范围和无数据场景均失败关闭或返回空集。 ### R2 风控公共查询接口 - [ ] 新增风控 Controller、请求参数和响应 Mapper。 - [ ] 实现风险概览。 - [ ] 实现预警队列和详情。 - [ ] 实现八类证据分页查询。 - [ ] 行为分、风险等级、规则编号、客户、产品、时间筛选。 - [ ] 使用 `AuthorizationService` 校验权限和客户范围。 - [ ] 所有响应符合 `{data, meta}`、错误码和 `X-Trace-ID`。 验收:Controller 不访问 Model/Repository;非法分页、非法筛选和越权请求有明确错误;接口测试通过。 ### R3 规则扫描、人工处置和行为分 - [ ] 迁移当前实际启用的 RW 规则和证据生成逻辑。 - [ ] 迁移同交易合并、重复扫描防重和事务边界。 - [ ] 迁移手动扫描接口。 - [ ] 迁移确认接收、调查、误报关闭、结案和升级标记。 - [ ] 结案时按低 3、中 5、高 20 扣减行为分,最低为 0。 - [ ] 误报和未闭环不扣分。 - [ ] 所有状态和行为分变化写入审计。 验收:非法状态跳转失败;重复请求幂等;预警、行为分和审计一致。 ### R4 证据归档、通知和日报 - [ ] 迁移证据文件上传,校验扩展名、签名、大小和文件名。 - [ ] 文件成功写入后再更新预警证据快照;失败回滚并清理文件。 - [ ] 迁移高风险通知创建和通知分页。 - [ ] 通知开关和邮件开关接入第二版配置中心。 - [ ] 迁移九段式日报,历史未闭环事项完整统计。 - [ ] 日报建议模型调用统一走第二版模型服务,失败时规则降级。 - [ ] 支持多邮箱校验和 dry-run,不建立额外邮件业务留痕。 验收:文件、通知和日报失败不会误写主业务状态;开关关闭时不调用外部服务。 ### R5 奶龙风控智能助手 - [ ] 按第二版 `BaseAgent` 实现 `RiskAgent`。 - [ ] 在 `register_business_agents()` 注册 `agent_type=risk`。 - [ ] 声明只读工具:风险概览、预警查询、预警证据。 - [ ] 支持客户、产品、风险等级、规则编号和时间筛选。 - [ ] 生成研判、话术和工单摘要时使用不同输出合同。 - [ ] 增加功能边界、免责声明、空内容校验和分类降级。 - [ ] 发布 `agent_tools` 和 `agent_intent_config` 配置。 - [ ] 使用真实 Agent Run、Worker、结果查询和 SSE 验收。 验收:没有发布配置时工具失败关闭;模型异常不能绕过合规审查;Agent 不能执行任何处置。 ### R6 定时扫描与后台任务 - [ ] 将定时扫描接入第二版 Worker 和租约机制。 - [ ] 扫描开关、周期、重试和并发策略使用环境变量配置。 - [ ] 手动扫描和定时扫描共用同一 Service。 - [ ] 默认关闭,不在 Web 进程启动后台线程。 验收:多次并发扫描不重复生成预警;关闭开关不执行;失败可重试。 ### R7 正式单页工作台 - [ ] 将完整 HTML、CSS 和 JavaScript 迁入主项目静态资源目录。 - [ ] 适配正式 JWT、统一错误信封、`/api/v1/risk` 和 Agent Run SSE。 - [ ] 保留概览、八类证据、预警队列、详情弹窗、处置、日报和 Agent 对话。 - [ ] 预警队列每页 5 条,其余表每页 10 条。 - [ ] 桌面和常用窄屏无文字重叠、无布局跳变。 验收:从登录到证据查询、预警处置、Agent 对话和日报发送均可通过正式接口完整操作。 ### R8 回归、清理和交付 - [ ] 风控单元测试、接口测试和数据库集成测试通过。 - [ ] Agent 正常路径、越权、工具拒绝、模型失败和 SSE 恢复通过。 - [ ] 主项目全量测试、Ruff、MyPy、架构检查和 schema audit 通过。 - [ ] 删除私有兼容依赖,私有页面不再参与正式迁移。 - [ ] 更新需求文档、接口文档、字段映射和回滚说明。 ## 四、当前下一步 ### R0.1 第二版模型缺口核对 状态:`[x] 已完成` - [x] 确认第二版已有 `app/model/fund.py`。 - [x] 确认风险预警、通知、客户画像、交易、资金、持仓等表已有模型。 - [x] 确认 `sys_user` 数据库表已存在;当前由 `IdentityRepository` 和 `SuitabilityService` 通过原生 SQL 访问,仅有通用 ORM 映射缺口。 - [x] 确认 `sys_login_record` 数据库表已存在,当前缺少 ORM 映射。 - [x] 确认 `biz_work_order` 数据库表已存在,当前缺少 ORM 映射。 - [x] 确认 R1 只新增上述三张已有表的最小只读 ORM 映射,不新增数据库表。 结论:`app/model/fund.py` 已覆盖大部分 `fin_*` 风控查询表;不得重复映射。R1 需要新增一个风控专用 ORM 模块,只容纳上述三张当前缺失映射的已有表,或优先通过现有 Repository 查询方式接入。此步骤不执行数据库迁移、不改变表结构。 ### R0.2 风控查询仓储设计 状态:`[x] 已完成` - [x] 明确哪些查询复用 `FundQueryRepository`。 - [x] 明确哪些查询需要新增风控专用 Repository。 - [x] 明确客户范围和字段脱敏的调用链。 - [x] 输出最小实现方案和测试清单。 确认点:R0.2 方案完成并测试后停下,等待确认再进入 R1。 #### 可直接复用 以下实体已由 `app/model/fund.py` 映射,并由 `FundQueryRepository` 提供静默拒绝的客户范围: - `fin_product` - `fin_customer_profile` - `fin_risk_assessment` - `fin_transaction` - `fin_capital_flow` - `fin_holding` - `fin_risk_alert` - `fin_risk_notification` 以下查询可以直接使用 `FundQueryRepository`: - 风险测评分页。 - 交易、资金、持仓基础分页。 - 预警和通知基础分页。 - 按客户范围限制的简单单表查询。 #### 需要新增 新增 `app/model/risk.py`,只映射以下数据库已存在、但当前缺少 ORM 的只读表,不执行建表或改字段: - `sys_user`:客户编号、用户名、用户类型、客户分层、投资人类型、状态。 - `sys_login_record`:登录时间、结果、地区、设备、常用设备和失败原因。 - `biz_work_order`:工单编号、客户、产品、渠道、状态、风险揭示、二次确认、录音编号、提交和处理时间。 新增 `app/repository/risk_repository.py`,原因如下: - 预警列表需要联表客户、画像、交易和产品,并在输出前统一脱敏。 - 客户证据列表需要关联 `sys_user + fin_customer_profile + fin_risk_assessment`。 - 交易证据需要关联客户、产品、工单和风险留痕字段。 - 登录证据和工单证据需要访问第二版尚未映射的表。 - 预警详情需要一次聚合预警、客户、交易、产品、工单和相关证据。 - 风险概览需要风险等级、待处理数量、超时数量和重点事件。 #### 最小文件计划 新增文件,不修改已有业务代码: ```text app/model/risk.py app/repository/risk_repository.py app/api/schemas/risk.py app/service/risk_query_service.py app/api/controllers/risk.py tests/unit/repository/test_risk_repository.py tests/unit/api/test_risk_controller.py ``` 接入正式路由时,`app/main.py` 只允许追加一行 import 和一行 `include_router`,不改变已有中间件、异常处理和其他路由。 #### 序列化和分页约定 - 主键和数据库 BIGINT 对外返回字符串。 - 金额、价格、数量和概率返回十进制字符串。 - 时间在数据库内按 UTC,输出 RFC 3339。 - 客户姓名只显示首字,其余替换为 `*`。 - 预警队列业务默认 limit=5,其他证据默认 limit=10。 - 公共列表接口使用 `{data, meta}`;游标和过滤条件绑定,非法游标返回 `INVALID_CURSOR`。 #### R1 测试清单 - [ ] 未授权角色、缺少权限和空客户范围返回拒绝或空集。 - [ ] 客户姓名、手机号和敏感编号脱敏。 - [ ] 风险概览统计与未闭环状态一致。 - [ ] 预警队列按风险等级和主键稳定排序。 - [ ] 八类证据查询分别覆盖有数据、空数据和越权范围。 - [ ] 金额、时间、布尔值和 BIGINT 序列化符合第二版契约。 - [ ] 非法游标、非法排序和非法筛选返回明确错误。 ## 五、逐步执行门禁 后续每一项都必须严格按以下顺序执行: 1. **思考**:阅读原项目对应文件、第二版目标模块、调用链、数据来源、写入点和权限来源。 2. **方案**:写出最小迁移范围、不修改的边界、影响模块和验证方式。 3. **迁移**:只实现当前一个步骤,不顺手迁移下一步。 4. **测试**:运行当前步骤的专项测试;失败先修复,不带问题进入下一步。 5. **确认**:汇报修改文件、测试结果、剩余风险和下一步,等待用户确认。 未完成当前步骤的“测试 + 确认”,不得开始下一步。 ## 六、执行记录 ### R1.1 补充只读 ORM 映射 状态:`[x] 已完成,已确认` 新增文件: - `app/model/risk.py` - `tests/unit/model/test_risk_models.py` 验证结果:使用 Python 3.13.5 运行专项测试,`3 passed`。 下一步:R1.2 新增 `RiskRepository`,实现风险概览、未闭环预警列表和预警详情查询。等待确认后执行。 ### R1.2 风控核心查询 Repository 状态:`[x] 已完成,已确认` 新增文件: - `app/repository/risk_repository.py` - `tests/unit/repository/test_risk_repository.py` 已实现: - `overview()`:未闭环总数、等级分布、待处理、超时和重点预警。 - `list_alerts()`:客户、产品、风险等级、规则编号、关键字和时间筛选。 - `get_alert_detail()`:聚合预警、客户、交易、产品、工单和证据快照。 - 客户与交易账号范围校验,范围缺失时失败关闭。 - 客户姓名脱敏和高风险优先排序。 验证结果:使用 Python 3.13.5 运行专项测试,`5 passed`。 下一步:R1.3 检查 Repository 只读契约和现有 `FundQueryRepository` 的复用边界。等待确认后执行。 ### R1.3 Repository 只读契约与复用边界 状态:`[x] 已完成,已确认` 新增文件: - `tests/unit/repository/test_risk_repository_contract.py` 已确认: - `RiskRepository` 不导入 insert/update/delete/merge 等写 SQL。 - 不调用 `session.add/commit/flush/rollback/with_for_update` 等写事务方法。 - 对外只暴露 `overview`、`list_alerts`、`get_alert_detail` 三类只读用例。 - 风险概览、预警列表和预警详情需要多表联表,保留在 `RiskRepository`。 - 后续单表或简单实体查询必须复用 `FundQueryRepository`,不得在 `RiskRepository` 重复实现。 验证结果:使用 Python 3.13.5 运行契约测试,`3 passed`。 下一步:R1.4 实现八类证据查询。优先复用 `FundQueryRepository`;只有需要客户身份、登录记录、工单联表或跨表拼装时才扩展 `RiskRepository`。等待确认后执行。 ### R1.4 八类证据查询 状态:`[x] 已完成,已确认` 已在 `RiskRepository` 实现: - `list_customers()`:客户画像、最新风测、行为和状态。 - `list_products()`:直接复用 `FundQueryRepository`。 - `list_transactions()`:交易、客户、产品、工单和留痕字段。 - `list_capital_flows()`:资金流水。 - `list_holdings()`:持仓、持有天数和持仓占比。 - `list_login_records()`:登录结果、地区和设备。 - `list_alerts()`:预警队列和筛选。 - `list_notifications()`:通知和预警编号。 验证结果:八类证据查询和只读契约专项测试共 `13 passed`。 下一步:R2.1 新增风控查询 Schema 与 Service,把 Repository 记录转换为第二版 `{data, meta}` 响应投影。等待确认后执行。 ### R2.1 风控查询 Schema、游标和 Service 状态:`[x] 已完成,待确认` 新增文件: - `app/core/risk_cursor.py` - `app/api/schemas/risk.py` - `app/service/risk_query_service.py` - `tests/unit/service/test_risk_query_service.py` 已实现: - 预警队列和证据列表参数校验,队列最多 5 条,证据最多 10 条。 - 不透明偏移游标及非法游标错误。 - 客户数据范围转换和失败关闭。 - Repository 记录到第二版响应字段的统一投影。 - BIGINT、Decimal、datetime、tuple 的序列化。 - 风险概览、预警列表、预警详情和八类证据 Service 方法。 验证结果:Schema、游标和 Service 专项测试共 `6 passed`。 下一步:R2.2 新增风控 Controller 和正式路由,只负责参数绑定、调用 Service 并包装 `{data, meta}`。等待确认后执行。 ### R2.2 风控只读 Controller 与正式路由 状态:`[x] 已完成,待确认` 新增和接入: - 新增 `app/api/controllers/risk.py`。 - `app/main.py` 仅追加一条 import 和一条 `include_router`。 - 新增 `tests/unit/api/test_risk_controller.py`。 正式接口: - `GET /api/v1/risk/overview` - `GET /api/v1/risk/alerts` - `GET /api/v1/risk/alerts/{alert_no}` - `GET /api/v1/risk/evidence/{source}` 验证结果:风控模型、Repository、Service 和 Controller 合并专项测试共 `26 passed`。 下一步:R3.1 迁移规则扫描,先实现扫描 Service 和重复预警防重测试。等待确认后执行。 ### R3.1 规则扫描 Service 与 RW-003 状态:`[x] 已完成,待确认` 新增文件: - `app/service/risk_scan_service.py` - `tests/unit/service/test_risk_scan_service.py` 已实现: - 异步 `RiskScanService` 和 `RiskRuleEngine`。 - 扫描权限校验、进程内并发保护、事务提交和失败回滚。 - 显式生成 `fin_risk_alert.id` 业务主键。 - 同交易多规则预警合并。 - 按交易和规则编号防重。 - RW-003、RW-007、RW-012、RW-015、RW-018 五条规则代码。 - RW-003 事实型摘要、防重、合并和扫描事务专项测试。 验证结果:当前风控迁移全部专项测试共 `31 passed`。 下一步:R3.2 为 RW-007、RW-012、RW-015、RW-018 补齐正例、反例和边界测试。等待确认后执行。 ### R3.2 剩余规则边界测试 状态:`[x] 已完成,待确认` 补充覆盖: - RW-007:C2/R5 高风险、C4/R5 中风险、留痕完整不触发、等级匹配不触发。 - RW-012:高龄大额赎回触发、历史均值三倍边界触发、超过三倍不触发、常用设备不触发。 - RW-015:凌晨零点且刚好 10000 元触发、非凌晨不触发、超过 10000 元不触发。 - RW-018:渠道“定投”和“自动定投”触发、普通手机渠道不触发。 验证结果:当前全部风控专项测试共 `37 passed`。 下一步:R3.3 新增扫描 Controller 和正式接口,将手动扫描映射到 `POST /api/v1/risk/alerts/scan`。等待确认后执行。 ### R3.3 手动扫描接口 状态:`[x] 已完成,待确认` 已接入: - `POST /api/v1/risk/alerts/scan` - Controller 调用 `RiskScanService`,只包装 `{data, meta}`。 - 未认证请求返回统一 401。 - 请求体为空,扫描权限由 Service 层校验。 验证结果:扫描 Controller 与 Service 联合专项测试共 `16 passed`。 下一步:R4.1 迁移人工处置状态机,覆盖确认接收、调查、误报关闭、结案、升级和行为分。等待确认后执行。 ### R4.1 人工处置状态机与行为分 状态:`[x] 已完成,待确认` 新增文件: - `app/service/risk_action_service.py` - `tests/unit/service/test_risk_action_service.py` 已实现: - 确认接收。 - 进入调查。 - 关闭误报并记录理由。 - 完成结案并记录处置结论。 - 升级处理,升级不是终态。 - 结案行为分扣减:低 3、中 5、高 20,最低为 0。 - 误报和未闭环不扣分。 - 每次状态变化写入 `interaction_audit`。 - 重复确认、未确认进入调查、重复升级和非法状态转换拒绝。 验证结果:当前全部风控专项测试共 `46 passed`。 下一步:R4.2 新增人工处置 Schema、Controller 和正式接口,将状态机映射到 `/api/v1/risk/alerts/{alert_no}` 子资源。等待确认后执行。 ### R4.2 人工处置 Controller 与接口 状态:`[x] 已完成,待确认` 新增接口: - `POST /api/v1/risk/alerts/{alert_no}/acknowledgements` - `POST /api/v1/risk/alerts/{alert_no}/investigations` - `POST /api/v1/risk/alerts/{alert_no}/exclusions` - `POST /api/v1/risk/alerts/{alert_no}/resolutions` - `POST /api/v1/risk/alerts/{alert_no}/escalations` 已实现: - 处置请求参数校验。 - JWT、限流和 `{data, meta}` 响应信封。 - 误报理由、处置结论和升级理由不能为空。 - Controller 只调用 `RiskActionService`。 验证结果:当前全部风控专项测试共 `48 passed`。 下一步:R5.1 迁移证据文件归档,覆盖类型、签名、大小、重复上传和失败回滚。等待确认后执行。 ### R5.1 证据文件归档 Service 状态:`[x] 已完成,待确认` 新增文件: - `app/service/risk_evidence_archive_service.py` - `tests/unit/service/test_risk_evidence_archive_service.py` 已实现: - 仅允许 JPG、PNG、WEBP、PDF、Word、Excel、PPT 和 UTF-8 TXT。 - 扩展名、文件签名、Office ZIP 结构和 TXT 编码校验。 - 文件大小限制,默认 10 MB。 - 文件名统一替换为预警编号加原扩展名。 - 项目目录内路径校验,禁止目录穿越。 - 同一预警禁止重复归档或覆盖。 - 文件先写临时文件再原子替换。 - 数据库提交失败时回滚并删除目标文件。 - 归档信息写入 `evidence_snapshot.evidence_archive`。 - 归档动作写入 `interaction_audit`。 验证结果:当前全部风控专项测试共 `54 passed`。 下一步:R5.2 新增证据上传 Controller 和正式接口。等待确认后执行。 ### R5.2 证据上传 Controller 与接口 状态:`[x] 已完成,待确认` 新增接口: - `POST /api/v1/risk/alerts/{alert_no}/evidence` 已实现: - multipart 文件上传。 - Controller 只调用 `RiskEvidenceArchiveService`。 - 上传对象在请求结束后关闭。 - `{data, meta}` 响应信封。 - Service 校验和失败回滚继续生效。 验证结果:证据上传接口与归档 Service 联合专项测试共 `14 passed`。 下一步:R6.1 迁移通知记录创建和分页查询,通知内容必须包含预警编号。等待确认后执行。 ### R6.1 通知记录创建与分页 状态:`[x] 已完成,待确认` 新增文件: - `app/service/risk_notification_service.py` - `tests/unit/service/test_risk_notification_service.py` 已实现: - 站内通知记录创建。 - 邮件通知记录创建;迁移阶段当时尚未接入 SMTP。 - 通知主键和通知编号显式生成。 - 通知内容自动包含“预警编号:xxx”。 - 高风险批量通知记录。 - 通知分页复用 `RiskQueryService` 和 `RiskRepository`。 > 2026-09-12 更新:本节“本阶段不外发 SMTP”只描述 R6.1 当时状态。当前手动扫描和定时扫描命中高风险后已恢复真实邮件发送,通知状态会回写 `已发送` 或 `发送失败`。当前高风险预警邮件只支持一个收件人。 验证结果:当前全部风控专项测试共 `58 passed`。 下一步:R6.2 将高风险通知创建接入扫描事务,并验证通知失败不破坏预警主事务。等待确认后执行。 ### R6.2 高风险通知接入扫描 状态:`[x] 已完成,待确认` 已实现: - 扫描生成高风险预警后自动创建站内通知记录。 - 高风险邮件通知记录和真实 SMTP 外发,发送失败不回滚预警。 - 高风险预警邮件当前只支持一个收件人,多个邮箱值只取第一个。 - 通知创建使用嵌套保存点隔离。 - 通知创建失败只记录日志,不回滚预警扫描主事务。 - 扫描结果增加 `notification_count`。 验证结果:当前全部风控专项测试共 `60 passed`。 下一步:R6.3 新增通知分页查询接口,复用统一游标和响应信封。等待确认后执行。 ### R6.3 通知分页查询接口 状态:`[x] 已完成,待确认` 新增接口: - `GET /api/v1/risk/notifications` 已实现: - 通知专用分页参数。 - 关键字、发送状态、时间范围和游标筛选。 - 每页最多 10 条。 - `{data, meta}` 响应信封。 - 复用通知 Service、Repository 和统一序列化。 验证结果:通知查询接口与 Service 联合专项测试共 `12 passed`。 下一步:R7.1 迁移九段式日报和日报邮件 Service。等待确认后执行。 ### R7.1 九段式日报和邮件 Service 状态:`[x] 已完成,待确认` 新增文件: - `app/service/risk_daily_report_service.py` - `app/service/risk_daily_report_mail_service.py` - `tests/unit/service/test_risk_daily_report_service.py` 已实现: - 九段式日报统计模板。 - 当日预警、历史未闭环、误报、处置结果和规则效果统计。 - 历史未闭环不受分页限制。 - 模型建议走第二版模型服务,失败、空内容或越权表述时规则降级。 - 日报生成审计。 - 日报异步流式事件。 - 邮件 Service 默认禁用、支持 dry-run、不主动连接 SMTP。 验证结果:当前全部风控专项测试共 `65 passed`。 下一步:R7.2 新增日报 Controller 和正式接口,覆盖结构化生成、流式生成和邮件发送。等待确认后执行。 ### R7.2 日报 Controller 与接口 状态:`[x] 已完成,待确认` 新增接口: - `POST /api/v1/risk/daily-report` - `POST /api/v1/risk/daily-report/stream` - `POST /api/v1/risk/daily-report/mail` 已实现: - 结构化日报生成。 - SSE 流式日报事件。 - 日报邮件发送。 - 多收件人去重和邮箱校验。 - `{data, meta}` 响应信封。 - 邮件默认禁用或 dry-run,不主动连接 SMTP。 验证结果:当前全部风控专项测试共 `66 passed`。 下一步:R8.1 迁移奶龙风控智能助手,按第二版 BaseAgent 和 AgentFactory 注册。等待确认后执行。 ### R8.1 奶龙风控智能助手 状态:`[x] 已完成,待确认` 新增和接入: - `app/core/risk_contracts.py` - `app/service/risk_tools.py` - `app/service/agent/implementations/risk_agent.py` - `app/service/agent/bootstrap.py` 追加 RiskAgent 和三个只读工具注册 - `tests/contract/test_risk_agent_contract.py` 已实现: - `agent_type=risk`,展示名称为“奶龙风控智能助手”。 - 四个意图:风险概览、风险查询、预警证据、通用边界。 - 三个只读工具:风险概览、预警查询、预警证据。 - 工具权限 `risk:alert:read`,允许角色 `risk_operator/admin`。 - Agent 不执行确认、调查、误报关闭、结案或升级。 - 未发布工具白名单时失败关闭。 - 返回功能边界和只读说明。 验证结果:当前全部风控专项测试和 Agent 契约测试共 `71 passed`。 下一步:R8.2 发布 risk Agent 的 `agent_tools` 和 `agent_intent_config` 配置,并执行真实 Agent Run/SSE 验收。等待确认后执行。 ### R8.2 配置发布与真实 Agent 验收 状态:`[x] 已完成,待确认` 已执行: - 生成并验证本地 RS256 JWT 公私钥。 - 对空库执行 Alembic 基线迁移,51 张表结构审计通过。 - 写入 9001/9002/9003 测试身份和基础 RBAC。 - 补充并授予风控读取、写入、扫描权限。 - 写入并激活 DeepSeek `deepseek-flash` 模型端点。 - 发布 risk Agent 工具白名单。 - 发布四条 risk 意图配置。 - 新增 `tools/seed_risk_agent_config.py` 和 `tools/risk_agent_e2e.py`。 真实验收结果: - Agent Run 受理成功。 - Worker 执行终态为 `succeeded`。 - 工具调用为 `get_risk_overview/succeeded`。 - 工具审计已写入。 - SSE 返回 `text/event-stream` 并包含 `done`。 - Milvus 不可用、Redis 模块不可用时按设计降级,没有阻塞风险概览查询。 下一步:R9 正式前端工作台迁移。等待确认后执行。 ### R9.1 正式工作台静态资源迁移 状态:`[x] 已完成,待确认` 已迁移: - `app/static/index.html` - `app/static/style.css` - `app/static/app.js` 已接入: - `app/main.py` 追加 `/static` 静态资源挂载。 - 新增静态资源测试 `tests/unit/api/test_risk_workbench_static.py`。 验证结果:页面、CSS 和 JavaScript 均可由第二版应用提供,专项测试 `1 passed`。 下一步:R9.2 适配前端 JWT、`/api/v1/risk` 接口、统一响应信封和 Agent Run SSE。等待确认后执行。 ### R9.2 私有前端 REST、JWT 和日报 SSE 适配 状态:`[x] 已完成,待确认` 只在 `private_frontend` 中完成: - 增加浏览器 Bearer 令牌管理和本地保存。 - `/api/risk/**` 迁移到 `/api/v1/risk/**`。 - 统一拆包 `{data, meta}` 响应。 - 适配游标分页的上一页/下一页。 - 适配八类证据、预警、通知、详情和处置子资源。 - 适配证据上传、扫描、日报生成、日报流和日报邮件。 - 流式解析同时支持 NDJSON 和 SSE。 - 新增私有静态代理服务器 `private_server.py`。 - 新增短期令牌生成脚本 `generate_token.py`。 验证结果:JavaScript 和 Python 语法检查通过,短期令牌生成成功。 剩余:Agent 对话仍使用原项目 `/api/risk/agent/chat/stream`,将在 R9.3 改为第二版 Agent Run/SSE 编排。 ### R9.3 私有前端 Agent Run 对话 状态:`[x] 已完成,待确认` 私有前端对话已改为: 1. `POST /api/v1/agent-runs` 创建 `agent_type=risk` 的真实运行。 2. 使用浏览器生成的 session_id 和 idempotency_key。 3. 订阅 `/api/v1/agent-runs/{run_id}/events` SSE。 4. 展示工具调用、replace/delta、done/error。 5. 当前预警自动将预警编号拼入 Agent 消息。 运行依赖: - Web 服务。 - 独立 Worker:`python -m app.worker`。 - 9002 访问令牌。 验证结果:私有前端 JavaScript 语法检查通过,旧对话接口已移除。 ### R10.1 结构化输出恢复 状态:`[x] 已完成,已确认` 新增或修改: - `app/service/risk_analysis_service.py` - `app/service/agent/implementations/risk_agent.py` 追加结构化分析路由 - `tests/unit/service/test_risk_analysis_service.py` - `tests/contract/test_risk_agent_contract.py` 已实现: - 恢复预警研判、回访话术、工单摘要三种独立输出。 - 三种输出使用不同的模型任务、提示词和模板降级内容。 - 模型返回空内容、超长内容或包含越权处置声明时自动降级。 - 生成结果写入 `fund_risk_alert.ai_analysis`,并追加交互审计。 - 生成前校验 `risk:alert:read` 权限和数据范围。 - 自然语言携带预警编号时,可路由到对应结构化输出。 验证结果:语法检查通过,结构化输出与 Agent 契约专项测试 `10 passed`。 遗留告警:Pydantic 对公共配置中 `model_` 前缀字段给出命名警告,不属于本次业务逻辑故障。 下一步:R10.2 模型自主选择工具和多轮工具调用。等待确认后执行。 ### R10.2 模型自主选择工具与多轮工具调用 状态:`[x] 已完成,已确认` 新增或修改: - `app/service/risk_agent_model_client.py` - `app/service/agent/implementations/risk_agent.py` - `app/service/risk_tools.py` - `tests/unit/service/test_risk_agent_model_client.py` - `tests/contract/test_risk_agent_contract.py` 已实现: - 奶龙风控智能助手由模型自主选择风险概览、预警查询和预警证据工具。 - 支持最多 4 轮模型调用和累计 6 次工具调用。 - 工具名、工具类型、调用编号和 JSON 参数均执行严格校验。 - 只允许调用当前 Agent 配置中已授权的只读工具。 - 工具仍通过公共 `ToolExecutor` 执行,权限校验、超时和审计规则不变。 - 工具结果移除内部数据库主键,并限制单次返回长度。 - 模型最终回复校验空内容、超长内容、越权处置声明和工具协议残留。 - 模型或工具编排失败时切换到既有确定性查询降级路径。 - `search_risk_alerts` 改为按每页 10 条循环读取全部未闭环预警,不再只取首批 10 条。 验证结果:R10.2 专项测试 `10 passed`,完整风控专项测试 `77 passed`。 遗留告警:Pydantic 对公共配置中 `model_` 前缀字段给出命名警告,不属于本次业务逻辑故障。 下一步:R10.3 恢复误报、可放行、疑似误判等业务研判意图。等待确认后执行。 ### R10.3 误报、可放行与疑似误判研判 状态:`[x] 已完成,已确认` 新增或修改: - `app/service/risk_judgement_service.py` - `app/service/risk_tools.py` - `app/repository/risk_repository.py` - `app/service/agent/implementations/risk_agent.py` - `tests/unit/service/test_risk_judgement_service.py` - `tests/contract/test_risk_agent_contract.py` - `tests/unit/repository/test_risk_repository.py` 已实现: - 预警列表增加只读 `disposition_hint` 复核方向。 - 预警证据增加只读 `disposition_assessment` 研判草案。 - 恢复有效定投场景的可放行候选识别。 - 恢复低风险小金额非正常时段操作的疑似误报识别。 - RW-007 会重新核对客户等级、产品等级和风险揭示、二次确认、录音留痕。 - RW-003 会重新核对大额门槛和赎回比例。 - RW-012 会核对年龄、赎回金额、历史门槛和登录设备常用性。 - 预警证据详情补充资金流、持仓和登录记录。 - 模型不可用时可使用规则化只读研判草案降级。 - 所有结论均明确标注为复核草案,Agent 不执行误报关闭、放行、结案或升级。 验证结果:R10.3 专项测试 `14 passed`,完整风控专项测试 `83 passed`。 遗留告警:Pydantic 对公共配置中 `model_` 前缀字段给出命名警告,不属于本次业务逻辑故障。 下一步:R10.4 恢复自然语言客户、产品、规则和时间筛选,并补齐完整回答场景。等待确认后执行。 ### R10.4 自然语言筛选与完整回答 状态:`[x] 已完成,已确认` 新增或修改: - `app/service/risk_natural_language.py` - `app/service/risk_tools.py` - `app/service/agent/implementations/risk_agent.py` - `tests/unit/service/test_risk_natural_language.py` - `tests/unit/service/test_risk_search_result.py` - `tests/contract/test_risk_agent_contract.py` 已实现: - 支持自然语言客户编号筛选。 - 支持产品代码和产品名称筛选。 - 支持风险等级和 RW 规则编号筛选。 - 支持今天、本月、最近 N 天、明确起止日期、日期以来和截至日期。 - 中文本地时间统一在 Asia/Shanghai 解析,再转换为 UTC 查询时间。 - 模型调用工具前会收到系统预解析的筛选条件,降低日期和编号解析偏差。 - 预警查询返回完整分组汇总和精简明细。 - 汇总包含全部客户、产品、风险等级、规则和研判分布,不会因明细截断而声称只覆盖部分记录。 - 模型不可用且问题属于预警列表查询时,可使用本地筛选和完整汇总规则化回答。 验证结果:R10.4 专项测试 `14 passed`,完整风控专项测试 `88 passed`。 遗留告警:Pydantic 对公共配置中 `model_` 前缀字段给出命名警告,不属于本次业务逻辑故障。 下一步:R10.5 对话历史、会话归属、审计和分类降级。等待确认后执行。 ### R10.5 对话历史、会话归属与长期留存 状态:`[-] 暂缓,全部迁移完成后再做` 已确认的边界: - 当前 MySQL `svc_conversation_session`、`conversation_message`、`agent_run` 和审计写入继续保留。 - 不在本阶段改为 Redis-only。 - 不在本阶段修改公共 Run、Worker、消息持久化和记忆抽取链路。 - 后续优先采用 MySQL 权威保存、Redis 缓存最近会话、原始消息分级保留的方案。 - 长期留存、脱敏、归档和删除策略作为独立迁移任务处理。 下一步:R10.6 真实 Agent Run、Worker、结果查询、SSE 和业务对话端到端验收。 ### R10.6 真实 Agent Run 与业务对话端到端验收 状态:`[x] 已完成,已确认` 新增: - `tools/risk_agent_business_e2e.py` 验收场景: - 风险概览对话。 - 低风险客户和产品完整筛选回答。 - 误报、可放行和疑似误判研判对话。 通过正式链路验证: - `POST /api/v1/agent-runs` 受理为 `queued`。 - `WorkerRuntime` 实际执行 Run。 - 模型自主调用 `get_risk_overview`、`search_risk_alerts` 和 `get_alert_evidence`。 - 查询结果通过 `summary` 表示完整分组,未因明细截断声称只覆盖部分记录。 - `GET /api/v1/agent-runs/{run_id}` 返回 `succeeded`。 - SSE 返回 `text/event-stream` 并包含 `done`。 - 工具调用审计和运行完成审计均可追溯。 - Agent 未执行确认、误报关闭、放行、结案或升级动作。 验收结果: - 风险概览:`PASSED`,运行编号 `40190585-2aad-4af5-b600-d3bf72471830`。 - 完整筛选回答:`PASSED`,运行编号 `b314ad10-1926-4ada-8b1c-a5facf96beca`。 - 误报研判:`PASSED`,运行编号 `cbcb3758-0b8c-4967-9582-ff7ab66de256`。 环境降级: - Milvus 未启动,语义记忆关闭,结构化流程继续运行。 - Redis 缓存读取降级,不影响 Agent Run 结果。 - `.env` 第 12 行存在 dotenv 解析警告,不影响本次业务验收。 下一步:R11 全量回归、代码质量检查、数据库结构审计和迁移收口。等待确认后执行。 ### R11 全量回归与迁移收口 状态:`[x] 业务侧与静态检查已完成;公共底座 3 项类型问题确认不修改` 全量测试: - `python -m pytest -q --basetemp=.pytest_rm2_tmp -p no:cacheprovider` - 结果:`567 passed, 1 skipped`。 类型检查: - 已修复本次风控迁移代码中的 30 项 MyPy 严格类型错误。 - 当前 `python -m mypy app` 仍报告 3 项公共底座既有错误: - `app/service/model_gateway.py` 两个端点适配器类型错误。 - `app/worker/runtime.py` 一个请求元数据类型错误。 - 经确认,公共基座不修改,上述 3 项作为已知类型例外接受。 - 该例外不影响当前业务功能、真实 Agent Run、工具调用和 SSE。 静态检查: - `python -m pip check` 通过。 - `python -m ruff check app tests tools alembic` 通过。 - 使用 Ruff `0.16.6` 修复导入排序、未使用变量、调用默认值标记、超长行和未使用统计变量。 数据库结构: - `python tools/audit_schema.py`:51 张业务表通过,无缺失或多余业务表。 - `python tools/audit_constraints.py`:唯一键和 ORM 映射与基线一致。 - `python tools/migration_state_check.py`:数据库版本为 head `20260910_drop_review_separation`,51 张业务表。 - `python -m alembic check` 无法执行,原因是公共 `alembic/env.py` 未提供 MetaData。 剩余环境项: - `.env` 第 12 行存在 dotenv 解析警告。 - Pydantic 对公共配置中 `model_` 前缀字段给出命名警告。 - 本地 Milvus 未启动;Redis 缓存读取降级。 - R10.5 对话历史与长期留存继续暂缓,全部迁移完成后再处理。 结论:R11 业务迁移和质量门禁已完成。公共底座 3 项 MyPy 例外保持现状,Ruff、全量测试、数据库结构和迁移状态均已通过。 ### R6.4 定时规则扫描补迁移 状态:`[x] 已完成,待确认` 新增: - `app/service/risk_scan_schedule_config.py` - `app/worker/risk_scan_scheduler.py` - `tests/unit/service/test_risk_scan_schedule_config.py` - `tests/unit/worker/test_risk_scan_scheduler.py` 已实现: - 独立定时扫描 Worker,不在 Web 进程启动后台线程。 - 从 `.env` 环境变量读取开关、周期、立即执行、重试次数和轮询间隔。 - 默认关闭。 - 使用 MySQL 咨询锁防止多进程重复扫描。 - 手动和定时扫描共用同一 `RiskScanService`。 - 扫描成功和失败写入系统审计。 - 支持 `--once` 和 `--force` 本地验证参数。 环境变量: - `RISK_SCAN_SCHEDULE_ENABLED` - `RISK_SCAN_INTERVAL_MINUTES` - `RISK_SCAN_RUN_IMMEDIATELY` - `RISK_SCAN_RETRY_LIMIT` - `RISK_SCAN_POLL_SECONDS` 验证结果:专项测试 `6 passed`,Ruff 通过,MyPy 通过,全量测试 `573 passed, 1 skipped`。 本地运行方式: ```powershell python -m app.worker.risk_scan_scheduler python -m app.worker.risk_scan_scheduler --once --force ``` 下一步:将定时扫描配置写入当前 active 配置发布,并同步推送代码。等待确认后执行。