# Implementation Plan: RNS1.1 任务与绩效接口

## Overview

基于 design.md 的 7 个组件结构，增量扩展现有后端路由和服务层，新增 FDW 查询封装、绩效服务、Pydantic Schema，并完成前端适配。所有 FDW 查询集中在 `fdw_queries.py`，服务层分为 `task_manager.py`（扩展）和 `performance_service.py`（新增）。

## Tasks

- [x] 1. Pydantic Schema 定义（组件 6）
  - [x] 1.1 扩展 `apps/backend/app/schemas/xcx_tasks.py`，新增 `PerformanceSummary`（15+ 字段：tierNodes, basicHours, bonusHours, currentTier, nextTierHours, tierCompleted, bonusMoney, incomeTrend, incomeTrendDir, prevMonth, currentTierLabel 等）、`TaskItem`（扩展版，含 lastVisitDays, balance, aiSuggestion 可选字段）、`TaskListResponse`（items + total + page + pageSize + performance）
    - 新增 `RetentionClue`、`ServiceRecord`（含 courseTypeClass 枚举）、`AiAnalysis`、`NoteItem`（含 score 可选字段）、`ServiceSummary`、`TaskDetailResponse` 模型
    - _Requirements: 1.1, 1.6, 2.1, 2.2, 2.4, 2.9_
  - [x] 1.2 新建 `apps/backend/app/schemas/xcx_performance.py`，定义 `DateGroupRecord`（含可选 avatarChar/avatarColor）、`DateGroup`、`TierInfo`、`IncomeItem`（含 desc）、`CustomerSummary`、`NewCustomer`、`RegularCustomer`、`PerformanceOverviewResponse` 模型
    - 定义 `RecordsSummary`（totalCount, totalHours, totalHoursRaw, totalIncome）、`PerformanceRecordsResponse`（summary + dateGroups + hasMore）模型
    - _Requirements: 3.1, 3.3, 3.4, 3.5, 4.1, 4.2, 4.3_

- [x] 2. FDW 查询封装服务（组件 3 — 新增 fdw_queries.py）
  - [x] 2.1 新建 `apps/backend/app/services/fdw_queries.py`，实现 `_fdw_context(conn, site_id)` 上下文管理器（BEGIN + SET LOCAL app.current_site_id）和 `get_member_info(conn, site_id, member_ids)` 批量查询会员信息
    - ⚠️ DQ-6：通过 member_id JOIN fdw_etl.v_dim_member，取 scd2_is_current=1
    - _Requirements: 8.7_
  - [x] 2.2 实现 `get_member_balance(conn, site_id, member_ids)` 批量查询会员储值卡余额
    - ⚠️ DQ-7：通过 member_id JOIN fdw_etl.v_dim_member_card_account，取 scd2_is_current=1
    - _Requirements: 1.8, 8.7_
  - [x] 2.3 实现 `get_last_visit_days(conn, site_id, member_ids)` 批量查询客户距上次到店天数
    - 来源：fdw_etl.v_dwd_assistant_service_log，WHERE is_trash = false
    - _Requirements: 1.7_
  - [x] 2.4 实现 `get_salary_calc(conn, site_id, assistant_id, year, month)` 查询助教绩效/档位/收入数据
    - ⚠️ DWD-DOC 规则 1：收入使用 items_sum 口径
    - ⚠️ DWD-DOC 规则 2：费用使用 assistant_pd_money + assistant_cx_money
    - _Requirements: 1.2, 1.3, 3.7, 8.5, 8.6_
  - [x] 2.5 实现 `get_service_records(conn, site_id, assistant_id, year, month, limit, offset)` 查询助教服务记录明细
    - ⚠️ 废单排除：WHERE is_trash = false；DQ-6：客户姓名通过 member_id JOIN dim_member
    - _Requirements: 2.11, 2.12, 3.5, 4.7, 4.8, 8.8_
  - [x] 2.6 实现 `get_service_records_for_task(conn, site_id, assistant_id, member_id, limit)` 查询特定客户的服务记录（TASK-2 用）
    - _Requirements: 2.1, 2.3_


