# 需求文档 — RNS1.2：客户与助教接口

## 简介

RNS1.2 是 NS1 小程序后端 API 补全项目的第三个子 spec，负责实现客户详情（CUST-1）、客户服务记录（CUST-2）、助教详情（COACH-1）3 个接口。这三个接口覆盖客户视角和助教视角的详情查看需求，是走查报告中 Gap 最集中的区域（GAP-23~30、GAP-32~35、GAP-38~44），数据结构最为复杂。

### 依赖

- RNS1.0（基础设施与契约重写）已完成：全局响应包装中间件（`ResponseWrapperMiddleware`）、camelCase 转换（`CamelModel`）、重写后的 API 契约
- RNS1.1（任务与绩效接口）可并行开发，无直接依赖
- 后端已有 `fdw_queries.py`（FDW 查询集中封装）、`task_manager.py`、`note_service.py`
- 前端已有 13 个页面（P5.2 交付），当前使用 mock 数据

### 来源文档

- `docs/prd/Neo_Specs/RNS1-split-plan.md` — 拆分计划主文档（RNS1.2 章节）
- `docs/miniprogram-dev/API-contract.md` — API 契约（CUST-1、CUST-2、COACH-1 完整定义）
- `docs/reports/storyboard-walkthrough-assistant-view.md` — 助教视角走查报告（GAP-23~30、GAP-32~35、GAP-38~44）
- `docs/reports/miniprogram-storyboard-walkthrough-gaps.md` — 管理层视角走查报告（G4、G5）
- `docs/reports/DWD-DOC/` — 金额口径与字段语义权威标杆文档
- `docs/architecture/backend-architecture.md` — 后端架构文档

## 术语表

- **Backend**：FastAPI 后端应用，位于 `apps/backend/`
- **Miniprogram**：微信小程序前端应用，位于 `apps/miniprogram/`
- **CUST_1_API**：客户详情接口 `GET /api/xcx/customers/{customerId}`，返回客户完整详情（Banner 概览、AI 洞察、关联助教任务、最亲密助教、消费记录、备注）
- **CUST_2_API**：客户服务记录接口 `GET /api/xcx/customers/{customerId}/records`，返回按月查询的服务记录列表
- **COACH_1_API**：助教详情接口 `GET /api/xcx/coaches/{coachId}`，返回助教完整详情（绩效、收入、档位、TOP 客户、历史月份、任务分组、备注）
- **FDW**：PostgreSQL Foreign Data Wrapper，后端通过直连 ETL 库查询 `app.v_*` RLS 视图
- **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 要求的拆分字段
- **v_dim_member**：ETL RLS 视图，提供会员基本信息（nickname、mobile），通过 `member_id` 关联，取 `scd2_is_current=1`
- **v_dim_member_card_account**：ETL RLS 视图，提供会员卡余额，通过 `tenant_member_id` 关联，取 `scd2_is_current=1`
- **v_dwd_assistant_service_log**：ETL RLS 视图，提供助教服务记录明细（基于 `dwd_assistant_service_log` 基表，废单字段为 `is_delete`）
- **v_dws_member_assistant_relation_index**：ETL RLS 视图，提供会员与助教的关系指数
- **v_dws_member_consumption_summary**：ETL RLS 视图，提供会员消费汇总
- **v_dws_assistant_salary_calc**：ETL RLS 视图，提供助教绩效/档位/收入数据
- **v_dim_assistant**：ETL RLS 视图，提供助教基本信息
- **v_dwd_table_fee_log**：ETL RLS 视图，提供台费明细
- **ai_cache**：业务库 `biz.ai_cache` 表，按 `cache_type` 存储不同类型的 AI 分析缓存
- **coach_tasks**：业务库 `biz.coach_tasks` 表，存储助教任务分配与状态
- **member_retention_clue**：业务库 `public.member_retention_clue` 表，存储维客线索
- **user_assistant_binding**：认证库 `auth.user_assistant_binding` 表，映射小程序用户与助教身份
- **settle_type**：结算类型字段，正向交易取 `IN (1, 3)`
- **is_delete**：RLS 视图中的废单标记字段（整数类型，0=正常），对应 design.md 中的 `is_trash`

