# NS1：小程序后端 API 补全 — xcx-backend-api

> 优先级：最高（上线关键路径）
> 预估工作量：大
> 前置条件：P1-P5 已完成（数据库、认证、任务、备注、AI 骨架均就绪）
> 契约基准：`docs/miniprogram-dev/API-contract.md`

---

## 一、背景与目标

小程序前端 13 个业务页面已全部完成（P5.2 交付），`services/api.ts` 已统一封装 mock → real 切换机制（`USE_REAL_API` 开关）。但后端 23 个接口中仅 16 个已实现，剩余 12 个业务接口缺失，是前后端联调的最大阻塞。

本 SPEC 目标：补全全部缺失接口 + FDW 端到端验证 + 前后端联调修复，使小程序可以连接真实后端运行。

### 已实现的接口（16 个）

| 模块 | 接口 | 路由文件 |
|------|------|---------|
| Auth（5） | AUTH-1~5（登录/dev-login/me/refresh/apply） | `xcx_auth.py` |
| Tasks（5） | GET tasks + pin/unpin/abandon/cancel-abandon | `xcx_tasks.py` |
| Notes（3） | POST/GET/DELETE notes | `xcx_notes.py` |
| AI Chat（3） | SSE stream + conversations + messages | `xcx_ai_chat.py` |

### 缺失的接口（12 个）

| 接口 ID | 端点 | 页面依赖 | 数据源 |
|---------|------|---------|--------|
| TASK-1 扩展 | GET /tasks（增加 performance 附带字段） | task-list | `dws_assistant_salary_calc` via FDW |
| TASK-2 | GET /tasks/{id}（完整详情） | task-detail | biz.coach_tasks + FDW 多表 + ai_cache |
| PERF-1 | GET /performance | performance | `dws_assistant_salary_calc` + `dws_assistant_daily_detail` via FDW |
| PERF-2 | GET /performance/records | performance-records | `dwd_assistant_service_log` via FDW |
| CUST-1 | GET /customers/{id} | customer-detail | `dim_member` + `dws_member_consumption_summary` via FDW |
| CUST-2 | GET /customers/{id}/records | customer-service-records | `dwd_assistant_service_log` + `dwd_table_fee_log` via FDW |
| COACH-1 | GET /coaches/{id} | coach-detail | `dim_assistant` + `dws_member_assistant_relation_index` via FDW |
| BOARD-1 | GET /board/coaches | board-coach | `dws_assistant_salary_calc` + `dws_assistant_monthly_summary` via FDW |
| BOARD-2 | GET /board/customers | board-customer | 多个 DWS 指数表 via FDW |
| BOARD-3 | GET /board/finance | board-finance | 6 个 `dws_finance_*` 表 via FDW |
| CONFIG-1 | GET /config/skill-types | board-coach | 静态配置或 cfg 表 |
| CHAT 路径对齐 | 对齐 CHAT-1/2/3 路径与契约 | chat/chat-history | 现有 xcx_ai_chat.py 路径调整 |

---

## 二、技术架构

### 2.1 后端代码模式（沿用现有模式）

```
apps/backend/app/
├── routers/          # FastAPI 路由（xcx_*.py）
│   ├── xcx_auth.py         ✅ 已有
│   ├── xcx_tasks.py        ✅ 已有（需扩展 TASK-1/TASK-2）
│   ├── xcx_notes.py        ✅ 已有
│   ├── xcx_ai_chat.py      ✅ 已有（需路径对齐）
│   ├── xcx_performance.py  🆕 新建
│   ├── xcx_customers.py    🆕 新建
│   ├── xcx_coaches.py      🆕 新建
│   ├── xcx_board.py        🆕 新建
│   └── xcx_config.py       🆕 新建
├── services/         # 业务逻辑层
│   ├── task_manager.py     ✅ 已有（需扩展）
│   ├── note_service.py     ✅ 已有
│   ├── perf_service.py     🆕 新建
│   ├── customer_service.py 🆕 新建
│   ├── coach_service.py    🆕 新建
│   └── board_service.py    🆕 新建
├── schemas/          # Pydantic 响应模型
└── auth/             # 认证依赖注入 ✅ 已有
```

### 2.2 数据库连接模式

- 业务库（zqyy_app）：`get_connection()` → psycopg2 直连，读写
- ETL 库（通过 FDW）：`fdw_etl.*` schema 查询，需先 `SET LOCAL app.current_site_id`
- ETL 库（直连只读）：`get_etl_readonly_connection(site_id)` → 用于复杂聚合查询

### 2.3 权限控制

- 所有接口需 JWT（`require_approved()`）
- 看板接口需额外权限检查（`require_permission("view_board_finance")` 等）
- 权限种子数据已就绪（auth.role_permissions，14 条映射）

---

## 三、接口详细设计

### 3.1 TASK-1 扩展：任务列表增加绩效概览

现有 `GET /api/xcx/tasks` 返回 `list[TaskListItem]`，需扩展为：
- 增加 `performance` 字段（当月工时/收入/客户数/月份标签）
- 增加分页支持（`page`/`pageSize` 参数）
- 增加 `status` 筛选参数

数据源：
- 任务列表：`biz.coach_tasks` + `fdw_etl.v_dim_member`（客户信息）+ `fdw_etl.v_dws_member_winback_index`/`v_dws_member_newconv_index`（指数）
- 绩效概览：`fdw_etl.v_dws_assistant_salary_calc`（当月薪资计算快照）

### 3.2 TASK-2：任务详情（完整版）

现有 `xcx_tasks.py` 无 `GET /tasks/{id}` 端点，需新增。

数据源（多表聚合）：
- 基础信息：`biz.coach_tasks`
- 客户信息：`fdw_etl.v_dim_member` + `fdw_etl.v_dws_member_consumption_summary`
- 亲密度：`fdw_etl.v_dws_member_assistant_intimacy`
- 近期服务记录：`fdw_etl.v_dwd_assistant_service_log`（最近 N 条）
- 备注：`biz.notes`（最近 N 条，含 ai_score）
- 维客线索：`public.member_retention_clue`
- AI 缓存：`biz.ai_cache`（app4/app5/app6/app7 缓存）
- 喜好标签：`fdw_etl.v_dwd_table_fee_log`（按房间类型统计）

> ⚠️ 这是最复杂的接口，涉及 8+ 张表的聚合。建议拆分为多个 service 函数组合。

### 3.3 PERF-1/PERF-2：绩效模块

PERF-1（绩效概览）数据源：
- `fdw_etl.v_dws_assistant_salary_calc`（收入/档位/工资）
- `fdw_etl.v_dwd_assistant_service_log`（当月服务记录明细）
- `fdw_etl.v_dws_assistant_customer_stats`（新客/常客统计）

PERF-2（绩效明细）数据源：
- `fdw_etl.v_dwd_assistant_service_log`（按月筛选，按日分组）
- `fdw_etl.v_dws_assistant_daily_detail`（定档折算惩罚字段）

### 3.4 CUST-1/CUST-2：客户模块

CUST-1（客户详情）数据源：
- `fdw_etl.v_dim_member` + `fdw_etl.v_dim_member_card_account`（基本信息 + 会员卡）
- `fdw_etl.v_dws_member_consumption_summary`（消费汇总）
- `fdw_etl.v_dws_member_assistant_relation_index`（关系指数）
- `public.member_retention_clue`（维客线索）
- 消费记录：`fdw_etl.v_dwd_settlement_head` + `fdw_etl.v_dwd_table_fee_log` + `fdw_etl.v_dwd_store_goods_sale` + `fdw_etl.v_dwd_recharge_order`

> ⚠️ 会员字段断档规则（DQ-6/DQ-7）：`member_phone`/`member_name`/`member_card_type_name` 不可靠，必须通过 `member_id` JOIN `dim_member`/`dim_member_card_account`