- [x] 3. Checkpoint — Schema 与 FDW 查询层验证
  - Ensure all tests pass, ask the user if questions arise.

- [x] 4. task_manager 服务扩展（组件 4 — 扩展现有 task_manager.py）
  - [x] 4.1 在 `apps/backend/app/services/task_manager.py` 中实现 `get_task_list_v2(user_id, site_id, status, page, page_size)`
    - 逻辑：_get_assistant_id() → 查询 coach_tasks 带分页 → fdw_queries 批量获取会员信息/余额/lastVisitDays → fdw_queries.get_salary_calc() 获取绩效概览 → 查询 ai_cache 获取 aiSuggestion → 组装 TaskListResponse
    - 扩展字段（lastVisitDays/balance/aiSuggestion）采用优雅降级：单个查询失败返回 null，不影响整体响应
    - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10_
  - [x] 4.2 在 `apps/backend/app/services/task_manager.py` 中实现 `get_task_detail(task_id, user_id, site_id)`
    - 逻辑：_get_assistant_id() + _verify_task_ownership() 权限校验 → 查询 coach_tasks 基础信息 → 查询 member_retention_clue 维客线索 → 查询 ai_cache（app5_talking_points → talkingPoints, app4_analysis → aiAnalysis）→ fdw_queries.get_service_records_for_task() 服务记录（最多 20 条）→ 查询 notes（最多 20 条）→ 组装 TaskDetailResponse
    - tag 字段净化：去除换行符 \n，多行标签使用空格分隔
    - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 2.10, 2.11, 2.12, 2.13, 2.14_
  - [x] 4.3 Write property test: 收入趋势计算正确性
    - **Property 1: 收入趋势计算正确性**
    - 生成器：st.floats(min_value=0, max_value=1e6) × 2
    - 验证：incomeTrendDir 方向正确、差值正确、前缀符号一致
    - **Validates: Requirements 1.4**
  - [x] 4.4 Write property test: lastVisitDays 计算正确性
    - **Property 2: lastVisitDays 计算正确性**
    - 生成器：st.dates(max_value=date.today())
    - 验证：天数差 = (today - date).days，非负
    - **Validates: Requirements 1.7**
  - [x] 4.5 Write property test: 维客线索 tag 净化与 source 枚举
    - **Property 3: 维客线索 tag 净化与 source 枚举**
    - 生成器：st.text() 生成含 \n 的 tag + st.sampled_from source
    - 验证：tag 无换行、source 在枚举内
    - **Validates: Requirements 2.4, 2.5**
  - [x] 4.6 Write property test: courseTypeClass 枚举映射
    - **Property 4: courseTypeClass 枚举映射**
    - 生成器：st.sampled_from(ALL_COURSE_TYPES)
    - 验证：结果在 {basic, vip, tip, recharge, incentive} 内、无 tag- 前缀
    - **Validates: Requirements 2.9, 4.4**