## 需求

### 需求 1：实现 CUST-1 客户详情 Banner 概览（T2-1 基础部分）

**用户故事：** 作为管理者或助教，我希望在客户详情页顶部看到客户的基本信息和关键指标（余额、近期消费、到店间隔、距上次到店天数），以便快速评估客户价值和活跃度。

#### 验收标准

1. THE CUST_1_API SHALL 返回客户基础信息字段：`id`（客户唯一 ID）、`name`（客户姓名）、`phone`（脱敏手机号，如 `"139****5678"`）、`phoneFull`（完整手机号）、`avatar`（头像 URL）、`memberLevel`（会员等级）、`relationIndex`（关系指数）、`tags`（客户标签列表）
2. THE CUST_1_API SHALL 通过 `member_id` LEFT JOIN `v_dim_member`（取 `scd2_is_current=1`）获取 `name`（`nickname` 字段）和 `phone`/`phoneFull`（`mobile` 字段），禁止使用 `settlement_head.member_phone`（DQ-6）
3. THE CUST_1_API SHALL 通过 `member_id` LEFT JOIN `v_dim_member_card_account`（`tenant_member_id=member_id`，取 `scd2_is_current=1`）获取 `memberLevel`，禁止使用 `member_card_type_name`（DQ-7）
4. THE CUST_1_API SHALL 返回 Banner 概览字段：`balance`（客户余额，元）、`consumption60d`（近 60 天消费金额，元）、`idealInterval`（理想到店间隔，天）、`daysSinceVisit`（距上次到店天数）
5. THE CUST_1_API SHALL 使用 `items_sum` 口径计算 `balance` 和 `consumption60d`（DWD-DOC 强制规则 1），禁止使用 `consume_money`
6. THE CUST_1_API SHALL 从 `v_dwd_assistant_service_log` 查询客户最后到店日期（`MAX(create_time)`，过滤 `is_delete=0`），计算 `daysSinceVisit`（当前日期与最后到店日期的天数差）
7. IF 某个 Banner 字段的数据源查询失败或无数据，THEN THE CUST_1_API SHALL 对该字段返回 `null`，不影响其他字段和整体响应

### 需求 2：实现 CUST-1 AI 洞察模块（T2-1 AI 部分）

**用户故事：** 作为管理者或助教，我希望在客户详情页看到 AI 生成的客户分析洞察和策略建议，以便制定针对性的服务策略。

#### 验收标准

1. THE CUST_1_API SHALL 返回 `aiInsight` 字段，包含 `summary`（AI 分析摘要文本）和 `strategies`（策略建议列表，每项含 `color` 和 `text`）
2. THE CUST_1_API SHALL 从 `biz.ai_cache` 查询 `cache_type='app4_analysis'` 且 `target_id=customerId` 的缓存记录，解析 `cache_value` JSON 生成 `aiInsight` 数据
3. IF `biz.ai_cache` 中无对应缓存记录，THEN THE CUST_1_API SHALL 返回 `aiInsight: { summary: "", strategies: [] }`

### 需求 3：实现 CUST-1 维客线索与备注（T2-1 线索与备注部分）

**用户故事：** 作为管理者或助教，我希望在客户详情页看到维客线索和历史备注，以便了解客户的留存风险和过往沟通记录。

#### 验收标准

1. THE CUST_1_API SHALL 返回 `retentionClues` 字段（维客线索列表），从 `public.member_retention_clue` 查询，按 `created_at` 倒序排列，格式与 TASK-2 一致
2. THE CUST_1_API SHALL 返回 `notes` 字段（备注列表），从 `biz.notes` 查询 `target_type='member'` 且 `target_id=customerId` 的记录，每项含 `id`、`tagLabel`、`createdAt`、`content`，按 `created_at` 倒序排列，最多返回 20 条

