# 需求文档 — RNS1.1：任务与绩效接口

## 简介

RNS1.1 是 NS1 小程序后端 API 补全项目的第二个子 spec，负责实现助教日常使用频率最高的 4 个核心接口（任务列表扩展、任务详情、绩效概览、绩效明细）、pin/unpin 端点、以及对应的前端适配。本 spec 覆盖助教视角的核心工作流，是助教登录后最先接触的功能集合。

### 依赖

- RNS1.0（基础设施与契约重写）必须先完成：全局响应包装中间件、camelCase 转换、重写后的 API 契约
- 前端已有 13 个页面（P5.2 交付），当前使用 mock 数据
- 后端已有 `xcx_tasks.py`（需扩展）、`xcx_notes.py`、`xcx_auth.py`

### 来源文档

- `docs/prd/Neo_Specs/RNS1-split-plan.md` — 拆分计划主文档
- `docs/miniprogram-dev/API-contract.md` — API 契约（RNS1.0 T0-5 重写后版本）
- `docs/prd/Neo_Specs/storyboard-walkthrough-assistant-view.md` — 助教视角走查报告（GAP-02~22）
- `docs/reports/DWD-DOC/` — 金额口径与字段语义权威标杆文档

## 术语表

- **Backend**：FastAPI 后端应用，位于 `apps/backend/`
- **Miniprogram**：微信小程序前端应用，位于 `apps/miniprogram/`
- **TASK_1_API**：任务列表接口 `GET /api/xcx/tasks`，返回任务列表 + 绩效概览
- **TASK_2_API**：任务详情接口 `GET /api/xcx/tasks/{taskId}`，返回单个任务的完整详情
- **PERF_1_API**：绩效概览接口 `GET /api/xcx/performance`，返回助教当月绩效汇总
- **PERF_2_API**：绩效明细接口 `GET /api/xcx/performance/records`，返回按日期分组的服务记录
- **PIN_API**：任务置顶/取消置顶接口 `POST /api/xcx/tasks/{id}/pin` 和 `POST /api/xcx/tasks/{id}/unpin`
- **PerformanceSummary**：任务列表响应中附带的绩效概览数据结构（15+ 字段）
- **DateGroup**：按日期分组的数据结构，包含日期标签、当日汇总、记录列表
- **FDW**：PostgreSQL Foreign Data Wrapper，用于从业务库 `zqyy_app` 访问 ETL 库 `etl_feiqiu` 的数据
- **items_sum**：DWD-DOC 强制使用的消费金额口径，= `table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money`
- **assistant_pd_money**：助教陪打费用（基础课），DWD-DOC 强制规则 2 要求的拆分字段
- **assistant_cx_money**：助教超休费用（激励课），DWD-DOC 强制规则 2 要求的拆分字段
- **enrichTask**：前端 `task-list` 页面中对原始任务数据进行扩展的函数，补充 `lastVisitDays`/`balance`/`aiSuggestion` 等字段
- **buildPerfData**：前端 `task-list` 页面中构建绩效进度条数据的函数，消费 `PerformanceSummary` 的 15+ 字段
- **courseTypeClass**：服务记录中课程类型的样式标识，统一使用 `basic`/`vip`/`tip` 枚举（不带 `tag-` 前缀）
- **user_assistant_binding**：业务库中用户与助教身份的绑定关系表，用于数据隔离
- **ai_cache**：业务库中 AI 分析结果的缓存表，按 `cache_type` 区分不同类型的 AI 输出

## 需求

### 需求 1：扩展 TASK-1 任务列表绩效概览（T1-1）

**用户故事：** 作为助教，我希望在任务列表页面看到完整的绩效进度条（含档位节点、基础/激励课时、升档奖金、收入趋势），以便快速了解本月绩效状态和距升档的差距。

#### 验收标准

