# 小程序 Storyboard 走查报告 — 助教视角（小燕）

> 走查日期：2026-03-18
> 走查角色：助教"小燕"，已登录，status=approved
> 对照文档：`docs/miniprogram-dev/API-contract.md`（契约）、`docs/prd/Neo_Specs/NS1-xcx-backend-api.md`（spec）
> 目标：提取 spec 未记录的接口需求细节

---

## 场景 1：登录 → 任务列表（app.ts）

### API 调用
- `checkAuthStatus()` → `GET /api/xcx/me` → 返回 `ApiUserInfo`
- 参数来源：token 从 `wx.getStorageSync('token')` 读取
- 成功后根据 `data.status` 路由：`approved` → `reLaunch('/pages/task-list/task-list')`

### 前端期望字段（/api/xcx/me 响应）
| 字段 | 类型 | 说明 | 契约定义 |
|------|------|------|---------|
| `user_id` | number | 用户 ID | ✅ |
| `status` | enum | `new/pending/approved/rejected/disabled` | ✅ |
| `nickname` | string | 昵称 | ✅ |
| `role` | string | 角色 | ✅ REQ-4 |
| `store_name` | string | 门店名 | ✅ REQ-4 |
| `coach_level` | string? | 助教等级 | ✅ REQ-4 |
| `avatar` | string? | 头像 URL | ✅ REQ-4 |

### globalData 结构
```typescript
globalData: {
  token?: string
  refreshToken?: string
  authUser?: { userId: number, status: string, nickname: string }
}
```

### spec 未记录的 Gap
- **GAP-01**：`globalData.authUser` 未存储 `role`、`store_name`、`coach_level`、`avatar`，但下游页面（task-list banner、performance banner、performance-records banner）需要这些字段。前端需扩展 `globalData.authUser` 或各页面单独请求 `/me`。

---

## 场景 2：任务列表页（task-list.ts）

### API 调用
- `fetchTasks(params?)` → `GET /api/xcx/tasks?status=&page=&pageSize=`
- 返回：`{ tasks: Task[], performance: PerformanceData, total: number, hasMore: boolean }`
- 页面加载时无参数调用（获取全部任务）

### 前端期望字段 — Task（任务卡片）
| 字段 | 类型 | 说明 | 契约 TASK-1 |
|------|------|------|------------|
| `id` | string | 任务 ID | ✅ |
| `customerName` | string | 客户姓名 | ✅ |
| `customerAvatar` | string | 客户头像 | ✅ |
| `taskType` | enum | `callback/priority_recall/relationship/high_priority` | ✅ |
| `taskTypeLabel` | string | 类型中文标签 | ✅ |
| `deadline` | string | 截止日期 ISO 8601 | ✅ |
| `heartScore` | number | 爱心评分 0-10 | ✅ |
| `hobbies` | string[] | 爱好标签 | ✅ |
| `isPinned` | boolean | 是否置顶 | ✅ |
| `hasNote` | boolean | 是否有备注 | ✅ |
| `status` | enum | `pending/completed/abandoned` | ✅ |

### 前端期望字段 — PerformanceData（绩效概览卡片）
| 字段 | 类型 | 说明 | 契约 TASK-1 performance | 前端实际使用 |
|------|------|------|------------------------|-------------|
| `monthlyIncome` | number | 月收入 | ❌ 契约仅有 `total_income` | `buildPerfData()` 用 |
| `incomeChange` | number | 收入变化百分比 | ❌ 契约无 | `incomeTrend` 展示 |
| `currentTier` | string | 当前档位名称 | ❌ 契约无 | `bannerTitle` |
| `nextTierGap` | number | 距下一档差距 | ❌ 契约无 | `remainHours` |
| `todayServiceCount` | number | 今日服务次数 | ❌ 契约无 | 未使用 |
| `weekServiceCount` | number | 本周服务次数 | ❌ 契约无 | 未使用 |
| `monthServiceCount` | number | 本月服务次数 | ❌ 契约无 | 未使用 |

### 前端期望字段 — buildPerfData() 需要后端返回的绩效字段
| 字段 | 类型 | 说明 | 契约/spec 定义 |
|------|------|------|---------------|
| `tierNodes` | number[] | 档位节点数组，如 `[0,100,130,160,190,220]` | ❌ 未定义 |
| `totalHours` | number | 当月总工时（折算后） | 契约有 `total_hours` |
| `basicHours` | number | 基础课时 | ❌ 未定义 |
| `bonusHours` | number | 激励课时 | ❌ 未定义 |
| `currentTier` | number | 当前档位索引 | ❌ 未定义 |
| `nextTierHours` | number | 下一档位工时阈值 | ❌ 未定义 |
| `tierCompleted` | boolean | 是否已达标（满档） | ❌ 未定义 |
| `bonusMoney` | number | 升档奖金 | ❌ 未定义 |
| `incomeFormatted` | string | 格式化收入 | 契约有 `total_income` |
| `incomeTrend` | string | 收入趋势（如 `↓368`） | ❌ 未定义 |
| `incomeTrendDir` | enum | `up/down` | ❌ 未定义 |
| `incomeMonth` | string | 当前月份标签 | 契约有 `month_label` |
| `prevMonth` | string | 上月标签 | ❌ 未定义 |

### enrichTask() 需要后端返回的扩展字段
| 字段 | 类型 | 说明 | 契约/spec 定义 |
|------|------|------|---------------|
| `lastVisitDays` | number | 距上次到店天数 | ❌ 前端 mock 计算，后端需返回 |
| `balance` | number | 客户余额 | ❌ 未定义在 TASK-1 |
| `aiSuggestion` | string | AI 建议摘要 | ❌ 未定义在 TASK-1 |

### 页面跳转
| 操作 | 目标 | 参数 |
|------|------|------|
| 点击任务卡片 | `/pages/task-detail/task-detail` | `?id={taskId}` |
| 点击绩效卡片 | `/pages/performance/performance` | 无参数 |
| 长按→AI助手 | `/pages/ai-chat/ai-chat` | `?taskId={id}&customerName={name}` |

### 写操作
| 操作 | API | 请求体 | 成功后行为 |
|------|-----|--------|-----------|
| 长按→置顶/取消置顶 | `POST /tasks/{id}/pin` 或 `/unpin` | 无 | 本地更新 `isPinned`，重新分组 |
| 长按→放弃 | `POST /tasks/{id}/abandon` | `{ reason: string }` | 本地移至 abandonedTasks |
| 长按→取消放弃 | `POST /tasks/{id}/restore` | 无 | 本地移至 normalTasks |
| 长按→添加备注 | `POST /notes` | `{ content, taskId?, customerId? }` | Toast 提示 |

