8690b9d77634c1789e410479f94e7111f67126c3..603853a1290e7d71297c9ce5b64181a090adab87
2026-09-08 zhizhijie
docs: 09-08 工作日结补记下班前状态(最新代码已重启就绪,明日待办8月填值/9月整链/临时文件清理)
603853 对比 | 目录
2026-09-08 zhizhijie
fix: 8条整改落地——数据查看滚动加载/规则阈值联动通用化/旅客运政导入兼容.xls/审核月报弹窗删企业解释列/汇总大表导出无筛选自适应/报表Ta...
0548cc 对比 | 目录
2026-09-08 zhizhijie
feat: 汇总大表日期格式/小数位复核+VBA列宽宏+DataView滚动加载+RULE_004双向与P005阈值联动
2b1bfd 对比 | 目录
2026-09-08 zhizhijie
feat: 汇总大表版式统一——数值0.00/排名整数/列宽自适应/去底色(7月8月母版+还原源+applyDonorNumberFormat)
8b7610 对比 | 目录
2026-09-08 zhizhijie
feat: 中口径排名左块删除(母版+程序防复活)→7月回归收口→8月扩列母版(M0 v4)
0684c5 对比 | 目录
2026-09-08 zhizhijie
docs: 修正 M1 回归结论——分析页与库 0 差异(8676.57 系漏算文本数字6月所致),排名左块错位记入 M2;扩列两行合并标题要求入确认回执
5cfffa 对比 | 目录
2026-09-08 zhizhijie
feat: 汇总大表数据驱动 M1-班线包车——新增 MidCalc 计算服务(企业+个体口径)与 7 月回归测试报告
be562b 对比 | 目录
2026-09-08 zhizhijie
docs: M1-城市客运 城市汇总整链回归(C7)测试报告——当月/累计块 0 差异
3572ff 对比 | 目录
2026-09-08 zhizhijie
feat: 汇总大表数据驱动 M1-网约车——新增 WycSplitCalc 拆分计算服务与 7 月回归测试报告
aee8c1 对比 | 目录
2026-09-08 zhizhijie
feat: 汇总大表数据驱动 M1-城市客运——新增 PaxCalc 计算服务(公交/出租/轨道轮渡)与 7 月回归测试报告
d35a0a 对比 | 目录
2026-09-08 zhizhijie
feat: 汇总大表数据驱动 M1-货运——新增 FreightCalc 计算服务(B1 口径)与 7 月回归测试报告
0d6de6 对比 | 目录
2026-09-08 zhizhijie
docs: M0 影响面实测与月度版式重建器实现策略(扩列仅10长表页,成品页按行块填值)
5e6a24 对比 | 目录
2026-09-08 zhizhijie
docs: 改造方案 v1.1 修订(按需扩列/先7月后8月/三类小表前端隐藏)与 09-08 工作日结
e26930 对比 | 目录
3个文件已删除
12个文件已修改
18个文件已添加
1886 ■■■■■ 已修改文件
docs/init.sql 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/公路旅客/模板_公路旅客运政车辆数.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/公路旅客/模板_公路旅客运政车辆数.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_M1-城市客运-公交出租轨道轮渡_2026-09-08.md 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_M1-城市客运-城市汇总整链回归_2026-09-08.md 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_M1-班线包车MidCalc_2026-09-08.md 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_M1-网约车拆分WycSplitCalc_2026-09-08.md 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_M1-货运取数链路与7月回归_2026-09-08.md 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_中口径排名左块删除与7月回归收口及8月扩列_2026-09-08.md 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_整改8项回归验证_2026-09-08.md 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_汇总大表日期格式与规则联动及数据查看滚动加载_2026-09-08.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_汇总大表版式统一_2026-09-08.md 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工作日结/工作日结_2026-09-08.md 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工具/VBA_生成物全表自动列宽_2026-09-08.txt 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/汇总大表数据驱动改造方案_2026-09-07.md 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/生成汇总大表/2026年7月道路运输量汇总表.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/生成汇总大表/2026年7月道路运输量汇总表_7月清空测试版.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/生成汇总大表/2026年7月道路运输量汇总表_7月清空测试版v2.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/生成汇总大表/2026年8月道路运输量汇总表.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/确认回执汇总与剩余疑问_汇总大表数据驱动改造_2026-09-07.md 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/要求与验收基线清单.md 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/FreightCalc.java 225 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/MidCalc.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/PaxCalc.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/rulemanage/controller/RuleManageController.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-web/src/views/AuditResult.vue 268 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-web/src/views/DataImport.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-web/src/views/DataView.vue 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-web/src/views/ReportExport.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/init.sql
@@ -290,7 +290,7 @@
('车辆数运政对比', 'RULE_001', '企业上报车辆数与运政数据对比,差距>5台需核实', 'vehicleTotal', 'DIFF', '5', 'RED', 'H2032', 1, 1),
('吨位运政对比', 'RULE_002', '企业上报吨位与运政数据对比,差距>50需核实', 'tonsTotal', 'DIFF', '50', 'RED', 'H2032', 1, 2),
('周转量轨迹对比', 'RULE_003', '上报周转量与轨迹测算周转量对比,高出30%需核实', 'turnoverTotal,trackMileage', 'RATIO', '1.3', 'RED', 'H2032', 1, 3),
('货运类型环比', 'RULE_004', '本月新增货运类型需核实(上月为0、本月有量)', 'freightContainer,freightCoal,freightOilGas,freightCrudeOil,freightMetalOre,freightIronOre,freightBuilding,freightGrain', 'MOM', '0', 'YELLOW', 'H2032', 1, 4),
('货运类型环比', 'RULE_004', '上月无,本月新增,提醒;上月有,本月无,提醒(货运类型环比)', 'freightContainer,freightCoal,freightOilGas,freightCrudeOil,freightMetalOre,freightIronOre,freightBuilding,freightGrain', 'MOM', '0', 'YELLOW', 'H2032', 1, 4),
('集装箱异常', 'RULE_005', '无集装箱车但有集装箱货运量需核实', 'freightContainer,vehicleContainer', 'NULL_CHECK', '0', 'RED', 'H2032', 1, 5),
('整车吨位范围', 'RULE_006', '整车吨位不在4-40吨之间', 'tonsWhole', 'OUT_RANGE', '4,40', 'YELLOW', 'H2032', 1, 6);
@@ -446,7 +446,7 @@
('载客位数运政对比(旅客)', 'RULE_P002', '核定载客位数与运政载客位数对比,差距>50需核实', 'seatTotal', 'DIFF', '50', 'RED', 'H2031', 1, 22),
('客运量环比=0核实', 'RULE_P003', '一类/二类/三类/四类/包车客运量环比上个月等于0需核实', 'passengerClass1,passengerClass2,passengerClass3,passengerClass4,passengerCharter', 'MOM_EQ_ZERO', '0', 'YELLOW', 'H2031', 1, 23),
('旅客周转量环比=0核实', 'RULE_P004', '一类/二类/三类/四类/包车旅客周转量环比上个月等于0需核实', 'turnoverClass1,turnoverClass2,turnoverClass3,turnoverClass4,turnoverCharter', 'MOM_EQ_ZERO', '0', 'YELLOW', 'H2031', 1, 24),
('平均运距环比超±10%核实', 'RULE_P005', '一类/二类/三类/四类平均运距环比上个月超过±10%需核实', 'avgDistanceClass1,avgDistanceClass2,avgDistanceClass3,avgDistanceClass4', 'MOM_PCT', '10', 'YELLOW', 'H2031', 1, 25),
('平均运距环比超±20%核实', 'RULE_P005', '一类/二类/三类/四类平均运距环比上个月超过±20%需核实', 'avgDistanceClass1,avgDistanceClass2,avgDistanceClass3,avgDistanceClass4', 'MOM_PCT', '20', 'YELLOW', 'H2031', 1, 25),
('包车平均运距等于上月核实', 'RULE_P006', '旅游包车平均运距等于上个月需核实', 'avgDistanceCharter', 'MOM_EQ', '0', 'YELLOW', 'H2031', 1, 26),
('无班线有班线客运量核实', 'RULE_P007', '无客运班车(班线车辆数为0)但有班线客运量需核实', 'vehicleSchedule,passengerClass1,passengerClass2,passengerClass3,passengerClass4', 'EXISTENCE', '0', 'RED', 'H2031', 1, 27),
('无包车有包车客运量核实', 'RULE_P008', '无客运包车(包车车辆数为0)但有包车客运量需核实', 'vehicleCharter,passengerCharter', 'EXISTENCE', '0', 'RED', 'H2031', 1, 28);
docs/¹«Â·ÂÿÍ/Ä£°å_¹«Â·ÂÿÍÔËÕþ³µÁ¾Êý.xls
Binary files differ
docs/¹«Â·ÂÿÍ/Ä£°å_¹«Â·ÂÿÍÔËÕþ³µÁ¾Êý.xlsx
Binary files differ
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_M1-³ÇÊпÍÔË-¹«½»³ö×â¹ìµÀÂÖ¶É_2026-09-08.md
New file
@@ -0,0 +1,35 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Šï¼šM1-城市客运 å…¬äº¤/出租/轨道轮渡 å–数核对与 7 æœˆå›žå½’(2026-09-08)
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
M1 ç¬¬äºŒå—:新增城市客运计算服务 `PaxCalc`(`traffic-audit-server/.../reportexport/calc/PaxCalc.java`),对 å…¬äº¤/巡游出租/轨道/轮渡 æä¾›â€œæœˆ â†’ å¸‚å·ž â†’ [客运量,周转量,城市内客运量,城市内周转量](轨道轮渡=轨道客运量,轨道周转量,轮渡客运量,轮渡周转量)”矩阵,与既有 exportCityBusDetail / exportCityTaxiDetail / exportCityRailFerryDetail çš„取数逻辑同构,供汇总大表与小表共用。
## äºŒã€æµ‹è¯•环境
- åº“:`traffic_audit`(127.0.0.1:3308),数据 = 2026-07 çŽ°çŠ¶ï¼›
- å¯¹è´¦æ¯ç‰ˆï¼š`docs/生成汇总大表/_备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx`(含 5-7 æœˆæ•°æ®çš„已修正母版;`2026å¹´7月道路运输量汇总表.xlsx` ä¸º 1-4 æœˆç©ºéª¨æž¶ï¼Œä¸å« 7 æœˆå°è´¦åˆ—);
- å·¥å…·ï¼šmysql CLI + Python é‡ç®— vs æ¯ç‰ˆ O åˆ—(2026-07);后端 `mvn -q -DskipTests compile` é€šè¿‡ã€‚
## ä¸‰ã€å–数口径(与既有导出一致)
| å°è´¦é¡µ | æ¥æº | è¡Œç»“构(O åˆ— 2026-07) |
|---|---|---|
| å…¬äº¤ | `city_bus_monthly` æŒ‰ city æ±‡æ€» [passenger_volume,turnover,passenger_city,turnover_city] | å…¨çœ r5-8;市州 r(9+4i)..r(12+4i)(客运量/周转量/城市内客运量/城市内周转量) |
| å‡ºç§Ÿ | `city_taxi_monthly` åŒå­—段 | åŒ å…¬äº¤ |
| è½¨é“、轮渡 | `city_bus_monthly` çš„ rail_*/ferry_*(轨道=武汉/黄石,轮渡=武汉) | è½¨é“ r6/7=武汉、r8/9=黄石;轮渡 r15/16=武汉;全省=合计公式 |
| å…¨çœ | 17 å¸‚州之和(含跨区县多行累加) | æ¯ç‰ˆå…¨çœè¡Œå…¬å¼ |
## å››ã€ç”¨ä¾‹è¡¨
| # | ç”¨ä¾‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|---|
| 1 | å…¬äº¤å°è´¦é¡µ 2026-07 | DB 2026-07 æŒ‰ city æ±‡æ€» | ä¸Žæ¯ç‰ˆ O åˆ— 18 è¡Œå—(全省+17 å¸‚州×4 æŒ‡æ ‡ï¼‰ä¸€è‡´ | diff=0 | é€šè¿‡ |
| 2 | å‡ºç§Ÿå°è´¦é¡µ 2026-07 | DB 2026-07 æŒ‰ city æ±‡æ€» | åŒä¸Š | diff=0 | é€šè¿‡ |
| 3 | è½¨é“台账页 2026-07 | city_bus rail å­—段(武汉/黄石) | æ­¦æ±‰ 11701.74/96240.86、黄石 30.09/404.37、全省 11731.83 | ä¸€è‡´ï¼ˆ0 diff) | é€šè¿‡ |
| 4 | è½®æ¸¡å°è´¦é¡µ 2026-07 | city_bus ferry å­—段(武汉) | å…¨çœ=武汉=46.05(客运量) | ä¸€è‡´ï¼ˆ0 diff) | é€šè¿‡ |
| 5 | Java ç¼–译 | `mvn -q -DskipTests compile` | æ— é”™è¯¯ | exit=0 | é€šè¿‡ |
## äº”、发现的问题与处置
1. æ³¨é‡Šå†…出现 `rail_*/ferry_*`,其中 `*/` æå‰ç»ˆæ­¢ Javadoc å—注释导致编译失败 â†’ æ”¹ä¸ºâ€œrail ä¸Ž ferry å­—段”,编译通过(本问题仅影响注释写法,与口径无关)。
2. å¯¹è´¦æ¯ç‰ˆé€‰æ‹©ï¼š`2026å¹´7月…汇总表.xlsx` æ˜¯ 1-4 æœˆç©ºéª¨æž¶ï¼ˆå°è´¦é¡µæ—  5-7 æœˆåˆ—),7 æœˆå°è´¦æ•°å€¼é¡»ä»¥ `_备份_…_清理前_20260904.xlsx` ä¸ºåŸºçº¿â€”—已在测试环境注明,M2 å›žå½’时统一用该备份文件。
3. æ•°æ®ä¾§å£å¾„复核(无需改动):市州存在多个区县代码行,须按 city æ±‡æ€»ç´¯åŠ ï¼ˆä¸Ž Java æ—¢æœ‰å®žçŽ°ä¸€è‡´ï¼‰ã€‚
## å…­ã€ç»“论与下一步
- ç»“论:公交/出租/轨道/轮渡 7 æœˆå°è´¦å¯ä»Žåº“完整复算(0 å·®å¼‚),`PaxCalc` å·²å…¥åº“并通过编译。
- ä¸‹ä¸€æ­¥ï¼šM1-网约车(WyCSplitCalc:订单×全省 pin×巡游出租拆分)→ åŸŽå¸‚客运台账/城市汇总/城市分市州 7 æœˆå›žå½’ â†’ M1-MidCalc â†’ M2 SummaryFiller å†™å›žä¸Žé€æ ¼ diff。
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_M1-³ÇÊпÍÔË-³ÇÊлã×ÜÕûÁ´»Ø¹é_2026-09-08.md
New file
@@ -0,0 +1,23 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Šï¼šM1-城市客运 åŸŽå¸‚汇总整链回归(C7,2026-09-08)
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
验证“公交/巡游出租/网约车/轨道/轮渡 â†’ åŸŽå¸‚汇总”的整链可复算性(口径 C7):总客运量(周转量)=公交城市内+出租车(巡游城市内+网约城市内)+轨道及轮渡(全部算城市内)。公交/出租取 city_bus/city_taxi çš„ passenger_city/turnover_city,网约取 WycSplitCalc pin(pinCK/pinCZ),轨道轮渡取 city_bus rail/ferry å­—段——均为当月值与 1-7 æœˆç´¯è®¡å€¼ä¸¤ç»„。
## äºŒã€æµ‹è¯•环境
- åº“:`traffic_audit`(2026-01~07);网约 pin:`docs/城市客运/模板_网约车订单及全省总量.xlsx`(1-7 æœˆè¡Œé½å…¨ï¼‰ï¼›
- å¯¹è´¦æ¯ç‰ˆï¼š`docs/生成汇总大表/_备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx` åŸŽå¸‚汇总页;
- å·¥å…·ï¼šmysql CLI + openpyxl + Python é€è¡Œæ¯”对;容差 0.011。
## ä¸‰ã€ç”¨ä¾‹ä¸Žç»“æžœ
| å— | æ¯”对范围 | ç»“æžœ |
|---|---|---|
| å½“月块(2026-07,r14-20) | æ€»/公交/出租车/巡游/网约/轨道/轮渡 Ã— å®¢è¿é‡(B)+周转量(D) | 0 å·®å¼‚ |
| ç´¯è®¡å—(2026-1-7,r3-9) | åŒä¸Š | 0 å·®å¼‚ |
关键数值命中:当月总 33068.03 / 206194.82;公交 11508.07 / 59265.28;巡游 5351.24 / 27422.13;网约 4430.84 / 22705.61(=pinCK/pinCZ);轨道 11731.83 / 96645.23;轮渡 46.05 / 156.57;累计总 237412.33 / 1479655.10。
## å››ã€ç»“论与说明
- C7 ç»„合口径可由库整链复算当月与累计两组(0 å·®å¼‚);网约 pin 1-7 æœˆè¾“入完整。
- åŸŽå¸‚客运台账页/城市分市州页为跨台账页公式页(已在分模式回归中逐页验证 0 å·®å¼‚),M2 æŒ‰åŒæž„公式/值写回即可,不需单独再对。
- M1-城市客运整体通过:公交/出租/轨道轮渡/网约/城市汇总 5 é¡¹å›žå½’ 0 å·®å¼‚。
- ä¸‹ä¸€æ­¥ï¼šM1-MidCalc(班线包车/中口径明细/客运量/排名/分析/道路运输周转量)。
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_M1-°àÏß°ü³µMidCalc_2026-09-08.md
New file
@@ -0,0 +1,46 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Šï¼šM1-班线包车(MidCalc ä¼ä¸š+个体取数)与 7 æœˆå›žå½’(2026-09-08)
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
汇总大表数据驱动改造 M1 ç¬¬äº”块:把“班线包车页”的数据库取数固化为可复用 Java è®¡ç®—服务
`MidCalc`(`traffic-audit-server/.../reportexport/calc/MidCalc.java`),输出
“月 â†’ å¸‚å·ž/全省 â†’ [客运量(含个体), å‘¨è½¬é‡(含个体), ä¸ªä½“客运量, ä¸ªä½“周转量](万人/万人公里)”。
它是中口径明细/排名/分析/道路运输周转量等派生页在数据库中唯一的公路班线数据源。
## äºŒã€æµ‹è¯•环境
- åº“:`traffic_audit`(127.0.0.1:3308),数据 = 2026-01~07 å·²å¯¼å…¥çŽ°çŠ¶ï¼›
- æ¯ç‰ˆåŸºçº¿ï¼š`docs/生成汇总大表/_备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx`(班线包车页,2026 æœˆåº¦æ•°å€¼åˆ— C/E/G/I/K/M/O å‡å·²ç¼“存);
- å·¥å…·ï¼šmysql CLI å–æ•° + Python é€æ ¼æ¯”对(`_m1_mid_regress.py`,结果 `_m1_mid_regress.txt`);后端 `mvn -q -DskipTests compile` é€šè¿‡ï¼ˆexit=0)。
## ä¸‰ã€å–数口径(经母版逐格反推确认,2026-07 ä¸Ž 1-7 æœˆå…¨éƒ¨å‘½ä¸­ï¼‰
| æ¯ç‰ˆè¡Œ | å£å¾„ | æ¥æº |
|---|---|---|
| å®¢è¿é‡ / æ—…客周转量(万人 / ä¸‡äººå…¬é‡Œï¼‰ | **企业总计 + ä¸ªä½“å¹¶å…¥** | `h2031_enterprise_monthly.passenger_total / turnover_total`(人/人公里)÷10000,叠加 `passenger_individual_monthly.passenger_count / turnover` Ã·10000 |
| å…¶ä¸­ï¼šä¸ªä½“客运量 / ä¸ªä½“旅客周转量 | ä¸ªä½“æ•° Ã·10000 | ä»…黄石/襄阳/荆门/孝感/黄冈/随州 6 å¸‚有数 |
| æ¹–北省(全省)行 | 17 å¸‚州之和 | ä¸Žæ¯ç‰ˆâ€œå…¨çœè¡Œ=Σ城市行”公式一致 |
| åŒæ¯” | ä¸è®¡ç®— | Q1:以母版缓存 2025 å‚照组为基准,由写回层填表时计算 |
## å››ã€ç”¨ä¾‹è¡¨
| # | ç”¨ä¾‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|---|
| 1 | ä¼ä¸šå®¢è¿é‡/周转量(含个体并入) | åº“ 2026-01~07 | ä¸Žæ¯ç‰ˆç­çº¿åŒ…车城市行“客运量/周转量”列逐格一致 | 350 æ ¼ diff=0 | é€šè¿‡ |
| 2 | å…¶ä¸­ä¸ªä½“客运量/周转量 | åº“ 2026-01~07 | ä¸Žæ¯ç‰ˆâ€œå…¶ä¸­ä¸ªä½“”行逐格一致(6 å¸‚) | å«äºŽä¸Šè¿° 350 æ ¼ï¼Œdiff=0 | é€šè¿‡ |
| 3 | æ¹–北省行 = Î£ 17 å¸‚å·ž | åº“汇总 | ä¸Žæ¯ç‰ˆå…¬å¼å£å¾„一致 | ä¸€è‡´ | é€šè¿‡ |
| 4 | ä¸­å£å¾„分析“公路班线”锚点 | åº“ 2026-07 / 1-7 ç´¯è®¡ | å½“月 1631.2013、累计 11204.5406(母版缓存值) | 1612.2015+18.9998=1631.2013;11062.7993+141.7413=11204.5406 | é€šè¿‡ |
| 5 | ä¸­å£å¾„分析页锚点(当月/累计) | åº“ 2026-01~07 é‡ç®— | å½“月 4028.3213、累计 27524.4906 åŠå››ç±»ç»†åˆ† | å½“月 1631.2013/1466.66/509/421.46、累计 11204.5406/10194.09/3640.95/2484.91,全部一致 | é€šè¿‡ |
| 6 | Java ç¼–译 | `mvn -q -DskipTests compile` | æ— é”™è¯¯ | exit=0 | é€šè¿‡ |
## äº”、发现的问题与处置
1. åˆç‰ˆå›žå½’误把“客运量”行当作纯企业数,比对发现黄石等 6 å¸‚母版值 = ä¼ä¸š+个体(差量恰为该市个体值);
   ç»é€æ ¼åæŽ¨ç¡®è®¤**班线包车“客运量/周转量”口径含个体**,“其中个体”行为其子集 â†’ å·²æŒ‰å«ä¸ªä½“口径实现并复跑 0 å·®å¼‚。
2. æ¯”对脚本初期把母版**文本数字**当作空值跳过(公交台账页 2026-06 M åˆ—以文本存储,如 `'4970.63'`),
   å¯¼è‡´â€œç´¯è®¡åŸŽé™…城乡公交”一度误算为 8676.57、与中口径分析页 10194.09 ä¸ç¬¦ï¼ˆå·®é‡æ°ä¸º 6 æœˆ 1517.52)。
   å·²æ”¹ä¸ºâ€œæ–‡æœ¬â†’数值”归一后复算:**中口径分析页当月/累计与库重算逐项 0 å·®å¼‚,并非旧残值**。
   å®žçŽ°æ³¨æ„ï¼šè¯»æ¯ç‰ˆç¼“å­˜ä¸€å¾‹å…¼å®¹æ–‡æœ¬æ•°å­—ã€‚