CUST-2（客户服务记录）数据源：
- `fdw_etl.v_dwd_assistant_service_log`（按客户+助教筛选）
- `fdw_etl.v_dwd_table_fee_log`（台桌信息）

### 3.5 COACH-1：助教详情

数据源：
- `fdw_etl.v_dim_assistant`（基本信息）
- `fdw_etl.v_dws_member_assistant_relation_index`（客户数统计，RS>2）
- `biz.coach_tasks`（任务统计：可见/隐藏/已放弃）
- `biz.notes`（备注列表）

### 3.6 BOARD-1/2/3：看板模块

BOARD-1（助教看板）数据源：
- `fdw_etl.v_dws_assistant_salary_calc`（定档业绩/工资）
- `fdw_etl.v_dws_assistant_monthly_summary`（月度汇总）
- `biz.coach_tasks`（任务完成数统计）

BOARD-2（客户看板）数据源（8 个维度排序）：
- 最应召回：`fdw_etl.v_dws_member_winback_index`（WBI 降序）
- 最大消费潜力：`fdw_etl.v_dws_member_spending_power_index`（SPI 降序）
- 最高余额：`fdw_etl.v_dws_member_consumption_summary`
- 最近充值：`fdw_etl.v_dwd_recharge_order`
- 最高消费 60 天：`fdw_etl.v_dws_member_consumption_summary`（基于 `items_sum`）
- 最频繁 60 天：`fdw_etl.v_dws_member_consumption_summary`
- 最近到店：`fdw_etl.v_dws_member_visit_detail`
- 最专一：`fdw_etl.v_dws_member_assistant_relation_index`（RS 最大值）

BOARD-3（财务看板）数据源：
- `fdw_etl.v_dws_finance_daily_summary`
- `fdw_etl.v_dws_finance_income_structure`
- `fdw_etl.v_dws_finance_recharge_summary`
- `fdw_etl.v_dws_finance_discount_detail`
- `fdw_etl.v_dws_finance_expense_summary`
- `fdw_etl.v_dws_platform_settlement`
- `biz.ai_cache`（app2_finance 缓存，AI 洞察）

### 3.7 CONFIG-1：技能类型列表

静态配置接口，返回助教技能类型列表。数据来源待定（硬编码 or cfg 表）。

### 3.8 CHAT 路径对齐

现有 `xcx_ai_chat.py` 路径：
- `POST /api/ai/chat/stream`
- `GET /api/ai/conversations`
- `GET /api/ai/conversations/{id}/messages`

契约定义路径：
- `GET /api/xcx/chat/history`
- `GET /api/xcx/chat/{chatId}/messages`
- `POST /api/xcx/chat/{chatId}/messages`

需要对齐路径，或在前端 service 层适配。

---

## 四、数据库审查与新增表建议

### 4.1 现有表结构满足度

| 接口 | 现有表是否满足 | 缺口 |
|------|--------------|------|
| TASK-1/2 | ✅ 基本满足 | 需 FDW 多表 JOIN |
| PERF-1/2 | ✅ 满足 | 无 |
| CUST-1/2 | ✅ 满足 | 消费记录需 3 种类型 UNION |
| COACH-1 | ✅ 满足 | 无 |
| BOARD-1 | ⚠️ 部分 | 任务完成数需实时统计 |
| BOARD-2 | ⚠️ 部分 | 8 维度排序需多表查询，性能风险 |
| BOARD-3 | ✅ 满足 | 环比需后端计算 |

### 4.2 建议新增的中间表/物化视图

#### 表 1：`biz.board_coach_snapshot`（助教看板快照）

用途：预聚合助教看板数据，避免每次请求实时 JOIN 多张 FDW 表。

```sql
CREATE TABLE biz.board_coach_snapshot (
    id BIGSERIAL PRIMARY KEY,
    site_id BIGINT NOT NULL,
    assistant_id BIGINT NOT NULL,
    snapshot_date DATE NOT NULL,
    -- 定档业绩
    performance_tier VARCHAR(20),
    tier_revenue NUMERIC(12,2),
    -- 工资
    salary_total NUMERIC(12,2),
    -- 客户数
    customer_count INTEGER,
    -- 高客源储值额
    high_value_balance NUMERIC(12,2),
    -- 任务完成数
    task_completed_count INTEGER,
    -- 服务时长
    total_hours NUMERIC(8,2),
    total_income NUMERIC(12,2),
    created_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE (site_id, assistant_id, snapshot_date)
);
```

刷新策略：ETL 数据更新后触发（`trigger_jobs` 事件驱动），或每日凌晨批量刷新。

#### 表 2：`biz.board_customer_snapshot`（客户看板快照）

用途：预聚合客户看板 8 个维度的排序数据，避免实时查询 8 张 FDW 表。

```sql
CREATE TABLE biz.board_customer_snapshot (
    id BIGSERIAL PRIMARY KEY,
    site_id BIGINT NOT NULL,
    member_id BIGINT NOT NULL,
    snapshot_date DATE NOT NULL,
    -- 8 个维度排序值
    wbi_score NUMERIC(8,4),           -- 最应召回
    spi_score NUMERIC(8,4),           -- 最大消费潜力
    balance_amount NUMERIC(12,2),     -- 最高余额
    last_recharge_date DATE,          -- 最近充值
    spend_60d NUMERIC(12,2),          -- 最高消费 60 天（items_sum 口径）
    visit_count_60d INTEGER,          -- 最频繁 60 天
    last_visit_date DATE,             -- 最近到店
    max_rs_score NUMERIC(8,4),        -- 最专一
    -- 展示字段
    member_name VARCHAR(100),
    member_avatar VARCHAR(500),
    member_tags TEXT[],               -- 喜好标签
    created_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE (site_id, member_id, snapshot_date)
);
```

#### 表 3：`biz.board_finance_snapshot`（财务看板快照）

用途：预聚合财务看板数据 + 环比计算结果。

```sql
CREATE TABLE biz.board_finance_snapshot (
    id BIGSERIAL PRIMARY KEY,
    site_id BIGINT NOT NULL,
    biz_date DATE NOT NULL,
    -- 核心指标（JSON 存储，灵活扩展）
    metrics JSONB NOT NULL,
    -- 环比数据
    prev_day_metrics JSONB,
    prev_week_metrics JSONB,
    prev_month_metrics JSONB,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE (site_id, biz_date)
);
```

#### 表 4：`biz.task_detail_cache`（任务详情缓存）

用途：TASK-2 接口涉及 8+ 张表聚合，首次查询后缓存结果，后续读缓存。

```sql
CREATE TABLE biz.task_detail_cache (
    id BIGSERIAL PRIMARY KEY,
    task_id BIGINT NOT NULL REFERENCES biz.coach_tasks(id),
    site_id BIGINT NOT NULL,
    detail_json JSONB NOT NULL,
    version INTEGER NOT NULL DEFAULT 1,
    expires_at TIMESTAMPTZ NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE (task_id)
);
```

失效策略：备注新增/AI 缓存更新/任务状态变更时，DELETE 对应缓存行。

---

## 五、FDW 端到端验证

### 5.1 验证范围

需验证 `fdw_etl` schema 下所有外部表可正常 SELECT，重点关注：

| 外部表 | 用途 | 验证 SQL |
|--------|------|---------|
| `v_dim_member` | 客户信息 | `SELECT count(*) FROM fdw_etl.v_dim_member` |
| `v_dim_assistant` | 助教信息 | `SELECT count(*) FROM fdw_etl.v_dim_assistant` |
| `v_dws_assistant_salary_calc` | 薪资计算 | `SELECT * FROM fdw_etl.v_dws_assistant_salary_calc LIMIT 5` |
| `v_dws_finance_daily_summary` | 财务日报 | `SELECT * FROM fdw_etl.v_dws_finance_daily_summary LIMIT 5` |
| `v_dws_member_winback_index` | WBI 指数 | `SELECT * FROM fdw_etl.v_dws_member_winback_index LIMIT 5` |
| `v_dws_member_spending_power_index` | SPI 指数 | `SELECT * FROM fdw_etl.v_dws_member_spending_power_index LIMIT 5` |

