编辑 | blame | 历史 | 原始文档

汇总大表 M0「月度版式重建器」规格(2026-10-06)

状态:**【待验收】本轮只定规格与工作量,不含代码改动**;规格经用户审定后再开工。
依据:docs/汇总大表数据驱动改造方案_2026-09-07.md(含 2026-09-08 附录)、HANDOFF.md 的 2026-09-28 / 09-29 两节、2026-09-29 用户手工产出的 9 月母版。

一、背景与目标

  • 现状:汇总大表的数据驱动回填(方案 M1–M3)已落地——exportSummaryWorkbook 已按「读库计算 → 回填母版 → 整本重算」运行(ReportExportService.java:3634 起,计算层 reportexport/calc)。
  • 唯一卡点:**月度列版式**。程序只按 {year}年{month}月道路运输量汇总表.xlsx 精确匹配母版(ReportExportService.resolveSummaryMother,同文件 4576 行),目标月没有对应母版就直接抛错,不会自动扩列。9 月母版是用户 09-29 在 Excel 里手工扩出来的(本机 docs/生成汇总大表/2026年9月道路运输量汇总表.xlsx、_试做_20260929.xlsx,均未入库)。
  • 目标(M0):给定「一本可用母版 + 目标年月」,程序自动把列版式重建到目标月,产出可直接回填的母版版式,替代每月手工扩列。
  • 出口验收:① 重建后生成 2026-07,数值与已修正 7 月母版逐格 diff=0(仅列位变化);② 生成 2026-09,与用户手工 9 月母版逐格 diff=0。

二、为什么是增量改造而不是重写

层次 现状 结论
计算层 reportexport/calc/{PaxCalc,FreightCalc,MidCalc,WycSplitCalc},约 900 行 直接复用
回填层 SummaryWorkbookFiller(899 行):fillMonthlyLedger / ensureMonthFormulas / ensureMonthYoyFormulas / repairCumulativeYoyFormulas / yearMonthCols / usableMonthCols / cumulativeHelperCols 直接复用
扩列原语 ReportExportService.java:1039-1200:moveColumnPair / cloneColumnPair / cloneCellForMonth / cloneMergedRegion / copyColumnWidth / fixCumulativeFormulas;且已跑通 extendMonthlyColumns(独立小表 1–6 月 → 1–12 月) 直接复用
缺口 把上述原语**编排**到汇总大表 19 个页签里的 10 个长表页,并逐页配置规则 + 回归自检 本次新增

已有投入(不可丢弃):38 个提交、930 格差异核对、A1–E3/Q1–Q3 一轮口径拍板。重写不划算。

三、列版式规则(0 基列号)

项 规则
当月值列 1 月 = 2(C 列);m 月 = 2 + 2 × (m − 1)
当月同比列 当月值列 + 1
累计列 / 累计同比列 2026 组末尾,当前 14/15(O/P);扩到 12 月后为 26/27
2025 及更早参照组 每扩一个月整体右移 2 列
已确认口径(方案 D1) 当月值列+同比列插在累计之前、累计滚动、2025+ 参照组右移——等价于本文件的「月度版式重建器」

SummaryWorkbookFiller.FIRST_MONTH_COL=2(同文件 47 行)与该公式一致,但属硬编码锚点,见第七节第 4 条。

四、需要扩列的 10 个长表页(数字取自方案 v1 附录实测)

页签 maxCol 合并区 需平移的公式数
货运(注意前导空格) 145 140 6638
班线包车 285 204 5688
中口径明细 87 157 6654
公交 86 76 2004
出租 86 76 1872
网约车 86 70 1664
公路总客运 274 194 未单独统计
城市客运 72 55 未单独统计
轨道、轮渡 72 110 未单独统计
中口径客运量 68 111 未单独统计

已知 6 页公式平移合计约 24,520 处(公交 2004 / 出租 1872 / 网约 1664 / 中口径明细 6654 / 货运 6638 / 班线 5688)。

