Files
ai-kefu-jc/docs/superpowers/specs/2026-10-08-cc-ip-country-confirm-design.md
T

122 lines
7.8 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.
# 设计:号码区号补全(IP 推测 + 回显确认)— 终版 v2
- 日期:2026-10-08(v2 融合版:两轮独立审查 + 上游同日设计对照吸收 + 需求方三处优化)
- 前置:2026-10-08-dual-contact-and-country-code-design.md 及墙层增补——本设计
**替换**其中"请客户重发完整号"路径
- 对照:上游汽车项目同日设计 `D:\refactor\ai-kefu\docs\superpowers\specs\
2026-10-08-whatsapp-cc-completion-design.md`(吸收其 6 点,见 §8)
## 1. 目标
客户号码缺国家区号时:按 IP 推测 → 补全为 E.164 → **回显给客户确认**(顺带
暴露抄错的数字)→ 首封线索邮件即带补全号(零延迟、可拨、标注透明)。杜绝
发出拨不通的本地格式号码,同时不阻塞线索交付。
## 2. 架构(确定性优先,LLM 只做它擅长的)
| 职责 | 归属 | 理由 |
|---|---|---|
| 判定(有无码/建议国)、E.164 补全、状态机、邮件渲染 | **Python 确定性**(新模块 `cc_completion` + phonenumberslite) | 状态不可能被幻觉;可离线确定性测试;邮件触发不押在 LLM |
| 客户答语理解(肯定/否定/国名) | **extractor LLM**(schema 加 `cc_reply` 可选字段,单轮无状态分类) | "yeah that's right"/"naur"/"我在迪拜"正则极脆弱,恰是 LLM 强项;extractor 本每轮跑,零额外调用 |
| 话术生成与确认句必出 | 提醒注入 + **既有墙骨架**(白名单放行/缓冲替换——四轮生产实测过的机制) | GLM 对反惯性指令 0/8 服从的血泪教训,确认回显必须墙保 |
## 3. 判定树(`cc_completion.classify(number, ip_iso)`)
```
号码
├─ 以 +/00 开头 ──────────────────► HAS_CC(现有流程直走,不动)
├─ 以 IP 国码开头且去码后剩余合法 ──► HAS_CC_NAKED → 静默规范化(无对话动作)
├─ phonenumbers region=IP国 解析合法 ─► NEED_CC,建议=IP 国(高置信)
└─ 均不匹配(VPN/罕见格式)─────────► NEED_CC,建议=naive(去前导0+IP码,回显靠客户纠错)
IP 无效/未知 → NEED_CC 无建议国 → 问"方便发带区号完整号吗"(1 轮为限)
```
## 4. 状态机(每 canon 组独立,存 `raw_info["_cc"]` 结构化 dict)
```python
raw_info["_cc"] = {
"whatsapp": {"e164": "+254712345678", "intl": "+254 712 345 678",
"confirmed": false, "attempts": 0,
"source": "ip_proposed", # ip_proposed|naive|ip_confirm|
# prefix_normalized|customer_stated|no_ip|fallback
"canon": "0712345678"}}
```
- **canon 组**:phone 与 whatsapp 同号(canon 相同)共享一个确认流,确认一次;
不同号各自独立状态——**换号(canon 变化)即重置该组状态机重走判定树**
(结构性解决"改号残留":旧补全永不跟随新号)
- attempts 只被 canon 变化重置;国名重述不重置(防"无限换国名")
- **回显确认轮**(NEED_CC 且有建议国):AI 注入逐字句 "…reach you at
{intl} — is that right?"(中英双语);**墙白名单**放行该精确号码
(`no_contact_value(allow_number=...)`,逐匹配段 digits_only 比对,只放
系统注入的那一个号);**确认句墙**(缓冲机制)保证必出
- 答语(下一轮 extractor 的 `cc_reply`):
- `affirm=true` → `confirmed=true, source=ip_confirm` → 落定
- `affirm=false, country=X` → `NAME_TO_REGION[X]`(中英映射表)转 ISO 重算
e164 → 再回显(attempts+1)
- `affirm=false` 无 country → 请重发完整号(新号到达=canon 变化自然重置)
- 无回应/不可解析 → attempts 随轮递增,**每渠道上限 2 轮** → 落定
`confirmed=false`(保持现有 e164 建议,不无限纠缠)
- **内部状态卫生(三通道封堵,上游对照吸收)**:
1. extractor 入口 `$existing_info` 过滤 `_` 前缀键(我们已有,复核)
2. **extractor 出口:消费 `cc_reply` 后即剥离**(瞬态分类非事实,不残留)
3. **extractor 出口:防御性丢弃任何回传的 `_` 前缀键**(防 LLM 回显 `_cc`
覆盖 Python 管理的状态机——上游发现的洞)
- **存量迁移**:升级部署时存量 `_cc_asked=True` 且号码裸的会话 → 映射为
`_cc{confirmed:false, source:fallback}`(已问过,不再追问,直通邮箱轻补)
## 5. 邮件/推送时序(需求方定案:首封即发补全号)
| 时点 | 发什么 | 标注 |
|---|---|---|
| 首封(裸号 confirmed 当轮,**零延迟**) | 判定树同步算出的 **e164 补全号**(ip_proposed/naive/prefix_normalized) | `confirmed=false` 时尾注 **"(区号按 IP 推测,未经客户确认)"**;`no_ip` 时裸号 + **"(缺国家区号)"** |
| 客户确认(号码未变) | **不补发**(首封已可拨;后续渲染自动去标注) | 状态升级透明 |
| 客户纠正/换号 | 现有**改号更正通道**(canon 变化自然触发) | 现有机制零新逻辑 |
- 渲染点:email_service(正文联系方式行 + 更正/回头客路径,签名加参取 `_cc`);
lead_push 的 contact 给干净 e164(契约要求"带国际区号且有效"),标注走
profile 派生(出站时由 `_cc` 计算,不落正常键——零 LLM 泄漏面)
- `profile_out` 先消费 `_cc` 生成补全 → 再过滤 `_` 键(顺序钉死)
- **面板同步**:cc-pending 期间 `ev.contact_info` 带未确认标注(防"邮件带
补全、后台显裸号"的分裂)
- 邮箱轻补串行:号码确认流落定(confirmed 或 attempts 耗尽)后才注入
## 6. IP 入口与基础修复
- `/session` 路由注入 `Request`,XFF 第一段合并进 `conv.client_info`(WS 路由
已有同款;chat_rest.py:107 只喂限流修不到数据流)
- `ip_service` 新增 `resolve_region_code(ip) -> ISO`(iptocc 直透);
**修既有 bug**:`"AU": "加拿大"` 重复键(应为 CA)
## 7. 依赖与测试
- `requirements.txt` + `phonenumberslite`(~3MB、零传递依赖、离线、Apache 2.0)
- 测试(离线确定性 + 集成):
1. 判定树矩阵(号码形态 × IP 国,含静默规范化分支)
2. 状态机全转移(确认/否定/国名/换号重置/2 轮无回应/国名重述不重置/
同 canon 组共享)
3. 三通道卫生(入口过滤/出口剥离 cc_reply/丢弃 `_` 回显)
4. 邮件时序(首封带补全+标注、确认后不补发、改号走更正、no_ip 兜底)
5. 墙(白名单精确放行:同号放/异号拦;确认句缓冲替换)
6. 存量迁移;推送 contact 纯 e164;`NAME_TO_REGION` 映射
7. 真实 LLM 验收(cc_reply 分类质量、回显确认全流程、误判对照)
- 既有测试迁移:D1 断言"confirmed 即发"若 fixture 用本地格式号 → 更新为
"confirmed 且判定树已同步跑"(行为等价,渲染值变化)
## 8. 与上游设计的对照结论
吸收:判定树静默规范化分支、`_cc` 结构化+canon 重置、extractor 答语分类、
出口卫生三封堵、同 canon 组共享、面板同步闸。
不采纳:上游"待确认暂缓发信"(违背零阻塞——以首封补全号+标注替代,兼得
及时性与可拨性);上游墙豁免参数(我们已有四轮实测的墙骨架+白名单)。
## 9. 风险
| 风险 | 等级 | 缓解 |
|---|---|---|
| IP 误判(VPN)致首封补全号错 | 中 | 标注"未经客户确认"透明 + 客户否定走重算/改号更正双通道 |
| naive 补全结构错 | 低 | 回显确认天然暴露;confirmed=false 标注兜底 |
| cc_reply 分类错误 | 低 | 最坏=多问一轮或保持未确认标注;attempts 上限防纠缠 |
| NAME_TO_REGION 覆盖不足 | 低 | 映射失败=未捕获,attempts 递增走兜底 |
| 墙白名单滥用扩大 | 低 | 逐段精确比对 + 评审断言锁死 |