# 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 已在拆分计划的追溯矩阵中映射到具体任务。 ### 原始任务清单(归档参考)
点击展开原始 Batch 0~C 任务清单(已被 RNS1 拆分计划替代) #### 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:前后端联调 + 修复