# 实施计划：RNS1.4 CHAT 对齐与联调收尾

## 概述

按照设计文档的 8 个组件，将实施拆分为：DDL 迁移 → 后端 Schema/Service/Router → FDW 验证 → 前端改造 → 联调收尾。每个任务增量构建，确保无孤立代码。属性测试（Hypothesis）和单元测试作为可选子任务紧跟实现步骤。

## 任务

- [x] 1. DDL 迁移：扩展 ai_conversations 和 ai_messages 表
  - [x] 1.1 创建 DDL 迁移脚本 `db/zqyy_app/migrations/2026-03-20__rns14_chat_module_extend.sql`
    - ALTER TABLE `biz.ai_conversations` 新增 `context_type varchar(20)`、`context_id varchar(50)`、`title varchar(200)`、`last_message text`、`last_message_at timestamptz` 五个字段
    - ALTER TABLE `biz.ai_messages` 新增 `reference_card` jsonb 字段
    - 创建索引 `idx_ai_conv_context` ON `(user_id, site_id, context_type, context_id, last_message_at DESC NULLS LAST) WHERE context_type IS NOT NULL`
    - 创建排序索引 `idx_ai_conv_last_msg` ON `(user_id, site_id, last_message_at DESC NULLS LAST)`
    - 添加 COMMENT ON COLUMN 注释
    - _需求: R2.3, R3.8, R3.10, R4.3, R7.5_

- [x] 2. 后端 Pydantic Schema 定义（组件 3）
  - [x] 2.1 创建 `apps/backend/app/schemas/xcx_chat.py`
    - 继承 `CamelModel` 基类，定义 `ChatHistoryItem`、`ChatHistoryResponse`、`ReferenceCard`、`ChatMessageItem`、`ChatMessagesResponse`、`SendMessageRequest`、`SendMessageResponse`、`MessageBrief`、`ChatStreamRequest`
    - 字段类型和可选性严格遵循设计文档组件 3 定义
    - _需求: R2.2, R3.3, R4.1, R5.1, R5.3, R6.1, R11.4_
  - [x] 2.2 编写 Schema 序列化单元测试 `apps/backend/tests/unit/test_xcx_chat_schema.py`
    - 验证 ChatHistoryItem / ChatMessageItem / SendMessageResponse 的 camelCase 序列化输出
    - 验证 ReferenceCard 可选字段为 None 时不报错
    - _需求: R2.2, R3.3, R5.3, R11.4_

