# 实现计划：RNS1.0 基础设施与契约重写

## 概述

按依赖关系排序实现 6 个主任务：T0-1（全局中间件）和 T0-2（CamelModel）为基础设施层，必须先行；T0-3（路由修正）和 T0-4（前端解包）为适配层；T0-5（契约重写）为文档层；T0-6（参数修复）为前端修复层。属性测试使用 Hypothesis，单元测试使用 pytest。

## 任务

- [x] 1. 全局响应包装中间件 + 异常处理器（T0-1）
  - [x] 1.1 实现 ResponseWrapperMiddleware ASGI 中间件
    - 创建 `apps/backend/app/middleware/response_wrapper.py`
    - 实现 ASGI 中间件类，拦截 `http.response.start` 和 `http.response.body`
    - JSON 成功响应（2xx + application/json）包装为 `{ "code": 0, "data": <原始body> }`
    - 跳过条件：`text/event-stream`（SSE）、非 `application/json`、非 2xx 状态码
    - _需求: 1.1, 1.5, 1.6_

  - [x] 1.2 实现异常处理器函数
    - 在同一文件中实现 `http_exception_handler` 和 `unhandled_exception_handler`
    - HTTPException → `{ "code": <status_code>, "message": <detail> }`
    - 未捕获异常 → `{ "code": 500, "message": "Internal Server Error" }` + `logger.exception` 写入完整堆栈
    - _需求: 1.2, 1.3_

  - [x] 1.3 在 main.py 中注册中间件和异常处理器
    - `app.add_exception_handler(HTTPException, http_exception_handler)`
    - `app.add_exception_handler(Exception, unhandled_exception_handler)`
    - `app.add_middleware(ResponseWrapperMiddleware)`（在 CORS 之后添加）
    - 验证现有 16 个端点（Auth/Tasks/Notes/AI Chat）在包装后行为正常
    - _需求: 1.4_

  - [x] 1.4 编写属性测试：响应包装-解包 Round Trip
    - **Property 1: 响应包装-解包 Round Trip**
    - 测试文件：`tests/test_rns1_properties.py`
    - 生成器：`st.from_type(dict|list|str|int|float|bool|None)` 生成随机 JSON 可序列化数据
    - 验证：包装后 JSON 包含 `code=0` 和 `data`；解包 `data` 等于原始输入
    - **验证需求: 1.1, 4.1**

  - [x] 1.5 编写属性测试：异常响应保持 code 和 message
    - **Property 2: 异常响应保持 code 和 message**
    - 测试文件：`tests/test_rns1_properties.py`
    - 生成器：`st.integers(min_value=400, max_value=599)` × `st.text(min_size=1)`
    - 验证：输出 JSON 的 `code` 等于输入状态码，`message` 等于输入 detail
    - **验证需求: 1.2, 1.3**

  - [x] 1.6 编写属性测试：非 JSON 响应透传
    - **Property 3: 非 JSON 响应透传**
    - 测试文件：`tests/test_rns1_properties.py`
    - 生成器：`st.sampled_from(["text/event-stream", "application/octet-stream", "text/html"])` × `st.binary()`
    - 验证：中间件输出的响应体与输入完全相同
    - **验证需求: 1.5, 1.6**

  - [x] 1.7 编写单元测试：中间件跳过逻辑
    - 测试文件：`apps/backend/tests/unit/test_response_wrapper.py`
    - 用例：SSE 端点不包装；health 端点包装；非 2xx 不二次包装；中间件自身异常时透传原始响应
    - _需求: 1.5, 1.6_

