W34 · 会签 20/21/22 三项落地(先立单、经授权、后动手) - 会签 20(白名单外):runtime_config_service 新增 load_collection_routes() / collection_routes() / _first_collection_name(),首次消费既有 JSON 列 collection_routes; 消费方 customer_service 走「配置优先、缺失回落代码常量」。零 DDL;该列当前全为 None ⇒ 实际走回落路径,行为与改动前一致。 - 会签 21(类 3 + 融合层):retrieval_fusion 新增 fuse_rrf() + RRF_K(排名融合,只吃名次 不吃分数 ⇒ 异质分数不可能污染判定分,best_vector_score 仍只取向量路原始 cosine); knowledge_search_service::search() 新增 literal_parallel: bool = False(默认值使行为 逐字等同现状)。工具层透传未做 —— 那需改 KnowledgeSearchInput 契约(extra="forbid"), 超出本单范围。 - 会签 22(发布配置 + bootstrap):customer_service 新增 INTENT_MARKET_QUOTE 常量 + TREND_WHITELIST_INTENT_CANDIDATES(按优先级回落)+ _trend_whitelist_intent() (运行时自检 + 自动回落,强于「仅报错」)。发布配置 customer_service:market_quote (release 260,allowed_tools=['query_fund_trend'])已写入并回读校验(9 → 10 行)。 刻意未加入 supported_intents:授权维度与判定维度解耦,不动判定分布。 - 阶段 0:customer_service_rules 新增 normalize_query() + QUERY_SYNONYMS + 等级代号大写 (纯函数;同义表只收纯书写差异,语义类同义留待金标 A/B 后逐条加;调用方默认不启用)。 W35 · 判定口径与融合口径分离(修 A-01 / C-04 / I-02 / E-04 四条) - _dual_route_output 返回值新增 vector_order(向量路原始 doc_id 顺序、去重); - 新增 _vector_decision_hits() 据此还原「判定序列」(带向量分的 basic 补位块回补首位; 无 vector_order / 空 / id 全对不上 ⇒ 返回 None 回落原分支); - _answer_from_knowledge 的 score / gap 与原文直返的 best 改从向量路原始序列取。 语义边界(刻意):_evidence_pack / _exit_clarify / _answer_from_evidence 仍吃融合序列 —— 融合的收益只留在「给哪些块、什么顺序」,符合三层分数分离约束。 W36 · 选块口径归一(收口最后一条 E-01) - 新增 _pack_order():order 命中的块排前,其余按原相对顺序追加在后; - _evidence_pack 新增 order= 参数,三处遍历 hits → ordered,top 由 hits[0] → ordered[0]; - _answer_from_knowledge 传入 order=[judge 的 doc_id 序列];judge is None ⇒ None (开关关闭时逐字零改动)。order 只当排序键、不当过滤器 ⇒ 证据包成员集合不变。 演示环境与文档 - start.ps1 / demo.ps1 默认端口 8000 → 8099(与 README / docs/06,07,09,14,15,32 / tools/smoke_check.py / login_console.py 的全仓口径对齐;字节级定长替换,保住 UTF-8 BOM + CRLF,字节数不变); - portal/README.md 更正 fin_nav_history 过期口径(「0 行」→ 实测 2494 行 / 20 个产品 / nav_date 覆盖 2026-03-18—2026-09-13)。 测试(新增 3 个文件、补强 2 个) - 新增 tests/unit/service/test_decision_scope_w35.py(10 条)、 tests/unit/service/test_evidence_pack_order_w36.py(13 条)、 tests/unit/service/test_customer_service_trend_chart_inv8.py(INV-8 字面级判定, 纳入 pytest 门禁,此前只在 jsdom 脚本里覆盖); - 补强 tests/unit/core/test_customer_service_rules.py(normalize_query 7 条)与 tests/integration/test_customer_service_trend_chart_persistence.py(图内每个数字 都必须在答复正文出现过,判定口径与 INV-8 单测一致)。 验证 - 全量 pytest:2642 passed / 3 skipped / 0 failed(基线 2629 + 新增 13); - 55 条金标真实链路 A/B 四组:off / norm / dual / on 均 55/55 = 100% (改前 dual 92.7%、on 90.9%);M-4 事实正确率恒 100%、M-7—M-10 全 0; - 红线四条守住:融合/精排层仍不持 Milvus 客户端(INV-1)、阈值一字未动、零 DDL; - 三个实验开关 CS_DUAL_ROUTE / CS_RERANK / CS_QUERY_NORM 仍默认关闭。
Portal 路由与角色映射
正式门户使用原生 JavaScript、原生 CSS 与同源 fetch,静态资源统一从 /static/portal/ 加载。
| 路由 | 角色 | 数据源 |
|---|---|---|
/portal/guest/home/ |
访客 / 全部 | P001(推荐位取前 3 只) |
/portal/guest/products/ |
访客 / 全部 | P001 |
/portal/guest/product-detail/?code= |
访客 / 全部 | P001(不含历史净值曲线) |
/portal/customer/login/ |
未登录客户 | A034 |
/portal/customer/dashboard/ |
customer / admin | T001、T002 |
/portal/customer/holdings/ |
customer / admin | T006 |
/portal/customer/profit-loss/ |
customer / admin | T001 |
/portal/customer/orders/ |
customer / admin | T003 |
/portal/customer/transactions/ |
customer / admin | T007 |
/portal/customer/cash-ledger/ |
customer / admin | T009 |
/portal/customer/risk-questionnaire/ |
customer | ONB001、ONB002 |
/portal/employee-console/login/ |
未登录员工 / 管理员 | A034 |
/portal/employee-console/workspace/ |
admin / super_admin | A002-A006、A012、A033、A035-A040、客服转人工管理接口 |
/portal/employee-risk/dashboard/ |
risk_operator / admin / super_admin | /api/v1/risk/**、R001-R003 |
/portal/employee-advisor/dashboard/ |
advisor / admin / super_admin | /api/v1/advisor/recommendations/published(本人 + 名下归属客户的已发布交付物) |
运营账号默认进入 /portal/employee-operations/offsite/ |
operator / admin / super_admin | /api/v1/offsite-fund/mails、/api/v1/offsite-fund/mailbox-status |
投顾页的数据口径:接口按「本人 +
sys_customer_assignment里名下归属客户」过滤,且同时覆盖investment_goal_book(方案书,发布后review_status='published')与advisor_recommendation_plan(推荐方案,审核后review_status='approved')两类内容。 方案书的审核与发布都要求管理员(investment-goal:review/publish都带admin=True), 投顾自己发不出来 —— 这是有意设计的复核环节,不是缺陷。
客服浮窗(common/customer-service-widget/)由公开首页、基金产品页和客户工作台统一挂载,
挂载点是 common/layout/app-shell.js 的 mountShell() —— 按页面形态白名单
(public / customer)挂,员工四类工作台刻意不挂;样式也是同一个地方注入,
避免九个页面各写一份 ?v= 版本号。
访客使用 /api/v1/visitor-tokens
获取短期令牌(角色 visitor,权限只有 agent:run + knowledge:query),因此必须走
query_knowledge 这个工具名;登录客户走 search_knowledge。两者都要出现在发布配置的
agent_tools/customer_service:<intent> 白名单里,缺哪一条,对应人群就一问即失败。
公开产品数据来自 GET /api/v1/products(编号 P001,见 docs/05 §19),
产品与净值取自 fin_product(status='上市')、行情取自 fin_market_price 的最新一行。
访客页面通过 common/visitor-token.js 取短期访客令牌后调用;该文件是访客令牌的唯一实现
(客服浮窗也用它),不要在页面里另写一份,否则存储 key 与过期判断迟早不一致。
公开页一律用访客令牌,即使浏览器里还留着登录令牌:api-client.js 对调用方
显式传入的 Authorization 不再覆盖(此前会覆盖,于是同一个公开页对访客和已登录
用户显示不同内容)。客户工作台则相反,走登录令牌并建会话(C001),
这样多轮澄清与「转人工」才能挂到同一个 session_id 上。
公开产品数据不得与登录后的真实账户数据混用。
三点口径(改前端前先读):
change_pct可能是null(行情只同步过一个交易日时算不出涨跌)。 调用方必须显示"暂无",不得当成0——formatPercent收到null会渲染成+0.00%, 那等于告诉客户"今天平盘"。- 历史净值曲线已有数据源(
W31起接入;本节此前写的"0 行"已过期):fin_nav_history实测 2494 行 / 20 个产品,nav_date覆盖2026-03-18—2026-09-13, 由tools/sync_nav_history.py从东财净值接口同步。详情页renderChart取到点位就画折线, 仅当该产品没有历史时才退化为"历史净值数据尚未接入,暂不展示走势图"。 ⇒ 演示走势图请用真实在场内、已同步的产品(如159382、511810)。 此前那条曲线曾是 mock 里 12 个编造点位 —— 走势图最容易被当成真业绩,故此处口径必须准确。 - 产品级披露在
common/product-notes.js(如 510300 的"同指数参考产品,非本公司发行")。fin_product没有这个字段,所以它留在前端;新增需要披露的产品时改那一份。 凡渲染公开产品的页面都要挂data-source-notice说明数据来源。
客户页面均由 common/auth.js 执行入口守卫,接口路径只在 common/api-client.js 的端点表登记。