### spec 未记录的 Gap
- **GAP-02**：契约 TASK-1 `performance` 仅有 4 个字段（`total_hours/total_income/total_customers/month_label`），但前端 `buildPerfData()` 需要 15+ 个字段（`tierNodes`、`basicHours`、`bonusHours`、`currentTier`、`nextTierHours`、`tierCompleted`、`bonusMoney`、`incomeTrend`、`incomeTrendDir`、`prevMonth` 等）。这是最大的 Gap。
- **GAP-03**：`enrichTask()` 需要 `lastVisitDays`（距上次到店天数）、`balance`（客户余额）、`aiSuggestion`（AI 建议），这 3 个字段在 TASK-1 契约中未定义。后端需在任务列表 item 中附带这些字段，或前端改为进入详情页后再加载。
- **GAP-04**：`pin/unpin` 操作的 API 端点在契约中未定义。spec 提到已实现 `pin/unpin`，但 `api.ts` 中无对应函数，前端仅做本地状态更新。需确认 API 路径（`POST /tasks/{id}/pin` 和 `POST /tasks/{id}/unpin`）。
- **GAP-05**：`createNote` 的 `score` 参数 — 前端备注弹窗 `onNoteConfirm` 传递 `{ score, content }`，但 `api.ts` 的 `createNote` 签名仅有 `{ content, taskId?, customerId? }`，缺少 `score` 字段。

---

## 场景 3：任务详情页（task-detail.ts）

### API 调用
- `fetchTaskDetail(taskId)` → `GET /api/xcx/tasks/{taskId}`
- 参数来源：`options.id`（从 task-list 跳转传入）
- `deleteNote(noteId)` → `DELETE /api/xcx/notes/{noteId}`

### 前端期望字段 — TaskDetail（完整清单）

#### 基础信息（继承 Task）
同 TASK-1 item 字段，契约已覆盖。

#### 维客线索 retentionClues（内联 mock，8 条）
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|------|------|------|------------|
| `tag` | string | 线索大类标签（如 `"客户\n基础"`） | ❌ 契约用 `tag` 但格式不同 |
| `tagColor` | enum | `primary/success/purple/error` | ✅ `tag_color` |
| `emoji` | string | 表情符号 | ✅ |
| `text` | string | 线索摘要 | ✅ |
| `source` | string | 来源（如 `"By:系统"`、`"By:小燕"`） | ⚠️ 契约有 `source` 但格式不同 |
| `desc` | string? | 详细描述（展开后显示） | ✅ |
| `expanded` | boolean | 展开状态（前端本地） | — |

#### 话术参考 talkingPoints（内联 mock，5 条）
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|------|------|------|------------|
| `talkingPoints` | string[] | 话术文本数组 | ✅ |

#### 服务记录 serviceRecords（内联 mock，4 条）
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|------|------|------|------------|
| `table` | string | 台桌号 | ✅ |
| `type` | string | 课程类型标签 | ✅ |
| `typeClass` | enum | `basic/vip/tip/recharge/incentive` | ✅ `type_class` |
| `recordType` | enum? | `course/recharge` | ✅ `record_type` |
| `duration` | number | 折算后课时（小时） | ✅ |
| `durationRaw` | number? | 折算前课时（小时） | ✅ `duration_raw` |
| `income` | number | 收入（元） | ✅ |
| `isEstimate` | boolean? | 是否预估金额 | ✅ `is_estimate` |
| `drinks` | string | 酒水描述 | ✅ |
| `date` | string | 日期（格式化后） | ✅ |

#### 服务汇总 serviceSummary
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|------|------|------|------------|
| `totalHours` | number | 总工时 | ✅ |
| `totalIncome` | number | 总收入 | ✅ |
| `avgIncome` | number | 平均收入 | ✅ |

#### AI 分析 aiAnalysis
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|------|------|------|------------|
| `summary` | string | AI 分析摘要 | ✅ |
| `suggestions` | string[] | AI 建议列表 | ✅ |

#### 备注 notes（内联 mock，5 条）
| 字段 | 类型 | 说明 | 契约 TASK-2 |
|------|------|------|------------|
| `id` | string | 备注 ID | ✅ |
| `content` | string | 备注内容 | ✅ |
| `tagType` | enum | `customer/coach/system` | ✅ `tag_type` |
| `tagLabel` | string | 标签文案 | ✅ `tag_label` |
| `createdAt` | string | 创建时间 | ✅ `created_at` |
| `score` | number? | 满意度评分 0-10 | ✅ |

### 页面跳转
| 操作 | 目标 | 参数 |
|------|------|------|
| 问问助手 | `/pages/chat/chat` | `?customerId={detail.id}` |
| 查看全部服务记录 | `/pages/customer-service-records/customer-service-records` | `?customerId={detail.id}` |

### 写操作
| 操作 | API | 请求体 | 成功后行为 |
|------|-----|--------|-----------|
| 添加备注 | `POST /notes` | `{ content }` | 本地 prepend 到 sortedNotes |
| 删除备注 | `DELETE /notes/{noteId}` | 无 | 本地 filter 移除 |
| 放弃任务 | `POST /tasks/{id}/abandon` | `{ reason }` | 更新 `detail.status='abandoned'` |
| 取消放弃 | `POST /tasks/{id}/restore` | 无 | 更新 `detail.status='pending'` |

### spec 未记录的 Gap
- **GAP-06**：维客线索的 `tag` 字段格式不一致 — 前端内联 mock 使用 `"客户\n基础"` 格式（含换行符），契约定义为普通 string。后端需明确 tag 的取值枚举和格式。
- **GAP-07**：维客线索的 `source` 字段格式不一致 — 前端显示 `"By:系统"`、`"By:小燕"`，但 mock-data.ts 中 `RetentionClue.source` 定义为 `'manual' | 'ai_consumption' | 'ai_note'`。前端内联 mock 与类型定义不匹配，后端需明确返回格式。
- **GAP-08**：`aiAnalysis` 数据来源 — spec §3.2 提到来自 `biz.ai_cache`（app4/app5/app6/app7），但契约未说明 `cache_type` 映射关系。需明确：`ai_analysis.summary` 来自哪个 `cache_type`？`talking_points` 来自哪个 `cache_type`？
- **GAP-09**：task-detail 跳转 chat 时传 `customerId={detail.id}`，但 `detail.id` 实际是 **taskId** 不是 customerId。前端需要一个 `customerId` 字段（TASK-2 响应中需包含 `customer_id`）。
- **GAP-10**：task-detail 跳转 customer-service-records 时同样传 `customerId={detail.id}`，存在同样的 taskId/customerId 混淆问题。TASK-2 响应需包含 `customer_id`。
- **GAP-11**：`storageLevel`（储值等级，如"非常多"）和 `relationLevel/relationLevelText/relationColor`（关系等级）在前端本地计算，但后端未定义这些字段。后端是否需要返回，还是前端根据 `heartScore` 自行计算？

---

## 场景 4：绩效概览页（performance.ts）

### API 调用
- `fetchPerformanceOverview({ year, month })` → `GET /api/xcx/performance?year=&month=`
- 参数来源：`new Date()` 取当前年月
- 返回类型：`PerformanceData`（mock-data.ts 定义）