3. ä¸­å£å¾„排名页**左块**(A..H,无期次标题)数值经比对为陈旧错位:A åˆ—客运量=2026-01 å½“月值、
   E åˆ—周转量=2026-02 å½“月值(17 å¸‚州逐格吻合);**右块(J..R,1-7 æœˆç´¯è®¡ï¼‰ä¸Žåº“重算 0 å·®å¼‚**。
   å¤„置:M2 ç”Ÿæˆæ—¶å·¦å—应改为“当月 2026-07”重算修正,右块沿用现有口径即可。
## å…­ã€ç»“论与下一步
- ç»“论:班线包车页可从库完整复算(350 æ ¼ 0 å·®å¼‚),口径锁定;`MidCalc` å·²å…¥åº“并通过编译。
- ä¸‹ä¸€æ­¥ï¼šâ‘  M1 å‰©ä½™æ´¾ç”Ÿé¡µå£å¾„核对(排名/分析/道路运输周转量,建议随 M2 é‡ç®—实现);② M2 SummaryFiller
  å†™å›žå°è´¦+成品页并逐格 diff(含排名页左块“当月 2026-07”错位修正的验证);③ 8 æœˆå®žå¼¹ã€‚
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_M1-ÍøÔ¼³µ²ð·ÖWycSplitCalc_2026-09-08.md
New file
@@ -0,0 +1,33 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Šï¼šM1-网约车 WycSplitCalc å–æ•°/拆分核对与 7 æœˆå›žå½’(2026-09-08)
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
M1 ç¬¬ä¸‰å—:新增网约车拆分计算服务 `WycSplitCalc`(`traffic-audit-server/.../reportexport/calc/WycSplitCalc.java`),把 exportWycSplit çš„æ‹†åˆ†ç®—法抽成可复用矩阵:输入=《网约车订单及全省总量.xlsx》(pin B-E + 17 å¸‚州订单 G-W)+ å·¡æ¸¸å‡ºç§ŸæœˆæŠ¥ï¼ˆDB),输出 17 å¸‚å·ž+全省的 å®¢è¿é‡/城市内客运量/周转量/城市内周转量。供汇总大表网约车台账页与城市客运各页共用(口径 B5)。
## äºŒã€æµ‹è¯•环境
- åº“:`traffic_audit`(2026-07 çŽ°çŠ¶ï¼‰ï¼›
- è¾“入文件:`docs/城市客运/模板_网约车订单及全省总量.xlsx`(月度输入 sheet,2026-07 è¡Œåœ¨ openpyxl ç¬¬ 8 è¡Œï¼‰ï¼›
- å¯¹è´¦æ¯ç‰ˆï¼š`docs/生成汇总大表/_备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx` ç½‘约车台账页;
- å·¥å…·ï¼šopenpyxl + mysql CLI + Python å¤åˆ»ç®—法比对;后端 `mvn -q -DskipTests compile` é€šè¿‡ã€‚
## ä¸‰ã€ç®—法(与 exportWycSplit ä¸€è‡´ï¼‰
- C[i]=订单占比×全省网约车客运量 pinK;F[i]=巡游出租城市内客运量/客运量(城市内占比代理);
- åŸŽå¸‚内 H:武汉=C0×F0(保持自身);其余 = (Ci×Fi)/(ΣG−C0F0) Ã— (pinCK−C0F0);
- å‘¨è½¬é‡ Gl[i]=Ci×平均运距 / Î£(Ci×平均运距) Ã— pinZ;城市内周转量 Hl åŒæ³•(用城市内平均运距、pinCZ);
- å·¡æ¸¸å‡ºç§Ÿå¹³å‡è¿è·ç¼ºçœæ—¶æŒ‰ å‘¨è½¬é‡/客运量 å– 2 ä½å°æ•°å…œåº•。
## å››ã€ç”¨ä¾‹è¡¨
| # | ç”¨ä¾‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|---|
| 1 | 2026-07 æ‹†åˆ†ï¼ˆ17 å¸‚州×4 æŒ‡æ ‡ï¼‰ | è®¢å•+pin+出租 DB | ä¸Žæ¯ç‰ˆç½‘约车台账 O åˆ—一致 | diff=0 | é€šè¿‡ |
| 2 | å…¨çœè¡Œ | pin è¾“å…¥ | å®¢è¿é‡ 4852.30 / å‘¨è½¬é‡ 28348.02 / åŸŽå¸‚内 4430.84 / åŸŽå¸‚内周转 22705.61 | ä¸Žæ¯ç‰ˆä¸€è‡´ | é€šè¿‡ |
| 3 | åŠ æ€»è‡ªæ´½ | è®¡ç®—数组 | Î£C=pinK、ΣH=pinCK、ΣGl=pinZ、ΣHl=pinCZ | å…¨ç­‰ | é€šè¿‡ |
| 4 | æ­¦æ±‰å£å¾„ | è®¡ç®— | åŸŽå¸‚内=总量(无城际城乡),H=1825.1975、Hl=13036.3929 | ä¸€è‡´ | é€šè¿‡ |
| 5 | Java ç¼–译 | `mvn -q -DskipTests compile` | æ— é”™è¯¯ | exit=0 | é€šè¿‡ |
## äº”、发现的问题与处置
1. è¾“入文件现状名称为 `模板_网约车订单及全省总量.xlsx`,而 ReportExportService.exportWycSplit ä»æŒ‰ç²¾ç¡®å `网约车订单及全省总量.xlsx` å®šä½ï¼ˆresolveAnyTemplate ä¸åšå‰ç¼€åŒ¹é…ï¼‰â†’ è¯¥å°è¡¨ç”Ÿæˆè·¯å¾„当前可能报“未找到模板”。WycSplitCalc.findInput å·²æ”¯æŒä¸¤ç§å‘½åï¼Œå…ˆä¿è¯å¤§è¡¨å–数可用;小表入口的模板定位待 M4 ç»Ÿä¸€æŽ¥çº¿æ—¶ä¿®æ­£ã€‚
2. ç±»åä¸Žæ–‡ä»¶åä¸ä¸€è‡´æ›¾å¯¼è‡´ javac å¤±è´¥å¹¶ç‰µè¿žæ— å…³æ–‡ä»¶æŠ¥é”™ï¼ˆjavac é”™è¯¯æ¢å¤çº§è”)→ å·²ç»Ÿä¸€ä¸º WycSplitCalc.java,编译通过。
## å…­ã€ç»“论与下一步
- ç»“论:网约车拆分可完整复算 7 æœˆæ¯ç‰ˆï¼ˆ0 å·®å¼‚),`WycSplitCalc` å·²å…¥åº“并通过编译。
- ä¸‹ä¸€æ­¥ï¼šåŸŽå¸‚客运台账/城市汇总/城市分市州 7 æœˆæ•´é“¾å›žå½’(公交+出租+网约+轨道轮渡组合口径 C7)→ M1-MidCalc â†’ M2 SummaryFiller。
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_M1-»õÔËÈ¡ÊýÁ´Â·Óë7Ô»عé_2026-09-08.md
New file
@@ -0,0 +1,41 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Šï¼šM1-货运 å–数链路核对与 7 æœˆå›žå½’(2026-09-08)
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
汇总大表数据驱动改造 M1 ç¬¬ä¸€å—:把“货运口径”(规上/规下/合计 Ã— è´§è¿é‡/周转量 Ã— å½“月/累计)固化为可复用的 Java è®¡ç®—服务 `FreightCalc`(`traffic-audit-server/.../reportexport/calc/FreightCalc.java`),并验证:**仅凭数据库即可重算出 7 æœˆå·²ä¿®æ­£æ¯ç‰ˆä¸­è´§è¿é‡æŽ’名、货运周转量排名两张成品页的全部规上/规下数值(逐格 diff=0)**,为后续 SummaryFiller å†™å›žæ¯ç‰ˆæä¾›åŒæºçŸ©é˜µã€‚
## äºŒã€æµ‹è¯•环境
- åº“:`traffic_audit`(127.0.0.1:3308),数据 = 2026-07 å·²å¯¼å…¥çŽ°çŠ¶ï¼›
- æ¯ç‰ˆåŸºçº¿ï¼š`docs/生成汇总大表/2026å¹´7月道路运输量汇总表.xlsx`(2026-07 å·²ä¿®æ­£ç‰ˆï¼‰ï¼›
- å·¥å…·ï¼šmysql CLI å–æ•° + Python é‡ç®—比对;后端 `mvn -q compile` é€šè¿‡ï¼ˆexit=0)。
## ä¸‰ã€å–数口径(与既有 exportFreightRank/exportTurnoverRank åŠç¡®è®¤å›žæ‰§ B1 ä¸€è‡´ï¼‰
| æŒ‡æ ‡ | æ¥æº |
|---|---|
| è§„上货运量(当月/累计,万吨) | `h2032_enterprise_monthly.freight_total`(吨)按市州汇总 Ã·10000 |
| åˆè®¡è´§è¿é‡ï¼ˆä¸‡å¨ï¼‰ | `freight_turnover_import.freight_mMM`(当月)/ `freight_m01..mMM`(累计) |
| è§„下货运量 | åˆè®¡ âˆ’ è§„上(拆分表不存货运量列,不可直接取) |
| è§„上/合计周转量 | `scale_split_transport` MONTH / CUMULATIVE çš„ `above_scale_turnover` / `total_turnover` |
| è§„下周转量 | åˆè®¡ âˆ’ è§„上(与拆分表 below åˆ—核对一致) |
| æ¹–北省行 | 17 å¸‚州之和(与母版“全省行=Σ城市行”公式一致) |
## å››ã€ç”¨ä¾‹è¡¨
| # | ç”¨ä¾‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|---|
| 1 | è´§è¿é‡æŽ’名·累计(1-7月) | åº“ 2026-01~07 | ä¸Žæ¯ç‰ˆ B4:B21(规上)、G4:G21(规下)逐格一致 | diff=0 | é€šè¿‡ |
| 2 | è´§è¿é‡æŽ’名·当月(7月) | åº“ 2026-07 | ä¸Žæ¯ç‰ˆ B25:B42、G25:G42 é€æ ¼ä¸€è‡´ | diff=0 | é€šè¿‡ |
| 3 | è´§è¿å‘¨è½¬é‡æŽ’名·累计 | scale_split CUMULATIVE | ä¸Žæ¯ç‰ˆ B5:B22、G5:G22 ä¸€è‡´ | diff=0 | é€šè¿‡ |
| 4 | è´§è¿å‘¨è½¬é‡æŽ’名·当月 | scale_split MONTH | ä¸Žæ¯ç‰ˆ B27:B44、G27:G44 ä¸€è‡´ | diff=0 | é€šè¿‡ |
| 5 | å…¨çœè¡Œä¸Ž Î£ åŸŽå¸‚一致性 | 17 å¸‚州求和 | å…¨çœè§„上当月=3754.7823(母版) | è®¡=3754.7823 | é€šè¿‡ |
| 6 | Java ç¼–译 | `mvn -q -DskipTests compile` | æ— é”™è¯¯ | exit=0 | é€šè¿‡ |
## äº”、发现的问题与处置
1. H2032 æŒ‰ `region_code` æ±‡æ€»æ—¶å­˜åœ¨â€œä¸€å¸‚多区县代码”,初版脚本误用覆盖而非累加 â†’ å·²æ”¹ç´¯åŠ ï¼Œ0 å·®å¼‚。
2. ç´¯è®¡åˆè®¡é¦–版只加到 6 æœˆï¼ˆæ¼ m07)→ å·²æ”¹ 1..7 å…¨åŠ ï¼Œ0 å·®å¼‚。
3. `freight_turnover_import.region_name` å­˜åœ¨â€œæž—区/神农架林区/天门/天门市”等别名 â†’ ç»Ÿä¸€ç» RegionUtil åˆ«åå½’一后比对,0 å·®å¼‚。
4. å‘¨è½¬é‡ä¸¤é¡µåœ¨ä»»ä½•版本比对中均 0 å·®å¼‚(拆分表口径稳定)。
(上述均为回归脚本问题,非业务口径问题;已在 Python å›žå½’与 Java å®žçŽ°ä¸­åŒæ­¥ä¿®æ­£ã€‚ï¼‰
## å…­ã€ç»“论与下一步
- ç»“论:货运口径链路可从库完整复算 7 æœˆæ¯ç‰ˆï¼Œå£å¾„锁定;`FreightCalc` å·²å…¥åº“并通过编译。
- è¯´æ˜Žï¼šåŒæ¯”未在本服务计算——按 Q1 ç”±å†™å›žå±‚以母版缓存 2025 å‚照组为基准(去年同期 DB åˆ—为空,已核对)。
- ä¸‹ä¸€æ­¥ï¼šâ‘  M1-PaxCalc(公交/出租/轨道轮渡/网约/班线包车/城市客运口径,用城市客运三表 7 æœˆå›žå½’);② M1-MidCalc;③ M2 SummaryFiller å†™å›žå°è´¦+成品页并逐格 diff;④ 8 æœˆå®žå¼¹ã€‚
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_Öпھ¶ÅÅÃû×ó¿éɾ³ýÓë7Ô»عéÊÕ¿Ú¼°8ÔÂÀ©ÁÐ_2026-09-08.md
New file
@@ -0,0 +1,32 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_中口径排名左块删除与7月回归收口及8月扩列_2026-09-08
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
1. æŒ‰ 2026-09-08 ä¸šåŠ¡æ‹æ¿ï¼ˆã€Šç¡®è®¤å›žæ‰§æ±‡æ€»â€¦ã€‹ç¬¬å…­èŠ‚ç¬¬ 6 æ¡ï¼‰ï¼šåˆ é™¤æ±‡æ€»å¤§è¡¨ `中口径排名` é¡µ r1-20 **左块**(A..H,陈旧当月数据:A=2026-01 å®¢è¿é‡ã€E=2026-02 å‘¨è½¬é‡ï¼Œæ— æœŸæ¬¡æ ‡é¢˜ï¼‰ï¼Œé¡µå†…仅保留两张可见表:r1-20「1-7 æœˆç´¯è®¡ã€ä¸Ž r22-41「7 æœˆå½“月」(均在 J..R)。
2. ç¨‹åºä¾§åŒæ­¥ï¼š`ReportExportService.restoreSummaryMonthlyBlocks` å¯¹ `中口径排名` é¡µ A..H(0-based 0-7 åˆ—)**永不从备份母版回填**,杜绝“母版删了、一生成又复活”。
3. 7 æœˆå›žå½’收口(含排名页左块删除一起验);通过后再扩 8 æœˆæ¯ç‰ˆï¼ˆM0 ç‰ˆå¼é‡å»ºå™¨ v4)。
## äºŒã€æµ‹è¯•环境
- æ¯ç‰ˆï¼š`docs/生成汇总大表/2026å¹´7月道路运输量汇总表.xlsx`(清理后骨架)与 `_备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx`(完整 7 æœˆï¼Œè¿˜åŽŸæºï¼‰ã€‚
- ç”Ÿæˆé“¾è·¯ï¼šåŽç«¯ `mvn spring-boot:run`(8090),接口 `GET /api/report/export/summaryWorkbook?period=2026-07|2026-08&mode=month`。
- æ ¡éªŒå·¥å…·ï¼šopenpyxl å†…容级 diff(data_only=False åŒè¯»ï¼‰ã€`mvn -DskipTests compile`。
## ä¸‰ã€ç”¨ä¾‹è¡¨
| # | ç”¨ä¾‹ | è¾“å…¥/操作 | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|---|
| 1 | æ¯ç‰ˆåˆ é™¤å·¦å— | 7 æœˆæ¯ç‰ˆ `中口径排名` A1:H20 æ¸…空 + ç§»é™¤ A1:H1/A22:H22 åˆå¹¶ | ä»… J1:R1、J22:R22 ä¿ç•™åˆå¹¶ï¼›A..H r1-20 æ— å†…容 | æ¸… 148 æ ¼ï¼›åˆå¹¶ä»…剩 J1:R1/J22:R22;整簿 diff ä»…此区 | é€šè¿‡ |
| 2 | ç¨‹åºä¸å›žå¡«å·¦å— | åŽç«¯ç”Ÿæˆ 2026-07 æ±‡æ€»è¡¨ï¼ˆèµ°è¿˜åŽŸé€»è¾‘ï¼‰ | ç”Ÿæˆç‰©æŽ’名页无 A..H é™ˆæ—§å— | ç”Ÿæˆç‰© vs å¤‡ä»½æ¯ç‰ˆ diff=179,其中 148=左块删除(预期)、31=日期列 int/float å†™æ³•(显示相同) | é€šè¿‡ |
| 3 | 7 æœˆ M1 æ•°å€¼å›žå½’ | è´§è¿/公交出租轨道/网约/城市汇总/班线包车脚本复跑(库 2026-01~07) | å„页与母版缓存 0 å·®å¼‚ | freight/pax/wyc/urban/mid å…¨ diffs=0 | é€šè¿‡ |
| 4 | Java ç¼–译 | `mvn -q -DskipTests compile` | æ— é”™è¯¯ | exit=0 | é€šè¿‡ |
| 5 | 8 æœˆæ‰©åˆ—(v4) | ä¿®æ­£åŽ 7 æœˆæ•´æœ¬ â†’ 8 æœˆæ¯ç‰ˆ `2026å¹´8月道路运输量汇总表.xlsx` | 8 ä¸ªå°è´¦/合成页 +2 åˆ—(2026å¹´8月/8月同比),累计滚动,2025 å‚照右移 | 8 é¡µ maxCol +2;累计 +Q、同比分母 +AJ;7→8 æœˆæ•°å€¼æ ¼ 2387 ä¸ª 0 å·®å¼‚ | é€šè¿‡ |
| 6 | æ‰©åˆ—版式细节 | æ–° 8 æœˆä¸¤åˆ—表头、隐藏列、排名页 | ä¸¤è¡Œçºµå‘合并表头;隐藏列平移保留;排名页无左块 | Q/R è¡¨å¤´ä¸Ž 15/16 åˆ—同款合并(各页行位正确);隐藏列按 >=17 +2 ä¿ç•™ï¼›æŽ’名页仅 J ç³»åˆå¹¶ | é€šè¿‡ |
| 7 | ç¨‹åºè¯»æ–° 8 æœˆæ¯ç‰ˆ | åŽç«¯ç”Ÿæˆ 2026-08(POI è¯»æ¯ç‰ˆï¼‰ | ä¸æŠ¥ Zip-bomb、输出=母版 | styles.xml æ¯”值 0.0099 è§¦å‘ POI è¯¯æŠ¥â†’`ZipSecureFile.setMinInflateRatio(0.0001)` åŽä¿®å¤ï¼›ç”Ÿæˆç‰©ä¸Ž 8 æœˆæ¯ç‰ˆå†…容 diff=0 | é€šè¿‡ |
## å››ã€å‘现的问题与处置
1. POI Zip-bomb è¯¯æŠ¥ï¼šopenpyxl é‡å†™åŽ styles.xml åŽ‹ç¼©æ¯” 0.009988 < 0.01,POI æ‹’读新版 8 æœˆæ¯ç‰ˆã€‚处置:`exportSummaryWorkbook` è¯»æ¯ç‰ˆå‰ä¸´æ—¶è°ƒä½Žé˜ˆå€¼ï¼ˆ0.0001)并在 finally è¿˜åŽŸï¼Œä»…å½±å“æœ¬æ–¹æ³•è¯»å–å†…éƒ¨æ¨¡æ¿ã€‚
2. åŽ†å²é—ç•™ï¼š8 æœˆç‰ˆå¼åŽŸåž‹ï¼ˆv3 äº§å‡ºï¼‰çš„æ–° 8 æœˆåˆ—无两行合并、隐藏列标记丢失、以“清理前备份”为源会带回排名左块;v4 ä¿®æ­£æºä¸ºâ€œå·²åˆ å·¦å—çš„ 7 æœˆç”Ÿæˆç‰©â€å¹¶è¡¥ä¸¤è¡Œåˆå¹¶ä¸Žéšè—åˆ—平移。
3. æœ¬ç‰ˆ 8 æœˆæ¯ç‰ˆ 2026 ç»„累计公式已滚动至 1-8 æœˆï¼ˆ+Q/+AJ),但 8 æœˆå½“月值仍为空,待 8 æœˆæ•°æ®å¯¼å…¥åŽç”± M2/M3 å¡«å€¼ã€‚
## äº”、结论
- ä¸­å£å¾„排名左块删除在母版与程序生成链路均生效;7 æœˆå›žå½’收口通过(排名页随同验证无左块、其余页与库 0 å·®å¼‚)。
- 8 æœˆæ‰©åˆ—母版已生成并通过程序端到端读取验证,可作为后续 8 æœˆæ•°æ®å¡«å……的母版。
- é—留:M2 SummaryFiller / M3 8 æœˆå®žå¼¹ï¼ˆå¯¼æ•°åŽå¡« 2026-08 å½“月值)、母版缓存校验走 Q1 å£å¾„。
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_Õû¸Ä8Ïî»Ø¹éÑéÖ¤_2026-09-08.md
New file
@@ -0,0 +1,40 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_整改8项回归验证_2026-09-08
## åŠŸèƒ½è¯´æ˜Ž
针对用户 2026-09-08 æå‡ºçš„ 8 æ¡ç•Œé¢ä¸ŽåŠŸèƒ½æ•´æ”¹ï¼ˆâ€œä¸Šæ¬¡è¯´æ”¹çš„æ²¡æ”¹â€ï¼‰é€æ¡è½åœ°å¹¶å›žå½’ï¼š
1. æ•°æ®æŸ¥çœ‹é¡µæ»šåŠ¨åˆ°åº•è‡ªåŠ¨åŠ è½½æ›´å¤šæ•°æ®è¡Œï¼ˆä¸å†åªæ˜¾ç¤ºä¸€é¡µï¼‰ï¼›
2. å®¡æ ¸è§„则阈值改动后,规则名称/规则说明中的百分比随阈值联动(如 Â±10%→±20%→±30%→回退),并对全部规则生效、持久化;
3. å…¬è·¯æ—…客“运政车辆数”导入兼容新版运政库导出文件(.xls,表头 OWNERNAME/总车辆数/客位数,无企业代码列),同时兼容旧中文四列表头;
4. å®¡æ ¸ç»“果:删除“企业解释”列及解释输入框;点击“数据”弹出该企业该报表期的月报样式查看;
5. ç”Ÿæˆæ±‡æ€»å¤§è¡¨ï¼ˆæ•´æœ¬ï¼‰ï¼šå¯¼å‡ºæ—¶å–消全部页筛选(班线包车等页不再像缺数据)、还原隐藏行、按数据自动加宽列宽、数值保留两位小数显示(不丢失全精度)、公式保留且打开自动重算;
6. æŠ¥è¡¨ç”Ÿæˆé¡µï¼šåŸŽå¸‚客运报表标签下的 tag åˆ é™¤ã€åç§°ä¸Žå…¶å®ƒä¸€è‡´ï¼›â€œé“路运输量汇总表(整本)”移入首个独立 Tab“汇总大表”;
7. å®¡æ ¸ç»“果页筛选栏顺序:报表期在报表类型左边;
8. æŠ¥è¡¨ç”Ÿæˆæœˆä»½ä¸‹æ‹‰ï¼šé»˜è®¤æœ€å…ˆå±•示当前年份(2026 å¹´ 1–12 æœˆï¼‰ï¼Œå†å±•示 2025 å¹´ä¸Ž 2027 å¹´ã€‚
## æµ‹è¯•环境
- æœ¬æœºï¼šWindows,后端 Spring Boot :8090,前端 Vue2 :8080(页面经重新编译加载最新代码,后端已重启)。
- ç™»å½•:admin(app.captcha.required=false æµ‹è¯•模式)。
- æ•°æ®åº“:本机 traffic-audit å¼€å‘库(含 2026-08 è´§è¿æœˆæŠ¥ 13056 æ¡ç­‰æ—¢æœ‰æ•°æ®ï¼‰ã€‚
- æ—¥æœŸï¼š2026-09-08。
## ç”¨ä¾‹è¡¨ï¼ˆè¾“å…¥ / é¢„期 / å®žé™… / ç»“论)
| # | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|------|------|------|------|
| 1 | æ•°æ®æŸ¥çœ‹é¡µï¼ˆH203-2,共 13056 æ¡ï¼‰ï¼Œå°†è¡¨æ ¼ç«–向滚动到底 | è‡ªåŠ¨è¿½åŠ ä¸‹ä¸€é¡µï¼Œè¡Œæ•° 20→40,页签从“第 1 é¡µâ€å˜â€œç¬¬ 2 é¡µâ€ | åˆå§‹ 20 è¡Œï¼ˆå®¹å™¨é«˜ 299、内容高 796);滚动后 40 è¡Œï¼ˆå†…容高 1592,scrollTop 496.8),“共 13056 æ¡è®°å½•,当前第 2 é¡µâ€ | é€šè¿‡ |
| 2 | è§„则管理:将 P005 é˜ˆå€¼ 20→30→20 åå¤ä¿å­˜ | åç§°/说明中的 Â±N% éšåŠ¨ï¼ˆÂ±20%↔±30%),其它无关数字不动,重开页面仍一致 | P005 ä»¥åŠ B001(增速)、E005(±%)、RULE_003(高出%)、T001(差距%)均实测通过,显式(0.2→0.1)与比值(1.3→1.5)口径均正确并持久化 | é€šè¿‡ |
| 3 | å¯¼å…¥ 2026-08 å…¬è·¯æ—…客运政车辆数(新 .xls:OWNERNAME/总车辆数/客位数) | 750 å®¶ä¼ä¸šå…¨éƒ¨å…¥åº“、抽样数值与源文件一致;旧中文表头文件仍可导入 | æ–°æ¨¡æ¿æ”¾å…¥ docs/公路旅客/模板_公路旅客运政车辆数.xls å¯¼å…¥æˆåŠŸï¼›æŠ½æŸ¥ç§­å½’åŽ¿å¿—å°š 5/35、安陆市涢安 26/668 ä¸Žæºæ–‡ä»¶ä¸€è‡´ï¼›ä¼ä¸šä»£ç åˆ—为空属正常(新模板无此列) | é€šè¿‡ |
| 4 | å®¡æ ¸ç»“果页:①检查行内操作列;②点击“数据” | â‘  æ— â€œä¼ä¸šè§£é‡Šâ€åˆ—/输入框,只有 æ•°æ®/确认/忽略;② å¼¹å‡ºè¯¥ä¼ä¸šè¯¥æŠ¥è¡¨æœŸçš„æœˆæŠ¥æ ·å¼ï¼ˆæ ‡é¢˜=企业名+报表期 æœˆæŠ¥æ•°æ®ï¼›åˆ†åŒº=运力情况→运输生产情况→平均指标→填报人信息;底部关闭可用) | å®žæµ‹æ—  4 åˆ—“企业解释”残留;“数据”弹窗内容与分区顺序符合预期,关闭正常 | é€šè¿‡ |
| 5 | å¯¼å‡ºâ€œé“路运输量汇总表(整本)”2026-08 å¹¶è„šæœ¬æ ¡éªŒ 19 ä¸ª sheet | å…¨éƒ¨é¡µæ—  autoFilter、无隐藏行;数值单元格保留两位小数显示与全精度;系统求和/同比公式保留;打开即重算 | æ ¡éªŒæ–‡ä»¶ï¼šdocs/生成汇总大表/输出/生成_道路运输量汇总表_2026-08_清理验证.xlsx;19 é¡µ filter=None、hidden=[],C4/D4 ç­‰é•¿æ±‚和与同比公式保留 | é€šè¿‡ |
| 6 | æŠ¥è¡¨ç”Ÿæˆé¡µæ£€æŸ¥ Tab ä¸Žæ ‡ç­¾ | é¡ºåºï¼šæ±‡æ€»å¤§è¡¨â†’货运报表→旅客报表→城市客运报表→投资报表→能耗报表;城市客运无 tag;整本从城市客运移出 | æ±‡æ€»å¤§è¡¨ç‹¬ç«‹ä¸ºé¦–组;城市客运标签无“公交/出租/轨道轮渡/汇总”tag | é€šè¿‡ |
| 7 | å®¡æ ¸ç»“果页筛选栏 | æŠ¥è¡¨æœŸåœ¨æŠ¥è¡¨ç±»åž‹å·¦è¾¹ | ç­›é€‰æ é¡ºåºä¸ºâ€œæŠ¥è¡¨æœŸ â†’ æŠ¥è¡¨ç±»åž‹ â†’ æ‰§è¡Œå®¡æ ¸æŒ‰é’®â€ | é€šè¿‡ |
| 8 | æŠ¥è¡¨ç”Ÿæˆé¡µç‚¹å¼€æœˆä»½ä¸‹æ‹‰ | é»˜è®¤å±•开当前年份 2026 å¹´ 1–12 æœˆï¼Œå…¶æ¬¡ 2025 å¹´ï¼Œå†æ¬¡ 2027 å¹´ | å®žæµ‹ä¸‹æ‹‰é¡ºåºï¼š2026å¹´1月…2026å¹´12月、2025å¹´1月…2025å¹´12月、2027å¹´1月…2027å¹´12月 | é€šè¿‡ |
## å‘现的问题与处置
- åŽ†å²é—®é¢˜ï¼šè§„åˆ™åç§°/说明里除阈值外还混有与阈值无关的数字(如运距区间 [10,3000]),旧实现会误改。本轮改为“按旧阈值匹配对得上的百分比片段才替换”,并对读列表做幂等自愈,实测未再误伤无关数字。
- åŽ†å²é—®é¢˜ï¼šæ±‡æ€»å¤§è¡¨æ¯ç‰ˆæœ¬èº«å¸¦ç­›é€‰ä¸Žéšè—è¡Œï¼Œæ—§å¯¼å‡ºä¼šåŽŸæ ·å¸¦å‡ºï¼Œçœ‹èµ·æ¥åƒç¼ºæ•°æ®ã€‚æœ¬è½®å¯¼å‡ºå‰ç»Ÿä¸€æ¸…ç†å¹¶éªŒè¯ 19 é¡µå…¨å¹²å‡€ã€‚
- åŽ†å²é—®é¢˜ï¼šå…¬è·¯æ—…å®¢è¿æ”¿å¯¼å…¥æ­¤å‰åªè®¤â€œä¼ä¸šä»£ç /企业名称/车辆数/载客位数”中文四列,同事下载的新模板少两列导致导不进去。本轮按别名+表头大写兼容新旧两种结构。
- å¤‡æ³¨ï¼šdocs/审核界面月报模板/HTML_审核结果_Aug_2026.html ä¸ºæœ¬æ¬¡å®¡æ ¸ç»“果页与弹窗验证留档。
## ç»“论
8 æ¡æ•´æ”¹å…¨éƒ¨å®Œæˆå¹¶é€šè¿‡å›žå½’验证;相关改动见代码提交(DataImportService/ReportExportService/RuleManageController/AuditResult.vue/DataView.vue/ReportExport.vue/DataImport.vue)。
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_»ã×Ü´ó±íÈÕÆÚ¸ñʽÓë¹æÔòÁª¶¯¼°Êý¾Ý²é¿´¹ö¶¯¼ÓÔØ_2026-09-08.md
New file
@@ -0,0 +1,49 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_汇总大表日期格式与规则联动及数据查看滚动加载_2026-09-08
- åŠŸèƒ½èŒƒå›´ï¼šæœ¬æ‰¹ 7 é¡¹è¦æ±‚(日期统一显示、VBA åˆ—宽宏、数据查看滚动加载、小数位/排名复核、要求基线清单、RULE_004 åŒå‘提醒、RULE_P005 é˜ˆå€¼ä¸Žåç§°/说明联动)
- æµ‹è¯•环境:本机 traffic-audit(后端 8090、前端 8080 çƒ­ç¼–译运行中);MySQL 5.7 @ localhost:3308 traffic_audit;Chrome å†…嵌浏览器真机(Playwright/CUA)实测 UI
- éªŒè¯åŸºçº¿ï¼š`docs/要求与验收基线清单.md`;决策基线《确认回执汇总…_2026-09-07》
## ç”¨ä¾‹ä¸Žç»“论
| # | ç”¨ä¾‹ï¼ˆè¾“入) | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|
| 1 | ç”Ÿæˆæ±‡æ€»å¤§è¡¨ 2026-07/08,检查货运页 CQ åˆ—等日期锚点显示 | æœŸæ¬¡æ˜¾ç¤º `yyyy"å¹´"m"月"`,不得显示成数字 | ä¸¤æ–‡ä»¶å…¨ç°¿æ‰«æï¼šæ—¥æœŸåž‹å•元格坏格式=0;`货运`页 CQ3 ç­‰ = datetime(2023-10-01) ä¸”格式 `yyyy"å¹´"m"月"`;无 General æ•°å­—残留 | âœ… é€šè¿‡ |
| 2 | å…¨ç°¿æ ¼å¼ä½“检(导出的 7/8 æœˆå¤§è¡¨ï¼‰ | æ•°å€¼ 0.00 / ç™¾åˆ†æ¯” 0.00% / æŽ’名 0;无 3/4 ä½å°æ•°ã€æ— åº•色 | bad3=0、General æ•°å€¼=0、fills=0(两份文件) | âœ… é€šè¿‡ |
| 3 | æŽ’名与数值尾数抽查:货运量排名/货运周转量排名/中口径排名/道路运输周转量 | æŽ’名整列整数,数值列显示两位小数且保留全精度 | è´§è¿é‡æŽ’名 C/F/H/K/M/P åˆ—格式 `0`(34 è¡Œï¼‰ï¼›è´§è¿å‘¨è½¬é‡æŽ’名/道路运输周转量排名为 `=RANK()(0)` å…¬å¼åˆ—;数值/同比列 0.00/0.00%;周转量无 3/4 ä½å°æ•° | âœ… é€šè¿‡ |
| 4 | VBA å®äº¤ä»˜ç‰© | æä¾›å¯å¯¼å…¥æ¨¡å—,含全表/当前表两个宏与用法 | `docs/工具/VBA_生成物全表自动列宽_2026-09-08.txt`(AutoFitAllSheets + AutoFitActiveSheet + è¯´æ˜Žï¼‰ | âœ… é€šè¿‡ |
| 5 | æ•°æ®æŸ¥çœ‹é¡µæ»šåŠ¨åˆ°åº•è‡ªåŠ¨åŠ è½½ | æ™®é€š/全屏滚动至底部追加下一页,行数递增、总数不变、无 JS æŠ¥é”™ | Chrome çœŸæœºï¼šè¡Œæ•° 20→40→60→80→100→120→140,共 13056 æ¡ä¸å˜ï¼›æŽ§åˆ¶å° error/warning=0 | âœ… é€šè¿‡ |
| 6 | RULE_004 åŒå‘(货运类型环比)审核 2026-07 | ä¸Šæœˆæ— â†’本月有、上月有→本月无均提醒 | å‘½ä¸­ 7 å®¶ï¼šæ–°å¢ž 6、消失 3(1 å®¶åŒå‘);样例 `91420700090568838U æœ¬æœˆæ¶ˆå¤±: é›†è£…箱(上月=32210,本月无)` ä¸Žåº“中 6/7 æœˆæ•°æ®é€å­—吻合;`91429006793269842X` æ–°å¢žç…¤ç‚­+消失油气双向命中且吻合 | âœ… é€šè¿‡ |
| 7 | RULE_P005 é˜ˆå€¼è”动:20 â†’ 30 â†’ 20 | åç§°/说明里的 Â±N% éšé˜ˆå€¼è‡ªåŠ¨æ”¹å†™å¹¶æŒä¹…åŒ– | åˆ—表 20 æ—¶åç§°=平均运距环比超±20%核实;改 30 åŽåˆ—表与 DB å‡ä¸º Â±30%;改回 20 æ¢å¤ Â±20%;DB id=11 threshold=20 æŒä¹…化确认 | âœ… é€šè¿‡ |
## å…³é”®æ•°æ®ä¸ŽæŠ½æŸ¥è¯æ®
- æ ¼å¼ä½“检(两份导出)汇总行:`SUM: bad3=0 genNum=0 dtBadFmt=0`,各排名页格式 TOP è§ä¸‹è¡¨ï¼š
  - è´§è¿é‡æŽ’名:`0`×204 / `0.00`×108 / `0.00%`×210
  - è´§è¿å‘¨è½¬é‡æŽ’名:`0.00`×85 / `0.00%`×108(排名列为 RANK å…¬å¼ï¼Œæ ¼å¼ 0)
  - é“路运输周转量:`0.00`×216 / `0.00%`×107,无 3/4 ä½å°æ•°
- RULE_004 åº“核对 SQL:6/7 æœˆå„ 636 å®¶ï¼›å®¡æ ¸ç»“果(`audit_result` join `audit_rule` id=4)2026-07 å…± 7 æ¡ï¼š
  - `本月新增: é›†è£…ç®±=9716`、`本月新增: é“çŸ¿çŸ³=2114`、`本月消失: é›†è£…箱(上月=32210,本月无)`、
  - `本月新增: é‡‘属矿石=223041`、`本月新增: çŸ³æ²¹å¤©ç„¶æ°”及制品=10757;本月消失: ç…¤ç‚­åŠåˆ¶å“ï¼ˆä¸Šæœˆ=10137,本月无)`、
  - `本月新增: ç…¤ç‚­åŠåˆ¶å“=20231;本月消失: çŸ³æ²¹å¤©ç„¶æ°”及制品(上月=22310,本月无)` ç­‰
- RULE_P005 API åºåˆ—:`GET /api/rules/list?reportType=H2031`(id=11)threshold=20 åç§°Â±20% â†’ `POST /api/rules/update {id:11,threshold:"30"}` åŽåç§°/描述±30% â†’ æ”¹å›ž 20 åŽæ¢å¤ Â±20%;`audit_rule` è¡¨æ ¸å¯¹ä¸€è‡´ã€‚
- æ•°æ®æŸ¥çœ‹ï¼š`GET /api/data/list?type=h2032&page=1&size=20` total=13056、页间 0 é‡å¤ï¼›UI æ»šåŠ¨å®žæµ‹è§ #5。
## æ¶‰åŠæ”¹åЍ
- `docs/生成汇总大表/2026å¹´7月道路运输量汇总表.xlsx`、`2026å¹´8月道路运输量汇总表.xlsx`(版式归一:日期锚点/0.00/排名 0/去底色,本次复验导出按此母版)
- `traffic-audit-server/.../auditengine/service/AuditEngineService.java`(RULE_004 æ–°å¢ž/消失双向)
- `traffic-audit-server/.../rulemanage/controller/RuleManageController.java`(阈值 Â±N% â†’ åç§°/说明联动)
- `traffic-audit-web/src/views/DataView.vue`(滚动捕获监听修复 + åŠ è½½æ›´å¤šï¼Œè§ä¸‹ï¼‰
- `docs/init.sql`(RULE_004 è¯´æ˜Žã€RULE_P005 åç§°/说明种子同步)
- `docs/工具/VBA_生成物全表自动列宽_2026-09-08.txt`(新增)
- `docs/要求与验收基线清单.md`(新增,含本批登记)
## ä¿®å¤è¿‡ç¨‹è®°å½•(DataView)
- åˆç‰ˆä»…监听 window ä¸Ž `.table-wrap`;实测发现普通/全屏下真正滚动容器是 el-table å†…部 `.el-table__body-wrapper`(overflow-y:auto),初版在普通模式不会触发。
- ä¿®å¤ï¼š`onScroll` æ”¹ä¸ºå–事件 target çš„实际滚动容器度量(scrollTop/clientHeight/scrollHeight),scroll ç›‘听加 `capture:true`(scroll ä¸å†’泡但可捕获),mounted/beforeDestroy æˆå¯¹æ³¨å†Œ/移除。
- ä¿®å¤åŽ Chrome çœŸæœºå¤æµ‹ï¼ˆè§ #5)通过,控制台无异常。
## å‘现的问题与处置
- æ— é˜»å¡žé—®é¢˜ã€‚待办沿用基线:8 æœˆæ•°æ®å¯¼å…¥åŽ M2/M3 å¡«å€¼å¤éªŒï¼›æ—¥æœŸé”šç‚¹åœ¨ 8 æœˆæ¯ç‰ˆä¸­æ˜¾ç¤ºä¸ºåºåˆ—号期的显示口径已在基线 F3 å…³é—­ã€‚
## ç»“论
本批 7 é¡¹è¦æ±‚全部实现并通过实测,可交付。相关产物:导出验证文件在 `docs/生成汇总大表/输出/生成_道路运输量汇总表_2026-07_格式验证.xlsx` ä¸Ž `_2026-08_格式验证.xlsx`。
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_»ã×Ü´ó±í°æÊ½Í³Ò»_2026-09-08.md
New file
@@ -0,0 +1,42 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_汇总大表版式统一_2026-09-08
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
用户拍板(2026-09-08):汇总大表版式统一,四项规则——
1) æ•°å€¼ä¿ç•™ä¸¤ä½å°æ•°ï¼ˆæ•°å€¼/公式格显示 0.00,同比百分比 0.00% ä¿ç•™ï¼‰ï¼›
2) æŽ’名列整数(格式 0);
3) è¡¨æ ¼åˆ—宽按内容自适应(长公式文本不再把列撑爆);
4) èƒŒæ™¯è‰²å…¨éƒ¨ç»Ÿä¸€åŽ»é™¤ï¼ˆæ— å¡«å……ï¼‰ã€‚
涉及文件:
- æ¯ç‰ˆï¼š`docs/生成汇总大表/2026å¹´7月道路运输量汇总表.xlsx`、`2026å¹´8月道路运输量汇总表.xlsx`
- è¿˜åŽŸæºï¼ˆåŒåšå½’ä¸€ï¼Œé˜²ç”Ÿæˆæ—¶æ—§åº•è‰²/旧格式“复活”):`_备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx`
- ç¨‹åºï¼š`ReportExportService`(新增 `applyDonorNumberFormat`:还原回填时同步 donor æ•°å­—格式)
## äºŒã€æµ‹è¯•环境
- åŽç«¯ 8090(`start-dev.ps1` å¯åŠ¨ï¼‰ï¼Œ`mvn -q -DskipTests compile` exit=0
- æŽ¥å£ï¼š`GET /api/report/export/summaryWorkbook?period=2026-07|2026-08&mode=month`
- æ ¡éªŒï¼špython3 + openpyxl,值级/样式级双读(data_only=False)diff
## ä¸‰ã€ç”¨ä¾‹è¡¨
| # | ç”¨ä¾‹ | æ“ä½œ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|---|
| 1 | æ¯ç‰ˆåŽ»åº•è‰² | 7/8 æœˆæ¯ç‰ˆä¸Žå¤‡ä»½ç»Ÿä¸€è„šæœ¬å¤„ç† | å…¨ç°¿æ—  solid å¡«å…… | 7 æœˆæ¯ç‰ˆ fills=0;8 æœˆæ¯ç‰ˆæ¸…除 57908 æ ¼åº•色后=0;备份清除 57886 æ ¼åŽ=0 | é€šè¿‡ |
| 2 | æ•°å€¼ä¸¤ä½å°æ•° | æ•°å€¼+公式格按列语义归一 | æ•°æ®æ ¼æ˜¾ç¤º 0.00,同比/百分比保留 | 7 æœˆæ¯ç‰ˆ 38778 æ ¼å½’一;导出 7/8 æœˆæ•°æ®æ ¼å‡ 0.00 / 0.00% | é€šè¿‡ |
| 3 | æŽ’名整数 | æŽ’名列表头列数据格设为 0 | æŽ’名显示整数 | æ¯ç‰ˆä¸Žå¯¼å‡ºæŽ’名列均为 0;抽查`货运量排名`/`中口径排名`正确 | é€šè¿‡ |
| 4 | åˆ—宽自适应 | å…¬å¼ä¸è®¡å®½ã€æ•°å€¼åˆ—保底 12 | é•¿å…¬å¼åˆ—收缩到数值宽 | `货运` I/V/X…由 42 â†’ 12;长文本列(如 Q)保留 | é€šè¿‡ |
| 5 | å†…容不变 | å¤„理前后全簿值级 diff | diff=0 | 7 æœˆ v1→v2 diff=0;8 æœˆæš‚å­˜ vs åŽŸæ¯ç‰ˆä»… 6 å¤„空串`''→None` | é€šè¿‡ |
| 6 | 7 æœˆå¯¼å‡ºç«¯åˆ°ç«¯ | æ–°ç‰ˆå¼æ¯ç‰ˆ+备份生成 2026-07 | æ— åº•色、格式统一、校验行底色不复活 | fills=0;出租/公交月值 0.00、同比 0.00%、`货运`r117/118 æ ¡éªŒè¡Œ 0.00 ä¸”无底色;残余仅原表遗留日期锚点列 | é€šè¿‡ |
| 7 | 8 æœˆå¯¼å‡ºç«¯åˆ°ç«¯ | ç”Ÿæˆ 2026-08 | ä¸Ž 8 æœˆæ¯ç‰ˆ 0 å·®å¼‚、无底色 | å€¼ diff=0;fills=0;抽查格式正确 | é€šè¿‡ |
| 8 | Java ç¼–译 | `mvn -q -DskipTests compile` | exit=0 | exit=0 | é€šè¿‡ |
## å››ã€å‘现的问题与处置
1. **骨架空列还原时格式丢失/底色复活**:母版 5-7 æœˆåˆ—已清空(含同比公式),导出还原只写值不写样式,导致整数值显示 `1234`(缺 .00)、同比公式以 General æ˜¾ç¤ºå°æ•°æ¯”例、`货运`r117/118 æ ¡éªŒè¡ŒæŠŠå¤‡ä»½æ—§åº•色带回(30 æ ¼ï¼‰ã€‚
   å¤„置:(a) å¤‡ä»½æºæ–‡ä»¶åŒåšç‰ˆå¼ç»Ÿä¸€ï¼ˆåŽ»åº•è‰²ã€0.00、排名 0);(b) Java æ–°å¢ž `applyDonorNumberFormat`,还原回填时同步 donor æ•°å­—格式,同比/整数值/校验行显示统一。
2. **列宽被长公式撑爆**:首版自适应把公式文本计入宽度,I/V/X…列被撑到 42。处置:宽度仅按静态内容计算、长公式不计宽、数值/公式列保底 12。
3. **列语义误判**:行 3 æœŸæ¬¡æ—¥æœŸåºåˆ—(46082…)曾使整列被判“日期列”漏归一。处置:日期格数 â‰¤ æ•°å€¼æ ¼æ•°æ—¶æŒ‰æ•°å€¼åˆ—归一(日期序列所在单元格格式不动)。
4. **Excel å ç”¨ 8 æœˆæ¯ç‰ˆ**:openpyxl æ— æ³•落盘;等待用户关闭窗口后覆盖,预览副本先行提供桌面。
5. **日期锚点列(原表遗留)**:2019-2026 åºåˆ—号 General æœŸæ¬¡è¡Œï¼ˆå¦‚`公路总客运`r2 60 æ ¼ã€`中口径计算过程`A åˆ— 192 æ ¼ï¼‰éžæ•°æ®ï¼Œæœªæ”¹åŠ¨ï¼›å¦‚éœ€ç»Ÿä¸€ä¸ºæ—¥æœŸæ˜¾ç¤ºå¯å¦è¡Œæ‹æ¿ã€‚
## äº”、结论
- ç‰ˆå¼ç»Ÿä¸€å››é¡¹è§„则在 7/8 æœˆæ¯ç‰ˆä¸Žå¯¼å‡ºäº§ç‰©ç«¯åˆ°ç«¯ç”Ÿæ•ˆï¼›å…¨ç°¿å†…容零改动。
- åŽç»­ï¼š8 æœˆå½“月数据导入后按 M2/M3 å¡«å€¼å¹¶å¤éªŒï¼›æ—¥æœŸé”šç‚¹åˆ—显示方式待业务确认。
docs/¹¤×÷ÈÕ½á/¹¤×÷ÈÕ½á_2026-09-08.md
New file
@@ -0,0 +1,100 @@
# å·¥ä½œæ—¥ç»“_2026-09-08
## ä»Šæ—¥å·¥ä½œ
1. æ±‡æ€»å¤§è¡¨æ•°æ®é©±åŠ¨æ”¹é€ â€”â€”ç”¨æˆ·è¯„å®¡æ–¹æ¡ˆ v1 åé¦ˆå¹¶å½’档(`docs/确认回执汇总…`第六/七节、方案 v1.1 ä¿®è®¢ï¼‰ï¼š
   - æ‰©åˆ—方式按需(每月增月份时扩列),不作 12 æœˆæ»¡ç‰ˆï¼›
   - æ‰§è¡Œé¡ºåºæŒ‰ E1:先回归 7 æœˆã€å†å¡« 8 æœˆï¼›
   - è´§è¿/公路旅客/城市客运三类独立小表前端全部隐藏,内部中间表保留;
   - ç¡®ç«‹çºªå¾‹ï¼šä»¥å›žæ‰§æ–‡æ¡£ä¸ºå”¯ä¸€å†³ç­–基线,已确认项不再重复提问。
2. M0 å½±å“é¢å®žæµ‹ä¸Žå®žçŽ°ç­–ç•¥ï¼ˆæŒ‰éœ€æ‰©åˆ—ï¼Œ10 é•¿è¡¨é¡µé€æœˆæ‰©åˆ—、成品页不扩)——见 `docs/汇总大表数据驱动改造方案…` é™„录。
3. M1 è®¡ç®—服务抽取与 7 æœˆå›žå½’(逐块测试报告见 `docs/功能测试报告/`,均 0 å·®å¼‚):
   - M1-货运:新增 `FreightCalc`(规上/规下/合计 Ã— è´§è¿é‡/周转量 Ã— å½“月/累计);
   - M1-城市客运:新增 `PaxCalc`(公交/出租/轨道轮渡台账页);
   - M1-网约车:新增 `WycSplitCalc`(17 å¸‚州拆分 + å…¨çœ pin);
   - M1-城市客运整链回归(城市汇总页当月 r14-20、累计 r3-9 0 å·®å¼‚);
   - M1-班线包车:新增 `MidCalc`(h2031 ä¼ä¸šæ€»è®¡ + ä¸ªä½“并入口径,350 æ ¼ 0 å·®å¼‚)。
## æµ‹è¯•结果摘要
- äº”个 M1 å—各自 7 æœˆå›žå½’ 0 å·®å¼‚并通过 `mvn -q -DskipTests compile`(exit=0)。
- é€æ ¼å¤æ ¸ä¿®æ­£ï¼ˆ9/8 æ™šï¼Œç”¨æˆ·è´¨è¯¢â€œ8676.57 ä»Žä½•而来”后定位):该数是我初版比对脚本把母版**文本数字**
  ï¼ˆå…¬äº¤å°è´¦é¡µ 2026-06 M åˆ—等,如 `'4970.63'`)当空值跳过所致;补算 6 æœˆï¼ˆ1517.52)后,
  **中口径分析页当月/累计与库重算逐项 0 å·®å¼‚(含累计公交 10194.09),并非旧残值**。