- [x] 3. 后端 chat_service 业务逻辑层（组件 2）
  - [x] 3.1 创建 `apps/backend/app/services/chat_service.py`
    - 实现 `ChatService` 类，包含 `get_chat_history`、`get_or_create_session`、`get_messages`、`send_message_sync`、`build_reference_card`、`generate_title` 方法
    - `get_chat_history`：查询 `biz.ai_conversations`，按 `last_message_at` 倒序，JOIN `v_dim_member` 获取 `customerName`，分页返回
    - `get_or_create_session`：按 `(user_id, site_id, context_type, context_id)` 查找或创建对话。复用规则：task 入口始终复用（无时限）；customer/coach 入口 ≤ 3 天复用、> 3 天新建；general 入口始终新建
    - `get_messages`：查询 `biz.ai_messages`，按 `created_at` 正序，验证 chatId 归属当前用户
    - `send_message_sync`：存入用户消息 → 调用 AI → 存入 AI 回复 → 更新 session 元数据；AI 失败时返回错误提示消息（HTTP 200）
    - ⚠️ **P5 PRD 合规**：对话落库必须遵循 `docs/prd/specs/P5-miniapp-ai-integration.md` 数据写入规则：
      - `app_id` 固定为 `app1_chat`
      - 用户消息发送时即写入 `ai_messages`（role=user）
      - 流式完成后完整 assistant 回复写入 `ai_messages`（role=assistant），含 `tokens_used`
      - 首条消息为页面上下文 JSON（`current_time`/`source_page`/`page_context`/`screen_content`）
      - `get_or_create_session` 仅用于 task/customer/coach 入口的对话复用（task 无时限，customer/coach 3 天时限）；general 入口始终新建（保持 P5 PRD 兼容）
    - `build_reference_card`：从 FDW 查询客户指标（`items_sum` 口径），组装 referenceCard JSON
    - `generate_title`：自定义标题 > 客户姓名 > 首条消息前 20 字
    - _需求: R2.1-R2.6, R3.1-R3.10, R4.1-R4.3, R5.1-R5.6, R11.2, R11.6_

  - [x] 3.2 编写属性测试：标题生成优先级
    - **Property 4: 对话标题生成优先级**
    - 使用 Hypothesis `st.fixed_dictionaries` 生成随机 title/customer_name/first_message 组合
    - 验证：有 title 用 title，否则用 customer_name，否则用首条消息前 20 字，结果始终非空
    - **验证: 需求 R2.4**

  - [x] 3.3 编写属性测试：对话复用规则正确性
    - **Property 6: 对话复用规则正确性**
    - 使用 Hypothesis 生成随机 context_type/context_id/last_message_at 组合
    - 验证：task 入口同一 context_id 始终返回同一 chatId；customer/coach 入口 ≤ 3 天复用、> 3 天新建；general 入口每次返回不同 chatId
    - **验证: 需求 R3.8, R3.9, R3.10**

  - [x] 3.4 编写属性测试：referenceCard Round Trip
    - **Property 7: referenceCard 持久化 Round Trip**
    - 使用 Hypothesis 生成随机 referenceCard JSON（type/title/summary/data）
    - 验证：JSON 序列化→存储→读取→反序列化等于原始对象
    - **验证: 需求 R4.1, R4.3**

  - [x] 3.5 编写属性测试：消息持久化与会话元数据更新
    - **Property 8: 消息持久化与会话元数据更新**
    - 使用 Hypothesis `st.text(min_size=1, max_size=500)` 生成随机消息内容
    - 验证：发送后 ai_messages 包含用户消息和 AI 回复，session 的 last_message 和 last_message_at 已更新
    - **验证: 需求 R5.2, R5.4, R6.3, R6.4**

  - [x] 3.6 编写单元测试：AI 失败降级
    - 测试文件 `apps/backend/tests/unit/test_xcx_chat_ai_fallback.py`
    - 验证 AI 服务超时/异常时，用户消息仍保存，AI 回复为错误提示消息，HTTP 200
    - _需求: R5.5_

- [x] 4. 后端路由迁移与 CHAT-1/2/3/4 端点实现（组件 1）
  - [x] 4.1 创建 `apps/backend/app/routers/xcx_chat.py`，实现 CHAT-1/2/3/4 五个端点
    - `GET /history` — CHAT-1 对话历史列表，调用 `chat_service.get_chat_history`
    - `GET /{chat_id}/messages` — CHAT-2a 通过 chatId 查询消息
    - `GET /messages?contextType=&contextId=` — CHAT-2b 通过上下文查询消息（按复用规则自动查找/创建对话）
    - `POST /{chat_id}/messages` — CHAT-3 发送消息（同步回复）
    - `POST /stream` — CHAT-4 SSE 流式端点，返回 `StreamingResponse(media_type="text/event-stream")`
    - 所有端点使用 `Depends(require_approved())` 权限检查
    - chatId 归属验证：CHAT-3/4 不属于当前用户返回 HTTP 403
    - _需求: R1.1, R1.3, R2.1, R3.1, R5.1, R6.1, R11.1, R11.2_

  - [x] 4.2 在 `apps/backend/app/main.py` 中注册 `xcx_chat.router`，移除 `xcx_ai_chat.router`
    - 删除 `xcx_ai_chat.py` 文件（不保留旧路径兼容）
    - _需求: R1.2, R1.3_

  - [x] 4.3 编写属性测试：SSE 事件类型有效性
    - **Property 9: SSE 事件类型有效性**
    - 使用 Hypothesis `st.sampled_from(["message", "done", "error"])` + 对应 data 结构
    - 验证：事件类型为三者之一，data 结构符合定义（message→token, done→messageId+createdAt, error→message）
    - **验证: 需求 R6.2**

  - [x] 4.4 编写属性测试：列表排序不变量
    - **Property 3: 列表排序不变量**
    - 使用 Hypothesis `st.lists(st.datetimes())` 生成随机时间戳列表
    - 验证：CHAT-1 对话列表按时间倒序，CHAT-2 消息列表按时间正序
    - **验证: 需求 R2.3, R3.5**

  - [x] 4.5 编写单元测试：路由迁移与权限控制
    - 测试文件 `apps/backend/tests/unit/test_xcx_chat_routes.py`
    - 验证 `/api/xcx/chat/history` 返回 200（需认证）；`/api/ai/conversations` 返回 404
    - 验证未审核用户收到 403；chatId 不属于当前用户收到 403
    - **Property 1: 路由迁移完整性** / **Property 5: 权限控制与数据隔离**
    - **验证: 需求 R1.1, R1.2, R5.6, R6.6, R11.1, R11.3**