1. THE TASK_1_API SHALL 在响应的 `performance` 字段中返回以下扩展字段：`tierNodes`（档位节点数组，如 `[0, 100, 130, 160, 190, 220]`）、`basicHours`（基础课时）、`bonusHours`（激励课时）、`currentTier`（当前档位索引，0-based）、`nextTierHours`（下一档位工时阈值）、`tierCompleted`（是否已达最高档）、`bonusMoney`（升档奖金，元）、`incomeTrend`（收入趋势描述，如 `"↓368"`）、`incomeTrendDir`（`up` 或 `down`）、`prevMonth`（上月标签，如 `"1月"`）、`currentTierLabel`（当前档位名称，如 `"初级"`）
2. THE TASK_1_API SHALL 从 `fdw_etl.v_dws_assistant_salary_calc` 查询当前助教的绩效和档位数据，通过 `user_assistant_binding` 获取 `assistant_id` 进行数据隔离
3. THE TASK_1_API SHALL 使用 `items_sum` 口径计算所有收入金额（DWD-DOC 强制规则 1），使用 `assistant_pd_money`（基础课）和 `assistant_cx_money`（激励课）拆分助教费用（DWD-DOC 强制规则 2）
4. THE TASK_1_API SHALL 通过对比当月和上月的收入数据计算 `incomeTrend` 和 `incomeTrendDir` 字段
5. WHEN `tierCompleted` 为 `true` 时，THE TASK_1_API SHALL 将 `bonusMoney` 设为 0，`nextTierHours` 设为当前档位工时阈值

#### 1.2 任务项扩展字段（GAP-03）

6. THE TASK_1_API SHALL 为每个任务 item 返回以下可选扩展字段：`lastVisitDays`（距上次到店天数，integer）、`balance`（客户余额，number，元）、`aiSuggestion`（AI 建议摘要，string）
7. THE TASK_1_API SHALL 从 `fdw_etl.dwd.dwd_settlement_head` 查询客户最后到店日期，计算 `lastVisitDays`（当前日期与 `MAX(settle_time)` 的天数差）
8. THE TASK_1_API SHALL 从 `fdw_etl.dwd.dim_member_card_account` 查询客户储值卡余额作为 `balance` 字段值
9. THE TASK_1_API SHALL 从 `biz.ai_cache` 查询 `cache_type` 对应的 AI 建议摘要作为 `aiSuggestion` 字段值
10. IF 某个扩展字段的数据源查询失败或无数据，THEN THE TASK_1_API SHALL 对该字段返回 `null`，不影响其他字段和整体响应

### 需求 2：实现 TASK-2 任务详情完整版（T1-2）

**用户故事：** 作为助教，我希望点击任务后能看到完整的任务详情（包括服务记录、维客线索、AI 分析、备注），以便全面了解客户情况并制定跟进策略。

#### 验收标准

1. THE TASK_2_API SHALL 返回完整的任务详情响应，包含基础信息、维客线索（`retentionClues`）、话术参考（`talkingPoints`）、服务记录摘要（`serviceSummary`）、服务记录列表（`serviceRecords`）、AI 分析（`aiAnalysis`）、备注列表（`notes`）
2. THE TASK_2_API SHALL 在响应中包含 `customerId` 字段（客户唯一 ID），供前端跳转 chat 和 customer-service-records 页面使用
3. WHEN 请求任务详情时，THE TASK_2_API SHALL 对服务记录（`serviceRecords`）和备注（`notes`）各返回最多 20 条，按时间倒序排列，支持前端懒加载

#### 2.2 维客线索格式统一（GAP-06~07）

4. THE TASK_2_API SHALL 返回维客线索的 `tag` 字段为纯文本字符串（不含换行符 `\n`），多行标签使用空格分隔
5. THE TASK_2_API SHALL 返回维客线索的 `source` 字段为以下枚举值之一：`manual`（手动创建）、`ai_consumption`（AI 消费分析生成）、`ai_note`（AI 备注分析生成）
6. THE TASK_2_API SHALL 从 `public.member_retention_clue` 查询维客线索数据，按 `created_at` 倒序排列

#### 2.3 AI 分析 cache_type 映射（GAP-08）

7. THE TASK_2_API SHALL 从 `biz.ai_cache` 查询 AI 分析数据，使用以下 `cache_type` 映射：`aiAnalysis.summary` 来自 `app4_analysis`，`talkingPoints` 来自 `app5_talking_points`
8. IF `biz.ai_cache` 中无对应 `cache_type` 的缓存记录，THEN THE TASK_2_API SHALL 对 `aiAnalysis` 返回 `{ summary: "", suggestions: [] }`，对 `talkingPoints` 返回空数组

#### 2.4 服务记录字段（GAP-06）