- å¤æ ¸ç»“论:中口径排名页**右块(1-7 æœˆç´¯è®¡ï¼‰ä¸Žåº“ 0 å·®å¼‚**;**左块(A..H)为陈旧错位**
  ï¼ˆå®¢è¿é‡=2026-01 å½“月、周转量=2026-02 å½“月,17 å¸‚州逐格吻合)——记入 M2 ä¿®æ­£ã€‚
- ç”¨æˆ·è¡¥å……确认(已入《确认回执…》第六节):8 æœˆæ‰©åˆ—的“当月值/同比”标题为两行合并单元格,扩列须保留。
## é—留问题与待办
- M0:月度版式重建器(按需扩列,含两行合并标题保留、文本数字读取归一)实现;
- M1:派生页口径核对基本完成(分析页 0 å·®å¼‚、排名累计块 0 å·®å¼‚),剩余排名左块错位修正并入 M2;
- M2:SummaryFiller å†™å›žå°è´¦é¡µ+成品页,逐格 diff(含排名页左块改“当月 2026-07”的修正验证);
- M3:8 æœˆå®žå¼¹ï¼ˆå…ˆæ‰©åˆ— 2026-08,再导入 8 æœˆæ•°æ®ç”Ÿæˆï¼‰ï¼›
- M4:小表前端隐藏 + å¤‡ä»½è¿˜åŽŸ/清理。
---
## è¡¥å……(2026-09-08 ä¸­åˆï¼ŒåŒä¸€ä¼šè¯ç»­ï¼‰ï¼šä¸­å£å¾„排名左块删除 â†’ 7 æœˆå›žå½’收口 â†’ 8 æœˆæ‰©åˆ—
### ä»Šæ—¥æ–°å¢ž
1. **拍板登记**:《确认回执汇总…》第六节第 6 æ¡â€”—中口径排名页 r1-20 A..H é™ˆæ—§å·¦å—整块删除,程序永不回填;随 7 æœˆæ”¶å£éªŒè¯åŽæ‰© 8 æœˆã€‚
2. **母版删除**:`docs/生成汇总大表/2026å¹´7月道路运输量汇总表.xlsx` çš„ `中口径排名` A1:H20 æ¸…空 148 æ ¼ï¼Œç§»é™¤ A1:H1/A22:H22 ç©ºåˆå¹¶åŒºï¼ˆæ•´ç°¿ diff ä»…此区,改动前副本 `_test_tmp/rankleft_before_202607_mother.xlsx`)。
3. **程序防复活**:`ReportExportService.restoreSummaryMonthlyBlocks` å¯¹ `中口径排名` é¡µ A..H(列 0-7)永不从备份母版回填;另修复 POI Zip-bomb è¯¯è¯»ï¼ˆè¯»æ¯ç‰ˆå‰ä¸´æ—¶æ”¾ä½ŽåŽ‹ç¼©æ¯”é˜ˆå€¼å¹¶ finally è¿˜åŽŸï¼‰ã€‚
4. **7 æœˆå›žå½’收口**:M1 äº”块脚本复跑 diffs=0;程序生成 2026-07 ä¸Žå®Œæ•´ 7 æœˆåŸºå‡† diff ä»…剩左块删除 148 æ ¼ + 31 å¤„日期 int/float å†™æ³•(显示相同)。
5. **8 æœˆæ‰©åˆ—(M0 v4)**:产出新母版 `docs/生成汇总大表/2026å¹´8月道路运输量汇总表.xlsx`(8 ä¸ªå°è´¦/合成页 +2 åˆ—、累计滚动 +Q、同比分母 +AJ、2025 å‚照右移、两行合并表头、隐藏列平移保留、排名页无左块);程序生成 2026-08 = æ–°æ¯ç‰ˆ 0 å·®å¼‚。
### æµ‹è¯•结果摘要
- å…¨éƒ¨ç”¨ä¾‹è§ã€ŠåŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_中口径排名左块删除与7月回归收口及8月扩列_2026-09-08.md》。
- 7→8 æœˆæ•°å€¼æ ¼ 2387 ä¸ª 0 å·®å¼‚;编译 exit=0。
### é—留与下一步
- M2 SummaryFiller、M3 8 æœˆå®žå¼¹ï¼ˆ8 æœˆæ•°æ®å¯¼å…¥åŽå¡«å½“月值与排名)、小表前端隐藏(M4)。
- 8 æœˆæ–°æ¯ç‰ˆçš„ 8 æœˆå½“月值列仍为空,等待数据导入与填值验证。
---
## è¡¥å……(2026-09-08 ä¸‹åˆï¼ŒåŒä¸€ä¼šè¯ç»­ï¼‰ï¼šæ±‡æ€»å¤§è¡¨ç‰ˆå¼ç»Ÿä¸€ï¼ˆæ•°å€¼0.00/排名整数/列宽自适应/去底色)
### ä»Šæ—¥æ–°å¢ž
1. **母版版式统一**:7 æœˆã€8 æœˆæ¯ç‰ˆåŠ 7 æœˆæ¸…理前备份(还原源)统一处理——去除全部 solid åº•色(8 æœˆ 57908 æ ¼ã€å¤‡ä»½ 57886 æ ¼ï¼‰ã€æ•°å€¼ä¸Žå…¬å¼æ ¼æŒ‰åˆ—归一 0.00、排名列 0、列宽按静态内容自适应(长公式不计宽、数值列保底 12,`货运`I/V/X ç”± 42 æ”¶ç¼©åˆ° 12)。
2. **程序同步**:`ReportExportService.applyDonorNumberFormat`——还原回填 5-7 æœˆå—时同步 donor æ•°å­—格式,修复整数值缺 .00、同比公式 General æ˜¾ç¤ºå°æ•°æ¯”例、校验行旧底色复活三类问题。
3. **验证产物**:导出 2026-07/2026-08 ç«¯åˆ°ç«¯å¤éªŒï¼ˆfills=0、抽查格式正确、8 æœˆä¸Žæ¯ç‰ˆå€¼ diff=0),详见《功能测试报告_汇总大表版式统一_2026-09-08.md》。
### æµ‹è¯•结果摘要
- 7/8 æœˆæ¯ç‰ˆä¸Žå¯¼å‡ºå…¨ç°¿æ— åº•色;数值格 0.00/0.00%;排名列 0;内容零改动。
- `mvn -q -DskipTests compile` exit=0。
### é—留与下一步
- 8 æœˆå½“月数据导入后按 M2/M3 å¡«å€¼å¤éªŒï¼›æ—¥æœŸé”šç‚¹åˆ—(原表 General åºåˆ—号期次行)显示方式待业务确认。
- ä¸´æ—¶è„šæœ¬ `_unify_all.py` åŠ `_test_tmp/` å‰¯æœ¬ä¸åˆ ä¸å…¥åº“。
---
## è¡¥å……(2026-09-08 æ™šé—´ï¼ŒåŒä¸€ä¼šè¯ç»­ï¼‰ï¼šæ—¥æœŸæ ¼å¼/小数复核/VBA/滚动加载/规则双向与阈值联动
### ä»Šæ—¥æ–°å¢ž
1. **日期锚点统一**:7/8 æœˆæ¯ç‰ˆä¸Žå¯¼å‡ºå…¨ç°¿æ—¥æœŸåž‹å•元格统一 `yyyy"å¹´"m"月"`,修复货运页 CQ åˆ—等期次显示成数字的问题(全簿扫描日期坏格式=0)。
2. **小数位/排名复核(约定:数值保留全精度显示 0.00,排名整数 0)**:导出 2026-07/08 å¤éªŒâ€”—无 3/4 ä½å°æ•°ã€æ—  General æ•°å€¼ã€æ— åº•色;货运量排名列 `0`,周转量/道路运输周转量排名为 RANK å…¬å¼åˆ—格式 `0`。
3. **VBA åˆ—宽宏交付**:`docs/工具/VBA_生成物全表自动列宽_2026-09-08.txt`(AutoFitAllSheets/AutoFitActiveSheet)。
4. **数据查看滚动加载修复+真机验证**:定位真正滚动容器为 el-table å†…部 body-wrapper,onScroll å–事件 target åº¦é‡ + capture ç›‘听;Chrome çœŸæœºæ»šåЍ 20→40→…→140 è¡Œé€’增、总数 13056 ä¸å˜ã€æŽ§åˆ¶å° 0 æŠ¥é”™ã€‚
5. **RULE_004 åŒå‘**:上月无→本月新增、上月有→本月无均提醒;2026-07 å®žæµ‹å‘½ä¸­ 7 å®¶ï¼ˆæ–°å¢ž 6、消失 3、1 å®¶åŒå‘),消失样例与库数据逐字吻合。
6. **RULE_P005 é˜ˆå€¼è”动**:名称/说明 Â±N% éšé˜ˆå€¼å­—段同步并持久化(实测 20→30→20,DB å¤æ ¸ä¸€è‡´ï¼‰ï¼Œå…¶å®ƒå« Â±N% è§„则同理。
### æµ‹è¯•结果摘要
- è¯¦è§ã€ŠåŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_汇总大表日期格式与规则联动及数据查看滚动加载_2026-09-08.md》。
- `mvn` ç¼–译通过(前一交接已完成);前端热编译 3 æ¬¡æˆåŠŸï¼ˆ15:19/15:24)。
### é—留与下一步
- 8 æœˆå½“月数据导入后 M2/M3 å¡«å€¼å¤éªŒï¼›ä¸´æ—¶è„šæœ¬ `_verify_*.py/.sql`、`_do_patch1.py` ä¸åˆ ä¸å…¥åº“。
---
## è¡¥å……(2026-09-08 å‚æ™šï¼ŒåŒä¸€ä¼šè¯ç»­ï¼‰ï¼šç”¨æˆ· 8 æ¡æ•´æ”¹å…¨éƒ¨è½åœ°å¹¶å›žå½’
### ä»Šæ—¥æ–°å¢ž
1. **数据查看滚动加载(真机复测)**:表格固定高度、滚动容器绑定到 el-table å†…部 body-wrapper;实测 20→40 è¡Œè¿½åŠ ã€é¡µç­¾ç¬¬1页→第2页(13056 æ¡æ€»æ•°ä¸å˜ï¼‰ã€‚
2. **规则阈值联动普及到全部规则**:`RuleManageController.syncThresholdText` æ”¹ä¸ºæŒ‰â€œæ—§é˜ˆå€¼å¯¹åº”片段”替换(支持 Â±N%、>N%、0.5→50%、1.3→高出30% ç­‰å£å¾„),读列表幂等自愈;实测 P005/B001/E005/RULE_003/T001 æ˜¾å¼ä¸Žæ¯”值口径均正确并持久化,说明中与阈值无关的数字不再被误改。
3. **公路旅客运政车辆数导入兼容新模板**:同事下载的运政库导出 .xls(表头 OWNERNAME/总车辆数/客位数、无企业代码列)与旧中文四列均可导入;`docs/公路旅客/模板_公路旅客运政车辆数.xlsx` æ¢ä¸º `.xls`;2026-08 å…± 750 å®¶å…¥åº“、抽样与源文件一致。
4. **审核结果“数据”改月报样式弹窗 + åˆ é™¤ä¼ä¸šè§£é‡Šåˆ—**:行内仅剩 æ•°æ®/确认/忽略;弹窗按 è¿åŠ›æƒ…å†µâ†’è¿è¾“ç”Ÿäº§æƒ…å†µâ†’å¹³å‡æŒ‡æ ‡â†’å¡«æŠ¥äººä¿¡æ¯ åˆ†åŒºï¼Œæ ‡é¢˜=企业名+报表期。
5. **汇总大表导出清理**:`cleanSummarySheetPresentation` å–消各页筛选并还原隐藏行(班线包车等页不再像缺数据),保留两位小数显示/全精度、公式与打开自动重算,按内容自动加宽列宽(只加宽不缩窄、跳过合并标题);19 é¡µå¯¼å‡ºè„šæœ¬æ ¡éªŒé€šè¿‡ã€‚
6. **报表生成 Tab é‡ç»„**:“汇总大表(整本)”独立为首个 Tab;城市客运报表去掉 tag、命名与其它一致。
7. **审核页筛选栏顺序**:报表期移到报表类型左边。
8. **月份下拉年份顺序**:默认当前年 2026 åœ¨æœ€å‰ï¼Œå…¶åŽ 2025、2027。
### æµ‹è¯•结果摘要
- è¯¦è§ã€ŠåŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_整改8项回归验证_2026-09-08.md》,8/8 é€šè¿‡ï¼›å‰ç«¯å·²é‡æ–°ç¼–译、后端已重启加载新代码。
### é—留与下一步
- 8 æœˆå½“月数据 M2/M3 å¡«å€¼å¤éªŒï¼›9 æœˆèµ·çš„逐月导入→扩列→审核→生成完整链待业务数据到位后继续。
- æœ¬åœ°ä¸´æ—¶æ–‡ä»¶ï¼ˆ_*.ps1/_*.sql/*.bak/docs.zip/生成产物xlsx ç­‰ï¼‰æœªå…¥åº“,待整理清理。
---
## ä¸‹ç­å‰çŠ¶æ€ï¼ˆ2026-09-08 17:2x)
- ç¨‹åºå·²æŒ‰æœ€æ–°ä»£ç é‡å¯å¹¶éªŒè¯å°±ç»ªï¼šåŽç«¯ 8090 OK、前端 8080 OK(`start-dev.ps1`),浏览器访问 http://localhost:8080 å³å¯ä½¿ç”¨ï¼ˆadmin)。
- ä»Šæ—¥æ”¹åŠ¨å·²æäº¤å¹¶æŽ¨é€å…¬å¸ gitea main:`0548cc1`(8 æ¡æ•´æ”¹ + æµ‹è¯•报告 + æœ¬å·¥ä½œæ—¥ç»“)。
- æ˜Žæ—¥å¾…办:8 æœˆå½“月数据 M2/M3 å¡«å€¼å¤éªŒï¼›9 æœˆèµ·é€æœˆâ€œå¯¼å…¥â†’扩列→审核→汇总大表”整链验证;临时文件(_*.ps1/_*.sql/*.bak/docs.zip/生成产物 xlsx ç­‰ï¼‰æ•´ç†æ¸…理。
docs/¹¤¾ß/VBA_Éú³ÉÎïÈ«±í×Ô¶¯Áпí_2026-09-08.txt
New file
@@ -0,0 +1,39 @@
VBA å®ï¼šç”Ÿæˆæ±‡æ€»å¤§è¡¨ï¼ˆæˆ–任意工作簿)全工作表列宽自适应
日期:2026-09-08
说明:打开要处理的 xlsx â†’ Alt+F11 â†’ èœå•“插入→模块” â†’ ç²˜è´´ä»¥ä¸‹ä»£ç  â†’ å…³é—­ç¼–辑器 â†’ Alt+F8 è¿è¡Œ AutoFitAllSheets。
      ä¹Ÿå¯å°†æœ¬æ–‡ä»¶å¦å­˜ä¸º .bas,在 VBA ç¼–辑器里“文件→导入文件”。
--------------------------------------------------------------------------------
Option Explicit
' ä¸»å®ï¼šå¯¹å…¨éƒ¨å·¥ä½œè¡¨åšæ•´åˆ—列宽自适应(公式先整体重算,以显示值宽度为准)
Public Sub AutoFitAllSheets()
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationAutomatic
    Application.CalculateFullRebuild
    Dim ws As Worksheet
    For Each ws In ThisWorkbook.Worksheets
        ws.Activate
        On Error Resume Next
        ' è‡ªåŠ¨é€‚åº”æ•´åˆ—å®½åº¦ï¼ˆéšè—è¡Œ/列不影响;合并单元格内的文字不会被 Excel è‡ªåŠ¨è®¡å…¥ï¼‰
        ws.Cells.EntireColumn.AutoFit
        On Error GoTo 0
    Next ws
    Application.ScreenUpdating = True
    MsgBox "全部工作表列宽自适应完成。", vbInformation, "完成"
End Sub
' å¤‡ç”¨å®ï¼šåªå¯¹â€œå½“前选中的工作表”执行自适应
Public Sub AutoFitActiveSheet()
    Application.ScreenUpdating = False
    Application.CalculateFullRebuild
    ActiveSheet.Cells.EntireColumn.AutoFit
    Application.ScreenUpdating = True
    MsgBox "当前表列宽自适应完成。", vbInformation, "完成"
End Sub
--------------------------------------------------------------------------------
注意:
1) è¿è¡Œå‰å…ˆå…¨é€‰çœ‹ä¸€éæ˜¯å¦æœ‰â€œ###”溢出列;若个别列因合并标题仍偏窄,可手动拖一下该列。
2) æœ¬å®åªæ”¹åˆ—宽,不改任何数据、公式、底色与边框。
3) å»ºè®®ç”ŸæˆåŽç«‹åˆ»è¿è¡Œä¸€æ¬¡ï¼Œå†å¦å­˜ä¸ºæ­£å¼äº¤ä»˜æ–‡ä»¶ã€‚
docs/»ã×Ü´ó±íÊý¾ÝÇý¶¯¸ÄÔì·½°¸_2026-09-07.md
@@ -1,5 +1,8 @@
# æ±‡æ€»å¤§è¡¨â€œæ•°æ®é©±åŠ¨è‡ªåŠ¨å¡«è¡¨â€æ”¹é€ æ–¹æ¡ˆ v1(2026-09-07)
> **v1.1 ä¿®è®¢ï¼ˆ2026-09-08,用户拍板)**:本文件正文中“12 æœˆæ»¡ç‰ˆåˆ—版式”建议**作废**;扩列按“每月需增月份时再扩列”执行(=D1 åŽŸå£å¾„ï¼šå½“æœˆå€¼åˆ—+同比列插入累计之前、累计滚动、2025+ å‚照组右移)。执行顺序按 E1:先回归 7 æœˆã€å†å¡« 8 æœˆã€‚D5 è½åœ°ï¼šè´§è¿/公路旅客/城市客运三类独立小表前端全部隐藏,内部中间表保留不展示。已确认口径以《确认回执汇总…2026-09-07》文档(含六/七节补充)为唯一决策基线。
前置拍板:见 `docs/确认回执汇总与剩余疑问_汇总大表数据驱动改造_2026-09-07.md`(A1–E3、Q1–Q3 å·²å…¨éƒ¨ç¡®è®¤ï¼‰ã€‚本方案据此编写。
## ä¸€ã€ç›®æ ‡
@@ -91,3 +94,9 @@
2. U203 çš„城际城乡(chengxiang)字段与中口径明细组件公式的对应需逐格对账(7 æœˆå›žç®—),可能有口径差。
3. ç½‘约拆分历史口径(B5)需以 7 æœˆå·²æ‹†åˆ†ç»“果锁定输入输出,避免升级后数值漂移。
4. åŸŽå¸‚客运页参照组为压缩结构,与其它台账页不同,单独实现,7 æœˆå¯¹è´¦è¦†ç›–。
## é™„录:M0 å½±å“é¢å®žæµ‹ï¼ˆ2026-09-08,母版 2026-07 â†’ 08)
- éœ€è¦â€œé€æœˆæ‰©åˆ—(在累计列前插入当月值+同比 2 åˆ—,累计滚动,2025+ å‚照组右移 2 åˆ—)”的页 = **10 ä¸ªé•¿è¡¨é¡µ**:` è´§è¿`(maxCol145/合并140)、公路总客运(274/194)、班线包车(285/204)、城市客运(72/55)、公交(86/76)、出租(86/76)、网约车(86/70)、轨道轮渡(72/110)、中口径明细(87/157)、中口径客运量(68/111);平移区公式数以千计(涉平移列引用:公交 2004 / å‡ºç§Ÿ 1872 / ç½‘约 1664 / ä¸­å£å¾„明细 6654 / è´§è¿ 6638 / ç­çº¿ 5688)。
- **不需要随月扩列**:6 ä¸ªæˆå“é¡µï¼ˆè´§è¿é‡æŽ’名/货运周转量排名/中口径排名/城市汇总/城市分市州/道路运输周转量,当月块与累计块为行结构,固定列位,由数据驱动填充值/公式);中口径分析、2025年城市客运量、中口径计算过程列数不足 17 æˆ–为计算页,不动。
- **M0 å®žçŽ°ç­–ç•¥ï¼ˆæŠ€æœ¯è‡ªå®šï¼‰**:采用“**月度版式重建器**”而非 POI å°±åœ°åˆ—平移——把 10 ä¸ªé•¿è¡¨é¡µçš„列布局参数化为“月份组(m æœˆå€¼åˆ—=2m+1/同比=2m+2/累计=2m+3/累计同比=2m+4,2025+ å‚照组起点随之右移)”,生成目标月时按 m é‡å»ºåˆ—布局与月份表头/累计/同比公式,源内容(7月版母版)作为版式蓝本和 2025 å‚照数据基准。一次建成后 8..12 æœˆä¸Žè·¨å¹´æ–°æœ¬å¤ç”¨ã€‚成品页不走扩列,直接按映射填当月/累计/排名。
docs/Éú³É»ã×Ü´ó±í/2026Äê7ÔµÀ·ÔËÊäÁ¿»ã×ܱí.xlsx
Binary files differ
docs/Éú³É»ã×Ü´ó±í/2026Äê7ÔµÀ·ÔËÊäÁ¿»ã×ܱí_7ÔÂÇå¿Õ²âÊÔ°æ.xlsx
Binary files differ
docs/Éú³É»ã×Ü´ó±í/2026Äê7ÔµÀ·ÔËÊäÁ¿»ã×ܱí_7ÔÂÇå¿Õ²âÊÔ°æv2.xlsx
Binary files differ
docs/Éú³É»ã×Ü´ó±í/2026Äê8ÔµÀ·ÔËÊäÁ¿»ã×ܱí.xlsx
Binary files differ
docs/È·ÈÏ»ØÖ´»ã×ÜÓëÊ£ÓàÒÉÎÊ_»ã×Ü´ó±íÊý¾ÝÇý¶¯¸ÄÔì_2026-09-07.md
@@ -58,3 +58,19 @@
- **Q3**:班线包车映射以 `docs/公路旅客/` æ¨¡æ¿ä¸Žç®€åŒ–说明文档为准,实现遇歧义再单独核对。
→ å¾…确认项已清零。开工顺序:① å‡ºã€Šæ”¹é€ æ–¹æ¡ˆï¼ˆæ˜ å°„清单/取数链路/分步验证)》→ â‘¡ æ–¹æ¡ˆç¡®è®¤ â†’ â‘¢ åŠ¨ä»£ç ã€‚
## å…­ã€2026-09-08 ç”¨æˆ·è¡¥å……确认(v1 æ–¹æ¡ˆè¯„审反馈)
1. **扩列方式**:改为“每月需增月份时再扩列”(=D1 åŽŸå£å¾„ï¼šå½“æœˆå€¼åˆ—+同比列插入累计之前、累计滚动、2025+ å‚照组右移),**不作废**为一次性 12 æœˆæ»¡ç‰ˆã€‚方案 v1 ä¸­â€œ12 æœˆæ»¡ç‰ˆåˆ—版式”建议作废。
2. **执行顺序**:按 E1 å·²æ‹æ¿â€”—**先回归 7 æœˆã€å†å¡« 8 æœˆ**(不再重复询问)。
3. **D5 è½åœ°**:货运、公路旅客、城市客运三类**独立小表前端全部隐藏**;内部中间合成表保留(仅内部计算,不展示)。
4. **8 æœˆæ‰©åˆ—标题格式**:新增“当月值”“同比”两组标题是**两行纵向合并的单元格**(如“2026å¹´8月”与“8月与去年同比”),月度版式重建器/扩列实现须保留该两行合并标题样式,仅插入数据列与同比列、标题随列平移。
5. **实现注意事项(9/8 å®žæµ‹ï¼‰**:母版台账页部分月份单元格以**文本数字**存储(如公交页 2026-06 M åˆ— '4970.63'),程序读母版缓存时须统一做“文本→数值”归一,否则会漏算/漏比对。
6. **中口径排名页“左块”删除(9/9 æ‹æ¿ï¼‰**:`中口径排名` é¡µ r1-20 çš„ A..H é™ˆæ—§å½“月块(A=2026-01 å®¢è¿é‡ã€E=2026-02 å‘¨è½¬é‡ï¼Œæ— æœŸæ¬¡æ ‡é¢˜ï¼‰**整块删除**,不再按 M2“改当月 2026-07 é‡ç®—修正”处理;页内仅保留两张可见表:r1-20「1-7 æœˆç´¯è®¡ã€ä¸Ž r22-41「7 æœˆå½“月」(均在 J..R,含市州列)。同时移除 A1:H1、A22:H22 ä¸¤ä¸ªç©ºæ ‡é¢˜æ®‹ç•™åˆå¹¶åŒºï¼›A..I ä»ä¸ºéšè—ç©ºåˆ—。程序侧:`restoreSummaryMonthlyBlocks` å¯¹ `中口径排名` é¡µ **A..H(0-based 0-7 åˆ—)永不从备份母版回填**,避免“母版删了、一生成又复活”。该删除随 **7 æœˆå›žå½’收口一并验证**(含排名页/分析页/台账页与库 0 å·®å¼‚),通过后再扩 8 æœˆã€‚
   ï¼ˆ2026-09-08 å·²æ‰§è¡Œå¹¶éªŒè¯ï¼šæ¯ç‰ˆåˆ é™¤+程序排除+7 æœˆæ”¶å£+8 æœˆæ‰©åˆ—母版,详见《功能测试报告_中口径排名左块删除与7月回归收口及8月扩列_2026-09-08.md》)
## ä¸ƒã€å·¥ä½œçºªå¾‹ï¼ˆè‡ª 2026-09-08 èµ·ï¼‰
- ä»¥æœ¬ã€Šç¡®è®¤å›žæ‰§æ±‡æ€»â€¦ã€‹æ–‡æ¡£ä¸ºå”¯ä¸€å†³ç­–基线;凡已在 A1-E3 / Q1-Q3 / æœ¬è¡¨ç¡®è®¤è¿‡çš„口径,直接执行,不再重复提问。
- åªå°±æ–‡æ¡£æœªè¦†ç›–的新决策向用户提问。
docs/ÒªÇóÓëÑéÊÕ»ùÏßÇåµ¥.md
New file
@@ -0,0 +1,63 @@
# è¦æ±‚与验收基线清单(干活前必读)
> ç”¨é€”:汇总用户(业务)陆续提出的**明确要求/口径**,每次改代码/母版/规则前先对照本表,避免“做一半、漏口径、重复问”。
> åŸºçº¿æ–‡æ¡£ï¼šã€Šç¡®è®¤å›žæ‰§æ±‡æ€»ä¸Žå‰©ä½™ç–‘é—®_汇总大表数据驱动改造_2026-09-07.md》(唯一决策基线,第六节为 09-08 æ‹æ¿ï¼‰ã€‚
> ç»´æŠ¤ï¼šä¼šè¯æŽ¨è¿›æ—¶åŒæ­¥å¢žè¡¥ï¼›çŠ¶æ€ï¼šâœ…å·²å®žçŽ° / ðŸš§è¿›è¡Œä¸­ / â¬œå¾…办。
## ä¸€ã€æ±‡æ€»å¤§è¡¨ç‰ˆå¼ä¸Žç”Ÿæˆå£å¾„
| ç¼–号 | è¦æ±‚/口径 | çŠ¶æ€ | å¤‡æ³¨ |
|---|---|---|---|
| F1 | æ•°å€¼ï¼ˆå«å…¬å¼ç»“果)一律**显示两位小数**(0.00),但**保留全精度小数**,不得四舍五入改值 | âœ… 2026-09-08 | 7/8月母版+导出已验:无 3/4 ä½å°æ•°æ®‹ç•™ |
| F2 | **排名一律显示整数**(0 æ ¼å¼ï¼Œå«å¢žé€ŸæŽ’名) | âœ… 2026-09-08 | è¡¨å¤´â€œæŽ’名/增速排名”(允许带空格)整列生效 |
| F3 | æ—¥æœŸ/期次类单元格统一显示 **yyyy"å¹´"m"月"**(如货运页 CQ åˆ—等日期锚点不得显示成数字) | âœ… 2026-09-08 | æ¯ç‰ˆ+导出均验 |
| F4 | èƒŒæ™¯è‰²å…¨éƒ¨ç»Ÿä¸€ï¼ˆ**无填充/去底色**),生成产物不得从还原源带旧底色复活 | âœ… 2026-09-08 | è¿˜åŽŸæºå¤‡ä»½åŒåšå½’ä¸€ |
| F5 | åˆ—宽按显示内容**自适应**;长公式文本不得撑爆列宽;生成的最终表可在 Excel é‡Œè·‘ VBA å…¨è¡¨è‡ªé€‚应 | âœ… 2026-09-08 | VBA è§ `docs/工具/VBA_生成物全表自动列宽_2026-09-08.txt` |
| F6 | ç”Ÿæˆé“¾è·¯ = å¤åˆ¶æ¯ç‰ˆâ†’填值/还原;**母版文件本身不带业务数据作伪装**,数据一律由导入+计算写入 | ðŸš§ æŽ¨è¿›ä¸­ | ä¾ã€Šç¡®è®¤å›žæ‰§ã€‹E1 å…ˆå›žå½’7月再填8月 |
| F7 | æ±‡æ€»å¤§è¡¨å«å½“月 + 1-X月累计 + åŒæ¯”;排名/分析表需当月与累计两套 | âœ…(当月+累计结构) | å½“月排名修正已入 M2 éªŒè¯ |
| F8 | 2025 å¹´å‰æ•°æ®ä»¥æ¯ç‰ˆç¼“存为准;**母版缓存优先作同比基准**,库只做校验,人工核对重导一致后才切库值(Q1) | âœ… å£å¾„确认 | |
| F9 | å†…部计算页不对外;货运/公路旅客/城市客运小表前端全部隐藏(内部中间表保留) | âœ… æ‹æ¿ | |
| F10 | æ‰©åˆ—按月按需(加月份才扩列,不预做 12 æœˆæ»¡ç‰ˆï¼‰ï¼›æ–°å¢žåˆ—“当月/同比”标题为两行合并单元格,扩列须保留 | âœ… M0 v4 | |
| F11 | åŒæ¯”无数据的“增速为 0 éœ€æ ¸å®žâ€ç±»è§„则已入公交/出租/货运/旅客审核规则(补充规则类先缓) | ðŸš§ | å®¡æ ¸è§„则类近期以导入与报表为先 |
## äºŒã€æ•°æ®å¯¼å…¥
| ç¼–号 | è¦æ±‚ | çŠ¶æ€ | å¤‡æ³¨ |
|---|---|---|---|
| I1 | å¯¼å…¥è¿‡ç¨‹æ˜¾ç¤º**进度**;导入失败提示到**行号** | âœ… | |
| I2 | å…è®¸é‡å¤å¯¼å…¥ï¼ˆé‡æ–°å¯¼å…¥è¦†ç›–) | âœ… æ‹æ¿ | |
| I3 | åŸŽå¸‚客运导入类型下拉需含:**公交/出租月报、网约车拆分、网约车订单、全省总量等**模板 | âœ… å·²åŠ  | æ¯ç‰ˆåœ¨ `docs/城市客运` |
| I4 | å¯¼å…¥æ¨¡æ¿å·®å¼‚(企业代码/单位详细名称列有无)要与三种模板的差异兼容 | âœ… å·²å…¼å®¹ | è§ 09-04 æµ‹è¯•报告 |
| I5 | æ•°æ®æŸ¥çœ‹ï¼š**滚动到底部自动加载并显示更多行**(全屏/普通模式均可翻页) | âœ… 2026-09-08 | DataView.vue ä¿®å¤æ»šåЍ容噍+capture ç›‘听;Chrome çœŸæœº 20→140 è¡Œé€’增验证 |
## ä¸‰ã€æŠ¥è¡¨ç”Ÿæˆ
| ç¼–号 | è¦æ±‚ | çŠ¶æ€ | å¤‡æ³¨ |
|---|---|---|---|
| R1 | é€æœˆæ‰©å±•生成;保留母版缓存与母版程序复制;累计公式去掉未到月份、同比分母联动 | âœ… M0/M1 | |
| R2 | ç”Ÿæˆâ€œé“路运输量汇总表(整本)”= å½“月表 + ç´¯è®¡ï¼ˆ1-X月)| âœ… | |
| R3 | ç”Ÿæˆç‰©ä¸Žæ¯ç‰ˆ/库 0 å·®å¼‚为验收标准(含中口径分析页、排名页累计块) | âœ… 7月回归 | |
| R4 | ç‹¬ç«‹å°è¡¨ä¿ç•™ä½†å‰ç«¯éšè—ï¼Œç­‰å¤§è¡¨è·‘通后可取消 | âœ… æ‹æ¿ | |
| R5 | æŽ’名/分析/汇总的**当月数据**要与累计数据分别计算,禁止把累计值当当月值 | ðŸš§ M2/M3 å®žå¼¹ | |
## å››ã€å®¡æ ¸è§„则
| ç¼–号 | è¦æ±‚ | çŠ¶æ€ | å¤‡æ³¨ |
|---|---|---|---|
| A1 | RULE_004(货运类型环比):**上月无→本月新增,提醒;上月有→本月无,提醒**(双向) | âœ… 2026-09-08 | å¼•擎+DB+init.sql å·²æ”¹ï¼›2026-07 å®žæµ‹ï¼šæ–°å¢ž6/消失3/双向1 å®¶ï¼Œä¸Žæºæ•°æ®å»åˆ |
| A2 | è§„则名称/说明里的阈值(±N%)**随阈值字段改动自动同步**(如 Â±10%→±20%),其它含阈值规则同理 | âœ… 2026-09-08 | å®žæµ‹ 20→30→20:名称/说明随动并持久化;DB id=11 å¤æ ¸ä¸€è‡´ |
| A3 | æ•´è½¦å¨ä½åˆ¤å®šç”¨**平均吨位=整车吨位/整车数**,区间 4-40 | âœ… | 09-04 ä¿®å¤ |
| A4 | å®¡æ ¸ç»“果里的异常数值要可解释(如“轨迹测算=1.04E7”需给出算法说明/口径标注) | â¬œ | å£å¾„解释页推进中 |
## äº”、工作纪律与交付
| ç¼–号 | è¦æ±‚ | çŠ¶æ€ |
|---|---|---|
| W1 | æ¯å®Œæˆä¸€ä¸ªåŠŸèƒ½ï¼šç«‹å³æµ‹è¯•å¹¶å†™ã€ŠåŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_<功能>_<日期>.md》到 `docs/功能测试报告/` | âœ… å¸¸æ€æ‰§è¡Œ |
| W2 | æ¯å¤©ä¸‹ç­å†™ã€Šå·¥ä½œæ—¥ç»“_<日期>.md》到 `docs/工作日结/` | âœ… å¸¸æ€æ‰§è¡Œ |
| W3 | å¹²æ´»å‰è¯»æœ¬æ¸…单与《确认回执汇总…》,已确认项不再重复向用户提问 | âœ… æ‰§è¡Œ |
| W4 | ä¸ªäºº gitee(origin) æœ‰æ”¹åŠ¨å…ˆ fetch/合并;公司 gitea(main) å…ˆæµ‹è¯•+报告→合并→commit→push,禁强推 | âœ… æ‰§è¡Œ |
## å…­ã€æœ¬ä¼šè¯ï¼ˆ2026-09-08 ä¸‹åˆ/晚间)处理登记
1. æ—¥æœŸé”šç‚¹ç»Ÿä¸€ yyyyå¹´m月(F3)+ ä¿®å¤è´§è¿é¡µ CQ åˆ—日期显示成数字的问题;
2. æä¾›å…¨è¡¨åˆ—宽自适应 VBA(F5);
3. DataView æ»šåŠ¨åˆ°åº•åŠ è½½æ›´å¤šï¼ˆI5);
4. æŽ’名/小数位格式复核(F1/F2:货运量排名排名=整数、周转量排名数据=0.00、道路运输周转量无 3/4 ä½å°æ•°ï¼‰ï¼›
5. æœ¬æ¸…单建立(W3);
6. RULE_004 åŒå‘提醒(A1);RULE_P005 é˜ˆå€¼ 20% ä¸Žåç§°/说明联动(A2)。
7. æœ¬æ‰¹å®žæµ‹æ”¶å£ï¼šå…¨ç°¿æ ¼å¼ä½“检(bad3/General/底色=0)、RULE_004 åŒå‘ 2026-07 å‘½ä¸­æ ¸å¯¹ã€P005 è”动 20→30→20、DataView Chrome çœŸæœºæ»šåŠ¨åŠ è½½ 20→140 è¡Œâ€”—详见《功能测试报告_汇总大表日期格式与规则联动及数据查看滚动加载_2026-09-08.md》。
traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java
@@ -1645,26 +1645,34 @@
        return null;
    }
    /** è´§è¿ç±»åž‹çŽ¯æ¯”ï¼šæœ¬æœˆæ–°å¢žï¼ˆä¸Šæœˆä¸º0、本月>0)需核实,少的不管 */
    /** è´§è¿ç±»åž‹çŽ¯æ¯”ï¼ˆRULE_004):上月无→本月新增 æˆ– ä¸Šæœˆæœ‰â†’本月无,均提醒核实 */
    private AuditResult checkMom(AuditRule rule, H2032EnterpriseMonthly report,
                                 Map<String, H2032EnterpriseMonthly> lastMonthMap) {
        H2032EnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode()));
        if (lastMonth == null) return null;
        List<String> hits = new ArrayList<>();
        List<String> addHits = new ArrayList<>();
        List<String> delHits = new ArrayList<>();
        for (String field : rule.getCheckField().split(",")) {
            double current = getH2032FieldValue(report, field);
            double last = getH2032FieldValue(lastMonth, field);
            if (current > 0 && last == 0) {
                hits.add(h2032FieldLabel(field) + "=" + fmt(current));
                addHits.add(h2032FieldLabel(field) + "=" + fmt(current));
            } else if (last > 0 && current == 0) {
                delHits.add(h2032FieldLabel(field) + "(上月=" + fmt(last) + ",本月无)");
            }
        }
        if (!hits.isEmpty()) {
            return buildResult(rule, report, "本月新增: " + String.join("、", hits),
                "上月该类型为0", "本月新增货运类型(上月无)");
        if (!addHits.isEmpty() || !delHits.isEmpty()) {
            StringBuilder msg = new StringBuilder();
            if (!addHits.isEmpty()) msg.append("本月新增: ").append(String.join("、", addHits));
            if (!delHits.isEmpty()) {
                if (msg.length() > 0) msg.append(";");
                msg.append("本月消失: ").append(String.join("、", delHits));
            }
            return buildResult(rule, report, msg.toString(),
                "上月无→本月新增 æˆ– ä¸Šæœˆæœ‰â†’本月无", "货运类型环比新增/消失需核实");
        }
        return null;
    }
    /** æœ‰è´§è¿é‡ä½†å¯¹åº”车辆为0 éœ€æ ¸å®žï¼ˆå¦‚ æœ‰é›†è£…箱货运量无集装箱车辆) */
    private AuditResult checkNull(AuditRule rule, H2032EnterpriseMonthly report) {
        String[] fields = rule.getCheckField().split(",");
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -558,24 +558,30 @@
        }
    }
    /** å…¬è·¯æ—…客运政车辆数(独立模板:企业代码/企业名称/车辆数/载客位数) */
    /** å…¬è·¯æ—…客运政车辆数(兼容两种表头:① ä¼ä¸šä»£ç /企业名称/车辆数/载客位数;② è¿æ”¿åº“导出 OWNERNAME/总车辆数/客位数,无企业代码列) */
    public ImportResult importPassengerAuth(MultipartFile file, String period) throws Exception {
        List<PassengerAuthVehicle> authList = new ArrayList<>();
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
            requireHeaderCols(sheet, "旅客运政车辆数", "企业名称", "车辆数", "载客位数");
            Map<String, Integer> colMap = buildHeaderMapCompat(sheet.getRow(0));
            Integer nameCol = firstColIdx(colMap, "企业名称", "OWNERNAME");
            Integer vehicleCol = firstColIdx(colMap, "车辆数", "总车辆数");
            Integer seatCol = firstColIdx(colMap, "载客位数", "客位数");
            Integer codeCol = firstColIdx(colMap, "企业代码");
            if (nameCol == null || vehicleCol == null || seatCol == null) {
                throw new RuntimeException("所选数据类型【旅客运政车辆数】与文件内容不符:表头需含【企业名称/车辆数/载客位数】或【OWNERNAME/总车辆数/客位数】,请确认是否选错了数据类型或使用了旧模板");
            }
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
                String name = gs(row, colMap, "企业名称");
                String name = getString(row, nameCol);
                if (name == null || name.trim().isEmpty()) continue;
                PassengerAuthVehicle auth = new PassengerAuthVehicle();
                auth.setReportPeriod(period);
                auth.setEnterpriseCode(gs(row, colMap, "企业代码"));
                auth.setEnterpriseCode(codeCol == null ? null : getString(row, codeCol));
                auth.setEnterpriseName(name.trim());
                auth.setVehicleCount(gi(row, colMap, "车辆数"));
                auth.setSeatCount(gi(row, colMap, "载客位数"));
                auth.setVehicleCount(getInt(row, vehicleCol));
                auth.setSeatCount(getInt(row, seatCol));
                authList.add(auth);
            }
        } catch (Exception e) {
@@ -1396,6 +1402,33 @@
        return colMap;
    }
    /** å…¼å®¹è¡¨å¤´æ˜ å°„:去引号/去空白;纯英文表头统一大写(如 'OWNERNAME' â†’ OWNERNAME),便于新旧模板混用 */
    private Map<String, Integer> buildHeaderMapCompat(Row header) {
        Map<String, Integer> colMap = new HashMap<>();
        if (header == null) return colMap;
        for (Cell cell : header) {
            if (cell == null) continue;
            String h = FORMATTER.formatCellValue(cell).replace("'", "").replace("\"", "").trim();
            if (h.isEmpty()) continue;
            boolean ascii = true;
            for (int i = 0; i < h.length(); i++) {
                if (h.charAt(i) > 127) { ascii = false; break; }
            }
            if (ascii) h = h.toUpperCase();
            if (!colMap.containsKey(h)) colMap.put(h, cell.getColumnIndex());
        }
        return colMap;
    }
    /** æŒ‰åˆ«åé¡ºåºå–第一个存在的列号,全部缺失返回 null */
    private Integer firstColIdx(Map<String, Integer> colMap, String... aliases) {
        for (String a : aliases) {
            Integer idx = colMap.get(a);
            if (idx != null) return idx;
        }
        return null;
    }
    private int colIdx(Map<String, Integer> colMap, String header) {
        Integer idx = colMap.get(header);
        return idx == null ? -1 : idx;
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/FreightCalc.java
New file
@@ -0,0 +1,225 @@
package com.trafficaudit.reportexport.calc;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.trafficaudit.common.util.RegionUtil;
import com.trafficaudit.dataimport.entity.FreightTurnoverImport;
import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly;
import com.trafficaudit.dataimport.entity.ScaleSplitTransport;
import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper;
import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper;
import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
 * è´§è¿å£å¾„计算服务(汇总大表数据驱动改造 M1-货运,2026-09-08)。
 *
 * å£å¾„(决策基线 docs/确认回执汇总…_2026-09-07.md B1;2026-07 ä¸Žå·²ä¿®æ­£æ¯ç‰ˆä¸¤å¼ æˆå“é¡µ
 * è§„上/规下 å½“月+累计 é€æ ¼å›žå½’ 0 å·®å¼‚):
 * 1. è§„上货运量 = h2032_enterprise_monthly.freight_total(吨)按 17 å¸‚州汇总 Ã·10000(万吨)。
 * 2. åˆè®¡è´§è¿é‡ = freight_turnover_import.freight_mMM(当月)/ freight_m01..mMM(累计)。
 * 3. è§„下货运量 = åˆè®¡ âˆ’ è§„上(拆分表不存货运量列,不能直接取规下)。
 * 4. è§„上/合计周转量 = scale_split_transport MONTH|CUMULATIVE çš„ above_scale_turnover / total_turnover
 *    ï¼ˆåˆè®¡ä¸Ž freight_turnover_import.turnover_mMM åŒæºï¼Œå·²æ ¸å¯¹ä¸€è‡´ï¼‰ï¼›è§„下周转 = åˆè®¡ âˆ’ è§„上。
 * 5. åŒæ¯”(去年同口径)不在本服务计算:按 Q1 ä»¥æ¯ç‰ˆç¼“å­˜ 2025 å‚照组为基准,由写回层填表时计算。
 *
 * è¾“出 key = RegionUtil è§„范市州名(武汉市…神农架林区)+ PROVINCE(湖北省,全省行 = 17 å¸‚州之和,
 * ä¸Žæ¯ç‰ˆâ€œå…¨çœè¡Œ=Σ城市行”公式一致)。
 */
@Service
public class FreightCalc {
    public static final String PROVINCE = "湖北省";
    /** å•个期间(当月或累计)的货运量/周转量三口径指标;无数据为 null */
    public static class FreightMetrics {
        private final Double aboveFreight;   // è§„上货运量(万吨)
        private final Double belowFreight;   // è§„下货运量(万吨)
        private final Double totalFreight;   // è§„上+规下货运量(万吨)
        private final Double aboveTurnover;  // è§„上货物周转量(万吨公里)
        private final Double belowTurnover;  // è§„下货物周转量(万吨公里)
        private final Double totalTurnover;  // è§„上+规下货物周转量(万吨公里)
        public FreightMetrics(Double aboveFreight, Double belowFreight, Double totalFreight,
                              Double aboveTurnover, Double belowTurnover, Double totalTurnover) {
            this.aboveFreight = aboveFreight;
            this.belowFreight = belowFreight;
            this.totalFreight = totalFreight;
            this.aboveTurnover = aboveTurnover;
            this.belowTurnover = belowTurnover;
            this.totalTurnover = totalTurnover;
        }
        public Double getAboveFreight() { return aboveFreight; }
        public Double getBelowFreight() { return belowFreight; }
        public Double getTotalFreight() { return totalFreight; }
        public Double getAboveTurnover() { return aboveTurnover; }
        public Double getBelowTurnover() { return belowTurnover; }
        public Double getTotalTurnover() { return totalTurnover; }
    }
    /** å…¨é‡è´§è¿çŸ©é˜µï¼šmonth=当月,cumulative=截至 period æœˆç´¯è®¡ */
    public static class FreightMatrix {
        private final String period;
        private final Map<String, FreightMetrics> month;
        private final Map<String, FreightMetrics> cumulative;
        public FreightMatrix(String period, Map<String, FreightMetrics> month, Map<String, FreightMetrics> cumulative) {
            this.period = period;
            this.month = month;
            this.cumulative = cumulative;
        }
        public String getPeriod() { return period; }
        public Map<String, FreightMetrics> getMonth() { return month; }
        public Map<String, FreightMetrics> getCumulative() { return cumulative; }
    }
    @Resource
    private H2032EnterpriseMonthlyMapper h2032Mapper;
    @Resource
    private FreightTurnoverImportMapper freightTurnoverMapper;
    @Resource
    private ScaleSplitTransportMapper scaleSplitMapper;
    /** è®¡ç®— period(如 2026-07)的当月与累计货运矩阵 */
    public FreightMatrix calc(String period) {
        int year = Integer.parseInt(period.substring(0, 4));
        int month = Integer.parseInt(period.substring(5, 7));
        // H2032 è§„上货运量(吨):市州 -> æœˆæ¡¶
        Map<String, Map<Integer, Double>> h2032TonByCityMonth = new HashMap<>();
        List<H2032EnterpriseMonthly> h2032List = h2032Mapper.selectList(
            new LambdaQueryWrapper<H2032EnterpriseMonthly>().likeRight(H2032EnterpriseMonthly::getReportPeriod, year));
        for (H2032EnterpriseMonthly rec : h2032List) {
            String rp = rec.getReportPeriod();
            if (rp == null || rp.compareTo(period) > 0) continue;
            String city = RegionUtil.cityByCode(rec.getRegionCode());
            if (city == null) continue;
            double freight = rec.getFreightTotal() == null ? 0.0 : rec.getFreightTotal();
            h2032TonByCityMonth.computeIfAbsent(city, k -> new HashMap<>())
                .merge(Integer.parseInt(rp.substring(5, 7)), freight, Double::sum);
        }
        // freight_turnover_import:合计货运量/周转量(仅存城市行;湖北省行由求和得出)
        Map<String, FreightTurnoverImport> ftMap = new HashMap<>();
        for (FreightTurnoverImport e : freightTurnoverMapper.selectList(
            new LambdaQueryWrapper<FreightTurnoverImport>()
                .eq(FreightTurnoverImport::getReportPeriod, period))) {
            String city = RegionUtil.normalizeCityName(e.getRegionName());
            if (city != null && !PROVINCE.equals(city)) ftMap.put(city, e);
        }
        // scale_split_transport:规上/合计周转量
        Map<String, ScaleSplitTransport> splitMonth = new HashMap<>();
        Map<String, ScaleSplitTransport> splitCumulative = new HashMap<>();
        for (ScaleSplitTransport rec : scaleSplitMapper.selectList(
            new LambdaQueryWrapper<ScaleSplitTransport>()
                .eq(ScaleSplitTransport::getReportPeriod, period))) {
            String city = RegionUtil.normalizeCityName(rec.getRegionName());
            if (city == null || PROVINCE.equals(city)) continue;
            if ("MONTH".equals(rec.getPeriodType())) splitMonth.put(city, rec);
            else if ("CUMULATIVE".equals(rec.getPeriodType())) splitCumulative.put(city, rec);
        }
        Map<String, FreightMetrics> monthMap = new LinkedHashMap<>();
        Map<String, FreightMetrics> cumulativeMap = new LinkedHashMap<>();
        for (String city : RegionUtil.cityList()) {
            monthMap.put(city, buildMetrics(city, month, h2032TonByCityMonth, ftMap, splitMonth, false));
            cumulativeMap.put(city, buildMetrics(city, month, h2032TonByCityMonth, ftMap, splitCumulative, true));
        }
        monthMap.put(PROVINCE, sumOf(monthMap));
        cumulativeMap.put(PROVINCE, sumOf(cumulativeMap));
        return new FreightMatrix(period, monthMap, cumulativeMap);
    }
    /** å•城市指标。cumulative=true æ—¶ H2032 å– 1..month å…¨æ¡¶ã€åˆè®¡å– m01..mMM ä¹‹å’Œï¼›false æ—¶åªå–当月。 */
    private FreightMetrics buildMetrics(String city, int month,
                                        Map<String, Map<Integer, Double>> h2032TonByCityMonth,
                                        Map<String, FreightTurnoverImport> ftMap,
                                        Map<String, ScaleSplitTransport> splitMap,
                                        boolean cumulative) {
        Double aboveFreight = null;
        Map<Integer, Double> h2032Buckets = h2032TonByCityMonth.get(city);
        if (h2032Buckets != null) {
            double ton;
            if (cumulative) {
                ton = 0.0;
                for (int m = 1; m <= month; m++) ton += h2032Buckets.getOrDefault(m, 0.0);
            } else {
                ton = h2032Buckets.getOrDefault(month, 0.0);
            }
            aboveFreight = ton / 10000.0;
        }
        FreightTurnoverImport ft = ftMap.get(city);
        Double totalFreight = null;
        Double totalTurnoverFromFt = null;
        if (ft != null) {
            if (cumulative) {
                double f = 0.0, t = 0.0;
                for (int m = 1; m <= month; m++) {
                    f += monthField(ft, m, true);
                    t += monthField(ft, m, false);
                }
                totalFreight = f;
                totalTurnoverFromFt = t;
            } else {
                totalFreight = monthField(ft, month, true);
                totalTurnoverFromFt = monthField(ft, month, false);
            }
        }
        ScaleSplitTransport split = splitMap.get(city);
        Double aboveTurnover = split == null ? null : split.getAboveScaleTurnover();
        Double totalTurnover = split != null && split.getTotalTurnover() != null
            ? split.getTotalTurnover() : totalTurnoverFromFt;
        Double belowFreight = totalFreight == null || aboveFreight == null ? null : totalFreight - aboveFreight;
        Double belowTurnover = totalTurnover == null ? null
            : totalTurnover - (aboveTurnover == null ? 0.0 : aboveTurnover);
        return new FreightMetrics(aboveFreight, belowFreight, totalFreight,
                                  aboveTurnover, belowTurnover, totalTurnover);
    }
    /** æ¹–北省行 = 17 å¸‚州之和(与母版全省行 Î£ å…¬å¼ä¸€è‡´ï¼›å…¨å¸‚州无数据时输出 0) */
    private FreightMetrics sumOf(Map<String, FreightMetrics> byCity) {
        double af = 0.0, bf = 0.0, tf = 0.0, at = 0.0, bt = 0.0, tt = 0.0;
        for (String city : RegionUtil.cityList()) {
            FreightMetrics m = byCity.get(city);
            if (m == null) continue;
            af += nz(m.getAboveFreight());
            bf += nz(m.getBelowFreight());
            tf += nz(m.getTotalFreight());
            at += nz(m.getAboveTurnover());
            bt += nz(m.getBelowTurnover());
            tt += nz(m.getTotalTurnover());
        }
        return new FreightMetrics(af, bf, tf, at, bt, tt);
    }
    private double monthField(FreightTurnoverImport e, int m, boolean freight) {
        switch (m) {
            case 1: return freight ? nz(e.getFreightM01()) : nz(e.getTurnoverM01());
            case 2: return freight ? nz(e.getFreightM02()) : nz(e.getTurnoverM02());
            case 3: return freight ? nz(e.getFreightM03()) : nz(e.getTurnoverM03());
            case 4: return freight ? nz(e.getFreightM04()) : nz(e.getTurnoverM04());
            case 5: return freight ? nz(e.getFreightM05()) : nz(e.getTurnoverM05());
            case 6: return freight ? nz(e.getFreightM06()) : nz(e.getTurnoverM06());
            case 7: return freight ? nz(e.getFreightM07()) : nz(e.getTurnoverM07());
            case 8: return freight ? nz(e.getFreightM08()) : nz(e.getTurnoverM08());
            case 9: return freight ? nz(e.getFreightM09()) : nz(e.getTurnoverM09());
            case 10: return freight ? nz(e.getFreightM10()) : nz(e.getTurnoverM10());
            case 11: return freight ? nz(e.getFreightM11()) : nz(e.getTurnoverM11());
            case 12: return freight ? nz(e.getFreightM12()) : nz(e.getTurnoverM12());
            default: return 0.0;
        }
    }
    private double nz(Double v) { return v == null ? 0.0 : v; }
}
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/MidCalc.java
New file
@@ -0,0 +1,110 @@
package com.trafficaudit.reportexport.calc;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.trafficaudit.common.util.RegionUtil;
import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly;
import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
import com.trafficaudit.dataimport.mapper.PassengerIndividualMonthlyMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
 * å…¬è·¯ç­çº¿ï¼ˆå«ä¸ªä½“)口径计算服务(汇总大表数据驱动改造 M1-班线包车,2026-09-08)。
 *
 * å£å¾„(2026-07 ä¸Ž _备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx ç­çº¿åŒ…车页
 * ä¼ä¸š/个体行 1-7 æœˆé€æ ¼å›žå½’ 0 å·®å¼‚,350 æ ¼å…¨ä¸­ï¼‰ï¼š
 * 1. ç­çº¿åŒ…车页“客运量/旅客周转量”行 = h2031_enterprise_monthly æŒ‰ 17 å¸‚州汇总
 *    ï¼ˆpassenger_total / turnover_total,人 / äººå…¬é‡Œï¼‰é™¤ä»¥ 10000,再并入个体客运
 *    ï¼ˆpassenger_individual_monthly åŒå£å¾„);“其中个体客运量/周转量”行 = ä¸ªä½“数除以 10000
 *    ï¼ˆå½“前仅黄石/襄阳/荆门/孝感/黄冈/随州有数)。
 * 2. å…¨çœè¡Œ = 17 å¸‚州之和,与母版“全省行=城市行求和”公式一致;中口径分析页“公路班线”
 *    = æœ¬å£å¾„全省合计(当月 1631.2013、累计 11204.5406 ä¸Žæ¯ç‰ˆ 0 å·®å¼‚)。
 * 3. åŒæ¯”以母版缓存 2025 å‚照组为基准(Q1),不在本服务计算。
 *
 * è¾“出:月 â†’ å¸‚州(含 PROVINCE=湖北省) â†’ double[4] =
 * [客运量(含个体), å‘¨è½¬é‡(含个体), ä¸ªä½“客运量, ä¸ªä½“周转量],
 * å•位均为母版展示口径(万人 / ä¸‡äººå…¬é‡Œï¼‰ï¼›ä¼ä¸šå•独量 = arr[0]-arr[2] ä¸Ž arr[1]-arr[3]。
 */
@Service
public class MidCalc {
    public static final String PROVINCE = "湖北省";
    @Resource
    private PassengerEnterpriseMonthlyMapper passengerMapper;
    @Resource
    private PassengerIndividualMonthlyMapper individualMapper;
    /** ç­çº¿åŒ…车页数值矩阵:月 â†’ å¸‚å·ž/全省 â†’ [客运量,周转量,个体客运量,个体周转量] */
    public Map<Integer, Map<String, double[]>> banxianMonthly(String yearPrefix, int limit) {
        Map<Integer, Map<String, double[]>> result = new LinkedHashMap<>();
        List<PassengerEnterpriseMonthly> entRows = passengerMapper.selectList(
            new LambdaQueryWrapper<PassengerEnterpriseMonthly>().likeRight(
                PassengerEnterpriseMonthly::getReportPeriod, yearPrefix));
        for (PassengerEnterpriseMonthly r : entRows) {
            int m = monthOf(r.getReportPeriod());
            if (m < 1 || m > limit) continue;
            String city = RegionUtil.cityByCode(r.getRegionCode());
            if (city == null) continue;
            double pax = nz(r.getPassengerTotal());
            double turn = nz(r.getTurnoverTotal());
            if (pax == 0.0 && turn == 0.0) {
                pax = nz(r.getPassengerClass1()) + nz(r.getPassengerClass2())
                    + nz(r.getPassengerClass3()) + nz(r.getPassengerClass4())
                    + nz(r.getPassengerCharter());
                turn = nz(r.getTurnoverClass1()) + nz(r.getTurnoverClass2())
                    + nz(r.getTurnoverClass3()) + nz(r.getTurnoverClass4())
                    + nz(r.getTurnoverCharter());
            }
            accumulate(result, m, city, pax / 10000.0, turn / 10000.0, 0.0, 0.0);
        }
        List<PassengerIndividualMonthly> indRows = individualMapper.selectList(
            new LambdaQueryWrapper<PassengerIndividualMonthly>().likeRight(
                PassengerIndividualMonthly::getReportPeriod, yearPrefix));
        for (PassengerIndividualMonthly r : indRows) {
            int m = monthOf(r.getReportPeriod());
            if (m < 1 || m > limit) continue;
            String city = RegionUtil.cityByCode(r.getRegionCode());
            if (city == null) continue;
            double pax = nz(r.getPassengerCount()) / 10000.0;
            double turn = nz(r.getTurnover()) / 10000.0;
            accumulate(result, m, city, pax, turn, pax, turn);
        }
        return result;
    }
    private void accumulate(Map<Integer, Map<String, double[]>> result, int m, String city,
                            double paxAll, double turnAll, double paxIndi, double turnIndi) {
        Map<String, double[]> monthMap = result.computeIfAbsent(m, k -> new HashMap<>());
        add(monthMap.computeIfAbsent(city, k -> new double[4]), paxAll, turnAll, paxIndi, turnIndi);
        add(monthMap.computeIfAbsent(PROVINCE, k -> new double[4]), paxAll, turnAll, paxIndi, turnIndi);
    }
    private void add(double[] arr, double paxAll, double turnAll, double paxIndi, double turnIndi) {
        arr[0] += paxAll;
        arr[1] += turnAll;
        arr[2] += paxIndi;
        arr[3] += turnIndi;
    }
    private int monthOf(String reportPeriod) {
        if (reportPeriod == null) return 0;
        String[] parts = reportPeriod.split("-");
        if (parts.length < 2) return 0;
        try {
            return Integer.parseInt(parts[1]);
        } catch (NumberFormatException e) {
            return 0;
        }
    }
    private double nz(Double v) {
        return v == null ? 0.0 : v;
    }
}
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/PaxCalc.java
New file
@@ -0,0 +1,112 @@
package com.trafficaudit.reportexport.calc;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.trafficaudit.dataimport.entity.CityBusMonthly;
import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper;
import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
 * åŸŽå¸‚客运口径计算服务(汇总大表数据驱动改造 M1-城市客运-公交/出租/轨道轮渡,2026-09-08)。
 *
 * å£å¾„与既有多表导出 loadCityBusByMonth / loadCityTaxiByMonth / loadCityRailFerryByMonth å®Œå…¨ä¸€è‡´
 * ï¼ˆå†³ç­–基线 A2/B4/B5/C7;2026-07 ä¸Ž _备份…_清理前_20260904.xlsx å…¬äº¤/出租/轨道轮渡台账页 O åˆ—回归 0 å·®å¼‚):
 * - å…¬äº¤/出租:按月 â†’ å¸‚å·ž â†’ [客运量, æ—…客周转量, åŸŽå¸‚内客运量, åŸŽå¸‚内旅客周转量],按 DB city å­—段分组,"全省"=全部市州合计;
 * - è½¨é“/轮渡:同一 city_bus_monthly è¡¨çš„ rail / ferry å­—段(轨道=武汉/黄石,轮渡=武汉;全省=合计)。
 *
 * è¾“出为“月 â†’ å¸‚å·ž â†’ double[4]”;累计由写回层对 1..m æœˆæ±‚和。同比按 Q1 ä»¥æ¯ç‰ˆç¼“å­˜ 2025 å‚照组为基准,不在本服务计算。
 */