- [x] 5. 检查点 — 后端实现验证
  - 确保所有后端测试通过，ask the user if questions arise.

- [x] 6. FDW 端到端验证脚本（组件 7）
  - [x] 6.1 创建 `scripts/ops/verify_fdw_e2e.py`
    - 验证所有 `fdw_etl.*` 视图在 `test_zqyy_app` 中可访问（SELECT 1 FROM ... LIMIT 1）
    - 验证带典型过滤条件（assistant_id、member_id、日期范围）的查询响应时间 < 3s
    - 检查关键索引存在：`chat_sessions(assistant_id, customer_id)`、`chat_messages(session_id, created_at)`
    - 输出结构化 JSON 报告，失败项标注需 DBA 介入
    - 使用 `load_dotenv` 加载根 `.env`，连接 `test_zqyy_app`（遵循 testing-env.md 规范）
    - _需求: R7.1, R7.2, R7.3, R7.4, R7.5_

- [x] 7. 前端 services/api.ts CHAT 模块对接（组件 6）
  - [x] 7.1 修改 `apps/miniprogram/miniprogram/services/api.ts`
    - `fetchChatHistory()`：调用 `GET /api/xcx/chat/history`
    - `fetchChatMessages(chatId)`：调用 `GET /api/xcx/chat/{chatId}/messages`
    - 新增 `fetchChatMessagesByContext(contextType, contextId)`：调用 `GET /api/xcx/chat/messages?contextType={type}&contextId={id}`
    - `sendChatMessage(chatId, content)`：调用 `POST /api/xcx/chat/{chatId}/messages`
    - 移除所有 CHAT 相关 mock 数据导入，`USE_REAL_API` 对 CHAT 模块设为 `true`
    - _需求: R1.4, R10.1_

- [x] 8. 前端 chat 页面改造（组件 4）
  - [x] 8.1 修改 `apps/miniprogram/miniprogram/pages/chat/chat.ts` 实现多入口参数路由
    - `onLoad(options)` 中按优先级处理：`historyId` → `taskId` → `customerId` → `coachId` → 无参数（通用对话）
    - `historyId` 入口：直接用作 chatId 加载历史消息
    - `taskId` 入口：调用 `fetchChatMessagesByContext('task', taskId)`，同一 taskId 始终复用同一对话（无时限）
    - `customerId` 入口：调用 `fetchChatMessagesByContext('customer', customerId)`，≤ 3 天复用、> 3 天新建
    - `coachId` 入口：调用 `fetchChatMessagesByContext('coach', coachId)`，≤ 3 天复用、> 3 天新建
    - 无参数入口：调用 `fetchChatMessagesByContext('general', '')`，始终新建
    - _需求: R4.5, R4.6, R4.7_

  - [x] 8.2 修改 chat.ts 将 `simulateStreamOutput()` 替换为真实 SSE 连接
    - 使用 `wx.request` + `enableChunked: true` 接收 `POST /api/xcx/chat/stream` 的 SSE 响应
    - 解析 `event: message`（逐 token 追加）、`event: done`（流结束）、`event: error`（错误处理）
    - 移除 `simulateStreamOutput()` 和 `mockAIReplies` 相关代码
    - SSE 连接中断时显示"连接中断"提示，允许重试
    - _需求: R6.7, R10.1_

  - [x] 8.3 确保 chat 页面 referenceCard 渲染与真实 API 数据兼容
    - 验证 `toDataList()` 和 WXML 模板能正确渲染后端返回的 referenceCard 结构
    - _需求: R4.4_