- [x] 2. Pydantic Schema 统一 camelCase（T0-2）
  - [x] 2.1 创建 CamelModel 基类
    - 创建 `apps/backend/app/schemas/base.py`
    - 实现 `CamelModel(BaseModel)` 配置 `alias_generator=to_camel`、`populate_by_name=True`、`from_attributes=True`
    - _需求: 2.1_

  - [x] 2.2 迁移现有 schema 到 CamelModel
    - `schemas/xcx_tasks.py`：`TaskListItem`、`AbandonRequest` 等所有类的 `BaseModel` → `CamelModel`
    - `schemas/xcx_auth.py`：所有 Auth schema 迁移
    - `schemas/xcx_notes.py`：所有 Notes schema 迁移
    - 确认路由函数 `response_model` 无需修改（Pydantic 自动使用 alias 序列化）
    - _需求: 2.2, 2.3_

  - [x] 2.3 编写属性测试：camelCase 转换 Round Trip
    - **Property 4: camelCase 转换 Round Trip**
    - 测试文件：`tests/test_rns1_properties.py`
    - 生成器：`st.fixed_dictionaries` 生成随机 snake_case 字段名和值
    - 验证：`model_dump(by_alias=True)` → `Model(**camel_dict)` → 等于原始实例
    - **验证需求: 2.2, 2.4**

  - [x] 2.4 编写单元测试：CamelModel 基类
    - 测试文件：`apps/backend/tests/unit/test_camel_model.py`
    - 用例：`TaskListItem` 序列化输出 camelCase 字段名；反序列化同时接受 snake_case 和 camelCase
    - _需求: 2.2, 2.4_

- [x] 3. 检查点 — 基础设施层验证
  - 确保所有测试通过，ask the user if questions arise.
  - 验证现有 16 个端点在中间件 + CamelModel 迁移后行为正常
  - 确认 JSON 响应格式为 `{ code: 0, data: { camelCaseFields... } }`

- [x] 4. 后端路由路径修正（T0-3）
  - [x] 4.1 修改路由路径和函数名
    - 文件：`apps/backend/app/routers/xcx_tasks.py`
    - `@router.post("/{task_id}/cancel-abandon")` → `@router.post("/{task_id}/restore")`
    - 函数名 `cancel_abandon` → `restore_task`（业务逻辑不变）
    - 移除旧路径，不保留兼容映射
    - _需求: 3.1, 3.2, 3.3_

  - [x] 4.2 编写单元测试：路由路径修正
    - 测试文件：`apps/backend/tests/unit/test_xcx_tasks_route.py`
    - 用例：`POST /restore` 返回 200；`POST /cancel-abandon` 返回 404/405
    - _需求: 3.1, 3.3_

- [x] 5. 前端 request() 解包适配（T0-4）
  - [x] 5.1 修改 request() 函数添加 .data 解包逻辑
    - 文件：`apps/miniprogram/miniprogram/utils/request.ts`（或 `services/api.ts`，视实际位置）
    - `code === 0` → 返回 `res.data.data`（业务数据）
    - `code !== 0` → 抛出 `{ code, message }` 错误对象
    - 无 `code` 字段 → 直接返回原始响应体（SSE 等非标准格式）
    - 确保所有现有 API 调用（Auth/Tasks/Notes/AI Chat）解包后行为不变
    - _需求: 4.1, 4.2, 4.3, 4.4_

  - [x] 5.2 编写属性测试：错误码解包抛出
    - **Property 5: 错误码解包抛出**
    - 测试文件：`tests/test_rns1_properties.py`
    - 生成器：`st.integers().filter(lambda x: x != 0)` × `st.text()`
    - 验证：解包函数对 `{ code: n, message: m }` 抛出异常，异常包含 code 和 message
    - **验证需求: 4.2**

  - [x] 5.3 编写属性测试：非标准格式响应透传
    - **Property 6: 非标准格式响应透传**
    - 测试文件：`tests/test_rns1_properties.py`
    - 生成器：`st.dictionaries(st.text(), st.text()).filter(lambda d: "code" not in d)`
    - 验证：解包函数直接返回原始 dict
    - **验证需求: 4.3**

- [x] 6. 检查点 — 适配层验证
  - 确保所有测试通过，ask the user if questions arise.
  - 验证后端路由 `/restore` 可正常调用
  - 验证前端 request() 解包逻辑对成功/错误/非标准格式三种场景均正确

