Files
Mutual_Fund/风控Agent需求文档.md
T

342 lines
16 KiB
Markdown
Raw Normal View History

# 风控 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...`) |