### 需求 4：实现 CUST-1 消费记录嵌套结构（T2-1 消费记录部分）

**用户故事：** 作为管理者或助教，我希望在客户详情页看到消费记录的完整拆分（台费、酒水、助教服务明细），以便分析客户的消费构成和偏好。

#### 验收标准

1. THE CUST_1_API SHALL 返回 `consumptionRecords` 字段（消费记录列表），每条记录包含嵌套结构：`id`、`type`（`table`/`shop`/`recharge`）、`date`、`tableName`、`startTime`、`endTime`、`duration`、`tableFee`、`tableOrigPrice`、`coaches`（助教服务子数组）、`foodAmount`、`foodOrigPrice`、`totalAmount`、`totalOrigPrice`、`payMethod`、`rechargeAmount`
2. THE CUST_1_API SHALL 为每条消费记录的 `coaches` 子数组返回助教服务明细，每项含 `name`、`level`、`levelColor`、`courseType`（`"基础课"` 或 `"激励课"`）、`hours`（服务时长）、`perfHours`（折算工时，可选）、`fee`（服务费用）
3. THE CUST_1_API SHALL 对 `coaches` 子数组中的 `fee` 字段使用 `assistant_pd_money`（基础课）和 `assistant_cx_money`（激励课）拆分（DWD-DOC 强制规则 2），禁止使用 `service_fee`
4. THE CUST_1_API SHALL 对 `totalAmount` 使用 `items_sum` 口径（DWD-DOC 强制规则 1），对 `tableFee` 使用 `table_charge_money`，对 `foodAmount` 使用 `goods_money`
5. THE CUST_1_API SHALL 仅查询正向交易记录（`settle_type IN (1, 3)`）
6. THE CUST_1_API SHALL 使用 `v_dwd_assistant_service_log` 的 `is_delete=0` 排除废单记录

### 需求 5：实现 CUST-1 coachTasks 模块（T2-2）

**用户故事：** 作为管理者或助教，我希望在客户详情页看到所有关联助教的任务信息和近期服务统计，以便了解该客户被哪些助教跟进、服务频率和质量如何。

#### 验收标准

1. THE CUST_1_API SHALL 返回 `coachTasks` 字段（关联助教任务列表），从 `biz.coach_tasks` 查询该客户（`member_id=customerId`）的所有任务记录
2. THE CUST_1_API SHALL 为每位关联助教返回以下字段：`name`（助教姓名）、`level`（助教等级：`star`/`senior`/`middle`/`junior`）、`levelColor`（等级对应颜色）、`taskType`（任务类型，如 `"回访"`、`"召回"`）、`taskColor`（任务类型对应颜色）、`bgClass`（背景样式类）、`status`（任务状态）、`lastService`（最后服务日期）、`metrics`（指标列表）
3. THE CUST_1_API SHALL 为每位助教的 `metrics` 返回近 60 天统计：服务次数、总时长、次均时长，从 `v_dwd_assistant_service_log` 按助教+客户聚合近 60 天数据（过滤 `is_delete=0`）
4. THE CUST_1_API SHALL 从 `v_dws_assistant_salary_calc` 获取助教等级（`assistant_level_name`），从 `v_dim_member` 获取助教姓名（DQ-6）

### 需求 6：实现 CUST-1 favoriteCoaches 模块（T2-3）

**用户故事：** 作为管理者或助教，我希望在客户详情页看到该客户最亲密的助教排名和详细服务统计，以便了解客户的助教偏好和关系深度。

#### 验收标准