9. THE TASK_2_API SHALL 为每条服务记录返回 `courseTypeClass` 字段，使用统一枚举值：`basic`（基础课）、`vip`（包厢课）、`tip`（激励课）、`recharge`（充值）、`incentive`（激励），不带 `tag-` 前缀
10. THE TASK_2_API SHALL 为每条服务记录返回可选字段 `recordType`（`course` 或 `recharge`）和 `isEstimate`（是否预估数据，boolean）
11. THE TASK_2_API SHALL 使用 `items_sum` 口径计算服务记录中的 `income` 字段（DWD-DOC 强制规则 1）
12. THE TASK_2_API SHALL 使用 `dwd_assistant_service_log_ex.is_trash` 排除废单记录

#### 2.5 权限与数据隔离

13. THE TASK_2_API SHALL 验证请求的 `taskId` 属于当前登录助教（通过 `user_assistant_binding` 获取 `assistant_id`，校验 `coach_tasks.assistant_id` 匹配）
14. IF 请求的 `taskId` 不属于当前助教，THEN THE TASK_2_API SHALL 返回 HTTP 403 `{ code: 403, message: "无权访问该任务" }`

### 需求 3：实现 PERF-1 绩效概览（T1-3）

**用户故事：** 作为助教，我希望查看本月绩效概览（包括收入明细、档位进度、服务记录按日期分组、新客和常客列表），以便了解本月工作成果和收入构成。

#### 验收标准

1. THE PERF_1_API SHALL 接受 `year` 和 `month` 查询参数，返回指定月份的绩效概览数据
2. THE PERF_1_API SHALL 通过 `user_assistant_binding` 获取当前助教的 `assistant_id`，仅查询该助教自己的绩效数据

#### 3.2 本月服务记录 DateGroup 结构（GAP-12）

3. THE PERF_1_API SHALL 将 `thisMonthRecords` 以 DateGroup 结构返回：每组包含 `date`（日期标签，如 `"2月7日"`）、`totalHours`（当日总工时，格式化字符串）、`totalIncome`（当日总收入，格式化字符串）、`records`（记录列表）
4. THE PERF_1_API SHALL 为 DateGroup 中每条记录返回以下字段：`customerName`、`avatarChar`（姓氏首字）、`avatarColor`（头像渐变色）、`timeRange`（时间段，如 `"20:00-22:00"`）、`hours`（工时，格式化字符串）、`courseType`（课程类型，如 `"基础课"`）、`courseTypeClass`（样式标识：`basic`/`vip`/`tip`，不带 `tag-` 前缀）、`location`（台桌/包厢名）、`income`（收入，格式化字符串）
5. THE PERF_1_API SHALL 从 `fdw_etl.v_dwd_assistant_service_log` 查询服务记录，按 `settle_time` 日期分组，每组内按时间倒序排列

#### 3.3 收入档位数据（GAP-13）

6. THE PERF_1_API SHALL 返回收入档位数据：`currentTier`（当前档，含 `basicRate` 和 `incentiveRate`）、`nextTier`（下一档，含 `basicRate` 和 `incentiveRate`）、`upgradeHoursNeeded`（距升档所需工时，number）、`upgradeBonus`（升档奖金，number，元）
7. THE PERF_1_API SHALL 从 `fdw_etl.v_dws_assistant_salary_calc` 查询档位和费率数据

#### 3.4 上月收入与收入明细（GAP-14~15）

8. THE PERF_1_API SHALL 返回 `lastMonthIncome` 字段（上月收入，格式化字符串，如 `"¥16,880"`）
9. THE PERF_1_API SHALL 为 `incomeItems` 每项返回 `desc` 字段（费率×工时的拆分描述，如 `"80元/h × 75h"`），由后端根据费率和工时数据计算生成
10. THE PERF_1_API SHALL 使用 `items_sum` 口径计算所有收入金额（DWD-DOC 强制规则 1），使用 `assistant_pd_money` 和 `assistant_cx_money` 拆分助教费用（DWD-DOC 强制规则 2）

#### 3.5 新客与常客列表（GAP-16）

