1891d9b7b4
CI / docs-build (push) Successful in 1m3s
- iteration-2 报告 01-08(PM 拆解/后端/Flutter 评估/现实核查/UI 规范/埋点规划/证据基线/Git 规划),04、06 已由正式角色复核定稿 - mkdocs 挂「第二迭代」导航,build --strict 通过 - ADR-009 新建 patbond-pet 模块、ADR-010 照片剪出 M2、ADR-011 dev 主干/master 发布、ADR-012 北极星与 H1-H4、ADR-013 废弃 health_record_action、ADR-014 DEBT-1 随 M2、ADR-015 照护人邀请后置 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
525 lines
41 KiB
Markdown
525 lines
41 KiB
Markdown
# 第二迭代埋点与实验规划(宠物健康档案)
|
||
|
||
> 角色:Experiment Tracker(本版为角色复核定稿;初版由通用 agent 代拟,已整体接管)
|
||
> 日期:2026-09-07
|
||
> 前序:iteration-1 `05-experiment-tracking-plan.md`(事件与指标规划)、`13-tracking-implementation-spec.md`(工程规范与字典 v1)、`19-analytics-implementation-report.md`(M0 简化版落地实况)
|
||
> 依据:`development-plan.md` 第 7 节 M2、第 9 节「可观测性与产品验证」;`patbond-api` `EventDictionary.java` 现行白名单;`patbond-flutter` `lib/analytics/analytics_service.dart` 现状;本迭代 `01-pm-task-breakdown.md`(M2 范围与验收)
|
||
> 范围:M2 健康档案纵切(宠物、体重、疫苗、健康事件、提醒);社区、AI 创作、本地服务不在本轮定义
|
||
> 性质:纯规划文档,供 M2 开发工单直接引用;不含任何代码改动
|
||
|
||
**本版相对初版的复核结论(速览)**:
|
||
|
||
1. 初版的事件字典 v2 增量(10 事件)、护栏指标、对账 SQL、基础设施评估经复核**基本成立,予以保留**;漏斗闭环复核见 §1.6,发现并修订一处实质缺口(pageName 枚举缺 `pet_form`)。
|
||
2. 北极星初版只给了方向没给可操作口径——本版**落定候选 A「7 日回访记录率」的完整定义式**(分母、去重、窗口边界、成熟期、SQL),见 §2.1。
|
||
3. **新增 4 条可证伪产品假设 H1–H4**(初版完全缺失),每条带判定指标、阈值、数据源、观察窗口与证伪后行动,见 §3——这是实验规划区别于纯埋点规划的核心。
|
||
4. 「M2 不启动 A/B」的判断成立,但初版只说「前置未绿」不给路线——本版给出 8 项前置条件 × 预计达成迭代,结论:**M3 末可全绿,M4 启动首个实验**,见 §4。
|
||
5. 客户端三处偏差声称**已由本角色重新实读代码逐一实锤**(§0);废弃 `health_record_action` 的立场:**同意直接移除**,并补充实验视角理由(§1.2)。
|
||
|
||
---
|
||
|
||
## 0. 基线现状(开工前核对)
|
||
|
||
| 项 | 现状 | 出处 |
|
||
| --- | --- | --- |
|
||
| 后端接收端 | `POST /api/v1/events` 已上线:批量 1–50 条、202 逐条结果、eventId 幂等、白名单剥离、红线拒绝、匿名可报 | 报告 19 §1.1 |
|
||
| 后端字典 | v1 的 11 个 `auth_*` 事件 + 工单增补 `page_viewed(pageName, referrer)`、`health_record_action(recordType, actionType)` | `EventDictionary.java` |
|
||
| 存储 | `platform.product_events`(Flyway V2,v1 不分区,触发分区阈值约 5,000 万行) | 报告 13 §2 |
|
||
| Flutter 采集 | `AnalyticsService` 已挂 3/5 挂接点(登录/注册/退出);`page_viewed`、`health_record_action` 仅 TODO 注释 | 报告 19 §1.2 |
|
||
| Flutter 队列 | shared_preferences 持久化,上限 500 条 | `analytics_service.dart` |
|
||
|
||
### 0.1 客户端三处偏差(本角色实读 `analytics_service.dart` 复核,全部实锤)
|
||
|
||
| # | 偏差 | 证据(行号) | 对实验数据的影响 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | `sessionId` 每事件独立生成 | 第 55 行 `'sessionId': const Uuid().v4(), // Simplified: unique per event (M0)` | 会话维度整体不可用:§6.3 巡检、护栏 5 的代偿口径、page_viewed 覆盖率 sanity 全部依赖它 |
|
||
| 2 | `eventId` 为 UUID v4 而非规范要求的 v7 | 第 50 行 `'eventId': const Uuid().v4()` | 去重不受影响;随机主键丧失插入时间局部性,量级上来后 B-tree 写放大 |
|
||
| 3 | `appVersion`/`osVersion` 硬编码 | 第 57 行 `'1.0.0+1'`;第 59–61 行 `'android-14'`/`'ios-17'`(均留 TODO) | 版本维度全体失真,M2 起按版本切片看回归不可行 |
|
||
|
||
结论:接收链路可信、可直接承载 M2 新事件;三处偏差**须在 M2 第一波修复**(§5、§7.2),否则本迭代新指标的会话与版本维度都是坏数据。§3 的假设判定与 §2.1 北极星均已刻意设计为**不依赖 sessionId**(只用 userId + server_ts),即便修复延迟,核心读数不受污染——但漏斗 sanity 与护栏会瞎。
|
||
|
||
---
|
||
|
||
## 1. 事件字典 v2 增量(health_record 域)
|
||
|
||
### 1.1 沿用 v1 的设计原则(不复述,仅列约束)
|
||
|
||
命名 `<域>_<动作>_<结果>` snake_case;`eventVersion` 起始 1、变更递增禁止原地改语义;客户端采集、`serverTs` 服务端补写为统计权威时间;`eventId` UUIDv7 幂等;属性 camelCase;公共属性(报告 13 §4.0 十项)全体必带。M2 新增两个域前缀:**`pet`**(宠物实体)与 **`health_record`**(档案记录)。
|
||
|
||
### 1.2 `health_record_action` 保留位的处置:废弃并直接移除(本角色立场:同意)
|
||
|
||
M0 工单在档案功能设计之前,往后端字典预置了通用事件 `health_record_action(recordType, actionType)`。v2 决定**不启用该保留位,以细分事件取代**:
|
||
|
||
1. v1 惯例把结果编码进事件名(`_succeeded`/`_failed`),使每个事件有独立 props 白名单与独立失败枚举;`actionType` 把 4 种动作塞进一个事件,白名单只能取并集,失败语义无处安放。
|
||
2. 漏斗指标(§2.2)需要 `started → succeeded` 配对事件,通用事件表达不了。
|
||
3. **实验视角补充理由(本角色)**:假设验证要求「一个指标定义式只引用语义单一的事件」。若 H1(记录类型分布)与漏斗完成率共用一个 `health_record_action`,则任何一次 `actionType` 枚举扩充都会同时污染两套指标口径的分母——细分事件把这种耦合从源头切断。§3 全部 4 条假设都以细分事件为数据源,保留位对假设验证零贡献。
|
||
4. **废弃是零成本的**:本角色 grep 全库核实,`patbond-flutter/lib` 下对该事件名 **0 处引用**(仅后端白名单一行 + 注释),不存在兼容负担。
|
||
|
||
处置:后端工单从 `EventDictionary` 白名单**直接移除**该条目(连同 `actionType`;`recordType` 作为属性名由 §1.4 各细分事件继承);Flutter 侧 TODO 注释指向的挂接位置改挂 §1.4 细分事件。
|
||
|
||
同场收编:`page_viewed(pageName, referrer)` 同为工单增补、未进字典正稿,v2 将其**转正**(定义见 §5.2,pageName 必须是枚举,禁止自由路由字符串)。
|
||
|
||
### 1.3 隐私红线增量(在 v1 六条红线之上追加,针对档案内容)
|
||
|
||
埋点只记录**行为**,不记录**内容**——内容分析一律走服务端事实表(M2 验收「体重、疫苗进度……从事实表聚合」本来就要求事实表可查)。任何事件禁止携带:
|
||
|
||
1. **宠物名、品种自由文本**:物种用 `species` 枚举(`cat`/`dog`/`other`),品种不上报。
|
||
2. **档案自由文本**:备注、症状描述、提醒文案原文。
|
||
3. **精确数值**:体重公斤数、花费金额、疫苗批号。
|
||
4. **媒体线索**:照片 URL、文件名、本地路径(只允许 `photoCount` 整数)。
|
||
5. **路由参数**:`page_viewed.pageName` 与 `referrer` 必须是归一化枚举——`/pet/3f8a…` 一律归一为 `pet_detail`,禁止把宠物/记录 UUID 混进页面名。
|
||
|
||
红线正则(`password|token|secret|phone|mobile|email|credential|idfa|gaid`)**本轮不扩**:加 `name`/`note` 类宽泛词会误伤 `pageName`、`recordType` 等合法字段;内容字段靠白名单剥离兜底,另新增值级巡检(§6.4)补防线。
|
||
|
||
### 1.4 新事件清单
|
||
|
||
`recordType` 枚举(多事件共用,对应 M2 四类记录接口):`weight` / `vaccine` / `health_event` / `reminder`。
|
||
失败枚举基底(在 v1 的 `validation_error`/`rate_limited`/`network_error`/`server_error` 之上,按 M2 验收新增):
|
||
|
||
- `permission_denied` — 无权限访问宠物(403;owner/caregiver/viewer 权限模型的观测点)
|
||
- `conflict` — 并发更新冲突(M2 验收「并发更新返回明确冲突」的观测点)
|
||
- `not_found` — 目标宠物/记录已被删除(多设备场景)
|
||
|
||
#### 宠物创建(pet 域)
|
||
|
||
| 事件名 | 触发时机 | 专有属性 |
|
||
| --- | --- | --- |
|
||
| `pet_create_started` | 用户进入建宠表单并产生**首次输入**(到达表单页由 `page_viewed(pageName=pet_form)` 承接,见 §1.6 修订),每次进入记一次 | `entryPoint`(`profile_empty_state` / `pet_list` / `post_register_guide`,枚举待 UI 定稿收敛) |
|
||
| `pet_create_succeeded` | 客户端收到建宠接口成功响应(code=0)后(**漏斗事件**) | `durationMs`、`species`(枚举)、`petIndex`(该用户第几只宠物,int,H2 假设的直接数据源) |
|
||
| `pet_create_failed` | 失败响应 / 超时 / 本地校验拦截 | `failureReason`、`errorCode`(可空)、`httpStatus`(可空)、`attemptSeq` |
|
||
|
||
`pet_create_failed.failureReason`:`validation_error`、`pet_limit_reached`(若产品设上限,**待拍板**:无上限则删此枚举)、`rate_limited`、`network_error`、`server_error`。
|
||
|
||
> 说明:示例名 `pet_created` 不符合 v1「结果后缀」惯例,按 `<域>_<动作>_<结果>` 正名为 `pet_create_succeeded` 系列。
|
||
|
||
#### 健康记录创建(health_record 域)
|
||
|
||
| 事件名 | 触发时机 | 专有属性 |
|
||
| --- | --- | --- |
|
||
| `health_record_create_started` | 进入某类记录的创建表单并产生首次输入 | `recordType`、`entryPoint`(`pet_detail` / `record_list` / `reminder`,待 UI 定稿收敛) |
|
||
| `health_record_create_succeeded` | 收到创建接口成功响应后(**漏斗事件**,北极星与 H1/H3/H4 的核心数据源) | `recordType`、`durationMs`、`photoCount`(int,无照片为 0) |
|
||
| `health_record_create_failed` | 失败响应 / 超时 / 本地校验拦截 | `recordType`、`failureReason`、`errorCode`、`httpStatus`、`attemptSeq` |
|
||
|
||
`failureReason`:`validation_error`、`permission_denied`、`not_found`、`rate_limited`、`network_error`、`server_error`。
|
||
|
||
#### 记录浏览 / 编辑 / 删除
|
||
|
||
| 事件名 | 触发时机 | 专有属性 |
|
||
| --- | --- | --- |
|
||
| `health_record_viewed` | 记录**详情**页可见(列表滚动曝光不算,防事件洪水) | `recordType`、`source`(`record_list` / `pet_detail` / `reminder`) |
|
||
| `health_record_edit_succeeded` | 编辑保存成功响应后 | `recordType`、`fieldCount`(本次变更字段数,int,可空) |
|
||
| `health_record_edit_failed` | 编辑保存失败 | `recordType`、`failureReason`(含 **`conflict`**)、`errorCode`、`httpStatus` |
|
||
| `health_record_deleted` | 删除成功响应后(仿 `auth_logout` 单事件风格;删除失败不埋,靠服务端接口错误率观测) | `recordType` |
|
||
|
||
宠物列表/详情的**浏览**不设 `pet_viewed`——由 `page_viewed`(`pageName = pet_list` / `pet_detail`)覆盖,避免双事件重复计数。编辑不设 `started`:短表单,started→succeeded 漏斗价值低于事件成本;若编辑放弃率成为问题再以 eventVersion=2 增补。
|
||
|
||
### 1.5 v2 增量总览(10 个新事件 + 1 转正 + 1 废弃)
|
||
|
||
| # | 事件名 | 版本 | 性质 |
|
||
| --- | --- | --- | --- |
|
||
| 12 | `pet_create_started` | 1 | 新增 |
|
||
| 13 | `pet_create_succeeded` | 1 | 新增(漏斗事件) |
|
||
| 14 | `pet_create_failed` | 1 | 新增 |
|
||
| 15 | `health_record_create_started` | 1 | 新增 |
|
||
| 16 | `health_record_create_succeeded` | 1 | 新增(漏斗事件) |
|
||
| 17 | `health_record_create_failed` | 1 | 新增 |
|
||
| 18 | `health_record_viewed` | 1 | 新增 |
|
||
| 19 | `health_record_edit_succeeded` | 1 | 新增 |
|
||
| 20 | `health_record_edit_failed` | 1 | 新增 |
|
||
| 21 | `health_record_deleted` | 1 | 新增 |
|
||
| — | `page_viewed` | 1 | 转正(工单增补 → 字典正稿,pageName 枚举化) |
|
||
| — | `health_record_action` | — | **废弃**(从未启用,后端白名单直接移除,见 §1.2) |
|
||
|
||
后端 `EventDictionary` 白名单增量(工单可直接抄):
|
||
|
||
```java
|
||
Map.entry("pet_create_started", Set.of("entryPoint")),
|
||
Map.entry("pet_create_succeeded", Set.of("durationMs", "species", "petIndex")),
|
||
Map.entry("pet_create_failed",
|
||
Set.of("failureReason", "errorCode", "httpStatus", "attemptSeq")),
|
||
Map.entry("health_record_create_started", Set.of("recordType", "entryPoint")),
|
||
Map.entry("health_record_create_succeeded", Set.of("recordType", "durationMs", "photoCount")),
|
||
Map.entry("health_record_create_failed",
|
||
Set.of("recordType", "failureReason", "errorCode", "httpStatus", "attemptSeq")),
|
||
Map.entry("health_record_viewed", Set.of("recordType", "source")),
|
||
Map.entry("health_record_edit_succeeded", Set.of("recordType", "fieldCount")),
|
||
Map.entry("health_record_edit_failed",
|
||
Set.of("recordType", "failureReason", "errorCode", "httpStatus")),
|
||
Map.entry("health_record_deleted", Set.of("recordType"))
|
||
// 同时删除 Map.entry("health_record_action", ...) —— 从未启用,见 §1.2
|
||
```
|
||
|
||
Flutter 侧沿用报告 13 §3.1 的强类型封装惯例:新建 `pet_analytics.dart` / `health_record_analytics.dart`,枚举编译期锁死,业务代码禁止手拼事件名与属性。
|
||
|
||
### 1.6 漏斗闭环与维度够用性复核(本角色新增)
|
||
|
||
复核方法:以 §3 的 4 条假设 + §2 全部指标逐条反推数据源,凡定义式引用了字典中不存在的事件/属性即判缺口。结论如下。
|
||
|
||
**闭环成立**:`pet_create` 与 `health_record_create` 两条漏斗均有 started → succeeded / failed 配对,失败枚举覆盖 M2 验收要求的权限(`permission_denied`)与并发(`conflict`)场景,闭环判定通过。编辑不设 started、删除不埋失败,属自觉取舍,同意(复活条件已在 §1.4 注明)。
|
||
|
||
**修订 1(实质缺口,本版已修)**:初版 pageName 枚举为 `login / register / home / profile / pet_list / pet_detail / record_form / record_detail`,**缺建宠表单页**。`pet_create_started` 定义在「首次输入」触发,意味着「到达表单即放弃」的人群只能靠 page_viewed 兜住——枚举里没有建宠表单页名,建宠漏斗的「到达 → 动笔」段就不可测,完成率分母系统性偏小、读数虚高。**修订:pageName 枚举增补 `pet_form`**,建宠漏斗三段式为 `page_viewed(pet_form) → pet_create_started → pet_create_succeeded`;健康记录漏斗同理由 `record_form` 承接到达段(初版已有此页名,无需改)。
|
||
|
||
**缺口 2(接受不埋)**:宠物编辑/删除无事件——低频管理动作,不构成漏斗,服务端事实表可查,不埋。
|
||
|
||
**缺口 3(接受不埋,有条件)**:提醒完成/忽略(pending→completed/dismissed)无事件。H3 的验证只需「提醒创建」(`recordType=reminder`)与回访事件,均已具备;提醒完成率从 `care_reminders` 事实表(`status`/`completed_at`)出数即可。**条件**:若 M3+ 要做提醒推送类实验,届时必须增补 `reminder_completed` 事件(记入字典 backlog),因为推送实验的主指标需要客户端行为时序而非仅终态。
|
||
|
||
**维度够用性**:H1 需 `recordType`(有);H2 需 `petIndex`(有,另以 `pet.pets` 事实表交叉验证);H3 需 `recordType=reminder` 分群 + userId 时序(有);H4 需 `pet_create_succeeded` 与 `health_record_create_succeeded` 的 userId + serverTs(有)。**全部假设可由本字典 + M2 事实表回答,维度判定通过。**
|
||
|
||
---
|
||
|
||
## 2. M2 指标体系(北极星定义式落定 + 漏斗 + 护栏)
|
||
|
||
统计口径沿用 v1:`serverTs` 划 UTC 日界;主体去重用 `userId`(M2 事件全部发生在登录后,`anonymousId` 兜底理论上不该出现——出现即数据质量信号,§6.5 巡检)。
|
||
|
||
### 2.1 北极星:7 日回访记录率(本角色裁定采用候选 A,定义式落定)
|
||
|
||
初版将 A/B 二选一列为待拍板且只给了方向性描述。本角色以实验专业裁定:**采用 A「7 日回访记录率」为北极星,B「档案激活率」降级为辅助漏斗指标**(保留 PM 否决权,见 §8)。理由:健康档案的产品价值在「持续记录」而非「一次性录入」;A 是留存型指标,难被一次性强引导冲高,B 恰恰易被冲高从而与长期价值背离——B 适合做诊断,不适合做方向。
|
||
|
||
初版定义(「首次成功后 7 个自然日内再次 ≥1 条」)存在三处不可操作的模糊:同日批量录入算不算回访?窗口从时刻算还是从日界算?分母是哪个「首次」?本版落定如下。
|
||
|
||
**定义式**:
|
||
|
||
```
|
||
7日回访记录率(w) =
|
||
| { u : firstRec(u) ∈ 周 w,且 ∃ e ∈ E(u):day(e) ∈ [day(firstRec(u))+1, day(firstRec(u))+7] } |
|
||
─────────────────────────────────────────────────────────────────────────────
|
||
| { u : firstRec(u) ∈ 周 w } |
|
||
```
|
||
|
||
口径逐项:
|
||
|
||
| 要素 | 落定口径 | 理由 |
|
||
| --- | --- | --- |
|
||
| firstRec(u) | 用户 u **平台生命周期内首条** `health_record_create_succeeded` 的 `server_ts`(min),非「本周期首条」 | 回访衡量习惯养成,只对真正的新记录用户有意义 |
|
||
| 分母 | firstRec 落在 ISO 周 w(UTC)内的去重 `userId`;`user_id IS NULL` 的事件不计入(应为空集,§6.5 兜底) | 按首记周分队列,队列间互斥 |
|
||
| 分子 | 分母中,在 **day(firstRec)+1 至 day(firstRec)+7**(UTC 自然日,**不含首记当日**)内再产生 ≥1 条 `health_record_create_succeeded` 者;任意 `recordType`、任意宠物均算 | **排除首记当日**是关键:不排除则首次使用时同会话批量录入 3 条体重也算「回访」,指标失去留存含义 |
|
||
| 回访事件范围 | 仅创建成功事件;`viewed`/`edit` 不算回访 | 北极星衡量「持续产生记录」,浏览是弱得多的信号,混入会稀释 |
|
||
| 删除处理 | 记录事后被删不影响计数(行为已发生) | 事件表不可变语义 |
|
||
| 队列成熟期 | 队列须等到 day(firstRec)+8(UTC)才可出数;未成熟队列不发布 | 防止半熟队列读数系统性偏低 |
|
||
| 去重 | 全程 `userId`;多设备同账号合并计 | 会话/设备维度不参与——刻意使北极星**不依赖 sessionId**(偏差 1 修复与否不污染北极星) |
|
||
|
||
**出数 SQL(巡检脚本可直抄)**:
|
||
|
||
```sql
|
||
WITH first_rec AS (
|
||
SELECT user_id,
|
||
date_trunc('day', min(server_ts) AT TIME ZONE 'UTC') AS first_day,
|
||
date_trunc('week', min(server_ts) AT TIME ZONE 'UTC') AS cohort_week
|
||
FROM platform.product_events
|
||
WHERE event_name = 'health_record_create_succeeded' AND user_id IS NOT NULL
|
||
GROUP BY user_id
|
||
),
|
||
returned AS (
|
||
SELECT DISTINCT f.user_id
|
||
FROM first_rec f
|
||
JOIN platform.product_events e
|
||
ON e.user_id = f.user_id
|
||
AND e.event_name = 'health_record_create_succeeded'
|
||
AND date_trunc('day', e.server_ts AT TIME ZONE 'UTC')
|
||
BETWEEN f.first_day + interval '1 day' AND f.first_day + interval '7 day'
|
||
)
|
||
SELECT f.cohort_week,
|
||
count(*) AS cohort_users,
|
||
count(r.user_id) AS returned_users,
|
||
round(100.0 * count(r.user_id) / count(*), 2) AS return_rate_pct
|
||
FROM first_rec f
|
||
LEFT JOIN returned r USING (user_id)
|
||
WHERE f.first_day + interval '8 day' <= date_trunc('day', now() AT TIME ZONE 'UTC') -- 只出成熟队列
|
||
GROUP BY f.cohort_week
|
||
ORDER BY f.cohort_week;
|
||
```
|
||
|
||
**统计纪律**:早期周队列样本小,读数按 Wilson 95% 置信区间发布(不裸报点估计);队列人数 < 50 的周与相邻周合并或改用 4 周滚动口径,禁止对小样本周环比做趋势解读。
|
||
|
||
**辅助指标 B(档案激活率,降级为诊断漏斗)**:当周新注册用户中,完成「建宠 + ≥1 条健康记录」全链路的比例(事件表 + `identity.users`)。读数即时,用于诊断激活链路(配合 H4),不作方向指标。
|
||
|
||
### 2.2 漏斗指标(随埋点上线即产出)
|
||
|
||
- **建宠三段漏斗**(§1.6 修订后):`page_viewed(pet_form)` → `pet_create_started` → `pet_create_succeeded`,各段按去重 userId、24 小时归因窗(v1 注册转化率同款口径)。「到达→动笔」流失指向入口与表单首屏,「动笔→成功」流失指向表单项与校验。
|
||
- **档案创建完成率** = `health_record_create_succeeded` / `health_record_create_started`,同口径,按 `recordType` 拆分——哪类表单流失最重是 UI 迭代的直接输入(`record_form` 到达段同理三段化)。
|
||
- 辅助:`*_create_failed` 按 `failureReason` 分布(`validation_error` 高 → 表单/文案问题;`network_error`/`server_error` 高 → 技术问题)。
|
||
|
||
### 2.3 护栏指标(M2 期间任何改动不得劣化)
|
||
|
||
| # | 护栏 | 口径 | 阈值(**待拍板**) |
|
||
| --- | --- | --- | --- |
|
||
| 1 | 并发冲突率 | `health_record_edit_failed(failureReason=conflict)` / 编辑尝试总数(= edit_succeeded + edit_failed) | 建议 < 1%;持续高于阈值说明乐观锁粒度或客户端刷新策略有问题(对应 M2 验收「并发更新返回明确冲突」) |
|
||
| 2 | 越权信号 | `permission_denied` 事件数(绝对值) | 期望≈0;任何持续非零都是权限模型或客户端入口控制回归,P1 排查 |
|
||
| 3 | M1 存量指标不回退 | 登录成功率、会话恢复成功率(v1 §2.2/2.3 口径) | 不低于 M2 开工前 2 周基线均值 − 2pp |
|
||
| 4 | 埋点自身健康 | 事件丢失率 < 5%、对账偏差 < 5%(§6)、去重命中率 < 10% | 沿用 v1 实验前置条件阈值 |
|
||
| 5 | 崩溃率 | **暂缺采集手段**(无崩溃上报 SDK,引第三方违反 v1「不绑定未评审供应商」约束) | 占位待拍板:M2 是否接受用「会话异常中断率」(§5.1 sessionId 落地后可推算)代偿 |
|
||
|
||
---
|
||
|
||
## 3. M2 产品假设(本角色新增,可证伪,上线前登记)
|
||
|
||
**方法约定**:以下阈值是**上线前登记的判定线,不是 KPI**——判定线先于数据存在,防止事后看图说话(HARKing)。每条假设的观察窗口届满即出判定,三种结局:支持 / 证伪 / 数据不足(样本未达最低量,顺延一个窗口并注明)。所有假设的数据源都已在 §1.6 验证「字典可答」。上线第 1 周为尝鲜噪声期,除 H4 外一律剔除。
|
||
|
||
### H1:体重是最高频的记录类型(信息架构假设)
|
||
|
||
- **陈述**:稳定期内,`weight` 在四类记录的创建量中占比第一且 ≥ 35%。
|
||
- **判定指标**:`health_record_create_succeeded` 按 `props->>'recordType'` 的分布占比(与 §6.2 对账 SQL 的 evt_side 同源;以事实表侧交叉验证)。
|
||
- **判定线**:支持 = weight 第一且 ≥ 35%;证伪 = 连续 4 周 weight 非第一,或占比 < 25%;中间地带 = 顺延观察。
|
||
- **窗口**:上线后第 2–5 周。
|
||
- **行动**:支持 → 记录入口默认落体重、快捷录入优化优先投给体重表单;证伪 → 按实际头部类型重排入口与 M3 表单优化优先级。
|
||
|
||
### H2:用户会为多只宠物建档(多宠价值假设)
|
||
|
||
- **陈述**:有宠用户中,拥有 ≥ 2 只宠物档案的占比 ≥ 20%。
|
||
- **判定指标**:主数据源为 `pet.pets` 事实表(按 owner 去重计宠物数——事实表无丢失率,作分布真值);`pet_create_succeeded.petIndex` 的 per-user 最大值作事件侧交叉验证。
|
||
- **判定线**:支持 = ≥ 20%;证伪 = < 10%;10–20% 顺延。
|
||
- **窗口**:上线后 4 周末读数。
|
||
- **行动**:支持 → 宠物切换器/多宠列表体验进 M3 优先级;证伪 → 多宠管理 UI 降级,`petIndex` 维度保留继续观察。
|
||
|
||
### H3:创建提醒的用户回访记录率更高(提醒价值假设)
|
||
|
||
- **陈述**:首记后 7 日内创建过 ≥ 1 条 `reminder` 类记录的用户,其 7 日回访记录率比未创建者高 ≥ 10pp。
|
||
- **判定指标**:§2.1 北极星 SQL 按「窗口内是否有 `recordType='reminder'` 的创建成功事件」分成两群,比较回访率之差(回访事件计算时**剔除 reminder 类型自身**,防止「建了提醒」同时既定义分群又充当回访,循环论证)。
|
||
- **判定线**:支持 = 差值 ≥ 10pp 且两群各 ≥ 100 人;证伪 = 差值 < 5pp 或倒挂;5–10pp 顺延。
|
||
- **窗口**:上线后 6 周(需 ≥ 2 个成熟队列)。
|
||
- **方法论警示**:这是**观察性对照,只能证明相关**——爱记录的用户本来就更可能建提醒(自选择偏差)。支持结论的正确用法不是宣布因果,而是把「默认引导创建提醒」列为**首个 A/B 实验候选**(§4.3),用随机化坐实因果后再全量。
|
||
- **行动**:支持 → 进 A/B 候选池;证伪 → 提醒功能保持工具定位,不投入引导资源。
|
||
|
||
### H4:建宠后会立即产生首条记录(激活链路假设)
|
||
|
||
- **陈述**:完成建宠的用户中,≥ 50% 在建宠后 24 小时内产生第一条 `health_record_create_succeeded`。
|
||
- **判定指标**:per user 的首次 `pet_create_succeeded` 与首次 `health_record_create_succeeded` 的 `server_ts` 差值分布中,≤ 24h 的占比。
|
||
- **判定线**:支持 = ≥ 50%;证伪 = < 30%;30–50% 顺延。
|
||
- **窗口**:上线后 4 周(含第 1 周——激活链路恰恰要看新用户首触行为)。
|
||
- **行动**:证伪 → 说明建宠成功页缺少「顺手记一笔」的引导落点,「建宠成功页引导首条记录」进 A/B 候选池(与 H3 候选竞争首实验席位,配合辅助指标 B 诊断);支持 → 激活链路健康,优化资源全部投向回访(北极星)。
|
||
|
||
---
|
||
|
||
## 4. A/B 实验:M2 不启动(判断成立),启动路线首次给出
|
||
|
||
### 4.1 M2 不启动的复核结论
|
||
|
||
初版判断**成立**:v1 前置条件截至今日一项未变绿——指标基线连一天真实数据都没有,此时分流实验只会产出噪声结论。M2 的正确动作是把漏斗测准、把 §3 的假设判定跑起来(观察性分析不需要分流基础设施)。但「不做」不等于「不规划」,前置条件与达成路线如下。
|
||
|
||
### 4.2 前置条件清单 × 预计达成迭代
|
||
|
||
| # | 前置条件 | 内容 | 责任侧 | 预计达成 |
|
||
| --- | --- | --- | --- | --- |
|
||
| 1 | 数据质量验收 | 丢失率 < 5%、对账偏差 < 5%、去重命中 < 10%、serverTs 覆盖 100%、无红线泄漏 | 数据(§6 巡检即验收手段) | **M2 内**(埋点上线 + 2 周巡检) |
|
||
| 2 | 指标基线 | §2 指标连续稳定产出 ≥ 2 周,形成均值与方差,与服务端日志交叉核对一致 | 数据 | **M2 末–M3 初** |
|
||
| 3 | 样本量规则成文 | 给定基线率、MDE、95% 置信度、80% 功效的样本量计算方法与查表;按实际 DAU 换算实验最短运行时长 | 本角色(纯文档) | **M3** |
|
||
| 4 | 稳定分流组件 | `hash(userId, experimentSalt) % buckets`,实验期内分组不变、跨端一致;登录前实验用 `anonymousId` 并定义登录后归并规则 | 后端 | **M3** |
|
||
| 5 | 曝光事件 | `experiment_exposed(experimentKey, variant)` 进字典;分析只统计实际曝光用户,杜绝按分配名单算分母 | 后端 + Flutter | **M3**(随 #4) |
|
||
| 6 | 实验设计模板与评审流程 | 假设、主指标、护栏、提前停止规则、多重比较校正约定 | 本角色(模板可先行) | **M3** |
|
||
| 7 | 护栏监控与回滚 | 护栏指标准实时监控 + feature flag 一键回滚 | 后端/DevOps | **M3–M4** |
|
||
| 8 | 隐私合规复核 | 实验分组数据同守红线 | 每实验各一次 | 常态 |
|
||
|
||
**结论:M3 末 8 项可全绿,M4 具备启动首个 A/B 的条件。**
|
||
|
||
### 4.3 首实验候选与样本量现实检验
|
||
|
||
候选按 §3 判定结果二选一:H3 支持 → 「新用户默认引导创建提醒」;H4 证伪 → 「建宠成功页引导首条记录」。两者主指标都直接挂北极星或其激活前置,护栏用 §2.3 全套。
|
||
|
||
样本量现实检验(启动前必须重算,此处给数量级感):若激活率基线 40%、检出 +8pp 绝对提升、双侧 α=0.05、功效 80%,每组约需 600 个新建档用户,合计 ~1,200;以回访率(基线假设 25%、MDE +8pp)为主指标则每组约需 ~640,且每人多等 8 天成熟期。**若按届时 DAU 换算实验需运行超过 8 周,判定该实验不可行**,退回观察性分析并继续攒流量——这条止损线与实验本身一起在设计文档里预登记。
|
||
|
||
---
|
||
|
||
## 5. 两个遗留高优项的验收标准与对账方法
|
||
|
||
这两项是 M2 埋点数据可信的**前置**,排入 M2 第一波工单(先于档案功能挂接)。
|
||
|
||
### 5.1 sessionId 生命周期(session_tracker + WidgetsBindingObserver)
|
||
|
||
现状:`analytics_service.dart` 第 55 行每事件 `const Uuid().v4()`,会话维度完全不可用(§0.1 偏差 1)。
|
||
|
||
**验收标准(全部满足才算关单)**:
|
||
|
||
1. 新建 `lib/analytics/session_tracker.dart`,注册为 `WidgetsBindingObserver`;`AnalyticsService` 从它读 sessionId,删除每事件生成逻辑。
|
||
2. 语义三条(即报告 13 §4.0 定义):冷启动生成新 sessionId;`paused → resumed` 间隔 **> 30 分钟**生成新 sessionId;**≤ 30 分钟**沿用原值。
|
||
3. 同一前台会话内产生的所有事件(跨不同 eventName)sessionId 完全一致。
|
||
4. sessionId 为 UUID,不落任何持久化存储(会话本该跨冷启动失效;`lastActiveAt` 时间戳可持久化用于判定,报告 13 §3.3 键位已预留)。
|
||
5. 单元测试 ≥ 3 例:冷启动新值 / 短后台沿用 / 长后台(注入时钟模拟 31 分钟)换新值。
|
||
6. 真机手测脚本:登录 → 退后台 5 分钟 → 回前台操作 → 退后台 35 分钟 → 回前台操作,库内应恰好出现 **2 个** sessionId,且切分点在长后台处。
|
||
|
||
**对账方法(上线后每日巡检 SQL,见 §6.3.1)**:每 sessionId 平均事件数。修复前该值恒等于 1;修复后应明显 > 1。告警口径:`distinct sessionId / 事件总数 > 0.9` 持续一天 = 生命周期逻辑未生效或回退。
|
||
|
||
### 5.2 page_viewed 路由埋点(RouteObserver)
|
||
|
||
**验收标准**:
|
||
|
||
1. `RouteObserver` 注册进 `MaterialApp.navigatorObservers`,`didPush`(含 `didPopNext` 返回露出)触发 `page_viewed`。
|
||
2. `pageName` 是**编译期枚举**,v2 初始集合:`login` / `register` / `home` / `profile` / `pet_list` / `pet_detail` / **`pet_form`**(§1.6 修订新增)/ `record_form` / `record_detail`(随 M2 页面定稿增删,进字典说明);带参数路由必须归一化——任何 UUID/ID 出现在 pageName 或 referrer 中即验收失败(§1.3 红线第 5 条)。
|
||
3. `referrer` = 前一页 pageName,栈底/冷启动首页为 null。
|
||
4. 不在字典枚举内的路由(如 dialog、临时调试页)**不上报**,而不是报未知名(后端会整条 rejected,白白消耗队列)。
|
||
5. 单测/widget 测试:push 两页断言两条事件且 referrer 链正确;pop 返回断言 `didPopNext` 补报。
|
||
6. M1 存量四页(登录/注册/首页/个人中心)与 M2 新页一次性挂全。
|
||
|
||
**对账方法(§6.3.2)**:两条 sanity 关系式——(a) 每个 sessionId 至少 1 条 `page_viewed`(进过 app 必然看过页面);(b) `page_viewed(pageName=login)` 日次数 ≥ `auth_login_succeeded + auth_login_failed` 的去重 sessionId 数(登录尝试必先到达登录页)。偏差持续 > 5% 告警。
|
||
|
||
---
|
||
|
||
## 6. 对账 SQL 草案 v2 增量
|
||
|
||
v1 的 5.2.1–5.2.5(登录/注册/刷新对账、红线扫描、技术指标)继续每日跑,本节只列**新增**。真值来源:M2 后端事实表。**表名以 M2 后端 DDL 定稿为准**,下文按开发计划域划分假定 `pet` schema:`pet.pets`、`pet.weight_records`、`pet.vaccine_records`、`pet.health_events`、`pet.reminders`——若实际命名不同,替换表名即可,结构不变。
|
||
|
||
### 6.1 宠物创建对账
|
||
|
||
`pet_create_succeeded` 事件数 vs `pet.pets` 当日新建行数,UTC 日界,偏差 > 5% 告警(连续 2 日再升级,队列延迟说明同 v1 5.2)。
|
||
|
||
```sql
|
||
SELECT coalesce(p.day, t.day) AS day, coalesce(api_cnt, 0) AS api_cnt,
|
||
coalesce(evt_cnt, 0) AS evt_cnt,
|
||
round(abs(coalesce(evt_cnt, 0) - coalesce(api_cnt, 0))::numeric
|
||
/ greatest(coalesce(api_cnt, 0), 1) * 100, 2) AS diff_pct -- > 5 告警
|
||
FROM (SELECT date_trunc('day', created_at AT TIME ZONE 'UTC') AS day, count(*) AS api_cnt
|
||
FROM pet.pets GROUP BY 1) p
|
||
FULL JOIN (SELECT date_trunc('day', server_ts AT TIME ZONE 'UTC') AS day, count(*) AS evt_cnt
|
||
FROM platform.product_events
|
||
WHERE event_name = 'pet_create_succeeded' GROUP BY 1) t USING (day)
|
||
ORDER BY day;
|
||
```
|
||
|
||
### 6.2 健康记录创建对账(按 recordType 分型)
|
||
|
||
事件侧按 `props->>'recordType'` 分组,真值侧四张事实表 UNION 后带类型标签,逐类型对账——单独一类偏差大能直接定位是哪个表单的挂接点漏报。该 SQL 的 api_side 分布同时就是 **H1 的真值侧读数**。
|
||
|
||
```sql
|
||
WITH api_side AS (
|
||
SELECT day, record_type, count(*) AS api_cnt FROM (
|
||
SELECT date_trunc('day', created_at AT TIME ZONE 'UTC') AS day,
|
||
'weight' AS record_type FROM pet.weight_records
|
||
UNION ALL
|
||
SELECT date_trunc('day', created_at AT TIME ZONE 'UTC'), 'vaccine' FROM pet.vaccine_records
|
||
UNION ALL
|
||
SELECT date_trunc('day', created_at AT TIME ZONE 'UTC'), 'health_event' FROM pet.health_events
|
||
UNION ALL
|
||
SELECT date_trunc('day', created_at AT TIME ZONE 'UTC'), 'reminder' FROM pet.reminders
|
||
) u GROUP BY 1, 2
|
||
),
|
||
evt_side AS (
|
||
SELECT date_trunc('day', server_ts AT TIME ZONE 'UTC') AS day,
|
||
props->>'recordType' AS record_type, count(*) AS evt_cnt
|
||
FROM platform.product_events
|
||
WHERE event_name = 'health_record_create_succeeded'
|
||
GROUP BY 1, 2
|
||
)
|
||
SELECT coalesce(a.day, e.day) AS day, coalesce(a.record_type, e.record_type) AS record_type,
|
||
coalesce(api_cnt, 0) AS api_cnt, coalesce(evt_cnt, 0) AS evt_cnt,
|
||
round(abs(coalesce(evt_cnt, 0) - coalesce(api_cnt, 0))::numeric
|
||
/ greatest(coalesce(api_cnt, 0), 1) * 100, 2) AS diff_pct -- > 5 告警
|
||
FROM api_side a
|
||
FULL JOIN evt_side e ON a.day = e.day AND a.record_type = e.record_type
|
||
ORDER BY day, record_type;
|
||
```
|
||
|
||
### 6.3 两个遗留项的健康巡检(§5 对账方法的可执行形式)
|
||
|
||
**6.3.1 sessionId 生命周期生效性**:
|
||
|
||
```sql
|
||
SELECT date_trunc('day', server_ts AT TIME ZONE 'UTC') AS day,
|
||
count(*) AS events,
|
||
count(DISTINCT session_id) AS sessions,
|
||
round(count(DISTINCT session_id)::numeric / greatest(count(*), 1), 3) AS session_ratio
|
||
FROM platform.product_events
|
||
GROUP BY 1 ORDER BY 1;
|
||
-- session_ratio 接近 1.0(每事件一会话)= sessionId 仍是每事件生成,未生效/回退,告警
|
||
-- 修复后预期显著 < 0.5(每会话多事件)
|
||
```
|
||
|
||
**6.3.2 page_viewed 覆盖率**:
|
||
|
||
```sql
|
||
-- (a) 无 page_viewed 的会话占比(进过 app 必看过页面,期望≈0)
|
||
SELECT date_trunc('day', server_ts AT TIME ZONE 'UTC') AS day,
|
||
round(100.0 * count(DISTINCT session_id)
|
||
FILTER (WHERE session_id NOT IN (
|
||
SELECT session_id FROM platform.product_events WHERE event_name = 'page_viewed'))
|
||
/ greatest(count(DISTINCT session_id), 1), 2) AS pct_sessions_without_pv -- > 5 告警
|
||
FROM platform.product_events
|
||
GROUP BY 1 ORDER BY 1;
|
||
|
||
-- (b) 登录页浏览 ≥ 登录尝试会话数(sanity)
|
||
SELECT coalesce(pv.day, la.day) AS day, coalesce(pv_cnt, 0) AS login_page_views,
|
||
coalesce(attempt_sessions, 0) AS login_attempt_sessions
|
||
FROM (SELECT date_trunc('day', server_ts AT TIME ZONE 'UTC') AS day, count(*) AS pv_cnt
|
||
FROM platform.product_events
|
||
WHERE event_name = 'page_viewed' AND props->>'pageName' = 'login' GROUP BY 1) pv
|
||
FULL JOIN (SELECT date_trunc('day', server_ts AT TIME ZONE 'UTC') AS day,
|
||
count(DISTINCT session_id) AS attempt_sessions
|
||
FROM platform.product_events
|
||
WHERE event_name IN ('auth_login_succeeded', 'auth_login_failed') GROUP BY 1) la
|
||
USING (day)
|
||
ORDER BY day;
|
||
-- login_page_views < login_attempt_sessions 持续出现 = 路由埋点漏报,告警
|
||
```
|
||
|
||
### 6.4 内容泄漏值级巡检(红线 regex 不扩的补防线,见 §1.3)
|
||
|
||
白名单字段的**值**若出现长自由文本,说明有人把备注/宠物名塞进了合法字段名里:
|
||
|
||
```sql
|
||
SELECT event_name, k AS prop_key, count(*) AS hits
|
||
FROM platform.product_events
|
||
CROSS JOIN LATERAL jsonb_each_text(props) AS kv(k, v)
|
||
WHERE server_ts >= now() - interval '1 day'
|
||
AND length(v) > 64 -- 字典 v2 所有枚举/数值字段值长远小于 64
|
||
GROUP BY 1, 2;
|
||
-- 期望恒为空集;命中即 P1:核对该字段是否被塞入内容数据并清洗
|
||
```
|
||
|
||
### 6.5 M2 新事件的匿名兜底巡检
|
||
|
||
M2 事件全部发生在登录后,`user_id` 为 NULL 即挂接点在 `identify()` 之前触发或时序 bug(同时会污染北极星分母,见 §2.1):
|
||
|
||
```sql
|
||
SELECT event_name, count(*) AS null_user_rows
|
||
FROM platform.product_events
|
||
WHERE event_name LIKE 'pet_%' OR event_name LIKE 'health_record_%'
|
||
GROUP BY 1 HAVING count(*) FILTER (WHERE user_id IS NULL) > 0;
|
||
-- 期望空集(退出后补冲刷的历史队列除外,占比应 < 1%)
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 埋点基础设施 M2 扩展性评估
|
||
|
||
**结论:接收端与存储零改动,客户端小修三处,无需任何架构扩展。**(复核初版量级估算,成立。)
|
||
|
||
### 7.1 量级估算(不需要扩容的依据)
|
||
|
||
- 单用户日事件量:auth 域 ~3–5 条 + `page_viewed` ~8–15 条(路由埋点补齐后的最大增量来源)+ pet/health_record 域 ~3–8 条 ≈ **15–30 条/DAU/日**,约为 v1 的 3–4 倍。
|
||
- 接收端:正常客户端 30 秒一批、批上限 50 条,日 30 条远填不满一批;限流 60 请求/5 分钟余量依旧十几倍。**`/api/v1/events` 契约、限流、64KB 上限均不动。**
|
||
- 存储:即便 1,000 DAU × 30 条 × 365 天 ≈ 1,100 万行/年,距报告 13 §2.3 的 5,000 万行分区阈值仍有数年余量。**v1 不分区的决策继续有效。**
|
||
- 客户端队列:500 条上限可容纳两周以上的离线积压(30 条/日),**不调**。`page_viewed` 是新的高频事件,唯一注意点:列表页快速进出可能瞬时产生密集事件,§5.2 验收第 4 条(字典外路由不上报)+ 详情页曝光而非列表曝光(§1.4 `health_record_viewed` 触发时机)已从源头限流。
|
||
|
||
### 7.2 需要落的三处客户端小修(随 M2 第一波工单,均对应 §0.1 实锤偏差)
|
||
|
||
1. **sessionId 生命周期**(§5.1,P0——不修则 M2 全部会话维度指标作废)。
|
||
2. **eventId 改回 UUIDv7**:现行 `Uuid().v4()`(第 50 行)去重仍有效,但 v4 随机主键使 `platform.product_events` 插入丧失时间局部性,量级上来后 B-tree 写放大;`uuid` 包本就支持 v7,一行改动,顺手修。
|
||
3. **动态 appVersion/osVersion**(引 `package_info_plus`/`device_info_plus`,报告 19 遗留第 6 项):M2 起指标要按版本切片看回归,硬编码 `1.0.0+1` / `android-14` / `ios-17` 会让版本维度全体失真;与本波一起落。
|
||
|
||
### 7.3 后端唯一改动
|
||
|
||
`EventDictionary` 白名单增补 10 事件 + 移除 `health_record_action`(§1.5 代码块可直抄),加集成测试各一例(沿用报告 19 §5.1 接入流程)。无表结构、无契约变更。
|
||
|
||
---
|
||
|
||
## 8. 待拍板清单(汇总)
|
||
|
||
| # | 事项 | 选项 | 本角色裁定/建议 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | 北极星指标 | A:7 日回访记录率 / B:档案激活率 | **已裁定 A**(定义式落定于 §2.1,B 降级辅助诊断;PM 保留否决权,否决须给替代定义式) |
|
||
| 2 | 产品假设 H1–H4 判定线 | §3 各阈值 | 上线前由 PM 会签一次,会签后**冻结**,窗口届满前不得修改(防事后画靶) |
|
||
| 3 | 护栏阈值 | 并发冲突率 < 1%?M1 指标回退容忍 2pp? | 按 §2.3 默认值先跑,两周数据后复核 |
|
||
| 4 | 崩溃率护栏 | 无采集手段:接受「会话异常中断率」代偿 or 排期评审崩溃 SDK | M2 用代偿,SDK 评审进 M6 交付加固 |
|
||
| 5 | `pet_limit_reached` 枚举 | 产品是否设单用户宠物数上限 | 无上限则从枚举删除 |
|
||
| 6 | `entryPoint`/`pageName` 枚举终稿 | 待 M2 UI 设计稿定稿后收敛(`pet_form` 为本版硬性新增,见 §1.6) | 埋点工单开工前由 UI + 本角色对齐一次 |
|
||
| 7 | `health_record_action` 移除 | 后端白名单直接删 vs 保留标 deprecated | **直接删**(零客户端引用已核实,零兼容成本,见 §1.2) |
|
||
| 8 | A/B 启动路线 | §4.2 八项前置 × 迭代 | M3 末全绿、M4 首实验;候选依 H3/H4 判定结果二选一 |
|
||
|
||
---
|
||
|
||
## 附:M2 埋点工单拆分建议(按依赖排序)
|
||
|
||
1. **Flutter P0 前置**:session_tracker(§5.1)+ eventId v7 + 动态设备信息(§7.2)——先于一切新事件。
|
||
2. **Flutter**:`RouteObserver` + `page_viewed` 全页面挂接(§5.2,含 `pet_form`),M1 存量四页一并补齐。
|
||
3. **后端**:`EventDictionary` v2 增量(§7.3)——可与 1、2 并行。
|
||
4. **Flutter**:档案功能开发时按 §1.4 挂接 10 个新事件(强类型封装先行)。
|
||
5. **数据**:§6 五组对账 SQL + §2.1 北极星 SQL 入巡检;上线首周每日人工看 §6.3 两项(遗留修复的生效性验证)。
|
||
6. **本角色**:H1–H4 判定线 PM 会签(拍板 #2)→ 冻结登记;M3 初产出样本量规则文档与实验设计模板(§4.2 #3、#6)。
|