1. THE CUST_1_API SHALL 返回 `favoriteCoaches` 字段（最亲密助教列表），从 `v_dws_member_assistant_relation_index` 获取关系指数，按关系指数降序排列
2. THE CUST_1_API SHALL 为每位亲密助教返回：`emoji`（亲密度 emoji）、`name`（助教姓名）、`relationIndex`（关系指数，如 `"0.92"`）、`indexColor`（关系指数对应颜色）、`bgClass`（背景样式类）、`stats`（统计指标列表）
3. THE CUST_1_API SHALL 为 `stats` 返回 4 项指标：基础课时（对应 `assistant_pd_money`）、激励课时（对应 `assistant_cx_money`）、上课次数、充值金额，使用 DWD-DOC 强制规则 2 拆分助教费用
4. THE CUST_1_API SHALL 根据关系指数（`rs_display`，0-10 范围）阈值映射 `emoji`（P6 AC3 四级映射）：`> 8.5` → `"💖"`，`> 7` → `"🧡"`，`> 5` → `"💛"`，`≤ 5` → `"💙"`，复用后端 `compute_heart_icon()` 函数

### 需求 7：实现 CUST-2 客户服务记录（T2-4）

**用户故事：** 作为管理者或助教，我希望按月查看客户的服务记录（替代前端全量加载本地过滤），以便高效浏览大量历史数据并查看月度统计汇总。

#### 验收标准

1. THE CUST_2_API SHALL 接受 `year`、`month`、`table`（可选，台桌筛选）查询参数，返回指定月份的客户服务记录
2. THE CUST_2_API SHALL 返回客户基础信息：`customerName`（客户姓名）、`customerPhone`（脱敏手机号）、`customerPhoneFull`（完整手机号）、`relationIndex`（关系指数）、`tables`（可选台桌列表）
3. THE CUST_2_API SHALL 通过 `member_id` LEFT JOIN `v_dim_member`（取 `scd2_is_current=1`）获取 `customerName` 和 `customerPhone`/`customerPhoneFull`（DQ-6）
4. THE CUST_2_API SHALL 返回 `totalServiceCount`（累计服务总次数，跨所有月份）
5. THE CUST_2_API SHALL 为每条服务记录返回 `recordType`（`course` 或 `recharge`）和 `isEstimate`（是否预估数据，boolean）字段
6. THE CUST_2_API SHALL 返回月度统计汇总：`monthCount`（当月服务次数）和 `monthHours`（当月总工时）
7. THE CUST_2_API SHALL 使用 `items_sum` 口径计算服务记录中的 `income` 字段（DWD-DOC 强制规则 1）
8. THE CUST_2_API SHALL 使用 `is_delete=0` 排除废单记录
9. THE CUST_2_API SHALL 按 `create_time` 倒序排列服务记录，返回 `hasMore` 标记指示是否有更多数据


### 需求 8：实现 COACH-1 助教详情基础信息与绩效（T2-5 基础部分）

**用户故事：** 作为管理者，我希望在助教详情页看到助教的基本信息和 6 项绩效指标，以便快速评估助教的工作表现和产出。

#### 验收标准

1. THE COACH_1_API SHALL 返回助教基础信息字段：`id`、`name`、`avatar`、`level`（初级/中级/高级/星级）、`skills`（技能标签列表）、`workYears`（工龄，年）、`customerCount`（客户数）、`hireDate`（入职日期）
2. THE COACH_1_API SHALL 从 `v_dim_assistant` 获取助教基本信息，从 `v_dws_assistant_salary_calc` 获取等级（`assistant_level_name`）
3. THE COACH_1_API SHALL 返回 `performance` 字段，包含 6 项绩效指标：`monthlyHours`（本月定档工时）、`monthlySalary`（本月工资预估）、`customerBalance`（客源储值余额合计）、`tasksCompleted`（本月任务完成数）、`perfCurrent`（当前绩效值）、`perfTarget`（绩效目标值）
4. THE COACH_1_API SHALL 从 `v_dws_assistant_salary_calc` 查询 `monthlyHours`（`effective_hours`）和 `monthlySalary`（`gross_salary`），使用 `items_sum` 口径（DWD-DOC 强制规则 1）
5. THE COACH_1_API SHALL 从 `biz.coach_tasks` 查询 `tasksCompleted`（当月 `status='completed'` 的任务数）

