feat(demo): 演示数据一键准备、启动脚本与演示流程文档

交付"别人能自己把系统跑起来做演示"所需的四件东西:

- tools/seed_demo_data.py      演示数据一键准备:10 步按依赖排序
  (此前散在 10 个脚本里,没人知道该跑哪些、按什么顺序跑,且知识库那步
  根本没有脚本、靠手工调接口)
- tools/seed_knowledge_demo.py 知识库演示素材灌入,幂等(先删同 source_file 再灌)
- start.ps1                    启动 API + Worker,含依赖检查与**自动刷新行情**
- docs/44-演示流程.md           8 个主线场景的照读流程 + 排障表 + 账号/命令速查

两条硬约束同时写进了脚本和文档:

1. **Worker 必须常驻**:没有它客服对话一直停在 queued(前端只显示"超时")、
   新知识不进 Milvus 且**没有任何报错**。
2. **行情有效期仅 15 分钟**(`trade_service.MAX_QUOTE_AGE`):超时后所有委托
   直接 503「行情已过期」,而系统**没有自动刷新机制**。故 start.ps1 启动时刷一次,
   文档另给"演示中途 503 时补刷、无需重启服务"的处置方法(已实测)。

实测证据:
- start.ps1 在 8099 完整启动,`/docs` 与 `/portal/` 均 HTTP 200(测试进程已清理)
- `tools/e2e_smoke_test.py` → 40/40 通过
- `tools/seed_demo_data.py --dry-run` → 10 步全部正常列出
- 文档中的下单命令实跑:510300 成交价 4.579、金额 457.90、手续费 0.05
This commit is contained in:
2026-09-13 21:56:27 +08:00
parent f37fd922a1
commit bfd3964ef8
4 changed files with 752 additions and 0 deletions
+290
View File
@@ -0,0 +1,290 @@
# 演示流程(照着走)
> **读者**:负责演示的人。本文按"操作 → 看到什么 → 这体现什么"三段式写,
> 可以直接照着念。
> **主线时长**:约 8 分钟;含讲解约 15 分钟。
> **全部内容均已实测**(2026-09-13);标 ⚠️ 的地方是容易翻车的点。
---
## 0. 演示前准备
### 0.1 准备演示数据(首次、或换了机器才需要)
```powershell
python tools/seed_demo_data.py
```
10 步:账号权限 → 演示口令 → 客户账户 → **场内行情** → 风控预警样本 → 投顾数据 →
三类发布配置 → 知识库素材。脚本会逐步打印完成/失败。
> ⚠️ 第 2 步**不是幂等的**:重跑等于重设密码(bcrypt 每次加盐不同)。
### 0.2 一条必须知道的硬规则:**行情有效期只有 15 分钟**
下单要求行情快照落在 **15 分钟内**(`app/service/trade_service.py` 的 `MAX_QUOTE_AGE`),
**系统没有任何自动刷新机制** —— 超时后**所有委托直接返回 `503 行情已过期`**。
演示时踩过这个坑:明明上午刷过行情,下午演示第一单就 503。
**怎么办**:`start.ps1` **已经会替你刷新一次**(见 0.3),所以正常不用管。
万一演示中途下单报 503 —— 在新窗口跑一次就行,**不用重启任何服务**(已实测):
```powershell
python tools/sync_market_prices.py
```
它按产品 upsert(幂等),几秒内把 20 只产品行情全部刷新,15 分钟窗口重新计时。
> 不想让启动脚本联网刷行情:`start.ps1 -SkipPriceSync`
### 0.3 启动平台
```powershell
powershell -ExecutionPolicy Bypass -File start.ps1
```
它按顺序做四件事:**找解释器 → 检查 MySQL/Redis/Milvus → 刷新行情 → 起两个窗口**,然后打印访问入口与账号。
它会**开两个窗口**:
| 窗口 | 作用 | 少了它会怎样 |
|---|---|---|
| API | 所有接口与页面 | 什么都没有 |
| **Worker** | Agent 对话、知识向量同步、记忆抽取、风控扫描 | 客服对话一直"超时";新知识不进 Milvus 且**无任何报错** |
### 0.4 开场前自检(务必做)
```powershell
python tools/e2e_smoke_test.py --read-only
```
看最后一行:**40/40 通过**才开始演示。有 FAIL 就按 §4 排查。
### 0.5 打开第一个页面
<http://127.0.0.1:8000/portal/>
---
## 1. 主线场景
### 场景 1 · 访客咨询与知识边界(1.5 min)
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/guest/products/> |
| **操作** | 点右下角客服浮窗 → 输入「场内基金的管理费率是多少」→ 发送 |
| **预期** | **约 3–4 秒**返回:本平台 20 只场内基金的管理费率 0.15%~1.20%/年,并列出 ETF 与 LOF 的差异 |
**这体现什么**:客服不是"模型随便聊",而是**检索已发布知识后作答**,答案可追溯;
延迟稳定在 3–4 秒(一次意图分类 + 一次向量检索)。
**接着再问一句(体现安全边界)**:「我的账户里有多少钱?」
→ 预期:回复"该服务需要登录后才能查询您的个人信息",**不编造账户数字**。
> 浮窗上方还有一条来源声明:产品页数据来自 `mock-data.js`(公开产品接口尚未实现),
> 页面显著标注了"非真实行情"。**这是我们主动标注的**,不是缺陷。
### 场景 2 · 客户资产(1 min)
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/customer/login/> |
| **账号** | `cust_t` / `123456` |
| **预期** | 自动进资产总览:总资产约 10 万、可用资金、持仓市值(7 只持仓) |
**这体现什么**:持仓按**真实行情**计价(不是写死的假数);页面底部有数据来源说明。
点左侧「我的持仓」「资金流水」「成交明细」各看一眼即可。
### 场景 3 · 客户下单(1.5 min)⭐ 重点
| | |
|---|---|
| **打开** | 客户页 → 交易记录 → 或直接用接口 |
| **说明** | 前端下单表单待完善,本场用接口演示更直观(见下) |
```powershell
# 演示用:买入 100 份沪深300ETF
$body = '{"product_code":"510300","order_side":"buy","quantity":100}'
$r = Invoke-RestMethod -Uri http://127.0.0.1:8000/api/v1/auth/tokens -Method Post `
-Body '{"username":"cust_t","password":"123456"}' -ContentType application/json
Invoke-RestMethod -Uri http://127.0.0.1:8000/api/v1/users/me/orders -Method Post `
-Headers @{ Authorization = "Bearer $($r.data.access_token)"; "Idempotency-Key" = [guid]::NewGuid().ToString("N") } `
-Body $body -ContentType application/json | ConvertTo-Json -Depth 4
```
**预期**:`status=已成交`、`executed_price=4.579`(**真实行情价**)、
成交金额 457.90、手续费 0.05、实际扣款 457.95(`net_amount`)。
**这体现什么**:
- **成交价来自外部行情**,不是模拟随机数;
- 下单前会做**适当性校验**(风险等级匹配),不匹配直接拒绝;
- 成交后持仓、资金流水、成交明细**三处同时落账**(可当场刷新场景 2 的页面看变化)。
> ⚠️ 如果返回 `503 FUND_QUOTE_UNAVAILABLE` → 跑 §0.2 的行情同步。
### 场景 4 · 客服对话与转人工(1 min)
| | |
|---|---|
| **操作** | 客户页右下角浮窗 → 问「基金定投是什么」→ 点「转人工客服」 |
| **预期** | 先得到知识库答案;转人工返回 `202`,工单进入管理员队列 |
**这体现什么**:客户**主动**请求人工与"答不上来"是两条不同路径 —— 前者建工单,
后者按既定口径引导拨打客服热线(**不建工单**,符合我们"答不了就转人工、不硬答"的原则)。
### 场景 5 · 风控预警处置(2 min)⭐ 重点
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/employee-console/login/> → `risk_t` / `666666` |
| **预期** | 概览:未闭环预警数、高风险数、待处理、已超时 |
**操作顺序(这是核心闭环)**:
1. 点预警队列里的某条 → 打开详情;
2. 详情里有**八类证据**入口:客户、产品、交易、资金流水、持仓、登录记录、预警、通知;
3. 点「确认接收」→ 状态变为已确认;
4. 点「进入调查」→ 状态变为调查中;
5. 点「完成结案」→ 状态变为**已结案**,并且**按风险等级扣减客户行为分**。
**这体现什么**:预警处置是**有状态机、有留痕、有连带影响**的 ——
不是改个字段。每一步都记审计(谁、何时、做了什么)。
> 若队列里没有「待处理」的预警(都已被处置过),跑
> `python tools/e2e_smoke_test.py` —— 它会自动造一条待处理预警并跑完整闭环。
### 场景 6 · 风控日报(1 min)
| | |
|---|---|
| **操作** | 风控工作台 → 日报 → 生成 |
| **预期** | 文本**流式**逐段出现(SSE),完成后可填多个邮箱发送 |
**这体现什么**:长任务走 SSE 流式,而不是让页面转圈等 30 秒。
### 场景 7 · 投顾工作台(1 min)
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/employee-advisor/dashboard/> |
| **账号** | `advisor_t` / `abc12345` |
| **预期** | 一张「投资目标方案书 · 客户 9001」卡片,含发布时间与方案内容 |
**这体现什么**:投顾看到的是**自己服务的客户**的已发布交付物。
数据范围按 `sys_customer_assignment` 归属关系判定(不是按 `data_scope` ——
那会把 all 级权限放大成"看全部客户")。
> 方案书的**审核与发布都要求管理员**(`admin=True`),投顾自己发不出来。
> 这是有意的复核环节,不是缺陷。
### 场景 8 · 管理员治理(1.5 min)
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/employee-console/workspace/> |
| **账号** | `admin_t` / `88888888` |
| **预期** | 四个指标卡 + 六个标签页 |
**挨个点一遍,每处一句话**:
| 标签页 | 讲解要点 |
|---|---|
| 角色权限 | 五个角色,权限数分别是 admin 59 / advisor 28 / customer 26 / risk_operator 10 / operator 2 |
| 配置发布 | 工具白名单、提示词**走发布状态机**(校验→审核→激活),不是改配置文件 |
| 审计 | 每一次权限判定、工具调用都有记录 |
| 转人工工单 | 场景 4 建的工单在这里;**不返回客户标识与原始对话**(脱敏) |
| 模型端点 | 当前用 `deepseek-flash`(意图分类)+ `qwen` 向量(embedding) |
**这体现什么**:**权限变更必须过审核流程并留痕** —— 这是金融场景的硬要求,
所以平台刻意**不提供**"直接改权限"的接口。
---
## 2. 备选场景(时间充足时)
| 场景 | 怎么演 | 体现什么 |
|---|---|---|
| 配置发布四态 | 管理员工作台建一个草稿版本 → 提交校验 → 审核 → 激活 | 改配置是走流程的,可回滚 |
| 五角色权限对比 | 用五个账号分别登录,看入口守卫各跳哪里 | 越权在**前端就被挡**,服务端还有一层 |
| 知识入库 | `python tools/seed_knowledge_demo.py` 后复测场景 1 | 知识是**可运营**的,不是写死的 |
| 平台体检 | `python tools/e2e_smoke_test.py` | 40 项全绿,交付质量可自证 |
---
## 3. 可能被问到的问题(建议话术)
**Q:这是真实行情吗?**
A:行情取自公开数据源(腾讯行情),成交价用的就是它。但**交易本身是模拟的** ——
平台是"场内基金模拟交易",不下真实单。
**Q:Agent 会不会乱答?**
A:不会。客服只回答**检索到的已发布知识**,且有置信度门槛:
命中分数 ≥0.75 直接答;0.55~0.75 之间还要求领先次优 ≥0.07;
不满足就引导人工。**宁可转人工也不答错** —— 这是刻意的取舍。
**Q:为什么有些问题答不上来?**
A:知识库是人工审核入库的,目前覆盖交易规则、20 只产品、费率与常见问答。
问不到的地方会转人工,**这是设计而不是故障**。
**Q:数据安全怎么保证?**
A:三层 —— 前端按权限隐藏入口;服务端每次请求**重新解析权限与数据范围**(令牌里不带权限);
工单等管理面接口**不返回客户标识与原始对话**。所有判定都有审计。
**Q:为什么不让管理员直接改权限?**
A:金融场景要求权限变更留痕可追溯,所以统一走配置发布流程(草稿→校验→审核→激活)。
---
## 4. 出问题怎么办
| 现象 | 原因 | 怎么办 |
|---|---|---|
| 客户下单返回 `503 行情已过期` | 行情快照过期(**有效期仅 15 分钟**) | `python tools/sync_market_prices.py` —— **立即生效,无需重启服务** |
| 客服一直"繁忙/超时" | **Worker 没在跑** | 看 `start.ps1` 起的第二个窗口;或 `python -m app.worker` |
| 改了页面看不到效果 | 浏览器缓存 | `Ctrl+F5` 硬刷新 |
| 知识库问答答不上 | 向量没同步 | 确认 Worker 在跑,然后跑 `tools/seed_knowledge_demo.py` |
| 平台起不来 | MySQL 没起 | 看 `start.ps1` 的依赖检查输出 |
| 风控队列没有"待处理"预警 | 演示样本都被处置过了 | `python tools/e2e_smoke_test.py`(自动造一条) |
**一条命令定位**:
```powershell
python tools/e2e_smoke_test.py
```
它逐条打印 6 条线 40 项的结果,FAIL 的那条就是问题所在。
---
## 5. 速查
**账号**
| 角色 | 用户名 | 密码 | 登录入口 |
|---|---|---|---|
| 客户 | `cust_t` | `123456` | `/portal/customer/login/` |
| 风控专员 | `risk_t` | `666666` | `/portal/employee-console/login/` |
| 管理员 | `admin_t` | `88888888` | 同上 |
| 投顾 | `advisor_t` | `abc12345` | 同上(→ 投顾工作台) |
| 运营 | `offsite_t` | `offsite123` | 同上(→ 运营工作台) |
**常用命令**
```powershell
python tools/seed_demo_data.py # 准备演示数据(10 步,首次/换机器)
python tools/sync_market_prices.py # 刷行情(15 分钟有效期,下单 503 时补跑)
powershell -ExecutionPolicy Bypass -File start.ps1 # 起 API + Worker(自动刷行情)
python tools/e2e_smoke_test.py # 全量体检(40 项)
python tools/e2e_smoke_test.py --read-only # 只读体检,不动数据
```
**已知的空数据**(演示时别点进去尴尬)
| 位置 | 现状 |
|---|---|
| 运营工作台 | 场外邮件 0 条、邮箱未初始化(需要真实邮件源) |
| 管理员 · 画像候选 | 0 条(需要客户对话触发画像抽取) |
**端口**:API `8000` / MySQL `3306` / Redis `6379` / Milvus `19530`