### 5.2 RLS 验证

```sql
-- 设置门店隔离
SET LOCAL app.current_site_id = '目标 site_id';
-- 验证只返回该门店数据
SELECT site_id, count(*) FROM fdw_etl.v_dim_member GROUP BY site_id;
-- 预期：只有一个 site_id
```

---

## 六、前后端联调

### 6.1 联调步骤

1. 后端接口开发完成后，逐个验证响应格式与 `API-contract.md` 一致
2. 小程序 `services/api.ts` 中 `USE_REAL_API = true`
3. 逐页面联调：login → task-list → task-detail → performance → board → customer-detail → chat
4. 修复 snake_case → camelCase 映射差异
5. 处理 inline-mock 页面的数据替换（7 个页面有 TODO 标记）

### 6.2 已知需要对齐的差异

- TASK-1 响应格式：现有返回 `list[TaskListItem]`，契约要求 `{ items, total, page, pageSize, performance }`
- CHAT 路径：现有 `/api/ai/*`，契约要求 `/api/xcx/chat/*`
- 响应包装：现有直接返回数据，契约要求 `{ code: 0, data: ... }` 包装

---

## 七、参考文档

| 文档 | 路径 | 用途 |
|------|------|------|
| API 契约 | `docs/miniprogram-dev/API-contract.md` | 接口定义基准 |
| 前端 Service 层 | `apps/miniprogram/miniprogram/services/api.ts` | 前端期望的响应格式 |
| BD 手册-认证表 | `docs/database/BD_Manual_auth_tables.md` | auth schema 表结构 |
| BD 手册-业务表 | `docs/database/BD_Manual_biz_tables.md` | biz schema 表结构 |
| DWD-DOC 标杆 | `docs/reports/DWD-DOC/` | 金额口径、字段语义权威参考 |
| 数据依赖矩阵 | `docs/prd/specs/00-数据依赖矩阵.md` | 页面→数据表映射 |
| 现有 task_manager | `apps/backend/app/services/task_manager.py` | 任务服务层模式参考 |
| 现有 note_service | `apps/backend/app/services/note_service.py` | 备注服务层模式参考 |
| 权限中间件 | `apps/backend/app/middleware/permission.py` | 权限检查模式参考 |
| 数据库连接 | `apps/backend/app/database.py` | 连接方式参考 |
| FDW 配置 | `db/fdw/setup_fdw_test.sql` | FDW 外部表映射 |

---

## 八、预审查清单（SPEC 启动前确认）

以下问题需要在启动 SPEC 前与用户确认，确保需求无歧义：

### 8.1 接口设计

1. **响应包装格式**：现有后端直接返回数据对象，契约要求 `{ code: 0, data: ... }` 包装。是全局统一改造（中间件），还是仅新接口使用包装格式？
2. **CHAT 路径对齐**：是修改后端路径从 `/api/ai/*` 改为 `/api/xcx/chat/*`，还是在前端 service 层适配现有路径？
3. **分页策略**：TASK-1 契约要求分页，但现有实现返回全量列表。是否所有列表接口都需要分页？看板接口（前 100 名）是否需要分页？
4. **CONFIG-1 技能类型**：数据来源是硬编码还是从 `cfg_*` 表读取？如果硬编码，具体的技能类型列表是什么？

### 8.2 数据库

5. **快照表刷新策略**：3 个看板快照表（coach/customer/finance）的刷新频率？是 ETL 数据更新后触发，还是每日定时刷新，还是请求时按需刷新？
6. **任务详情缓存**：`task_detail_cache` 的过期时间设多久？缓存失效触发条件是否完整（备注新增、AI 缓存更新、任务状态变更）？
7. **FDW 外部表命名**：现有代码中使用 `fdw_etl.v_dim_member`（带 `v_` 前缀），需确认所有 FDW 外部表的实际命名是否一致。

### 8.3 业务逻辑

8. **TASK-2 服务记录范围**：近期服务记录取最近多少条？最近 30 天还是最近 10 条？
9. **BOARD-2 客户看板**：8 个维度排序是否都取前 100 名？是否需要支持类型筛选（如按会员等级筛选）？
10. **BOARD-3 财务看板**：环比计算的基准是什么？日环比、周环比、月环比都需要吗？
11. **CUST-1 消费记录**：3 种消费类型（台桌/商城/充值）是混合排序还是分类展示？分页策略？
12. **COACH-1 任务统计**：可见/隐藏/已放弃的分类逻辑是什么？"隐藏"指什么状态？

### 8.4 性能与安全

13. **FDW 查询性能**：看板接口涉及多张 FDW 表的聚合查询，是否需要设置查询超时？超时后返回什么？
14. **维客线索脱敏**：TASK-2 返回维客线索时，后端脱敏规则是否与 P6 spec 中定义的一致（移除 recorded_by_assistant_id/name）？
15. **金额口径确认**：所有涉及消费金额的接口，确认使用 `items_sum` 口径（= table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money），不使用 `consume_money`。

---

## 八½、前置审查发现（2026-03-18 Kiro 审查）

> 审查方法：对照现有代码（`xcx_auth.py`、`xcx_tasks.py`、`xcx_ai_chat.py`、`task_manager.py`、`permission.py`、`database.py`）、API 契约（`API-contract.md`）、前端 Service 层（`api.ts`）、FDW 配置（`setup_fdw_test.sql`）、BD 手册、数据依赖矩阵、DWD-DOC 标杆文档，逐项交叉验证。

### R1：接口清单准确性

| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R1-1 | 接口总数算术错误 | 🟡 低 | spec 称"23 个接口中仅 16 个已实现，剩余 12 个缺失"，16+12=28≠23。需修正总数或重新盘点 |
| R1-2 | Auth 已实现接口数低估 | 🟡 低 | spec 列 Auth 5 个，实际 `xcx_auth.py` 生产端点 7 个（login/apply/me/me-sites/switch-site/refresh/dev-login），含 dev 端点共 11 个 |
| R1-3 | TASK-4 路径不一致 | 🔴 高 | API 契约定义 `POST /tasks/{taskId}/restore`，前端 `api.ts` 调用 `/tasks/${taskId}/restore`，但后端实现的是 `/tasks/{taskId}/cancel-abandon`。联调必然失败 |

**R1-3 ✅ 已确认**：统一为 `/restore`（改后端路由路径，契约和前端不动）。后端 `xcx_tasks.py` 中 `/cancel-abandon` 端点已实现，需改路径为 `/restore`。

### R2：响应格式（全局架构决策，阻塞所有新接口开发）

| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R2-1 | 成功响应无包装 | 🔴 高 | 契约要求 `{ code: 0, data: ... }`，现有后端直接返回 Pydantic model。全局改造影响所有已实现接口 |
| R2-2 | 错误响应格式不一致 | 🔴 高 | 契约要求 `{ code: number, message: string }`，现有 FastAPI 默认 `{ detail: "..." }`。需自定义异常处理器 |
| R2-3 | snake_case vs camelCase | 🔴 高 | 后端返回 snake_case，前端期望 camelCase。api.ts 有 TODO 标记但未实现转换。需决定转换层位置 |

**R2 ✅ 已确认：采用方案 A**（后端全局包装 `{ code: 0, data: ... }` + Pydantic `alias_generator=to_camel`）

实施路径：
1. 新增全局响应包装中间件（或 `response_model` 基类），成功响应统一 `{ code: 0, data: ... }`
2. 新增全局异常处理器，`HTTPException` → `{ code: status_code, message: detail }`
3. 所有 Pydantic schema 加 `model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)`
4. 前端 `request()` 工具函数加 `.data` 解包（一处改动）
5. 已有接口（Auth/Tasks/Notes/AI Chat）逐个验证兼容性