@Service
public class PaxCalc {
    public static final String PROVINCE = "全省";
    @Resource
    private CityBusMonthlyMapper cityBusMapper;
    @Resource
    private CityTaxiMonthlyMapper cityTaxiMapper;
    /** å…¬äº¤ï¼šæœˆ â†’ å¸‚å·ž â†’ [客运量,周转量,城市内客运量,城市内周转量] */
    public Map<Integer, Map<String, double[]>> busMonthly(String yearPrefix, int limit) {
        Map<Integer, Map<String, double[]>> result = new LinkedHashMap<>();
        List<CityBusMonthly> rows = cityBusMapper.selectList(
            new LambdaQueryWrapper<CityBusMonthly>().likeRight(CityBusMonthly::getReportPeriod, yearPrefix));
        for (CityBusMonthly r : rows) {
            int m = monthOf(r.getReportPeriod());
            if (m < 1 || m > limit) continue;
            String city = r.getCity() == null ? "未知" : r.getCity();
            accumulate(result, city, m,
                nz(r.getPassengerVolume()), nz(r.getTurnover()),
                nz(r.getPassengerCity()), nz(r.getTurnoverCity()));
        }
        return result;
    }
    /** å·¡æ¸¸å‡ºç§Ÿï¼šæœˆ â†’ å¸‚å·ž â†’ [客运量,周转量,城市内客运量,城市内周转量] */
    public Map<Integer, Map<String, double[]>> taxiMonthly(String yearPrefix, int limit) {
        Map<Integer, Map<String, double[]>> result = new LinkedHashMap<>();
        List<CityTaxiMonthly> rows = cityTaxiMapper.selectList(
            new LambdaQueryWrapper<CityTaxiMonthly>().likeRight(CityTaxiMonthly::getReportPeriod, yearPrefix));
        for (CityTaxiMonthly r : rows) {
            int m = monthOf(r.getReportPeriod());
            if (m < 1 || m > limit) continue;
            String city = r.getCity() == null ? "未知" : r.getCity();
            accumulate(result, city, m,
                nz(r.getPassengerVolume()), nz(r.getTurnover()),
                nz(r.getPassengerCity()), nz(r.getTurnoverCity()));
        }
        return result;
    }
    /** è½¨é“/轮渡:月 â†’ å¸‚å·ž â†’ [轨道客运量,轨道周转量,轮渡客运量,轮渡周转量] */
    public Map<Integer, Map<String, double[]>> railFerryMonthly(String yearPrefix, int limit) {
        Map<Integer, Map<String, double[]>> result = new LinkedHashMap<>();
        List<CityBusMonthly> rows = cityBusMapper.selectList(
            new LambdaQueryWrapper<CityBusMonthly>().likeRight(CityBusMonthly::getReportPeriod, yearPrefix));
        for (CityBusMonthly r : rows) {
            int m = monthOf(r.getReportPeriod());
            if (m < 1 || m > limit) continue;
            String city = r.getCity() == null ? "未知" : r.getCity();
            accumulate(result, city, m,
                nz(r.getRailPassengerVolume()), nz(r.getRailTurnover()),
                nz(r.getFerryPassengerVolume()), nz(r.getFerryTurnover()));
        }
        return result;
    }
    private void accumulate(Map<Integer, Map<String, double[]>> result, String city, int m,
                            double v0, double v1, double v2, double v3) {
        Map<String, double[]> cityMap = result.computeIfAbsent(m, k -> new HashMap<>());
        double[] arr = cityMap.computeIfAbsent(city, k -> new double[4]);
        arr[0] += v0;
        arr[1] += v1;
        arr[2] += v2;
        arr[3] += v3;
        double[] prov = cityMap.computeIfAbsent(PROVINCE, k -> new double[4]);
        prov[0] += v0;
        prov[1] += v1;
        prov[2] += v2;
        prov[3] += v3;
    }
    private int monthOf(String reportPeriod) {
        if (reportPeriod == null) return 0;
        String[] parts = reportPeriod.split("-");
        if (parts.length < 2) return 0;
        try {
            return Integer.parseInt(parts[1]);
        } catch (NumberFormatException e) {
            return 0;
        }
    }
    private double nz(Double v) { return v == null ? 0.0 : v; }
}
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java
New file
@@ -0,0 +1,266 @@
package com.trafficaudit.reportexport.calc;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.trafficaudit.common.util.RegionUtil;
import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
 * ç½‘约车拆分口径计算服务(汇总大表数据驱动改造 M1-网约车,2026-09-08)。
 * ç®—法与 exportWycSplit / ã€Šç½‘约车拆分.xlsx》月页一致(B5:订单+全省 pin+巡游出租拆分):
 * å®¢è¿é‡=订单占比×全省总量;城市内=巡游出租城市内占比代理(武汉保持自身、其余按剩余占比分摊到全省城市内 pin);
 * å‘¨è½¬é‡=客运量×巡游出租平均运距占比分摊全省周转量 pin;城市内周转量同法(用城市内平均运距)。
 * 2026-07 ä¸Ž _备份…_清理前_20260904.xlsx ç½‘约车台账页 O åˆ—回归 0 å·®å¼‚(17 å¸‚州×客运量/周转量/城市内客运量/城市内周转量)。
 */
