feat(cs)+docs: 修掉 P1 错分「风险测评结果」—— 画像问答改由受控工具作答(W15)

背景(用户提问触发):
  §1.2.1「客户能看本人的持仓/交易/账户/画像与风评」与 §1.4.5 P1
  「账户与个人数据(含风险测评结果)Agent 无权限读取」读起来互相矛盾。

实测根因(两处):
  1) route_message() 在画像分支之前,且 P1_KEYWORDS 含裸词「风险测评结果」
     ⇒「我的风险等级是多少」走画像作答,
       「我的风险测评结果是什么」被降级成「无法读取本人账户数据」
       —— 同一诉求两种结论,属「能答而不答」(H-03 同类)。
  2) 画像词在前、账户词在后的混问法漏网
     (「我的风险测评结果和持仓一起给我」落 P3 ⇒ 只答画像、静默忽略账户诉求)。

依据(决定性):D2.2 §1.7 第 21 项「画像问答字段直返」;
  D3.1 §0.3 术语表「画像问答属客服能力,与持仓查询严格区分」。

代码:
  - app/core/customer_service_rules.py:P1_KEYWORDS 移除裸词 + 口径说明;
    P1_PATTERNS 新增混问法守卫(第一人称 + 画像词 + 并列连词 + 账户词)。
  - customer_service.py / profile_projection.py:补「投影层白名单 ⊇ 客服对话
    渲染集」口径(total_asset / behavior_score / risk_tags 刻意不陈述,
    渲染它们等于用画像工具绕过 P1)。

守卫:
  - test_customer_service_rules.py:RT-004 → None;新增 RT-004b → P1;
    SAFETY_CASES 由字面区间改显式名单(RT-004b 字母后缀会落区间外被静默漏掉)。
  - test_customer_service_agent.py:画像端到端 3 条 + 混问法反向守卫。

安全不降:答案只来自 query_customer_profile(self 作用域 + 字段白名单 + 工具
  审计),查不到失败关闭、绝不猜等级;P0/P2 未动、P1 其余字面未动;
  混问法仍走 P1;访客问画像仍引导登录。

文档:D2.2 v2.5→v2.6 / D3.1 v2.4→v2.5 / D2.6 更正 / D4.6 追加 §3(不改正文)
  / D1.1 §24 + 版本位 / D1.6 §4.41 修正误记 + §4.42 / D2.1 v6.29。

实测:pytest 1914 passed / 3 skipped / 0 failed(+5);ruff 20(无新债);
  check_authoritative_docs 54 文档无冲突;_consistency GATE PASS;
  http_probe 11/11;定向真机复验 9/9;portal_api_check 35/0/5;
  e2e_smoke 31/31;fe_boundary 12/12;demo.ps1 五项自检全过。
This commit is contained in:
张胜宇
2026-09-20 16:22:02 +08:00
parent 56cb15342b
commit c3d57fbf3f
12 changed files with 493 additions and 26 deletions
@@ -111,3 +111,37 @@
> **维护责任**:留痕件。**不随实现变化修改正文**;后续轮次若需更新结论,一律**追加新的一节**并注明日期与依据。
> 落档:2026-09-18(`D2.1` `C-06-a`)。
---
## 3. 2026-09-20 `W15`:`RT-004` 判定口径更正(**追加节,正文未改**)
> 本节按上文「维护责任」的要求**追加**,不修改第 1 节原样抄录的正文与第 2 节的 `C-06` 回填。
**改了什么**
| 项 | 原文(第 1 节,2026-09-16 基线) | 现口径(2026-09-20 起) |
|---|---|---|
| `RT-004` 输入「我的风险测评结果是什么」 | 预期路由 `account_entry`;预期结果「引导独立账户入口」;**禁止现象「推断或返回风险等级」** | 预期落 **画像出口**(`is_profile_question()` → 受控工具 `query_customer_profile`),以**字段级只读**作答风险测评等级 |
| `RT-004b`(新增反向守卫) | —— | 输入「我的风险测评结果**和持仓**一起给我」⇒ 仍必须走 `P1`,**禁止**只答画像、静默忽略账户那半句 |
**为什么改(依据)**
1. `D2.2` v2.6 **§1.7 第 21 项**:画像字段级读取(`risk_level` / `customer_level`)「只读,且仅用于确定性规则(适当性过滤 / 转人工优先级 / **画像问答字段直返**)」。
2. `D3.1` v2.5 **§0.3 术语表「画像问答」**:「客服可回答的客户画像类问题(既有实现入口 `is_profile_question()` → `query_customer_profile` 工具)。**与「持仓查询」严格区分**:画像问答属客服能力,持仓查询不属」。
3. `D2.2` v2.6 **§1.2.1** 三类主体表:客户「能看什么」含「自己的画像与风评」。
4. **实测事实**(`W15`):`route_message("我的风险等级是多少")` → `None`(走画像作答),而 `route_message("我的风险测评结果是什么")` → `P1`(降级成「无法读取本人账户数据」)—— **同一诉求两种结论**,属「能答而不答」,与 `H-03` 同类。
**为什么不算「变松」(安全不降)**
- 答案**只来自**底座受控工具 `query_customer_profile`:`context.user_id` 自我作用域、字段白名单投影(`PROFILE_FIELD_POLICY`)、工具调用留审计;模板不推断、不承诺收益。
- 查不到时 `_exit_profile_miss()` **失败关闭**为「如实告知 + 引导自助」,**绝不猜一个等级**(原「禁止现象」中「**推断**」一词仍然成立)。
- 原「禁止现象」里的另一件 —— **不返回账户数据** —— 仍然成立:`P1` 的其余字面(持仓 / 收益 / 订单 / 银行卡 / 投诉进度)一条未动,且新增 `RT-004b` 守住**画像 + 账户混问法**。
- 改动是**移除误分类的关键词裸词**(`P1_KEYWORDS` 去掉「风险测评结果」),不是放宽任何 `P0` / `P2` 分支。
**守卫落点**
- `tests/unit/core/test_customer_service_rules.py`:`RT_INPUT_BASELINE` 的 `RT-004` 改 `None`、新增 `RT-004b` 为 `P1`;`SAFETY_CASE_IDS` 由字面区间改为**显式名单**(避免字母后缀落区间外被静默漏掉)。
- `tests/unit/service/test_customer_service_agent.py`:新增端到端用例(三条问法必须调到画像工具且**不出现** `P1_REPLY`)+ 混问法反向守卫。
> 落档:2026-09-20(`D2.1` `W15`)。