汇总大表「生成数据是否正确」验证方法(2026-10-12)
用途:回答"程序生成的数据对不对"。
一句话原则:每一段都要有一个**独立于该段的参照**——两边同源,就验不出东西。
本方法是 2026-10-12 用两组真实数据跑出来并修正过的(见第七节)。
一、先把问题拆开(否则"正确"没有定义)
"程序生成的数据是否正确"实际是 4 个不同的问题,参照和责任人都不同:
| # |
问题 |
参照(必须独立于被验对象) |
谁负责 |
程序能自证吗 |
| 1 |
源数据对不对 |
源文件 / 省里下发表模板 |
数据、业务 |
不能 |
| 2 |
算法、口径对不对 |
手写 SQL 复算(不看母版、不看定稿) |
程序 |
能 |
| 3 |
写回、版式对不对 |
上一期母版(同源对比,要求"不该动的格未变") |
程序 |
能 |
| 4 |
与人工定稿一致吗 |
手工定稿(必须记版本 + sha256) |
双方复核 |
部分 |
所以单问"数据对不对"没有答案;先问"**哪一段**"。
二、五段链条与各自的验证手段(含失败模式)
L0 源文件 → 库
- 参照:源文件本身。
- 手段:行数(
import_batch 的 total/success/fail vs 表内 COUNT(*));逐行抽查(每市州 1~2 条 + 边界:最大、最小、缺报市州);单位核对(吨 vs 万吨);合计勾稽(Σ17 市州 = 源表"湖北省"行)。
- 典型失败:某月漏导;列错位;单位差 10000;重复导入叠加("某月逐市州值与相邻月完全相同"是重复导入的指纹)。
- 工具:
_compare_freight.py 那类"文件 vs 库"脚本;import_batch。
L1 库 → 口径值(**最值得投入的一层**)
- 参照:把口径写成 SQL/脚本**自己算一遍**,不依赖母版、不依赖定稿。
- 手段:① 异常体检
GET /api/report/summaryWorkbook/freightCheck?period=YYYY-MM(倒减为负、①漏报、Σ市州≠省行、环比异常、累计勾稽);② 缺数清单(目标期该有的月份/市州是否齐全);③ 锚点复算(全省 + 抽 2 个市州;覆盖 规上/规下/合计/周转量/中口径四维/城市内)。
- 典型失败:规下倒减为负或虚高;全省行与 Σ市州不闭合;来源表缺期。
- 注意:**体检通过 ≠ 数据齐全**。2026-08 体检 0 命中,但 9 月生成件的 8 月列仍空、9 月周转量仍为 0 → 异常清单与缺数清单必须**分开输出**。
L2 口径值 → 单元格(写回层)
- 参照:上一期母版 / 上一期生成件。
- 手段:逐格 diff,**值模式与公式模式各跑一遍**;目标月列是否真的写进去了;上期及以前列是否 0 变化。
- 典型失败:当月列没写(本次实证:货运、网约车只回填"目标月",母版缺上月就补不上);上期列被重算冲掉;公式断链。
L3 单元格 → 整本(出表层)
- 参照:手工定稿(**必须记录版本 + sha256**,只在定稿未滞后时有效)。
- 手段:
python docs\工具\汇总大表差异比对.py <生成件> <手工定稿> <输出前缀>;只判读 实质数值差异 / 需处理(非数值文本) 两类;差异按预登记白名单分类。
- 典型失败:把"定稿滞后"当成"程序回归"(本次:定稿是 9/13 版,缺 09-28 之后的手工改动 → 多出 364 条手工侧错误值 + 10 条已确认项)。
L4 跨页合成页勾稽(**最容易被漏**)
- 参照:勾稽式本身。
- 手段:
城市汇总、城市分市州、中口径明细、道路运输周转量 每页给一条勾稽式自检,例:道路运输周转量 B = G(货运页规上+规下周转量) + L/2(城市客运量) + Q/10(公路旅客周转量)。
- 典型失败:源页都对、合成页引用错列。本次 2025-03/04 网约车拆分的影响面,就是靠"排名页 vs 明细页两条链路对不上"暴露的。
三、反同源盲区(**最重要的一条**)
今日实证:**"生成件 vs 手工定稿"这一层,在母版与定稿同源时,源数据本身的错永远查不出来。**
- 2025-11 货运宜昌周转量差 34.30(2 格)、班线包车个体周转量差(5 格),在"生成件 vs 定稿"里是 0 条,只有"**库现算 vs 母版缓存**"才暴露。
- 所以:**必须专门做一次"库内值 vs 母版缓存值"的全量 diff**——这也正是 2026-10-09 交接列为"需先做"的待办,是"能不能切读库"的唯一前置条件。
- 措辞要分清:**出表口径**上"库不能替代母版缓存"(F8/Q1);**验证方法**上必须专门比一次。两者不矛盾。
四、操作顺序(照抄即可)
- 冻结:记 母版 sha256、关键源表
max(report_period) 与行数、后端进程 PID + 启动时间。
("判断是否换了新进程"必须看 netstat -ano 的真实监听 PID + 日志启动行,不能只信启停脚本输出。)
- 异常体检 + 缺数清单(**分两份输出**)。
- 口径复算锚点(全省 + 2 市州;至少覆盖 规上/规下/合计/周转量/中口径/城市内)。
- 版式自检:当月列已写;上期及以前列 0 变化;
#REF! 数不增;标题期次 = 目标期;累计 = 1..M。
- 与定稿逐格 diff(只看两类);差异按白名单分类。
- 合成页勾稽(L4)。
- 写结论:通过项 / 未通过项 / 未覆盖项(明确说清哪几层没验)。
五、通过标准(建议写死)
- L0:行数与勾稽一致(0 差异)。
- L1:体检 0 命中 + 缺数清单为空(或缺数已确认为业务事实)+ 锚点复算 0 差异。
- L2:上期及以前列 0 变化;当月列写入格数 = 预期。
- L3:定稿 diff 的"实质数值差异"只含白名单项。
- L4:勾稽式 0 差异。
- 任一层未覆盖 → 结论只能写"未覆盖",不得写"已验证"。
六、三条纪律
- 独立参照:同源的两种产物互比等于没验。
- 一次只换一个变量:换代码 / 换数据 / 换母版分开验,否则差异无法归因。
- 差异先分类再结论:结构/格式/浮点尾差只计数;只有"实质数值差异 + 需处理"进清单。白名单(
USER_EDITS、已确认项)预登记,不要每次重新判读。
七、本方法的实证(2026-10-12)
- 2026-08 生成件 vs 9/13 手工定稿:1020 条明细里只有 11 条要关注(10 条货运 2022 累计同比【业务已确认软件对】+ 1 条手工侧脏字符),其余全是约定差异(结构/草稿列/文本型数字 354/取整尾差/手工侧错误值 364)→ 算法层通过。
- 库内 2025 vs 母版 2025:公交、出租、轨道轮渡 100% 一致;货运、班线包车除个别格一致;查出 3 处需业务确认:
- 网约车 2025-03/04「其中城市内」口径 60 格(另有 12 月 12 格小差)——2026-09-21 已定位(母版用了另一份旧的出租城市内输入);
- 货运 宜昌 2025-11 周转量 2 格(差 34.30);
- 班线包车 2025-11 个体旅客周转量 5 格。
后两处为**源数据层新发现**,此前无任何记录(因为历史核对都在同源的"生成件 vs 定稿"层)。