Files
group_xinghuo_jinrong/docs/PRD/PRD-风控监测Agent.md
T

497 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PRD · 风控监测 Agent 模块
> 版本:v1.1(**已冻结部分 v1.0 原文不动** · 2026-09-07 追加 FR-8/FR-9/FR-10 并入,追加内容经独立 AI 评审→修订→复审通过)
> 日期:2026-09-06(v1.0 冻结)· 2026-09-07(v1.1 追加并入)
> 负责人:E(风控/记忆/测试)· 分支 `risk-control-agent`(旧称 feature/risk 已过时)
> 上游依据:`docs/需求拆解/Agent风险与合规约束汇总.md` §5、`docs/memory/REQUIREMENTS.md` Wave 2、`docs/memory/FRAMEWORK.md`、`docs/项目框架设计/表设计/`(表结构 · Redis key)、规则冻结版 `docs/PRD/附-风控规则表.md`
> 状态流转:草稿 → AI 评审 → 修订 v0.2 → 复审 → 修订 v0.3 → **用户确认冻结 v1.0** → 需求追加 v1.1 独立评审不通过(P0×4)→ 修订 v1.2 → 复审通过 → **并入 v1.1**
> 修订记录:v0.2 修复首轮评审 P0×2、P1×9、P2×10;v0.3 修复复审 P1×3 与 P2×3;v1.0 用户确认三个新增项(risk_aml_list 表、STAFF-90001 演示账号、scripts/demo/prepare_risk_demo.sql),§13 文档联动修正已执行;**v1.1 追加 FR-8(RISK-006 集中度)/ FR-9(RISK-007 时效升级)/ FR-10(RISK-008 代理人行为链)与 A-10/A-11/A-12 验收,落库映射按真实 DDL 定案(alert_type 复用 pattern + payload.alert_subtype、status 不扩枚举、payload.actor_id 承载代理人)、新增 risk_manager 上级角色(用户拍板 2026-09-07,roles JSON 承载不改表)**
---
## 1. 背景与目标
风控监测 Agent 是四 Agent 体系中**唯一可阻断交易请求**的模块(其余一律只监测不拦截)。当前后端仅有脚手架,本 PRD 定义风控模块从零到可演示的完整需求。
**目标(一句话)**:交易事件进来能实时预警,适当性不匹配能在交易前拦住,AML 命中能紧急通知人工,风控专员能对话查台账——全程留痕、全部待人工审核。
**本期不做**:自动冻户、自动上报监管、自动改正式风险等级 C1~C5、限制客户交易次数/金额、R-05 完整评分模型(只预留)、真实支付/TA 清算对接、"仅提示不阻断"的放行路径(R-02 一期所有不匹配一律阻断,放行+提示留二期)、AML 开户/信息变更触发(模拟环境无开户流程,仅预留事件入口定义,见 FR-5)。
---
## 2. 已拍板的决策(不重议)
| # | 决策 | 内容 |
| --- | --- | --- |
| D1 | Agent 划分 | 以主分支新四 Agent 为准(客户财富/代理人助手/数据分析/风控监测);本地旧文档中"客服/投顾/业务操作"命名废弃 |
| D2 | 推荐边界(全局修订) | Agent **可以推荐产品并说明理由**,但:①推荐前必须过适当性校验(SUIT-007);②输出附免责声明;③标注"需经持证投顾审核";④**禁止具体操作指令**("现在买入 X 万元""我帮您下单"类表述一律拦截) |
| D3 | 冻结边界 | 任何 Agent 不得自动冻结账户;AML 命中只做"最高级预警 + 多渠道通知 + 标记待复核",处置由**风控专员**人工完成,合规官**知悉**(见 FR-5 通知机制) |
| D4 | 范围 | R-01、R-02、R-03、R-04 全做;R-05 只预留 L3 写入接口,评分模型后置 |
| D5 | 事件来源 | **方案 A:模拟交易网关**(扮演外部 Core 交易系统,独立于 Agent 分层)——交易请求先过 R-02 校验(阻断点),通过后写 `core_trade` 并触发规则引擎 |
| D6 | 模块形态 | 两条线并存:事件驱动线(无对话)+ 对话线(风控专员 chat) |
**D2 的文档联动修正**(PRD 冻结后执行,见 §13)。
---
## 3. 系统上下文
```text
┌────────────────────────────────────────────┐
│ 客户端 / 前端 │
└──────┬──────────────────────┬──────────────┘
│ 交易请求 │ 风控专员对话
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ 模拟交易网关(外部替身) │ │ api/chat (risk) │
│ app/api/simulate.py │ │ X-Agent-Type=risk │
│ → app/gateway/* │ └──────────┬──────────┘
└──────┬──────────────┘ │ LangGraph
│ ①R-02 适当性校验 ▼
│ ├─不匹配→阻断(不落 trade) service/agent_service
│ └─匹配→②INSERT core_trade │
▼ │
┌─────────────────────┐ │
│ 风控规则引擎 │◄────────────────┘
│ service/risk/* │ R-01/R-03/R-04
└──────┬──────────────┘
│ ③写预警单 risk_alert(单事件聚合出单)
│ ④写 risk_suitability_log
│ ⑤写 L3(取最高档合并)
│ ⑥PUBLISH risk:pub:alert
▼
┌─────────────────────┐ ┌──────────────┐
│ 人工处置 API │ │ Redis 通知 │
│ 风控专员改状态 │ │ risk:pub:alert│
└─────────────────────┘ └──────────────┘
```
**角色澄清(红线自证)**:模拟交易网关是**外部 Core 交易系统的替身**(生产环境由真实交易系统回调替代),它写 `core_trade` 是扮演 L0 系统的角色,**不属于 Agent 写 Core**;Agent 侧代码依旧只读 `jinrong_core`。
**分层落地(对照 FRAMEWORK.md)**:网关独立为 `app/gateway/` 包(`trade_gateway.py` 业务 + `gateway_repository.py` **仅 INSERT core_trade**),不在 api/service/tool/repository 四层之内,声明为"模拟外部系统模块";`app/api/simulate.py` 仍为薄路由。§13 联动修正 FRAMEWORK.md 增补该分层例外说明。规则引擎、校验服务、预警处置全部遵守"repository 不写 Core"约束。
---
## 4. 功能需求
### FR-1 模拟交易网关(D5)
- `POST /api/simulate/trade`:入参 `customer_id, product_id, trade_type, amount`;服务端生成 `trade_id` + `trace_id`
- `trade_type` 一期仅接受 `subscribe/redeem`;**`convert` 显式拒绝(400,文案"转换交易暂不支持,请分别发起申购/赎回")**;规则引擎入口对未知 trade_type 抛错兜底(真实交易系统接入时再定义 convert 拆算规则)
- 流程:
1. 调用适当性校验服务(FR-2);**不匹配 → 返回阻断响应(HTTP 200,`blocked=true` + 阻断文案),交易不落 `core_trade`**,但落 `risk_suitability_log`(`is_blocked=1`)+ 生成 suitability 预警单(同客户+产品+日去重,见 FR-4)
2. 匹配 → INSERT `core_trade`(`trade_status='confirmed'`)→ 同步调用规则引擎处理本笔交易(FR-3)→ 返回 `blocked=false` + `trade_id`
- 阻断文案要素(G-08 + 合规 §5.3):**不匹配原因(客户等级 vs 产品等级)+ 引导"请联系持证投资顾问" + "本次请求已记录"声明**
- 鉴权:依赖 T-01 JWT。一期网关接受两类身份:①`roles` 含 `risk_demo` 的演示账号;②客户本人(`customer_id == JWT subject`,客户 JWT 方案由 T-01 提供,未就绪前仅接受 risk_demo)。演示账号方案见 §10
- 阻断/放行的全量输入输出写 `audit_log`(`agent_type='platform'`)
### FR-2 适当性校验服务(R-02 · 全系统唯一阻断点)
- 公共函数 `service/suitability.py :: suitability_check(customer_id, product_id) -> SuitabilityResult`
- 数据源(全只读,经扩展后的 `core_ro`):`core_customer_risk.risk_code`(C1~C5)、`core_customer.age`、`core_product.min_risk_code`(R1~R5)
- 规则(冻结,代码硬编码,详见附表):
- SUIT-001~005:客户 C 级 ≥ 产品 R 级方可购买(C1↔仅R1 … C5↔R1-R5)
- SUIT-006:年龄 ≥70 岁的客户按最高 C3 处理(即使测评得 C4/C5);**`age IS NULL` 时跳过本条,`reasons` 标注"年龄缺失,建议人工复核"**(不单独构成阻断)
- SUIT-008:风险测评有效期默认 **365 天**(`.env: RISK_ASSESSMENT_VALID_DAYS` 可配),`evaluated_at` 超期 → **等级矩阵照常计算(`is_matched` 不受影响),但最终 `blocked=true`**,阻断原因"风险测评已过期,请重新测评"
- 返回结构语义(两字段不冗余):`is_matched` = 纯等级矩阵结果;`blocked` = 最终是否阻断(= `NOT is_matched` **或** 测评过期);`reasons[]` 列明每条规则的判定与封顶说明(如"C4 因年龄≥70 按 C3 处理")
- `risk_suitability_log.customer_risk_level` 记录**原测评等级**(SUIT-006 封顶只进 `reasons`,不篡改落库值)
- 每次校验落 `risk_suitability_log`(含 `trace_id`、`profile_l1_version` 可空)
- **数据归属校验(G-01)**:customer → 仅本人;advisor → 经 `customer_advisor_rel` 归属校验;risk_officer → 全部;越权 403 + audit
- **复用方**:模拟交易网关(FR-1)、客户财富/代理人助手的推荐前校验(D2)、风控对话线(FR-6)
### FR-3 风控规则引擎(R-01 / R-04,事件驱动)
- 入口:网关交易落库后**进程内同步调用**(不用消息队列保证阻断演示可靠性);每个事件处理 <100ms
- 规则清单(阈值支持 `.env` 覆盖,默认值冻结):
| 规则 ID | 名称 | 触发条件 | 优先级 | 出单类型 |
| --- | --- | --- | --- | --- |
| RISK-001 | 单笔大额 | `amount ≥ 500,000` | P0 | large_amount |
| RISK-002 | 单日累计大额 | 同一客户当日 subscribe+redeem 合计 `≥ 500,000`(含本笔) | P0 | large_amount |
| RISK-003 | 频繁交易 | 同一客户同一产品当日申赎合计 `≥ 3 笔` | P1 | freq_trade |
| RISK-004 | 接近阈值试探 | 5 分钟内 `≥3 笔` 且每笔 `≥400,000` | P1 | pattern |
| RISK-005 | 先小后大 | 当日**时间序上首次大额之前**已存在 `≥3 笔 ≤10,000`(不要求连续、中间可穿插其他金额) | P0 | pattern |
- **累计口径统一为 `core_trade` 当日流水**;`core_cash_flow` 不参与触发计算(仅作预警单 payload 上下文展示,避免与 trade 双计)
- "当日"口径:**服务器本地时区自然日(YYYYMMDD)**,以 `traded_at` 为准
- 风险评分一期为**静态映射**:aml=95、suitability=90、pattern=80、large_amount=70、freq_trade=50(R-05 时替换为动态评分)
- **单事件聚合出单**:同一笔交易命中的全部规则合并进**一张**预警单(`triggered_rules` JSON 数组、`risk_score` 取命中规则映射的最高值、`alert_type` 取分值最高规则对应的类型);详见 FR-4 去重与聚合
- 触发数据源:`core_trade` 当日流水 + `core_customer`/`core_customer_risk`(上下文)
### FR-4 预警单生命周期
- 生成:`risk_alert`,`status='pending_review'`,**`triggered_rules` 字段写规则 ID 数组**;`payload` 含脱敏交易明细、客户画像摘要(L0 事实 + 可得的 L1/L2 只读)、近 30 天交易统计、`core_cash_flow` 上下文(如有)
- **聚合与去重**:
- **事件类(large_amount/freq_trade/pattern)视作同一类**:同客户同自然日仅**一张**事件类 pending 单——`alert_type` 随当前命中分值最高的规则**动态更新**,新事件命中的规则一律追加进 `payload.events[]` 与 `triggered_rules`、`risk_score` 取 max(不按类型分单,避免类型漂移产生多单);无 pending 单则新建
- suitability:同客户+产品+自然日仅一张 pending 单(防反复重试刷单),同上追加
- aml:**独立出单不聚合**(最高级,单事件单张)
- Redis 去重键 `risk:dedup:{customer_id}:{rule_id}:{date}` 仅作规则级防重入辅助(24h TTL)
- 通知:`PUBLISH risk:pub:alert`,payload=`{alert_id, alert_type, customer_id_mask, risk_score, trace_id, notify_role}`;以 PRD 版本为准,§13 联动修正 02-redis-keys.md §2.4
- 人工处置(唯一允许改状态的角色 = 风控专员 `risk_officer`,经 JWT):
- `GET /api/risk/alerts`:参数 `status, alert_type, customer_id, start_date, end_date, page, page_size`
- `POST /api/risk/alerts/{alert_id}/handle`:`{handler_result: confirmed_normal|confirmed_suspicious|reported, handler_comment?}`;写 `handler_id/handled_at`,状态机只允许 `pending_review → 其余三种`,禁止跳改已处置单
- 处置动作全量写 `audit_log`(`agent_type='risk'`)
### FR-5 AML 名单监测(R-03)
- 新增表 `risk_aml_list`(agent 库,风控专用,DDL 见 §6.2)
- 种子数据:≥8 条假名单,其中**故意包含 1 条与种子客户 `display_name` 同名的记录**用于演示命中
- 触发时机(P0 做前两个;开户/信息变更触发因模拟环境无开户流程**本期不做**,仅在规则引擎预留 `on_customer_created/on_customer_updated` 事件入口定义):
1. **交易事件触发**:规则引擎处理每笔交易时,比对该客户姓名(同步,<100ms)
2. **手动全量扫描**:`POST /api/risk/aml/scan`(模拟每日批量;P1 起挂定时任务)
- **匹配算法(一期降级)**:仅 `display_name` 归一化(去空格、大小写折叠)+ 相似度 ≥ `match_threshold`(默认 0.85);表结构保留 `id_no`/`bank_card_no` 字段,**证件/银行卡匹配待 Core 提供证件数据后启用**(当前 `core_customer` 无证件字段,不修改 Core 表结构)
- 命中动作:生成 `alert_type='aml'` 独立预警单(risk_score=95,每事件一张)+ Redis 紧急推送(`notify_role=["risk_officer","compliance"]`)+ **标记客户 L3 `monitor_tier='high'` + `monitor_tags` 追加 `"aml_hit_pending_review"`**;**不冻结、不自动上报**
- **合规官知悉路径**:①`risk:pub:alert` payload 带 `notify_role`,风控工作台按角色过滤展示;②compliance 账号(STAFF-40001/40002)登录预警列表可见 aml 类型预警单(只读)
- 命中记录入 `risk_alert.payload`(含名单类型、匹配字段、相似度、名单版本),全量审计
### FR-6 对话线(风控专员 chat)
- 入口:`POST /api/chat`,`X-Agent-Type=risk`,JWT 角色 `risk_officer`;LangGraph StateGraph + DeepSeek(依赖 T-01/T-03/T-07)
- Tool 节点(只读 + 专用查询):
1. 查预警台账(按状态/类型/客户/日期统计,含"今日新增/待审数",复用 FR-4 查询参数)
2. 查客户风险上下文:L0(只读 core_ro)+ L1/L2(只读画像表)+ L3(自有)+ 近 30 天交易摘要
3. 发起适当性校验并解读结果(调 FR-2)
4. AML 名单查询(某客户是否命中过)
- **对话线只读不处置**:改预警状态必须走 FR-4 结构化 API,对话中输出"处置建议"但不执行——防止 LLM 误操作,审计口径清晰
- 输出规范:引用预警单必须带 `alert_id`;两级文案——**面向专员的对话输出不含 G-08 客户免责声明,但评分/分层必须标注"仅供参考,不自动决策"**;系统级预警 `disclaimer`("本预警由系统自动生成,最终判定需经风控专员人工审核")固定出现在预警 API 响应体中,两者是不同文案、各自适用
### FR-7 L3 画像写入(R-05 预留接口)
- 本期仅实现最小写入:预警单生成时 UPSERT `customer_profile_l3`
- **合并规则(防降级)**:`monitor_tier` 取**最高档**(`new_tier = max(existing_tier, mapped_tier)`,normal < watch < high;映射:aml→high、pattern→watch、large_amount→watch、freq_trade→watch、suitability→normal);`monitor_tags` **追加合并**不覆盖;`computed_at` 写当前时间(字段 NOT NULL 必须显式赋值);`last_alert_id` 联动
- `risk_score` 动态评分、定期批量重算、监测报告:**本期不做**,接口签名预留 `service/risk/scoring.py :: recompute_customer_score(customer_id)`
---
## 4A. 追加需求(v1.1 并入 · FR-8 / FR-9 / FR-10)
> 2026-09-07 经独立 AI 评审(不通过 P0×4)→ 修订 → 复审通过后并入。冻结部分 FR-1~FR-7 原文不动;本节与冻结条款的关系见 §4A.0。
### 4A.0 与冻结条款的修订关系(不推翻,仅显式扩展)
1. **升级标记的第二写入方**:FR-4 冻结"人工处置是唯一改状态入口"**维持不变**——RISK-007 升级**不修改 `status`**,仅由定时任务写 `payload` 升级标记(`escalation_level`/`escalated_at`),该标记为定时任务独占写入,人工 API 无法触碰
2. **`risk_alert.alert_type` 不扩枚举**:新增两类预警复用冻结枚举值 `pattern`,以 `payload.alert_subtype`(`concentration` / `agent_behavior`)区分子类型(DDL 核实:ENUM 五值不含新类型,不改表)
3. **出单维度扩展(非例外)**:FR-4"同客户同自然日仅一张"继续对**客户维度**成立;RISK-008 是**新增的代理人维度出单线**(去重键 = `payload.actor_id` + 自然日),与客户维度聚合并存、互不影响
4. **`risk_alert.status` ENUM 扩展(`escalated`)列入二期改表挂账**,本期一律不使用该状态值
### 4A.1 新增角色:risk_manager(风控专员上级 · 用户拍板 2026-09-07)
- **角色定位**:`risk_officer`(风控专员)的上级管理岗,升级通知的接收方(系统原先无此角色,通知会推空;用户拍板新增并模拟生成上下级关系)
- **承载方式(不改表)**:`core_staff.staff_type` 为冻结 ENUM(advisor/analyst/risk_officer/compliance/ops)不能加值 → 上级账号 `staff_type='risk_officer'`、`roles=["risk_manager"]`(roles 为 JSON 字段,不撞红线 7);种子新增 2 个模拟账号 `STAFF-31001`/`STAFF-31002`
- **鉴权判定口径**:一律按 JWT `roles` 数组判定(含 `risk_manager` 即可),`staff_type` 不参与鉴权判定;台账查询(GET alerts)新增 risk_manager → 全量只读分支;处置 API(handle)角色白名单维持 roles 含 `risk_officer`,manager 账号不含该角色自然 403,零代码改动
- **权限边界**:预警台账只读全量(同 risk_officer);RISK-008 行为链明细可见(见 FR-10);**无处置权**——FR-4"人工处置唯一角色 = risk_officer"冻结条款不变,经理只升级知悉不代为处置
- **可见渠道**:FR-6 对话线入口维持冻结"仅 risk_officer"不变;`risk_manager` 查看预警台账与行为链明细统一走 HTTP 台账(GET alerts 全量只读分支),不扩展对话线角色
- **联动清单(C5 开工前执行)**:`scripts/core/02-seed-base.sql` 追加账号;JWT 手册 §5.3 角色清单增补;`AGENT_ACCESS_MATRIX` 增补行
- **通知升级链总览**:预警 → `risk_officer`(专员);超时 LEVEL_1 → +`risk_manager`(专员上级);LEVEL_2 → +`compliance`(合规官知悉)
### FR-8 客户资产配置集中度预警(RISK-006)
> 目标:弥补"单笔合规、整体失衡"盲区。现有 SUIT/RISK-001~005 均聚焦单笔交易,不感知客户持仓结构。
**触发时机**:交易网关落库后,规则引擎处理完 RISK-001~005 后**同步追加**本规则;对话线 Tool 实时查询为**只读展示,不触发 RISK-006 出单**。
**规则定义**:
| 字段 | 定义 |
|---|---|
| 规则 ID | RISK-006 |
| 名称 | 高风险资产配置集中度预警 |
| 触发条件 | 客户持仓中 R4+R5 产品市值合计 ÷ 总持仓市值 ≥ `RISK_CONCENTRATION_THRESHOLD`(默认 80%,`.env` 可配) |
| 计算口径 | **市值直接取 `core_holding.market_value` 现成列汇总**(与 T-04 `core_ro.list_holdings` 既有返回一致;不按"份额×最新净值"重算,避免 N+1 调 `get_latest_nav` 威胁引擎 <100ms 约束);总持仓 = 全产品 `market_value` 求和;空仓或总持仓为 0 时不触发 |
| 优先级 | P1 |
| 出单类型 | **落库复用 `alert_type='pattern'` + `payload.alert_subtype='concentration'`**(不改表);聚合口径归入事件类,与 large_amount/freq_trade/pattern 同口径 |
| 评分映射 | `risk_score = 60`(静态映射,R-05 接入后替换) |
**系统动作**:
1. 引擎调用 `core_ro.list_holdings(customer_id)` 汇总持仓:按产品风险等级(`core_product.min_risk_code`)分组求和 R4+R5 市值与总市值。**截断防护**:`list_holdings` 默认 limit=500,命中上限即视同达到阈值(保守告警)并在 `input_summary` 标注 `holdings_truncated=true`
2. 命中 → `alert_service.agg_upsert` 并入当日事件类 pending 单:`payload.alert_subtype` 追加 `"concentration"`、`triggered_rules` 追加 `"RISK-006"`、`risk_score` 取 max(60 不覆盖 RISK-001/002 的 70,与 FR-4 聚合语义一致)
3. `profile_l3.upsert`:`monitor_tier = max(existing, watch)`,`monitor_tags` 追加 `"high_risk_concentration"`
4. `audit_log` INSERT(`event_type='risk_concentration'`,`input_summary` 含脱敏后的 R4+R5 占比与总持仓;金额按 DESENS-005 口径截断展示,`customer_id` 为内部键不脱敏)
5. Redis `PUBLISH risk:pub:alert`,`notify_role=["risk_officer"]`
**API 变更**:对话线 Tool `customer_context` 扩展 `concentration_ratio`(R4+R5 占比,脱敏后百分比);无新增 HTTP API。
**验收标准(A-10)**:
- C5 客户持仓 90% 在 R5 产品 → 触发 RISK-006,当日事件类预警单 `triggered_rules` 含 `"RISK-006"` 且 `payload.alert_subtype` 含 `"concentration"`
- C2 客户持仓 100% 在 R1 → 不触发
- 交易落库后 5 秒内完成持仓重算并出单(集成测试以 `TRD-TEST-` 交易断言)
- 对话线查询"哪些客户资产配置过于集中"返回正确列表及占比
- **回归口径**:现有用例全量回归;既有断言按"`triggered_rules` 含新增规则 ID 不判失败"口径预先声明
### FR-9 预警单处置时效监控与自动升级(RISK-007)
> 目标:防止风险事件因人工响应延迟而积压扩大。现有 FR-4 只有生成与处置,无时效监控。
**触发时机**:定时扫描(非交易事件驱动),每 15 分钟执行一次(`RISK_ESCALATION_SCAN_MINUTES` 可配)。
**规则定义**:
| 字段 | 定义 |
|---|---|
| 规则 ID | RISK-007 |
| 名称 | 预警单处置时效监控与升级 |
| 触发条件 | `status='pending_review'` 且距 `created_at` 超过配置时效;**每张单独立计时、独立升级**;降噪:同一客户多张单在同一扫描周期命中同级别升级时,合并为一次推送 |
| 升级策略(阈值全部 settings 注入) | LEVEL_1:≥ `RISK_ESCALATION_L1_HOURS`(默认 4h)未处置 → Redis 升级推送,`notify_role` 追加 `risk_manager`(见 4A.1)<br>LEVEL_2:≥ `RISK_ESCALATION_L2_HOURS`(默认 24h)未处置 → `payload.escalation_level=2` + `payload.escalated_at`,`notify_role` 追加 `compliance`,对话线查询置顶<br>AML 特殊通道:`RISK_ESCALATION_AML_L1_HOURS`(默认 1h)/ `RISK_ESCALATION_AML_L2_HOURS`(默认 4h) |
| 优先级 | P0(运营红线) |
| 出单类型 | 不新建预警单、**不改 `status`**(冻结 ENUM 无 `escalated`,升级信息全部由 `payload` 承载) |
| 评分映射 | 不修改 `risk_score` |
**系统动作**:
1. 定时任务 `scripts/cron/escalation_scan.py`(独立脚本优先)查询 `risk_alert`:`status='pending_review'`
2. 按规则计算超时时长,命中升级条件:
- LEVEL_1:先 `UPDATE payload` 持久化 `escalation_level=1` + `escalated_at`(幂等依据,与 LEVEL_2 同机制),再 Redis `PUBLISH`,`notify_role` 追加 `risk_manager`
- LEVEL_2:`UPDATE payload` 写入 `escalation_level=2` + `escalated_at`,`PUBLISH`,`notify_role` 追加 `compliance`
- **幂等约束**:仅当"本次计算级别 > `payload.escalation_level` 已记录级别"才执行升级动作;同级别不重复推送(每级别至多推送一次)
3. 每次升级动作写 `audit_log`(`event_type='alert_escalation'`,`input_summary` 含 `alert_id`、已达级别、升级原因、超时时长);定时任务无 HTTP 上下文,**显式 `new_trace()` 生成 trace_id** 并与预警单 payload、推送消息贯通(§7.5 可还原性)
4. 状态机约束:`status` 全程保持 `pending_review`,人工处置照常(`pending_review → 三态`,冻结不变),处置后单据退出扫描范围;`payload` 升级标记**仅定时任务可写**,人工处置 API 不感知、不覆盖(`handler_result` 枚举外值由 Pydantic 返回 422)
5. 定时任务对升级决策的全量输入输出写 `audit_log`,审计写库失败降级不阻塞但本地日志 error 留底(红线 5 口径)
**API 变更**:新增对话线 Tool `query_overdue_alerts(hours?)`(返回超期预警列表含 `alert_id`、`created_at`、`escalation_level`(读自 payload,缺省 0)、`overdue_hours`);无新增 HTTP API。
**验收标准(A-11)**:
- **免等待测试方案**:升级阈值走 settings 注入(测试用小时以下阈值);fixture 支持直接 INSERT `created_at` 回拨的 pending 单(conftest 扩展)
- pending 单超 L1 阈值 → 收到升级推送,`notify_role` 含 `risk_manager`;**同一单重复扫描不重复推送**(幂等断言)
- AML 预警单超 AML L1 阈值 → 触发 LEVEL_1(与普通单阈值不同,两条断言分开)
- 风控专员在阈值内处置 → 不触发任何升级;处置后超期单不再进入扫描结果
- 状态机:全程 `status='pending_review'`;`handler_result` 枚举外值 → 422;`payload.escalation_level` 与人工处置字段互不覆盖
- 对话线"今天有哪些超期预警?"返回正确列表及超时时长、升级级别
### FR-10 代理人异常行为链识别(RISK-008)
> 目标:单点拦截(如 T-04 的 `AUTH_403_SCOPE`)只能阻断单次请求,无法识别代理人本身的操作风险模式。
**触发时机**:定时扫描(每 30 分钟,`RISK_AGENT_BEHAVIOR_SCAN_MINUTES` 可配),数据源为 T-02 审计中间件留痕的 `audit_log`(**input_guard_log 不作为数据源**:网关路径不写该表且无 code 字段)。
**规则定义**:
| 字段 | 定义 |
|---|---|
| 规则 ID | RISK-008 |
| 名称 | 代理人异常行为链识别 |
| 触发条件(阈值/窗口全部 settings 注入可配) | **条件 A(频繁诱导调仓)**:`RISK_AGENT_BEHAVIOR_A_WINDOW_HOURS`(默认 24h)内,同一代理人对同一客户发生 ≥ `RISK_AGENT_BEHAVIOR_A_COUNT`(默认 3)次产品切换操作(赎回某产品后 2 小时内申购另一产品)。**归属口径**:以 `audit_log`(`event_type='trade_request'`,网关审计)中的发起人 actor 归属;**排除发起人 == 客户本人的交易**(防客户正常调仓误报)。系统 `core_trade` 无 actor 字段、无代理人交易路径(FR-1 鉴权仅 risk_demo/客户本人),演示期由 risk_demo 账号扮演代理人发起(写入演示 SOP);真实代理人交易路径接入时以 Core 提供 actor 字段为准(挂账)<br>**条件 B(试探越权)**:`RISK_AGENT_BEHAVIOR_B_WINDOW_HOURS`(默认 72h)内,同一代理人累计 ≥ `RISK_AGENT_BEHAVIOR_B_COUNT`(默认 5)次 `AUTH_403_SCOPE` 越权查询。**唯一数据口径**:`audit_log WHERE event_type='authz' AND input_summary 中 code='AUTH_403_SCOPE'`(拒绝标记为 `decision='forbidden'`);对话线 Tool blocked 事件经 `record_authz_denial` 双写后同口径可查<br>**条件 C(高频查询非名下客户)**:`RISK_AGENT_BEHAVIOR_C_WINDOW_HOURS`(默认 24h)内,同一代理人查询非名下客户信息被拒 ≥ `RISK_AGENT_BEHAVIOR_C_COUNT`(默认 10)次。**数据源**:`event_type='customer_query'` 在系统中无生产方,条件 C 定义为**"非名下客户查询被拒"事件族**:`audit_log WHERE event_type='authz' AND code IN ('AUTH_403_NOT_OWNER','AUTH_403_NOT_ASSIGNED')`(API 线归属校验拒绝)∪ 对话线 `agent_tool_call` blocked 中归属类拒绝码(经 `record_authz_denial` 双写);`customer_query` 事件类型补建列为二期 T-02 侧变更(挂账) |
| 优先级 | P1 |
| 出单类型 | `agent_behavior` 语义预警单:**落库 `alert_type='pattern'` + `payload.alert_subtype='agent_behavior'`**;**独立出单不按客户聚合**(出单维度**扩展**,见 4A.0 第 3 条):去重键 = `payload.actor_id` + 自然日,同代理人同日仅一张 pending 单 |
| 评分映射 | `risk_score = 75`(静态映射,R-05 评分模型接入后替换) |
**系统动作**:
1. 定时任务扫描 `audit_log` 滑动窗口(24h/72h,按上表口径过滤)
2. 命中任一子条件,生成预警单,**落库映射(对照真实 DDL 定案)**:
- `alert_type='pattern'`,`payload.alert_subtype='agent_behavior'`
- `customer_id`(NOT NULL 冻结约束)= 行为链中**主要涉及客户**(按证据条数取众数);payload 显式声明该字段语义为"涉及客户"而非"归属客户"
- 代理人标识:`payload.actor_id` + `payload.actor_type='agent'`(`risk_alert` 无 `actor_id` 列)
- `payload` 含行为链明细:时间线、涉及客户 ID(内部键,不脱敏)、命中子条件(A/B/C)、证据 trace_id 列表
- `risk_score = 75`
3. **代理画像存储**:本期最小实现仅在 `risk_alert.payload` 承载(不新建 `agent_profile_l3` 表、不扩展 `customer_profile_l3` 语义);长期画像追踪 M4 后评估
4. Redis `PUBLISH`,`notify_role=["risk_officer","risk_manager"]`
5. **行为链明细展示范围**:仅 `risk_officer` / `risk_manager` 角色可见(其他角色 fail-closed;对话线入口维持仅 risk_officer,经理走 HTTP 台账);对话输出对客户身份信息二次脱敏
6. 审计:每条命中行为链写 `audit_log`(`event_type='agent_behavior_detected'`),显式 `new_trace()`(同 FR-9 口径)
**API 变更**:对话线 Tool 扩展 `query_agent_behavior(agent_id?)`(检索 `alert_type='pattern' AND payload.alert_subtype='agent_behavior'` 双条件;`agent_id` 匹配 `payload.actor_id`;JSON 检索演示规模可接受,性能挂账);无新增 HTTP API。
**验收标准(A-12)**:
- 代理人在 24h 内对同一客户执行 3 次"赎回→申购" → 触发条件 A,生成代理人维度预警单(`payload.actor_id` 正确、`customer_id` = 涉及客户)
- **客户本人交易不误报**:`actor_id == customer_id` 的正常调仓不计数
- 代理人在 72h 内累计 5 次 `AUTH_403_SCOPE` → 触发条件 B
- 24h 内 10 次 `AUTH_403_NOT_OWNER`/`AUTH_403_NOT_ASSIGNED` 被拒 → 触发条件 C(数据经 `backdated_audit_event` fixture 回拨构造)
- 风控专员对话查询"代理人 STAFF-20001 最近有没有异常行为" → 返回正确预警及证据链
- 同代理人同日多次命中不同子条件 → 只一张 pending 单,`triggered_rules`/`payload` 追加子条件标识
- 预警单 `payload.actor_id` 正确指向代理人而非客户;非授权角色查行为链明细 → fail-closed
- **测试基建**:conftest 扩展 `backdated_audit_event` fixture(显式 `created_at` 回拨写入 `audit_log`,teardown 按 `TRD-TEST-` 标记清理);窗口边界用例(A:2/3;B:4/5;C:9/10);现有用例全量回归
**新增配置项(C4~C6 开工时同步 `.env.example` + `settings.py`)**:
| 配置项 | 默认值 | 用途 |
|---|---|---|
| `RISK_CONCENTRATION_THRESHOLD` | 0.80 | RISK-006 集中度阈值 |
| `RISK_ESCALATION_SCAN_MINUTES` | 15 | RISK-007 扫描周期 |
| `RISK_ESCALATION_L1_HOURS` / `L2_HOURS` | 4 / 24 | 普通单升级阈值 |
| `RISK_ESCALATION_AML_L1_HOURS` / `AML_L2_HOURS` | 1 / 4 | AML 单升级阈值 |
| `RISK_AGENT_BEHAVIOR_SCAN_MINUTES` | 30 | RISK-008 扫描周期 |
| `RISK_AGENT_BEHAVIOR_A_WINDOW_HOURS` / `A_COUNT` | 24 / 3 | 条件 A 窗口/次数 |
| `RISK_AGENT_BEHAVIOR_B_WINDOW_HOURS` / `B_COUNT` | 72 / 5 | 条件 B 窗口/次数 |
| `RISK_AGENT_BEHAVIOR_C_WINDOW_HOURS` / `C_COUNT` | 24 / 10 | 条件 C 窗口/次数 |
---
## 5. 数据设计
### 5.1 读写权限总表
| 表 / Key | 操作 | 说明 |
| --- | --- | --- |
| `jinrong_core.core_trade` | **INSERT(仅网关 gateway_repository)** | 网关扮演外部交易系统 |
| `jinrong_core.core_*` 其余 | SELECT | 经 `CoreReadOnlyRepository`(需扩展方法),Agent 只读 |
| `jinrong_agent.risk_alert` | INSERT + UPDATE(状态机) | 风控写 |
| `jinrong_agent.risk_suitability_log` | INSERT | 每次校验落一条 |
| `jinrong_agent.customer_profile_l3` | UPSERT(最高档合并) | 风控独写 |
| `jinrong_agent.risk_aml_list` | SELECT + 种子脚本维护 | 风控读,admin 维护 |
| `jinrong_agent.customer_profile_l1/l2` | SELECT | 只读,辅助判断 |
| `jinrong_agent.audit_log` | INSERT | 只增 |
| Redis `risk:pub:alert` | PUBLISH | 预警通知广播(payload 含 `notify_role`) |
| Redis `risk:dedup:{customer_id}:{rule_id}:{date}` | SET EX 24h | 规则级防重复预警 |
| Redis `profile:l3:{customer_id}` | SET EX 5m | L3 热缓存,MySQL 更新时 DEL |
### 5.2 新增表 DDL(需用户确认后并入 `02-mysql-agent专用.sql`)
```sql
CREATE TABLE risk_aml_list (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
list_id VARCHAR(64) NOT NULL,
list_type ENUM('sanction','terror','pep') NOT NULL,
full_name VARCHAR(128) NOT NULL COMMENT '与 core_customer.display_name 同为脱敏展示名口径',
id_no VARCHAR(32) NULL COMMENT '预留:待 Core 提供证件数据后启用匹配',
bank_card_no VARCHAR(32) NULL COMMENT '预留:同上',
match_threshold DECIMAL(3,2) NOT NULL DEFAULT 0.85,
source VARCHAR(64) NOT NULL,
list_version VARCHAR(16) NOT NULL,
effective_date DATE NOT NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
UNIQUE KEY uk_list_id (list_id),
KEY idx_name (full_name),
KEY idx_active (is_active)
) ENGINE=InnoDB COMMENT='【风控专用】AML 名单本地镜像';
```
其余复用底座已有表,**不修改任何现有表结构**(含 Core)。
---
## 6. API 清单汇总
| 方法 | 路径 | 角色 | 说明 |
| --- | --- | --- | --- |
| POST | `/api/simulate/trade` | risk_demo 演示账号 / customer 本人 | 模拟交易网关(R-02 阻断点) |
| GET | `/api/risk/alerts` | risk_officer 全量;**compliance 只读且服务端强制 `alert_type=aml`** | 预警台账分页查询 |
| POST | `/api/risk/alerts/{id}/handle` | risk_officer | 人工处置(唯一改状态入口) |
| POST | `/api/risk/suitability/check` | customer(本人)/advisor(名下)/risk_officer | 适当性校验(推荐前复用;用 POST 保幂等语义——每次调用落 suitability_log) |
| POST | `/api/risk/aml/scan` | risk_officer | 手动全量 AML 扫描 |
| POST | `/api/chat`(X-Agent-Type=risk) | risk_officer | 对话线 |
统一响应格式复用 `utils/response.py`;预警类 API 响应体含固定 `disclaimer`:"本预警由系统自动生成,最终判定需经风控专员人工审核。"(阻断类 API 响应体含 G-08 客户免责声明。)
---
## 7. 合规与审计(红线)
1. **唯一阻断点**:全系统仅 R-02 交易前校验可阻断**请求**;其余场景只预警不拦截
2. **不自动冻户、不自动上报监管、不自动改 C1~C5**(L3 只是监测标签)
3. **审计字段对齐 `audit_log` 实际表结构**:`trace_id, created_at, agent_type, rule_id, customer_id, event_type, input_summary(脱敏), decision, risk_score, handler_id, handler_result, handler_comment`——任何风控判定(校验/预警/阻断/通知)全落 `audit_log`,只 INSERT;**`customer_id` 为系统内部代理键,不脱敏**(保证按客户检索审计的索引有效性)
4. **脱敏规则**:权威定义见附表 `docs/PRD/附-风控规则表.md` §4——身份证/手机号前 3 后 4、姓名留姓氏、银行卡留后 4;`customer_id` 内部键不脱敏;姓名/证件/手机号在 `input_summary`、预警 payload、通知、对话输出中脱敏;LLM 上下文传入前必须已脱敏
5. **trace_id 贯通**:网关生成 → suitability_log → risk_alert → audit_log → Pub/Sub 消息,同一链路可还原
6. **处置人工化**:预警状态仅 `risk_officer` 经结构化 API 变更;compliance 只读;对话线不处置
---
## 8. 验收标准(对照 REQUIREMENTS Wave 2 + 评分项)
> 前置:先执行 §10 演示数据准备。
| # | 演示场景 | 预期结果 |
| --- | --- | --- |
| A-1 | C1 客户(测评有效期内,CUST-1001)申购 R4 产品(PROD-161725 科技成长主题) | 响应 `blocked=true`,`block_reason` 命中 SUIT-001(C1 仅可购 R1);`core_trade` 无记录;`risk_suitability_log.is_blocked=1`;suitability 预警单生成;audit 可查 |
| A-2 | CUST-4001(70 岁、测评 C5、已刷新有效)购买 R4 产品(PROD-161725) | `blocked=true`,`reasons[]` 含 SUIT-006 封顶说明("C5 因年龄≥70 按 C3 处理")与 SUIT-003 不匹配,**无 SUIT-008 干扰**;其余同 A-1 |
| A-3 | CUST-3001(C3、已刷新有效)单笔申购 50 万 PROD-510300 沪深300指数(R3,等级匹配) | `core_trade` 落库;事件类预警单 `pending_review`(`triggered_rules` 含 RISK-001+RISK-002,`risk_score=70`);Redis 收到 `risk:pub:alert` 推送 |
| A-4 | 同产品当日第 3 笔申赎 | freq_trade 规则并入当日预警单(不另开新单);当日去重不重复报 |
| A-5 | AML 名单命中客户交易 | aml 独立预警单(score=95)+ L3 `monitor_tier='high'` + 紧急推送(`notify_role` 含 compliance)+ compliance 账号可见该单;**账户未被冻结** |
| A-6 | 风控专员对话 | "今天有多少待审预警" 返回正确统计 + alert_id 可溯源 |
| A-7 | 人工处置 | 状态 `pending_review→confirmed_suspicious`;compliance 调 `/handle` → 403(其调 GET 仅返回 aml 单);audit 留痕 |
| A-8 | `suitability_check` 单元级验收 | 脚本/单测直接调用,断言 C1+R4 → `is_matched=false, blocked=true` 且返回结构完整;"不匹配拒绝生成推荐语"的端到端验收归客户/代理人 Agent 各自 PRD(见 §9 依赖) |
| A-9 | 越权 | customer 查他人 suitability、advisor 查非名下客户 → 403 + audit |
| A-10 | 集中度预警(FR-8 · v1.1) | C5 客户持仓 90% R5 → 事件类预警单含 RISK-006 + `payload.alert_subtype='concentration'`;C2 全 R1 不触发;落库后 5s 内出单 |
| A-11 | 时效升级(FR-9 · v1.1) | 超阈值 → LEVEL_1 推送含 risk_manager(幂等:重复扫描不重复推);AML 短通道 1h;阈值内处置不升级;全程 status=pending_review;枚举外 handler_result → 422(免等待:阈值 settings 注入 + created_at 回拨 fixture) |
| A-12 | 代理人行为链(FR-10 · v1.1) | 条件 A(3 次赎回→申购,本人交易排除)/ B(5 次 AUTH_403_SCOPE)/ C(10 次归属拒绝)各自触发;代理人维度独立出单(payload.actor_id);同日多子条件只一张单;行为链明细仅 risk_officer/risk_manager 可见 |
**通用验收**:所有预警单初始 `pending_review`;审计链路任取一条 `trace_id` 可还原全流程;越权 403。
---
## 9. 依赖与前置
| 依赖 | 状态 | 影响 |
| --- | --- | --- |
| T-01 JWT/RBAC 中间件 | 未做 | 所有 /api/risk 接口角色校验 + 客户本人 JWT + 最简签发端点 |
| T-02 audit 中间件 + trace_id | 未做 | §7 审计要求 |
| T-03 输入防护(F-03) | 未做 | 对话线接收专员输入的全员底线 |
| T-04 core_ro 扩展 | **需扩展** | 现仅有部分方法;需补 `get_customer_risk`(含 evaluated_at)、当日交易统计、近 N 天流水等查询(含 SUIT-008 所需 `evaluated_at`) |
| T-05 Core 模拟库灌库 | 脚本已有未执行 | 演示数据 |
| T-07 LangGraph + DeepSeek | 未做 | 仅对话线 FR-6 依赖;事件驱动线不依赖 |
| 共用底座 SQL 灌库 | 待执行 | risk_alert 等表 |
| 客户/代理人 Agent | Wave 1/3 | A-8 端到端部分归各自 PRD;本期只交付 `suitability_check` 公共函数 + 单测 |
**排期策略**:事件驱动线(FR-1~FR-5)不依赖 T-07,可先行;对话线(FR-6)等 Wave 0 底座就绪后接入。具体切任务见后续开发计划文档。
---
## 10. 演示账号与演示数据准备
### 10.1 演示账号(需确认后并入 seed)
| 项 | 值 | 说明 |
| --- | --- | --- |
| 账号 | `STAFF-90001`(display_name=风控演示账号) | `staff_type='risk_officer'`、`roles=["risk_officer","risk_demo"]` |
| 网关鉴权 | roles 含 `risk_demo` 即可发起任意客户的模拟交易 | 演示期口径;生产由真实交易系统回调替代,无此角色 |
| AML 演示名单 | 种子含 1 条与某种子客户 `display_name` 同名的 sanction 记录 | 演示命中 |
### 10.2 测评有效期演示数据(修复 SUIT-008 与种子冲突)
种子库 28 位客户的 `evaluated_at` 均已过期(最晚 2025-09-01),若不处理,**所有交易都会被 SUIT-008 阻断,A-3/A-4/A-5 无法走通**。演示前执行 `scripts/demo/prepare_risk_demo.sql`:
```sql
-- 演示客户测评日期刷新至演示日前 90 天内(仅演示用例涉及的客户)
UPDATE jinrong_core.core_customer_risk SET evaluated_at = CURDATE() - INTERVAL 90 DAY
WHERE customer_id IN ('CUST-1001','CUST-1002','CUST-1003','CUST-3001','CUST-4001','CUST-9527');
```
- 该脚本与 `reset.ps1` 分离(reset 重建全库后需重跑),并在 FLOW.md bootstrap 补一步
- 同时保留 **1 位测评过期客户**(CUST-1004,C4,不刷新)用于演示 SUIT-008 阻断路径:"测评过期 → 等级匹配仍阻断 → 提示重新测评"
- A-1/A-2/A-3 的断言均写明命中的具体规则编号与 `block_reason` 内容,避免规则叠加时断言含糊
---
## 11. 性能与非功能
- 规则引擎单事件处理 <100ms(含 AML 比对)
- 适当性校验 P95 <300ms(两次索引查询)
- 预警通知从交易落库到 Pub/Sub 推送 <1s(演示用"实时感")
- AML 全量扫描(28 客户 × 8 名单)<3s
- 所有金额用 `Decimal`,禁止 float
---
## 12. 边界与不做清单(复述强约束)
- 不自动冻结账户、不自动上报监管、不自动调整正式风险等级、不代替客户重测
- 不拦截已发生交易(只拦请求)
- 不限制客户交易次数/金额
- 不生成投资建议/收益承诺
- 不做"仅提示不阻断"的放行路径(R-02 一期全部阻断)
- 不做 AML 开户/信息变更触发(无开户流程,仅预留事件入口)
- 数据分析 Agent 只能统计预警台账,不能处置
- 不做:真实支付对接、TA 清算、毫秒级行情、多租户、convert 交易类型
---
## 13. 文档联动修正(PRD 冻结后执行)
| 文档 | 修正内容 |
| --- | --- |
| `docs/需求拆解/Agent风险与合规约束汇总.md` | "产品推荐:禁止"→"允许:须过 R-02 校验 + 免责声明 + 标注需持证审核;禁止具体操作指令"(§1 G-08 附近及各 Agent 边界表) |
| `docs/PRD/附-风控规则表.md`(已入库) | 后续仅此表维护规则;原本地《02-业务规则表》停止维护(本地工作区文件可在冻结后删除) |
| 本地《03-团队分工》 | Git 分支策略对齐实际仓库(main + feature/*,无 develop) |
| `docs/memory/FRAMEWORK.md` | §3 分层增补例外说明:`app/gateway/` 为模拟外部系统模块,仅 `gateway_repository` 可 INSERT `core_trade`;§3 脚本清单增补 `scripts/demo/`(演示数据准备)与 `scripts/agent/`(agent 库种子:risk_aml_list 名单、演示交易数据) |
| `docs/项目框架设计/表设计/02-redis-keys.md` | §2.4 `risk:pub:alert` payload 口径对齐本 PRD(`{alert_id, alert_type, customer_id_mask, risk_score, trace_id, notify_role}`) |
| `docs/项目框架设计/表设计/02-mysql-agent专用.sql` | 追加 `risk_aml_list`(经用户确认) |
| `scripts/core/02-seed-base.sql` | 追加演示账号 `STAFF-90001`(staff_type='risk_officer',roles=["risk_officer","risk_demo"]) |
| `docs/memory/REQUIREMENTS.md` / `TODO.md` / `FLOW.md` | Wave 2 增加 T-30~T-32 细化任务与 PRD 链接;bootstrap 补演示数据准备步骤(`scripts/demo/prepare_risk_demo.sql`) |
| (v1.1 追加)`scripts/core/02-seed-base.sql` | 追加 `risk_manager` 上级角色账号 STAFF-31001/31002(staff_type='risk_officer' + roles=["risk_manager"])——C5 开工前 |
| (v1.1 追加)JWT 手册 §5.3 + `AGENT_ACCESS_MATRIX` | 增补 `risk_manager` 角色行(台账只读全量、无处置权、对话线不放行)——C5 开工前 |
| (v1.1 追加)`.env.example` + `settings.py` | §4A FR-8/9/10 配置项清单全量登记(集中度阈值/升级双通道/行为链窗口)——C4~C6 开工时 |
| (v1.1 追加)`02-redis-keys.md` | `risk:pub:alert` payload 增补 `escalation_level`/`alert_subtype` 字段说明 |
| (v1.1 追加)演示 SOP | 补 risk_demo 扮演代理人发起"赎回→申购"演示步骤——C6 开工前 |
| (v1.1 追加)`docs/memory/MEMORY.md` / `TODO.md` / `FRAMEWORK.md` | 登记追加需求 FR-8/9/10 与 C4~C6 任务、挂账 #1~#9 |
---
*复审通过并经用户确认后冻结为 v1.0(FR-1~FR-7,2026-09-06);v1.1 追加 FR-8/FR-9/FR-10(2026-09-07 经独立 AI 评审→修订→复审通过后并入)。本 PRD 为风控模块开发唯一依据。*