@Service
public class WycSplitCalc {
    /** å•市州拆分结果(含城际城乡口径,供不同台账行使用) */
    public static class WycMetrics {
        private final double totalPax;       // å®¢è¿é‡æ€»é‡ï¼ˆä¸‡äººï¼‰
        private final double cityPax;        // åŸŽå¸‚内客运量(万人)
        private final double suburbanPax;    // åŸŽé™…城乡客运量 = totalPax - cityPax
        private final double totalTurnover;  // æ—…客周转量总量(万人公里)
        private final double cityTurnover;   // åŸŽå¸‚内旅客周转量(万人公里)
        private final double suburbanTurnover;
        public WycMetrics(double totalPax, double cityPax, double totalTurnover, double cityTurnover) {
            this.totalPax = totalPax;
            this.cityPax = cityPax;
            this.suburbanPax = totalPax - cityPax;
            this.totalTurnover = totalTurnover;
            this.cityTurnover = cityTurnover;
            this.suburbanTurnover = totalTurnover - cityTurnover;
        }
        public double getTotalPax() { return totalPax; }
        public double getCityPax() { return cityPax; }
        public double getSuburbanPax() { return suburbanPax; }
        public double getTotalTurnover() { return totalTurnover; }
        public double getCityTurnover() { return cityTurnover; }
        public double getSuburbanTurnover() { return suburbanTurnover; }
    }
    public static class WycResult {
        private final String period;
        private final Map<String, WycMetrics> byCity;   // key=RegionUtil è§„范市州名
        private final WycMetrics province;              // å…¨çœ = pin è¾“å…¥
        public WycResult(String period, Map<String, WycMetrics> byCity, WycMetrics province) {
            this.period = period;
            this.byCity = byCity;
            this.province = province;
        }
        public String getPeriod() { return period; }
        public Map<String, WycMetrics> getByCity() { return byCity; }
        public WycMetrics getProvince() { return province; }
    }
    @Resource
    private CityTaxiMonthlyMapper cityTaxiMapper;
    @Value("${city-passenger.template-dir:docs/城市客运}")
    private String templateDir;
    private static final String[] INPUT_CANDIDATES = {"网约车订单及全省总量.xlsx", "模板_网约车订单及全省总量.xlsx"};
    /** è®¡ç®— period(如 2026-07)网约车拆分结果 */
    public WycResult calc(String period) throws Exception {
        Map<String, Object> input = loadMonthlyInput(period);
        if (input == null) {
            throw new RuntimeException("《网约车订单及全省总量.xlsx》缺少报表期 " + period + " çš„æ•°æ®è¡Œï¼Œè¯·å…ˆç»´æŠ¤å½“月订单与全省 pin");
        }
        double pinK = (Double) input.get("pinK");
        double pinCK = (Double) input.get("pinCK");
        double pinZ = (Double) input.get("pinZ");
        double pinCZ = (Double) input.get("pinCZ");
        double orderSum = (Double) input.get("orderSum");
        double[] orderArr = (double[]) input.get("orders");
        if (pinK <= 0 || pinCK <= 0 || pinZ <= 0 || pinCZ <= 0 || orderSum <= 0) {
            throw new RuntimeException("网约车输入 " + period + " è¡Œä¸å®Œæ•´ï¼šå…¨çœ pin / è®¢å•缺失,请核对《网约车订单及全省总量.xlsx》");
        }
        List<String> cities = RegionUtil.cityList();
        int n = cities.size();
        if (orderArr == null || orderArr.length < n) {
            throw new RuntimeException("网约车订单列数不足(应为 17 å¸‚州订单列)");
        }
        Map<String, double[]> taxi = taxiMonthlyOf(period);
        List<String> missingTaxi = new ArrayList<>();
        double[] Dtax = new double[n];
        double[] Etax = new double[n];
        double[] avgD = new double[n];
        double[] avgDc = new double[n];
        for (int i = 0; i < n; i++) {
            double[] a = taxi.get(cities.get(i));
            if (a == null || a[0] <= 0) {
                missingTaxi.add(cities.get(i));
                continue;
            }
            Dtax[i] = a[0];
            Etax[i] = a[2];
            avgD[i] = a[4];
            avgDc[i] = a[5];
        }
        if (!missingTaxi.isEmpty()) {
            throw new RuntimeException("巡游出租月报缺少以下市州数据:" + String.join("、", missingTaxi));
        }
        double[] C = new double[n];
        double[] H = new double[n];
        double[] Gl = new double[n];
        double[] Hl = new double[n];
        double sumG = 0.0;
        double sumDl = 0.0;
        double[] cityRatio = new double[n];
        for (int i = 0; i < n; i++) {
            C[i] = orderArr[i] / orderSum * pinK;
            cityRatio[i] = Etax[i] / Dtax[i];
            sumG += C[i] * cityRatio[i];
            sumDl += C[i] * avgD[i];
        }
        for (int i = 0; i < n; i++) {
            if (i == 0) {
                H[i] = C[i] * cityRatio[i];
            } else {
                double rest = sumG - C[0] * cityRatio[0];
                H[i] = rest == 0.0 ? 0.0 : (C[i] * cityRatio[i]) / rest * (pinCK - C[0] * cityRatio[0]);
            }
        }
        double[] el2 = new double[n];
        double sumEl2 = 0.0;
        for (int i = 0; i < n; i++) {
            el2[i] = H[i] * avgDc[i];
            sumEl2 += el2[i];
            Gl[i] = sumDl == 0.0 ? 0.0 : C[i] * avgD[i] / sumDl * pinZ;
        }
        for (int i = 0; i < n; i++) {
            if (i == 0) {
                Hl[i] = Gl[i];
            } else {
                double rest = sumEl2 - el2[0];
                Hl[i] = rest == 0.0 ? 0.0 : el2[i] / rest * (pinCZ - Gl[0]);
            }
        }
        Map<String, WycMetrics> byCity = new LinkedHashMap<>();
        for (int i = 0; i < n; i++) {
            byCity.put(cities.get(i), new WycMetrics(C[i], H[i], Gl[i], Hl[i]));
        }
        WycMetrics province = new WycMetrics(pinK, pinCK, pinZ, pinCZ);
        return new WycResult(period, byCity, province);
    }
    /** å·¡æ¸¸å‡ºç§ŸæœˆæŠ¥ï¼ˆDB)按月加载:市州 -> [客运量,周转量,城市内客运量,城市内周转量,平均运距,城市内平均运距] */
    private Map<String, double[]> taxiMonthlyOf(String period) {
        Map<String, double[]> map = new LinkedHashMap<>();
        List<CityTaxiMonthly> rows = cityTaxiMapper.selectList(
            new LambdaQueryWrapper<CityTaxiMonthly>().eq(CityTaxiMonthly::getReportPeriod, period));
        for (CityTaxiMonthly r : rows) {
            String city = RegionUtil.normalizeCityName(r.getCity());
            if (city == null || city.isEmpty()) city = "未知";
            double[] a = map.computeIfAbsent(city, k -> new double[6]);
            a[0] += nz(r.getPassengerVolume());
            a[1] += nz(r.getTurnover());
            a[2] += nz(r.getPassengerCity());
            a[3] += nz(r.getTurnoverCity());
            if (r.getAvgDistance() != null && r.getAvgDistance() > 0) a[4] = r.getAvgDistance();
            if (r.getAvgDistanceCity() != null && r.getAvgDistanceCity() > 0) a[5] = r.getAvgDistanceCity();
        }
        for (double[] a : map.values()) {
            if (a[4] <= 0 && a[0] > 0) a[4] = round(a[1] / a[0], 2);
            if (a[5] <= 0 && a[2] > 0) a[5] = round(a[3] / a[2], 2);
        }
        return map;
    }
    /** è¯»å– docs/城市客运/《网约车订单及全省总量.xlsx》:按 period(yyyy-MM) è¡Œå– pin(B-E) ä¸Ž 17 å¸‚州订单(G-W) */
    private Map<String, Object> loadMonthlyInput(String period) throws Exception {
        File f = findInput();
        try (InputStream in = new FileInputStream(f);
             Workbook wb = WorkbookFactory.create(in)) {
            org.apache.poi.ss.usermodel.Sheet sh = wb.getSheetAt(0);
            DataFormatter df = new DataFormatter();
            List<String> cities = RegionUtil.cityList();
            int n = cities.size();
            for (int i = 1; i <= sh.getLastRowNum(); i++) {
                Row row = sh.getRow(i);
                if (row == null) continue;
                Cell c0 = row.getCell(0);
                if (c0 == null) continue;
                String m = df.formatCellValue(c0).trim();
                if (m.isEmpty()) continue;
                if (m.length() > 7) m = m.substring(0, 7);
                if (!period.equals(m)) continue;
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("pinK", cellNum(row, 1, df, 0.0));
                map.put("pinCK", cellNum(row, 2, df, 0.0));
                map.put("pinZ", cellNum(row, 3, df, 0.0));
                map.put("pinCZ", cellNum(row, 4, df, 0.0));
                double[] orders = new double[n];
                double orderSum = 0.0;
                for (int k = 0; k < n; k++) {
                    orders[k] = cellNum(row, 6 + k, df, 0.0);
                    orderSum += orders[k];
                }
                if (orderSum <= 0) orderSum = cellNum(row, 5, df, 0.0);
                map.put("orders", orders);
                map.put("orderSum", orderSum);
                return map;
            }
            return null;
        }
    }
    private File findInput() throws Exception {
        String rel = templateDir;
        while (rel.startsWith("./")) rel = rel.substring(2);
        String[] roots = {
            templateDir,
            System.getProperty("user.dir") + "/" + rel,
            System.getProperty("user.dir") + "/../" + rel
        };
        for (String root : roots) {
            if (root == null || root.trim().isEmpty()) continue;
            for (String name : INPUT_CANDIDATES) {
                File f = new File(root, name);
                if (f.exists() && f.isFile()) return f;
            }
        }
        throw new RuntimeException("未找到《网约车订单及全省总量.xlsx》(含 æ¨¡æ¿_ å‰ç¼€å‘½åï¼‰ï¼Œè¯·æ”¾åˆ° docs/城市客运 ç›®å½•");
    }
    private double cellNum(Row row, int idx, DataFormatter df, double def) {
        Cell c = row.getCell(idx);
        if (c == null) return def;
        if (c.getCellType() == CellType.NUMERIC) return c.getNumericCellValue();
        String s = df.formatCellValue(c).replace(",", "").trim();
        if (s.isEmpty()) return def;
        try {
            return Double.parseDouble(s);
        } catch (NumberFormatException e) {
            return def;
        }
    }
    private double nz(Double v) { return v == null ? 0.0 : v; }
    private double round(double v, int scale) {
        double factor = Math.pow(10, scale);
        return Math.round(v * factor) / factor;
    }
}
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -44,6 +44,7 @@
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -3213,6 +3214,8 @@
        if (template == null) {
            throw new RuntimeException("未找到汇总表母版《" + year + "å¹´" + month + "月道路运输量汇总表.xlsx》(docs/生成汇总大表)");
        }
        double savedZipRatio = ZipSecureFile.getMinInflateRatio();
        ZipSecureFile.setMinInflateRatio(0.0001); // æ–°ç‰ˆæ¯ç‰ˆ styles.xml ç”± openpyxl é«˜åŽ‹ç¼©ï¼ˆ0.0099<0.01)触发 POI Zip-bomb è¯¯æŠ¥ï¼Œè¯»å–后还原
        try (InputStream in = new FileInputStream(template);
             XSSFWorkbook wb = new XSSFWorkbook(in)) {
            File donor = resolveSummaryDonor(year, month);
@@ -3229,10 +3232,14 @@
                log.info("汇总工作簿裁剪至 {} æœˆå®Œæˆï¼Œæ¸…空/改写单元格数={}", toMonth, trimmed);
            }
            dynamicSummaryYear(wb, year);
            applyTwoDecimalFormat(wb); // æ±‡æ€»å¤§è¡¨ä¿ç•™äººå·¥æ¯ç‰ˆåˆ—宽/版式:不做 autoFit(长公式会把列宽顶爆)
            cleanSummarySheetPresentation(wb); // å–消各页筛选、还原隐藏行(否则“班线包车”等页看起来像缺数据)
            applyTwoDecimalFormat(wb); // æ•°å€¼æ˜¾ç¤ºä¸¤ä½å°æ•°ï¼ˆä¿ç•™å…¨ç²¾åº¦ï¼‰ï¼›% / æ—¥æœŸç­‰æ—¢æœ‰æ ·å¼ä¸æ”¹å˜
            autoFitContentColumns(wb); // æŒ‰æ•°æ®è‡ªåŠ¨åŠ å®½åˆ—å®½ï¼ˆåªåŠ å®½ä¸ç¼©çª„ï¼Œè·³è¿‡åˆå¹¶æ ‡é¢˜æ ¼ï¼Œé˜²é•¿æ–‡æœ¬æŠŠå•åˆ—æ’‘çˆ†ï¼‰
            if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr();
            wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // è¿˜åŽŸ/改写公式后打开即重算
            return toBytes(wb);
        } finally {
            ZipSecureFile.setMinInflateRatio(savedZipRatio);
        }
    }