- [x] 9. 前端 chat-history 页面改造（组件 5）
  - [x] 9.1 修改 `apps/miniprogram/miniprogram/pages/chat-history/chat-history.ts`
    - 移除 `mockChatHistory` 导入，调用 `fetchChatHistory()` 获取真实数据
    - 响应字段映射：后端 `timestamp`（ISO 8601）→ `formatRelativeTime()` 处理
    - 点击对话项跳转 chat 页面时传递 `historyId` 参数
    - _需求: R2.1, R10.1, R10.2_

- [x] 10. 前端联调修复（组件 8）
  - [x] 10.1 修改 notes 页面实现触底加载
    - 在 `apps/miniprogram/miniprogram/pages/notes/notes.ts` 实现 `onReachBottom()` 生命周期函数
    - 维护 `page` 状态，触底时 `page++` 调用 `fetchNotes({ page, pageSize })`
    - 追加数据到已有列表，`hasMore === false` 时停止加载并显示"没有更多了"
    - 加载过程中显示加载状态指示器，防止重复触发
    - _需求: R8.1, R8.2, R8.3, R8.4_

  - [x] 10.2 修改 customer-service-records 页面实现按月请求
    - 在 `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts` 修改月份切换逻辑
    - 月份切换时调用 `fetchCustomerRecords({ customerId, year, month })`，清空列表 → loading → 渲染
    - 首次加载默认当前月份数据
    - _需求: R9.1, R9.2, R9.3, R9.4_

- [x] 11. 检查点 — 前端改造验证
  - 确保所有前端改造完成，chat 页面 4 个入口（task-detail、customer-detail、coach-detail、chat-history）均能正确进入并加载对话。ask the user if questions arise.

- [x] 12. Mock 数据移除与全量联调
  - [x] 12.1 移除所有页面中的 mock 数据残留
    - 搜索并移除所有 `import { mockXxx } from '../../utils/mock-data'` 引用
    - 确保 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）均使用真实 API
    - _需求: R10.1, R10.2_

  - [x] 12.2 验证全量联调
    - 确保所有页面 API 调用失败时显示友好错误提示（Toast 或空状态占位），不出现白屏
    - 验证页面间跳转参数传递正确（RNS1.0 T0-6 已修复的跨页面参数）
    - 验证 chat 页面从 4 个入口进入时均能正确关联上下文
    - _需求: R10.2, R10.3, R10.4, R10.5_