五、不需要扩列的页

  • 6 个成品页(当月块与累计块是行结构、列位固定,直接按映射填值/公式):货运量排名、货运周转量排名、中口径排名、城市汇总、城市分市州、道路运输周转量。
  • 3 个静态页(不动):中口径分析、2025年城市客运量、中口径计算过程。

六、改动文件清单

  • 新增 traffic-audit-server/src/main/java/com/trafficaudit/reportexport/layout/SummaryLayoutRebuilder.java:编排层,按目标月重建 10 个长表页的列布局。
  • 新增 traffic-audit-server/src/main/java/com/trafficaudit/reportexport/layout/SummarySheetRule.java:逐页规则配置(表头行、数据起止行、月份组起点、累计列位、是否派生页、合并区策略)。
  • 新增 traffic-audit-server/src/test/java/com/trafficaudit/reportexport/layout/SummaryLayoutRebuilderTest.java:扩列后 7/9 月数值回归自检。
  • 改造 ReportExportService.exportSummaryWorkbook(同文件 3634 行起):母版缺失时不再抛错,改为「取最近可用母版 → 重建到目标月 → 继续回填」。
  • 改造 ReportExportService.resolveSummaryMother(同文件 4576 行):放开"仅精确匹配"限制,保留"找不到任何蓝本才报错"。
  • 可选(建议做):把 SummaryWorkbookFiller.FIRST_MONTH_COL 硬编码改为按表头 yearMonthCols 语义定位,避免母版挪列后静默写错列。
  • 前端:无需改动(入口与参数不变)。
  • 文档:本规格 + HANDOFF.md 结论;_试做_20260929.xlsx 等过程产物不入库。

七、关键算法

  1. 选蓝本:目标月母版存在则直接用;不存在则取最近可用母版(默认 9 月手工版,见第十节待确认项 1)。
  2. 定位:复用 yearMonthCols 的表头识别(支持文本 2026年8月 与数值日期序列表头,只认今年/去年),在前 5 行内找出各月份组的实际列位——先认位置再动列,不盲信固定列号。
  3. 重建(对每个长表页):移动累计列对到 2026 组末尾 → 按月克隆月份列对(表头文本/日期修正、样式、合并区、列宽、跨页公式列引用同步)→ 修正累计与累计同比公式。
  4. 重算与自检:setFullCalcOnLoad(true) + 整本重算;输出前跑「#REF! 计数 = 0」「累计列覆盖到当期」自检,不通过则中止。

八、验证方案(M0 出口)

编号 用例 预期
1 重建版式后生成 2026-07 与已修正 7 月母版被填区域逐格 diff=0,仅列位变化
2 生成 2026-09 与用户手工 9 月母版逐格 diff=0
3 公式自检 全簿 #REF! = 0;累计/累计同比覆盖到当期
4 版式自检 合并区、列宽、隐藏标记、打印区与手工 9 月母版一致;Excel/WPS 打开无修复告警
5 负向:目标月源数据缺失 给出缺数清单,不静默输出空表
6 负向:母版被改导致月份表头识别不到 明确报错,不猜测列位

九、工作量预估

阶段 预估
规格表定稿(本文件 + 用户审定) 0.5 天
Rebuilder + 逐页规则 + 单测 1.5–2.5 天
7/8 月回归 + 9 月对照核对 1 天
风险缓冲 0.5–1 天
合计 约 3.5–5 天(含测试与验收)

说明:HANDOFF.md 09-29 节曾估「0.5~1 天」,那只是扩列原语的复用估计;按附录实测(约 2.45 万处公式平移 + 2 千余处合并区)重估为 3.5–5 天。

