# dws_finance_board_cache 看板缓存层表

> 生成时间：2026-03-28
> 关联 SPEC：board-finance-dws-area-refactor

## 表信息

| 属性 | 值 |
|------|-----|
| Schema | dws |
| 表名 | dws_finance_board_cache |
| 主键 | id |
| 唯一键 | (site_id, time_range, area_code) |
| 数据来源 | dws_finance_area_daily（日粒度原子层） |
| 更新频率 | 每天一次（营业日切点后） |
| 幂等策略 | ON CONFLICT (site_id, time_range, area_code) DO UPDATE |
| 说明 | 缓存已完成周期的 overview 聚合结果，避免重复 SUM 计算 |

## 字段说明

| 序号 | 字段名 | 类型 | 可空 | 说明 |
|------|--------|------|------|------|
| 1 | id | BIGSERIAL | NO | 自增主键 |
| 2 | site_id | BIGINT | NO | 门店ID |
| 3 | time_range | VARCHAR(20) | NO | 时间范围：lastMonth/lastWeek/lastQuarter/quarter3/half6 |
| 4 | area_code | VARCHAR(20) | NO | 区域编码：all/hall/hallA/hallB/hallC/vip/snooker/mahjong/ktv |
| 5 | start_date | DATE | NO | 当期起始日期 |
| 6 | end_date | DATE | NO | 当期结束日期 |
| 7 | prev_start_date | DATE | YES | 上期起始日期（环比用） |
| 8 | prev_end_date | DATE | YES | 上期结束日期（环比用） |
| 9 | occurrence | NUMERIC(14,2) | NO | 发生额（gross_amount 周期汇总） |
| 10 | discount | NUMERIC(14,2) | NO | 优惠合计（discount_total 周期汇总） |
| 11 | discount_rate | NUMERIC(8,4) | NO | 优惠占比 = discount / occurrence |
| 12 | confirmed_revenue | NUMERIC(14,2) | NO | 确认收入 = occurrence - discount |
| 13 | cash_in | NUMERIC(14,2) | NO | 现金流入合计（仅 area_code=all 有效） |
| 14 | cash_out | NUMERIC(14,2) | NO | 现金流出合计（仅 area_code=all 有效） |
| 15 | cash_balance | NUMERIC(14,2) | NO | 现金余额变动 = cash_in - cash_out |
| 16 | balance_rate | NUMERIC(8,4) | NO | 余额变动率 = cash_balance / cash_in |
| 17 | data_fingerprint | VARCHAR(64) | YES | 源数据 MD5 指纹，用于检测补录导致的数据变化 |
| 18 | computed_at | TIMESTAMPTZ | NO | 缓存计算时间 |
| 19 | created_at | TIMESTAMPTZ | NO | 创建时间 |
| 20 | updated_at | TIMESTAMPTZ | NO | 更新时间 |

## 约束与索引

| 约束/索引 | 类型 | 列 |
|-----------|------|-----|
| dws_finance_board_cache_pkey | PRIMARY KEY | id |
| dws_finance_board_cache_site_id_time_range_area_code_key | UNIQUE | (site_id, time_range, area_code) |

## RLS 视图

```sql
CREATE OR REPLACE VIEW dws.v_dws_finance_board_cache AS
SELECT * FROM dws.dws_finance_board_cache
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
```

## 缓存策略

- 已完成周期（缓存）：lastMonth, lastWeek, lastQuarter, quarter3, half6
- 当期周期（不缓存）：month, week, quarter
- 失效条件：`data_fingerprint` 变化（补录导致源数据变化）
- 指纹算法：`MD5(sorted [(stat_date, gross_amount, discount_total), ...])`

## 变更原因

为已完成周期的聚合结果提供缓存，避免每次查询都从日粒度表 SUM 计算。通过数据指纹机制自动检测补录导致的数据变化并重算。

## 兼容性影响

| 组件 | 影响 |
|------|------|
| ETL 任务 | 新增 DWS_FINANCE_BOARD_CACHE 任务，依赖 DWS_FINANCE_AREA_DAILY |
| 后端 API | board_service.py 已完成周期先查缓存，未命中从日粒度表 SUM |
| 小程序 | 无直接影响（API 签名不变） |

## 回滚策略

```sql
DROP VIEW IF EXISTS dws.v_dws_finance_board_cache;
DROP TABLE IF EXISTS dws.dws_finance_board_cache;
```

回滚后后端需移除缓存查询逻辑，改为每次从日粒度表 SUM。

## 验证 SQL

```sql
-- 1. 验证表存在且有数据
SELECT COUNT(*), COUNT(DISTINCT time_range), COUNT(DISTINCT area_code)
FROM dws.dws_finance_board_cache
WHERE site_id = 1;

-- 2. 验证唯一约束生效（应返回 0 行）
SELECT site_id, time_range, area_code, COUNT(*)
FROM dws.dws_finance_board_cache
GROUP BY site_id, time_range, area_code
HAVING COUNT(*) > 1;

-- 3. 验证 confirmed_revenue = occurrence - discount
SELECT time_range, area_code,
       occurrence, discount, confirmed_revenue,
       (occurrence - discount) AS expected
FROM dws.dws_finance_board_cache
WHERE site_id = 1
  AND ABS(confirmed_revenue - (occurrence - discount)) > 0.01;

-- 4. 验证当期周期不在缓存中
SELECT * FROM dws.dws_finance_board_cache
WHERE time_range IN ('month', 'week', 'quarter');
```

## 可回溯性

| 项目 | 说明 |
|------|------|
| 可回溯 | ✅ 完全可回溯（upsert 幂等） |
| 数据范围 | 取决于 dws_finance_area_daily 的数据范围 |
| 依赖表 | dws_finance_area_daily |
| 回填脚本 | `scripts/ops/backfill_finance_area_daily.py`（阶段 2 自动重算缓存） |
