Files
group_fqcd_jr/app/core/exit_codes.py
T
张胜宇 da1d14cb31 chore(sync): zsy_developcc 全量同步至 qyqy_develop(W27 口径)
- 分支内容对齐 qyqy_develop 3e24033,树完全一致(同步后 git diff 为空)
- 覆盖本轮 W27 交付:L0 表层判定层 + 出口 E6 行情 + 收益过滤槽位白名单
  + 免责声明分档 + 金标扩容至 55 条(全绿)+ 配置版本 244 已发布
- 新增 app/core/exit_codes.py、app/service/fund_trend_service.py 及 3 个测试文件
- 新增 开发文档\D3.9-客服Agent智能路由与行情出口设计-2026-09-21.md
- 基线:e239eb7(2026-09-17 品牌口径统一快照),本提交为其直接后继
2026-09-21 22:33:09 +08:00

101 lines
5.2 KiB
Python
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.
"""客服 Agent 的**出口码注册表**(`W27` 新增,`D3.9` §3.1 / §6.2)。
## 为什么要有这个模块
`D3.7` §3 记着一条老口径:「旧实现**没有** `E1`—`E5` 出口码」,评测探针只能靠"包装终止型
方法"给每条打标。那套包装法有两个代价:
1. **它测的是探针的包装,不是产品声明** —— 出口改名/换位置,标就变了,而 `D3.7` 的
验收结论是拿这些标算出来的;
2. 有些**行为正确但没出口方法**的分支(例如 `E8` 风险等级代办拦截)会被记成
「没有任何出口」,于是**做对的安全行为被判未达标**(`D3.9` §2 的度量失真)。
因此 `W27` 起改为:`CoreResult.exit_code` 由**出口自己声明**,读侧不反解文本。
第一个(也是目前唯一)消费方是门禁 `F5` 的**免责声明分档**(`DEC-W27-10`):
业务出口附完整投资免责话术,非业务出口只附轻量提示。
## 与 `D3.6` / `D3.7` 的编号关系
本模块的编号**直接沿用** `D3.6` §3 的出口码(`E1` 澄清 / `E2a`—`E2e` / `E3` / `E4` /
`E5a`—`E5c`),并补三个 `D3.6` 未给码的出口:
- `E0`:`L0` 表层判定层本身(`D3.9` §3.1)。它是**路由层**不是终态出口,因此
**不写入 `CoreResult.exit_code`**,只作为日志/审计的分组名;
- `E6`:行情出口(`D3.9` §4);
- `E8`:风险等级代办拦截(`D3.7` 的 `E-08` 红线 1 ④);
- 安全出口沿用 `SafetyRoute.priority`(`P0` / `P1` / `P2` / `COMPLIANCE` /
`PROMPT_INJECTION` / `ADVICE_BOUNDARY`),`D3.7` §3 已按这个口径给安全出口打标;
- `LOGIN` 沿用 `D3.7` 金标里已经在用的写法(`H-03` 的期望出口并列里就有它);
- `CHAT` / `CONTACT` 是本轮新增:闲聊与"问渠道联系方式"没有历史编号,但
`D3.9` §6.2 明确把它们列入**非业务档**,所以必须有一个可声明的码。
"""
from __future__ import annotations
# ---- `L0` 表层判定层(路由分组名,不写入 `CoreResult.exit_code`) ----
EXIT_SURFACE = "E0"
# ---- 出口 `E1` / `E2` 族:澄清与计算型 ----
#: `E1`:澄清。**非业务档**(`D3.9` §6.2)—— 「请问您想了解哪一只」不该挂投资风险提示。
EXIT_CLARIFY = "E1"
#: `E2a`:类别费率试算(给算法 + 区间,不给单一结论金额)。
EXIT_CALC_CATEGORY_FEE = "E2a"
#: `E2b`:单只产品赎回费递进。
EXIT_CALC_REDEMPTION = "E2b"
#: `E2c`:`C—R` 通用匹配规则(矩阵一格)。
EXIT_SUITABILITY_RULE = "E2c"
#: `E2d`:适当性裁决(产品风险等级 × 客户档案等级)。
EXIT_SUITABILITY_VERDICT = "E2d"
#: `E2c-my`(`W21` 新增):**以本人等级为对象**的可购买范围清单。
#: 与 `E2c` 的分工是"问句里有没有自带参数":`E2c` 收 `C1 能买 R3 吗` 这类自带 C/R
#: 的规则题,本码收 `我能买什么等级的产品` 这类**只问范围**、等级从画像里取的问法。
EXIT_ELIGIBLE = "E2c-my"
#: `E2e`:本人画像 / 客户分层作答(`query_customer_profile`)。
EXIT_PROFILE = "E2e"
# ---- 出口 `E3` / `E4`:知识直返与证据约束生成 ----
EXIT_KNOWLEDGE_DIRECT = "E3"
EXIT_EVIDENCE = "E4"
# ---- 出口 `E5` 族:分级回退 ----
#: `E5a`:知识侧的澄清(候选来自当前可见档位)。**非业务档**。
EXIT_CLARIFY_KB = "E5a"
#: `E5b`:部分答 + 引导。**业务档**—— 即使正文为空,客户拿到的也是"业务上答不上来",
#: 该给的投资风险提示一个字都不能少(这正是分档判据必须用出口码的原因)。
EXIT_PARTIAL = "E5b"
#: `E5c`:转人工。**业务档**(安全与责任披露最不能省的一档)。
EXIT_TRANSFER = "E5c"
# ---- 出口 `E6` / `E8` ----
#: `E6`:行情出口(净值序列 / 区间涨跌)。数字来自受控数据源,**业务档**。
EXIT_QUOTE = "E6"
#: `E8`:风险等级代办拦截(`D3.7` `E-08` 红线 1 ④)。业务档。
EXIT_RISK_LEVEL_GUARD = "E8"
# ---- 无编号出口:沿用既有语义名 ----
#: 闲聊。非业务档。
EXIT_CHITCHAT = "CHAT"
#: 引导登录(访客请求超出公开服务范围)。非业务档;`D3.7` 金标已用 `LOGIN` 这个名字。
EXIT_LOGIN = "LOGIN"
#: 问渠道联系方式(公开信息,不是提交投诉)。非业务档 —— 「电话是多少」后面跟一句
#: 投资风险提示属于挂错话术(`D3.9` §6.2 的例证之一)。
EXIT_CONTACT = "CONTACT"
#: **非业务档**出口集合:门禁 `F5` 只对这些出口追加**轻量提示**,其余一律追加完整
#: 投资免责话术。集合取"白名单式"是刻意的 —— 新增出口若忘记登记,默认落到**业务档**
#: (多一句披露),失败方向与 `INV-5`(收敛)一致。
NON_BUSINESS_EXIT_CODES = frozenset({
EXIT_CLARIFY,
EXIT_CLARIFY_KB,
EXIT_CHITCHAT,
EXIT_LOGIN,
EXIT_CONTACT,
})
#: 安全出口的优先级标签(与 `app/core/customer_service_rules.py` 的
#: `SafetyRoute.priority` 同源)。列在这里是为了让"哪些 priority 是安全出口"可被
#: 静态检查与测试引用,而不是散落在注释里。
SAFETY_PRIORITIES = frozenset({
"P0", "P1", "P2", "COMPLIANCE", "PROMPT_INJECTION", "ADVICE_BOUNDARY",
})