- [x] 7. API 契约完全重写（T0-5）
  - [x] 7.1 重写 BOARD-3 财务看板契约
    - 文件：`docs/miniprogram-dev/API-contract.md`
    - 定义 6 板块嵌套结构：overview / recharge / revenue / cashflow / expense / coachAnalysis
    - 定义请求参数枚举：time（8 种）、area（7 种）、compare（0/1）
    - 标注 `area≠all` 时 recharge 不返回；标注 `items_sum` 口径和助教费用拆分规则
    - 每个指标附带环比字段（`xxxCompare` + `isDown` + `isFlat`）
    - _需求: 5.1.1 ~ 5.1.10, 7.1 ~ 7.5_

  - [x] 7.2 重写 BOARD-1 助教看板契约
    - 定义基础字段 + 4 维度专属字段（perf/salary/sv/task）
    - `skills` 类型为 `Array<{ text, cls }>`，`topCustomers` 类型为 `string[]`
    - 定义请求参数枚举：sort（6 种）、skill（5 种）、time（6 种）
    - 标注交叉约束：`time=last_6m` 与 `sort=sv_desc` 不兼容 → HTTP 400
    - _需求: 5.2.1 ~ 5.2.8, 7.1, 7.6_

  - [x] 7.3 重写 BOARD-2 客户看板契约
    - 定义基础字段 + 8 维度专属字段（recall/potential/balance/recharge/recent/spend60/freq60/loyal）
    - `assistants` 含 heartScore/badge 等字段
    - 定义请求参数：dimension（8 种）、project（5 种）、page/pageSize（分页）
    - _需求: 5.3.1 ~ 5.3.11, 7.1_

  - [x] 7.4 重写 CUST-1 客户详情契约
    - 补充 Banner 字段：balance/consumption60d/idealInterval/daysSinceVisit
    - 补充 aiInsight / coachTasks / favoriteCoaches / notes 模块
    - 消费记录重写为嵌套结构（含 coaches 子数组、tableFee/foodAmount 分项）
    - 标注会员信息 JOIN 规则（DQ-6）和 `items_sum` 口径
    - _需求: 5.4.1 ~ 5.4.7, 7.1 ~ 7.4_

  - [x] 7.5 重写 COACH-1 助教详情契约
    - 补充 performance（6 指标）/ income（本月/上月各 4 项）/ tierNodes / historyMonths
    - 扩展 topCustomers（heartEmoji/score/balance）
    - 补充近期服务明细 perfHours/customerId
    - 任务分组补充 noteCount/pinned/notes/reason
    - 补充 notes 模块
    - _需求: 5.5.1 ~ 5.5.9, 7.1 ~ 7.4_

  - [x] 7.6 重写 PERF-1 绩效概览契约
    - thisMonthRecords 改为 DateGroup 分组结构
    - 补充收入档位数据：currentTier/nextTier/upgradeHoursNeeded/upgradeBonus
    - 补充 lastMonthIncome、incomeItems.desc
    - 补充 newCustomers 和 regularCustomers 扩展字段
    - _需求: 5.6.1 ~ 5.6.5, 7.1_

  - [x] 7.7 重写 TASK-1 绩效概览字段契约
    - performance 从 4 字段扩展为 15+ 字段
    - 补充 tierNodes/basicHours/bonusHours/currentTier/nextTierHours/tierCompleted/bonusMoney/incomeTrend/incomeTrendDir/prevMonth
    - 任务 item 补充 lastVisitDays/balance/aiSuggestion
    - _需求: 5.7.1, 5.7.2_

  - [x] 7.8 重写 CHAT-1/2 对话模块契约
    - 统一时间字段名为 `createdAt`
    - CHAT-1 补充 `title` 字段
    - CHAT-2 补充 `referenceCard` 可选字段
    - 补充 SSE 流式端点定义：`POST /api/xcx/chat/stream`
    - 标注 `customerId` 查询参数支持
    - _需求: 5.8.1 ~ 5.8.5_

  - [x] 7.9 契约一致性审查
    - 确保所有字段名统一 camelCase
    - 确保所有金额字段标注 `items_sum` 口径
    - 确保所有助教费用标注 `assistant_pd_money` + `assistant_cx_money` 拆分
    - 确保所有会员信息标注 `member_id` JOIN `dim_member` 规则
    - 每个接口标注数据源（FDW 表名或业务表名）
    - 对照前端 mock 数据结构逐字段比对，确保无遗漏或类型不匹配
    - _需求: 7.1 ~ 7.6_

