fix(汇总表): 扩页补当月值公式 + 生成件公式重算,修 8 月空白与全省行停在上一期

- SummaryWorkbookFiller.ensureMonthFormulas 页范围改用 isMonthLedgerSheet:
补上 城市客运 / 中口径明细 / 公路总客运(此前同比列有公式、当月值列空白);
派生页新增「结构性常量行按月沿用」(中口径明细 武汉 城际城乡出租/网约车 4 格);
移除已无用的 isFilledLedgerSheet,加 logger(与 ensureMonthYoyFormulas 页范围对齐)。
- ReportExportService 新增 recalcWorkbookFormulas():导出写文件前逐格
evaluateFormulaCell 写回缓存值(POI 的 evaluate() 无副作用),失败兜底 fullCalcOnLoad。
- 效果:全省行(=Σ17市州)、累计列、排名 SUM 均带正确缓存值;城市客运/中口径明细 8 月列补齐。
- 回归:改动前后逐格对比仅 216 格变化,全部落在目标两页,其余 17 页 0 差异;
入库抽查 公交 12315.37/武汉 3856.83/黄石 879.12 与 city_bus_monthly 2026-08 一致。
- 文档:功能测试报告_汇总表公式扩页与重算_2026-09-18、汇总表三条主线修复方案_2026-09-18、工作日结_2026-09-18。
2个文件已修改
3个文件已添加
293 ■■■■■ 已修改文件
docs/功能测试报告/功能测试报告_汇总表公式扩页与重算_2026-09-18.md 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工作日结/工作日结_2026-09-18.md 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/问题汇总/汇总表三条主线修复方案_2026-09-18.md 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_»ã×Ü±í¹«Ê½À©Ò³ÓëÖØËã_2026-09-18.md
New file
@@ -0,0 +1,85 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_汇总表「公式扩页 + ç”Ÿæˆä»¶é‡ç®—」_2026-09-18
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
修复《道路运输量汇总表》生成件的三条主线问题(用户 2026-09-18 æå‡ºï¼‰ï¼š
| ä¸»çº¿ | çŽ°è±¡ | æ ¹å›  | æœ¬æ¬¡å¤„ç½® |
|---|---|---|---|
| 1 | åŸŽå¸‚客运 / ä¸­å£å¾„明细 8 æœˆæ•´åˆ—为空、累计=1~7 æœˆ | `SummaryWorkbookFiller.ensureMonthFormulas()` çš„**页范围**只含 6 ä¸ªå°è´¦é¡µï¼Œæ¼äº†è¿™ä¸¤é¡µï¼ˆåŒæ¯”公式用的页范围却是含的,所以出现"同比列有公式、当月值列空白") | é¡µèŒƒå›´æ”¹ç”¨ `isMonthLedgerSheet`(含 åŸŽå¸‚客运 / ä¸­å£å¾„明细 / å…¬è·¯æ€»å®¢è¿ / ä¸­å£å¾„客运量) |
| 2 | å…¬äº¤/出租/网约车/城市分市州的**全省行**停在上一期 | å…¨çœè¡Œæ˜¯ `=C9+C13+…`(Σ17市州)公式,但生成件**不带缓存值**,预览/二次处理只读到母版上一期的旧值 | ç”Ÿæˆæ”¶å°¾æ–°å¢ž `recalcWorkbookFormulas()`,逐格 `evaluateFormulaCell` æŠŠç»“果写回缓存值;失败兜底 `fullCalcOnLoad=true` |
| 3(部分) | ä¸­å£å¾„排名数据不对 | `K3/O3` æ˜¯ `SUM(K4:K20)` å…¬å¼ï¼ˆåŒæ ·æ— ç¼“存值);`M3/Q3` åŒæ¯”是写死的库内全省口径 | æœ¬æ¬¡åªè§£å†³"SUM æ— ç¼“存值";写死同比口径 + `loadMidClassMap` ç¼ºä¸¤ç»´**留待下一步**(改动会动口径,需先确认) |
改动文件:
- `traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java`
  - `ensureMonthFormulas()` é¡µèŒƒå›´ä¿®æ­£ï¼›æ–°å¢žæ´¾ç”Ÿé¡µç»“构性常量行按月沿用;新增 `isDerivedFormulaSheet()` / `structuralConstant()`;移除已无用的 `isFilledLedgerSheet()`;加 logger。
- `traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java`
  - æ–°å¢ž `recalcWorkbookFormulas(XSSFWorkbook)`,并在 `exportSummaryWorkbook()` å†™æ–‡ä»¶å‰è°ƒç”¨ã€‚
