diff --git a/docs/superpowers/plans/2026-10-08-dual-contact-and-country-code.md b/docs/superpowers/plans/2026-10-08-dual-contact-and-country-code.md index 27c4858..839db06 100644 --- a/docs/superpowers/plans/2026-10-08-dual-contact-and-country-code.md +++ b/docs/superpowers/plans/2026-10-08-dual-contact-and-country-code.md @@ -48,40 +48,46 @@ def test_reask_wall_channel_precision(): - [ ] **Step 2: 跑红** `python -m pytest tests/test_boundary_filter.py::test_reask_wall_channel_precision -v` → TypeError(asks_contact 不收第二参) -- [ ] **Step 3: 实现**。boundary_filter.py 把 asks_contact 替换为(保持 REASK_PATTERNS 不动,新增渠道映射层): +- [ ] **Step 3: 实现**。boundary_filter.py 把 asks_contact 替换为(保持 REASK_PATTERNS 不动,新增渠道映射层)。 +⚠️ 分支顺序是评审修正版(plan review Issue 1:初版顺序会让 Step 1 的两条断言必挂——"泛词+仅邮箱"和"精确+泛同句"两个分支被兜底吞掉)。判定顺序固定:①在手精确词→丢;②泛词+有号码渠道→丢;③命中但句中无任何渠道词→保守丢;④其余→放行: ```python # 渠道精确化(2026-10-08 spec §4.4): REASK 模式命中后按渠道词分辨—— # 索要已在手的渠道才丢,索要缺失渠道放行(双要补邮箱场景);泛渠道词 -# (number/phone/contact/电话/联系方式/号码)只要手上有任何号码渠道即丢, -# 宁丢勿放防重复索要;精确+泛同句共存时任一命中判丢。 -_PRECISE_CHANNEL_RE = re.compile( - r"whatsapp|e-?mail|邮箱|微信|wechat", re.I) -_GENERIC_CONTACT_RE = re.compile( - r"(?:^|\b)(?:number|phone|contact|电话|联系方式|号码)", re.I) +# 只要手上有任何号码渠道即丢(宁丢勿放);精确+泛同句共存时按①②自然 +# 实现任一命中判丢。中文泛词不用 \b(CJK 无词边界,"电话"前 \b 永不成立)。 +_PRECISE_CHANNEL_RE = re.compile(r"whatsapp|e-?mail|邮箱|微信|wechat", re.I) +_GENERIC_CONTACT_RE = re.compile(r"\b(?:number|phone|contact)\b|电话|联系方式|号码", re.I) + + +def _precise_channels_in(s: str) -> set: + out = set() + for m in _PRECISE_CHANNEL_RE.finditer(s): + w = m.group(0).lower() + out.add("email" if w in ("e-mail", "email", "邮箱") + else "wechat" if w in ("微信", "wechat") else "whatsapp") + return out def asks_contact(sentence: str, confirmed_channels: set | None = None) -> bool: """输出句是否在索要联系方式(已留资时用于丢弃)。 confirmed_channels=None → 原行为(命中即丢,向后兼容); - 传入已确认渠道集合(如 {"whatsapp","phone"}) → 渠道精确化。""" + 传入已确认渠道集合 → 渠道精确化(①②③④判定,见上注释)。""" s = sentence or "" - if confirmed_channels is None: - return any(p.search(s) for p in REASK_PATTERNS) - if not any(p.search(s) for p in REASK_PATTERNS): - return False - has_number_channel = bool( - confirmed_channels & {"whatsapp", "phone", "wechat"}) - for m in _PRECISE_CHANNEL_RE.finditer(s): - w = m.group(0).lower() - ch = ("email" if w in ("e-mail", "email", "邮箱") - else "wechat" if w in ("微信", "wechat") else "whatsapp") - if ch not in confirmed_channels: - return False # 索要缺失的精确渠道 → 放行 - if has_number_channel and _GENERIC_CONTACT_RE.search(s): - return True # 泛词 + 手上有号码 → 丢 - return True # 命中但无精确渠道词(纯泛词/其他形态) → 保守丢 + hit = any(p.search(s) for p in REASK_PATTERNS) + if confirmed_channels is None or not hit: + return hit + has_number = bool(confirmed_channels & {"whatsapp", "phone", "wechat"}) + precise = _precise_channels_in(s) + has_generic = bool(_GENERIC_CONTACT_RE.search(s)) + if precise & confirmed_channels: # ① 句中有在手精确渠道词 + return True + if has_number and has_generic: # ② 泛词 + 手上有号码渠道 + return True + if not precise and not has_generic: # ③ 命中但无渠道词 → 保守丢 + return True + return False # ④ 精确词全缺失/泛词但无号码 → 放行 ``` - [ ] **Step 4: 跑绿**(该测试 + 现有全部 REASK 测试不回归) @@ -98,6 +104,14 @@ if has_contact and asks_contact(sentence, _confirmed_channels): elif has_contact and asks_contact(pending, _confirmed_channels): ``` +- [ ] **Step 5b: 更新两处被新语义打破的既有测试**(plan review Issue 2): + `tests/test_lead_email_trigger.py` 的 `test_reask_wall_drops_ask_sentence_when_contact` 与 + `test_reask_wall_all_dropped_falls_back_to_closing`(场景=客户留 phone、AI 问 + "Got your WhatsApp?"、期望丢句)——新语义下 WhatsApp 是缺失渠道 → 放行。 + 改法:测试场景改为 AI 问**已在手的渠道**(如 "Got your phone number?")保持 + 丢句语义;同时各补一条"有 phone 在手问 WhatsApp → 放行并流出"的断言 + (钉死 spec 未明说的语义决定:号码渠道间互视为可补全)。 + - [ ] **Step 6: 全量绿 + commit** ```bash @@ -212,7 +226,13 @@ def test_contact_completion_note_serial_priority(): - [ ] **Step 5: 接线 _build_context_reminder**(有画像路径 parts 拼接处追加 note;空画像早退路径不注入——没联系方式谈不上补全)+ stream 方法流式前写回计数键 -- [ ] **Step 6: 集成测试**(spec §6-6 计数持久化)——用同文件现有 manager 流程测试风格构造两轮对话:第一轮后查 DB `raw_info` 含 `_cc_asked`、第二轮 reminder 不再含追问句 +- [ ] **Step 5b: 改写 conversation.py:646 确认路径提醒句的硬禁令**(plan review Issue 3——防自锁不只 system.md 一处): + 该句现为"绝对禁止再索要任何联系方式!",与新轻补 note 同轮注入时 GLM 收到 + 自相矛盾指令。改为:"绝对禁止再索要客户**已提供过**的联系方式;缺失渠道 + 仅可按上述轻补指引提一次。" 同步更新 `tests/test_lead_email_trigger.py` + 中 :356/:475/:881 附近的"绝对禁止再索要"类文案断言。 + +- [ ] **Step 6: 集成测试**(spec §6-6 计数持久化)——用同文件现有 manager 流程测试风格(CaptureAI 捕获注入 context)构造两轮对话:**第一轮正向断言**(context 含追问句原文——防"写了计数却没拼进 reminder"的接线失误)+ 第一轮后查 DB `raw_info` 含 `_cc_asked`、第二轮 reminder 不再含追问句 - [ ] **Step 7: 全量绿 + commit** @@ -249,7 +269,12 @@ def test_system_md_dual_contact_wording(): assert "国家代码追问句" in src and "邮箱轻补句" in src ``` -- [ ] **Step 2: 跑红** → **Step 3: 改 system.md 七处**(话术全文见 spec §4.1;⚠️ 第 6 处"已留资时不用任何索要句"改写是防自锁关键,逐字改为"已留资时不再索要**已在手的渠道**(改确认式);**缺失渠道**可按【联系方式引导策略】轻补一次")→ **Step 4: 跑绿 + 全量绿**(现有 system.md 文案断言若冲突同步更新) +- [ ] **Step 2: 跑红** → **Step 3: 改 system.md 七处**(话术全文见 spec §4.1;⚠️ 第 6 处"已留资时不用任何索要句"改写是防自锁关键,逐字改为"已留资时不再索要**已在手的渠道**(改确认式);**缺失渠道**可按【联系方式引导策略】轻补一次")。 + ⚠️ **plan review Issue 3 补充——防自锁是四处不是一处**:除 L7 外,system.md + 还有三处同款硬禁令必须同步改写(否则更显眼的老禁令抑制新行为): + - 红线节(约 :59)"不再三索要已留资客户的联系方式"→ 补"(缺失渠道可轻补一次)" + - 系统提醒配合节(约 :201)同款表述同步 + - 对话流程节(约 :166)"拿到联系方式后…别再提联系方式"→ 补"(缺失渠道轻补一次除外)" → **Step 4: 跑绿 + 全量绿**(现有 system.md 文案断言若冲突同步更新) - [ ] **Step 5: 模板句过墙断言**(spec §6-4,防自锁报警线):