十、风险与待确认

  1. 待确认:以哪本作扩列蓝本。 建议以用户手工的 9 月母版为准(版式已由用户固化)。
  2. ~~待确认:是否一次建成 12 月满版式。~~ 已裁定(2026-10-09):采用 F10「按月按需扩列」、不预留空列;本项作废,见第十三/十四节。
  3. 母版历史坑要一并覆盖:母版由 openpyxl 写出,存在高压缩(已用 ZipSecureFile.setMinInflateRatio 绕过)与残留 <is> 非法格(normalizeStaleInlineStrings 清理)两类问题,逐列克隆时需保持这两处处理。
  4. 城市客运页参照组是压缩结构(同比 + 1-X 月累计),与其它长表页不同,需单独规则。
  5. 跨年开新本(2027)不在 M0 范围,需另定"新年块"规则。
  6. 未覆盖页:中口径分析等 3 个静态页仍沿用母版静态内容,本阶段不改。

    十一、参照同事「专项统计」的设计映射与落地路线(2026-10-08)

11.1 对齐表:他为什么能一个母版,我们对应怎么做

同事专项统计的做法 他为什么能成立 汇总大表的对应做法
一份母版(2026年十一数据.xlsx),**天为行、年为列** 增长维度是「年」,一年加一组列;天数固定 8 按 F10「按月按需扩列」:读 N 月母版自动产出 N+1 月版式(不预留空列),母版只当版式来源
母版同时是数据容器(历史年份的值就在并排年列里) 历史值随模板一起走 反过来做:历史参照值入库,母版只留版式,参照组由 DB 回填
表头/行标签语义定位(findYearColumn / parseDayIndex) 列序变了也能导 新增页规则表 SummarySheetRule:按表头认月份组、按行标签认市州/指标,不用硬编码列锚点
易漂移口径外置成表(holiday_calendar / passenger_coefficient / vehicle_adjustment) 规则不散在代码与单元格里 参照年保留组数、排名口径(全省不排名)、周转量系数 B=G+L/2+Q/10、缺数置空规则 外置
DECIMAL(30,15) + BigDecimal + 按 Excel 显示精度取值 精度口径统一,无浮点尾差 承接长期待办「16 张表 228 个 double 列改 DECIMAL + BigDecimal」
导入两阶段确认 + 全程只读核对 历史数据不被误覆盖 生成前先出缺数/差异清单,人工确认后再出表(现有 B4 基数体检补齐)
输出走模板文件(docx / PNG) 版式来源单一 保留现有做法(快报改 word/document.xml、JFreeChart 出 PNG)

11.2 落地路线(四阶段)

  • 阶段 1(= M0,3.5–5 天):实现**按月按需扩列器**(读 N 月母版 → 产出 N+1 月版式,不预留空列)+ 页规则表(对齐 F10 / D1)。
    新增 SummaryLayoutRebuilder / SummarySheetRule / 单测。
    出口:7 月回归 diff=0;9 月对照手工版 diff=0;10 月起可直接出表。
  • 阶段 2(+1–2 天):把现有三份文件依赖合并为一份。
    现状依赖:月度母版(resolveSummaryMother)+ _备份_ donor(resolveSummaryDonor / restoreSummaryMonthlyBlocks)+ 版式参照件(resolveSummaryLayoutRef)。
    目标:只留《道路运输量汇总表_标准母版.xlsx》,参照值从库回填,停用 donor 与还原机制。
    出口:docs/生成汇总大表/ 下只剩 1 个模板文件 + 输出目录(_bak_* 等本地留存、不入库)。
  • 阶段 3(可选,+2–3 天):母版内用命名区域/占位符标注需填格,程序按名填写;母版被挪列/插行时明确报错。
  • 阶段 4(可选,+5–10 天):把 10 个长表页的版式参数化(列组规则 + 合并区 + 列宽 + 数字格式 + 打印区),程序从版式定义渲染,母版文件降级为一次性样式提取源;
    3 个隐藏公式页(中口径计算过程 / 2025年城市客运量 / 中口径分析)保留文件形态。

11.3 两条不照搬

  1. 不把 Excel 当数据源:同事那边业务每天填同一形状的 Excel 文件;汇总大表数据必须走 DB 导入 + 两阶段确认,不能退回"手工填母版"。
  2. 必须有「新年组」逻辑:他的天数是常数(8 天),跨年只是加一组年列;我们跨年要新增下一年度列组并把当年组降级为参照组,这一步他没有对应实现。

