# 2026 年 4 月数据导入物化全流程复盘与 5 月数据接入优化方案

> 复核日期：2026-08-01  
> 复核范围：本应用、PostgreSQL `bill_query`、4 月历史导入记录、5 月数据目录  
> 5 月数据目录：`/Users/freedak/Documents/AIDashboard/西部马华数据分析/5月/`  
> 5 月基础账单目录：`/Users/freedak/Documents/AIDashboard/西部马华数据分析/5月/营业数据/账单查询-菜品销售明细表/`  
> 本阶段边界：只输出方案，不修改应用代码，不写入 5 月数据，不刷新现有物化视图。  
> 月份口径：4 月为 `[2026-04-01, 2026-05-01)`；5 月为 `[2026-05-01, 2026-06-01)`。

---

## 一、执行结论

### 1. 当前不能直接按 4 月方式追加并发布 5 月数据

5 月目录已经具备菜品销售明细、库存倒挤成本、菜品成本、营业费用、中央厨房、配送、采购和薪资等文件，但尚不满足完整经营看板发布条件，主要原因如下：

1. **5 月基础账单数据已经确认位于 `营业数据/账单查询-菜品销售明细表/`。** 该目录有 57 个 Excel、约 523 MB，每个文件为 20 列账单—菜品行明细，包含门店、账单号、人数、开结账时间、菜品、销量、金额和实收金额。它可以生成 5 月基础账单事实和菜品事实，但并非 4 月 196 列主账单的同构文件，不含会员、支付方式、营销方案、收银员、账单状态及理论成本等扩展字段。
2. **5 月考勤仅有 10 条员工记录。** 4 月数据库有 3,556 条考勤记录，5 月文件明显只覆盖少量总部人员，不能用于门店排班和人效分析。
3. **现有薪资导入脚本按固定列号读取。** 5 月薪资表的组织层级、姓名、工号和身份证等列位置已经变化，直接执行会造成字段错位。
4. **5 月营业费用虽然仍为 213 列，但费用科目集合发生变化。** 文件中出现备用金和维修费手工报销，部分 4 月科目未出现，且合计行位置变化，不能只按行号复用。
5. **现有物化脚本存在全表聚合和固定 4 月对象。** 如果先追加 5 月原始数据，再执行旧物化脚本，多个页面会把 4 月、5 月及边界日期混合。

### 2. 5 月当前状态应定义为“可预检、不可正式发布”

| 状态 | 数据域 |
|---|---|
| 可进入预检，待月度框架完成后导入 | 账单菜品明细、库存倒挤成本、菜品成本、中央厨房、配送、采购货品明细 |
| 需调整规则后导入 | 营业费用、薪资、采购订货单 |
| 阻断基础经营版发布 | 57 个账单菜品明细分片未完成全量预检、入库和财务收入对账 |
| 阻断排班和人效模块发布 | 完整考勤缺失 |
| 阻断完整功能版发布 | 会员、支付、营销、收银和账单状态等扩展字段来源未补齐 |
| 只能作为补充数据 | 正餐事业部全渠道订单明细、正餐事业部品项销售明细 |

### 3. 核心建议

- 保留现有 4 月数据和物化对象作为只读基线。
- 在任何 5 月正式导入前，先建立月份隔离、文件哈希幂等、批次回滚和发布状态。
- 月度经营 API 必须显式接收 `month`，默认读取“最后一个已发布月份”，不能自动读取最新导入批次。
- 传统全量物化视图逐步改为带 `month_start` 的月度汇总表；每次只重算目标月份。
- 账单菜品明细未完成全量导入和财务对账前，5 月基础经营看板应显示“数据未发布”；会员、支付、营销等无来源模块应显示“本月数据不可用”，不能用 0 替代。

---

## 二、4 月现有数据链路复盘

### 1. 全流程结构

```mermaid
flowchart LR
    A["Excel / ZIP / XLS 原始文件"] --> B["文件结构与期间检查"]
    B --> C["import_log 导入批次"]
    C --> D["public 原始明细表"]
    D --> E["门店、SKU、原料、成本中心映射"]
    E --> F["analytics 普通视图与标准事实"]
    F --> G["物化视图 / 月度汇总"]
    G --> H["后端 API"]
    H --> I["驾驶舱、分析报告、整改任务"]
    C --> J["行数、金额、日期、重复批次校验"]
    E --> J
    G --> J
```

4 月已经形成了从原始文件到分析页面的完整雏形，但各数据域的批次控制、月份字段和物化方式不完全一致。

### 2. 4 月数据源与数据库现状