- [x] 8. 检查点 — 契约文档验证
  - 确保所有测试通过，ask the user if questions arise.
  - 确认 8 个接口契约定义完整、字段名 camelCase、金额口径标注正确
  - 确认契约文档可作为后续子 spec（RNS1.1-1.4）的唯一实现基准

- [x] 9. 前端跨页面参数修复（T0-6）
  - [x] 9.1 修复 task-detail 页面跳转参数
    - 文件：`apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts`
    - 跳转 chat 时传 `customerId={detail.customerId}`（而非 `detail.id`）
    - 跳转 customer-service-records 时传 `customerId={detail.customerId}`（而非 `detail.id`）
    - _需求: 6.1.1, 6.1.2_

  - [x] 9.2 修复 customer-detail 页面跳转参数和 ID 获取方式
    - 文件：`apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.ts`
    - 跳转 customer-service-records 时传 `customerId={detail.id}`
    - 跳转 chat 时传 `customerId={detail.id}`
    - `loadDetail()` 从 `onLoad(options)` 的 `options.id` 获取客户 ID，替代 `__route__` 解析
    - _需求: 6.2.1, 6.2.2, 6.2.3_

  - [x] 9.3 修复 coach-detail 页面跳转参数
    - 文件：`apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts`
    - 任务项跳转 customer-detail 时传 `id={customerId}`（而非 `name={customerName}`）
    - 依赖 COACH-1 响应中 TaskItem 包含 `customerId` 字段
    - _需求: 6.3.1_

  - [x] 9.4 修复 performance 页面跳转参数
    - 文件：`apps/miniprogram/miniprogram/pages/performance/performance.ts`
    - 跳转 task-detail 时传 `id={taskId}`（而非 `customerName={name}`）
    - 依赖 PERF-1 响应中记录包含 `taskId` 字段
    - _需求: 6.4.1_

  - [x] 9.5 实现 chat 页面多入口参数路由
    - 文件：`apps/miniprogram/miniprogram/pages/chat/chat.ts`
    - 支持 `customerId` 入口：使用 `customerId` 查询参数调用 CHAT 端点
    - 支持 `historyId` 入口：使用 `historyId` 作为 `chatId` 加载历史消息
    - 支持 `coachId` 入口：使用 `coachId` 作为上下文参数
    - _需求: 6.5.1, 6.5.2, 6.5.3_

  - [x] 9.6 扩展 globalData.authUser 字段
    - 文件：`apps/miniprogram/miniprogram/app.ts`
    - 登录成功后将 `role`/`storeName`/`coachLevel`/`avatar` 存入 `globalData.authUser`
    - 各页面 Banner 直接从 `globalData.authUser` 读取，不再单独请求 `/me`
    - _需求: 6.6.1, 6.6.2_

- [x] 10. 最终检查点 — 全量验证
  - 确保所有测试通过，ask the user if questions arise.
  - 验证后端：中间件包装 + CamelModel + 路由修正 + 异常处理器全部生效
  - 验证前端：request() 解包 + 8 个跳转场景参数正确 + globalData 字段完整
  - 验证契约：8 个接口定义完整，可作为 RNS1.1-1.4 实现基准

## 备注

- 标记 `*` 的子任务为可选测试任务，可跳过以加速 MVP
- 每个任务引用具体需求编号，确保可追溯
- 属性测试使用 Hypothesis（`tests/test_rns1_properties.py`），单元测试使用 pytest（`apps/backend/tests/unit/`）
- 检查点确保增量验证，避免问题累积
- T0-5 契约重写为文档任务，产出物为 `docs/miniprogram-dev/API-contract.md`
- T0-6 前端参数修复中部分跳转依赖后续子 spec 的 API 响应字段（如 COACH-1 的 `customerId`、PERF-1 的 `taskId`），当前先修改跳转代码，待后端实现后联调验证
