Files
group_fqcd_jr/docs/演示用/软件需求文档-2026-09-14.md
T
lzf_0626 9dd2802d67 客户看板「今日盈亏」不再是硬编码 0:按行情/净值基准真实计算(含当日买卖)
- 口径:今日盈亏 = 今日市值 − 昨日持仓市值 − 今日买入金额 + 今日卖出金额(不含费用,与「持有盈亏」同口径)
- 昨日持仓数量由当日成交反推,不需要新表新字段
- 基准优先场内行情最近两个交易日收盘价(与同页 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
2026-09-15 00:03:23 +08:00

713 lines
54 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.
# 软件需求文档(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` 为准,并回头修订本文件。