| 数据域 | 4 月来源与结构 | 原始层/日志 | 当前数据库实证 |
|---|---|---|---:|
| 主账单 | 17 个 `账单查询0723103743_*.xlsx`，196 个原始字段 | `bill_records`、`bill_columns` | 原始 1,677,990 行；有效账单物化 1,677,989 行 |
| 菜品销售明细 | 56 个 Excel，20 个业务字段 | `dish_sales_details`、`dish_sales_import_log` | 5,553,314 行 |
| 库存倒挤成本 | `4.13盘点倒挤成本报表2026年4月明细.xlsx`，35 列 | `inventory_cost_records`、`inventory_cost_import_log` | 54,973 行 |
| 菜品成本/BOM | `菜品成本分析报表.xlsx`，24 列 | `dish_cost_analysis_summary`、`dish_cost_analysis_material_detail`、导入日志 | 788 条菜品汇总、3,568 条原料明细 |
| 营业费用 | `2026年4月营业费用分析.xls`，213 列 | `operating_expense_records`、导入日志及门店映射 | 3,952 条长表记录；源合计 36,960,829.47 元 |
| 薪资 | 4 月脱敏薪资明细 | `salary_detail_records`、`salary_import_log` | 3,596 行 |
| 考勤 | 4 月脱敏考勤明细 | `attendance_records`、`attendance_import_log` | 3,556 行 |
| 中央厨房按货品耗用 | 4 月按货品实际与理论耗用 | `central_kitchen_material_daily` | 3,329 行 |
| 中央厨房按配方耗用 | 4 月按配方实际与理论耗用 | `central_kitchen_recipe_consumption` | 4,842 行 |
| 中央厨房完工入库 | 4 月完工入库统计 | `central_kitchen_finished_receipt` | 1,708 行 |
| 中央厨房加工单价 | 4 月加工单价 | `central_kitchen_processing_cost` | 157 行 |
| 配送明细 | 4 月 1—15 日、16—30 日两个文件 | `distribution_detail_records`、`distribution_import_log` | 325,922 行 |

### 3. 4 月主账单标准化过程

4 月主账单没有直接改造成业务字段表，而是先原样保存：

- `bill_records` 保留 `c001` 至 `c196`。
- `bill_columns` 保存 Excel 列号、分组表头和子表头。
- 每条原始记录保留 `source_file` 和 `source_row`。
- `analytics.bill_fact` 将主要文本列转换为门店、账单号、金额、会员、渠道、时间和理论成本等类型化字段。

当前 `analytics.bill_fact` 的日期范围实际为：

| 月份 | 有效账单数 | 实收金额 |
|---|---:|---:|
| 2026 年 3 月 | 3 | 48.00 |
| 2026 年 4 月 | 1,669,925 | 56,156,726.95 |
| 2026 年 5 月 | 8,061 | 210,378.43 |

这说明 4 月源文件本身已经包含 3 月和 5 月 1 日的边界数据。任何月度分析都必须按完整半开区间过滤，不能把整张 `bill_fact` 直接视为 4 月。

### 4. 4 月菜品销售标准化过程

菜品明细已经保存为明确字段，包括门店、账单号、开台/结账/下单时间、菜品名称、分类、销量、金额和实收金额。

当前明细日期范围同样跨越边界：

| 月份 | 菜品明细行数 | 菜品实收金额 |
|---|---:|---:|
| 2026 年 3 月 | 4 | 48.00 |
| 2026 年 4 月 | 5,524,594 | 56,150,168.18 |
| 2026 年 5 月 | 28,716 | 210,365.43 |

严格 4 月口径下，主账单实收与菜品明细实收相差 6,558.77 元，约为主账单实收的 0.012%。该差异应作为月度对账项目保留，不应默认两者完全一致。

### 5. 4 月映射和分析层

现有分析层主要完成了以下映射与聚合：

- 门店编码、门店名称和费用成本中心映射。
- 菜品名称、SKU 分类和 ABC 分类。
- BOM 原料与库存货品名称匹配。
- 理论成本与库存倒挤成本对比。
- 营业费用科目汇总及门店贡献利润估算。
- 会员、复购、餐段、渠道、异常账单和平台经济性分析。
- 中央厨房、配送、BOM 穿透及供应链对账。

应用页面和 API 主要读取 `analytics` 层，方向正确；问题在于部分分析视图仍然聚合所有月份，部分接口又写死 4 月。

### 6. 现有物化处理

数据库目前有 16 个已填充物化视图，另有 `dish_pair_summary_april` 作为普通表保存固定 4 月结果。

主要物化对象分为三类：

1. **全量事实物化**
   - `analytics.bill_fact`
   - 包含全部有效原始账单，没有 `month_start` 列，也没有在物化时限制月份。

2. **固定 4 月商品和门店物化**
   - `dish_sales_april`
   - `dish_basket_april`
   - `dish_sku_summary_april`
   - `dish_category_summary_april`
   - `dish_store_summary_april`
   - `dish_store_sku_april`
   - `dish_member_sku_april`
   - `dish_pair_summary_april`
   - `mv_store_action_priority_deep_april`
   - `mv_store_theoretical_actual_cost_april`