### 前端期望字段 — 完整清单（对比契约 PERF-1）

#### Banner 数据（内联 mock，非 API 返回）
| 字段 | 类型 | 说明 | 契约 PERF-1 | 数据源 |
|------|------|------|------------|--------|
| `coachName` | string | 助教姓名 `'小燕'` | ❌ 未定义 | 应从 globalData.authUser 或 /me |
| `coachRole` | string | 角色 `'助教'` | ❌ 未定义 | 同上 |
| `storeName` | string | 门店名 `'广州朗朗桌球'` | ❌ 未定义 | 同上 |
| `monthlyIncome` | string | 本月收入 `'¥6,206'` | ⚠️ 契约有 `total_income` 但是 number | 需格式化 |
| `lastMonthIncome` | string | 上月收入 `'¥16,880'` | ❌ 未定义 | 需后端额外返回 |

#### 收入档位（内联 mock）
| 字段 | 类型 | 说明 | 契约 PERF-1 |
|------|------|------|------------|
| `currentTier.basicRate` | number | 当前档基础费率 80 | ❌ 未定义 |
| `currentTier.incentiveRate` | number | 当前档激励费率 95 | ❌ 未定义 |
| `nextTier.basicRate` | number | 下一档基础费率 90 | ❌ 未定义 |
| `nextTier.incentiveRate` | number | 下一档激励费率 114 | ❌ 未定义 |
| `upgradeHoursNeeded` | number | 距升档所需工时 15 | ❌ 未定义 |
| `upgradeBonus` | number | 升档奖金 800 | ❌ 未定义 |

#### 收入明细 incomeItems（内联 mock，4 项）
| 项目 | icon | desc 格式 | 契约 PERF-1 |
|------|------|----------|------------|
| 基础课 | 🎱 | `80元/h × 75h` | ⚠️ 契约有 `income_items` 但字段不同 |
| 激励课 | ⭐ | `95.05元/h × 10h` | 同上 |
| 充值激励 | 💰 | `客户充值返佣` | 同上 |
| TOP3 销冠奖 | 🏆 | `全店业绩前三名奖励` | 同上 |

契约 `income_items` 定义：`Array<{ label, amount, icon }>`，缺少 `desc` 字段。

#### 服务记录 thisMonthRecords（内联 mock，按日期分组）
| 字段 | 类型 | 说明 | 契约 PERF-1 |
|------|------|------|------------|
| `date` | string | 日期标签 `'2月7日'` | ❌ 契约 `this_month_records` 结构完全不同 |
| `totalHours` | string | 当日总工时 `'4.0h'` | ❌ |
| `totalIncome` | string | 当日总收入 `'¥350'` | ❌ |
| `records[].customerName` | string | 客户名 | ⚠️ 契约有 `customer_name` |
| `records[].avatarChar` | string | 头像首字 | ❌ |
| `records[].avatarColor` | string | 头像颜色 | ❌ |
| `records[].timeRange` | string | 时间段 `'20:00-22:00'` | ❌ |
| `records[].hours` | string | 工时 `'2.0h'` | ⚠️ 契约有 `hours` 但是 number |
| `records[].courseType` | string | 课程类型 | ❌ |
| `records[].courseTypeClass` | string | 样式类 | ❌ |
| `records[].location` | string | 台号 | ❌ |
| `records[].income` | string | 收入 `'¥160'` | ⚠️ 契约有 `income` 但是 number |

#### 新客列表 newCustomers
| 字段 | 类型 | 说明 | 契约 PERF-1 |
|------|------|------|------------|
| `name` | string | 客户名 | ✅ |
| `avatarChar` | string | 头像首字 | ❌ 契约有 `avatar_char` |
| `avatarColor` | string | 头像颜色 | ❌ 未定义 |
| `lastService` | string | 最后服务日期 | ❌ 未定义 |
| `count` | number | 服务次数 | ❌ 未定义 |

#### 常客列表 regularCustomers
| 字段 | 类型 | 说明 | 契约 PERF-1 |
|------|------|------|------------|
| `name` | string | 客户名 | ✅ |
| `avatarChar` | string | 头像首字 | ❌ 契约有 `avatar_char` |
| `avatarColor` | string | 头像颜色 | ❌ 未定义 |
| `hours` | number | 总工时 | ❌ 未定义 |
| `income` | string | 总收入 | ❌ 未定义 |
| `count` | number | 到店次数 | ⚠️ 契约有 `visits` |

### 页面跳转
| 操作 | 目标 | 参数 |
|------|------|------|
| 查看全部记录 | `/pages/performance-records/performance-records` | 无参数 |
| 点击客户卡片 | `/pages/task-detail/task-detail` | `?customerName={name}` |
| 点击服务记录 | `/pages/task-detail/task-detail` | `?customerName={name}&taskId={taskId}` |
| 点击收入概览 | `/pages/performance-records/performance-records` | 无参数 |

### spec 未记录的 Gap
- **GAP-12**：契约 PERF-1 `this_month_records` 是扁平数组 `Array<{ customer_name, hours, income, date }>`，但前端需要 **按日期分组的 DateGroup 结构**（含 `date`、`totalHours`、`totalIncome`、`records[]`），且每条记录需要 `timeRange`、`courseType`、`courseTypeClass`、`location` 等字段。契约需大幅扩展。
- **GAP-13**：收入档位数据（`currentTier`、`nextTier`、`upgradeHoursNeeded`、`upgradeBonus`）在契约 PERF-1 中完全未定义。这些数据来自 `dws_assistant_salary_calc`，后端需返回。
- **GAP-14**：`lastMonthIncome`（上月收入）在契约中未定义，前端 Banner 需要展示。
- **GAP-15**：`incomeItems` 的 `desc` 字段（如 `"80元/h × 75h"`）在契约中未定义。后端需返回费率和工时的拆分数据，或直接返回格式化的 desc 字符串。
- **GAP-16**：`newCustomers` 缺少 `lastService`（最后服务日期）和 `count`（服务次数）字段；`regularCustomers` 缺少 `hours`（总工时）和 `income`（总收入）字段。
- **GAP-17**：跳转 task-detail 时传 `customerName` 而非 `taskId`，但 task-detail 页面 `onLoad` 读取的是 `options.id`。参数传递不匹配，联调时会导致页面无法加载正确数据。
- **GAP-18**：页面无月份切换功能（F8），API 支持 `year/month` 参数但页面固定当前月。

---

## 场景 5：绩效明细页（performance-records.ts）

### API 调用
- `fetchPerformanceRecords({ year, month })` → `GET /api/xcx/performance/records?year=&month=&page=&pageSize=`
- 参数来源：`currentYear`/`currentMonth`（页面 data，支持月份切换）
- Banner 数据从 `getApp().globalData.authUser` 读取

