zhizhijie
9 天以前 595b31273d583fbe367b3bfbebb6fc02e318abc0
fix(投资): 明细表取数三处静默缺陷 + 导入两处丢数/选错页签缺陷

导出(ReportExportService):
- 项目名归一化补标点,修复荆州「中国供销.公安商贸物流园」匹配失败(保留底稿7月值)
- 同名多档优先取「当月有数据」的档,修复十堰快递物流产业园二期取到旧档
- 未按当月数据覆盖的行逐行 WARN 留痕(行号+市州+项目名+原因),不再静默结转上期值

导入(DataImportService):
- 表头关键词判定先去换行/全角空格,修复天门 H/J/K 三列被写成 0
- sheet 选择新增「表内报表期」命中(权重高于 sheet 名同月),修复潜江把 2018 年历史 sheet 当 2026-08

回归: 2026-08 明细逐格差异 156 -> 129,无新增差异;报告见 docs/功能测试报告
4个文件已修改
1个文件已添加
249 ■■■■■ 已修改文件
HANDOFF.md 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/功能测试报告/功能测试报告_投资明细表取数修复_2026-09-22.md 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工作日结/工作日结_2026-09-22.md 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HANDOFF.md
@@ -11,6 +11,13 @@
- **今日已完成(远端提交)**:数据查看补齐网约车/个体客运、按市州汇总与导出 Excel、汇总大表进度弹窗与结果落盘缓存。
- **未完成重点不变**:P0-2《亿元投资项目》按 Q7 è¿½åˆ—、P0-3 å¯¼å‡ºå‰åŸºæ•°ä½“检、P0-4 2026-09 æ¯ç‰ˆï¼›å…¶ä½™è§ `docs/交接文档_2026-09-21.md` ç¬¬ 4 ç« ã€‚
### è¡¥å……(2026-09-22 ä¸‹åˆï¼ŒæŠ•资取数修复第一轮)
- **已修(代码 + é‡å¯¼ + å›žå½’)**:① å¯¼å‡ºåŽ»æ ‡ç‚¹å½’ä¸€åŒ–åŒ¹é…ï¼ˆè†å·žï¼‰ï¼›â‘¡ åŒååŒæ¡£ä¼˜å…ˆå–当月有数据的档(十堰快递产业园二期);③ æœªè¦†ç›–行逐行 WARN ç•™ç—•;④ å¯¼å…¥è¡¨å¤´åŽ»æ¢è¡Œï¼ˆå¤©é—¨ H/J/K ä¸å†ä¸¢ï¼‰ï¼›â‘¤ å¯¼å…¥æŒ‰è¡¨å†…报表期选 sheet(**潜江不再把 2018 å¹´æ•°æ®å½“ 2026-08**)。
- **结果**:2026-08 ç‰©æµ/客运明细逐格差异 **156 â†’ 129**,无新增差异;未覆盖清单已能自动列出(物流武汉 6 è¡Œï¼›å®¢è¿é»„石1/黄冈9/恩施1/仙桃1)。
- **遗留(下一轮)**:L/D/E/F è¦†ç›–口径(需《建设阶段归一表》)、武汉物流与黄冈客运 8 æœˆæºæ•°æ®ã€æ½œæ±Ÿ 2026-06/07 åŽ†å²é”™å€¼é‡å¯¼ã€æœªè¦†ç›–æ¸…å•å‡ºæ–‡ä»¶/页面提示(P1-9)。
- **报告**:`docs/功能测试报告/功能测试报告_投资明细表取数修复_2026-09-22.md`。
### è¡¥å……(2026-09-22 ä¸‹åˆï¼ŒæŠ•资明细表比对)
- **已完成**:软件《客运站投资明细表》《物流站场投资明细》与人工版逐格比对(156 æ ¼å·®å¼‚),报告 `docs/功能测试报告/数据比对报告_投资明细表软件生成与人工版_2026-09-22.md`。
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_Ͷ×ÊÃ÷ϸ±íÈ¡ÊýÐÞ¸´_2026-09-22.md
New file
@@ -0,0 +1,91 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Š â€” æŠ•资明细表取数修复(软件生成 vs äººå·¥ç‰ˆ å·®å¼‚收敛)
> æµ‹è¯•日期:2026-09-22
> è¢«æµ‹å¯¹è±¡ï¼šæŠ¥è¡¨ç”Ÿæˆ â†’ æŠ•资《客运站投资明细表》《物流站场投资明细》导出 + æŠ•资月报(市州单表)导入
> èƒŒæ™¯ï¼šã€Šæ•°æ®æ¯”对报告_投资明细表软件生成与人工版_2026-09-22.md》定位出 156 æ ¼å·®å¼‚与 3 ç±»"会静默出数"的取数缺陷,本报告为第一轮修复与回归验证。
## ä¸€ã€åŠŸèƒ½è¯´æ˜Žï¼ˆæœ¬æ¬¡æ”¹äº†ä»€ä¹ˆï¼‰
### 1. å¯¼å‡ºï¼š`traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java`
| # | æ”¹åЍ | è¯´æ˜Ž |
| --- | --- | --- |
| 1-1 | `normInvestName()` å½’一化补标点 | å¢žåŠ åŠ/全角句点、顿号、逗号、分号、冒号等(原只去 `·•—-_-`)。库内项目名「中国供销**.**公安商贸物流园」与明细表行名「中国供销公安商贸物流园(公安县域集采集配中心)」归一化后前缀一致 â†’ å¯åŒ¹é… |
| 1-2 | æ–°å¢ž `preferMonthlyCandidate()` | åŒ¹é…åˆ°çš„æ¡£åœ¨**当月无数据**时,改选「归一化同名且当月有数据」的档(取 id æœ€å¤§è€…)。修复十堰「快递物流产业园二期」取到旧档 id=154(月度只到 7 æœˆï¼‰çš„问题 |
| 1-3 | æ–°å¢ž `logSkippedRows()` + å®¢è¿/物流导出留痕 | æœªæŒ‰å½“月数据覆盖的行**逐行 WARN**:`[未覆盖] ç¬¬N行 å¸‚å·ž é¡¹ç›®å â†’ åŽŸå› `,并给出按市州统计。彻底消除"静默结转上期值" |
### 2. å¯¼å…¥ï¼š`traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java`
| # | æ”¹åЍ | è¯´æ˜Ž |
| --- | --- | --- |
| 2-1 | `investHeaderKey()` å…ˆåŽ»ç©ºç™½å†åˆ¤å®š | è¡¨å¤´å•元格常含换行/全角空格(天门「自开始建\n设累计完\n成投资\n(万元)」),原逻辑 `v.contains("自开始建设")` åŒ¹é…ä¸ä¸Š â†’ H/J/K ä¸‰åˆ—映射落到 -1 è¢«å†™æˆ 0。改为 `replaceAll("[\\s\\u3000]+","")` åŽå†åˆ¤å®š |
| 2-2 | æ–°å¢ž `investSheetPeriodCellHit()` + è¯„分加权 | å¤š sheet åŽ†å²æ–‡ä»¶æŒ‰ sheet ååŒ¹é…æœˆä»½ä¼šæŠŠ 2018 å¹´çš„「潜江(8月)」当成 2026-08。新增"表内报表期"命中(sheet å‰ 8 è¡Œçš„ `2026.08.24` / `2026å¹´8月` / æ—¥æœŸå•元格),权重 2000000 é«˜äºŽ sheet ååŒ¹é…ï¼ˆ1000000) |
## äºŒã€æµ‹è¯•环境
- åŽç«¯ï¼š`mvn spring-boot:run`(8090),重启后日志出现 `Started TrafficAuditApplication`;前端 8080 åœ¨è¿è¡Œ
- æ•°æ®åº“:`localhost:3308/traffic_audit`(本机开发库);登录 `admin/123456` å– JWT
- äººå·¥ç‰ˆåŸºå‡†ï¼š`D:\05_数据分类\投资\8月\8月客运站投资明细表.xlsx`、`8月物流站场投资明细.xls`
- ä¿®å¤å‰è½¯ä»¶ç‰ˆï¼š`D:\桌面\生成数据\生成_所选报表_2026-08 (1)\…`
- ä¿®å¤åŽè½¯ä»¶ç‰ˆï¼š`_tmp/gen_new_0922/…`(本次重跑导出)
- æ¯”对脚本:`_tmp_diff_invest_0922.py`(修复前)、`_tmp_diff_after_fix_0922.py`(修复后)
## ä¸‰ã€æµ‹è¯•用例与结果
| # | ç”¨ä¾‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
| --- | --- | --- | --- | --- | --- |
| 1 | å¯¼å‡º-项目名带标点 | 2026-08 ç‰©æµæ˜Žç»†å¯¼å‡º | ç¬¬119/120行「中国供销公安商贸物流园」按库内当月写入 | ä¿®å¤å‰ï¼šæœªè¦†ç›–,保留底稿 7758.5/7758.5/1600.1;修复后:写入 **15986/15986/8227.5** ä¸Žæºè¡¨ä¸€è‡´ï¼›æ—¥å¿—不再列该行 | âœ… |
| 2 | å¯¼å‡º-同名双档 | åŒä¸Š | å–「当月有数据」的档(id=181) | ä¿®å¤å‰ï¼š20081/2900/214、竣工 2026;修复后:**20377/3196/296、竣工 2029**(= åå °æºè¡¨ R71) | âœ… |
| 3 | å¯¼å‡º-未覆盖留痕(物流) | åŒä¸Š | æœªè¦†ç›–行列出行号+市州+项目名+原因 | æ—¥å¿—:`物流站场投资明细 2026-08:写入 93 è¡Œï¼Œç»“转母版原值 6 è¡Œ` + 6 æ¡ `[未覆盖] ç¬¬12行 æ­¦æ±‰å¸‚ èˆªå‘智慧物流园 â†’ åº“内无 2026-08 æœˆåº¦æ•°æ®`(按市州统计 `{武汉市=6}`) | âœ… |
| 4 | å¯¼å‡º-未覆盖留痕(客运) | åŒä¸Š | åŒä¸Š | `写入 37 è¡Œï¼Œç»“转母版原值 12 è¡Œï¼ˆå…¶ä¸­åº“内有项目但当月无数据 9 è¡Œï¼‰` + 12 æ¡æ˜Žç»†ï¼ˆé»„石 1 / é»„冈 9 / æ©æ–½ 1 / ä»™æ¡ƒ 1) | âœ… |
| 5 | å¯¼å…¥-表头含换行(天门) | `8月份物流园中心建设项目(天门).xlsx`,period=2026-08,type=investmentLogistics | å…¥åº“ 16677.7 / 16677.7 / 500 | ä¿®å¤å‰åº“内 0/0/0;修复后 `investment_monthly` = **16677.7 / 16677.7 / 500**,接口返回 success=1 | âœ… |
| 6 | å¯¼å…¥-历史页签(潜江) | `潜江2026.08.xlsx`(含 20 ä¸ªåŽ†å² sheet) | é€‰å½“期 sheet(表内报表期 2026.08.24) | ä¿®å¤å‰ï¼šå…¥åº“ **2018 å¹´**「潜江市公路项目」5173/5173/1440(错);修复后:入库「**中国虾谷冷链仓储物流园**」11947.9/1135/0(= æºè¡¨å½“期 sheet),旧错行被同市州同期间清理 | âœ… |
| 7 | å›žå½’-逐格比对 | ä¿®å¤åŽå¯¼å‡º vs äººå·¥ç‰ˆ | å·®å¼‚数下降且无新增差异 | 156 æ ¼ â†’ **129 æ ¼**(降 27),剩余差异与修复前逐条对应,无新增 | âœ… |
| 8 | è´Ÿå‘-未匹配行不写错值 | æ­¦æ±‰ 6 è¡Œï¼ˆåº“内无 2026-08 æ•°æ®ï¼‰ | ä¿æŒåº•稿原值且必须有日志告警 | 6 è¡Œä¿æŒåŽŸå€¼ï¼Œæ—¥å¿—é€è¡Œå‘Šè­¦ï¼ˆä¸é™é»˜ï¼‰ | âœ… |
### ä¿®å¤å‰åŽé€é¡µç­¾å¯¹ç…§
| æ–‡ä»¶ / é¡µç­¾ | ä¿®å¤å‰ | ä¿®å¤åŽ | è¯´æ˜Ž |
| --- | --- | --- | --- |
| å®¢è¿-明细 | 12 | 12 | æœ¬æ¬¡æœªåŠ¨å®¢è¿åŒ¹é…é€»è¾‘ï¼ˆå·®å¼‚å±žå–æ•´/文本口径) |
| å®¢è¿-汇总 | 4 | 4 | åŒä¸Š |
| ç‰©æµ-月2 | 0 | 0 | â€” |
| ç‰©æµ-分项目投资完成情况 | 108 | **91** | è†å·ž âˆ’10、十堰 âˆ’5、天门 âˆ’2 |
| ç‰©æµ-分市州规划内外项目完成情况 | 15 | **11** | è†å·žã€å¤©é—¨éšæ˜Žç»†é¡µå½’位 |
| ç‰©æµ-市州汇总 | 17 | **11** | åŒä¸Š |
| **合计** | **156** | **129** | æ”¶æ•› 27 æ ¼ |
## å››ã€å‰©ä½™å·®å¼‚(129 æ ¼ï¼‰åˆ†ç±»ä¸Žè´£ä»»
| ç±»åˆ« | æ ¼æ•° | åŽŸå›  | è´£ä»»æ–¹ / ä¸‹ä¸€æ­¥ |
| --- | --- | --- | --- |
| æ­¦æ±‰æ•´æ®µï¼ˆæ˜Žç»† 24 + åˆè®¡ 3) | 27 | åº“内**无武汉物流当月数据**(8 æœˆæ­¦æ±‰æ— å•表),导出只能结转底稿 7 æœˆå€¼ | ä¸šåŠ¡è¡¥æ­¦æ±‰å•è¡¨ï¼›æˆ–æ˜Žç¡®"武汉由人工填"(现已有告警) |
| åå °æž¢çº½ 2 ä¸‡å…ƒå·®å¼‚ | 3 | äººå·¥ç‰ˆç”¨æ—§å€¼ 11047/589/0,源表与库内是 11049/591/2 | **软件对**,人工版需更新(已列入差异清单) |
| L åˆ—(建设阶段)未覆盖 | 3 | å¯¼å‡ºæŒ‰ 2026-09-18 å£å¾„不写 L,沿用底稿 | éœ€ã€Šå»ºè®¾é˜¶æ®µå½’一表》后放开(见第五节) |
| D åˆ—(建设性质)未覆盖 | 3 | åŒä¸Šï¼ˆI/L/G/D/E/F ä¸å†™ï¼‰ | åŒä¸Š |
| å·¥æœŸ E/F å†™æ³•(日期 vs å¹´ä»½ï¼‰ | 4 | äººå·¥ç‰ˆæ˜¯æ—¥æœŸåºåˆ—号,软件写年份/文本 | æ ¼å¼å£å¾„,建议以源表原样为准 |
| åˆ†ç»„小计行(6 ä¸ªå¸‚州) | 25 | è½¯ä»¶æŒ‰æ¸ é“求和不填=市州合计,人工版留空 | å£å¾„差异,建议人工版补齐(软件规则与武汉 R11/R16 å®Œå…¨ä¸€è‡´ï¼‰ |
| 0 ä¸Žç©ºç™½ | 11 | å†™æ³•不同 | ç»Ÿä¸€è§„则:源表空→空、源表 0→0 |
| å®¢è¿å–æ•´ + æ–‡æœ¬ | 16 | äººå·¥ç‰ˆæ‰‹å†™å–整(203/2531/43225.9);M26/M85 äººå·¥ç‰ˆç©ºã€L91 åº•稿旧值 | äººå·¥ç‰ˆå¯¹é½ï¼›L91 éœ€å½’一表 |
| æœªè¦†ç›–行(数据缺口清单) | 18 è¡Œ | ç‰©æµï¼šæ­¦æ±‰ 6;客运:黄石 1、黄冈 9、恩施 1、仙桃 1 | è¡¥æºæ–‡ä»¶/项目主档后重导(日志已逐行列出) |
## äº”、遗留问题与处置
1. **【新发现,已修+重导】潜江导入选错历史页签**:2026-08 æ›¾å¯¼å…¥ 2018 å¹´ã€Œæ½œæ±Ÿå¸‚公路项目」的数据;已修复选页签逻辑并重导 2026-08。
   - **仍遗留**:潜江 **2026-06 / 2026-07** ä¸¤ä¸ªæœˆçš„æœˆåº¦æ•°æ®ä»æ˜¯åŽ†å² sheet é”™å€¼ï¼Œéœ€ç”¨ 6/7 æœˆæºè¡¨é‡å¯¼ï¼ˆä¸è¦ç”¨ 8 æœˆæ–‡ä»¶é‡å¯¼ï¼Œä¼šæŠŠ 8 æœˆæ•°å†™æˆ 7 æœˆï¼‰ã€‚
2. **【需口径拍板】"全量覆盖"未实施**:现行口径(2026-09-18 ç¡®è®¤ï¼‰æ˜¯åªå†™ H/J/K/M,I/L/G ä¸åŠ¨ã€‚è‹¥è¦ä¸Žäººå·¥ç‰ˆå®Œå…¨ä¸€è‡´ï¼Œéœ€ç¡®è®¤ L/D/E/F/I æ˜¯å¦ä¹ŸæŒ‰å½“月写,且 L éœ€è¦ã€Šæºè¡¨ç”¨è¯â†’输出用词》归一表(源表有 ä¸»ä½“施工/竣工收尾/未开工/本年正式施工/暂停施工/前期 ç­‰ï¼Œäººå·¥ç‰ˆå†™ åœ¨å»º/完工/前期/暂停施工)。
3. **【数据缺口】黄冈客运 8 æœˆæ— æºæ–‡ä»¶**(9 è¡Œï¼‰ã€é»„石/恩施/仙桃 3 è¡Œåº“内无对应项目主档、武汉物流无单表 â€”— å¯¼å‡ºå·²èƒ½è‡ªåŠ¨å‘Šè­¦ï¼Œä½†æ•°æ®æœ¬èº«éœ€è¦ä¸šåŠ¡æä¾›ã€‚
4. **【对比工具化】** ç›®å‰"未覆盖行"只进后端日志;建议按 P1-9 çº¦å®šï¼Œåœ¨å¯¼å‡ºç»“果旁生成《校验报错_<年月>.xlsx》或前端弹窗提示(用户要求"失败要显示行号",本次已做到日志留痕,页面提示待做)。
## å…­ã€ç»“论
- æœ¬æ¬¡ä¿®å¤åŽï¼Œ**逐格差异由 156 æ ¼é™è‡³ 129 æ ¼**;三处"会影响数字"的取数缺陷(荆州项目名匹配失败、十堰同名双档取旧值、天门导入丢 H/J/K)全部闭环并逐条回溯源文件验证。
- åŒæ—¶ç«¯åˆ°ç«¯æš´éœ²å¹¶ä¿®å¤äº†ä¸€ä¸ªæ›´ä¸¥é‡çš„问题:**潜江导入选到 2018 å¹´åŽ†å²é¡µç­¾**(属"把旧年份数据当当期数据入库");未覆盖行现已在日志中逐行列出(行号+市州+项目名+原因),不再静默。
- å‰©ä½™ 129 æ ¼å·®å¼‚中,**只有武汉 27 æ ¼å±ž"缺数据"、L/D åˆ— 6 æ ¼å±ž"待放开口径"**,其余为填表习惯(分组行/0 ä¸Žç©ºç™½/取整)与人工版自身旧值,均已在《数据比对报告》中逐条标注"以谁为准"。
- å»ºè®®ä¸‹ä¸€æ­¥ï¼šâ‘  ä¸šåŠ¡ç¡®è®¤ L/D/E/F è¦†ç›–口径并出《建设阶段归一表》;② è¡¥æ­¦æ±‰ç‰©æµã€é»„冈客运源数据;③ æŒ‰ P1-9 æŠŠæœªè¦†ç›–清单输出成校验文件/页面提示。
## ä¸ƒã€æœ¬æ¬¡æ•°æ®å˜æ›´è¯´æ˜Žï¼ˆæœªåˆ é™¤ä»»ä½•项目主档)
| è¡¨ | å˜æ›´ | ä¿®å¤å‰ | ä¿®å¤åŽ |
| --- | --- | --- | --- |
| `investment_monthly`(天门市物流中心建设项目 2026-08) | é‡å¯¼ä¿®æ­£ | start_cum/year_cum/month_done = 0/0/0 | **16677.7 / 16677.7 / 500** |
| `investment_monthly`(潜江市 2026-08) | é‡å¯¼ä¿®æ­£ï¼ˆæ—§é”™è¡Œåˆ é™¤ã€æ–°è¡Œå†™å…¥ï¼‰ | æ½œæ±Ÿå¸‚公路项目 5173/5173/1440 | **中国虾谷冷链仓储物流园 11947.9/1135/0** |
- æœªåˆ é™¤ä»»ä½• `investment_project` é¡¹ç›®ä¸»æ¡£ï¼›æœªæ”¹åŠ¨å…¶ä»–å¸‚å·žã€å…¶ä»–æœŸé—´æ•°æ®ã€‚
- å¤çŽ°å‘½ä»¤ï¼š`POST /api/import/batch?type=investmentLogistics&period=2026-08`(multipart,字段名 `files`)+ `GET /api/report/export/investLogisticsDetail?period=2026-08`;均需 `Authorization: Bearer <JWT>`。
docs/¹¤×÷ÈÕ½á/¹¤×÷ÈÕ½á_2026-09-22.md
@@ -160,3 +160,21 @@
- **影响金额**:软件版物流「自开始累计」「自年初累计」各少 **34399.2 ä¸‡å…ƒ**、「本月完成」少 **6291.4 ä¸‡å…ƒ**。
- **报告**:`docs/功能测试报告/数据比对报告_投资明细表软件生成与人工版_2026-09-22.md`(含 156 æ ¼å…¨é‡æ¸…单 + é€æ¡æ ¸å¯¹ç»“论 + å¤„置建议)。
- **临时脚本(保留)**:`_tmp_diff_invest_0922.py`、`_tmp_dump_invest_full_0922.py`、`_tmp_diff_invest_annot_0922.py`、`_tmp_diff_tables_0922.py`、`_tmp_src_spotcheck_0922.py`、`_tmp_src_spotcheck2_0922.py`、`_tmp_db_probe*_0922.py`、`_tmp_tpl_*_0922.py` åŠå¯¹åº” `_*.txt` è¾“出。
## ä¸ƒã€æŠ•资明细表取数修复(第一轮,已编译/重启/端到端验证)
按第六节比对结论动手修了三处"会静默出数"的取数缺陷,并顺带端到端暴露+修复了潜江导入选错历史页签。
- **导出**(`ReportExportService.java`):
  1. `normInvestName()` å½’一化补标点(半/全角句点、顿号、分号等)→ è†å·žã€Œä¸­å›½ä¾›é”€.公安商贸物流园」可匹配,写入库内 15986/15986/8227.5;
  2. æ–°å¢ž `preferMonthlyCandidate()`:同名多档优先取「当月有数据」的档 â†’ åå °ã€Œå¿«é€’物流产业园二期」改取 id=181,写入 20377/3196/296、竣工 2029;
  3. æ–°å¢ž `logSkippedRows()`:未覆盖行逐行 WARN(`[未覆盖] ç¬¬N行 å¸‚å·ž é¡¹ç›®å â†’ åŽŸå› ` + æŒ‰å¸‚州统计),不再静默结转上期值。
- **导入**(`DataImportService.java`):
  1. `investHeaderKey()` å…ˆåŽ»å…¨éƒ¨ç©ºç™½ï¼ˆå«æ¢è¡Œ/全角空格)再判定关键词 â†’ å¤©é—¨ H/J/K ä¸‰åˆ—恢复识别,2026-08 å…¥åº“ 16677.7/16677.7/500(原为 0/0/0);
  2. æ–°å¢ž `investSheetPeriodCellHit()` + è¯„分加权 2000000:按表内报表期(`2026.08.24`)选 sheet,优先于「sheet åå«åŒæœˆã€â†’ æ½œæ±Ÿä¸å†æŠŠ **2018 å¹´**「潜江(8月)」当 2026-08,重导后 2026-08 å…¥åº“「中国虾谷冷链仓储物流园」11947.9/1135/0。
- **验证**:`mvn -o compile` é€šè¿‡ï¼›åœæ—§èµ·æ–°åŽç«¯ï¼ˆ8090 å°±ç»ªï¼‰ï¼›æŽ¥å£é‡å¯¼ + é‡è·‘ 2026-08 å¯¼å‡ºï¼›é€æ ¼æ¯”对**156 æ ¼ â†’ 129 æ ¼**(降 27 æ ¼ï¼šç‰©æµåˆ†é¡¹ç›® 108→91、分市州 15→11、市州汇总 17→11),无新增差异。
- **未覆盖清单(新增能力,来自日志)**:物流 6 è¡Œï¼ˆå…¨éƒ¨æ­¦æ±‰ï¼Œåº“内无当月数据);客运 12 è¡Œï¼ˆé»„石 1「阳新综合客运枢纽站」、黄冈 9、恩施 1「咸丰县坪坝营旅游客运站」、仙桃 1「仙桃市毛嘴客运站」)。
- **报告**:`docs/功能测试报告/功能测试报告_投资明细表取数修复_2026-09-22.md`。
- **遗留(需拍板/补数)**:① L/D/E/F æ˜¯å¦æŒ‰å½“月覆盖(需《建设阶段归一表》);② æ­¦æ±‰ç‰©æµã€é»„冈客运 8 æœˆæºæ•°æ®ç¼ºå¤±ï¼›â‘¢ æ½œæ±Ÿ 2026-06/07 æœˆåº¦ä»æ˜¯åŽ†å² sheet é”™å€¼ï¼Œéœ€ç”¨ 6/7 æœˆæºè¡¨é‡å¯¼ï¼ˆ**不要**用 8 æœˆæ–‡ä»¶é‡å¯¼ï¼‰ï¼›â‘£ æœªè¦†ç›–清单目前只进日志,建议按 P1-9 å‡ºã€Šæ ¡éªŒæŠ¥é”™_<年月>.xlsx》或页面提示。
- **数据变更**:仅重导修正了天门、潜江两处 2026-08 æœˆåº¦å€¼ï¼Œæœªåˆ é™¤ä»»ä½•项目主档、未动其他市州/期间;临时脚本与产物保留(`_tmp_*_0922.*`、`_tmp/gen_new_0922/`)。
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -2442,6 +2442,41 @@
            || normDash.contains(period) || normDash.contains(period.replace("-0", "-"));
    }
    /**
     * æŠ¥è¡¨æœŸå•元格命中:扫描 sheet å‰ 8 è¡Œï¼Œæ‰¾å½¢å¦‚ 2026.08 / 2026å¹´8月 / 2026-08-24 çš„æŠ¥è¡¨æœŸæ–‡å­—或日期单元格。
     * èƒŒæ™¯ï¼ˆ2026-09-22):潜江单表含 20 ä¸ªåŽ†å² sheet(潜江(1月)…潜江(8月)…),旧逻辑只按「8月」匹配 sheet åï¼Œ
     * ä¼šæŠŠ 2018 å¹´çš„「潜江(8月)」当成 2026-08,导入成 2018 å¹´æ•°æ®ï¼›å½“期 sheet åæ˜¯ã€Œæ½œæ±Ÿï¼ˆ2022.3)」,
     * åªèƒ½é è¡¨å†…报表期(2026.08.24)识别。
     */
    private boolean investSheetPeriodCellHit(Sheet sh, String period) {
        if (sh == null || period == null || !period.matches("\\d{4}-\\d{2}")) return false;
        String year = period.substring(0, 4);
        int month = Integer.parseInt(period.substring(5, 7));
        int last = Math.min(sh.getLastRowNum(), 7);
        for (int r = 0; r <= last; r++) {
            Row row = sh.getRow(r);
            if (row == null) continue;
            int lastCell = Math.max(1, row.getLastCellNum());
            for (int c = 0; c < Math.min(lastCell, 12); c++) {
                Cell cell = row.getCell(c);
                if (cell == null) continue;
                if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {
                    java.time.LocalDate d = cell.getLocalDateTimeCellValue().toLocalDate();
                    if (d.getYear() == Integer.parseInt(year) && d.getMonthValue() == month) return true;
                    continue;
                }
                String v = cellStr(row, c);
                if (v == null) continue;
                String t = v.trim().replace(" ", "");
                if (t.isEmpty() || t.length() > 30) continue;
                java.util.regex.Matcher mm = java.util.regex.Pattern
                        .compile("(\\d{4})\\s*[å¹´.\\-/]\\s*(\\d{1,2})").matcher(t);
                if (mm.find() && year.equals(mm.group(1)) && month == Integer.parseInt(mm.group(2))) return true;
            }
        }
        return false;
    }
    /** è§£æžå•个市州报表:选 sheet(报表期名优先 + æ•°æ®è¡Œæ•°ï¼‰ï¼ŒæŒ‰åˆ—名映射提取项目行 */
    private void parseInvestCityWorkbook(Workbook wb, String fileName, String category, String period,
                                         Set<String> fiveYearNames, List<InvestCityRow> rows, Map<String, Object> fr) {
@@ -2453,7 +2488,10 @@
            Map<String, Integer> col = findInvestHeader(sh);
            if (col == null) continue;
            int cnt = countInvestRows(sh, col);
            int score = cnt + (cnt > 0 && investSheetMatchesPeriod(sh, period) ? 1000000 : 0);
            // 2026-09-22:表内报表期(单元格)命中权重最高——历史同月 sheet(如潜江(8月))不能盖过当期 sheet
            boolean cellHit = cnt > 0 && investSheetPeriodCellHit(sh, period);
            int score = cnt + (cnt > 0 && investSheetMatchesPeriod(sh, period) ? 1000000 : 0)
                    + (cellHit ? 2000000 : 0);
            if (cnt > 0 && investSheetMatchesPeriod(sh, period)) {
                // å½“期真实报表 sheet åé€šå¸¸å¸¦å¹´ä»½ï¼ˆå¦‚ 2026.8/2026å¹´8月),历史sheet多为纯月份名(8月):带年份的优先
                String norm = sh.getSheetName() == null ? "" : sh.getSheetName().replace(" ", "");
@@ -2618,25 +2656,28 @@
    /** å•个表头单元格 -> å­—段键(null è¡¨ç¤ºä¸æ˜¯è¡¨å¤´å­—段);供表头行判定与列映射共用 */
    private String investHeaderKey(String v) {
        if (v == null) return null;
        String flat = v.replace(" ", "");
        if (v.contains("项目名称")) return "name";
        // 2026-09-22:表头单元格常带换行/全角空格(如天门「自开始建\n设累计完\n成投资\n(万元)」),
        // ç»Ÿä¸€åŽ»æŽ‰å…¨éƒ¨ç©ºç™½åŽå†åˆ¤å®šå…³é”®è¯ï¼›å¦åˆ™ H(自开始建设累计)/J(自年初累计)/K(本月完成) ä¼šåŒ¹é…å¤±è´¥ï¼Œ
        // åˆ—映射落到 -1 â†’ ä¸‰ä¸ªæ•°å€¼è¢«å†™æˆ 0(天门 2026-08 å°±æ˜¯è¿™æ ·ä¸¢çš„ 16677.7/16677.7/500)。
        String flat = v.replaceAll("[\\s\\u3000]+", "");
        if (flat.contains("项目名称")) return "name";
        // ã€ŒåŽ¿ï¼ˆå¸‚ã€åŒºï¼‰ã€çš„å„ç§å†™æ³•ï¼šåŠ/全角括号、顿号/逗号、有无括号,统一归一化后再判定
        String flatCounty = flat.replace("(", "(").replace(")", ")").replace("、", "").replace(",", "").replace(",", "");
        if (flatCounty.contains("县(市区)") || flatCounty.contains("县市区") || flatCounty.contains("区县")) return "county";
        if (v.contains("投资建设") || v.contains("建设单位") || v.contains("项目业主")) return "builder";
        if (v.contains("建设性质")) return "nature";
        if (v.contains("开工")) return "start";
        if (v.contains("竣工") || v.contains("建成")) return "end";
        if ((v.contains("计划总投资") || v.contains("总投资")) && !v.contains("累计")) return "total";
        if (v.contains("自开始建设")) return "startcum";
        if (v.contains("本年计划")) return "yearplan";
        if (v.contains("自年初累计")) return "yearcum";
        if (v.contains("本月完成")) return "monthdone";
        if (v.contains("建设阶段")) return "stage";
        if (v.contains("形象进度") || v.contains("建设进展")) return "desc";
        if (v.contains("工可") && !v.contains("项目名称")) return "gk";
        if (v.contains("初设") && !v.contains("项目名称")) return "cs";
        if (v.contains("建筑面积") && !v.contains("新增")) return "area";
        if (flat.contains("投资建设") || flat.contains("建设单位") || flat.contains("项目业主")) return "builder";
        if (flat.contains("建设性质")) return "nature";
        if (flat.contains("开工")) return "start";
        if (flat.contains("竣工") || flat.contains("建成")) return "end";
        if ((flat.contains("计划总投资") || flat.contains("总投资")) && !flat.contains("累计")) return "total";
        if (flat.contains("自开始建设")) return "startcum";
        if (flat.contains("本年计划")) return "yearplan";
        if (flat.contains("自年初累计")) return "yearcum";
        if (flat.contains("本月完成")) return "monthdone";
        if (flat.contains("建设阶段")) return "stage";
        if (flat.contains("形象进度") || flat.contains("建设进展")) return "desc";
        if (flat.contains("工可") && !flat.contains("项目名称")) return "gk";
        if (flat.contains("初设") && !flat.contains("项目名称")) return "cs";
        if (flat.contains("建筑面积") && !flat.contains("新增")) return "area";
        return null;
    }
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -5781,6 +5781,8 @@
            String curCity = null;
            Set<Long> used = new HashSet<>();
            int filled = 0, kept = 0, dup = 0;
            List<String> skipped = new ArrayList<>();
            Map<String, Integer> skippedByCity = new LinkedHashMap<>();
            for (int r = 8; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
@@ -5792,11 +5794,16 @@
                if (!a.matches("\\d+(\\.\\d+)?")) continue;
                if (b.isEmpty() || curCity == null) continue;
                InvestmentProject p = matchInvest(projects, b, curCity, "客运站场", used);
                p = preferMonthlyCandidate(projects, p, mByProject, "客运站场", used);
                InvestmentMonthly m = p == null ? null : mByProject.get(p.getId());
                if (p == null || m == null) {
                    kept++;
                    // æœ‰åº“内项目但当月无数据 â†’ è®°ä¸ºå¾…核对;无匹配项目 â†’ ç»“转上月
                    if (p != null) dup++;
                    // 2026-09-22:不再静默结转上月,未覆盖的行必须留痕(行号+市州+项目名+原因)
                    skipped.add("第" + (r + 1) + "行 " + curCity + " " + b
                            + (p == null ? " â†’ åº“内无匹配项目" : " â†’ åº“内无 " + period + " æœˆåº¦æ•°æ®"));
                    skippedByCity.merge(curCity, 1, Integer::sum);
                    continue;
                }
                used.add(p.getId());
@@ -5826,6 +5833,7 @@
            }
            log.info("客运站投资明细表 {}:写入 {} è¡Œï¼Œç»“转母版原值 {} è¡Œï¼ˆå…¶ä¸­åº“内有项目但当月无数据 {} è¡Œï¼‰",
                    period, filled, kept, dup);
            logSkippedRows("客运站投资明细表", period, skipped, skippedByCity);
            return toBytes(wb);
        }
    }