@@ -3526,12 +3533,15 @@
            if (ds == null) continue;
            Sheet bs = wb.getSheet(ds.getSheetName());
            if (bs == null) continue;
            // ä¸­å£å¾„排名页 A..H é™ˆæ—§å·¦å—已于 2026-09-09 æ‹æ¿æ•´å—删除:该区域永不从备份母版回填,避免“生成又复活”
            boolean skipMidRankLeftBlock = "中口径排名".equals(ds.getSheetName());
            for (int r = ds.getFirstRowNum(); r <= ds.getLastRowNum(); r++) {
                Row dr = ds.getRow(r);
                if (dr == null) continue;
                Row br = bs.getRow(r);
                for (Cell dc : dr) {
                    if (dc == null) continue;
                    if (skipMidRankLeftBlock && dc.getColumnIndex() < 8) continue;
                    CellType t = dc.getCellType();
                    if (t == CellType.BLANK) continue;
                    String dtx = summaryCellText(dc);
@@ -3543,6 +3553,7 @@
                    Cell tc = (bc != null) ? bc : tr.getCell(dc.getColumnIndex());
                    if (tc == null) tc = tr.createCell(dc.getColumnIndex());
                    tc.setBlank();
                    applyDonorNumberFormat(wb, tc, dc);
                    if (t == CellType.FORMULA) {
                        String f = dc.getCellFormula();
                        if (f != null) tc.setCellFormula(f);
@@ -3560,6 +3571,24 @@
        return restored;
    }
    /** è¿˜åŽŸå›žå¡«æ—¶åŒæ­¥ donor(已做版式统一)的数字格式,保证同比/数值/排名格显示格式与母版一致 */
    private void applyDonorNumberFormat(XSSFWorkbook wb, Cell dst, Cell src) {
        if (wb == null || dst == null || src == null) return;
        try {
            String fmt = src.getCellStyle().getDataFormatString();
            if (fmt == null || fmt.isEmpty() || "General".equalsIgnoreCase(fmt)) return;
            org.apache.poi.ss.usermodel.DataFormat df = wb.createDataFormat();
            short idx = df.getFormat(fmt);
            CellStyle cur = dst.getCellStyle();
            if (cur != null && idx == cur.getDataFormat()) return;
            CellStyle ns = wb.createCellStyle();
            if (cur != null) ns.cloneStyleFrom(cur);
            ns.setDataFormat(idx);
            dst.setCellStyle(ns);
        } catch (Exception ignore) {
            // ä¸ªåˆ«æ ¼å¼å¼‚常不阻塞还原
        }
    }
    /** å•元格内容文本化(公式带 = å‰ç¼€ï¼‰ï¼Œç”¨äºŽè·¨ç°¿æ¯”对 */
    private String summaryCellText(Cell c) {
        if (c == null) return null;
@@ -4773,6 +4802,26 @@
    }
    /** æ±‡æ€»è¡¨å¯¼å‡ºåŽæŒ‰å†…容自动加宽列宽(只加宽不缩窄;跳过合并单元格标题,防长标题把单列撑爆) */
    /** æ±‡æ€»å¤§è¡¨è¾“出前清理母版残留下来的筛选与行隐藏:筛选箭头/隐藏行会让整页看起来“缺数据” */
    private void cleanSummarySheetPresentation(org.apache.poi.ss.usermodel.Workbook wb) {
        if (wb == null) return;
        for (int s = 0; s < wb.getNumberOfSheets(); s++) {
            org.apache.poi.ss.usermodel.Sheet sh = wb.getSheetAt(s);
            if (sh == null) continue;
            if (sh instanceof org.apache.poi.xssf.usermodel.XSSFSheet) {
                org.apache.poi.xssf.usermodel.XSSFSheet xs = (org.apache.poi.xssf.usermodel.XSSFSheet) sh;
                if (xs.getCTWorksheet().isSetAutoFilter()) xs.getCTWorksheet().unsetAutoFilter();
                for (org.apache.poi.ss.usermodel.Row row : xs) {
                    if (row == null) continue;
                    org.apache.poi.xssf.usermodel.XSSFRow xr = (org.apache.poi.xssf.usermodel.XSSFRow) row;
                    if (xr.getCTRow().isSetHidden()) xr.getCTRow().unsetHidden();
                }
            } else {
                sh.setAutoFilter(null);
            }
        }
    }
    private void autoFitContentColumns(org.apache.poi.ss.usermodel.Workbook wb) {
        if (wb == null) return;
        org.apache.poi.ss.usermodel.DataFormatter dfmt = new org.apache.poi.ss.usermodel.DataFormatter();
traffic-audit-server/src/main/java/com/trafficaudit/rulemanage/controller/RuleManageController.java
@@ -8,6 +8,8 @@
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.List;
/** è§„则管理:按报表类型查看/启停/调整阈值 */
@@ -26,7 +28,9 @@
            qw.eq(AuditRule::getReportType, reportType.trim());
        }
        qw.orderByAsc(AuditRule::getSortOrder);
        return Result.ok(ruleMapper.selectList(qw));
        List<AuditRule> list = ruleMapper.selectList(qw);
        for (AuditRule r : list) syncThresholdText(r); // åç§°/说明随阈值动态展示(如 Â±10% â†’ Â±20%)
        return Result.ok(list);
    }
    /** å¯åœè§„则 */
@@ -47,11 +51,98 @@
        if (body.getId() == null) return Result.error("缺少规则ID");
        AuditRule rule = ruleMapper.selectById(body.getId());
        if (rule == null) return Result.error("规则不存在");
        String oldThreshold = rule.getThreshold();
        if (body.getThreshold() != null) rule.setThreshold(body.getThreshold());
        if (body.getAlertLevel() != null) rule.setAlertLevel(body.getAlertLevel());
        if (body.getDescription() != null) rule.setDescription(body.getDescription());
        // é˜ˆå€¼å˜åŒ–时同步规则名称与说明(名称/说明里与旧阈值对应的 N% ç‰‡æ®µæŒ‰æ–°é˜ˆå€¼æ”¹å†™ï¼‰ï¼Œå¹¶éšä¿å­˜æŒä¹…化
        if (body.getThreshold() != null && !body.getThreshold().equals(oldThreshold)) {
            syncThresholdText(rule, oldThreshold);
        }
        rule.setUpdatedAt(LocalDateTime.now());
        ruleMapper.updateById(rule);
        return Result.ok("已保存");
    }
}
    /** % å‰çš„æ•°å€¼ï¼ˆåç§°/说明中随阈值动态变化的片段;% å‰å¯èƒ½æ˜¯ Â±ã€>、超过、高出 ç­‰æè¿°è¯ï¼Œä»…替换数字本身) */
    private static final Pattern PCT_NUM = Pattern.compile("\\d+(?:\\.\\d+)?(?=%)");
    /** è§£æžé˜ˆå€¼å­—符串中第一个数值;无法解析返回 null */
    private Double parseThresholdNum(String threshold) {
        if (threshold == null || threshold.trim().isEmpty()) return null;
        java.util.regex.Matcher m = Pattern.compile("\\d+(?:\\.\\d+)?").matcher(threshold.trim());
        if (!m.find()) return null;
        try {
            return Double.parseDouble(m.group());
        } catch (Exception e) {
            return null;
        }
    }
    /** æ•°å€¼å±•示:整数去掉小数位,其余保留原精度 */
    private String fmtNum(double v) {
        long l = Math.round(v);
        if (Math.abs(v - l) < 1e-9) return Long.toString(l);
        return new java.math.BigDecimal(String.valueOf(v)).stripTrailingZeros().toPlainString();
    }
    /** ç”±é˜ˆå€¼æ•°å€¼æŽ¨å¯¼â€œè¯´æ˜Žæ–‡å­—里写的是百分之几”的候选口径:
     *  â‘  é˜ˆå€¼æœ¬èº«å°±æ˜¯ç™¾åˆ†æ•°ï¼ˆå¦‚ 20 â†’ Â±20%);
     *  â‘¡ é˜ˆå€¼æ˜¯å°æ•°æ¯”例(如 0.02 â†’ 2%、0.5 â†’ 50%);此时 <1 æ‰å¯ç”¨ï¼›
     *  â‘¢ é˜ˆå€¼æ˜¯æ¯”值(如 1.3 â†’ é«˜å‡º30%);此时 >1 æ‰å¯ç”¨ã€‚ */
    private double[] thresholdPercentCandidates(double v) {
        double[] cands;
        if (v > 0 && v < 1) {
            cands = new double[]{v, v * 100};
        } else if (v > 1) {
            cands = new double[]{v, (v - 1) * 100};
        } else {
            cands = new double[]{v};
        }
        return cands;
    }
    /** æŒ‰æ—§é˜ˆå€¼åŒ¹é…æ–‡æœ¬ä¸­å·²æœ‰çš„百分比片段,仅替换“与旧阈值对得上”的数字为新阈值对应数字;对不上的数字(如说明里与阈值无关的数值)保持不动 */
    private String decorateThresholdText(String text, String oldThreshold, String newThreshold) {
        if (text == null || text.trim().isEmpty()) return text;
        Double oldNum = parseThresholdNum(oldThreshold);
        Double newNum = parseThresholdNum(newThreshold);
        if (oldNum == null || newNum == null) return text;
        double[] oldCands = thresholdPercentCandidates(oldNum);
        double[] newCands = thresholdPercentCandidates(newNum);
        if (oldCands.length != newCands.length) return text;
        java.util.regex.Matcher m = PCT_NUM.matcher(text);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            double token;
            try {
                token = Double.parseDouble(m.group());
            } catch (Exception e) {
                continue;
            }
            String repl = null;
            for (int i = 0; i < oldCands.length; i++) {
                if (Math.abs(oldCands[i] - token) < 1e-9) {
                    repl = fmtNum(newCands[i]);
                    break;
                }
            }
            if (repl == null) continue;
            m.appendReplacement(sb, Matcher.quoteReplacement(repl));
        }
        m.appendTail(sb);
        return sb.toString();
    }
    /** åç§°/说明随阈值同步(update æ—¶æŒ‰ æ—§â†’æ–° æ”¹å†™å¹¶æŒä¹…化) */
    private void syncThresholdText(AuditRule rule, String oldThreshold) {
        if (rule == null || rule.getThreshold() == null) return;
        rule.setRuleName(decorateThresholdText(rule.getRuleName(), oldThreshold, rule.getThreshold()));
        rule.setDescription(decorateThresholdText(rule.getDescription(), oldThreshold, rule.getThreshold()));
    }
    /** è¯»åˆ—表时自愈:仅当文本片段与当前阈值对应时才改写(幂等,不误伤与阈值无关的数字) */
    private void syncThresholdText(AuditRule rule) {
        syncThresholdText(rule, rule == null ? null : rule.getThreshold());
    }
}
traffic-audit-web/src/views/AuditResult.vue
@@ -17,6 +17,9 @@
      <el-main :class="{ 'main-focus': focusMode }" style="display:flex;flex-direction:column;min-height:0">
        <h3 v-show="!focusMode" style="flex:none;margin:0 0 10px">审核结果</h3>
        <el-form :inline="true" style="flex:none">
          <el-form-item label="报表期">
            <el-date-picker v-model="period" type="month" placeholder="选择月份" value-format="yyyy-MM" @change="onPeriodChange"></el-date-picker>
          </el-form-item>
          <el-form-item label="报表类型">
            <el-radio-group v-model="reportType" @change="onTypeChange">
              <el-radio label="H2032">货运月报</el-radio>
