e186ba3da9
- 时间线页:occurred_at DESC cursor 分页 + 月分组组头,四态齐备; 六类事件经 RecordTypeDot 映射(增补喂养/洗护/测量三型,色族复用 已审计色对),类型 TagPill 双通道 - 事件录入表单:六类类型选择器、金额以元录入/整数分传输(money.dart 换算)、UTC 时间戳约定与体重表单一致 - 事件编辑页:顶层 PATCH 差量提交(title/notes/amountCents + version), 40902 照 T2-12 模式自动经时间线检索取新 version 重提(保留输入) - 档案页:月度花费卡接 summary.monthlyExpense(分→元展示)、tz 透传 设备时区固定偏移(T2-13 遗留③)、健康时间线导航入口 - 埋点:health_record 域 create 三事件 + viewed(recordType=health_event) 挂通;新增 edit_succeeded/failed 封装(failureReason 含 conflict) - 测试 224 → 250 全绿(+26);analyze 0 问题;format 无 diff Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
159 lines
5.1 KiB
Dart
159 lines
5.1 KiB
Dart
import 'package:patbond_flutter/analytics/page_view_tracker.dart';
|
||
|
||
/// health_record 域埋点强类型封装(06 号规划 §1.4 字典 v2;后端白名单
|
||
/// 已扩充就绪,24 号报告 §2.2)。沿用 13 号规范 §3.1 惯例:枚举编译期
|
||
/// 锁死,业务代码禁止手拼事件名与属性。
|
||
///
|
||
/// T2-13 挂接创建漏斗三事件 + viewed;T2-14 补挂 edit_succeeded/failed
|
||
/// (健康事件编辑、疫苗标记完成/取消)。`health_record_deleted` 因 M2
|
||
/// 契约无删除端点暂无挂接点,留待删除交互落地。
|
||
|
||
/// 记录类型(06 §1.4 recordType 枚举,四类记录接口对应)。
|
||
enum HealthRecordType {
|
||
weight('weight'),
|
||
vaccine('vaccine'),
|
||
healthEvent('health_event'),
|
||
reminder('reminder');
|
||
|
||
const HealthRecordType(this.value);
|
||
|
||
final String value;
|
||
}
|
||
|
||
/// 记录创建入口(06 §1.4 entryPoint 枚举)。
|
||
enum HealthRecordEntryPoint {
|
||
petDetail('pet_detail'),
|
||
recordList('record_list'),
|
||
reminder('reminder');
|
||
|
||
const HealthRecordEntryPoint(this.value);
|
||
|
||
final String value;
|
||
}
|
||
|
||
/// 创建/编辑失败原因(06 §1.4 基底 + M2 验收新增三值;`conflict`
|
||
/// 仅编辑链路会出现——40902 乐观锁/条件更新守卫落空)。与 pet 域同款
|
||
/// 网络归并口径:断网/超时/5xx 均并入 network_error,server_error
|
||
/// 保留给无法归类的兜底。
|
||
enum HealthRecordFailureReason {
|
||
validationError('validation_error'),
|
||
permissionDenied('permission_denied'),
|
||
notFound('not_found'),
|
||
rateLimited('rate_limited'),
|
||
networkError('network_error'),
|
||
serverError('server_error'),
|
||
conflict('conflict');
|
||
|
||
const HealthRecordFailureReason(this.value);
|
||
|
||
final String value;
|
||
}
|
||
|
||
/// viewed 的来源(06 §1.4 source 枚举)。
|
||
enum HealthRecordViewSource {
|
||
recordList('record_list'),
|
||
petDetail('pet_detail'),
|
||
reminder('reminder');
|
||
|
||
const HealthRecordViewSource(this.value);
|
||
|
||
final String value;
|
||
}
|
||
|
||
class HealthRecordAnalytics {
|
||
HealthRecordAnalytics(this._track);
|
||
|
||
/// 生产传 `AnalyticsService.trackEvent`,测试传录制桩。
|
||
final TrackEventFn _track;
|
||
|
||
/// 进入某类记录的创建表单并产生首次输入(每次进入记一次,表单层去重)。
|
||
void createStarted({
|
||
required HealthRecordType recordType,
|
||
required HealthRecordEntryPoint entryPoint,
|
||
}) {
|
||
_track('health_record_create_started', {
|
||
'recordType': recordType.value,
|
||
'entryPoint': entryPoint.value,
|
||
});
|
||
}
|
||
|
||
/// 创建接口成功响应(漏斗事件,北极星与 H1/H3/H4 的核心数据源)。
|
||
///
|
||
/// [photoCount] 无照片为 0(M2 不做媒体上传,恒 0,字段随字典保留)。
|
||
void createSucceeded({
|
||
required HealthRecordType recordType,
|
||
required int durationMs,
|
||
int photoCount = 0,
|
||
}) {
|
||
_track('health_record_create_succeeded', {
|
||
'recordType': recordType.value,
|
||
'durationMs': durationMs,
|
||
'photoCount': photoCount,
|
||
});
|
||
}
|
||
|
||
/// 创建失败:失败响应 / 超时 / 本地校验拦截。
|
||
///
|
||
/// [errorCode] 为业务错误码(本地校验/网络错误时缺席);
|
||
/// [httpStatus] 由五位业务码推导(`code ~/ 100`);
|
||
/// [attemptSeq] 为本次表单会话内第几次提交尝试(从 1 起)。
|
||
void createFailed({
|
||
required HealthRecordType recordType,
|
||
required HealthRecordFailureReason reason,
|
||
required int attemptSeq,
|
||
int? errorCode,
|
||
}) {
|
||
_track('health_record_create_failed', {
|
||
'recordType': recordType.value,
|
||
'failureReason': reason.value,
|
||
'attemptSeq': attemptSeq,
|
||
'errorCode': ?errorCode,
|
||
if (errorCode != null && errorCode >= 10000)
|
||
'httpStatus': errorCode ~/ 100,
|
||
});
|
||
}
|
||
|
||
/// 记录曝光。06 §1.4 定义在详情页可见;M2 体重/疫苗无独立详情页,
|
||
/// 以「列表页每次进入首个成功加载」为曝光时点(每次进入一次、
|
||
/// 不随滚动逐条上报,防事件洪水的意图不变)。
|
||
void viewed({
|
||
required HealthRecordType recordType,
|
||
required HealthRecordViewSource source,
|
||
}) {
|
||
_track('health_record_viewed', {
|
||
'recordType': recordType.value,
|
||
'source': source.value,
|
||
});
|
||
}
|
||
|
||
/// 编辑保存成功响应(06 §1.4:编辑不设 started)。
|
||
///
|
||
/// [fieldCount] 为本次变更字段数(差量 PATCH 的键数,不含 version)。
|
||
void editSucceeded({
|
||
required HealthRecordType recordType,
|
||
required int fieldCount,
|
||
}) {
|
||
_track('health_record_edit_succeeded', {
|
||
'recordType': recordType.value,
|
||
'fieldCount': fieldCount,
|
||
});
|
||
}
|
||
|
||
/// 编辑保存失败(失败原因含 `conflict`——40902 版本/状态守卫冲突,
|
||
/// M2 验收「并发冲突明确」场景的数据面)。属性集无 attemptSeq
|
||
/// (白名单对齐 06 §1.5)。
|
||
void editFailed({
|
||
required HealthRecordType recordType,
|
||
required HealthRecordFailureReason reason,
|
||
int? errorCode,
|
||
}) {
|
||
_track('health_record_edit_failed', {
|
||
'recordType': recordType.value,
|
||
'failureReason': reason.value,
|
||
'errorCode': ?errorCode,
|
||
if (errorCode != null && errorCode >= 10000)
|
||
'httpStatus': errorCode ~/ 100,
|
||
});
|
||
}
|
||
}
|