### R3：CHAT 模块路径与功能差异

| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R3-1 | 路径前缀不同 | 🟠 中 | 现有 `/api/ai/*`，契约 `/api/xcx/chat/*` |
| R3-2 | 功能模式不同 | 🔴 高 | 现有是 SSE 流式（`POST /chat/stream`），契约是同步 JSON（`POST /chat/{chatId}/messages` 返回 `{ userMessage, aiReply }`）。两者不可互替 |
| R3-3 | 历史列表字段不同 | 🟠 中 | 现有返回 `{ id, app_id, source_page, first_message_preview }`，契约要求 `{ id, customer_name, last_message, last_time, unread_count }` |

**R3 ✅ 已确认：采用选项 A**（保留 SSE 流式 + 新增同步端点）

实施方案：
- 保留 `POST /api/xcx/chat/stream`（SSE 流式对话，路径从 `/api/ai/chat/stream` 迁移）
- 新增 `GET /api/xcx/chat/history`（历史对话列表，替代 `/api/ai/conversations`）
- 新增 `GET /api/xcx/chat/{chatId}/messages`（消息查看，替代 `/api/ai/conversations/{id}/messages`）
- 契约补充 SSE 端点定义
- `ai_conversations` 表无 `unread_count` 字段，CHAT-1 响应中此字段暂返回 0（或后续按需新增）

### R4：数据库新增表设计

| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R4-1 | 快照表刷新时机未定义 | 🟠 中 | 3 个 board_*_snapshot 表的刷新策略（ETL 后触发 / 每日定时 / 按需）直接影响数据新鲜度和实现复杂度 |
| R4-2 | task_detail_cache 命中率存疑 | 🟡 低 | TASK-2 涉及 8+ 张表，备注/AI 缓存/服务记录变更频繁，缓存频繁失效。建议评估：优化 SQL + 并行查询是否足够，不一定需要缓存表 |
| R4-3 | board_finance_snapshot.metrics 缺 schema | 🟡 低 | JSONB 灵活但无约束，建议在 spec 中定义 metrics 的 key 清单（如 `total_revenue`、`total_expense`、`net_income` 等） |
| R4-4 | board_customer_snapshot.spend_60d 窗口定义 | 🟡 低 | "60 天"是 `snapshot_date - 60` 到 `snapshot_date`，还是自然月？需明确 |
| R4-5 | 快照表是否真的需要 | 🟠 中 | 看板数据量不大（单店助教 < 50 人、活跃客户 < 5000 人），FDW 直查 + 合理索引可能已够用。快照表增加了维护复杂度（刷新逻辑、数据一致性）。建议先不建快照表，性能不足时再加 |

**R4 ✅ 已确认：采用选项 A**（先直查 FDW，做好索引和查询优化）
- 不建 `board_coach_snapshot`、`board_customer_snapshot`、`board_finance_snapshot` 三张快照表
- 不建 `task_detail_cache` 缓存表
- 看板接口直接查 FDW 表，通过合理索引 + SQL 优化保证性能
- 如后续出现性能瓶颈，再按需引入快照/缓存机制
- spec 第四章中的 4 张新增表建议全部移除

### R5：权限模型

| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R5-1 | 看板接口权限映射未明确 | 🟠 中 | BOARD-1 → `view_board_coach`？BOARD-2 → `view_board_customer`？BOARD-3 → `view_board_finance`？需确认 |
| R5-2 | PERF/CUST/COACH 权限未定义 | 🟠 中 | 绩效/客户/助教模块是否只需 `require_approved()`？助教只能看自己的绩效，管理员能看所有人的？ |
| R5-3 | 数据隔离粒度 | 🟠 中 | 助教只能看自己的任务/绩效/客户（通过 `assistant_id` 过滤），管理员能看全店。这个逻辑在 service 层实现还是中间件层？ |

**R5 ✅ 已确认**：现有 5 个权限 code 够用，从上游入口控制即可。
- `view_tasks`：任务模块（TASK-1/2、PERF-1/2、CUST-1/2、COACH-1 均通过此权限 + `require_approved()` 控制）
- `view_board` + `view_board_finance` / `view_board_customer` / `view_board_coach`：看板模块
- 数据隔离在 service 层实现：助教通过 `user_assistant_binding` 获取 `assistant_id`，只查自己的数据；管理员角色可查全店
- 权限选项固定，角色-权限映射通过租户管理后台灵活配置
- 不新增权限 code

### R6：数据源与口径

| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R6-1 | TASK-1 performance.total_customers 来源不明 | 🟡 低 | `dws_assistant_salary_calc` 是否有客户数字段？可能需要从 `dws_assistant_customer_stats` 单独查 |
| R6-2 | CUST-1 消费记录混合 vs 分类 | 🟡 低 | 3 种消费类型（台桌/商城/充值）在 API 契约中是单一 `consumption_records` 数组，暗示混合排序。确认？ |
| R6-3 | BOARD-2 维度切换方式 | 🟡 低 | API 契约通过 `dimension` 参数切换 8 个维度，一次返回一个维度的排序结果。确认？ |

### R7：spec 中引用但未定义的表

| # | 表名 | 引用位置 | 说明 |
|---|------|---------|------|
| R7-1 | `biz.ai_cache` | TASK-2（app4/5/6/7）、BOARD-3（app2） | 多处引用但 BD 手册无定义。需确认表结构或补充 BD 手册 |
| R7-2 | `biz.ai_conversations` + `biz.ai_messages` | CHAT 模块 | 现有 `xcx_ai_chat.py` 通过 `ConversationService` 操作，但 spec 未引用其结构 |
| R7-3 | `public.member_retention_clue` | TASK-2、CUST-1 | 维客线索表，spec 未描述字段结构和脱敏规则 |

**R7 ✅ 已确认（查库验证）**：4 张表全部已建，无需新增 DDL。

| 表名 | Schema | 字段数 | 关键字段 |
|------|--------|--------|---------|
| `ai_cache` | biz | 9 | `cache_type`（app2_finance/app3_clue/app4_analysis/app5_script/app6_note/app7_task）、`target_id`、`result_json`(JSONB)、`expires_at` |
| `ai_conversations` | biz | 8 | `user_id`(varchar)、`app_id`、`site_id`、`source_page`、`source_context`(JSONB) |
| `ai_messages` | biz | 6 | `conversation_id`(FK)、`role`、`content`、`tokens_used` |
| `member_retention_clue` | public | 10 | `member_id`、`category`、`summary`、`detail`、`recorded_by_assistant_id`、`recorded_by_name`、`source`(默认 'manual') |

脱敏规则：TASK-2/CUST-1 返回维客线索时，后端应排除 `recorded_by_assistant_id` 和 `recorded_by_name` 字段（仅返回 `category`、`summary`、`detail`、`source`、`recorded_at`）。

### R8：其他待确认项

| # | 问题 | 来源 | 状态 |
|---|------|------|------|
| R8-1 | CONFIG-1 技能类型 | spec §3.7 + api.ts | ✅ 使用 cfg 表 |
| R8-2 | TASK-2 近期服务记录分页 | spec §3.2 | ✅ 一批 20 条，懒加载 |
| R8-3 | TASK-2 备注分页 | spec §3.2 | ✅ 一批 20 条，懒加载 |
| R8-4 | BOARD-2 每维度取前多少名 | spec §3.6 | ✅ 一批 20 条，懒加载，无上限 |
| R8-5 | BOARD-3 环比 | spec §3.6 | ✅ 全部月环比（详见下方） |
| R8-6 | COACH-1 "隐藏任务" | spec §3.5 + API 契约 | ✅ `inactive` 状态（详见下方） |
| R8-7 | FDW 查询超时 | 性能风险 | 待定（实施时按需设置） |
| R8-8 | 金额口径 | DWD-DOC 标杆 | ✅ `items_sum`，禁用 `consume_money` |

