Files
group_fqcd_jr/docs/40-前端验收清单.md
T
lzf_0626 f72a545c39 refactor: 品牌统一为「南方财富」(项目方定)
背景:品牌名此前三处不一致 —— 后端 Agent 自称「奶龙基金」(customer_service_rules
的防诈骗/转人工话术、risk_agent 与 risk_analysis_service 的系统提示词)、前端全站
「南方财富」、闲聊提示词与知识素材「南方科技」。docs/36 已把它登记为"上报项目方后
待定",现按项目方决定统一为「南方财富」。

改动:
- app/core/customer_service_rules.py:P0 防诈骗话术与 P2 转人工话术里的品牌名
- app/service/agent/implementations/customer_service.py:COMPANY 常量,
  以及那处引用实测样本的注释(改为不绑定具体品牌名,免得下次改名又过时)
- app/service/agent/implementations/risk_agent.py:docstring、自我介绍、system prompt
- app/service/risk_analysis_service.py:SYSTEM_PROMPT
- app/worker/risk_scan_scheduler.py:--help 描述
- app/static/index.html(旧联调页 4 处)、portal/employee-risk/dashboard/index.html
- tests/unit/api/test_customer_service_test_page.py:同步断言(它断言的正是页面里的品牌名)
- tools/publish_chitchat_prompt.py:SYSTEM_PROMPT 改品牌;并修掉"存在即跳过"的检查
  —— 原来只判当前版本有没有这一行,于是改了文案也发不出去(脚本打印"无需发布"直接
  退出),没有任何提示。改为比对 system_prompt/user_prompt_template 内容。

闲聊提示词已重发为 release 308 / v5,生效内容为"你是南方财富的智能客服助手…"。

刻意未动:
- fin_product.fund_manager = "南方基金" —— 它被 market_quote_sync_service 与
  product_history_sync_service 当过滤条件使用,改名会让同步链路查不到产品
- knowledge_search_service.py 注释里引用的知识库实际标题「南方科技有限公司…」
- docs/客服docs 下的历史素材与 docs/ 下的过程记录(属历史留痕)

⚠️ Milvus 里的知识条目仍写「奶龙基金」(RAG-*/NF-*)与「南方科技」(PROD-*),
属知识数据,需重灌才能统一;本轮不动。

同时:
- docs/40 把品牌条目标为已处理,并补上知识库缺口的现状
- 新增 docs/42-场内基金知识条目草稿.md:按 fin_product 的 20 只产品生成,
  含通用交易规则与产品清单;费率等缺失字段一律标"以交易页面为准",未编造数字。
  **该文件是草稿,未入库**,待审核后走 POST /api/v1/knowledge/documents 灌库。
2026-09-13 19:17:56 +08:00