3. **名称不带月份但实际聚合全部原始数据的物化**
   - `v_store_platform_economics`
   - `v_store_category_mix`
   - `v_store_benchmark`
   - `v_store_action_list`
   - `v_store_execution_priority`
   - `mv_store_scorecard`

`server/sql/10_materialize_slow_views.sql` 中的平台、品类和门店基准物化直接读取全部 `bill_records`，没有月份过滤；后续组合视图又将复购月份固定为 `2026-04-01`。因此该脚本不能用于 5 月直接刷新。

### 7. 当前刷新审计状态

应用已经存在 `analytics.data_refresh_log`，字段包括刷新月份、动作、状态、详情、操作人和时间，但当前没有刷新记录。

这意味着目前无法仅依靠数据库回答：

- 某个月何时完成导入；
- 哪些数据域成功或失败；
- 哪些物化对象被刷新；
- 刷新前后行数和金额是否一致；
- 出现问题时应回滚哪个批次。

---

## 三、现有流程的主要风险

| 编号 | 风险 | 实证 | 可能后果 | 优化方向 |
|---|---|---|---|---|
| P0-01 | 主事实和多张视图缺少月份隔离 | `bill_fact`、门店评分和平台物化聚合全量数据 | 追加 5 月后污染 4 月指标 | 每条事实和汇总均带 `month_start`，API 强制传月份 |
| P0-02 | 旧物化脚本不能按目标月份刷新 | `10_materialize_slow_views.sql` 直接聚合全部原始表 | 4、5 月混合，页面利润和排名失真 | 停止用旧脚本刷新新增月份，改成月度汇总框架 |
| P0-03 | 大量对象固定 `_april` | 商品、成本、门店优先级等对象固定 4 月 | 每月复制对象，维护成本不断上升 | 使用同一对象和 `month_start` 维度 |
| P0-04 | `latest` 视图按最大导入批次切换 | 菜品成本/BOM使用 `max(import_id)` | 导入 5 月后，4 月页面自动显示 5 月成本 | 业务查询必须按 `report_month`，不能按最新批次 |
| P0-05 | 主账单缺少统一导入日志和文件哈希 | `bill_records` 只有源文件和源行号 | 重复文件难以识别，批次难以回滚 | 建立统一 `import_batch` 和 SHA-256 唯一约束 |
| P0-06 | 薪资、考勤导入写死 4 月和列号 | 脚本固定 `report_month='2026-04-01'` 并按数组下标取值 | 5 月整列错位，人员和金额失真 | 按规范化表头名称映射，月份由参数传入 |
| P0-07 | 部分 API 写死 4 月或读取全部人事数据 | 费用接口固定 `2026-04-01`，排班接口直接读全表 | 月份选择无效，新增月后混算 | 所有 API 使用统一月份解析器和发布状态 |
| P1-01 | 标准事实表未成为主链路 | `fact_bill`、`fact_bill_item`、`fact_employee_shift`、`fact_purchase_receipt` 均为空 | 各页面继续依赖历史宽表和临时视图 | 分阶段迁移到标准事实模型 |
| P1-02 | 导入日志字段不统一 | 有的有哈希、有的只有文件名；金额对账字段不一致 | 难以形成统一月结审计 | 统一批次状态、源行数、金额、日期和校验结果 |
| P1-03 | 薪资文件包含个人敏感信息 | 5 月新增姓名、身份证等字段 | 数据泄露和越权使用风险 | 敏感列进入受限区，分析层仅保留脱敏员工标识 |
| P1-04 | 缺失数据可能被当作 0 | 当前部分利润接口使用 `COALESCE` | 不完整数据被展示为盈利 | 缺失成本或费用时返回 `NULL` 和覆盖率状态 |

---

## 四、5 月文件盘点与兼容性判断

### 1. 营业与销售数据

| 文件/数据域 | 文件实证 | 与 4 月兼容性 | 当前建议 |
|---|---|---|---|
| 账单查询—菜品销售明细 | 已确认目录内有 57 个 Excel、约 523 MB；20 个字段；文件头明确为 2026-05-01 至 2026-05-31 | 高 | 作为 5 月基础账单和菜品明细的正式来源；一次解析同时生成账单事实与菜品事实，避免重复导入 |
| 账单扩展字段 | 当前 20 列文件不含会员、支付方式、营销方案、收银员、账单状态和理论成本等字段 | 与 4 月 196 列主账单不完全兼容 | 不阻断基础营收/SKU版本，但对应会员、支付、营销和账单风险模块标记为不可用 |
| 正餐事业部全渠道订单明细 | 工作表 13,595 行，其中约 13,592 条数据；51 列；仅 9 家门店；订单收入合计约 14,947,586.52 元 | 低 | 仅作为正餐事业部补充订单域，不能替代主账单 |
| 正餐事业部品项销售明细 | 工作表 104,113 行，其中约 104,110 条数据；50 列；仅 9 家门店；品项收入合计约 14,286,930.30 元 | 低 | 单独建立正餐事业部补充事实，不能替代全公司菜品明细 |
| 5 月菜品成本分析 | 工作表 4,146 行、24 列，层级表头与 4 月一致 | 高 | 可导入，但导入日志必须补齐 5 月期间，不能继续使用 `latest` |