**R8-1 ✅ 已确认**：使用 cfg 表。后端从 ETL 库的 cfg 表读取技能类型列表，前端 api.ts 中的硬编码仅作为 mock 回退。

**R8-2/3 ✅ 已确认**：一批 20 条，懒加载。
- 前端修改需求：TASK-2 页面的服务记录区域和备注区域需从一次性渲染改为懒加载模式（`page` + `pageSize=20` 参数 + 滚动到底部触发"加载更多"）

**R8-4 ✅ 已确认**：一批 20 条，懒加载，无上限。
- 前端修改需求：BOARD-2 客户看板需从一次性渲染改为懒加载模式

**R8-5 ✅ 已确认（环比调研完成）**：
- 所有环比均为**月环比**（与上月同期对比）
- 共 60+ 个环比数据点，覆盖 6 个板块：经营一览（8 项）、预收资产（6 项 + 赠送卡矩阵 12 项）、应计收入确认（收入结构 9 项 + 正价 4 项 + 优惠 4 项 + 渠道 3 项）、现金流入（5 项）、现金流出（15 项）、助教分析（基础课 + 激励课各 6 项）
- 前端已实现环比开关（`compareEnabled`），环比值格式为百分比字符串（如 `"+12.5%"`）
- API 契约缺陷：BOARD-3 响应中 `trend` 字段仅支持 `up/down/flat`，**未定义环比百分比值**，需补充 `compareValue: string`（如 `"12.5%"`）字段
- 后端需计算所有指标的月环比值（当前期 vs 上期），返回百分比和方向
- 页面筛选支持：本月、上月、本周、上周、前3个月、本季度、上季度、最近6个月

**R8-6 ✅ 已确认**："隐藏任务"= 回访任务被顶替后，仍有一段生效时间但前端不显示。
- 映射关系：`hidden_tasks` 对应 `coach_tasks.status = 'inactive'`（被新任务顶替但未过期）
- 后端在 COACH-1 接口中按 status 分组返回：`active` → `visible_tasks`，`inactive` → `hidden_tasks`，`abandoned` → `abandoned_tasks`
- 前端不展示 `hidden_tasks`，但后端仍需返回（管理端可能需要查看）

---

## 八¾、看板筛选项交叉矩阵（2026-03-18 Kiro 调研）

> 调研方法：逐文件阅读 `board-coach.ts`、`board-customer.ts`、`board-finance.ts` 的完整源码，提取所有筛选项定义、可选值、默认值、交叉约束、前端传参方式，并与 API 契约和 `api.ts` service 层交叉验证。

### B1：BOARD-1 助教看板（board-coach）

#### 筛选项清单

| 参数名 | 类型 | 可选值 | 默认值 | 说明 |
|--------|------|--------|--------|------|
| `sort` | 排序维度 | `perf_desc`、`perf_asc`、`salary_desc`、`salary_asc`、`sv_desc`、`task_desc` | `perf_desc` | 6 种排序，决定卡片模板 |
| `skill` | 技能筛选 | `all`、`chinese`、`snooker`、`mahjong`、`karaoke` | `all` | 不限、中式/追分、斯诺克、麻将/棋牌、团建/K歌 |
| `time` | 时间范围 | `month`、`quarter`、`last_month`、`last_3m`、`last_quarter`、`last_6m` | `month` | 本月、本季度、上月、前3个月、上季度、最近6个月 |

#### 排序→卡片模板映射（`SORT_TO_DIM`）

| sort 值 | dimType | 卡片显示内容 |
|---------|---------|------------|
| `perf_desc` / `perf_asc` | `perf` | 定档业绩工时、上期工时、距升档差距、是否达标 |
| `salary_desc` / `salary_asc` | `salary` | 工资总额、定档工时、上期工时 |
| `sv_desc` | `sv` | 客源储值额、储值客户数、储值消耗 |
| `task_desc` | `task` | 召回任务完成数、回访任务完成数 |

#### 各维度卡片所需字段（后端响应必须包含）

所有维度共享的基础字段（每个 item 都返回）：

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 助教 ID |
| `name` | string | 助教姓名 |
| `initial` | string | 姓名首字（头像占位） |
| `avatarGradient` | string | 头像渐变色（blue/green/pink/amber/violet/cyan） |
| `level` | string | 等级 key：`star`/`senior`/`middle`/`junior` |
| `levelClass` | string | 等级样式类（前端可自行映射，后端可不返回） |
| `skills` | array | 技能列表 `[{ text: '🎱', cls: 'skill--chinese' }]` |
| `topCustomers` | string[] | Top 客户列表（如 `['💖 王先生', '💛 李女士']`） |

各维度专属字段：

| dimType | 维度名 | 专属字段 | 类型 | 说明 |
|---------|--------|---------|------|------|
| `perf` | 定档业绩 | `perfHours` | number | 当期定档工时 |
| | | `perfHoursBefore` | number? | 上期定档工时（可选，无上期数据时不返回） |
| | | `perfGap` | string? | 距升档差距描述（如 `"距升档 13.8h"`，已达标时不返回） |
| | | `perfReached` | boolean | 是否已达标 |
| `salary` | 工资 | `salary` | number | 工资总额（元） |
| | | `salaryPerfHours` | number | 定档工时 |
| | | `salaryPerfBefore` | number? | 上期定档工时 |
| `sv` | 客源储值 | `svAmount` | number | 客源储值总额（元） |
| | | `svCustomerCount` | number | 储值客户数 |
| | | `svConsume` | number | 储值消耗额（元） |
| `task` | 任务完成 | `taskRecall` | number | 召回任务完成数 |
| | | `taskCallback` | number | 回访任务完成数 |

> 后端设计建议：所有维度的字段统一返回在同一个 item 对象中（扁平结构），前端根据当前 `dimType` 选择性渲染。这样切换维度时无需重新请求（数据已在本地），仅切换卡片模板即可。如果数据量大或查询开销高，也可按 `sort` 参数只返回当前维度所需字段。

#### 交叉约束

| 约束 | 说明 |
|------|------|
| `time=last_6m` + `sort=sv_desc` | ⚠️ 不兼容。前端 TIME_OPTIONS 注释标注"不支持客源储值最高"。后端需返回 400 或忽略 |
| 其他组合 | 无限制，3 参数自由组合 |

#### 交叉组合总数
- sort(6) × skill(5) × time(6) = **180 种**（减去 1 个不兼容 = **175 种有效组合**）

#### 前端传参（`api.ts`）
```typescript
fetchBoardCoaches({ skill, sort, time })
```

#### ⚠️ 前端 Bug：筛选变更不触发重新请求
`onSortChange`/`onSkillChange`/`onTimeChange` 仅更新 `data` 状态（`selectedSort`/`selectedSkill`/`selectedTime`），**未调用 `loadData()`**。联调时前端需修复：筛选变更后重新调用 `loadData()`。

---

### B2：BOARD-2 客户看板（board-customer）

#### 筛选项清单

| 参数名 | 类型 | 可选值 | 默认值 | 说明 |
|--------|------|--------|--------|------|
| `dimension` | 维度切换 | `recall`、`potential`、`balance`、`recharge`、`recent`、`spend60`、`freq60`、`loyal` | `recall` | 8 个维度，决定卡片模板和排序逻辑 |
| `project` | 项目筛选 | `all`、`chinese`、`snooker`、`mahjong`、`karaoke` | `all` | 全部、中式/追分、斯诺克、麻将/棋牌、团建/K歌 |
| `page` | 分页页码 | 正整数 | `1` | 前端待补充（R8-4 决策：20 条懒加载） |
| `pageSize` | 每页条数 | 正整数 | `20` | 前端待补充 |

