Files
Neo-ZQYY/.kiro/specs/rns1-task-performance-api/tasks.md

169 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_logWHERE 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 = falseDQ-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_cacheapp5_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
- 组件 3fdw_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、废单排除