主线 1: rns1-customer-coach-api + 04-miniapp-core-business 后端实施
- 新增 GET /xcx/coaches/{id}/banner 轻量接口
- performance/records 加 coach_id 参数 + view_board_coach 权限分流
- coach/customer/performance/board/task 服务层重构
- fdw_queries 结算单粒度聚合 + consumption_summary 视图统一
- task_generator 回访宽限 72h + UPSERT 替代策略 + Step 5 保底清理
- recall_detector settle_type=3 双重限制 + 门店级 resolved
主线 2: 小程序权限分流 + 新增 coach-service-records 管理者视角业绩明细页
- perf-progress 共享模块去重 task-list/coach-detail 动画逻辑
- isScattered 散客标记端到端
- foodDetail/phoneFull/creator* 字段透传
主线 3: P19 指数回测框架 Phase 1+2
- 3 个指数表 stat_date 日快照模式
- 新增 DWS_INDEX_BACKFILL / DWS_TASK_SIMULATION 工具任务
- task_engine 升级 HTTP 实时 + 推演回测双模式
主线 4: Core 维度层启用
- 新增 CORE_DIM_SYNC 任务(DWD → core 4 维度表)
- 修复 app 视图空查询问题
主线 5: member_project_tag 改为 LAST_30_VISITS 消费次数窗口
主线 6: 2 个迁移 SQL 已执行(stat_date + member_project_tag 新窗口)
- schema 基线与 DDL 快照同步
主线 7: 开发机路径迁移 C:\NeoZQYY → C:\Project\NeoZQYY(约 95% 改动量)
附带: 新建运维脚本(churned_customer_report / simulate_historical_tasks /
backfill_index_snapshots)+ tools/task-analysis/ 任务分析工具
合计 157 文件。未包含中间产物(tmp/ .playwright-mcp/ inspect-* excel/sheet 分析 txt)。
审计记录见下一个 commit。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15 KiB
实施计划:小程序数据获取与展示效率优化
概述
按"数据正确性优先于性能"原则,先拍基线快照、再做 DDL 变更、然后后端优化、前端优化,最后收尾验证。后端使用 Python(FastAPI + pytest/hypothesis),前端使用 TypeScript(微信小程序 + fast-check),数据库使用 PostgreSQL DDL。
任务
-
1. 数据基线快照(优化前)
-
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
- 使用小燕真实数据(
-
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.sqlALTER 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
- 验证边界条件:空列表不执行 SQL,直接返回
-
-
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
- 确认 SQL WHERE 子句已包含
-
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
- 将 3 个独立 ETL 查询替换为
-
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
- 服务记录 > 50 条时使用 CTE 预聚合替代
-
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
- 8.1 运行基线快照 diff 对比
-
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.
- 运行 Monorepo 属性测试:
-
10. 前端优化:CacheStore 缓存模块
-
10.1 创建
utils/cache-store.ts缓存模块- 实现
CacheEntry<T>接口(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
- 点击客户卡片跳转 Task-Detail 时,将
-
11.4 集成
onHide和下拉刷新清空缓存- 在 App 级
onHide回调中调用cacheStore.clear() - 在支持下拉刷新的页面
onPullDownRefresh中调用cacheStore.clear() - Requirements: 3.6
- 在 App 级
-
-
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
- 14.1 启动后端服务,验证各端点完整请求-响应链路
-
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
- 15.1 审计本次实现中对数据库的所有改动
-
16. BD 手册更新
- 16.1 更新 BD 手册
docs/database/BD_Manual_coach_tasks.md- 新增
updated_at字段明细、触发器说明 - 包含:字段明细、约束与索引、验证 SQL(≥3 条)、兼容性影响、回滚策略
- 记录变更原因和影响范围
- Requirements: 6.4, 6.5
- 新增
- 16.1 更新 BD 手册
-
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
- 在
- 17.1 更新后端 API 参考文档
-
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.
- 运行 Monorepo 属性测试:
备注
- 标记
*的子任务为可选(属性测试/单元测试),可跳过以加速 MVP - 每个任务引用了具体的需求编号以确保可追溯性
- 属性测试验证通用正确性属性(Property 1-12),单元测试验证具体边界条件
- 检查点任务确保增量验证,避免问题累积
- 需求 10(AI 分析数据校对预留)通过基线快照覆盖(任务 1.1 和 8.1),无需独立实施任务
硬性规定:属性测试执行规范
- 分组执行:每次只运行单个属性测试函数(
-k "test_property_N"),禁止一次性跑全部 - 后台 CLI:使用
controlPwshProcess启动测试,getProcessOutput轮询结果(间隔 ≥ 15 秒) - 确认完成再继续:必须在输出中看到明确的
passed/failed/error后才可进入下一步,禁止"启动测试后立即继续编码" - 超时保护:
executePwsh的timeout设为预估时间 3 倍(最少 120 秒),hypothesismax_examples=100 - 详见:
.kiro/steering/testing-env.md属性测试执行规范章节
硬性规定:前后端联调期间的效率优化框架
当用户在联调过程中提及"效率优化"或"本页已完成,进行效率优化"时,所有调试和改动必须在本 spec 的框架下进行:
- 数据正确性优先:任何优化改动前先确认基线快照存在(任务 1),改动后跑 diff 验证(任务 8)
- AI 数据不动:
aiAnalysis、talkingPoints的查询逻辑和返回结构保持不变(需求 10),后期单独校对 - 联调中的样式/交互调整:属于联调范畴,不受效率优化 spec 约束,但改动不得破坏优化后的数据链路
- 联调中发现的数据问题:如果是优化引入的偏差,回退到基线快照对比定位;如果是原有问题,单独记录不混入本 spec