- 口径:今日盈亏 = 今日市值 − 昨日持仓市值 − 今日买入金额 + 今日卖出金额(不含费用,与「持有盈亏」同口径)
- 昨日持仓数量由当日成交反推,不需要新表新字段
- 基准优先场内行情最近两个交易日收盘价(与同页 latest_price/market_value 同源、客户可核对),
行情只有一天时回退基金净值(15911/159991-159995 的行情本身就来自净值序列)
- 只认 transaction_type ∈ {买入,卖出}:演示库里混进的风控场外申购/赎回(RISKDEMO-*)
会把「昨日持仓数量」抬到 76 万份、今日盈亏从 -21.22 变成 -1612.72
- 接口字段与前端一行未改:HoldingItem.today_profit_loss / PortfolioSummary 两个字段数值变真
- 新增 tools/check_today_profit_loss.py:纯 SQL 独立复算并与接口逐只比对(实测 9001 = -132.70 / -0.2528%)
- 新增 6 条单测;pytest tests/unit tests/contract → 1466 passed, 0 failed
713 lines
54 KiB
Markdown
713 lines
54 KiB
Markdown
# 软件需求文档(SRS)——金融 Agent 平台
|
||
|
||
> 文档版本:v1.0(2026-09-14)
|
||
> 文档状态:**待确认稿**——第 0 章列出 22 项必须由业务方补充确认的问题,确认后本文件升为 v1.1
|
||
> 适用范围:场内基金模拟交易 + 客服问答 + 风控 + 投顾 + 场外基金运营 + 推介材料(六大业务域)
|
||
> 依据材料(均为仓库内既有文档,非本文件臆造):`docs/00` 数据库基线、`docs/03` 端到端流程、`docs/05` 接口文档、`docs/22` 四大 Agent 拆解、`docs/10` 业务域接入评估、`docs/43` 场内产品手册、`docs/44` 演示流程、`docs/验收与审计/phase1-*`、`docs/28` 场外表登记、`docs/31` 投顾灰度、`docs/场外申购赎回工作流程图.md`、以及 `app/service/*` 实际实现代码。
|
||
|
||
---
|
||
|
||
## 0. 需要业务方补充确认的问题(★ 请先回答本章)
|
||
|
||
本文件是在**不臆造业务**的前提下,依据仓库既有设计文档与已落地代码反推整理的。以下 22 项在现有材料中**确实没有权威答案**或存在**口径冲突**,其中标 `[阻塞]` 的会直接影响功能需求与验收标准的写法;标 `[影响]` 的只影响细节精度,可先按建议默认值推进。
|
||
|
||
### 0.1 范围与优先级(最高优先回答)
|
||
|
||
| # | 问题 | 现状 / 冲突点 | 影响 |
|
||
|---|---|---|---|
|
||
| Q1 | **一期交付范围是否就是"六大业务域全部"?** 还是要分两批(如客服+场内交易先上,场外+推介后上)? | `docs/22` §7 建议的建设顺序是 客服 → 投顾 → 风控 → 场外;但代码里**六个域都已落地**,`docs/44` 演示流程 8 个场景横跨全部域。文档需要一个明确的"本期交付边界"。 | `[阻塞]` |
|
||
| Q2 | **本期是"演示验收"还是"生产上线"?** 两者的非功能需求(可用性、备份、等保、并发量)量级差一个数量级。 | 现状:数据库口令、JWT 密钥走本地配置;`docs/26` 有密钥轮换设计;无出金/入金通道(`docs/00` §5 明示"无充值提现与银行流水匹配通道")。 | `[阻塞]` |
|
||
| Q3 | **是否仍严格限定"仅场内基金模拟交易"?** 场外运营是否仅做"辅助核对+留痕",不产生真实资金动作? | `AGENTS.md` 规则 8 明确:场外运营**独立**,不得写入场内交易表;`app/service/offsite_fund_service.py` 确实只读到 `OffsiteFundDocument` 等 `offsite_*` 表。请确认这条边界在需求层面不可松动。 | `[阻塞]` |
|
||
|
||
### 0.2 业务规则口径(现有文档明确写了"未定义",需业务填空)
|
||
|
||
| # | 问题 | 现状 / 冲突点 | 影响 |
|
||
|---|---|---|---|
|
||
| Q4 | **客服 RAG 的混合检索决策公式与分数分段阈值**(向量分/关键词分如何加权、多少分以上直答、多少分以下转人工、中间区间怎么办)? | `docs/22` §2.3 明确列为**该设计自身未定义**项;代码侧只落了 `snippet`/`knowledge_id` 与 TopK(FAQ 3 / 产品 5 / 政策 5),未见阈值常量。 | `[阻塞]`(直接影响"低置信不硬答"的验收判定) |
|
||
| Q5 | **低置信转人工的触发阈值与话术**,以及"多轮澄清后仍无法解决"的兜底路径? | `docs/03` 只说"低置信不得硬答、转人工需带完整上下文";`phase1` 教师标准认可 `[无法确认]` 标记 + 真实热线 `15936583816`。阈值本身未给。 | `[阻塞]` |
|
||
| Q6 | **风控规则引擎的规则清单从哪来?** 是业务方给一份规则表(规则名/口径/阈值/等级),还是要我们从 `docs/21`、`docs/25` 反推? | `docs/22` §2.3 明确列为未定义;代码 `app/service/risk_scan_service.py` 已有扫描实现但规则阈值需与业务对齐。 | `[阻塞]` |
|
||
| Q7 | **场上/场下若干"业务阈值"请业务给数**:申购单笔份额上限比例(现为总份额 10%)、单一投资者持有比例上限(现为 20%)、赎回巨额比例(现为 20%)、申购最低金额(现为 >1 元)是否为最终口径? | 来自 `app/service/offsite_fund_rules.py` 实际常量 `TEN_PERCENT=0.10`、`TWENTY_PERCENT=0.20`、`ONE_YUAN=1`。这些是**代码现状**,不排除是占位值。 | `[影响]` |
|
||
| Q8 | **场外"巨额赎回"是否要联动风控通知?清算是按单只基金聚合还是全市场聚合?** | `docs/场外申购赎回工作流程图.md` 边界明确:AML 与开放期规则**不在本次实现范围**;风险通知/邮件回复/清算通知**三条路径相互独立、无自动联动**。请确认这是有意为之还是待补。 | `[影响]` |
|
||
| Q9 | **合规检查放在生成前还是生成后?**(`docs/22` §6 列为待定决策) | 现状:推介材料代码里**两者都有**(`_generate_tx` 有"输入资料未通过合规校验"与"生成内容未通过合规校验"两个 422);客服侧未见显式前置校验。 | `[阻塞]` |
|
||
| Q10 | **是否允许 Agent 给出"投资建议"?** 边界是什么? | 现状代码是**保守**的:`asset_allocation_service.py` 常量 `analysis_only: True` 且 `disclaimer = "分析结果仅供参考,不生成交易指令。"`;`docs/43` 明确客服**不得**承诺收益、不得修改风险测评。 | `[影响]` |
|
||
| Q11 | **投顾灰度白名单的最终开放范围**(现为 `ADVISOR_ROLLOUT_CUSTOMER_IDS`,空名单 = 全体客户 fail-closed)? | `docs/31` 有开关与回滚手册;具体放量节奏需业务定。 | `[影响]` |
|
||
|
||
### 0.3 缺失的输入材料(需要业务方提供文件)
|
||
|
||
| # | 问题 | 影响 |
|
||
|---|---|---|
|
||
| Q12 | **原始需求文档 HTML**(设计提到 `C:\Users\Windows\Desktop\金融\需求文档-修改版.html`,本工作区不可达)能否提供? | `[阻塞]`——本文件是从代码+设计反推的,若有原始需求,可对齐措辞与遗漏项。 |
|
||
| Q13 | **场外申购/赎回单的真实样例件**(至少各 1 份 PDF/扫描件)。 | `docs/22` 明确"场外是唯一需要新表的模块,且**缺样例单据**"。没有样例,OCR 字段与规则无法验收。`[阻塞]` |
|
||
| Q14 | **投顾 21 张表的登记文档**是否已有?(`docs/28` 只登记了场外 10 张 + 推广 7 张,投顾 21 张"登记文档待补") | `[影响]`——影响投顾模块的数据字典完整性。 |
|
||
| Q15 | **部署环境是否具备 LibreOffice / soffice?** | `product_promotion_to_do_list.md` 记录:推介材料 PPTX 已能真实生成,但 **PDF 转换依赖部署环境的 LibreOffice**,本机未验证。`[阻塞]`(影响"PPT 导出"这项验收) |
|
||
| Q16 | **生产数据库/中间件规格**(MySQL 8.0 版本、Redis、Milvus 版本与部署形态、Neo4j 是否必装)? | 现状:`app/core/knowledge_schema.py` 显示**Milvus 集合 schema 因环境而异**(本机 `knowledge_id/snippet`,架构师环境 `doc_id/content/visibility/chapter`),检索层做了运行时探测。生产环境以哪套为准需定。`[影响]` |
|
||
| Q17 | **模型与供应商清单**(对话模型、Embedding 模型及其**向量维度**、是否允许外网调用、是否需私有化)? | `docs/22` §6 明确:模型选择决定 Milvus schema,是**待定决策**。现状配置含 `deepseek-flash` 等。`[阻塞]` |
|
||
|
||
### 0.4 流程与角色
|
||
|
||
| # | 问题 | 影响 |
|
||
|---|---|---|
|
||
| Q18 | **客服工单与风控工单是否要真正打通?**(`docs/22` §2.3 列"跨 Agent 联动规则未定义") | `docs/03` 验收要求"客服工单与风控工单不得混用",跨域升级路径未定。`[影响]` |
|
||
| Q19 | **是否需要"客户主动发起风险评估"入口**,还是测评只由运营/客服在后台维护? | `docs/43` 说客服**不能改**风险测评;那客户自己能否改?未写明。`[影响]` |
|
||
| Q20 | **人工兜底的值守时段与 SLA**(客服转人工后多久响应?风控告警多久必须处置?)? | `docs/03` 有状态机但无时间承诺;风控有 `overdue` 概念但阈值来源未明。`[影响]` |
|
||
| Q21 | **免责声明 / 适当性提示的标准文案**由谁出?(合规部?) | 现状:代码里有 `disclaimer` 常量与 `disclosures` 结构,但**正式文案需合规确认**。`[影响]` |
|
||
|
||
### 0.5 占位实现与本期边界(★ 接口有、值还没真算)
|
||
|
||
本节专门登记一类偏差:**接口存在、字段有返回、但值是占位常量**。它不报错、单元测试全绿,
|
||
也不会被现有任何自动化检查发现 —— 只有业务对着真实口径才看得出不对,
|
||
因此**是演示现场最容易被追问的地方**。以后发现同类问题请继续往本节追加。
|
||
|
||
| # | 问题 | 现状 / 冲突点 | 影响 |
|
||
|---|---|---|---|
|
||
| Q22 | **客户看板的「今日盈亏」本期是否实现?** | ~~硬编码 `ZERO`~~ **已于 2026-09-14 实现为真实计算**(`app/service/trade_service.py`,口径与基准见下)。✅ **已闭环** | 实现口径:`今日盈亏 = 今日市值 − 昨日持仓市值 − 今日买入金额 + 今日卖出金额`(不含费用);基准**优先场内行情最近两个交易日收盘价**(与同页 `latest_price` 同源、可核对),行情只有一天时**回退净值最近两个净值日**。逐只复算工具:`python tools/check_today_profit_loss.py`;说明见 `docs/演示用/今日盈亏实现说明-2026-09-14.md` |
|
||
|
||
**实现时核实的数据现状(2026-09-14 实测,修正了原先"行情基准不可行"的判断)**
|
||
|
||
| 候选口径 | 数据现状 | 结论 |
|
||
|---|---|---|
|
||
| `(今收 − 昨收) × 数量`,基准取 `fin_market_price` | 每个产品**恰好 2 个交易日**(`2026-09-11` 与 `2026-09-14`,中间是周末)——**正好够算「昨收 → 今收」**;但 `15911` / `159991-159995` **只有 1 天**(这几个产品的行情源就是净值序列) | ✅ 可用,**不足两日时回退净值** |
|
||
| `(今日净值 − 上一净值日) × 数量`,基准取 `fin_nav_history` | 每个产品 **120~160 行连续交易日净值**(`tools/sync_nav_history.py` 同步,按 `(product_id, nav_date)` 幂等 upsert);但 `511810`(货币 ETF)净值序列与价格不同量纲(净值 0.24 / 价格 100.0),做逐份盈亏会算错 | ⚠️ 可作**兜底**,不宜作首选 |
|
||
|
||
⇒ **最终口径:行情优先、净值兜底**。理由:行情与客户能看到的 `latest_price` / `market_value` 同源,
|
||
客户可以自己核对;净值兜底覆盖了"行情只有一天"的 6 个产品,且这 6 个产品的行情本身就来自净值序列。
|
||
语义上仍是「**最近一个交易日**相对前一交易日的盈亏」,**不是盘中实时**(日终数据),
|
||
当天净值/行情同步之前它会与"昨日"一致——这点界面上要讲清楚。
|
||
|
||
> **建议回答方式**:不必逐条写长文,可直接在本章表格后追加"Q1:……"式的短答,或在对话中直接回复序号+结论。**标 `[阻塞]` 的 12 项(Q1/Q2/Q3/Q4/Q5/Q6/Q9/Q12/Q13/Q15/Q17/Q22)建议优先给出**,其余可先按本文给出的默认口径执行,后续修订。
|
||
|
||
---
|
||
|
||
## 1. 项目背景与目标
|
||
|
||
### 1.1 项目背景
|
||
|
||
本项目是一个**面向基金业务的通用 Agent 平台**,采用"**厚平台、薄 Agent**"的设计哲学:
|
||
|
||
- **厚平台**:统一承担会话、幂等、长短期记忆、关系推理(Neo4j)、配置快照与发布、多模型路由、工具执行、合规校验、审计留痕、可靠事件投递(Outbox)等横切能力;
|
||
- **薄 Agent**:业务 Agent 只负责"检索、分析、生成、归纳、分诊",不得自行实现鉴权、记忆、模型调用、审计等能力,必须继承公共 `BaseAgent` 并由 `AgentFactory` 创建。
|
||
|
||
平台当前覆盖六大业务域:
|
||
|
||
| 域 | 业务Agent / 服务 | 数据表归属 |
|
||
|---|---|---|
|
||
| 客服问答 | `CustomerServiceAgent` | 场内基线的客服相关表 |
|
||
| 场内基金模拟交易 | 交易服务(非 Agent,确定性服务) | 场内 51 张表 |
|
||
| 风控 | `RiskAgent` + 规则引擎 | 场内基线的风控相关表 |
|
||
| 投顾 | `AdvisorAgent` | `advisor_*` 21 张表 |
|
||
| 场外基金运营 | `OffsiteFundAgent` | `offsite_*` 10 张表 |
|
||
| 基金推介材料 | `PromotionMaterialAgent` | `promotion_*` 7 张表 |
|
||
|
||
### 1.2 项目目标
|
||
|
||
**目标一:建一个"不绕过统一执行骨架"的 Agent 平台。**
|
||
所有业务 Agent 走同一套七步执行流程(输入校验 → 记忆召回 → 意图识别 → 核心业务 → 生成与合规 → 数据落库 → 事件广播),保证鉴权、合规、审计、事件四条链路不可能被旁路。
|
||
|
||
**目标二:让四类高风险业务在"人在环中"(Human-in-the-loop)下可用。**
|
||
|
||
- 场内交易:**模拟撮合**,所有成交、资金、持仓均为平台内部虚拟数据,不涉及真实资金;
|
||
- 风控:**规则引擎做确定性扫描**,模型只用于生成"建议优化方向",不得替代规则判断;
|
||
- 场外运营:Agent 只做识别与核对,**最终确认必须由运营人员点击**;
|
||
- 投顾:只输出分析,**不生成交易指令**。
|
||
|
||
**目标三:权威业务事实不可被记忆或模型覆盖。**
|
||
记忆分层中,MySQL 中的权威事实(持仓、现金、成交、风险测评)优先级高于用户自述与 AI 推断;低版本记忆不得覆盖高版本。
|
||
|
||
**目标四:满足课程/项目的阶段性验收标准。**
|
||
一期 7 条验收标准(见第 6 章)已全部达成,附真机证据;本文件在此基础上把需求正式化。
|
||
|
||
### 1.3 业务模式
|
||
|
||
| 维度 | 说明 |
|
||
|---|---|
|
||
| **产品形态** | B2C 模拟交易 + B2B2C 运营辅助。客户端为访客/客户自助问答与模拟下单;员工端为客服工作台、风控工作台、投顾工作台、运营控制台 |
|
||
| **收入模式** | **无收入**(模拟盘)。若转生产需业务补充商业模式(见 Q2) |
|
||
| **资金流** | **无真实资金流**。场内为虚拟账户记账;场外只做单据核对与通知留痕,不做划款 |
|
||
| **数据流** | 行情来自外部接口(实时价 + 日K,落 `fin_nav_history`);其余全部为平台内部产生 |
|
||
| **合规模式** | 事前(适当性校验、白名单发布)+ 事中(模型生成后合规检查、人工确认闸门)+ 事后(全链路审计、`trace_id` 贯穿) |
|
||
|
||
---
|
||
|
||
## 2. 用户角色与典型使用场景
|
||
|
||
### 2.1 角色清单
|
||
|
||
平台共定义 **8 类角色**(5 个人类角色 + 3 个非人角色),每一类都带**明确的禁止事项**——这是本项目合规设计的核心,需求层面不可弱化。
|
||
|
||
| # | 角色 | 系统角色码 | 可以做 | **明确禁止** |
|
||
|---|---|---|---|---|
|
||
| 1 | **访客** | `visitor` | 浏览公开产品页、提问(受知识边界限制) | 不得访问任何客户数据;不得下单 |
|
||
| 2 | **客户** | `customer` | 提问、看自己资产/持仓/成交、做测评、确认方案、下单(模拟) | **不得查询他人任何数据** |
|
||
| 3 | **客服人员** | `employee` + 客服权限 | 受理咨询/投诉/转交工单、查知识库 | **不得做产品推荐** |
|
||
| 4 | **持证投顾** | `advisor` | 复核方案、解释风险、确认适当性 | **不得绕过适当性、不得承诺收益** |
|
||
| 5 | **运营人员** | `operator` / `employee` | 核实业务数据、处理异常、**做最终确认** | **不得让 Agent 自动完成最终确认** |
|
||
| 6 | **风控专员** | `risk_operator` | 调查/排除/升级/结案告警 | **不得修改交易、资金、持仓事实** |
|
||
| 7 | **平台管理员** | `admin` | 配置、模型路由、知识发布、权限治理 | 不得绕过配置不可变版本机制 |
|
||
| 8 | **规则引擎**(非人) | — | 确定性扫描 → 产生告警 | **不得用模型判断替代规则** |
|
||
| 9 | **Agent**(非人) | — | 检索/分析/生成/归纳/分诊 | **不得代客交易、不得越权查询、不得做最终业务决策** |
|
||
| 10 | **通用平台**(非人) | — | 鉴权/记忆/工具/合规/审计/事件 | **不得被业务旁路** |
|
||
|
||
> 演示账号(来自 `docs/44`):`cust_t` / `123456`(客户)、`risk_t` / `666666`(风控)、`admin_t` / `88888888`(管理员)、`advisor_t` / `abc12345`(投顾)。另种子脚本 `tools/seed_test_rbac.py` 内置 `review_t`(**故意不授予角色、不设密码**,用于边界测试)。
|
||
|
||
### 2.2 典型使用场景
|
||
|
||
| 编号 | 场景名 | 角色 | 触发 | 期望结果 |
|
||
|---|---|---|---|---|
|
||
| S-01 | 访客知识边界问答 | 访客 | 问"基金申购后多久确认" | 命中知识库给出答案;问超出范围内容时**不硬答**,给 `[无法确认]` 与热线 |
|
||
| S-02 | 客户查看资产 | 客户 | 打开"我的资产" | 返回现金余额、总市值、总资产;**持仓查询不校验行情时效** |
|
||
| S-03 | 客户模拟买入 ⭐ | 客户 | 选基金、填数量(100 的整数倍) | 走完校验链→立即全额成交→生成订单+流水+持仓+资金变动 |
|
||
| S-04 | 客服多轮对话与转人工 | 客户/客服 | 连续追问或明确要求转人工 | 3 轮以上保持上下文;转人工时**上下文完整带入**工单 |
|
||
| S-05 | 风控告警处置 ⭐ | 风控专员 | 规则扫描产生告警 | 走 `待处理 → 调查中 → 已排除/已结案`;升级为独立标记 |
|
||
| S-06 | 风控日报流式查看 | 风控专员 | 打开日报 | SSE 流式产出;规则部分**确定性**,仅"建议优化方向"用模型 |
|
||
| S-07 | 投顾方案生成与复核 | 投顾 | 客户已填目标与画像 | 生成资产配置与产品推荐;**只分析、不下单**;需适当性+合同证据三重校验 |
|
||
| S-08 | 管理员配置治理 | 管理员 | 改配置/知识 | 走 draft→review→activate 不可变版本;`If-Match` 乐观锁防并发覆盖 |
|
||
| S-09 | 场外单据核对与回复 | 运营 | 收到申购/赎回邮件 | OCR+字段识别→NL2SQL 查数→规则核对→**人工确认**→按类型发通知 |
|
||
| S-10 | 推介材料生成 | 运营/市场 | 上传结构化资料 | 合规校验→生成 PPTX→复核→内部发送记录留痕 |
|
||
|
||
---
|
||
|
||
## 3. 功能需求(按模块划分 + 优先级)
|
||
|
||
> **优先级定义**
|
||
> **P0** = 本期必须交付,缺失则验收不通过;
|
||
> **P1** = 本期应交,可接受降级实现(如以人工替代自动化);
|
||
> **P2** = 本期可不做,但需在架构上预留。
|
||
|
||
---
|
||
|
||
### 3.1 模块一:通用 Agent 平台底座(P0)
|
||
|
||
| 编号 | 需求 | 优先级 | 验收要点 |
|
||
|---|---|---|---|
|
||
| F-1.1 | 提供统一七步执行骨架,业务 Agent 必须继承 `BaseAgent`,由 `AgentFactory` 创建 | P0 | 无法绕过鉴权/记忆/模型路由/工具/合规/审计/事件 |
|
||
| F-1.2 | 统一成功信封 `{"data":..., "meta":{"trace_id":...}}`;列表额外含 `next_cursor`/`has_more` | P0 | 业务接口不得新增其他顶层字段 |
|
||
| F-1.3 | 统一错误信封 `{"error":{code,message,retryable,field_errors}, "meta":{trace_id}}` | P0 | 36 个错误码,`retryable` 逐码标注(不由状态码推导) |
|
||
| F-1.4 | `trace_id` 全链路贯穿 | P0 | 取值顺序:请求上下文→`request.state`→`X-Trace-ID`→空串;**不得伪造** |
|
||
| F-1.5 | 幂等:`Idempotency-Key`,范围 = `user_id + method + 规范化路径 + key` | P0 | 同键并发只产生一条消息/工单/领域事件;**记录与业务写入同事务** |
|
||
| F-1.6 | 乐观并发:`If-Match` vs 行内容摘要,冲突→409 `RESOURCE_VERSION_CONFLICT`(**可重试**) | P0 | 每个支持 `If-Match` 的资源**必须同时提供返回 etag 的读接口** |
|
||
| F-1.7 | 游标分页:`cursor` 表示"取更旧一页";非法游标→400 `INVALID_CURSOR` | P0 | 校验在**权限闸门之后、数据访问之前** |
|
||
| F-1.8 | 限流 `enforce_rate_limit` 作为路由依赖,鉴权始终先于限流 | P0 | 交易类(`/api/v1/users/me/**`)**不限流** |
|
||
| F-1.9 | 三段式异步 Agent 运行:`POST` → 202 + `status_url`/`events_url`;轮询或 SSE 取结果 | P0 | 需常驻 Worker,否则 `agent_run` 停在 `queued` |
|
||
| F-1.10 | SSE 契约:`start`/`tools`/`replace`/`delta`/`done`/`error`,心跳为注释行 | P0 | 终态走重放模式;`delta` 按 `sse_chunk_characters`(默认 256)切分 |
|
||
| F-1.11 | 记忆分层:短期(Redis) → 情节(中期) → 长期(promotion) | P0 | 召回权威性:**MySQL 权威事实 > 用户自述 > AI 推断** |
|
||
| F-1.12 | 配置发布:不可变版本,draft→review→activate→rollback | P0 | 一次请求内配置**不得漂移**;发布白名单为工具可用范围上限 |
|
||
| F-1.13 | 审计与事件:所有写操作留痕;Outbox 保证事件可靠投递 | P0 | 每条请求可凭 `trace_id` 追溯 |
|
||
|
||
---
|
||
|
||
### 3.2 模块二:认证与权限(P0)
|
||
|
||
| 编号 | 需求 | 优先级 | 验收要点 |
|
||
|---|---|---|---|
|
||
| F-2.1 | 登录:`POST /api/v1/auth/tokens` | P0 | 失败**不区分原因**,统一"用户名或密码不正确";用户不存在时仍走 dummy bcrypt 防时序侧信道 |
|
||
| F-2.2 | JWT **只携带 `sub`**;角色/权限/`data_scope` **每请求实时解析** | P0 | 支持即时吊销;`ACCESS_TOKEN_TTL_SECONDS=1800` |
|
||
| F-2.3 | 访客令牌**跳过**身份解析 | P0 | 访客只能命中公开面接口 |
|
||
| F-2.4 | 新人引导闸门:客户未完成引导访问 `/api/v1/**`(`onboarding` 除外)→ 403 `ONBOARDING_REQUIRED` | P0 | — |
|
||
| F-2.5 | RBAC:权限码定义源为 `tools/seed_test_rbac.py` 的 `PERMISSIONS` | P0 | 该脚本为 **DELETE 重建**语义,未并入的权限码重建即消失(表现为"接口突然 403 且无报错") |
|
||
| F-2.6 | 三种 `data_scope`:`self` / `own_customers` / `all` | P0 | **零跨客户访问**(重要验收红线) |
|
||
|
||
---
|
||
|
||
### 3.3 模块三:客服 Agent 与知识库(P0)
|
||
|
||
| 编号 | 需求 | 优先级 | 验收要点 |
|
||
|---|---|---|---|
|
||
| F-3.1 | 五类意图识别:`faq` / `product_inquiry` / `policy_explain` / `chitchat` / `transfer_human` | P0 | 五类均需端到端可测 |
|
||
| F-3.2 | 三集合知识路由:FAQ TopK 3、产品 TopK 5、政策 TopK 5 | P0 | 检索层**运行时探测字段名**,不得硬编码 |
|
||
| F-3.3 | 知识只使用"已发布 + 在有效期"的版本 | P0 | 未发布版本不得被召回 |
|
||
| F-3.4 | 低置信**不得硬答**;转人工上下文完整 | P0 | 需 Q4/Q5 提供阈值 |
|
||
| F-3.5 | 知识管理三端点:上传 / 列表 / 删除 | P0 | 上传 201 / 列表 200 / 删除 200;客户访问上传应 403 |
|
||
| F-3.6 | 知识发布流程:上传→草稿/待审→抽取分块→法务审核→批准→向量化→发布生效 | P0 | 512/64 **字符**分块(非 token) |
|
||
| F-3.7 | 客服工单状态机:`pending→assigned→processing→resolved→closed` | P1 | 与风控工单**不得混用** |
|
||
| F-3.8 | 客户画像候选流程:候选→客户确认→管理员复核 | P1 | 候选视图**不含对话原文证据** |
|
||
| F-3.9 | 一期验收:产品咨询 ≥5 题、准确率 ≥80% | P0 | 已达成(5/5) |
|
||
| F-3.10 | 一期验收:政策解释 ≥2 题 | P0 | 已达成(3/3) |
|
||
| F-3.11 | 一期验收:多轮上下文 ≥3 轮 | P0 | 已达成(3 轮) |
|
||
|
||
---
|
||
|
||
### 3.4 模块四:场内基金模拟交易(P0)
|
||
|
||
| 编号 | 需求 | 优先级 | 验收要点 |
|
||
|---|---|---|---|
|
||
| F-4.1 | 20 只场内基金(13 ETF + 7 LOF)产品数据 | P0 | 风险等级 R1–R5;管理费 0.15%–1.20%/年;托管费 0.05%–0.20%/年 |
|
||
| F-4.2 | 账户看板:现金余额、总市值、总资产 | P0 | `today_profit_loss` / `_ratio` **2026-09-14 已实现为真实计算**(不再是占位 0)→ 口径见 **Q22** |
|
||
| F-4.3 | 持仓查询 | P0 | 只读持仓**故意不校验行情时效** |
|
||
| F-4.4 | 下单(买入/卖出),市价全额成交 | P0 | 首版**仅支持 `price_type="market"`**,`limit_price` 恒为 `null` |
|
||
| F-4.5 | 完整校验链(**顺序不可调**) | P0 | 见 5.5 节 |
|
||
| F-4.6 | 交易数量约束:1 手 = 100 份,最低 0.001 元价位 | P0 | `quantity % lot_size == 0` |
|
||
| F-4.7 | 行情时效:`MAX_QUOTE_AGE = 15 分钟` | P0 | 超龄→**所有委托 503**,**无自动刷新**;需 `python tools/sync_market_prices.py` 补刷 |
|
||
| F-4.8 | 撤单 | P0 | 仅 `待风控` 状态可撤;其余→409 `ORDER_NOT_CANCELLABLE` |
|
||
| F-4.9 | 流水与资金流水查询 | P0 | 金额字段**全部为字符串化 Decimal**,非 JSON number |
|
||
| F-4.10 | 成交需满足幂等与事务原子性 | P0 | 订单+流水+持仓+账户同事务 |
|
||
|
||
---
|
||
|
||
### 3.5 模块五:风控(P0)
|
||
|
||
| 编号 | 需求 | 优先级 | 验收要点 |
|
||
|---|---|---|---|
|
||
| F-5.1 | 规则引擎**确定性**扫描 → 产生告警 | P0 | **不得用模型判断替代规则** |
|
||
| F-5.2 | 告警处置:`待处理 → 调查中 → 已排除/已结案` | P0 | 五动作均需 `risk:alert:write` |
|
||
| F-5.3 | 升级为**独立标记**(非状态) | P0 | 返回 `is_escalated`/`escalated_at`/`escalation_reason` |
|
||
| F-5.4 | 风控日报:前 8 项确定性计算,**仅"建议优化方向"可用模型** | P0 | SSE 事件 `start`/`progress`/`replace`/`done` |
|
||
| F-5.5 | 证据查询:8 类来源 `customers/products/transactions/capital_flows/holdings/login_records/alerts/notifications` | P0 | 未知来源→404;须与前端 `EVIDENCE_COLUMNS` 对齐 |
|
||
| F-5.6 | 证据归档(multipart 上传) | P1 | 成功/失败均审计;`try/finally close()` |
|
||
| F-5.7 | 日报邮件发送 | P1 | **唯一曾漏鉴权的风控端点**,现于 Service 层强制 `risk:report:mail`;默认 `dry_run` |
|
||
| F-5.8 | 扫描互斥:MySQL `GET_LOCK('jr_risk_scan_schedule', 0)` | P0 | 锁在**入口层**获取,**不得**放在 `RiskScanService.scan()` 内(否则调度器自锁死) |
|
||
| F-5.9 | 风控专员**不得修改交易/资金/持仓事实** | P0 | 边界红线 |
|
||
| F-5.10 | 列表 `limit` 上限严格:告警 **max 5**;证据/通知 max 10 | P0 | 风控是**唯一**扩展 `meta.total`/`meta.page_size` 的域 |
|
||
|
||
---
|
||
|
||
### 3.6 模块六:投顾(P1)
|
||
|
||
| 编号 | 需求 | 优先级 | 验收要点 |
|
||
|---|---|---|---|
|
||
| F-6.1 | 投资目标管理(AD001–AD007) | P1 | 目标/方案状态机失败返回 **409 但复用 `RUN_NOT_CANCELLABLE` 字面量**(已知文档缺陷,见 Q 补充说明) |
|
||
| F-6.2 | 组合分析(AD008) | P1 | `status ∈ no_positions/valuation_required/ready`;7 类警告码;图上下文**降级不抛错** |
|
||
| F-6.3 | 资产配置(AD009) | P1 | 硬编码 C1–C5 战略比例;常量 `analysis_only: True` |
|
||
| F-6.4 | 产品推荐(AD010/AD011) | P1 | **三重校验**:场内可交易 + 权威适当性 + 合同证据 |
|
||
| F-6.5 | 灰度放量:`ADVISOR_ROLLOUT_ENABLED` + `ADVISOR_ROLLOUT_CUSTOMER_IDS` | P0 | 空白名单 = 全体 fail-closed;拒绝→403 `AGENT_PERMISSION_DENIED` + 审计 `advisor.rollout_denied` |
|
||
| F-6.6 | `admin`/`super_admin` 始终放行 | P0 | — |
|
||
| F-6.7 | **不承诺收益、不绕过适当性** | P0 | 边界红线 |
|
||
|
||
---
|
||
|
||
### 3.7 模块七:场外基金运营(P1)
|
||
|
||
| 编号 | 需求 | 优先级 | 验收要点 |
|
||
|---|---|---|---|
|
||
| F-7.1 | 邮件增量收取:IMAP UID 增量扫描 → MIME 解析 → 附件落盘 | P1 | 原文 EML + 附件哈希留存 |
|
||
| F-7.2 | 附件识别:OCR → 结构化字段抽取 | P1 | 申购 8 个必填字段;赎回 7 个必填字段 |
|
||
| F-7.3 | 单据字段:基金代码/名称/账户标识/申请编号/申请日期/代销机构/申购金额/金额单位/赎回份额 | P1 | 金额支持"万元"换算、币种前缀剥离 |
|
||
| F-7.4 | NL2SQL 只读查数(最新净值、最新总份额、申请前持有份额、可用份额) | P1 | 查询失败→`无法判断`,**不得猜测** |
|
||
| F-7.5 | 确定性规则核对 | P0 | 见 5.7 节四规则 |
|
||
| F-7.6 | **运营人员最终确认**(确认正常/确认异常) | P0 | 识别未完成时→422 不得确认 |
|
||
| F-7.7 | 通知四类型:`risk`/`settlement`/`mail_return`/`normal_return`/`exception_return` | P1 | 校验规则严格:风控只发"确认异常"、清算只发"确认正常" |
|
||
| F-7.8 | 发送前**必须**完成运营确认(`operator_confirmed`) | P0 | 未确认→422"邮件发送前必须完成运营确认" |
|
||
| F-7.9 | 清算统计**只统计**"确认正常 + 回复发送成功"的单据 | P1 | 口径见 5.7 |
|
||
| F-7.10 | 字段人工修正(OCR 字段 + NL2SQL 字段) | P1 | 修正值优先,原值落库不变;有缺失/低置信字段时默认进"编辑态" |
|
||
| F-7.11 | 附件在线预览(PDF/图片)与下载 | P2 | 仅白名单 MIME 内联;其余强制下载 + `X-Content-Type-Options: nosniff` |
|
||
| F-7.12 | **AML 与开放期规则不在本次范围**(边界明确) | — | 见 Q8 |
|
||
|
||
---
|
||
|
||
### 3.8 模块八:基金推介材料(P2)
|
||
|
||
| 编号 | 需求 | 优先级 | 验收要点 |
|
||
|---|---|---|---|
|
||
| F-8.1 | 任务创建与结构化资料维护 | P2 | `X001..X008` |
|
||
| F-8.2 | 附件类型与限额:经理照片(.jpg/.jpeg/.png/.webp)、业绩数据(.csv/.xlsx/.xlsm)、来源佐证(30MB, .pdf/.docx/.xlsx/.csv)、模板(50MB, .pptx) | P2 | sha256 去重→`"duplicate": true` |
|
||
| F-8.3 | 生成:4 种 `code` 结果(0 / 422 缺资料 / 422 输入未过合规 / 422 生成未过合规 / 503) | P2 | HTTP 恒 200,业务码在 body |
|
||
| F-8.4 | 复核:要求 `version.status == "pending_review"` 且无阻断性合规发现 | P2 | — |
|
||
| F-8.5 | 投递:要求 `version.status == "approved"` 且 `delivery_channel == "internal_record"` | P2 | **首版只支持内部发送记录**,未接入外部投顾端 |
|
||
| F-8.6 | PPTX 真实生成(10 段式大纲) | P2 | 已达成 |
|
||
| F-8.7 | PDF 转换 | P2 | **依赖部署环境 LibreOffice/soffice**,本机未验证(见 Q15) |
|
||
|
||
---
|
||
|
||
### 3.9 模块九:前端与运维(P0)
|
||
|
||
| 编号 | 需求 | 优先级 | 验收要点 |
|
||
|---|---|---|---|
|
||
| F-9.1 | 四套页面:`guest/`、`customer/`、`employee-console/`、`employee-risk/`(+ `employee-advisor`) | P0 | 挂载于 `/portal/`;`/` 与 `/portal/` 均 307 跳 `/portal/guest/home/` |
|
||
| F-9.2 | 端点表集中在 `common/api-client.js` | P0 | 改接口调用只改这一份 |
|
||
| F-9.3 | 健康检查:`/internal/health/live`、`/internal/health/ready`、`/internal/metrics` | P0 | **无鉴权、无信封**;ready 真实探测 MySQL/Redis/Milvus,超时 2s;不 ready→503 |
|
||
| F-9.4 | 一键启动:双击 `启动金融Agent平台.bat` | P0 | 找解释器→检查中间件→刷新行情→起 API+Worker→等应答→开浏览器;重复双击安全 |
|
||
| F-9.5 | 演示数据一键准备:`tools/seed_demo_data.py` | P0 | 10 步有依赖顺序;**第 2 步非幂等**(重跑会重置密码) |
|
||
| F-9.6 | 自检工具两条线互补 | P0 | `e2e_smoke_test.py`(业务 6 线 40 项)+ `portal_api_check.py`(契约 41 项) |
|
||
|
||
---
|
||
|
||
## 4. 非功能需求
|
||
|
||
### 4.1 性能需求
|
||
|
||
| 编号 | 指标 | 目标值 | 说明 / 现状依据 |
|
||
|---|---|---|---|
|
||
| N-1.1 | 访客一问端到端响应 | **≤ 5 秒** | 本机实测(Worker 在跑 + `deepseek-flash`)**4.1–4.8 秒**;其中受理环节仅占很小部分,主要耗时在模型生成 |
|
||
| N-1.2 | Agent 受理接口(`POST /agent-runs`) | **≤ 200ms**(同步返回 202) | 三段式设计目的即为把长耗时甩给 Worker |
|
||
| N-1.3 | 异步运行结果可见时延 | **≤ 8 秒**(P95) | 轮询/SSE 均可;需 Q20 补充 SLA |
|
||
| N-1.4 | 场内下单接口 | **≤ 500ms** | 纯数据库事务 + 一次行情读取,无模型调用 |
|
||
| N-1.5 | SSE 首字节 | **≤ 1 秒** | 日报流式产出 |
|
||
| N-1.6 | 健康检查就绪判定 | **≤ 3 秒** | Milvus 探测超时 2s |
|
||
| N-1.7 | 列表接口分页 | 全部走游标/限额 | 风控告警 `limit` **最大 5**,避免大结果集 |
|
||
| N-1.8 | 并发承载 | **需业务确认**(见 Q2) | 演示级无需压测;生产级需给出目标 QPS |
|
||
| N-1.9 | 行情数据量 | ~120 个交易日日K | `fin_nav_history` 同步量级 |
|
||
|
||
### 4.2 安全需求
|
||
|
||
| 编号 | 需求 | 优先级 | 说明 |
|
||
|---|---|---|---|
|
||
| N-2.1 | **鉴权先于一切**:鉴权 → 限流 → 权限 → 游标校验 → 数据访问 | P0 | 顺序错乱会产生"存在性预言机"(406 vs 404 泄露资源是否存在) |
|
||
| N-2.2 | **零跨客户访问**:`data_scope` 三值严格生效 | P0 | 验收红线 |
|
||
| N-2.3 | **零适当性违规**:无匹配风险等级不得成交 | P0 | 校验链第 2 位 |
|
||
| N-2.4 | **零敏感信息泄露**:`audit:read-sensitive` 脱敏为 `{"redacted": true}` | P0 | — |
|
||
| N-2.5 | JWT 密钥管理与轮换 | P0 | `docs/26` 有设计;密钥**不得**进仓库 |
|
||
| N-2.6 | 登录防暴力破解:`enforce_login_rate_limit` | P0 | — |
|
||
| N-2.7 | 登录不区分失败原因 + dummy hash 抗时序侧信道 | P0 | — |
|
||
| N-2.8 | 幂等防重:同键并发只产生一条副作用 | P0 | — |
|
||
| N-2.9 | 乐观锁防并发覆盖 | P0 | `If-Match`;冲突 409 且 `retryable=true` |
|
||
| N-2.10 | 附件安全:白名单 MIME/后缀;非白名单强制下载 + `nosniff` | P0 | 防 XSS |
|
||
| N-2.11 | **不得绕过统一执行骨架**(含合规、审计、事件) | P0 | `AGENTS.md` 规则 7 |
|
||
| N-2.12 | AI 输出**不得**作为最终业务决策 | P0 | 场外确认、风控处置、投顾下单均需人在环中 |
|
||
| N-2.13 | 工具可用范围 = 代码上限 ∩ 当前 active 发布白名单;缺配置**失败关闭** | P0 | fail-closed |
|
||
| N-2.14 | 生产环境**不得**启用 SMTP 真实发信而未做鉴权 | P0 | 曾有风控邮件端点漏鉴权,已修(F-5.7) |
|
||
|
||
### 4.3 兼容性需求
|
||
|
||
| 编号 | 需求 | 说明 |
|
||
|---|---|---|
|
||
| N-3.1 | **数据库兼容**:MySQL 8.0,`utf8mb4`,默认 schema `aaa` | 主键 BIGINT UNSIGNED;金额 `DECIMAL(18,2)`,价格 `DECIMAL(18,6)`,场内数量 `DECIMAL(18,4)` |
|
||
| N-3.2 | **基线不可变**:`docs/00` 为不可变业务基线 | 只允许新增表/新增字段;**禁止**重命名、删除、复用已有字段,禁止改类型/可空性/业务含义 |
|
||
| N-3.3 | **前后端契约兼容**:信封例外仅三处 | ① `V001` 裸 body;② `K003` 的 `data` 包 `{items,count}`;③ **场外运营全域沿用旧式 `{code,message,data}`(成功 = `code===0`)** |
|
||
| N-3.4 | **推介材料特例**:`422`/`503` 放 body `code`,HTTP 保持 **200** | 前端必须按 body 判断,不能只看 HTTP 状态 |
|
||
| N-3.5 | **Milvus schema 环境差异**:检索层运行时探测字段名 | 本机 `knowledge_id`/`snippet`;架构师环境 `doc_id`/`content`/`visibility`/`chapter`。**任何地方不得硬编码字段名** |
|
||
| N-3.6 | **`config_release` 是环境数据,不随代码合并** | "白名单已发布"必须带环境限定;换环境需重发 |
|
||
| N-3.7 | 浏览器兼容:现代 Chromium / Edge / Safari(ESM 模块) | 注意 `employee-console/workspace/workspace.js` 存在**语法错误**(见第 6 章风险项) |
|
||
| N-3.8 | 解释器与依赖:Python **≥ 3.11**(使用 `datetime.UTC`)+ `fastapi/sqlalchemy/asyncmy/pydantic` | 版本门槛必须**实测 import 依赖**,不能只看 `--version` |
|
||
| N-3.9 | 脚本编码:`start.ps1` 必须 **UTF-8 with BOM**;启动器 bat 必须 **GBK + CRLF + 无 BOM** | 缺 BOM 会导致 PowerShell 5.1 按 GBK 解析报语法错 |
|
||
| N-3.10 | 场外/推广/投顾 38 张表**不进** `docs/00` 基线 | 依据 `AGENTS.md` 规则 8(场外独立);投顾 21 张登记文档待补(Q14) |
|
||
|
||
### 4.4 可靠性与可维护性
|
||
|
||
| 编号 | 需求 | 说明 |
|
||
|---|---|---|
|
||
| N-4.1 | 三段式异步 + 常驻 Worker | **无 Worker 时 `agent_run` 停在 `queued`**,前端只显示"客服响应超时"——排查第一步是查 `agent_run` 最新行是否 `queued` |
|
||
| N-4.2 | 事件可靠投递(Outbox) | 幂等键并发只产生一条领域事件 |
|
||
| N-4.3 | 降级不抛错:外部依赖(Neo4j/NL2SQL)失败时**降级标记**而非异常 | 如 `neo4j_unavailable:...` |
|
||
| N-4.4 | 配置文件不可变版本 + 回滚演练记录 | `docs/31` 要求保留回滚演练记录 |
|
||
| N-4.5 | 观测:`trace_id` 可追溯 + `/internal/metrics` | metrics 当前为 Prometheus 桩 `jr_agent_up 1` |
|
||
| N-4.6 | Redis 丢失后,澄清轮次可从 MySQL 恢复 | 验收红线 |
|
||
|
||
---
|
||
|
||
## 5. 关键业务流程说明
|
||
|
||
### 5.1 通用 Agent 七步执行骨架(P0 核心)
|
||
|
||
所有业务 Agent 必须走同一骨架,任何一步都不可省略:
|
||
|
||
```
|
||
① 输入校验 → 结构校验(Pydantic)+ 业务前置校验;失败→422 AGENT_INPUT_INVALID
|
||
② 记忆召回 → 短期(Redis) → 情节(中期) → 长期;权威性:MySQL 事实 > 用户自述 > AI 推断
|
||
③ 意图识别 → 配置化意图分类(config_release 中的意图配置)
|
||
④ 核心业务 → 检索 / 计算 / 查询(工具执行,受白名单约束)
|
||
⑤ 生成与合规 → 模型生成 → 合规校验(不通过则不得输出)
|
||
⑥ 数据落库 → 与幂等记录同事务写入
|
||
⑦ 事件广播 → Outbox → 可靠投递
|
||
```
|
||
|
||
**关键约束**:④ 步的工具可用范围 = **代码上限 ∩ 当前 active `config_release` 的发布白名单**,缺发布配置则**失败关闭**(fail-closed)。
|
||
|
||
### 5.2 客服五意图与知识路由(P0)
|
||
|
||
```
|
||
用户提问
|
||
↓
|
||
意图识别 ──┬─ faq → FAQ 集合 TopK 3
|
||
├─ product_inquiry → 产品集合 TopK 5
|
||
├─ policy_explain → 政策集合 TopK 5
|
||
├─ chitchat → 闲聊话术模板(customer_service_chitchat)
|
||
└─ transfer_human → 转人工,上下文完整带入工单
|
||
↓
|
||
置信度判定 ─┬─ 高 → 组织答案输出
|
||
└─ 低 → 【不得硬答】→ 标记 [无法确认] + 给人工热线 15936583816
|
||
↓
|
||
工单状态机:pending → assigned → processing → resolved → closed
|
||
```
|
||
|
||
**已知口径**:分块为 **512/64 字符**(非 token,不引入分词器依赖);低置信热线为**真实号码** `15936583816`(非占位符)。
|
||
**待确认**:混合检索加权公式与分数分段(Q4)、转人工阈值(Q5)。
|
||
|
||
### 5.3 知识发布流程(P0)
|
||
|
||
```
|
||
上传文档 → 草稿/待审 → 文本抽取与分块 → 法务审核 → 批准
|
||
→ 向量化(Embedding)→ 发布生效(published + active)
|
||
```
|
||
**约束**:检索时**只使用已发布且在有效期内的版本**;发布是**不可变版本**,可回滚;一次请求内配置不得漂移。
|
||
|
||
### 5.4 投顾业务流程(9 步,P1)
|
||
|
||
```
|
||
① 客户填投资目标
|
||
② 客户完成风险测评(权威事实,Agent/客服不可修改)
|
||
③ 拉取客户画像(持仓/交易/偏好)
|
||
④ 组合分析(AD008)—— Neo4j 关系推理,失败降级
|
||
⑤ 资产配置(AD009)—— C1–C5 战略比例,analysis_only
|
||
⑥ 候选产品筛选(场内可交易 + 适当性 + 合同证据 三重校验)
|
||
⑦ 生成推荐方案(AD010/AD011)—— 含证据卡、排除清单、披露信息
|
||
⑧ 投顾复核 + 风险解释
|
||
⑨ 客户确认方案
|
||
✗ 全流程【不生成交易指令】,如需交易跳转至场内模拟交易
|
||
```
|
||
|
||
**灰度闸门**:`/api/v1/advisor` 除限流外,额外经 `enforce_advisor_rollout`;空白名单 = 全体客户 **fail-closed**;`admin`/`super_admin` 始终放行;拒绝→403 + 审计 `advisor.rollout_denied`。
|
||
|
||
### 5.5 场内模拟交易流程与校验链(P0 核心 ⭐)
|
||
|
||
```
|
||
客户提交委托
|
||
↓
|
||
【校验链——顺序不可调,任何一步失败即终止】
|
||
① 产品可交易? 否 → 422 PRODUCT_NOT_TRADABLE
|
||
② 适当性匹配? 否 → 422 SUITABILITY_MISMATCH
|
||
③ 行情未超 15 分钟? 否 → 503 FUND_QUOTE_UNAVAILABLE ← 全委托级拦截
|
||
④ 账户存在且已开户? 否 → 404 ACCOUNT_NOT_FOUND
|
||
⑤ quantity > 0?
|
||
⑥ quantity % lot_size == 0?
|
||
⑦ 买入:available_cash >= gross + fee?
|
||
否 → 422 INSUFFICIENT_FUNDS
|
||
⑧ 持仓比例上限? 超 → 422 HOLDING_RATIO_EXCEEDED
|
||
⑨ 卖出:available_quantity >= quantity?
|
||
否 → 422 INSUFFICIENT_HOLDING
|
||
↓
|
||
【成交】市价全额成交(首版仅 market)
|
||
买入 net = gross + fee
|
||
卖出 net = gross - fee
|
||
↓
|
||
【同事务写入】订单(SO…) + 成交(TX…) + 持仓 + 账户 + 资金流水(L…)
|
||
↓
|
||
【撤单】仅 status == "待风控" 可撤,其余 → 409 ORDER_NOT_CANCELLABLE
|
||
```
|
||
|
||
**重要注意事项**:
|
||
|
||
- 行情有效期仅 **15 分钟**(`MAX_QUOTE_AGE`),超时后**所有委托一律 503 且无自动刷新**——这是演示最容易翻的一环。补刷命令:`python tools/sync_market_prices.py`(立即生效,**无需重启服务**)。
|
||
- 只读持仓查询**故意不校验时效**(`enforce_freshness=False`)。
|
||
- 账户看板的 `today_profit_loss` / `today_profit_loss_ratio` **已是真实计算**(2026-09-14 起):
|
||
语义是「**最近一个交易日**相对前一交易日的盈亏」,**不是盘中实时**;当天行情/净值同步之前
|
||
它会与"昨日"一致。别把它当 bug,见 **Q22**。
|
||
- 所有金额字段均为**字符串化 Decimal**,前端不得按 number 处理。
|
||
- 客户必须状态为 `已开户` 才能访问账户接口(`employee` 映射为 `closed`)。
|
||
|
||
### 5.6 风控流程(P0)
|
||
|
||
```
|
||
【扫描】定时/手动 → MySQL 互斥锁 GET_LOCK('jr_risk_scan_schedule', 0)
|
||
↓(锁在入口层获取,绝不在 RiskScanService.scan() 内——否则调度器自锁死)
|
||
规则引擎确定性扫描 → 产生告警(含等级:高/中/低)
|
||
↓ 高风险 → 创建通知(通知失败不回归已建告警,返回 notification_failure)
|
||
【处置】风控专员:待处理 → 调查中 → 已排除 / 已结案
|
||
· acknowledge 需 status == 待处理 且 ack_at is None
|
||
· investigate 需已确认且 status == 待处理
|
||
· exclude/resolve 需已确认且未结案
|
||
· escalate 独立标记(非状态),可随时升级
|
||
【日报】SSE 流式:start → progress → replace → done
|
||
前 8 类指标确定性计算;仅"建议优化方向"可用模型
|
||
【边界】风控专员不得修改交易、资金、持仓事实
|
||
```
|
||
|
||
**列表限额**:`/risk/alerts` **max 5**(默认 5);`/risk/evidence/{source}` 与 `/risk/notifications` max 10。
|
||
**幂等范围**:风控写操作使用**实际路径(含 `alert_no`)**作为 scope,绝不用模板路径。
|
||
|
||
### 5.7 场外基金运营流程(P1)
|
||
|
||
```
|
||
【收取】IMAP UID 增量扫描
|
||
→ MIME 解析 + 附件拉取 → 发件人/鉴权校验
|
||
→ 原始 EML + 附件哈希落盘(去重)
|
||
【识别】OCR(附件)→ DeepSeek 分类与字段抽取
|
||
申购必填 8 字段:基金代码/基金名称/账户标识/申请编号/申请日期/代销机构/申购金额/金额单位
|
||
赎回必填 7 字段:基金代码/基金名称/账户标识/申请编号/申请日期/代销机构/赎回份额
|
||
缺失或低置信 → 该附件默认进入【编辑态】,等人工修正
|
||
【查数】NL2SQL 只读查询(三阶段:query → calc → verify)
|
||
申购依赖:最新净值 / 基金最新总份额 / 申请前持有份额
|
||
赎回依赖:基金最新总份额 / 当前最新可用份额
|
||
查询失败 → 规则结果 = 无法判断(【不得猜测】)
|
||
【核对】确定性规则引擎(不读库、不调模型)
|
||
① 申购最低金额 标准化金额 ≤ 1 元 → 异常
|
||
② 申购后单一投资者持有比例 (申请前持有 + 本次份额) / 最新总份额 > 20% → 异常
|
||
③ 申购单笔份额上限 本次份额 > 最新总份额 × 10% → 异常
|
||
④ 赎回巨额比例 赎回份额 / 最新总份额 > 20% → 异常
|
||
⑤ 账户可用份额 赎回份额 > 可用份额 → 异常
|
||
每条规则输出:规则码/规则名/结果/单据值/库值/计算过程(实际值·规则值·比较)
|
||
【人工】运营人员确认 → 确认正常 / 确认异常
|
||
✗ 识别或核对未完成(recognition_exception / recognition_review /
|
||
recognition_retrying / query_failed)→ 422 不得确认
|
||
【通知】类型 → 接收方与前置条件
|
||
· risk → 风控接收人 ;仅允许"确认异常"
|
||
· settlement → 清算接收人 ;仅允许"确认正常"
|
||
· normal_return → 邮件返回接收人;仅允许"确认正常"
|
||
· exception_return→ 邮件返回接收人;仅允许"确认异常"
|
||
· mail_return → 邮件返回接收人;必须先完成人工确认
|
||
✗ 发送前必须 operator_confirmed = true,否则 422
|
||
【清算】统计口径:仅纳入「operator_decision == 确认正常」
|
||
且「notification_type ∈ (mail_return, normal_return) 且 status == 发送成功」的单据
|
||
按基金代码聚合
|
||
```
|
||
|
||
**明确边界(来自流程图文档,不可自行扩张)**:
|
||
|
||
- 本次仅实现后端 / Agent / 数据 / 接口 / 异步 / 审计;
|
||
- **AML 与开放期规则不在本次实现范围**;
|
||
- **邮件回复、风控通知、清算通知三条路径相互独立,无自动联动**;
|
||
- 邮件收取超时或断连时,按 IDLE 超时做 UID 补偿扫描;
|
||
- 场外流程**独立**,不得写入场内交易表。
|
||
|
||
### 5.8 记忆分层与召回权威性(P0)
|
||
|
||
```
|
||
短期记忆(Redis,短 TTL)
|
||
↓ 沉淀
|
||
情节记忆(中期,episodes)
|
||
↓ 提升(promotion)
|
||
长期记忆(含证据/冲突/提升/同步/删除链路)
|
||
↓
|
||
召回权威性(由高到低):MySQL 权威业务事实 > 用户自述 > AI 推断
|
||
约束:低版本记忆【不得覆盖】高版本;澄清轮次在 Redis 丢失后可从 MySQL 恢复
|
||
```
|
||
|
||
### 5.9 异常处理矩阵(摘要)
|
||
|
||
`docs/03` §13 列出 15 行异常场景。核心原则:
|
||
|
||
| 异常类型 | 处理原则 |
|
||
|---|---|
|
||
| 模型超时/失败 | 降级答复,**不得**编造业务事实 |
|
||
| 外部依赖不可用(Neo4j/NL2SQL/Milvus) | 降级标记返回,**不抛错**给用户 |
|
||
| 行情过期 | 一律 503,明确提示,**不静默用旧价** |
|
||
| 识别失败/低置信 | 进入人工修正态,**不猜测** |
|
||
| 通知发送失败 | 记录失败原因与重试次数,**不回归**已产生的业务数据 |
|
||
| 并发冲突 | 409 + `retryable=true`,客户端可重试 |
|
||
| 幂等重复请求 | 返回首次结果,**不产生重复副作用** |
|
||
|
||
---
|
||
|
||
## 6. 验收标准
|
||
|
||
### 6.1 一期验收标准(教师给定 7 条 —— 全部已达成)
|
||
|
||
| # | 验收标准(原文口径) | 结果 | 证据 |
|
||
|---|---|---|---|
|
||
| A1 | FastAPI 能启动,`/docs` Swagger 可访问 | ✅ | `docs/验收与审计/phase1-acceptance-report.md` 真机证据 |
|
||
| A2 | 建表成功(`SHOW TABLES` 返回 10 张) | ✅ | 实际库中为 52 张(超出标准) |
|
||
| A3 | FAQ 问答对导入 Milvus 且检索正确("基金申购后多久确认") | ✅ | FAQ 106 行,命中分 **0.7837** |
|
||
| A4 | 客服 Agent 回答产品咨询 ≥5 题,准确率 ≥80% | ✅ | **5/5** |
|
||
| A5 | 能处理政策解释类问题 ≥2 题 | ✅ | **3/3** |
|
||
| A6 | 多轮上下文保持 ≥3 轮 | ✅ | 3 轮 |
|
||
| A7 | 知识管理接口:上传/列表/删除可用 | ✅ | 上传 201 / 列表 200 / 删除 200;客户访问上传 403 |
|
||
|
||
**两项已记录的取舍(有意为之,非缺陷)**:
|
||
|
||
1. 分块按 **512/64 字符**,非 token(避免引入分词器依赖);
|
||
2. 低置信兜底热线为**真实号码** `15936583816`,非占位符 `400-XXX-XXXX`。
|
||
|
||
### 6.2 平台验收标准(18 条红线 —— 来自 `docs/03` §15)
|
||
|
||
| # | 验收点 |
|
||
|---|---|
|
||
| B1 | Agent 均由工厂创建,权限隔离正确 |
|
||
| B2 | 七步骨架不可被旁路 |
|
||
| B3 | 客服五意图端到端可测 |
|
||
| B4 | 低置信不硬答;转人工上下文完整 |
|
||
| B5 | 知识只使用"已发布 + 有效"版本 |
|
||
| B6 | 客服工单与风控工单**不得混用** |
|
||
| B7 | 场内模拟成交满足幂等与事务原子性 |
|
||
| B8 | 规则引擎与风控 Agent 边界正确(模型不替代规则) |
|
||
| B9 | 权威业务事实不被记忆覆盖 |
|
||
| B10 | **零跨客户访问 / 零适当性违规 / 零敏感信息泄露** |
|
||
| B11 | 每条请求可凭 `trace_id` 追溯 |
|
||
| B12 | 同幂等键并发只产生一条消息/工单/领域事件 |
|
||
| B13 | Redis 丢失后,澄清轮次可从 MySQL 恢复 |
|
||
| B14 | 配置按不可变版本发布/评审/生效/回滚;一次请求内无漂移 |
|
||
| B15 | 只使用备份端点;模型与提示词版本可审计 |
|
||
| B16 | 长期记忆具备证据/冲突/提升/同步/删除测试;低版本不覆盖高版本 |
|
||
| B17 | Neo4j 受视图/深度/数量/超时/data-scope 约束;失败不编造关系 |
|
||
| B18 | **所有迁移中,原有表名与既有字段定义保持不变** |
|
||
|
||
> B18 是本项目最硬的一条:修改数据库文档或迁移前,必须对比基线并**证明**没有改变任何已有表名和字段定义。核验命令:`python tools/audit_schema.py`。
|
||
|
||
### 6.3 交付前自检(两条线互补,**都跑一遍**)
|
||
|
||
| 工具 | 覆盖 | 命令 | 说明 |
|
||
|---|---|---|---|
|
||
| 业务链路冒烟 | 登录→下单→成交、风控扫描→处置闭环、客服问答,共 6 条线 40 项 | `python tools/e2e_smoke_test.py` | `--read-only` 不动数据 |
|
||
| 接口契约体检 | 按前端方式调每个端点,核对状态码/信封形状/字段,共 41 项 | `python tools/portal_api_check.py` | `--write` 加测写操作;`--dangerous` 再加测改生效配置的操作 |
|
||
| 数据库基线校验 | 表/字段与 `docs/00` 基线对比 | `python tools/audit_schema.py` | — |
|
||
| RBAC 种子一致性 | 权限码两套映射一致性 | `python tools/check_rbac_seed_consistency.py` | 曾有 `advisor` 在种子重建后拿到语义错误权限 |
|
||
|
||
> ⚠️ 跑验收脚本前**必须先停掉常驻 Worker**,否则会抢队列(见 `docs/20`)。
|
||
|
||
### 6.4 本期已知风险与未闭环项(验收时应逐条确认状态)
|
||
|
||
| # | 风险/缺口 | 影响 | 当前状态 |
|
||
|---|---|---|---|
|
||
| R1 | `app/static/portal/employee-console/workspace/workspace.js` 曾存在**真实语法错误**(`submitRule`,约 L500–539,缺一个 `}`) | 管理端工作台该模块无法加载 | ✅ **已修复**(2026-09-14 提交 `4b7ee13`「修复管理员工作台函数缺少闭合大括号」)。复验:`node --experimental-vm-modules` + `vm.SourceTextModule` 遍历 `app/static/portal`,**44 个模块全部通过**。⚠️ 注意 `node --check` 会**假通过**,必须用真实 ESM 解析 |
|
||
| R2 | ~~`today_profit_loss` 为占位 0~~ | 客户看板"今日盈亏"无意义 | ✅ **已修复(2026-09-14)**:改为真实计算(行情优先、净值兜底,当日买卖按成交价计入),见 **Q22**;复算工具 `tools/check_today_profit_loss.py` |
|
||
| R3 | 投顾目标/方案状态机失败 **409 复用 `RUN_NOT_CANCELLABLE`** 字面量 | 错误码语义不准 | ⚠️ 已知文档缺陷 |
|
||
| R4 | `ResourceNotFoundError` 直接抛出得到 `SESSION_NOT_FOUND`,集成测试期望 `RESOURCE_NOT_FOUND` | 错误码不一致 | ⚠️ 推介材料模块遗留(来自 `product_promotion_to_do_list.md`) |
|
||
| R5 | PDF 转换依赖部署环境 LibreOffice/soffice | 推介材料 PDF 导出不可用 | ⚠️ 本机未验证(Q15) |
|
||
| R6 | 仅支持 `price_type="market"` | 无限价单 | ✅ 设计如此(首版范围) |
|
||
| R7 | 投顾 21 张表登记文档缺失 | 数据字典不完整 | ⚠️ 待补(Q14) |
|
||
| R8 | 场外缺真实样例单据 | OCR/规则无法端到端验收 | ⚠️ 需业务提供(Q13) |
|
||
| R9 | 无充值提现/银行流水匹配通道 | 依赖真实资金流的 AML 规则**不可做** | ✅ 已在基线中明示"不得伪造"(Q8) |
|
||
| R10 | `config_release` 是环境数据不随代码合并 | 换环境需重发白名单 | ✅ 已知,需环境限定声明 |
|
||
| R11 | `RUN_CANCELLED` **不是** HTTP 错误类 | 不要当错误码处理 | ✅ 已澄清(它是 `request_idempotency` 的状态标记) |
|
||
| R12 | `/internal/**` 无鉴权(含真实 Milvus 探测) | 生产部署需网关层限制访问 | ⚠️ 上线前需网络隔离 |
|
||
|
||
---
|
||
|
||
## 7. 附录
|
||
|
||
### 7.1 端点规模
|
||
|
||
22 个路由模块,端点数量约 **140** 个(表格驱动的路由为循环注册,故为估算值)。精确核对命令:
|
||
|
||
```bash
|
||
python -c "from app.main import app; print(len([r for r in app.routes if hasattr(r,'methods')]))"
|
||
```
|
||
|
||
> 注:本工作区无 `.venv`(被 `.gitignore` 忽略,未随仓库分发),受管解释器缺项目依赖,故该命令**未能本地执行**。请在有 `.venv` 的环境执行:`.\.venv\Scripts\python.exe -c "..."`。
|
||
|
||
### 7.2 环境与命令口径
|
||
|
||
| 项 | 值 |
|
||
|---|---|
|
||
| 本机解释器 | `.\.venv\Scripts\python.exe` |
|
||
| 架构师环境 | `D:\conda\envs\jr_py313\python.exe` |
|
||
| 两者关系 | **等价**,各用本机可用的那个(`.venv` 不进仓库,不存在统一问题) |
|
||
| 启动 API | `python -m uvicorn app.main:app --port 8000`(模块级变量是 **`app`**,不是 `application`) |
|
||
| 启动 Worker | `python -m app.worker`(**必需**,否则 Agent 永远 queued) |
|
||
| 一键启动 | 双击 `启动金融Agent平台.bat` 或 `powershell -ExecutionPolicy Bypass -File start.ps1` |
|
||
| 刷新行情 | `python tools/sync_market_prices.py` |
|
||
| 演示数据 | `python tools/seed_demo_data.py`(10 步,第 2 步非幂等) |
|
||
| 前端入口 | `/portal/`(访问 `/` 会 307 跳到访客首页) |
|
||
|
||
### 7.3 数据表规模
|
||
|
||
| 域 | 表数 | 是否进 `docs/00` 基线 |
|
||
|---|---|---|
|
||
| 场内(含客服、风控相关) | **51** | ✅ 是 |
|
||
| 场外 `offsite_*` | 10 | ❌ 否(规则 8) — 登记于 `docs/28` |
|
||
| 推广 `promotion_*` | 7 | ❌ 否 — 登记于 `docs/28` |
|
||
| 投顾 `advisor_*` | 21 | ❌ 否 — **登记文档待补** |
|
||
| **合计(业务表)** | **89** | 含 `alembic_version` 则为 90 |
|
||
|
||
### 7.4 已注册业务 Agent(7 个)
|
||
|
||
`FundQueryDemoAgent`、`CustomerServiceAgent`、`RiskAgent`、`PlatformProbeAgent`、`AdvisorAgent`、`OffsiteFundAgent`、`PromotionMaterialAgent`
|
||
|
||
### 7.5 已注册公共只读工具
|
||
|
||
`search_knowledge`(客服知识检索)、`check_suitability`(适当性校验)、`query_customer_profile`(画像)、`query_fund_quote`(行情)。
|
||
`query_knowledge` 是 `search_knowledge` 的**别名**(同一 handler,为兼容一期发布配置与旧客户端保留)。
|
||
其余业务线工具(风控、投顾、NL2SQL)按各自 Agent 白名单注册,全部在 `bootstrap.py` 的 `get_agent_factory()` 中。
|
||
|
||
### 7.6 本文档与既有文档的关系
|
||
|
||
| 本文档章节 | 权威来源 |
|
||
|---|---|
|
||
| 第 0 章 待确认问题 | 本文档新增(汇总 `docs/22`/`docs/10`/`docs/28`/`product_promotion_to_do_list.md` 的未定义项) |
|
||
| 第 1 章 背景目标 | `docs/01`、`docs/03`、`AGENTS.md` |
|
||
| 第 2 章 角色场景 | `docs/03` §2、`tools/seed_test_rbac.py`、`docs/44` |
|
||
| 第 3 章 功能需求 | `docs/05` 接口文档 + `app/service/*` 实际实现 |
|
||
| 第 4 章 非功能需求 | `docs/00` §3、`docs/26`、`docs/07`、`docs/08`、实测数据 |
|
||
| 第 5 章 业务流程 | `docs/03` §5–§13、`docs/场外申购赎回工作流程图.md`、`app/service/offsite_fund_rules.py` |
|
||
| 第 6 章 验收标准 | `docs/验收与审计/phase1-*`、`docs/03` §15、`docs/44` |
|
||
|
||
> **本文档不替代** `docs/00`(不可变业务基线)与 `docs/05`(接口唯一权威)。若三者出现冲突,以 `docs/00` / `docs/05` 为准,并回头修订本文件。
|
||
|