### 前端期望字段 — summary
| 字段 | 类型 | 说明 | 契约 PERF-2 |
|------|------|------|------------|
| `totalCount` | number | 总笔数 32 | ✅ `total_count` |
| `totalHours` | number | 总工时 59.0 | ✅ `total_hours` |
| `totalHoursRaw` | number | 折算前总工时 63.5 | ✅ `total_hours_raw` |
| `totalIncome` | number | 总收入 4720 | ✅ `total_income` |

### 前端期望字段 — dateGroups（按日期分组）
| 字段 | 类型 | 说明 | 契约 PERF-2 |
|------|------|------|------------|
| `date` | string | 日期标签 `'2月7日'` | ✅ |
| `totalHours` | number | 当日总工时 | ✅ |
| `totalIncome` | number | 当日总收入 | ✅ |
| `records[].id` | string | 记录 ID | ✅ |
| `records[].customerName` | string | 客户名 | ✅ |
| `records[].avatarChar` | string | 头像首字 | ❌ 未定义 |
| `records[].avatarColor` | string | 头像颜色 | ❌ 未定义 |
| `records[].timeRange` | string | 时间段 | ✅ |
| `records[].hours` | number | 折算后工时 | ✅ |
| `records[].hoursRaw` | number? | 折算前工时 | ✅ |
| `records[].courseType` | string | 课程类型 | ✅ |
| `records[].courseTypeClass` | string | 样式类 | ✅ |
| `records[].location` | string | 台号 | ✅ |
| `records[].income` | number | 收入 | ✅ |

### courseTypeClass 完整枚举
前端使用的样式类：`tag-basic`（基础课）、`tag-vip`（包厢课）、`tag-tip`（打赏课）。
契约定义的 `course_type_class`：未明确枚举值。

### spec 未记录的 Gap
- **GAP-19**：`avatarChar`（头像首字）和 `avatarColor`（头像颜色）在契约 PERF-2 中未定义。前端通过 `nameToAvatarColor()` 工具函数从姓名生成，但后端是否需要返回？建议前端自行计算。
- **GAP-20**：`courseTypeClass` 的完整枚举值未在契约中明确。前端使用 `tag-basic`/`tag-vip`/`tag-tip`，但后端返回的是 `basic`/`vip`/`tip` 还是带 `tag-` 前缀？需统一。
- **GAP-21**：月份切换时未重置分页（F9 bug），`switchMonth()` 中 `page` 未重置为 1。
- **GAP-22**：Banner 字段 `coachName`/`coachLevel`/`storeName` 从 `globalData.authUser` 读取，但 `authUser` 当前未存储 `coachLevel` 和 `storeName`（见 GAP-01）。

---

## 场景 6：客户详情页（customer-detail.ts）

### API 调用
- `fetchCustomerDetail(customerId)` → `GET /api/xcx/customers/{customerId}`
- 参数来源：页面路由参数（但当前代码从 `__route__` 解析，有 bug）

### 前端期望字段 — 完整清单

#### 客户基本信息（内联 mock）
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|------|------|------|------------|
| `id` | string | 客户 ID | ✅ |
| `name` | string | 客户名 | ✅ |
| `avatarChar` | string | 头像首字 | ❌ 未定义 |
| `phone` | string | 手机号（完整） | ⚠️ 契约有 `phone`（脱敏）和 `phone_full`（完整） |
| `balance` | string | 余额 `'8,600'` | ❌ 未定义 |
| `consumption60d` | string | 近60天消费 `'2,800'` | ❌ 未定义 |
| `idealInterval` | string | 理想到店间隔 `'7天'` | ❌ 未定义 |
| `daysSinceVisit` | string | 距上次到店 `'12天'` | ❌ 未定义 |

#### AI 洞察 aiInsight（内联 mock）
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|------|------|------|------------|
| `summary` | string | AI 分析摘要 | ❌ 未定义 |
| `strategies` | Array<{color, text}> | 策略建议列表 | ❌ 未定义 |

#### 维客线索 clues（内联 mock，7 条）
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|------|------|------|------------|
| `category` | string | 线索大类（含换行符） | ⚠️ 契约说"同 TASK-2 格式" |
| `categoryColor` | string | 颜色 | ⚠️ |
| `text` | string | 含 emoji 的摘要 | ⚠️ |
| `source` | string | 来源 `'系统'`/`'小燕'` | ⚠️ |
| `detail` | string? | 详细描述 | ⚠️ |

#### 关联助教 coachTasks（内联 mock，4 位助教）
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|------|------|------|------------|
| `name` | string | 助教名 | ❌ 完全未定义 |
| `level` | string | 等级 `senior/middle/junior` | ❌ |
| `levelColor` | string | 等级颜色 | ❌ |
| `taskType` | string | 任务类型标签 | ❌ |
| `taskColor` | string | 任务颜色 | ❌ |
| `bgClass` | string | 背景样式类 | ❌ |
| `status` | string | 状态 `normal/pinned/abandoned` | ❌ |
| `lastService` | string | 最后服务 `'02-20 21:30 · 2.5h'` | ❌ |
| `metrics` | Array<{label,value,color}> | 指标（近60天次数/总时长/次均时长） | ❌ |

#### 最亲密助教 favoriteCoaches（内联 mock，2 位）
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|------|------|------|------------|
| `emoji` | string | 爱心 emoji | ❌ 完全未定义 |
| `name` | string | 助教名 | ❌ |
| `relationIndex` | string | 关系指数 `'9.2'` | ❌ |
| `indexColor` | string | 指数颜色 | ❌ |
| `bgClass` | string | 背景样式类 | ❌ |
| `stats` | Array<{label,value,color}> | 统计（基础/激励/上课/充值） | ❌ |

#### 消费记录 consumptionRecords（内联 mock，3 条，3 种类型）
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|------|------|------|------------|
| `id` | string | 记录 ID | ✅ |
| `type` | enum | `table/shop/recharge` | ⚠️ 契约有 `type` 但枚举不同 |
| `date` | string | 日期 | ✅ |
| `tableName` | string? | 台桌名 | ❌ 契约有 `table` |
| `startTime` | string? | 开始时间 | ❌ 未定义 |
| `endTime` | string? | 结束时间 | ❌ 未定义 |
| `duration` | string? | 时长 `'3h 20min'` | ⚠️ 契约有 `duration` 但是 number |
| `tableFee` | number? | 台费 | ❌ 未定义 |
| `tableOrigPrice` | number? | 台费原价 | ❌ 未定义 |
| `coaches` | Array<{name,level,levelColor,courseType,hours,perfHours?,fee}> | 助教服务明细 | ❌ 完全未定义 |
| `foodAmount` | number? | 酒水金额 | ❌ 未定义 |
| `foodOrigPrice` | number? | 酒水原价 | ❌ 未定义 |
| `totalAmount` | number? | 总金额 | ⚠️ 契约有 `amount` |
| `totalOrigPrice` | number? | 总原价 | ❌ 未定义 |
| `payMethod` | string? | 支付方式 | ❌ 未定义 |
| `rechargeAmount` | number? | 充值金额 | ❌ 未定义 |