- [x] 5. performance_service 服务（组件 5 — 新增 performance_service.py）
  - [x] 5.1 新建 `apps/backend/app/services/performance_service.py`，实现 `get_overview(user_id, site_id, year, month)`
    - 逻辑：获取 assistant_id → fdw_queries.get_salary_calc() 档位/收入/费率 → fdw_queries.get_service_records() 按日期分组为 DateGroup → 聚合新客/常客列表 → 计算 incomeItems（含 desc 费率描述）→ 查询上月收入 lastMonthIncome
    - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13_
  - [x] 5.2 实现 `get_records(user_id, site_id, year, month, page, page_size)`
    - 逻辑：获取 assistant_id → fdw_queries.get_service_records() 带分页 → 按日期分组为 dateGroups → 计算 summary 汇总 → 返回 { summary, dateGroups, hasMore }
    - PERF-2 不返回 avatarChar/avatarColor（前端自行计算）
    - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8_
  - [x] 5.3 Write property test: 分页与排序
    - **Property 5: 列表分页与排序**
    - 生成器：st.lists(st.fixed_dictionaries(...)) + st.integers(1,10) page/pageSize
    - 验证：记录数 ≤ pageSize、hasMore 正确、排序正确
    - **Validates: Requirements 2.3, 4.2**
  - [x] 5.4 Write property test: DateGroup 分组正确性
    - **Property 6: DateGroup 分组正确性**
    - 生成器：st.lists(st.fixed_dictionaries({date, hours, income}))
    - 验证：日期唯一、组内日期一致、汇总正确、按日期倒序
    - **Validates: Requirements 3.3, 4.3**
  - [x] 5.5 Write property test: incomeItems desc 格式化
    - **Property 7: incomeItems desc 格式化**
    - 生成器：st.floats(min_value=0.01) rate × st.floats(min_value=0) hours
    - 验证：desc 包含费率值和工时值，格式为 "{rate}元/h × {hours}h"
    - **Validates: Requirements 3.9**
  - [x] 5.6 Write property test: 月度汇总聚合正确性
    - **Property 8: 月度汇总聚合正确性**
    - 生成器：st.lists(st.fixed_dictionaries({hours, hours_raw, income}))
    - 验证：totalCount/totalHours/totalHoursRaw/totalIncome 聚合正确
    - **Validates: Requirements 4.6**

- [x] 6. Checkpoint — 服务层验证
  - Ensure all tests pass, ask the user if questions arise.

- [x] 7. xcx_tasks Router 扩展（组件 1）
  - [x] 7.1 修改 `apps/backend/app/routers/xcx_tasks.py`：将 `GET /api/xcx/tasks` 响应从 `list[TaskListItem]` 改为 `TaskListResponse`（含 items + performance + 分页），新增 status/page/pageSize 查询参数，调用 `task_manager.get_task_list_v2()`
    - _Requirements: 1.1, 1.2_
  - [x] 7.2 在 `routers/xcx_tasks.py` 中新增 `GET /api/xcx/tasks/{task_id}` 端点，调用 `task_manager.get_task_detail()`，含 require_approved() 权限校验
    - _Requirements: 2.1, 2.13, 2.14_
  - [x] 7.3 修改 `routers/xcx_tasks.py` 中 `POST .../pin` 和 `POST .../unpin` 端点，响应对齐契约格式 `{ isPinned: bool }`
    - _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5_
  - [x] 7.4 修改 `POST /api/xcx/notes` 端点（`xcx_notes.py`），接受请求体中的可选 `score` 字段（number, 1-5），存入 `biz.coach_notes.score` 列；超出 1-5 范围返回 422
    - _Requirements: 6.3_
  - [x] 7.5 Write property test: Pin/Unpin 状态往返
    - **Property 9: Pin/Unpin 状态往返**
    - 生成器：st.booleans() 初始状态
    - 验证：pin→true、unpin→false、往返恢复
    - **Validates: Requirements 5.1, 5.2, 5.3**
  - [x] 7.6 Write property test: 权限与数据隔离
    - **Property 10: 权限与数据隔离**
    - 生成器：st.sampled_from(INVALID_USER_SCENARIOS)
    - 验证：未审核/无绑定/无权限/非本人任务 → 所有端点返回 403
    - **Validates: Requirements 2.13, 8.1, 8.2, 8.4**
  - [x] 7.7 Write property test: 备注 score 输入验证
    - **Property 12: 备注 score 输入验证**
    - 生成器：st.integers()
    - 验证：1-5 接受、超范围拒绝 422、null 接受
    - **Validates: Requirements 6.3**