- [x] 13. 全链路端到端测试（真实 AI 接口）
  - [x] 13.1 后端全链路测试：使用真实百炼 API 验证 CHAT-3（同步）和 CHAT-4（SSE 流式）
    - 启动后端服务连接 `test_zqyy_app`，使用真实测试用户 token
    - 调用 `POST /api/xcx/chat/{chatId}/messages` 发送真实消息，验证：
      - AI 回复内容质量（非乱码、语义相关、中文正常）
      - 用户消息和 AI 回复均已持久化到 `biz.ai_messages`
      - `biz.ai_conversations` 的 `last_message` 和 `last_message_at` 已更新
      - `tokens_used` 字段已记录
    - 调用 `POST /api/xcx/chat/stream` 验证 SSE 流式返回：
      - 逐 token 事件格式正确（`event: message`、`event: done`）
      - 完整回复拼接后语义通顺
      - 流结束后消息已落库
    - 手动评估 AI 返回内容质量（至少 3 轮对话），记录评估结果
    - _需求: R5.2, R5.4, R6.2, R6.3, R6.4, AC1, AC9_

  - [x] 13.2 前端→后端→AI→数据库全链路验证
    - 在微信开发者工具中启动小程序，连接本地后端
    - 从 4 个入口（task-detail、customer-detail、coach-detail、chat-history）进入 chat 页面
    - 每个入口发送至少 1 条消息，验证：
      - SSE 流式逐字显示正常
      - 消息发送后页面状态正确（loading → 显示回复）
      - 返回 chat-history 页面能看到刚才的对话记录
      - referenceCard 在有客户关联时正确渲染（如有数据）
    - 验证错误场景：网络断开时的提示、空消息拦截
    - _需求: R6.7, R10.5, R4.4_

  - [x] 13.3 AI 对话落库合规性验证（对照 P5 PRD + 用户确认的复用规则）
    - 验证对话复用规则：task 入口同一 taskId 始终复用；customer/coach 入口 ≤ 3 天复用、> 3 天新建；general 入口始终新建
    - 验证首条消息格式：应用 1 的首条消息应为页面上下文 JSON（`source_page`/`page_context`/`screen_content`/`current_time`），对照 P5 PRD 应用 1 Prompt 数据结构
    - 验证 `app_id` 字段：CHAT 模块对话的 `app_id` 应为 `app1_chat`
    - 验证 `ai_messages.role` 值：仅 `user`/`assistant`/`system` 三种（CHECK 约束）
    - 验证 `tokens_used` 记录：AI 回复消息应记录 token 消耗量
    - 验证 `source_page` 和 `source_context`：从不同入口进入时应正确记录来源页面和上下文
    - 验证 `context_type` 和 `context_id`：不同入口写入正确的上下文类型和 ID
    - _需求: AC9, P5-PRD 数据写入规则_

- [x] 14. DDL 迁移合并到主 DDL 基线
  - [x] 14.1 执行迁移脚本到 `test_zqyy_app`
    - 运行 `db/zqyy_app/migrations/2026-03-20__rns14_chat_module_extend.sql`（任务 1 创建的脚本）
    - 验证新字段和索引已正确创建（使用 BD 手册中的验证 SQL）
    - _需求: R2.3, R3.8, R3.10, R4.3, R7.5_

  - [x] 14.2 合并到主 DDL 基线 `docs/database/ddl/zqyy_app__biz.sql`
    - 在 `biz.ai_conversations` 表定义中追加 5 个新字段：`context_type varchar(20)`、`context_id varchar(50)`、`title varchar(200)`、`last_message text`、`last_message_at timestamptz`
    - 在 `biz.ai_messages` 表定义中追加 1 个新字段：`reference_card jsonb`
    - 在索引区追加 2 个新索引：`idx_ai_conv_context`（条件索引，WHERE context_type IS NOT NULL）、`idx_ai_conv_last_msg`
    - 更新文件头部的"生成日期"注释
    - _需求: DDL 基线同步_

