chore: 移除误提交的 .agents/skills 与 skills-lock.json(AI 助手配置,与项目无关)
- 这 15 个文件随 e38ece3 前端提交一起进了仓库,内容与本项目无关
- 用 git rm --cached 从索引移除(本地文件保留),并加入 .gitignore
This commit is contained in:
+19
-44
@@ -72,38 +72,25 @@ D:\conda\envs\jr_py313\python.exe tools\portal.py --base-url http://127.0.0.1:80
|
||||
|
||||
## 3. 员工 · 风控工作台(`risk_t`)
|
||||
|
||||
> 本节按 `docs/风控业务演示文档/17-风控模块-前端合并提示词与验收约束.md` 重写过,功能清单与交互约束都对齐了那份文档。
|
||||
|
||||
| # | 操作 | 预期结果 |
|
||||
|---|---|---|
|
||||
| 3-1 | 进入即自动加载"风险概览" | 五张指标卡:**预警总量 / 待处理 / 已超时 / 高风险 / 重点预警** ✅实测(total=2 pending=1 overdue=2 高风险=2 重点=2) |
|
||||
| 3-2 | 看"预警队列" | **每页 5 条**、按风险等级优先、行高固定且超长省略 ✅实测<br>本机 2 条:`ALDEMO0002`(高,RW-015/RW-003)、`ALDEMO0001`(高,RW-007/RW-002/RW-012)<br>⚠️ 该接口 `limit` 上限就是 **5**,传 20 会 422 且**整张表渲染不出来** |
|
||||
| 3-3 | 用筛选栏逐项筛 | 8 个条件:关键词、客户号、风险等级、规则码、产品代码、产品名、起止时间 ✅实测<br>`rule_code=RW-015 → ALDEMO0002`、`customer_no=T-CUST → 2 条`、`keyword=ALDEMO0002 → 1 条`<br>⚠️ **风险等级必须填「高/中/低」**:预警对象用「高」,而概览 `levels` 用「高风险」,填「高风险」会 422 |
|
||||
| 3-4 | 点「下一页 / 上一页」 | 走游标分页(`meta.next_cursor` + `has_more`),页脚显示"第 N 页 · 每页 5 条" ✅实测(本机 2 条 → 已到末页) |
|
||||
| 3-5 | 点某行「详情」 | **弹窗**显示:预警编号、类型、等级与优先级分、处置状态、回执状态、证据归档、规则、到期时间、结案原因、证据摘要、证据快照、客户(行为分/投资者类型/风险标签)✅实测(alert 23 字段、customer 13 字段) |
|
||||
| 3-6 | 弹窗里点「确认接收」 | 二次确认后提交。✅实测:`POST .../acknowledgements`(无 body)→ **409「只有待处理的预警才能确认解决」**,说明该动作有状态前置 |
|
||||
| 3-7 | 弹窗里点「进入调查」 | 二次确认后 `POST .../investigations`(无 body)⚠️按契约 |
|
||||
| 3-8 | 弹窗里点「关闭误报」 | **必须填写理由**(1-500 字),空值会被前端与后端双重拒绝 → `POST .../exclusions {reason}` ✅实测(body 字段已核对) |
|
||||
| 3-9 | 弹窗里点「升级」 | **必须先「确认接收」**,否则 409「请先确认接收预警」;填 `reason` → `POST .../escalations` ✅实测 |
|
||||
| 3-10 | 弹窗里点「完成结案」 | 填 `resolution`(**字段名不是 reason**)→ `POST .../resolutions` ✅实测 |
|
||||
| 3-11 | 点「手动扫描」 | 二次确认后 `POST /alerts/scan` → **HTTP 200, code=0** ✅实测 |
|
||||
| 3-12 | 点「生成日报」 | 弹窗内**流式生成**(SSE:`start` / `progress` / `replace`),生成后可**编辑内容**再填收件人发送 ✅实测(事件类型已核对) |
|
||||
| 3-13 | 点「八类证据」 | 八个页签:**客户 / 产品 / 交易 / 资金 / 持仓 / 登录 / 预警 / 通知**,各带关键词、行为分等级、发送状态、起止时间筛选 ✅实测(8/8 全部 HTTP 200)<br>⚠️ 正确路径是 **`holdings`**,写成 `positions` 会被 422 拒绝 |
|
||||
| 3-14 | 点「通知记录」 | 表格:预警编号 / 类型 / 发送状态 / 时间 ✅实测(HTTP 200) |
|
||||
| 3-15 | 用**客户**账号访问风控接口 | **403 缺少操作权限** ✅实测 |
|
||||
| 3-1 | 进入即自动加载"总览" | 数字卡片;数据范围 `all`(能看全部客户的预警) ✅实测 |
|
||||
| 3-2 | 点「刷新总览」 | 同上,HTTP 200 ✅实测 |
|
||||
| 3-3 | 进入即自动加载"预警列表" | **表格出现**:预警号 / 客户 / 等级 / 规则 / 状态 / 操作。本机实测 2 条:`ALDEMO0002`(高,RW-015/RW-003)、`ALDEMO0001`(中,RW-007/RW-002/RW-012),均"待处理" ✅实测<br>⚠️ **门户刻意不传 `limit`**:该接口 `limit` 上限是 **5**,传 20 会得到 `422 query.limit: Input should be less than or equal to 5`,**整张表格渲染不出来**(行内按钮也随之消失) |
|
||||
| 3-4 | 点「触发一次扫描」 | **HTTP 200,`body.code=0`**(这条以前会因缺幂等头报 422,已修) ✅实测 |
|
||||
| 3-5 | 点「生成日报」 | HTTP 200,返回日报内容 ⚠️按契约 |
|
||||
| 3-6 | 对某条预警点「确认」 | 二次确认后返回业务结果。✅实测:`POST .../acknowledgements`(**无必填 body**)→ **409「只有待处理的预警才能确认解决」** —— 该动作**有状态前置条件**,不是任意状态都能点 |
|
||||
| 3-7 | 点「升级」/「解决」 | ✅实测:**必须先「确认」接收预警**,否则两者都返回 **409「请先确认接收预警」**。<br>升级要填 <code>reason</code>、解决要填 <code>resolution</code>(**字段名不同**,各 1-500 字),门户已做成弹窗必填;不填会是 422 |
|
||||
| 3-8 | 点一个**不存在**的预警号 | 404 资源不存在 —— 正常 fail closed ⚠️按契约 |
|
||||
| 3-9 | 用**客户**账号访问风控接口 | **403 缺少操作权限**(`risk_t` 能看,`cust_t` 不能) ✅实测(客户访问 `/admin/roles` 为 403) |
|
||||
|
||||
> ⚠️ **注意**:`risk_t` 有 `audit:read`,所以它访问 `/api/v1/admin/roles` 是 **200 而不是 403** ——
|
||||
> 这是种子设计如此,不是越权漏洞。
|
||||
>
|
||||
> ⚠️ 五个处置动作都是**写操作**,会在库里留数据与审计,且**受状态机约束**(**先确认 → 再调查/误报/升级/结案**)。
|
||||
> 各自动作的请求体不同:确认与进入调查无 body、误报与升级要 `reason`、结案要 `resolution`。
|
||||
> 列表拿不到数据时行内按钮不会出现 —— 先确认 3-2 是否正常。
|
||||
>
|
||||
> ⚠️ **排序局限**:接口没有排序参数,门户只在**当前页内**按风险等级+优先级分排序;跨页整体排序需要服务端支持。
|
||||
>
|
||||
> ⚠️ 本轮顺带修掉一个平台 bug:`rule_code` 筛选此前**恒返回 0 条**(`risk_repository.py` 用
|
||||
> `.contains([code])`,SQLAlchemy 把它编译成 `LIKE`,而列是 JSON 数组,等于匹配字符串
|
||||
> `'["RW-015"]'`)。已改为 `func.json_contains(col, json.dumps(code))`,扫描去重处的同一写法也一并修了。
|
||||
> ⚠️ 行内三条按钮对应 `acknowledgements` / `escalations` / `resolutions` 三个端点,都是**写操作**:
|
||||
> 会在库里留数据与审计,且**受状态机约束**(**确认 → 升级/解决**)。三个动作的请求体各不相同:
|
||||
> 确认无 body、升级要 `reason`、解决要 `resolution`。列表拿不到数据时这三个按钮不会出现 ——
|
||||
> 先确认 3-3 是否正常。
|
||||
|
||||
---
|
||||
|
||||
@@ -112,26 +99,14 @@ D:\conda\envs\jr_py313\python.exe tools\portal.py --base-url http://127.0.0.1:80
|
||||
| # | 操作 | 预期结果 |
|
||||
|---|---|---|
|
||||
| 4-1 | 进入即加载"邮箱状态" | 数字卡片,HTTP 200 ✅实测 |
|
||||
| 4-2 | 点「拉取邮件列表」 | 表格(邮件 ID / 主题 / 状态 / 操作)。**邮箱未配置时条数为 0**、不白屏 ✅实测(HTTP 200,0 条) |
|
||||
| 4-2 | 点「拉取邮件列表」 | 表格(邮件 ID / 主题 / 状态 / 操作);**邮箱未配置时**返回空列表或错误说明,不是白屏 ✅实测(HTTP 200) |
|
||||
| 4-3 | 点某封邮件的「识别字段」 | 返回识别结果 JSON ⚠️按契约(需库里有邮件数据) |
|
||||
| 4-4 | 点「删除」邮件 | 二次确认后写操作。门户自动带 `operator_id`(= 当前登录 user_id)✅实测(字段齐了才会进业务层) |
|
||||
| 4-5 | 点「触发邮箱恢复」 | ✅实测:`HTTP 200 + body.code=404「邮箱尚未初始化」` —— 请求体已合法,是本机没配邮箱,属正常 |
|
||||
| 4-4 | 点「删除」邮件 | 二次确认后写操作;审计留痕 ⚠️按契约 |
|
||||
| 4-5 | 点「触发邮箱恢复」 | 二次确认后 HTTP 200 ⚠️按契约 |
|
||||
| 4-6 | 在"单据处理"填一个**真实存在**的 task_id,点「识别字段」「规则结果」 | 返回该单据的字段与规则判定 ⚠️按契约 |
|
||||
| 4-7 | 填一个**不存在**的 task_id | 422/404,页面上原样显示 —— 正常 ⚠️实测(假 task_id 得到 422) |
|
||||
| 4-8 | 点「确认单据」/「重试识别」/「创建通知」 | 见下方"三个写操作的必填字段",各自会弹窗要必填值 ⚠️按契约 |
|
||||
| 4-9 | 点「重算结算统计」 | ✅实测:要填 `fund_code` + `application_date`(门户已弹窗),**HTTP 200 `code=0` ok** |
|
||||
|
||||
> **场外写操作的三条硬约束**(实测得出,门户已按此实现):
|
||||
>
|
||||
> | 接口 | 必填字段 |
|
||||
> |---|---|
|
||||
> | `mailbox-status/recoveries`、`mails/{id}/deletions`、`documents/{id}/recognition-retries` | `operator_id` |
|
||||
> | `documents/{id}/confirmations` | `decision`(**中文枚举**:确认无误 / 确认异常 / 未处理)+ `operator_id` |
|
||||
> | `documents/{id}/notifications` | `notification_type`(risk / settlement / mail_return / normal_return / exception_return…)+ `operator_id` |
|
||||
> | `settlement-statistics/recalculate` | `fund_code` + `application_date`(**没有** operator_id) |
|
||||
>
|
||||
> `operator_id` 是**防伪校验**:平台会核对它是否等于当前登录用户(传别人的会被拒)。
|
||||
> 实测不传它必得 **422**,所以门户一律自动带本次登录的 user_id,不让你手填。
|
||||
| 4-7 | 填一个**不存在**的 task_id | 404 资源不存在,页面上原样显示 —— 正常 ⚠️按契约 |
|
||||
| 4-8 | 点「确认单据」/「重试识别」/「创建通知」 | 二次确认后写操作;审计留痕 ⚠️按契约 |
|
||||
| 4-9 | 点「重算结算统计」 | 二次确认后 HTTP 200 ⚠️按契约 |
|
||||
|
||||
> **运营为什么只有 2 项权限却能用**:场外线的服务层用的是**角色门槛**
|
||||
> `{"operator","risk_operator","admin","super_admin"}`(`offsite_fund_service.py:2600`),
|
||||
|
||||
Reference in New Issue
Block a user