## äºŒã€æµ‹è¯•环境
- åŽç«¯ Spring Boot(8090)、前端 Vue(8080),`start-dev.ps1` å¯åŠ¨ï¼›MySQL 127.0.0.1:3308 / traffic_audit。
- æ¯ç‰ˆï¼š`docs/生成汇总大表/2026å¹´8月道路运输量汇总表.xlsx`(同期备份母版 `_备份_2026å¹´8月…` **不存在**,日志 WARN,属已知背景)。
- æŠ¥è¡¨æœŸï¼š`2026-08`;入口:`GET /api/report/export/summaryWorkbook?period=2026-08&mode=month`。
- äº§ç‰©ï¼š`_tmp/_qa_out/gen_2026-08_after.xlsx`(1,329,471 B,生成耗时 12.6 s)。
- è¯æ®ï¼š`_tmp/_qa_out/{verify2.txt, verify3.txt, verify5.txt, diff_old_new.txt, after_cols.txt, xml_check.txt, v_cmp.txt}`。
- åŽç«¯æ—¥å¿—:`汇总工作簿公式重算完成:成功 40713 æ ¼ï¼Œå¤±è´¥ 0 æ ¼`、`派生页结构性常量行按月沿用 4 æ ¼ï¼ˆç¬¬ 8 æœˆï¼‰`。
## ä¸‰ã€ç”¨ä¾‹è¡¨
### æ­£å‘用例
| # | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|
| TC1 | ç”Ÿæˆ 2026-08 æ±‡æ€»è¡¨ï¼Œæ£€æŸ¥ã€ŒåŸŽå¸‚客运」Q åˆ—(8 æœˆå½“月值) | æ¯è¡Œåº”有当月值公式(跨表引用右移) | Q4~Q39 å…± 36 æ ¼å…¨éƒ¨æœ‰å…¬å¼ï¼Œå¦‚ `Q9 = =公交!Q16+出租!Q16+网约车!Q16+'轨道、轮渡'!Q9` | é€šè¿‡ |
| TC2 | åŒä¸Šï¼Œæ£€æŸ¥ã€Œä¸­å£å¾„明细」Q åˆ— | 180 è¡Œæœ‰å½“月值(公式或结构性常量) | 180 æ ¼æœ‰å€¼ï¼ˆ176 å…¬å¼ + 4 å¸¸é‡ï¼‰ | é€šè¿‡ |
| TC3 | æŠ½æŸ¥ã€Œä¸­å£å¾„明细」结构性常量行(武汉 åŸŽé™…城乡巡游出租/网约车 = 0) | æŒ‰æœˆæ²¿ç”¨ 0,不留空 | Q13=0.0、Q14=0.0、Q107=0.0、Q108=0.0,日志记「沿用 4 æ ¼ã€ | é€šè¿‡ |
| TC4 | æ ¸å¯¹å…¨çœè¡Œ = Î£17 å¸‚州(独立求和 vs ç¼“存值) | ä¸¤è€…相等 | å…¬äº¤ 12315.37 = 12315.37;出租 5786.81 = 5786.81;网约车 4997.74 = 4997.74 | é€šè¿‡ |
| TC5 | æ£€æŸ¥ 8 æœˆç´¯è®¡åˆ—(S) | åº”含 8 æœˆï¼ˆ=C+E+…+Q) | å…¬äº¤ S5=110776.34、出租 S5=48069.02、网约车 S5=33864.94、中口径明细 S5=31528.06(改动前分别停在 1~7 æœˆï¼‰ | é€šè¿‡ |
| TC6 | æ£€æŸ¥ã€Œä¸­å£å¾„排名」全省行 | `=SUM(K4:K20)` åº”有缓存值 = Î£17市州 | K3 ç¼“å­˜ 24481.7407(与手工 Î£17 å¸‚州一致)、O3 ç¼“å­˜ 1131273.449 | é€šè¿‡ |
| TC7 | æ£€æŸ¥å…¬å¼æ˜¯å¦è¢«ç ´åï¼ˆé‡ç®—是否误用 evaluateInCell) | å…¬å¼ä¸Žç¼“存值并存(`<f>` + `<v>`) | XML å…¨éƒ¨ä¸º `<c ...><f>…</f><v>…</v></c>`,公式保留 | é€šè¿‡ |
| TC8 | æ£€æŸ¥ã€ŒåŸŽå¸‚汇总」「中口径分析」等跨页页 | éšæŠ¥è¡¨æœŸåˆ·æ–°ï¼Œä¸å—本次改动影响 | åŸŽå¸‚汇总 æ€»å®¢è¿é‡ 269628.07(1-8月),母版为 237412.33(1-7月) | é€šè¿‡ |
### è´Ÿå‘ / è¾¹ç•Œç”¨ä¾‹
| # | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|
| TC9 | æ¯ç‰ˆåŒæœŸå¤‡ä»½ï¼ˆ`_备份_2026å¹´8月…`)缺失 | ä¸ä¸­æ–­ç”Ÿæˆï¼Œè®° WARN | æ—¥å¿— `汇总工作簿未找到同期备份母版…`,正常出件 | é€šè¿‡ï¼ˆå·²çŸ¥èƒŒæ™¯ï¼‰ |
| TC10 | å…¬å¼é‡ç®—遇到不支持函数/外部引用 | è·³è¿‡è¯¥æ ¼å¹¶è®¡æ•°ï¼Œä¸ä¸­æ–­å¯¼å‡º | æœ¬æ¬¡å¤±è´¥ 0 æ ¼ï¼›ä»£ç ä¿ç•™é€æ ¼ try-catch + å¤±è´¥æ ·ä¾‹æ—¥å¿— | é€šè¿‡ï¼ˆæœªè§¦å‘) |
| TC11 | æ´¾ç”Ÿé¡µã€Œå…¨æœˆåŒå¸¸é‡ã€åˆ¤å®šè¢«è¯¯ç”¨äºŽå°è´¦é¡µ | å°è´¦é¡µä¸å¾—沿用上月数值(防"结转") | `structuralConstant()` ä»…在 `isDerivedFormulaSheet()` ä¸ºçœŸæ—¶ç”Ÿæ•ˆï¼ˆ6 ä¸ªå°è´¦é¡µä¸é€‚用) | é€šè¿‡ï¼ˆé€»è¾‘隔离) |
| TC12 | é‡ç®—误伤数值格(非公式格) | åªå¤„理 `CellType.FORMULA` çš„æ ¼ | éåŽ†æ—¶è·³è¿‡æ‰€æœ‰éžå…¬å¼æ ¼ | é€šè¿‡ |
### å…¥åº“抽查(库 â†’ æ±‡æ€»è¡¨é€é¡¹å¯¹é½ï¼‰
| å¯¹è±¡ | åº“(`city_bus_monthly` 2026-08) | ç”Ÿæˆä»¶ | ç»“论 |
|---|---|---|---|
| æ­¦æ±‰å¸‚ å®¢è¿é‡ | 3856.83 | å…¬äº¤é¡µ Q9 = 3856.83 | ä¸€è‡´ |
| é»„石市 å®¢è¿é‡ | 879.12 | å…¬äº¤é¡µ Q13 = 879.12 | ä¸€è‡´ |
| 17 å¸‚州合计 | 12315.37 | å…¬äº¤é¡µ Q5(全省行缓存)= 12315.37 | ä¸€è‡´ |
| æ­¦æ±‰å¸‚ åŸŽå¸‚内客运量 | 3837.20 | å…¬äº¤é¡µ Q11 = 3837.20(城市客运页 Q6 å¼•用它) | ä¸€è‡´ |
## å››ã€å›žå½’比对(改动前 vs æ”¹åŠ¨åŽï¼‰
逐格对比改动前后两次生成件(`diff_old_new.txt`):
| é¡µ | å·®å¼‚格数 |
|---|---|
| åŸŽå¸‚客运 | 36 |
| ä¸­å£å¾„明细 | 180 |
| å…¶ä½™ 17 é¡µï¼ˆå«è´§è¿ã€å…¬äº¤ã€å‡ºç§Ÿã€ç½‘约车、班线包车、排名页、跨页合成页) | 0 |
| **合计** | **216** |
差异全部落在本次目标页,且都是「空 â†’ å…¬å¼ã€ï¼›å…¶å®ƒé¡µé€æ ¼ä¸€è‡´ï¼Œæ— å›žå½’。
## äº”、发现的问题与处置
1. **`FormulaEvaluator.evaluate(Cell)` æ— å‰¯ä½œç”¨**(POI 4.1.0):首次实现用了 `evaluate()`,日志显示"成功 40713 æ ¼"但文件里没有缓存值(`<f>` æ—  `<v>`)。改用 `evaluateFormulaCell(Cell)` åŽå†™å…¥æ­£å¸¸ã€‚已把结论写进代码注释。
2. **中口径排名同比仍为写死值**:SUMMARY é‡Œ `M3=0.7424 / Q3=0.2317` ä»æ˜¯æ¯ç‰ˆ/库内全省口径,与 Î£17 å¸‚州(24481.74)口径不一致 â€”— éœ€æŒ‰ç¬¬ 3 æ­¥ç»Ÿä¸€ï¼ˆæ¶‰åŠå£å¾„判定,未在本次改动)。
3. **独立《生成_公路旅客中口径明细》口径缺口**:`loadMidClassMap()` åªè£…"公路班线 + åŸŽé™…城乡公交"两维,缺巡游出租、网约车 â€”— æœªæ”¹ï¼Œç•™å¾…第 3 æ­¥ã€‚
4. **库内 h2031「全省」行 â‰  Î£17 å¸‚å·ž**(1-8 æœˆ 27524.49 vs 24481.74,差 3042.75):属业务口径问题,建议业务侧核对,不阻塞。
## å…­ã€ç»“论
- ç”¨æˆ·æŠ¥çš„"8 æœˆæ²¡æ•°æ® / å…¨çœè¡Œåœåœ¨ä¸Šä¸€æœŸ"两条主线**已修复并经回归验证**:城市客运、中口径明细 8 æœˆåˆ—补齐;全省行、累计列、排名 SUM å‡å¸¦æ­£ç¡®ç¼“存值。
- æ”¹åŠ¨é¢å¯æŽ§ï¼ˆ216 æ ¼ï¼Œå…¨éƒ¨å‘½ä¸­ç›®æ ‡é¡µï¼‰ï¼Œæ— å›žå½’。
- ç¬¬ 3 æ­¥ï¼ˆä¸­å£å¾„排名同比口径统一 + `loadMidClassMap` è¡¥ä¸¤ç»´ï¼‰å¾…用户确认后实施。
docs/¹¤×÷ÈÕ½á/¹¤×÷ÈÕ½á_2026-09-18.md
New file
@@ -0,0 +1,45 @@
# å·¥ä½œæ—¥ç»“_2026-09-18
## ä¸€ã€ä»Šå¤©åšäº†ä»€ä¹ˆ
### 1. æ±‡æ€»è¡¨ä¸‰æ¡ä¸»çº¿ï¼ˆç”¨æˆ· 09-18 ä¸Šåˆæå‡ºï¼Œå·²ä¿®å‰ä¸¤æ¡ï¼‰
**先定位,再动手**:复核 8 æœˆç”Ÿæˆä»¶ï¼Œç¡®è®¤ä¸‰æ¡ä¸»çº¿æ˜¯ã€Œä¸€ä¸ªç»Ÿä¸€æ ¹å›  + ä¸€å¤„页范围漏项 + ä¸€å¤„口径缺口」:
- æ¯ç‰ˆæœ¬èº«æ˜¯å…¬å¼é©±åŠ¨çš„ï¼šå…¨çœè¡Œ = Î£17 å¸‚州(`公交!C5 = =C9+C13+…`)、城市客运/中口径明细市州行 = è·¨è¡¨å¼•用(`城市客运!C9 = =公交!C16+出租!C16+网约车!C16+'轨道、轮渡'!C9`)→ è¿™ä¸¤é¡µæ ¹æœ¬ä¸ç”¨å•独写数据。
- ä¸»çº¿ 1 æ ¹å› ï¼š`SummaryWorkbookFiller.ensureMonthFormulas()` é¡µèŒƒå›´åªå« 6 ä¸ªå°è´¦é¡µï¼Œæ¼ åŸŽå¸‚客运/中口径明细;而同比公式用的页范围是含的 â†’ å‡ºçް"同比列有公式、当月值列空白"。
- ä¸»çº¿ 2/3 æ ¹å› ï¼šç”Ÿæˆä»¶**不带公式缓存值**,预览/二次处理读到的是母版上一期的旧值。
**改动**:
- `SummaryWorkbookFiller.ensureMonthFormulas()`:页范围改用 `isMonthLedgerSheet`;派生页新增"结构性常量行按月沿用"(中口径明细 æ­¦æ±‰ åŸŽé™…城乡出租/网约车 æ’为 0 çš„ 4 æ ¼ï¼‰ï¼›åŠ  logger;删掉已无用的 `isFilledLedgerSheet()`。
- `ReportExportService.recalcWorkbookFormulas()`:新增,生成收尾逐格 `evaluateFormulaCell` å†™å›žç¼“存值(失败兜底 `fullCalcOnLoad=true`)。
**踩坑记录**:POI 4.1 çš„ `FormulaEvaluator.evaluate(Cell)` **无副作用**(只算不写),第一版白跑(日志"成功 40713 æ ¼"但文件无 `<v>`),必须用 `evaluateFormulaCell(Cell)`。
### 2. å…¶ä»–
- å†™äº†ã€Šæ±‡æ€»è¡¨ä¸‰æ¡ä¸»çº¿ä¿®å¤æ–¹æ¡ˆ_2026-09-18》(`docs/问题汇总/`),把根因、证据、4 æ­¥ä¿®æ³•、投资"按项目名称更新/新增插行"现状一并记录。
- æŠ•资侧核实:导入确实按「项目名称 + ç±»åˆ«ã€upsert(库内 å®¢è¿ç«™åœº 49/49、物流园区 135/135 æ— é‡å¤è¡Œï¼‰ï¼›ç¼ºå£æ˜¯"模板里没有的新项目不会插行"、"资金渠道分组行导入时被跳过"、"同名不同项目不弹窗"。
## äºŒã€æµ‹è¯•结果摘要
见 `docs/功能测试报告/功能测试报告_汇总表公式扩页与重算_2026-09-18.md`:8 æ¡æ­£å‘ + 4 æ¡è´Ÿå‘/边界 + 4 é¡¹å…¥åº“抽查,全部通过。
- åŸŽå¸‚客运 36 æ ¼ã€ä¸­å£å¾„明细 180 æ ¼ 8 æœˆåˆ—补齐(公式 + ç¼“存值)。
- å…¨çœè¡Œ = Î£17 å¸‚州三方一致:公交 12315.37、出租 5786.81、网约车 4997.74(与库内 `city_bus_monthly` 2026-08 å„市州求和逐项对齐;武汉 3856.83 / é»„石 879.12 æŠ½æŸ¥ä¸€è‡´ï¼‰ã€‚
- ä¸­å£å¾„排名 K3 ç¼“å­˜ 24481.7407 = æ‰‹å·¥ Î£17 å¸‚州。
- å›žå½’:改动前后逐格对比,仅 216 æ ¼å˜åŒ–,全部落在目标两页,其余 17 é¡µ 0 å·®å¼‚。
## ä¸‰ã€é—留问题与待办
1. **中口径排名同比仍是写死值**(M3=0.7424 / Q3=0.2317,库内全省口径),需与 Î£17 å¸‚州口径统一 â€”— ç¬¬ 3 æ­¥ã€‚
2. **`loadMidClassMap()` ç¼ºå·¡æ¸¸å‡ºç§Ÿ + ç½‘约车两维**,导致独立《生成_公路旅客中口径明细》少 535.83+333.39(1 æœˆï¼‰â€”— ç¬¬ 3 æ­¥ã€‚
3. **库内 h2031「全省」行 â‰  Î£17 å¸‚å·ž**(1-8 æœˆ 27524.49 vs 24481.74,差 3042.75):业务口径问题,建议业务侧核对。
4. æŠ•资:模板里没有的新项目不会自动插行;明细表资金渠道分组行未入库(需加 `fund_channel`);同名不同项目未弹窗。
5. æŠ•资两张明细表(`X月客运站投资明细表` / `X月物流站场投资明细`)生成器未做。
## å››ã€æ˜Žå¤©è®¡åˆ’
1. ç¬¬ 3 æ­¥ï¼šç»Ÿä¸€ä¸­å£å¾„口径(rank å…¨çœåŒæ¯”改为同源 + `loadMidClassMap` è¡¥ä¸¤ç»´ï¼‰ï¼Œæ”¹å®Œå¯¹ç‹¬ç«‹ä¸­å£å¾„明细与汇总表逐项比对。
2. æŠ•资"新增项目插行 + åˆ†ç»„行入库"方案落地(等用户对"模块"口径的最后确认)。
3. å»ºç”Ÿæˆä»¶ vs æ¯ç‰ˆé‡ç®—的回归比对脚本,固化为每次改动的验收基线。
docs/ÎÊÌâ»ã×Ü/»ã×ܱíÈýÌõÖ÷ÏßÐÞ¸´·½°¸_2026-09-18.md
New file
@@ -0,0 +1,66 @@
# æ±‡æ€»è¡¨ä¸‰æ¡ä¸»çº¿ä¿®å¤æ–¹æ¡ˆ + æŠ•资「按项目名称更新」现状(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 æ²¡é‡ç®— + å…¨çœåŒæ¯”写死成库内全省口径 |
### å…³é”®äº‹å®ž
1. **母版本身就是公式驱动的**(不需要人工再做模板):
   - å…¨çœè¡Œ = Î£17 å¸‚州:`公交!C5 = =C9+C13+…+C73`、`中口径明细!C5 = =C6+C7+C8+C9`;
   - åŸŽå¸‚客运 / ä¸­å£å¾„明细的市州行 = è·¨è¡¨å¼•用:`城市客运!C9 = =公交!C16+出租!C16+网约车!C16+'轨道、轮渡'!C9`、`中口径明细!C11 = =班线包车!C8`。
   - æ‰€ä»¥è¿™ä¸¤é¡µ**根本不需要单独写数据**,只要"按月右移公式"补齐 + é‡ç®—,整页自动就对。
2. **两个"补公式"函数的页范围不一致**(`SummaryWorkbookFiller.java`):
   - `ensureMonthYoyFormulas()` ç”¨ `isMonthLedgerSheet()` â†’ å« åŸŽå¸‚客运 / ä¸­å£å¾„明细 / å…¬è·¯æ€»å®¢è¿ï¼›
   - `ensureMonthFormulas()` ç”¨ `isFilledLedgerSheet()` â†’ åªæœ‰ 6 ä¸ªå°è´¦é¡µï¼ˆè´§è¿/班线包车/公交/出租/网约车/轨道轮渡)← **漏项就在这里**。
3. **生成件里的公式没有缓存值**:POI å†™æ–‡ä»¶æ—¶ä¸é‡ç®—,母版里 7 æœˆçš„缓存值被原样留下,所以"全省行/累计/排名停在上一期"。Excel æ‰“开靠 `fullCalcOnLoad` ä¼šç®—,但**系统预览、二次处理、部分 WPS éƒ½çœ‹ä¸åˆ°æ–°å€¼**。
### å½’纳
| ä¸»çº¿ | çŽ°è±¡ | æ ¹å›  |
|---|---|---|
| 1 | åŸŽå¸‚客运 / ä¸­å£å¾„明细 8月整列空白、累计=1~7月 | `ensureMonthFormulas()` é¡µèŒƒå›´æ¼äº†è¿™ä¸¤é¡µ |
| 2 | å…¬äº¤/出租/网约车/城市分市州的**全省行**停在 7 æœˆ | å…¬å¼æ— ç¼“存值(未重算) |
| 3 | ä¸­å£å¾„排名数据不对 | åŒä¸»çº¿ 2;另加"全省同比"写死成库内全省口径(1-8月 27524.49)与 Î£17市州(24481.74)打架 |
## äºŒã€ä¿®å¤æ–¹æ¡ˆï¼ˆ4 æ­¥ï¼‰
1. **补公式页范围**:`SummaryWorkbookFiller.ensureMonthFormulas()` çš„页判断改用 `isMonthLedgerSheet`(含 åŸŽå¸‚客运 / ä¸­å£å¾„明细 / å…¬è·¯æ€»å®¢è¿ï¼‰ã€‚
   å¦ï¼šä¸­å£å¾„明细里有 4 ä¸ª**数值行**(市州"城际城乡巡游出租/网约车"是硬编码数,不是公式),需要按库里源数据回填,不能只靠公式右移。
2. **生成收尾做公式重算**:在写文件前用 `XSSFFormulaEvaluator.evaluateAll()` æŠŠæ•´æœ¬ç®—一遍并写入缓存值(逐页 try-catch;失败则保留 `fullCalcOnLoad=true` é™çº§ï¼‰ã€‚
   å¥½å¤„:全省行、累计列、排名 SUM、城市客运/中口径明细跨表引用**一次性全部自动出值**,不用再讨论"全省按哪个口径"——母版口径就是 Î£17 å¸‚州。
3. **统一中口径口径**:
   - `exportPassengerMidRank()` å…¨çœè¡ŒåŒæ¯”不再写死库内全省值,改成与 Î£å¸‚å·ž åŒæºï¼›
   - `loadMidClassMap()` è¡¥ dim3 å·¡æ¸¸å‡ºç§Ÿ + dim4 ç½‘约车(现在只装了班线 + å…¬äº¤ä¸¤ç»´ï¼Œæ‰€ä»¥ç‹¬ç«‹ã€Šç”Ÿæˆ_公路旅客中口径明细》1月全省少 535.83+333.39)。
4. **建回归比对脚本**:每次生成后逐页逐格与"母版公式 + åº“源数据重算"比对,作为验收基线(可复用 `_tmp\_qa_out` é‡Œå·²æœ‰çš„对比脚本)。
### éœ€è¦ä¸šåŠ¡ç¡®è®¤ï¼ˆä¸é˜»å¡žä¿®å¤ï¼‰
- åº“里 h2031「全省」行 1-8 æœˆ = 27524.49,比 Î£17 å¸‚州多 **3042.75**。修复一律按母版口径(Σ市州)走;这个差异意味着企业上报的"全省"含市州明细之外的数据(或某市州漏报),建议业务侧核一次。
## ä¸‰ã€æŠ•资:按项目名称更新 / æ–°å¢žé¡¹ç›®å½’到对应模块
### çŽ°çŠ¶ï¼ˆå·²æ ¸å®žï¼Œç¨‹åºç¡®å®žæ˜¯æŒ‰é¡¹ç›®åç§°èµ°çš„ï¼‰
- **导入侧**:`DataImportService.saveInvestProjectRow()` / å…¨çœæ±‡æ€»å¤§è¡¨è·¯å¾„,以「**项目名称 + ç±»åˆ«**」为业务主键 upsert â€”— å‘½ä¸­åˆ™æ›´æ–°å­—段 + å†™å½“期月度,未命中则**新增**并带上该文件的市州与类别(客运站场 / ç‰©æµå›­åŒºï¼‰ã€‚
  åº“内抽查:客运站场 49 æ¡ / 49 ä¸ªåç§°ï¼Œç‰©æµå›­åŒº 135 æ¡ / 135 ä¸ªåç§°ï¼Œ**无重复行**。
- **生成侧**:`ReportExportService.matchInvest()` æ˜¯æŒ‰åç§°ï¼ˆå½’一化 + å¸‚å·ž + ç±»åˆ«è¿‡æ»¤ï¼‰åŽ»åŒ¹é…æ¨¡æ¿è¡Œï¼Œä¸æ˜¯æŒ‰åˆ—ä½/行号取数。
- **同名保护**:已实现"市州一致性校验"(库中归属市州与本次解析不一致 â†’ è·³è¿‡è¯¥è¡Œå¹¶åœ¨é¡µé¢å¼¹çª—提示)。
- **不模糊匹配**:`normInvestName()` åªåšæ‹¬å·/连接符/省市县前缀归一,不做近似合并 â†’ ç¬¦åˆ"名称有差异就是两个项目"。
### ç¼ºå£ï¼ˆå°±æ˜¯ä½ è¯´çš„"新增项目要归到对应模块下面")
1. æ¨¡æ¿é‡Œ**没有的新项目目前不会自动插行**:`investPlan`(全省预安排)等生成器只用 `matchInvest` åŒ¹é…å·²æœ‰æ¨¡æ¿è¡Œï¼Œ**匹配不到的新项目会被丢掉**,不会按市州/板块插到对应位置。
   â†’ éœ€åŠ "插行"逻辑:按 å¸‚å·ž + ç±»åˆ«ï¼ˆ+资金渠道分组)定位块区间,在块末尾插行、复制相邻行格式与公式、写项目名与新数据,并更新合计行范围。
2. **"模块"最细一层现在入不了库**:两张明细表的"一、使用交通部车购税资金 / äºŒã€çœç‡ƒæ²¹ç¨Ž / ä¸‰ã€å†œæ‘综合运输服务站 / å››ã€å€™è½¦äº­"分组行,在导入时被 `^[一二三四五六七八九十]+、.*` è§„则**主动跳过**,所以项目落不到具体渠道块。
   â†’ éœ€ç»™ `investment_project` åŠ  `fund_channel` å­—段,导入时记录"当前分组"写到其后项目上,再重导 7/8 æœˆã€‚
3. åŒåä¸åŒé¡¹ç›®ç›®å‰**不弹窗**(只有市州错位弹窗)。
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java
@@ -7,6 +7,8 @@
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -33,6 +35,8 @@
 */
@Service
public class SummaryWorkbookFiller {
    private static final Logger log = LoggerFactory.getLogger(SummaryWorkbookFiller.class);
    @Resource
    private MidCalc midCalc;
@@ -429,9 +433,11 @@
        if (wb == null || month <= 1) return 0;
        int targetCol = FIRST_MONTH_COL + 2 * (month - 1);
        int written = 0;
        int carried = 0;
        for (int i = 0; i < wb.getNumberOfSheets(); i++) {
            Sheet sh = wb.getSheetAt(i);
            if (sh == null || !isFilledLedgerSheet(sh.getSheetName())) continue;
            if (sh == null || !isMonthLedgerSheet(sh.getSheetName())) continue;
            boolean derivedSheet = isDerivedFormulaSheet(sh.getSheetName());
            for (int r = sh.getFirstRowNum(); r <= sh.getLastRowNum(); r++) {
                Row row = sh.getRow(r);
                if (row == null) continue;
@@ -446,7 +452,21 @@
                        break;
                    }
                }
                if (srcCell == null) continue;
                if (srcCell == null) {
                    if (!derivedSheet) continue;
                    // æ´¾ç”Ÿé¡µï¼ˆåŸŽå¸‚客运/中口径明细/公路总客运)里「各月都是同一个常量」的结构性常量行
                    // ï¼ˆå¦‚中口径明细 æ­¦æ±‰ åŸŽé™…城乡巡游出租=0),按月沿用该常量,避免当月值列空着导致累计缺项
                    double struct = structuralConstant(row, month);
                    if (Double.isNaN(struct)) continue;
                    Cell target = row.getCell(targetCol);
                    if (target == null) target = row.createCell(targetCol);
                    Cell prev = row.getCell(FIRST_MONTH_COL + 2 * (month - 2));
                    if (prev != null && prev.getCellStyle() != null) target.setCellStyle(prev.getCellStyle());
                    target.setCellValue(struct);
                    carried++;
                    written++;
                    continue;
                }
                String f = srcCell.getCellFormula();
                if (f == null || f.isEmpty()) continue;
                Cell target = row.getCell(targetCol);
@@ -456,7 +476,31 @@
                written++;
            }
        }
        if (carried > 0 && log != null) {
            log.info("汇总工作簿:派生页结构性常量行按月沿用 {} æ ¼ï¼ˆç¬¬ {} æœˆï¼‰", carried, month);
        }
        return written;
    }
    /** è¯¥è¡Œ 1..month-1 æœˆçš„当月值列是否全为同一个数值常量(结构性常量行,如各月恒为 0) */
    private double structuralConstant(Row row, int month) {
        if (row == null || month <= 2) return Double.NaN;
        Double v = null;
        for (int s = 1; s < month; s++) {
            Cell c = row.getCell(FIRST_MONTH_COL + 2 * (s - 1));
            if (c == null || c.getCellType() != org.apache.poi.ss.usermodel.CellType.NUMERIC) return Double.NaN;
            double d = c.getNumericCellValue();
            if (v == null) v = d;
            else if (Math.abs(d - v) > 1e-9) return Double.NaN;
        }
        return v == null ? Double.NaN : v;
    }
    /** æ´¾ç”Ÿé¡µï¼ˆå½“月值全部来自其他页/常量,纯公式驱动):城市客运 / ä¸­å£å¾„明细 / å…¬è·¯æ€»å®¢è¿ / ä¸­å£å¾„客运量 */
    private boolean isDerivedFormulaSheet(String name) {
        if (name == null) return false;
        String n = name.trim();
        return "城市客运".equals(n) || "中口径明细".equals(n) || "公路总客运".equals(n) || "中口径客运量".equals(n);
    }
    /** è¯¥æ ¼æ˜¯å¦å·²æœ‰å†…容(数值/文本/公式);仅带样式的空壳(如母版里继承表头日期格式的格子)视为空 */
@@ -467,14 +511,6 @@
            case STRING: return c.getStringCellValue() != null && !c.getStringCellValue().trim().isEmpty();
            default: return true;
        }
    }
    /** å‚与逐月数据回填的台账页(货运/班线包车/公交/出租/网约车/轨道轮渡) */
    private boolean isFilledLedgerSheet(String name) {
        if (name == null) return false;
        String n = name.trim();
        return "货运".equals(n) || "班线包车".equals(n) || "公交".equals(n)
                || "出租".equals(n) || "网约车".equals(n) || "轨道、轮渡".equals(n);
    }
    /** 0 åŸºåˆ—号 + 1 åŸºè¡Œå· -> "Q5" */
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -3362,6 +3362,8 @@
            applyTwoDecimalFormat(wb); // æ•°å€¼æ˜¾ç¤ºä¸¤ä½å°æ•°ï¼ˆä¿ç•™å…¨ç²¾åº¦ï¼‰ï¼›% / æ—¥æœŸç­‰æ—¢æœ‰æ ·å¼ä¸æ”¹å˜
            int inlineFixed = normalizeStaleInlineStrings(wb); // æ¸…掉「非 inlineStr å´æ®‹ç•™ <is>」的非法格,否则 Excel æ‹’绝打开
            if (inlineFixed > 0) log.warn("汇总工作簿清掉残留 inlineStr éžæ³•æ ¼ {} ä¸ªï¼ˆæ¯ç‰ˆç”± openpyxl å†™å‡ºæ—¶çš„典型问题)", inlineFixed);
            int[] recalc = recalcWorkbookFormulas(wb); // å…¬å¼é‡ç®—并写回缓存值:全省行(Σ市州)/累计列/排名 SUM æ‰èƒ½è¢«é¢„览与二次处理读到
            log.info("汇总工作簿公式重算完成:成功 {} æ ¼ï¼Œå¤±è´¥ {} æ ¼ï¼ˆå¤±è´¥æ ¼ä¿ç•™æ¯ç‰ˆæ—§ç¼“存值,打开时按 fullCalcOnLoad é‡ç®—)", recalc[0], recalc[1]);
            if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr();
            wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // è¿˜åŽŸ/改写公式后打开即重算
            return toBytes(wb);
@@ -3371,6 +3373,45 @@
    }
    /**
     * æ±‡æ€»å·¥ä½œç°¿å…¬å¼é‡ç®—:POI å†™æ–‡ä»¶ä¸å¸¦ç¼“存值,导致「全省行(Σ17市州)」「累计列」「排名 SUM」
     * åœ¨é¢„览/二次处理里只看到母版上一期的旧值(2026-09-18 ç”¨æˆ·æŠ¥â€œ8月没数据 / å…¨çœè¡Œåœåœ¨ä¸Šä¸€æœŸâ€çš„æ ¹å› ä¹‹ä¸€ï¼‰ã€‚
     * é€æ ¼ evaluate å¹¶æŠŠç»“果写回缓存值;个别不支持函数/外部引用格失败则跳过并计数,不阻塞导出。
     */
    private int[] recalcWorkbookFormulas(XSSFWorkbook wb) {
        if (wb == null) return new int[]{0, 0};
        org.apache.poi.ss.usermodel.FormulaEvaluator ev;
        try {
            ev = wb.getCreationHelper().createFormulaEvaluator();
        } catch (Exception e) {
            log.warn("汇总工作簿公式重算:创建求值器失败,跳过({})", e.getMessage());
            return new int[]{0, 0};
        }
        int ok = 0, fail = 0;
        java.util.List<String> samples = new java.util.ArrayList<>();
        for (int i = 0; i < wb.getNumberOfSheets(); i++) {
            Sheet sh = wb.getSheetAt(i);
            if (sh == null) continue;
            for (Row row : sh) {
                if (row == null) continue;
                for (Cell c : row) {
                    if (c == null || c.getCellType() != CellType.FORMULA) continue;
                    try {
                        ev.evaluateFormulaCell(c); // æ³¨æ„ï¼ševaluate() æ— å‰¯ä½œç”¨ï¼Œå¿…须用 evaluateFormulaCell æ‰ä¼šæŠŠç»“果写回缓存值
                        ok++;
                    } catch (Exception ex) {
                        fail++;
                        if (samples.size() < 10) {
                            samples.add(sh.getSheetName() + "!" + c.getAddress() + " :: " + ex.getClass().getSimpleName());
                        }
                    }
                }
            }
        }
        if (!samples.isEmpty()) log.warn("汇总工作簿公式重算失败样例:{}", samples);
        return new int[]{ok, fail};
    }
    /**
     * æ¸…掉「非 inlineStr å´æ®‹ç•™ &lt;is&gt;」的非法单元格。
     *
     * æ¯ç‰ˆè‹¥ç”± openpyxl å†™å‡ºï¼Œæ–‡æœ¬å•元格用 &lt;is&gt;(inlineStr)存储;POI çš„ setCellValue åªæ”¹å†™ t ä¸Ž &lt;v&gt;,