#### 8 维度→卡片模板映射（`DIMENSION_TO_DIM`）

| dimension | 中文名 | 排序依据（后端） | 数据源（FDW 表） | 卡片关键字段 |
|-----------|--------|----------------|-----------------|------------|
| `recall` | 最应召回 | WBI 降序 | `v_dws_member_winback_index` | idealDays、elapsedDays、overdueDays、visits30d、balance、recallIndex |
| `potential` | 最大消费潜力 | SPI 降序 | `v_dws_member_spending_power_index` | potentialTags、spend30d、avgVisits、avgSpend |
| `balance` | 最高余额 | balance_amount 降序 | `v_dws_member_consumption_summary` | lastVisit、monthlyConsume、availableMonths |
| `recharge` | 最近充值 | last_recharge_date 降序 | `v_dwd_recharge_order` | lastRecharge、rechargeAmount、recharges60d、currentBalance |
| `recent` | 最近到店 | last_visit_date 降序 | `v_dws_member_visit_detail` | daysAgo、visitFreq、idealDays |
| `spend60` | 最高消费 近60天 | items_sum_60d 降序 | `v_dws_member_consumption_summary` | spend60d、visits60d、highSpendTag |
| `freq60` | 最频繁 近60天 | visit_count_60d 降序 | `v_dws_member_consumption_summary` | avgInterval、weeklyVisits（8 周柱状图） |
| `loyal` | 最专一 近60天 | max_rs 降序 | `v_dws_member_assistant_relation_index` | intimacy、topCoachName、coachDetails（助教明细表） |

#### 各维度卡片所需字段（后端响应必须包含）

所有维度共享的基础字段（每个 item 都返回）：

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 客户 member_id |
| `name` | string | 客户姓名 |
| `initial` | string | 姓名首字（头像占位） |
| `avatarCls` | string | 头像样式类（avatar--amber/pink/blue 等） |
| `assistants` | array | 关联助教列表 `[{ name, cls, heartScore, badge?, badgeCls? }]` |

各维度专属字段：

| dimType | 维度名 | 专属字段 | 类型 | 说明 |
|---------|--------|---------|------|------|
| `recall` | 最应召回 | `idealDays` | number | 理想到店间隔（天） |
| | | `elapsedDays` | number | 已过天数 |
| | | `overdueDays` | number | 超期天数（= elapsedDays - idealDays） |
| | | `visits30d` | number | 近 30 天到店次数 |
| | | `balance` | string | 余额（格式化，如 `"¥2,680"`） |
| | | `recallIndex` | string | 召回指数（如 `"9.2"`） |
| `potential` | 最大消费潜力 | `potentialTags` | array | 潜力标签 `[{ text: '高频', theme: 'primary' }]` |
| | | `spend30d` | string | 近 30 天消费 |
| | | `avgVisits` | string | 月均到店（如 `"6.2次"`） |
| | | `avgSpend` | string | 次均消费 |
| `balance` | 最高余额 | `balance` | string | 当前余额 |
| | | `lastVisit` | string | 最近到店（如 `"3天前"`） |
| | | `monthlyConsume` | string | 月均消耗 |
| | | `availableMonths` | string | 可用月数（如 `"约0.8个月"`） |
| `recharge` | 最近充值 | `lastRecharge` | string | 最后充值日期（如 `"2月15日"`） |
| | | `rechargeAmount` | string | 充值金额 |
| | | `recharges60d` | string | 近 60 天充值次数 |
| | | `currentBalance` | string | 当前余额 |
| `recent` | 最近到店 | `daysAgo` | number | 距今天数（右上角大字） |
| | | `visitFreq` | string | 到店频率（如 `"6.2次/月"`） |
| | | `idealDays` | number | 理想间隔 |
| | | `visits30d` | number | 近 30 天到店 |
| | | `avgSpend` | string | 次均消费 |
| `spend60` | 最高消费 近60天 | `spend60d` | string | 近 60 天消费总额 |
| | | `visits60d` | string | 近 60 天到店次数 |
| | | `highSpendTag` | boolean | 是否高消费标签 |
| | | `avgSpend` | string | 次均消费 |
| `freq60` | 最频繁 近60天 | `visits60d` | string | 近 60 天到店次数（右上角大字） |
| | | `avgInterval` | string | 平均到店间隔（如 `"5.0天"`） |
| | | `weeklyVisits` | array | 8 周到店柱状图 `[{ val: number, pct: number }]` |
| | | `spend60d` | string | 近 60 天消费 |
| `loyal` | 最专一 近60天 | `intimacy` | string | 亲密度指数 |
| | | `topCoachName` | string | 最亲密助教姓名 |
| | | `topCoachHeart` | number | 最亲密助教爱心分 |
| | | `topCoachScore` | string | 最亲密助教关系指数 |
| | | `coachName` | string | 主助教姓名 |
| | | `coachRatio` | string | 主助教占比（如 `"78%"`） |
| | | `coachDetails` | array | 助教服务明细表 `[{ name, cls, heartScore, badge?, avgDuration, serviceCount, coachSpend, relationIdx }]` |

> 后端设计建议：与助教看板不同，客户看板 8 个维度的字段差异很大，且数据来源不同（8 张不同的 FDW 表）。建议后端按 `dimension` 参数只查询和返回当前维度所需字段，切换维度时前端重新请求。这样避免一次查询 8 张表的性能开销。

#### 交叉约束
- **无交叉限制**：dimension 和 project 完全独立，可自由组合

#### 交叉组合总数
- dimension(8) × project(5) = **40 种有效组合**

#### 前端传参（`api.ts`）
```typescript
fetchBoardCustomers({ dimension, project, sort })
// 注意：sort 参数在 api.ts 签名中存在但前端未使用（无排序筛选 UI）
// page/pageSize 参数缺失，需前端补充（R8-4 决策）
```

#### ⚠️ 前端 Bug：筛选变更不触发重新请求
`onDimensionChange`/`onProjectChange` 仅更新 `data` 状态，**未调用 `loadData()`**。联调时前端需修复。

#### ⚠️ 前端缺失：分页参数
R8-4 已确认 20 条懒加载，但 `fetchBoardCustomers` 签名和 `board-customer.ts` 均无 `page`/`pageSize` 参数和"加载更多"逻辑。联调时前端需补充。

---

### B3：BOARD-3 财务看板（board-finance）

#### 筛选项清单

| 参数名 | 类型 | 可选值 | 默认值 | 说明 |
|--------|------|--------|--------|------|
| `time` | 时间范围 | `month`、`lastMonth`、`week`、`lastWeek`、`quarter3`、`quarter`、`lastQuarter`、`half6` | `month` | 8 种时间范围 |
| `area` | 区域筛选 | `all`、`hall`、`hallA`、`hallB`、`hallC`、`mahjong`、`teamBuilding` | `all` | 7 种区域 |
| `compareEnabled` | 环比开关 | `true` / `false` | `false` | 控制环比数据显示/隐藏 |

#### 6 个板块及筛选影响

| 板块 | sectionId | 受 time 影响 | 受 area 影响 | 受 compare 影响 | 特殊规则 |
|------|-----------|:-----------:|:-----------:|:--------------:|---------|
| 经营一览 | section-overview | ✅ | ✅ | ✅ | 无 |
| 预收资产 | section-recharge | ✅ | ⚠️ | ✅ | **仅 `area=all` 时显示**，选中具体区域时整个板块隐藏 |
| 应计收入确认 | section-revenue | ✅ | ✅ | ✅ | 含收入结构表（按区域分行）、正价明细、优惠明细、渠道明细 |
| 现金流入 | section-cashflow | ✅ | ✅ | ✅ | 无 |
| 现金流出 | section-expense | ✅ | ✅ | ✅ | 含经营支出、固定支出、助教分成、平台服务费 4 个子分组 |
| 助教分析 | section-coach | ✅ | ✅ | ✅ | 含基础课、激励课两个子表（各按等级分行） |