#### 备注 sortedNotes（内联 mock，3 条）
| 字段 | 类型 | 说明 | 契约 CUST-1 |
|------|------|------|------------|
| `id` | string | 备注 ID | ❌ 契约无备注字段 |
| `tagLabel` | string | 标签 | ❌ |
| `createdAt` | string | 创建时间 | ❌ |
| `content` | string | 内容 | ❌ |

### 页面跳转
| 操作 | 目标 | 参数 |
|------|------|------|
| 查看服务记录 | `/pages/customer-service-records/customer-service-records` | 无参数（⚠️ 未传 customerId） |
| 问问助手 | `/pages/chat/chat` | 无参数（⚠️ 未传 customerId） |

### spec 未记录的 Gap
- **GAP-23**：`balance`（余额）、`consumption60d`（近60天消费）、`idealInterval`（理想到店间隔）、`daysSinceVisit`（距上次到店天数）在契约 CUST-1 中完全未定义。这些是客户详情页 Banner 的核心展示字段。
- **GAP-24**：`aiInsight`（AI 洞察：summary + strategies）在契约 CUST-1 中完全未定义。数据来源应为 `biz.ai_cache`，但 `cache_type` 未明确（可能是 `app4_analysis` 或新增类型）。
- **GAP-25**：`coachTasks`（关联助教任务列表）在契约 CUST-1 中完全未定义。这是一个复杂的数据结构，需要从 `biz.coach_tasks` + `fdw_etl.v_dwd_assistant_service_log` 聚合。每位助教需返回：任务类型、状态、最后服务时间、近60天服务次数/总时长/次均时长。
- **GAP-26**：`favoriteCoaches`（最亲密助教）在契约 CUST-1 中完全未定义。需要从 `fdw_etl.v_dws_member_assistant_relation_index` 获取关系指数，并聚合基础课时/激励课时/上课次数/充值金额。
- **GAP-27**：消费记录的 `coaches` 子数组（助教服务明细，含 `perfHours` 折算工时）在契约中完全未定义。这需要从 `fdw_etl.v_dwd_assistant_service_log` 按结算单关联查询。
- **GAP-28**：消费记录的 `tableFee`/`tableOrigPrice`/`foodAmount`/`foodOrigPrice`/`totalOrigPrice`/`payMethod` 等拆分字段在契约中未定义。契约仅有 `amount`（总金额）。
- **GAP-29**：消费记录的 `type` 枚举不一致 — 前端使用 `table/shop/recharge`（3 种），契约使用 `type_class`（未明确枚举）。
- **GAP-30**：备注列表在契约 CUST-1 中未定义。前端需要展示客户相关的备注。
- **GAP-31**：跳转 customer-service-records 和 chat 时未传 `customerId` 参数，联调时目标页面无法获取客户 ID。

---

## 场景 7：客户服务记录页（customer-service-records.ts）

### API 调用
- `fetchCustomerRecords({ customerId, year?, month?, table? })` → `GET /api/xcx/customers/{customerId}/records`
- 参数来源：`options.customerId` 或 `options.id`
- 当前实现：首次加载拉取全部记录，月份切换仅本地过滤

### 前端期望字段 — ServiceRecord
| 字段 | 类型 | 说明 | 契约 CUST-2 |
|------|------|------|------------|
| `table` | string | 台桌号 | ✅ |
| `type` | string | 课程类型标签 | ✅ |
| `typeClass` | enum | `basic/vip/tip/recharge` | ✅ `type_class` |
| `recordType` | enum | `course/recharge` | ❌ 未定义 |
| `duration` | number | 折算后小时 | ✅ |
| `durationRaw` | number | 折算前小时 | ✅ `duration_raw` |
| `income` | number | 到手金额 | ✅ |
| `isEstimate` | boolean | 是否预估 | ❌ 未定义 |
| `drinks` | string | 饮品描述 | ✅ |
| `date` | string | 显示日期 | ✅ |

### 页面头部字段
| 字段 | 类型 | 说明 | 契约 CUST-2 |
|------|------|------|------------|
| `customerName` | string | 客户名 | ✅ |
| `customerPhone` | string | 脱敏手机号 | ✅ `customer_phone` |
| `customerPhoneFull` | string | 完整手机号 | ❌ 未定义（需从 CUST-1 获取） |
| `relationIndex` | string | 关系指数 | ✅ |
| `totalServiceCount` | number | 累计服务次数 | ❌ 未定义 |
| `monthCount` | string | 当月次数 | ❌ 需前端计算 |
| `monthHours` | string | 当月工时 | ❌ 需前端计算 |
| `monthRelation` | string | 当月关系指数 | ❌ 未定义 |

### spec 未记录的 Gap
- **GAP-32**：`recordType`（`course/recharge`）和 `isEstimate`（是否预估）在契约 CUST-2 中未定义。
- **GAP-33**：`customerPhoneFull`（完整手机号）在 CUST-2 响应中未定义，前端需要点击查看完整号码。
- **GAP-34**：`totalServiceCount`（累计服务次数）在 CUST-2 中未定义。
- **GAP-35**：月份切换采用本地筛选（F10），联调后数据量大时需改为按月请求 API。

---

## 场景 8：备注列表页（notes.ts）

### API 调用
- `fetchNotes({ page?, pageSize? })` → `GET /api/xcx/notes?page=&pageSize=`
- `deleteNote(noteId)` → `DELETE /api/xcx/notes/{noteId}`

### 前端期望字段 — Note
| 字段 | 类型 | 说明 | 契约 NOTE-1 |
|------|------|------|------------|
| `id` | string | 备注 ID | ✅ |
| `content` | string | 备注内容 | ✅ |
| `tagType` | enum | `customer/coach/system` | ✅ `tag_type` |
| `tagLabel` | string | 标签文案 | ✅ `tag_label` |
| `createdAt` | string | 创建时间 | ✅ `created_at` |
| `score` | number? | 满意度评分 | ✅ |
| `timeLabel` | string | 格式化时间（前端计算） | — |

### 写操作
| 操作 | API | 成功后行为 |
|------|-----|-----------|
| 删除备注 | `DELETE /notes/{noteId}` | 本地 filter 移除 + Toast |

### spec 未记录的 Gap
- **GAP-36**：无触底加载逻辑（F11），API 支持分页但 `onReachBottom()` 未实现。
- **GAP-37**：`tagType` 枚举 — mock 数据中有 `'system'` 类型，但契约 NOTE-1 仅定义 `customer/coach/system`。需确认 `system` 类型的备注由谁创建、如何展示。

---

## 场景 9：助教详情页（coach-detail.ts）

### API 调用
- `fetchCoachDetail(coachId)` → `GET /api/xcx/coaches/{coachId}`
- 参数来源：`options.id`
- 返回类型：`CoachCard`（api.ts 定义），但前端实际需要远超 `CoachCard` 的数据

