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

12 KiB
Raw Blame History

采集阶段B:业务事件体系(零网站改动)— 设计文档

日期:2026-09-18 分支:m1 状态:设计已获用户批准(四轮自审+独立复审,共 28 项修正) 依据: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
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——原生 submit 捕获时生成并写入隐藏字段(§1.5 时序设计),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}(非锚定——覆盖 /{lang}/product/{slug}/ 多语言前缀;实测样本 9 种语言前缀 ar/de/es/fr/it/pt/ro/ru/th,锚定匹配会静默丢失约半数产品页);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(服务端确认契约预留的浏览器侧半边)

  • submission_id 写入时序(结构性修正):wpcf7submit 在 AJAX 响应到达后才触发——POST 已发出,届时更新字段必然晚。因此改为:capture 阶段监听表单原生 submit 事件(document 级 capture 委托,先于 CF7 的 handler 构造 FormData 执行)——原生 submit 时生成当次 submission_id 并写入隐藏字段;wpcf7submit 触发的 form_submit 事件引用同一 ID。
  • form_start 时注入 <input type="hidden" name="_ctk_sid" value=""> 空占位(提交前字段恒存在;值由上述 submit 捕获填充)。
  • 作用:CF7 提交 POST 天然携带,阶段C 的 WP mu-plugin 从 $_POST['_ctk_sid'] 读取对齐。CF7 忽略未注册字段、正常传输——对现有表单行为无害。
  • known-limitation:_ctk_sid 空值仍可能出现(capture submit 未及触发的极端时序),阶段C 按 §5.4 兜底(浏览器侧缺失不影响服务器事实)。

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 格式(≤100 字符——独立于清洗 _SAFE_VALUE 的 31 上限,产品 slug 可较长);field_count 非负整数(≤99);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 规范化映射(写死):fb 家族 utm_source + 任意付费类 medium(cpc/ppc/paidsearch/paid/social/paid_social/social_ads/paidsocial)→ paid_social,此规则优先于强化集(实测样本主导模式即 utm_medium=paid&utm_source=fb,gpt/14 §6.1 的 28 条 fb/paid——不放本规则则它们仍全落 paid_search,违背本节目的);其余付费 medium(无 fb source)→ paid_search 强化(label_only 证据)。
  • 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) → 咨询点击 → 表单开始 → 提交 → 邮件已发(显示措辞"邮件已发(未验证)"——客户端声明的诚实边界)。首级用 page_view 而非上游文档的 product_view:product_view 为 inferred 推导会低漏计,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,原生 submit 捕获时生成、form_submit 事件引用(§1.5 时序),_ctk_sid 隐藏字段随 POST 传输。
  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 忽略未注册字段);capture 原生 submit 先于 CF7 handler 构造 FormData,写入顺序确定性成立;残余风险仅为 capture 未及触发的极端时序空值(§5.4 兜底)。
  • product_view 为 inferred URL 推导:跨语言同产品不关联、URL 结构变化需 bump rule_version。
  • track 与自动监听双发:靠文档约定(不强制拦截),RawBatch 可诊断。
  • 询盘漏斗末级为客户端声明(未验证措辞),阶段C 接确认通道后升级为"已验证询盘"。