5 月 20 列账单菜品明细可生成的基础指标包括：

- 按 `门店编码 + 账单号` 去重后的账单数；
- 菜品金额、菜品实收、折让额和折让率；
- 门店实收、客单价和客均消费；
- 堂食/外卖等业务类型；
- 开台、结账和下单时间，可派生日、星期、小时和餐段；
- 菜品、品类、出品部门、销量、单价、实收和搭售。

生成账单事实时，人数、开结账时间等账单级字段不能在菜品行上直接求和。应先按 `门店编码 + 账单号` 聚合：人数取同一账单一致值或最大值，开台时间取最小值，结账时间取最大值，金额和实收按菜品行求和。

当前文件不能直接生成会员复购、支付渠道、营销方案效果、收银员风险、账单状态、平台佣金和理论毛利等指标。这些模块应返回“字段来源未提供”，而不是显示 0。

### 2. 成本、费用和人事数据

| 文件/数据域 | 文件实证 | 兼容性问题 | 当前建议 |
|---|---|---|---|
| 盘点倒挤成本 | 工作表 56,538 行、35 列，期间为 5 月 1—31 日 | 结构基本一致 | 可按 5 月批次导入，导入后核对成本单位、总成本和负倒挤 |
| 5 月营业费用 | 18 行、213 列；合计 48,775,884.64 元 | 成本单位列结构相同，但科目行集合和合计行位置变化 | 不能按固定行号导入；必须按科目编码识别并对账 |
| 5 月管理费用 | 16 行、213 列；合计 5,754,540.18 元 | 与门店营业费用是不同利润层级 | 单独进入总部/管理费用域，不得直接追加到门店营业费用表 |
| 5 月薪资 | 工作表 3,529 行、98 列；组织层级改为三级成本中心，并新增姓名、身份证等字段 | 当前脚本假定 99 个固定位置字段、8 级组织，工号位置已变化 | 先改为表头映射；敏感列受限存储；未改前禁止导入 |
| 5 月考勤 | 工作表 11 行、35 列，即 10 条人员记录；新增姓名列并包含 31 天 | 4 月有 3,556 条；当前脚本只处理 30 天且列位不同 | 判定为不完整文件，等待全量考勤；不得用于全公司排班 |

5 月营业费用不能简单视为与 4 月完全一致。直接对比可见：

- 4 月营业费用为 21 行、213 列；5 月为 18 行、213 列。
- 5 月合计行不在文件末尾。
- 5 月出现 `11901 备用金`、`50384 维修费手工报销`。
- 4 月存在的刷卡手续费、清洗费、烟道清洗、自送快递费、外卖佣金等科目未在当前 5 月文件中出现。

因此必须先由财务确认 5 月文件的科目范围、缺失科目是否转入其他报表，以及 48,775,884.64 元是否为完整门店营业费用口径。

### 3. 中央厨房、配送和采购数据

| 文件/数据域 | 工作表规模 | 与 4 月兼容性 | 当前建议 |
|---|---:|---|---|
| 按货品实际与理论耗用 | 3,396 行、33 列 | 高 | 可按 5 月批次导入 |
| 按配方实际与理论耗用 | 4,948 行、42 列 | 高 | 可按 5 月批次导入 |
| 完工入库统计 | 1,784 行、43 列 | 高 | 可按 5 月批次导入 |
| 加工单价 | 179 行、37 列 | 高 | 可按 5 月批次导入 |
| 配送明细 1—15 日 | 165,272 行、76 列 | 高 | 与下半月共同组成 5 月完整配送批次 |
| 配送明细 16—31 日 | 178,949 行、76 列 | 高 | 导入后两文件业务日期必须无重叠、无缺日 |
| 采购货品明细 | 30,555 行、63 列 | 中高 | 可作为采购收货事实来源，填充当前为空的采购事实表 |
| 采购订货单 ZIP | 71 个 `.xls` 文件 | 文件名存在乱码，且包含 4 月 10 日和 6 月 1 日文件 | 只能按单据业务日期筛选 5 月，不得按压缩包名称整包导入 |

采购货品明细中的部分制单时间和生产时间可能早于 5 月，但业务日期和实际到货日期属于 5 月。这类单据应以业务口径字段确定月份，同时保留制单、审核、到货和生产时间，不能只用单一时间字段。