11.4 底线与风险

  • 3 个隐藏公式页依赖母版现成公式,阶段 1–2 必须原样保留,阶段 4 才谈重建。
  • 打印区、页眉页脚、条件格式、列宽必须逐页核对,是"肉眼版式"最易漏的点。
  • 业务改指标行/页签/边框属于需求变更,任何方案都要一次适配;本方案把频率从「每月一次」降到「偶尔一次」。
  • 母版改为只读,可顺带规避 WPS/openpyxl 重存带来的高压缩与残留 <is> 非法格两类历史坑。

    十二、存储层/展示层约定:参照同事「二维表」模式(2026-10-09)

12.1 三层职责

  • 存储层(DB):一张张二维表,键与值明确;存三类内容——原始值、配置值、人工调整值。
  • 计算层:导出时按口径计算(累计、同比、环比、占比、排名)。
  • 展示层(Excel):只把值和公式按版式摆出来,不承担存储职责。

12.2 「一表一粒度」约定(示例取自同事的专项统计)

表 主键(粒度) 值 类别
holiday_passenger_flow 年 + 日期 + 运输方式 客运量 原始值
holiday_vehicle_flow 年 + 日期 + 道路类别 + 车型 车流量、环比基期 原始值
holiday_calendar 年 + 节假日类型 + 第几日 日期 配置值
holiday_passenger_coefficient 年 + 日期类别 客运系数 配置值
holiday_vehicle_adjustment 年 + 日期 调整后值 + 原因 人工调整值

我们现有导入表同样满足该约定:city_bus_monthly(报表期 + 地区 + 企业)、city_taxi_monthly(报表期 + 地区)、freight_turnover_import(报表期 + 地区)、scale_split_transport(报表期 + 地区 + 月/累计)、h2031_enterprise_monthly(报表期 + 企业)、h2032_enterprise_monthly、wyc_order_monthly / wyc_total_monthly、passenger_individual_monthly。存储层形态是对的。

12.3 人工调整值一律单独建表(覆盖层)

  • 模式:**原始记录不动**,另表存「覆盖值 + 原因 + 时间」,唯一键与所覆盖对象一致(参照同事的 holiday_vehicle_adjustment)。
  • 我们的适用对象:2025 参照值修正、异常站剔除、业务临时改数等。

12.4 现状核验(2026-10-09 实测,修正早期判断)

实测库 127.0.0.1:3308 / traffic_audit(MySQL 8.0.29):

表 2025 月份数 结论
city_bus_monthly 12/12 齐全
city_taxi_monthly 12/12 齐全
wyc_order_monthly / wyc_total_monthly 12/12 齐全
h2031_enterprise_monthly / h2032_enterprise_monthly 12/12 齐全
passenger_individual_monthly 12/12 齐全
freight_turnover_import 1/12(仅 2025-12) 不构成缺口:表带 last_freight_m01..m12 / last_turnover_m01..m12,2026 各期文件自带去年同月值
scale_split_transport 0/12 不构成缺口:表带 above/below/total_yoy,同比已随 2026 各期入库
  • 早期"库里没有 2025 年城市客运数据"的注释与提示文案(ReportExportService.java:499 / 558 / 561)**已过期**,触发条件不再成立;同比取数走的是库内去年同期(aggOf(data, year-1, m, "湖北省"),约 540 行)。
  • 真正遗留的是口径问题,不是缺数据:中口径排名/明细的 2025 列仍以**母版静态定稿值**为准(readMidDetailLastYearCumulative,ReportExportService.java:2745 起),代码注释(2686-2689 行)说明原因——"排名页原先查库现算,两者在 2025-03/04 网约车拆分上不同"。
  • 处置(**2026-10-09 修正**):该"待确认"判断有误——同比基准已在 2026-09-07 拍板为「**母版缓存优先,库仅作校验**」(docs/要求与验收基线清单.md F8、确认回执 Q1);库内 2025 现算值**不得**直接替代母版静态定稿值,仅当人工核对并重导一致后才切库值。详见第十三节。

