## 问题 `svc_handover_ticket` 的 DDL 与状态机在 `docs/02` §7.2 早就定好了 (pending → assigned → processing → resolved → closed,未解决可 cancelled), 但平台**只有 handover:read(只读队列)**:没有任何入口能改状态、assigned_to / accepted_at / resolved_at / closed_at / resolution 五列**全库 0 非空**, 于是 40 张工单永远停在 pending —— 用户看到的就是"工单全都长一样"。 ## 改了什么 后端: - 新增 `app/service/customer_service_handover_action_service.py`:五个动作 (分配/接单/解决/关闭/取消),`SELECT ... FOR UPDATE` 锁单后判状态; 接单允许从 pending 自助接管(同时记受理人);取消不写 closed_at(该列属 closed 状态); 每次流转写一条 interaction_audit(handover.assigned/accepted/resolved/closed/cancelled); 非法流转 409、坐席不存在 422、工单不存在 404;回包不含 customer_id/session_id。 - 只读服务保持只读(读侧与写侧是两条边界,单测守着"读侧不许长出写方法"), 但列表支持 `?status=` 六态筛选、详情补上受理人与流转时间(坐席侧路由信息,非客户数据)。 - `app/api/controllers/admin.py`:五个 action 端点 A049–A053 (assignments / acceptances / resolutions / closures / cancellations), 走 `ApiTransactionService.execute_in` —— 幂等记录与业务写入同事务、重复键回放。 - 权限:新增 `handover:write`(9069,只授 admin),已并进种子 `tools/seed_test_rbac.py`;配套幂等脚本 `tools/grant_handover_write_permission.py`。 前端(管理员工作台 · 转人工工单页): - 按状态给按钮(待处理→分配/直接接单、已分配→接单、处理中→解决、已解决→关闭、 未解决都可取消),加了状态筛选与"刷新";摘要弹窗补上受理人与四个时间点、处置结论。 - api-client 注册五个端点;workspace.js 的 api-client 引用与页面自身的 ?v= 一并升版, 避免浏览器拿旧缓存(旧缓存里没有这些端点)。 冒烟与测试: - `tools/e2e_smoke_test.py`:B 段建的测试工单由 F 段走完 分配→接单→解决→关闭 收尾 —— 既不再把测试件堆在 pending 队列里(此前每次冒烟攒一张),又让每次冒烟都覆盖一遍状态机。 总数 40 → 44 项,实测 44/44 全绿。 - 新增单测 24 条(状态机合法/非法路径、越权、坐席不存在、审计、视图不泄漏客户标识) 与一条真机集成用例(HTTP 十步 + 数据库侧审计证据 + 自动清理)。 - 读侧那条"详情不得返回 assigned_to"的旧断言按新口径更新,并写清为什么。 ## 验证 - `pytest tests/unit tests/contract` → 1489 passed, 2 skipped, 0 failed - 新增集成用例通过;`tests/integration` 全量跑时 `test_memory_extraction` / `test_run_cancellation_mysql` 两条偶发红 —— 单独跑都通过, 是 AGENTS.md 已登记的"常驻 Worker 抢队列"(跑验收前须先停 Worker) - `tools/portal_api_check.py` → 41 项通过 39、失败 0 - `tools/e2e_smoke_test.py` → 44/44 全通过 - `python tools/check_rbac_seed_consistency.py` → 通过(种子 63 条权限) - 真机 HTTP 实测:分配→接单→解决→关闭四步 200 且时间戳齐全;取消路径 200 且 closed_at 为空; 同键重发回放不二次推进;对已关闭工单再分配 409;风控账号处置 403 ## 文档 `docs/44-演示流程.md`(场景 4/8 + 命令 + 44 项)、`docs/演示用/后端接口文档`(新增 §11.4b 与 A049–A053)、`docs/演示用/全功能流程-大白话版.md`(工单页签改"读写"+ 已知偏差)、 `AGENTS.md`(9066-9069 号段演进 + 冒烟 44 项)
27 KiB
演示流程(照着走)
读者:负责演示的人。本文按"操作 → 看到什么 → 这体现什么"三段式写, 可以直接照着念。 主线时长:约 8 分钟;含讲解约 15 分钟。 全部内容均已实测;标 ⚠️ 的地方是容易翻车的点。
2026-09-14 复核:本文档已按当天实测数据更新四处(「今日盈亏」已实现、投顾推荐方案 已开放自助审核发布、管理员工作台为八个标签页、角色权限数与已知空数据)。数字类结论都标了 实测日期——换环境或换库后要重查。
配套文档(想看"为什么"而不是"怎么演"就看这两份):
docs/演示用/全功能流程-大白话版.md—— 从登入讲到六个门户的全部流程,不用术语;docs/演示用/记忆架构与Worker工作流程-大白话版.md—— 记忆链路与后台 Worker 的详细工作流程;docs/演示用/今日盈亏实现说明-2026-09-14.md—— 「今日盈亏」的口径、数据坑与复算命令。
0. 演示前准备
0.1 准备演示数据(首次、或换了机器才需要)
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 —— 在新窗口跑一次就行,不用重启任何服务(已实测):
python tools/sync_market_prices.py
它按产品 upsert(幂等),几秒内把**全部产品(当前库里 26 只)**行情刷新,15 分钟窗口重新计时。
不想让启动脚本联网刷行情:
start.ps1 -SkipPriceSync
0.3 启动平台
最省事的办法:双击桌面上的 启动金融Agent平台.bat。 不用开 PowerShell、不用敲命令。
(仓库根目录也有一份 启动平台.bat,是同一个东西。)
⚠️
启动金融Agent平台.bat在桌面上,不在仓库里(2026-09-14 补注): 仓库中只有启动平台.bat这一份(Glob 启动*.bat只命中它)。 两份内容是同一个东西、由同一个生成器产出:python tools/make_launcher_bat.py # 改完 start.ps1 或想换路径就重跑它,桌面与仓库两份一起更新不要手写那个 bat —— 它必须同时满足 GBK 编码 + CRLF 换行 + 无 BOM, 缺任何一条
cmd都会解析错乱(LF 换行会把echo的说明文字当命令执行, 实测报AT 命令已弃用、']' 不是内部或外部命令)。
它按顺序做六件事:找解释器 → 检查 MySQL/Redis/Milvus → 刷新行情 → 起两个窗口 → 等 API 真正应答 → 自动开浏览器,然后打印访问入口与账号。
重复双击是安全的:API 按端口判断、Worker 按进程判断,已经在跑的不会起第二份。 演示前如果服务已经开着,直接双击它就等于"刷新行情 + 打开页面"。
想传参数就用命令行(bat 会把参数原样转给 start.ps1):
powershell -ExecutionPolicy Bypass -File start.ps1 # 等价于双击
powershell -ExecutionPolicy Bypass -File start.ps1 -NoBrowser # 不自动开浏览器
powershell -ExecutionPolicy Bypass -File start.ps1 -Port 8100 # 换端口(8000 被别的程序占了)
它开两个窗口:
| 窗口 | 作用 | 少了它会怎样 |
|---|---|---|
| API | 所有接口与页面 | 什么都没有 |
| Worker | Agent 真正执行、领域事件投递、知识向量同步、记忆抽取与画像投影、会话片段聚合、场外收件 | 客服对话一直"超时";新知识不进 Milvus(无任何报错);记忆不更新;场外收不到邮件 |
⚠️ 两个窗口都别关。启动脚本自己的窗口跑完就可以关(服务在独立窗口里)。
注:风控定时扫描不在这个 Worker 里 —— 它是独立进程 (
python -m app.worker.risk_scan_scheduler)且默认关闭;演示时用的是风控工作台上的 「扫描」按钮(同步执行,最长 60 秒)。 详细分工见docs/演示用/记忆架构与Worker工作流程-大白话版.md。
0.4 开场前自检(务必做)
两条线互补,都跑一遍:
python tools/e2e_smoke_test.py --read-only # 业务链路:登录→下单→成交、风控闭环、客服问答
python tools/portal_api_check.py # 接口契约:按前端的方式核对每个端点的状态码与字段
看最后一行:44/44 与 41 项全通过才开始演示(用例数会随开发增减, 判断健康的准则是 0 FAIL,不是绝对条数)。有 FAIL 就按 §4 排查。
两者的分工:
e2e_smoke_test.py回答"这条业务走得通吗",portal_api_check.py回答"接口返回的东西前端能不能正确显示"。 后者是 2026-09-13 那轮"照着接口把前端测一遍"的固化 —— 当时靠它查出 6 个真缺陷 (知识库页永远显示 0 块、详情页 5 行全是--、配置项首次编辑必然 409、非法场景值冒成 500 等)。 加--write会再测一遍写操作(会产生数据);--dangerous会动生效配置,演示前不要加。
0.5 打开第一个页面
1. 主线场景
场景 1 · 访客咨询与知识边界(1.5 min)
| 打开 | http://127.0.0.1:8000/portal/guest/products/ |
| 操作 | 点右下角客服浮窗 → 输入「场内基金的管理费率是多少」→ 发送 |
| 预期 | 约 3–4 秒返回:本平台场内基金的管理费率区间(0.15%~1.20%/年),并列出 ETF 与 LOF 的差异 |
⚠️ 知识库文案是人工审核入库时的快照(当前 active 知识 24 份、元数据 199 行), 里面写的产品数量可能与产品列表的当前数量不一致。 被问到时如实说:"这是知识库里的内容,知识是人工入库的,产品列表取的是实时数据。" 不要现场改知识库文案来对齐数字。
这体现什么:客服不是"模型随便聊",而是检索已发布知识后作答,答案可追溯; 延迟稳定在 3–4 秒(一次意图分类 + 一次向量检索)。
接着再问一句(体现安全边界):「我的账户里有多少钱?」 → 预期:回复"该服务需要登录后才能查询您的个人信息",不编造账户数字。
产品页的数据来自真实接口(
GET /api/v1/products):产品与净值取自fin_product, 行情由tools/sync_market_prices.py从行情源同步,页面底部有来源声明。⚠️ 两个可能被问到、但其实不是故障的地方:
- 「最近涨跌」列取的是行情源直接给出的当日涨跌幅(腾讯返回,存在
fin_market_price.change_pct),已接入。若某只显示"暂无",说明它走的是净值降级 路径(源没给涨跌幅),或行情没同步过 —— 跑一次sync_market_prices.py即可。 我们不编这个数:缺数据就显示"暂无",而不是 0(0 会被读成"今天平盘")。- 产品详情的净值走势图现在是真实数据:
fin_nav_history由tools/sync_nav_history.py(演示数据第 5 步)从东财净值接口同步,近 120 个交易日。 若图上显示"尚未接入",说明第 5 步没跑 —— 补跑即可,不用重启服务。
场景 2 · 客户资产(1 min)
| 打开 | http://127.0.0.1:8000/portal/customer/login/ |
| 账号 | cust_t / 123456 |
| 预期 | 自动进资产总览:总资产约 10 万、可用资金、持仓市值(7 只持仓) |
这体现什么:持仓按真实行情计价(不是写死的假数);页面底部有数据来源说明。
点顶部导航「我的持仓」「资金流水」「成交明细」「盈亏分析」各看一眼即可。
✅ 「今日盈亏」现在可以直接讲(2026-09-14 起是真实计算,不再是占位 0)。 实测口径与数字(当天数据):
cust_t看板 今日盈亏 −132.70(−0.2528%); 逐只:510300−94.50、510500−27.30、159948−8.80、511810−1.80、515450+2.00、588890−2.30、1601280.00。被追问口径时一句话答:"今天收盘值多少钱 − 昨天收盘值多少钱 − 今天买入花的钱 + 今天卖出收回的钱" (不含手续费);基准优先用行情(和同页的"最新价/市值"同源,客户能自己核对), 行情只有一天的产品回退用净值。 语义上是"最近一个交易日"而不是盘中实时。 想现场证明它没算错:
python tools/check_today_profit_loss.py(纯 SQL 独立复算并逐只比对)。 详见docs/演示用/今日盈亏实现说明-2026-09-14.md。
场景 3 · 客户下单(1.5 min)⭐ 重点
| 打开 | 客户页 → 资产总览 → 「去下单」(页面本身就有下单弹窗),或直接用下边的接口 |
| 说明 | 弹窗里填基金代码 / 方向 / 数量即可;接口方式更直观,二者等价 |
# 演示用:买入 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 = 当时的最新行情价(演示前先刷一次行情,
按页面/返回里的真实数字念,别背固定价);成交金额 = 100 × 该价格;
再扣一笔手续费(按费率规则),net_amount 就是实际扣款。
这体现什么:
- 成交价来自外部行情,不是模拟随机数;
- 下单前会做适当性校验(风险等级匹配),不匹配直接拒绝;
- 成交后委托、成交、资金流水、持仓四处同时落账(可当场刷新场景 2 的页面看变化)。
⚠️ 如果返回
503 FUND_QUOTE_UNAVAILABLE→ 跑 §0.2 的行情同步。⚠️ 幂等键只防"同一次请求被重发",在页面上连点两下仍会下两笔(前端每次提交生成新键, 属已知前端缺口)。演示时点一次就好。
场景 4 · 客服对话与转人工(1 min)
| 操作 | 客户页右下角浮窗 → 问「基金定投是什么」→ 点「转人工客服」 |
| 预期 | 先得到知识库答案;转人工返回 202,工单进入管理员队列(新建的工单状态是 pending) |
这体现什么:客户主动请求人工与"答不上来"是两条不同路径 —— 前者建工单, 后者按既定口径引导拨打客服热线(不建工单,符合我们"答不了就转人工、不硬答"的原则)。
✅ 这张工单现在可以在管理员工作台里被真正处置掉(2026-09-14 补齐): 分配 → 接单 → 解决 → 关闭(未解决可取消),每一步都写审计。 演完场景 8 时顺手走一遍,就能把"客户请求人工 → 坐席接管 → 结案归档"讲完整。 状态机与权限见 §场景 8 的表格。
场景 5 · 风控预警处置(2 min)⭐ 重点
| 打开 | http://127.0.0.1:8000/portal/employee-console/login/ → risk_t / 666666 |
| 预期 | 概览:未闭环预警数、高风险数、待处理、已超时(2026-09-14 实测:未闭环 7 条 / 预警总数 32 条) |
队列空或想现场造一条:页面上的**「扫描」按钮**会同步跑一次风控扫描(最长 60 秒)。 也可以跑
python tools/e2e_smoke_test.py—— 它会自动造一条待处理预警并跑完整闭环。
操作顺序(这是核心闭环):
- 点预警队列里的某条 → 打开详情;
- 详情里有八类证据入口:客户、产品、交易、资金流水、持仓、登录记录、预警、通知;
- 点「确认接收」→ 处置状态不变,只把"确认状态"置为已确认(记录谁、何时看到了这条预警);
- 点「进入调查」→ 处置状态:待处理 → 调查中;
- 点「完成结案」→ 状态变为已结案(只有"调查中"的才能结案),并且按风险等级扣客户行为分: 低 3 / 中 5 / 高 20(行为分 0–20 封顶)。
另有两个动作可以按需插入:关闭误报(→ 已排除,必须填理由)与升级处理 (只打"已升级"标记,不改处置状态)。
这体现什么:预警处置是有状态机、有留痕、有连带影响的 —— 不是改个字段。每一步都记审计(谁、何时、做了什么),结案还会影响客户行为分。
若队列里没有「待处理」的预警(都已被处置过),跑
python tools/e2e_smoke_test.py—— 它会自动造一条待处理预警并跑完整闭环。
补一步「风控助手」(30 秒,很值得演):点上方「风控助手」标签 → 点预设的
「当前风险概览」按钮 → Agent 调 get_risk_overview 只读工具返回未闭环预警概览,
结尾并声明"以上仅为查询与复核线索,不构成任何已确认、误报、关闭或升级的处置结论,
最终请由风控专员人工复核并留痕"。
这体现什么:Agent 只做只读查询与研判草案,处置动作一律留给人工。
实测这条问法命中的是 risk_overview 意图、置信度 1.0000 ——
是配置好的意图分派,不是让模型自由发挥。
场景 6 · 风控日报(1 min)
| 操作 | 风控工作台 → 日报 → 生成 |
| 预期 | 文本流式逐段出现(SSE),一共 9 段;完成后可填多个邮箱发送 |
这体现什么:长任务走 SSE 流式,而不是让页面转圈等 30 秒。
结尾会标来源:模型可用时是"大模型生成",不可用时自动降级为规则模板并如实标注—— 降级不是报错,被问到就这么讲。 ⚠️ 发送邮件默认是"演练":本机 SMTP 开关默认关着、dry-run 默认开着,所以流程会走完、 状态会返回,但不会真的投递。要真发得先配 SMTP。
场景 7 · 投顾工作台(1 min)
| 打开 | http://127.0.0.1:8000/portal/employee-advisor/dashboard/ |
| 账号 | advisor_t / abc12345 |
| 预期 | 概览四张卡 + 客户区(页面内置 4 位演示客户:张总/李阿姨/王工/陈医生,对应真实客户号 9101–9104)+ 7 个动作按钮 + 「已发布交付物」 |
这体现什么:投顾看到的是自己服务的客户的交付物。
数据范围按 sys_customer_assignment 归属关系判定(不是按 data_scope —— 那会把 all 级权限放大成"看全部客户"),
并且有灰度开关:没开放的账号直接 403。
选一个客户点「生成推荐方案」,结果区会给出方案与六步流水线进度。 若候选池为空,页面会把原因写清楚(证据缺失/流动性不够/适当性不匹配/没有客户目标)+ 计数 + 两条出路。
审核与发布的口径(2026-09-14 更新,别再照旧话术讲):
- 产品推荐方案:投顾可以自助审核 / 驳回 / 发布给客户(本次已开放),管理员复核队列同样能做;
- 投资目标方案书:投顾做到"录入目标 → 确认目标 → 查看方案书"为止,审核与发布仍归管理员。
没有后端也能演:网址加
?demo=1进离线演示模式(本地引擎、无令牌、页面明确标注 "演示模式 · 本地引擎"、方案标"规则模拟,不构成真实推荐")。用它时务必主动说明这是演示模式。
场景 8 · 管理员治理(1.5 min)
| 打开 | http://127.0.0.1:8000/portal/employee-console/workspace/ |
| 账号 | admin_t / 88888888 |
| 预期 | 四张指标卡(有效角色 / 生效配置版本 / 可用模型端点 / 待人工复核)+ 八个标签页 |
八个标签页,挨个点一遍,每处一句话:
| 标签页 | 讲解要点 |
|---|---|
| 角色与权限 | 五个角色的权限数(2026-09-14 实测):admin 62 / advisor 31 / customer 26 / risk_operator 10 / operator 7;还能按用户 ID 查"服务端实际解析出来的角色与数据范围" |
| 配置与模型 | 工具白名单、提示词走发布状态机(草稿 → 待审核 → 已审核 → 生效,可回滚;被取代的版本落 superseded),不是改配置文件;模型路由规则也在这里 |
| 审计记录 | 每一次权限判定、工具调用都有记录;没有"看敏感详情"的权限时,详情由服务端脱敏 |
| 转人工工单 | 场景 4 建的工单在这里(当前库里 40+ 张,多数是冒烟测试件);只回二次脱敏摘要,不返回客户标识与原始对话。可以处置:分配 → 接单 → 解决 → 关闭(未解决可取消),按状态给按钮、可按状态筛 |
| 画像候选 | 客服从对话里提炼的画像候选,批准后才进正式记忆 |
| 投顾复核 | 待审的投顾交付物(推荐方案 + 投资方案书)→ 审核通过 / 驳回 / 发布给客户 |
| 知识库 | 上传文档(faq / product / policy)→ 自动切分入库并投向量同步;客服就是靠它作答(当前 active 知识 24 份、知识元数据 199 行) |
| 画像漂移复核 | 客户重新测评导致画像变化时人工确认是否采纳 |
这体现什么:权限变更必须过审核流程并留痕 —— 这是金融场景的硬要求, 所以平台刻意不提供"直接改权限"的接口。
⚠️ 三个"前端未接、后端已有"的小缺口,被问到照实说:配置发布页没有"驳回/回滚"按钮、 模型端点页是只读的(当前 3 个端点)、审计页没有筛选框(只看最近 20 条)。
2. 备选场景(时间充足时)
| 场景 | 怎么演 | 体现什么 |
|---|---|---|
| 配置发布四态 | 管理员工作台建一个草稿版本 → 提交校验 → 审核 → 激活 | 改配置是走流程的,可回滚 |
| 五角色权限对比 | 用五个账号分别登录,看入口守卫各跳哪里 | 前端只是体验层,真正的拦截在服务端权限码与数据范围 |
| 知识入库 | python tools/seed_knowledge_demo.py 后复测场景 1 |
知识是可运营的,不是写死的 |
| 平台体检 | python tools/e2e_smoke_test.py |
40 项全绿,交付质量可自证 |
| 记忆链路 | 客户连问几轮 → 看 memory_unit 里新增长期记忆 → Worker 投影到 Neo4j/Milvus |
"记住客户"是可查的,不是模型自己说记住了(见 docs/演示用/记忆架构与Worker工作流程-大白话版.md) |
| 后台排障 | 停掉 Worker → 客服变"繁忙" → 查 agent_run.status='queued' → 起回 Worker 自动恢复 |
异步三段式的可观测性 |
3. 可能被问到的问题(建议话术)
Q:这是真实行情吗? A:行情取自公开数据源(腾讯行情),成交价用的就是它。但交易本身是模拟的 —— 平台是"场内基金模拟交易",不下真实单。
Q:Agent 会不会乱答? A:不会。客服只回答检索到的已发布知识,且有置信度门槛: 命中分数 ≥0.75 直接答;0.55~0.75 之间还要求领先次优 ≥0.07; 不满足就引导人工。宁可转人工也不答错 —— 这是刻意的取舍。
Q:为什么有些问题答不上来? A:知识库是人工审核入库的,目前覆盖交易规则、20 只产品、费率与常见问答。 问不到的地方会转人工,这是设计而不是故障。
Q:数据安全怎么保证? A:三层 —— 前端按权限隐藏入口;服务端每次请求重新解析权限与数据范围(令牌里不带权限); 工单等管理面接口不返回客户标识与原始对话。所有判定都有审计。
Q:为什么不让管理员直接改权限? A:金融场景要求权限变更留痕可追溯,所以统一走配置发布流程(草稿→校验→审核→激活)。
Q:客户看板上的"今日盈亏"是怎么算的? A:2026-09-14 起是真实计算的(此前是占位 0,已修)。一句话口径:
今日盈亏 = 今日市值 − 昨日持仓市值 − 今日买入金额 + 今日卖出金额 (不含交易费用)
昨日持仓数量 = 今日持仓数量 − 今日买入份额 + 今日卖出份额 (由当日成交反推)
基准优先用行情(最近两个交易日收盘价,与同页"最新价/市值"同源、客户能自己核对),
行情只有一天的产品(演示数据里 15911 / 159991-159995)回退用净值。
语义是「最近一个交易日相对前一交易日」的盈亏,不是盘中实时 —— 这句一定要讲,
否则会被当成 bug。
现场可自证:python tools/check_today_profit_loss.py(纯 SQL 独立复算,与接口逐只比对,
不一致会以退出码 1 结束)。实测:cust_t = −132.70(−0.2528%)。
细节见 docs/演示用/今日盈亏实现说明-2026-09-14.md。
💡 当天买卖会让"今日盈亏"包含当日已实现部分(卖出份额仍算"昨收→卖出价", 当日买入的份额只算"买入价→今收")。这是刻意的,不是算错。
4. 出问题怎么办
| 现象 | 原因 | 怎么办 |
|---|---|---|
客户下单返回 503 行情已过期 |
行情快照过期(有效期仅 15 分钟) | python tools/sync_market_prices.py —— 立即生效,无需重启服务 |
| 客服一直"繁忙/超时" | Worker 没在跑 | 看 start.ps1 起的第二个窗口;或 python -m app.worker |
| 所有页面一起变慢/卡住(不是某一个请求超时,而是客服、风控、看板同时迟钝) | 检索层用的是同步 Milvus 客户端,单次 search 卡住会阻塞整个 API 进程(连 Worker 心跳一起停)—— 已知项,见 代码库全面审查报告-2026-09-14.md P1-2 |
先在 Docker Desktop 确认 Milvus 在跑;不用改代码也不用重启 —— Milvus 恢复后自动正常。这条是「演示版本不修、但要能一眼认出」的典型:平时完全无感,只有 Milvus 抖动时才现形 |
| 改了页面看不到效果 | 浏览器缓存 | Ctrl+F5 硬刷新 |
| 知识库问答答不上 | 向量没同步 | 确认 Worker 在跑,然后跑 tools/seed_knowledge_demo.py |
| 场外发送通知"成功"但对方没收到 | SMTP 默认是演练模式(开关关、dry-run 开) | 这是刻意配置:要在页面上演示"发送"就走完了;要真发得先配 SMTP(见 §5 已知空数据) |
| 风控扫描报"正在扫描中/忙" | 手工扫描与定时扫描共用同一把数据库咨询锁,互斥 | 等这一轮结束再点(定时扫描默认是关闭的,一般遇不到) |
| 平台起不来 | MySQL 没起 | 看 start.ps1 的依赖检查输出 |
| 风控队列没有"待处理"预警 | 演示样本都被处置过了 | python tools/e2e_smoke_test.py(自动造一条) |
一条命令定位:
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 |
同上(→ 运营工作台) |
⚠️
advisor_t与offsite_t不在 RBAC 种子脚本里(2026-09-14 补注):tools/seed_test_rbac.py的演示用户只有 4 个——cust_t(9001)、risk_t(9002)、admin_t(9003)、review_t(9004)。advisor_t(9020) 与offsite_t(9006) 由tools/grant_*.py系列 / 投顾线自己的脚本创建,重跑种子不会重建它们。 换机器时若这两个账号登录失败,先查sys_user里到底有没有这两行,而不是查密码。 (此点docs/40-前端验收清单.md已有自述。)另注:
sys_user已改为「存在则更新、不存在才插入」,故重跑种子不会弄丢演示密码。
常用命令
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 # 全量体检(6 条线 44 项,含工单处置闭环)
python tools/e2e_smoke_test.py --read-only # 只读体检,不动数据
python tools/check_today_profit_loss.py # 「今日盈亏」纯 SQL 复算并与接口逐只比对
python tools/portal_api_check.py # 接口契约体检(按前端的方式调,41 项)
已知的空数据 / 会"看着像坏了"的地方(演示时别点进去尴尬;2026-09-14 实测)
| 位置 | 现状 | 原因 |
|---|---|---|
| 运营 · 场外邮件 | 当前 4 封(不是 0 了),但不会有新的 | 收件依赖真实可用的邮箱配置与常驻 Worker;发件还依赖 SMTP |
| 运营 · 通知发送 | 只"演练",不真发 | SMTP 开关默认关闭 + dry-run 默认开启 |
| 管理员 · 画像候选 | 0 条 | 需要客户对话触发候选抽取,且要管理员批准才进正式记忆 |
| 投顾 · 客户列表 | 页面内置 4 位演示客户 | 前端暂无"查我的客户"接口,真实范围在服务端按归属判定 |
| 风控 · 未闭环预警 | 7 条(总数 32) | 想造新的就点页面上的「扫描」 |
端口:API 8000 / MySQL 3306 / Redis 6379 / Milvus 19530 / Neo4j 7687