# SPEC: 财务看板 Phase 2 — 144 组合全量验证

> 创建日期：2026-03-28
> 前置 SPEC：`board-finance-phase2`（已完成 T1-T6 + bugfix）
> 状态：已完成 ✅（2026-03-28）
> 优先级：P1

---

## 一、目标

遍历财务看板全部 144 种筛选组合（8 时间 × 9 区域 × 2 环比），验证后端 API 返回数据与前端 `page.data` 一致性。

## 二、组合矩阵

### 时间筛选（8 种）
| 枚举值 | 显示 | 当期范围 | 上期范围 |
|--------|------|---------|---------|
| month | 本月 | 月首~今天 | 上月首~上月同日 |
| lastMonth | 上月 | 上月首~上月末 | 再上月首~再上月末 |
| week | 本周 | 周一~今天 | 上周一~上周同天 |
| lastWeek | 上周 | 上周一~上周日 | 再上周一~再上周日 |
| quarter3 | 前3个月 | 往前3月首~上月末 | 再往前等长 |
| quarter | 本季度 | 季首~今天 | 上季首~上季同天 |
| lastQuarter | 上季度 | 上季首~上季末 | 再上季首~再上季末 |
| half6 | 最近6个月 | 往前6月首~上月末 | 再往前等长 |

### 区域筛选（9 种）
| 枚举值 | 显示 | 影响板块 |
|--------|------|---------|
| all | 全部区域 | 6 板块全显示 |
| hall | 大厅 | 隐藏：预收资产/现金流出/助教分析 |
| hallA | A区 | 同上 |
| hallB | B区 | 同上 |
| hallC | C区 | 同上 |
| vip | 台球包厢 | 同上 |
| snooker | 斯诺克 | 同上 |
| mahjong | 麻将房 | 同上 |
| ktv | 团建房 | 同上 |

### 环比开关（2 种）
| 值 | 说明 |
|----|------|
| 0 | 关闭，环比字段为空 |
| 1 | 开启，环比字段有值 |

## 三、验证项清单

每种组合需验证以下字段（共 6 板块）：

### 3.1 经营一览（overview）— 始终显示
| # | 字段 | 验证规则 |
|---|------|---------|
| O1 | occurrence | ≥0，数字类型 |
| O2 | discount | ≥0，数字类型 |
| O3 | discountRate | 0~1 之间 |
| O4 | confirmedRevenue | = occurrence - discount |
| O5 | cashIn | ≥0 |
| O6 | cashOut | ≥0 |
| O7 | cashBalance | = cashIn - cashOut |
| O8 | balanceRate | cashIn>0 时 = cashBalance/cashIn |
| O9 | area≠all 时 | occurrence/discount/confirmedRevenue 应与 revenue 板块一致 |
| O10 | compare=1 时 | 8 个 xxxCompare 字段非空，格式为 "X.X%" / "持平" / "新增" |
| O11 | compare=0 时 | 8 个 xxxCompare 字段为空/null |

### 3.2 预收资产（recharge）— 仅 area=all 时返回
| # | 字段 | 验证规则 |
|---|------|---------|
| R1 | area≠all 时 | recharge 为 null |
| R2 | actualIncome | ≥0 |
| R3 | firstCharge + renewCharge | ≈ actualIncome |
| R4 | cardBalance | ≥0（快照值） |
| R5 | allCardBalance | ≥ cardBalance |
| R6 | giftRows | 长度 3（新增/消费/余额） |
| R7 | compare=1 时 | allCardBalanceCompare 非空 |

