# 需求文档：小程序数据获取与展示效率优化

## 简介

对小程序三个核心页面（Performance 绩效页、Task-List 任务列表页、Task-Detail 任务详情页）进行数据获取和展示效率优化。优化覆盖两个维度：页面加载速度（减少 SQL 查询次数和连接开销）和页面间跳转体验（减少重复请求）。通过后端批量查询、SQL 层过滤、前端内存缓存 + 版本校验等手段，提升整体用户体验。

## 术语表

- **小程序（Miniprogram）**：微信小程序客户端，面向门店助教使用
- **Performance_页**：绩效概览页面，展示助教当月绩效、收入、TOP 客户、服务记录等
- **Task_List_页**：任务列表页面，展示助教待办任务卡片列表及绩效进度条
- **Task_Detail_页**：任务详情页面，展示单个任务的客户信息、维客线索、服务记录、AI 分析等
- **ETL_库**：数据仓库（`test_etl_feiqiu`），通过 `_fdw_context` 设置 RLS 参数后访问 `app.v_*` 视图
- **业务库**：应用数据库（`test_zqyy_app`），存储 `biz.coach_tasks`、`biz.notes`、`biz.ai_cache` 等业务表
- **N_Plus_1_问题**：循环中逐条执行 SQL 查询的反模式，导致查询次数随数据量线性增长
- **Batch_Query**：`fdw_queries.batch_query_for_task_list()`，单连接内批量执行多条 ETL 查询的封装函数
- **RS_指数**：关系指数（`rs_display`），来自 `v_dws_member_assistant_relation_index`，衡量助教与客户的关系紧密度
- **updated_at**：数据版本时间戳，用于前端缓存校验后端数据是否已变更
- **Cache_Store**：前端内存缓存模块，存储跨页面共享的 ETL 数据（会员信息、余额、RS 指数等）
- **salary_calc**：`v_dws_assistant_salary_calc` 视图，按 `assistant_id + salary_month` 唯一，包含工资计算参数
- **relation_index**：`v_dws_member_assistant_relation_index` 视图，按 `assistant_id + member_id` 唯一

## 需求

### 需求 1：Performance 页 N+1 查询消除

**用户故事：** 作为助教，我希望绩效页加载更快，以便在繁忙时段快速查看当月绩效数据。

#### 验收标准

1. WHEN `get_coach_detail` 被调用, THE Coach_Service SHALL 通过单条批量 SQL 查询获取所有 TOP 客户的 RS 指数，替代当前按 `member_id` 逐条循环调用 `get_relation_index` 的模式
2. THE FDW_Queries 模块 SHALL 提供 `get_relation_index_batch(conn, site_id, assistant_id, member_ids)` 函数，接受 `member_ids` 列表参数，返回 `dict[int, float]`（member_id → rs_display 映射）
3. WHEN `member_ids` 列表为空, THE `get_relation_index_batch` 函数 SHALL 返回空字典，不执行任何 SQL 查询
4. WHEN 批量查询执行后, THE 返回结果 SHALL 与逐条查询 `get_relation_index` 对相同 `member_ids` 的结果在 RS 指数值上完全一致
5. THE `_build_top_customers` 函数 SHALL 调用 `get_relation_index_batch` 一次性获取所有客户的 RS 指数，替代当前的 `for mid in member_ids` 循环

### 需求 2：Task-List 页服务端过滤优化

**用户故事：** 作为助教，我希望任务列表加载更快且数据准确，以便高效管理待办任务。

#### 验收标准

1. WHEN `get_task_list_v2` 查询 `biz.coach_tasks` 时, THE Task_Manager SHALL 在 SQL WHERE 子句中直接排除不满足展示条件的任务，替代拉取全量 200 条后在内存中过滤的模式
2. THE SQL 查询 SHALL 在 WHERE 子句中包含 `relationship_building` 任务的 RS 范围过滤条件（排除 RS 指数不在 `[rs_min, rs_max]` 范围内的 `member_id`）
3. WHEN RS 排除列表查询失败时, THE Task_Manager SHALL 降级为不排除任何任务（当前已有的容错行为保持不变）
4. THE 分页 `total` 计数 SHALL 与实际返回的过滤后结果集一致，跨页翻页时 `total` 值保持准确
5. WHILE 前端请求 `pageSize=200` 一次性加载时, THE 后端 SHALL 仅返回满足展示条件的任务，减少无效数据传输量

