Files
fangke/docs/superpowers/specs/2026-09-18-collection-phase-b-design.md
T

119 lines
11 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.
# 采集阶段B:业务事件体系(零网站改动)— 设计文档
日期:2026-09-18
分支:m1
状态:设计已获用户批准(含三轮自审 21 项修正)
依据:gpt/14 §5/§6.2;前置阶段A 已上线(2026-09-18,归属冻结/区间停留/清洗三轨制/契约 v2)
实测基础:hesucar.com 载体盘点(2026-09-18 Playwright)——tel/mailto/wa.me 链接、Contact Form 7 v6.1.6 留言表单(Name/Tel/Message→Inquiry Now)、悬浮聊天组件
## 核心约束与决策
1. **零网站改动**:hesucar WordPress 不加任何代码。表单服务端确认通道**只做契约预留**(阶段C 接入)。
2. SDK 自动监听(内置埋点),不用 track 手动调用承载业务事件。
3. 事件全部携带阶段A 归属四元组(PageSession.attribution())。
4. 范围:contact_click / form_*(CF7 全过程)/ product_view(URL 推导)/ track 通道修复 / D06 归因扩展(paid_social + utm_medium)/ admin 时间线新事件 / 询盘漏斗最小视图。
5. 排除:服务端确认实现(阶段C)、次批事件(scroll_depth/section_view/media_progress 等)、sdk_diag、capabilities、完整漏斗报表、导出对账。
## 1. SDK(0.2.0 → 0.2.1):新模块 `sdk/src/business.ts`
与 page.ts/stay.ts 同模式,init 时安装;所有回调 try/catch 静默(红线)。
### 1.1 contact_click
- document 级点击委托(capture):命中链接 `wa.me / api.whatsapp.com / whatsapp://` → `channel:"whatsapp"`;`tel:` → `"phone"`;`mailto:` → `"email"`。属性仅 `{channel}`(枚举;placement 已砍——YAGNI)。
- 点击后立即 `transport.flush(true)`(beacon)——跳转窗口内保证发出。
- **known-limitation**:非链接按钮(如悬浮组件 Get Quotation)无标准特征,不识别不硬造。
- 语义:只证明点击意图,不宣称联系成功(OT09)。
### 1.2 form_*(Contact Form 7 全过程)
监听 CF7 DOM 事件(冒泡至 document)。**事件名/载荷以实测与源码为准**(见 §6 验证路径),设计按 CF7 5.x/6.x 已知形态:`wpcf7submit / wpcf7invalid / wpcf7spam / wpcf7mailsent / wpcf7mailfailed`。
| 事件 | 触发 | 属性 |
|---|---|---|
| form_start | .wpcf7-form 内首次 isTrusted focusin/input(每表单实例一次) | form_id |
| form_submit | wpcf7submit(**每次尝试**) | form_id, submission_id(当次新生成 uuid) |
| form_error | wpcf7invalid / wpcf7spam | form_id, submission_id, error_code(invalid/spam/other——other 为 forward-compat 桶,若 CF7 源码核验发现其他失败事件则启用), field_count |
| form_submit_result | wpcf7mailsent → result_code:"mail_sent";wpcf7mailfailed → "mail_failed" | form_id, submission_id, result_code |
- **submission_id 语义**:一次提交尝试 = 一个 ID(form_submit 时生成;result 引用最近一次的 ID)。form_start 不生成。
- **form_id 规整**:从 CF7 unit_tag/action 解析表单编号 → `cf7-{ID}`(如 cf7-156),不暴露完整内部串。
- **不采任何输入值**(OT10);error_code 只取状态码 + 字段数,不采 invalid_fields 明细。
- form_submit_result 是**客户端声明**(事件名本身表达,不升级为可信确认)。
### 1.3 product_view
- 规则:path 匹配 `/product/{slug}/`(pattern 内置默认,init 可覆盖——为未来站点预留,hesucar 用默认)。
- 属性:`{product_id: slug, inferred: true, rule_version: "v1"}`。多语言同产品无法关联——诚实标注(inferred 降级路径,gpt/14 §5.1)。
- **触发时机**:init 时同步判定 + SPA 切段回调。PageSession 新增 `onSegmentChange(fn)` 钩子——**触发点钉死在 `onRouteChange` 内新段创建之后**(模块级注册表,不在 `close()` 内触发——close 还覆盖 pagehide/重复 init,放在那里会在卸载时重复发 product_view);单点 hook history,business.ts 注册回调判定新段 path,不重复 wrap。
- PV 不双计:服务端 PV 统计已有 event_name 过滤(87261ab)。
### 1.4 track 通道修复(阶段A 终审 I-1)
- `ctk('track', name, props)`:transport.push 前用 `getPageSession()?.attribution()` 补归属顶层字段;事件名必须在服务端白名单内(白名单扩后合法名可通过)。
- **约定**:自动监听的事件(contact_click/form_*/product_view)勿手动 track,否则双发。
### 1.5 隐藏字段 `_ctk_sid`(服务端确认契约预留的浏览器侧半边)
- form_start 时向表单注入 `<input type="hidden" name="_ctk_sid" value="">`(**空占位**——不用预生成 ID:若 submit 时更新对 CF7 FormData 不可见,空值=干净缺失,预生成 ID 则阶段C 会收到匹配不到任何事件的孤儿确认,更糟);form_submit 事件触发时更新该字段为当次 submission_id(更新可见性以 CF7 源码核验为准,不可靠则该次对齐缺失)。
- 作用:CF7 提交 POST 天然携带,阶段C 的 WP mu-plugin 从 `$_POST['_ctk_sid']` 读取对齐。CF7 忽略未注册字段、正常传输——对现有表单行为无害。
- known-limitation:无任何交互直接提交的极端场景,submission_id 可能为空(事件与漏斗不受损,仅确认对齐缺失)。
## 2. Server:契约扩展
- `EVENT_NAMES` +6:contact_click / form_start / form_error / form_submit / form_submit_result / product_view。
- `EVENT_PROP_WHITELIST` 严格枚举(§1 表格所列键;无自由文本字段)。
- validate_event 新键校验:`channel` ∈ {whatsapp, phone, email};`result_code` ∈ {mail_sent, mail_failed};`error_code` 短枚举(invalid/spam/other);`submission_id` uuid 或空串;`form_id` 格式 `cf7-\d+`;`product_id` slug 格式(复用清洗 `_SAFE_VALUE` 同款正则);`inferred` 布尔;`rule_version` 短 slug。
- 幂等沿 uq_site_event;归属走既有 v2 路径,零新机制。
## 3. D06 归因扩展(attribution.py + constants.py)
- **优先级重排**:点击参数决定平台性质——fbclid → **paid_social**(Meta 平台本质),gclid/msclkid/yclid → paid_search;utm_medium 仅在无点击参数时参与判定(label_only 证据)。
- `CHANNELS` +`paid_social`;utm_medium 规范化映射(写死):paid_search 强化集 {cpc, ppc, paidsearch, paid}、paid_social 判定集 {social, paid_social, social_ads, paidsocial}(需同时有 fb 家族 utm_source)。
- **`ATTRIBUTION_RULE_VERSION` bump v1→v2** + 历史重算:`recompute_attribution` 管理命令**已存在**(management/commands/recompute_attribution.py,含 --site 参数与既有幂等测试 test_attribution.py:226 可参照),本任务只需 bump 版本 + 新渠道分支测试 + 线上重算执行。
- fb 家族 utm_source 判定复用既有广告家族归一表(132e2fe 引入的家族映射)。
- admin 中文标签同步:付费社交(渠道徽章/来源分布,grep 全部渠道显示点统一改)。
## 4. Admin 前端
### 4.1 时间线新事件展示
- EventTimeline/访客详情的事件类型映射扩展:contact_click(咨询点击 💬+channel)、form_start/submit/error/result(表单各阶段 📋+关键属性)、product_view(产品查看 🚗+product_id)——具体图标按现有组件风格。
- 新事件自动进时间线(timeline API 原样返回),仅前端徽章/文案工作。
### 4.2 询盘漏斗卡(数据总览)
- 五级:浏览(page_view) → 咨询点击 → 表单开始 → 提交 → 邮件已发(**显示措辞"邮件已发(未验证)"**——客户端声明的诚实边界)。
- **口径:逐级独立 distinct session 计数**(各级 Count("session", distinct=True)),**非严格嵌套漏斗**——允许跳级真实路径(gpt/14 §5.4"允许直接进入表单");各级数字不嵌套,**UI 不显示级间百分比转化率**(避免嵌套误读),只显示各级计数。
- **全级过滤 `is_bot=False`**(Event.is_bot 既有标记——爬虫浏览/垃圾提交不灌漏斗)。
- 当日/近 7 天切换;新 API `GET /api/admin/funnel-summary?days=N`(site 权限沿既有)。
- 卡片位置随 dashboard 现有排版(趋势/渠道卡附近),实现时看布局。
## 5. 服务端确认——契约预留(不实现)
spec 附录写死(阶段C 直接照做,客迹侧零改动):
1. submission_id:uuid v4,form_submit 生成,`_ctk_sid` 隐藏字段随 POST 传输(§1.5)。
2. 认证端点:`POST /api/integrations/form-confirm`,站点级密钥(HMAC 签名 + 时间窗 ±5min + nonce 防重放 + submission_id 幂等);事件 form_submission_confirmed(白名单届时加入)。
3. WordPress mu-plugin 示例(约 20 行 PHP):hook `wpcf7_mail_sent` → 读 `$_POST['_ctk_sid']` + 表单摘要 → 签名转发。
4. 浏览器事件缺失时服务器确认仍成立(业务事实保留,浏览轨迹标缺);同 ID 重试不重复计数,内容变化报冲突。
## 6. 验证与部署
- **CF7 事实核验(实现第一步)**:读线上 CF7 版本(当前 6.1.6)公开源码(wp-content/plugins/contact-form-7/includes/js/index.js)确认事件 dispatch 名/payload/FormData 时序;Playwright 在 hesucar 空表单提交实测 invalid+submit 路径(无副作用)。开发期不真提交验证 mailsent——按源码实现,**部署后以本节末条的真实测试询盘闭环验证**。
- SDK 单测:点击委托命中/漏判、CF7 事件模拟、submission_id 每次尝试独立、form_start 去重、pattern 匹配、onSegmentChange、track 补归属。
- server 测试:6 新事件校验(枚举/格式)、漏斗 API distinct session 口径、归因 paid_social/utm_medium 分支、规则 v2 重算幂等。
- admin:时间线渲染新事件、漏斗卡数据正确。
- **部署顺序**:server → admin → SDK 0.2.1(SDK 先上会被白名单拒);**整体排在 9/20 观察窗 D02 确认之后**(开发期自然覆盖,非阻塞开发)。
- **归因重算的预期管理**:D06 重算在部署时一次执行——渠道分布/来源证据中部分"付费搜索"数字移至"付费社交"(Meta 付费流量类目变化),属预期口径调整非 bug,部署汇报中说明。
- 线上全链路:Playwright 点 hesucar WhatsApp 链接/tel/mailto → 事件落库;空表单提交 → form_start+submit+error 落库。
- **mailsent 真实闭环**(用户已接受):部署后在 hesucar 表单真实提交一次测试询盘(Name 填 "TEST-KEJI" 标记,商家邮箱收一封测试邮件)——一次性验证 form_submit_result 全链路(事件→白名单→落库→漏斗末级计数)。
## 7. 风险与边界
- CF7 事件形态按公开源码+invalid 实测锚定,mailsent 分支上线后以真询盘验证(诚实标注)。
- `_ctk_sid` 注入对表单无行为影响(CF7 忽略未注册字段);submit 时更新值的可见性受 CF7 内部时序影响,不可靠场景该次对齐缺失(known-limitation)。
- product_view 为 inferred URL 推导:跨语言同产品不关联、URL 结构变化需 bump rule_version。
- track 与自动监听双发:靠文档约定(不强制拦截),RawBatch 可诊断。
- 询盘漏斗末级为客户端声明(未验证措辞),阶段C 接确认通道后升级为"已验证询盘"。