#### 环比开关行为

| 状态 | 行为 |
|------|------|
| `compareEnabled=false`（默认） | 仅显示当期数值，隐藏所有环比箭头和百分比 |
| `compareEnabled=true` | 每个数据单元格下方显示环比方向+百分比 |

环比样式规则：
- 上升：绿色 ↑ + 百分比（如 `↑12.5%`）
- 下降：红色 ↓ + 百分比（如 `↓3.2%`）
- 持平：灰色文字 `持平`

环比计算基准：与上一个相同时间周期对比（本月 vs 上月、本周 vs 上周、本季度 vs 上季度等）

#### 交叉约束

| 约束 | 说明 |
|------|------|
| `area ≠ all` → 预收资产板块隐藏 | 储值卡数据不按区域拆分，选中具体区域时无意义 |
| `compareEnabled` 独立 | 与 time/area 无交叉限制 |
| time + area 自由组合 | 无其他限制 |

#### 交叉组合总数
- time(8) × area(7) × compare(2) = **112 种组合**
- 其中 area≠all 时预收资产板块隐藏（不影响请求参数，仅影响前端渲染）

#### ⚠️ 重大设计缺陷：筛选参数不传后端

当前 `fetchBoardFinance` 签名：
```typescript
fetchBoardFinance({ date?: string })  // 仅 date 参数
```

前端 `onTimeChange`/`onAreaChange`/`toggleCompare` 仅更新本地 data 状态，**不重新调用 API**。mock 模式下不需要重新请求（数据内联），但联调后必须：

1. `fetchBoardFinance` 签名扩展为 `{ time, area, compareEnabled }` 三个参数
2. 筛选变更后重新调用 `fetchBoardFinance`
3. 后端根据 `time` 计算日期范围、根据 `area` 过滤区域、根据 `compareEnabled` 决定是否返回环比数据

#### 后端 API 参数设计建议

```
GET /api/xcx/board/finance?time={time}&area={area}&compare={0|1}
```

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|:----:|--------|------|
| `time` | string | 否 | `month` | 时间范围枚举 |
| `area` | string | 否 | `all` | 区域枚举 |
| `compare` | 0/1 | 否 | `0` | 是否返回环比数据（减少不需要时的计算开销） |

---

### 前端修改需求汇总（联调前必须完成）

#### 看板页修复（F1-F6）

| # | 页面 | 问题 | 修改内容 |
|---|------|------|---------|
| F1 | board-coach | 筛选变更不触发重新请求 | `onSortChange`/`onSkillChange`/`onTimeChange` 末尾加 `this.loadData()` |
| F2 | board-customer | 筛选变更不触发重新请求 | `onDimensionChange`/`onProjectChange` 末尾加 `this.loadData()` |
| F3 | board-customer | 缺少分页参数 | `fetchBoardCustomers` 签名加 `page`/`pageSize`，页面加"加载更多"逻辑 |
| F4 | board-finance | 筛选参数不传后端 | `fetchBoardFinance` 签名扩展为 `{ time, area, compare }`，筛选变更后重新调用 |
| F5 | board-finance | 筛选变更不触发重新请求 | `onTimeChange`/`onAreaChange` 末尾加 `this.loadData()` |
| F6 | board-coach | `time=last_6m` + `sort=sv_desc` 不兼容 | 前端在选择 `last_6m` 时禁用 `sv_desc` 选项，或选择 `sv_desc` 时禁用 `last_6m` |

#### 列表页修复（F7-F11）

| # | 页面 | 问题 | 修改内容 |
|---|------|------|---------|
| F7 | task-list | status 筛选 UI 未实现 | API 支持 `status` 参数但页面无筛选控件，需添加 Tab 或筛选栏 |
| F8 | performance | 无月份切换功能 | API 支持 `year`/`month` 参数但页面固定当前月，需添加月份切换按钮 |
| F9 | performance-records | 月份切换时未重置分页 | `switchMonth()` 中需将 `page` 重置为 1 |
| F10 | customer-service-records | 月份切换采用本地筛选 | `updateMonthView()` 仅本地过滤，联调后需改为重新调用 API |
| F11 | notes | 无触底加载逻辑 | API 支持分页但页面未实现 `onReachBottom()` |

---

### 非看板列表页筛选矩阵

#### L1：task-list（任务列表）

| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `status` | string | `pending`、`completed`、`abandoned` | 无（全部） | ❌ 缺失 | API 支持但页面无筛选控件 |
| `page` | number | 1+ | `1` | ✅ 自动 | 触底加载 |
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |

交互：下拉刷新重置 page=1；触底 page++。前端将任务分为 pinnedTasks / normalTasks / abandonedTasks 三组渲染。

#### L2：performance（绩效概览）

| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `year` | number | 当前年 ± N | 当前年 | ❌ 缺失 | 固定当前年 |
| `month` | number | 1-12 | 当前月 | ❌ 缺失 | 固定当前月 |

交互：无筛选 UI，页面加载时固定请求当前年月。无分页。

#### L3：performance-records（绩效明细）

| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `year` | number | 当前年 ± N | 当前年 | ✅ 月份切换按钮 | 上月/下月箭头 |
| `month` | number | 1-12 | 当前月 | ✅ 月份切换按钮 | 不可超过当前月 |
| `page` | number | 1+ | `1` | ✅ 自动 | 触底加载 |
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |

月份切换详细交互：
1. 页面顶部显示 `{year}年{month}月` 标签，左右各有箭头按钮
2. 点击左箭头 → `currentMonth--`（跨年自动 `currentYear--`，`currentMonth=12`）
3. 点击右箭头 → `currentMonth++`（跨年自动 `currentYear++`，`currentMonth=1`）
4. 边界：`canGoNext = false` 当 `currentYear == nowYear && currentMonth == nowMonth`（不可超过当前月）；`canGoPrev = true`（无下限）
5. 月份变更后 → 调用 `loadData()` → 重新请求 API `fetchPerformanceRecords({ year, month, page, pageSize })`
6. ⚠️ Bug：`switchMonth()` 中未将 `page` 重置为 1，月份切换后可能请求第 N 页数据

后端响应结构（按月返回）：
- `summary`：当月汇总（总笔数、总工时、折算前总工时、总收入）
- `date_groups`：按日期降序分组，每组含日期标签、当日总工时/总收入、记录列表
- 每条记录字段：`id`、`customerName`、`timeRange`（如 `"20:00-22:00"`）、`hours`（折算后）、`hoursRaw`（折算前，可选）、`courseType`、`courseTypeClass`（`tag-basic`/`tag-vip`/`tag-tip`）、`location`（台号）、`income`

后端时间范围处理：
- 接收 `year` + `month` → 转换为 `biz_date BETWEEN '{year}-{month}-01' AND '{year}-{month}-{lastDay}'`
- 数据源：`fdw_etl.v_dwd_assistant_service_log`（按 `assistant_id` + `biz_date` 范围查询）

#### L4：customer-service-records（客户服务记录）

| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `customerId` | string | 客户 ID | 从 options 读取 | ✅ 自动 | 页面参数 |
| `year` | number | 当前年 ± N | 当前年 | ✅ 月份切换按钮 | 上月/下月箭头 |
| `month` | number | 1-12 | 当前月 | ✅ 月份切换按钮 | 不可超过当前月 |
| `table` | string | 台号 | 无 | ❌ 缺失 | API 支持但页面未使用 |