- [x] 8. xcx_performance Router（组件 2 — 新增）
  - [x] 8.1 新建 `apps/backend/app/routers/xcx_performance.py`，实现 `GET /api/xcx/performance` 端点（接受 year/month 参数），调用 `performance_service.get_overview()`，含 require_approved() 权限校验
    - _Requirements: 3.1, 3.2_
  - [x] 8.2 在 `routers/xcx_performance.py` 中实现 `GET /api/xcx/performance/records` 端点（接受 year/month/page/pageSize 参数），调用 `performance_service.get_records()`
    - _Requirements: 4.1, 4.2_
  - [x] 8.3 修改 `apps/backend/app/main.py`，导入并注册 `xcx_performance.router`
    - _Requirements: 3.1, 4.1_

- [x] 9. Checkpoint — 后端路由层验证
  - Ensure all tests pass, ask the user if questions arise.

- [x] 10. 前端适配（组件 7）
  - [x] 10.1 修改 `apps/miniprogram/miniprogram/services/api.ts`：新增 `pinTask()`、`unpinTask()`、`fetchTaskDetail()`、`fetchPerformanceOverview(year, month)`、`fetchPerformanceRecords(year, month, page)` 函数；`createNote()` 增加可选 `score` 参数
    - _Requirements: 5.6, 6.1, 6.2, 7.1_
  - [x] 10.2 修改 `apps/miniprogram/miniprogram/pages/task-list/task-list.ts`：消费 `TaskListResponse` 新结构（items + performance），`buildPerfData()` 使用 15+ 字段绩效数据，移除 mock 数据
    - _Requirements: 6.4, 6.5_
  - [x] 10.3 修改 task-detail 页面：调用 `fetchTaskDetail()`；根据 `balance` 前端本地计算 `storageLevel`，根据 `heartScore` 计算 `relationLevel`/`relationLevelText`/`relationColor`
    - _Requirements: 6.4, 6.5_
  - [x] 10.4 修改 `apps/miniprogram/miniprogram/pages/performance/performance.ts`：添加月份切换控件（左右箭头 + 月份标签），实现 `switchMonth()` 调用 `fetchPerformanceOverview`，含加载状态管理
    - _Requirements: 7.1, 7.2, 7.3_
  - [x] 10.5 修改 `apps/miniprogram/miniprogram/pages/performance-records/performance-records.ts`：修复 `switchMonth()` 中 page 未重置的 Bug，切换月份时清空记录列表并重新加载（page 重置为 1）
    - _Requirements: 7.4, 7.5_
  - [x] 10.6 在 performance 和 performance-records 页面中，使用 `nameToAvatarColor()` 从 `customerName` 计算 `avatarChar`/`avatarColor`，不依赖后端
    - _Requirements: 7.6_
  - [x] 10.7 Write property test: 前端派生字段计算
    - **Property 11: 前端派生字段计算**
    - 生成器：st.text(min_size=1) name + st.floats(0, 1e6) balance + st.floats(0, 10) heartScore
    - 验证：avatarChar=name[0]、确定性、storageLevel/relationLevel 单调性
    - **Validates: Requirements 6.4, 6.5, 7.6**

- [x] 11. Final checkpoint — 全量验证
  - Ensure all tests pass, ask the user if questions arise.

## Notes

- Tasks marked with `*` are optional and can be skipped for faster MVP
- 组件 3（fdw_queries.py）是所有 FDW 查询的集中封装，组件 4/5 通过调用它访问 ETL 数据
- 组件 4 扩展现有 task_manager.py（不新建 task_perf_service.py），组件 5 新建 performance_service.py
- Schema 文件命名：`xcx_tasks.py`（扩展）+ `xcx_performance.py`（新增），与 design.md 一致
- 所有 12 个正确性属性（P1-P12）均有对应的属性测试任务
- DWD-DOC 强制规则在 fdw_queries.py 中统一实施：items_sum 口径、费用拆分、DQ-6/DQ-7 JOIN、废单排除