11. THE PERF_1_API SHALL 为 `newCustomers` 每项返回 `lastService`（最后服务日期，如 `"2月7日"`）和 `count`（服务次数，number）字段
12. THE PERF_1_API SHALL 为 `regularCustomers` 每项返回 `hours`（总工时，number）和 `income`（总收入，格式化字符串）字段
13. THE PERF_1_API SHALL 通过 `member_id` JOIN `fdw_etl.dwd.dim_member`（取 `scd2_is_current=1`）获取客户姓名（DWD-DOC 强制规则 DQ-6），禁止直接使用 `member_phone` 或 `member_name`

### 需求 4：实现 PERF-2 绩效明细（T1-4）

**用户故事：** 作为助教，我希望查看指定月份的绩效明细（按日期分组的服务记录列表），以便回顾每天的工作详情。

#### 验收标准

1. THE PERF_2_API SHALL 接受 `year`、`month`、`page`、`pageSize` 查询参数，返回指定月份的绩效明细数据
2. THE PERF_2_API SHALL 每页返回最多 20 条记录（默认 `pageSize=20`），按日期分组为 `dateGroups` 结构，并返回 `hasMore` 标记指示是否有更多数据

#### 4.2 按日期分组（GAP-19~20）

3. THE PERF_2_API SHALL 将服务记录按日期分组，每组包含 `date`（日期标签）、`totalHours`（当日总工时）、`totalIncome`（当日总收入）、`records`（记录列表）
4. THE PERF_2_API SHALL 为每条记录返回 `courseTypeClass` 字段，使用统一枚举值 `basic`/`vip`/`tip`（不带 `tag-` 前缀）
5. THE PERF_2_API SHALL 不返回 `avatarChar` 和 `avatarColor` 字段（前端通过 `nameToAvatarColor()` 工具函数从 `customerName` 自行计算）

#### 4.3 月度汇总

6. THE PERF_2_API SHALL 返回月度汇总数据 `summary`：`totalCount`（总记录数）、`totalHours`（总工时）、`totalHoursRaw`（原始工时，未折算）、`totalIncome`（总收入）
7. THE PERF_2_API SHALL 使用 `items_sum` 口径计算 `totalIncome` 和每条记录的 `income`（DWD-DOC 强制规则 1）
8. THE PERF_2_API SHALL 通过 `member_id` JOIN `fdw_etl.dwd.dim_member`（取 `scd2_is_current=1`）获取客户姓名（DWD-DOC 强制规则 DQ-6）

### 需求 5：实现 pin/unpin API 端点（T1-5）

**用户故事：** 作为助教，我希望将重要任务置顶或取消置顶，以便优先处理关键客户的跟进任务。

#### 验收标准

1. THE Backend SHALL 实现 `POST /api/xcx/tasks/{taskId}/pin` 端点，将指定任务的 `is_pinned` 字段设为 `true`
2. THE Backend SHALL 实现 `POST /api/xcx/tasks/{taskId}/unpin` 端点，将指定任务的 `is_pinned` 字段设为 `false`
3. WHEN pin 或 unpin 操作成功时，THE PIN_API SHALL 返回 `{ isPinned: true }` 或 `{ isPinned: false }`
4. THE PIN_API SHALL 验证请求的 `taskId` 属于当前登录助教（通过 `user_assistant_binding` 校验），不属于时返回 HTTP 403
5. IF 请求的 `taskId` 不存在，THEN THE PIN_API SHALL 返回 HTTP 404 `{ code: 404, message: "任务不存在" }`
6. THE Miniprogram SHALL 在 `services/api.ts` 中新增 `pinTask(taskId: string)` 和 `unpinTask(taskId: string)` 函数，分别调用 pin 和 unpin 端点
7. WHEN pin/unpin API 调用成功后，THE Miniprogram SHALL 更新本地任务列表状态（将任务移入/移出置顶分组），无需重新请求完整任务列表

### 需求 6：前端适配 — 任务页面（T1-6 任务部分）

**用户故事：** 作为助教，我希望任务列表和任务详情页面能正确展示后端返回的真实数据（替代当前的 mock 数据），以便看到真实的客户信息和绩效状态。

#### 验收标准

#### 6.1 createNote 补充用户手动评分参数（GAP-05）

