汇总表三条主线修复方案 + 投资「按项目名称更新」现状(2026-09-18)
结论先行:三条主线不是三个独立 bug,而是 一个统一根因(公式不重算)+ 一处页范围漏项 + 一处口径缺口。
证据文件:_tmp\_qa_out\生成_道路运输量汇总表_2026-08.xlsx、_tmp\_qa_out\gencols.txt、_tmp\_qa_out\genrows.txt。
一、三条主线的根因
证据(刚复核)
| 页 |
8月当月值列(Q) |
8月同比列(R) |
说明 |
| 公交 / 出租 / 网约车 / 班线包车 / 轨道、轮渡 |
有公式(=Q9+Q13+…),**无缓存值**(cached=None) |
有公式 |
公式写了但没算,读值工具只看到空/旧值 |
| 城市客运 / 中口径明细 |
连公式都没有(Q5=None、Q9=None) |
有公式(=Q5/AJ5-1) |
同比写在、当月值没写 → 典型"页范围不一致" |
| 中口径排名 |
K3='=SUM(K4:K20)'、O3='=SUM(O4:O20)'(无缓存值) |
M3=0.7424、Q3=0.2317 是写死的数 |
SUM 没重算 + 全省同比写死成库内全省口径 |
关键事实
- 母版本身就是公式驱动的(不需要人工再做模板):
- 全省行 = Σ17 市州:
公交!C5 = =C9+C13+…+C73、中口径明细!C5 = =C6+C7+C8+C9;
- 城市客运 / 中口径明细的市州行 = 跨表引用:
城市客运!C9 = =公交!C16+出租!C16+网约车!C16+'轨道、轮渡'!C9、中口径明细!C11 = =班线包车!C8。
- 所以这两页**根本不需要单独写数据**,只要"按月右移公式"补齐 + 重算,整页自动就对。
- 两个"补公式"函数的页范围不一致(
SummaryWorkbookFiller.java):
ensureMonthYoyFormulas() 用 isMonthLedgerSheet() → 含 城市客运 / 中口径明细 / 公路总客运;
ensureMonthFormulas() 用 isFilledLedgerSheet() → 只有 6 个台账页(货运/班线包车/公交/出租/网约车/轨道轮渡)← 漏项就在这里。
- 生成件里的公式没有缓存值:POI 写文件时不重算,母版里 7 月的缓存值被原样留下,所以"全省行/累计/排名停在上一期"。Excel 打开靠
fullCalcOnLoad 会算,但**系统预览、二次处理、部分 WPS 都看不到新值**。
归纳
| 主线 |
现象 |
根因 |
| 1 |
城市客运 / 中口径明细 8月整列空白、累计=1~7月 |
ensureMonthFormulas() 页范围漏了这两页 |
| 2 |
公交/出租/网约车/城市分市州的**全省行**停在 7 月 |
公式无缓存值(未重算) |
| 3 |
中口径排名数据不对 |
同主线 2;另加"全省同比"写死成库内全省口径(1-8月 27524.49)与 Σ17市州(24481.74)打架 |
二、修复方案(4 步)
- 补公式页范围:
SummaryWorkbookFiller.ensureMonthFormulas() 的页判断改用 isMonthLedgerSheet(含 城市客运 / 中口径明细 / 公路总客运)。
另:中口径明细里有 4 个**数值行**(市州"城际城乡巡游出租/网约车"是硬编码数,不是公式),需要按库里源数据回填,不能只靠公式右移。
- 生成收尾做公式重算:在写文件前用
XSSFFormulaEvaluator.evaluateAll() 把整本算一遍并写入缓存值(逐页 try-catch;失败则保留 fullCalcOnLoad=true 降级)。
好处:全省行、累计列、排名 SUM、城市客运/中口径明细跨表引用**一次性全部自动出值**,不用再讨论"全省按哪个口径"——母版口径就是 Σ17 市州。
- 统一中口径口径:
exportPassengerMidRank() 全省行同比不再写死库内全省值,改成与 Σ市州 同源;
loadMidClassMap() 补 dim3 巡游出租 + dim4 网约车(现在只装了班线 + 公交两维,所以独立《生成_公路旅客中口径明细》1月全省少 535.83+333.39)。
- 建回归比对脚本:每次生成后逐页逐格与"母版公式 + 库源数据重算"比对,作为验收基线(可复用
_tmp\_qa_out 里已有的对比脚本)。
需要业务确认(不阻塞修复)
- 库里 h2031「全省」行 1-8 月 = 27524.49,比 Σ17 市州多 3042.75。修复一律按母版口径(Σ市州)走;这个差异意味着企业上报的"全省"含市州明细之外的数据(或某市州漏报),建议业务侧核一次。
三、投资:按项目名称更新 / 新增项目归到对应模块
现状(已核实,程序确实是按项目名称走的)
- 导入侧:
DataImportService.saveInvestProjectRow() / 全省汇总大表路径,以「**项目名称 + 类别**」为业务主键 upsert —— 命中则更新字段 + 写当期月度,未命中则**新增**并带上该文件的市州与类别(客运站场 / 物流园区)。
库内抽查:客运站场 49 条 / 49 个名称,物流园区 135 条 / 135 个名称,**无重复行**。
- 生成侧:
ReportExportService.matchInvest() 是按名称(归一化 + 市州 + 类别过滤)去匹配模板行,不是按列位/行号取数。
- 同名保护:已实现"市州一致性校验"(库中归属市州与本次解析不一致 → 跳过该行并在页面弹窗提示)。
- 不模糊匹配:
normInvestName() 只做括号/连接符/省市县前缀归一,不做近似合并 → 符合"名称有差异就是两个项目"。
缺口(就是你说的"新增项目要归到对应模块下面")
- 模板里**没有的新项目目前不会自动插行**:
investPlan(全省预安排)等生成器只用 matchInvest 匹配已有模板行,**匹配不到的新项目会被丢掉**,不会按市州/板块插到对应位置。
→ 需加"插行"逻辑:按 市州 + 类别(+资金渠道分组)定位块区间,在块末尾插行、复制相邻行格式与公式、写项目名与新数据,并更新合计行范围。
- "模块"最细一层现在入不了库:两张明细表的"一、使用交通部车购税资金 / 二、省燃油税 / 三、农村综合运输服务站 / 四、候车亭"分组行,在导入时被
^[一二三四五六七八九十]+、.* 规则**主动跳过**,所以项目落不到具体渠道块。
→ 需给 investment_project 加 fund_channel 字段,导入时记录"当前分组"写到其后项目上,再重导 7/8 月。
- 同名不同项目目前**不弹窗**(只有市州错位弹窗)。