- [x] 15. 文档更新落地
  - [x] 15.1 更新 BD 手册 `docs/database/BD_Manual_ai_tables.md`
    - 在 `biz.ai_conversations` 字段明细中追加 5 个新字段（context_type/context_id/title/last_message/last_message_at）
    - 在 `biz.ai_messages` 字段明细中追加 reference_card 字段
    - 在约束与索引表中追加 2 个新索引（idx_ai_conv_context、idx_ai_conv_last_msg）
    - 更新兼容性影响：标注 RNS1.4 CHAT 模块依赖新字段
    - 更新验证 SQL：追加新字段和索引的验证查询
    - 更新回滚策略：追加 DROP INDEX 和 ALTER TABLE DROP COLUMN
    - _规范: db-docs.md 强制要求_

  - [x] 15.2 更新 API 契约 `docs/miniprogram-dev/API-contract.md`
    - CHAT 部分路径从 `/api/ai/*` 更新为 `/api/xcx/chat/*`
    - 补充 CHAT-1（历史列表）、CHAT-2a/2b（消息查询，含 customerId 参数）、CHAT-3（发送消息）端点定义
    - 补充 referenceCard 结构定义
    - 补充 SSE 事件类型定义（message/done/error）
    - _需求: R1.1, R1.4_

  - [x] 15.3 更新后端 API 参考 `apps/backend/docs/API-REFERENCE.md`
    - 新增 `xcx_chat` 路由模块文档（5 个端点）
    - 移除 `xcx_ai_chat` 路由模块文档（已删除）
    - _需求: R1.2, R1.3_

  - [x] 15.4 更新后端 README `apps/backend/README.md`
    - 路由模块摘要中：移除 `xcx_ai_chat`，新增 `xcx_chat`（CHAT-1/2/3/4）
    - 服务层中：新增 `chat_service.py` 说明
    - _需求: R1.3_

  - [x] 15.5 更新文档地图 `docs/DOCUMENTATION-MAP.md`
    - 在 3.1 FastAPI 后端部分新增 RNS1.4 模块（xcx_chat.py、chat_service.py、xcx_chat schema）
    - 在 5.6 Spec 文件表中新增 `rns1-chat-integration` 条目
    - 更新"最后更新"日期
    - _规范: doc-map.md 归档规则_

  - [x] 15.6 更新 RNS1 拆分计划 `docs/prd/Neo_Specs/RNS1-split-plan.md`
    - 标注 RNS1.4 状态为"实施中"或"已完成"（视进度）
    - _需求: 项目追踪_

- [x] 16. 最终检查点 — 全量验证
  - 确保所有测试通过，13 个页面均连接真实后端运行，无 mock 数据残留
  - 确保 DDL 迁移已合并到主基线，BD 手册已同步更新
  - 确保 API 契约、后端 README、文档地图均已更新
  - 确保 AI 对话落库符合 P5 PRD 规范
  - ask the user if questions arise.

## 备注

- 标记 `*` 的子任务为可选，可跳过以加速 MVP 交付
- 每个任务引用了具体的需求编号（R1-R11）以确保可追溯性
- 属性测试验证通用正确性属性（Property 1-9），单元测试验证具体边界条件
- 检查点任务确保增量验证，避免问题累积
- 后端使用 Python（FastAPI + Pydantic），前端使用 TypeScript（微信小程序）

### PRD 合规注意事项

- **P5 PRD 数据写入规则**（`docs/prd/specs/P5-miniapp-ai-integration.md`）：
  - 流式返回完成后，完整 assistant 回复写入 `ai_messages`（role=assistant）
  - 用户消息在发送时即写入（role=user）
  - 所有 AI 调用记录写入 `ai_conversations` + `ai_messages`（含 tokens_used 统计）
  - 首条消息为页面上下文 JSON（`source_page`/`page_context`/`screen_content`/`current_time`）
  - `app_id` 固定为 `app1_chat`
- **对话复用规则**（用户已确认，覆盖 P5 PRD 的"始终新建"规则）：
  - `task` 入口：同一 taskId 始终复用同一对话（无时限）
  - `customer` / `coach` 入口：最后消息 ≤ 3 天复用，> 3 天新建
  - `general` 入口（无参数）：始终新建
  - `chat-history` 入口：直接打开已有对话（传 historyId）

### 文档更新清单

| 文档 | 更新内容 | 任务 |
|------|---------|------|
| `docs/database/ddl/zqyy_app__biz.sql` | 合并 5+1 新字段、2 新索引 | 14.2 |
| `docs/database/BD_Manual_ai_tables.md` | 新字段（context_type/context_id/title/last_message/last_message_at/reference_card）/索引/兼容性/回滚/验证 SQL | 15.1 |
| `docs/miniprogram-dev/API-contract.md` | CHAT 路径迁移 + 新端点定义 | 15.2 |
| `apps/backend/docs/API-REFERENCE.md` | xcx_chat 路由模块文档 | 15.3 |
| `apps/backend/README.md` | 路由模块 + 服务层更新 | 15.4 |
| `docs/DOCUMENTATION-MAP.md` | RNS1.4 模块 + spec 条目 | 15.5 |
| `docs/prd/Neo_Specs/RNS1-split-plan.md` | RNS1.4 状态更新 | 15.6 |
