Files
Mutual_Fund/风控Agent需求文档.md
zhangyongcai 69db5c46bd docs:补充风控Agent需求文档与规则初始化SQL
- 新增《风控Agent需求文档》:规则引擎、前置拦截、人工处置、工单闭环
- 新增 sql/risk_rule_seed.sql:20条风控规则种子数据(INSERT IGNORE)
2026-09-12 21:15:42 +08:00

342 lines
16 KiB
Markdown
Raw Permalink 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.
# 风控 Agent 需求文档
> 版本:v1.0 | 日期:2026-09-12 | 状态:待评审
> 依据:《开发计划.md》§2.5 风控控制台、§5 风控Agent、Phase 4
> 关联:《风控Agent设计方案.md》
---
## 1. 背景与目标
为系统接入**风控 Agent**:每一笔申购/赎回在下单落账前,先经过风控规则引擎检测,命中规则的订单被「挂起」等待人工处置,未命中的正常成交。
**核心目标**:形成「检测 → 阻断 → 人工处置 → 回写」的合规闭环。
**合规铁律**:机器只负责告警 + 阻断;放行/拦截/冻结的处置权永远在人工。
---
## 2. 术语与核心概念
| 术语 | 表 | 定位 |
|---|---|---|
| 交易申请单 | `trade_order` | 风控**拦截对象**,状态机主表(事前) |
| 成交流水 | `fin_transaction` | 账务凭证,只含真正成交的单(事后) |
| 风控预警 | `fin_risk_alert` | 命中记录 + 人工处置留痕 |
| 风控规则 | `risk_rule` | 规则引擎的表驱动配置(参数) |
| 业务工单 | `biz_work_order` | 拦截/冻结后开启的独立审批链 |
**两张链的关系**:预警链(`fin_risk_alert`)在人工处置后结束;工单链(`biz_work_order`)只在「拦截/冻结」时**额外开启**,是处置的下游产物。
---
## 3. 现状与差距
- 当前 `service/purchase.py` / `service/redeem.py` 为**即时成交**(直接扣款+加仓 / 减仓+入账),不产生任何交易留痕。
- `trade_order` / `fin_transaction` / `fin_risk_alert` / `risk_rule` / `biz_work_order` 五张表**只有 DDL,无 model/repo/schema 实现**。
- 结论:风控 Agent 当前无数据可检测,需先补齐交易留痕。
---
## 4. 核心需求
### FR1 · 前置拦截(交易留痕)
申购/赎回由「即时成交」改为「先建单 → 检测 → 通过才落账」。
```
请求(申购/赎回)
↓
FastAPI 鉴权(get_current_user)→ 获取当前用户
↓
适当性校验(硬拦截,申购专用):
客户风险等级 ≥ 产品风险等级,否则 1005 直接拒绝,不生成订单
↓
创建 trade_order,status = '待确认'
- 生成唯一 order_no(前缀+日期+序列,如 PO20260912xxxx)
- 填入 order_type / amount / shares / nav(申请口径)
- ★ 不碰账户余额、不碰持仓
↓
同步进入风控规则引擎检测(FR2)
↓
├─ 未命中 ──► status='已确认' + 落账(扣款+加仓 / 减仓+入账)
│ + 写 fin_transaction + confirm_time=now
│
└─ 命中 ────► 生成 fin_risk_alert(未处理)
+ trade_order.status='风控挂起' + risk_alert_id 关联
账户、持仓保持不动,等待人工处置(FR4)
```
**关键约束**:
- 建单 → 检测 → 落账/挂起 **在单个事务内原子完成**(无外部调用,全为 DB + 纯计算)。
- 落账逻辑**复用**现有 `deduct_balance`/`holdings.upsert`、`holdings.redeem`/`credit_balance`,不重写。
### FR2 · 规则引擎检测(纯代码,不用大模型)
- **同步执行**:在下单请求内当场完成,结果驱动状态流转。
- **纯规则引擎**:阈值比较 + 历史流水统计,全程 `if/else + SQL`,**不调用 LLM**。
- **表驱动**:`risk_rule` 表存参数(阈值/级别/权重/启停),代码存判断逻辑。
检测输入:当前 `trade_order` + 该客户历史流水 + 客户画像/账户/注册时间。
检测输出:命中规则列表(规则 ID + 规则名 + 命中详情)。
### FR3 · 预警生成
- **聚合粒度**:一笔订单命中多条规则,只生成**一条**预警;`alert_type` 取主规则,`trigger_detail` 记录全部命中规则,`alert_level` 取最高档。
- **分级**:级别取命中规则中 `risk_level` 最高档(低→蓝 / 中→黄 / 高→红)。
- **置信度**:`confidence = 1 − Π(1 − weight_i)`(命中规则越多、权重越高,置信度越高)。
### FR4 · 人工处置(挂起自动,处置人工)
三个处置动作均为**风控专员手动**,系统绝不自动放行/自动作废:
| 动作 | 订单 | 预警 | 客户 | 工单 |
|---|---|---|---|---|
| 放行 | `已确认` + 落账 | `已排除` | 正常成交 | 无 |
| 拦截 | `失败`(作废) | `已确认` | 站内信通知 | 生成「可疑交易上报」 |
| 冻结 | `失败`(作废) | `已确认` | `sys_user.status=冻结` + 站内信通知 | 生成「冻结复核」 |
**约束**:
- 处置接口的 `handler_id` **由后端从当前登录用户获取**,不信任前端传入。
- 处置接口需校验角色:风控专员(`user_type=EMPLOYEE` 且 `employee_role=风控专员`,或 `ADMIN`)。
- 处置结果回写订单与预警表(`handler_id`/`handle_result`/`handle_time`),全程留痕可追责。
- **放行时重新校验余额**:挂起期间余额可能变化,申购放行若余额不足则放行失败、订单转「失败」。
### FR5 · 工单链
- **生成时机**:仅在「拦截」或「冻结」处置时自动创建,系统自动挂起阶段不产生工单。
- **创建方式**:系统自动 INSERT,预填 `order_type`(可疑交易上报/冻结复核)、`customer_id`、`submitter_id`(当前专员)、`priority`(按预警级别)、`biz_content`(打包命中规则+订单号+预警 id);`status` 默认「待处理」、`current_node` 默认「初审」。
- **流转(后端接口驱动)**:
```
认领 提交审核 复核通过
待处理 ──────────► 处理中 ──────────► 待审核 ──────────► 已完成
(handler_id=自己) │
└──────► 已驳回(复核驳回,终态)
```
- **职责分离**:复核人 ≠ 处理人,先做**软校验**(仅提示,不强制阻断)。
- **终点**:工单「已完成」即项目内终点,**不上报人行**(监管报送为系统外动作,不在本项目范围)。
- **每次流转写 `audit_log`**(谁、何时、从什么态改到什么态)。
---
## 5. 状态机
### 5.1 `trade_order` 订单状态机
```
待确认 ──(未命中)──────► 已确认 ──► 落账 + 写 fin_transaction
│
└──(命中)──────────► 风控挂起
│
├──(放行)──► 已确认 ──► 落账 + 写 fin_transaction
├──(拦截)──► 失败(作废)+ 通知客户 + 生成工单
└──(冻结)──► 失败(作废)+ 客户冻结 + 通知客户 + 生成工单
```
### 5.2 `fin_risk_alert` 预警状态机(无「处理中」中间态)
```
未处理 ──(放行)────► 已排除
│
├──(拦截)──────► 已确认
└──(冻结)──────► 已确认
```
**防并发**:处置接口用条件更新 `WHERE id=? AND status='未处理'`,更新不到(rowcount=0)即视为已被他人处置,返回提示,不重复落账/工单/通知。
### 5.3 `biz_work_order` 工单状态机
```
待处理 ──认领──► 处理中 ──提交审核──► 待审核 ──复核通过──► 已完成
└──复核驳回──► 已驳回(终态)
```
---
## 6. 数据表设计
> 五张表 DDL 已在 `sql/schema.sql`,本次需补 ORM model + repository + schema。
### 6.1 `trade_order` 字段映射
| 字段 | 来源 |
|---|---|
| `order_no` | 生成唯一单号 `PO+日期+序列` |
| `customer_id` | 当前用户 id |
| `product_id` | 入参 product_id |
| `advisor_id` | 客户自购为 null |
| `order_type` | 申购 / 赎回 |
| `amount` / `shares` | 申购金额+折算份额;赎回份额+折算金额 |
| `nav` | 产品当前净值 |
| `fee` | 0(暂无手续费) |
| `status` | 待确认 / 风控挂起 / 已确认 / 失败 |
| `risk_alert_id` | 挂起时关联预警 id |
| `confirm_time` | 确认时间 |
### 6.2 `fin_transaction` 成交流水
仅在**风控通过、落账成交**时写一笔:`transaction_no`、`order_id`(关联申请单)、`customer_id`、`product_id`、`transaction_type`(申购/赎回)、`amount`、`shares`、`nav`、`fee`、`status='已确认'`。
### 6.3 `fin_risk_alert` 预警
| 字段 | 填什么 |
|---|---|
| `customer_id` | 交易客户 |
| `order_id` | 触发预警的申请单 |
| `alert_type` | 主命中规则名 |
| `alert_level` | 低(蓝)/中(黄)/高(红),取命中规则最高档 |
| `trigger_detail` | 全部命中规则 + 触发详情 |
| `transaction_ids` | 关联历史交易 ID 列表(聚合规则用) |
| `confidence` | 预警置信度 |
| `status` | 未处理 → 已确认 / 已排除 |
| `handler_id` / `handle_result` / `handle_time` | 处置留痕 |
### 6.4 `risk_rule` 规则(表驱动)
20 条规则配置见 §7,字段:`rule_id` / `rule_name` / `trigger_condition` / `threshold`(JSON) / `risk_level` / `weight` / `status`。
### 6.5 `biz_work_order` 工单
见 §4 FR5,`work_order_no` 生成 `WO+日期+序列`。
---
## 7. 规则清单(20 条)
### A 类 · 单笔规则(只看当前订单)
| 编号 | 规则名 | 触发条件 | 阈值 JSON | 级别 | 权重 |
|---|---|---|---|---|---|
| R001 | 大额申购-红 | 单笔申购金额 ≥ 100万 | `{"amount":1000000}` | 高 | 0.90 |
| R002 | 大额赎回-红 | 单笔赎回金额 ≥ 100万 | `{"amount":1000000}` | 高 | 0.90 |
| R003 | 大额申购-黄 | 单笔申购金额 ≥ 50万 | `{"amount":500000}` | 中 | 0.70 |
| R004 | 大额赎回-黄 | 单笔赎回金额 ≥ 50万 | `{"amount":500000}` | 中 | 0.70 |
| R014 | 适当性异常 | 客户风险等级 < 产品风险等级 | `{}` | 高 | 0.95 |
| R020 | 夜间交易 | 下单时间 00:00–06:00 | `{"start":"00:00","end":"06:00"}` | 低 | 0.35 |
### B 类 · 聚合规则(查客户历史流水)
| 编号 | 规则名 | 触发条件 | 阈值 JSON | 级别 | 权重 |
|---|---|---|---|---|---|
| R005 | 高频交易 | 近 7 天申赎 ≥ 10 笔 | `{"days":7,"count":10}` | 中 | 0.65 |
| R006 | 当日密集交易 | 近 1 天申赎 ≥ 5 笔 | `{"days":1,"count":5}` | 中 | 0.60 |
| R007 | 密集小额申购 | 近 3 天 ≥ 5 笔、每笔 < 1万 | `{"days":3,"count":5,"max_amount":10000}` | 低 | 0.50 |
| R008 | 短炒赎回 | 同产品申购后 7 天内赎回 | `{"days":7}` | 中 | 0.65 |
| R009 | 快进快出 | 同产品申购后 3 天内全额赎回 | `{"days":3}` | 高 | 0.85 |
| R010 | 资金快进快出 | 入账后 24 小时内赎回 | `{"hours":24}` | 高 | 0.90 |
| R011 | 短期累计大额 | 近 7 天累计申赎 ≥ 200万 | `{"days":7,"amount":2000000}` | 高 | 0.85 |
| R012 | 中期累计大额 | 近 30 天累计申赎 ≥ 500万 | `{"days":30,"amount":5000000}` | 中 | 0.65 |
| R013 | 拆分规避大额 | 同 1 天多笔合计 ≥ 100万、单笔 < 50万 | `{"days":1,"total":1000000,"each_lt":500000}` | 中 | 0.70 |
| R018 | 全额清仓赎回 | 赎回后该产品持仓归 0 且金额 ≥ 10万 | `{"amount":100000}` | 低 | 0.45 |
| R019 | 对倒交易 | 近 7 天多产品互买互卖 | `{"days":7}` | 中 | 0.70 |
### C 类 · 客户维度规则(看画像/账户/注册时间)
| 编号 | 规则名 | 触发条件 | 阈值 JSON | 级别 | 权重 |
|---|---|---|---|---|---|
| R015 | 新客户大额 | 注册 < 30 天且单笔 ≥ 50万 | `{"days":30,"amount":500000}` | 中 | 0.65 |
| R016 | 休眠账户激活 | 90 天无交易后首笔 ≥ 10万 | `{"days":90,"amount":100000}` | 低 | 0.55 |
| R017 | 整数金额 | 金额为整万且 ≥ 10万 | `{"amount":100000}` | 低 | 0.40 |
**统计**:高 6 条、中 8 条、低 6 条。
**规则启用状态**:R019 对倒交易初始 `status=停用`(实现最复杂、Mock 下难触发),其余 19 条默认启用。
---
## 8. 接口清单
### 8.1 交易接口(改造)
| 接口 | 说明 |
|---|---|
| `POST /api/purchase` | 改造为前置拦截:适当性校验 → 建单 → 检测 → 落账/挂起 |
| `POST /api/redeem` | 同上(赎回无适当性校验) |
### 8.2 处置接口(新增,风控专员)
| 接口 | 动作 | 副作用 |
|---|---|---|
| `POST /api/risk/alert/{id}/release` | 放行 | 订单已确认 + 落账;预警已排除 |
| `POST /api/risk/alert/{id}/block` | 拦截 | 订单失败 + 通知;预警已确认 + 生成工单 |
| `POST /api/risk/alert/{id}/freeze` | 冻结 | 订单失败 + 客户冻结 + 通知;预警已确认 + 生成工单 |
### 8.3 工单接口(新增)
| 接口 | 动作 | 状态变化 |
|---|---|---|
| `GET /api/work-order/list` | 列表 | — |
| `GET /api/work-order/{id}` | 详情 | — |
| `POST /api/work-order/claim` | 认领 | 待处理 → 处理中 |
| `POST /api/work-order/submit-review` | 提交审核 | 处理中 → 待审核 |
| `POST /api/work-order/review` | 复核 | 待审核 → 已完成 / 已驳回 |
### 8.4 规则引擎(内部服务,非 HTTP 接口)
- `detect(order, customer) -> 命中规则列表`:加载启用规则 → 按 `rule_id` 路由到判断函数 → 传入交易数据 + `threshold` 执行 → 汇总命中。
---
## 9. 关键设计约束(汇总)
1. **单事务原子**:建单 → 检测 → 落账/挂起在单个事务内完成。
2. **落账复用**:复用现有扣款/加仓、减仓/入账逻辑,不重写。
3. **纯代码检测**:规则匹配用 `if/else + SQL`,不用 LLM。
4. **人工处置**:挂起自动,放行/拦截/冻结人工,系统不自动处置。
5. **防并发**:处置接口条件更新 `status='未处理'`,防重复处置。
6. **handler_id 后端取**:不信任前端传入;处置/工单接口均校验角色权限。
7. **放行重新校验余额**:防挂起期间余额变化导致落账失败。
8. **预警聚合**:一笔订单一条预警;级别取最高;置信度按权重合成。
9. **工单后置**:仅拦截/冻结时生成,为处置的下游产物。
10. **审计留痕**:处置与工单流转均写 `audit_log`。
---
## 10. 实施分期
```
Step 1 建 5 张表的 model + repo + schema
Step 2 改 purchase/redeem:适当性校验 → 建单(待确认) → 调风控引擎
Step 3 写规则引擎(读 risk_rule + 查历史流水,输出命中列表)
Step 4 命中分支:挂起 + 生成预警;未命中:落账 + 写流水
Step 5 处置接口(放行/拦截/冻结)+ 权限 + 防并发 + 审计
Step 6 工单链(自动建单 + claim/review 接口) ← 可后置
```
---
## 11. 验收标准
1. **正常申购/赎回**:未命中规则 → 订单「已确认」、账户/持仓变动、`fin_transaction` 生成一条成交流水。
2. **命中挂起**:大额申购(如 ≥100万)→ 订单「风控挂起」、生成一条 `fin_risk_alert`(未处理)、账户与持仓不变。
3. **放行**:风控专员放行 → 订单「已确认」+ 落账、预警「已排除」。
4. **拦截**:风控专员拦截 → 订单「失败」、预警「已确认」、生成「可疑交易上报」工单、客户收到站内信。
5. **冻结**:风控专员冻结 → 订单「失败」、预警「已确认」、客户 `sys_user.status=冻结`、生成「冻结复核」工单。
6. **防并发**:同一预警被两次处置,第二次返回"已被处理",不重复落账/工单/通知。
7. **权限**:非风控专员调用处置接口被拒绝。
8. **工单链**:拦截/冻结生成的工单能走完「待处理 → 处理中 → 待审核 → 已完成/已驳回」。
9. **留痕**:处置与工单流转均有 `audit_log` 记录(谁、何时、从什么态到什么态)。
---
## 12. 已确认决策清单
| # | 决策点 | 结论 |
|---|---|---|
| 1 | 风控时机 | 前置拦截(先建单 → 检测 → 通过才落账) |
| 2 | 检测方式 | 同步 + 纯规则引擎,不用 LLM |
| 3 | 落表 | `trade_order` 为风控主表,`fin_transaction` 为成交凭证 |
| 4 | 预警粒度 | 一笔订单聚合一条预警 |
| 5 | 分级置信度 | 级别取最高;`confidence = 1 − Π(1 − weight_i)` |
| 6 | 处置方式 | 挂起自动,放行/拦截/冻结人工 |
| 7 | 预警中间态 | 不加「处理中」,用条件更新防并发 |
| 8 | 工单时机 | 仅拦截/冻结时生成,人工处置之后 |
| 9 | 工单复核分离 | 软校验(仅提示),不强制 |
| 10 | 工单驳回 | 已驳回为终态,不回流 |
| 11 | 工单终点 | 「已完成」即终点,不上报人行 |
| 12 | R019 对倒交易 | 初始停用 |
| 13 | R014 适当性异常 | 保留作兜底 |
| 14 | 单号生成 | 前缀+日期+序列(`PO...` / `WO...`) |