zhizhijie
2026-09-20 a08c70db67e49bf6cd125df04ebaf68c26f0d8dd
feat(投资): 市州月报统一填报模板 + 县(市、区)列解析 + 表头行判定收紧

- 新增两份市州填报模板(客运站场/物流园区): 月报页17列单行表头 + 建设性质下拉 + 填报说明11条

- 解析支持「县(市、区)」列写入主档 county(半/全角括号、顿号逗号归一化匹配)

- 表头行判定要求「含项目名称 + 至少2个短表头字段(<=20字)」, 修复模板自带填报说明页被误当数据表(8条误建档)

- 回归: 8月真实市州重导 客运39/物流128 与历史一致; 2026-08 月度数136行逐值一致

- 文档: 填报口径与统一模板_2026-09-20.md + 功能测试报告 + 工作日结1.8
2个文件已修改
4个文件已添加
218 ■■■■■ 已修改文件
docs/功能测试报告/功能测试报告_市州统一填报模板与县市区解析_2026-09-20.md 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工作日结/工作日结_2026-09-20.md 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/填报口径与统一模板_2026-09-20.md 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/模板/模板_市州月报_客运站场.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/模板/模板_市州月报_物流园区.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_ÊÐÖÝͳһÌģ°åÓëÏØÊÐÇø½âÎö_2026-09-20.md
New file
@@ -0,0 +1,50 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_市州统一填报模板与县(市、区)解析_2026-09-20
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
| åŠŸèƒ½ | è¯´æ˜Ž |
| --- | --- |
| å¸‚州填报模板 | æ–°å¢žä¸¤ä»½å¸‚州月报模板:`docs/投资/模板/模板_市州月报_客运站场.xlsx`、`docs/投资/模板/模板_市州月报_物流园区.xlsx`;单页签「月报」17 åˆ—单行表头 + ã€Œå¡«æŠ¥è¯´æ˜Žã€é¡µ |
| åŽ¿ï¼ˆå¸‚ã€åŒºï¼‰åˆ—è§£æž | å¸‚州单表导入新增对「县(市、区)」列的识别,写入项目主档 `county`(原先仅投资系统导出会写) |
| è¡¨å¤´è¡Œåˆ¤å®šæ”¶ç´§ | `findInvestHeader` å¢žåŠ ç»Ÿä¸€çš„å…³é”®è¯åˆ¤å®š `investHeaderKey`,并要求表头行同时满足「含项目名称」+「至少 2 ä¸ªçŸ­è¡¨å¤´å­—段(≤20 å­—)」,避免把「填报说明」页的长句当作数据表 |
改动文件:`traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java`
## äºŒã€æµ‹è¯•环境
- åŽç«¯ Spring Boot 8090(重启后启动时间 2026-09-20 16:06:48),前端 8080,MySQL 3308 / åº“ `traffic_audit`
- æµ‹è¯•数据:以「模板_市州月报_客运站场.xlsx」为底稿生成 `武汉市_2026-09_客运站场月报.xlsx`(2 æ¡æµ‹è¯•项目)
- è°ƒç”¨æ–¹å¼ï¼š`POST /api/import/investCityBatch?period=2026-09&category=客运站场`(本地自签 JWT)
- å…¥åº“抽查:`investment_project` / `investment_monthly` é€å­—段与源文件比对
## ä¸‰ã€ç”¨ä¾‹è¡¨
| # | ç”¨ä¾‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
| --- | --- | --- | --- | --- | --- |
| T01 | æ¨¡æ¿ç»“构(客运站场) | æ‰“开模板 | ã€ŒæœˆæŠ¥ã€é¡µç¬¬ 4 è¡Œ 17 åˆ—表头;E åˆ—建设性质下拉;第二个页签为填报说明且含 11 æ¡è¦æ±‚ | ä¸Žé¢„期一致 | é€šè¿‡ |
| T02 | æ¨¡æ¿ç»“构(物流园区) | æ‰“开模板 | åŒ T01,标题为「交通物流(物流园区)」 | ä¸Žé¢„期一致 | é€šè¿‡ |
| T03 | æ­£å‘导入(2 æ¡é¡¹ç›®ï¼‰ | æŒ‰æ¨¡æ¿å¡«æŠ¥ 2 æ¡é¡¹ç›®ï¼ˆå«åŽ¿åŒºã€æ‰¹æ–‡å·ï¼‰ | total=2、success=2、city=武汉市 | total=2、success=2、fail=0、city=武汉市 | é€šè¿‡ |
| T04 | å…¥åº“抽查-主档 | åŒä¸Š | 8 ä¸ªä¸»æ¡£å­—段逐项等于源值 | æ±Ÿå²¸åŒº/测试建设单位A/新建/2026/2027/12345.67/测试工可A/测试初设A;洪山区/测试建设单位B/续建/2024/2026/20000/测试工可B/初设为空 | é€šè¿‡ |
| T05 | å…¥åº“抽查-月度数 | åŒä¸Š | 6 ä¸ªæœˆåº¦å­—段逐项等于源值、报表期正确 | æŠ¥å‘ŠæœŸ 2026-09;1000/5000/1200/300/在建/测试形象进度A;15000/8000/6000/1500/完工/测试形象进度B | é€šè¿‡ |
| T06 | è´Ÿå‘:说明页被当数据表(修复前) | æ¨¡æ¿æ–‡ä»¶ï¼ˆå«å¡«æŠ¥è¯´æ˜Žé¡µï¼‰ | åªè§£æžã€ŒæœˆæŠ¥ã€é¡µ | **失败**:误解析 8 æ¡è¯´æ˜Žé¡µé•¿å¥ä¸ºé¡¹ç›®ï¼ˆid 233~240),city è¢«æ–‡ä»¶åå…œåº•为「武汉市」 | å·²ä¿®å¤ï¼ˆè§ T07) |
| T07 | è´Ÿå‘:说明页被当数据表(修复后) | åŒ T06 | è¯´æ˜Žé¡µä¸å†è¢«è¯†åˆ«ä¸ºæ•°æ®è¡¨ | åŒä¸€æ–‡ä»¶ç”± 8 æ¡é™ä¸º 2 æ¡æ­£ç¡®è®°å½•,说明页 0 æ¡ | é€šè¿‡ |
| T08 | è´Ÿå‘:县区列写法变体 | è¡¨å¤´ã€ŒåŽ¿(市、区)」「县(市、区)」「县市区」 | å‡è¯†åˆ«ä¸ºåŽ¿åŒºåˆ— | å½’一化匹配后三种写法均命中(修复前写成「县(市,区)」带逗号,均不命中) | é€šè¿‡ |
| T09 | å›žå½’:8 æœˆçœŸå®žå¸‚州文件重导 | 8 æœˆå®¢è¿ 9 ä»½ã€ç‰©æµ 16 ä»½åŽŸå§‹æœˆæŠ¥ | è§£æžæ¡æ•°ä¸ŽåŽ†å²æ‰¹æ¬¡ä¸€è‡´ã€æ— æŠ¥é”™ | å®¢è¿ 39 æ¡ã€ç‰©æµ 128 æ¡ï¼Œå‡ fail=0(与 09-19 æ‰¹æ¬¡ 531/532 ä¸€è‡´ï¼‰ | é€šè¿‡ |
| T10 | å›žå½’:8 æœˆæœˆåº¦æ•°é€å€¼æ¯”对 | é‡å¯¼å‰ 136 è¡Œ vs é‡å¯¼åŽ 136 è¡Œ | é€å€¼ä¸€è‡´ | åŽ»ä¸»é”®åŽé€è¡Œæ¯”å¯¹ï¼š**IDENTICAL** | é€šè¿‡ |
## å››ã€å‘现的问题与处置
| # | é—®é¢˜ | å¤„ç½® |
| --- | --- | --- |
| P1 | ã€Œå¡«æŠ¥è¯´æ˜Žã€é¡µé‡Œå«ã€Œé¡¹ç›®åç§°ã€çš„长句被当作表头行,整页 8 æ¡è¢«è¯¯å»ºæˆé¡¹ç›® | å·²ä¿®ï¼šè¡¨å¤´è¡Œåˆ¤å®šè¦æ±‚「含项目名称」+「至少 2 ä¸ªçŸ­è¡¨å¤´å­—段(≤20 å­—)」;测试数据已从库中清除 |
| P2 | åŽ¿åŒºåˆ—å…³é”®è¯å†™æˆ `县(市,区)`(逗号),实际表头是 `县(市、区)`(顿号),导致 county å–不到 | å·²ä¿®ï¼šå…ˆå½’一化(全/半角括号、顿号、逗号全部去掉)再匹配「县(市区)」「县市区」「区县」 |
| P3 | **回归重导 8 æœˆç‰©æµæ—¶ï¼Œã€Šæ¹–北省十堰交通物流基础设施投资统计汇总表(月报)》再次带出非十堰的武汉 27 + é»„石 4 = 31 ä¸ªé¡¹ç›®** | å·²å†æ¬¡æ¸…除(`investment_project` 152 / `investment_monthly` 284 æ¢å¤åŽŸçŠ¶ï¼‰ï¼›æ ¹æ²»éœ€æ¸…ç† 8 æœˆæºæ–‡ä»¶ï¼Œ**待用户确认是否由我代做** |
| P4 | çœŸå®žå¸‚州月报表头里**没有**「县(市、区)」列(抽查 8 æœˆ 25 ä»½æ–‡ä»¶ï¼Œ0 ä»½å«è¯¥åˆ—) | å±žæ­£å¸¸ï¼šåŽ†å²æ•°æ® county ä¸ºç©ºæ˜¯æ•°æ®æºé™åˆ¶ï¼›9 æœˆèµ·æŒ‰ç»Ÿä¸€æ¨¡æ¿å¡«æŠ¥åŽè¯¥åˆ—才有值 |
| P5 | æµ‹è¯•用的临时文件与临时数据 | æµ‹è¯•文件 `docs/投资/输入/客运(客运站)/9月/武汉市_2026-09_客运站场月报.xlsx`、测试项目 A/B、2026-09 å¯¼å…¥æ‰¹æ¬¡å·²å…¨éƒ¨åˆ é™¤ |
## äº”、结论
- ä¸¤ä»½å¸‚州填报模板可用,正向链路(模板 â†’ è§£æž â†’ ä¸»æ¡£ + æœˆåº¦æ•°ï¼‰é€å­—段对齐;
- è¡¨å¤´åˆ¤å®šæ”¶ç´§åŽï¼Œæ¨¡æ¿è‡ªå¸¦çš„「填报说明」页不再被误当数据表(负向用例 T06/T07);
- 8 æœˆçœŸå®žå¸‚州数据重导回归无反例:条数与历史一致,月度数逐值一致;
- é—留:8 æœˆåå °æ±‡æ€»è¡¨æºæ–‡ä»¶çš„非十堰段未清理(P3),需用户确认清理方式。
docs/¹¤×÷ÈÕ½á/¹¤×÷ÈÕ½á_2026-09-20.md
@@ -82,6 +82,19 @@
- **同时修掉一个严重回归**:本次新增的 `computed` ä¸Žæ–‡ä»¶ä¸­å·²æœ‰ `computed` **同名重复**,JS å¯¹è±¡åŽè€…覆盖前者 â†’ `username/myModules/visibleModules` å…¨éƒ¨ä¸¢å¤±ï¼Œ
  ã€ŒæŠ¥è¡¨ç”Ÿæˆã€é¡µ `created()` ç›´æŽ¥æŠ›é”™ã€**整页白屏**。接口测试发现不了,是浏览器实测才暴露的(详见测试报告第四节)。
### 1.8 å¸‚州统一填报模板(用户提问「按哪个格式来最好?要不要生成两个模板」)
- **背景**:用户提出「这几个表的更新其实很简单——按项目名称更新汇总表指标、新增项目就新增到明细表;
  é¢„安排汇总表和明细表方式一样,剩下三张表项目固定只更新指标——为什么现在弄得有点复杂」。
- **核对**:需求文档《投资月报数据处理与多表更新需求说明》Step1/2/3 ä¸Žç”¨æˆ·æè¿°å®Œå…¨ä¸€è‡´ï¼Œç¨‹åºé€»è¾‘也是这么实现的。
- **结论**:复杂不在「表」,在「源」——版式不统一(27 ä»½é‡Œ 7 ä»½æœ‰å·®å¼‚)、一个文件混多个市州、没有县区列、列位置靠兜底。
- **产出**:
  1. æ–°å¢žä¸¤ä»½å¸‚州填报模板 `模板_市州月报_客运站场.xlsx` / `模板_市州月报_物流园区.xlsx`
     ï¼ˆã€ŒæœˆæŠ¥ã€é¡µ 17 åˆ—单行表头 + å»ºè®¾æ€§è´¨ä¸‹æ‹‰ + ã€Œå¡«æŠ¥è¯´æ˜Žã€é¡µ 11 æ¡è¦æ±‚);
  2. å£å¾„说明文档 `docs/投资/填报口径与统一模板_2026-09-20.md`(模板字段去向、复杂度来源、待定死的 3 ä»¶äº‹ã€è¿‡æ¸¡å®‰æŽ’)。
- **配套解析改动**:新增「县(市、区)」列解析(写入主档 `county`);表头行判定收紧为
  ã€Œå«é¡¹ç›®åç§° + è‡³å°‘ 2 ä¸ªçŸ­è¡¨å¤´å­—段」,避免把模板自带的「填报说明」页当数据表。
## äºŒã€æµ‹è¯•结果摘要
《功能测试报告_投资明细表就绪与预览类型补齐_2026-09-20.md》:**7 æ¡ç”¨ä¾‹é€šè¿‡ï¼Œ1 æ¡æœªå®žæµ‹**(缺母版提示,未临时移动用户母版文件)。
@@ -139,6 +152,20 @@
| é™„带修复 | `computed` é‡å¤é”®å¯¼è‡´æŠ¥è¡¨ç”Ÿæˆé¡µæ•´é¡µç™½å±ï¼ˆå·²ä¿®å¤å¹¶æµè§ˆå™¨å¤éªŒï¼‰ |
| å¤éªŒï¼ˆç”¨æˆ·é—®é¢˜ 2) | é¢„览同比 12 è¡Œé½å…¨ï¼›ã€Œç´¯è®¡è´§ç‰©å‘¨è½¬é‡ 0%」经查库为取数源本身 `total_yoy=0.00000346`,非缺陷 |
《功能测试报告_市州统一填报模板与县市区解析_2026-09-20.md》:**10 æ¡ç”¨ä¾‹é€šè¿‡ï¼ˆæ­£å‘ 5 + è´Ÿå‘ 3 + å›žå½’ 2)**。
| æ ¡éªŒé¡¹ | ç»“æžœ |
| --- | --- |
| æ¨¡æ¿ç»“æž„ | ä¸¤ä»½æ¨¡æ¿å‡ä¸ºã€ŒæœˆæŠ¥ã€17 åˆ—表头 + å»ºè®¾æ€§è´¨ä¸‹æ‹‰ + å¡«æŠ¥è¯´æ˜Ž 11 æ¡ |
| æ­£å‘导入(模板填报) | total=2、success=2、city=武汉市 |
| ä¸»æ¡£é€å­—段 | åŽ¿åŒºï¼ˆæ±Ÿå²¸åŒº/洪山区)、建设单位、性质、开工/竣工年、总投资、工可/初设全部对齐 |
| æœˆåº¦æ•°é€å­—段 | è‡ªå¼€å§‹ç´¯è®¡/本年计划/自年初累计/本月完成/建设阶段/形象进度全部对齐,报表期 2026-09 |
| è´Ÿå‘:说明页被当数据表 | ä¿®å¤å‰ 8 æ¡è¯¯è§£æžï¼ˆid 233~240)→ ä¿®å¤åŽ 0 æ¡ |
| è´Ÿå‘:县区列写法变体 | ã€ŒåŽ¿ï¼ˆå¸‚ã€åŒºï¼‰/县(市、区)/县市区」归一化后均命中 |
| å›žå½’:8 æœˆçœŸå®žå¸‚州重导 | å®¢è¿ 39、物流 128,与 09-19 æ‰¹æ¬¡ä¸€è‡´ï¼Œfail=0 |
| å›žå½’:8 æœˆæœˆåº¦æ•°æ¯”对 | é‡å¯¼å‰åŽ 136 è¡Œé€å€¼ **IDENTICAL** |
| æ•°æ®æ¸…理 | æµ‹è¯•项目 A/B、2026-09 æ‰¹æ¬¡ã€æµ‹è¯•文件均已删除 |
## ä¸‰ã€é—留问题与待办
1. æ­¦æ±‰ç‰©æµé¡¹ç›®æ¸…单口径(已确认忽略)。
@@ -161,5 +188,8 @@
9. **8 æœˆç‰ˆåå °æ±‡æ€»è¡¨æœªæ¸…理**:用户只清了 7 æœˆç‰ˆï¼ˆ108032→90112 å­—节),8 æœˆç‰ˆï¼ˆmtime 2026-08-20)仍含武汉 27 + é»„石 4 æ®µï¼›
   è‹¥é‡å¯¼ 8 æœˆç‰©æµï¼Œå·²åˆ é™¤çš„ 31 ä¸ªé¡¹ç›®ä¼šé‡æ–°å»ºæ¡£ â†’ é‡å¯¼å‰éœ€åŒæ ·æ¸…理(或由我代做,待用户确认)。
    **2026-09-20 ä¸‹åˆåšå›žå½’重导时已实测复现**(8 æœˆç‰©æµé‡å¯¼åŽ +31 ä¸ªé¡¹ç›® / +31 æ¡æœˆåº¦æ•°ï¼‰ï¼Œå·²å†æ¬¡æ¸…除,库回到 152 ä¸ªé¡¹ç›® / 284 æ¡æœˆåº¦æ•°ï¼Œ2026-08 æœˆåº¦æ•°é€å€¼ä¸Žé‡å¯¼å‰ä¸€è‡´ã€‚
10. å¸‚州单表导入的**多市州块兜底提示**(识别一份文件含多个市州块并提示/跳过)待用户确认是否要做。
11. ç”Ÿæˆè¡¨æ­¦æ±‰ç‰©æµå°è®¡çŽ°åœ¨åªæœ‰ã€Œæ€»æŠ•èµ„ã€ï¼Œç¼º 8 æœˆçš„自开始累计/本年计划/月完成 â†’ éœ€æ­¦æ±‰ 8 æœˆç‰©æµå•表(或省厅口径)才能补齐。
12. ä¸¤ä»½å¸‚州填报模板待用户过目后下发市州(建议 9 æœˆèµ·æ‰§è¡Œï¼‰ï¼›å¦‚需「模板体检 + è¡¨å¤´ä¸ç¬¦å¼¹çª—」,待用户确认后排期。
13. 8 æœˆåå °ç‰©æµæ±‡æ€»è¡¨æºæ–‡ä»¶ï¼ˆå«æ­¦æ±‰ 27 + é»„石 4)是否由我代做清理,待用户确认(见 1.8 / æµ‹è¯•报告 P3)。
docs/Ͷ×Ê/Ì¿Ú¾¶Óëͳһģ°å_2026-09-20.md
New file
@@ -0,0 +1,78 @@
# å¸‚州月报填报口径与统一模板(2026-09-20)
> èµ·å› ï¼šç”¨æˆ·æé—®ã€Œä»–们导入的数据按哪个格式来最好?要不要生成两个模板(客运站一个、物流园一个)?
> è¿™å‡ ä¸ªè¡¨çš„æ›´æ–°å…¶å®žå¾ˆç®€å•——按项目名称更新汇总表指定指标、新增项目就新增到明细表,
> é¢„安排汇总表和明细表方式一样,剩下三张表项目固定只更新指标——为什么现在弄得有点复杂了?」
## ä¸€ã€ç»“论先说
1. **你的心智模型是对的**,而且就是需求文档《投资月报数据处理与多表更新需求说明》里写的三步:
   - Step1 æœˆæŠ¥ â†’ ä¸¤å¼ æ˜Žç»†è¡¨ï¼šä»¥**项目名称**为键,已有→更新 5 åˆ—,没有→新增整行;
   - Step2 æ˜Žç»†è¡¨ â†’ å…¨çœé¢„安排计划进度情况汇总:同名更新 4 é¡¹ï¼Œæ²¡æœ‰çš„æ–°å¢žï¼Œæ¯ç‰ˆæœ‰è€ŒæœˆæŠ¥æ²¡æœ‰çš„保持原值;
   - Step3 æ˜Žç»†è¡¨ â†’ ä¸‰å¼ å›ºå®šé¡¹ç›®è¡¨ï¼ˆåäº”五规划物流 / äº¿å…ƒæŠ•资项目 / ç»æµŽå¼ºåŽ¿ï¼‰ï¼šé¡¹ç›®æ¸…å•ä¸åŠ¨ï¼Œåªæ›´æ–° 3 é¡¹æŒ‡æ ‡ã€‚
2. **复杂不在「表」,在「源」**。程序侧要处理的麻烦全部来自市州报上来的原始表格格式不统一,不来自汇总规则本身。
3. **建议从 9 æœˆèµ·ç»Ÿä¸€ç”¨ä¸¤ä»½æ¨¡æ¿**(客运站场、物流园区各一份)。模板已经做好:
   - `docs/投资/模板/模板_市州月报_客运站场.xlsx`
   - `docs/投资/模板/模板_市州月报_物流园区.xlsx`
## äºŒã€ä¸¤ä»½æ¨¡æ¿é•¿ä»€ä¹ˆæ ·
只有一个数据页「月报」+ ä¸€ä¸ªã€Œå¡«æŠ¥è¯´æ˜Žã€é¡µã€‚第 4 è¡Œæ˜¯è¡¨å¤´ï¼Œä¸€è¡Œä¸€æ¡é¡¹ç›®ï¼Œå…± 17 åˆ—:
| åˆ— | è¡¨å¤´ | ç¨‹åºé‡Œçš„去向 |
| --- | --- | --- |
| A | åºå· | ä»…用于识别市州小节行/合计行,不参与匹配 |
| B | åŽ¿ï¼ˆå¸‚ã€åŒºï¼‰ | é¡¹ç›®ä¸»æ¡£ `county`(全省汇总表要显示这一列) |
| C | **项目名称及站级** | **匹配键**(市州 + ç±»åˆ« + é¡¹ç›®åç§°ï¼‰ |
| D | æŠ•资建设单位名称 | ä¸»æ¡£ `builder_name` |
| E | å»ºè®¾æ€§è´¨ | ä¸»æ¡£ `construct_nature`(下拉:新建/续建/改扩建/改建) |
| F | å¼€å·¥å¹´ | ä¸»æ¡£ `start_time` |
| G | ç«£å·¥å¹´ | ä¸»æ¡£ `end_time` |
| H | è®¡åˆ’总投资(万元) | ä¸»æ¡£ `total_investment` |
| I | è‡ªå¼€å§‹å»ºè®¾ç´¯è®¡å®ŒæˆæŠ•资(万元) | **当期月度数** |
| J | æœ¬å¹´è®¡åˆ’投资(万元) | **当期月度数** |
| K | è‡ªå¹´åˆç´¯è®¡å®ŒæˆæŠ•资(万元) | **当期月度数** |
| L | å…¶ä¸­ï¼šæœ¬æœˆå®ŒæˆæŠ•资(万元) | **当期月度数** |
| M | å»ºè®¾é˜¶æ®µ | **当期月度数** |
| N | å½¢è±¡è¿›åº¦ | **当期月度数** |
| O | å·¥å¯æ‰¹å¤æ–‡å· | ä¸»æ¡£ `approval_gk` |
| P | åˆè®¾/施工图批复文号 | ä¸»æ¡£ `approval_cs` |
| Q | å¤‡æ³¨ | ä¸å–æ•° |
- **主档列(A、B、D、E、F、G、H、O、P)**:第一次建档时写入,之后只要项目名不变就不动;
- **月度数(I、J、K、L、M、N)**:每个月报多少写多少,同一(项目,报表期)覆盖式重建;
- **「项目名称及站级」是唯一匹配键**:改一个字 = æ–°å»ºä¸€ä¸ªé¡¹ç›®ï¼Œæ‰€ä»¥è¦æ±‚与批复名称一字不差;
- ã€Œå¡«æŠ¥è¯´æ˜Žã€é¡µå†™äº† 11 æ¡è¦æ±‚(一份文件只放本市州、文件名带市州名、数值不带字符、
  åŽ¿ï¼ˆå¸‚ã€åŒºï¼‰å¿…å¡«ã€å»ºè®¾æ€§è´¨åªèƒ½å››é€‰ä¸€ã€æ–°å¢žé¡¹ç›®ç›´æŽ¥åŠ è¡Œã€éšè—è¡Œä¸æ”¾æ•°æ®ã€åˆè®¡è¡Œå¯ä¸å¡«ç­‰ï¼‰ã€‚
## ä¸‰ã€ä¸ºä»€ä¹ˆçŽ°åœ¨æ„Ÿè§‰å¤æ‚ï¼ˆ4 ä¸ªçœŸå®žåŽŸå› ï¼Œéƒ½åœ¨è¾“å…¥ä¾§ï¼‰
1. **版式不统一**:27 ä»½å¸‚州月报里,20 ä»½ä¸Žçœç»Ÿä¸€æ¨¡æ¿ä¸€è‡´ï¼Œ7 ä»½æœ‰å·®å¼‚——
   éšå·žå¤šä¸€ä¸ª Sheet2、神农架/潜江/襄阳拆成多个分月页签、襄阳整体右移 1 åˆ—、
   æ©æ–½/黄石合并表头、武汉(南山/汉口)只报两个项目的简表。详见
   `docs/投资/输出/市州月报格式差异报告_2026-07.md`。程序为了读全,只能一个版式一个兜底分支。
2. **一个文件里混多个市州**:《湖北省十堰交通物流基础设施投资统计汇总表(月报)》一个页签收了全省,
   2026 å¹´ 8 æœˆè¿™ä»½ 38 è¡Œé‡Œåªæœ‰ **7 è¡Œæ˜¯åå °**,另外 **27 è¡Œæ­¦æ±‰ + 4 è¡Œé»„石**。
   ç¨‹åºæ”¯æŒæŒ‰ã€Œå°èŠ‚è¡Œã€åˆ‡æ¢å¸‚å·žï¼ˆè¿™æ˜¯æœ‰æ„åšçš„åŠŸèƒ½ï¼‰ï¼Œæ‰€ä»¥èƒ½è¯»è¿›æ¥ï¼Œä½†ä»£ä»·æ˜¯ï¼š
   **只要重导 8 æœˆç‰©æµï¼Œè¿™ 31 ä¸ªé¡¹ç›®å°±ä¼šé‡æ–°å»ºåˆ°æ­¦æ±‰/黄石名下**(今天做回归重导时复现,已再次清除,见测试报告 P3)。
3. **项目清单有两个来源**:一是市州当月报上来的,二是母版/库里已经有的(如预安排母版里的武汉物流行)。
   äºŽæ˜¯äº§ç”Ÿã€Œæœ‰æ€»æŠ•资、没有本期月度数」的项目,要不要补进小计/合计就成了要单独决策的事。
4. **列位置靠兜底**:早期实现按「自年初累计 = è‡ªå¼€å§‹å»ºè®¾ç´¯è®¡ + 2 åˆ—」这类列序推算,
   å¸‚州一挪列就得再加一条兜底——这是「猜」出来的复杂度,不是业务复杂度。
## å››ã€æ¢æˆç»Ÿä¸€æ¨¡æ¿åŽï¼Œè¿˜è¦å®šæ­» 3 ä»¶äº‹
模板解决的是「怎么读」。下面 3 ä»¶äº‹å®šä¸‹æ¥ï¼Œæ•´æ¡é“¾å°±æ²¡æœ‰æ­§ä¹‰äº†ï¼š
| # | å¾…定事项 | å»ºè®® |
| --- | --- | --- |
| 1 | å¹´åº¦é¡¹ç›®æ¸…单以谁为准?库里有什么就出什么,还是以「年度预安排计划表」为准? | ä»¥å¹´åº¦é¢„安排计划表为准(随母版下发或单独建一张《年度预安排计划》表),每月只更新指标 |
| 2 | æŸå¸‚州当月**没报**怎么办? | è¯¥å¸‚州指标留空 + å¯¼å‡ºå‰å¼¹çª—列出「未报送市州」,不阻断导出 |
| 3 | å¸‚å·ž**新增**的项目插到哪? | æ’到该市州「预安排计划内」小节末尾;计划外的单独标注,导向上仍出但明显区别于计划内 |
## äº”、过渡安排与下一步
- **8 æœˆåŠä»¥å‰**:已按旧格式导入完毕,程序继续兼容旧版式,**不改、不重导**(8 æœˆåå °æºæ–‡ä»¶æ¸…理另行确认)。
- **9 æœˆèµ·**:市州按统一模板填报;建议再加一个「模板体检」——导入时校验表头是否为本模板 17 åˆ—,
  æ˜¯åˆ™èµ°ã€ŒæŒ‰è¡¨å¤´å–数」的直路,不是则**弹窗提示**(哪一列对不上),而不是继续猜列位置。
- è¿™æ ·è§£æžé€»è¾‘可以退化成「按表头名取值」,现有那些容错兜底分支可以逐步下线,复杂度自然就降下来了。
docs/Ͷ×Ê/Ä£°å/Ä£°å_ÊÐÖÝÔ±¨_¿ÍÔËÕ¾³¡.xlsx
Binary files differ
docs/Ͷ×Ê/Ä£°å/Ä£°å_ÊÐÖÝÔ±¨_ÎïÁ÷Ô°Çø.xlsx
Binary files differ
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -2489,6 +2489,8 @@
            project.setProjectName(nt);
            project.setCategory(category);
            project.setCity(currentCity);
            String countyCell = cellStr(row, col.get("county"));
            if (countyCell != null && !countyCell.trim().isEmpty()) project.setCounty(countyCell.trim());
            project.setBuilderName(cellStr(row, col.get("builder")));
            project.setConstructNature(cellStr(row, col.get("nature")));
            project.setStartTime(investTime(cellStr(row, col.get("start"))));
@@ -2524,11 +2526,19 @@
            Row row = sh.getRow(r);
            if (row == null) continue;
            boolean hasName = false;
            int headerCells = 0;
            for (int c = 0; c <= row.getLastCellNum(); c++) {
                String v = cellStr(row, c);
                if (v != null && v.contains("项目名称")) { hasName = true; break; }
                if (v == null) continue;
                String t = v.trim();
                if (t.isEmpty() || t.length() > 20) continue;   // è¯´æ˜Žæ€§é•¿å¥ä¸ç®—表头字段
                String k = investHeaderKey(t);
                if (k == null) continue;
                headerCells++;
                if ("name".equals(k)) hasName = true;
            }
            if (!hasName) continue;
            // çœŸè¡¨å¤´è¡Œè‡³å°‘有 2 ä¸ªçŸ­è¡¨å¤´å­—段;只有一格长句含「项目名称」的说明页会被排除
            if (!hasName || headerCells < 2) continue;
            Map<Integer, String> colTexts = new HashMap<>();
            int scanEnd = investHeaderScanEnd(sh, r, row);
            for (int rr = r; rr <= scanEnd; rr++) {
@@ -2542,23 +2552,8 @@
            }
            Map<String, Integer> col = new HashMap<>();
            for (Map.Entry<Integer, String> e : colTexts.entrySet()) {
                int c = e.getKey();
                String v = e.getValue();
                if (v.contains("项目名称")) col.putIfAbsent("name", c);
                else if (v.contains("投资建设") || v.contains("建设单位") || v.contains("项目业主")) col.putIfAbsent("builder", c);
                else if (v.contains("建设性质")) col.putIfAbsent("nature", c);
                else if (v.contains("开工")) col.putIfAbsent("start", c);
                else if (v.contains("竣工") || v.contains("建成")) col.putIfAbsent("end", c);
                else if ((v.contains("计划总投资") || v.contains("总投资")) && !v.contains("累计")) col.putIfAbsent("total", c);
                else if (v.contains("自开始建设")) col.putIfAbsent("startcum", c);
                else if (v.contains("本年计划")) col.putIfAbsent("yearplan", c);
                else if (v.contains("自年初累计")) col.putIfAbsent("yearcum", c);
                else if (v.contains("本月完成")) col.putIfAbsent("monthdone", c);
                else if (v.contains("建设阶段")) col.putIfAbsent("stage", c);
                else if (v.contains("形象进度") || v.contains("建设进展")) col.putIfAbsent("desc", c);
                else if (v.contains("工可") && !v.contains("项目名称")) col.putIfAbsent("gk", c);
                else if (v.contains("初设") && !v.contains("项目名称")) col.putIfAbsent("cs", c);
                else if (v.contains("建筑面积") && !v.contains("新增")) col.putIfAbsent("area", c);
                String k = investHeaderKey(e.getValue());
                if (k != null) col.putIfAbsent(k, e.getKey());
            }
            if (!col.containsKey("name")) continue;
            for (int c = 0; c <= row.getLastCellNum(); c++) {
@@ -2582,7 +2577,7 @@
                if (dc <= row.getLastCellNum()) col.put("desc", dc);
            }
            for (String k : new String[]{"builder", "nature", "start", "end", "total", "startcum",
                "yearplan", "yearcum", "monthdone", "stage", "desc", "gk", "cs", "area"}) {
                "yearplan", "yearcum", "monthdone", "stage", "desc", "gk", "cs", "area", "county"}) {
                col.putIfAbsent(k, -1);
            }
            col.put("headerRow", r);
@@ -2591,6 +2586,31 @@
        return null;
    }
    /** å•个表头单元格 -> å­—段键(null è¡¨ç¤ºä¸æ˜¯è¡¨å¤´å­—段);供表头行判定与列映射共用 */
    private String investHeaderKey(String v) {
        if (v == null) return null;
        String flat = v.replace(" ", "");
        if (v.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";
        return null;
    }
    /** è¡¨å¤´æ–‡æœ¬æ‰«æä¸‹ç•Œï¼šé»˜è®¤è¡¨å¤´è¡Œ +3;若「项目名称」列首个数据行更靠下,则扩展到数据行前一行(上限 +12)。
     *  èƒŒæ™¯ï¼šå’¸å®/十堰等市州把「建设阶段/形象进度」标签写在表头下第 4~8 è¡Œï¼Œå›ºå®š +3 ä¼šæ¼åˆ—(形象进度读不到)。 */
    private int investHeaderScanEnd(Sheet sh, int headerRow, Row headerRowObj) {