Files
group_fqcd_jr/app/core/exit_codes.py
T

101 lines
5.2 KiB
Python
Raw Normal View History

"""客服 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",
})