1. THE Miniprogram SHALL 修改 `services/api.ts` 中 `createNote` 函数的签名，增加可选参数 `manualScore?: number`（用户手动评分，1-5 星）
2. WHEN 用户在备注弹窗中提交备注时，THE Miniprogram SHALL 将 `manualScore` 字段一并传递给 `POST /api/xcx/notes` 端点
3. THE Backend SHALL 修改 `POST /api/xcx/notes` 端点，接受请求体中的可选 `manualScore` 字段（number，1-5），并存入 `biz.notes.score` 列。注意：此字段为用户手动评分（再次服务意愿 + 再来店可能性），与 AI 应用 6 的 `aiScore`（1-10 分，展示用）语义不同

#### 6.2 storageLevel/relationLevel 前端计算（GAP-11）

4. THE Miniprogram SHALL 在 task-detail 页面根据后端返回的 `balance` 字段值，在前端本地计算 `storageLevel`（储值等级，如 "非常多"/"较多"/"一般"/"较少"）
5. THE Miniprogram SHALL 在 task-detail 页面根据后端返回的 `heartScore` 字段值（0-10 范围），在前端本地计算 `relationLevel`、`relationLevelText`、`relationColor`，阈值遵循 P6 AC3 四级映射（>8.5 / >7 / >5 / ≤5）

### 需求 7：前端适配 — 绩效页面（T1-6 绩效部分）

**用户故事：** 作为助教，我希望绩效概览和绩效明细页面支持月份切换，以便查看历史月份的绩效数据。

#### 验收标准

#### 7.1 绩效概览月份切换（F8, GAP-18）

1. THE Miniprogram SHALL 在 performance 页面添加月份切换控件（左右箭头 + 月份标签），允许用户在当前月和历史月份之间切换
2. WHEN 用户切换月份时，THE Miniprogram SHALL 使用新的 `year`/`month` 参数重新调用 `fetchPerformanceOverview` 接口，加载对应月份的绩效数据
3. THE Miniprogram SHALL 在月份切换期间显示加载状态，防止用户重复操作

#### 7.2 绩效明细月份切换重置分页（F9, GAP-21）

4. WHEN 用户在 performance-records 页面切换月份时，THE Miniprogram SHALL 将 `page` 重置为 1，清空已加载的记录列表，重新从第一页加载
5. THE Miniprogram SHALL 修复 `switchMonth()` 函数中 `page` 未重置的 Bug

#### 7.3 avatarChar/avatarColor 前端计算（GAP-19 决策）

6. THE Miniprogram SHALL 在 performance 和 performance-records 页面，使用 `nameToAvatarColor()` 工具函数从 `customerName` 计算 `avatarChar`（姓氏首字）和 `avatarColor`（头像渐变色），不依赖后端返回这两个字段

### 需求 8：全局约束与数据隔离

**用户故事：** 作为系统管理员，我希望所有任务和绩效接口都遵循统一的权限控制和数据隔离规则，以确保每位助教只能访问自己的数据。

#### 验收标准

1. THE Backend SHALL 对所有 RNS1.1 接口（TASK-1、TASK-2、PERF-1、PERF-2、PIN/UNPIN）执行 `require_approved()` 权限检查，确保用户状态为 `approved`
2. THE Backend SHALL 对所有 RNS1.1 接口验证用户具有 `view_tasks` 权限
3. THE Backend SHALL 通过 `user_assistant_binding` 表获取当前用户对应的 `assistant_id`，所有数据查询均以该 `assistant_id` 作为过滤条件
4. IF 当前用户在 `user_assistant_binding` 中无绑定记录，THEN THE Backend SHALL 返回 HTTP 403 `{ code: 403, message: "未绑定助教身份" }`
5. THE Backend SHALL 对所有涉及金额的字段统一使用 `items_sum` 口径（DWD-DOC 强制规则 1），禁止使用 `consume_money`
6. THE Backend SHALL 对所有涉及助教费用的字段使用 `assistant_pd_money`（陪打/基础课）+ `assistant_cx_money`（超休/激励课）拆分（DWD-DOC 强制规则 2），禁止使用 `service_fee`
7. THE Backend SHALL 对所有涉及会员信息的查询通过 `member_id` LEFT JOIN `fdw_etl.dwd.dim_member`（取 `scd2_is_current=1`）获取姓名和手机号（DWD-DOC 强制规则 DQ-6），禁止直接使用 `settlement_head.member_phone` 或 `member_name`
8. THE Backend SHALL 使用 `dwd_assistant_service_log_ex.is_trash` 排除废单记录，禁止使用已废弃的 `dwd_assistant_trash_event` 表