12.5 结果层(比同事更进一步,可选)

  • 同事库里**不存计算结果**:累计、同比、占比、环比均导出时算,Excel 内表现为公式。
  • 我们可选增加一张结果表,粒度 = 期间 + 页 + 指标 + 市州 + 当月/累计;Calc 写入、Filler 读取。
  • 收益:小表与跨页合成页共用一套结果;逐格 diff 时能直接区分「算错」还是「填错」。
  • 约定:按期间**幂等重算**(先删该期间再插);显示取整不下沉到库,只在展示层舍入。

12.6 自检纪律(红线)

母版里不允许存在「只有它才有的数据」。

判定方法:把母版所有值清空后,程序能否重新生成一张完全一致的表?能 → 数据都在库;不能 → 还有数据藏在 Excel 里,继续往库里搬。

12.7 与 M0 的关系(依赖顺序)

  • 12.4 的口径核对不阻塞 M0 阶段 1(版式重建)。
  • M0 阶段 2「停用 _备份_ donor 还原」的数据前提**已基本满足**(2025 已在库);但仍需先关闭 12.4 的差异,否则切换取数来源会让数字变动、无法通过 diff=0 回归。

    十三、既有拍板核验(2026-10-09 查证)——本节优先级高于前述各节,冲突以本节为准

13.1 同比基准:以母版缓存为准(已确认,不是待定项)

出处 原文要点 状态
docs/要求与验收基线清单.md F8 2025 年前数据以母版缓存为准;**母版缓存优先作同比基准,库只做校验**,人工核对重导一致后才切库值(Q1) ✅ 口径确认
docs/确认回执汇总与剩余疑问_汇总大表数据驱动改造_2026-09-07.md Q1 以母版缓存为同比基准并仅提示;待人工核对后重导一致再切换为库值("母版缓存优先,库仅作校验参考") 已确认
docs/汇总大表数据驱动改造方案_2026-09-07.md 第九节 2025 定稿数据"前台导入校验"另立任务;**本期同比一律以母版缓存为基准** —
docs/待确认_汇总大表数据驱动改造问题清单_2026-09-07.md A1 2025 及以前历史列一律**保留母版缓存、不重算** —
docs/问题汇总/汇总大表问题交接_2026-09-21.md 方案 A 中口径排名取去年同期累计改用母版 2025 静态列(与中口径明细同源),已实施 已实施

结论:**库内 2025 现算值不能直接替代母版 2025 静态定稿值**;切换的唯一前提是"人工核对并重导一致"。第十二节 12.4 中"交业务确认以哪边为准"的表述据此修正(见该节 2026-10-09 修正行)。

13.2 扩列方式:按月按需(已拍板)——与本文档"满版式"建议冲突

出处 原文要点 状态
docs/要求与验收基线清单.md F10 扩列按月按需(加月份才扩列,不预做 12 月满版);新增列"当月/同比"标题为两行合并单元格,扩列须保留 ✅ M0 v4
docs/汇总大表数据驱动改造方案_2026-09-07.md v1.1 修订(2026-09-08 用户拍板) 正文"12 月满版列版式"建议**作废**;扩列按"每月需增月份时再扩列"执行(=D1 原口径) 已拍板

→ 已裁定(2026-10-09 用户确认):沿用既有口径 F10「按月按需」,本文档第九/十一节的"满版式"建议**作废**(上文已就地标注)。