@@ -5866,6 +5874,8 @@
        Map<String, double[]> cityStats = new LinkedHashMap<>();  // å…¨å -> {总数, ç´¯è®¡, è§„划内数, è§„划内累计, è§„划外数, è§„划外累计}
        Set<Long> used = new HashSet<>();
        int filled = 0, kept = 0;
        List<String> skipped = new ArrayList<>();
        Map<String, Integer> skippedByCity = new LinkedHashMap<>();
        for (int i = 0; i < cityRows.size(); i++) {
            int cr = cityRows.get(i);
            String cityLabel = nvl(cellText(sheet.getRow(cr).getCell(0)));
@@ -5888,8 +5898,16 @@
                if (!a.matches("\\d+(\\.\\d+)?")) continue;
                if (b.isEmpty()) continue;
                InvestmentProject p = matchInvest(projects, b, city, "物流园区", used);
                p = preferMonthlyCandidate(projects, p, mByProject, "物流园区", used);
                InvestmentMonthly m = p == null ? null : mByProject.get(p.getId());
                if (p == null || m == null) { kept++; continue; }
                if (p == null || m == null) {
                    kept++;
                    // 2026-09-22:不再静默结转上月,未覆盖的行必须留痕
                    skipped.add("第" + (r + 1) + "行 " + city + " " + b
                            + (p == null ? " â†’ åº“内无匹配项目" : " â†’ åº“内无 " + period + " æœˆåº¦æ•°æ®"));
                    skippedByCity.merge(city, 1, Integer::sum);
                    continue;
                }
                used.add(p.getId());
                // å£å¾„同客运站明细表:只覆盖 H è‡ªå¼€å§‹å»ºè®¾ç´¯è®¡ / J è‡ªå¹´åˆç´¯è®¡ / K æœ¬æœˆå®Œæˆ / M å½¢è±¡è¿›åº¦
                if (m.getStartCum() != null) putNum(row, 7, m.getStartCum());
@@ -6020,6 +6038,7 @@
            log.warn("物流站场投资明细公式求值失败: {}", e.getMessage());
        }
        log.info("物流站场投资明细 {}:写入 {} è¡Œï¼Œç»“转母版原值 {} è¡Œ", period, filled, kept);
        logSkippedRows("物流站场投资明细", period, skipped, skippedByCity);
        return toBytesHssf(wb);
    }