### 前端期望字段 — CoachDetail（完整清单）

#### 基本信息
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|------|------|------|-------------|
| `id` | string | 助教 ID | ✅ |
| `name` | string | 助教名 | ✅ |
| `avatar` | string | 头像 | ✅ |
| `level` | string | 等级 `'星级'` | ✅ |
| `skills` | string[] | 技能 | ✅ |
| `workYears` | number | 工龄 3 | ❌ 未定义 |
| `customerCount` | number | 客户数 68 | ❌ 未定义 |
| `hireDate` | string | 入职日期 | ❌ 未定义 |
| `store_name` | string | 门店名 | ✅ |

#### 绩效指标 performance（6 个指标）
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|------|------|------|-------------|
| `monthlyHours` | number | 本月工时 87.5 | ❌ 未定义 |
| `monthlySalary` | number | 本月工资 6950 | ❌ 未定义 |
| `customerBalance` | number | 客源储值余额 86200 | ❌ 未定义 |
| `tasksCompleted` | number | 任务完成数 38 | ❌ 未定义 |
| `perfCurrent` | number | 当前绩效 80 | ❌ 未定义 |
| `perfTarget` | number | 目标绩效 100 | ❌ 未定义 |

#### 绩效指标卡片 perfCards（4 张）
| 卡片 | 需要字段 | 契约 |
|------|---------|------|
| 本月定档业绩 | `monthlyHours` + 折算前工时 | ❌ |
| 本月工资（预估） | `monthlySalary` | ❌ |
| 客源储值余额 | `customerBalance` + `customerCount` | ❌ |
| 本月任务完成 | `tasksCompleted` + 覆盖客户数 | ❌ |

#### 进度条数据
| 字段 | 类型 | 说明 | 契约 |
|------|------|------|------|
| `tierNodes` | number[] | 档位节点 `[0,100,130,160,190,220]` | ❌ 未定义 |

#### 收入明细 income（本月/上月各 4 项）
| 项目 | 说明 | 契约 |
|------|------|------|
| 基础课时费 | `¥3,500` | ❌ 未定义 |
| 激励课时费 | `¥1,800` | ❌ 未定义 |
| 充值提成 | `¥1,200` | ❌ 未定义 |
| 酒水提成 | `¥450` | ❌ 未定义 |

#### 任务执行
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|------|------|------|-------------|
| `visibleTasks` | TaskItem[] | 可见任务（active） | ⚠️ 契约说"同 TASK-1 item 格式"但实际需要更多字段 |
| `hiddenTasks` | TaskItem[] | 隐藏任务（inactive） | ⚠️ |
| `abandonedTasks` | AbandonedTask[] | 已放弃任务 | ⚠️ |

TaskItem 需要的字段（超出 TASK-1 item）：
| 字段 | 类型 | 说明 |
|------|------|------|
| `typeLabel` | string | 任务类型标签 |
| `typeClass` | string | 样式类 `high-priority/priority/relationship/callback` |
| `customerName` | string | 客户名 |
| `noteCount` | number | 备注数量 |
| `pinned` | boolean | 是否置顶 |
| `notes` | Array<{pinned?,text,date}> | 备注列表（含置顶标记） |

AbandonedTask 字段：
| 字段 | 类型 | 说明 |
|------|------|------|
| `customerName` | string | 客户名 |
| `reason` | string | 放弃原因 |

#### 客户关系 TOP20 — TopCustomer
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|------|------|------|-------------|
| `id` | string | 客户 ID | ✅ |
| `name` | string | 客户名 | ✅ |
| `initial` | string | 姓名首字 | ❌ |
| `avatarGradient` | string | 头像渐变色 | ❌ |
| `heartEmoji` | string | 爱心 emoji `💖/🧡/💛/💙` | ❌ |
| `score` | string | 关系指数 `'9.5'` | ❌ 契约有 `total_spend`/`visit_count` 但无 score |
| `scoreColor` | string | 指数颜色 | ❌ |
| `serviceCount` | number | 服务次数 | ⚠️ 契约有 `visit_count` |
| `balance` | string | 余额 `'¥8,600'` | ❌ 未定义 |
| `consume` | string | 消费 `'¥12,800'` | ⚠️ 契约有 `total_spend` |

#### 近期服务明细 — ServiceRecord
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|------|------|------|-------------|
| `customerId` | string? | 客户 ID | ❌ |
| `customerName` | string | 客户名 | ⚠️ |
| `initial` | string | 姓名首字 | ❌ |
| `avatarGradient` | string | 头像渐变色 | ❌ |
| `type` | string | 课程类型 | ⚠️ |
| `typeClass` | string | 样式类 | ⚠️ |
| `table` | string | 台号 | ⚠️ |
| `duration` | string | 时长 `'2.5h'` | ⚠️ |
| `income` | string | 收入 `'¥200'` | ⚠️ |
| `date` | string | 日期时间 | ⚠️ |
| `perfHours` | string? | 折算工时 `'2h'` | ❌ 未定义 |

#### 历史月份统计 — HistoryMonth
| 字段 | 类型 | 说明 | 契约 COACH-1 |
|------|------|------|-------------|
| `month` | string | 月份标签 `'本月'`/`'上月'`/`'4月'` | ❌ 完全未定义 |
| `estimated` | boolean | 是否预估 | ❌ |
| `customers` | string | 客户数 `'22人'` | ❌ |
| `hours` | string | 工时 `'87.5h'` | ❌ |
| `salary` | string | 工资 `'¥6,950'` | ❌ |
| `callbackDone` | number | 回访完成数 | ❌ |
| `recallDone` | number | 召回完成数 | ❌ |

### 页面跳转
| 操作 | 目标 | 参数 |
|------|------|------|
| 点击任务项 | `/pages/customer-detail/customer-detail` | `?name={customerName}` |
| 点击客户卡片 | `/pages/customer-detail/customer-detail` | `?id={customerId}` |
| 点击服务记录 | `/pages/customer-detail/customer-detail` | `?id={customerId}` |
| 查看更多服务记录 | `/pages/performance-records/performance-records` | `?coachId={coachId}` |
| 问问助手 | `/pages/chat/chat` | `?coachId={coachId}` |

### spec 未记录的 Gap
- **GAP-38**：契约 COACH-1 严重不完整。前端需要 `performance`（6 个绩效指标）、`income`（本月/上月各 4 项收入明细）、`tierNodes`（档位节点数组），契约均未定义。
- **GAP-39**：`TopCustomer` 需要 `heartEmoji`、`score`（关系指数）、`scoreColor`、`balance`（余额）、`consume`（消费），契约仅有 `total_spend`/`visit_count`。
- **GAP-40**：`ServiceRecord.perfHours`（折算工时）在契约中未定义。
- **GAP-41**：`HistoryMonth`（历史月份统计）在契约中完全未定义。需要返回最近 N 个月的汇总数据（客户数、工时、工资、回访/召回完成数）。
- **GAP-42**：`TaskItem.notes`（任务关联的备注列表，含置顶标记）在契约中未定义。契约的 `visible_tasks` 说"同 TASK-1 item 格式"，但前端需要额外的 `noteCount`、`pinned`、`notes[]` 字段。
- **GAP-43**：`AbandonedTask.reason`（放弃原因）在契约中未定义。
- **GAP-44**：`workYears`（工龄）、`hireDate`（入职日期）在契约中未定义。

