# 汇总大表「生成数据是否正确」验证方法(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);**验证方法**上必须专门比一次。两者不矛盾。 ## 四、操作顺序(照抄即可) 1. **冻结**:记 母版 sha256、关键源表 `max(report_period)` 与行数、后端进程 PID + 启动时间。 ("判断是否换了新进程"必须看 `netstat -ano` 的真实监听 PID + 日志启动行,不能只信启停脚本输出。) 2. 异常体检 + 缺数清单(**分两份输出**)。 3. 口径复算锚点(全省 + 2 市州;至少覆盖 规上/规下/合计/周转量/中口径/城市内)。 4. 版式自检:当月列已写;上期及以前列 0 变化;`#REF!` 数不增;标题期次 = 目标期;累计 = 1..M。 5. 与定稿逐格 diff(只看两类);差异按白名单分类。 6. 合成页勾稽(L4)。 7. 写结论:通过项 / 未通过项 / **未覆盖项**(明确说清哪几层没验)。 ## 五、通过标准(建议写死) - **L0**:行数与勾稽一致(0 差异)。 - **L1**:体检 0 命中 + 缺数清单为空(或缺数已确认为业务事实)+ 锚点复算 0 差异。 - **L2**:上期及以前列 0 变化;当月列写入格数 = 预期。 - **L3**:定稿 diff 的"实质数值差异"只含白名单项。 - **L4**:勾稽式 0 差异。 - **任一层未覆盖 → 结论只能写"未覆盖",不得写"已验证"。** ## 六、三条纪律 1. **独立参照**:同源的两种产物互比等于没验。 2. **一次只换一个变量**:换代码 / 换数据 / 换母版分开验,否则差异无法归因。 3. **差异先分类再结论**:结构/格式/浮点尾差只计数;只有"实质数值差异 + 需处理"进清单。白名单(`USER_EDITS`、已确认项)预登记,不要每次重新判读。 ## 七、本方法的实证(2026-10-12) 1. **2026-08 生成件 vs 9/13 手工定稿**:1020 条明细里只有 11 条要关注(10 条货运 2022 累计同比【业务已确认软件对】+ 1 条手工侧脏字符),其余全是约定差异(结构/草稿列/文本型数字 354/取整尾差/手工侧错误值 364)→ **算法层通过**。 2. **库内 2025 vs 母版 2025**:公交、出租、轨道轮渡 **100% 一致**;货运、班线包车除个别格一致;查出 3 处需业务确认: - 网约车 2025-03/04「其中城市内」口径 60 格(另有 12 月 12 格小差)——2026-09-21 已定位(母版用了另一份旧的出租城市内输入); - 货运 宜昌 2025-11 周转量 2 格(差 34.30); - 班线包车 2025-11 个体旅客周转量 5 格。 后两处为**源数据层新发现**,此前无任何记录(因为历史核对都在同源的"生成件 vs 定稿"层)。