@@ -6314,6 +6333,40 @@
        return full != null && RegionUtil.cityList().contains(full);
    }
    /**
     * åŒåï¼ˆå½’一化后一致)多档时,优先选「当月有数据」的那一档。
     * èƒŒæ™¯ï¼ˆ2026-09-22):十堰「快递物流产业园二期」在库内有两档 id=154(竣工 2026,月度只到 7 æœˆï¼‰ä¸Ž
     * id=181(竣工 2029,有 8 æœˆï¼‰ï¼Œæ¨¡æ¿è¡Œåå‰ç¼€ä¸Ž 154 ä¸€è‡´ â†’ å¯¼å‡ºå–到 154 çš„ 7 æœˆå€¼ã€‚
     */
    private InvestmentProject preferMonthlyCandidate(List<InvestmentProject> pool, InvestmentProject p,
                                                    Map<Long, InvestmentMonthly> mByProject,
                                                    String category, Set<Long> excludeIds) {
        if (p == null || mByProject.containsKey(p.getId())) return p;
        String norm = normInvestName(p.getProjectName());
        if (norm.isEmpty()) return p;
        InvestmentProject better = null;
        for (InvestmentProject c : pool) {
            if (c == null || c.getId().equals(p.getId())) continue;
            if (excludeIds != null && excludeIds.contains(c.getId())) continue;
            if (category != null && !category.equals(c.getCategory())) continue;
            if (!mByProject.containsKey(c.getId())) continue;
            if (!norm.equals(normInvestName(c.getProjectName()))) continue;
            if (better == null || c.getId() > better.getId()) better = c;
        }
        return better != null ? better : p;
    }
    /** æœªæŒ‰å½“月数据覆盖的行必须留痕:逐行列出行号+市州+项目名,避免「静默结转上期值」再次发生 */
    private void logSkippedRows(String reportName, String period, List<String> skipped, Map<String, Integer> byCity) {
        if (skipped == null || skipped.isEmpty()) return;
        log.warn("{} {}:{} è¡ŒæœªæŒ‰å½“月数据覆盖(结转母版原值),需人工核对源文件/库内数据:",
                reportName, period, skipped.size());
        int limit = Math.min(skipped.size(), 50);
        for (int i = 0; i < limit; i++) log.warn("  [未覆盖] {}", skipped.get(i));
        if (skipped.size() > limit) log.warn("  â€¦å¦æœ‰ {} è¡Œæœªé€è¡Œåˆ—出", skipped.size() - limit);
        if (byCity != null && !byCity.isEmpty()) log.warn("  æœªè¦†ç›–行按市州统计:{}", byCity);
    }
    /** åœ¨å€™é€‰é¡¹ç›®æ± ä¸­æŒ‰åç§°åŒ¹é… DB é¡¹ç›®ï¼ˆå¯é€‰ å¸‚å·ž/类别 è¿‡æ»¤ + åŽ¿åŒºå‰ç¼€æ¶ˆæ­§ï¼‰ï¼Œæœªå‘½ä¸­è¿”å›ž null */
    private InvestmentProject matchInvest(List<InvestmentProject> pool, String name, String cityFull, String category) {
        return matchInvest(pool, name, cityFull, category, null);
@@ -6362,7 +6415,8 @@
        if (name == null) return "";
        String s = name.trim();
        s = s.replaceAll("[((]", "").replaceAll("[))]", "");
        s = s.replaceAll("[·•—-_\\-]", "");
        // 2026-09-22:补充半/全角句点、顿号、分号等(库内「中国供销.公安商贸物流园」因半角句点匹配不上模板行)
        s = s.replaceAll("[·•—-_\\-..。、,,;;::]", "");
        s = s.replaceAll("[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ]", "I");
        s = s.replaceAll("^(湖北省|武汉市|黄石市|十堰市|宜昌市|襄阳市|鄂州市|荆门市|孝感市|荆州市|黄冈市|咸宁市|随州市|恩施州|仙桃市|潜江市|天门市|神农架林区)", "");
        s = s.replaceAll("^(?:[\\u4e00-\\u9fa5]{2,4}(?:县|市|区))", "");