# 需求文档 — RNS1.4：CHAT 对齐与联调收尾

## 简介

RNS1.4 是 NS1 小程序后端 API 补全项目的最后一个子 spec，负责 CHAT 模块路径迁移和功能补全、FDW 端到端验证、以及全量前后端联调。本 spec 依赖 RNS1.0-1.3 全部完成，是整个 RNS1 系列的收尾阶段，确保 13 个页面全部连接真实后端运行，无 mock 数据残留。

### 依赖

- RNS1.0（基础设施与契约重写）— 全局响应包装中间件、camelCase 转换、重写后的 API 契约
- RNS1.1（任务与绩效接口）— TASK-1/2、PERF-1/2、PIN 接口已实现
- RNS1.2（客户与助教接口）— CUST-1/2、COACH-1 接口已实现
- RNS1.3（三看板接口）— BOARD-1/2/3、CONFIG-1 接口已实现
- 后端已有 `xcx_ai.py`（现有 `/api/ai/*` 路由，需迁移）
- 前端已有 chat.ts 和 chat-history.ts 页面（P5.2 交付），当前使用 mock 数据和模拟流式输出

### 来源文档

- `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-45~51）

## 术语表

- **Backend**：FastAPI 后端应用，位于 `apps/backend/`
- **Miniprogram**：微信小程序前端应用，位于 `apps/miniprogram/`
- **CHAT_1_API**：对话历史列表接口 `GET /api/xcx/chat/history`，返回分页的对话列表
- **CHAT_2_API**：对话消息接口 `GET /api/xcx/chat/{chatId}/messages` 或 `GET /api/xcx/chat/messages?customerId={customerId}`，返回指定对话的消息列表
- **CHAT_3_API**：发送消息接口 `POST /api/xcx/chat/{chatId}/messages`，发送用户消息并获取 AI 同步回复
- **CHAT_4_SSE**：SSE 流式端点 `POST /api/xcx/chat/stream`，通过 Server-Sent Events 逐 token 返回 AI 回复
- **SSE**：Server-Sent Events，服务端推送事件协议，用于 AI 流式回复的逐 token 输出
- **referenceCard**：引用卡片，消息中附带的结构化上下文数据（类型/标题/摘要/键值对），用于展示客户概览等信息
- **FDW**：PostgreSQL Foreign Data Wrapper，用于从业务库 `zqyy_app` 访问 ETL 库 `etl_feiqiu` 的数据
- **chat_sessions**：业务库 `zqyy_app` 中的对话会话表，存储对话元数据
- **chat_messages**：业务库 `zqyy_app` 中的消息表，存储对话消息内容
- **Response_Wrapper**：RNS1.0 实现的全局响应包装中间件，`text/event-stream` 响应自动跳过包装
- **items_sum**：DWD-DOC 强制使用的消费金额口径
- **联调**：前后端联合调试，验证所有页面使用真实后端数据正常运行

## 需求

### 需求 1：CHAT 路径迁移（T4-1）

**用户故事：** 作为前后端开发者，我希望 CHAT 模块的 API 路径从 `/api/ai/*` 统一迁移到 `/api/xcx/chat/*`，以便与其他小程序接口保持一致的路径命名规范。

#### 验收标准

1. THE Backend SHALL 将现有 `/api/ai/*` 路由全部迁移到 `/api/xcx/chat/*` 路径下，包括同步端点和 SSE 流式端点
2. THE Backend SHALL 在迁移后移除原 `/api/ai/*` 路径，不保留旧路径的兼容映射
3. THE Backend SHALL 将迁移后的路由注册到 `xcx_chat` router（或等效命名），与其他 `xcx_*` 路由模块保持一致的组织结构
4. THE Miniprogram SHALL 更新 `services/api.ts` 中所有 CHAT 相关的 API 调用路径，从 `/api/ai/*` 改为 `/api/xcx/chat/*`
5. WHEN CHAT_4_SSE 端点迁移到 `/api/xcx/chat/stream` 后，THE Response_Wrapper SHALL 继续对 `text/event-stream` 响应跳过包装（RNS1.0 已实现的行为不受路径变更影响）

### 需求 2：实现 CHAT-1 对话历史列表（T4-2 历史列表部分）

**用户故事：** 作为助教，我希望在对话历史页面看到所有对话记录（含对话标题和最后消息摘要），以便快速找到并继续之前的对话。

#### 验收标准

1. THE CHAT_1_API SHALL 实现 `GET /api/xcx/chat/history` 端点，接受 `page`（默认 1）和 `pageSize`（默认 20）查询参数，返回分页的对话历史列表
2. THE CHAT_1_API SHALL 为每条对话记录返回以下字段：`id`（对话 ID）、`title`（对话标题）、`customerName`（关联客户姓名，可选）、`lastMessage`（最后一条消息摘要）、`timestamp`（最后消息时间，ISO 8601 格式）、`unreadCount`（未读消息数）
3. THE CHAT_1_API SHALL 从 `zqyy_app.chat_sessions` 查询对话列表，按最后消息时间倒序排列
4. THE CHAT_1_API SHALL 为 `title` 字段生成对话标题：优先使用对话会话中存储的自定义标题，若无自定义标题则使用关联客户姓名，若均无则使用首条消息内容的前 20 个字符作为标题
5. THE CHAT_1_API SHALL 返回标准分页字段：`total`（总记录数）、`page`（当前页码）、`pageSize`（每页条数）
6. THE CHAT_1_API SHALL 通过当前登录用户的身份过滤对话列表，确保每位助教只能看到自己的对话记录

### 需求 3：实现 CHAT-2 对话消息查看（T4-2 消息查看部分）

**用户故事：** 作为助教，我希望查看指定对话的消息列表，以便回顾与 AI 助手的对话内容。

#### 验收标准

1. THE CHAT_2_API SHALL 实现两个等效的消息查询端点：`GET /api/xcx/chat/{chatId}/messages`（通过对话 ID 查询）和 `GET /api/xcx/chat/messages?contextType={type}&contextId={id}`（通过上下文类型和 ID 查询）
2. THE CHAT_2_API SHALL 接受 `page`（默认 1）和 `pageSize`（默认 50）查询参数，返回分页的消息列表
3. THE CHAT_2_API SHALL 为每条消息返回以下字段：`id`（消息 ID）、`role`（`user` 或 `assistant`）、`content`（消息内容）、`createdAt`（创建时间，ISO 8601 格式）、`referenceCard`（引用卡片，可选）
4. THE CHAT_2_API SHALL 统一使用 `createdAt` 作为消息时间字段名（替代前端使用的 `timestamp` 和旧契约的 `created_at`，遵循 camelCase 规范）
5. THE CHAT_2_API SHALL 从 `zqyy_app.chat_messages` 查询消息列表，按 `createdAt` 正序排列（最早的消息在前）
6. THE CHAT_2_API SHALL 在响应中返回 `chatId` 字段，供前端后续发送消息时使用（尤其是通过上下文入口时，前端需要获取对应的 `chatId`）
7. THE CHAT_2_API SHALL 返回标准分页字段：`total`（总记录数）、`page`（当前页码）、`pageSize`（每页条数）

#### 3.2 上下文对话复用规则

8. WHEN 通过 `contextType` 和 `contextId` 查询参数调用消息端点时，THE CHAT_2_API SHALL 按以下规则查找或创建对话：
   - `contextType='task'`：查找同一用户、同一 `contextId`（taskId）的已有对话，找到则复用（无时限），找不到则新建
   - `contextType='customer'` 或 `contextType='coach'`：查找同一用户、同一 `contextId` 的已有对话，若最后消息时间 ≤ 3 天则复用，> 3 天或不存在则新建
   - `contextType='general'`：始终新建对话
9. THE CHAT_2_API SHALL 在 `ai_conversations` 中记录 `context_type` 和 `context_id` 字段，用于后续对话查找
10. THE CHAT_2_API SHALL 确保对话复用查找基于 `(user_id, site_id, context_type, context_id)` 组合，不同用户的对话互不影响


### 需求 4：CHAT referenceCard 支持（T4-3）

**用户故事：** 作为助教，我希望在与 AI 助手对话时，消息中能附带客户概览卡片（含余额、消费、到店频次等键值对数据），以便在对话上下文中快速查看客户关键信息。

#### 验收标准

1. THE CHAT_2_API SHALL 为消息返回可选的 `referenceCard` 字段，结构包含：`type`（引用类型，`customer` 或 `record` 枚举）、`title`（卡片标题，如 `"张伟 — 消费概览"`）、`summary`（摘要文字）、`data`（`Record<string, string>` 键值对详情，如 `{ "近30天消费": "¥2,380", "到店次数": "8次" }`）
2. WHEN AI 助手回复消息涉及特定客户时，THE Backend SHALL 从 FDW 查询该客户的关键指标（余额、近期消费、到店频次等），组装为 `referenceCard` 附加到 AI 回复消息中
3. THE Backend SHALL 将 `referenceCard` 数据持久化存储到 `chat_messages` 表中（作为 JSON 字段），以便历史消息查看时仍能展示引用卡片
4. THE Miniprogram SHALL 在 chat 页面的消息列表中，检测消息的 `referenceCard` 字段，若存在则渲染为结构化卡片组件（标题 + 摘要 + 键值对列表）

#### 4.2 多入口参数路由（GAP-50）

5. THE Miniprogram SHALL 在 chat 页面的 `onLoad(options)` 中实现多入口参数路由逻辑，按以下优先级处理入口参数：
   - 若 `options.historyId` 存在（从 chat-history 跳转），使用 `historyId` 作为 `chatId` 直接加载历史消息
   - 若 `options.taskId` 存在（从 task-detail 跳转），使用 `contextType=task` + `contextId=taskId` 调用 CHAT_2_API，由后端查找同一任务的已有对话（始终复用，无时限）
   - 若 `options.customerId` 存在（从 customer-detail 跳转），使用 `contextType=customer` + `contextId=customerId` 调用 CHAT_2_API，由后端按 3 天时限判断复用或新建
   - 若 `options.coachId` 存在（从 coach-detail 跳转），使用 `contextType=coach` + `contextId=coachId` 调用 CHAT_2_API，由后端按 3 天时限判断复用或新建
6. WHEN 通过上下文入口进入对话后，THE Miniprogram SHALL 将后端返回的 `chatId` 缓存到页面 data 中，后续发送消息和 SSE 流式请求均使用该 `chatId`
7. IF chat 页面未收到任何入口参数（`historyId`/`taskId`/`customerId`/`coachId` 均为空），THEN THE Miniprogram SHALL 使用 `contextType=general` 调用 CHAT_2_API 创建一个通用对话

### 需求 5：CHAT-3 发送消息（T4-2 发送部分）

**用户故事：** 作为助教，我希望在对话页面发送消息后能立即收到 AI 的同步回复，以便在不支持 SSE 的场景下也能正常使用 AI 助手。

#### 验收标准

1. THE CHAT_3_API SHALL 实现 `POST /api/xcx/chat/{chatId}/messages` 端点，接受请求体 `{ content: string }`
2. THE CHAT_3_API SHALL 将用户消息存入 `chat_messages` 表，调用 AI 服务获取回复，将 AI 回复也存入 `chat_messages` 表
3. THE CHAT_3_API SHALL 返回包含用户消息和 AI 回复的响应：`userMessage`（含 `id`/`content`/`createdAt`）和 `aiReply`（含 `id`/`content`/`createdAt`）
4. THE CHAT_3_API SHALL 在发送消息后更新 `chat_sessions` 表的 `lastMessage` 和最后消息时间字段
5. IF AI 服务调用失败或超时，THEN THE CHAT_3_API SHALL 仍保存用户消息，并返回 AI 回复为错误提示消息（如 `{ content: "抱歉，AI 助手暂时无法回复，请稍后重试" }`），HTTP 状态码保持 200
6. THE CHAT_3_API SHALL 验证请求的 `chatId` 属于当前登录助教，不属于时返回 HTTP 403

### 需求 6：CHAT-4 SSE 流式端点（T4-1 SSE 部分）

**用户故事：** 作为助教，我希望 AI 助手的回复能以流式方式逐字显示，以便获得更自然的对话体验，减少等待感。

#### 验收标准

1. THE CHAT_4_SSE SHALL 实现 `POST /api/xcx/chat/stream` 端点，接受请求体 `{ chatId: string, content: string }`，响应内容类型为 `text/event-stream`
2. THE CHAT_4_SSE SHALL 发送以下三种 SSE 事件类型：
   - `event: message` — 逐 token 输出，`data` 为 `{"token": "<文本片段>"}`
   - `event: done` — 流结束，`data` 为 `{"messageId": "<完整消息ID>", "createdAt": "<ISO 8601>"}`
   - `event: error` — 错误，`data` 为 `{"message": "<错误描述>"}`
3. THE CHAT_4_SSE SHALL 在流开始前将用户消息存入 `chat_messages` 表，在流结束后将完整的 AI 回复存入 `chat_messages` 表
4. THE CHAT_4_SSE SHALL 在流结束后更新 `chat_sessions` 表的 `lastMessage` 和最后消息时间字段
5. THE Response_Wrapper SHALL 对 `text/event-stream` 响应跳过全局包装，直接透传 SSE 事件流（RNS1.0 已实现）
6. THE CHAT_4_SSE SHALL 验证请求的 `chatId` 属于当前登录助教，不属于时返回 HTTP 403（此时响应为普通 JSON 错误，非 SSE）
7. THE Miniprogram SHALL 将 chat 页面现有的 `simulateStreamOutput()`（模拟逐字输出）替换为真实的 SSE 连接，通过 `wx.request` 或兼容方案接收 `text/event-stream` 响应


### 需求 7：FDW 端到端验证（T4-4）

**用户故事：** 作为后端开发者，我希望验证所有 FDW 查询在测试环境链路（`test_zqyy_app` → `test_etl_feiqiu`）上正常工作，以便确保 RNS1.1-1.3 实现的所有接口在真实数据链路上不会因 FDW 连接、权限或性能问题而失败。

#### 验收标准

1. THE Backend SHALL 验证所有 `fdw_etl.*` 视图在 `test_zqyy_app` 数据库中可正常访问，包括但不限于：`v_dws_assistant_salary_calc`、`v_dwd_assistant_service_log`、`v_dim_member`、`v_dim_assistant`、`v_dws_member_consumption_summary`、`v_dws_member_assistant_relation_index`、`v_dws_finance_*` 系列视图
2. THE Backend SHALL 验证每个 FDW 视图的查询响应时间在可接受范围内（单次查询不超过 3 秒），对超时的查询记录慢查询日志并评估是否需要添加索引
3. THE Backend SHALL 验证 FDW 查询在带有典型过滤条件（如 `assistant_id`、`member_id`、日期范围）时能正确返回数据，且结果集与直接查询 `test_etl_feiqiu` 的结果一致
4. IF 某个 FDW 视图不存在或权限不足，THEN THE Backend SHALL 记录具体的错误信息（视图名、错误类型），并在验证报告中标注需要 DBA 介入修复
5. THE Backend SHALL 检查 FDW 链路上的关键索引是否存在：`chat_sessions` 表的 `(assistant_id, customer_id)` 索引、`chat_messages` 表的 `(session_id, created_at)` 索引

### 需求 8：前端联调修复 — notes 页触底加载（T4-5 F11）

**用户故事：** 作为助教，我希望在备注列表页面滚动到底部时自动加载更多备注，以便查看全部备注记录而不需要手动翻页。

#### 验收标准

1. THE Miniprogram SHALL 在 notes 页面实现 `onReachBottom()` 生命周期函数，当用户滚动到页面底部时自动请求下一页备注数据
2. WHEN 触底加载触发时，THE Miniprogram SHALL 将 `page` 参数加 1，调用 `fetchNotes({ page, pageSize })` 接口，将返回的备注追加到已有列表末尾
3. WHEN 后端返回的 `hasMore` 为 `false` 或返回的备注数量小于 `pageSize` 时，THE Miniprogram SHALL 停止触底加载，显示"没有更多了"提示
4. THE Miniprogram SHALL 在触底加载过程中显示加载状态指示器，防止重复触发请求

### 需求 9：前端联调修复 — customer-service-records 按月请求（T4-5 F10）

**用户故事：** 作为助教，我希望客户服务记录页面在切换月份时向后端请求对应月份的数据，以便在数据量大时页面仍能快速响应，而不是全量加载后本地过滤。

#### 验收标准

1. THE Miniprogram SHALL 修改 customer-service-records 页面的月份切换逻辑，从当前的"全量加载后本地过滤"改为"按月请求 API"
2. WHEN 用户切换月份时，THE Miniprogram SHALL 使用新的 `year`/`month` 参数调用 `fetchCustomerRecords({ customerId, year, month })` 接口，加载对应月份的服务记录
3. THE Miniprogram SHALL 在月份切换时清空已有记录列表，显示加载状态，待新数据返回后渲染
4. THE Miniprogram SHALL 在首次加载时默认请求当前月份的数据，而非全量数据
5. THE Backend SHALL 确保 CUST-2 接口支持 `year` 和 `month` 查询参数，仅返回指定月份的服务记录（RNS1.2 T2-4 已实现按月查询能力）

### 需求 10：全量前后端联调（T4-5 联调部分）

**用户故事：** 作为开发团队，我们希望 13 个小程序页面全部连接真实后端运行，无 mock 数据残留，以便确认整个应用在真实数据环境下功能完整、交互正常。

#### 验收标准

1. THE Miniprogram SHALL 移除所有页面中的内联 mock 数据和 mock 数据导入（`import { mockXxx } from '../../utils/mock-data'`），全部替换为真实 API 调用
2. THE Miniprogram SHALL 确保以下 13 个页面均能使用真实后端数据正常渲染：`task-list`、`task-detail`、`notes`、`performance`、`performance-records`、`customer-detail`、`customer-service-records`、`coach-detail`、`board-coach`、`board-customer`、`board-finance`、`chat-history`、`chat`
3. WHEN 某个页面的 API 调用失败时，THE Miniprogram SHALL 显示友好的错误提示（如 Toast 或空状态占位），不出现白屏或未捕获异常
4. THE Miniprogram SHALL 验证所有页面间的跳转参数传递正确（RNS1.0 T0-6 已修复的跨页面参数），确保目标页面能正确加载对应数据
5. THE Miniprogram SHALL 验证 chat 页面从 4 个入口（task-detail、customer-detail、coach-detail、chat-history）进入时均能正确关联上下文并加载对应对话
6. IF 联调过程中发现新的 Bug 或数据不一致问题，THEN THE Miniprogram 和 Backend SHALL 在本 spec 范围内修复，修复内容记录到联调问题清单中

### 需求 11：全局约束与权限控制

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

#### 验收标准

1. THE Backend SHALL 对所有 RNS1.4 CHAT 接口（CHAT-1、CHAT-2、CHAT-3、CHAT-4）执行 `require_approved()` 权限检查，确保用户状态为 `approved`
2. THE Backend SHALL 通过当前登录用户的身份信息过滤对话数据，确保每位助教只能访问自己创建的或与自己关联的对话
3. IF 当前用户未通过审核（状态非 `approved`），THEN THE Backend SHALL 返回 HTTP 403 `{ code: 403, message: "用户未通过审核，无法访问此资源" }`
4. THE Backend SHALL 对所有 CHAT 接口的响应字段名统一使用 camelCase 格式（与 RNS1.0 的 CamelCase_Converter 一致）
5. THE Backend SHALL 确保 CHAT 模块的错误响应格式与全局异常处理器一致：`{ code: <HTTP状态码>, message: <错误详情> }`
6. WHEN CHAT 模块查询 FDW 数据（如为 referenceCard 获取客户指标）时，THE Backend SHALL 遵循 DWD-DOC 强制规则：金额使用 `items_sum` 口径，会员信息通过 `member_id` JOIN `dim_member` 获取