月份切换详细交互：
1. 页面顶部显示 `{year}年{month}月` 标签，左右各有箭头按钮
2. 点击左箭头 → `currentMonth--`（跨年处理同 L3）
3. 点击右箭头 → `currentMonth++`（跨年处理同 L3）
4. 边界：`canPrev` = `yearMonth > minYearMonth`（最早 12 个月前）；`canNext` = `yearMonth < maxYearMonth`（不超过当前月）
5. 月份变更后 → 调用 `updateMonthView()` → **仅本地过滤**（从 `allRecords` 中按 `date.startsWith('{year}-{month}')` 筛选）
6. ⚠️ 设计问题：首次 `loadData()` 一次性拉取全部记录，月份切换仅本地过滤。联调后如果数据量大（单客户数百条记录），需改为按月请求 API

本地筛选逻辑：
- `allRecords`（首次加载时获取全部）→ 按 `date` 字段前缀 `{year}-{month}` 过滤 → 转换为 `ServiceRecord` 展示格式
- 月度统计：`monthCount`（当月记录数）、`monthHours`（当月总时长）
- 记录字段：`table`（台号）、`type`（课程类型标签）、`typeClass`（`basic`/`vip`/`tip`/`recharge`）、`recordType`（`course`/`recharge`）、`duration`（折算后小时）、`income`（到手金额）、`drinks`（饮品描述）、`date`（显示日期+时间段）

后端时间范围处理（联调后建议改为按月请求）：
- 当前方案：一次返回全部记录，前端本地按月过滤
- 建议方案：`fetchCustomerRecords({ customerId, year, month })` → 后端按月查询 `fdw_etl.v_dwd_assistant_service_log`（按 `member_id` + `assistant_id` + `biz_date` 范围）

#### L5：notes（备注列表）

| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `page` | number | 1+ | `1` | ✅ 自动 | 下拉刷新重置 |
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |

交互：下拉刷新重置 page=1。⚠️ 无触底加载（`onReachBottom()` 未实现）。

#### L6：customer-detail / coach-detail（详情页）

无筛选项，单次加载全部数据。

---

## 八⅞、Storyboard 走查 Gap 汇总（2026-03-18）

> 走查方法：两个子代理分别以助教视角和管理层视角，逐页面对照前端内联 mock 数据 vs API 契约 vs 八¾节字段定义，提取所有未记录的接口需求 Gap。
> 完整报告：`docs/reports/storyboard-walkthrough-assistant-view.md`（助教视角，51 Gap）+ `docs/reports/miniprogram-storyboard-walkthrough-gaps.md`（管理层视角，31 Gap）

### 统计

| 严重度 | 助教视角 | 管理层视角 | 去重后合计 |
|--------|:--------:|:----------:|:----------:|
| 🔴 高（阻塞联调） | 17 | 14 | ~20 |
| 🟠 中（影响完整性） | 19 | 12 | ~18 |
| 🟡 低（可后续修复） | 15 | 5 | ~12 |

### 核心发现

1. **契约与前端严重脱节**：BOARD-1/2/3、CUST-1、COACH-1、PERF-1、TASK-1 performance 的契约响应定义与前端实际需求严重不匹配，需完全重写
2. **BOARD-3 财务看板**：契约定义扁平 `metrics` 数组，前端需要 6 个独立板块（overview/recharge/revenue/cashflow/expense/coachAnalysis）的深度嵌套结构，含 200+ 字段
3. **CUST-1 客户详情**：缺少 5 大模块（aiInsight/coachTasks/favoriteCoaches/消费记录嵌套结构/备注列表）
4. **COACH-1 助教详情**：缺少 6 大模块（performance/income/tierNodes/historyMonths/topCustomers 扩展/备注列表）
5. **跨页面参数传递**：多处 taskId/customerId 混淆、未传参数、用 name 代替 id 跳转（用户决策：统一用唯一 ID）
6. **CHAT 模块**：referenceCard 未定义、customerId→chatId 映射缺失、SSE 端点未在契约中定义

### 处置方案

全部 Gap 已纳入 **[RNS1 拆分计划](./RNS1-split-plan.md)**，分 5 个子 Spec 逐步实施。Gap 追溯矩阵见拆分计划末尾。

---

## 九、任务清单

> ⚠️ **已拆分**：原始任务清单（T0-1 ~ T18）已被 Storyboard 走查发现的 80+ 个 Gap 大幅扩展，体量超出单个 SPEC 可控范围。
>
> 完整的拆分计划见 **[RNS1-split-plan.md](./RNS1-split-plan.md)**，将 NS1 拆为 5 个子 Spec：
>
> | 子 Spec | 名称 | 任务数 | 状态 |
> |---------|------|:------:|:----:|
> | RNS1.0 | 基础设施与契约重写 | 6 | 待启动 |
> | RNS1.1 | 任务与绩效接口 | 6 | 待启动 |
> | RNS1.2 | 客户与助教接口 | 6 | 待启动 |
> | RNS1.3 | 三看板接口 | 7 | 待启动 |
> | RNS1.4 | CHAT 对齐与联调收尾 | 5 | 待启动 |
>
> 执行顺序：RNS1.0 → (RNS1.1 ∥ RNS1.2 ∥ RNS1.3) → RNS1.4
>
> 需求追溯：两份走查报告（`docs/reports/storyboard-walkthrough-assistant-view.md` + `docs/reports/miniprogram-storyboard-walkthrough-gaps.md`）中的全部 Gap 已在拆分计划的追溯矩阵中映射到具体任务。

### 原始任务清单（归档参考）

<details>
<summary>点击展开原始 Batch 0~C 任务清单（已被 RNS1 拆分计划替代）</summary>

#### Batch 0：基础设施改造（阻塞所有新接口）
- [ ] T0-1：全局响应包装中间件（`{ code: 0, data: ... }` + 异常处理器 `{ code, message }`）
- [ ] T0-2：Pydantic schema 统一 camelCase（`alias_generator=to_camel`，所有现有 + 新增 schema）
- [ ] T0-3：后端 TASK-4 路径从 `/cancel-abandon` 改为 `/restore`（对齐契约）
- [ ] T0-4：前端 `request()` 工具函数加 `.data` 解包适配

#### Batch A：核心业务接口
- [ ] T1：扩展 TASK-1（任务列表 + 绩效概览 + 分页 + 懒加载）
- [ ] T2：实现 TASK-2（任务详情完整版，服务记录/备注各 20 条懒加载）
- [ ] T3：实现 PERF-1（绩效概览）
- [ ] T4：实现 PERF-2（绩效明细，20 条懒加载）
- [ ] T5：实现 CUST-1（客户详情，消费记录 3 类混合排序 + 懒加载）
- [ ] T6：实现 CUST-2（客户服务记录，20 条懒加载）
- [ ] T7：实现 COACH-1（助教详情，任务按 active/inactive/abandoned 分组）

#### Batch B：看板 + 配置接口
- [ ] T8：实现 BOARD-1（助教看板，FDW 直查）
- [ ] T9：实现 BOARD-2（客户看板，8 维度切换，20 条懒加载，FDW 直查）
- [ ] T10：实现 BOARD-3（财务看板，60+ 指标月环比计算，FDW 直查）
- [ ] T11：实现 CONFIG-1（技能类型列表，从 cfg 表读取）

#### Batch C：CHAT 对齐、前端修复与联调
- [ ] T12：CHAT 路径迁移（`/api/ai/*` → `/api/xcx/chat/*`，保留 SSE 流式端点）
- [ ] T13：新增 CHAT-1/2 同步端点（历史列表 + 消息查看）
- [ ] T14：API 契约补充（SSE 端点定义 + BOARD-3 `compareValue` 字段 + BOARD-3 `time`/`area`/`compare` 参数）
- [ ] T15：FDW 端到端验证
- [ ] T16：前端懒加载改造（TASK-2 服务记录/备注、BOARD-2 客户列表）
- [ ] T17：前端看板筛选修复（F1-F6，详见八¾节）
- [ ] T18：前后端联调 + 修复

</details>