### 需求 9：实现 COACH-1 收入明细与档位节点（T2-5 收入部分）

**用户故事：** 作为管理者，我希望在助教详情页看到本月和上月的收入明细拆分（基础课时费、激励课时费、充值提成、酒水提成）以及档位进度节点，以便了解助教的收入构成和升档进度。

#### 验收标准

1. THE COACH_1_API SHALL 返回 `income` 字段，包含 `thisMonth` 和 `lastMonth` 两个子数组，各含 4 项收入分类：基础课时费（`assistant_pd_money`）、激励课时费（`assistant_cx_money`）、充值提成、酒水提成
2. THE COACH_1_API SHALL 使用 `assistant_pd_money`（基础课/陪打）和 `assistant_cx_money`（激励课/超休）拆分助教费用（DWD-DOC 强制规则 2），禁止使用 `service_fee`
3. THE COACH_1_API SHALL 从 `v_dws_assistant_salary_calc` 分别查询当月和上月的收入数据（`salary_month` 字段为 date 类型，存储为 `YYYY-MM-01`）
4. THE COACH_1_API SHALL 返回 `tierNodes` 字段（档位节点数组，如 `[0, 100, 130, 160, 190, 220]`），供前端绩效进度条组件使用

### 需求 10：实现 COACH-1 TOP 客户与近期服务记录（T2-5 客户部分）

**用户故事：** 作为管理者，我希望在助教详情页看到该助教的 TOP 客户排名（含关系指数、余额、消费）和近期服务明细（含折算工时），以便评估助教的客户关系质量和服务产出。

#### 验收标准

1. THE COACH_1_API SHALL 返回 `topCustomers` 字段（TOP 客户列表，最多 20 条），每项含扩展字段：`id`、`name`、`initial`（姓氏首字）、`avatarGradient`（头像渐变色）、`heartEmoji`（关系 emoji，P6 AC3 四级映射：💖/🧡/💛/💙）、`relationScore`（关系指数，0-10）、`scoreColor`（分数颜色）、`serviceCount`（服务次数）、`balance`（余额，格式化）、`consume`（消费总额，格式化）
2. THE COACH_1_API SHALL 对 `topCustomers[].consume` 使用 `items_sum` 口径（DWD-DOC 强制规则 1）
3. THE COACH_1_API SHALL 对 `topCustomers[].balance` 通过 `member_id` LEFT JOIN `v_dim_member_card_account`（取 `scd2_is_current=1`）获取（DQ-7）
4. THE COACH_1_API SHALL 通过 `member_id` LEFT JOIN `v_dim_member`（取 `scd2_is_current=1`）获取客户姓名（DQ-6）
5. THE COACH_1_API SHALL 返回 `serviceRecords` 字段（近期服务记录列表），每项含 `customerId`、`customerName`、`initial`、`avatarGradient`、`type`（课程类型）、`typeClass`（样式类）、`table`（台桌名）、`duration`（时长）、`income`（收入）、`date`（日期时间）、`perfHours`（折算工时，可选）
6. THE COACH_1_API SHALL 对 `serviceRecords[].income` 使用 `ledger_amount`（对应 `items_sum` 口径，DWD-DOC 强制规则 1）
7. THE COACH_1_API SHALL 使用 `is_delete=0` 排除废单记录

### 需求 11：实现 COACH-1 任务分组与备注（T2-5 任务部分）

**用户故事：** 作为管理者，我希望在助教详情页看到该助教的任务按状态分组展示（进行中/已过期/已放弃），每个任务含关联备注和放弃原因，以便全面了解助教的任务执行情况。

#### 验收标准