---

## 场景 10：对话页面（chat.ts + chat-history.ts）

### chat.ts — API 调用
- `fetchChatMessages(customerId)` → `GET /api/xcx/chat/{chatId}/messages`
- ⚠️ 注意：前端传入的是 `customerId`（或 `'default'`），但 API 路径参数是 `chatId`。参数语义不匹配。
- `sendChatMessage(chatId, content)` → `POST /api/xcx/chat/{chatId}/messages`
- 页面参数：`options.customerId`

### chat.ts — 前端期望字段 — ChatMessage
| 字段 | 类型 | 说明 | 契约 CHAT-2 |
|------|------|------|------------|
| `id` | string | 消息 ID | ✅ |
| `role` | enum | `user/assistant` | ✅ |
| `content` | string | 消息内容 | ✅ |
| `timestamp` | string | 时间戳 | ⚠️ 契约用 `created_at` |
| `referenceCard` | object? | 引用卡片 | ❌ 完全未定义 |
| `referenceCard.type` | enum | `customer/record` | ❌ |
| `referenceCard.title` | string | 卡片标题 | ❌ |
| `referenceCard.summary` | string | 卡片摘要 | ❌ |
| `referenceCard.data` | Record<string,string> | 键值对数据 | ❌ |

### chat.ts — SSE 流式回复
- 前端 `simulateStreamOutput()` 模拟流式输出（逐字显示）
- 联调时需替换为真实 SSE 连接（`POST /api/xcx/chat/stream`）
- spec R3 已确认保留 SSE 流式端点

### chat.ts — 页面参数
| 来源页面 | 传递参数 | 用途 |
|---------|---------|------|
| task-detail | `?customerId={taskId}` | ⚠️ 实际传的是 taskId |
| customer-detail | 无参数 | ⚠️ 未传 customerId |
| coach-detail | `?coachId={coachId}` | 助教 ID |
| chat-history | `?historyId={id}` | 历史对话 ID |

### chat-history.ts — API 调用
- `fetchChatHistory({ page?, pageSize? })` → `GET /api/xcx/chat/history`
- 返回：`{ items: ChatHistoryItem[], total, hasMore }`

### chat-history.ts — 前端期望字段 — ChatHistoryItem
| 字段 | 类型 | 说明 | 契约 CHAT-1 |
|------|------|------|------------|
| `id` | string | 对话 ID | ✅ |
| `title` | string | 对话标题 | ❌ 契约无 `title` |
| `lastMessage` | string | 最后消息 | ✅ `last_message` |
| `timestamp` | string | 时间戳 | ⚠️ 契约用 `last_time` |
| `customerName` | string? | 关联客户名 | ✅ |

### chat-history.ts — 页面跳转
| 操作 | 目标 | 参数 |
|------|------|------|
| 点击对话记录 | `/pages/chat/chat` | `?historyId={id}` |

### spec 未记录的 Gap
- **GAP-45**：`ChatMessage.referenceCard`（引用卡片）在契约 CHAT-2 中完全未定义。前端需要展示客户概览卡片（含键值对数据），后端需在消息中附带结构化引用数据。
- **GAP-46**：`ChatMessage.timestamp` vs 契约 `created_at` — 字段名不一致，前端使用 `timestamp`，契约定义 `created_at`。
- **GAP-47**：`ChatHistoryItem.title`（对话标题）在契约 CHAT-1 中未定义。契约仅有 `customer_name`/`last_message`/`last_time`/`unread_count`。
- **GAP-48**：`ChatHistoryItem.timestamp` vs 契约 `last_time` — 字段名不一致。
- **GAP-49**：chat 页面接收 `customerId` 参数但 API 路径需要 `chatId`。前端需要一个"根据 customerId 查找或创建对话"的机制，或 API 支持 `customerId` 作为查询参数。
- **GAP-50**：chat 页面还接收 `historyId`（从 chat-history 跳转）和 `coachId`（从 coach-detail 跳转），但 `loadMessages()` 仅使用 `customerId`。多种入口参数的路由逻辑未实现。
- **GAP-51**：SSE 流式端点 `POST /api/xcx/chat/stream` 在契约中未定义（spec R3 已确认需补充）。

---

## 汇总：Gap 清单

### 一、架构级 Gap（影响多个页面）

| # | Gap | 严重度 | 影响页面 | 说明 |
|---|-----|--------|---------|------|
| GAP-01 | globalData.authUser 缺少 role/store_name/coach_level/avatar | 🟠 中 | task-list, performance, performance-records | 多个页面 Banner 需要这些字段，当前硬编码 |
| GAP-04 | pin/unpin API 端点未在契约中定义 | 🔴 高 | task-list | api.ts 无对应函数，前端仅本地更新 |
| GAP-05 | createNote 缺少 score 参数 | 🟠 中 | task-list, task-detail | 前端传 score 但 api.ts 签名无此字段 |
| GAP-09/10 | task-detail 跳转 chat/customer-service-records 时传 taskId 而非 customerId | 🔴 高 | task-detail → chat/customer-service-records | TASK-2 响应需包含 customer_id |
| GAP-31 | customer-detail 跳转时未传 customerId | 🔴 高 | customer-detail → chat/customer-service-records | 联调时目标页面无法获取客户 ID |
| GAP-17 | performance 跳转 task-detail 传 customerName 而非 taskId | 🟠 中 | performance → task-detail | 参数不匹配 |

### 二、TASK-1 扩展 Gap（任务列表绩效卡片）

| # | Gap | 严重度 | 说明 |
|---|-----|--------|------|
| GAP-02 | performance 字段严重不足（需 15+ 字段，契约仅 4 个） | 🔴 高 | tierNodes、basicHours、bonusHours、currentTier、nextTierHours、tierCompleted、bonusMoney、incomeTrend、incomeTrendDir、prevMonth 等 |
| GAP-03 | enrichTask 需要 lastVisitDays/balance/aiSuggestion | 🟠 中 | 任务卡片扩展字段 |

### 三、PERF-1 Gap（绩效概览）

| # | Gap | 严重度 | 说明 |
|---|-----|--------|------|
| GAP-12 | this_month_records 需要 DateGroup 分组结构 | 🔴 高 | 契约是扁平数组，前端需按日期分组 + timeRange/courseType/location |
| GAP-13 | 收入档位数据完全未定义 | 🔴 高 | currentTier/nextTier/upgradeHoursNeeded/upgradeBonus |
| GAP-14 | lastMonthIncome 未定义 | 🟠 中 | Banner 需展示上月收入 |
| GAP-15 | incomeItems.desc 未定义 | 🟠 中 | 需费率×工时的拆分描述 |
| GAP-16 | newCustomers/regularCustomers 缺少多个字段 | 🟠 中 | lastService/count/hours/income |
| GAP-18 | 页面无月份切换功能 | 🟡 低 | F8 前端修复项 |