### 4. 5 月数据就绪度

| 等级 | 数据域 | 含义 |
|---|---|---|
| 绿色 | 账单菜品明细、库存成本、菜品成本、中央厨房、配送、采购货品明细 | 文件结构可进入月度预检和暂存 |
| 黄色 | 营业费用、管理费用、薪资、采购订货单、正餐事业部补充报表 | 需先确认范围或调整映射规则 |
| 红色 | 完整考勤 | 严重不完整，阻断全公司排班和人效模块发布 |
| 功能受限 | 会员、支付、营销、收银、账单状态等扩展字段 | 不阻断基础经营版，阻断相应扩展模块和与 4 月完全同口径对比 |

---

## 五、目标月度数据架构

### 1. 分层原则

| 层级 | 目标 | 关键要求 |
|---|---|---|
| 文件登记层 | 登记每个文件和压缩包成员 | 文件哈希、数据域、月份、期间、大小、状态、来源人 |
| 暂存层 `staging` | 原样解析，尚不影响正式数据 | 保留原始值、源文件、源行、批次号和解析错误 |
| 原始层 `public` | 保存可追溯的标准字段 | 每条记录必须有 `batch_id`、`report_month` 或业务日期 |
| 标准事实层 `analytics.fact_*` | 跨月统一业务模型 | 统一门店、SKU、原料、供应商、员工和单据主键 |
| 月度汇总层 `analytics.mart_*_monthly` | 支撑页面快速查询 | 每条汇总必须带 `month_start`，按目标月份可重算 |
| 发布层 | 控制 API 可见月份和功能范围 | 基础指标通过后标记 `published_basic`，扩展指标全部通过后标记 `published_full` |

### 2. 统一导入批次

建议建立统一批次登记概念，至少包含：

| 字段 | 用途 |
|---|---|
| `batch_id` | 唯一导入批次 |
| `dataset_type` | 账单菜品明细、账单扩展、库存、费用、薪资、考勤等 |
| `report_month` | 归属月份 |
| `period_start`、`period_end` | 文件实际覆盖日期 |
| `source_file` | 原文件名或 ZIP 成员名 |
| `file_sha256` | 防重复导入 |
| `source_rows`、`imported_rows`、`rejected_rows` | 行数审计 |
| `source_amount`、`loaded_amount` | 金额对账 |
| `status` | `discovered/validated/staged/loaded/reconciled/published_basic/published_full/blocked/rolled_back` |
| `error_message` | 失败原因 |
| `supersedes_batch_id` | 替代旧批次时保留关系 |
| `imported_at`、`completed_at` | 执行时间 |

### 3. 幂等规则

1. 同一 `dataset_type + report_month + file_sha256` 再次执行时直接跳过。
2. 同一月份收到修订版文件时，不在正式表无条件追加。
3. 修订版先进入新批次，预检通过后在单个事务内替换目标月份或旧批次。
4. 删除和回滚必须只针对明确 `batch_id`，不得按模糊文件名或整个表清空。
5. ZIP 文件既登记压缩包哈希，也登记每个成员文件哈希。

### 4. 月份确定规则

- 月份优先由业务日期字段计算，而不是仅信任文件名。
- 所有记录统一计算：`month_start = date_trunc('month', business_date)::date`。
- 文件内出现目标月以外数据时进入异常清单：
  - 可解释的制单、生产时间不影响归属；
  - 业务日期跨月的记录必须拆分到实际月份；
  - 无法确定业务日期的文件不能发布。
- 月度查询统一使用：

```sql
business_date >= :month_start
AND business_date < (:month_start + INTERVAL '1 month')
```

### 5. 不再使用“最新批次”代表业务月份

`max(import_id)` 只能用于技术监控，不能用于 4 月或 5 月经营查询。

正确方式应为：

- 页面传入 `month=2026-04` 或 `month=2026-05`；
- 后端转换为 `month_start`；
- 查询同月份事实和汇总；
- 默认月份为“最后一个已发布月份”，不是最后一个上传文件的月份。

---

## 六、物化与刷新优化方案

### 1. 推荐使用“月度汇总表”代替大量固定月份物化视图

PostgreSQL 物化视图不能带运行时月份参数。如果继续使用 `dish_sales_may`、`dish_sales_june` 等对象，每个月都会新增一组对象，后续维护和 API 路由会越来越复杂。

推荐建立带月份字段的汇总表，例如：

- 门店月度经营汇总；
- 门店日度经营汇总；
- SKU 月度汇总；
- 门店—SKU 月度汇总；
- 品类月度汇总；
- 会员复购月度汇总；
- 理论与实际成本月度汇总；
- 费用和门店贡献月度汇总；
- 中央厨房、配送和采购月度汇总。