1. THE COACH_1_API SHALL 返回任务分为三组：`visibleTasks`（active 状态任务）、`hiddenTasks`（inactive 状态任务）、`abandonedTasks`（abandoned 状态任务），从 `biz.coach_tasks` 查询该助教的所有任务
2. THE COACH_1_API SHALL 为 `visibleTasks` 和 `hiddenTasks` 每项返回：`typeLabel`（任务类型标签）、`typeClass`（样式类）、`customerName`（客户姓名）、`customerId`（客户 ID，用于跳转）、`noteCount`（备注数量）、`pinned`（是否置顶）、`notes`（备注列表，可选，每项含 `pinned`、`text`、`date`）
3. THE COACH_1_API SHALL 为 `abandonedTasks` 每项返回：`customerName`（客户姓名）、`reason`（放弃原因，来自 `coach_tasks.abandon_reason`）
4. THE COACH_1_API SHALL 从 `biz.notes` 查询每个任务关联的备注（`task_id` 关联），按 `created_at` 倒序排列
5. THE COACH_1_API SHALL 返回 `notes` 字段（助教相关备注列表），每项含 `id`、`content`、`timestamp`、`score`、`customerName`、`tagLabel`、`createdAt`，按 `created_at` 倒序排列，最多返回 20 条

### 需求 12：实现 COACH-1 historyMonths 模块（T2-6）

**用户故事：** 作为管理者，我希望在助教详情页看到该助教最近 5 个以上月份的历史统计（客户数、工时、工资、回访/召回完成数），以便追踪助教的长期表现趋势。

#### 验收标准

1. THE COACH_1_API SHALL 返回 `historyMonths` 字段（历史月份统计列表），包含最近 5 个以上月份的汇总数据，第一条为本月
2. THE COACH_1_API SHALL 为每个月份返回：`month`（月份标签，如 `"本月"`、`"上月"`、`"4月"`）、`estimated`（是否为预估数据，boolean）、`customers`（客户数，格式化，如 `"22人"`）、`hours`（工时，格式化，如 `"87.5h"`）、`salary`（工资，格式化，如 `"¥6,950"`）、`callbackDone`（回访任务完成数）、`recallDone`（召回任务完成数）
3. THE COACH_1_API SHALL 从 `v_dws_assistant_salary_calc` 查询各月的工时（`effective_hours`）和工资（`gross_salary`）数据
4. THE COACH_1_API SHALL 从 `biz.coach_tasks` 查询各月的回访完成数（`task_type='follow_up_visit' AND status='completed'`）和召回完成数（`task_type IN ('high_priority_recall', 'priority_recall') AND status='completed'`）
5. THE COACH_1_API SHALL 将本月标记为 `estimated: true`（预估数据），历史月份标记为 `estimated: false`
6. THE COACH_1_API SHALL 从 `v_dwd_assistant_service_log` 按月统计不重复的 `tenant_member_id` 数量作为客户数（过滤 `is_delete=0`）

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

**用户故事：** 作为系统管理员，我希望所有客户和助教接口都遵循统一的权限控制、数据隔离和数据质量规则，以确保数据安全和口径一致。

#### 验收标准

#### 13.1 权限与认证

1. THE Backend SHALL 对所有 RNS1.2 接口（CUST_1_API、CUST_2_API、COACH_1_API）执行 `require_approved()` 权限检查，确保用户状态为 `approved`
2. THE Backend SHALL 对所有 RNS1.2 接口通过 `SET LOCAL app.current_site_id` 实现门店级数据隔离（FDW 查询通过 `_fdw_context` 上下文管理器统一执行）

#### 13.2 DWD-DOC 强制规则