### 四、CUST-1 Gap（客户详情 — 最大 Gap 集中区）

| # | Gap | 严重度 | 说明 |
|---|-----|--------|------|
| GAP-23 | balance/consumption60d/idealInterval/daysSinceVisit 未定义 | 🔴 高 | Banner 核心展示字段 |
| GAP-24 | aiInsight (summary + strategies) 未定义 | 🔴 高 | AI 洞察模块 |
| GAP-25 | coachTasks（关联助教任务列表）未定义 | 🔴 高 | 复杂数据结构，需多表聚合 |
| GAP-26 | favoriteCoaches（最亲密助教）未定义 | 🔴 高 | 需关系指数 + 服务统计 |
| GAP-27 | 消费记录 coaches 子数组（助教服务明细）未定义 | 🔴 高 | 含 perfHours 折算工时 |
| GAP-28 | 消费记录拆分字段（tableFee/foodAmount/totalOrigPrice 等）未定义 | 🟠 中 | 契约仅有 amount |
| GAP-29 | 消费记录 type 枚举不一致 | 🟠 中 | 前端 table/shop/recharge vs 契约未明确 |
| GAP-30 | 备注列表未定义 | 🟠 中 | 客户详情需展示备注 |

### 五、COACH-1 Gap（助教详情 — 第二大 Gap 集中区）

| # | Gap | 严重度 | 说明 |
|---|-----|--------|------|
| GAP-38 | performance/income/tierNodes 未定义 | 🔴 高 | 6 个绩效指标 + 本月/上月各 4 项收入 + 档位节点 |
| GAP-39 | TopCustomer 缺少 heartEmoji/score/scoreColor/balance | 🔴 高 | 客户关系 TOP20 核心字段 |
| GAP-40 | ServiceRecord.perfHours 未定义 | 🟠 中 | 折算工时 |
| GAP-41 | HistoryMonth 完全未定义 | 🔴 高 | 历史月份统计（5+ 个月） |
| GAP-42 | TaskItem.notes（含置顶标记）未定义 | 🟠 中 | 任务关联备注 |
| GAP-43 | AbandonedTask.reason 未定义 | 🟡 低 | 放弃原因 |
| GAP-44 | workYears/hireDate 未定义 | 🟡 低 | 助教基本信息 |

### 六、CHAT 模块 Gap

| # | Gap | 严重度 | 说明 |
|---|-----|--------|------|
| GAP-45 | ChatMessage.referenceCard 未定义 | 🔴 高 | 引用卡片（客户概览数据） |
| GAP-46 | timestamp vs created_at 字段名不一致 | 🟠 中 | 前端用 timestamp，契约用 created_at |
| GAP-47 | ChatHistoryItem.title 未定义 | 🟠 中 | 对话标题 |
| GAP-48 | ChatHistoryItem.timestamp vs last_time 不一致 | 🟠 中 | 字段名不一致 |
| GAP-49 | customerId → chatId 映射机制缺失 | 🔴 高 | 前端传 customerId 但 API 需要 chatId |
| GAP-50 | 多入口参数路由逻辑未实现 | 🟠 中 | historyId/coachId/customerId 三种入口 |
| GAP-51 | SSE 流式端点未在契约中定义 | 🟠 中 | spec 已确认需补充 |

### 七、其他 Gap

| # | Gap | 严重度 | 说明 |
|---|-----|--------|------|
| GAP-06 | 维客线索 tag 格式不一致（含换行符） | 🟡 低 | 需统一 |
| GAP-07 | 维客线索 source 格式不一致 | 🟡 低 | mock 用 `'By:系统'`，类型定义用 `'manual'` |
| GAP-08 | aiAnalysis/talkingPoints 的 ai_cache cache_type 未明确 | 🟠 中 | 需明确映射 |
| GAP-11 | storageLevel/relationLevel 计算逻辑未明确 | 🟡 低 | 前端本地计算 |
| GAP-19 | avatarChar/avatarColor 是否后端返回 | 🟡 低 | 建议前端自行计算 |
| GAP-20 | courseTypeClass 枚举值前缀不统一 | 🟡 低 | `tag-basic` vs `basic` |
| GAP-21 | 月份切换未重置分页 | 🟡 低 | F9 前端 bug |
| GAP-22 | performance-records Banner 字段来源 | 🟡 低 | 依赖 GAP-01 |
| GAP-32 | recordType/isEstimate 未定义 | 🟡 低 | CUST-2 扩展字段 |
| GAP-33 | customerPhoneFull 未定义 | 🟡 低 | CUST-2 需完整手机号 |
| GAP-34 | totalServiceCount 未定义 | 🟡 低 | CUST-2 累计次数 |
| GAP-35 | 月份切换本地筛选需改 API 请求 | 🟡 低 | F10 前端修复 |
| GAP-36 | notes 页无触底加载 | 🟡 低 | F11 前端修复 |
| GAP-37 | Note.tagType 'system' 类型语义 | 🟡 低 | 需确认 |

---

### 统计

| 严重度 | 数量 | 说明 |
|--------|------|------|
| 🔴 高 | 17 | 阻塞联调，必须在后端开发前解决 |
| 🟠 中 | 19 | 影响功能完整性，需在联调前补充 |
| 🟡 低 | 15 | 可联调后修复或前端自行处理 |
| **合计** | **51** | |

### 最高优先级修复建议

1. **契约大幅扩展 CUST-1**：客户详情是 Gap 最集中的页面（GAP-23~30），需新增 `balance`、`consumption60d`、`idealInterval`、`daysSinceVisit`、`aiInsight`、`coachTasks`、`favoriteCoaches`、消费记录拆分字段、备注列表。
2. **契约大幅扩展 COACH-1**：助教详情是第二大 Gap 集中区（GAP-38~44），需新增 `performance`（6 指标）、`income`（8 项）、`tierNodes`、`TopCustomer` 扩展字段、`HistoryMonth`、`ServiceRecord.perfHours`。
3. **契约扩展 PERF-1**：绩效概览需要 DateGroup 分组结构（GAP-12）和收入档位数据（GAP-13）。
4. **契约扩展 TASK-1 performance**：绩效卡片需要 15+ 字段（GAP-02）。
5. **修复 customerId/taskId 混淆**：TASK-2 响应需包含 `customer_id`（GAP-09/10），customer-detail 跳转需传参（GAP-31）。
6. **补充 CHAT 模块**：referenceCard（GAP-45）、customerId→chatId 映射（GAP-49）、SSE 端点定义（GAP-51）。
