# 实施计划:小程序数据获取与展示效率优化 ## 概述 按"数据正确性优先于性能"原则,先拍基线快照、再做 DDL 变更、然后后端优化、前端优化,最后收尾验证。后端使用 Python(FastAPI + pytest/hypothesis),前端使用 TypeScript(微信小程序 + fast-check),数据库使用 PostgreSQL DDL。 ## 任务 - [ ] 1. 数据基线快照(优化前) - [x] 1.1 创建基线快照测试脚本 `tests/test_perf_optimization_baseline.py` - 使用小燕真实数据(`assistant_id: 2964673443302213`、`site_id: 2790685415443269`) - 对 `get_coach_detail`、`get_task_list_v2`、`get_task_detail` 各执行一次 - 将完整返回值序列化为 JSON 存储到 `tests/fixtures/perf_optimization/` - 实现逐字段 diff 对比函数,排除 `updated_at`、`data_updated_at` 等时间戳字段 - diff 覆盖:到手金额、RS 指数、会员余额、服务记录条数/排序、酒水明细、TOP 客户列表、`aiAnalysis`、`talkingPoints` - _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 10.4_ - [x] 1.2 执行基线快照采集 - 运行 `pytest tests/test_perf_optimization_baseline.py -v` 生成基线 JSON 文件 - 确认三个快照文件已生成并纳入版本控制 - _Requirements: 9.1, 9.6_ - [ ] 2. 数据库 DDL 变更 - [ ] 2.1 创建迁移脚本 `db/zqyy_app/migrations/YYYY-MM-DD__coach_tasks_updated_at.sql` - `ALTER TABLE biz.coach_tasks ADD COLUMN updated_at TIMESTAMPTZ NOT NULL DEFAULT now()` - 创建触发器函数 `biz.coach_tasks_set_updated_at()` 和触发器 `trg_coach_tasks_updated_at` - 触发器在 `BEFORE UPDATE` 时自动设置 `NEW.updated_at = now()` - _Requirements: 6.4, 6.5_ - [ ] 2.2 编写属性测试:updated_at 自动更新 - **Property 9: 任务更新后 updated_at 自动变化** - 使用 hypothesis 生成随机任务状态变更 - 验证 UPDATE 后 `updated_at` 严格大于更新前的值 - **验证: 需求 6.4** - [ ] 3. 检查点 — DDL 变更验证 - 执行迁移脚本到测试库,验证新字段和触发器已正确创建 - 确保 Property 9 测试通过,ask the user if questions arise. - [ ] 4. 后端优化:Performance 页 N+1 查询消除 - [ ] 4.1 新增 `fdw_queries.get_relation_index_batch()` 函数 - 接受 `conn, site_id, assistant_id, member_ids` 参数 - 返回 `dict[int, float]`(member_id → rs_display 映射) - `member_ids` 为空时直接返回空字典,不执行 SQL - 使用 `WHERE assistant_id = %s AND member_id = ANY(%s)` 查询 - 复用已有 `_fdw_context` 连接管理 - _Requirements: 1.2, 1.3_ - [ ] 4.2 编写属性测试:批量 RS 查询与逐条查询等价性 - **Property 1: 批量 RS 查询与逐条查询等价性** - 使用 hypothesis `st.lists(st.integers(min_value=1))` 生成 member_ids - 验证 `get_relation_index_batch` 返回与逐条 `get_relation_index` 结果完全一致 - **验证: 需求 1.4** - [ ] 4.3 修改 `coach_service._build_top_customers()` 使用批量查询 - 将 `for mid in member_ids` 循环替换为单次 `get_relation_index_batch` 调用 - 返回数据结构不变 - _Requirements: 1.1, 1.5_ - [ ] 4.4 修改 `coach_service.get_coach_detail()` 添加 `data_updated_at` 字段 - `top_customers` 列表中每个客户新增 `data_updated_at`(取查询时 `now()`) - _Requirements: 6.2_ - [ ] 4.5 编写单元测试:`member_ids` 为空返回空字典 - 验证边界条件:空列表不执行 SQL,直接返回 `{}` - _Requirements: 1.3_ - [ ] 5. 后端优化:Task-List 页服务端过滤 - [ ] 5.1 验证并完善 `task_manager.get_task_list_v2()` SQL 层过滤 - 确认 SQL WHERE 子句已包含 `relationship_building` 任务的 RS 范围过滤 - 验证 `total` 计数与实际过滤后结果集一致 - 返回每个任务项新增 `updated_at` 字段 - _Requirements: 2.1, 2.2, 2.4, 2.5, 6.1_ - [ ] 5.2 编写属性测试:RS 范围过滤 - **Property 2: relationship_building 任务 RS 范围过滤** - 生成随机 RS 值和 rs_min/rs_max 范围 - 验证返回的 `relationship_building` 任务 RS 指数在 `(rs_min, rs_max)` 范围内 - **验证: 需求 2.2** - [ ] 5.3 编写属性测试:分页 total 一致性 - **Property 3: 分页 total 与实际结果集一致性** - 生成随机 page/page_size 参数 - 验证 `total` 等于遍历所有页累计的 `items` 总数 - **验证: 需求 2.4** - [ ] 5.4 编写单元测试:RS 排除列表查询失败降级 - 验证查询失败时降级为不排除任何任务 - _Requirements: 2.3_ - [ ] 6. 后端优化:Task-Detail 页 ETL 查询合并 - [ ] 6.1 新增 `fdw_queries.batch_query_for_task_detail()` 函数 - 接受 `conn, site_id, assistant_id, member_id` 参数 - 单连接批量查询 `member_info`、`balance`、`rs_score` - 每个子查询用 try/except 包裹,失败返回默认值 - 复用 `_fdw_context` 单连接模式 - _Requirements: 5.1, 5.2, 5.3_ - [ ] 6.2 编写属性测试:Task-Detail 批量查询等价性 - **Property 8: Task-Detail 批量查询与独立查询等价性** - 生成随机 member_id - 验证 `batch_query_for_task_detail` 返回与三个独立查询结果一致 - **验证: 需求 5.4** - [ ] 6.3 修改 `task_manager.get_task_detail()` 使用批量查询 - 将 3 个独立 ETL 查询替换为 `batch_query_for_task_detail` 单次调用 - 返回数据结构不变,新增 `updated_at` 字段 - _Requirements: 5.1, 6.3_ - [ ] 6.4 编写属性测试:接口返回包含版本时间戳 - **Property 4: 接口返回包含版本时间戳** - 验证 `get_task_list_v2`、`get_task_detail`、`get_coach_detail` 返回数据包含有效 ISO 8601 时间戳 - **验证: 需求 3.2, 6.1, 6.2, 6.3** - [ ] 6.5 编写单元测试:子查询独立降级 - 验证某子查询失败时返回默认值,不影响其他子查询 - _Requirements: 5.3_ - [ ] 7. 后端优化:酒水聚合 CTE 预聚合(低优先级) - [ ] 7.1 修改 `fdw_queries.get_service_records_for_task()` 添加 CTE 分支 - 服务记录 > 50 条时使用 CTE 预聚合替代 `LEFT JOIN LATERAL` - ≤ 50 条时保持现有方式 - _Requirements: 8.1, 8.3_ - [ ] 7.2 编写属性测试:CTE 预聚合与 LEFT JOIN LATERAL 等价性 - **Property 12: CTE 预聚合与 LEFT JOIN LATERAL 等价性** - 生成随机服务记录集合(> 50 条) - 验证两种方式返回的 `drinks` 字段内容完全一致 - **验证: 需求 8.2** - [ ] 8. 后端回归验证 - [ ] 8.1 运行基线快照 diff 对比 - 使用相同参数再次调用三个核心接口 - 逐字段 diff 对比优化前后返回值 - 确认 `aiAnalysis`、`talkingPoints` 字段未被改变 - _Requirements: 9.2, 9.3, 9.4, 10.1, 10.2, 10.3, 10.4_ - [ ] 9. 检查点 — 后端优化完成验证 - 运行 Monorepo 属性测试:`cd C:\Project\NeoZQYY && pytest tests/ -v` - 运行后端单元测试:`cd apps/backend && pytest tests/ -v` - 确保所有属性测试(Property 1-4, 8-9, 12)和单元测试全部通过 - 确保基线快照 diff 无数据偏差 - ask the user if questions arise. - [ ] 10. 前端优化:CacheStore 缓存模块 - [ ] 10.1 创建 `utils/cache-store.ts` 缓存模块 - 实现 `CacheEntry` 接口(`data`、`updatedAt`、`cachedAt`) - 实现 `CacheStore` 类:`get()`、`set()`、`clear()`、`setTaskPreload()`、`getTaskPreload()` - 缓存 key 格式:`{dataType}:{id}` - 所有读写操作用 try/catch 包裹,异常时静默降级 - _Requirements: 3.1, 3.3, 3.4, 3.5, 3.6_ - [ ] 10.2 编写属性测试:缓存版本校验行为 - **Property 5: 缓存版本校验行为** - 使用 fast-check `fc.string()` 生成 updatedAt 时间戳 - 验证缓存命中/过期判定逻辑 - **验证: 需求 3.3, 3.4** - [ ] 10.3 编写属性测试:缓存 clear 后为空 - **Property 6: 缓存 clear 后为空** - 使用 fast-check `fc.dictionary()` 生成随机缓存条目 - 验证 `clear()` 后所有 `get()` 返回 `null` - **验证: 需求 3.6** - [ ] 10.4 编写属性测试:预加载数据 round-trip - **Property 7: 预加载数据 round-trip** - 使用 fast-check `fc.record()` 生成 TaskPreloadData - 验证 `setTaskPreload` → `getTaskPreload` 数据完全一致 - **验证: 需求 4.1** - [ ] 10.5 编写单元测试:CacheStore 异常降级 - 验证缓存读写异常时静默降级为网络请求 - _Requirements: 3.5_ - [ ] 11. 前端优化:跳转传参与骨架渲染 - [ ] 11.1 修改 Task-List 页跳转传参 - 点击任务卡片时调用 `cacheStore.setTaskPreload()` 写入预加载数据 - 传递字段:`customer_name`、`heart_score`、`balance`、`task_type`、`task_type_label` - _Requirements: 4.1_ - [ ] 11.2 修改 Task-Detail 页骨架渲染 - `onLoad` 时先从 `cacheStore.getTaskPreload()` 读取预加载数据 - 有缓存:立即渲染骨架内容,同时异步请求完整详情数据 - 完整数据返回后覆盖骨架内容,实现无缝过渡 - 无缓存:回退为当前全量加载模式 - _Requirements: 4.2, 4.3, 4.4_ - [ ] 11.3 修改 Performance 页跳转传参 - 点击客户卡片跳转 Task-Detail 时,将 `customer_name`、`heart_score` 写入 `cacheStore` - _Requirements: 4.5_ - [ ] 11.4 集成 `onHide` 和下拉刷新清空缓存 - 在 App 级 `onHide` 回调中调用 `cacheStore.clear()` - 在支持下拉刷新的页面 `onPullDownRefresh` 中调用 `cacheStore.clear()` - _Requirements: 3.6_ - [ ] 12. 前端优化:折前/折后小时数条件显示 - [ ] 12.1 实现折前/折后条件显示逻辑 - `duration_raw` 不为 null 且 `duration_raw ≠ duration` 时显示"折前 XX.Xh"标注 - `duration_raw` 为 null 或等于 `duration` 时仅显示折后小时数 - 格式化为一位小数(如"折前 2.5h") - _Requirements: 7.1, 7.2, 7.3, 7.4_ - [ ] 12.2 编写属性测试:折前/折后条件显示逻辑 - **Property 10: 折前/折后条件显示逻辑** - 使用 fast-check `fc.float()` 生成 duration 和 duration_raw - 验证显示/隐藏"折前"标注的条件判断 - **验证: 需求 7.1, 7.2, 7.3** - [ ] 12.3 编写属性测试:折前小时数格式化 - **Property 11: 折前小时数格式化** - 使用 fast-check `fc.float({min: 0, max: 1000})` - 验证输出恰好一位小数且 `parseFloat(formatted) ≈ round(hours, 1)` - **验证: 需求 7.4** - [ ] 13. 检查点 — 前端优化完成验证 - 运行前端测试:`cd apps/miniprogram && npm test` - 确保所有属性测试(Property 5-7, 10-11)和单元测试全部通过 - ask the user if questions arise. - [ ] 14. 前后端联调与集成验证 - [ ] 14.1 启动后端服务,验证各端点完整请求-响应链路 - 使用真实 FDW 连接验证 SQL 查询正确性 - 验证 JSON 响应结构与 Schema 定义一致(camelCase 序列化) - 验证 `updated_at` / `data_updated_at` 字段在响应中正确返回 - _Requirements: 6.1, 6.2, 6.3_ - [ ] 14.2 前端联调验证 - 确认前端页面能正确调用优化后的 API 并渲染数据 - 验证缓存命中/过期场景下前端行为正确 - 验证空数据/降级场景下前端不崩溃 - _Requirements: 3.3, 3.4, 3.5, 4.2, 4.3, 4.4_ - [ ] 15. 数据库变更审计与 DDL 合并 - [ ] 15.1 审计本次实现中对数据库的所有改动 - 检查新增字段(`updated_at`)、新增触发器(`trg_coach_tasks_updated_at`) - 确认无遗漏的 DDL 变更 - _Requirements: 6.4, 6.5_ - [ ] 15.2 执行迁移脚本到测试库 - 验证新字段和触发器已正确创建(使用 BD 手册中的验证 SQL) - _Requirements: 6.4_ - [ ] 15.3 合并到主 DDL 基线文件 - 业务库 → `docs/database/ddl/zqyy_app__biz.sql` - _Requirements: 6.5_ - [ ] 15.4 编写回滚脚本(逆序 DROP TRIGGER / DROP FUNCTION / DROP COLUMN) - _Requirements: 6.5_ - [ ] 16. BD 手册更新 - [ ] 16.1 更新 BD 手册 `docs/database/BD_Manual_coach_tasks.md` - 新增 `updated_at` 字段明细、触发器说明 - 包含:字段明细、约束与索引、验证 SQL(≥3 条)、兼容性影响、回滚策略 - 记录变更原因和影响范围 - _Requirements: 6.4, 6.5_ - [ ] 17. 文档同步更新 - [ ] 17.1 更新后端 API 参考文档 - 在 `apps/backend/docs/API-REFERENCE.md` 更新接口返回结构变更(`updated_at`、`data_updated_at` 字段) - 更新 `apps/backend/README.md` 相关模块摘要 - _Requirements: 6.1, 6.2, 6.3_ - [ ] 17.2 更新小程序接口契约文档 - 在 `docs/miniprogram-dev/API-contract.md` 更新接口返回结构 - _Requirements: 3.2, 6.1, 6.2, 6.3_ - [ ] 17.3 更新文档地图 - 在 `docs/DOCUMENTATION-MAP.md` 新增本次变更相关条目 - _Requirements: 6.5_ - [ ] 18. 最终检查点 — 全量验证 - 运行 Monorepo 属性测试:`cd C:\Project\NeoZQYY && pytest tests/ -v` - 运行后端单元测试:`cd apps/backend && pytest tests/ -v` - 运行前端测试:`cd apps/miniprogram && npm test` - 确保所有属性测试(Property 1-12)和单元测试全部通过 - 确保基线快照 diff 无数据偏差 - 确保 DDL 迁移已合并到主基线 - 确保 BD 手册已同步更新 - 确保 API 文档、后端 README、文档地图均已更新 - 确保前端页面连接真实后端运行正常 - ask the user if questions arise. ## 备注 - 标记 `*` 的子任务为可选(属性测试/单元测试),可跳过以加速 MVP - 每个任务引用了具体的需求编号以确保可追溯性 - 属性测试验证通用正确性属性(Property 1-12),单元测试验证具体边界条件 - 检查点任务确保增量验证,避免问题累积 - 需求 10(AI 分析数据校对预留)通过基线快照覆盖(任务 1.1 和 8.1),无需独立实施任务 ## 硬性规定:属性测试执行规范 1. **分组执行**:每次只运行单个属性测试函数(`-k "test_property_N"`),禁止一次性跑全部 2. **后台 CLI**:使用 `controlPwshProcess` 启动测试,`getProcessOutput` 轮询结果(间隔 ≥ 15 秒) 3. **确认完成再继续**:必须在输出中看到明确的 `passed`/`failed`/`error` 后才可进入下一步,禁止"启动测试后立即继续编码" 4. **超时保护**:`executePwsh` 的 `timeout` 设为预估时间 3 倍(最少 120 秒),hypothesis `max_examples=100` 5. **详见**:`.kiro/steering/testing-env.md` 属性测试执行规范章节 ## 硬性规定:前后端联调期间的效率优化框架 当用户在联调过程中提及"效率优化"或"本页已完成,进行效率优化"时,所有调试和改动必须在本 spec 的框架下进行: 1. **数据正确性优先**:任何优化改动前先确认基线快照存在(任务 1),改动后跑 diff 验证(任务 8) 2. **AI 数据不动**:`aiAnalysis`、`talkingPoints` 的查询逻辑和返回结构保持不变(需求 10),后期单独校对 3. **联调中的样式/交互调整**:属于联调范畴,不受效率优化 spec 约束,但改动不得破坏优化后的数据链路 4. **联调中发现的数据问题**:如果是优化引入的偏差,回退到基线快照对比定位;如果是原有问题,单独记录不混入本 spec