3. THE Backend SHALL 对所有涉及金额的字段统一使用 `items_sum` 口径（DWD-DOC 强制规则 1），禁止使用 `consume_money`
4. THE Backend SHALL 对所有涉及助教费用的字段使用 `assistant_pd_money`（陪打/基础课）+ `assistant_cx_money`（超休/激励课）拆分（DWD-DOC 强制规则 2），禁止使用 `service_fee`
5. THE Backend SHALL 对所有涉及会员信息的查询通过 `member_id` LEFT JOIN `v_dim_member`（取 `scd2_is_current=1`）获取姓名和手机号（DWD-DOC 强制规则 DQ-6），禁止直接使用 `settlement_head.member_phone` 或 `member_name`
6. THE Backend SHALL 对所有涉及会员卡信息的查询通过 `member_id` LEFT JOIN `v_dim_member_card_account`（`tenant_member_id=member_id`，取 `scd2_is_current=1`）获取（DWD-DOC 强制规则 DQ-7），禁止使用 `member_card_type_name`
7. THE Backend SHALL 使用 `v_dwd_assistant_service_log` 的 `is_delete=0` 排除废单记录，禁止使用已废弃的 `dwd_assistant_trash_event` 表

#### 13.3 优雅降级

8. IF 某个扩展模块（`aiInsight`/`coachTasks`/`favoriteCoaches`/`historyMonths`）的数据源查询失败，THEN THE Backend SHALL 对该模块返回空默认值（空数组或空对象），不影响其他模块和整体响应
9. THE Backend SHALL 对所有 FDW 查询异常进行捕获和日志记录，返回降级响应而非 HTTP 500

#### 13.4 列名映射

10. THE Backend SHALL 在 SQL 中使用 AS 别名将 RLS 视图原始列名转换为代码语义名（如 `site_assistant_id AS assistant_id`、`tenant_member_id AS member_id`、`create_time AS settle_time`、`ledger_amount AS income`、`income_seconds / 3600.0 AS service_hours`），统一在 `fdw_queries.py` 中封装

### 需求 14：正确性属性（Property-Based Testing）

**用户故事：** 作为开发者，我希望通过属性测试验证接口的数据一致性和业务规则正确性，以便在开发阶段发现口径错误和数据异常。

#### 验收标准

#### 14.1 金额口径不变量

1. FOR ALL 消费记录，THE CUST_1_API 返回的 `totalAmount` SHALL 等于 `tableFee + foodAmount + SUM(coaches[].fee)`（在浮点精度范围内），验证 `items_sum` 口径拆分的一致性
2. FOR ALL 助教费用拆分，`coaches[].fee` 中基础课记录 SHALL 对应 `assistant_pd_money`，激励课记录 SHALL 对应 `assistant_cx_money`，两者之和 SHALL 等于该结算单的助教费用总额

#### 14.2 数据隔离不变量

3. FOR ALL CUST_1_API 响应中的 `coachTasks`，每条任务的 `member_id` SHALL 等于请求路径中的 `customerId`，验证客户-任务关联的正确性
4. FOR ALL COACH_1_API 响应中的 `serviceRecords`，每条记录的 `assistant_id` SHALL 等于请求路径中的 `coachId`，验证助教数据隔离

#### 14.3 排序与分组不变量

5. FOR ALL CUST_1_API 响应中的 `favoriteCoaches`，列表 SHALL 按 `relationIndex` 降序排列（前一项的 `relationIndex` ≥ 后一项的 `relationIndex`）
6. FOR ALL COACH_1_API 响应中的 `historyMonths`，列表 SHALL 按月份降序排列（最近月份在前），且第一条的 `estimated` SHALL 为 `true`

#### 14.4 幂等性

7. FOR ALL 相同参数的 CUST_2_API 请求（相同 `customerId`、`year`、`month`），在数据未变更的情况下，两次请求 SHALL 返回相同的 `monthCount` 和 `monthHours` 值

#### 14.5 废单排除一致性

8. FOR ALL 服务记录查询，返回的记录集合中 SHALL 不包含 `is_delete != 0` 的记录，验证废单排除规则在所有接口中一致执行