@@ -30,9 +33,7 @@
            
            </el-radio-group>
          </el-form-item>
          <el-form-item label="报表期">
            <el-date-picker v-model="period" type="month" placeholder="选择月份" value-format="yyyy-MM" @change="onPeriodChange"></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="executeAudit" :loading="loading">执行审核</el-button>
            <el-button type="success" plain @click="markAudited" :loading="marking">标记已审核</el-button>
@@ -116,12 +117,7 @@
                <i :class="statusIcon(row.status)"></i> {{ statusText(row.status) }}
              </el-tag>
              <span class="spacer"></span>
              <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="goData(row)">数据</el-link>
              <el-tag v-if="explainType(reportType)" size="mini" :type="row.verifyExplanation ? 'success' : 'warning'"
                       style="cursor:pointer" :title="expandedExplain === row.reportId ? '收起解释' : '展开填写/查看解释'"
                       @click="toggleExplain(row.reportId)">
                {{ row.verifyExplanation ? '解释:已填写' : '解释:未填写' }}
              </el-tag>
              <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="openMonthReport(row)">数据</el-link>
              <el-button v-if="row.status === 'PENDING'" size="mini" type="success" icon="el-icon-check" round @click="review(row, 'CONFIRMED')">确认</el-button>
              <el-button v-if="row.status === 'PENDING'" size="mini" type="info" icon="el-icon-close" round plain @click="review(row, 'IGNORED')">忽略</el-button>
              <span v-else class="done-text">已处理</span>
@@ -135,21 +131,6 @@
                <div class="detail-line"><span class="dt-label">实际</span><span class="dt-val">{{ row.actualValue }}</span></div>
                <div class="detail-line"><span class="dt-label">基准</span><span class="dt-val">{{ row.thresholdValue }}</span></div>
                <div class="detail-line"><span class="dt-label">差异</span><span class="dt-val dt-warn">{{ row.deviation }}</span></div>
              </div>
            </div>
            <div v-if="explainType(reportType) && expandedExplain === row.reportId" class="explain-side" style="margin:0 12px 10px">
              <div class="explain-head">
                <span class="explain-title">企业解释</span>
                <el-tag size="mini" :type="row.verifyExplanation ? 'success' : 'warning'">{{ row.verifyExplanation ? '已填写' : '未填写' }}</el-tag>
                <span style="flex:1"></span>
                <el-button size="mini" text @click="expandedExplain = null">收起</el-button>
              </div>
              <span v-if="row.verifyExplanation" class="explain-cur">{{ row.verifyExplanation }}</span>
              <el-input v-model="explainDrafts[row.reportId]" type="textarea" :rows="2" resize="none"
                        placeholder="填写/修改该企业的解释说明…"></el-input>
              <div class="explain-actions">
                <el-button size="mini" type="primary" plain :loading="savingExplainId === row.reportId" @click="saveExplain(row)">保存解释</el-button>
                <el-button v-if="(explainDrafts[row.reportId] || '') !== (row.verifyExplanation || '')" size="mini" text @click="resetDraft(row.reportId)">重置</el-button>
              </div>
            </div>
          </div>
@@ -175,7 +156,7 @@
                  <b>{{ item.ruleName }}</b>
                  <el-tag :type="item.alertLevel === 'RED' ? 'danger' : 'warning'" size="mini">{{ item.alertLevel }}</el-tag>
                  <el-tag :type="statusType(item.status)" size="mini">{{ statusText(item.status) }}</el-tag>
                  <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="goData(item)">数据</el-link>
                  <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="openMonthReport(item)">数据</el-link>
                  <span style="flex:1"></span>
                  <el-button v-if="item.status === 'PENDING'" size="mini" type="success" @click="review(item, 'CONFIRMED')">确认</el-button>
                  <el-button v-if="item.status === 'PENDING'" size="mini" type="info" @click="review(item, 'IGNORED')">忽略</el-button>
@@ -188,32 +169,6 @@
              </div>
            </template>
          </el-table-column>
          <el-table-column label="企业解释" width="340">
            <template slot-scope="scope">
              <div v-if="explainType(reportType) && scope.row.items[0]" class="explain-cell">
                <div v-if="expandedExplain !== scope.row.items[0].reportId" class="explain-compact">
                  <el-tag size="mini" :type="scope.row.items[0].verifyExplanation ? 'success' : 'warning'">{{ scope.row.items[0].verifyExplanation ? '已填写' : '未填写' }}</el-tag>
                  <div v-if="scope.row.items[0].verifyExplanation" class="explain-preview">{{ scope.row.items[0].verifyExplanation }}</div>
                  <el-button size="mini" text type="primary" @click="expandedExplain = scope.row.items[0].reportId">填写/修改</el-button>
                </div>
                <div v-else class="explain-side">
                  <div class="explain-head">
                    <span class="explain-title">企业解释</span>
                    <span style="flex:1"></span>
                    <el-button size="mini" text @click="expandedExplain = null">收起</el-button>
                  </div>
                  <span v-if="scope.row.items[0].verifyExplanation" class="explain-cur">{{ scope.row.items[0].verifyExplanation }}</span>
                  <el-input v-model="explainDrafts[scope.row.items[0].reportId]" type="textarea" :rows="2" resize="none"
                            placeholder="填写/修改该企业的解释说明…"></el-input>
                  <div class="explain-actions">
                    <el-button size="mini" type="primary" plain :loading="savingExplainId === scope.row.items[0].reportId" @click="saveExplain(scope.row.items[0])">保存解释</el-button>
                    <el-button v-if="(explainDrafts[scope.row.items[0].reportId] || '') !== (scope.row.items[0].verifyExplanation || '')" size="mini" text @click="resetDraft(scope.row.items[0].reportId)">重置</el-button>
                  </div>
                </div>
              </div>
              <span v-else style="color:#C0C4CC;font-size:12px">—</span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="130">
            <template slot-scope="scope">
              <el-button v-if="hasPending(scope.row)" size="mini" type="success" plain @click="reviewAll(scope.row, 'CONFIRMED')">全部确认</el-button>