### 3.3 应计收入确认（revenue）— 始终显示
| # | 字段 | 验证规则 |
|---|------|---------|
| V1 | structureRows | 长度 ≥ 3（至少有主行+助教+食品） |
| V2 | area≠all 时 | structureRows 只含对应区域 |
| V3 | totalOccurrence | = SUM(structureRows 非 isSub 行的 amount) |
| V4 | discountTotal | = SUM(discountItems 的 amount) |
| V5 | confirmedTotal | = totalOccurrence - discountTotal |
| V6 | discountItems | 长度 5（团购/会员折扣/手动调整/赠送卡/其他） |
| V7 | channelItems | 长度 3 |
| V8 | priceItems | 长度 3 |
| V9 | structureRows 优惠列 | 主行 discount = discountTotal，子行按占比分摊 |
| V10 | compare=1 时 | totalOccurrenceCompare/confirmedTotalCompare 非空 |
| V11 | compare=1 时 | structureRows 各行 bookedCompare 非空 |

### 3.4 现金流入（cashflow）— 始终显示
| # | 字段 | 验证规则 |
|---|------|---------|
| C1 | consumeItems | 长度 2-3（纸币/线上/团购 或 合并项/团购） |
| C2 | rechargeItems | 长度 1 |
| C3 | total | = SUM(consumeItems) + SUM(rechargeItems) |
| C4 | consumeItems 各项 | desc 非空（柜台现金收款等） |
| C5 | compare=1 时 | totalCompare 非空，各项 compare 非空 |

### 3.5 现金流出（expense）— 仅 area=all 时显示
| # | 字段 | 验证规则 |
|---|------|---------|
| E1 | operationItems | 长度 ≥ 3 |
| E2 | fixedItems | 长度 ≥ 4 |
| E3 | coachItems | 长度 ≥ 4 |
| E4 | platformItems | 长度 ≥ 3 |
| E5 | total | = SUM(所有 items) |

### 3.6 助教分析（coachAnalysis）— 仅 area=all 时显示
| # | 字段 | 验证规则 |
|---|------|---------|
| A1 | basic.rows | 长度 1-4（初/中/高/星） |
| A2 | basic.totalPay | = SUM(rows.pay) |
| A3 | basic.totalShare | = SUM(rows.share) |
| A4 | basic.avgHourly | 13~28 范围（基础课） |
| A5 | incentive.rows | 长度 0-4 |
| A6 | compare=1 时 | basic/incentive 各行 payCompare/shareCompare 非空 |

## 四、执行方案

### 4.1 后端 API 验证（Python 脚本）

写 `scripts/ops/validate_board_finance.py`，遍历 144 种组合：
1. 登录获取 token（dev-login）
2. 循环调用 `GET /api/xcx/board/finance?time=X&area=Y&compare=Z`
3. 对每个响应按验证项清单检查
4. 输出问题清单到 `export/board-finance-validation.md`

### 4.2 前端 page.data 验证（微信开发者工具）

对后端验证发现问题的组合，用 `evaluate_script` 验证：
1. 连接开发者工具（ws://127.0.0.1:9420）
2. `page.setData({ selectedTime, selectedArea, compareEnabled })` + `page._loadData()`
3. 等待 5 秒后读取 `page.data`
4. 对比后端 API 返回值与 `page.data` 是否一致

### 4.3 优化策略

144 种组合中，很多是等价的：
- area=all 时 6 板块全显示，area≠all 时只有 3 板块（overview/revenue/cashflow）
- compare=0 时不需要验证环比字段
- 不同区域的验证逻辑相同，只是数据不同

可以分层验证：
1. 第一层：8 时间 × 2 环比 × area=all = 16 种（全板块）
2. 第二层：8 时间 × 1 区域（hallA 代表）× 2 环比 = 16 种（验证区域过滤）
3. 第三层：对剩余 7 个区域，只验证 month × compare=0 = 7 种（验证区域数据差异）
4. 总计：16 + 16 + 7 = 39 种（覆盖所有逻辑分支）

## 五、执行环境

- 后端：`cd apps/backend && uvicorn app.main:app --reload`
- 微信开发者工具：端口 9420
- 数据库：pg-etl-test（test_etl_feiqiu）
- 脚本 cwd：项目根目录

## 六、产出物

- `scripts/ops/validate_board_finance.py` — 验证脚本
- `export/board-finance-validation.md` — 验证报告（问题清单）
- 如有问题，修复后重跑验证直到全部通过