每次刷新目标月份时：

1. 锁定目标月份刷新任务；
2. 在事务内删除该月份旧汇总；
3. 从标准事实重算该月份；
4. 写入新汇总；
5. 执行校验；
6. 通过后提交并登记刷新日志；
7. 失败则整体回滚，不影响 4 月。

### 2. 如短期仍保留物化视图

短期可以建立包含全部月份的物化视图，但必须：

- 在结果中保留 `month_start`；
- 所有分组包含 `month_start`；
- 建立 `(month_start, store_code)`、`(month_start, sku_code)` 等唯一索引；
- API 查询必须传月份；
- 使用 `REFRESH MATERIALIZED VIEW CONCURRENTLY` 时先满足唯一索引条件；
- 评估全量刷新耗时，数据量增大后迁移到月度汇总表。

### 3. 5 月正式刷新顺序

```text
文件预检
  → 导入批次登记
  → 暂存层
  → 维度映射（门店/SKU/原料/供应商/员工）
  → 账单菜品明细一次解析
  → 账单基础事实 + 菜品销售事实
  → 可选账单扩展事实
  → 库存、采购、配送、中央厨房事实
  → 费用、薪资、考勤事实
  → 日度/月度汇总
  → 跨表对账
  → 发布状态
  → API 缓存和整改任务
```

57 个账单菜品明细分片是 5 月基础经营分析的主锚点，应一次解析后同时生成账单基础事实和菜品事实。账单数必须按 `门店编码 + 账单号` 去重，人数等账单级字段必须先去重再汇总。会员、支付、营销等扩展事实可以后补，但相关模块在补齐前只能标记为功能受限。

### 4. 现有 4 月对象的过渡策略

- 暂不删除或刷新 `_april` 对象。
- 5 月框架验证期间，4 月页面继续读取现有 4 月基线。
- 新月度汇总完成后，用 4 月原始事实回算一遍新框架。
- 新框架的 4 月核心指标与当前严格 4 月基线一致后，再切换 API。
- 切换后保留旧对象一个观察周期，确认无回退需求再清理。

---

## 七、5 月实施步骤

### 阶段 0：冻结 4 月基线

正式改造前记录以下只读基线：

| 检查项 | 4 月基线 |
|---|---:|
| 主账单有效账单数 | 1,669,925 |
| 主账单实收 | 56,156,726.95 元 |
| 菜品明细行数 | 5,524,594 |
| 菜品明细实收 | 56,150,168.18 元 |
| 库存成本明细 | 54,973 行 |
| 营业费用长表 | 3,952 行 |
| 营业费用源合计 | 36,960,829.47 元 |
| 薪资明细 | 3,596 行 |
| 考勤明细 | 3,556 行 |

同时记录现有物化对象行数、更新时间和 API 返回值，作为回归测试基准。

### 阶段 1：确认发布范围并补齐阻断数据

按用户补充信息，`账单查询-菜品销售明细表/` 作为 5 月基础账单来源。实施前还需完成：

1. 核对 57 个分片是否覆盖全部应营业门店、全部 31 天和完整账单号范围。
2. 明确本次先发布“基础经营版”，还是要求与 4 月全部模块完全同口径；若要求完整版，需补充会员、支付、营销、收银、账单状态等扩展字段来源。
3. 补充完整 5 月考勤，覆盖门店和总部全部应统计员工。
4. 财务确认 5 月营业费用科目范围，解释缺少的 4 月科目及新增科目。
5. 明确 5 月管理费用是否用于总部损益桥，以及分摊规则。

### 阶段 2：完成导入前预检

每个文件至少检查：

- 文件 SHA-256；
- 文件名和内部期间；
- 工作表名称；
- 表头数量和规范化表头；
- 数据行数；
- 日期最小值和最大值；
- 门店、科目、SKU、原料、供应商数量；
- 金额合计；
- 重复主键和空主键；
- 是否包含目标月份以外业务日期；
- 是否包含个人敏感信息。

预检结果应生成机器可读日志和人工可读报告。任何红色检查失败时，只允许进入暂存层，不得进入正式事实层。

### 阶段 3：按风险顺序导入

建议顺序：

1. 维度和映射变更；
2. 账单菜品明细一次解析，生成账单基础事实和菜品销售事实；
3. 可选账单扩展数据；
4. 库存倒挤成本；
5. 菜品成本/BOM；
6. 采购货品明细；
7. 配送明细；
8. 中央厨房；
9. 营业费用；
10. 管理费用；
11. 薪资；
12. 考勤；
13. 正餐事业部补充订单和品项报表。

正餐事业部补充报表必须使用独立数据域标识，避免与全公司账单、菜品明细重复累计。

### 阶段 4：刷新和发布

基础经营版只有下列条件全部满足，才将 5 月状态改为 `published_basic`：

