Files
Neo-ZQYY/docs/prd/Neo_Specs/NS1-xcx-backend-api.md

998 lines
54 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 个)
| 模块 | 接口 | 路由文件 |
|------|------|---------|
| Auth5 | AUTH-1~5登录/dev-login/me/refresh/apply | `xcx_auth.py` |
| Tasks5 | GET tasks + pin/unpin/abandon/cancel-abandon | `xcx_tasks.py` |
| Notes3 | POST/GET/DELETE notes | `xcx_notes.py` |
| AI Chat3 | 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_permissions14 条映射)
---
## 三、接口详细设计
### 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逐个验证兼容性
### R3CHAT 模块路径与功能差异
| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| 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 个维度,一次返回一个维度的排序结果。确认? |
### R7spec 中引用但未定义的表
| # | 表名 | 引用位置 | 说明 |
|---|------|---------|------|
| R7-1 | `biz.ai_cache` | TASK-2app4/5/6/7、BOARD-3app2 | 多处引用但 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 层交叉验证。
### B1BOARD-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()`
---
### B2BOARD-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、weeklyVisits8 周柱状图) |
| `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` 参数和"加载更多"逻辑。联调时前端需补充。
---
### B3BOARD-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()` |
---
### 非看板列表页筛选矩阵
#### L1task-list任务列表
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `status` | string | `pending``completed``abandoned` | 无(全部) | ❌ 缺失 | API 支持但页面无筛选控件 |
| `page` | number | 1+ | `1` | ✅ 自动 | 触底加载 |
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |
交互:下拉刷新重置 page=1触底 page++。前端将任务分为 pinnedTasks / normalTasks / abandonedTasks 三组渲染。
#### L2performance绩效概览
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `year` | number | 当前年 ± N | 当前年 | ❌ 缺失 | 固定当前年 |
| `month` | number | 1-12 | 当前月 | ❌ 缺失 | 固定当前月 |
交互:无筛选 UI页面加载时固定请求当前年月。无分页。
#### L3performance-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` 范围查询)
#### L4customer-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` 范围)
#### L5notes备注列表
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `page` | number | 1+ | `1` | ✅ 自动 | 下拉刷新重置 |
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |
交互:下拉刷新重置 page=1。⚠ 无触底加载(`onReachBottom()` 未实现)。
#### L6customer-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-2Pydantic 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 CCHAT 对齐、前端修复与联调
- [ ] T12CHAT 路径迁移(`/api/ai/*``/api/xcx/chat/*`,保留 SSE 流式端点)
- [ ] T13新增 CHAT-1/2 同步端点(历史列表 + 消息查看)
- [ ] T14API 契约补充SSE 端点定义 + BOARD-3 `compareValue` 字段 + BOARD-3 `time`/`area`/`compare` 参数)
- [ ] T15FDW 端到端验证
- [ ] T16前端懒加载改造TASK-2 服务记录/备注、BOARD-2 客户列表)
- [ ] T17前端看板筛选修复F1-F6详见八¾节
- [ ] T18前后端联调 + 修复
</details>