### 需求 3：前端内存缓存与版本校验

**用户故事：** 作为助教，我希望在页面间跳转时不重复等待相同数据加载，以获得流畅的操作体验。

#### 验收标准

1. THE Cache_Store SHALL 缓存以下跨页面共享的 ETL 数据：会员信息（`member_info`）、会员余额（`balance`）、RS 指数（`relation_index`）
2. THE 后端接口 SHALL 在每条返回数据中包含 `updated_at` 时间戳字段，表示该数据在后端的最后更新时间
3. WHEN 前端请求数据时, THE Cache_Store SHALL 先检查本地缓存是否存在且 `updated_at` 未过期；若缓存有效则直接使用，若缓存过期或不存在则发起网络请求
4. WHEN 后端数据发生变更（`updated_at` 更新）时, THE Cache_Store SHALL 在下次请求时检测到版本变化并重新获取最新数据
5. IF 缓存校验逻辑发生异常, THEN THE Cache_Store SHALL 降级为直接发起网络请求，确保功能可用性不受影响
6. THE Cache_Store SHALL 在小程序 `onHide`（切后台）或用户主动下拉刷新时清空全部缓存，确保回到前台时获取最新数据

### 需求 4：跳转传参优化

**用户故事：** 作为助教，我希望从任务列表点击进入任务详情时，已加载的客户信息能直接展示，减少等待时间。

#### 验收标准

1. WHEN 用户从 Task_List_页 点击任务卡片跳转到 Task_Detail_页 时, THE Task_List_页 SHALL 将已加载的任务数据（`customer_name`、`heart_score`、`balance`、`task_type`、`task_type_label`）通过全局 Cache_Store 传递给 Task_Detail_页
2. WHEN Task_Detail_页 加载时, THE Task_Detail_页 SHALL 优先从 Cache_Store 读取已有数据立即渲染骨架内容，同时异步请求完整详情数据
3. WHEN 完整详情数据返回后, THE Task_Detail_页 SHALL 用完整数据覆盖骨架内容，实现无缝过渡
4. IF Cache_Store 中无对应任务的预加载数据, THEN THE Task_Detail_页 SHALL 回退为当前的全量加载模式（显示 loading → 请求 → 渲染）
5. WHEN 用户从 Performance_页 点击客户卡片跳转到 Task_Detail_页 时, THE Performance_页 SHALL 同样将已有的客户数据（`customer_name`、`heart_score`）通过 Cache_Store 传递

### 需求 5：Task-Detail 页 ETL 查询合并

**用户故事：** 作为助教，我希望任务详情页加载更快，以便快速查看客户信息并采取行动。

#### 验收标准

1. WHEN `get_task_detail` 查询 ETL 数据时, THE Task_Manager SHALL 将 `member_info`、`balance`、`relation_index` 三个独立 ETL 连接合并为单连接批量查询
2. THE 合并后的查询 SHALL 复用 `_fdw_context` 单次连接，在同一个事务中依次执行会员信息、余额、RS 指数查询
3. WHEN 批量 ETL 查询失败时, THE Task_Manager SHALL 对每个子查询独立降级（返回默认值），不影响其他子查询的结果
4. THE 合并后的查询结果 SHALL 与当前三个独立查询的结果在数据内容上完全一致

### 需求 6：后端 updated_at 版本字段支持

**用户故事：** 作为开发者，我希望后端接口返回数据版本时间戳，以便前端缓存能准确判断数据是否过期。

#### 验收标准

1. THE 后端 SHALL 在 `get_task_list_v2` 返回的每个任务项中包含 `updated_at` 字段（来自 `biz.coach_tasks.updated_at`）
2. THE 后端 SHALL 在 `get_coach_detail` 返回的 `top_customers` 列表中，为每个客户附加 `data_updated_at` 字段（取 ETL 查询时的服务器时间戳）
3. THE 后端 SHALL 在 `get_task_detail` 返回中包含 `updated_at` 字段
4. IF `biz.coach_tasks` 表当前缺少 `updated_at` 列, THEN THE 数据库迁移 SHALL 添加该列，默认值为 `now()`，并在任务状态变更时自动更新
5. THE DDL 变更 SHALL 合并到主 DDL 迁移文件中，并通过审计流程记录