- 必需文件齐全；
- 所有必需批次状态为 `reconciled`；
- 57 个账单菜品分片日期、文件编号和门店覆盖完整；
- 由菜品行生成的账单基础事实与明细金额完全回勾；
- 账单基础实收与财务收入对账通过；
- 成本和费用源表合计对账通过；
- 门店、SKU、原料和成本中心映射达到阈值；
- 无未解释跨月记录；
- 月度汇总与事实表重算一致；
- 4 月回归测试无变化；
- 5 月 API 显示正确月份和数据完整性状态。

会员、支付、营销、收银和账单风险等模块只有在相应扩展字段补齐并对账后，才能将月份升级为 `published_full`。

---

## 八、数据质量和对账闸门

### 1. 文件级闸门

| 检查 | 通过标准 |
|---|---|
| 文件重复 | 同一哈希只登记一次 |
| 表头变化 | 未登记的新表头必须人工确认 |
| 日期范围 | 业务日期只归属目标月份，跨月记录有明确处置 |
| 行数波动 | 相比上月异常下降或增加时需解释 |
| 文件完整性 | ZIP 分片数量连续，无空文件和损坏文件 |

### 2. 账单基础事实与菜品明细对账

至少检查：

- 57 个分片的编号连续性、重复文件和损坏文件；
- 按 `门店编码 + 账单号` 去重后的账单数；
- 账单基础事实的金额、实收与菜品行汇总是否完全一致；
- 同一账单的人数、业务类型和时间字段是否存在冲突；
- 负数、零实收、撤单和退款的口径差异；
- 91 家门店覆盖及新增/闭店说明。

由于 5 月账单基础事实和菜品事实来自同一批 20 列文件，两者内部回勾原则上应为零差异。独立性更强的收入校验应使用财务营业收入；9 家正餐事业部全渠道订单明细只能用于相同门店范围的补充交叉验证。

### 3. 成本与供应链对账

| 对账关系 | 管理问题 |
|---|---|
| 采购货品明细 ↔ 库存购入 | 采购入库是否完整进入库存成本 |
| 配送出库 ↔ 门店库存购入 | 总仓配送与门店收货是否一致 |
| 中央厨房完工入库 ↔ 加工成本 | 半成品产量与单位加工成本是否匹配 |
| 按配方理论耗用 ↔ 按货品实际耗用 | 用量、出成率和损耗差异 |
| BOM 理论成本 ↔ 菜品实际成本 | 菜品成本断链和异常损耗 |

### 4. 费用和人工对账

- 营业费用主科目合计必须等于源文件确认合计。
- 工资父科目与前厅/后厨子科目不能重复累计。
- 管理费用不得未经确认直接进入门店贡献利润。
- 薪资总额与费用报表工资科目差异必须解释。
- 考勤员工数、薪资员工数和在职员工数需要建立覆盖率。

### 5. 发布状态展示

每个页面应展示：

- 当前月份；
- 数据状态：完整、部分、阻断、已发布；
- 销售门店数、费用匹配门店数、成本匹配门店数；
- 最后刷新时间；
- 未覆盖数据和影响金额；
- 指标证据等级。

如果 57 个账单菜品分片、成本或费用任一不完整，利润字段应为 `NULL/不可计算`，而不是 0。会员、支付、营销等无字段来源的指标也应返回不可用状态。

---

## 九、API 和页面优化方向

### 1. 统一月份参数

所有经营接口使用统一规则：

- 入参：`month=YYYY-MM`；
- 后端校验合法月份；
- 统一计算月初和下月月初；
- 查询所有相关表时使用相同月份；
- 返回 `month_start`、`data_status`、`last_refresh_at` 和覆盖率。

### 2. 页面不得自动切换到最新导入文件

页面默认读取最后已发布月份。上传 5 月菜品成本但 57 个账单菜品分片尚未完成导入和对账时，4 月页面仍显示 4 月，5 月页面显示“待发布”，不能自动切换到 5 月菜品成本。基础经营版发布后，缺少扩展字段的模块继续显示“本月数据不可用”。

### 3. 利润页面的月份和范围

利润展示至少区分：

- 门店营业贡献：实收－食材成本－门店营业费用；
- 总部管理费用；
- 中央厨房和配送中心损益；
- 未分配费用；
- 财务净利润桥。

5 月管理费用应单列，未确认分摊规则前不得混入门店排名。

### 4. 人事数据隐私

- API 不返回身份证号、手机号等分析不需要的字段。
- 员工使用内部脱敏 ID 连接薪资和考勤。
- 薪资明细仅授权人事和财务角色访问。
- 日志中不记录完整敏感字段。
- 导出文件默认脱敏。

---

## 十、实施优先级

### P0：5 月基础导入及对应模块发布前必须完成