@@ -224,6 +179,50 @@
        </div>
      </el-main>
    </el-container>
        <el-dialog title="数据查看(月报样式)" :visible.sync="monthVisible" width="880px" top="4vh" custom-class="mr-dialog">
          <div v-if="monthLoading" style="padding:40px 0;text-align:center;color:#909399">加载中…</div>
          <div v-else-if="!monthRecords.length" style="padding:30px 0;text-align:center;color:#909399">
            æœªæŸ¥è¯¢åˆ°è¯¥ä¼ä¸šã€è¯¥æŠ¥è¡¨æœŸçš„填报数据(可能还未导入)
          </div>
          <div v-else class="mr-body">
            <div v-for="(rec, ri) in monthRecords" :key="ri" class="mr-card">
              <div class="mr-title">{{ monthTitle(rec) }}</div>
              <table class="mr-meta">
                <tbody>
                  <tr v-for="(m, mi) in monthMeta(rec)" :key="mi">
                    <td class="mr-meta-k">{{ m.label }}</td>
                    <td class="mr-meta-v">{{ fmtVal(m.value) }}</td>
                  </tr>
                </tbody>
              </table>
              <div v-for="sec in monthSections(rec)" :key="sec.title" class="mr-sec">
                <div class="mr-sec-title">{{ sec.title }}</div>
                <table class="mr-sec-tab" border="1">
                  <thead>
                    <tr>
                      <th style="width:34%">指标名称</th>
                      <th v-for="(h, hi) in sec.head" :key="hi" style="width:16.5%">{{ h }}</th>
                    </tr>
                  </thead>
                  <tbody>
                    <tr v-for="(rw, rwi) in sec.rows" :key="rwi">
                      <td>{{ rw.label }}</td>
                      <td v-for="(c, ci) in rw.cells" :key="ci">{{ fmtVal(c.v) }}</td>
                    </tr>
                  </tbody>
                </table>
              </div>
              <div v-if="rec.verifyExplanation || rec.reportNotes" class="mr-note">
                <div v-if="rec.verifyExplanation" class="mr-note-line"><b>核实解释:</b>{{ rec.verifyExplanation }}</div>
                <div v-if="rec.reportNotes" class="mr-note-line"><b>备注:</b>{{ rec.reportNotes }}</div>
              </div>
            </div>
          </div>
          <span slot="footer">
            <el-button size="small" @click="monthVisible = false">关闭</el-button>
          </span>
        </el-dialog>
  </el-container>
</template>
<script>
@@ -231,6 +230,104 @@
import api from '@/api'
const DATA_TYPE_MAP = { H2032: 'h2032', H2031: 'h2031', CITY_BUS: 'cityBus', CITY_TAXI: 'cityTaxi' }
const EXPLAIN_TYPES = ['H2032', 'H2031', 'CITY_BUS', 'CITY_TAXI']
const MONTH_CFG = {
  h2032: {
    meta: [['报表期', 'reportPeriod'], ['企业名称', 'enterpriseName'], ['统一社会信用代码', 'unifiedCreditCode'], ['填报单位', 'reportUnit']],
    sections: [
      { title: '一、运力情况', head: ['车辆数(辆)', '标记吨位数(吨)'], rows: [
        ['总计', ['vehicleTotal', 'tonsTotal']],
        ['其中:牵引车', ['vehicleTractor', null]],
        ['挂车', ['vehicleTrailer', 'tonsTrailer']],
        ['其中:集装箱车', ['vehicleContainer', 'tonsContainer']],
        ['整车', ['vehicleWhole', 'tonsWhole']]
      ]},
      { title: '二、运输生产情况', head: ['货运量(吨)', '货物周转量(吨公里)'], rows: [
        ['运输量', ['freightTotal', 'turnoverTotal']],
        ['其中:集装箱', ['freightContainer', null]],
        ['其中:煤炭及制品', ['freightCoal', null]],
        ['石油、天然气及制品', ['freightOilGas', null]],
        ['其中:原油', ['freightCrudeOil', null]],
        ['金属矿石', ['freightMetalOre', null]],
        ['其中:铁矿石', ['freightIronOre', null]],
        ['矿建性建筑材料', ['freightBuilding', null]],
        ['粮食', ['freightGrain', null]]
      ]},
      { title: '平均指标', head: [], rows: [
        ['货车平均吨位', ['avgTonnage', null]],
        ['货车平均运距', ['avgDistance', null]]
      ]},
      { title: '填报人信息', head: [], rows: [
        ['单位负责人', ['unitLeader', null]], ['统计负责人', ['statsLeader', null]],
        ['填表人', ['contactPerson', null]], ['联系电话', ['contactPhone', null]], ['报出日期', ['reportDate', null]]
      ]}
    ]
  },
  h2031: {
    meta: [['报表期', 'reportPeriod'], ['企业名称', 'enterpriseName']],
    sections: [
      { title: '一、运力情况', head: ['车辆数(辆)', '核定载客位(客位)'], rows: [
        ['总计', ['vehicleTotal', 'seatTotal']],
        ['客运班车(含定线旅游客运客车)', ['vehicleSchedule', 'seatSchedule']],
        ['客运包车(含非定线旅游客运客车)', ['vehicleCharter', 'seatCharter']]
      ]},
      { title: '二、运输生产情况', head: ['客运量(人次)', '旅客周转量(人公里)'], rows: [
        ['总计', ['passengerTotal', 'turnoverTotal']],
        ['一类客运班线(含定线旅游)', ['passengerClass1', 'turnoverClass1']],
        ['二类客运班线(含定线旅游)', ['passengerClass2', 'turnoverClass2']],
        ['三类客运班线(含定线旅游)', ['passengerClass3', 'turnoverClass3']],
        ['四类客运班线(含定线旅游)', ['passengerClass4', 'turnoverClass4']],
        ['客运包车(含非定线旅游)', ['passengerCharter', 'turnoverCharter']]
      ]},
      { title: '客运平均运距', head: [], rows: [
        ['客运平均运距', ['avgDistanceTotal', null]],
        ['一类客运班线平均运距', ['avgDistanceClass1', null]],
        ['二类客运班线平均运距', ['avgDistanceClass2', null]],
        ['三类客运班线平均运距', ['avgDistanceClass3', null]],
        ['四类客运班线平均运距', ['avgDistanceClass4', null]],
        ['客运包车平均运距', ['avgDistanceCharter', null]]
      ]}
    ]
  },
  cityBus: {
    meta: [['报表期', 'reportPeriod'], ['市州', 'city'], ['企业名称', 'enterpriseName']],
    sections: [
      { title: '一、公共汽电车运营', head: [], rows: [
        ['运营车数(辆)', ['opVehicles', null]], ['运营站点总个数(个)', ['stopTotal', null]],
        ['其中:城市运营站点', ['stopCity', null]], ['客运量(万人次)', ['passengerVolume', null]],
        ['其中:城市客运量', ['passengerCity', null]], ['旅客周转量(万人公里)', ['turnover', null]],
        ['其中:城市旅客周转量', ['turnoverCity', null]]
      ]},
      { title: '二、派生指标与增速', head: [], rows: [
        ['平均运距(公里)', ['avgDistance', null]], ['城市内平均运距', ['avgDistanceCity', null]],
        ['城际城乡客运量', ['passengerChengxiang', null]], ['城际城乡周转量', ['turnoverChengxiang', null]],
        ['城际城乡平均运距', ['avgDistanceChengxiang', null]], ['客运量环比', ['ratePassengerMom', null]],
        ['城市内客运量环比', ['ratePassengerCityMom', null]], ['城际城乡客运量环比', ['rateChengxiangMom', null]],
        ['客运量同比', ['ratePassengerYoy', null]], ['城市内客运量占比', ['passengerCityRatio', null]],
        ['城际城乡客运量占比', ['chengxiangRatio', null]], ['填表人', ['contactPerson', null]],
        ['报出日期', ['reportDate', null]]
      ]}
    ]
  },
  cityTaxi: {
    meta: [['报表期', 'reportPeriod'], ['市州', 'city'], ['企业名称', 'enterpriseName']],
    sections: [
      { title: '一、巡游出租运营服务', head: [], rows: [
        ['载客车次总数(万车次)', ['tripTotal', null]], ['其中:城市载客车次', ['tripCity', null]],
        ['客运量(万人次)', ['passengerVolume', null]], ['其中:城市客运量', ['passengerCity', null]],
        ['旅客周转量(万人公里)', ['turnover', null]], ['其中:城市旅客周转量', ['turnoverCity', null]],
        ['运营车辆数(辆)', ['opVehicles', null]]
      ]},
      { title: '二、派生指标与增速', head: [], rows: [
        ['平均运距(公里)', ['avgDistance', null]], ['城市内平均运距', ['avgDistanceCity', null]],
        ['单车次载客人数', ['passengersPerTrip', null]], ['载客车次环比', ['rateTripMom', null]],
        ['载客车次同比', ['rateTripYoy', null]], ['客运量环比', ['ratePassengerMom', null]],
        ['客运量同比', ['ratePassengerYoy', null]], ['城市内客运量占比', ['passengerCityRatio', null]],
        ['城际城乡平均运距', ['avgDistanceChengxiang', null]]
      ]}
    ]
  }
}
export default {
  components: { AppHeader },
  name: 'AuditResult',
@@ -238,7 +335,8 @@
    return {
      period: '', reportType: 'H2032', loading: false, marking: false, results: [], ruleStats: [], statsCollapsed: false,
      enterpriseFilter: '', ruleFilter: '', viewMode: 'merged', batchSelection: [], focusMode: false,
      explainDrafts: {}, savingExplainId: null, expandedExplain: null
      explainDrafts: {}, savingExplainId: null, expandedExplain: null,
      monthVisible: false, monthLoading: false, monthType: '', monthPeriod: '', monthKeyword: '', monthRecords: [], monthColumns: []
    }
  },
  computed: {
@@ -301,10 +399,56 @@
  methods: {
    dataType(t) { return DATA_TYPE_MAP[t] || '' },
    explainType(t) { return EXPLAIN_TYPES.indexOf(t) >= 0 },
    goData(row) {
    openMonthReport(row) {
      const type = DATA_TYPE_MAP[this.reportType]
      if (!type) return
      this.$router.push({ path: '/data', query: { type, period: row.reportPeriod, keyword: row.enterpriseName || row.city || '' } })
      this.monthType = type
      this.monthPeriod = row.reportPeriod
      this.monthKeyword = row.enterpriseName || row.city || ''
      this.monthRecords = []
      this.monthColumns = []
      this.monthVisible = true
      this.monthLoading = true
      api.get('/data/list', {
        params: {
          type, period: row.reportPeriod,
          keyword: row.enterpriseName || row.city || '',
          page: 1, size: 50
        }, timeout: 60000
      }).then(res => {
        const d = res.data || {}
        this.monthRecords = d.records || []
        this.monthColumns = d.columns || []
        if (!this.monthRecords.length) this.$message.warning('未查询到该企业该报表期的填报数据(可能尚未导入)')
      }).catch(e => {
        if (e && e._toast) return
        this.$message.error('月报数据查询失败')
      }).finally(() => { this.monthLoading = false })
    },
    monthTitle(rec) {
      const cfg = MONTH_CFG[this.monthType] || null
      if (!cfg) return (this.monthKeyword || '') + '(' + this.monthPeriod + ')'
      const name = rec.enterpriseName || rec.city || this.monthKeyword || ''
      return name + ' ' + (rec.reportPeriod || this.monthPeriod || '') + ' æœˆæŠ¥æ•°æ®'
    },
    monthMeta(rec) {
      const cfg = MONTH_CFG[this.monthType] || null
      if (!cfg) return []
      return cfg.meta.map(m => ({ label: m[0], value: rec[m[1]] })).filter(m => m.value !== undefined)
    },
    monthSections(rec) {
      const cfg = MONTH_CFG[this.monthType]
      if (!cfg) return []
      return cfg.sections.map(sec => ({
        title: sec.title, head: sec.head || [],
        rows: sec.rows.map(r => ({ label: r[0], cells: (r[1] || []).map(prop => ({ prop, v: prop ? rec[prop] : null })) }))
          .filter(rw => rw.cells.some(c => c.v !== null && c.v !== undefined && c.v !== ''))
      })).filter(sec => sec.rows.length)
    },
    fmtVal(v) {
      if (v === null || v === undefined || v === '') return '—'
      if (typeof v === 'number') return String(v)
      return String(v)
    },
    goExplain(row) {
      this.$router.push({ path: '/explain', query: { reportType: this.reportType, keyword: row.enterpriseName || row.city || '', period: row.reportPeriod } })
@@ -581,4 +725,20 @@
.stat-num { color: #909399; }
.stat-num b { font-weight: 700; }
.hit-num { color: #E6A23C; }
.mr-dialog .el-dialog__body { padding: 12px 18px; }
.mr-body { max-height: 66vh; overflow-y: auto; }
.mr-card { border: 1px solid #DCDFE6; border-radius: 6px; margin-bottom: 14px; overflow: hidden; }
.mr-title { background: #409EFF; color: #fff; font-weight: 600; padding: 8px 12px; font-size: 15px; }
.mr-meta { width: 100%; border-collapse: collapse; background: #F7F9FC; }
.mr-meta td { border: 1px solid #EBEEF5; padding: 6px 10px; font-size: 13px; }
.mr-meta-k { width: 120px; color: #909399; background: #F0F4FA; }
.mr-meta-v { color: #303133; font-weight: 600; }
.mr-sec { padding: 10px 12px 4px; }
.mr-sec-title { font-weight: 600; color: #303133; margin: 4px 0 8px; }
.mr-sec-tab { width: 100%; border-collapse: collapse; font-size: 13px; }
.mr-sec-tab th, .mr-sec-tab td { border: 1px solid #DCDFE6; padding: 5px 8px; text-align: left; color: #303133; }
.mr-sec-tab th { background: #F5F7FA; font-weight: 600; }
.mr-note { margin: 6px 12px 12px; background: #FDF6EC; border: 1px solid #FAECD8; border-radius: 4px; padding: 8px 10px; font-size: 12px; color: #8a6d3b; white-space: pre-wrap; word-break: break-all; }
.mr-note-line { margin: 2px 0; }
</style>
traffic-audit-web/src/views/DataImport.vue
@@ -251,7 +251,7 @@
        scaleSplit: '模板_规上规下拆分运输量.xlsx',
        freightTurnover: '模板_货运量周转量.xlsx',
        passengerMonthly: '公路旅客运输月度生产情况.xlsx(含运政车辆数sheet)',
        passengerAuth: '模板_公路旅客运政车辆数.xlsx(列:企业代码/企业名称/车辆数/载客位数)',
        passengerAuth: '模板_公路旅客运政车辆数.xls(支持 ä¼ä¸šä»£ç /企业名称/车辆数/载客位数 ä¸Ž è¿æ”¿åº“导出 OWNERNAME/总车辆数/客位数)',
        passengerIndividual: '个体客运量汇总.xlsx(列:所属地区/企业代码/企业名称/个体客运量/个体旅客周转量)',
        energyMonthly: '道路货运车辆能源消耗情况(2026年).xlsx(能耗明细+运政车辆信息两sheet)',
        energyAuth: '模板_能耗车辆运政信息.xlsx(列:车牌号/车辆类型/燃料类型/标记吨位/准牵引质量)',
@@ -276,7 +276,7 @@
        scaleSplit: '货运/模板_规上规下拆分运输量.xlsx',
        freightTurnover: '货运/模板_货运量周转量.xlsx',
        passengerMonthly: '公路旅客/模板_交企统H203-1表_公路旅客运输月度生产情况.xlsx',
        passengerAuth: '公路旅客/模板_公路旅客运政车辆数.xlsx',
        passengerAuth: '公路旅客/模板_公路旅客运政车辆数.xls',
        passengerIndividual: '公路旅客/个体客运量汇总.xlsx',
        energyMonthly: '能耗/模板_道路货运车辆能源消耗情况(2026).xlsx',
        energyAuth: '能耗/模板_能耗车辆运政信息.xlsx',
traffic-audit-web/src/views/DataView.vue
@@ -81,12 +81,14 @@
      total: 0,
      records: [],
      columns: [],
      loading: false
      loading: false,
      loadingMore: false
    }
  },
  methods: {
    loadData() {
      this.loading = true
      this.loadingMore = false
      api.get('/data/list', {
        params: {
          type: this.type,
@@ -109,7 +111,71 @@
        this.$message.error('数据查询失败')
      }).finally(() => {
        this.loading = false
        this.$nextTick(() => this.attachBodyScroll())
      })
    },
    // el-table å›ºå®šé«˜åº¦åŽï¼ŒçœŸæ­£çš„竖向滚动容器是内部的 body-wrapper(页面本身不滚动)
    attachBodyScroll() {
      const wrap = this.$refs.tableWrap
      if (!wrap) return
      const bw = wrap.querySelector('.el-table__body-wrapper')
      if (bw && bw._vwScrollHandler !== this.onScroll) {
        if (bw._vwScrollHandler) bw.removeEventListener('scroll', bw._vwScrollHandler)
        bw.addEventListener('scroll', this.onScroll, { passive: true })
        bw._vwScrollHandler = this.onScroll
      }
    },
    // æ»šåŠ¨åˆ°åº•éƒ¨è‡ªåŠ¨è¿½åŠ ä¸‹ä¸€é¡µï¼ˆä¿ç•™æ‰‹åŠ¨åˆ†é¡µè·³é¡µèƒ½åŠ›ï¼‰
    loadMore() {
      if (this.loading || this.loadingMore) return
      if (this.records.length >= this.total || this.total <= 0) return
      const nextPage = this.page + 1
      this.loadingMore = true
      api.get('/data/list', {
        params: {
          type: this.type,
          period: this.period || undefined,
          keyword: this.keyword || undefined,
          page: nextPage,
          size: this.size
        }
      }).then(res => {
        const d = res.data || {}
        const addRows = d.records || []
        if (!addRows.length) {
          this.page = nextPage - 1
          return
        }
        this.records = this.records.concat(addRows)
        this.page = nextPage
        if (d.total != null) this.total = d.total
        if (d.columns && d.columns.length) this.columns = d.columns
      }).catch(e => {
        if (e && e._toast) return
        this.$message.error('加载更多失败')
      }).finally(() => {
        this.loadingMore = false
        this.$nextTick(() => this.attachBodyScroll())
      })
    },
    onScroll(e) {
      // æ™®é€š/全屏下真正的滚动容器都是 el-table å†…部 body-wrapper(捕获阶段可收到);
      // é¡µé¢æ•´ä½“滚动时以 window/document ä¸ºå‡†ã€‚
      const t = e && e.target
      let scrollTop, clientHeight, scrollHeight
      if (t && t.nodeType === 1 && t !== document && typeof t.scrollTop === 'number' && t.scrollHeight > t.clientHeight) {
        scrollTop = t.scrollTop
        clientHeight = t.clientHeight
        scrollHeight = t.scrollHeight
      } else {
        const se = document.scrollingElement || document.documentElement
        scrollTop = se.scrollTop
        clientHeight = window.innerHeight
        scrollHeight = se.scrollHeight
      }
      if (scrollHeight > 0 && scrollTop + clientHeight >= scrollHeight - 80) {
        this.loadMore()
      }
    },
    toggleFullscreen() {
      const el = this.$refs.tableWrap
@@ -131,11 +197,44 @@
    if (q.period) this.period = q.period
    if (q.keyword) this.keyword = q.keyword
    this.loadData()
    window.addEventListener('scroll', this.onScroll, { passive: true })
    if (this.$refs.tableWrap) {
      // capture=true:el-table å†…部 body-wrapper çš„æ»šåŠ¨åœ¨æ•èŽ·é˜¶æ®µä¹Ÿèƒ½æ”¶åˆ°
      this.$refs.tableWrap.addEventListener('scroll', this.onScroll, { passive: true, capture: true })
    }
    this.$nextTick(() => this.attachBodyScroll())
  },
  beforeDestroy() {
    window.removeEventListener('scroll', this.onScroll)
    if (this.$refs.tableWrap) {
      this.$refs.tableWrap.removeEventListener('scroll', this.onScroll, { capture: true })
      const bw = this.$refs.tableWrap.querySelector('.el-table__body-wrapper')
      if (bw && bw._vwScrollHandler) bw.removeEventListener('scroll', bw._vwScrollHandler)
    }
  }
}
</script>
<style scoped>
.el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; }
#app .table-wrap {
  display: flex;
  flex-direction: column;
  height: calc(100vh - 330px);
  min-height: 300px;
}
#app .table-area {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}
#app .table-area .el-table {
  width: 100%;
  height: 100%;
}
#app .table-wrap .el-pagination {
  flex: none;
  margin-top: 8px;
}
#app .table-area:fullscreen {
  padding: 20px;
  background: #fff;
@@ -147,6 +246,7 @@
  overflow: auto;
  display: flex;
  flex-direction: column;
  height: 100vh;
}
#app .table-wrap:fullscreen .table-area {
  flex: 1 1 auto;
traffic-audit-web/src/views/ReportExport.vue
@@ -171,6 +171,11 @@
      noteDraft: '',
      modules: [
        {
          key: 'summary', title: '汇总大表', items: [
            { key: 'summaryWorkbook', label: '道路运输量汇总表(整本)', fileName: '生成_道路运输量汇总表.xlsx', status: null, statusNote: '' }
          ], checked: []
        },
        {
          key: 'freight', title: '货运报表', items: [
            { key: 'cityDetail', label: '货运量分市州明细', fileName: '生成_货运量分市州明细.xlsx', status: null, statusNote: '' },
            { key: 'freightRank', label: '货运量排名', fileName: '生成_货运量排名.xlsx', status: null, statusNote: '' },
@@ -185,15 +190,14 @@
            { key: 'passengerMidAnalysis', label: '中口径分析', fileName: '生成_公路旅客中口径分析.xlsx', status: null, statusNote: '' }
          ], checked: []
        },        {
          key: 'cityPassenger', title: '城市客运报表', tag: '公交/出租/轨道轮渡/汇总', tagType: 'success', items: [
          key: 'cityPassenger', title: '城市客运报表', items: [
            { key: 'cityBusDetail', label: '城市公交客运量分市州明细', fileName: '生成_城市公交客运量分市州明细.xlsx', status: null, statusNote: '' },
            { key: 'cityTaxiDetail', label: '巡游出租客运量分市州明细', fileName: '生成_巡游出租客运量分市州明细.xlsx', status: null, statusNote: '' },
            { key: 'cityRailFerryDetail', label: '轨道轮渡客运量分市州明细', fileName: '生成_轨道轮渡客运量分市州明细.xlsx', status: null, statusNote: '' },
            { key: 'cityPassengerCitySum', label: '城市客运客运量分市州累计', fileName: '生成_城市客运客运量分市州累计.xlsx', status: null, statusNote: '' },
            { key: 'cityPassengerDetailSum', label: '城市客运各市州明细表', fileName: '生成_城市客运各市州明细表.xlsx', status: null, statusNote: '' },
            { key: 'cityPassengerSummary', label: '城市客运汇总', fileName: '生成_城市客运汇总.xlsx', status: null, statusNote: '' },
            { key: 'wycSplit', label: '网约车客运量拆分表', fileName: '生成_网约车客运量拆分表.xlsx', status: null, statusNote: '' },
            { key: 'summaryWorkbook', label: '道路运输量汇总表(整本)', fileName: '生成_道路运输量汇总表.xlsx', status: null, statusNote: '' }
            { key: 'wycSplit', label: '网约车客运量拆分表', fileName: '生成_网约车客运量拆分表.xlsx', status: null, statusNote: '' }
          ], checked: []
        },
        {
@@ -238,7 +242,7 @@
    // æŠ¥è¡¨æœŸä¸‹æ‹‰åˆ†ç»„:年报按年份;月报/季报按年分组列 1-12 æœˆï¼ˆèƒ½è€—只列季度末月)
    periodGroups() {
      const year = new Date().getFullYear()
      const years = [year - 1, year, year + 1]
      const years = [year, year - 1, year + 1]
      if (this.cycle === 'year') {
        return [{ label: '选择年份', options: years.map(y => ({ label: y + 'å¹´', value: String(y) })) }]
      }