### 需求 7：Task-Detail 页折前/折后小时数条件显示

**用户故事：** 作为助教，我希望只在折前和折后小时数有差异时才看到折前小时数，以减少信息干扰。

#### 验收标准

1. WHEN 服务记录的折前小时数（`service_hours_raw`）与折后小时数（`service_hours`）不相等时, THE Task_Detail_页 SHALL 在折后小时数旁显示"折前 XX.Xh"标注
2. WHEN 折前小时数与折后小时数相等时, THE Task_Detail_页 SHALL 仅显示折后小时数，不显示折前标注
3. IF 折前小时数（`duration_raw`）为 `null` 或未返回, THEN THE Task_Detail_页 SHALL 视为与折后小时数相等，不显示折前标注
4. THE 折前小时数 SHALL 格式化为一位小数（如"折前 2.5h"），与折后小时数的格式保持一致

### 需求 8：LEFT JOIN LATERAL 酒水聚合优化（低优先级）

**用户故事：** 作为开发者，我希望服务记录的酒水明细聚合查询在数据量增长时仍保持高效。

#### 验收标准

1. WHILE 单次查询涉及的服务记录超过 50 条时, THE FDW_Queries 模块 SHALL 使用 CTE 预聚合方式替代当前的 `LEFT JOIN LATERAL` 子查询聚合酒水商品明细
2. WHEN 使用 CTE 预聚合后, THE 查询结果 SHALL 与 `LEFT JOIN LATERAL` 方式返回的 `drinks` 字段内容完全一致
3. WHILE 单次查询涉及的服务记录不超过 50 条时, THE FDW_Queries 模块 SHALL 保持当前的 `LEFT JOIN LATERAL` 方式（小数据量下性能已足够）

### 需求 9：数据基线快照与回归验证

**用户故事：** 作为开发者，我希望每次效率优化后能自动验证数据正确性，确保优化不引入数据偏差。

#### 验收标准

1. BEFORE 任何优化任务开始实施前, THE 验证脚本 SHALL 使用小燕（assistant_id: `2964673443302213`、site_id: `2790685415443269`）的真实数据，对三个核心接口（`get_coach_detail`、`get_task_list_v2`、`get_task_detail`）各执行一次，将完整返回值序列化为 JSON 基线快照
2. AFTER 每个优化任务完成后, THE 验证脚本 SHALL 使用相同参数再次调用接口，逐字段 diff 对比优化前后的返回值
3. THE diff 对比 SHALL 覆盖以下关键校验点：到手金额（hours × net_rate）、RS 指数、会员余额、服务记录条数和排序、酒水商品明细、TOP 客户列表
4. IF diff 发现任何数据值不一致（排除 `updated_at`、`data_updated_at` 等时间戳字段）, THEN THE 验证脚本 SHALL 报告失败并输出差异详情
5. THE 验证脚本 SHALL 以 pytest 形式编写，放置在 `tests/` 目录下，可通过 `pytest tests/test_perf_optimization_baseline.py -v` 执行
6. THE 基线快照 JSON 文件 SHALL 存储在 `tests/fixtures/perf_optimization/` 目录下，纳入版本控制

### 需求 10：AI 分析数据校对预留（后期）

**用户故事：** 作为开发者，我希望 AI 分析相关的数据字段在优化过程中保持接口兼容，以便后期校对和调整。

#### 验收标准

1. THE 优化过程 SHALL 不改变 `aiAnalysis`（`app4_analysis`）和 `talkingPoints`（`app5_talking_points`）字段的数据来源、查询逻辑和返回结构
2. THE `biz.ai_cache` 表的查询方式（按 `target_id + site_id + cache_type` 查询，`ORDER BY created_at DESC`）SHALL 在优化后保持不变
3. IF 后续需要对 AI 分析数据进行校对或调整, THEN THE 接口返回结构 SHALL 保持向后兼容（新增字段可以，删除或改名已有字段不可以）
4. THE 数据基线快照（需求 9）SHALL 包含 `aiAnalysis` 和 `talkingPoints` 字段的完整内容，作为后期 AI 校对的参考基准