336 lines
22 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.
# 前端验收清单(正式门户)
> **被测对象**:**正式前端** `app/static/portal/`,由 `app/main.py` 挂载在 **`/portal/`**
> **入口**:<http://127.0.0.1:8000/portal/>(`/` 与 `/portal/` 都会 307 跳到 `/portal/guest/home/`)
> **时点**:2026-09-13(第三次更新:补入投顾/运营两套页面、访客客服浮窗、Worker 前置)
>
> **证据口径**
> **✅实测** = 我用真实 HTTP 请求打过,数字是那次响应的真实值;
> **⚠️待点验** = 只能从代码与接口推断的**页面交互**,需要你在浏览器里点一遍。
>
> 交付方自述的路由与数据源见 `app/static/portal/README.md`。
---
## 0. 启动与前置 ⚠️ 这一节最容易踩
| # | 项 | 命令 / 检查 | 预期 |
|---|---|---|---|
| 0-1 | 依赖服务 | MySQL、Redis 可用;Milvus 需要 Docker Desktop 在运行 | 平台能起,无 500 |
| 0-2 | RBAC 种子 | `python tools/seed_test_rbac.py` | 五个演示账号可登录 ✅实测 |
| 0-3 | 演示口令 | `python tools/set_user_password.py` | 口令生效(**非幂等**,重跑等于改密)✅实测 |
| 0-4 | **虚拟资金账户** | `python -m tools.seed_sim_account_demo` | 客户 9001 开 10 万初始资金 + 2 只持仓。<br>**不跑这步,`/customer/dashboard/` 与 `/customer/cash-ledger/` 必然打不开**(404「客户未开户」)✅实测 |
| 0-5 | 风控 Agent 配置 | `python tools/publish_risk_agent_config.py` | `risk_overview`/`risk_search`/`risk_evidence` 三个工具生效 ✅实测 |
| 0-6 | **Agent Worker(必开!)** | `python -m app.worker` | **不启动它,所有客服对话都会"超时"** —— 见下方说明 |
### ⚠️ 0-6 为什么是"必开"而不是"必停"
Agent 请求是**三段式**:API 受理(返回 202)→ **Worker 领单执行** → 落结果。
**没有 Worker 时**,`agent_run` 会一直停在 `status='queued'`、`worker_id` 为空,
前端轮询到底只会显示"客服繁忙/超时" —— **看起来像链路慢,实际是没人处理**。
(2026-09-13 的访客浮窗"回答超时"就是这么来的。)
> `docs/20` 里写的是"跑验收**脚本**前先停 Worker"(免得抢队列)—— 两件事不矛盾:
> **要跟 Agent 对话就必须开着;要跑验收脚本就关掉。**
排查第一步永远是:查 `agent_run` 最新那行的 `status` 是不是 `queued`。
```powershell
# 一次性把前置跑齐(两个终端)
python tools/seed_test_rbac.py
python tools/set_user_password.py
python -m tools.seed_sim_account_demo
python tools/publish_risk_agent_config.py
# 终端 1:API
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
# 终端 2:Worker(客服对话依赖它)
python -m app.worker
```
> 模块级变量是 **`app`**,不是 `application`。
---
## 1. 页面路由与静态资源 ✅实测
**18 个路径全部 200**(`/` 与 `/portal/` 记的是跟随重定向后的最终 200):
| 分组 | 路径 | 实测 |
|---|---|---|
| 跳转 | `/` → `/portal/guest/home/`<br>`/portal/` → 同上 | ✅ |
| 访客 | `/portal/guest/home/`(5740 B)、`products/`(2153 B)、`product-detail/`(2317 B) | ✅ |
| 客户 | `customer/login/`、`dashboard/`、`holdings/`、`orders/`、`transactions/`、`profit-loss/`、`cash-ledger/`、`risk-questionnaire/` | ✅ 8/8 |
| 员工 | `employee-console/login/`、`employee-console/workspace/` | ✅ |
| 风控 | `employee-risk/dashboard/`(11872 B) | ✅ |
| **投顾** | `employee-advisor/dashboard/`(2449 B) | ✅ 本轮新增 |
| **运营** | `employee-operations/dashboard/`(1968 B) | ✅ 本轮新增 |
**12 个关键静态资源全部 200**:`api-client.js` / `auth.js` / `login-controller.js` / `permission-guard.js` /
`layout/app-shell.js` / `mock-data.js` / `customer-service-widget/widget.js` / `widget.css` /
`base.css` / `tokens.css` / `operations.css` / `auth-layout.css` ✅
---
## 2. 访客(未登录)
| # | 操作 | 预期 | 证据 |
|---|---|---|---|
| 2-1 | 打开 `/portal/guest/home/` | 首屏含品牌与产品摘要 | ⚠️待点验 |
| 2-2 | **看页面的数据来源提示条** | 产品列表页与详情页顶部都有浅色提示条,写明"公开产品接口尚未提供…演示数据,**非真实行情**" | ✅ 页面 HTML 与三个 JS 资源实测都带 `data-source-notice` / `MOCK_SOURCE_NOTICE`<br>⚠️ 渲染位置待点验 |
| 2-3 | 进产品列表 / 详情 | 能筛选、能按排名排序(`?view=ranking`)、详情有净值走势 | ⚠️待点验(数据来自 `common/mock-data.js`,**非真实接口**) |
| 2-4 | 点登录入口 | 进 `/portal/customer/login/` | ⚠️待点验 |
> **关于 mock**:访客三页用 `common/mock-data.js`,因为**公开产品 HTTP 接口尚未实现**。
> 页面显著标注了来源 —— 这个标注**不能删**:删掉不会让数据变真,只会让客户以为看到的是真实净值。
### 2.5 访客智能客服浮窗 ⭐ 本轮重点
公开首页、产品列表页、产品详情页右下角都有客服浮窗。
| # | 操作 | 预期 | 证据 |
|---|---|---|---|
| 2-5 | 点浮窗,问一个**知识库里有**的问题 | 约 4 秒内给出答案 | ✅实测端到端 **4.11s**<br>问「基金定投是什么」→ score 0.8203 → 直接答 |
| 2-6 | 问一个**知识库里没有**的问题 | 引导拨打客服热线(**这是正确行为**,见第 9 节) | ✅实测 **4.09s / 4.82s** |
| 2-7 | 问个人账户相关问题(如"我的风险等级是多少") | 提示"该服务需要登录后才能查询您的个人信息" | ⚠️待点验(代码路径已确认:`_guide_to_login`) |
| 2-8 | 浮窗里点"登录后查询账户" | 跳客户登录页 | ⚠️待点验 |
**访客令牌机制**:`POST /api/v1/visitor-tokens`(无鉴权)
→ 返回短期令牌,身份是 `roles=("visitor",)`、`permissions=("agent:run","knowledge:query")` ✅实测
> ⚠️ **访客与客户走的是不同的检索工具名**:访客走 `query_knowledge`(要 `knowledge:query`),
> 登录客户走 `search_knowledge`(要 `knowledge:reference:read`)。两者**必须同时**在发布配置
> `agent_tools/customer_service:<intent>` 的白名单里 —— 缺哪一条,对应人群就一问即失败。
> 2026-09-13 修过一次:当时白名单只有 `search_knowledge`,访客一问就抛 `ForbiddenAgentError`。
---
## 3. 客户(`cust_t` / `123456`)
### 3.1 登录与账户域
| # | 操作 | 预期 | 证据 |
|---|---|---|---|
| 3-1 | 登录(A034) | 200,拿到令牌,跳 dashboard | ✅ 200,`user_id=9001`,`expires_in=1800` |
| 3-2 | dashboard(T001) | 总资产 / 可用资金 / 持仓市值 | ✅ 200(**修前置前是 404「客户未开户」**) |
| 3-3 | 持仓(T006) | 持仓表 | ✅ 200 |
| 3-4 | 交易流水(T007) | 流水表 | ✅ 200 |
| 3-5 | 资金流水(T009) | 资金明细 | ✅ 200 |
| 3-6 | 订单列表(T003) | 空列表不报错 | ✅ 200 |
| 3-7 | 下单(T002) | 报文 `{product_code, order_side, quantity}`,`order_side ∈ {buy, sell}` | ⚠️待点验<br>需真实 `product_code`(`7002` 是 product_id 不是 code) |
| 3-8 | 盈亏(T001) | 收益曲线 | ⚠️待点验 |
| 3-9 | 风险测评(ONB001) | 返回问卷定义 | ✅ 200 |
| 3-10 | 提交问卷(ONB002) | 提交后状态变更 | ⚠️待点验 |
### 3.2 客户客服浮窗
| # | 操作 | 预期 | 证据 |
|---|---|---|---|
| 3-11 | 在客户工作台用浮窗提问 | 与访客同一浮窗,但身份是登录客户 | ⚠️待点验(需 Worker 在跑) |
| 3-12 | 点"转人工客服" | `POST /conversations/{sid}/handover-requests` → 202,工单进管理员队列 | ✅ 实测 202(**前提:先由 C001 真实创建会话**) |
> **转人工的实现口径**:按你的要求,**只回话术引导拨客服热线,不创建工单**用于"答不上来"的场景;
> 上面 3-12 是客户**主动**点"转人工"按钮才建工单,两者不是一回事。
---
## 4. 员工 / 管理员(`admin_t` / `88888888`)
| # | 操作 | 预期 | 证据 |
|---|---|---|---|
| 4-1 | 员工入口登录 | 进 workspace | ✅ 200 |
| 4-2 | 配置发布列表(A002) | 版本列表 | ✅ 200 |
| 4-3 | 模型端点(A012) | 端点列表 | ✅ 200(`deepseek-flash`、`qwen-embedding`) |
| 4-4 | 审计查询(A033) | 时间/动作/操作者/结果 | ✅ 200 |
| 4-5 | 角色 / 权限 / 用户身份(A035–A038) | 角色与权限明细 | ✅ 200(`customer` 26 项、`advisor` 28 项、`admin` 59 项、`operator` 2 项、`risk_operator` 10 项) |
| 4-6 | 画像候选审核(A039) | 候选列表 | ✅ 200 |
| 4-7 | 客服转人工工单(ADMIN_HANDOVERS) | 工单队列,**不返回客户标识与原始正文** | ✅ 200 |
| 4-8 | 配置发布四态(A004/A005/A006) | 校验→审核→激活,**必须带 `Idempotency-Key`** | ⚠️待点验(写操作) |
> **平台只提供 RBAC 只读查询**:没有改权限的写接口,权限变更走 `config_release` 发布。这是设计。
---
## 5. 风控(`risk_t` / `666666`)—— 对齐 `docs/风控业务演示文档/17-*.md`
| # | 操作 | 预期 | 证据 |
|---|---|---|---|
| 5-1 | 风险概览(RK001) | 总量/等级/待处理/超时/重点预警 | ✅ 200(total=2 pending=1 overdue=2 levels{高风险:2}) |
| 5-2 | 预警队列(RK002) | **每页 5 条**、筛选、分页 | ✅ 200(2 条:`ALDEMO0002`、`ALDEMO0001`)<br>⚠️ `limit` **上限就是 5**,传 20 会 422 且表格渲染不出来 |
| 5-3 | 队列筛选 | 关键词/客户号/风险等级/规则码/产品/时间 | ✅ `rule_code=RW-015 → ALDEMO0002`、`customer_no=T-CUST → 2 条`<br>⚠️ **风险等级填「高/中/低」**:预警对象用「高」,概览 `levels` 用「高风险」 |
| 5-4 | 预警详情(RK003) | 编号/状态/规则/证据/回执/客户 | ✅ 200(alert 23 字段 + customer 13 字段) |
| 5-5 | 八类证据(RK004) | `customers`/`products`/`transactions`/`capital_flows`/**`holdings`**/`login_records`/`alerts`/`notifications` | ✅ 8/8 全 200<br>⚠️ 是 **`holdings`**,写 `positions` 会被 422 |
| 5-6 | 通知记录(RK005) | 预警编号/类型/发送状态 | ✅ 200 |
| 5-7 | 手动扫描(RK006) | 200 `code=0`,**要带 `Idempotency-Key`** | ✅ 200 |
| 5-8 | 确认接收(RK007) | 二次确认;状态不符返回 409 | ✅ 409「只有待处理的预警才能确认解决」 |
| 5-9 | 进入调查(RK008)/ 关闭误报(RK009) | 误报**必须填理由** | ⚠️待点验(写操作) |
| 5-10 | 结案(RK010)/ 升级(RK011) | 结案填 `resolution`、升级填 `reason`;**需先确认接收** | ⚠️待点验(顺序:确认 → 调查/误报/升级/结案) |
| 5-11 | 证据上传(RK012) | **`multipart/form-data`** | ⚠️待点验 |
| 5-12 | 日报(RK013/RK014/RK015) | 生成 / **SSE 流式** / 多邮箱发送 | ✅ RK013 200;SSE 事件类型实测 `start`/`progress`/`replace` |
---
## 6. 投顾(`advisor_t` / `abc12345`)⭐ 本轮新增页面
登录入口同样是**员工登录页** `/portal/employee-console/login/`,登录后自动落到投顾工作台。
| # | 操作 | 预期 | 证据 |
|---|---|---|---|
| 6-1 | 用 `advisor_t` 登录 | 跳 `/portal/employee-advisor/dashboard/` | ✅ 实测守卫生效(此前会**静默弹回登录页**,已修) |
| 6-2 | 工作台列表 | 显示"**投资目标方案书 · 客户 9001**"卡片 + 发布时间 + 方案内容 | ✅ 接口实测返回 1 条(`content_id=1`、`customer_id=9001`、`type=investment_goal_book`、`published_at=2026-09-13T10:50:17`) |
| 6-3 | 点"刷新" | 重新拉取 `ADVISOR_PUBLISHED` | ⚠️待点验 |
| 6-4 | 用别的角色访问该页 | `risk_operator` 会被弹回风控页;`operator` 弹回运营页 | ✅ 实测守卫矩阵 |
**数据口径**(`ProductRecommendationService.published`):
按「**本人 + `sys_customer_assignment` 里名下归属客户**」过滤,且覆盖两类内容 ——
`investment_goal_book`(方案书,发布后 `review_status='published'`)与
`advisor_recommendation_plan`(推荐方案,审核后 `'approved'`)。
> **不用 `data_scope`** 的原因:投顾持有 `promotion:*` 这类 all 级权限,
> `IdentityService` 会把整个身份的 scope 抬到 `all`,按它判定会放开到**全部客户**。
> 归属关系来自 `sys_customer_assignment`(逐条授权 + 时间窗校验),比 scope 更窄。
>
> **本机归属数据**:`sys_customer_assignment` 只有一行 `(customer_id=9001, employee_id=9020, advisor)`。
> 所以投顾现在能看到客户 9001 的交付物;**换环境这条数据不跟着走,页面会是空态**。
### 6.5 方案书的审核与发布必须由管理员做
```
客户确认目标 → 管理员审核方案书 → 管理员发布方案书
```
`review_book` 与 `publish_book` 都带 `admin=True`(`investment_goal_service.py:174/216`)——
**投顾虽然有 `investment-goal:review` 权限码也做不了**。这是有意的复核环节,不是缺陷。
实测:投顾调用审核 → 403;管理员调用 → 200 ✅
---
## 7. 运营(`offsite_t` / `offsite123`)⭐ 本轮新增页面
| # | 操作 | 预期 | 证据 |
|---|---|---|---|
| 7-1 | 用 `offsite_t` 登录 | 跳 `/portal/employee-operations/dashboard/` | ✅ 实测守卫生效(此前同样静默弹回) |
| 7-2 | 场外邮件列表(`OFFSITE_MAILS`) | 分页列表:主题/发件人/时间/状态 | ✅ 200 `data=dict{items,page,page_size,total}` |
| 7-3 | 收件箱状态(`OFFSITE_MAILBOX`) | 邮箱 / 游标状态 / 阻塞标记 | ✅ 200 `data=dict{mailbox,status,blocked,last_uid,…}` |
| 7-4 | 用客户/风控访问这两个接口 | 被拦 | ✅ 客户 `code=403 当前角色不能操作场外基金流程`;风控 `code=403 缺少场外基金操作权限` |
> 场外线按**角色**收口(`operator`/`risk_operator`/`admin`/`super_admin`),不是按权限码。
---
## 8. 通用预期(跨页面)
| # | 情形 | 预期 |
|---|---|---|
| 8-1 | **业务失败也返回 HTTP 200** | 平台把业务错误放在 `body.code`(如生成失败 `HTTP 200 + code=422`),前端按 `error-codes.js` 解析 ✅实测 |
| 8-2 | 403 | "没有当前操作权限";按钮可见性应与服务端权限一致 ✅实测 |
| 8-3 | 404 | `SESSION_NOT_FOUND` 被**三个异常类共用**(会话不存在 / 客户不可访问 / 知识文档不存在),只能看 message ✅实测 |
| 8-4 | 幂等 | 写请求必须带唯一 `Idempotency-Key`(`api-client.js` 用 `idempotent: true` 标记)✅实测 |
| 8-5 | 多标签 | 各页面独立。**注意**:`auth.js` 现在以 cookie 为权威(跨标签同步),所以**同一浏览器两个账号不能并存** ✅实测代码逻辑 |
| 8-6 | 排版 | 文字不重叠、内容不溢出、表格行高稳定 ⚠️待点验 |
| 8-7 | 缓存 | 产品列表/详情页的 CSS/JS 已带 `?v=20260913-4`;若改了资源仍看不到效果,请硬刷新 ⚠️待点验 |
---
## 9. ⚠️ 看起来像 bug、其实是设计(测试时最容易被误判的三件事)
### 9-1 "客服答不上来,总是引导转人工" —— 这是置信度防线在工作
客服命中知识后要过两道阈值(`customer_service.py:148-150`):
```
HIGH_SCORE = 0.75 # ≥ 0.75 直接答
MID_SCORE = 0.55 # 0.55~0.75 之间,必须同时满足 MIN_GAP
MIN_GAP = 0.07 # top1 领先次优的最小间隙
```
**实测三条,逐条对上**:
| 提问 | top1 分数 | 次优 | 间隙 | 判定 |
|---|---|---|---|---|
| 基金定投是什么 | **0.8203** | 0.5886 | 0.2317 | ≥0.75 → **直接答** ✅ |
| 南方沪深300ETF的起投金额是多少 | 0.6818 | 0.6784 | **0.0034** | 间隙 < 0.07 → **转人工** |
| 购买基金需要什么条件 | 0.5221 | 0.5162 | 0.0059 | 分数 < 0.55 → **转人工** |
第二问如果硬答,会答成"**南方结构性存款(挂钩型):起投金额 20 万元**" —— 那是另一个产品,
而且客户问的是场内 ETF。**转人工在这里是正确行为**,符合你定的"不会的就转人工,首先要保证稳定性"。
> 真正要改的是**知识库内容**,不是代码:检索命中的是一套"南方科技有限公司 个人理财产品手册"
> 的素材(`PROD-*`/`FAQ-*`/`POL-*`),**里面没有本项目的场内 ETF 条目**,所以问 ETF 注定分数上不去。
### 9-2 "客户能读投顾的接口" —— 不是越权
`GET /api/v1/advisor/recommendations/published` 客户调用返回 200,但 SQL 里按
`customer_id IN (本人 + 归属客户)` 过滤,**读的是自己的**。风控返回 403 是因为没有
`product-recommendation:read:self` 权限码。用"能访问"判断越权会误判。
### 9-3 "投顾发布不了方案书" —— 是有意的复核环节
见 6.5。审核与发布都要求 `admin=True`。
---
## 10. 我做了什么验证(可复现)
| 项 | 方式 | 结果 |
|---|---|---|
| 18 个页面路径 + 12 个静态资源 | 逐个 GET | 全部 200 |
| 五个角色登录 | `POST /api/v1/auth/tokens` × 5 | 全部 200 |
| 各角色登录后落点与越权矩阵 | **真实执行 `auth.js` 的守卫函数**(node + DOM 桩) | advisor→投顾页、operator→运营页、risk→风控页、admin→工作台;交叉误入能弹回自己家 |
| 访客客服问答端到端 | 与浮窗同链路(V001 → R001 → 轮询 R002) | **4.11s / 4.09s / 4.82s**,三条全部 succeeded |
| 投顾页数据 | 走完整业务流(客户确认→管理员审核→管理员发布)后复查 | 投顾读到归属客户 9001 的方案书;客户只读到自己的;风控 403 |
| 平台门禁 | ruff / mypy / pytest / 表审计 / 文档检查 | ruff 干净 · **mypy 249 文件 0 错** · 单元+契约 **1376 passed** 2 skipped · 集成 **104 passed** · **89 张业务表** · 文档 55 份无编号冲突 |
| 前端 JS 语法 | 7 个文件按 ESM 解析检查 | 全部通过 |
**联调中发现并已修的问题**:
1. **`T001`/`T009` 恒 404「客户未开户」** —— `tools/seed_test_rbac.py` 把 `fund_account_status`
硬编码成 `'closed'`,与 `create_test_user.py` 的 `{customer: 已开户, employee: closed}` 不一致。
已改为按 `user_type` 取;配合 `seed_sim_account_demo` 后 T001/T006/T007/T009 全部 200。
2. **`rule_code` 筛选恒为空** —— `risk_repository.py` 用 `.contains([code])`,SQLAlchemy 编译成 `LIKE`,
而 `trigger_rule_codes` 是 JSON 数组。已改为 `func.json_contains(...)`,扫描去重处同一写法一并修。
3. **误提交的 `.agents/skills`(15 个文件)** —— AI 助手配置,与本项目无关,已移除并加入 `.gitignore`。
4. **投顾/运营登录后静默弹回登录页** —— `staffHomeForRoles` 缺这两个角色的落点,
进 workspace 后被 `requireAdmin` 弹回且**不带任何提示**,看起来像"登录没反应"。
已补两个落点与 `requireAdvisor`/`requireOperator` 守卫。
5. **访客客服浮窗一问即失败** —— 客服代码让访客走 `query_knowledge`,但发布配置的三个知识意图
只发了 `search_knowledge`,工具白名单直接抛 `ForbiddenAgentError`。已补发 `query_knowledge`
(保留 `search_knowledge` 给登录客户)。
6. **访客页的"演示数据"声明被删掉、但假数据还在渲染** —— 已恢复 `MOCK_SOURCE_NOTICE`、
两个页面的提示条与样式,并给 `link`/`script` 加版本参数(此前会被浏览器缓存)。
7. **发布脚本会静默丢提示词** —— `publish_customer_service_config.py` 只继承
`platform_config_item`,把 `customer_service_chitchat` 提示词漏在了旧版本里。
已改用 `effective_snapshot()` 读全三张受管表、补提示词搬运与条数硬校验;丢失的提示词已按原文恢复。
---
## 11. 已知问题(未修)
| 项 | 说明 |
|---|---|
| **知识库没有场内 ETF 内容** | 检索命中的是"南方科技理财产品手册"素材,问 ETF 类问题必然分数不足而转人工。**属知识内容工作,需业务提供素材后灌库** |
| `fin_knowledge_meta` 表为空 | **Milvus 里有知识**(`FAQ-*`/`PROD-*`/`POL-*` 都能检索到),但 MySQL 元数据表 0 行 ⇒ 管理端列表(`GET /api/v1/knowledge/list`)看不到内容、也无从删除。元数据与向量不一致 |
| ~~品牌名三处不一致~~ **已处理(2026-09-13)** | 已按项目方决定统一为「**南方财富**」:`customer_service_rules.py` 的防诈骗/转人工话术、`risk_agent.py` 与 `risk_analysis_service.py` 的风控助手名、`customer_service.COMPANY` 常量、`app/static/index.html` 旧联调页、风控页助手标题,以及闲聊提示词(已重发为 release **308** / v5)。<br>⚠️ **仍是旧品牌的地方**:Milvus 里的知识素材(`RAG-*`/`NF-*` 写"奶龙基金"、`PROD-*` 写"南方科技")—— 属知识数据,需重灌才能统一。<br>⚠️ **不能改**:`fin_product.fund_manager = "南方基金"`,它被 `market_quote_sync_service` / `product_history_sync_service` 当**过滤条件**用 |
| **知识库缺场内基金条目** | 已产出草稿 `docs/42-场内基金知识条目草稿.md`(含 20 只产品,数据取自 `fin_product`),**待审核后入库**。草稿里费率等缺失字段一律标"以交易页面为准",未编造数字 |
| `ADVISOR_GOAL` / `ADVISOR_ANALYSIS` 声明未用 | 投顾页只调了 `ADVISOR_PUBLISHED`;且 `portfolio-analysis` 的 body 必须是 `{}`(空模型 + `extra="forbid"`) |
| `widget.js` 里的 `portal:auth-changed` 监听 | 是死代码 —— `auth.js` 从不派发该事件(它用 BroadcastChannel) |
| 同一浏览器不能并存两个账号 | 见 8-5,是跨标签同步的代价,若不符合预期需要改回 |
---
## 附:演示账号
| 角色 | 用户名 | 密码 | user_id | 入口 |
|---|---|---|---|---|
| 客户 | `cust_t` | `123456` | 9001 | `/portal/customer/login/` |
| 风控专员 | `risk_t` | `666666` | 9002 | `/portal/employee-console/login/` |
| 管理员 | `admin_t` | `88888888` | 9003 | 同上 |
| **运营** | `offsite_t` | `offsite123` | 9006 | 同上(→ 运营工作台) |
| **投顾** | `advisor_t` | `abc12345` | 9020 | 同上(→ 投顾工作台) |
> ⚠️ `tools/set_user_password.py` 的内置演示规则**只写了 9001/9002/9003**;
> 9006 与 9020 的口令是建号时单独设的,**换环境重建时会没有密码**,需要用
> `python tools/set_user_password.py --user 9006 --password <口令>` 补设。
> 另:9004 `review_t`、9005 `offsite_worker` 是占位符,**登不了**。