1. 将已确认的 57 个账单菜品明细分片登记为 5 月基础账单源，并完成全量覆盖预检。
2. 统一导入批次、文件哈希和月份字段。
3. 阻止旧物化脚本在追加 5 月后直接刷新。
4. 改造菜品成本 `latest` 口径为按月份查询。
5. 薪资和考勤改为按表头映射。
6. 所有月度 API 使用统一月份边界。
7. 建立 `published_basic` 和 `published_full` 两级发布状态，未通过时不对外显示对应经营指标。
8. 排班和人效模块发布前补齐完整考勤。

### P1：5 月正式发布前完成

1. 建立费用科目白名单和管理费用独立域。
2. 建立账单基础事实—菜品—财务收入—库存—费用对账。
3. 建立门店、SKU、原料、供应商和员工映射质量检查。
4. 将采购货品明细接入采购事实。
5. 使用月度汇总表替代固定 `_april` 对象。
6. 写入并展示数据刷新日志。

### P2：连续月度运营阶段完成

1. 将历史宽表逐步迁移到标准 `fact_*` 模型。
2. 建立可比店、预算、环比和整改收益验证。
3. 建立中央厨房、采购、配送、门店库存的完整成本桥。
4. 建立月度版本、指标版本和数据血缘管理。
5. 对已发布月份实施不可变快照和回归测试。

---

## 十一、5 月最终验收清单

### 文件和批次

- [ ] 账单菜品明细 57 个分片齐全、无重复、无损坏。
- [ ] 57 个分片覆盖全部应营业门店和 5 月 1—31 日。
- [ ] 若要求完整功能版，会员、支付、营销、收银和账单状态等扩展字段来源齐全。
- [ ] 完整考勤覆盖全部应统计员工。
- [ ] 每个文件均有 SHA-256 和批次号。
- [ ] 跨月采购订货单已按业务日期拆分。

### 数据入库

- [ ] 所有正式事实表带月份或业务日期。
- [ ] 所有记录可追溯到源文件和源行。
- [ ] 薪资字段按表头映射，无列错位。
- [ ] 身份证、手机号等敏感字段受限或脱敏。
- [ ] 正餐事业部补充报表未与全公司数据重复累计。

### 对账

- [ ] 账单基础事实与菜品行金额完全回勾。
- [ ] 账单基础实收与财务收入对账。
- [ ] 库存成本与财务成本报表对账。
- [ ] 营业费用与源表合计对账。
- [ ] 薪资与工资费用科目对账。
- [ ] 采购、配送、库存三方对账。

### 物化和页面

- [ ] 5 月汇总仅包含 `[2026-05-01, 2026-06-01)`。
- [ ] 4 月核心指标与冻结基线一致。
- [ ] API 支持显式月份并返回数据状态。
- [ ] 5 月未完整时页面不显示虚假 0 值或完整利润。
- [ ] 刷新日志记录所有对象、耗时、结果和校验差异。
- [ ] 失败批次可以按 `batch_id` 回滚。

---

## 十二、最终建议

5 月数据的正确处理方式不是复制一套 `_may` 视图，也不是直接把文件追加到现有原始表后刷新全部物化视图。推荐顺序是：

```text
先确认基础版与完整版发布范围
→ 建立月份隔离和统一批次
→ 文件预检
→ 暂存导入
→ 按月写入事实
→ 四项总额和跨表对账
→ 只重算 5 月汇总
→ 4 月回归验证
→ 标记 5 月基础版或完整版已发布
→ 页面开放 5 月分析和整改任务
```

用户补充的 57 个账单菜品明细分片可以作为全公司 5 月基础账单来源。完成月份隔离、全量预检、账单去重、财务收入对账及费用范围确认后，可以发布基础营收、账单数、客单、菜品、品类、堂食/外卖、时段及初步门店贡献分析。会员、支付、营销、收银、账单状态和排班模块仍需相应扩展字段及完整考勤，未补齐前应保持功能受限状态。

---

## 附录：本次只读复核依据

- 应用数据库：PostgreSQL `bill_query`。
- 应用 SQL：`server/sql/10_materialize_slow_views.sql`、历史 4 月分析 SQL。
- 人事导入脚本：`db/import_salary_attendance.py`。
- 4 月全流程手册：`/Users/freedak/Documents/AIDashboard/西部马华数据分析/0马兰拉面数字化运营数据治理与分析全流程操作手册.md`。
- 5 月文件目录：`/Users/freedak/Documents/AIDashboard/西部马华数据分析/5月/`。
- 数据库导入日志：菜品销售、库存成本、菜品成本、营业费用、薪资、考勤、中央厨房和配送导入日志。
- 数据库物化对象：`pg_matviews` 中 `analytics` 模式的现有对象。

本次未执行任何 5 月数据库写入、删除、替换或物化刷新。