13.3 已有的 09-29 扩列工作产物(本地未入库;docs/问题汇总/_*.md 已被 gitignore 命中)

  • docs/问题汇总/_扩列规格表_2026-09-29.md / .csv:19 页逐页规格——类型(A 月份对+累计 / B 部分月份对 / C 未识别月份对)、各页月份对列位、累计列、建议插入位置、数据行范围、标题写法。
  • docs/问题汇总/_扩列配置初稿_20260929.md / .csv、_扩列计划_v3..v10.json:配置与计划迭代(v10 仅剩 轨道、轮渡 一页)。
  • docs/问题汇总/_扩列进度_20260929.md:8 月→9 月扩列进度——8 页已插列;6 项待修(表头显示/合并区/累计只到 8 月且多写 +Q/累计同比去年基数只到 8 月/标题月份未 +1/轨道轮渡需按块分别插列);若干页插入位置待确认。
  • 试做件 docs/生成汇总大表/2026年9月道路运输量汇总表_试做_20260929.xlsx(结构可看、数值未修完,勿用于出表)。

→ 本规格应与上述产物对齐推进,不再另行设计。

13.4 母版维护目标(既有口径)

  • docs/待确认_汇总大表数据驱动改造问题清单_2026-09-07.md A4:**"稳定后母版只当版式模板,月度数据一律程序填"**——与本文档目标一致。

    十四、最终执行口径(2026-10-09 用户确认,取代前述冲突表述)

做法:本次只做一件事——**把"扩列"变成程序的常规能力**,其余已确认口径一律不动。

# 动作 依据
1 实现**按月按需扩列器**:读 N 月母版 → 克隆 N+1 月"值列+同比列"一对 → 累计列后移 → 显式重写累计/累计同比 → 同步表头(两行纵向合并标题)与页面标题 → 输出(**只到当月、不预留空列**);同时把结果落盘为下期母版 F10 / D1 / D2 / 确认回执六.4
2 三条必改点做全:① 新月份同比公式;② 累计列枚举补当月;③ 插列右侧 2025/2024/2023 历史区几千条公式引用 +2 09-21 交接 §5.2
3 先修两处写死的列位置:SM_CUR_COL0=2(2026年1月=C)、SM_LAST_COL0=21(2025年1月=V)改为**按表头动态定位**;否则 2025 块一右移,四张跨页合成页会读错去年基数 docs/汇总大表母版扩列配置初稿_2026-09-29.md 第五节
4 累计同比分母**按表头定位"去年 M 月"列**重写公式(只累加奇数列),不写死列清单;版式对齐改为按表头文字匹配列 C3 / C4(09-18 长效方案)
5 同比基准不动:2025 列原样保留;库只做校验(B4 基数体检可作校验位),**不切库值**;_备份_ donor 还原先保留作回归对照 F8 / Q1 / A3 / 09-21 方案 A
6 每页规则用配置钉死、不靠自动识别;识别不了的页跳过并报告 配置初稿 §六.3

验收(沿用既有 E1 / §5.4)

  1. 7 月母版扩出 8 月 → 与真实《2026年8月道路运输量汇总表.xlsx》逐格 diff=0;
  2. 8 月母版扩出 9 月 → 与用户手工《2026年9月道路运输量汇总表.xlsx》diff=0(_试做_20260929.xlsx 只作结构参考,不作数值真值);
  3. 自检:#REF!=0、8 月及以前列逐格未变、累计=1~M 月、累计同比含 2025 年第 M 月、两行合并标题保留、错误值不增。

规格来源(2026-10-09 由本地过程产物转正入库)

  • docs/汇总大表母版扩列规格_2026-09-29.md / .csv —— 19 页逐页规格(A/B/C 类型、月份对列位、累计列、插入位置、数据行范围、标题)
  • docs/汇总大表母版扩列配置初稿_2026-09-29.md —— 逐页列位配置 + 硬前提(写死列位置)+ 建议方案

未决(待用户确认后写入本节)

  • 扩列走"程序自动"(09-21 §5.3 方案 A)还是"本机 Excel 小工具"(方案 B):09-21 交接 §11.2 未记录结论;09-29 本机产物呈"程序生成计划 + 人工确认"的混合路线。
  • C 类 9 页(排名/汇总/分析等)是否随月份推进:据配置初稿第五节,其中 6 页由生成程序刷新或按模板填数、**母版不需要扩列**;2025年城市客运量、中口径计算过程 为静态页。