import 'package:patbond_flutter/analytics/page_view_tracker.dart'; import 'package:patbond_flutter/features/pets/pet_models.dart'; /// pet 域埋点强类型封装(06 号规划 §1.4/§4 字典 v2 三事件;沿用 /// 13 号规范 §3.1 惯例:枚举编译期锁死,业务代码禁止手拼事件名与属性)。 /// /// 三事件只覆盖「建宠」漏斗:`page_viewed(pet_form)` 承接到达段(路由 /// 埋点自动采集),started 在表单**首次输入**触发、每次进入记一次; /// 编辑不设事件(06 §1.4 自觉取舍:短表单漏斗价值低于事件成本)。 /// 建宠表单入口(06 §4:pet_create_started.entryPoint 枚举)。 /// post_register_guide 预留:注册后引导流尚未落地,落地时启用。 enum PetCreateEntryPoint { profileEmptyState('profile_empty_state'), petList('pet_list'), postRegisterGuide('post_register_guide'); const PetCreateEntryPoint(this.value); final String value; } /// 建宠失败原因(06 §4 枚举;pet_limit_reached 因产品未设上限未纳入, /// 若后续拍板设上限随字典增补)。客户端网络层不区分 5xx 与断网/超时 /// (均为 ApiNetworkException),两者并入 network_error,server_error /// 保留给无法归类的兜底。 enum PetCreateFailureReason { validationError('validation_error'), rateLimited('rate_limited'), networkError('network_error'), serverError('server_error'); const PetCreateFailureReason(this.value); final String value; } class PetAnalytics { PetAnalytics(this._track); /// 生产传 `AnalyticsService.trackEvent`,测试传录制桩。 final TrackEventFn _track; /// 进入建宠表单并产生首次输入(每次进入记一次,表单层负责去重)。 void createStarted({required PetCreateEntryPoint entryPoint}) { _track('pet_create_started', {'entryPoint': entryPoint.value}); } /// 建宠接口成功响应(漏斗事件)。 /// /// [durationMs] 为表单打开到成功的耗时;[petIndex] 为该用户第几只宠物 /// (H2 假设的直接数据源,与 pet.pets 事实表交叉验证)。 void createSucceeded({ required int durationMs, required PetSpecies species, required int petIndex, }) { _track('pet_create_succeeded', { 'durationMs': durationMs, 'species': species.name, 'petIndex': petIndex, }); } /// 建宠失败:失败响应 / 超时 / 本地校验拦截。 /// /// [errorCode] 为业务错误码(本地校验/网络错误时缺席); /// [httpStatus] 由五位业务码推导(`code ~/ 100`); /// [attemptSeq] 为本次表单会话内第几次提交尝试(从 1 起)。 void createFailed({ required PetCreateFailureReason reason, required int attemptSeq, int? errorCode, }) { _track('pet_create_failed', { 'failureReason': reason.value, 'attemptSeq': attemptSeq, 'errorCode': ?errorCode, if (errorCode != null && errorCode >= 10000) 'httpStatus': errorCode ~/ 100, }); } }