From f7131667350cfa278c935c7c628fcf3d15f00d03 Mon Sep 17 00:00:00 2001
From: xyc <jc_xiong@hotmail.com>
Date: 星期四, 27 八月 2026 14:53:42 +0800
Subject: [PATCH] chore: 同事开发结果(郑玉洁 2026-08-27):城市客运/公路旅客/投资/能耗模块、报表Tab工作台、目录导入、操作日志、DB登录校验等(不含其独立实现的 ShapeCaptcha 验证码)

---
 docs/投资/模板/模板_全省预安排计划进度情况汇总.xls                                                                             |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerEnterpriseMonthly.java       |   73 
 docs/投资/模板_查询结果(投资系统-客运+物流 2026.7).xls                                                                      |    0 
 docs/公路旅客/公路旅客指的是公路班线                                                                                       |    0 
 docs/投资/输出/湖北省(客货站场)(亿元投资项目)7月.xls                                                                          |    0 
 docs/投资/输入/物流(物流园区)/6月/202606---黄冈物流站场投资表(最终版) (1).xls                                                      |    0 
 docs/投资/输入/物流(物流园区)/6月/荆门2026年06月项目投资总报表(物流) (1).xls                                                        |    0 
 docs/投资/输入/物流(物流园区)/7月/2026年7月份孝感物流项目投资统计表.xls                                                              |    0 
 docs/城市客运/导入模板_城市汇总.xlsx                                                                                    |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentProjectMapper.java          |    9 
 docs/投资/输入的内容要与《模板_查询结果(投资系统-客运+物流 2026.7)》进行校验,存在数据不一致的弹窗提示.txt                                            |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/auditengine/entity/AuditRun.java                        |   16 
 docs/database.md                                                                                            |  169 
 docs/投资/输入/客运(客运站)/6月/襄阳市道路运输服务中心2026年6月投资.et                                                               |    0 
 docs/投资/输入/物流(物流园区)/6月/2026年6月鄂州企业月报汇总.xls                                                                  |    0 
 docs/投资/输入/客运(客运站)/6月/2026年6月林区道路客运站场投资统计报表(月、年报).xls                                                       |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityBusMonthly.java                   |  117 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentSystemMapper.java           |    9 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerIndividualMonthlyMapper.java |    6 
 docs/公路旅客/公路班线注意点.png                                                                                       |    0 
 docs/投资模块实施方案.md                                                                                            |  118 
 docs/开源项目对比.md                                                                                              |   90 
 docs/投资/输入/客运(客运站)/7月/2026湖北省道路客运站场投资统计报7月(恩施州).xls                                                         |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerEnterpriseMonthlyMapper.java |    6 
 docs/投资/输入/客运(客运站)/7月/十堰市2026年7月固定资产月报电子版投资表格.xlsx                                                          |    0 
 docs/投资/输入/物流(物流园区)/6月/湖北宜昌市物流基础设施投资统计报表(月、年报)06月.xls                                                       |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/system/service/OperationLogService.java                 |   75 
 traffic-audit-web/src/views/Dashboard.vue                                                                   |    9 
 docs/投资/模板/模板_十五五规划物流项目进展情况.xls                                                                             |    0 
 traffic-audit-web/src/views/DataImport.vue                                                                  |  764 +++
 docs/投资/输出/每个月做出这些固定格式的表,给领导看.txt                                                                           |    0 
 docs/货运/货运简化.docx                                                                                           |    0 
 docs/公路旅客/模板_公路旅客运政车辆数.xlsx                                                                                 |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/auditengine/controller/AuditController.java             |   53 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/EnergyAuthVehicleMapper.java          |    9 
 docs/城市客运/模板_交行统U402表-巡游出租汽车运营服务情况月报(2026).xls                                                              |    0 
 docs/城市客运/生成_城市客运汇总.xlsx                                                                                    |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataViewController.java           |  177 
 traffic-audit-server/src/main/java/com/trafficaudit/system/entity/User.java                                 |    2 
 traffic-audit-server/src/main/java/com/trafficaudit/system/mapper/OperationLogMapper.java                   |    8 
 docs/城市客运/生成_巡游出租客运量分市州明细.xlsx                                                                              |    0 
 docs/投资/输入/物流(物流园区)/6月/林区物流投资月报2026.06.xlsx                                                                 |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/rulemanage/controller/RuleManageController.java         |   57 
 traffic-audit-web/src/views/RuleManage.vue                                                                  |  116 
 docs/城市客运/导入模板_城市客运各市州明细表.xlsx                                                                              |    0 
 docs/投资/输入/物流(物流园区)/7月/2026年7月企业月报汇总(鄂州).xls                                                                |    0 
 docs/公路旅客/生成_中口径明细.xlsx                                                                                     |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataImportController.java         |  301 +
 docs/公路旅客/公路客运简化.docx                                                                                       |    0 
 docs/货运/生成_货运量排名.xlsx                                                                                       |    0 
 docs/公路旅客/生成_中口径分析.xlsx                                                                                     |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentSystem.java                 |   34 
 docs/投资/输入/物流(物流园区)/6月/湖北省十堰交通物流基础设施投资统计汇总表(月报)(2026年5月) (2).xls                                            |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java               | 1923 ++++++++
 docs/扩展模块评估.md                                                                                              |  117 
 docs/城市客运/生成_轨道轮渡客运量分市州明细.xlsx                                                                              |    0 
 docs/城市客运/客运量的同比环比是0% 需核实(主要出现在公交).txt                                                                      |    0 
 HANDOFF.md                                                                                                  |  723 +++
 docs/报表生成界面优化指令.docx                                                                                        |    0 
 docs/能耗/生成_能运汇总表.xlsx                                                                                       |    0 
 docs/投资/输入/客运(客运站)/7月/2026年7月林区道路客运站场投资统计报表(月、年报).xls                                                       |    0 
 项目说明.txt                                                                                                    |   39 
 docs/投资/输入/物流(物流园区)/7月/湖北宜昌市物流基础设施投资统计报表(月、年报)07月.xls                                                       |    0 
 docs/投资/输入/物流(物流园区)/6月/2026年6月份孝感物流项目投资统计表.xls                                                              |    0 
 docs/投资/输入/物流(物流园区)/6月/仙桃市物流园投资统计报表(202606).xls                                                             |    0 
 traffic-audit-web/src/main.js                                                                               |   13 
 docs/能耗/数据导入失败 .png                                                                                         |    0 
 docs/货运/货运简化工作.xlsx                                                                                         |    0 
 traffic-audit-web/src/views/UserManage.vue                                                                  |  187 
 traffic-audit-server/src/main/java/com/trafficaudit/config/TemplateDownloadController.java                  |   64 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentMonthlyMapper.java          |    9 
 docs/投资/输入/物流(物流园区)/根据6月7月文件夹里面的数据分别更新《6月物流站场投资明细》和《7月物流站场投资明细》里面的项目和数据.txt                                 |    0 
 docs/公路旅客/生成_公路旅客分市州.xlsx                                                                                   |    0 
 docs/能耗/模板_能耗车辆运政信息.xlsx                                                                                    |    0 
 docs/城市客运/导入模板_巡游出租.xlsx                                                                                    |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/security/controller/LoginController.java                |   43 
 docs/init.sql                                                                                               |  204 
 docs/投资/输入/物流(物流园区)/6月/2026年6月湖北省襄阳市交通物流基础设施投资统计报表(2).xls                                                   |    0 
 traffic-audit-web/src/views/ExplainReview.vue                                                               |  126 
 docs/投资/输入/客运(客运站)/6月/2026年6月咸宁市客运站场投资统计月报表(1)省表.xls                                                        |    0 
 docs/报表生成界面优化.doc                                                                                           |    0 
 docs/能耗/能耗简化.docx                                                                                           |    0 
 docs/投资/输入/物流(物流园区)/6月/咸宁市2026年6月份物流(货运)项目投资明细.xls                                                          |    0 
 docs/公路旅客/公路旅客简化工作.xlsx                                                                                     |    0 
 docs/城市客运/生成_城市客运各市州明细表.xlsx                                                                                |    0 
 docs/投资/输入/客运(客运站)/7月/2026年7月省站场投资表(黄冈) -.xlsx                                                              |    0 
 docs/投资/输入/物流(物流园区)/6月/荆州物流投资(2026年6月报省) .xls                                                               |    0 
 docs/投资/输出/2026年全省预安排计划进度情况汇总(7月).xls                                                                       |    0 
 docs/投资/输入/客运(客运站)/7月/湖北省宜昌市道路客运站场投资报表(月、年报)07月.xls                                                         |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentProject.java                |   53 
 docs/投资/输入/客运(客运站)/6月/(孝感市道运中心)202606孝感市道路客运站场投资统计月报表 (1).xls                                               |    0 
 docs/投资/输入/客运(客运站)/更新_7月客运站投资明细表(市州汇总大表,给领导).xlsx                                                           |    0 
 docs/投资/输入/物流(物流园区)/7月/7月份物流园中心建设项目 (天门).xlsx                                                               |    0 
 docs/投资/输入/客运(客运站)/7月/2026年7月咸宁市客运站场投资统计月报表(1)省表(1).xls                                                     |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/auditengine/mapper/AuditRunMapper.java                  |    6 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentMonthly.java                |   37 
 docs/投资/输入/客运(客运站)/6月/2026年固投月报(汉口、南山).xlsx                                                                 |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/ExplainController.java            |  192 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityTaxiMonthlyMapper.java            |    6 
 docs/投资/输入/客运(客运站)/更新_6月客运站投资明细表.xlsx                                                                       |    0 
 docs/能耗/能耗简化(2026年).xlsx                                                                                    |    0 
 docs/城市客运/导入模板_轨道、轮渡.xlsx                                                                                   |    0 
 docs/货运/生成_周转量排名.xlsx                                                                                       |    0 
 docs/投资/输入/物流(物流园区)/6月/2026年6月份物流(货运)投资项目明细表(黄石市)(1).xls                                                    |    0 
 docs/投资/输出/“十五五”规划纳入省发改委重点项目清单物流项目进展情况(7月)1.xls                                                             |    0 
 docs/投资/输入/物流(物流园区)/7月/202607---黄冈物流站场投资表(最终版).xls                                                          |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityTaxiMonthly.java                  |   95 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityTaxiAuthMapper.java               |    6 
 traffic-audit-server/src/main/java/com/trafficaudit/common/util/RegionUtil.java                             |    1 
 docs/货运/生成_货运量分市州明细.xlsx                                                                                    |    0 
 docs/投资/输入/客运(客运站)/7月/荆门市客运站场投资统计7月.xls.xls                                                                 |    0 
 docs/投资/输入/客运(客运站)/6月/2026湖北省道路客运站场投资统计报5月(恩施州) (1).xls                                                     |    0 
 docs/投资/输入/物流(物流园区)/6月/6月天门份物流园中心建设项目(1).xlsx                                                               |    0 
 docs/投资/输入/物流(物流园区)/更新_7月物流站场投资明细.xls                                                                       |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java                  |    6 
 docs/城市客运/城市客运数据优化.docx                                                                                     |    0 
 docs/城市客运/导入模板_城市公交.xlsx                                                                                    |    0 
 docs/城市客运/生成_城市公交客运量分市州明细.xlsx                                                                              |    0 
 docs/投资/输入/客运(客运站)/7月/(孝感市道运中心)202607孝感市道路客运站场投资统计月报表.xls                                                   |    0 
 docs/投资/输入/物流(物流园区)/7月/湖北省十堰交通物流基础设施投资统计汇总表(月报)(2026年7月).xls                                                |    0 
 docs/城市客运/客运简化.xlsx                                                                                         |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java             |   22 
 traffic-audit-web/src/App.vue                                                                               |    4 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/EnergyAuthVehicle.java                |   32 
 docs/货运/模板_运政车辆.xlsx                                                                                        |    0 
 traffic-audit-web/src/router/index.js                                                                       |   13 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityTaxiAuth.java                     |   49 
 docs/投资/输入/物流(物流园区)/6月/2026年6月物流基础设施建设投资月报.xls                                                              |    0 
 docs/投资/输入/客运(客运站)/6月/20260623095853.pdf                                                                    |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/system/entity/OperationLog.java                         |   22 
 docs/能耗/生成的能运汇总表要根据模板表《能运汇总表》的列宽、表头、合并规则完全一致,仅替换数据。导出为Excel格式,生成17个市州的分表和全省的汇总表.txt                         |    0 
 traffic-audit-web/src/views/Login.vue                                                                       |   83 
 docs/投资/输入/物流(物流园区)/7月/报表—武汉七月.xls                                                                          |    0 
 docs/投资/输入/物流(物流园区)/7月/2026年7月份物流(货运)投资项目明细表(黄石市) (2).xls                                                   |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerAuthVehicleMapper.java       |    6 
 traffic-audit-web/src/views/DataView.vue                                                                    |   29 
 docs/货运/模板_规上规下拆分运输量.xlsx                                                                                   |    0 
 traffic-audit-web/src/views/AuditResult.vue                                                                 |  503 ++
 docs/能耗/模板_道路货运车辆能源消耗情况(2026).xlsx                                                                          |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerAuthVehicle.java             |   29 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityBusMonthlyMapper.java             |    6 
 traffic-audit-web/src/views/ReportExport.vue                                                                |  498 ++
 docs/货运/模板_货运量周转量.xlsx                                                                                      |    0 
 .gitignore                                                                                                  |   26 
 traffic-audit-server/src/main/java/com/trafficaudit/system/controller/UserController.java                   |  107 
 docs/公路旅客/生成_中口径排名.xlsx                                                                                     |    0 
 docs/投资/输入/客运(客运站)/7月/襄阳市道路运输服务中心2026年6月投资 (1).et                                                           |    0 
 docs/投资/模板/模板_亿元投资项目.xls                                                                                    |    0 
 docs/城市客运/模板_交企统U203表_城市公共交通月度运营情况(2026年7月).xls                                                             |    0 
 docs/投资/输出/(经济强县项目)湖北省交通物流基础设施投资统计报表(7月月报)xls.xls                                                           |    0 
 docs/城市客运/生成_城市客运客运量分市州累计.xlsx                                                                              |    0 
 docs/README.md                                                                                              |    6 
 traffic-audit-server/src/main/java/com/trafficaudit/common/GlobalExceptionHandler.java                      |   22 
 docs/投资/输入/物流(物流园区)/更新_6月物流站场投资明细.xls                                                                       |    0 
 docs/投资/输入/客运(客运站)/6月/荆门市客运站场投资统计6月.xls.xls                                                                 |    0 
 docs/投资/输入/物流(物流园区)/7月/2026年7月湖北省襄阳市交通物流基础设施投资统计报表 (1).xls                                                  |    0 
 docs/投资/输入/物流(物流园区)/6月/2026年6月恩施州物流(货运)项目投资明细.xls                                                           |    0 
 traffic-audit-server/src/main/resources/application.yml.example                                             |    2 
 docs/投资/输入/客运(客运站)/7月/2026年7月站场固投(南山、汉口站).xlsx                                                              |    0 
 docs/投资/输入/物流(物流园区)/7月/2026年7月恩施州物流(货运)项目投资明细 .xls                                                          |    0 
 docs/投资/输入/客运(客运站)/6月/湖北省宜昌市道路客运站场投资报表(月、年报)06月.xls                                                         |    0 
 docs/投资/输入/物流(物流园区)/6月/潜江2026.06.xlsx                                                                       |    0 
 docs/投资/输入/物流(物流园区)/7月/潜江2026.07.xlsx                                                                       |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java     |  240 +
 docs/投资/输入/物流(物流园区)/7月/荆门2026年07月项目投资总报表(物流).xls                                                            |    0 
 docs/公路旅客/模板_交企统H203-1表_公路旅客运输月度生产情况.xlsx                                                                   |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java             | 1823 +++++++
 docs/投资/输入/物流(物流园区)/7月/林区物流投资月报2026.07.24.xlsx                                                              |    0 
 docs/投资/输入/客运(客运站)/根据6月七月文件夹里面的数据更新《更新_6月客运站投资明细表》和《更新_6月客运站投资明细表》两张大表里面的数据.txt                             |    0 
 docs/投资/输入/物流(物流园区)/7月/2026年7月物流基础设施建设投资月报(随州) (1).xls                                                      |    0 
 docs/投资/输入/物流(物流园区)/7月/荆州物流投资(2026年7月报省) .xls                                                               |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/EnergyVehicleQuarterly.java           |   70 
 docs/投资/输入/物流(物流园区)/7月/咸宁市2026年7月份物流(货运)项目投资明细 (1).xls                                                      |    0 
 /dev/null                                                                                                   |  105 
 docs/投资/输入/物流(物流园区)/7月/仙桃市物流园投资统计报表(202607).xls                                                             |    0 
 docs/投资/模板/模板_经济强县投资统计报表.xls                                                                                |    0 
 docs/货运/模板_轨迹里程.xlsx                                                                                        |    0 
 docs/城市客运/导入模板_城市分市州.xlsx                                                                                   |    0 
 traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java           | 3327 ++++++++++++++
 traffic-audit-server/pom.xml                                                                                |    4 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerIndividualMonthly.java       |   31 
 traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/EnergyVehicleQuarterlyMapper.java     |    9 
 docs/货运/模板_交企统H203-2表_道路货物运输月度生产情况.xlsx                                                                     |    0 
 docs/投资/输入/物流(物流园区)/6月/报表—武汉六月.xls                                                                          |    0 
 184 files changed, 12,558 insertions(+), 578 deletions(-)

diff --git a/.gitignore b/.gitignore
index 558e2e7..d0f5cae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,29 @@
 .DS_Store
 Thumbs.db
 desktop.ini
+
+# ==== ??/?????????====
+_shots/
+*.nb3
+*.sql
+*.dmp
+backend.log*
+energy-test.log
+diag*.log
+_*.py
+_*.txt
+_*.xlsx
+_*.png
+_*.json
+_test_check*/
+_test_passenger/
+check_*.py
+cmp*.py
+dump_*.py
+fix_*.py
+inspect_*.py
+patch_*.py
+verify_*.py
+write_readme.py
+clear-bom.ps1
+verify_city*.txt
diff --git a/HANDOFF.md b/HANDOFF.md
index ac6761b..6f3feeb 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -3,11 +3,78 @@
 > **鐢ㄦ硶**锛氭柊瀵硅瘽寮�濮嬪悗锛屽厛璇绘湰鏂囦欢 + 椤圭洰鏍圭洰褰� `AGENTS.md`锛屽嵆鍙棤缂濇帴鍔涖��
 > **姣忓ぉ缁撴潫鏃�**锛氭妸褰撳ぉ杩涘睍銆佽俯杩囩殑鍧戙�佹柊闇�姹傘�佹湭瀹屾垚浜嬮」鏇存柊鍒版湰鏂囦欢锛岀劧鍚庡彲浠ユ斁蹇冨紑鏂板璇濄��
 
-## 涓�銆佸綋鍓嶇姸鎬侊紙2026-08-23锛�
-- 鍓嶅悗绔�**鍧囨湭杩愯**锛�8080/8090 杩炴帴琚嫆锛夛紝闇�瑕佸厛鍚姩锛堣涓嬶級銆�
-- 鏈�杩戜唬鐮佹敼鍔ㄦ埅鑷� 08-07锛�08-14 璧蜂唬鐮佸凡鍏ュ簱 Gitee 绉佹湁浠撳簱锛堣绗節鑺傦級锛宮ain 鍒嗘敮宸ヤ綔鍖哄共鍑�銆佸凡鎺ㄩ�併��
-- 鏃犲凡鐭ラ樆濉炴�ч棶棰樸��
+## 涓�銆佸綋鍓嶇姸鎬侊紙2026-08-27锛�
+- **2026-08-27锛堢敤鎴风幆澧冧慨澶嶏細Chrome 娓叉煋杩涚▼宕╂簝=杩呴浄鎵╁睍锛屼笌搴旂敤鏃犲叧锛�**锛氱敤鎴峰弽棣堛�岀櫥褰曞悗鐐规暟鎹鍏ユ祻瑙堝櫒宕╂簝銆嶃�傛帓鏌ョ粨璁猴細**闈炲簲鐢ㄤ唬鐮侀棶棰�**鈥斺�旀棤澶� Chrome 瀹屾暣娴佺▼澶氳疆锛堢湡瀹炵櫥褰曗啋鐐瑰嚮鑿滃崟鈫掑垏 tab/涓嬫媺/鍒锋柊锛�+ Edge 鍧� 0 宕╂簝銆丣S 0 閿欒锛涘悗绔� /import/batches 鏈� limit 涓婇檺鏃犲ぇ鍝嶅簲銆傛湰鏈� Crashpad 鏈� 3 涓�**娓叉煋杩涚▼**宕╂簝 dump锛�10:02/10:03/11:15锛夛紝寮傚父鐗瑰緛瀹屽叏涓�鑷达紙code 0x517a7ed / addr 0xb57d5567=鏈垵濮嬪寲鍐呭瓨璺宠浆锛夛紝椤甸潰鍧囦负 localhost:8080锛屼笁娆″穿婧冩椂鍞竴绗笁鏂规墿灞�=銆岃繀闆蜂笅杞芥敮鎸併�嶏紙id ncennffkjdiamlpmcbajkmaiiiddgioo锛宒ocument_start 鍏ㄩ〉闈㈡敞鍏ヨ嚜甯� Vue3 runtime + MutationObserver 瑙傚療鏁翠釜 body DOM锛夛紝Intel 鏃ч┍鍔� 27.20.100.9365 涓哄彔鍔犲洜绱犮�傜敤鎴峰凡鍦� Chrome 绉婚櫎璇ユ墿灞曞苟鏀圭敤 Edge锛屽潎涓嶅啀宕╂簝銆�
+  - **鍧戯紙鍔″繀璁颁綇锛�**锛氣憼姝e紡鐗� Chrome 宸茬姝㈠懡浠よ鍔犺浇鎵╁睍锛�--load-extension/--disable-extensions-except 琚拷鐣ワ紝鏃ュ織鎻愮ず not allowed in Google Chrome锛夛紝headless 鏃犳硶澶嶇幇銆屽甫鎵╁睍銆嶅満鏅紱**Edge 鍏佽鍛戒护琛屽姞杞芥墿灞�**锛坢sedge.exe --load-extension=... + --user-data-dir=鐙珛鐩綍锛夛紝鍙敤浜庡鐜版墿灞曠浉鍏抽棶棰樸�傗憽鐢ㄦ埛 Chrome 宕╂簝璇佹嵁鐪� %LOCALAPPDATA%\Google\Chrome\User Data\Crashpad\reports\*.dmp锛坢inidump 鍙墜宸ヨВ鏋愶細寮傚父娴� type=6銆佹ā鍧楄〃 type=4锛屾敞閲婂惈 gpu-driver/extension-1/椤甸潰 URL锛夈�傗憿鍐嶉亣銆屾祻瑙堝櫒宕╂簝/鐧藉睆銆嶅厛鏌� Crashpad 鏄惁鍚涓夋柟鎵╁睍锛屽啀鏌ユ墿灞�/椹卞姩锛屼笉瑕佸厛鎬�鐤戝簲鐢ㄣ��
+  - 浜х墿锛歘shots/import_crash_repro*.js锛坔eadless 澶嶇幇锛夈�乢shots/edge_repro.js锛圗dge+鎵╁睍澶嶇幇锛夈�乢shots/xunlei_ext/锛堟墿灞曞壇鏈級銆乢shots/cft/锛圕hrome for Testing 涓嬭浇娈嬬暀锛夈��
 
+- **2026-08-26锛堟櫄闂寸画8锛屾姤琛ㄧ敓鎴愰〉 UI 浼樺寲涓恒�孴ab 宸ヤ綔鍙般��+ 澶囨敞寮圭獥锛宔2e 鍏ㄧ豢锛�**锛歚ReportExport.vue`锛�23:27:55 Compiled锛夌户缁敼鐗堬細
+  - 鎶ヨ〃绫诲瀷鏀瑰洖**椤堕儴 5 涓� Tab**锛堣揣杩�/鏃呭/鍩庡競瀹㈣繍/鎶曡祫/鑳借�楋紝鍩庡競瀹㈣繍甯︺�屽叕浜�/鍑虹/杞ㄩ亾杞浮/姹囨�汇�嶆爣绛撅級锛屾浛浠g画7鐨勪笅鎷夋鏂规銆�
+  - 鏌ヨ鍖猴細鍛ㄦ湡鍗曢�夛紙鏈堟姤/骞存姤锛岃兘鑰楀彧鏄剧ず瀛f姤锛�+ 鏈堜唤/骞翠唤**鍒嗙粍涓嬫媺**锛堣兘鑰楀彧鍒楀搴︽湯鏈� 3/6/9/12锛�+ 鏌ヨ鎸夐挳锛涢�� 2026-07 鍚庣姸鎬佸埛鏂般��
+  - 璐ц繍妯″潡鏂板銆屾簮鏁版嵁褰曞叆涓庢牎楠屻�嶅崱鐗囷細200 琛屾簮鏁版嵁琛ㄦ牸锛堜紒涓氬悕绉�/璐ц繍閲�/杩愯緭鏂瑰紡/缁熻鏈堜唤锛夛紝銆屾牎楠屾暟鎹�嶁啋 閿欒鍒楄〃 + 鍑洪敊鍗曞厓鏍肩孩鑹查珮浜紙cell-error锛夛紝銆岃嚜鍔ㄨ绠椼�嶁啋 姹囨�昏锛堟�昏揣杩愰噺/骞冲潎璐ц繍閲�/鍚勪紒涓氬崰姣旓級锛涘叾浣欐ā鍧楁樉绀恒�屾殏鏈厤缃湪绾挎牎楠岃鍒欍�嶆彁绀恒��
+  - 鎶ヨ〃鍒楄〃锛氬嬀閫� + 鎶ヨ〃鍚� + 鐘舵�佹爣绛撅紙鏁版嵁灏辩华/鏈鏍�/缂烘暟鎹級+ 澶囨敞鍒� + 棰勮/瀵煎嚭锛涘簳閮ㄥ叏閫�/鎵归噺棰勮/鎵归噺瀵煎嚭淇濈暀銆�
+  - **澶囨敞寮圭獥**锛坄openNoteDialog`/`saveNote`锛夛細鎸夋姤琛� key 鏈湴璁板繂锛坄localStorage report_note_<key>`锛夛紝淇濆瓨鍚庡垪琛ㄥ嵆鏃舵樉绀恒��
+  - 鏃犲ご e2e `_shots/report_ui_opt_e2e.js`锛�5 Tab銆佸懆鏈�/鏈堜唤涓嬫媺銆�2026-07 涓夊紶鎶ヨ〃鏁版嵁灏辩华銆佹牎楠屽け璐� 1 鏉�+绾㈣壊楂樹寒銆佽嚜鍔ㄨ绠楁眹鎬汇�佹姇璧� 4 寮�/鑳借�椾粎瀛f姤+瀛e害鏈湀銆佸娉ㄥ脊绐椾繚瀛樺叏閮ㄩ�氳繃锛孞S 0 閿欒锛涙埅鍥� `_shots/report_ui_opt.png`銆�
+  - **鍧戯紙宸蹭慨澶嶏級**锛氬娉ㄥ脊绐� e2e 鏇捐鎶ャ�岀┖棰勮寮圭獥鏍囬銆嶁�斺�擿querySelector('.el-dialog__title')` 鍛戒腑鐨勬槸 DOM 涓涓�涓紙闅愯棌鐨勶級棰勮寮圭獥鏍囬锛涙敼涓哄彇**鍙** `.el-dialog__wrapper` 鍐呯殑鏍囬/textarea/鎸夐挳鍚庨�氳繃锛涘彟 localStorage 鏂█閿簲涓哄綋鍓� Tab 鎶ヨ〃锛坋nergySummary 鑰岄潪 cityDetail锛夈�傚簲鐢ㄦ湰韬棤缂洪櫡锛堟帰閽� `_shots/probe_note.js` 纭鐐圭紪杈戝浘鏍囧嵆寮�銆岀紪杈戝娉ㄣ�嶅脊绐楋級銆�
+  - 閬楃暀/寰呭姙涓嶅彉锛氳缁�5锛堣揣杩� 7 鏈堟眹鎬昏〃鏈鍏ャ�佹梾瀹� 1-6 鏈�/2025 鍚屾湡缂哄け銆�8-12 鏈堜緷璧栨暟鎹簮銆佽兘鑰楁柊瑙勫垯闃堝�煎緟纭锛夈��
+- **2026-08-26锛堟櫄闂寸画4鍐嶈ˉ锛岃В閲婃ā鍧楁敼銆屾寜闇�灞曞紑銆嶏級**锛氱敤鎴峰弽棣堝乏鍙冲竷灞�浠嶄笉缇庤銆傝璁″垽鏂細闂涓嶅湪浣嶇疆鑰屽湪銆屽父椹绘樉绀恒�嶁�斺�旀妸瑙i噴缂栬緫鍣ㄤ竴鐩存憜鍦ㄦ瘡鏉¢棶棰樻梺蹇呯劧鏄炬尋銆傛敼涓�**娴忚杞汇�佺紪杈戞寜闇�灞曞紑**锛堝墠绔� 17:41:58 Compiled锛夛細鏄庣粏瑙嗗浘鍗$墖澶存槸銆岃В閲婏細宸插~鍐�/鏈~鍐欍�嶅彲鐐瑰嚮鏍囩锛岀偣寮�鎵嶅湪鍗$墖鍐呭睍寮�瑙i噴缂栬緫鍖猴紙鏀惰捣鎸夐挳鍙叧锛夛紱鍚堝苟瑙嗗浘銆屼紒涓氳В閲娿�嶅垪榛樿鍙樉绀虹姸鎬佹爣绛�+2 琛屾憳瑕�+銆屽~鍐�/淇敼銆嶆寜閽紝鐐瑰紑鎵嶅睍寮� textarea锛涗袱鏉¤鍥惧唴璺宠浆銆岃В閲娿�嶉摼鎺ュ凡绉婚櫎锛堝師 goExplain 閾炬帴鏀圭敱灞曞紑闈㈡澘鎵挎媴锛孉I 鍒嗘瀽浠嶅湪浼佷笟瑙i噴椤碉級銆傞粯璁ら〉闈㈠洖鍒板共鍑�鐘舵�併��
+- **2026-08-26锛堟櫄闂寸画4琛ワ紝瑙i噴妯″潡鏀瑰乏鍙冲竷灞�锛�**锛氱敤鎴峰弽棣堛�岃В閲婃斁闂涓嬮潰涓嶅ソ鐪嬶紝鏀惧彸杈广�嶃�傚凡鏀癸紙鍓嶇 17:38:55 Compiled锛夛細鏄庣粏瑙嗗浘鍗$墖鍐� `card-body` 鏀� flex 涓ゆ爮鈥斺�斿乏 `issue-detail`锛堣鍒�+瀹為檯/鍩哄噯/宸紓锛夈�佸彸 `explain-side`锛堝浐瀹氬 340 鐏板簳闈㈡澘锛氭爣绛�+宸叉湁瑙i噴+textarea+淇濆瓨/閲嶇疆锛夛紱鍚堝苟瑙嗗浘鎶婅В閲婁粠闂鏄庣粏鍗曞厓鏍肩Щ鍑猴紝**鍗曠嫭鏂板銆屼紒涓氳В閲娿�嶅垪锛堝 360锛�**锛屼綅浜庛�屽鏍搁棶棰樻槑缁嗐�嶄笌銆屾搷浣溿�嶄箣闂达紝闈炶В閲婄被鎶ヨ〃锛圛NVEST/H204锛夋樉绀恒�屸�斻�嶃��
+- **2026-08-26锛堟櫄闂寸画4锛屽鏍哥粨鏋滈〉鍐呭祵浼佷笟瑙i噴锛�**锛氱敤鎴疯姹傘�屽鏍搁棶棰樻槑缁嗗悗闈㈢洿鎺ュ姞瑙i噴妯″潡锛屼笉鐢ㄨ烦杞埌鍙︿竴涓晫闈€�嶃�傜幇鐘讹細瀹℃牳缁撴灉椤垫瘡鏉¢棶棰樺崱鐗囧彧鏈夎烦杞摼鎺ワ紝涓斿悗绔�**鍘熸湰娌℃湁淇濆瓨瑙i噴鐨勬帴鍙�**锛坴erifyExplanation 鍙锛夈��
+  - 鍚庣 `ExplainController` 鏂板 `POST /api/explain/save`锛坮eportType/reportId/explanation锛孒2032/H2031/CITY_BUS/CITY_TAXI 鍥涚被锛屾寜 reportId 鏇存柊瀵瑰簲鏉ユ簮琛� verifyExplanation锛岄敊璇繑鍥� Result.error锛夈��
+  - 鍓嶇 `AuditResult.vue`锛�17:35:24 Compiled锛夛細鏄庣粏瑙嗗浘姣忔潯闂鍗$墖搴曢儴銆佸悎骞惰鍥炬瘡瀹朵紒涓氶棶棰樻槑缁嗕笅鏂癸紝鍐呭祵銆屼紒涓氳В閲娿�嶆ā鍧椻�斺�斿凡鏈夎В閲�/鏈~鍐欐爣绛� + 鑽夌 textarea + 淇濆瓨瑙i噴/閲嶇疆锛涜崏绋挎寜 reportId 鍏变韩锛堝悓浼佷笟澶氭潯闂鍚� draft锛屼繚瀛樺悗鍚屾鎵�鏈夎锛夛紱鍔犺浇缁撴灉缁熶竴璧� `applyResults` 鍒濆鍖栬崏绋裤�備紒涓氳В閲婇〉锛圗xplainReview锛変笌瀹℃牳缁撴灉椤靛叡鐢ㄥ悓涓�鏉ユ簮琛紝淇濆瓨鍚庝袱杈逛竴鑷达紱鍘熴�岃В閲娿�嶈烦杞摼鎺ヤ繚鐣欍��
+  - 楠岃瘉锛歚POST /api/explain/save` 淇濆瓨鈫扗B 鏇存柊鈫抏xplain/list 鍙鈫掑凡娓呮祴璇曟暟鎹紱鍚庣缂栬瘧閫氳繃+閲嶅惎灏辩华锛圥ID 11732锛夈��
+- **2026-08-26锛堟櫄闂寸画3锛岀洰褰曞鍏ョ洿杩炵増 + 鏂囦欢鍚嶈嚜鍔ㄨ瘑鍒湀浠斤級**锛氱敤鎴峰弽棣堢洰褰曞鍏ュお鎱�/鎿嶄綔缁曪紝涓旀槑纭�屾枃浠跺す閲屾槸澶氫釜鏈堢殑鏁版嵁锛岃鎸� Excel 鏂囦欢鍚嶅垎鍒瓨鍒版瘡涓湀銆嶃��
+  - 鍓嶇 `DataImport.vue`锛�17:21:59 Compiled锛夛細鍘绘帀銆岄�夋嫨鏂囦欢澶广�嶆寜閽紝鏀逛负**鐩綍璺緞杈撳叆妗� + 銆屽鍏ヨ鐩綍銆�**锛堝洖杞︿篃鍙Е鍙戯級+銆屾祻瑙堚�︺�嶈緟鍔╂煡鎵� +銆岃烦杩囧凡瀵煎叆鏂囦欢銆嶅嬀閫夛紙榛樿寮�锛岃浣忎笂娆$洰褰� localStorage `dirLast_<type>`锛夛紱閫愭枃浠跺鍏ユ樉绀哄疄鏃惰繘搴︼紙寰呭鍏�/瀵煎叆涓�/鎴愬姛/澶辫触/宸茶烦杩� + 璇嗗埆鍒扮殑鏈堜唤鏍囩锛夛紱鐩綍瀵煎叆涓嶅啀寮哄埗瑕佹眰鍏堥�夋姤琛ㄦ湡锛堟姤琛ㄦ湡浠呬綔鏃犲勾浠芥枃浠剁殑鍚庡骞翠唤锛夈��
+  - 鍚庣锛堢紪璇戦�氳繃+閲嶅惎灏辩华锛孭ID 10932锛夛細`importDirFileByPath`/`importFromDir` 澧炲姞 **`detectPeriod(fileName, period)` 鎸夋枃浠跺悕璇嗗埆鎶ヨ〃鏈�**锛堟敮鎸� `2026骞�7鏈坄/`2026骞�07鏈坄/`2026.7`/`2026-07`/`202607`/`7鏈坄锛涙棤骞翠唤鐢� period 骞翠唤鎴栧綋鍓嶅勾浠斤級锛岃瘑鍒笉鍒颁笖鏈�夋姤琛ㄦ湡鍒欓�愭枃浠舵姤閿欙紱鏂板**璺宠繃宸插鍏�**锛坕mport_batch 鍚� period+fileName 涓� failRows=0 鍒欒烦杩囷紝`skipImported` 鍙傛暟榛樿 true锛夛紱`fromDirFile`/`fromDir` 鐨� period 鏀逛负鍙�夈�傝瘑鍒鍒欐敞鎰忥細`202607` 闇� 6 浣嶈繛鏁板瓧涓斿勾 2000-2100銆佹湀 1-12锛岄伩鍏嶈鍒ゃ��
+  - 楠岃瘉锛歚2026.6.xlsx鈫�2026-06`銆乣202607.xlsx鈫�2026-07`銆乣2026骞�5鏈�.xlsx鈫�2026-05`銆乣7鏈�.xlsx鈫�2026-07`锛堝綋鍓嶅勾锛夈�乣鏈爣娉ㄦ湀浠�.xlsx鈫掓姤閿檂锛涢噸璺� skipImported=true 鍏ㄩ儴璺宠繃锛涙祴璇曟壒娆″凡娓呫��
+  - 璇箟鎻愰啋锛氬悓涓�绫诲瀷鍚屼竴鎶ヨ〃鏈熷涓枃浠朵粛浼氥�屽厛娓呭悗鍐欍�嶄簰鐩歌鐩栵紙绯荤粺骞傜瓑瑙勫垯锛夛紝鎶曡祫甯傚窞澶氭枃浠惰鐢ㄤ笓闂ㄧ殑銆屽競宸炲崟琛ㄦ壒閲忓鍏ャ�嶃��
+- **2026-08-26锛堟櫄闂寸画2锛屾枃浠跺す瀵煎叆鏀规湇鍔$鐩綍娴忚鍣� + 鍙屽嚮鍗冲鍏ワ級**锛氱敤鎴峰弽棣堝師鐢熸枃浠跺す閫夋嫨妗嗐�屽崟鍑�/鍙屽嚮浼氳烦杩涗笅涓�绾х洰褰曘�佺湅涓嶅埌閫夋嫨纭銆嶃��
+  - 鏍瑰洜涓�锛氭祻瑙堝櫒鍘熺敓 `webkitdirectory` 閫夋嫨妗嗕氦浜掞紙Chrome 闇�鐐瑰彸涓嬭銆岄�夋嫨鏂囦欢澶广�嶇‘璁わ紝鐢ㄦ埛娌$湅鍒帮級锛涙牴鍥犱簩锛歚DataImport.vue` 鏁翠綋閲嶅啓鏃朵涪浜� `endpoint()` 鏂规硶锛坄importOne` 璋� `this.endpoint(row.type)` 蹇呮姏 TypeError锛�**鎵�鏈夋枃浠跺鍏ュ叾瀹為兘鏄潖鐨�**锛夆�斺�旀湰娆″凡琛ュ洖瀹屾暣 type鈫扷RL 鏄犲皠銆�
+  - 鏂规锛氬純鐢ㄦ祻瑙堝櫒鍘熺敓鏂囦欢澶归�夋嫨妗嗭紝鏀逛负**鏈嶅姟绔洰褰曟祻瑙堝櫒**锛堝墠鍚庣鍚屾満锛屾暟鎹湪 `docs/`锛夛細鍚庣鏂板 `GET /api/import/dirs?path=`锛坧ath 绌�=椤圭洰 docs锛屽惈瀛愮洰褰曞垪琛�+褰撳墠鐩綍 Excel 鏂囦欢鍚�/鏁伴噺锛沝ocs 瀹氫綅鍚戜笂鏌ユ壘 4 灞傦紝鍏煎 IDEA 浠� server 妯″潡鐩綍鍚姩锛変笌 `POST /api/import/fromDir`锛坱ype/period/dir锛屾壂鎻忕洿鎺ュ寘鍚殑 .xlsx/.xls锛宍ByteArrayMultipartFile` 鍖呰鍚庡鐢ㄥ悇绫诲瀷鏃㈡湁瑙f瀽鏂规硶锛岄�愭枃浠惰繑鍥炴垚鍔�/澶辫触/閿欒锛夈��
+  - 鍓嶇 `DataImport.vue`锛氥�岄�夋嫨鏂囦欢澶广�嶆墦寮�搴旂敤鍐呭璇濇锛堝綋鍓嶈矾寰�/涓婄骇鐩綍/鏁版嵁鐩綍 docs/瀛愮洰褰曞垪琛�/褰撳墠鐩綍 Excel 棰勮锛夛紝**鍗曞嚮=杩涘叆瀛愭枃浠跺す锛屽弻鍑荤洰鏍囨枃浠跺す=绔嬪嵆瀵煎叆**锛涗篃鍙繘鍏ュ悗鐐广�岄�夋嫨姝ゆ枃浠跺す骞跺鍏ャ�嶏紱瀵煎叆缁撴灉閫愭枃浠跺脊绐楀睍绀猴紱闃熷垪鍗曟枃浠跺鍏ユ祦绋嬩繚鐣欍��
+  - 楠岃瘉锛氬悗绔紪璇戦�氳繃+閲嶅惎灏辩华锛坰tart-dev.ps1锛孭ID 32576锛夛紱`/api/import/dirs` 杩斿洖 docs 鍚勬ā鍧楃洰褰曚笌 15 涓煄甯傚杩� Excel锛沗/api/import/fromDir` 瀹炴祴妯℃澘_杞ㄨ抗閲岀▼.xlsx鈫�628 琛岋紙鐪熷疄瑙f瀽鎴愬姛锛夛紱娴嬭瘯浜х敓鐨� track_mileage 2026-03 鏁版嵁宸茬敤绌烘枃浠堕噸瀵兼竻闆躲�乮mport_batch 娴嬭瘯璁板綍(34/35/36)宸插垹锛涘墠绔� 17:03:58 Compiled successfully銆�
+  - 寰呭姙锛氳鐢ㄦ埛鍒锋柊椤甸潰鎵嬫祴銆岄�夋嫨鏂囦欢澶光啋鍙屽嚮鐩爣鏂囦欢澶瑰嵆瀵煎叆銆嶏紱涓存椂娴嬭瘯鐩綍 `%TEMP%\dirimport_test`銆乣%TEMP%\dirimport_empty` 淇濈暀鏈垹銆�
+- **2026-08-26锛堟櫄闂达紝鎶ヨ〃鏍囬鍏ㄥ姩鎬佸寲锛�**锛氭寜鐢ㄦ埛瑕佹眰銆屾爣棰樹笉瑕佸啓姝伙紝鏍规嵁瀹¤鍛橀�夋嫨鐨勬湀浠�/骞翠唤鏄剧ず銆嶅畬鎴愭敹灏撅紙鎵垮墠锛氬煄甯傚杩� 6 琛ㄣ�屽鍏ユā鏉縚*.xlsx銆嶅凡鐢辨棦鏈夈�岀敓鎴恄*.xlsx銆嶅鍒惰ˉ榻愶紝姝ゅ墠寮曠敤鏂囦欢涓嶅瓨鍦ㄥ鑷� 500锛夛細
+  - 鈶� `dynamicTitle` 浠庛�屽墠 6 琛屻�嶆敼涓哄叏琛ㄦ壂鎻忥細琛ヤ笂鍒嗗競宸炵 2 鍧楁爣棰橈紙r23銆屽煄甯傚杩愬杩愬懆杞噺鍒嗗競宸�1-N鏈堢疮璁℃儏鍐点�嶏級锛涖��1-12鏈堛�嶆暣骞村姣斿潡锛�2024/2025锛夋爣棰樹繚鎸佸師鏍凤紝涓嶅啀琚鏀规垚閫変腑骞淬��
+  - 鈶� 鏂板 `cumRange(month)`锛�1 鏈堟樉绀恒��1鏈堛�嶄笉鍐嶅嚭鐜般��1-1鏈堛�嶏紝搴旂敤浜庤揣杩愰噺/鍛ㄨ浆閲忔帓鍚嶇疮璁″潡銆佷腑鍙e緞鍒嗘瀽銆佹姇璧勯瀹夋帓琛ㄥご銆佸煄甯傚杩愭爣棰樸��
+  - 鈶� 鏂板 `fixPassengerHeaderYear`锛氬叕璺梾瀹㈠垎甯傚窞/涓彛寰勬槑缁嗚〃澶村勾浠藉姩鎬佸寲锛堟鍓嶉�� 2027 浠嶆樉绀恒��2026骞�1鏈�/2026骞寸疮璁°�嶏級銆�
+  - 鈶� 杞ㄩ亾杞浮鏄庣粏琛ヨ皟 `dynamicTitle`锛堟鍓嶈〃澶村勾浠藉啓姝� 2026锛夈��
+  - 楠岃瘉锛�2026-01/2026-07/2026-12锛堝勾鎶ワ級/2027-01 脳 鍏ㄦā鍧� 17 寮犲鍑烘爣棰樺叏閮ㄦ柇瑷�閫氳繃锛堟牱渚嬭 `%TEMP%\title_check\*FINAL*`锛夛紱姹囨�昏〃 G1/M1/G12 瀵规瘮鍧楁爣棰樻纭繚鐣� 2025/2024銆�
+  - 閬楃暀锛堝睘妯℃澘缁撴瀯锛屾湭鏀癸級锛氭姇璧勪嚎鍏冭〃 3 鐨勫搴﹀浐瀹氬垪锛�1-3鏈堝疄闄�/4鏈堣鍒�/Q2/1-4鏈堢疮璁�/5鏈�/6鏈堢瓑锛変负妯℃澘鍥哄畾鍒楋紝骞翠唤宸插姩鎬併�佹湀浠藉浐瀹氭槸妯℃澘璁捐锛涙姇璧勫崄浜斾簲妯℃澘 sheet0銆屽勾    鏈堛�嶅崰浣嶄笌瀹樻柟鏍蜂緥涓�鑷淬��
+- **2026-08-26锛堟櫄闂寸画锛屾姤琛ㄦ暟鎹瑙堣交閲忕増锛�**锛氬鍑哄墠鍏堢湅鏁版嵁鏈夋病鏈夌己/涓嶅銆傚悗绔柊澧� `GET /api/report/preview`锛坧eriod/mode/types锛屽鐢� checkExportReady 鐨勫氨缁�/瀹℃牳鐘舵�侊紝閫愭ā鍧楃畻鏈堝害瑕嗙洊 + 鏍稿績鎸囨爣鎽樿 + 闂娓呭崟锛夛細璐ц繍=妯℃澘婀栧寳鐪佽 1..N 鏈堝垪瑕嗙洊 + 鍏ㄧ渷绱璐ц繍閲�/鍛ㄨ浆閲忥紙鍚堣/瑙勪笂/瑙勪笅锛夛紱鏃呭=婧愯〃閫愭湀瑕嗙洊 + 浼佷笟/涓綋绱锛涘煄甯傚杩�=鍏氦/鍑虹/杞ㄩ亾/杞浮绱锛堟眹鎬� 3 琛ㄨ鐩�=鍏氦鈭嚭绉燂紝鍏氦鏄庣粏/杞ㄩ亾杞浮鍙湅鍏氦琛級锛涙姇璧�=褰撴湀鎶ヨ〃鍙彁绀哄綋鏈堢己澶� + 椤圭洰鏁�/鏈湀瀹屾垚/鑷勾鍒�/鑷紑濮嬬疮璁★紱鑳借��=瀛e害鏁版嵁 + 杞﹁締鏁�/鐕冩补娑堣�椼�傚墠绔� `ReportExport.vue` 姣忔ā鍧楀姞銆岄瑙堟暟鎹�嶆寜閽� + 寮圭獥锛堢姸鎬佹爣绛� 灏辩华/缂烘暟鎹�/鏈鏍搞�佹湀搴﹁鐩栥�佹憳瑕佽〃銆侀棶棰樻竻鍗曪級銆傞獙璇侊細2026-07 鍏ㄦā鍧� 18 寮犮��2026-08锛堟湭瀹℃牳+缂烘暟鎹級銆�2026-12 骞存姤銆�2027-01 鍏ㄩ儴姝g‘锛涘墠绔� Compiled successfully銆�
+  - 棰勮椤哄甫鏆撮湶鐨勬暟鎹幇鐘讹紙渚涚敤鎴锋牳瀵癸級锛氳揣杩愭ā鏉夸粎 2026-07 涓�鏈熶笖 7 鏈堝垪涓虹┖锛堢疮璁″彧鏈� 1-6 鏈� 9.7 涓囧惃锛岃涓� 3754 涓囧惃 鈫� 瑙勪笅涓鸿礋锛屽彛寰勫瓨鐤戯級锛涙梾瀹�/鍑虹浠� 7 鏈堛�佹姇璧勪粎 6-7 鏈堝鍏ワ紱鍏氦鏈堟姤鏈鍏ワ紙杞ㄩ亾/杞浮 0 鏁版嵁锛夈��
+- **2026-08-26锛堝崍鍚庯紝鎶ヨ〃鐢熸垚 P0 涓夐」 + 妯℃澘瑙勫垯姊崇悊锛�**锛氣憼瀵煎嚭鍓嶆暟鎹氨缁牎楠屸�斺�斿悗绔� `GET /api/report/ready`锛堟寜鎶ヨ〃绫诲瀷妫�鏌ヤ緷璧栬〃琛屾暟锛氳揣杩�=妯℃澘_璐ц繍閲忓懆杞噺+瑙勪笂瑙勪笅鎷嗗垎琛�+H203-2銆佹梾瀹�=H203-1+涓綋銆佸叕浜�/鍑虹銆佹姇璧�=investment_monthly銆佽兘鑰�=h204_vehicle_quarterly锛夛紝鍓嶇瀵煎嚭鍓嶅脊銆屾暟鎹湭灏辩华銆嶇‘璁ゆ锛堝垪鍑哄叿浣撶己鍝紶琛紝鍙户缁鍑虹┖琛級锛涒憽閿欒閫忓嚭鈥斺�旀柊澧� `GlobalExceptionHandler`锛圚TTP 500 + Result{code,message}锛夛紝鍓嶇 `failWith` 鏄剧ず鍚庣鍏蜂綋鍘熷洜锛涒憿鏂囦欢鍚嶇粺涓�鈥斺�斿墠绔� 4 涓梾瀹� fileName 瀵归綈鍚庣锛堢敓鎴恄鍏矾鏃呭鍒嗗競宸炴槑缁�/涓彛寰勬槑缁�/涓彛寰勬帓鍚�/涓彛寰勫垎鏋�.xlsx锛夛紝`buildResponse` 鎸� .xls/.xlsx 璁剧疆 MIME锛涒懀椤烘墜淇� `ReportExport.vue` 缂� `activeMenu` 瀹氫箟锛堜晶杈规爮楂樹寒澶辨晥锛夈�傞獙璇侊細ready 鎺ュ彛鏁板�间笌 DB 閫愯〃鏍稿涓�鑷达紙cityDetail=690=ft18+scale36+h2032=636銆佹梾瀹� 771銆佸嚭绉� 17銆佹姇璧� 179銆佽兘鑰� 126銆佸叕浜� 0 鍥犲叕浜ゆ湀鎶ユ湭瀵煎叆锛夛紱鏃犲ご娴忚鍣ㄤ袱鍦烘櫙 PASS锛堢己鏁版嵁寮圭‘璁ゆ銆佹暟鎹綈鍏ㄧ洿鎺ヤ笅杞斤級锛涘墠绔� 12:55:10 Compiled銆傛埅鍥� `_shots/report_ready_dialog.png`銆�
+### 2026-08-26 鏅氶棿浼氳瘽锛氭姤琛ㄧ敓鎴愪笁鍙e緞钀藉湴锛堝叕寮忎繚鐣�/鍔ㄦ�佹彃琛�/瀹℃牳鍓嶇疆+1~N绱锛屽凡瀹屾垚楠岃瘉锛�
+**鑳屾櫙**锛氱敤鎴风‘璁や笁椤瑰彛寰勨�斺�斺憼鎵�鏈夋ā鏉夸繚鐣欏叕寮忋�佸彧濉師濮嬮噺銆佸叕寮忛儴鍒嗚嚜鍔ㄧ畻锛涒憽鍔ㄦ�佹彃琛屽苟鍚屾鍏紡鑼冨洿锛涒憿鍏堝鏍稿啀鍑鸿〃銆�1~N鏈堢疮璁�=涓�鏈堝埌N鏈堟眰鍜屻��
+**鈶犲叕寮忎繚鐣欙紙鐜扮姸鏍稿锛屾棤闇�鏀癸級**
+- 鑳借��/鍩庡競瀹㈣繍/鏃呭 8 寮犲鍑哄凡鏄�屾ā鏉垮簳绋� + 鍏紡鍗曞厓鏍间繚鐣� + evaluateAll 閲嶇畻銆嶏細鏃呭鍒嗗競宸�/涓彛寰勬帓鍚�/鍒嗘瀽銆佽兘鑰楁眹鎬汇�佸煄甯傚杩� 3 寮犲悎璁�/鍚屾瘮/鍗犳瘮鍏紡鍧囧湪瀵煎嚭鍚庨噸绠楋紱鎶曡祫 4 琛ㄤ笌璐ц繍妯℃澘鏈韩 0 鍏紡锛堝悎璁�/鍚屾瘮鐢辩郴缁熺畻濂藉啓鍏ワ級銆傛梾瀹㈡ā鏉垮巻鍙� `#REF!` 寮曠敤涓嶅奖鍝嶅~鍏呫��
+- 椤烘墜淇锛堜笂涓嬪崍浼氳瘽锛夛細`application.yml` 鐨� `passenger.template-dir` 鍘熸寚鍚戜笉瀛樺湪鐨� `docs/鍏矾鏃呭/杈撳嚭` 瀵艰嚧鏃呭瀵煎嚭 500锛屽凡鏀逛负 `docs/鍏矾鏃呭`锛沗REPORT_FILE_NAMES` 鏃呭 zip 鍐呮枃浠跺悕鏀瑰叏鍚嶃�傛梾瀹� 4 琛ㄥ鍑哄凡鎭㈠ HTTP 200锛坰ize鈮�13.6KB锛夈��
+**鈶″姩鎬佹彃琛岋紙鏈鏂板锛屾姇璧� 3 琛級**
+- 鏀瑰姩闆嗕腑鍦� `ReportExportService.java`锛坄investPlan`/`investFiveYearLogistics`/`investCounty`锛夛細缁撴瀯鎵弿锛堝潡鈫掑競宸炲皬鑺傗啋椤圭洰琛岋級鈫� 鍘熶綅濉厖妯℃澘琛岋紙璁板綍宸插尮閰嶉」鐩� id锛夆啋 鑷簳鍚戜笂鎸夊皬鑺傛彃琛岃ˉ榻� DB 澶氫綑椤圭洰 鈫� 鎸夊潡/甯傚窞閲嶆帓搴忓彿銆俙insertRowsDown`锛坰hiftRows + 鍚堝苟鍖哄厛鎽橀櫎鍐嶆寜鏂拌鍙锋仮澶� + `createRowLike` 澶嶅埗鏍峰紡/琛岄珮/鍗曡鍚堝苟锛夈�乣renumberInvestSeq`锛堣〃1鎸夊潡閲嶇疆銆佽〃2鎸夊競宸為噸缃�佽〃4鍏ㄨ〃杩炵画锛夈�乣InvestSection` 绛夎緟鍔╂柟娉曚竴骞跺姞鍏ャ�傝〃3锛堜嚎鍏冩姇璧勶級鍘熸湰灏辨槸鍔ㄦ�佺敓鎴愶紝鏈姩銆�
+- 楠岃瘉锛�2026-07锛夛細妯℃澘 琛�1 瀹㈣繍 45 琛�/鐗╂祦 121 琛岋紝DB 46/132 鈫� 杈撳嚭 179 琛岋紙46+133锛屽叾涓� 1 鏉′负鏃㈡湁妯$硦鍖归厤閲嶅銆岃崋宸炲競鐟炴捣鍥介檯鐗╂祦鍥�嶏紝闈炴湰娆″紩鍏ワ級锛涘崄鍫� 鐗╂祦灏忚妭妯℃澘 7 琛屸啋DB 38 涓啋鎻� 31 琛岋紙DB 濉姤鍗曚綅涓哄崄鍫扮殑 38 涓」鐩叏閮ㄤ笂琛紝鍚悕绉板甫姝︽眽/榛勭煶鐨勮法甯傞」鐩紝灞炴暟鎹幇鐘讹級銆傝〃2 鍗佷簲浜� 35 琛�=DB 35 涓紙妯℃澘 63 琛屾棤鎻掕锛夛紱琛�4 缁忔祹寮哄幙 38 琛岋紙36 涓� DB + 2 鏉℃棦鏈夐噸澶嶅尮閰嶏細鏋濇睙/搴斿煄锛夈�傚悎璁�/鍧�/甯傚窞灏忚妭鏁板�间笌 DB 鑱氬悎涓�鑷达紙鍏ㄧ渷 11429050.52銆佸崄鍫扮墿娴� 5111586锛夛紱鍚堝苟鍖� 111 涓��0 閲嶅彔銆侀殢鎻掕姝g‘涓嬬Щ锛涘簭鍙疯繛缁紙瀹㈣繍 1-46銆佺墿娴� 1-133锛夈��
+- 璇存槑锛氭姇璧勬ā鏉挎棤鍏紡锛屻�屽叕寮忚寖鍥村悓姝ャ�嶆棤闇�澶勭悊锛涙梾瀹�/鍩庡競瀹㈣繍/鑳借�楁ā鏉挎暟鎹尯鍥哄畾锛�17/18 甯傚窞琛岋級涓嶄細瓒呰锛岃嫢鏈潵闇�瑕佹彃琛岋紝shiftRows 浼氳嚜鍔ㄥ悓姝ュ叕寮忓紩鐢ㄣ��
+**鈶㈠厛瀹℃牳鍐嶅嚭琛� + 1~N 绱锛堝凡灏辩华锛�**
+- `checkExportReady` 宸叉柊澧� `auditRows`/`audited` 瀛楁锛堟寜 `audit_rule.report_type` 鈫� `audit_result.report_period` 缁熻锛夛紝鍓嶇瀵煎嚭鍓嶅脊绐楀尯鍒嗐�屻�愮己鏁版嵁銆�/銆愭湭瀹℃牳銆戙�嶃�傞獙璇侊細璺� CITY_TAXI 瀹℃牳锛坄POST /api/audit/execute?period=2026-07&reportType=CITY_TAXI`锛�4 鏉★級鍚� ready 鎺ュ彛 `cityTaxiDetail auditRows=4 audited=true`锛屼笉鍐嶆彁绀烘湭瀹℃牳銆�
+- 1~N 鏈堢疮璁¤涔夌‘璁わ細`freightCum`/`midCumClass`/`passengerCumSum` 鍧囦负 1..N 鏈堟眰鍜岋紙濡� 2026-07 鏈堟姤绱=1-7鏈堜箣鍜岋紱鏃呭鍒嗗競宸炴ā鏉挎湀搴﹀垪鍥哄畾 1-6 鏈堬紝鏁� fillMonths=min(currentMonth,6)锛岃秴鍑洪儴鍒嗕互妯℃澘鍒楁暟涓哄噯锛夈��
+**寰呯‘璁�/閬楃暀**
+- 鍏朵綑绫诲瀷瀹℃牳灏氭湭閲嶈窇锛堝叏搴撴竻绌哄悗浠� H2032=1002 鏉� + 鏈 CITY_TAXI=4 鏉★級锛氬闇� ready 涓嶅啀鎻愮ず鏈鏍革紝璇峰湪椤甸潰鎵ц H2031/INVEST/H204 瀹℃牳锛堟垨璁� AI 浠h窇锛夈��
+- 鎶曡祫妯$硦鍖归厤瀛樺湪閲嶅锛堝悓妯℃澘琛屽尮閰嶅埌鍚屼竴 DB 椤圭洰锛夛紝濡傞渶鍙敤銆屽凡鐢ㄩ」鐩泦鍚堛�嶇害鏉熷尮閰嶏紙浼氭敼鍙樼幇鏈夎緭鍑猴紝闇�纭锛夈��
+- 琛�1 鍗佸牥鐗╂祦 38 涓� DB 椤圭洰鍚ぇ閲忓悕绉板甫姝︽眽/榛勭煶鐨勯」鐩紙濉姤鍗曚綅=鍗佸牥锛夛紝鑻ユ暟鎹彛寰勬湁璇渶鍦ㄥ鍏ユ簮澶存牳瀵广��
+- 閬楃暀涓嶅彉锛�7-12 鏈堟ā鏉垮緟鐢ㄦ埛鎻愪緵锛涜兘鑰楁柊 6 鏉¤鍒欓槇鍊煎彛寰勫緟纭锛涗唬鐮佷粛鏈彁浜� Git銆�
+
+- **2026-08-26锛堜腑鍗堬紝M2 瀹屾垚锛�**锛氭姇璧勫競宸炲崟琛ㄦ壒閲忓鍏ヨ惤鍦板苟鍏ㄩ噺楠岃瘉锛坄/api/import/investCityBatch`锛屽弬鏁� period/category=瀹㈣繍绔欏満|鐗╂祦鍥尯锛屾壂鎻� `docs/鎶曡祫/杈撳叆/{瀹㈣繍锛堝杩愮珯锛墊鐗╂祦锛堢墿娴佸洯鍖猴級}/{N鏈坿`锛岃烦杩� pdf锛夛細瀹㈣繍 6鏈�35/7鏈�46銆佺墿娴� 6鏈�132/7鏈�133 鏉★紝0 澶辫触锛沗total_investment` 淇鐢熸晥锛堣埅鍙戞櫤鎱х墿娴佸洯=69876锛屽鍑哄悎璁� 2038714.59 涓� DB 鑱氬悎涓�鑷达級锛涗慨澶嶃�屾�绘姇璧� vs 璁″垝鎬绘姇璧勶紙琛ㄥご甯︾┖鏍硷級銆�+銆屽叕寮忓崟鍏冩牸璇荤紦瀛樺�硷紙.et 璺ㄨ〃鍏紡锛夈��+銆屽 sheet 鍘嗗彶鏂囦欢鎸夋姤琛ㄦ湡閫� sheet锛堣闃� .et 姝ゅ墠閿欓�� 2鏈� sheet锛夈�嶃�傚崄浜斾簲妯℃澘 63 椤硅鑼冨寲鍖归厤 35 瑕嗙洊锛�28 涓湡瀹炵己鍙f竻鍗曞湪 `_shots/five_year_report.txt`锛堟暟鎹簮涓嶅湪甯傚窞鍗曡〃锛岄渶鐢ㄦ埛鎻愪緵锛夈��
+- **2026-08-26锛堝噷鏅級**锛氱敤鎴� 4 椤规剰瑙佸叏閮ㄥ鐞嗗畬姣曪紙璇﹁鏈熬銆�2026-08-26 鍑屾櫒浼氳瘽銆嶏級锛氣憼妯℃澘涓�鑷存�р�斺�斾慨姝� `application.yml` 妯℃澘璺緞锛坧assenger鈫抈docs/鍏矾鏃呭/杈撳嚭`锛屾柊澧� `energy.template-dir: docs/鑳借�梎锛夛紝鑳借�楁ā鏉挎枃浠跺悕鏀逛负鐢ㄦ埛鏈�鏂版洿鏂扮殑 `鐢熸垚_鑳借繍姹囨�昏〃.xlsx`锛岃揣杩�/鏃呭/鑳借��/鍩庡競瀹㈣繍 10 寮犲鍑虹粨鏋�+鍚堝苟+鍒楀鍏ㄩ儴涓庢ā鏉� PASS锛涒憽瀹℃牳瑙勫垯鈥斺�擧204 鑳借�楄ˉ 6 鏉¤鍒欙紙RULE_E005~E010锛屽叡 10 鏉★紝闇�姹� 7 鏉″叏瑕嗙洊锛夛紝瀹炵幇瑙勫垯绠$悊椤碉紙鍚庣 `RuleManageController` + `RuleManage.vue`锛屾寜鎶ヨ〃绫诲瀷鏌ョ湅 6 绫诲叏閮ㄨ鍒欍�佸彲鍚仠/鏀归槇鍊硷級锛孋ITY_TAXI 7 鏉$‘璁ゅ紩鎿庝笌鍏ュ簱瀹屾暣锛涒憿浼佷笟瑙i噴琛岄珮鍘嬬缉锛圗xplainReview.vue 绱у噾琛岄珮 + 瑙i噴鍒楅檺楂樻粴鍔級锛涒懀閲嶅瀵煎叆纭鍚庣鍏ㄩ儴骞傜瓑锛堝悓鎶ヨ〃鏈熷厛鍒犲悗鎻掞級+ 鍓嶇 loading 闃茶繛鐐癸紝涓嶄細瀛樹袱浠姐��
+- **娉ㄦ剰**锛氭暟鎹簱宸蹭簬 2026-08-26 00:45 鎸夌敤鎴疯姹傚叏搴撴竻绌猴紙26 寮犺〃 TRUNCATE锛屽浠� `_shots/db_backup_20260826_004520.sql` 28MB锛夛紝`audit_rule` 宸叉仮澶� 33 鏃ф潯 + 鏂板 6 鏉�=39 鏉★紙H2032=6/H2031=8/H204=10/INVEST=4/CITY_BUS=4/CITY_TAXI=7锛夛紝`sys_role` 绛夐厤缃〃浠嶄负绌洪渶閲嶆柊鍒濆鍖栵紱涓氬姟鏁版嵁闇�閲嶆柊瀵煎叆銆�
+- **2026-08-25锛堟櫄涓婄画锛�**锛氭寜鐢ㄦ埛纭鍙e緞瀹屾垚锛氭姤琛ㄧ敓鎴愰〉閲嶆帓锛堣揣杩愨啋鍏矾鏃呭鈫掑煄甯傚杩愬湪鏃呭鍙宠竟鈫掕兘鑰椻啋鎶曡祫锛夈�佺敓鎴愬唴瀹规敼銆屽閫変笅鎷�+瀵煎嚭銆嶅崟鎸夐挳銆佽揣杩�/鏃呭/鍩庡競瀹㈣繍/鎶曡祫鍏ㄩ儴鏀寔鏈堟姤/骞存姤锛堣兘鑰椾繚鎸佸搴︽湯鏈堬級銆佽兘鑰楁眹鎬昏〃鏀逛负妯℃澘搴曠鐢熸垚锛堟ā鏉縚鑳借繍姹囨�昏〃 .xlsx锛夈�佹姇璧� 4 琛ㄥ垪瀹戒笌鏍蜂緥瀵归綈锛堟牱渚嬫棤鍚堝苟/鏃犺嚜瀹氫箟鍒楀锛夈�佷紒涓氳В閲婃敮鎸佲�滄湭濉啓鈥�+鈥滀粎鏄剧ず宸插~鍐欌�濆紑鍏炽�佹暟鎹鍏ラ〉缇庡寲锛堢姸鎬佸垪/绫诲瀷璇存槑/榛樿鏈燂級銆傝瑙佹湯灏句氦鎺ヨ褰曘��
+- **2026-08-25锛堟櫄涓婄画锛�**锛氭寜鐢ㄦ埛纭鍙e緞瀹屾垚锛氭姤琛ㄧ敓鎴愰〉閲嶆帓锛堣揣杩愨啋鍏矾鏃呭鈫掑煄甯傚杩愬湪鏃呭鍙宠竟鈫掕兘鑰椻啋鎶曡祫锛夈�佺敓鎴愬唴瀹规敼銆屽閫変笅鎷�+瀵煎嚭銆嶅崟鎸夐挳銆佽揣杩�/鏃呭/鍩庡競瀹㈣繍/鎶曡祫鍏ㄩ儴鏀寔鏈堟姤/骞存姤锛堣兘鑰椾繚鎸佸搴︽湯鏈堬級銆佽兘鑰楁眹鎬昏〃鏀逛负妯℃澘搴曠鐢熸垚锛堟ā鏉縚鑳借繍姹囨�昏〃 .xlsx锛夈�佹姇璧� 4 琛ㄥ垪瀹戒笌鏍蜂緥瀵归綈锛堟牱渚嬫棤鍚堝苟/鏃犺嚜瀹氫箟鍒楀锛夈�佷紒涓氳В閲婃敮鎸佲�滄湭濉啓鈥�+鈥滀粎鏄剧ず宸插~鍐欌�濆紑鍏炽�佹暟鎹鍏ラ〉缇庡寲锛堢姸鎬佸垪/绫诲瀷璇存槑/榛樿鏈燂級銆傝瑙佹湯灏句氦鎺ヨ褰曘��
+- **2026-08-25锛堜笅鍗堬級**锛氭姤琛ㄧ敓鎴�/浼佷笟瑙i噴/鏁版嵁瀵煎叆涓夐〉 UI 閲嶆瀯锛堣瑙佹湯灏句氦鎺ヨ褰曪級锛氬煄甯傚杩愪笁妯″潡鍚堝苟涓轰竴寮犲崱銆佷簲妯″潡缁熶竴銆屽嬀閫�+涓�閿叏閮▃ip銆嶃�佹姤琛ㄦ湡鏀寔鏈堟姤/骞存姤銆佷紒涓氳В閲婃墿灞� H2031/CITY_BUS/CITY_TAXI銆佹暟鎹鍏ュ垎缁�+鏈�杩戞壒娆°�佹姤琛ㄧ敓鎴愮暀鐥曪紙sys_operation_log锛夈��
+- **2026-08-25锛堟渶鏂帮級**锛氭暟鎹簱杩炴帴鏍瑰洜淇锛圝DBC URL 缂� `allowPublicKeyRetrieval=true锛孧ySQL8 caching_sha2_password 瀵艰嚧鎵�鏈� DB 鏌ヨ 500锛夛紝宸插姞鍥炲苟閲嶅惎楠岃瘉锛涘煄甯傚杩愬叕浜�/鍑虹 2026-07 瀹℃牳缁撴灉閲嶈窇鎭㈠锛�53/6 鏉★紝涓� 08-24 涓�鑷达級锛�6 寮犳姤琛ㄩ噸鏂扮敓鎴愭暟鍊煎叏閮ㄦ牳瀵归�氳繃锛涙眹鎬昏〃缃戠害杞﹁淇涓烘樉寮� 0锛堣瑙佹湯灏句氦鎺ヨ褰曪級銆�
+- **2026-08-24锛堟渶鏂帮級**锛氬煄甯傚杩愬叕浜�/宸℃父鍑虹鎶ヨ〃鏀逛负**鎸夌洰褰曚笅妯℃澘搴曠鐢熸垚**锛坄鍩庡競鍏氦_妯℃澘.xlsx`/`宸℃父鍑虹_妯℃澘.xlsx`锛夛紝淇濈暀妯℃澘鏍囬/琛ㄥご/鍚堝苟鍗曞厓鏍�/鍏紡/鏍峰紡锛屼粎濉暟鎹尯锛涘叏鐪� 7 鏈堝洓鎸囨爣涓庢ā鏉� 0 宸紓銆�
+- **2026-08-24锛堟渶鏂帮級**锛氬煄甯傚杩愮 2 姝ワ紙宸℃父鍑虹锛夊凡钀藉湴锛氬贰娓稿嚭绉熸湀鎶ュ鍏ワ紙`/api/import/cityTaxi`锛屽競宸炵骇 17 琛岋級銆佸嚭绉熻溅杩愭斂杞﹁締瀵煎叆锛坄/api/import/cityTaxiAuth`锛�42636 琛岋紝甯傚窞鐢辫瘉鍙�/妗f鍙�/缁忚惀鏉�/鍦板潃姝e垯鎻愬彇锛夈��7 缁勫鏍歌鍒欙紙RULE_T001~T007锛宺eport_type=CITY_TAXI锛�7 鏈堝鏍� 6 鏉★級銆佸垎甯傚窞鎶ヨ〃锛坄/api/report/export/cityTaxiDetail`锛屽叏鐪�+17 甯傚窞脳4 鎸囨爣锛屼笌 `宸℃父鍑虹_妯℃澘.xlsx` 7 鏈堟暟鍊� 0 宸紓锛�5860.24/34236.60/5351.24/27422.13锛夈�傜 1 姝ュ叕浜わ紙CITY_BUS锛変繚鎸佹甯革紙53 鏉★級銆�
+- 鍓嶅悗绔潎鍦ㄨ窇锛氬悗绔� 8090锛坰tart-dev.ps1 鍚姩锛屾棩蹇� backend.log锛夈�佸墠绔� 8080锛坴ue-cli serve锛岀儹鏇存柊宸插姞杞藉叕浜�+宸℃父鍑虹鍏ュ彛锛夈��
+- **2026-08-24锛堟渶鏂帮級**锛氬煄甯傚杩愭姤琛ㄧ郴鍒楋紙6 琛� + zip锛夋寜鐩綍妯℃澘鐢熸垚钀藉湴锛氬叕浜�/鍑虹/杞ㄩ亾杞浮鏄庣粏銆佸垎甯傚窞绱銆佸悇甯傚窞鏄庣粏琛ㄣ�佹眹鎬伙紝瑙� `docs/鍩庡競瀹㈣繍/杈撳嚭/`锛涜建閬�/杞浮鏁版嵁浠庡叕浜ゆ簮鏂囦欢鎵╁睍鍏ュ簱锛堣瑙佺画5锛夈��
+- 鍩庡競瀹㈣繍绗� 3 姝ワ紙缃戠害杞︽媶鍒嗭級寰呴儴绾ф暟鎹簮锛涙姇璧� M2锛堝競宸炲崟琛ㄨВ鏋愶級宸插畬鎴愶紙瑙佷笂鏂规潯鐩級銆�
+- 鎶曡祫 M2锛堝競宸炲崟琛ㄦ壒閲忓鍏ワ級宸插畬鎴愬苟楠岃瘉锛堣瑙佹湯灏俱��2026-08-26 涓崍浼氳瘽锛歁2 甯傚窞鍗曡〃鎵归噺瀵煎叆銆嶏級锛�28 涓崄浜斾簲椤圭洰缂哄彛鏁版嵁婧愬緟鐢ㄦ埛鎻愪緵銆�
 ## 浜屻�佸揩閫熷惎鍔�
 缁熶竴鐢ㄤ竴閿剼鏈紙蹇呴』娌欑澶�/鎻愭潈杩愯锛夛細
 ```
@@ -44,7 +111,6 @@
 6. AI 浜や簰鏁版嵁**鍏堣劚鏁忓啀鍙戦��**锛堥渶姹傝 `requirement/璐ц繍绠�鍖�.docx` 绗�78琛岋紱璁捐瑙� `docs/database.md` 绗竷绔� `llm_desensitize_map`锛夈��
 7. AI 鍒嗘瀽杈撳嚭瑕�"涓�鐪肩湅鍒扮粨鏋勫拰閲嶇偣"锛氭爣棰樺姞绮椼�佺粨璁�/寤鸿/渚濇嵁绛夋爣绛惧潡銆�
 8. 瀹℃牳缁撴灉涓�"浼佷笟瑙i噴 AI 鍒嗘瀽"鏄袱涓嫭绔嬪姛鑳斤紝涓嶈娣峰湪涓�璧枫��
-9. **鏈満鏈夊叾浠栭」鐩湪璺� node 杩涚▼**锛�08-23 宸茬煡 08-21 19:17 鍚姩鐨勪袱涓� node 杩涚▼锛堝綋鏃� PID 13616/15412锛夊睘浜�**鍏朵粬椤圭洰**锛岄噸鍚湰椤圭洰鏃朵笉瑕佹潃锛涙潃浠讳綍杩涚▼鍓嶅厛鏍稿鍛戒护琛岋紙CIM Win32_Process锛変笌鍚姩鏃堕棿銆�
 
 ## 鍏�佹枃妗g储寮�
 - 闇�姹傦細`requirement/璐ц繍绠�鍖�.docx`锛�+ `docs/` 鍓湰锛夈�乣docs/01-浜ら�氳繍杈撻儴闂ㄧ粺璁¤皟鏌ュ埗搴�.docx`銆乣docs/02-浜ら�氳繍杈撲紒涓氱粺璁¤皟鏌ュ埗搴�.docx`銆�
@@ -65,9 +131,648 @@
 - 杩滅▼锛欸itee **绉佹湁**浠撳簱 https://gitee.com/jichengxiong/trafficAudit 锛屽垎鏀� `main`锛堟彁浜よ韩浠� xyc <jc_xiong@hotmail.com>锛夈��
 - 鏁忔劅閰嶇疆涓嶅叆搴擄細`traffic-audit-server/src/main/resources/application.yml` 宸插湪 `.gitignore` 涓紝
   浠撳簱鍐呭彧鎻愪氦鍗犱綅绗︽ā鏉� `application.yml.example`锛坄${DEEPSEEK_API_KEY}` 绛夛級銆傛柊鐜锛氬鍒舵ā鏉夸负 application.yml 骞跺~鐪熷疄鍊笺��
-- 鏃ュ父娴佺▼锛歚git add -A` 鈫� `git commit -m "..."` 鈫� `git push`锛堝嚟鎹凡瀛� Windows 鍑嵁绠$悊鍣紝鏈満 push 鏃犻渶鍐嶈緭浠ょ墝锛涙崲鏈哄櫒闇�閲嶆柊鎻愪緵浠ょ墝锛夈��
+- 鏃ュ父娴佺▼锛歚git add -A` 鈫� `git commit -m "..."` 鈫� `git push`锛圚TTPS push 闇�瑕� Gitee 绉佷汉浠ょ墝鎴栧凡淇濆瓨鍑嵁锛夈��
 - 娉ㄦ剰锛歚backend.log`銆乣frontend.log`銆乣node_modules/`銆乣dist/`銆乣target/` 绛変笉鍏ュ簱銆�
 
-## 鍗併�佷細璇濊褰�
-- 2026-08-14锛氬惎鍔ㄥ墠鍚庣楠岃瘉姝e父锛堝悗鍧囧凡鍋滄锛夛紱寤虹珛 git 浠撳簱锛堟柊澧� `.gitignore`銆佽劚鏁忔ā鏉� `application.yml.example`锛岀湡瀹� `application.yml` 涓嶅叆搴擄級锛屽垱寤� Gitee 绉佹湁浠撳簱 `jichengxiong/trafficAudit` 骞舵帹閫� `main`锛�2 commits锛氬垵濮嬫彁浜� + HANDOFF 琛ュ厖 git 璇存槑锛夈��
-- 2026-08-23锛氫氦鎺ュ墠鏍稿疄鈥斺�攇it 宸ヤ綔鍖哄共鍑�銆佽繙绔凡鍚屾锛涘墠鍚庣鍧囨湭鍦ㄨ繍琛屻��
+## 鍗併�佷氦鎺ヨ褰曪細2026-08-22 ~ 08-23锛堟墿灞曟ā鍧楄皟鐮旓級
+
+### 鑳屾櫙
+鐢ㄦ埛鎻愬嚭鍦ㄨ揣杩愶紙H203-2锛夊熀纭�涓婃墿灞曟柊鍔熻兘锛屼笟鍔¤寖鍥村惈锛氬叕璺梾瀹㈣繍杈撱�佽兘鑰椼�佸煄甯傚杩愩�佹姇璧勩��
+
+### 宸插仛
+1. **閫氳椤圭洰**锛欰GENTS.md / HANDOFF.md / docs 闇�姹備笌璁捐 / requirement/璐ц繍绠�鍖�.docx锛屾帉鎻$幇鐘讹紙浠g爜浠呰揣杩愪竴鏉¢摼璺級銆�
+2. **妯℃澘瀹炴媶**锛坉ocs/鎵╁睍琛ㄦā鏉�/ 鐢ㄦ埛鏂版斁锛夛細
+   - 鍏矾鏃呭 H203-1锛氫紒涓氭湀鎶ユ槑缁嗭紙28 鍒楋級+ 杩愭斂瀵规瘮 + 鍒嗗競宸炵敓鎴愯〃锛涘鏍� 4 缁勮鍒�
+   - 鑳借�� H204锛堝鎶ワ級锛氳溅杈嗙矑搴︽槑缁嗭紙18 鍒楋級+ 杩愭斂淇℃伅 + 姹囨�昏〃锛涘鏍� 7 缁勮鍒欙紙2 缁勯渶璺ㄨ〃寮曠敤璐ц繍鏈堟姤锛�
+   - 鍩庡競瀹㈣繍锛氬競宸炵骇鐢熸垚琛� + 缃戠害杞﹂儴绾т笅鍙戞媶鍒嗭紱鍏氦 4 缁� + 鍑虹 7 缁勮鍒�
+   - 鎶曡祫锛氳緭鍏�=17 甯傚窞 6/7 鏈堝杩愮珯鍦�+鐗╂祦鍥尯鏈堟姤锛�**鏍煎紡涓嶇粺涓�**锛寈ls/xlsx/et/pdf 娣峰瓨锛�20~26 鍒楋級锛涜緭鍑�=4 寮犲浐瀹氭牸寮忛瀵艰〃锛涘彟鏈夋姇璧勭郴缁熸煡璇㈠鍑哄彲瀵规帴
+3. **浜у嚭鏂囨。**锛�
+   - docs/鎵╁睍妯″潡璇勪及.md锛氭ā鍧椕楀鐢ㄥ害璇勪及銆佸疄鏂介『搴忥紙H203-1鈫扝204鈫掑煄甯傚杩愨啋鎶曡祫锛夈�佹灦鏋勫缓璁紙鎶ヨ〃绫诲瀷娉ㄥ唽琛ㄥ寲銆佸鏍稿紩鎿庢柊澧炴瘮杈冪被鍨嬶級銆佸伐浣滈噺绮椾及
+   - docs/寮�婧愰」鐩姣�.md锛欸itHub 妫�绱㈢粨璁猴紙鏃犵洿鎺ュ悓绫伙級銆佸洓绫诲弬鐓ч」鐩紙NIC MSR / RuoYi / JimuReport+DataEase / Griffin+GX+Soda锛夊姣斾笌鍚ず
+4. **涓存椂鏂囦欢**锛氭牴鐩綍 .tmp_*.py锛堣В鏋愯剼鏈級+ .tmp_璐ц繍绠�鍖�.txt锛屾湭鍒狅紙绾﹀畾锛夛紱xlrd 宸茶鍏� bundled python锛圖:\...codex-runtimes\dependencies\python锛夈��
+
+### 鍏抽敭缁撹
+- 鑷爺鏂瑰悜姝g‘锛屼笉寮曞叆澶ф鏋堕噸鍐欙紱鏈�鍊煎緱鍊熼壌鐨勬槸 RuoYi 浠g爜鐢熸垚鍣紙鎵╁睍鎻愰�燂級涓庡鏍歌鍒欏0鏄庡紡璁捐锛圙riffin 鐞嗗康锛夈��
+- 寤鸿寮�宸ラ『搴忥細鍏矾鏃呭 H203-1锛堥珮澶嶇敤銆佸揩閫熻鏁堬級鈫� 鑳借�� H204锛堣ˉ璺ㄨ〃寮曠敤锛夆啋 鍩庡競瀹㈣繍锛堣鍒欐渶澶嶆潅锛夆啋 鎶曡祫锛堢嫭绔嬫ā鍧楋紝鍏堝畾"瀵规帴鎶曡祫绯荤粺 vs 瑙f瀽甯傚窞鎶ヨ〃"锛夈��
+
+### 寰呭姙 / 涓嬩竴姝�
+- 绛夌敤鎴风‘璁ゅ紑宸ユ柟鍚戯紙鐢ㄦ埛璇�"鍏堣璁哄啀寮�宸�"锛夈��
+- 鑻ュ紑宸ワ細鍏堝仛 H203-1 琛ㄧ粨鏋勮璁� + 瑙勫垯鍒濆鍖� SQL + 鍓嶇鑿滃崟銆�
+- 鎶�鏈�虹櫥璁帮細Vue2/SB2 鍗囩骇銆佷唬鐮佺敓鎴愬櫒銆佹搷浣滄棩蹇楁敞瑙e寲锛堣繙鏈燂級銆�
+- Gitee API 鏈璇佽繑鍥炵┖锛屽彲浜哄伐娴忚鍣ㄨˉ鏌� gitee 鎼滅储銆�
+
+
+## 鍗佷竴銆佷氦鎺ヨ褰曪細2026-08-23锛圚203-1 鍏矾鏃呭妯″潡宸插紑宸ュ苟瀹屾垚涓讳綋锛�
+
+### 鐢ㄦ埛纭鐨� 3 涓彛寰勶紙寮�宸ュ墠璁ㄨ缁撹锛�
+1. 鎶ヨ〃鏈燂細涓绘ā寮忔部鐢ㄣ�屽勾-鏈堛�嶇幇绠楃幇瀵硷紙浠绘剰鍘嗗彶鏈堜唤鍙煡锛夛紝鍙﹀姞銆屾寜骞淬�嶆ā寮忥紙閫夊勾浠解啋鍏ㄥ勾 12 涓湀+绱+鍚屾瘮锛夛紝涓嶅仛棰勭敓鎴愩��
+2. 鍒嗗競宸炴槑缁嗛噷鐨勫煄闄呭煄涔″叕浜�/宸℃父鍑虹/缃戠害杞︼細**淇濈暀鎸囨爣琛屼絾鐣欑┖**锛岃〃灏惧姞璇存槑琛岋紙鏁版嵁寰呭煄甯傚杩愭ā鍧楁帴鍏ワ級銆�
+3. 杩愭斂杞﹁締鏁帮細闅忔梾瀹㈡湀鎶ヤ竴璧峰鍏ワ紙鍚屾枃浠剁浜� sheet锛夛紝鎸夋姤琛ㄦ湡瀛樺偍銆侀噸澶嶅鍏ヨ鐩栵紱瀹℃牳鎸夊悓鎶ヨ〃鏈熷尮閰嶃��
+
+### 鏈疆宸插畬鎴愶紙鍏ㄩ儴缁忔牱渚嬫暟鎹仈璋冮獙璇侊級
+1. **鏁版嵁搴�**锛坉ocs/init.sql 杩藉姞銆屽叓銆佸叕璺梾瀹㈣繍杈擄紙H203-1锛夈�嶆锛夛細
+   - 鏂拌〃 h2031_enterprise_monthly锛�28 瀛楁锛屽榻愭牱渚嬪垪锛夈�乸assenger_auth_vehicle锛堜紒涓氫唬鐮�/鍚嶇О/杞﹁締鏁�/杞藉浣嶆暟锛�
+   - 棰勭疆 H2031 瑙勫垯 8 鏉★紙RULE_P001~P008锛宺eport_type=H2031锛沜ompare_type 鏂板 MOM_EQ_ZERO/MOM_PCT/MOM_EQ/EXISTENCE锛�
+2. **鍚庣**锛�
+   - 瀵煎叆锛歚DataImportService.importPassengerMonthly`锛坰heet0 鏈堟姤鎸夎〃澶磋В鏋� + sheet1 杩愭斂鍥哄畾 4 鍒楋紱骞傜瓑瑕嗙洊锛涘彧娓� H2031 瑙勫垯瀹℃牳缁撴灉锛屼笉璇垹璐ц繍缁撴灉锛夛紱`/api/import/passengerMonthly`
+   - 瀹℃牳锛歚AuditEngineService.executePassengerAudit`锛�4 缁勮鍒欙細杩愭斂 DIFF 杞﹁締卤3/杞藉浣嵚�50锛涘杩愰噺+鍛ㄨ浆閲忕幆姣�=0锛涘钩鍧囪繍璺濈幆姣斅�10%锛涘寘杞﹀钩鍧囪繍璺�=涓婃湀锛涙棤鐝嚎/鏃犲寘杞︽湁閲忕煕鐩撅級锛沗/api/audit/execute|results` 澧炲姞 reportType 鍙傛暟锛圚2032/H2031锛�
+   - 鎶ヨ〃锛歚ReportExportService.exportPassengerCityDetail(period, mode)`锛坢onth/year 涓ょ妯″紡锛涘綋骞� 1-N 鏈堟垨鍏ㄥ勾 12 鏈� + 绱 + 鍚屾瘮锛涜嚜鍔ㄥ甫鍑鸿繎 3 骞村尯鍧楋紱鍏ㄧ渷=17 甯傚窞姹囨�伙紱瀹㈣繍閲�/鍛ㄨ浆閲忔崲绠椾竾浜恒�佸钩鍧囪繍璺�=鍛ㄨ浆閲�/瀹㈣繍閲忓姞鏉冿級锛沗/api/report/export/passengerCityDetail?period=&mode=`
+   - 鏁版嵁鏌ョ湅锛歚DataViewController` 鏂板 h2031 / passengerAuth 绫诲瀷
+3. **鍓嶇**锛氭暟鎹鍏ュ姞銆孒203-1鏃呭鏈堟姤銆嶏紱鏁版嵁鏌ョ湅鍔犮�孒203-1鏃呭鏈堟姤/鏃呭杩愭斂杞﹁締銆嶏紱瀹℃牳缁撴灉鍔犳姤琛ㄧ被鍨嬪垏鎹紱鎶ヨ〃鐢熸垚鍔犳梾瀹㈠尯鍧楋紙鏈堟姤/骞存姤鍒囨崲锛夛紱宸ヤ綔鍙版枃妗堟洿鏂�
+4. **鏂囨。**锛歞ocs/database.md 琛� H203-1 涓よ〃 + 14 鏉¤鍒� + 鏃呭鎶ヨ〃琛�
+
+### 鑱旇皟缁撴灉锛堟牱渚� 2026 骞� 6 鏈堬紝769 瀹朵紒涓氾級
+- 瀵煎叆锛�769 琛屾垚鍔燂紙5 鏈�/6 鏈堝悇涓�娆★紝5 鏈堜负妯℃嫙涓婃湀鏁版嵁锛�
+- 瀹℃牳 2026-06锛氬叡 2057 鏉¢棶棰橈紝6 绫昏鍒欏叏閮ㄨЕ鍙戦獙璇侀�氳繃锛堣繍鏀垮姣� 67/110銆佺幆姣�=0 鍚� 737銆佸寘杞﹀钩鍧囪繍璺�=涓婃湀 397銆佹棤鍖呰溅鏈夐噺 4銆佸钩鍧囪繍璺濈幆姣� 5锛�
+- 鎶ヨ〃锛氭湀鎶� 94 鍒� / 骞存姤 106 鍒楋紝绱=5+6 鏈堛�佸悓姣斾笌鍘诲勾鍖哄潡姝g‘銆佽鏄庤鍦�
+- 鍓嶇 4 涓� chunk 宸插寘鍚柊椤甸潰锛坖s/3,4,5,7.js锛�
+
+### 鐜閲嶈鍙樺寲锛堝姟蹇呰褰曪級
+- 鏈満 MySQL 5.7锛圖:\mysql\mysql-5.7.41-winx64锛夊師鏉ユ病鍦ㄨ窇锛屼笖 root 瀵嗙爜涓庨厤缃笉绗︼紱宸茬敤 --skip-grant-tables 閲嶇疆 root 瀵嗙爜涓� `root` 骞舵甯稿惎鍔ㄥ湪 **3305**锛堜笌 application.yml 涓�鑷达級銆�
+- traffic_audit 搴撳師鏉ユ槸鏃犲叧鏃х粨鏋勶紙audit_results/reports 绛� 5 寮犺〃锛夛紝宸叉寜 README 娴佺▼閲嶈窇瀹屾暣 docs/init.sql 閲嶅缓椤圭洰搴擄紱閬楃暀鏃ц〃 audit_results/audit_rules/report_data/reports 鏈垹锛堟棤瀹筹級銆�
+- 鍚姩锛歚powershell -NoProfile -ExecutionPolicy Bypass -File start-dev.ps1`锛堟矙绠卞唴鐩存帴 ProcessStartInfo 璧峰悗鍙拌绛栫暐鎷︽埅锛屽繀椤昏蛋 start-dev.ps1锛夈��
+
+### 宸茬煡杈圭晫 / 寰呭姙
+- H203-1 浼佷笟瑙i噴椤碉紙ExplainReview锛変粛鍙垪 H2032锛涙梾瀹� sheet 鏃犳牳瀹炶В閲婂垪锛屾殏涓嶆帴瑙i噴娴佺▼锛堥渶姹傛湭瑕佹眰锛夈��
+- 瀹℃牳缁撴灉鎸� reportType 杩囨护鍦ㄥ墠绔垏鎹紱audit_result.report_id 鍚屾椂鍙寚鍚� h2032/h2031锛宎ttachInfo 宸插吋瀹瑰弻琛ㄣ��
+- 涓嬩竴姝ワ細鑳借�� H204锛堣溅杈嗙矑搴︼紝闇�瀹℃牳寮曟搸琛� CROSS_TABLE 璺ㄨ〃寮曠敤璐ц繍鏈堟姤锛夆啋 鍩庡競瀹㈣繍锛圱REND/RATIO_CHANGE 瑙勫垯 + 閮ㄧ骇涓嬪彂鏁版嵁婧愶級鈫� 鎶曡祫锛堝厛瀹氬鎺ユ姇璧勭郴缁� vs 瑙f瀽甯傚窞 Excel锛夈��
+- 寰呯敤鎴风‘璁ゅ悗鎻愪氦 Git锛堜粨搴� Gitee 绉佹湁锛岃绗節鑺傦級銆�
+
+### 2026-08-23 IDEA 鎵嬪姩杩愯鎶ャ�屾壘涓嶅埌鎴栨棤娉曞姞杞戒富绫� com.trafficaudit.TrafficAuditApplication銆�
+- 鏍瑰洜锛氫笉鏄唬鐮侀棶棰樸�侷DEA 鐨� Maven 瀵煎叆/绱㈠紩閲嶅缓灏氭湭瀹屾垚鏃剁偣 Run锛�	raffic-audit-server\target\classes 涓嬫病鏈夌紪璇戝嚭鐨� .class锛堝彧鍓� yml锛夛紝Spring Boot 杩愯閰嶇疆鐨� Make 姝ラ娌′骇鍑猴紝classpath 閲屾病鏈変富绫汇��
+- 淇锛氬懡浠よ鎵ц cmd /c mvn.cmd -q -f traffic-audit-server\pom.xml compile 琛ュ嚭 55 涓� .class锛堝惈 TrafficAuditApplication.class锛夛紱鍋滄帀浜嗗懡浠よ鍚庣閾撅紙cmd 33764 / mvn java 28228 / app java 15952锛岀敱 start-dev.ps1 鍚姩锛夛紝閲婃斁 8090銆�
+- 鐢ㄦ埛鍐嶇偣 IDEA 缁胯壊 鈻� 鍗冲彲锛涜嫢浠嶅け璐ワ細Build 鈫� Rebuild Project锛屾垨 File 鈫� Invalidate Caches and Restart銆�
+- 娉ㄦ剰锛氱敤鎴疯创鐨勮繍琛屽懡浠ら噷 Java 璺緞鏄剧ず jdk1.8.0\_202锛堝甫涓嬪垝绾匡級鏄鍒舵樉绀鸿宸紝瀹為檯 JDK 鏄� C:\Program Files\Java\jdk1.8.0_202锛堝瓨鍦紝鏃犻渶鏀癸級銆�
+- 鍓嶇 8080锛坣ode 31196/24168锛変粛鍦ㄨ窇锛屼唬鐞嗘寚鍚� 8090锛汭DEA 鍚庣涓�璧峰嵆鍙敤銆�
+
+### 2026-08-23 涓嬪崍浼氳瘽锛欼DEA 璺戦�� + 鏁版嵁鏌ヨ澶辫触鎺掓煡锛堝凡瑙e喅锛�
+**鏈�缁堢姸鎬侊細椤圭洰鍙甯镐娇鐢紝鏃呭杩愯緭鏁版嵁鍙鍏�**
+- 鍚庣鏈�缁堝湪 **IntelliJ IDEA** 閲岃窇閫氾細鐢ㄦ埛鎵嬪姩鍦� IDEA 鎵撳紑椤圭洰鍚庯紝閫氳繃 UI 鑷姩鍖栬Е鍙戣繍琛岋紙Shift+F10锛夛紝鎴愬姛鍚姩 `TrafficAuditApplication`锛堟渶缁堣繘绋� PID 9188锛�14:42 鍚姩锛�8090 鐩戝惉锛夈�傛鍚庣敤鎴峰彲鍦� IDEA 閲岀洿鎺ョ偣 鈻�/鈻� 绠$悊鍚庣銆�
+- 鍓嶇锛�8080 涓�旀帀绾匡紝宸茬敤 `npm run serve` 閲嶆柊鎷夎捣锛坄traffic-audit-web/frontend.log` 璁板綍锛孭ID 3324锛夛紝`/api` 浠g悊姝e父銆�
+- 鐜纭锛欼DEA 璺緞 `D:\Development_Tool\IntelliJ IDEA 2020.1.1\bin\idea64.exe`锛堟敞鎰忔槸 `Development_Tool` 鏃犱笅鍒掔嚎锛夛紱JDK `C:\Program Files\Java\jdk1.8.0_202`锛汭DEA 杩愯閰嶇疆 `TrafficAuditApplication`锛坢odule=traffic-audit-server锛夊凡瀛樺湪浜� `.idea/workspace.xml`锛屾棤闇�鏂板缓銆�
+
+**鏁版嵁鏌ヨ澶辫触鎺掓煡璁板綍锛堥噸瑕侊紝閬垮厤涓嬩釜浼氳瘽閲嶅韪╁潙锛�**
+- 鐜拌薄锛氬墠绔� `DataView.vue` 鎶ャ�屾暟鎹煡璇㈠け璐ャ�嶏紝鍚庣 `/api/data/list?type=h2032` 绛夊叏閮� 500锛堝彧鏈� default 鍒嗘敮鐨� type 杩斿洖 200锛夈��
+- 鎺掓煡缁撹锛�**涓嶆槸浠g爜/鏁版嵁搴撻棶棰�**銆傚凡鏍稿锛�7 寮犺〃鍏ㄩ儴瀛樺湪涓斾负绌恒�佸疄浣撳瓧娈典笌琛ㄥ垪瀹屽叏鍖归厤锛堣剼鏈�愪竴瀵规瘮锛夈�丼QL 閫昏緫姝e父銆�
+- 楠岃瘉鏂规硶锛氫复鏃跺懡浠よ璧� 8091 璇婃柇瀹炰緥锛坄mvn spring-boot:run` 鍔� `-Dspring-boot.run.arguments="--server.port=8091 ..."`锛屽弬鏁板繀椤荤敤寮曞彿鍖呰9銆佺┖鏍煎垎闅旓級锛屽悓涓�浠戒唬鐮佹墍鏈夌被鍨嬫煡璇㈠叏閮� 200 鈫� 鎺掗櫎浠g爜/DB銆�
+- 缁撴灉锛氫箣鍚� IDEA 瀹炰緥锛堝悓涓� PID 9188 鏈噸鍚級鎵�鏈夌被鍨嬩篃鎭㈠ 200锛屽垽鏂负**鍚姩鍒濇湡鐨勪复鏃跺紓甯�**锛堝彲鑳戒笌 IDEA Make 澧為噺缂栬瘧/classpath 灏辩华鏃跺簭鏈夊叧锛夛紝鏃犻渶淇浠g爜銆�
+- 宸查獙璇佺被鍨嬶細`h2032 / transportAuth / trackMileage / scaleSplit / freightTurnover / h2031 / passengerAuth`锛�8090 鐩磋繛鍜� 8080 鍓嶇浠g悊鍧� 200銆傝瘖鏂疄渚嬪凡鍋滐紙8091 鏃犳畫鐣欒繘绋嬶級銆�
+- 鑻ュ啀閬囧埌鍚岀被 500锛氬厛鐩存帴 curl `http://localhost:8090/api/data/list?type=h2032` 鍒ゆ柇鍚庣鐘舵�侊紱鍚庣娌¢棶棰樺氨鍒锋柊椤甸潰锛涘繀瑕佹椂璁╃敤鎴峰湪 IDEA 閲� **Build 鈫� Rebuild Project** 鍚庨噸 Run銆�
+
+**鏈細璇濆凡璁ㄨ銆佸緟涓嬩釜浼氳瘽寮�宸ョ殑鍐呭锛堢敤鎴峰凡纭鏂瑰悜锛�**
+1. 闇�姹傝寖鍥达細璐ц繍瀹℃牳瑙勫垯锛堝凡鏈夛級+ **鍏矾鏃呭杩愯緭锛圚203-1锛屽凡瀹炵幇骞跺彲瀵煎叆锛�** + **鍩庡競鍏氦/鍩庡競瀹㈣繍** + **鎶曡祫妯″潡**锛堢敤鎴峰凡鎶婃ā鏉挎斁鍒� `鎵╁睍琛ㄦā鏉縛锛夈��
+2. 鎶ヨ〃浣撻獙闇�姹傦紙鐢ㄦ埛闂 1锛夛細甯屾湜**鎸夊勾/鎸夋湀鍔ㄦ�佸嚭琛�**鈥斺�斾笉棰勭敓鎴愭墍鏈夎〃锛岃�屾槸鐢ㄦ埛鏌ヨ鍝勾鍝湀灏辩敓鎴愬摢绉嶈〃锛屽弬鑰冩姤琛ㄥ鍑虹幇鏈夋ā寮忓仛鍔ㄦ�佸弬鏁板寲锛堟湀鎶�/骞存姤宸插湪鏃呭妯″潡瀹炵幇 `passengerCityDetail?period=&mode=`锛屽彲浣滄牱鏉匡級銆�
+3. 鍔熻兘 2/3锛堢敤鎴烽棶棰� 2銆�3锛夛細鏁翠綋鎬濊矾鍙傝�冭揣杩愭ā鍧楃殑鍋氭硶锛堝鍏モ啋瀹℃牳鈫掓姤琛� 鍚岄摼璺級锛屽叿浣撶粏鑺傚緟鏂颁細璇濇寜 `docs/鎵╁睍妯″潡璇勪及.md` 灞曞紑銆�
+4. 绔炲搧/寮�婧愬姣斿凡浜у嚭锛歚docs/寮�婧愰」鐩姣�.md`锛堢粨璁猴細鏃犵洿鎺ュ彲澶嶇敤鐨勫紑婧愮珵鍝侊紝鑷爺+妯″潡鍖栨墿灞曟槸鍚堢悊璺嚎锛夈��
+5. 浠g爜浠嶆湭鎻愪氦 Git锛圙itee 绉佹湁浠撳簱锛夆�斺�斿緟鐢ㄦ埛纭鍚庢彁浜ゃ��
+
+**涓嬩釜浼氳瘽寤鸿浠庡摢寮�濮�**
+- 鍏堣鐢ㄦ埛纭浼樺厛绾э細a) 鍔ㄦ�佸勾/鏈堟姤琛ㄧ敓鎴愶紙浣撻獙浼樺寲锛屾敼鍔ㄩ泦涓湪鎶ヨ〃瀵煎嚭锛夆啋 b) 鍩庡競鍏氦/鍩庡競瀹㈣繍妯″潡 鈫� c) 鎶曡祫妯″潡銆�
+- 鑻ヤ細璇濆啀娆″彉闀匡紝缁х画鎶婅繘灞曡拷鍔犲埌鏈枃浠舵湯灏惧苟鎻愰啋鐢ㄦ埛寮�鏂颁細璇濄��
+
+### 2026-08-23 鏅氶棿浼氳瘽锛氭梾瀹腑鍙e緞 3 琛� + 鑳借�� H204 妯″潡锛堝凡瀹屾垚骞堕獙璇侊級
+**闇�姹傦紙鐢ㄦ埛鍘熻瘽锛�**
+1. 銆婂叕璺梾瀹㈣繍杈撴湀搴︾敓浜ф儏鍐点�嬫牴鎹�婂叕璺梾瀹㈡湀鎶ャ�嬬敓鎴愶細鍏矾鏃呭鍒嗗競宸炴槑缁嗭紙宸叉湁锛�+ **涓彛寰勬槑缁嗐�佷腑鍙e緞鎺掑悕銆佷腑鍙e緞鍒嗘瀽**锛堟湰娆℃柊澧烇級銆�
+2. 銆婇亾璺揣杩愯溅杈嗚兘婧愭秷鑰楁儏鍐碉紙2026骞达級銆嬫牴鎹�婄浜屽搴﹁揣杞﹁兘鑰楁槑缁嗐�嬩笌銆婅繍鏀胯溅杈嗕俊鎭�嬪搴斿瓧娈垫牎楠岋紝**鏈夎鎶ュ嚭鎻愰啋**锛涙棤璇垯鐢熸垚銆婅兘杩愭眹鎬昏〃銆嬨��
+
+**宸插畬鎴愶紙鍚庣宸茬紪璇戯紝绔埌绔獙璇侀�氳繃锛�**
+- 鏁版嵁瀵煎叆锛歚DataImportService.importEnergyMonthly`锛堣兘鑰楁槑缁� sheet0锛氭瘡琛� 5 缁劽�18 鍒楋紝鍒楄捣鐐� 0-based 3/21/39/57/75锛涜繍鏀� sheet1 鍥哄畾 5 鍒楋紱鐮佽〃杞腑鏂囷紱骞傜瓑鍒犻櫎鍚� period 涓よ〃+H204 瀹℃牳缁撴灉锛涘け璐ュ甫琛屽彿锛夛紝绔偣 `POST /api/import/energyMonthly`銆傚疄浣� `EnergyVehicleQuarterly` 鐨� `fuelPer100km` 鍔犱簡 `@TableField("fuel_per_100km")`锛堣〃鍒楀悕甯︿笅鍒掔嚎锛屾浘瀵艰嚧瀵煎叆鍏ㄥけ璐ワ級銆�
+- 瀹℃牳锛歚AuditEngineService.executeEnergyAudit`锛�4 鏉¤鍒欏凡鍏ュ簱锛歊ULE_E001/E002 杞﹁締绫诲瀷/鐕冩枡绫诲瀷 CROSS_DIFF銆丷ULE_E003 鏍囪鍚ㄤ綅 CROSS_DIFF threshold=0.05 涓旂壍寮曡溅(鐮�11)鐢ㄥ噯鐗靛紩璐ㄩ噺銆丷ULE_E004 EXISTENCE_AUTH 杞︾墝缂哄け锛夛紱`AuditController` 宸插姞 H204 鍒嗘敮銆傛牱渚嬬粨鏋� 577 鏉★紙鍚ㄤ綅 383銆佽繍鏀跨己澶� 192銆佽溅杈嗙被鍨� 2锛夈��
+- 姹囨�昏〃锛歚ReportExportService.exportEnergySummary(period)` 鈫� 鍏ㄧ渷 + 17 甯傚窞鍚勪竴寮� sheet锛堟寜鐕冩枡绫诲瀷鍒� 6 琛� + 鍚堣锛涘钩鍧囧惃浣�=鎬诲惃浣�/杞﹁締鏁般�佸钩鍧囪繍璺�=鍛ㄨ浆閲�/璐ц繍閲忋�佺櫨鍏噷鍗曡��=娑堣�楅噺/鎬昏绋�*100銆佸懆杞噺鍗曡��=娑堣�楅噺/鍛ㄨ浆閲�*100*绯绘暟锛氭煷娌�0.86*1.4571銆佹苯娌�0.73*1.4714銆丩NG/CNG 1.7572銆佺函鐢�0.1229銆佹阿0.3329*12.1951锛夈�傛牱渚嬪鍑轰笌妯℃澘涓�鑷达紙浠呮�昏绋�/杞借揣閲岀▼姣旀ā鏉跨ず渚嬪皯 34锛岀粡婧愭枃浠剁洿鎺ユ眰鍜岀‘璁ゆ槸妯℃澘鏃ф暟鎹紝闈炰唬鐮侀棶棰橈級銆�
+- 鏃呭涓彛寰勶細`exportPassengerMidDetail/Rank/Analysis(period, mode)`锛坢onth/year 鍔ㄦ�佸嚭琛紱鏄庣粏=瀹㈣繍閲忓潡+鍛ㄨ浆閲忓潡鍚� 18 鍦板尯脳5 鎸囨爣锛涙帓鍚�=宸﹀潡褰撴湀+鍙冲潡 1-N 鏈堢疮璁★紝鍙屽潡鍧囦负瀹㈣繍閲�/鍛ㄨ浆閲�+鎺掑悕/鍚屾瘮/澧為�熸帓鍚嶏紱鍒嗘瀽=鍏ㄧ渷绱+鍚屾瘮+鍗犳瘮锛夛紝绔偣 `/api/report/export/passengerMidDetail|MidRank|MidAnalysis`銆�
+- 鍓嶇锛歚ReportExport.vue` 鏃呭鍗$墖鍔� 3 涓腑鍙e緞鎸夐挳 + H204 鑳借繍姹囨�诲崱鐗囷紱`DataImport.vue` 鍔� `energyMonthly` 瀵煎叆绫诲瀷锛沗AuditResult.vue` 鍔� H204 鎶ヨ〃绫诲瀷銆�
+- 鏂囨。锛歚docs/init.sql` 杩藉姞銆屼節銆丠204銆嶆锛堜袱琛� DDL + 4 鏉¤鍒欙級銆乣docs/database.md` 琛� h204 涓よ〃璇存槑锛沗HANDOFF.md` 鏈銆�
+- 淇锛歚AuditEngineService.attachInfo` 鏀逛负鎸夎鍒� reportType 鍒嗚〃鍥炲~浼佷笟鍚嶇О锛坔2032/鏃呭/H204 涓夎〃 id 鍚勮嚜鑷锛屽師鏉� putIfAbsent 浼氫覆浼佷笟鍚嶏級銆�
+
+**褰撳墠杩愯鐘舵�侊紙23:31锛�**
+- 鍚庣锛氬懡浠よ `start-dev.ps1` 鍚姩锛�8090锛屾棩蹇� `backend.log`锛孭ID 閾� cmd鈫抦vn鈫抝ava锛夈��**IDEA 瀹炰緥宸蹭笉鍦�**锛堟棤 java 杩涚▼锛夛紝鐢ㄦ埛鑻ヨ鍥� IDEA 闇� **Build 鈫� Rebuild Project** 鍚庡啀 Run銆�
+- 鍓嶇锛歚npm run serve` 閲嶅惎锛�8080锛孭ID 15900锛屾棩蹇� `traffic-audit-web\frontend.log`锛夈��
+- DB 宸叉湁 H204 鏍蜂緥鏁版嵁锛�1284 杈� + 1114 杩愭斂锛宲eriod=2026-06锛変笌 577 鏉″鏍哥粨鏋滐紝椤甸潰鍙洿鎺ユ煡鐪嬨��
+- 涓存椂鏂囦欢锛氶」鐩牴 `energy-test.log`锛�8091 璇婃柇鏃ュ織锛変繚鐣欐湭鍒狅紱涓存椂 py 鑴氭湰鍦� `%TEMP%\check_*.py`銆�
+
+**宸茬煡杈圭晫/鍐崇瓥**
+- 涓彛寰勭殑鍩庨檯鍩庝埂鍏氦/宸℃父鍑虹/缃戠害杞︽棤鏁版嵁婧愶紙绛夊煄甯傚杩愭ā鍧楋級锛屾槑缁�/鎺掑悕/鍒嗘瀽涓繖浜涜鐣欑┖锛屽叕璺彮绾�=鏃呭鏈堟姤涓�~鍥涚被+鍖呰溅鍚堣锛涙��=鍥涜�呬箣鍜岋紙褰撳墠=鍏矾鐝嚎锛夈��
+- 鑳借�楄溅杈嗙被鍨嬬爜鈫掍腑鏂囬噰鐢ㄥ弽鎺ㄧ爜琛紙01 鏅��/02 骞虫澘/03 浠撴爡/04 鍘㈠紡/06 缃�/07 鐗规畩缁撴瀯/08 鑷嵏/09 杞﹁締杩愯緭杞�/11 鍗婃寕鐗靛紩杞︼級锛涘鏍稿姣旀椂鍏堝幓鎺夎繍鏀�"閲�/涓�/杞诲瀷"鍓嶇紑鍐嶆寜鍏佽闆嗗悎鍖归厤锛岄伩鍏嶈鎶ャ��
+- 鑳借�楁眹鎬诲彧缁熻鑳借�楁槑缁嗗叏閮ㄨ溅杈嗭紙鍚繍鏀跨己澶辩殑锛夛紝"杩愭斂缂哄け"鐢卞鏍告彁閱掕�岄潪鍓旈櫎銆�
+
+**涓嬩釜浼氳瘽寤鸿**
+- 鍩庡競瀹㈣繍锛堝叕浜�/鍑虹/缃戠害杞︽暟鎹簮锛夆啋 鎶曡祫妯″潡銆傝嫢鐢ㄦ埛鍥� IDEA 杩愯锛岄渶鍏� Rebuild銆�
+
+### 2026-08-24 浼氳瘽锛氭姇璧勬ā鍧楀疄鎷� + 瀹炴柦鏂规锛堝緟鐢ㄦ埛纭鍚庡紑宸ワ級
+**浜у嚭**
+- 宸插疄鎷� `docs/鎵╁睍琛ㄦā鏉�/鎶曡祫/` 鍏ㄩ儴鏍蜂緥锛堣緭鍑� 4 寮犻瀵艰〃銆佹姇璧勭郴缁熷鍑恒�佸競宸炰笂鎶ュ杩�/鐗╂祦鎶ヨ〃銆佸叏鐪佹眹鎬讳腑闂磋〃锛夛紝缁撹涓庢柟妗堣 **`docs/鎶曡祫妯″潡瀹炴柦鏂规.md`**锛堟柊澧烇級銆�
+- 鍏抽敭鍙戠幇锛�
+  - 鎶曡祫绯荤粺瀵煎嚭锛坄鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7).xls`锛�8 鍒楁渶骞插噣锛屼絾**鏃犲競宸炪�佹棤杩涘害/鎵瑰瀛楁**锛屽彧鑳藉仛瀹℃牳姣斿鍩哄噯銆�
+  - 甯傚窞涓婃姤琛ㄦ牸寮忎笉缁熶竴锛堝垪鏁� 18~24銆�.xls/.xlsx/.et/.pdf 娣峰瓨銆佹爣棰樿浣嶇疆涓嶄竴锛夛紝闇�瀹归敊瑙f瀽銆�
+  - 鍏ㄧ渷姹囨�讳腑闂磋〃锛坄6鏈�/7鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx`銆乣6鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls`锛�**鏍煎紡缁熶竴**锛屽缓璁竴鏈熶富鍏ュ彛銆�
+  - 4 寮犺緭鍑鸿〃 = 鍚屼竴鎵归」鐩� 4 绉嶈瑙掞紙鍏ㄧ渷棰勫畨鎺掕鍒� / 鍗佷簲浜旂墿娴侀噸鐐� / 鈮�1浜胯涓� / 缁忔祹寮哄幙锛夛紝鎶戒竴濂楅」鐩富妗e嵆鍙鐢ㄣ��
+- 鏂规瑕佺偣锛氫袱鏈熻惤鍦帮紙M1 鍏ㄧ渷姹囨�诲ぇ琛ㄥ鍏�+4 寮犳姤琛紱M2 甯傚窞鍗曡〃瀹归敊瑙f瀽+绯荤粺瀵煎嚭姣斿瀹℃牳锛夛紱鏁版嵁妯″瀷 `investment_project` + `investment_monthly` 涓ゅ紶琛紱瀹℃牳瑙勫垯 report_type=INVEST锛圕ROSS_DIFF/RANGE/LOGIC/NULL_CHECK锛夛紱4 涓鍑虹鐐� `investPlan/investFiveYearLogistics/investBillion/investCounty`銆�
+
+**寰呯敤鎴风‘璁わ紙鏄庡ぉ寮�宸ュ墠锛�**
+1. 涓绘暟鎹簮锛氬厛"鍏ㄧ渷姹囨�诲ぇ琛�"锛圡1锛夎繕鏄洿鎺�"甯傚窞鍗曡〃瑙f瀽"锛�
+2. 绯荤粺瀵煎嚭鏄惁姣忔湀閮芥湁銆佽兘鍚︽彁渚涢」鐩�-甯傚窞瀵圭収锛堝鍑烘棤甯傚窞瀛楁锛夛紵
+3. 浜垮厓椤圭洰鍙e緞 鈮�10000 涓囨槸鍚﹀噯纭紵缁忔祹寮哄幙鏄惁鏈夊浐瀹氬悕鍗曪紵
+
+**褰撳墠杩愯鐘舵��**
+- 鍚庣 8090锛坰tart-dev.ps1 鍚姩锛岃窇鍚� H204 鐨勬柊浠g爜锛夈�佸墠绔� 8080锛坣pm run serve锛孭ID 15900锛夊潎鍦ㄨ窇锛汬204 鏍蜂緥鏁版嵁涓� 577 鏉″鏍哥粨鏋滃湪搴撱��
+- 涓存椂鏂囦欢锛氶」鐩牴 `energy-test.log` 淇濈暀锛沗%TEMP%\pyxlrd`锛坸lrd 2.0.2 涓存椂瀹夎锛夈�乣%TEMP%\dump_xls*.py`銆乣%TEMP%\check_*.py` 淇濈暀銆�
+- 鐜澶囨敞锛氱郴缁� python 鐨� site-packages 鏃犲啓鏉冮檺锛岃搴撻渶 `pip install --target %TEMP%\pyxlrd` 鍚� `sys.path.insert`銆�
+
+**涓嬩釜浼氳瘽寤鸿**
+- 鍏堣鐢ㄦ埛纭涓婇潰 3 涓棶棰� 鈫� 鎸� `docs/鎶曡祫妯″潡瀹炴柦鏂规.md` 寮�宸� M1锛堝叏鐪佹眹鎬诲ぇ琛ㄥ鍏� + 4 寮犳姤琛紝鐢� 6/7 鏈堟牱渚嬫牳瀵癸級銆�
+- 鑻ョ敤鎴锋兂鍏堝仛鍩庡競瀹㈣繍锛屽垯闇�鍏堢‘璁ょ綉绾﹁溅鎷嗗垎鍙e緞涓庨儴绾ф暟鎹簮锛堟鍓嶇粨璁猴細鎶曡祫鍦ㄥ墠銆佸煄甯傚杩愬湪鍚庯級銆�
+
+### 2026-08-24 鏅氫細璇濓細鎶曡祫妯″潡 M1 钀藉湴楠岃瘉 + 瀹℃牳鍖归厤璇姤淇
+**鑳屾櫙**锛氱敤鎴风‘璁� 鈶犱富鏁版嵁婧愬厛鎸夆�滃叏鐪佹眹鎬诲ぇ琛ㄢ�濆仛锛涒憽甯傚窞灏忚〃涓昏鏇存柊 鑷紑濮嬪缓璁剧疮璁�/鑷勾鍒濈疮璁�/鏈湀瀹屾垚/寤鸿闃舵/褰㈣薄杩涘害 瀛楁锛屽苟涓庛�婃煡璇㈢粨鏋�(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7)銆嬬浉搴斿瓧娈垫牎楠岋紱鈶�4 寮犺緭鍑鸿〃涓哄浐瀹氭牸寮忛」鐩��
+**宸插畬鎴愶紙M1锛�**
+- 鏁版嵁妯″瀷锛歚investment_project`(148) + `investment_monthly`(148, 2026-07) + `investment_system`(148) 涓夎〃锛涘鍏ョ被鍨�=鎶曡祫鏈堟姤锛堝叏鐪佹眹鎬诲ぇ琛級+鎶曡祫绯荤粺瀵煎嚭銆�
+- 4 寮犳姤琛ㄥ鍑虹鐐瑰凡璺戦�氾細`investPlan`(棰勫畨鎺掕鍒掕繘搴︽眹鎬� 247琛�)銆乣investFive`(鍗佷簲浜旂墿娴� 54琛�)銆乣investBillion`(浜垮厓椤圭洰 2sheet锛氭姇璧勬�昏〃+瑙勪笂椤圭洰琛� 56椤�)銆乣investCounty`(缁忔祹寮哄幙 45琛�)锛涙牱渚嬫枃浠跺湪 `%TEMP%\invest_*.xlsx`銆�
+- 瀹℃牳瑙勫垯 4 鏉★紙RULE_I001~I004锛宺eport_type=INVEST锛夈��
+**鎶ヨ〃鍐呭鏍稿缁撹锛堜笌 docs/鎵╁睍琛ㄦā鏉�/鎶曡祫/杈撳嚭/ 7鏈堟牱渚嬫瘮瀵癸級**
+- 缁忔祹寮哄幙琛細41 椤圭洰閫愯涓庢牱渚嬩竴鑷达紙鎬绘姇璧�/鑷紑濮嬬疮璁�/鏈勾璁″垝/鑷勾鍒濈疮璁�/鏈湀瀹屾垚 0 宸紓锛夈��
+- 棰勫畨鎺掕鍒掕〃锛氬唴閮ㄨ嚜娲斤紙29 甯傚窞灏忚=鏄庣粏锛屽杩�+鐗╂祦=鍚堣锛夛紱83 涓悓鍚嶉」鐩暟鍊间粎 5 澶勨��2026骞磋鍒掆�濆樊寮傦紙涓规睙鍙e彸宀�/涓规睙鍙f煈妗�/鍐涘北鐭夸笟/鍚屾床鏅烘収/绁炲啘鏋剁墿娴佸洯锛屾簮浜庢眹鎬诲ぇ琛ㄤ笌甯傚窞琛ㄦ暟鎹笉鍚岋級锛涚敓鎴� 149 椤圭洰 vs 鏍蜂緥 167锛堣鐩栧樊寮傦級銆�
+- 鍗佷簲浜旂墿娴佽〃锛�35 椤圭洰鏁板�间笌鏍蜂緥 0 宸紓锛涙牱渚嬪彟鏈� 28 椤圭洰涓嶅湪姹囨�诲ぇ琛ㄤ腑锛堝緟浜屾湡甯傚窞鍗曡〃琛ワ級銆�
+- 浜垮厓椤圭洰琛細56 椤圭洰鍏ㄩ儴 鈮�1浜� 绛涢�夋纭�佸悎璁¤嚜娲斤紱涓庢牱渚� 7 澶勬暟鍊煎樊寮傚潎涓�**鏍蜂緥鑷韩濉敊**锛堟牱渚嬫妸 鑷紑濮嬬疮璁�/1-7鏈堢疮璁� 璇~鍒� 鎬绘姇璧�/鍏ㄥ勾璁″垝 鍒楋紝鐢熸垚琛ㄧ敤姹囨�诲ぇ琛ㄦ纭�硷級锛涙湀搴﹁鍒�/瀹屾垚鍒楁牱渚嬫湁銆佺敓鎴愯〃鐣欑┖锛堟簮鏁版嵁鏃狅級銆�
+**瀹℃牳鍖归厤璇姤淇锛圓uditEngineService 鎶曡祫閮ㄥ垎锛�**
+- 淇鍓� 15 鏉¢棶棰樹腑 4 鏉¤法琛ㄥ樊寮備负璇姤锛氣憼瀛濇劅涓滃煄瀹㈣繍鎹箻涓績(鏇村悕涓衡��) 琚ā绯婂尮閰嶉敊閰嶅埌 閮цタ鍘垮杩愭崲涔樹腑蹇冿紱鈶″捀涓板幙缁煎悎鐗╂祦鍥� 涓� 缃楃敯鍘跨患鍚堢墿娴佸洯 褰掍竴鍖栧悗鍚屽悕(鈥滅患鍚堢墿娴佸洯鈥�)瀵艰嚧閿欓厤銆�
+- 淇鍐呭锛歴ysMap 鏀逛负 褰掍竴鍖栧悕鈫掑�欓�夊垪琛紱绮剧‘鍛戒腑鎸夊幙鍖烘秷姝э紱妯$硦鍖归厤鏀光�滆瘎鍒嗗彇鏈�浼樷�濓紙鍓嶇紑>涓悗閮ㄥ寘鍚�>5瀛�>瀛愬簭鍒楋級锛屼笉鍐嶉鍛戒腑杩斿洖锛涘綊涓�鍖栧鍔� 鈪�/II 缁熶竴锛涙柊澧炲瓙搴忓垪鍏滃簳锛堢患鍚堟灑绾解啍缁煎悎瀹㈣繍鏋㈢航銆佺墿娴佷腑蹇冣啍鐗╂祦鍥腑蹇冦�佹櫤鎱�/浜岀骇绔欑瓑鎻掑叆璇嶏級銆�
+- 淇鍚庯細RULE_I001/I002/I003 鍏ㄩ儴 0 璇姤锛涚己澶辨彁閱� 11鈫�5锛堜腹姹熷彛鍙冲哺/姹夊窛鍖楃珯/閫氬北鍩庤タ/鍥借儨鏅烘収/灏忔渤娓尯鈪℃湡/澶╅棬 6 鏉″凡姝g‘鍖归厤涓旀暟鍊煎叏鍚岋紱鍓╀綑 5 鏉′负鐪熷疄缂哄け闇�浜哄伐鏍稿疄锛氶儜涓囬珮閾佸反涓滅患鍚堝杩愭灑绾姐�佸攼鐧芥渤娓暎璐ц浆杩愪腑蹇冮」鐩�佹箹鍖楅暱姹熺幇浠g墿娴佷骇涓氶泦鑱氱ず鑼冨尯路鐜颁唬鐗╂祦鍥�佸紭涓氱幇浠g墿娴佷骇涓氬洯銆佸磭闃冲幙鍥藉绾х患鍚堢墿娴佹灑绾藉強閰嶅璁炬柦寤鸿椤圭洰锛夈��
+**褰撳墠杩愯鐘舵��**
+- 鍚庣 8090锛坰tart-dev.ps1 閲嶅惎锛孭ID 7668锛屾棩蹇� backend.log锛夈�佸墠绔� 8080锛坣pm run serve锛屽師杩涚▼锛夊潎鍦ㄨ窇锛汬204 涓庢姇璧勬ā鍧楁暟鎹兘鍦ㄥ簱銆�
+- 涓存椂鏂囦欢锛歚%TEMP%\invest_*.xlsx`锛�4 寮犲鍑洪獙璇佷欢锛夈�乣%TEMP%\invest_dump.txt`銆乣%TEMP%\sample_dump.txt`銆乣%TEMP%\sample_plan.txt`銆乣%TEMP%\plan_diff.txt`銆佹牴鐩綍 energy-test.log 鍧囦繚鐣欍��
+**寰呭姙**
+- M2锛�17 甯傚窞鍗曡〃瀹归敊瑙f瀽锛堣ˉ鍏呭崄浜斾簲琛ㄧ己鐨� 28 涓」鐩級+ 甯傚窞灏忚〃鈥滄湰鏈堝畬鎴�/寤鸿闃舵/褰㈣薄杩涘害鈥濆瓧娈垫洿鏂板叆搴撱��
+- 4 寮犳姤琛ㄤ笌 7 鏈堟牱渚嬪樊寮傜偣锛�5 澶� 2026骞磋鍒掋�侀瀹夋帓瑕嗙洊宸紓銆佷嚎鍏冭〃鏈堝害鍒楃暀绌猴級璇风敤鎴锋牳瀵瑰彛寰勩��
+- 浜垮厓琛ㄢ�滄姇璧勬�昏〃鈥漵heet 鐩墠浠呪�滐紙浜旓級鍏朵粬=106.65浜匡紙鏈勾璁″垝鍚堣锛夆�濓紝鍏朵綑琛岀┖鐧斤紝寰呯‘璁ゅ搴︽媶鍒嗗彛寰勩��
+
+### 2026-08-24 娣卞浼氳瘽锛氬煄甯傚杩愬紑宸ヨ璁猴紙缁撹锛氬彲浠ュ仛锛岀瓑鏁版嵁婧愶級
+**鐢ㄦ埛鎻愰棶**锛氱幇鍦ㄦ槸鍚﹀彲浠ュ姞鍩庡競瀹㈣繍妯″潡锛�
+**缁撹**锛氬彲浠ヤ笂锛屼笖姝eソ琛ュ叏鏃呭涓彛寰勬姤琛ㄢ�斺�擧203-1 涓彛寰勬槑缁�/鎺掑悕/鍒嗘瀽閲屼竴鐩寸┖鐫�鐨� 鍩庨檯鍩庝埂鍏氦/宸℃父鍑虹/缃戠害杞� 涓夎锛岀瓑鍩庡競瀹㈣繍鏁版嵁杩涙潵鍗冲彲濉笂銆傛鏋跺凡鐢辨姇璧勬ā鍧楅獙璇侊紙澶氭暟鎹簮瀵煎叆+瀹℃牳+鎶ヨ〃鍏ㄩ摼璺鐢級锛屾棤闇�鏂版灦鏋勩�傛ā鏉� `docs/鍩庡競瀹㈣繍/鍩庡競瀹㈣繍鏁版嵁妯℃澘.xlsx`锛�11 sheet锛氬叕浜�/鍑虹/缃戠害杞�/杞ㄩ亾杞浮/鍩庡競鍒嗗競宸�/涓彛寰勭瓑锛屽惈 2026 骞� 1-7 鏈堝垎甯傚窞鏁板�硷級銆�
+**寮�宸ュ墠闇�鐢ㄦ埛纭锛堟寜浼樺厛绾э級**
+1. 鍏氦銆佸贰娓稿嚭绉熷垎甯傚窞鏈堝害鍘熷鏁版嵁鏉ユ簮锛堝競宸炰笂鎶� or 绯荤粺鍙煡锛夆�斺�旈渶涓�浠界湡瀹炴暟鎹枃浠跺仛瀵煎叆鏍蜂緥锛堟渶澶у墠缃級銆�
+2. 缃戠害杞︽媶鍒嗗彛寰勪笌閮ㄧ骇涓嬪彂鏂囦欢锛堟ā鏉挎媶鍒嗗叕寮忓澶� #REF!锛屾斁鏈�鍚庡仛锛夈��
+3. 鈥滃杩愰噺鍚屾瘮鐜瘮 0% 闇�鏍稿疄鈥濅笟鍔″彛寰勶紙妯℃澘鐩綍鍚屽悕 txt锛夛紝纭鍝簺绠楁甯搁伩鍏嶈鎶ャ��
+**寤鸿瀹炴柦涓夋锛堝厛鏄撳悗闅撅級**锛氣憼鍏氦锛�4 缁勮鍒欙級鍏堣窇閫氬鍏�+瀹℃牳+鍒嗗競宸炴姤琛紱鈶″贰娓稿嚭绉燂紙7 缁勮鍒欙紝琛� 杩愯惀杞︽暟 vs 杩愭斂 瀵规瘮锛屽鐢� H203-1 杩愭斂瀵规瘮锛夛紱鈶㈢綉绾﹁溅鎷嗗垎锛堥儴绾ф暟鎹埌浣嶅悗鍋氾紝椤烘墜琛ヤ腑鍙e緞涓夎〃绌鸿锛夈��
+**宸ヤ綔閲�**锛氬悗绔� 6~10 + 鍓嶇 2~3 浜哄ぉ锛坉ocs/鎵╁睍妯″潡璇勪及.md锛夛紱姣旀姇璧� M2 灏忋��
+**涓嬩竴姝�**锛氱敤鎴锋彁渚涘叕浜ゆ湀搴︽暟鎹枃浠跺悗寮�宸ョ 1 姝ャ��
+
+
+### 2026-08-24 娣卞浼氳瘽锛堢画锛夛細鍩庡競瀹㈣繍绗� 1 姝ワ紙鍏氦锛夎惤鍦�
+**鑳屾櫙**锛氱敤鎴风‘璁ゅ叕浜ゆ湀搴︽暟鎹簮 = `docs/鍩庡競瀹㈣繍/瀵煎叆_鍩庡競鍏叡浜ら�氭湀搴﹁繍钀ユ儏鍐碉紙2026骞�7鏈堬級.xls`锛堝疄涓� xlsx 鍐呭銆�.xls 鎵╁睍鍚嶏紝浼佷笟绾ф槑缁� 143 琛岋紝鍚叕鍏辨苯鐢佃溅/杞ㄩ亾/杞浮涓夌被锛岀 1 姝ュ彧鍙栧叕鍏辨苯鐢佃溅瀛楁锛夛紱杩愭斂鏁版嵁寰呯敤鎴峰悗琛ワ紙灞炲嚭绉熸楠わ級銆�
+**宸插畬鎴愶紙鍏氦 M1锛�**
+- 鏁版嵁妯″瀷锛歚city_bus_monthly` 琛紙浼佷笟绾ф湀搴︼紝44 瀛楁锛夛紱寤鸿〃+瑙勫垯 SQL 瑙� `docs/sql_city_bus.sql`銆�
+- 瀵煎叆锛歚/api/import/cityBus`锛坧eriod=2026-07 鎴愬姛 142 鏉°��0 澶辫触锛涘箓绛夛細鍚屾姤琛ㄦ湡鍏堟竻鏃ф暟鎹笌 CITY_BUS 瀹℃牳缁撴灉锛涘け璐ユ樉绀� Excel 琛屽彿锛夈�傚競宸炵敱 `RegionUtil.cityByCode(鎵�灞炲湴鍖�)` 鏄犲皠鍒� 17 甯傚窞銆�
+- 瀹℃牳锛�4 鏉¤鍒欙紙report_type=CITY_BUS锛宑ompare_type=TREND_CONSISTENCY / RATIO_CHANGE / AVG_DIST_CHANGE锛屽潎涓� YELLOW锛夛細
+  - RULE_B001 鍩庡競鍐呬笌鍩庨檯鍩庝埂瀹㈣繍閲忚秼鍔夸竴鑷达紙鐜瘮/鍚屾瘮鍚勪竴瀵癸紝瓒嬪娍涓嶄竴鑷翠笖澧為��>2%锛�
+  - RULE_B002 瀹㈣繍閲忎笌鏃呭鍛ㄨ浆閲忚秼鍔夸竴鑷达紙鐜瘮/鍚屾瘮鍚勪竴瀵癸紝瓒嬪娍涓嶄竴鑷翠笖澧為��>2%锛�
+  - RULE_B003 鍩庡競鍐�/鍩庨檯鍩庝埂瀹㈣繍閲忓崰姣斾笌涓婃湀鍙樺寲>1%锛堢敤鏂囦欢鍐呯幆姣斿閫熷弽鎺ㄤ笂鏈堝崰姣旓級
+  - RULE_B004 骞冲潎杩愯窛鐜瘮澧炲噺>3 鍏噷锛堟枃浠剁洿璇伙級鎴栧煄闄呭煄涔″钩鍧囪繍璺濆彉鍖�>3 鍏噷锛堢敱 鍛ㄨ浆閲�/瀹㈣繍閲� 鐜瘮鍙嶆帹锛�
+  - 2026-07 瀹℃牳缁撴灉 53 鏉★細B001=25銆丅002=12銆丅003=15銆丅004=1锛堝槈楸奸缚鏄屽煄闄呭煄涔″钩鍧囪繍璺� 4.8 鍏噷锛夈��
+- 鎶ヨ〃锛歚/api/report/export/cityBusDetail` 鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堝叏鐪�+17 甯傚窞 脳 4 鎸囨爣 脳 1..N 鏈�+鏈堝悓姣�+鏈勾绱+涓婂勾 1-12 鏈�+绱锛夛紱鍏ㄧ渷 7 鏈� 12974.73/87147.68/11508.07/59265.28 涓庢ā鏉� 0 宸紓銆�
+- 鍓嶇锛氭暟鎹鍏ラ〉鏂板銆屽煄甯傚叕浜ゆ湀鎶ャ�嶃�佹暟鎹煡鐪嬮〉鏂板銆屽煄甯傚叕浜ゆ湀鎶ャ�嶃�佸鏍哥粨鏋滈〉鏂板銆孋ITY_BUS銆嶃�佹姤琛ㄧ敓鎴愰〉鏂板銆屽煄甯傚杩愭姤琛紙鍏氦锛岀1姝ワ級銆嶅崱鐗囷紱鍧囧凡鐑洿鏂扮敓鏁堛��
+**韪╁潙璁板綍**
+- 鏁版嵁鏂囦欢鎵╁睍鍚� .xls 浣嗗唴瀹规槸 xlsx锛圸IP 榄旀暟锛夛紝openpyxl 鎸夋墿灞曞悕鎷掔粷鈥斺�斿鍒舵垚 .xlsx 鍗冲彲锛涘悗绔� POI `WorkbookFactory` 鎸夊唴瀹硅嚜鍔ㄨ瘑鍒紝涓嶅彈褰卞搷銆�
+- `start-dev.ps1` 缁� `powershell ... | Select-Object` 绠¢亾鍚姩鏃讹紝鍚庡彴 cmd/mvn/java 缁ф壙绠¢亾鍙ユ焺浼氬鑷翠細璇濇寕璧凤紱鏀逛负 `*> start-script.log` 杈撳嚭閲嶅畾鍚戝悗鑴氭湰姝e父鏀跺熬锛堜絾浼氳瘽浠嶄細鎸傚埌缁撴潫锛屽嬁 Ctrl+C 浠ュ厤璇潃鍚庣杩涚▼鏍戯級銆傚彟娉ㄦ剰锛氬悗绔浘琚� IntelliJ 鍚姩锛坕dea64 涓嬬殑 java 搴旂敤杩涚▼锛夛紝鎸� PID 鍙潃搴旂敤閾惧嵆鍙紝鍕垮姩 idea64銆�
+- MySQL 5.7 鐨� `INSERT ... SELECT ... WHERE NOT EXISTS(娲剧敓琛ㄥ垪)` 鎶� Unknown column锛屾敼閫愭潯 `WHERE NOT EXISTS(SELECT 1 FROM audit_rule WHERE rule_code=...)`銆�
+**寰呭姙/鍙e緞寰呯‘璁�**
+- 瀹㈣繍閲忓悓姣旂幆姣� 0% 闇�鏍稿疄锛坄docs/鍩庡競瀹㈣繍/瀹㈣繍閲忕殑鍚屾瘮鐜瘮鏄�0% 闇�鏍稿疄锛堜富瑕佸嚭鐜板湪鍏氦锛�.txt`锛夋殏鏈撼鍏ヨ鍒欙紝绛変笟鍔″彛寰勭‘璁ゃ��
+- 鍏氦鎶ヨ〃鐨� 2025 骞村悇鏈堝垪寰� 2025 鏁版嵁瀵煎叆鍚庤嚜鍔ㄥ~鍏咃紙鍚屾瘮鍚岀悊鐢卞簱鍐呬笂骞存暟鎹绠楋級銆�
+- 绗� 2 姝ュ贰娓稿嚭绉熷凡瀹屾垚锛堣涓嬫柟琛ュ厖璁板綍锛夛紱绗� 3 姝ョ綉绾﹁溅鎷嗗垎绛夐儴绾ф暟鎹��
+**褰撳墠杩愯鐘舵��**
+- 鍚庣 8090锛坰tart-dev.ps1 閲嶅惎锛孭ID 12712锛屾棩蹇� backend.log锛夈�佸墠绔� 8080 鍧囧湪璺戯紱`city_bus_monthly` 宸叉湁 2026-07 鏁版嵁锛�142 琛岋級銆�
+- 涓存椂鏂囦欢锛歚%TEMP%\bus_data.xlsx / bus_dump.txt / tpl_dump.txt / city_bus_detail.xlsx / city_bus_report*.txt / dump*.py`銆侀」鐩牴 `start-script.log`銆乣docs/sql_city_bus.sql`锛堝缓琛�+瑙勫垯杩佺Щ鑴氭湰锛屽缓璁繚鐣欙級鍧囨湭鍒犮��
+
+### 2026-08-24锛堢画2锛夛細鍩庡競瀹㈣繍绗� 2 姝ワ紙宸℃父鍑虹锛夎惤鍦�
+**鑳屾櫙**锛氱敤鎴峰凡鏀惧叆杩愭斂鏁版嵁锛坄杈撳叆_鍑虹杞︿俊鎭�(杩愭斂杞﹁締淇℃伅).xls`锛岀湡 OLE .xls锛�28MB锛�42636 琛岋級銆傛湀鎶ユ暟鎹簮 = `瀵煎叆_宸℃父鍑虹姹借溅杩愯惀鏈嶅姟鎯呭喌鏈堟姤锛�2026骞�7鏈堬級.xls`锛堢湡 .xls锛屽競宸炵骇 17 琛屻��41 鍒楋級銆�
+**宸插畬鎴愶紙宸℃父鍑虹锛�**
+- 鏁版嵁妯″瀷锛歚city_taxi_monthly`锛堝競宸炴湀鎶� 41 瀛楁锛�+ `city_taxi_auth`锛堣繍鏀胯溅杈嗘槑缁嗭級锛涘缓琛�+瑙勫垯 SQL 瑙� `docs/sql_city_taxi.sql`锛堝惈瑙勫垯 INSERT锛屽彲閲嶅鎵ц锛夈��
+- 瀵煎叆锛歚/api/import/cityTaxi`锛�17 琛� 0 澶辫触锛屽箓绛夋竻鏃�+瀹℃牳缁撴灉锛夛紱`/api/import/cityTaxiAuth`锛�42636 琛岋紝绾� 6 鍒嗛挓锛屽箓绛夋竻鏃э級銆傚競宸炲綊灞烇細鏈堟姤鎸夊尯鍒掍唬鐮侊紙`RegionUtil.cityByCode`锛屾仼鏂藉窞 422800/绁炲啘鏋� 429021 姝e父璇嗗埆锛夛紝杩愭斂鎸� 璇佸彿鈫掓。妗堚啋缁忚惀鏉冣啋鍦板潃 姝e垯 `(4290\d{2}|42(?:0[1-9]|1[0-3]|28)\d{2})` 鎻愬彇锛堝疄娴� 42636 琛屽叏閮ㄥ綊甯傚窞锛夈��
+- 瀹℃牳锛�7 鏉¤鍒欙紙report_type=CITY_TAXI锛夛細
+  - RULE_T001 杩愯惀杞︽暟 vs 杩愭斂瀵规瘮锛圕ROSS_DIFF_PCT锛�3%锛孯ED锛�
+  - RULE_T002 鍩庡競鍐�/鍩庨檯鍩庝埂瀹㈣繍閲忚秼鍔夸竴鑷达紙TREND_CONSISTENCY锛�
+  - RULE_T003 瀹㈣繍閲�/鍛ㄨ浆閲忚秼鍔夸竴鑷达紙TREND_CONSISTENCY锛屽懆杞噺鐜瘮鐢� 瀹㈣繍閲忕幆姣斆楀钩鍧囪繍璺濈幆姣� 鎺ㄥ锛宑heck_field=turnoverMomDerived锛�
+  - RULE_T004 鍩庡競鍐呭杩愰噺鍗犳瘮鍙樺寲>1%锛圧ATIO_CHANGE锛�
+  - RULE_T005 骞冲潎杩愯窛鍙樺寲>3鍏噷锛圓VG_DIST_CHANGE锛屽煄闄呭煄涔$敱涓婃湡骞冲潎杩愯窛=鏈湡鈭掑鍑忓弽鎺級
+  - RULE_T006 鍗曡溅娆¤浇瀹汉鏁板彉鍖�>0.2浜猴紙PAX_PER_TRIP_CHANGE锛岀敱涓婃湡 杞藉杞︽/瀹㈣繍閲忕幆姣� 鍙嶆帹锛�
+  - RULE_T007 杞藉杞︽/瀹㈣繍閲忚秼鍔夸竴鑷达紙TREND_CONSISTENCY锛岄槇鍊� 0.01锛�
+  - 2026-07 瀹℃牳 6 鏉★細T001 榛勫唸 1931vs2043(5.48%)銆佺鍐滄灦 59vs71(16.9%)锛汿002 娼滄睙锛汿004 瀛濇劅/鍜稿畞/娼滄睙銆�
+- 鎶ヨ〃锛歚/api/report/export/cityTaxiDetail` 鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堝叏鐪�+17甯傚窞脳4鎸囨爣脳1..N鏈�+鍚屾瘮+绱+鍘诲勾12鏈堬級锛屽叏鐪� 7 鏈� 5860.24/34236.60/5351.24/27422.13 涓� `宸℃父鍑虹_妯℃澘.xlsx` 0 宸紓銆�
+- 鍓嶇锛氭暟鎹鍏ラ〉鏂板銆屽贰娓稿嚭绉熸湀鎶ャ�嶃�屽嚭绉熻溅杩愭斂杞﹁締銆嶃�佹暟鎹煡鐪嬮〉鏂板鍚屼袱椤广�佸鏍哥粨鏋滈〉鏂板銆孋ITY_TAXI銆嶃�佹姤琛ㄧ敓鎴愰〉鏂板銆屽煄甯傚杩愭姤琛紙宸℃父鍑虹锛岀2姝ワ級銆嶏紱鍧囧凡鐑洿鏂扮敓鏁堛��
+**韪╁潙璁板綍**
+- Spring 榛樿涓婁紶涓婇檺 10MB锛�28MB 杩愭斂鏂囦欢琚嫆 鈫� `application.yml` 鍔� `spring.servlet.multipart.max-file-size/max-request-size=100MB`銆�
+- `audit_result` 琛ㄦ棤 enterprise_name 鍒楋紙鐬�佸瓧娈碉級锛宍attachInfo` 鍥炲~鏃� CITY_BUS/CITY_TAXI 钀藉埌 H2032 琛ㄥ鑷村悕绉版樉绀衡�滃師鏁版嵁宸查噸鏂板鍏モ�濆崰浣� 鈫� 宸蹭慨锛氭寜 report_type 鍒嗚〃鍥炲~鍏氦锛堜紒涓氬悕锛�/宸℃父鍑虹锛堝競宸炲悕锛�+鏍稿疄璇存槑锛屽叕浜�/鍑虹鍧囧彈鐩娿��
+- 鏈堟姤鍩庡競瀛楁鍘熺敤 `normalizeCityName(浼佷笟鍚嶇О)`锛屾仼鏂解�滄仼鏂藉湡瀹舵棌鑻楁棌鑷不宸炩�濇棤娉曡瘑鍒� 鈫� 鏀圭敤 `cityOf(鍦板尯浠g爜,鍚嶇О)` 浼樺厛鎸夊尯鍒掍唬鐮侊紝骞剁粰 RegionUtil 澧炲姞鈥滄仼鏂藉湡瀹舵棌鑻楁棌鑷不宸炩啋鎭╂柦宸炩�濆埆鍚嶃��
+- 杩愭斂 42636 琛岄�愭潯 insert 鏇剧害 6 鍒嗛挓锛屽墠绔� axios 30s 瓒呮椂瀵艰嚧椤甸潰"瀵煎叆澶辫触"锛涘凡鏀� MyBatis BATCH 鎵归噺鎻掑叆 + JDBC `rewriteBatchedStatements=true` + 鍓嶇 timeout 600000锛屾暟鎹簱鎻掑叆闃舵绾� 30 绉掞紙鎬昏�楁椂绾� 5 鍒嗛挓涓昏鍦� 26.8MB xls 鐨� POI 瑙f瀽锛夛紱鍚屾姤琛ㄦ湡閲嶅鍏堝垹鍚庢彃锛屽箓绛夊疄娴嬫敹鏁� 42636 琛屻��
+**褰撳墠杩愯鐘舵��**
+- 鍚庣 8090锛坰tart-dev.ps1 閲嶅惎锛屾棩蹇� backend.log锛夛細褰撳墠杩涚▼閾� cmd 31740 鈫� mvn(java) 31652 鈫� app(java) 31452锛堝嬁璇潃锛涢噸鍚椂鎸夋閾惧仠锛夛紱鍓嶇 8080锛堢敤鎴疯繘绋嬶紝鍕挎潃锛夊潎鍦ㄨ窇锛沗city_taxi_monthly` 17 琛屻�乣city_taxi_auth` 42636 琛岋紙2026-07/2026-08 鍚� 42636 琛岋紝2026-08 涓轰慨澶嶆壒閲忓鍏ュ悗鐨勫畬鏁撮噸瀵硷紝闈炶剰鏁版嵁锛夈��
+- 涓存椂鏂囦欢锛歚%TEMP%\taxi_data.xlsx / taxi_dump.txt / yz_stat2.txt / yz_full.txt / city_taxi_detail.xlsx`銆佹牴鐩綍 `start-script.log`銆乣docs/sql_city_taxi.sql` 鍧囦繚鐣欐湭鍒犮��
+- 寰呭姙锛氱 3 姝ョ綉绾﹁溅鎷嗗垎锛堢瓑閮ㄧ骇鏁版嵁锛夛紱鍏氦/鍑虹鎶ヨ〃鐨� 2025 骞村悇鏈堝垪寰� 2025 鏁版嵁瀵煎叆鍚庤嚜鍔ㄥ~鍏呫��
+
+
+### 2026-08-24锛堢画3锛夛細鍩庡競瀹㈣繍鎶ヨ〃鏀逛负鎸夋ā鏉垮簳绋跨敓鎴�
+**闇�姹�**锛氬煄甯傚杩愯緭鍑鸿鈥滄牴鎹洰褰曚笅鐨勬ā鏉挎寜鐓фā鏉跨敓鎴愬搴旂殑杈撳嚭妯℃澘鈥濄��
+**瀹炵幇**锛坄ReportExportService`锛�
+- `exportCityBusDetail` / `exportCityTaxiDetail` 涓嶅啀浠庨浂寤鸿〃锛屾敼涓� `exportCityByTemplate(妯℃澘鏂囦欢鍚�, period, 浠婂勾鍚勬湀鏁版嵁, 鍘诲勾鍚勬湀鏁版嵁)`锛氳 `docs/鍩庡競瀹㈣繍/鍩庡競鍏氦_妯℃澘.xlsx` / `宸℃父鍑虹_妯℃澘.xlsx` 涓哄簳绋匡紝淇濈暀鏍囬/琛ㄥご/鍚堝苟鍗曞厓鏍硷紙63 澶勶級/杈规/鏁板瓧鏍煎紡锛�0.00_锛�/鍏紡锛屼粎鏇挎崲鏁版嵁鍖哄競宸炴湀搴﹀�笺��
+- 妯℃澘缁撴瀯锛歋heet1锛宺ow1 鏍囬銆乺ow3-4 琛ㄥご銆佹暟鎹尯 row5-76锛堝叏鐪�+17甯傚窞脳4鎸囨爣锛屾瘡鍦板尯 4 琛岋級锛�2026 鏈堝害鍒� 0-based col2,4,...,14锛�2025 鏈堝害鍒� col19,21,...,41锛屽悓姣�/绱鍒椾负鍏紡锛堝叏鐪佽鏈堝害鍒�=17甯傚窞姹傚拰鍏紡锛夈��
+- 濉厖瑙勫垯锛氬競宸炶 2026 骞� 1..N 鏈堟湁鍊煎~ 2 浣嶅皬鏁般�佹棤鍊兼竻绌烘ā鏉挎牱渚嬶紱2025 骞� 1..12 鏈堝簱鍐呮湁鍊煎~銆佸惁鍒欐竻绌猴紙绛� 2025 鏁版嵁瀵煎叆鍚庤嚜鍔ㄥ~鍏呭苟瑙﹀彂鍚屾瘮鍏紡锛夛紱鍏ㄧ渷琛屼笌鍚屾瘮/绱鍏紡涓�寰嬩繚鐣欙紱鍐欏洖鍓� `FormulaEvaluator.evaluateAll()` 閲嶇畻鍏紡缂撳瓨鍊硷紙Excel 鎵撳紑涔熷彲閲嶇畻锛夈��
+- 妯℃澘瀹氫綅锛歚application.yml` 鏂板 `city-passenger.template-dir: docs/鍩庡競瀹㈣繍`锛宍resolveTemplate` 鎸� 閰嶇疆鐩綍鈫抲ser.dir鈫抲ser.dir/.. 涓夌骇鍥為��锛涙暟鎹尯琛屾暟瀹堝崼锛�<75 琛屾姤閿欓槻妯℃澘鏀圭増閿欎綅锛夈��
+**楠岃瘉**锛�2026-07 瀵煎嚭锛屽叏鐪�+17甯傚窞脳4鎸囨爣锛堝杩愰噺/鍛ㄨ浆閲�/鍩庡競鍐呭杩愰噺/鍩庡競鍐呭懆杞噺锛変笌搴撳唴鑱氬悎 0 宸紓锛涘叕浜ゅ叏鐪� 12974.73/87147.68/11508.07/59265.28锛屽贰娓稿嚭绉� 5860.24/34236.60/5351.24/27422.13锛涜緭鍑� sheet 鍚�/鍚堝苟鍗曞厓鏍�/琛ㄥご涓庢ā鏉垮畬鍏ㄤ竴鑷淬��
+**娉ㄦ剰**锛氭ā鏉� 2026 娈靛悓姣斿叕寮忓紩鐢� 2025 鍒楋紝褰撳墠 2025 鍒楁棤鏁版嵁锛孍xcel 鎵撳紑鍚屾瘮鏄剧ず #DIV/0!锛堟ā鏉垮師鏍疯涓猴級锛�2025 鏁版嵁瀵煎叆鍚庤嚜鍔ㄧ畻瀵广��
+
+
+### 2026-08-24锛堢画4锛夛細杩愭斂杞﹁締瀵煎叆"涓嶈兘瀵煎叆"淇
+**鐜拌薄**锛歚瀵煎叆_鍑虹杞︿俊鎭�(杩愭斂杞﹁締淇℃伅).xls`锛�26.8MB锛�42636 琛岋級鍦ㄦ暟鎹鍏ラ〉鐐�"寮�濮嬪鍏�"涓�鐩村け璐ャ��
+**鏍瑰洜**锛�
+- 鍓嶇 `src/api/index.js` 鍏ㄥ眬 axios `timeout: 30000`锛屽悗绔棫瀹炵幇閫愭潯 `insert` 绾� 6 鍒嗛挓 鈫� 30 绉掑悗璇锋眰琚墠绔柇寮�锛岄〉闈㈡姤"瀵煎叆澶辫触"銆�
+- 鍓嶇 `DataImport.vue` 鐨� cityTaxiAuth 妯℃澘鎻愮ず鍚嶈鍐欎负 `杈撳叆_...`锛堝疄闄呮枃浠舵槸 `瀵煎叆_...`锛夈��
+**淇**锛堝潎宸茬敓鏁堬級锛�
+- `DataImportService.importCityTaxiAuth` 鏀� MyBatis BATCH 妯″紡锛坄openSession(ExecutorType.BATCH)` + `flushStatements()` + `commit()`锛夛紝鏂规硶鍔� `synchronized` 闃插苟鍙戦噸澶嶅鍏ワ紱
+- `application.yml` JDBC URL 鍔� `rewriteBatchedStatements=true`锛堟壒閲� flush 鐢辩害 4 鍒嗛挓闄嶈嚦绾� 30 绉掞級锛�
+- `DataImport.vue` cityTaxiAuth 妯℃澘鍚嶆敼鍥� `瀵煎叆_`锛屽鍏ヨ姹� `timeout: 600000`銆�
+**楠岃瘉**锛�21:57 閲嶆柊瀵煎叆 2026-07锛宍import_batch` id=31 涓� 42636/42636/0锛宍city_taxi_auth` 2026-07 鎭板ソ 42636 琛岋紙骞傜瓑锛歩d=30 涓� id=31 涓ゆ瀵煎叆鍧囨敹鏁� 42636锛夛紱2026-08 鏃т唬鐮侀噸澶嶅鍏ヤ骇鐢熺殑 77262 琛岃剰鏁版嵁宸� DELETE銆�
+**娉ㄦ剰**锛氭�昏�楁椂绾� 5.3 鍒嗛挓锛屼富瑕佹槸 POI(HSSF) 瑙f瀽 26.8MB/42636 琛岋紝浠嶅湪 600s 瓒呮椂鍐咃紱濡傞渶鏇村揩鍙悗缁敼浜嬩欢娴佽В鏋愶紙easyexcel锛夋垨鎷嗗垎鏂囦欢銆�
+### 2026-08-24锛堢画5锛夛細鍩庡競瀹㈣繍鎶ヨ〃绯诲垪鎸夋ā鏉跨敓鎴愶紙6 琛� + zip锛�
+**闇�姹�**锛氳瘑鍒�婂鍏鍩庡競鍏叡浜ら�氭湀搴﹁繍钀ユ儏鍐碉紙2026骞�7鏈堬級銆嬨�婂鍏鍑虹杞︿俊鎭�(杩愭斂杞﹁締淇℃伅)銆嬨�婂鍏宸℃父鍑虹姹借溅杩愯惀鏈嶅姟鎯呭喌鏈堟姤锛�2026骞�7鏈堬級銆嬩笁浠芥簮鏁版嵁锛屾寜鐩綍涓嬫ā鏉跨敓鎴愮郴鍒楄緭鍑鸿〃锛涙牸寮忥紙鍒楀/琛ㄥご/鍚堝苟瑙勫垯锛変笌妯℃澘瀹屽叏涓�鑷达紝浠呮浛鎹㈡暟鎹��
+**鐩綍鍙樺寲**锛氭ā鏉跨洰褰曠敱 `docs/鎵╁睍琛ㄦā鏉�/鍩庡競瀹㈣繍` 绉昏嚦 `docs/鍩庡競瀹㈣繍`锛沗application.yml` 鐨� `city-passenger.template-dir` 宸插悓姝ユ敼涓� `docs/鍩庡競瀹㈣繍`锛坄ReportExportService` 鐨� `@Value` 榛樿鍥為��鍊间竴骞朵慨姝o級銆�
+**瀹炵幇**锛坄ReportExportService` + `ReportExportController` + `ReportExport.vue`锛�
+- 鏁版嵁鎵╁睍锛氬叕浜ゆ簮鏂囦欢锛堜紒涓氱骇 142 琛岋級闄ゅ叕鍏辨苯鐢佃溅澶栬繕鍚�**杞ㄩ亾**锛堟姹� 11701.74/鍛ㄨ浆 96240.86锛岄粍鐭� 30.09/404.37锛変笌**杞浮**锛堟姹� 46.05/156.57锛夛紱`city_bus_monthly` 鍔� 4 鍒楋紙rail/ferry 瀹㈣繍閲�+鍛ㄨ浆閲忥級锛宍importCityBus` 鏄犲皠 0 鍩哄垪 13/14/17/18锛圢/O/R/S锛夊悗 142 琛岄噸瀵兼垚鍔熴��
+- 鏂板鍑烘帴鍙o紙`/api/report/export/`锛夛細
+  - `cityRailFerryDetail`锛氳建閬撹 Excel 4/6/8锛堝叏鐪�/姝︽眽/榛勭煶锛�+1 涓哄懆杞噺琛岋級锛岃疆娓¤ 13/15锛涘叏鐪佹湀搴﹀垪淇濈暀姹傚拰鍏紡銆�
+  - `cityPassengerCitySum`锛坄鍩庡競鍒嗗競宸瀇妯℃澘` / `鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃_妯℃澘`锛夛細瀹㈣繍閲忓尯 r5-22銆佸懆杞噺鍖� r27-44锛汢=鎬荤疮璁°�丟=鍏氦銆丩=鍑虹銆丵=缃戠害杞︼紙鏃犳簮鏁版嵁娓呯┖锛夈�乂=杞ㄩ亾銆乆=杞浮锛汦/J/O/T/W/Y 澧為�熷垪鏃犲幓骞村悓鏈� 鈫� 娓呯┖妯℃澘鏍蜂緥銆�
+  - `cityPassengerSummary`锛坄鍩庡競姹囨�籣妯℃澘`锛夛細2026 娈� B/D 濉簱鍐呯疮璁°�丆/E 鍚屾瘮娓呯┖銆佺綉绾﹁溅琛岀疆 0锛�2025/2024 娈碉紙G..Q 鍒楋級淇濈暀妯℃澘鍘嗗彶鍙傝�冨�间笉鍔ㄣ��
+  - `cityPassengerSeries`锛氫竴閿� zip锛堝叕浜�/鍑虹/杞ㄩ亾杞浮鏄庣粏 + 鍒嗗競宸炵疮璁� + 鍚勫競宸炴槑缁嗚〃 + 姹囨�伙級銆�
+- 鍓嶇 `ReportExport.vue` 鏂板鈥滃煄甯傚杩愭姤琛紙绯诲垪锛夆�濆崱鐗囷紙4 涓崟琛ㄦ寜閽� + 涓�閿墦鍖� zip锛夈��
+**POI 0 鍩烘暀璁�**锛歅OI 琛屽彿鏄� 0 鍩猴紝涔嬪墠鎸� Excel 1 鍩哄啓瀵艰嚧鏁村潡閿欎綅涓�琛岋紙杞ㄩ亾璇诲埌 96645.23 绛夐敊浣嶅�硷級锛涘凡鏀� 0 鍩猴紙杞ㄩ亾 3/5/7銆佽疆娓� 12/14銆佸垎甯傚窞 4/26銆佹眹鎬� 2..8锛夛紝閲嶇敓鎴愬悗鏁版嵁鍏ㄩ儴鏍稿閫氳繃銆�
+**楠岃瘉锛�2026-07锛�**锛氳建閬� 姝︽眽 11701.74/96240.86銆侀粍鐭� 30.09/404.37锛涜疆娓� 鍏ㄧ渷=姝︽眽 46.05/156.57锛涘垎甯傚窞绱 姝︽眽 B=17060.44锛�=鍏氦 4218.74+鍑虹 1093.91+杞ㄩ亾 11701.74+杞浮 46.05锛夈�侀粍鐭� B=1254.8銆佹灄鍖� B=27.15锛涙眹鎬� 鎬诲杩愰噺 B3=30612.85銆佸叕浜� 12974.73銆佸嚭绉�/宸℃父 5860.24銆佽建閬� 11731.83銆佽疆娓� 46.05锛�2025/2024 娈典笌妯℃澘涓�鑷达紱琛ㄥご/鍚堝苟/鍒楀鍏ㄩ儴 MATCH锛堜粎鏁版嵁琛屾寜璁捐鏇挎崲锛夈��
+**杈撳嚭**锛歚docs/鍩庡競瀹㈣繍/杈撳嚭/鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx`銆乣鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx`銆乣鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx`銆乣鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx`銆乣鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx`銆乣鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx` 鍏� 6 涓枃浠躲��
+**鍧戯紙鑴氭湰缂栫爜锛�**锛歚%TEMP%\gen_series2.ps1` 鑻ョ敤鏃� BOM 鐨� UTF-8 淇濆瓨锛孭owerShell 5.1 鎸� ANSI 璇诲彇浼氭妸涓枃璺緞璇绘垚涔辩爜锛屽鍒跺埌 `D:\閺傚洦銆俓ChatGPT\traffic-audit\docs\...`锛堟湰娆¤寤虹殑涔辩爜鐩綍锛屾湭鍒狅級锛涘凡鐢ㄥ甫 BOM 鐗堟湰閲嶅啓锛岀幇姝g‘鍐欏叆 `docs\鍩庡競瀹㈣繍\杈撳嚭`銆�
+
+
+
+### 2026-08-25 涓婂崍浼氳瘽锛氬煄甯傚杩愰摼璺仮澶嶉獙璇� + 鏁版嵁搴撹繛鎺ユ牴鍥犱慨澶�
+**鐜拌薄**锛氭柊瀵硅瘽鍚姩鍓嶅悗绔紙start-dev.ps1 纭 8090/8080 灏辩华锛夊悗锛宍/api/data/list` 鍏ㄩ儴绫诲瀷 500銆乣/api/audit/results` 500锛涗粎 default 鍒嗘敮锛坕nvestment*锛夎繑鍥� 200 绌烘暟鎹��
+**鎺掓煡**锛氬悗绔槸 IDEA 鍚姩瀹炰緥锛堟棩蹇椾笉杩� backend.log锛夛紝璧� 8091 璇婃柇瀹炰緥锛坄mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8091`锛夊鐜帮紝鍫嗘爤鏍瑰洜锛�
+- `java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed`锛圚ikariPool 鍒濆鍖栧け璐ワ級銆�
+- 鏈満 MySQL 8 root 璁よ瘉鎻掍欢涓� `caching_sha2_password`锛坢ysql 瀹㈡埛绔彲杩烇級锛孞DBC URL 缂� `allowPublicKeyRetrieval=true` 鈫� 闈� SSL 杩炴帴鎷掔粷鍙栧叕閽ャ��
+**淇**锛歚application.yml` / `application.yml.example` / `target/classes`锛堝悓姝ワ級JDBC URL 鍔� `allowPublicKeyRetrieval=true`锛堜繚鐣� rewriteBatchedStatements=true锛夛紱鍋� IDEA 鏃у疄渚� 鈫� start-dev.ps1 閲嶅惎 鈫� 鍏ㄩ儴绫诲瀷 200銆�
+**鎭㈠楠岃瘉锛堝叏閮ㄤ笌 08-24 璁板綍涓�鑷达級**锛�
+- 閲嶈窇瀹℃牳锛欳ITY_BUS 2026-07 = 53 鏉★紙B001=25/B002=12/B003=15/B004=1锛夛紱CITY_TAXI 2026-07 = 6 鏉★紙T001=2/T002=1/T004=3锛夈�備箣鍓嶅叕浜� 53 鏉¤ 23:21 鐨� 142 琛岄噸瀵硷紙骞傜瓑娓呯粨鏋滐級鍐叉帀涓旀湭閲嶈窇锛涘嚭绉熷簱閲屾畫鐣欑殑鏄� 2026-08 鐨� 6 鏉°��
+- 6 寮犳姤琛ㄩ噸鏂扮敓鎴愶紙`docs/鍩庡競瀹㈣繍/杈撳嚭/`锛夛紝鍏抽敭鏁板�煎叏閮ㄦ牳瀵癸細鍏氦鍏ㄧ渷 12974.73/87147.68/11508.07/59265.28锛涘嚭绉熸姹� 1093.91/8532.5锛涜建閬撴姹� 11701.74/96240.86銆侀粍鐭� 30.09/404.37锛涜疆娓� 46.05/156.57锛涘垎甯傚窞绱 姝︽眽 B=17060.44/榛勭煶 1254.8/鏋楀尯 27.15锛涙眹鎬� 鎬� 30612.85/鍏氦 12974.73/鍑虹 5860.24/杞ㄩ亾 11731.83/杞浮 46.05锛涚郴鍒� zip 6 鏂囦欢榻愬叏銆�
+**灏忎慨澶嶏紙涓� HANDOFF 08-24 璁捐涓嶇澶勶級**锛歚setDataCell` 瀵� `v==0` 鎵ц `c.setBlank()`锛屽鑷存眹鎬昏〃"缃戠害杞﹁缃� 0"瀹為檯杈撳嚭涓虹┖锛涙柊澧� `setZeroCell`锛堟樉寮忓啓 0.0锛夌敤浜� B7/D7锛岄噸鐢熸垚鍚� B7=0銆丏7=0锛屽叾浣欐暟鍊间笉鍙樸��
+**寰呭姙涓嶅彉**锛氬煄甯傚杩愮 3 姝ワ紙缃戠害杞︽媶鍒嗭級绛夐儴绾ф暟鎹簮锛涘叕浜� 2025 鍚勬湀鍒楃瓑 2025 鏁版嵁瀵煎叆锛�"瀹㈣繍閲忓悓姣旂幆姣� 0% 闇�鏍稿疄"锛坱xt 涓虹┖锛夌瓑涓氬姟鍙e緞锛涗腑鍙e緞涓夎〃锛圚203-1 鏄庣粏/鎺掑悕/鍒嗘瀽锛夌殑 鍩庨檯鍩庝埂鍏氦/宸℃父鍑虹 琛屽彲鍦ㄥ煄甯傚杩愭暟鎹熀纭�涓婃帴鍏ワ紙寤鸿涓嬩竴姝ワ紝闇�纭鍙e緞锛氬叕浜ゅ彇 passengerChengxiang 瀛楁锛屽嚭绉熸簮鏂囦欢鏃犲煄闄呭煄涔″杩愰噺瀛楁闇�纭鎷嗗垎鍙e緞锛夈��
+**鍧�**锛歛pply_patch 鍦ㄦ湰鐜鍏ㄩ儴鎶� "Access is denied"锛堣繛鏍圭洰褰曟枃浠朵篃鎷掞級锛屾敼鐢� PowerShell `[System.IO.File]::ReadAllText/WriteAllText`锛圲TF-8 鏃� BOM锛夌紪杈戯紱`mvn spring-boot:run` 涓嶄細鑷姩閲嶆嫹 resources锛屾敼 yml 鍚庨渶鎵嬪姩鍚屾 target/classes 鎴栬窇 `mvn compile`銆�
+
+
+### 2026-08-25 涓嬪崍浼氳瘽锛氭姤琛ㄧ敓鎴�/浼佷笟瑙i噴/鏁版嵁瀵煎叆 UI 閲嶆瀯锛堢敤鎴风‘璁ゅ彛寰勫悗寮�宸ワ級
+**闇�姹�**锛氣憼鍩庡競瀹㈣繍鎶ヨ〃鍦ㄦ姤琛ㄧ敓鎴愰〉鍚堝苟涓轰竴涓ā鍧楋紱鈶¢〉闈㈢編鍖� + 鎶ヨ〃鍙�夌敓鎴�/涓�閿叏閮� + 鏀寔涓�涓湀/鍑犱釜鏈�/涓�骞达紱鈶紒涓氳В閲婂姞鍏ュ叕璺梾瀹笌鍩庡競瀹㈣繍锛堣兘鑰�/鎶曡祫鏃犺В閲婏紝涓嶅仛锛夛紱鈶f暟鎹鍏ョ晫闈紭鍖栵紱鈶よ揣杩�/鏃呭/鎶曡祫绛夋ā鍧楀悓姝ョ粺涓�銆�
+**鍚庣鏂板/淇敼**锛堝凡缂栬瘧閲嶅惎锛�8090 灏辩华锛�
+- `ReportExportService`锛氭柊澧� `exportFreightSeries` / `exportPassengerSeries` / `exportInvestSeries` / `exportSelectedSeries`锛堝墠绔�岀敓鎴愰�変腑銆嶅閫夋椂鎸� type 鍒楄〃鎵撳寘 zip锛岄伩鍏嶆祻瑙堝櫒鎷︽埅澶氫釜涓嬭浇锛夛紱`REPORT_FILE_NAMES` 鏄犲皠 18 寮犺〃銆�
+- `ReportExportController`锛氭柊澧� `/export/freightSeries`銆乣/export/passengerSeries`銆乣/export/investSeries`銆乣/export/selected?types=a,b,c&mode=` 鍥涗釜 zip 绔偣锛涘叏閮ㄥ鍑虹鐐瑰姞 `logExport` 鐣欑棔銆�
+- 鏂板 `OperationLog` 瀹炰綋 + `OperationLogMapper` + `OperationLogService`锛堣嚜鍔ㄤ粠 Authorization 澶磋В鏋愮敤鎴枫�乆-Forwarded-For 鍙� IP锛夛紝鍐欏叆 `sys_operation_log`锛堣〃鍘熸湰涓虹┖锛岀幇宸叉湁璁板綍锛夈��
+- `ExplainController`锛歚/api/explain/list` 鍔� `reportType` 鍙傛暟锛屾敮鎸� H2032/H2031/CITY_BUS/CITY_TAXI 鍥涚被锛岃繑鍥炵被鍨嬪搴斿垪銆�
+- `DataImportController`锛氭柊澧� `GET /api/import/batches?type=&limit=`锛堝鍏ユ壒娆″巻鍙诧紝`import_batch` 琛級銆�
+- `DbQueryExecutor`锛氱櫧鍚嶅崟娉ㄥ唽 `h2031_enterprise_monthly` / `city_bus_monthly` / `city_taxi_monthly`锛圓I 瀵硅瘽鍙煡锛夛紱`LLMService` 绯荤粺鎻愮ず鏀寔 context 浼犲叆 `reportType`/`tableName` 寮曞鏌ヨ〃銆�
+**鍓嶇锛坴ue-cli 鐑洿鏂板凡鐢熸晥锛�**
+- `ReportExport.vue` 閲嶅啓锛氫簲涓ā鍧楋紙璐ц繍/鏃呭/鑳借��/鎶曡祫/鍩庡競瀹㈣繍锛夌粺涓�鍗$墖甯冨眬锛坓rid锛夛紝鍩庡競瀹㈣繍 6 寮犺〃鍚堝苟涓轰竴寮犲崱锛涙瘡妯″潡 checkbox 澶氶�� + 銆岀敓鎴愰�変腑銆嶏紙鍗曢�夌洿涓嬨�佸閫夋墦鍖� zip锛�+銆屼竴閿敓鎴愬叏閮�(zip)銆嶏紱鏃呭淇濈暀鏈堟姤/骞存姤涓ゆ。锛堝勾鎶ラ�夊勾浠斤紝period=yyyy&mode=year锛夛紱搴曢儴浣跨敤鎻愮ず璇存槑鏈熸暟鍙e緞锛堥�� 07=1~7 鏈堛�侀�� 12=鍏ㄥ勾+涓婂勾鍚屾瘮锛夈��
+- `ExplainReview.vue`锛氭姤琛ㄧ被鍨嬩笅鎷夛紙璐ц繍/鍏矾鏃呭/鍩庡競鍏氦/宸℃父鍑虹锛夛紝琛ㄦ牸鍒楁寜绫诲瀷鍔ㄦ�佹樉绀猴紝鍏抽敭瀛楁爣绛鹃殢绫诲瀷鍒囨崲锛堝嚭绉熸寜甯傚窞锛夛紱AI 鍒嗘瀽 context 澧炲姞 reportType/tableName銆�
+- `DataImport.vue`锛氬鍏ョ被鍨嬫寜 璐ц繍/鍏矾鏃呭/鑳借��/鍩庡競瀹㈣繍/鎶曡祫 浜旂粍鍒嗗尯灞曠ず锛涘彸渚у鍏ヨ缃崱锛涗笅鏂广�屾渶杩戝鍏ヨ褰曘�嶈〃鏍硷紙绫诲瀷/鏈�/鏂囦欢鍚�/鎬昏鏁�/鎴愬姛/澶辫触/鏃堕棿锛屾潵鑷� /import/batches锛夛紱cityTaxiAuth 鏄剧ず绾� 5 鍒嗛挓鑰楁椂鎻愮ず銆�
+**楠岃瘉锛堝叏閮ㄩ�氳繃锛�**
+- `/api/import/batches`锛氭甯歌繑鍥炴渶杩� 10 鏉°��
+- `/api/explain/list`锛欻2032=28 鏉★紙2026-07锛夈�丠2031=0銆丆ITY_BUS=109銆丆ITY_TAXI=8锛堢鍐滄灦绛� 8 甯傚窞鏈夎В閲婏級銆�
+- zip锛歴elected锛坈ityBusDetail+cityTaxiDetail+cityPassengerSummary锛夈�乫reightSeries銆乸assengerSeries锛坢onth/year锛夈�乮nvestSeries 鏂囦欢榻愬叏锛涙梾瀹㈠勾鎶� 104810/47498 瀛楄妭锛�>鏈堟姤 95824/43439锛屾纭級銆�
+- 鐣欑棔锛歴ys_operation_log 宸茶褰� 6 鏉°�屾姤琛ㄧ敓鎴愩�嶏紙鏈櫥褰曟椂 userId 涓虹┖锛宒etail 涓� UTF-8 涓枃姝e父锛夈��
+- 鍓嶇 chunk 7/4/1 宸插寘鍚柊浠g爜锛坮eport/export/selected銆乮mport/batches銆乺eportType 绛夋爣璁帮級锛屾棤缂栬瘧閿欒銆�
+**璇存槑/杈圭晫**
+- 鑳借�楁ā鍧楀彧鏈� 1 寮犺〃锛屼笉鏄剧ず銆屼竴閿敓鎴愬叏閮ㄣ�嶃��
+- 銆屽嚑骞淬�嶇淮搴︼細鍥哄畾妯℃澘琛ㄥ彧鏀寔褰撳勾 1~12 鏈� + 涓婂勾鍚屾瘮涓ゆ锛堥瀵艰〃鏍煎紡锛夛紝澶氬勾瀵规瘮濡傞渶鍙悗缁姞闈炴ā鏉裤�屽尯闂存槑缁嗗鍑恒�嶃��
+- 浼佷笟瑙i噴 H2031 褰撳墠 0 鏉℃槸鍥犱负鏍蜂緥鏁版嵁璇ユ湡鏈~瑙i噴锛屾帴鍏ラ�昏緫鏈韩宸查�氥��
+- apply_patch 浠嶄笉鍙敤锛圓ccess denied锛夛紝鍓嶇澶ф枃浠剁敤 Python 鏁存枃浠堕噸鍐欙紝灏忔敼鍔ㄧ敤瀛楃涓叉浛鎹€��
+
+
+
+### 2026-08-25 鏅氫笂浼氳瘽锛氭湀鎶�/骞存姤鍏ㄦā鍧� + 鑳借�楁ā鏉垮寲 + 浼佷笟瑙i噴鏈~鍐� + 鎶ヨ〃椤典笅鎷夊鍑猴紙鐢ㄦ埛纭鍙e緞鍚庡疄鏂斤級
+**闇�姹�**锛氣憼甯冨眬鏀逛负 璐ц繍鈫掑叕璺梾瀹⑩啋鍩庡競瀹㈣繍锛堟梾瀹㈠彸杈癸級鈫掕兘鑰椻啋鎶曡祫锛涒憽鏈堟姤/骞存姤鍒囨崲鍔犲埌 璐ц繍/鏃呭/鍩庡競瀹㈣繍/鎶曡祫锛涒憿鐢熸垚鍐呭鏀瑰閫変笅鎷夋 + 鍗曟寜閽�婂鍑恒�嬶紱鈶f暟鎹鍏ョ晫闈㈣繘涓�姝ョ編鍖栵紱鈶や紒涓氳В閲婂姞鈥滄湭濉啓鈥濅笌杩囨护寮�鍏筹紱鈶ョ‖鎬ц姹傦細璐ц繍/鏃呭/鎶曡祫/瀹㈣繍鏂拌〃蹇呴』涓庛�愭牸寮忔ā鏉裤�戝垪瀹�/琛ㄥご/鍚堝苟瑙勫垯瀹屽叏涓�鑷达紝浠呮浛鎹㈡暟鎹紝瀵煎嚭 Excel銆�
+**鍚庣锛堝凡缂栬瘧閲嶅惎楠岃瘉锛�**
+- 骞存姤 mode 鎵╁睍锛歚monthOf(period, mode)`锛坹ear鈫�12锛夛紱`exportCityDetail/exportFreightRank/exportTurnoverRank`銆乣exportCityBusDetail/exportCityTaxiDetail/exportCityRailFerryDetail/exportCityPassengerCitySum/exportCityPassengerSummary`銆乣exportCityByTemplate`銆乣investPlan/investFiveYearLogistics/investBillion/investCounty` 鍙婁笁涓� series 涓� `selectedReportData` 鍏ㄩ儴鍔� mode 閫忎紶锛涙帶鍒跺櫒 16 涓鐐瑰姞 `mode` 鍙傛暟銆傝揣杩愬勾鎶ユ爣棰樻樉绀恒�屾姤琛ㄦ湡锛�2026骞村叏骞淬�嶃��
+- 鑳借�� `exportEnergySummary` 鏀逛负妯℃澘搴曠锛歚docs/鍏矾鏃呭+鑳借��/妯℃澘_鑳借繍姹囨�昏〃 .xlsx`锛堟柊澧� `energy.template-dir` 閰嶇疆 + `resolveAnyTemplate`锛夛紝淇濈暀鏍囬/琛ㄥご/鍚堝苟(A1:M1銆丄11:G11)/鍏紡(D/J/L/M 涓庡悎璁� SUM)/鍒楀/鏍峰紡锛屼粎濉暟鍊煎垪锛圔/C/E/F/G/H/I/K锛夛紱鍏ㄧ渷 + 17 甯傚窞 clone sheet锛涘搴︽爣棰樻敼涓枃鏁板瓧锛�2026骞寸浜屽搴︼級銆�
+- 鎶曡祫 4 琛ㄥ幓鎺� `autoWidth`锛氫笌 7 鏈堟牱渚嬩竴鑷达紙鏍蜂緥 0 鍚堝苟銆佹棤鑷畾涔夊垪瀹斤紝琛ㄥご/鍐呭 08-24 宸查獙璇侀�愯涓�鑷达級銆�
+- `ExplainController`锛氶粯璁よ繑鍥炲叏閮ㄨ褰曪紙鍚┖瑙i噴锛夛紝鏂板 `hasExplanation=true` 杩囨护锛沗DataImportService.importPassengerMonthly` 琛ヨВ鏋愩�屾牳瀹炴�у師鍥犺В閲娿�嶅垪锛堝綋鍓嶆簮鏂囦欢 28 鍒楁棤姝ゅ垪 鈫� H2031 鍏ㄩ儴鏄剧ず鈥滄湭濉啓鈥濓紝灞炴簮鏁版嵁闂锛夈��
+**鍓嶇锛坈hunk 1/7 宸插惈鏂颁唬鐮侊級**
+- `ReportExport.vue` 閲嶅啓锛氭ā鍧楅『搴� 璐ц繍鈫掑叕璺梾瀹⑩啋鍩庡競瀹㈣繍鈫掕兘鑰椻啋鎶曡祫锛坓rid锛氳揣杩愭暣琛岋紝鏃呭宸�/鍩庡競瀹㈣繍鍙筹紝鑳借�楀乏/鎶曡祫鍙筹級锛涚敓鎴愬唴瀹� = el-select 澶氶�変笅鎷夛紙鍏ㄩ��/娓呯┖锛�+ 鍗曟寜閽�屽鍑恒�嶏紙閫� 1 寮犵洿涓嬨�佸寮� zip锛屾寜閽樉绀哄紶鏁帮級锛涜揣杩�/鏃呭/鍩庡競瀹㈣繍/鎶曡祫鍧囨湁鏈堟姤/骞存姤 radio锛堣兘鑰椾繚鎸佸搴︽湯鏈堬級銆�
+- `ExplainReview.vue`锛氥�屼粎鏄剧ず宸插~鍐欍�峞l-switch锛堚啋 hasExplanation锛�+ 绌鸿В閲婃樉绀恒�屾湭濉啓銆峸arning 鏍囩銆�
+- `DataImport.vue`锛氶粯璁ゆ姤琛ㄦ湡=褰撳墠鏈堬紱鎵规琛ㄥ姞鎴愬姛/澶辫触鐫�鑹蹭笌銆岀姸鎬併�嶅垪锛堟垚鍔�/閮ㄥ垎澶辫触/鏃犳暟鎹級锛涙瘡绫诲瀷鍔犱竴鍙ュ鍏ヨ鏄庛��
+**楠岃瘉锛堝叏閮ㄩ�氳繃锛�**
+- explain锛欻2031 鏃犺繃婊� 771 / 杩囨护 0锛汬2032 杩囨护 28锛汣ITY_BUS 142锛汣ITY_TAXI 17銆�
+- 鑳借�楋細18 sheets锛堝叏鐪�+17 甯傚窞锛夛紝鏍囬銆�2026骞寸浜屽搴﹁兘鑰楁眹鎬绘儏鍐佃〃銆嶏紝鍏紡/鍚堝苟淇濈暀锛屾姹夋煷娌硅溅 375 绛夋暟鍊兼纭��
+- 骞存姤锛氳揣杩� 28 鍒楋紙12 鏈�+鍚屾瘮锛夈�佸煄甯傚叕浜� 45 鍒楋紙2026 12 鏈�+2025 12 鏈堬級锛沗selected?period=2026&mode=year&types=...` zip 4 鏂囦欢姝e父銆�
+- 鍓嶇缂栬瘧鎴愬姛锛圕ompiled successfully锛夛紝chunk 1/7 鍚� hasExplanation/select-wrap/exportChecked 鏍囪銆�
+**鍧�**
+- 鍒嗘鏇挎崲锛圥ython锛夋椂 `c.find()` 鎵惧埌鏃╀簬鐩爣鐨勮瀵艰嚧鏂囦欢澶嶅埗鍑轰竴鏁存鍩庡競瀹㈣繍浠g爜 鈫� 缂栬瘧鎶モ�滈噸澶嶆柟娉曗��+ Lombok 杩為攣鎶ラ敊 鈫� 鐢ㄨ绱㈠紩绮剧‘鍒犻櫎閲嶅鍧� [2033..2560) 鍚� `mvn clean compile` 閫氳繃銆傛暀璁細鎵归噺鏇挎崲鍚庡繀椤� clean compile + 妫�鏌ユ柟娉曞敮涓�鎬с��
+- start-dev.ps1 鑻ヤ笂娆′細璇濇湭閫�鍑猴紙`*>` 閲嶅畾鍚戣鍚庡彴瀛愯繘绋嬫寔鏈夊彞鏌勶級浼氬苟鍙戦噸澶嶅惎鍔� 鈫� 鍏堢‘璁� 8090/8080 绌虹鍙e啀鍚姩锛沚ackend.log 鍒ゅ氨缁纭鏄柊鍐欏叆鍐呭锛堟棫鏃ュ織浼氳鍒� OK锛夈��
+**寰呭姙 / 闇�鐢ㄦ埛鎻愪緵**
+- 銆愭牸寮忔ā鏉裤�戝熀鍑嗘枃浠讹細璐ц繍杈撳嚭琛紙璐ц繍閲忓垎甯傚窞鏄庣粏/璐ц繍閲忔帓鍚�/鍛ㄨ浆閲忔帓鍚嶏級涓庢梾瀹㈣緭鍑鸿〃锛堝垎甯傚窞鏄庣粏 + 涓彛寰� 3 琛級鐩墠浠撳簱鏃犺緭鍑烘牸寮忔ā鏉匡紙`docs/璐ц繍` 鍙湁杈撳叆妯℃澘涓� 08-04 鐢熸垚浠讹紱`妯℃澘_浜や紒缁烪203-1琛╜ 鏄┖澹筹級锛岄渶鐢ㄦ埛鎻愪緵鍩哄噯鏂囦欢鎵嶈兘鍋氣�滀粎鏇挎崲鏁版嵁鈥濇敼閫狅紱鍩庡競瀹㈣繍锛�8 妯℃澘锛夈�佽兘鑰楋紙1 妯℃澘锛夈�佹姇璧勶紙7鏈堟牱渚� 4 .xls锛夊凡鎸夋ā鏉�/鏍蜂緥瀵归綈銆�
+- 鎶曡祫 DB 褰撳墠浠� 50 鏉� 2026-07 鏈堝害鏁版嵁锛�41 涓粡娴庡己鍘块」鐩粎 1 鏉℃湁鏈堝害鏁版嵁锛夛紝闇�閲嶆柊瀵煎叆瀹屾暣鎶曡祫鏈堟姤鎵嶈兘鐢熸垚瀹屾暣鎶ヨ〃锛堟姤琛ㄩ�昏緫姝e父锛屾槸鏁版嵁閲忛棶棰橈級銆�
+- 鏃呭涓彛寰勭殑鍩庨檯鍩庝埂鍏氦/宸℃父鍑虹/缃戠害杞﹁浠嶅緟鍩庡競瀹㈣繍鍙e緞鎺ュ叆銆�
+
+### 2026-08-25锛堢画5锛岃皟鐮旀祴璇曪級锛氱敤鎴锋洿鏂版梾瀹㈣緭鍏�/杈撳嚭鏍蜂緥 + 鎶曡祫鏍¢獙闇�姹傦紙寰呯‘璁ゅ彛寰勫悗瀹炴柦锛�
+**鐢ㄦ埛鏀瑰姩锛堟湰娆′細璇濆紑澶村彂鐜帮級**
+- 鍏矾鏃呭杈撳叆 `docs/鍏矾鏃呭+鑳借��/鍏矾鏃呭绠�鍖栧伐浣�.xlsx` 鏇存柊锛�11:21锛�707KB锛夛紝鐜板惈 6 sheet锛氬叕璺梾瀹�6鏈堟姤锛�28鍒椕�770琛岋紝浠嶆棤鏍稿疄鎬у師鍥犺В閲婂垪锛夈�佽繍鏀胯溅杈嗘暟銆佸叕璺梾瀹㈠垎甯傚窞鏄庣粏/涓彛寰勬槑缁�/涓彛寰勬帓鍚�/涓彛寰勫垎鏋愶紙4 涓唴宓屾牱渚嬶級銆�
+- 鏂板 `docs/鍏矾鏃呭+鑳借��/杈撳嚭/` 4 寮�**妯℃澘鏍煎紡鏍蜂緥**锛堢敤鎴锋墜宸ュ熀鍑嗭紝闈炵▼搴忕敓鎴愶級锛�
+  - `鐢熸垚_鍏矾鏃呭鍒嗗競宸�.xlsx`锛�58琛屆�284鍒椼��237 鍚堝苟锛況4-7=鍏ㄧ渷(瀹㈣繍閲�/鍛ㄨ浆閲�/涓綋瀹㈣繍閲�/涓綋鍛ㄨ浆閲�)锛宺8 璧� 17 甯傚窞 2~4 琛屼笉绛夛紙姝︽眽/閯傚窞/鑽嗗窞/鎭╂柦/浠欐/娼滄睙/澶╅棬/鏋楀尯鏃犱釜浣撹锛夛紱C..O=2026骞�1-6鏈�+绱銆丷..AP=2025骞�12鏈�+绱銆丄S..=2024 鍏ㄥ勾锛涘悓姣�/绱=鍏紡锛屽巻鍙插�兼牱渚嬭嚜甯︺��
+  - `鐢熸垚_涓彛寰勬槑缁�.xlsx`锛�188琛屆�43鍒椼��143 鍚堝苟锛涘杩愰噺鍧� r5-94 + 鍛ㄨ浆閲忓潡 r99-188锛屽悇=鍏ㄧ渷+17甯傚窞脳5鎸囨爣(鎬�/鐝嚎/鍏氦/鍑虹/缃戠害杞�)锛涙暟鎹尯 **2378 涓閮ㄥ紩鐢ㄥ叕寮�**锛坄[1]鐝嚎鍖呰溅!C8`銆乣[1]鍏氦!C9-[1]鍏氦!C11`銆乣[1]鍑虹!`銆乣[1]缃戠害杞�!`锛夋寚鍚� `D:\鐜嬬惔鐨勫伐浣滄枃浠禱瀹㈣揣杩愭湀搴︽暟鎹眹鎬籠2026骞碶6鏈圽2026骞�6鏈堝杩愰噺銆佽揣杩愰噺(111)1.xlsx`锛涘唴閮ㄥ叕寮� 4818 涓紙鍚屾瘮/绱锛夈��
+  - `鐢熸垚_涓彛寰勬帓鍚�.xlsx`锛�20琛屆�9鍒�**鍗曞潡绱**锛堝競宸�/瀹㈣繍閲�/鎺掑悕/鍚屾瘮/澧為�熸帓鍚�/鍛ㄨ浆閲�/鎺掑悕/鍚屾瘮/澧為�熸帓鍚嶏紝RANK+SUM 鍏紡锛屾爣棰�"2026骞�1-6鏈堝叏鐪佸垎甯傚窞绱瀹屾垚閬撹矾瀹㈣繍鐢熶骇鎯呭喌"锛夆�斺�旀敞鎰忓伐浣滅翱鍐呭祵 sheet 鏄弻鍧�(褰撴湀+绱18鍒�)锛岀嫭绔嬫牱渚嬫敼鎴愬崟鍧楋紝闇�涓庣敤鎴风‘璁ゆ寜鍝鐢熸垚銆�
+  - `鐢熸垚_涓彛寰勫垎鏋�.xlsx`锛�7琛屆�8鍒椼��1 鍚堝苟锛涙��/鐝嚎/鍏氦/鍑虹/缃戠害杞� 脳 绱瀹㈣繍閲�/鍚屾瘮/鍗犳瘮 + 绱鍛ㄨ浆閲�/鍚屾瘮/鍗犳瘮锛堟��=23496.1693/0.0777銆佸懆杞� 917059.1993/-0.0295锛夈��
+- 鎶曡祫鏂板 3 涓� 0 瀛楄妭闇�姹� txt锛氣憼鎶曡祫杈撳叆瑕佷笌銆婃ā鏉縚鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7)銆嬫牎楠屻�佷笉涓�鑷村脊绐楋紱鈶℃寜 6/7 鏈堟枃浠跺す甯傚窞鎶ヨ〃鏇存柊銆�6/7鏈堢墿娴佺珯鍦烘姇璧勬槑缁嗐�嬮」鐩拰鏁版嵁锛涒憿鎸� 6/7 鏈堟枃浠跺す鏇存柊銆婃洿鏂癬6/7鏈堝杩愮珯鎶曡祫鏄庣粏琛ㄣ�嬩袱寮犲ぇ琛紙鐢ㄦ埛鍘熸枃鐤戜技绗旇锛屽簲涓� 6鏈�/7鏈堜袱寮狅級銆�
+**娴嬭瘯缁撴灉锛堝墠鍚庣鍧囧湪璺戯細8090/8080锛�**
+- 鏃呭瀵煎叆鏂� xlsx锛歚POST /api/import/passengerMonthly` 鈫� 769 鏉℃垚鍔�+767 杩愭斂锛�0 澶辫触锛沗/api/data/list?type=h2031&period=2026-07` total=769 鉁擄紱cityBus=142銆乧ityTaxi=17 鉁撱��
+- 鏃呭 4 琛ㄥ綋鍓嶅鍑虹粨鏋勫叏閮ㄤ笌鏍蜂緥涓嶇锛氬垎甯傚窞 401脳96/0鍚堝苟 vs 58脳284/237锛涗腑鍙e緞鏄庣粏 191脳96/0 vs 188脳43/143锛涙帓鍚� 18鍒楀弻鍧� vs 9鍒楀崟鍧楋紱鍒嗘瀽 7脳8/0鍚堝苟 vs 7脳8/1鍚堝苟 鈫� 闇�鍏ㄩ儴鎸夋牱渚嬫ā鏉垮寲銆�
+- 鎶曡祫鏍¢獙锛歚POST /api/audit/execute?reportType=INVEST&period=2026-07` 鈫� 2 鏉� PENDING锛圧ULE_I004 閮戜竾楂橀搧宸翠笢缁煎悎瀹㈣繍鏋㈢航鎶曡祫绯荤粺缂哄け锛汻ULE_I003 澶╅棬甯傜墿娴佷腑蹇冩湰鏈堝畬鎴� 3058 vs 鎶曡祫绯荤粺 600锛岀粷瀵瑰樊2458/鐩稿宸�409.7%锛夆湏 鏍¢獙鏁版嵁宸插叿澶囷紙investment_project/monthly/system 鍚� 148 鏉★級锛�**鍓嶇寮圭獥鏈疄鐜�**銆�
+- 鎶曡祫 4 琛ㄥ鍑� investPlan/FiveYearLogistics/Billion/County 鍏ㄩ儴 OK 鉁擄紙08-24 宸查獙璇佷笌 7 鏈堟牱渚嬩竴鑷达級銆�
+**寰呯敤鎴风‘璁ゅ彛寰勫悗瀹炴柦**
+1. 鏃呭 4 琛ㄦā鏉垮寲锛堜豢 `exportCityByTemplate`锛夛細浠� `docs/鍏矾鏃呭+鑳借��/杈撳嚭/` 4 寮犱负搴曠锛屼繚鐣欒〃澶�/鍚堝苟/鍒楀/琛岄珮/鏍峰紡/鍐呴儴鍏紡锛屾暟鎹尯濉簱鍐呭�硷紱**澶栭儴寮曠敤鍏紡蹇呴』鏇挎崲涓烘暟鍊�**锛堟簮鏂囦欢 `鐜嬬惔鐨勫伐浣滄枃浠禶 涓嶅湪椤圭洰鍐咃紝鍚﹀垯鎵撳紑鏂摼寮规洿鏂帮級锛涚敓鎴愬悗 `FormulaEvaluator.evaluateAll()` 閲嶇畻缂撳瓨鍊笺��
+2. 闇�纭锛氬垎甯傚窞"涓綋瀹㈣繍閲�/鍛ㄨ浆閲�"琛屾暟鎹簮锛堣緭鍏ユ棤涓綋瀛楁锛夛紱涓彛寰� 鍑虹/缃戠害杞� 鍩庨檯鍩庝埂鍙e緞锛堟牱渚嬪嚭绉� 3131.95/缃戠害杞� 2063.45 涓虹敤鎴峰弬鑰冨�硷紝DB 鏃犳瀛楁锛屽叕浜ゆ湁 passengerChengxiang/turnoverChengxiang 鍙~锛夛紱鎺掑悕琛ㄦ寜鍗曞潡绱杩樻槸褰撴湀+绱鍙屽潡銆�
+3. 鎶曡祫寮圭獥锛欴ataImport.vue 瀵煎叆鎶曡祫鏈堟姤鍚庤皟 audit/execute?reportType=INVEST锛屾湁 PENDING 寮圭獥鍒楀樊寮傛槑缁嗐��
+4. 鎶曡祫杈撳叆鏇存柊 M2锛氳В鏋� 6/7 鏈堟枃浠跺す甯傚窞 xls 鎶ヨ〃鈫掓寜椤圭洰鍚嶆洿鏂般�婃洿鏂癬6/7鏈堝杩愮珯/鐗╂祦绔欏満鎶曡祫鏄庣粏銆嬪瓧娈碉紙鑷紑濮嬬疮璁�/鑷勾鍒濈疮璁�/鏈湀瀹屾垚/寤鸿闃舵/褰㈣薄杩涘害锛夛紝闇�鍏堝畾鍖归厤閿笌瑕嗙洊绛栫暐锛屽缓璁� 1 甯傚窞鍘熷瀷銆�
+**涓存椂浜х墿**锛歚_test_passenger/`锛�4 寮犳梾瀹㈢幇瀵煎嚭 + 4 寮犳姇璧勭幇瀵煎嚭锛屽姣旂敤锛夈��
+
+### 2026-08-25锛堢画6锛屽疄鏂藉畬鎴愶級锛氭梾瀹� 4 琛ㄦā鏉垮寲 + 涓綋瀹㈣繍閲忓鍏� + 鎶曡祫鏍¢獙寮圭獥锛堢敤鎴峰凡鎷嶆澘 3 鍙e緞锛�
+**鍙e緞纭**锛氣憼涓彛寰勬槑缁嗚法绨垮叕寮忊啋鏇挎崲涓哄簱鍐呮暟鍊硷紙2025 鍒椾繚鐣欐ā鏉跨紦瀛樺�硷級锛涒憽鍒嗗競宸�"涓綋瀹㈣繍閲�/鍛ㄨ浆閲�"琛�=鐢ㄦ埛瀵煎叆涓綋鏁版嵁鍚庢寜甯傚窞鑱氬悎濉厖锛涒憿涓彛寰勬帓鍚�=鍗曞潡绱锛堥�� 1 涓湀鍑哄綋鏈堛��1-6 鏈堝嚭绱锛夈��
+**鍚庣锛堝凡缂栬瘧閲嶅惎楠岃瘉锛�**
+- `application.yml` 鏂板 `passenger.template-dir: docs/鍏矾鏃呭+鑳借��/杈撳嚭`锛沗ReportExportService` 4 涓梾瀹㈠鍑烘柟娉曞叏閮ㄦ敼涓烘ā鏉垮簳绋跨敓鎴愶紙淇濈暀琛ㄥご/鍚堝苟/鍒楀/鍏紡/鏍峰紡锛屼粎鏇挎崲鏁版嵁锛夛細
+  - 鍒嗗競宸烇細2026 骞� 1..6 鏈堝垪濉簱鍐呯彮绾垮杩愰噺/鍛ㄨ浆閲忥紙metric 0/1锛夛紝涓綋琛屽~涓綋瀵煎叆鏁版嵁锛�2025/2024 鍘嗗彶鍒椾繚鐣欐ā鏉垮�硷紝绱/鍚屾瘮/鍏ㄧ渷琛屽叕寮忎繚鐣欓噸绠椼��
+  - 涓彛寰勬槑缁嗭細鏁版嵁鍖� 2378 涓法绨垮紩鐢ㄥ叏閮ㄦ浛鎹⑩�斺��2026 鏈堝害鍒楀~搴撳唴鍊硷紙鐝嚎=h2031銆佸叕浜�=cityBus 鍩庨檯鍩庝埂銆佸嚭绉�/缃戠害杞�=鏄惧紡 0锛夛紝2025 鍒楁浛鎹负妯℃澘缂撳瓨鍊硷紙鍘诲勾鏁版嵁涓嶄涪锛夛紝绱/鍚屾瘮/鎬昏鍏紡淇濈暀閲嶇畻銆�
+  - 涓彛寰勬帓鍚嶏細鍗曞潡绱锛屾爣棰橀殢鎶ヨ〃鏈燂紙1 鏈�="2026骞�1鏈堚��"锛�1-6 鏈�="2026骞�1-6鏈堚�︾疮璁�"锛屽勾鎶�="1-12鏈堚��"锛夛紱B/D/F/H 濉�硷紝RANK/SUM 鍏紡淇濈暀銆�
+  - 涓彛寰勫垎鏋愶細B/C/F/G 濉�硷紙鎬�/鐝嚎/鍏氦/鍑虹/缃戠害杞︾疮璁�+鍚屾瘮锛夛紝鍗犳瘮鍏紡淇濈暀銆�
+- 鏂板涓綋瀹㈣繍閲忓鍏ワ細琛� `passenger_individual_monthly`锛坧eriod/region_code/enterprise_code/enterprise_name/passenger_count/turnover锛夛紝鎺ュ彛 `POST /api/import/passengerIndividual`锛圥OI 瑙f瀽鍒楀悕鍚�"涓綋瀹㈣繍閲�/涓綋鍛ㄨ浆閲�"锛屽悓 period 骞傜瓑锛夛紝瀹炰綋/Mapper `PassengerIndividualMonthly`銆�
+- 鏂囦欢鍚嶇粺涓�涓烘牱渚嬪悕锛氱敓鎴恄鍏矾鏃呭鍒嗗競宸�.xlsx銆佺敓鎴恄涓彛寰勬槑缁�.xlsx銆佺敓鎴恄涓彛寰勬帓鍚�.xlsx銆佺敓鎴恄涓彛寰勫垎鏋�.xlsx锛圧EPORT_FILE_NAMES + series zip + 鍓嶇鍚屾锛夈��
+**鍓嶇锛坈hunk 4/7 宸插惈鏂颁唬鐮侊級**
+- `DataImport.vue`锛氬叕璺梾瀹㈢粍鏂板銆屼釜浣撳杩愰噺/鍛ㄨ浆閲忋�嶅鍏ワ紱鎶曡祫鏈堟姤锛堝杩�/鐗╂祦锛夊鍏ユ垚鍔熷悗鑷姩璋� `audit/execute?reportType=INVEST`锛屾湁 PENDING 寮圭獥鍒楀嚭宸紓鏄庣粏锛堣鍒欏悕/椤圭洰/瀹為檯/鍩哄噯/宸紓锛夛紝鏃犲樊寮傛彁绀轰竴鑷淬��
+- `ReportExport.vue`锛氭梾瀹� 4 寮犻�夐」鍚嶄笌鏍蜂緥涓�鑷达紝鎻愮ず璇洿鏂般��
+**楠岃瘉锛堝叏閮ㄩ�氳繃锛�**
+- 4 寮犺〃 dims/鍚堝苟鍗曞厓鏍间笌鏍蜂緥瀹屽叏涓�鑷达紙鍒嗗競宸� 58脳284/237銆佷腑鍙e緞鏄庣粏 188脳43/143銆佹帓鍚� 20脳9/1銆佸垎鏋� 7脳8/1锛夛紝澶栭儴寮曠敤娈嬬暀 0銆�
+- 鏁板�兼娊鏌ワ細姝︽眽 6 鏈堢彮绾垮杩愰噺 341.1759/鍛ㄨ浆閲� 19301.074锛堜笌鏍蜂緥涓�鑷达級锛涢粍鐭充釜浣撳杩愰噺 3.0/鍛ㄨ浆閲� 150.0锛堝鍏� 30000 浜�/150 涓囦汉鍏噷锛夛紱2025 鍒椾繚鐣欐ā鏉垮�间笖杞暟鍊硷紙R11=255.17锛夛紱鍏ㄧ渷琛屽叕寮忛噸绠楁纭紙鍛ㄨ浆閲� K 鍒� 112416.37锛夈��
+- 涓綋瀵煎叆锛�4 鏉℃垚鍔� 0 澶辫触锛岄噸澶嶅鍏ュ箓绛夛紱娴嬭瘯鏁版嵁宸叉竻鐞嗭紙DB 0 鏉★級銆�
+- 骞存姤妯″紡锛氭爣棰�"2026骞�1-12鏈堚��"姝g‘銆�
+- 鎶曡祫鏍¢獙锛�2 鏉� PENDING锛堥儜涓囬珮閾佸反涓滄灑绾界己澶便�佸ぉ闂ㄧ墿娴� 3058 vs 600锛夆啋 鍓嶇寮圭獥鏁版嵁婧愬氨缁��
+**韪╁潙锛堥噸瑕侊級**
+- **POI 5.2.5 `Cell.setCellValue(double)` 瀵瑰叕寮忓崟鍏冩牸鍙洿鏂扮紦瀛樺�笺�佷笉绉婚櫎鍏紡**锛佸啓鍏ュ墠蹇呴』鍏� `setBlank()` 鍐� `setCellValue`锛涙樉寮� 0 闇�鎿嶄綔搴曞眰 XML锛坄getCTCell().setT(N).setV("0")`锛夈�備腑鍙e緞鏄庣粏澶栭儴寮曠敤鏇挎崲鍥犳杩斿伐涓�娆°��
+- 鍒嗗競宸炴ā鏉垮懆杞噺/涓綋琛� A 鍒椾负绌猴紙鍚堝苟鍗曞厓鏍煎彧鍐欓琛岋級鈫� 琛岃瘑鍒』鐢� B 鍒楁寚鏍� + 鍓嶄竴涓潪绌� A 鍒楋紙currentRegion锛夛紝鍚﹀垯杩欎簺琛岃璺宠繃淇濈暀妯℃澘鏍蜂緥鍊笺��
+- 鍘� `metricValue(agg, 1)` 杩斿洖瀹㈣繍閲忥紙0/1 鍙e緞鐩稿悓锛夆啋 鍒嗗競宸炲懆杞噺琛岀洿鎺ュ彇 `agg.turnoverTotal/10000`銆�
+- 鍏变韩鍏紡鍒ゆ柇锛歅OI `getCellFormula()` 灞曞紑姝e父锛屼絾 `keepCached`/`fillMidCell` 蹇呴』鍏堣缂撳瓨鍊煎啀 setBlank锛堥『搴忎笉鍙弽锛夈��
+**寰呭姙**锛氣憼鍒嗗競宸�/涓彛寰勬槑缁嗘ā鏉夸粎 1-6 鏈堝垪锛�7-12 鏈堥渶鐢ㄦ埛涓嬪崐骞存ā鏉匡紱鈶′腑鍙e緞鍑虹/缃戠害杞﹀煄闄呭煄涔℃暟鎹簮寰呮帴鍏ワ紙鐜版樉寮� 0锛夛紱鈶㈡姇璧� M2锛堟寜 6/7 鏈堟枃浠跺す甯傚窞鎶ヨ〃鏇存柊銆婃洿鏂癬6/7鏈堝杩愮珯/鐗╂祦绔欏満鎶曡祫鏄庣粏銆嬶級鏈紑宸ワ紝闇�瀹氬尮閰嶈鍒欙紱鈶B 鎶曡祫鏈堝害浠� 50 鏉� 2026-07锛坕nvestment_monthly锛夛紝瀹屾暣鎶ヨ〃闇�琛ュ鍏ャ��
+
+### 2026-08-25锛堢画7锛夛細妯℃澘涓�鑷存�у叏閲忔牳鏌� + 璐ц繍 3 琛ㄦā鏉垮寲鏀归�狅紙宸插畬鎴愰獙璇侊級
+**鐢ㄦ埛瑕佹眰**锛氭牳鏌� 鍩庡競瀹㈣繍/璐ц繍/鑳借��/鏉垮潡锛堟梾瀹�/鎶曡祫锛夊鍑烘槸鍚︽弧瓒炽�屼笌瀵煎叆妯℃澘鐨勫垪瀹姐�佽〃澶淬�佸悎骞惰鍒欏畬鍏ㄤ竴鑷达紝浠呮浛鎹㈡暟鎹紝瀵煎嚭 Excel銆嶃��
+**鏍告煡缁撹锛�14 寮犲叏閮� PASS锛�**
+- 鍩庡競瀹㈣繍 6 寮狅紙鍏氦/宸℃父鍑虹/杞ㄩ亾杞浮/鍒嗗競宸炵疮璁�/鍚勫競宸炴槑缁�/姹囨�伙級锛氱粨鏋勫畬鍏ㄤ竴鑷达紙鍚� 63/63/95/18/18/4 鍚堝苟銆佸垪瀹姐�佽〃澶达級锛涙暟鎹樊寮傚潎涓哄彛寰勬墍鑷达紙閫夊綋鏈�/绱銆佸悓姣旀竻绌恒�佺綉绾﹁溅鏄惧紡 0锛夈��
+- 鍏矾鏃呭 4 寮狅紙鍒嗗競宸�/涓彛寰勬槑缁�/鎺掑悕/鍒嗘瀽锛夛細缁撴瀯瀹屽叏涓�鑷达紙237/143/1/1 鍚堝苟锛夈��
+- 鑳借�� 1 寮狅細鍏ㄧ渷 Sheet1 涓� `妯℃澘_鑳借繍姹囨�昏〃 .xlsx` 瀹屽叏涓�鑷达紙鍚堝苟 2銆佸垪瀹� 5锛夛紱鍙︽寜闇�姹傜敓鎴� 17 甯傚窞 sheet锛涙爣棰樻敼鍥炴ā鏉跨殑銆岀X瀛e害銆嶏紙鍘熶唬鐮佸啓銆孹瀛e害銆嶏級銆�
+- 璐ц繍 3 寮狅細**鏈浠庨浂寤鸿〃鏀归�犱负妯℃澘搴曠鐢熸垚**锛堟ā鏉�=`docs/璐ц繍/鐢熸垚_*.xlsx`锛屼笌 `璐ц繍绠�鍖栧伐浣�.xlsx` 鍚岀粨鏋勶級锛�
+  - 鍒嗗競宸炴槑缁嗭細浠ユā鏉夸负搴曠锛屽~ C~M 鏈堝害鍊�/鍚屾瘮 + O/P 绱/绱鍚屾瘮锛涙湀搴﹀垪浠� 1-6 鏈堬紙妯℃澘鍥哄畾锛夛紝绱鍒楁寜鎵�閫夋湡 1..N 鏈堬紙N>6 鏃舵湀搴﹀垪浠嶅彧鏄剧ず 1-6 鏈堬紝涓庢梾瀹㈠彛寰勪竴鑷达紝7-12 鏈堝緟涓嬪崐骞存ā鏉匡級銆�
+  - 璐ц繍閲忔帓鍚�/鍛ㄨ浆閲忔帓鍚嶏細浠ユā鏉夸负搴曠锛屽~ B~P锛堣涓�/瑙勪笅/鍚堣 + 鎺掑悕/鍗犲叏鐪佹瘮閲�/鍚屾瘮/澧為�熸帓鍚嶏級锛涜揣杩愰噺 Q/R 鍗犳瘮鍏紡淇濈暀涓嶅姩锛屽懆杞噺 Q/R 鎸夋柊鏁版嵁閲嶇畻銆�
+  - 鎺掑悕琛ㄨˉ涓婁簡鍘熷厛缂哄け鐨� 瑙勪笂鍚屾瘮/瑙勪笅鍚屾瘮锛堝叏鐪�+甯傚窞锛涜涓嬪悓姣�=妯℃澘鍘诲勾鍚堣-H2032鍘诲勾瑙勪笂锛夈��
+- 鏂板 `freight.template-dir: docs/璐ц繍` 閰嶇疆锛坅pplication.yml锛�+ `resolveFreightTemplate`銆�
+- 淇杩囩▼涓彂鐜扮殑鍧戯細POI 鍒楀彿 0-based 宸竴鍒楋紙鏈堝害鍊煎啓鍒� B/D/F/..锛屽凡淇涓� C/E/G/..锛夛紱鍐欏�煎墠椤� setBlank锛圥OI 鍏紡鍗曞厓鏍煎彧鏇存柊缂撳瓨锛夈��
+**楠岃瘉鏂瑰紡**锛歚_test_check2/v5_*.xlsx`锛�14 寮犵幇瀵煎嚭锛夐�愪竴涓庢ā鏉垮姣� dims/鍚堝苟闆嗗悎/鍒楀闆嗗悎锛屽叏閮� PASS锛涙娊鏌ユ帓鍚嶈〃 2026-07 瑙勪笂鏁版嵁钀戒綅姝g‘锛堝叏鐪� 186.8988銆佹姹� 72.9224 鎺掑悕 1銆佸崰鍏ㄧ渷姣旈噸 0.3902锛夛紱鑳借�楁爣棰�=2026骞寸浜屽搴﹁兘鑰楁眹鎬绘儏鍐佃〃銆�
+**娉ㄦ剰**锛氳揣杩� DB 褰撳墠浠� h2032 鏈� 2026-07 鏁版嵁锛�40 鏉★級锛宍freight_turnover_import`/`scale_split_transport` 涓虹┖ 鈫� 鏄庣粏/鎺掑悕鐜板鍑哄ぇ閮ㄥ垎涓虹┖锛堜粎瑙勪笂琛屾湁鍊硷級锛屽睘鏁版嵁鏈鍏ワ紝闈炴牸寮忛棶棰橈紱鐢ㄦ埛闇�鍏堝鍏ャ�婃ā鏉縚璐ц繍閲忓懆杞噺銆嬨�婅涓婅涓嬫媶鍒嗚〃銆嬨��
+**寰呭姙涓嶅彉**锛氣憼妯℃澘浠� 1-6 鏈堝垪锛�7-12 鏈堥渶涓嬪崐骞存ā鏉匡紙璐ц繍/鏃呭鍚岋級锛涒憽鎶曡祫 4 琛ㄤ粛涓轰唬鐮佽嚜寤哄竷灞�锛堟牱渚嬩负 .xls 鏃犲悎骞�/鏃犺嚜瀹氫箟鍒楀锛屾殏涓庢牱渚嬪榻愶級锛屾湭绾冲叆妯℃澘搴曠銆�
+
+### 2026-08-25锛堢画8锛夛細鎶曡祫 4 琛ㄦ敼涓恒�屾ā鏉垮簳绋垮師浣嶆浛鎹€�嶏紙宸插畬鎴愰獙璇侊級
+**鐢ㄦ埛瑕佹眰**锛氬叕璺梾瀹€�佸煄甯傚杩愩�佽兘鑰椼�佹姇璧勫洓涓ā鍧楀繀椤绘寜銆愭牸寮忔ā鏉裤�戠殑鍒楀銆佽〃澶淬�佸悎骞惰鍒欏畬鍏ㄤ竴鑷寸敓鎴愶紝浠呮浛鎹㈡暟鎹紝瀵煎嚭 Excel銆�
+**鏈疆鏀归�狅紙ReportExportService.java锛宍traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java`锛�**
+- `investPlan`锛堝叏鐪侀瀹夋帓璁″垝杩涘害鎯呭喌姹囨�伙紝绾� 2380 琛岃捣锛夛細閫愯鎵弿 R5~鏈熬锛屾寜 鍚堣琛�/鍧楁爣棰樿/甯傚窞灏忚妭/鍒嗙粍鏍囬/椤圭洰琛� 鍒嗙被锛涙湭鍖归厤椤圭洰鏁磋娓呯┖锛堜繚鐣欏悎骞舵牱寮忥級锛沗涓�銆佸杩愮珯鍦哄熀纭�璁炬柦寤鸿`鈫掑杩愮珯鍦哄潡銆乣浜屻�佷氦閫氱墿娴佸熀纭�璁炬柦寤鸿`鈫掔墿娴佸洯鍖哄潡銆乣涓夈�佽�佹棫钀ヨ繍璐ц溅鎶ュ簾鏇存柊`鈫掓暣鍧楁竻绌猴紙鏃犳暟鎹簮锛夈��
+- `investFiveYearLogistics`锛堝崄浜斾簲瑙勫垝鐗╂祦椤圭洰锛岀害 2511 琛岃捣锛夛細sheet1 鍏ㄩ噺鍘熶綅鏇挎崲锛�85脳248/99 鍚堝苟锛夛紱sheet0锛堟湀2锛変粎鏇存柊鏃ュ巻鏍囬锛堟ā鏉� sheet0 鏈韩鏃犳牱渚嬫暟鎹級銆�
+- `investBillion`锛堜嚎鍏冩姇璧勶級锛氫笂杞凡涓烘ā鏉垮簳绋匡紝淇濇寔锛�17脳6/2 + 80脳24/25锛夈��
+- `investCounty`锛堢粡娴庡己鍘匡紝绾� 2659 琛岃捣锛夛細閬嶅巻 R4 璧锋寜 B 鍒楅」鐩悕鍖归厤锛屾湭鍖归厤淇濈暀绌虹櫧銆�
+- 鏂板杈呭姪鏂规硶锛堢害 2710 琛岃捣锛夛細`nvl`/`blankInvestRow`锛坰etBlank 0..lastCol 淇濈暀鏍峰紡/鍚堝苟锛�/`templateCityFull`锛堟ā鏉跨煭鍚嶁啋瑙勮寖甯傚窞锛屽惈 鏋楀尯/鎭╂柦 鍒悕锛岃蛋 RegionUtil锛�/`isCityLabel`/`matchInvest`锛堝厛鎸夌被鍒�/甯傚窞杩囨护鍊欓�夛紝鍐嶈瘎鍒嗗彇鏈�浼橈級/`normInvestName`/`investNameScore`锛堝綊涓�鍖栫簿纭�>鍓嶇紑鍖呭惈>涓悗閮ㄥ寘鍚�>5瀛�>瀛愬簭鍒�>LCS 鍏滃簳锛涘幙鍖哄墠缂�涓嶄竴鑷存墸 400锛涘綊涓�鍖栧悓鍚嶄笖妯℃澘甯﹀幙鍖轰絾鍊欓�変笉鍚啋鎷掔粷锛岄伩鍏� 瀹滈兘鈫斿綋闃� 璇厤锛�/`isInvestSubsequence`/`investLcs`/`extractInvestCounty`銆�
+- `writeInvestAggHssf`锛氱Щ闄� col12锛堣嚜绛癸級鍐欏叆锛堟棤鏁版嵁婧愶級锛屼繚鐣� 7/8/9/22/23銆�
+**楠岃瘉锛堝叏閮ㄩ�氳繃锛�**
+- 缁撴瀯瀵规瘮 `_shots/cmp_invest2.py`锛�4 琛� dims/鍚堝苟/鍒楀涓庢ā鏉垮畬鍏ㄤ竴鑷达紙investPlan 272脳38/111锛沬nvestFiveYearLogistics sheet0 32脳24/33 + sheet1 85脳248/99锛沬nvestBillion 17脳6/2 + 80脳24/25锛沬nvestCounty 65534脳256/57锛塒ASS銆�
+- 鏁版嵁钀戒綅 `_shots/dump_data_check.py`锛氬叏鐪佸悎璁°�佸競宸炲皬鑺傘�侀」鐩暟鎹�佽�佹棫璐ц溅娈垫竻绌恒�佷簲浜旇〃鍚堣琛屾竻绌恒�佸己鍘胯〃椤圭洰鍙婂妗堝彿鍐欏叆鍧囨纭��
+- 鍚嶇О鍖归厤璐ㄩ噺 `_shots/sim_match3.py`锛歅LAN 130/183銆丗IVEYEAR 40/63銆丆OUNTY 41/41锛涙湭鍖归厤鍧囦负鈥滄ā鏉挎牱渚嬫湁浣嗘眹鎬诲ぇ琛�/DB 鏃犫�濈殑椤圭洰锛堣�佹棫璐ц溅娈点�佸啘鏉戠珯浜�佹姹夊ぉ娌崇患鍚堝杩愭灑绾姐�佽崋宸炲競閾佹按鑱旇繍鐗╂祦涓績绛夛級锛屽睘鏁版嵁缂哄け闈炶閰嶃��
+- 浜х墿锛歚_shots/invest_out2/`锛�4 .xls + selected4.zip锛夛紱鏈疆 API 澶嶆祴锛歚GET /api/report/export/selected?period=2026-07&mode=month&types=investPlan,investFiveYearLogistics,investBillion,investCounty` 姝e父杩斿洖 zip锛�4 鏂囦欢榻愬叏锛屽懡鍚嶆纭級銆�
+**鍓嶇鐜扮姸纭锛堟湰杞牳瀵癸紝鏃犻渶鍐嶆敼锛�**
+- 鎶ヨ〃鐢熸垚椤碉細绗竴鎺� 璐ц繍/鍏矾鏃呭/鍩庡競瀹㈣繍锛岀浜屾帓 鑳借��/鎶曡祫锛涚敓鎴愬唴瀹瑰閫変笅鎷�+鍏ㄩ��/娓呯┖+鍗曘�屽鍑恒�嶆寜閽紙1 寮犵洿鎺ヤ笅杞姐�佸寮犳墦鍖� zip锛夛紱鏈堟姤/骞存姤 鍗曢�夐綈鍏ㄣ�傛埅鍥� `_shots/report_page.png`銆�
+- 鏁版嵁瀵煎叆椤碉細鎶ヨ〃鏈熷崱鐗� + 璐ц繍/鍏矾鏃呭/鍩庡競瀹㈣繍/鑳借��/鎶曡祫浜斾釜涓嬫媺妗嗭紙妯″潡閫夋嫨锛夛紝涓婁紶/瀵煎叆/鏈�杩戣褰曢綈鍏ㄣ��
+- 浼佷笟瑙i噴椤碉細4 绫绘姤琛ㄥ瓧娈靛畬鏁达紙浼佷笟鍚嶇О/杞﹁締鏁�/瀹㈣繍閲�/鍛ㄨ浆閲�/骞冲潎杩愯窛/浼佷笟瑙i噴绛夛級锛屾棤鈥滃彧鑳界湅鍒颁紒涓氬悕鈥濋棶棰樸��
+- 鏁版嵁鏌ョ湅锛氳建杩归噷绋�/璐ц繍閲忓懆杞噺/H203-1/鍩庡競鍏氦鏈堟姤/宸℃父鍑虹鏈堟姤 鍧囨湁銆屽叏灞忔煡鐪嬨�嶃��
+- 瀵嗙爜鍔犲瘑锛歴ys_user.admin 宸叉槸 BCrypt锛�60 浣嶏級锛屾弧瓒斥�滆处鍙峰姞瀵嗗瓨鍌ㄢ�濋渶姹傘��
+**閬楃暀**锛氣憼鑰佹棫璐ц溅鎶ュ簾鏇存柊鍧�/浜斾簲琛ㄥ悎璁¤鏃犳暟鎹簮锛堢瓑浜屾湡鏁版嵁锛夛紱鈶¤揣杩� DB 缂恒�婃ā鏉縚璐ц繍閲忓懆杞噺銆嬨�婅涓婅涓嬫媶鍒嗚〃銆嬪鍏ワ紝鏄庣粏/鎺掑悕瀵煎嚭澶ч儴鍒嗕负绌猴紙鏍煎紡姝e父锛屾暟鎹湭瀵煎叆锛夛紱鈶㈡ā鏉夸粎 1-6 鏈堝垪锛�7-12 鏈堥渶涓嬪崐骞存ā鏉匡紙璐ц繍/鏃呭鍚岋級锛涒懀鎶曡祫 M2锛堝競宸炲崟琛ㄨВ鏋愶級鏈紑宸ャ��
+
+
+### 2026-08-25锛堢画9锛夛細HANDOFF.md 鎭㈠ + 涓嬬彮鍓嶆敹灏撅紙宸插畬鎴愶級
+**浜嬩欢**锛氭湰浼氳瘽涓� HANDOFF.md 鏇捐璇鐩栵紙涓�鏉� PowerShell WriteAllLines 鍛戒护鍥� AddRange(Object[]) 绫诲瀷閿欒澶辫触锛宍$result` 鍙墿鏂板ご琛� 鈫� 鏂囦欢琚鐩栨垚 506 瀛楄妭锛夛紝闅忓悗涓�娆℃湁 bug 鐨勯噸鏀惧張鎶婂畠姹℃煋鎴� 28,084 瀛楄妭锛堛�屽崄鑺傘�嶉噸澶嶏級銆�
+**鎭㈠鏂规硶锛堝凡瀹屾垚骞堕獙璇侊級**锛氫粠 4 涓細璇� JSONL 鏃ュ織閲嶆斁 31 鏉″巻鍙插啓鍏ュ懡浠ゅ埌娌欑洅 `_shots/handoff_replay/`锛堝繀椤荤敤 pwsh 7.6.4 + 璺緞鏇挎崲淇鐗堣剼鏈紝璺緞鍚弽鏂滄潬闇�鐢� chr(92) 鎷兼帴閬垮厤琚� Python 瑙f瀽鎴愯浆涔夛級锛岄�愬瓧鏍¢獙锛歜ase 涓� 08-22 鍘熸枃涓�鑷淬�佹枃浠跺ご灏句笌鐮村潖鍓嶈鍙栧揩鐓т竴鑷淬�佸悇姝ヨ緭鍑轰笌鍘熷杈撳嚭瀵归綈銆傚凡鎭㈠涓� 74,447 瀛楄妭锛堝師 74,547锛屽樊 100 瀛楄妭/52 瀛楃涓轰腑闂存绌虹櫧绾у樊寮傦紝鏃ュ織宸叉埅鏂棤娉曟仮澶嶏紝涓嶅奖鍝嶅唴瀹癸級銆傛薄鏌撶増澶囦唤鍦� `_shots/HANDOFF.md.polluted_1633.bak`锛堜繚鐣欐湭鍒狅級銆�
+**涓嬬彮鍓嶆敹灏�**锛氬凡鐢� `start-dev.ps1` 瀹屾暣閲嶅惎鍓嶅悗绔苟楠岃瘉锛�
+- 鍚庣 8090 灏辩华锛堟棩蹇� backend.log锛宍Started TrafficAuditApplication` + `Tomcat started on port(s): 8090`锛孭ID 30380锛孒TTP 200锛夈��
+- 鍓嶇 8080 灏辩华锛堟棩蹇� traffic-audit-web/frontend.log锛宍Compiled successfully` + `App running at:`锛孭ID 23624锛孒TTP 200锛夈��
+- IDEA 鏈綋鍦ㄨ繍琛岋紙idea64.exe锛�11:00 鍚姩锛夛紱鍚庣褰撳墠鐢卞懡浠よ/start-dev 绠$悊锛岃嫢鎯虫敼鍥� IDEA 绠$悊锛氬厛鍦� IDEA Run 閰嶇疆閫� `TrafficAuditApplication` 鐐� 鈻讹紙浼氬崰鐢� 8090锛岄渶鍏堝仠鍛戒护琛屽疄渚嬶級锛屾垨鐩存帴鐢ㄨ剼鏈噸鍚��
+**涓嬫寮�宸ュ缓璁�**锛氬厛璇� AGENTS.md + 鏈枃浠讹紱纭鎶曡祫 M2锛堝競宸炲崟琛ㄨВ鏋愶級浼樺厛绾э紱閬楃暀浜嬮」瑙佺画8锛堣揣杩愭槑缁�/鎺掑悕缂烘暟鎹簮銆佹ā鏉夸粎 1-6 鏈堝垪銆佽�佹棫璐ц溅鎶ュ簾鏇存柊鍧楃瓑浜屾湡锛夈��
+
+
+### 2026-08-26 鍑屾櫒浼氳瘽锛氭暟鎹簱娓呯┖ + 鐢ㄦ埛 4 椤规剰瑙佸鐞嗭紙宸插畬鎴愰獙璇侊級
+**鐢ㄦ埛鎸囦护**锛氣憼鎵�鏈夎緭鍑烘枃妗e繀椤讳笌銆愭牸寮忔ā鏉裤�戝垪瀹�/琛ㄥご/鍚堝苟瀹屽叏涓�鑷淬�佷粎鏇挎崲鏁版嵁锛涒憽閫愭ā鍧楁牳瀵归渶姹傦紙鐐瑰悕锛氬贰娓稿嚭绉熻鍒� 7 鏉$晫闈㈠彧鏄剧ず 4 鏉°�佽兘鑰楄鍒欐病鐪嬶級锛涒憿浼佷笟瑙i噴琛岄棿璺濆お澶э紱鈶i噸澶嶅鍏ユ槸鍚︿細瀛樹袱浠姐��
+**0. 鏁版嵁搴撴竻绌猴紙鐢ㄦ埛瑕佹眰锛�**
+- 26 寮犺〃 TRUNCATE 鍏ㄦ竻锛堣〃缁撴瀯淇濈暀銆佽嚜澧為噸缃級锛屽浠� `_shots/db_backup_20260826_004520.sql`锛�28,115,426 瀛楄妭锛屽惈鍏ㄥ簱缁撴瀯涓庢暟鎹級銆�
+- 娓呯┖鍚庡凡鎭㈠ `audit_rule`锛氫粠澶囦唤鎻愬彇鍘� 33 鏉� + 鏂板 H204 6 鏉� = 39 鏉°�俙sys_role`(4)/`sys_dept`/`sys_user` 鏈仮澶嶏紙鍘� sys_user 鏈氨 0 琛岋紱闇�瑕佹椂浠庡浠芥垨 init.sql 閲嶆柊鍒濆鍖栵級銆�
+**1. 妯℃澘涓�鑷存�э紙鏍稿績淇锛氳矾寰勯厤缃敊璇級**
+- 鍙戠幇锛歚application.yml` 鐨� `passenger.template-dir: docs/鍏矾鏃呭+鑳借��/杈撳嚭` 鎸囧悜**涓嶅瓨鍦�**鐨勭洰褰曪紙瀹為檯鐩綍 `docs/鍏矾鏃呭/杈撳嚭`锛夛紱鑳借�楁棤 `energy.template-dir` 閰嶇疆锛堥粯璁� `docs/鍏矾鏃呭+鑳借�梎 鍚屾牱涓嶅瓨鍦級锛涗唬鐮佽鑳借�楁ā鏉� `妯℃澘_鑳借繍姹囨�昏〃 .xlsx` 鑰岀敤鎴峰噷鏅ㄦ洿鏂扮殑鏄� `docs/鑳借��/鐢熸垚_鑳借繍姹囨�昏〃.xlsx`锛�00:24~00:43 鐢ㄦ埛鎵归噺鏇存柊浜嗚揣杩� 3 寮� + 鏃呭 4 寮� + 鑳借�� 1 寮犳ā鏉匡級銆�
+- 淇锛歚application.yml` 鈫� passenger=docs/鍏矾鏃呭/杈撳嚭銆佹柊澧� energy=docs/鑳借�楋紱`ReportExportService` 鑳借�楁ā鏉垮悕鏀逛负 `鐢熸垚_鑳借繍姹囨�昏〃.xlsx`銆�
+- 楠岃瘉锛�10 寮犲鍑猴紙璐ц繍 3 + 鏃呭 4 + 鑳借�� 1 + 鍩庡競鍏氦/宸℃父鍑虹锛夎皟鐢� `/api/report/export/*` 鍏ㄩ儴 200锛岀敤 openpyxl 瀵规瘮妯℃澘涓庡鍑猴細sheet 缁村害/鍚堝苟鍗曞厓鏍兼暟/鍒楀 鍏ㄩ儴 PASS锛涙姇璧� 4 琛� zip锛坰elected锛夋甯歌繑鍥烇紙缁撴瀯姝ゅ墠宸查獙锛夈��
+**2. 瀹℃牳瑙勫垯鏍稿**
+- CITY_TAXI锛歚sql_city_taxi.sql` 涓庢竻绌哄墠 `audit_rule` 鍧� 7 鏉★紙RULE_T001~T007锛夛紝`AuditEngineService` 寮曟搸 5 绉� compareType 鍏ㄩ儴瀹炵幇锛堝惈 T003 鍛ㄨ浆閲忕幆姣旀帹瀵笺�乀006 鍗曡溅娆¤浇瀹汉鏁板弽鎺ㄣ�乀007 杞藉杞︽涓庡杩愰噺瓒嬪娍锛夈�傗�滅晫闈㈠彧鏈� 4 鏉♀�濆師鍥狅細瀹℃牳缁撴灉椤靛彧鏄剧ず**瑙﹀彂鐨勯棶棰樿褰�**锛屾湭瑙﹀彂鐨勮鍒欑湅涓嶅埌锛涗笖鍘熻鍒欑鐞嗛〉鏄┖澹� `<div>瑙勫垯绠$悊</div>`銆佸悗绔棤瑙勫垯 API銆傗啋 宸插疄鐜拌鍒欑鐞嗛〉锛堣涓嬶級銆�
+- H204 鑳借�楋細闇�姹� 7 鏉★紝鍘熷彧瀹炵幇 4 鏉★紙E001~E004锛孍004 涓洪渶姹傚琛ュ厖锛夈��**鏂板 RULE_E005~E010**锛堝紩鎿� `checkEnergyLoadedRatio/FuelRange/FuelMom/VsFreightReport/AvgDistVsFreight/LoadedTonnageRatio` + `prevQuarter`/`fuelPer100kmMedian` 杈呭姪锛沗executeEnergyAudit` 澧炲姞涓婁竴瀛e害鐜瘮鍩哄噯涓� H2032 浼佷笟鏈堟姤鍩哄噯锛沗docs/init.sql` 琛ョ瀛愶紱宸插叆搴擄級銆�
+- 瑙勫垯绠$悊椤碉細鏂板 `RuleManageController.java`锛圙ET /api/rules/list銆丳OST /api/rules/toggle銆丳OST /api/rules/update锛�+ 閲嶅啓 `RuleManage.vue`锛�6 绫绘姤琛ㄧ被鍨嬪垏鎹€�佽鍒欏叏鍒楄〃銆侀槇鍊煎彲缂栬緫銆佺瓑绾у彲閫夈�佸惎鐢ㄥ紑鍏筹級銆�
+**3. 浼佷笟瑙i噴琛岄珮**
+- `ExplainReview.vue`锛氳〃鏍� `size="mini"` + 琛岄珮 32px + 鍗曞厓鏍� padding 3px锛涜В閲婂垪 `max-height:62px` 鍐呴儴婊氬姩锛屼笉鍐嶆妸琛屾拺寰楄繃楂樸��
+**4. 閲嶅瀵煎叆骞傜瓑纭**
+- 鍚庣鎵�鏈夊鍏ユ柟娉曪紙H2032/H2031/涓綋/鑳借��/杩愭斂/杞ㄨ抗/瑙勪笂瑙勪笅/璐ц繍鍛ㄨ浆閲�/鎶曡祫/绯荤粺瀵煎嚭/鍏氦/鍑虹/鍑虹杩愭斂锛夊潎涓恒�屽悓鎶ヨ〃鏈熷厛鍒犳棫鏁版嵁+娓呯悊瀵瑰簲瀹℃牳缁撴灉鍐嶆彃鍏ャ�嶏紝閲嶅瀵煎叆**涓嶄細瀛樹袱浠�**锛沗import_batch` 姣忔瀵煎叆璁颁竴鏉″睘鎿嶄綔鐣欑棔锛堥鏈燂級銆�
+- 鍓嶇 `DataImport.vue` 鎸夐挳 `:loading` 闃茶繛鐐癸紱`importCityTaxiAuth` 鏈� `synchronized`锛屽叾浣欐柟娉曟棤閿侊紙鍗曠敤鎴峰疄闄呮棤骞跺彂闂锛屽闇�寮轰竴鑷村彲鍔狅級銆�
+**褰撳墠杩愯鐘舵��**
+- 鍚庣 8090 宸茬敤 start-dev.ps1 閲嶅惎鍔犺浇鏂颁唬鐮侊紙backend.log `Started TrafficAuditApplication` + `Tomcat started on port(s): 8090`锛孒TTP 200锛沗/api/rules/list?reportType=H204` 杩斿洖 10 鏉★級銆�
+- 鍓嶇 8080 鍦ㄨ窇锛坒rontend.log `Compiled successfully` + `App running at:`锛岀儹鏇存柊宸插姞杞� RuleManage/ExplainReview 鏂伴〉闈級銆�
+- 楠岃瘉浜х墿锛歚_shots/verify_export/`锛�10 寮犲鍑� + invest_selected.zip锛夈�乣_shots/cmp_struct2.py`銆乣_shots/cmp_width.py`銆乣_shots/extract_rules.py`銆乣_shots/old_audit_rule.sql`銆乣_shots/patch_*.py` 鍧囦繚鐣欍��
+**閬楃暀/寰呭姙**锛氣憼鎶曡祫 M2锛堝競宸炲崟琛ㄥ閿欒В鏋愶級浠嶆湭寮�宸ワ紙绱犳潗鍦� `docs/鎶曡祫/杈撳叆/` 榻愬锛夛紱鈶�7-12 鏈堟ā鏉块渶鐢ㄦ埛鎻愪緵涓嬪崐骞存ā鏉匡紙鎴栫‘璁よ嚜琛屾墿灞� 1-6 鏈堟ā鏉匡級锛涒憿璐ц繍銆婃ā鏉縚璐ц繍閲忓懆杞噺銆嬨�婅涓婅涓嬫媶鍒嗚〃銆嬫湭瀵煎叆锛堟槑缁�/鎺掑悕瀵煎嚭鏁版嵁涓虹┖锛夛紱鈶h�佹棫璐ц溅鎶ュ簾鏇存柊鍧�/浜斾簲琛ㄥ悎璁¤鏃犳暟鎹簮锛涒懁鏁版嵁搴撳凡娓呯┖锛屼笟鍔℃暟鎹笌 `sys_role`/`sys_user` 闇�閲嶆柊鍒濆鍖栵紱鈶ヨ兘鑰楁柊 6 鏉¤鍒欑殑闃堝�硷紙涓綅鏁奥�50%銆佺幆姣斅�50%銆佹湀鎶ュ姣� 50%/60% 绛夛級璇风敤鎴风‘璁ゅ彛寰勩��
+
+
+### 2026-08-26 涓婂崍浼氳瘽锛氬鏍哥粨鏋滈〉浣撻獙浼樺寲锛坘eep-alive + 鎸夋潯鏄庣粏鍗$墖瑙嗗浘锛屽凡瀹屾垚楠岃瘉锛�
+**鐢ㄦ埛 2 涓棶棰�**锛氣憼鐐广�屾暟鎹�嶉摼鎺ヨ烦杞� DataView 杩斿洖鍚庯紝瀹℃牳缁撴灉涓㈠け闇�閲嶆柊鐐广�屾墽琛屽鏍搞�嶏紱鈶°�屾寜鏉℃槑缁嗐�嶇晫闈㈠お涓戙�佷紒涓氬拰闂閮界湅涓嶅叏銆�
+**鏈疆鏀瑰姩锛堢函鍓嶇锛屾棤闇�鍚庣锛�**
+- `traffic-audit-web/src/App.vue`锛歚<router-view />` 澶栧寘 `<keep-alive include="AuditResult">`锛岃烦杞�屾暟鎹�/瑙i噴銆嶅啀杩斿洖鏃剁粍浠跺疄渚嬭缂撳瓨锛屾姤琛ㄦ湡/杩囨护鏉′欢/瀹℃牳缁撴灉/閫変腑鐘舵�佸師鏍蜂繚鐣欙紝涓嶅啀闇�瑕侀噸鏂版墽琛屽鏍搞��
+- `traffic-audit-web/src/views/AuditResult.vue`锛氭寜鏉℃槑缁嗙敱 el-table 鏀逛负銆屼竴鏉¢棶棰樹竴寮犲崱鐗囥�嶏細
+  - 鍗$墖澶撮儴锛氬嬀閫夋 + 浼佷笟鍚嶏紙瀹屾暣灞曠ず锛�+ 鎶ヨ〃鏈� + 绛夌骇 tag + 鐘舵�� tag + 銆屾暟鎹�/瑙i噴銆嶉摼鎺� + 纭/蹇界暐鎸夐挳锛�
+  - 鍗$墖涓讳綋锛氳鍒欏悕 + 瑙勫垯缂栫爜 + 瀹為檯/鍩哄噯/宸紓 涓夎锛堟祬鐏板簳鍒嗙粍灞曠ず锛岄暱鏂囨湰鑷姩鎹㈣锛夛紱
+  - 椤堕儴宸ュ叿鏍忓姞銆屽叏閫夊綋鍓� N 鏉°�嶏紙鍚崐閫夋�� indeterminate锛夛紝閰嶅悎鍘熸湁鎵归噺纭/蹇界暐锛�
+  - 绾㈣壊闂鍗$墖宸﹁竟妗嗙孩鑹层�侀粍鑹插乏杈规姗欒壊銆佸凡澶勭悊鍗$墖鏁翠綋娣″寲锛屽垪琛ㄥ尯鐙珛婊氬姩銆�
+- 鏁版嵁鍔犺浇鏃剁粰姣忔潯缁撴灉鍒濆鍖� `_selected:false`锛堜繚璇� el-checkbox 鍝嶅簲寮忥級锛屾壒閲忛�夋嫨鏀逛负 `filteredResults.filter(r => r._selected)` 瀹炴椂璁$畻銆�
+**楠岃瘉**锛歠rontend.log `Compiled successfully in 678ms`锛堢儹鏇存柊鐢熸晥锛�10:31:11锛夛紱椤甸潰璺敱璺宠浆/杩斿洖鐘舵�佷繚鐣欓�昏緫鐢� keep-alive 淇濊瘉銆�
+- 杩藉姞锛�10:42锛夛細鐢ㄦ埛鍙嶉闂鍖哄煙浠嶅亸灏� 鈫� 鏂板銆岃仛鐒︽煡鐪嬨�嶆ā寮忥紙`focusMode`锛夛細涓�閿殣钘忕粺璁″崱/绛涢�夋爮/瑙勫垯缁熻/h3 鏍囬锛屼粎鐣欐姤琛ㄧ被鍨�+鎶ヨ〃鏈�+鎸夐挳涓�琛� + 闂鍒楄〃鍗犳弧锛坄main-focus` 鏀剁揣鍐呰竟璺濓級锛涢��鍑鸿仛鐒﹁繕鍘熴�俧rontend.log `Compiled successfully in 1103ms`锛�10:42:30锛夈�俻atch锛歚_shots/patch_focus_mode.py`銆�
+**浜х墿**锛歚_shots/patch_detail_view.py`锛堟湰杞� patch 鑴氭湰锛屼繚鐣欙級銆�
+**閬楃暀/寰呭姙涓嶅彉**锛氭姇璧� M2 浠嶆湭寮�宸ワ紙绱犳潗鍦� `docs/鎶曡祫/杈撳叆/` 榻愬锛夛紱7-12 鏈堟ā鏉垮緟鐢ㄦ埛鎻愪緵鎴栫‘璁ゆ墿灞曪紱璐ц繍銆婃ā鏉縚璐ц繍閲忓懆杞噺銆嬨�婅涓婅涓嬫媶鍒嗚〃銆嬫湭瀵煎叆锛堟槑缁�/鎺掑悕瀵煎嚭鏁版嵁涓虹┖锛夛紱鑳借�楁柊 6 鏉¤鍒欓槇鍊煎彛寰勫緟鐢ㄦ埛纭銆�
+
+
+### 2026-08-26 涓婂崍浼氳瘽锛堢画2锛夛細鏁版嵁瀵煎叆妯″潡琛ュ叏鏃呭/鑳借�楄繍鏀跨嫭绔嬪鍏ワ紙宸插畬鎴愰獙璇侊級
+**鐢ㄦ埛鍙嶉**锛氭暟鎹鍏ラ〉銆屽叕璺梾瀹€�嶄笅鎷夊彧鏈夋湀鎶�+涓綋涓ら」锛堣揣杩愬嵈鏈� 5 椤癸級锛屻�岃兘鑰椼�嶅彧鏈� 1 椤癸紱瀹為檯 `docs/鍏矾鏃呭/妯℃澘_鍏矾鏃呭杩愭斂杞﹁締鏁�.xlsx`銆乣docs/鑳借��/妯℃澘_鑳借�楄溅杈嗚繍鏀夸俊鎭�.xlsx` 閮芥湁鐙珛妯℃澘浣嗘棤娉曞崟鐙鍏ワ紙鍙兘闅忔湀鎶�/瀛f姤绗簩涓� sheet 鑷姩瑙f瀽锛夈��
+**璁捐缁撹**锛氭ā鍧椾笅鎷� = 璇ユā鍧楁ā鏉挎竻鍗曪紱杩愭斂绫绘ā鏉挎敮鎸佺嫭绔嬪鍏ワ紝鍚屾椂淇濈暀銆岄殢涓绘枃浠剁浜屼釜 sheet 鑷姩瀵煎叆銆嶇殑鍏煎琛屼负锛堝悗瀵煎叆瑕嗙洊锛屽箓绛夛級銆�
+**鍚庣**锛坄DataImportService.java` + `DataImportController.java`锛�
+- 鏂板 `POST /api/import/passengerAuth`锛氳В鏋� 4 鍒楋紙浼佷笟浠g爜/浼佷笟鍚嶇О/杞﹁締鏁�/杞藉浣嶆暟锛夆啋 `passenger_auth_vehicle`锛屽悓鎶ヨ〃鏈熷厛鍒犳棫鏁版嵁 + 娓� H2031 瀹℃牳缁撴灉锛坄deleteRuleResults` 鏂扮鏈夋柟娉曪級銆�
+- 鏂板 `POST /api/import/energyAuth`锛氳В鏋� 5 鍒楋紙杞︾墝鍙�/杞﹁締绫诲瀷/鐕冩枡绫诲瀷/鏍囪鍚ㄤ綅/鍑嗙壍寮曡川閲忥級鈫� `h204_auth_vehicle`锛屽悓鎶ヨ〃鏈熷厛鍒� + 娓� H204 瀹℃牳缁撴灉銆�
+**鍓嶇**锛坄DataImport.vue`锛�
+- 鍏矾鏃呭涓嬫媺鍔犮�岃繍鏀胯溅杈嗘暟銆嶏紝鑳借�椾笅鎷夊姞銆岃兘鑰楄溅杈嗚繍鏀夸俊鎭�嶏紱TYPE_LABELS 琛ユ柊绫诲瀷 + 澶у啓閿紙鏈�杩戝鍏ヨ褰� H2031/H204/PASSENGER_AUTH 绛夋樉绀哄弸濂藉悕锛夛紱typeDesc/templateName/endpoint 鍚屾琛ュ厖銆�
+**楠岃瘉**锛氬悗绔� `mvn compile` 閫氳繃锛涢噸鍚悗 curl 瀹炴祴锛氭梾瀹㈣繍鏀挎ā鏉垮鍏� 767 鏉°�佽兘鑰楄繍鏀挎ā鏉垮鍏� 1114 鏉★紝鍧囨垚鍔燂紙娴嬭瘯鏈� 2099-01 宸叉竻鐞嗭級锛涘墠绔� frontend.log `Compiled successfully in 308ms`锛�10:55:35锛夈��
+**璇存槑**锛氬煄甯傚杩愮殑杞ㄩ亾/杞浮/缃戠害杞︽ā鏉垮睘鏈堟姤瀛楁涓庢媶鍒嗛�昏緫锛堥渶姹傛枃妗g‘璁わ級锛岄潪鐙珛瀵煎叆绫诲瀷锛屼笅鎷変繚鎸� 3 椤癸紱鎶曡祫妯℃澘鎸夊競宸炰笂鎶ュ師濮嬫枃浠� + 姹囨�诲ぇ琛ㄥ鐞嗭紝涓嬫媺淇濇寔 3 椤广��
+**浜х墿**锛歚_shots/patch_auth_import.py`锛堝悗绔級銆乣_shots/patch_dataimport_options.py`锛堝墠绔級銆乣_shots/compile_auth_import.log`銆�
+
+
+### 2026-08-26 涓婂崍浼氳瘽锛堢画3锛夛細鏁版嵁瀵煎叆椤垫敼鐗堚�斺�斿緟瀵煎叆闃熷垪 + 鏈湀瀵煎叆杩涘害鎬昏锛堝凡瀹屾垚楠岃瘉锛�
+**鐢ㄦ埛鍙嶉**锛氬鍏ラ〉浜斾釜妯″潡涓嬫媺浜掓枼锛堥�変竴涓竻绌哄叾浠栵級锛屼竴娆″彧鑳戒紶涓�涓枃浠讹紝姣忔湀閲嶅鎿嶄綔澶氾紱瑕佷竴浠�"鏈湀鍚勬ā鍧楀鍏ョ姸鎬�"鐨勬鏌ユ竻鍗曘��
+**鍓嶇**锛坄DataImport.vue` 鏁翠綋閲嶅啓锛宍_shots/rewrite_dataimport.py`锛�
+- 浜斾釜妯″潡涓嬫媺浜掔浉鐙珛锛堜簰鏂ラ�昏緫鍒犻櫎锛夛紝閫夌被鍨嬪悗琛屽唴鍑虹幇銆岄�夋嫨鏂囦欢 鈫� 鍔犲叆闃熷垪銆嶏紱妯℃澘鍚�/璇存槑/澶ф枃浠惰�楁椂鎻愮ず璺熼殢褰撳墠閫変腑绫诲瀷鏄剧ず銆�
+- 鏂板銆屽緟瀵煎叆闃熷垪銆嶅崱鐗囷細妯″潡/绫诲瀷/鏂囦欢/鐘舵�侊紙寰呭鍏�/瀵煎叆涓�/鎴愬姛/澶辫触锛�/缁撴灉锛堝け璐ュ甫鍘熷洜锛�/鎿嶄綔锛堝崟鏉″鍏ャ�佺Щ闄わ級锛岄《閮ㄣ�屽叏閮ㄥ鍏ワ紙N锛夈�嶄覆琛岄�愪釜鎵ц锛屾姇璧勭被鍨嬪鍏ュ悗鑷姩瑙﹀彂鎶曡祫绯荤粺鏍¢獙寮圭獥銆�
+- 鏂板銆屾湰鏈堝鍏ヨ繘搴︺�嶅崱鐗囷細鎸夋姤琛ㄦ湡 脳 妯″潡绫诲瀷灞曠ず 宸插鍏琛岋紙缁匡級/閮ㄥ垎澶辫触锛堟锛�/鏈鍏ワ紙鐏帮級锛岄殢鎶ヨ〃鏈熷垏鎹㈣嚜鍔ㄥ埛鏂般��
+- 銆屽鍏ヨ褰曘�嶆敼涓烘寜褰撳墠鎶ヨ〃鏈熻繃婊ゆ樉绀猴紙limit 50锛夛紱鏂囨淇涓�"閫夋嫨绫诲瀷骞舵寕杞芥枃浠�"銆�
+**鍚庣**锛坄DataImportController.java`锛�
+- `/api/import/batches` 澧炲姞鍙�� `period` 鍙傛暟锛坙imit 涓婇檺鎻愬埌 200锛夛紝渚涜繘搴︽�昏鎸夋姤琛ㄦ湡鎷夊彇銆�
+**楠岃瘉**锛氬悗绔� `mvn compile` 閫氳繃 + start-dev.ps1 閲嶅惎灏辩华锛沗GET /api/import/batches?period=2026-07` 姝e父杩斿洖璇ユ湡璁板綍锛堣揣杩愰噺鍛ㄨ浆閲�/瑙勪笂瑙勪笅/杞ㄨ抗/杩愭斂/宸℃父鍑虹鍧囧凡瀵煎叆锛夛紱鍓嶇 frontend.log `Compiled successfully in 522ms`锛�11:01:41锛夈��
+**寰呭姙锛堢敤鎴峰彲閫夌殑閿︿笂娣昏姳锛�**锛氭瘡涓鍏ョ被鍨嬫梺鍔犮�屼笅杞芥ā鏉裤�嶉摼鎺ワ紙鍚庣鏆撮湶 docs 妯℃澘闈欐�佹枃浠讹級锛岀敤鎴蜂笉鐢ㄥ幓鏂囦欢澶规壘妯℃澘銆�
+
+
+### 2026-08-26 涓婂崍浼氳瘽锛堢画4锛夛細鏁版嵁瀵煎叆椤点�屼笅杞芥ā鏉裤�嶅叆鍙o紙宸插畬鎴愰獙璇侊級
+**瀹炵幇**
+- 鍚庣锛歚application.yml` 鏂板 `docs.template-root: docs`锛涙柊寤� `TemplateDownloadController.java`锛坄com.trafficaudit.config`锛夛細`GET /api/templates/{*path}` 涓嬭浇 docs 涓嬫ā鏉匡紝Content-Disposition 甯� UTF-8 鏂囦欢鍚嶏紝闃茶矾寰勭┛瓒婏紱妯℃澘瀹氫綅涓庢姤琛ㄦā鍧椾竴鑷达紙閰嶇疆鐩綍 鈫� user.dir 鈫� user.dir/.. 閫愮骇鍥為��锛屽洜 spring-boot:run 鐨� user.dir 鏄� traffic-audit-server锛夈�傞潤鎬佽祫婧愭槧灏勬柟妗堬紙WebConfig锛夊洜鐩稿璺緞瑙f瀽澶辫触宸插純鐢ㄥ垹闄ゃ��
+- 鍓嶇 `DataImport.vue`锛氭瘡涓鍏ョ被鍨嬫柊澧� `templateUrl`锛坉ocs 鐩稿璺緞锛夛紝妯℃澘璇存槑鍖烘樉绀恒�屼笅杞芥ā鏉裤�嶉摼鎺ワ紱鎶曡祫涓ゆā鏉挎寚鍚� `docs/鎶曡祫/杈撳叆/瀹㈣繍锛堝杩愮珯锛�/` 涓� `docs/鎶曡祫/杈撳叆/鐗╂祦锛堢墿娴佸洯鍖猴級/` 涓嬬殑鏇存柊鏄庣粏鏂囦欢锛沗passengerIndividual` 鏃犵嫭绔嬫ā鏉挎枃浠讹紝涓嶆樉绀轰笅杞姐��
+**楠岃瘉**锛�14 涓ā鏉夸笅杞藉叏閮� 200锛堝惈涓枃璺緞涓庡叏瑙掓嫭鍙�/閫楀彿锛夛紱鍓嶇 frontend.log `Compiled successfully in 505ms`锛�11:10:10锛夈��
+**娉ㄦ剰**锛氭姇璧勬ā鏉挎枃浠跺悕甯︽湀浠斤紙鏇存柊_7鏈堚�︼級锛屾瘡鏈堥渶鍚屾鏇存柊 `DataImport.vue` 鐨� templateUrl 鎴栨敼涓哄姩鎬佹槧灏勩��
+
+
+### 2026-08-26 涓崍浼氳瘽锛氭暟鎹鍏ラ〉 tabs 鍖� + 鍏ㄥ眬妯″潡椤哄簭缁熶竴锛堝凡瀹屾垚楠岃瘉锛�
+**鐢ㄦ埛鍙嶉**锛氣憼鏁版嵁瀵煎叆鐣岄潰妯″潡澶銆侀鏍肩箒鐞愶紱鈶℃墍鏈夐〉闈㈡ā鍧楅『搴忓簲缁熶竴涓恒�岃揣杩� 鈫� 鍏矾鏃呭 鈫� 鍩庡競瀹㈣繍 鈫� 鎶曡祫 鈫� 鑳借�椼�嶏紙褰撳墠閮ㄥ垎椤甸潰鎶婃姇璧�/鑳借�楁帓鍦ㄥ墠闈級銆�
+**鍓嶇鏀瑰姩锛�4 涓� Vue 鏂囦欢锛�**
+- `DataImport.vue`锛氫簲涓ā鍧椾笅鎷夊苟鎺掓敼涓� **el-tabs 閫夐」鍗�**锛堥『搴忥細璐ц繍/鍏矾鏃呭/鍩庡競瀹㈣繍/鎶曡祫/鑳借�楋級锛岄�変腑绫诲瀷鍚庢樉绀恒�岄�夋嫨鏂囦欢 鈫� 绔嬪嵆瀵煎叆 / 鍔犲叆闃熷垪銆嶏紱鍗$墖澶撮儴鍔犲紩瀵兼枃妗堬紱`groups` 閲嶆帓涓烘柊椤哄簭锛沗activeModule: 'freight'` 鍔犲叆 data銆傛搷浣滈摼涓嶅彉锛氶�夌被鍨嬧啋鎸傛枃浠垛啋绔嬪嵆瀵煎叆/鍔犲叆闃熷垪鈫掑叏閮ㄥ鍏ャ��
+- `AuditResult.vue`锛氭姤琛ㄧ被鍨� radio 椤哄簭鏀逛负 璐ц繍H2032鈫掓梾瀹2031鈫掑煄甯傚叕浜ITY_BUS鈫掑贰娓稿嚭绉烠ITY_TAXI鈫掓姇璧処NVEST鈫掕兘鑰桯204銆�
+- `RuleManage.vue`锛氬悓涓婇『搴忋��
+- `ReportExport.vue`锛歚modules` 椤哄簭鏀逛负 freight鈫抪assenger鈫抍ityPassenger鈫抜nvest鈫抏nergy锛屽崱鐗� CSS `repeat(6,1fr)`锛岀1-3寮� span2銆佺4-5寮� span3锛堢涓�鎺� 璐ц繍/鏃呭/鍩庡競瀹㈣繍锛岀浜屾帓 鎶曡祫/鑳借�楋級銆�
+**楠岃瘉锛堟棤澶存祻瑙堝櫒鐪熷疄 DOM + 鎴浘锛�**
+- 鍓嶇 frontend.log 鏈�鏂� `Compiled successfully`锛�11:30:34锛夛紝鏃犵紪璇戦敊璇��
+- 瀵煎叆椤� tabs = `["璐ц繍","鍏矾鏃呭","鍩庡競瀹㈣繍","鎶曡祫","鑳借��"]`锛宎ctive=璐ц繍锛屾寜閽� = `["閫夋嫨鏂囦欢","绔嬪嵆瀵煎叆","鍔犲叆闃熷垪"]`锛宱verviewGroups 鍚岄『搴忥紝0 涓� JS 閿欒锛涙埅鍥� `_shots/import_page_tabs2.png`锛堜汉宸ョ‘璁ゆ竻鐖芥棤寮傚父锛夈��
+- 渚ц竟鏍忓垏椤甸獙璇侊細瀹℃牳缁撴灉 radio=璐ц繍/鏃呭/鍩庡競鍏氦/宸℃父鍑虹/鎶曡祫/鑳借�� + 鎸夋潯鏄庣粏/鎸変紒涓氬悎骞讹紱瑙勫垯绠$悊 radio 鍓�6椤瑰悓搴忥紱鎶ヨ〃鐢熸垚鍗$墖=璐ц繍(H203-2)/鍏矾鏃呭(H203-1)/鍩庡競瀹㈣繍/鎶曡祫/鑳借��(H204)锛涙埅鍥� `_shots/order_check3.png`锛堝崱鐗囧竷灞�缇庤锛夈��
+- **璺敱鍒囬〉鍧�**锛歚location.hash='#/audit'` 鐩存帴璧嬪�间笉鐢熸晥锛堣閲嶇疆鍥� #/import锛夛紝蹇呴』鐐瑰嚮渚ц竟鏍� `.el-menu-item` 鍒囬〉銆�
+**浜х墿**锛歚_shots/patch_module_order.py`銆乣_shots/patch_radio_order.py`銆乣_shots/patch_tab_clean.py`銆乣_shots/shot_import_tabs.js`銆乣_shots/shot_import_tabs2.js`銆乣_shots/order_check.js`銆乣_shots/order_check2.js`銆乣_shots/order_check3.js`銆�
+**寰呭姙锛堝彲閫夐敠涓婃坊鑺憋級**锛氳嫢鐢ㄦ埛浠嶈绻佺悙锛屽彲鎶娿�屾湰鏈堝鍏ヨ繘搴︺�嶄笌銆岄�夋嫨绫诲瀷鎸傝浇鏂囦欢銆嶄袱寮犲崱鍚堝苟鎴愪竴寮狅紱瀵煎叆绫诲瀷鏃併�屼笅杞芥ā鏉裤�嶉摼鎺ュ凡鍦ㄧ画4钀藉湴銆�
+
+
+### 2026-08-26 涓崍浼氳瘽锛氭姇璧� M2 甯傚窞鍗曡〃鎵归噺瀵煎叆锛堝凡瀹屾垚楠岃瘉锛�
+**闇�姹�**锛欻ANDOFF 閬楃暀銆屾姇璧� M2锛堝競宸炲崟琛ㄨВ鏋愶級鏈紑宸ャ�嶁啋 瀹炵幇鎸夊競宸炲崟琛ㄧ洰褰曟壒閲忓鍏ュ杩愮珯鍦�/鐗╂祦鍥尯鏈堝害鏁版嵁銆�
+**瀹炵幇**
+- 鍚庣 `DataImportService.importInvestCityDir(period, category)` + `POST /api/import/investCityBatch`锛坧eriod=yyyy-MM锛宑ategory=瀹㈣繍绔欏満|鐗╂祦鍥尯锛夛細鎵弿 `docs/鎶曡祫/杈撳叆/{绫诲埆鐩綍}/{N鏈坿` 涓嬪叏閮� xls/xlsx/et锛堣烦杩� pdf锛孨鏈堢敱 period 鎺ㄥ锛夛紝瀹归敊瑙f瀽姣忎釜鏂囦欢锛堝畾浣嶈〃澶磋 + 鍒楀悕鍏抽敭璇嶆槧灏� + 鍙屽眰琛ㄥご鍚堝苟 + 甯傚窞灏忚妭琛岃瘑鍒級锛岄�愭枃浠惰繑鍥炶鏁�/澶辫触琛屽彿锛屽箓绛夛紙鍚岀被鍒悓鎶ヨ〃鏈熸湀搴�+INVEST 瀹℃牳鍏堝垹锛岄」鐩富妗f寜銆岄」鐩悕+绫诲埆銆島psert锛夛紱涓庛�屾ā鏉縚鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls銆嶈鑼冨寲鍖归厤鏍囪 is_five_year銆�
+- 鍓嶇 `DataImport.vue` 鎶曡祫 tab 鏂板銆屽競宸炲崟琛ㄦ壒閲忓鍏ワ紙瀹㈣繍绔欏満/鐗╂祦鍥尯锛夈�嶇被鍨� +銆屼粠鐩綍鎵归噺瀵煎叆銆嶆寜閽� + 閫愭枃浠剁粨鏋滃脊绐椼��
+- 鏂囦欢璇诲彇锛氬彧璇绘枃浠跺繀椤� `new FileInputStream(f)` 鍖呬竴灞傦紙鐩存帴 `WorkbookFactory.create(File)` 鎶涖�屾嫆缁濊闂�嶏級銆�
+**DB 鐜扮姸锛坢ysql 3308 / traffic_audit锛�**
+- investment_project锛氬杩愮珯鍦� 46銆佺墿娴佸洯鍖� 132锛堝凡娓� 2 涓鍎挎畫鐣欙級锛涘叏閮� total_investment>0銆�
+- investment_monthly锛氬杩� 6鏈�35/7鏈�46銆佺墿娴� 6鏈�132/7鏈�133锛堝叡 346锛夛紝0 澶辫触锛�2026-07 鍗佷簲浜旈」鐩� 35 涓��
+- 瀵煎嚭 `GET /api/report/export/investFiveYearLogistics?period=2026-07` 鍚堣锛氭�绘姇璧� 2038714.59 / 鑷紑濮嬬疮璁� 691375.3 / 鑷勾鍒濈疮璁� 246779.55锛屼笌 DB 鑱氬悎閫愬垎涓�鑷达紙probe锛歚_shots/verify_export_m2/report_v2.txt`锛夈��
+**鏈疆鏂拌俯鍧戯紙鍔″繀璁颁綇锛�**
+1. 銆屾�绘姇璧勩�峷s銆岃鍒掓�绘姇璧勩�嶏細姝︽眽绛夎〃澶存槸銆屾�绘姇璧勩�嶏紱瀹滄槍/瑗勯槼绛夋槸銆岃鍒�   鎬绘姇璧勶紙涓囧厓锛夈�嶏紙涓棿鏈夌┖鏍硷級鈫� 鍒楁槧灏勫繀椤� `contains("璁″垝鎬绘姇璧�")||contains("鎬绘姇璧�")` 涓旀帓闄ゃ�岀疮璁°�嶏紝鍚﹀垯 total=0銆�
+2. **鍏紡鍗曞厓鏍�**锛�.et锛圵PS 璺ㄨ〃鍏紡 `='2026骞�6鏈� '!I12+L12`锛塦DataFormatter.formatCellValue` 鏃� evaluator 鏃跺彧杩斿洖鍏紡鏂囨湰鈫掕В鏋� 0銆備慨澶嶏細`getDecimal/getDecimalOrNull/getString` 瀵� FORMULA 鍗曞厓鏍艰 `getCachedFormulaResultType()`/`getNumericCellValue()`銆�
+3. **澶� sheet 鍘嗗彶鏂囦欢閫夋湡**锛氳闃� .et 鍚� 1-7 鏈� 12 涓� sheet锛屽師閫昏緫銆岄�� countInvestRows 鏈�澶氥�嶄細閿欓�� 2鏈� sheet锛堥涓� 6 琛岋級锛�7鏈堟暟鎹敊瀛� 2鏈堝�笺�備慨澶嶏細`investSheetMatchesPeriod` 鎸� sheet 鍚嶅惈銆孨鏈�/0N鏈�/period銆嶄紭鍏堬紙鏁版嵁琛�>0 鏃� +1000000 鍒嗭級銆�
+4. 椤圭洰鍚嶅惈鎹㈣锛坄鍜稿畞楂樻柊鍖哄喕搴撳喎閾鹃」鐩�
+锛堜竴鏈燂級`锛夛細`replaceAll("[\r\n]+","")`锛涗慨澶嶅墠娈嬬暀鐨勫鍎块」鐩紙榛戣惫鍥介檯鐗╂祦鍥�/鍜稿畞鍐诲簱锛夊凡浠� investment_project 鍒犻櫎锛堟棤鏈堝害寮曠敤锛夈��
+5. 鏃� seq 鍒楋紙闅忓窞 Sheet2锛夛細鏈� seq 鎸夊簭鍙峰垽瀹氥�佹棤 seq 鐢� `investmentCity(nt)` 璇嗗埆甯傚窞琛屻��
+6. 銆屽~鎶ュ崟浣嶃�嶅競宸炴彁鍙栧繀椤� contains 鍖归厤锛�"姝︽眽甯備氦閫氳繍杈撳眬瀹㈣繍浜嬩笟鍙戝睍涓績"锛夈��
+7. 娼滄睙/鏋楀尯鏂囦欢瑙f瀽 1 鏉★紙娼滄睙浼犲寲鍏矾娓級鏄纭殑锛涘 sheet 閫夋湡淇涓嶅奖鍝嶅叾缁撹銆�
+**鍗佷簲浜� 28 涓己椤圭洰锛堢湡瀹炵己鍙o級**
+- 妯℃澘 63 椤癸紝瑙勮寖鍖栧尮閰� 35 瑕嗙洊 / 缂� 28锛堟暟鎹簮涓嶅湪甯傚窞鍗曡〃锛夛細娓呭崟瑙� `_shots/five_year_report.txt`锛岄渶鐢ㄦ埛鎻愪緵鏁版嵁鎴栫‘璁ゅ彛寰勩��
+**浜х墿**锛歚_shots/insert_m2.py`锛圡2 浠g爜鎻掑叆锛夈�乣_shots/fix_*.py`锛堝悇 bug 淇锛夈�乣_shots/fix_formula_cached.py`銆乣_shots/fix_sheet_period.py`銆乣_shots/m2_compile*.log`銆乣_shots/ProbeEt*.java`锛圥OI 鍏紡/澶� sheet 鎺㈡祴锛夈�乣_shots/verify_export_m2/`锛堝崄浜斾簲瀵煎嚭澶嶉獙锛夈�乣_shots/five_year_report.txt`銆乣_shots/verify_m2.js` + `_shots/m2_batch_dialog.png`锛堝墠绔脊绐楅獙璇侊級銆�
+**寰呭姙**锛氣憼28 涓崄浜斾簲椤圭洰缂哄彛绛夌敤鎴锋彁渚涙暟鎹簮锛涒憽鑳借�楁柊 6 鏉¤鍒欓槇鍊煎彛寰勫緟纭锛涒憿璐ц繍銆婃ā鏉縚璐ц繍閲忓懆杞噺銆嬨�婅涓婅涓嬫媶鍒嗚〃銆嬫湭瀵煎叆锛涒懀7-12 鏈堟ā鏉垮緟鐢ㄦ埛鎻愪緵锛涒懁鍙�夈�屾湰鏈堝鍏ヨ繘搴︺�嶄笌銆屾寕杞芥枃浠躲�嶄袱鍗″悎骞躲��
+
+
+### 2026-08-26 鍗堝悗浼氳瘽锛氭姤琛ㄧ敓鎴� P0 涓夐」 + 妯℃澘鏁版嵁瑙勫垯姊崇悊锛堝凡瀹屾垚楠岃瘉锛�
+**鑳屾櫙**锛氱敤鎴烽棶銆屾姤琛ㄧ敓鎴愭ā鍧楁�庝箞鐪嬨�嶁啋 缁欏嚭璇勪及锛堟ā鏉垮簳绋跨敓鎴愭槸浜偣锛涙暟鎹畬澶囨�т笉鍙/閿欒淇℃伅琚悶/鏂囦欢鍚嶄笉涓�鑷�/鍗曟枃浠惰繃澶�/鏈堟姤璇箟鏄撹璇伙級锛岀敤鎴峰悓鎰忓厛鍋� P0 涓夐」銆�
+**P0-1 鏁版嵁灏辩华鏍¢獙**
+- 鍚庣 `ReportExportService.checkExportReady(period, mode, types)` + `GET /api/report/ready`锛氭寜鎶ヨ〃绫诲瀷缁熻渚濊禆琛ㄨ鏁帮紙month 绮剧‘鍖归厤 report_period銆亂ear 鐢� likeRight 骞翠唤鍓嶇紑锛夛細
+  - 璐ц繍涓夎〃 鈫� 妯℃澘_璐ц繍閲忓懆杞噺(freight_turnover_import) + 瑙勪笂瑙勪笅鎷嗗垎琛�(scale_split_transport) + H203-2 鏈堟姤(h2032_enterprise_monthly)
+  - 鏃呭鍥涜〃 鈫� H203-1 鏈堟姤(h2031_enterprise_monthly) + 涓綋(passenger_individual_monthly)
+  - cityBus/cityRailFerry 鈫� city_bus_monthly锛沜ityTaxi 鈫� city_taxi_monthly锛涘煄甯傚杩愭眹鎬讳笁琛� 鈫� 鍏氦+鍑虹
+  - 鎶曡祫鍥涜〃 鈫� investment_monthly锛涜兘鑰� 鈫� h204_vehicle_quarterly
+- 鍓嶇 `exportChecked` 鍏堣皟 ready锛氭湁缂烘暟鎹〃 鈫� `$confirm`銆屾暟鎹湭灏辩华銆嶅垪鍑虹己鍝簺锛堝銆屽煄甯傚叕浜ゅ杩愰噺鍒嗗競宸炴槑缁嗭紙缂烘暟鎹細鍩庡競鍏氦鏈堟姤锛夈�嶏級锛屽彲銆屼粛瑕佸鍑�/鍙栨秷銆嶏紱鏃犵己 鈫� 鐩存帴瀵煎嚭銆�
+**P0-2 閿欒閫忓嚭**
+- 鏂板 `GlobalExceptionHandler`锛坈om.trafficaudit.common锛夛細Exception 鈫� HTTP 500 + `Result.error(500, message)`锛坙og.error 鍏ㄩ噺鍫嗘爤锛夛紱鍓嶇 `failWith(err)` 璇� `err.response.data.message` 鏄剧ず鍏蜂綋鍘熷洜銆�
+- 楠岃瘉锛歚curl /api/report/export/energySummary?period=2026-99` 鈫� `{"code":500,"message":"32",...}` 閾捐矾閫氥��
+**P0-3 鏂囦欢鍚嶇粺涓� + MIME**
+- 鍓嶇鏃呭 4 涓� fileName 瀵归綈鍚庣鍏ㄥ悕锛堝叕璺梾瀹㈠垎甯傚窞鏄庣粏/涓彛寰勬槑缁�/涓彛寰勬帓鍚�/涓彛寰勫垎鏋愶級锛沗buildResponse` 鎸� .xls 鐢� `application/vnd.ms-excel`銆�.xlsx 鐢� ooxml銆�
+**椤烘墜淇**锛歚ReportExport.vue` data 缂� `activeMenu: '/report'`锛堟鍓嶄晶杈规爮褰撳墠椤典笉楂樹寒 + Vue warn锛夈��
+**楠岃瘉**锛歳eady 鍚勮〃鏁板�间笌 DB 閫愯〃鏍稿涓�鑷达紙cityDetail=690銆乸assenger=771+缂轰釜浣撱�乧ityTaxi=17銆乮nvest=179銆乪nergy=126銆乧ityBus=0 鍑嗙‘鍙嶆槧鍏氦鏈堟姤鏈鍏ワ級锛涙棤澶存祻瑙堝櫒锛坄_shots/report_ready_dialog.js`锛夛細case1 缂烘暟鎹脊銆屾暟鎹湭灏辩华銆嶇‘璁ゆ鍐呭姝g‘銆乧ase2 鏁版嵁榻愬叏鐩存帴涓嬭浇+銆屾姤琛ㄥ鍑烘垚鍔熴�嶏紱JS 0 閿欒锛涘墠绔� 12:55:10 Compiled successfully銆�
+**妯℃澘鏁版嵁瑙勫垯锛堢敤鎴锋柊纭鐨勯渶姹傛柟鍚戯紝寰呭彛寰勭‘璁わ級**锛氭ā鏉垮嵆鏉冨▉锛岀郴缁熶弗鏍兼寜妯℃澘涓夌被瑙勫垯濉暟鈥斺�擜 濉斁瑙勫垯锛堟暟鎹尯琛�/鍒�/鍚堝苟瀵瑰彿鍏ュ骇锛夈�丅 璁$畻瑙勫垯锛堟ā鏉垮凡鏈夊叕寮忓鑳借�楁姌绠楃郴鏁� D3=C3/B3銆丮3=K3/I3*100*0.86*1.4571銆佸煄甯傛眹鎬诲悎璁� P3=P4+P5+P8+P9銆佸悓姣� L14=H14/M14-1 绛� 鈫� 淇濈暀鍏紡鍙~鍘熷閲忥紱妯℃澘鏃犲叕寮忓璐ц繍_璐ц繍閲忓懆杞噺 鈫� 绯荤粺鎸夊彛寰勭畻濂藉~鍏ワ級銆丆 鏍煎紡瑙勫垯锛堝垪瀹�/琛ㄥご/鍚堝苟/鍗曚綅瀹屽叏涓�鑷达級銆�**寰呯敤鎴风‘璁�**锛氣憼鍏紡绫绘ā鏉夸繚鐣欏叕寮� vs 绯荤粺鍐欐绠楀ソ鐨勫�硷紱鈶℃暟鎹鏁拌秴妯℃澘棰勭暀琛屾暟鏃舵槸鍚﹀姩鎬佹彃琛屽苟鍚屾鍏紡锛涒憿銆屾暟鎹鍒欍�嶆槸鍚﹁繕鍖呮嫭鍗曚綅鎹㈢畻/瀹℃牳鍚庡~鏁�/鏈堟姤 1~N 鏈堝彛寰勩��
+
+### 2026-08-26 涓嬪崍缁細瀹℃牳鏍囪琛紙audit_run锛夎惤鍦帮紝鍘嗗彶宸插鏍告暟鎹笉鍐嶈鎶ャ�屾湭瀹℃牳銆嶏紙宸插畬鎴愰獙璇侊級
+**鐢ㄦ埛鍙e緞锛堝叧閿級**锛氱幇鏈夋暟鎹紙7 鏈堝強浠ュ墠锛夐兘鏄嚎涓嬪鏍搁�氳繃鐨勩�佸彲鐩存帴瀵煎嚭锛屼笉搴旀彁绀烘湭瀹℃牳锛涘彧鏈� **8 鏈堟暟鎹湭瀹℃牳**銆備箣鍓嶇敤 `audit_result` 闂鏉℃暟鍒ゆ柇 audited 鏄敊鐨勨�斺�斿鏍稿叏閫氳繃鏃� 0 鏉′細璇垽涓烘湭瀹℃牳銆�
+**鏂规**锛氭柊澧� `audit_run` 鏍囪琛紙report_type + report_period 鍞竴锛夛紝銆屽凡瀹℃牳銆嶄互璇ヨ〃涓哄噯锛涘鍑哄墠 ready 鏍¢獙鏌� audit_run锛屼笉鍐嶄緷璧� audit_result 琛屾暟锛坅uditRows 淇濈暀涓轰俊鎭瓧娈碉級銆�
+**鍚庣锛堝凡缂栬瘧閲嶅惎楠岃瘉锛�**
+- 寤鸿〃 `audit_run`锛坕d/report_type/report_period/created_at锛寀k_type_period锛寀tf8mb4锛夛紝MySQL 3308 traffic_audit銆�
+- 鏂板 `AuditRun` 瀹炰綋 + `AuditRunMapper`锛圔aseMapper锛夈��
+- `AuditEngineService`锛�4 鍙傛暟 `markAudited(reportType, period, rules, hasData)`锛堝箓绛夛紝鏃犺鍒�/鏃犳暟鎹笉鍐欙級+ 鏂板 2 鍙傛暟閲嶈浇 `boolean markAudited(reportType, period)`鈥斺�斿唴閮ㄦ寜绫诲瀷鍙栧惎鐢ㄨ鍒欙紙rulesOf锛�+ 鎸夌被鍨嬫煡鏁版嵁琛紙H2032鈫抙2032銆丠2031鈫抪assenger銆丠204鈫抏nergy銆両NVEST鈫抜nvestMonthly銆丆ITY_BUS鈫抍ityBus銆丆ITY_TAXI鈫抍ityTaxi锛夊啀鍐欐爣璁帮紱6 涓� execute 鏂规硶鏈熬宸茶皟 2 鍙傛暟鐗堬紙鎵ц瀹℃牳鍚庤嚜鍔ㄥ啓鏍囪锛夈��
+- `AuditController`锛歚POST /api/audit/markAudited?period=&reportType=` 杩斿洖 `Result<Boolean>`锛堟槸鍚﹀啓鍏ユ爣璁帮級銆�
+- `ReportExportService.checkExportReady`锛歚audited` 鏀逛负閫� auditType 鏌� `audit_run`锛坢onth=eq 绮剧‘ / year=likeRight锛夛紝浠讳竴绫诲瀷鏈夋爣璁板嵆 audited=true锛涙敞鍏� `AuditRunMapper`銆�
+**鍓嶇锛堢儹鏇存柊宸茬敓鏁堬紝Compiled successfully锛�**
+- `AuditResult.vue`锛氥�屾墽琛屽鏍搞�嶆梺鏂板銆屾爣璁板凡瀹℃牳銆嶆寜閽紙璋� markAudited锛屾垚鍔熸彁绀�/鏃犺鍒欐棤鏁版嵁鎻愮ず骞跺埛鏂板垪琛級銆�
+- `ReportExport.vue` 鏃犻渶鏀癸細`unaudited = ready && audited===false` 閫昏緫宸蹭笌 audit_run 璇箟鍚诲悎銆�
+**鍘嗗彶鏁版嵁琛ユ爣锛坅udit_run 宸插啓鍏� 6 鏉★級**锛欻2031 07銆丠2032 07銆丠204 07銆両NVEST 06+07銆丆ITY_TAXI 07锛�**H2032 08 鏈爣**锛�8 鏈堟湭瀹℃牳锛夛紱CITY_BUS 鏃犳暟鎹笉鏍囥��
+**楠岃瘉锛堝叏閮ㄩ�氳繃锛�**
+- `GET /api/report/ready?period=2026-07&mode=month&types=cityDetail,passengerCityDetail,cityTaxiDetail,investPlan,energySummary` 鈫� 鍏ㄩ儴 audited=true锛坧assengerCityDetail auditRows=0 浣� audited=true锛屾鏄慨澶嶇偣锛夈��
+- `period=2026-08` 鈫� cityDetail rows=142锛圚2032 8 鏈堟湁鏁版嵁锛塧udited=false锛屽叾浣欐棤鏁版嵁 ready=false銆�
+- `POST /api/audit/markAudited`锛欻2032/2026-07 鈫� true锛堝箓绛夛級锛汣ITY_BUS/2026-07 鈫� false锛堟棤鏁版嵁涓嶆爣璁帮級銆�
+- 鍚庣 start-dev.ps1 閲嶅惎 OK锛圫tarted + 8090锛夛紱鍓嶇 14:32:12 Compiled銆�
+**浜х墿**锛歚_shots/compile_auditrun.log`銆乣%TEMP%\patch_*.py`锛坅udit_2arg / audit_controller2 / report_audited / auditresult_vue2锛夈�乣%TEMP%\db_mark_history.py`銆�
+**閬楃暀/寰呭姙涓嶅彉**锛氭姇璧� M2 鏈紑宸ワ紱7-12 鏈堟ā鏉垮緟鎻愪緵锛涜揣杩愩�婃ā鏉縚璐ц繍閲忓懆杞噺銆嬨�婅涓婅涓嬫媶鍒嗚〃銆嬫湭瀵煎叆锛涜兘鑰楁柊瑙勫垯闃堝�煎緟纭銆�
+
+### 2026-08-26 涓嬪崍缁�2锛氱櫥褰曟帴閫� + 鐢ㄦ埛绠$悊锛堣礋璐fā鍧楀嬀閫夛級+ 鎶ヨ〃鐢熸垚椤佃蒋闅旂锛堝凡瀹屾垚楠岃瘉锛�
+**闇�姹傦紙鐢ㄦ埛纭锛�**锛氭姤琛ㄧ敓鎴愰〉鎸夈�屼笁浜哄垎宸ャ�嶅仛杞殧绂伙紙璐ц繍1浜恒�佹梾瀹�+鎶曡祫+鑳借��1浜恒�佸煄甯傚杩�1浜猴級鈥斺�旂櫥褰曞悗榛樿鍙湅鑷繁璐熻矗鐨勬ā鍧椼�佸彲鍒囨崲銆屾煡鐪嬪叏閮ㄦā鍧椼�嶏紱妯″潡褰掑睘鍦�**鐢ㄦ埛涓婂嬀閫�**锛堟洿鐏垫椿锛夈��
+**鐜扮姸鍙戠幇**锛氱櫥褰曞師涓哄亣鐧诲綍锛圠ogin.vue 鐩存帴璺宠浆涓嶆牎楠岋級銆乁serManage.vue 涓哄崰浣嶉〉銆乻ys_user/sys_role 琛ㄤ负绌� 鈫� 鏈涓�骞惰ˉ榻愰摼璺��
+**鏁版嵁搴�**锛歚sys_user` 鍔� `modules` 瀛楁锛堥�楀彿鍒嗛殧锛歠reight,passenger,invest,energy,cityPassenger锛夛紱鍒濆鍖� 4 涓处鍙凤紙瀵嗙爜鍧� 123456锛孊Crypt 鐢� spring-security-crypto 鐢熸垚锛夛細
+- admin/绠$悊鍛橈紙5 妯″潡鍏ㄩ�夛級銆亃hangsan/寮犱笁锛坒reight锛夈�乴isi/鏉庡洓锛坧assenger,invest,energy锛夈�亀angwu/鐜嬩簲锛坈ityPassenger锛夈��
+**鍚庣锛堝凡缂栬瘧閲嶅惎锛�**
+- `User` 瀹炰綋鍔� modules锛涙柊澧� `UserController`锛�/api/user/list 鍏抽敭瀛楁悳绱€��/save 鏂板缂栬緫=瀵嗙爜鐣欑┖涓嶆敼+BCrypt銆�/delete 绂佹鍒� admin锛夛紱`LoginController` 鐧诲綍杩斿洖 token+username+realName+modules锛堥敊璇� 401锛夈��
+**鍓嶇锛堢儹鏇存柊宸茬敓鏁堬紝Compiled successfully 15:20:43锛�**
+- `Login.vue` 璋� /api/auth/login锛屽瓨 token/username/realName/modules锛沗router/index.js` 鍔犵櫥褰曞畧鍗紙鏃� token 璁块棶涓氬姟椤佃烦 /login锛夛紱`main.js` 鍏ㄥ眬 mixin logout锛堟竻 localStorage 鍥炵櫥褰曢〉锛夛紝8 涓笟鍔¢〉渚ц竟鏍忕粺涓�鍔犮�岄��鍑虹櫥褰曘�嶈彍鍗曢」銆�
+- `UserManage.vue` 閲嶅啓锛氱敤鎴峰垪琛紙鐢ㄦ埛鍚�/濮撳悕/鎵嬫満鍙�/璐熻矗妯″潡 tags/鐘舵��/鍒涘缓鏃堕棿/缂栬緫鍒犻櫎锛�+ 鏂板缂栬緫寮圭獥锛堝惈銆岃礋璐fā鍧椼�峞l-checkbox-group 5 椤� + 鐘舵�侊級銆�
+- `ReportExport.vue` 杞殧绂伙細椤堕儴 focus-bar 鏄剧ず銆屽綋鍓嶇敤鎴�/璐熻矗妯″潡銆�+ 銆屾煡鐪嬪叏閮ㄦā鍧椼�峞l-switch锛沗visibleModules` computed 榛樿杩囨护鑷繁鐨勬ā鍧楋紙鏈厤缃垨杩囨护涓虹┖鍒欐樉绀哄叏閮級銆�
+**楠岃瘉锛堝叏閮ㄩ�氳繃锛�**
+- 鐧诲綍锛歛dmin 杩斿洖 5 妯″潡銆亃hangsan 杩斿洖 freight銆侀敊璇瘑鐮� 401锛涚敤鎴� CRUD锛氭柊澧�/閲嶅悕 400/缂栬緫鏀规ā鍧�/鍒犻櫎锛屾祴璇曡处鍙峰凡娓呯悊锛屾渶缁� 4 鐢ㄦ埛銆�
+- 鏃犲ご娴忚鍣� e2e锛坄_shots/usermgr_e2e.js/png`锛夛細鏈櫥褰曡闂� #/report 鈫� 璺� #/login锛泎hangsan 鐧诲綍 鈫� /report 鍙樉绀鸿揣杩愬崱锛坒ocus-bar锛氬綋鍓嶇敤鎴凤細寮犱笁 璐熻矗妯″潡锛氳揣杩愭姤琛紙H203-2锛夛級锛岀偣寮�鍏� 鈫� 5 寮犲崱鍏ㄦ樉绀猴紱admin 鐧诲綍 鈫� /users 琛ㄦ牸 4 琛岋紱JS 0 閿欒銆�
+**浜х墿**锛歚_shots/usermgr_e2e.js/png`銆乣_shots/compile_usermgr.log`銆乣%TEMP%\db_init_users.py`锛堝垵濮嬪寲璐﹀彿 SQL锛屽彲澶嶇敤锛夈��
+**閬楃暀/寰呭姙涓嶅彉**锛氭姇璧� M2 鏈紑宸ワ紱7-12 鏈堟ā鏉垮緟鎻愪緵锛涜揣杩愩�婃ā鏉縚璐ц繍閲忓懆杞噺銆嬨�婅涓婅涓嬫媶鍒嗚〃銆嬫湭瀵煎叆锛涜兘鑰楁柊瑙勫垯闃堝�煎緟纭銆傛姤琛ㄩ〉銆岀姸鎬佸彲瑙嗗寲锛堟暟鎹氨缁�/宸插鏍哥姸鎬佹潯锛夈�嶄笌銆屾渶杩戠敓鎴愯褰曘�嶄粛鍦ㄥ緟鍔炶璁哄垪琛ㄣ��
+
+### 2026-08-26 鏅氶棿缁�5锛氭姤琛ㄦā鏉胯嚜鍔ㄥ悜鍙虫墿鍒楋紙1-6鏈� 鈫� 1-12鏈堬級+ 鎶ヨ〃鐢熸垚椤靛崟妯″潡宸ヤ綔鍙帮紙宸插畬鎴愰獙璇侊級
+**闇�姹傦紙鐢ㄦ埛纭锛�**锛氣憼鎸夊綋鍓嶆兂娉曞厛鍑轰竴鐗堢湅鏁堟灉锛堝崟妯″潡宸ヤ綔鍙扮晫闈級锛涒憽鏈堝害妯℃澘**鑷姩鍚戝彸鎵╁睍鍒�**锛氬鍒跺垪澶�/鍏紡/鏍峰紡骞跺悓姝ュ叕寮忓紩鐢紝鎵╁垪鏍煎紡涓庤鍒欐寜 1-6 鏈堟棦鏈夎鍒欎妇涓�鍙嶄笁锛屾棤闇�閫愭湀浜哄伐纭銆�
+**鍚庣鎵╁垪锛堝凡缂栬瘧閲嶅惎 + HTTP 瀵煎嚭楠岃瘉閫氳繃锛�**
+- 涓夊紶鎶ヨ〃鍚敤鎵╁垪锛歚exportCityDetail`锛堣揣杩愬垎甯傚窞锛夈�乣exportPassengerCityDetail`锛堝叕璺梾瀹㈠垎甯傚窞锛夈�乣exportPassengerMidDetail`锛堜腑鍙e緞鏄庣粏锛夈�俙fillMonths = Math.min(currentMonth,6)` 鏀逛负 `currentMonth`锛沗fillMonths > 6` 鏃惰皟鐢� `extendMonthlyColumns(sheet, headerRow, year, dataStart, dataEnd)`銆�
+- 鎵╁垪閫昏緫锛氱疮璁″垪瀵逛粠 14/15 鍒楃Щ鍒� 26/27 鍒楋紙AB锛夛紱7-12 鏈堝垪瀵圭敱 1 鏈堝垪瀵瑰厠闅嗭紙琛ㄥご鏂囨湰/鏃ユ湡淇銆佹牱寮忓鍒躲�侀潪璺ㄧ翱鍏紡鍏嬮殕涓斿垪寮曠敤鍚屾骞崇Щ锛屾鍒� `shiftMonthFormula`锛夛紱绱鍒楀叕寮忛噸鍐欎负 12 涓湀鍊间箣鍜岋紙`fixCumulativeFormulas`锛屽叕寮忎笉甯� `=` 鍓嶇紑锛孭OI `setCellFormula` 涓嶆帴鍙� `=` 寮�澶粹�斺�斿凡韪╁潙淇锛夈��
+- 鍚屾瘮鍒楋細鍘� #REF! 鍏紡瑕嗗啓涓� Java 搴撳唴璁$畻鍊笺�傝揣杩愯皟 `getCityMonthYoy`/`getProvinceMonthYoy`锛涙梾瀹㈡柊澧� `passengerCumYoy`/`passengerMonthVal` + 绱鍚屾瘮瑕嗗啓锛涗腑鍙e緞鏂板 `fillMidYoyCell`/`fillMidCumYoyColumn`銆傚惊鐜捣鐐� r=4 鈫� r=3锛圧4 鏄叏鐪佸尯鍩熸爣璁拌锛孉4:A7 鍚堝苟锛夛紝鏃呭 #REF! 娓呴浂銆�
+- 宸茬煡鍙e緞锛堟部鐢ㄧ幇鐘讹紝鏈敼锛夛細妯℃澘琛ㄥご涓恒��1鏈堜笌鍘诲勾鍚屾瘮銆嶏紝7-12 鏈堝悓姣斿垪娌跨敤 1-6 鏈堝垪鍋氭硶锛屽嵆銆屽幓骞村悓鏈� vs 浠婂勾鍚屾湀銆嶇殑鍚屾瘮鏁板�硷紙`yearYoy`锛夈�傝嫢鍚庣画瑕佹眰鏀逛负銆岀幆姣斾笂鏈堛�嶉渶鍙︽敼銆�
+- **鏁版嵁婧愮己鍙o紙闈炰唬鐮侀棶棰橈紝闇�鐢ㄦ埛琛ユ暟鎹級**锛氣憼`freight_turnover_import` 2026-07 鐨勫綋鏈堝瓧娈碉紙freight_m07/turnover_m07锛変负 NULL锛屽彧鏈夊幓骞村悓鏈� 鈫� 7 鏈堝垪鍙湁 H2032 瑙勪笂璐ц繍閲� + 鎷嗗垎琛ㄥ懆杞噺锛屽叏鐪佸悎璁�/瑙勪笅璐ц繍閲� 7 鏈堜负绌猴紙棰勮寮圭獥銆岀己鏈堟暟鎹細7鏈堛�嶄笌姝や竴鑷达級锛涒憽`h2031_enterprise_monthly` 鍙湁 2026-07锛�771 琛岋級锛�1-6 鏈堜笌 2025 骞村潎涓虹┖ 鈫� 鏃呭琛� 1-6 鏈堝垪绌恒�佺疮璁″悓姣旓紙AB 鍒楋級绌猴紱鈶�8-12 鏈堝垪鏁版嵁渚濊禆鍚庣画鏈堜唤鏁版嵁婧愩��
+- HTTP 瀵煎嚭楠岃瘉锛�2026-07 鏈堟姤锛夛細cityDetail A1:AB112銆乸assengerCityDetail A1:AB58銆乸assengerMidDetail A1:AB94锛涜〃澶� 1-12 鏈� + 绱 + 鍚屾瘮姝g‘锛�#REF! 鍏ㄩ儴娓呴浂锛涚疮璁″垪鍏紡 =C+E+G+鈥�+Y锛�12 涓湀鍊煎垪涔嬪拰锛夛紱鍚堝苟鍗曞厓鏍煎悓姝ャ��
+**鍓嶇锛歊eportExport.vue 閲嶅啓涓哄崟妯″潡宸ヤ綔鍙帮紙鐑洿鏂板凡鐢熸晥锛屾棤缂栬瘧閿欒锛�**
+- 宸︿晶 240px 妯″潡瀵艰埅锛堟ā鍧楀悕 + 寮犳暟 + 褰撳墠鎶ヨ〃鏈燂級锛屽彸渚у崟妯″潡闈㈡澘锛氶《閮ㄦ姤琛ㄦ柟寮�/鎶ヨ〃鏈燂紝涓棿鎶ヨ〃琛ㄦ牸锛堝嬀閫� + 鎶ヨ〃鍚� + 鏁版嵁鐘舵�佹爣绛俱�屾暟鎹氨缁�/鏈鏍�/缂烘暟鎹��+ 澶囨敞 + 棰勮/瀵煎嚭锛夛紝搴曢儴鍏ㄩ�� +銆岄瑙堥�変腑/瀵煎嚭閫変腑锛坺ip锛夈�嶃��
+- 榛樿鑱氱劍褰撳墠鐢ㄦ埛璐熻矗妯″潡锛堢櫥褰曟椂鎸夌敤鎴� modules 杩囨护锛夛紝椤堕儴銆屾煡鐪嬪叏閮ㄦā鍧椼�嶅紑鍏冲彲鍒囨崲鍏ㄩ儴 5 妯″潡锛涗繚鐣� `/report/ready` 鐘舵�佸埛鏂般�乣/report/preview` 棰勮寮圭獥銆乣/report/export/{key}` 鍗曞紶瀵煎嚭銆乣/report/export/selected` zip 澶氬紶瀵煎嚭銆�
+- 鏃犲ご娴忚鍣� e2e锛坄_shots/report_workbench_e2e2.js`锛夛細寮犱笁鐧诲綍 鈫� 鍙樉绀鸿揣杩愭ā鍧楋紙navCount=1锛夛紱寮�銆屾煡鐪嬪叏閮ㄦā鍧椼�嶁啋 5 妯″潡鍏ㄦ樉锛涜揣杩� 2026-07 涓夊紶鎶ヨ〃鐘舵�佸潎銆屾暟鎹氨缁�嶏紱棰勮寮圭獥鏍囬銆岄瑙堟暟鎹� - 璐ц繍鎶ヨ〃锛圚203-2锛夛紙2026-07锛夈��+ 姹囨��/瑕嗙洊/缂烘湀鎻愮ず姝g‘锛汮S 0 閿欒銆傛埅鍥� `_shots/report_workbench*.png`锛汬TTP 瀵煎嚭鏍峰紶 `_shots/dl/*.xlsx`銆�
+**楠岃瘉鍛戒护/浜х墿**锛歚GET /api/report/export/{key}?period=2026-07&mode=month`锛堜笁绉� key 鍧� 200锛夛紱`_verify_http_export*.py`銆乣_verify_expand*.py`銆乣_test_expand_*.xlsx`銆�
+**閬楃暀/寰呭姙涓嶅彉**锛氭姇璧� M2 鏈紑宸ワ紱璐ц繍銆婃ā鏉縚璐ц繍閲忓懆杞噺銆嬫眹鎬昏〃 7 鏈堟暟鎹湭瀵煎叆锛堝奖鍝嶅悎璁�/瑙勪笅 7 鏈堝垪锛夛紱鏃呭 2025 骞村悓鏈熸暟鎹己澶憋紙绱鍚屾瘮绌猴級锛�8-12 鏈堝垪鏁版嵁渚濊禆鏁版嵁婧愶紱鑳借�楁柊瑙勫垯闃堝�煎緟纭銆�
+
+### 2026-08-26 鏅氶棿缁�6锛氭姤琛ㄧ敓鎴愰〉銆屾姤琛ㄦ柟寮�/鎶ヨ〃鏈熴�嶆敼涓轰笅鎷夋锛堝凡瀹屾垚楠岃瘉锛�
+**闇�姹傦紙鐢ㄦ埛纭锛�**锛氱晫闈笂鍙�夐」鏀逛负涓嬫媺妗嗛�夋嫨鏂瑰紡銆�
+**鏀瑰姩锛圧eportExport.vue锛�**
+- 銆屾姤琛ㄦ柟寮忋�嶇敱 el-radio-group 鏀逛负 el-select锛堟湀鎶�/骞存姤锛夛紱鍒囨崲鏂瑰紡鏃惰嚜鍔ㄦ竻绌烘姤琛ㄦ湡锛堟柊澧� `onModeChange`锛夈��
+- 銆屾姤琛ㄦ湡銆嶇敱 el-date-picker 鏀逛负 el-select 鍒嗙粍涓嬫媺锛堟柊澧� `periodGroups`锛夛細骞存姤鍒楀嚭 2025/2026/2027 骞达紱鏈堟姤鎸夊勾鍒嗙粍鍒楀嚭 1-12 鏈堬紱鑳借�楁ā鍧楋紙瀛e害鎶ワ級鍙垪瀛e害鏈湀 3/6/9/12锛屽垎缁勬爣棰樻爣娉ㄣ�岋紙瀛e害鏈湀锛夈�嶃��
+- 鏃犲ご娴忚鍣� e2e锛坄_shots/report_dropdowns_e2e.js`锛夛細鏈堟姤鈫斿勾鎶ュ垏鎹€�佸勾浠�/鏈堜唤鍒嗙粍涓嬫媺銆侀�� 2026-07 鍚庣姸鎬佸埛鏂般�屾暟鎹氨缁�嶃�丣S 0 閿欒銆傛埅鍥� `_shots/report_dropdowns.png`銆�
+**閬楃暀/寰呭姙涓嶅彉**锛氳鏅氶棿缁�5锛堟暟鎹簮缂哄彛绛夛級銆�
+
+### 2026-08-26 鏅氶棿缁�7锛氭姤琛ㄧ被鍨嬫敼涓轰笅鎷夋锛堝凡瀹屾垚楠岃瘉锛�
+**闇�姹傦紙鐢ㄦ埛婢勬竻锛�**锛氫笂娆℃敼鐨勬槸銆屾姤琛ㄦ柟寮�/鎶ヨ〃鏈熴�嶄笅鎷夋锛岀敤鎴峰疄闄呮寚鐨勬槸銆岄�夋嫨鎶ヨ〃鐨勭被鍨嬨�嶄篃瑕佺敤涓嬫媺妗嗐��
+**鏀瑰姩锛圧eportExport.vue锛�**
+- 鍒犻櫎宸︿晶 240px 妯″潡瀵艰埅鏍忥紙module-nav锛変笌銆屾煡鐪嬪叏閮ㄦā鍧椼�嶅紑鍏筹紙showAllModules 鍙� visibleModules 璁$畻灞炴�т竴骞剁Щ闄わ級銆�
+- 闈㈡澘澶撮儴鏀逛负銆屾姤琛ㄧ被鍨嬨�峞l-select锛氬垪鍑哄叏閮� 5 涓ā鍧楋紙璐ц繍/鍏矾鏃呭/鍩庡競瀹㈣繍/鎶曡祫/鑳借�楋級锛岄粯璁や粛鑱氱劍褰撳墠鐢ㄦ埛璐熻矗妯″潡锛堝紶涓夆啋璐ц繍锛夛紝鍙湪涓嬫媺妗嗚嚜鐢卞垏鎹㈠叾浠栨ā鍧楋紙杞殧绂� + 鐏垫椿鍏奸【锛夈��
+- 鎶ヨ〃鏂瑰紡銆佹姤琛ㄦ湡涓嬫媺妗嗕繚鎸佷笉鍙橈紱鑳借�楁ā鍧楁姤琛ㄦ湡浠嶅彧鍒楀搴︽湯鏈� 3/6/9/12銆�
+- 娓呯悊锛歴electModule鈫抩nModuleChange銆佸垹闄� periodText銆佸垹闄� module-nav 鐩稿叧 CSS銆�
+**楠岃瘉锛堟棤澶存祻瑙堝櫒 `_shots/report_type_dropdown_e2e.js`锛�**
+- 鏃犵紪璇戦敊璇�丣S 0 閿欒锛涙姤琛ㄧ被鍨嬩笅鎷� 5 涓�夐」榻愬叏锛涢粯璁ゅ�笺�岃揣杩愭姤琛紙H203-2锛夈�嶏紱鍒囨姇璧勨啋4 寮犳姤琛ㄣ�佸垏鑳借�椻啋1 寮狅紙鑳借繍姹囨�昏〃锛夛紱鑳借�楁姤琛ㄦ湡浠呭搴︽湯鏈堬紱鍒囧洖璐ц繍姝e父銆傛埅鍥� `_shots/report_type_dropdown.png`銆�
+**閬楃暀/寰呭姙涓嶅彉**锛氳鏅氶棿缁�5锛堣揣杩� 7 鏈堟眹鎬昏〃鏈鍏ャ�佹梾瀹� 1-6 鏈�/2025 鍚屾湡缂哄け銆�8-12 鏈堟暟鎹緷璧栨暟鎹簮锛夈��
diff --git a/check_h2032.py b/check_h2032.py
deleted file mode 100644
index 441eabd..0000000
--- a/check_h2032.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, glob, os
-docs = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs'
-f = glob.glob(os.path.join(docs, '妯℃澘_浜や紒缁烪203-2琛╛閬撹矾璐х墿杩愯緭鏈堝害鐢熶骇鎯呭喌.xlsx'))[0]
-ws = openpyxl.load_workbook(f)[openpyxl.load_workbook(f).sheetnames[0]]
-empty = []
-for r in range(2, ws.max_row+1):
-    name = ws.cell(r, 3).value
-    code = ws.cell(r, 2).value
-    if name is None or str(name).strip() == '':
-        empty.append((r, 'name empty', code))
-    elif code is None or str(code).strip() == '':
-        empty.append((r, 'code empty', name))
-print('rows with missing name/code:', empty)
-print('max_row:', ws.max_row, '=> data rows:', ws.max_row - 1)
\ No newline at end of file
diff --git a/check_schema.py b/check_schema.py
deleted file mode 100644
index b1687bf..0000000
--- a/check_schema.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-import io, os, re, glob
-
-base = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit'
-with io.open(os.path.join(base, 'docs', 'init.sql'), encoding='utf-8') as f:
-    sql = f.read()
-
-def snake(name):
-    return re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower()
-
-tables = {}
-for m in re.finditer(r'CREATE TABLE\s+(\w+)\s*\((.*?)\)\s*ENGINE', sql, re.S):
-    name, body = m.group(1), m.group(2)
-    cols = set()
-    for cm in re.finditer(r'^\s*(\w+)\s', body, re.M):
-        cols.add(cm.group(1))
-    tables[name] = cols
-
-entity_files = glob.glob(os.path.join(base, 'traffic-audit-server', 'src', 'main', 'java', 'com', 'trafficaudit', '**', 'entity', '*.java'), recursive=True)
-for f in entity_files:
-    with io.open(f, encoding='utf-8') as fh:
-        src = fh.read()
-    tm = re.search(r'@TableName\("(\w+)"\)', src)
-    if not tm:
-        print(os.path.basename(f), '-> NO @TableName'); continue
-    tname = tm.group(1)
-    fields = re.findall(r'private\s+[\w<>]+\s+(\w+);', src)
-    fields = [x for x in fields if x != 'serialVersionUID']
-    col_fields = {snake(fld) for fld in fields}
-    missing = sorted(col_fields - tables.get(tname, set()))
-    extra = sorted(tables.get(tname, set()) - col_fields)
-    status = 'OK' if not missing else 'MISSING: ' + ', '.join(missing)
-    print(f'{os.path.basename(f)} -> {tname}: {status}')
-    if extra:
-        print('   (table-only cols):', ', '.join(extra))
\ No newline at end of file
diff --git a/clear-bom.ps1 b/clear-bom.ps1
deleted file mode 100644
index 28f5a43..0000000
--- a/clear-bom.ps1
+++ /dev/null
@@ -1,10 +0,0 @@
-Get-ChildItem -Path "$PSScriptRoot\traffic-audit-server\src" -Recurse -Filter "*.java" | ForEach-Object {
-    $content = Get-Content $_.FullName -Raw
-    if ($content.StartsWith("`uFEFF")) {
-        $content = $content.Substring(1)
-        $utf8 = New-Object System.Text.UTF8Encoding $false
-        [System.IO.File]::WriteAllText($_.FullName, $content, $utf8)
-        Write-Host "Fixed: $($_.Name)"
-    }
-}
-Write-Host "BOM cleared. Ready to compile." -ForegroundColor Green
diff --git a/cmp2.py b/cmp2.py
deleted file mode 100644
index 569f224..0000000
--- a/cmp2.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, glob, os
-
-tmp = r'C:\Users\jcxiong\AppData\Local\Temp\ta_e2e_524cecf1'
-docs = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs'
-
-pairs = [
-    ('gen_freight_turnover.xlsx', '鐢熸垚_璐ц繍閲忓懆杞噺.xlsx'),
-    ('gen_city_detail.xlsx', '鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx'),
-    ('gen_freight_rank.xlsx', '鐢熸垚_璐ц繍閲忔帓鍚�.xlsx'),
-    ('gen_turnover_rank.xlsx', '鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx'),
-]
-for gen_name, sample_name in pairs:
-    gen_path = os.path.join(tmp, gen_name)
-    sglob = glob.glob(os.path.join(docs, sample_name))[0]
-    wg = openpyxl.load_workbook(gen_path)
-    ws = wg[wg.sheetnames[0]]
-    wss = openpyxl.load_workbook(sglob)[openpyxl.load_workbook(sglob).sheetnames[0]]
-    print(f'{gen_name}: dims {ws.max_row}x{ws.max_column} | sample: {wss.max_row}x{wss.max_column}')
-    # compare row1 col1 and structure markers
-    print('  gen R1:', ws.cell(1,1).value)
-    print('  smp R1:', wss.cell(1,1).value)
\ No newline at end of file
diff --git a/cmp_reports.py b/cmp_reports.py
deleted file mode 100644
index b120bf5..0000000
--- a/cmp_reports.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, glob, os
-
-tmp = r'C:\Users\jcxiong\AppData\Local\Temp\ta_e2e_524cecf1'
-docs = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs'
-
-pairs = [
-    ('gen_freight_turnover.xlsx', '鐢熸垚_璐ц繍閲忓懆杞噺.xlsx'),
-    ('gen_city_detail.xlsx', '鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx'),
-    ('gen_freight_rank.xlsx', '鐢熸垚_璐ц繍閲忔帓鍚�.xlsx'),
-    ('gen_turnover_rank.xlsx', '鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx'),
-]
-for gen_name, sample_name in pairs:
-    gen_path = os.path.join(tmp, gen_name)
-    sglob = glob.glob(os.path.join(docs, sample_name))
-    if not sglob:
-        print(gen_name, '-> sample not found'); continue
-    sample_path = sglob[0]
-    wg = openpyxl.load_workbook(gen_path)
-    ws = wg[wg.sheetnames[0]]
-    wsg = openpyxl.load_workbook(sample_path)
-    wss = wsg[wsg.sheetnames[0]]
-    print('='*70)
-    print(f'{gen_name}: dims {ws.max_row}x{ws.max_column} | sample {sample_name}: {wss.max_row}x{wss.max_column}')
-    print(' gen R1:', [str(ws.cell(1,c).value)[:14] for c in range(1, min(ws.max_column,6)+1)])
-    print(' smp R1:', [str(wss.cell(1,c).value)[:14] for c in range(1, min(wss.max_column,6)+1)])
-    print(' gen R2:', [str(ws.cell(2,c).value)[:14] for c in range(1, min(ws.max_column,6)+1)])
-    print(' smp R2:', [str(wss.cell(2,c).value)[:14] for c in range(1, min(wss.max_column,6)+1)])
-    print(' gen last row:', [str(ws.cell(ws.max_row,c).value)[:14] for c in range(1, min(ws.max_column,5)+1)])
\ No newline at end of file
diff --git a/cmp_xml.py b/cmp_xml.py
deleted file mode 100644
index 56d79dd..0000000
--- a/cmp_xml.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-import zipfile, os, re, glob
-
-tmp = r'C:\Users\jcxiong\AppData\Local\Temp\ta_e2e_524cecf1'
-docs = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs'
-
-def row_summary(path):
-    zf = zipfile.ZipFile(path)
-    xml = zf.read('xl/worksheets/sheet1.xml').decode('utf-8')
-    dim = re.search(r'<dimension ref="([^"]+)"', xml)
-    rows = re.findall(r'<row r="(\d+)"[^>]*?(/?)>', xml)
-    empties = [r for r, selfclose in rows if selfclose]
-    return dim.group(1) if dim else None, len(rows), empties
-
-for gen_name, sample_name in [('gen_freight_rank.xlsx', '鐢熸垚_璐ц繍閲忔帓鍚�.xlsx'),
-                              ('gen_turnover_rank.xlsx', '鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx')]:
-    g = row_summary(os.path.join(tmp, gen_name))
-    s = row_summary(glob.glob(os.path.join(docs, sample_name))[0])
-    print(gen_name, '-> dim:', g[0], 'rows:', g[1], 'empty rows:', g[2][-5:])
-    print(sample_name, '-> dim:', s[0], 'rows:', s[1], 'empty rows:', s[2][-5:])
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index ec3f44c..b71c41b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,6 +1,6 @@
 # 浜ら�氱粺璁℃姤琛ㄥ鏍哥郴缁� - Demo 璇存槑
 
-> Demo 鑼冨洿锛氫氦浼佺粺H203-2琛紙閬撹矾璐х墿杩愯緭鏈堝害鐢熶骇鎯呭喌锛�
+> 宸插疄鐜帮細浜や紒缁烪203-2锛堥亾璺揣鐗╄繍杈撴湀搴︾敓浜ф儏鍐碉級+ H203-1锛堝叕璺梾瀹㈣繍杈撴湀搴︾敓浜ф儏鍐碉級
 > 闇�姹傛枃妗o細`璐ц繍绠�鍖�.docx`銆乣database.md`銆乣architecture.md`锛涙暟鎹簱鑴氭湰锛歚init.sql`
 
 ## 涓�銆佺幆澧冭姹�
@@ -10,12 +10,12 @@
 | JDK | 1.8 |
 | Maven | 3.x |
 | Node.js | 14+锛圴ue CLI锛� |
-| MySQL | 5.7锛堟湰鏈� `localhost:3305`锛岃处鍙� `root/root`锛� |
+| MySQL | 8.0锛堟湰鏈� `localhost:3308`锛岃处鍙� `root/123456`锛屾湇鍔� MySQL80锛� |
 
 ## 浜屻�佸垵濮嬪寲鏁版嵁搴�
 
 ```bash
-mysql --default-character-set=utf8mb4 -h localhost -P 3305 -u root -proot < docs/init.sql
+mysql --default-character-set=utf8mb4 -h localhost -P 3308 -u root -p123456 < docs/init.sql
 ```
 
 `init.sql` 浼氶噸寤� `traffic_audit` 搴擄紙鍚棫鐗堣〃娓呯悊锛夛紝骞堕缃細
diff --git a/docs/database.md b/docs/database.md
index 11f29bf..b245236 100644
--- a/docs/database.md
+++ b/docs/database.md
@@ -211,7 +211,99 @@
 | import_batch_id | bigint | 瀵煎叆鎵规 |
 | created_at | datetime | 鍒涘缓鏃堕棿 |
 
+
+### h2031_enterprise_monthly锛堜氦浼佺粺H203-1琛� 鍏矾鏃呭杩愯緭鏈堝害鐢熶骇鎯呭喌锛�
+> 鏁版嵁鏉ユ簮锛氬叕璺梾瀹㈣繍杈撴湀搴︾敓浜ф儏鍐�.xlsx 鐨勩�屽叕璺梾瀹�6鏈堟姤銆峴heet锛涘悓鏂囦欢銆岃繍鏀胯溅杈嗘暟銆峴heet 瀵煎叆 passenger_auth_vehicle
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | bigint | PK |
+| report_period | varchar(20) | 鎶ヨ〃鏈� 2026-06 |
+| region_code | varchar(20) | 鎵�灞炲湴鍖猴紙琛屾斂鍖哄垝浠g爜锛� |
+| enterprise_code | varchar(50) | 浼佷笟浠g爜 |
+| enterprise_name | varchar(200) | 浼佷笟鍚嶇О |
+| vehicle_total | int | 瀹㈣繍杞﹁締_鎬昏 |
+| seat_total | int | 鏍稿畾杞藉浣峗鎬昏 |
+| vehicle_schedule | int | 杞﹁締鏁癬瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父锛� |
+| seat_schedule | int | 鏍稿畾杞藉浣峗瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父锛� |
+| vehicle_charter | int | 杞﹁締鏁癬瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓革級 |
+| seat_charter | int | 鏍稿畾杞藉浣峗瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓革級 |
+| passenger_total | double | 瀹㈣繍閲廮鎬昏锛堜汉锛� |
+| turnover_total | double | 鏃呭鍛ㄨ浆閲廮鎬昏锛堜汉鍏噷锛� |
+| passenger_class1 ~ passenger_class4 | double | 瀹㈣繍閲廮涓�~鍥涚被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛� |
+| turnover_class1 ~ turnover_class4 | double | 鏃呭鍛ㄨ浆閲廮涓�~鍥涚被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛� |
+| passenger_charter | double | 瀹㈣繍閲廮瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓革級 |
+| turnover_charter | double | 鏃呭鍛ㄨ浆閲廮瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓革級 |
+| avg_distance_total | double | 瀹㈣繍骞冲潎杩愯窛锛堝叕閲岋級 |
+| avg_distance_class1 ~ avg_distance_class4 | double | 涓�~鍥涚被瀹㈣繍鐝嚎骞冲潎杩愯窛锛堝叕閲岋級 |
+| avg_distance_charter | double | 鏃呮父銆佸寘杞﹀钩鍧囪繍璺濓紙鍏噷锛� |
+| verify_explanation | text | 鏍稿疄鎬у師鍥犺В閲� |
+| import_batch_id | bigint | 瀵煎叆鎵规 |
+| created_at | datetime | 鍒涘缓鏃堕棿 |
+
+### passenger_auth_vehicle锛堝叕璺梾瀹㈣繍鏀胯溅杈嗘暟鎹級
+> 鏁版嵁鏉ユ簮锛氬叕璺梾瀹㈣繍杈撴湀搴︾敓浜ф儏鍐�.xlsx 鐨勩�岃繍鏀胯溅杈嗘暟銆峴heet锛堜紒涓氫唬鐮�/浼佷笟鍚嶇О/杞﹁締鏁�/杞藉浣嶆暟锛夛紝闅忔湀鎶ヤ竴璧锋寜鎶ヨ〃鏈熷鍏�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | bigint | PK |
+| report_period | varchar(20) | 鎶ヨ〃鏈� |
+| enterprise_code | varchar(50) | 浼佷笟浠g爜 |
+| enterprise_name | varchar(200) | 浼佷笟鍚嶇О |
+| vehicle_count | int | 杞﹁締鏁� |
+| seat_count | int | 杞藉浣嶆暟 |
+| import_batch_id | bigint | 瀵煎叆鎵规 |
+| created_at | datetime | 鍒涘缓鏃堕棿 |
+
 ---
+
+### h204_vehicle_quarterly锛堜氦浼佺粺H204琛� 閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐德疯溅杈嗙矑搴︼級
+> 鏁版嵁鏉ユ簮锛氶亾璺揣杩愯溅杈嗚兘婧愭秷鑰楁儏鍐碉紙2026骞达級.xlsx 鐨勩�岀浜屽搴﹁揣杞﹁兘鑰楁槑缁嗐�峴heet锛堟瘡琛屽乏鍙冲悇5缁勮溅杈嗐�佹瘡缁�18鍒楋級锛涘悓鏂囦欢銆岃繍鏀胯溅杈嗕俊鎭�峴heet 瀵煎叆 h204_auth_vehicle
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | bigint | PK |
+| report_period | varchar(20) | 鎶ヨ〃鏈� 2026-06锛堝搴︽湯鏈堬級 |
+| region_code | varchar(20) | 鎵�灞炲湴鍖猴紙琛屾斂鍖哄垝浠g爜锛� |
+| enterprise_code | varchar(50) | 浼佷笟浠g爜 |
+| enterprise_name | varchar(200) | 浼佷笟鍚嶇О |
+| plate_no | varchar(30) | 杞︾墝鍙� |
+| vehicle_type_code | varchar(10) | 杞﹁締绫诲瀷浠g爜 |
+| vehicle_type | varchar(50) | 杞﹁締绫诲瀷锛堜腑鏂囷級 |
+| fuel_type_code | varchar(10) | 鐕冩枡绫诲瀷浠g爜 |
+| fuel_type | varchar(50) | 鐕冩枡绫诲瀷锛堜腑鏂囷級 |
+| manufacture_year | int | 鍑哄巶骞翠唤 |
+| marked_tonnage | double | 鏍囪鍚ㄤ綅锛堝惃锛� |
+| total_mileage | double | 鎬昏绋嬶紙鍏噷锛� |
+| loaded_mileage | double | 杞借揣閲岀▼锛堝叕閲岋級 |
+| empty_mileage | double | 绌洪┒閲岀▼锛堝叕閲岋級 |
+| freight | double | 璐ц繍閲忥紙鍚級 |
+| turnover | double | 璐х墿鍛ㄨ浆閲忥紙鍚ㄥ叕閲岋級 |
+| trip_count | int | 瓒熸鏁帮紙涓級 |
+| fuel_unit | varchar(20) | 鐕冩枡1璁¢噺鍗曚綅 |
+| fuel_consumption | double | 鐕冩枡1娑堣�楅噺 |
+| avg_distance | double | 骞冲潎杩愯窛锛堝叕閲岋級 |
+| avg_loaded_tonnage | double | 骞冲潎杞借揣鍚ㄤ綅锛堝惃锛� |
+| avg_trip_mileage | double | 鍗曡稛骞冲潎閲岀▼锛堝叕閲岋級 |
+| fuel_per_100km | double | 鐧惧叕閲屽崟鑰� |
+| turnover_fuel | double | 鍛ㄨ浆閲忓崟鑰� |
+| import_batch_id | bigint | 瀵煎叆鎵规 |
+| created_at | datetime | 鍒涘缓鏃堕棿 |
+
+### h204_auth_vehicle锛堥亾璺揣杩愯兘鑰梍杩愭斂杞﹁締淇℃伅锛�
+> 鏁版嵁鏉ユ簮锛氶亾璺揣杩愯溅杈嗚兘婧愭秷鑰楁儏鍐碉紙2026骞达級.xlsx 鐨勩�岃繍鏀胯溅杈嗕俊鎭�峴heet锛堣溅鐗屽彿/杞﹁締绫诲瀷/鐕冩枡绫诲瀷/鏍囪鍚ㄤ綅/鍑嗙壍寮曡川閲忥級锛岄殢瀛f姤涓�璧锋寜鎶ヨ〃鏈熷鍏�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | bigint | PK |
+| report_period | varchar(20) | 鎶ヨ〃鏈� |
+| plate_no | varchar(30) | 杞︾墝鍙� |
+| vehicle_type | varchar(50) | 杞﹁締绫诲瀷锛堜腑鏂囷級 |
+| fuel_type | varchar(50) | 鐕冩枡绫诲瀷锛堜腑鏂囷級 |
+| marked_tonnage | double | 鏍囪鍚ㄤ綅锛堝惃锛� |
+| traction_quality | double | 鍑嗙壍寮曡川閲忥紙鐗靛紩杞︽爣璁板惃浣嶏級 |
+| import_batch_id | bigint | 瀵煎叆鎵规 |
+| created_at | datetime | 鍒涘缓鏃堕棿 |
 
 ## 涓夈�佸鏍歌鍒欐ā鍧�
 
@@ -232,13 +324,25 @@
 | sort_order | int | 鎺掑簭 |
 | created_at | datetime | - |
 
-Demo棰勭疆6鏉¤鍒欙細
+Demo棰勭疆14鏉¤鍒欙紙report_type 鍖哄垎 H2032 璐ц繍 / H2031 鏃呭锛夛細
+
+璐ц繍 H2032 6鏉★細
 1. 杞﹁締杩愭斂瀵规瘮锛圖IFF>5 鈫� RED锛�
 2. 鍚ㄤ綅杩愭斂瀵规瘮锛圖IFF>50 鈫� RED锛�
 3. 鍛ㄨ浆閲忚建杩瑰姣旓紙RATIO>1.3 鈫� RED锛�
 4. 璐ц繍绫诲瀷鐜瘮锛圡OM 鈫� YELLOW锛�
 5. 闆嗚绠卞紓甯革紙NULL_CHECK 鈫� RED锛�
 6. 鏁磋溅鍚ㄤ綅鑼冨洿锛圤UT_RANGE 涓嶅湪4-40涔嬮棿 鈫� YELLOW锛�
+
+鏃呭 H2031 8鏉★紙compare_type 鏂板 MOM_EQ_ZERO/MOM_PCT/MOM_EQ/EXISTENCE锛夛細
+1. 杞﹁締鏁拌繍鏀垮姣旓紙DIFF>3 鈫� RED锛�
+2. 杞藉浣嶆暟杩愭斂瀵规瘮锛圖IFF>50 鈫� RED锛�
+3. 瀹㈣繍閲忕幆姣�=0锛堜竴~鍥涚被+鍖呰溅锛屽�间笌涓婃湀鐩稿悓 鈫� YELLOW锛�
+4. 鏃呭鍛ㄨ浆閲忕幆姣�=0锛堝悓涓� 鈫� YELLOW锛�
+5. 骞冲潎杩愯窛鐜瘮瓒吢�10%锛堜竴~鍥涚被 鈫� YELLOW锛�
+6. 鍖呰溅骞冲潎杩愯窛绛変簬涓婃湀锛堚啋 YELLOW锛�
+7. 鏃犵彮绾挎湁鐝嚎瀹㈣繍閲忥紙EXISTENCE 鈫� RED锛�
+8. 鏃犲寘杞︽湁鍖呰溅瀹㈣繍閲忥紙EXISTENCE 鈫� RED锛�
 
 ## 鍥涖�佸鏍哥粨鏋滄ā鍧�
 
@@ -278,6 +382,7 @@
 |------|------|------|
 | 鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx | freight_turnover_import + h2032_enterprise_monthly + scale_split_transport | 1-N鏈堟槑缁�+鍚屾瘮锛涜揣杩愰噺鍚堣=妯℃澘銆佽涓�=H2032銆佽涓�=鍚堣-瑙勪笂锛涘懆杞噺=鎷嗗垎琛� |
 | 鐢熸垚_璐ц繍閲忔帓鍚�.xlsx | freight_turnover_import + h2032_enterprise_monthly | 瑙勪笂=H2032绱銆佸悎璁�=妯℃澘绱銆佽涓�=鍚堣-瑙勪笂 |
+| 鐢熸垚_鍏矾鏃呭鍒嗗競宸炴槑缁�.xlsx | h2031_enterprise_monthly锛堟寜甯傚窞姹囨�伙級 | 鏈堟姤锛堥�夊勾-鏈堬紝浠婂勾1-N鏈�+绱锛�/骞存姤锛堥�夊勾浠斤紝鍏ㄥ勾12涓湀+绱锛夛紱姣忔湡鍚綋骞�+杩�3骞村尯鍧椼�佸悇鏈堝悓姣斻�佺疮璁″悓姣旓紱鍩庨檯鍩庝埂鍏氦/宸℃父鍑虹/缃戠害杞﹀垪鏆傜暀绌� |
 | 鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx | scale_split_transport | 瑙勪笂/瑙勪笅/鍚堣鍛ㄨ浆閲忔帓鍚嶏紙鍚繝瀹炲鍏ョ殑鍏ㄧ渷琛岋級 |
 
 ## 鍏�佺郴缁熸棩蹇�
@@ -314,3 +419,65 @@
 | masked_value | varchar(200) | 鑴辨晱鍊� |
 | mask_type | varchar(20) | ENTERPRISE/REGION |
 | call_id | varchar(50) | 璋冪敤ID |
+
+## 鍏�佹姇璧勬ā鍧楋紙瀹㈣繍绔欏満 / 鐗╂祦鍥尯锛�
+
+> 鏁版嵁婧愶細鍏ㄧ渷姹囨�诲ぇ琛紙`7鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx` 鏄庣粏sheet銆乣6鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls` 鍒嗛」鐩姇璧勫畬鎴愭儏鍐祍heet锛夛紝瀹℃牳姣斿鍩哄噯涓烘姇璧勭郴缁熷鍑猴紙`鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7).xls`锛夈��
+
+### investment_project锛堟姇璧勯」鐩富妗o級
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | bigint | PK |
+| project_name | varchar(200) | 椤圭洰鍚嶇О锛堜笌 category 缁勬垚鍞竴閿級 |
+| category | varchar(20) | 瀹㈣繍绔欏満 / 鐗╂祦鍥尯 |
+| city | varchar(50) | 甯傚窞 |
+| county | varchar(100) | 鍘匡紙甯傘�佸尯锛� |
+| builder_name | varchar(200) | 鎶曡祫寤鸿鍗曚綅 |
+| construct_nature | varchar(50) | 寤鸿鎬ц川锛堟柊寤�/缁缓/鏀规墿寤猴級 |
+| start_time / end_time | varchar(20) | 寮�宸� / 寤烘垚鏃堕棿 |
+| total_investment | double | 璁″垝鎬绘姇璧勶紙涓囧厓锛� |
+| approval_gk / approval_cs | varchar(500) | 宸ュ彲 / 鍒濊鎵瑰 |
+| building_area | double | 寤虹瓚闈㈢Н锛堝钩鏂圭背锛岀墿娴佽〃锛� |
+| is_billion / is_county / is_five_year | tinyint | 浜垮厓 / 缁忔祹寮哄幙 / 鍗佷簲浜旈噸鐐� 鍥哄畾鍚嶅崟鏍囪 |
+
+### investment_monthly锛堟姇璧勯」鐩湀搴﹀畬鎴愭儏鍐碉級
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | bigint | PK |
+| report_period | varchar(20) | 鎶ヨ〃鏈� 2026-07 |
+| project_id | bigint | 鍏宠仈 investment_project.id |
+| year_plan | double | 鏈勾璁″垝鎶曡祫锛堜竾鍏冿級 |
+| year_cum | double | 鑷勾鍒濈疮璁″畬鎴愭姇璧勶紙涓囧厓锛� |
+| month_done | double | 鍏朵腑锛氭湰鏈堝畬鎴愭姇璧勶紙涓囧厓锛� |
+| start_cum | double | 鑷紑濮嬪缓璁剧疮璁″畬鎴愭姇璧勶紙涓囧厓锛� |
+| progress_stage | varchar(100) | 寤鸿闃舵 |
+| progress_desc | varchar(2000) | 褰㈣薄杩涘害 |
+| building_area | double | 寤虹瓚闈㈢Н锛堝钩鏂圭背锛� |
+
+### investment_system锛堟姇璧勭郴缁熷鍑郝峰鏍告瘮瀵瑰熀鍑嗭級
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | bigint | PK |
+| report_period | varchar(20) | 鎶ヨ〃鏈� |
+| project_name | varchar(200) | 椤圭洰鍚嶇О锛堝崟浣嶏級 |
+| total_investment | double | 璁″垝鎬绘姇璧勶紙涓囧厓锛� |
+| start_cum | double | 鑷紑濮嬪缓璁捐嚦鏈湀绱瀹屾垚鎶曡祫锛堜竾鍏冿級 |
+| year_plan | double | 鏈勾璁″垝鎶曡祫锛堜竾鍏冿級 |
+| year_cum | double | 鑷勾鍒濈疮璁″畬鎴愭姇璧勶紙涓囧厓锛� |
+| month_done | double | 褰撴湀瀹屾垚鎶曡祫锛堜竾鍏冿級 |
+
+### 鎶曡祫瀹℃牳瑙勫垯锛坮eport_type=INVEST锛�
+| 瑙勫垯 | 姣旇緝绫诲瀷 | 璇存槑 |
+|------|---------|------|
+| RULE_I001 鑷紑濮嬬疮璁″姣� | CROSS_DIFF | 缁濆宸�>100涓� 涓� 鐩稿宸�>5% |
+| RULE_I002 鑷勾鍒濈疮璁″姣� | CROSS_DIFF | 缁濆宸�>50涓� 涓� 鐩稿宸�>5% |
+| RULE_I003 鏈湀瀹屾垚瀵规瘮 | CROSS_DIFF | 缁濆宸�>30涓� 涓� 鐩稿宸�>30% |
+| RULE_I004 绯荤粺缂哄け | EXISTENCE_SYS | 姹囨�诲ぇ琛ㄩ」鐩湪鎶曡祫绯荤粺瀵煎嚭涓笉瀛樺湪 |
+
+### 鎶曡祫 4 寮犻瀵兼姤琛紙ReportExportService锛�
+| 绔偣 | 琛ㄥ悕 | 鏁版嵁 |
+|------|------|------|
+| /api/report/export/investPlan | 鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽眹鎬� | 瀹㈣繍+鐗╂祦澶у潡銆�17甯傚窞灏忚妭+椤圭洰琛� |
+| /api/report/export/investFiveYearLogistics | "鍗佷簲浜�"鐗╂祦椤圭洰杩涘睍 | 鍥哄畾鍚嶅崟杩囨护锛屽競宸炲皬鑺�+椤圭洰琛� |
+| /api/report/export/investBillion | 浜垮厓鎶曡祫椤圭洰 | 鎶曡祫鎬昏〃 + 瑙勪笂椤圭洰琛ㄤ袱sheet锛屼嚎鍏冨悕鍗曡繃婊� |
+| /api/report/export/investCounty | 缁忔祹寮哄幙鎶曡祫缁熻 | 缁忔祹寮哄幙鍚嶅崟杩囨护 |
diff --git a/docs/init.sql b/docs/init.sql
index e824b6f..f7518c6 100644
--- a/docs/init.sql
+++ b/docs/init.sql
@@ -289,7 +289,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', '鏈湀鏂板璐ц繍绫诲瀷闇�鏍稿疄', 'freightContainer,freightCoal', 'MOM', '0', 'YELLOW', 'H2032', 1, 4),
+('璐ц繍绫诲瀷鐜瘮', 'RULE_004', '鏈湀鏂板璐ц繍绫诲瀷闇�鏍稿疄锛堜笂鏈堜负0銆佹湰鏈堟湁閲忥級', '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);
 
@@ -385,3 +385,205 @@
 ('admin', '.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5Eh', '绯荤粺绠$悊鍛�', 1);
 
 INSERT INTO sys_user_role (user_id, role_id) VALUES (1, 1);
+
+
+-- ============================================
+-- 鍏�佸叕璺梾瀹㈣繍杈擄紙浜や紒缁烪203-1锛夋墿灞曟ā鍧�
+-- ============================================
+
+DROP TABLE IF EXISTS h2031_enterprise_monthly;
+CREATE TABLE h2031_enterprise_monthly (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    report_period VARCHAR(20) NOT NULL COMMENT '鎶ヨ〃鏈� 2026-06',
+    region_code VARCHAR(20) COMMENT '鎵�灞炲湴鍖�',
+    enterprise_code VARCHAR(50) COMMENT '浼佷笟浠g爜',
+    enterprise_name VARCHAR(200) COMMENT '浼佷笟鍚嶇О',
+    vehicle_total INT COMMENT '瀹㈣繍杞﹁締_鎬昏',
+    seat_total INT COMMENT '鏍稿畾杞藉浣峗鎬昏',
+    vehicle_schedule INT COMMENT '杞﹁締鏁癬瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父瀹㈣繍瀹㈣溅锛�',
+    seat_schedule INT COMMENT '鏍稿畾杞藉浣峗瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父瀹㈣繍瀹㈣溅锛�',
+    vehicle_charter INT COMMENT '杞﹁締鏁癬瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓稿杩愬杞︼級',
+    seat_charter INT COMMENT '鏍稿畾杞藉浣峗瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓稿杩愬杞︼級',
+    passenger_total DOUBLE COMMENT '瀹㈣繍閲廮鎬昏锛堜汉锛�',
+    turnover_total DOUBLE COMMENT '鏃呭鍛ㄨ浆閲廮鎬昏锛堜汉鍏噷锛�',
+    passenger_class1 DOUBLE COMMENT '瀹㈣繍閲廮涓�绫诲杩愮彮绾匡紙鍚畾绾挎梾娓革級',
+    turnover_class1 DOUBLE COMMENT '鏃呭鍛ㄨ浆閲廮涓�绫诲杩愮彮绾匡紙鍚畾绾挎梾娓革級',
+    passenger_class2 DOUBLE COMMENT '瀹㈣繍閲廮浜岀被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�',
+    turnover_class2 DOUBLE COMMENT '鏃呭鍛ㄨ浆閲廮浜岀被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�',
+    passenger_class3 DOUBLE COMMENT '瀹㈣繍閲廮涓夌被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�',
+    turnover_class3 DOUBLE COMMENT '鏃呭鍛ㄨ浆閲廮涓夌被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�',
+    passenger_class4 DOUBLE COMMENT '瀹㈣繍閲廮鍥涚被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�',
+    turnover_class4 DOUBLE COMMENT '鏃呭鍛ㄨ浆閲廮鍥涚被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�',
+    passenger_charter DOUBLE COMMENT '瀹㈣繍閲廮瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓革級',
+    turnover_charter DOUBLE COMMENT '鏃呭鍛ㄨ浆閲廮瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓革級',
+    avg_distance_total DOUBLE COMMENT '瀹㈣繍骞冲潎杩愯窛锛堝叕閲岋級',
+    avg_distance_class1 DOUBLE COMMENT '涓�绫诲杩愮彮绾垮钩鍧囪繍璺濓紙鍏噷锛�',
+    avg_distance_class2 DOUBLE COMMENT '浜岀被瀹㈣繍鐝嚎骞冲潎杩愯窛锛堝叕閲岋級',
+    avg_distance_class3 DOUBLE COMMENT '涓夌被瀹㈣繍鐝嚎骞冲潎杩愯窛锛堝叕閲岋級',
+    avg_distance_class4 DOUBLE COMMENT '鍥涚被瀹㈣繍鐝嚎骞冲潎杩愯窛锛堝叕閲岋級',
+    avg_distance_charter DOUBLE COMMENT '鏃呮父銆佸寘杞﹀钩鍧囪繍璺濓紙鍏噷锛�',
+    verify_explanation TEXT COMMENT '鏍稿疄鎬у師鍥犺В閲�',
+    import_batch_id BIGINT COMMENT '瀵煎叆鎵规',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浜や紒缁烪203-1琛╛鍏矾鏃呭杩愯緭鏈堝害鐢熶骇鎯呭喌';
+
+DROP TABLE IF EXISTS passenger_auth_vehicle;
+CREATE TABLE passenger_auth_vehicle (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    report_period VARCHAR(20) COMMENT '鎶ヨ〃鏈�',
+    enterprise_code VARCHAR(50) COMMENT '浼佷笟浠g爜',
+    enterprise_name VARCHAR(200) COMMENT '浼佷笟鍚嶇О',
+    vehicle_count INT DEFAULT 0 COMMENT '杞﹁締鏁�',
+    seat_count INT DEFAULT 0 COMMENT '杞藉浣嶆暟',
+    import_batch_id BIGINT COMMENT '瀵煎叆鎵规',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鍏矾鏃呭杩愭斂杞﹁締鏁版嵁';
+
+-- H203-1 棰勭疆瀹℃牳瑙勫垯锛�4缁�8鏉★紝report_type=H2031锛�
+INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES
+('杞﹁締鏁拌繍鏀垮姣�(鏃呭)', 'RULE_P001', '瀹㈣繍杞﹁締鏁颁笌杩愭斂杞﹁締鏁板姣旓紝宸窛>3鍙伴渶鏍稿疄', 'vehicleTotal', 'DIFF', '3', 'RED', 'H2031', 1, 21),
+('杞藉浣嶆暟杩愭斂瀵规瘮(鏃呭)', '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),
+('鍖呰溅骞冲潎杩愯窛绛変簬涓婃湀鏍稿疄', '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);
+
+
+-- ============================================
+-- 涔濄�侀亾璺揣杩愯兘婧愭秷鑰楋紙浜や紒缁烪204锛夋墿灞曟ā鍧�
+-- ============================================
+
+-- 閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙杞﹁締绮掑害瀛f姤锛�
+DROP TABLE IF EXISTS h204_vehicle_quarterly;
+CREATE TABLE h204_vehicle_quarterly (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    report_period VARCHAR(20) NOT NULL COMMENT '鎶ヨ〃鏈� 2026-06锛堝搴︽湯鏈堬級',
+    region_code VARCHAR(20) COMMENT '鎵�灞炲湴鍖�',
+    enterprise_code VARCHAR(50) COMMENT '浼佷笟浠g爜',
+    enterprise_name VARCHAR(200) COMMENT '浼佷笟鍚嶇О',
+    plate_no VARCHAR(30) COMMENT '杞︾墝鍙�',
+    vehicle_type_code VARCHAR(10) COMMENT '杞﹁締绫诲瀷浠g爜',
+    vehicle_type VARCHAR(50) COMMENT '杞﹁締绫诲瀷锛堜腑鏂囷級',
+    fuel_type_code VARCHAR(10) COMMENT '鐕冩枡绫诲瀷浠g爜',
+    fuel_type VARCHAR(50) COMMENT '鐕冩枡绫诲瀷锛堜腑鏂囷級',
+    manufacture_year INT COMMENT '鍑哄巶骞翠唤',
+    marked_tonnage DOUBLE COMMENT '鏍囪鍚ㄤ綅锛堝惃锛�',
+    total_mileage DOUBLE COMMENT '鎬昏绋嬶紙鍏噷锛�',
+    loaded_mileage DOUBLE COMMENT '杞借揣閲岀▼锛堝叕閲岋級',
+    empty_mileage DOUBLE COMMENT '绌洪┒閲岀▼锛堝叕閲岋級',
+    freight DOUBLE COMMENT '璐ц繍閲忥紙鍚級',
+    turnover DOUBLE COMMENT '璐х墿鍛ㄨ浆閲忥紙鍚ㄥ叕閲岋級',
+    trip_count INT COMMENT '瓒熸鏁帮紙涓級',
+    fuel_unit VARCHAR(20) COMMENT '鐕冩枡1璁¢噺鍗曚綅',
+    fuel_consumption DOUBLE COMMENT '鐕冩枡1娑堣�楅噺',
+    avg_distance DOUBLE COMMENT '骞冲潎杩愯窛锛堝叕閲岋級',
+    avg_loaded_tonnage DOUBLE COMMENT '骞冲潎杞借揣鍚ㄤ綅锛堝惃锛�',
+    avg_trip_mileage DOUBLE COMMENT '鍗曡稛骞冲潎閲岀▼锛堝叕閲岋級',
+    fuel_per_100km DOUBLE COMMENT '鐧惧叕閲屽崟鑰�',
+    turnover_fuel DOUBLE COMMENT '鍛ㄨ浆閲忓崟鑰�',
+    import_batch_id BIGINT COMMENT '瀵煎叆鎵规',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    KEY idx_period_plate (report_period, plate_no),
+    KEY idx_period_ent (report_period, enterprise_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浜や紒缁烪204琛╛閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙杞﹁締绮掑害锛�';
+
+-- 閬撹矾璐ц繍鑳借�梍杩愭斂杞﹁締淇℃伅锛堣溅鐗岀矑搴︼級
+DROP TABLE IF EXISTS h204_auth_vehicle;
+CREATE TABLE h204_auth_vehicle (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    report_period VARCHAR(20) NOT NULL COMMENT '鎶ヨ〃鏈�',
+    plate_no VARCHAR(30) NOT NULL COMMENT '杞︾墝鍙�',
+    vehicle_type VARCHAR(50) COMMENT '杞﹁締绫诲瀷',
+    fuel_type VARCHAR(50) COMMENT '鐕冩枡绫诲瀷',
+    marked_tonnage DOUBLE COMMENT '鏍囪鍚ㄤ綅锛堝惃锛�',
+    traction_quality DOUBLE COMMENT '鍑嗙壍寮曡川閲忥紙鐗靛紩杞︽爣璁板惃浣嶏級',
+    import_batch_id BIGINT COMMENT '瀵煎叆鎵规',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    KEY idx_period_plate (report_period, plate_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='閬撹矾璐ц繍鑳借�梍杩愭斂杞﹁締淇℃伅锛堣溅鐗岀矑搴︼級';
+
+-- H204 棰勭疆瀹℃牳瑙勫垯锛�4鏉★紝report_type=H204锛�
+INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES
+('杞﹁締绫诲瀷涓庤繍鏀垮姣�', 'RULE_E001', '鑳借�楁槑缁嗚溅杈嗙被鍨嬩笌杩愭斂杞﹁締淇℃伅涓嶄竴鑷撮渶鏍稿疄', 'vehicleType', 'CROSS_DIFF', '0', 'RED', 'H204', 1, 31),
+('鐕冩枡绫诲瀷涓庤繍鏀垮姣�', 'RULE_E002', '鑳借�楁槑缁嗙噧鏂欑被鍨嬩笌杩愭斂杞﹁締淇℃伅涓嶄竴鑷撮渶鏍稿疄', 'fuelType', 'CROSS_DIFF', '0', 'RED', 'H204', 1, 32),
+('鏍囪鍚ㄤ綅涓庤繍鏀垮姣�', 'RULE_E003', '鑳借�楁槑缁嗘爣璁板惃浣嶄笌杩愭斂瀵规瘮锛屽樊璺�>0.05鍚ㄩ渶鏍稿疄锛堢壍寮曡溅瀵规瘮鍑嗙壍寮曡川閲忥級', 'markedTonnage', 'CROSS_DIFF', '0.05', 'YELLOW', 'H204', 1, 33),
+('杩愭斂杞﹁締淇℃伅缂哄け', 'RULE_E004', '鑳借�楁槑缁嗚溅鐗屽彿鍦ㄨ繍鏀胯溅杈嗕俊鎭腑涓嶅瓨鍦ㄩ渶鏍稿疄', 'plateNo', 'EXISTENCE_AUTH', '0', 'RED', 'H204', 1, 34),
+('杞借揣閲岀▼鍗犳瘮涓庡崟瓒熼噷绋嬫瘮鏍稿疄', 'RULE_E005', '杞借揣閲岀▼/鎬昏绋� 涓� 骞冲潎杩愯窛/鍗曡稛骞冲潎閲岀▼ 鐩稿樊卤10%闇�鏍稿疄', 'loadedMileage,totalMileage,avgDistance,avgTripMileage', 'LOADED_RATIO_DIFF', '0.1', 'YELLOW', 'H204', 1, 35),
+('鐧惧叕閲屽崟鑰楄秴鑼冨洿鏍稿疄', 'RULE_E006', '鐧惧叕閲屽崟鑰楀亸绂诲悓鐕冩枡绫诲瀷涓綅鏁拌秴杩�50%闇�鏍稿疄', 'fuelPer100km,fuelTypeCode', 'FUEL_RANGE', '0.5', 'YELLOW', 'H204', 1, 36),
+('鐧惧叕閲屽崟鑰楃幆姣斿彉鍖栨牳瀹�', 'RULE_E007', '鍚屼竴杈嗚溅鐧惧叕閲屽崟鑰楃幆姣斿彉鍖栬秴杩嚶�50%闇�鏍稿疄', 'fuelPer100km,plateNo', 'FUEL_MOM', '0.5', 'YELLOW', 'H204', 1, 37),
+('鑳借�楅噺涓庤揣杩愭湀鎶ュ姣旀牳瀹�', 'RULE_E008', '浼佷笟鑳借�楄揣杩愰噺/鍛ㄨ浆閲忎箣鍜屽ぇ浜庤揣杩愭湀鎶�50%闇�鏍稿疄', 'freight,turnover,enterpriseName', 'FREIGHT_VS_H2032', '0.5', 'RED', 'H204', 1, 38),
+('骞冲潎杩愯窛涓庤揣杩愭湀鎶ュ姣旀牳瀹�', 'RULE_E009', '杞﹁締骞冲潎杩愯窛涓庤揣杩愭湀鎶ュ钩鍧囪繍璺濈浉宸�60%闇�鏍稿疄', 'avgDistance,enterpriseName', 'AVG_DIST_VS_H2032', '0.6', 'YELLOW', 'H204', 1, 39),
+('骞冲潎杞借揣鍚ㄤ綅涓庢爣璁板惃浣嶆瘮鍊兼牳瀹�', 'RULE_E010', '骞冲潎杞借揣鍚ㄤ綅/鏍囪鍚ㄤ綅 灏忎簬0.5鎴栧ぇ浜�1闇�鏍稿疄', 'avgLoadedTonnage,markedTonnage', 'LOADED_TON_RATIO', '0.5,1', 'YELLOW', 'H204', 1, 40);
+
+-- ============================================
+-- 鍗併�佹姇璧勬ā鍧楋紙瀹㈣繍绔欏満/鐗╂祦鍥尯锛夋墿灞�
+-- ============================================
+
+-- 鎶曡祫椤圭洰涓绘。锛堟寜 椤圭洰鍚�+绫诲埆 鍞竴锛�
+DROP TABLE IF EXISTS investment_project;
+CREATE TABLE investment_project (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    project_name VARCHAR(200) NOT NULL COMMENT '椤圭洰鍚嶇О',
+    category VARCHAR(20) NOT NULL COMMENT '绫诲埆锛氬杩愮珯鍦�/鐗╂祦鍥尯',
+    city VARCHAR(50) COMMENT '甯傚窞',
+    county VARCHAR(100) COMMENT '鍘匡紙甯傘�佸尯锛�',
+    builder_name VARCHAR(200) COMMENT '鎶曡祫寤鸿鍗曚綅',
+    construct_nature VARCHAR(50) COMMENT '寤鸿鎬ц川',
+    start_time VARCHAR(20) COMMENT '寮�宸ユ椂闂�',
+    end_time VARCHAR(20) COMMENT '寤烘垚/绔e伐鏃堕棿',
+    total_investment DOUBLE COMMENT '璁″垝鎬绘姇璧勶紙涓囧厓锛�',
+    approval_gk VARCHAR(500) COMMENT '宸ュ彲鎵瑰',
+    approval_cs VARCHAR(500) COMMENT '鍒濊鎵瑰',
+    building_area DOUBLE COMMENT '寤虹瓚闈㈢Н锛堝钩鏂圭背锛�',
+    is_billion TINYINT DEFAULT 0 COMMENT '浜垮厓鎶曡祫椤圭洰鏍囪',
+    is_county TINYINT DEFAULT 0 COMMENT '缁忔祹寮哄幙椤圭洰鏍囪',
+    is_five_year TINYINT DEFAULT 0 COMMENT '鍗佷簲浜旈噸鐐圭墿娴侀」鐩爣璁�',
+    source VARCHAR(50) COMMENT '鏁版嵁鏉ユ簮',
+    import_batch_id BIGINT COMMENT '瀵煎叆鎵规',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_name_category (project_name, category)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鎶曡祫椤圭洰涓绘。';
+
+-- 鎶曡祫椤圭洰鏈堝害瀹屾垚鎯呭喌
+DROP TABLE IF EXISTS investment_monthly;
+CREATE TABLE investment_monthly (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    report_period VARCHAR(20) NOT NULL COMMENT '鎶ヨ〃鏈� 2026-07',
+    project_id BIGINT NOT NULL COMMENT '椤圭洰涓绘。ID',
+    year_plan DOUBLE COMMENT '鏈勾璁″垝鎶曡祫锛堜竾鍏冿級',
+    year_cum DOUBLE COMMENT '鑷勾鍒濈疮璁″畬鎴愭姇璧勶紙涓囧厓锛�',
+    month_done DOUBLE COMMENT '鍏朵腑锛氭湰鏈堝畬鎴愭姇璧勶紙涓囧厓锛�',
+    start_cum DOUBLE COMMENT '鑷紑濮嬪缓璁剧疮璁″畬鎴愭姇璧勶紙涓囧厓锛�',
+    progress_stage VARCHAR(100) COMMENT '寤鸿闃舵',
+    progress_desc VARCHAR(2000) COMMENT '褰㈣薄杩涘害',
+    building_area DOUBLE COMMENT '寤虹瓚闈㈢Н锛堝钩鏂圭背锛�',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    KEY idx_period (report_period),
+    KEY idx_project (project_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鎶曡祫椤圭洰鏈堝害瀹屾垚鎯呭喌';
+
+-- 鎶曡祫绯荤粺鏌ヨ瀵煎嚭锛堝鏍告瘮瀵瑰熀鍑嗭級
+DROP TABLE IF EXISTS investment_system;
+CREATE TABLE investment_system (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    report_period VARCHAR(20) NOT NULL COMMENT '鎶ヨ〃鏈� 2026-07',
+    project_name VARCHAR(200) NOT NULL COMMENT '椤圭洰鍚嶇О锛堝崟浣嶏級',
+    total_investment DOUBLE COMMENT '璁″垝鎬绘姇璧勶紙涓囧厓锛�',
+    start_cum DOUBLE COMMENT '鑷紑濮嬪缓璁捐嚦鏈湀绱瀹屾垚鎶曡祫锛堜竾鍏冿級',
+    year_plan DOUBLE COMMENT '鏈勾璁″垝鎶曡祫锛堜竾鍏冿級',
+    year_cum DOUBLE COMMENT '鑷勾鍒濈疮璁″畬鎴愭姇璧勶紙涓囧厓锛�',
+    month_done DOUBLE COMMENT '褰撴湀瀹屾垚鎶曡祫锛堜竾鍏冿級',
+    import_batch_id BIGINT COMMENT '瀵煎叆鎵规',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    KEY idx_period (report_period)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鎶曡祫绯荤粺鏌ヨ瀵煎嚭锛堝鏍告瘮瀵瑰熀鍑嗭級';
+
+-- INVEST 棰勭疆瀹℃牳瑙勫垯锛�4鏉★紝report_type=INVEST锛�
+INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES
+('鑷紑濮嬬疮璁′笌鎶曡祫绯荤粺瀵规瘮', 'RULE_I001', '鑷紑濮嬪缓璁剧疮璁″畬鎴愭姇璧勪笌鎶曡祫绯荤粺瀵煎嚭宸紓瓒呴槇鍊奸渶鏍稿疄', 'startCum', 'CROSS_DIFF', '100|5', 'YELLOW', 'INVEST', 1, 41),
+('鑷勾鍒濈疮璁′笌鎶曡祫绯荤粺瀵规瘮', 'RULE_I002', '鑷勾鍒濈疮璁″畬鎴愭姇璧勪笌鎶曡祫绯荤粺瀵煎嚭宸紓瓒呴槇鍊奸渶鏍稿疄', 'yearCum', 'CROSS_DIFF', '50|5', 'YELLOW', 'INVEST', 1, 42),
+('鏈湀瀹屾垚涓庢姇璧勭郴缁熷姣�', 'RULE_I003', '鏈湀瀹屾垚鎶曡祫涓庢姇璧勭郴缁熷鍑哄樊寮傝秴闃堝�奸渶鏍稿疄', 'monthDone', 'CROSS_DIFF', '30|30', 'YELLOW', 'INVEST', 1, 43),
+('鎶曡祫绯荤粺椤圭洰缂哄け鏍稿疄', 'RULE_I004', '姹囨�诲ぇ琛ㄩ」鐩湪鎶曡祫绯荤粺瀵煎嚭涓笉瀛樺湪闇�鏍稿疄', 'projectName', 'EXISTENCE_SYS', '0', 'YELLOW', 'INVEST', 1, 44);
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\345\256\242\350\277\220\347\256\200\345\214\226.docx" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\345\256\242\350\277\220\347\256\200\345\214\226.docx"
new file mode 100644
index 0000000..4e74364
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\345\256\242\350\277\220\347\256\200\345\214\226.docx"
Binary files differ
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\346\227\205\345\256\242\346\214\207\347\232\204\346\230\257\345\205\254\350\267\257\347\217\255\347\272\277" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\346\227\205\345\256\242\346\214\207\347\232\204\346\230\257\345\205\254\350\267\257\347\217\255\347\272\277"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\346\227\205\345\256\242\346\214\207\347\232\204\346\230\257\345\205\254\350\267\257\347\217\255\347\272\277"
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\346\227\205\345\256\242\347\256\200\345\214\226\345\267\245\344\275\234.xlsx" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\346\227\205\345\256\242\347\256\200\345\214\226\345\267\245\344\275\234.xlsx"
new file mode 100644
index 0000000..1f18649
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\346\227\205\345\256\242\347\256\200\345\214\226\345\267\245\344\275\234.xlsx"
Binary files differ
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\347\217\255\347\272\277\346\263\250\346\204\217\347\202\271.png" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\347\217\255\347\272\277\346\263\250\346\204\217\347\202\271.png"
new file mode 100644
index 0000000..13ff273
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\345\205\254\350\267\257\347\217\255\347\272\277\346\263\250\346\204\217\347\202\271.png"
Binary files differ
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237H203-1\350\241\250_\345\205\254\350\267\257\346\227\205\345\256\242\350\277\220\350\276\223\346\234\210\345\272\246\347\224\237\344\272\247\346\203\205\345\206\265.xlsx" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237H203-1\350\241\250_\345\205\254\350\267\257\346\227\205\345\256\242\350\277\220\350\276\223\346\234\210\345\272\246\347\224\237\344\272\247\346\203\205\345\206\265.xlsx"
new file mode 100644
index 0000000..c3af5b3
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237H203-1\350\241\250_\345\205\254\350\267\257\346\227\205\345\256\242\350\277\220\350\276\223\346\234\210\345\272\246\347\224\237\344\272\247\346\203\205\345\206\265.xlsx"
Binary files differ
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\346\250\241\346\235\277_\345\205\254\350\267\257\346\227\205\345\256\242\350\277\220\346\224\277\350\275\246\350\276\206\346\225\260.xlsx" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\346\250\241\346\235\277_\345\205\254\350\267\257\346\227\205\345\256\242\350\277\220\346\224\277\350\275\246\350\276\206\346\225\260.xlsx"
new file mode 100644
index 0000000..069aab1
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\346\250\241\346\235\277_\345\205\254\350\267\257\346\227\205\345\256\242\350\277\220\346\224\277\350\275\246\350\276\206\346\225\260.xlsx"
Binary files differ
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\344\270\255\345\217\243\345\276\204\345\210\206\346\236\220.xlsx" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\344\270\255\345\217\243\345\276\204\345\210\206\346\236\220.xlsx"
new file mode 100644
index 0000000..5181a4a
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\344\270\255\345\217\243\345\276\204\345\210\206\346\236\220.xlsx"
Binary files differ
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\344\270\255\345\217\243\345\276\204\346\216\222\345\220\215.xlsx" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\344\270\255\345\217\243\345\276\204\346\216\222\345\220\215.xlsx"
new file mode 100644
index 0000000..4f1026c
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\344\270\255\345\217\243\345\276\204\346\216\222\345\220\215.xlsx"
Binary files differ
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\344\270\255\345\217\243\345\276\204\346\230\216\347\273\206.xlsx" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\344\270\255\345\217\243\345\276\204\346\230\216\347\273\206.xlsx"
new file mode 100644
index 0000000..2973105
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\344\270\255\345\217\243\345\276\204\346\230\216\347\273\206.xlsx"
Binary files differ
diff --git "a/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\345\205\254\350\267\257\346\227\205\345\256\242\345\210\206\345\270\202\345\267\236.xlsx" "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\345\205\254\350\267\257\346\227\205\345\256\242\345\210\206\345\270\202\345\267\236.xlsx"
new file mode 100644
index 0000000..134144d
--- /dev/null
+++ "b/docs/\345\205\254\350\267\257\346\227\205\345\256\242/\347\224\237\346\210\220_\345\205\254\350\267\257\346\227\205\345\256\242\345\210\206\345\270\202\345\267\236.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\237\216\345\270\202\345\256\242\350\277\220\346\225\260\346\215\256\344\274\230\345\214\226.docx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\237\216\345\270\202\345\256\242\350\277\220\346\225\260\346\215\256\344\274\230\345\214\226.docx"
new file mode 100644
index 0000000..8b15a4c
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\237\216\345\270\202\345\256\242\350\277\220\346\225\260\346\215\256\344\274\230\345\214\226.docx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\256\242\350\277\220\347\256\200\345\214\226.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\256\242\350\277\220\347\256\200\345\214\226.xlsx"
new file mode 100644
index 0000000..f214984
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\256\242\350\277\220\347\256\200\345\214\226.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\256\242\350\277\220\351\207\217\347\232\204\345\220\214\346\257\224\347\216\257\346\257\224\346\230\2570% \351\234\200\346\240\270\345\256\236\357\274\210\344\270\273\350\246\201\345\207\272\347\216\260\345\234\250\345\205\254\344\272\244\357\274\211.txt" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\256\242\350\277\220\351\207\217\347\232\204\345\220\214\346\257\224\347\216\257\346\257\224\346\230\2570% \351\234\200\346\240\270\345\256\236\357\274\210\344\270\273\350\246\201\345\207\272\347\216\260\345\234\250\345\205\254\344\272\244\357\274\211.txt"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\256\242\350\277\220\351\207\217\347\232\204\345\220\214\346\257\224\347\216\257\346\257\224\346\230\2570% \351\234\200\346\240\270\345\256\236\357\274\210\344\270\273\350\246\201\345\207\272\347\216\260\345\234\250\345\205\254\344\272\244\357\274\211.txt"
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\345\205\254\344\272\244.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\345\205\254\344\272\244.xlsx"
new file mode 100644
index 0000000..d482f29
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\345\205\254\344\272\244.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\345\210\206\345\270\202\345\267\236.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\345\210\206\345\270\202\345\267\236.xlsx"
new file mode 100644
index 0000000..0c7c749
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\345\210\206\345\270\202\345\267\236.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\345\256\242\350\277\220\345\220\204\345\270\202\345\267\236\346\230\216\347\273\206\350\241\250.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\345\256\242\350\277\220\345\220\204\345\270\202\345\267\236\346\230\216\347\273\206\350\241\250.xlsx"
new file mode 100644
index 0000000..ab29de6
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\345\256\242\350\277\220\345\220\204\345\270\202\345\267\236\346\230\216\347\273\206\350\241\250.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\346\261\207\346\200\273.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\346\261\207\346\200\273.xlsx"
new file mode 100644
index 0000000..cf67b43
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\237\216\345\270\202\346\261\207\346\200\273.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\267\241\346\270\270\345\207\272\347\247\237.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\267\241\346\270\270\345\207\272\347\247\237.xlsx"
new file mode 100644
index 0000000..e995990
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\345\267\241\346\270\270\345\207\272\347\247\237.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\350\275\250\351\201\223\343\200\201\350\275\256\346\270\241.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\350\275\250\351\201\223\343\200\201\350\275\256\346\270\241.xlsx"
new file mode 100644
index 0000000..14bee3d
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\345\257\274\345\205\245\346\250\241\346\235\277_\350\275\250\351\201\223\343\200\201\350\275\256\346\270\241.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237U203\350\241\250_\345\237\216\345\270\202\345\205\254\345\205\261\344\272\244\351\200\232\346\234\210\345\272\246\350\277\220\350\220\245\346\203\205\345\206\265\357\274\2102026\345\271\2647\346\234\210\357\274\211.xls" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237U203\350\241\250_\345\237\216\345\270\202\345\205\254\345\205\261\344\272\244\351\200\232\346\234\210\345\272\246\350\277\220\350\220\245\346\203\205\345\206\265\357\274\2102026\345\271\2647\346\234\210\357\274\211.xls"
new file mode 100644
index 0000000..0407a14
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237U203\350\241\250_\345\237\216\345\270\202\345\205\254\345\205\261\344\272\244\351\200\232\346\234\210\345\272\246\350\277\220\350\220\245\346\203\205\345\206\265\357\274\2102026\345\271\2647\346\234\210\357\274\211.xls"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\344\272\244\350\241\214\347\273\237U402\350\241\250-\345\267\241\346\270\270\345\207\272\347\247\237\346\261\275\350\275\246\350\277\220\350\220\245\346\234\215\345\212\241\346\203\205\345\206\265\346\234\210\346\212\245\0502026\051.xls" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\344\272\244\350\241\214\347\273\237U402\350\241\250-\345\267\241\346\270\270\345\207\272\347\247\237\346\261\275\350\275\246\350\277\220\350\220\245\346\234\215\345\212\241\346\203\205\345\206\265\346\234\210\346\212\245\0502026\051.xls"
new file mode 100644
index 0000000..fb661d7
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\344\272\244\350\241\214\347\273\237U402\350\241\250-\345\267\241\346\270\270\345\207\272\347\247\237\346\261\275\350\275\246\350\277\220\350\220\245\346\234\215\345\212\241\346\203\205\345\206\265\346\234\210\346\212\245\0502026\051.xls"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\205\254\344\272\244\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\205\254\344\272\244\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
new file mode 100644
index 0000000..d482f29
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\205\254\344\272\244\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\256\242\350\277\220\345\220\204\345\270\202\345\267\236\346\230\216\347\273\206\350\241\250.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\256\242\350\277\220\345\220\204\345\270\202\345\267\236\346\230\216\347\273\206\350\241\250.xlsx"
new file mode 100644
index 0000000..ab29de6
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\256\242\350\277\220\345\220\204\345\270\202\345\267\236\346\230\216\347\273\206\350\241\250.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\256\242\350\277\220\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\347\264\257\350\256\241.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\256\242\350\277\220\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\347\264\257\350\256\241.xlsx"
new file mode 100644
index 0000000..0c7c749
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\256\242\350\277\220\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\347\264\257\350\256\241.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\256\242\350\277\220\346\261\207\346\200\273.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\256\242\350\277\220\346\261\207\346\200\273.xlsx"
new file mode 100644
index 0000000..cf67b43
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\237\216\345\270\202\345\256\242\350\277\220\346\261\207\346\200\273.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\267\241\346\270\270\345\207\272\347\247\237\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\267\241\346\270\270\345\207\272\347\247\237\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
new file mode 100644
index 0000000..e995990
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\345\267\241\346\270\270\345\207\272\347\247\237\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
Binary files differ
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\350\275\250\351\201\223\350\275\256\346\270\241\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\350\275\250\351\201\223\350\275\256\346\270\241\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
new file mode 100644
index 0000000..14bee3d
--- /dev/null
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\347\224\237\346\210\220_\350\275\250\351\201\223\350\275\256\346\270\241\345\256\242\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
Binary files differ
diff --git "a/docs/\345\274\200\346\272\220\351\241\271\347\233\256\345\257\271\346\257\224.md" "b/docs/\345\274\200\346\272\220\351\241\271\347\233\256\345\257\271\346\257\224.md"
new file mode 100644
index 0000000..7785df1
--- /dev/null
+++ "b/docs/\345\274\200\346\272\220\351\241\271\347\233\256\345\257\271\346\257\224.md"
@@ -0,0 +1,90 @@
+# 鍚岀被寮�婧愰」鐩皟鐮斾笌瀵规瘮锛�2026-08-23锛�
+
+> 妫�绱㈣寖鍥达細GitHub Search API锛堜腑鑻辨枃澶氬叧閿瘝锛夈�丟itee API锛堟湭璁よ瘉涓嶅彲鐢紝宸茶烦杩囷級銆佺煡鍚嶉」鐩� README 鏍稿疄銆�
+> 妫�绱㈡棩鏈燂細2026-08-23
+
+## 涓�銆佺粨璁哄厛琛�
+
+**鏈彂鐜颁笌鏈」鐩洿鎺ュ悓绫荤殑寮�婧愰」鐩�**锛堜氦閫氱粺璁℃姤琛ㄥ鏍� + 涓夊憳鏉冮檺 + Excel 瀵煎叆瀹℃牳 + 鎶ヨ〃鐢熸垚 + AI 瑙i噴锛岃繖鏉$粍鍚堝湪 GitHub/Gitee 鍧囨棤鐜版垚瀹炵幇锛夈��
+
+鍘熷洜锛�
+1. 杩欑被绯荤粺寮虹粦瀹氬悇鐪�/鍚勮涓氱粺璁¤皟鏌ュ埗搴︼紝涓氬姟鍙e緞鏃犳硶閫氱敤鍖栵紱
+2. 鏀垮姟椤圭洰閫氬父绉佹湁鍖栦氦浠橈紝寰堝皯寮�婧愶紱
+3. 鏍稿績浠峰�煎湪瀹℃牳瑙勫垯涓庝笟鍔″彛寰勶紝涓嶅湪閫氱敤鍔熻兘銆�
+
+鍥犳**鑷爺鏂瑰悜姝g‘锛屼笉闇�瑕佺籂缁�"閫夊瀷/鎶勭幇鎴�"**锛涗絾鍙互浠庡洓绫�"鑳藉姏缁勪欢鍨�"寮�婧愰」鐩惛鏀惰璁℃�濊矾銆�
+
+## 浜屻�佸弬鐓ч」鐩姣�
+
+### A. 鏀垮簻缁熻鎶ヨ〃鐢熸垚锛堟渶鎺ヨ繎鐨勭洿鎺ュ悓绫伙級
+**NIC Chennai 鈥� Monthly Statistical Report Generation**
+- 瀹氫綅锛氬嵃搴﹀浗瀹朵俊鎭腑蹇冿紙NIC锛夊疄涔犵敓椤圭洰锛岃嚜鍔ㄥ寲鐢熸垚鏀垮簻閲囪喘鏈堝害缁熻鎶ヨ〃锛圡SR锛�
+- 鎶�鏈爤锛歋pring Boot + Vue.js + PostgreSQL + Apache POI
+- 鍔熻兘锛氬鏁版嵁搴撳姩鎬佽繛鎺ワ紙绠$悊绔厤缃級銆佹寜璐㈠勾/鏈堜唤鐢熸垚鎶ヨ〃銆丒xcel 鏍峰紡鍖栬緭鍑猴紙鍚堝苟鍗曞厓鏍�/鍒嗙粍/鍚堣锛夈�乄eb 涓嬭浇
+- 涓庢湰椤圭洰瀵规瘮锛�
+  - 鐩镐技锛歋pring Boot + Vue銆丒xcel 鎶ヨ〃杈撳嚭銆佹寜鏈堢敓鎴愮粺璁℃姤琛�
+  - 宸窛锛堟垜浠洿寮猴級锛氭棤瀵煎叆瑙f瀽銆佹棤瀹℃牳瑙勫垯寮曟搸銆佹棤涓夊憳鏉冮檺銆佹棤 AI銆佸崟鎶ヨ〃鍦烘櫙
+- 鍙�熼壌锛氬鏁版嵁婧愯繛鎺ョ鐞嗙晫闈紙鍔ㄦ�侀厤缃暟鎹簮锛夆�斺�旀姇璧勬ā鍧楀鎺�"鎶曡祫绯荤粺"鏃跺彲鍙傝��
+
+### B. 蹇�熷紑鍙戝钩鍙帮紙鍥藉唴鏀垮姟搴曞骇锛�
+**RuoYi-Vue锛堣嫢渚濓級**
+- 瀹氫綅锛氬浗鍐呮渶涓绘祦鐨� Java 鍓嶅悗绔垎绂诲揩閫熷紑鍙戝钩鍙帮紝澶ч噺鏀垮姟绯荤粺鍩轰簬瀹�
+- 鎶�鏈爤锛歋pring Boot锛�2/3/4 澶氬垎鏀級+ Spring Security + JWT + Redis + Vue2/3 + Element UI/Plus
+- 鍔熻兘锛氫唬鐮佺敓鎴愬櫒銆佸姩鎬佹潈闄愯彍鍗曘�佹寜閽骇鏉冮檺銆佹搷浣滄棩蹇椼�佸畾鏃朵换鍔°�佸绉熸埛
+- 涓庢湰椤圭洰瀵规瘮锛�
+  - 鐩镐技锛氭潈闄愪綋绯昏璁★紙鐢ㄦ埛/瑙掕壊/鑿滃崟/鏃ュ織锛変笌鏈」鐩悓婧愶紝灞炰笟鐣屾爣鍑嗗仛娉�
+  - 宸窛锛堟垜浠己锛夛細浠g爜鐢熸垚鍣紙鍔犺〃鏃剁渷澶ч噺閲嶅浠g爜锛夈�丷edis 缂撳瓨銆佸畾鏃朵换鍔°�佹搷浣滄棩蹇楁敞瑙e寲
+- 鍙�熼壌锛�**琛ㄧ粨鏋勫彉鏇村悗鐨勪竴閿唬鐮佺敓鎴�**锛圧uoYi-generator锛夆�斺�旀墿灞� H203-1/H204/鎶曡祫妯″潡鏃舵渶瀹炵敤锛涙湰椤圭洰褰撳墠鎵╁睍閮芥槸鎵嬪啓 Controller/Service/Mapper
+
+### C. 鎶ヨ〃 / BI 鐢熸垚
+**JimuReport锛堢Н鏈ㄦ姤琛級鈽�8k+**
+- 瀹氫綅锛氬紑婧愭姤琛ㄥ伐鍏凤紝绫� Excel 鍦ㄧ嚎鎶ヨ〃璁捐鍣� + Chat2BI锛堝璇濆紡 AI 鎶ヨ〃锛�
+- 鎶�鏈爤锛歋pring Boot 绯伙紝鍏煎鍥戒骇淇″垱鏁版嵁婧�
+- 涓庢湰椤圭洰瀵规瘮锛氭湰椤圭洰鎶ヨ〃鐢� EasyExcel + 鍥哄畾妯℃澘锛堝鐢ㄤ笖鍙帶锛夛紱JimuReport 鐨�"妯℃澘鍦ㄧ嚎璁捐"涓�"Chat2BI"鎬濊矾涓庢垜浠� AI 鍒嗘瀽鏂瑰悜涓�鑷达紝浣嗗紩鍏ユ垚鏈珮
+- 鍙�熼壌锛堣繙鏈燂級锛氳嫢鎶ヨ〃鏁伴噺澧為暱鍒板嚑鍗佸紶锛屽彲鑰冭檻鎶�"鎶ヨ〃妯℃澘"閰嶇疆鍖�/鍦ㄧ嚎璁捐鍖栵紱褰撳墠闃舵鍥哄畾妯℃澘鏇寸ǔ
+
+**DataEase 鈽�24k**
+- 瀹氫綅锛氬紑婧� BI 宸ュ叿锛圱ableau 鏇夸唬锛夛紝鏁版嵁婧愯繛鎺� + 鍙鍖� + 澶у睆
+- 涓庢湰椤圭洰瀵规瘮锛氭湰椤圭洰涓嶉渶瑕� BI 鍒嗘瀽鑳藉姏锛涗絾鏈潵"鏁版嵁鐪嬫澘"鎵╁睍锛坅rchitecture.md 鎻愬埌鐨勬暟鎹湅鏉匡級鍙洿鎺ョ敤 DataEase 鎴栧�熼壌鍏惰璁�
+
+### D. 鏁版嵁璐ㄩ噺 / 瀹℃牳瑙勫垯寮曟搸锛堜笌"瀹℃牳寮曟搸"鐞嗗康鏈�鎺ヨ繎锛�
+**Apache Griffin 鈽�1.1k**
+- 瀹氫綅锛欰pache 椤剁骇椤圭洰锛屾ā鍨嬮┍鍔ㄧ殑鏁版嵁璐ㄩ噺骞冲彴
+- 妯″紡锛氬畾涔� measure锛堣鍒�/鎸囨爣锛夆啋 瀹氭椂鎵ц 鈫� 鐢熸垚璐ㄩ噺鎶ュ憡锛涜鍒欓厤缃寲銆佽法鏁版嵁婧�
+- 涓庢湰椤圭洰瀵规瘮锛�**瀹℃牳寮曟搸璁捐鐞嗗康瀹屽叏涓�鑷�**锛坅udit_rule 琛� = measure锛屾墽琛屽悗鐢熸垚 audit_result = 璐ㄩ噺鎶ュ憡锛夛紝鎴戜滑宸叉湁闆忓舰涓旀洿璐村悎涓氬姟锛坈ompare_type 鏇翠笟鍔″寲锛�
+
+**Great Expectations 鈽�11k / Soda Core 鈽�2.4k**
+- 瀹氫綅锛歅ython 鏁版嵁璐ㄩ噺妗嗘灦锛屽0鏄庡紡 expectation/checks + 鏁版嵁濂戠害
+- 涓庢湰椤圭洰瀵规瘮锛氬0鏄庡紡瑙勫垯 + 鍛婅鍒嗙骇锛圧ED/YELLOW锛夋�濊矾涓庢垜浠竴鑷�
+- 鍙�熼壌锛氳鍒欑殑缁勭粐鏂瑰紡锛堟寜琛�/鎸夊瓧娈靛垎缁勩�佽鍒欎緷璧栧叧绯伙級銆佽鍒欐墽琛岀粨鏋滅殑鍙鍖栵紙Dashboard锛�
+
+### E. 鍓嶇 Excel 瀵煎叆缁勪欢
+**react-spreadsheet-import 鈽�553**
+- 瀹氫綅锛欵xcel/CSV 瀵煎叆娴佺▼缁勪欢锛岃嚜鍔ㄥ垪鍖归厤 + 鏍¢獙 + 棰勮
+- 鍙�熼壌锛堜綋楠屽眰锛夛細瀵煎叆鏃惰嚜鍔ㄨ瘑鍒垪鍚嶆槧灏勩�佸鍏ラ瑙堛�侀敊璇楂樹寒鈥斺�旀湰椤圭洰瀵煎叆宸插仛琛屽彿鎶ラ敊锛屽彲鍔�"鍒楀悕鑷姩鍖归厤"鎻愬崌浣撻獙
+
+## 涓夈�佹妧鏈爤妯悜瀵规瘮
+
+| 缁村害 | 鏈」鐩� | 涓绘祦鍙傜収锛圧uoYi 绛夛級 | 璇勪环 |
+|------|--------|---------------------|------|
+| 鍚庣 | Spring Boot 2.x | Spring Boot 3/4 | 宸插亸鏃э紝浣嗙煭鏈熶笉褰卞搷寮�鍙戯紱闀挎湡闇�瑙勫垝鍗囩骇 |
+| 鍓嶇 | Vue 2 + Element UI | Vue 3 + Element Plus | Vue2 宸插仠姝㈢淮鎶わ紱鐭湡澶熺敤锛岄暱鏈熼渶鍗囩骇 |
+| 鏁版嵁搴� | MySQL 8 | MySQL/PG | 姝e父 |
+| 瀵煎叆瀵煎嚭 | EasyExcel | POI/EasyExcel | 涓绘祦 |
+| 鏉冮檺 | 鑷爺 JWT + 涓夊憳 | Spring Security + JWT | 涓庝富娴佸榻� |
+| AI | DeepSeek 闆嗘垚 | JimuReport Chat2BI | 鏈」鐩湪鏀垮姟鍦烘櫙鏇村厠鍒讹紙鑴辨晱+瑙i噴锛� |
+
+## 鍥涖�佸鏈」鐩殑鍚ず锛堝紑宸ュ墠鍏辫瘑锛�
+
+1. **涓嶅紩鍏ュぇ妗嗘灦閲嶅啓**锛氭棤鐩存帴鍚岀被鍙妱锛岃嚜鐮旇矾绾挎纭紱RuoYi/DataEase 鍏ㄩ噺寮曞叆鎴愭湰杩滃ぇ浜庢敹鐩娿��
+2. **鏈�鍊煎緱鍊熼壌鐨勬槸 RuoYi 鐨勪唬鐮佺敓鎴愬櫒**锛氭墿灞� H203-1/H204/鎶曡祫鏃讹紝鑻ュ厛鎼竴涓�"琛ㄧ粨鏋� 鈫� 鐢熸垚 CRUD+瀵煎叆妯℃澘楠ㄦ灦"鐨勫皬宸ュ叿锛堟垨鎵嬪伐妯℃澘锛夛紝鑳芥樉钁楁彁閫熶笖淇濇寔椋庢牸涓�鑷淬��
+3. **瀹℃牳寮曟搸缁х画璧�"瑙勫垯閰嶇疆鍖�"**锛坅udit_rule 椹卞姩锛夛紝涓� Griffin/GX 鐞嗗康涓�鑷达紱鎵╁睍姣旇緝绫诲瀷锛圡OM_EQ_ZERO/EXISTENCE/CROSS_TABLE/TREND/RATIO_CHANGE锛夋椂淇濇寔澹版槑寮忥紝涓嶅啓姝汇��
+4. **鎶ヨ〃杈撳嚭缁存寔鍥哄畾妯℃澘 + EasyExcel**锛屾殏涓嶄笂鍦ㄧ嚎璁捐鍣紱绛夋姤琛ㄦ暟閲忓浜嗗啀璇勪及 JimuReport 寮忛厤缃寲銆�
+5. **鎶曡祫妯″潡瀵规帴"鎶曡祫绯荤粺"鏃�**锛屽彲鍙傝�� NIC MSR 鐨勫姩鎬佹暟鎹簮杩炴帴绠$悊銆�
+6. **鎶�鏈�虹櫥璁�**锛歏ue2/SB2 鍗囩骇銆佷唬鐮佺敓鎴愬櫒銆佹搷浣滄棩蹇楁敞瑙e寲锛屽垪鍏ヨ繙鏈熸竻鍗曪紝涓嶉樆濉炲綋鍓嶆墿灞曘��
+
+## 浜斻�佹绱㈣褰曪紙澶囨煡锛�
+
+- GitHub 涓枃鍏抽敭璇嶏細缁熻鎶ヨ〃绯荤粺/鎶ヨ〃瀹℃牳/鏁版嵁瀹℃牳骞冲彴/鎶ヨ〃绠$悊 Spring Boot 鈫� 鏃犵洿鎺ュ悓绫�
+- GitHub 鑻辨枃鍏抽敭璇嶏細data quality validation / report generation excel spring boot / data validation excel import / statistical reporting 鈫� 鏃犵洿鎺ュ悓绫�
+- Gitee API锛氭湭璁よ瘉杩斿洖绌猴紝寰呰ˉ锛堝彲娴忚鍣ㄨ闂� gitee.com 鎼滅储"缁熻鎶ヨ〃/浜ら�氱粺璁�"浜哄伐鏍稿锛�
diff --git "a/docs/\346\211\251\345\261\225\346\250\241\345\235\227\350\257\204\344\274\260.md" "b/docs/\346\211\251\345\261\225\346\250\241\345\235\227\350\257\204\344\274\260.md"
new file mode 100644
index 0000000..8bf4a7f
--- /dev/null
+++ "b/docs/\346\211\251\345\261\225\346\250\241\345\235\227\350\257\204\344\274\260.md"
@@ -0,0 +1,117 @@
+# 鎵╁睍妯″潡璇勪及锛堝叕璺梾瀹� / 鑳借�� / 鍩庡競瀹㈣繍 / 鎶曡祫锛�
+
+> 鏇存柊鏃ユ湡锛�2026-08-22
+> 渚濇嵁锛歚docs/鎵╁睍琛ㄦā鏉�/` 涓嬮渶姹傛枃妗d笌妯℃澘瀹炴媶锛堝叕璺杩愮畝鍖�.docx銆佸煄甯傚杩愭暟鎹紭鍖�.docx銆佸叕璺梾瀹㈣繍杈撴湀搴︾敓浜ф儏鍐�.xlsx銆侀亾璺揣杩愯溅杈嗚兘婧愭秷鑰楁儏鍐碉紙2026骞达級.xlsx銆佸煄甯傚杩愭暟鎹ā鏉�.xlsx銆佹姇璧� 杈撳叆/杈撳嚭 鏍蜂緥锛夈��
+
+## 涓�銆佹�昏锛氭ā鍧� 脳 鐜版湁妗嗘灦澶嶇敤搴�
+
+| 妯″潡 | 瀵瑰簲鍒跺害琛� | 瀵煎叆 | 瀹℃牳 | 鎶ヨ〃 | 妗嗘灦澶嶇敤搴� | 寤鸿鎺掓湡 |
+|------|-----------|------|------|------|-----------|---------|
+| 鍏矾鏃呭杩愯緭 | 浜や紒缁烪203-1 | 楂� | 楂� | 楂� | 楂橈紙鍑犱箮鍏ㄥ鐢級 | 1 |
+| 閬撹矾璐ц繍鑳借�� | 浜や紒缁烪204锛堝鎶ワ級 | 涓� | 涓紙闇�璺ㄨ〃寮曠敤锛� | 楂� | 涓珮 | 2 |
+| 鍩庡競瀹㈣繍锛堝叕浜�/鍑虹/缃戠害锛� | 浜や紒缁烾203绛� | 涓紙渚濊禆澶栭儴鏁版嵁婧愶級 | 浣庯紙瑙勫垯绫诲瀷鏂帮級 | 涓� | 涓� | 3 |
+| 鎶曡祫锛堝杩愮珯鍦�/鐗╂祦鍥尯锛� | 閮ㄩ棬缁熻锛堣〃1-1/1-2绛夛級 | 浣庯紙鏍煎紡涓嶇粺涓�锛� | 浣庯紙瑙勫垯灏戯級 | 涓� | 浣庯紙鐙珛寤烘ā锛� | 4 |
+
+## 浜屻�佸叕璺梾瀹㈣繍杈擄紙H203-1锛夆�斺�� 寤鸿鏈�鍏堝仛
+
+### 鏁版嵁缁撴瀯锛堝疄鎷� `鍏矾鏃呭杩愯緭鏈堝害鐢熶骇鎯呭喌.xlsx`锛�
+- `鍏矾鏃呭6鏈堟姤`锛氫紒涓氭湀鎶ユ槑缁嗭紝28 鍒楋紝绾� 768 瀹朵紒涓氥�傚瓧娈典笌璐ц繍 H203-2 楂樺害鍚屾瀯锛�
+  - 浼佷笟缁村害锛氭墍灞炲湴鍖恒�佷紒涓氫唬鐮併�佷紒涓氬悕绉�
+  - 杞﹁締/杞藉浣嶏細鎬昏銆佸杩愮彮杞︼紙鍚畾绾挎梾娓革級銆佸杩愬寘杞︼紙鍚潪瀹氱嚎鏃呮父锛�
+  - 瀹㈣繍閲�/鏃呭鍛ㄨ浆閲忥細鎬昏銆佷竴绫�/浜岀被/涓夌被/鍥涚被鐝嚎銆佸寘杞�
+  - 骞冲潎杩愯窛锛氭�昏銆佷竴~鍥涚被銆佸寘杞�
+- `杩愭斂杞﹁締鏁癭锛�4 鍒楋紙浼佷笟浠g爜銆佷紒涓氬悕绉般�佽溅杈嗘暟銆佽浇瀹綅鏁帮級鈥斺�旇繍鏀垮姣旀暟鎹簮锛堝悓鏂囦欢鍐� sheet 鎴栨柊瀵煎叆绫诲瀷锛�
+- `鍏矾鏃呭鍒嗗競宸炴槑缁哷锛氱敓鎴愯〃锛�284 鍒楋細12 涓湀脳鍙e緞脳鍚屾瘮+绱锛夛紝鍙e緞鍚�诲杩愰噺銆佸叕璺彮绾裤�佸煄闄呭煄涔″叕浜ゃ�佸贰娓稿嚭绉熴�佺綉绾﹁溅
+- `涓彛寰勮绠楄繃绋媊锛氭湀搴︽眹鎬昏绠楄〃锛堟�诲杩愰噺=鐝嚎+鍩庨檯鍏氦+宸℃父鍑虹+缃戠害杞︼級
+
+### 瀹℃牳瑙勫垯锛堥渶姹傚師鏂囷紝4 缁勶級
+1. 杞﹁締鏁�/杞藉浣嶆暟涓庤繍鏀垮姣旓細杞﹁締宸窛 卤3 鍙般�佽浇瀹綅宸窛 卤50 鈫� 鏍稿疄
+2. 瀹㈣繍閲�/鏃呭鍛ㄨ浆閲忥紙涓�~鍥涚被銆佸寘杞︼級鐜瘮涓婃湀 = 0 鈫� 鏍稿疄
+3. 骞冲潎杩愯窛锛堜竴~鍥涚被锛夌幆姣� 卤10%銆佸寘杞﹀钩鍧囪繍璺� = 涓婃湀 鈫� 鏍稿疄
+4. 鏃犵彮绾垮嵈鏈夌彮绾垮杩愰噺銆佹棤鍖呰溅鍗存湁鍖呰溅瀹㈣繍閲� 鈫� 鏍稿疄
+
+### 澶嶇敤鐐�
+- 瀵煎叆锛氭寜浼佷笟閫愯瑙f瀽锛屽畬鍏ㄥ鐢� H2032 瀵煎叆妯″紡锛堟柊澧炶В鏋愬櫒+鐩爣琛ㄥ嵆鍙級
+- 瀹℃牳锛欴IFF/RATIO/MOM 宸叉湁锛涢渶鏂板 `鐜瘮=0 鏍稿疄`锛圡OM_EQ_ZERO锛変笌 `鏃犺溅鏈夐噺鐭涚浘`锛圗XISTENCE锛変袱绉嶆瘮杈冪被鍨�
+- 鎶ヨ〃锛氬垎甯傚窞鏄庣粏+鍚屾瘮+绱锛屽鐢ㄨ揣杩愭姤琛ㄦā寮�
+
+## 涓夈�侀亾璺揣杩愯兘鑰楋紙H204 瀛f姤锛夆�斺�� 闇�鎵╁睍瀹℃牳寮曟搸
+
+### 鏁版嵁缁撴瀯锛堝疄鎷� `閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙2026骞达級.xlsx`锛�
+- 杞﹁締瀛f姤鏄庣粏锛�**姣忚涓�杈嗚溅**锛堜笉鏄瘡琛屼竴瀹朵紒涓氾級锛岀害 18 鍒楋細鎵�灞炲湴鍖恒�佷紒涓氫唬鐮併�佷紒涓氬悕绉般�佽溅鐗屽彿銆佽溅杈嗙被鍨嬨�佺噧鏂欑被鍨嬨�佹爣璁板惃浣嶃�佹�昏绋嬨�佽浇璐ч噷绋嬨�佺┖椹堕噷绋嬨�佽揣杩愰噺銆佽揣鐗╁懆杞噺銆佸钩鍧囪繍璺濄�佺櫨鍏噷鍗曡�椼�佸懆杞噺鍗曡�楃瓑
+- `杩愭斂杞﹁締淇℃伅`锛�5 鍒楋紙杞︾墝鍙枫�佽溅杈嗙被鍨嬨�佺噧鏂欑被鍨嬨�佹爣璁板惃浣嶃�佸噯鐗靛紩璐ㄩ噺锛�
+- `鑳借繍姹囨�昏〃`锛氱敓鎴愯〃锛�13 鍒楋紝鎸夌噧鏂欑被鍨嬶紙鏌存补/姹芥补/LNG/CNG/绾數/鐕冩枡鐢垫睜锛夋眹鎬�
+
+### 瀹℃牳瑙勫垯锛堥渶姹傚師鏂囷紝7 缁勶級
+1. 杞﹁締绫诲瀷/鐕冩枡绫诲瀷/鏍囪鍚ㄤ綅涓庤繍鏀垮姣旓紝涓嶄竴鑷� 鈫� 鏍稿疄
+2. 杞借揣閲岀▼/鎬昏绋� 涓� 骞冲潎杩愯窛/鍗曡稛骞冲潎閲岀▼ 鐩稿樊 卤10% 鈫� 鏍稿疄
+3. 鍚勭噧鏂欑被鍨嬬櫨鍏噷鍗曡�楄秴鑼冨洿 鈫� 鏍稿疄
+4. 鍚屼竴杈嗚溅鐧惧叕閲屽崟鑰楃幆姣� 卤50% 鈫� 鏍稿疄
+5. 鍚屼竴浼佷笟杞﹁締璐ц繍閲�/鍛ㄨ浆閲忎箣鍜� > 璐ц繍鏈堟姤 50% 鈫� 鏍稿疄锛�**璺ㄨ〃寮曠敤璐ц繍 H203-2**锛�
+6. 杞﹁締骞冲潎杩愯窛 涓� 璐ц繍鏈堟姤骞冲潎杩愯窛 鐩稿樊 卤60% 鈫� 鏍稿疄锛�**璺ㄨ〃寮曠敤**锛�
+7. 骞冲潎杞借揣鍚ㄤ綅/鏍囪鍚ㄤ綅 灏忎簬 0.5 鎴栧ぇ浜� 1 鈫� 鏍稿疄
+
+### 澶嶇敤鐐�
+- 鏁版嵁妯″瀷鏄�"杞﹁締"绮掑害锛岄渶鏂板缓 `h204_vehicle_quarterly` 琛紝浣嗗鍏�/瀹℃牳/鎶ヨ〃閾捐矾鍚屾鏋�
+- 瀹℃牳寮曟搸闇�鏂板**璺ㄨ〃寮曠敤**姣旇緝锛圕ROSS_TABLE锛夛細瑙勫垯鑳藉紩鐢ㄥ彟涓�寮犳姤琛ㄧ殑鏁版嵁鍋氬姣�
+
+## 鍥涖�佸煄甯傚杩愶紙鍏氦 / 宸℃父鍑虹 / 缃戠害杞︼級鈥斺�� 瑙勫垯鏈�澶嶆潅
+
+### 鏁版嵁缁撴瀯锛堝疄鎷� `鍩庡競瀹㈣繍鏁版嵁妯℃澘.xlsx`锛�
+- `鍏矾鎬诲杩恅銆乣鍩庡競瀹㈣繍`锛氬潎涓哄競宸炵骇鐢熸垚琛紙274 鍒� / 69 鍒楋細鍚勬湀瀹㈣繍閲�/鍛ㄨ浆閲� + 鍚屾瘮 + 绱锛夛紝妯℃澘鍐呭叕寮忓紩鐢ㄥ閮ㄦ暟鎹紙澶氬 #REF!锛岄渶閲嶆柊鎺ユ暟鎹簮锛�
+- 缃戠害杞︽媶鍒嗭細瀹㈣繍閲�/璁㈠崟鎬绘暟鐢遍儴绾т笅鍙戯紝宸℃父鍑虹鍦ㄧ郴缁熷唴鍙煡锛岄渶鎸夊叕寮忔媶鍒嗗埌鍚勫競宸烇紙Excel 鍐呮湁鎷嗗垎鍏紡锛岄渶閫愭牸鏍稿锛�
+
+### 瀹℃牳瑙勫垯锛堥渶姹傚師鏂囷級
+- 鍏氦 4 缁勶細鍩庨檯鍩庝埂涓庡煄甯傚唴瓒嬪娍涓�鑷达紙涓嶄竴鑷翠笖鐜瘮澧為��>2% 鏍稿疄锛夛紱瀹㈣繍閲忎笌鍛ㄨ浆閲忚秼鍔夸竴鑷达紱鍩庡競鍐呭崰姣斿彉鍖�>1% 鏍稿疄锛涘钩鍧囪繍璺濆彉鍖�>3 鍏噷鏍稿疄
+- 宸℃父鍑虹 7 缁勶細杩愯惀杞︽暟涓庤繍鏀垮姣� >3%锛涜秼鍔夸竴鑷达紱瀹㈣繍閲�/鍛ㄨ浆閲忚秼鍔夸竴鑷达紱鍗犳瘮鍙樺寲>1%锛涘钩鍧囪繍璺濆彉鍖�>3 鍏噷锛涘崟杞︽杞藉绯绘暟鍙樺寲>0.2锛涜浇瀹㈣溅娆′笌瀹㈣繍閲忚秼鍔夸竴鑷�
+- 璁$畻鍏紡锛氬钩鍧囪繍璺�=鏃呭鍛ㄨ浆閲�/瀹㈣繍閲忥紱鍗曡溅娆¤浇瀹汉鏁�=瀹㈣繍閲�/杞藉杞︽鎬绘暟
+
+### 澶嶇敤鐐�
+- 瀹℃牳寮曟搸闇�鏂板锛氳秼鍔夸竴鑷存�э紙TREND锛夈�佸崰姣斿彉鍖栵紙RATIO_CHANGE锛夌瓑姣旇緝绫诲瀷
+- 鏁版嵁鏉ユ簮涓嶆 Excel锛氶儴绾т笅鍙戠綉绾﹁溅鏁般�佽繍鏀胯溅杈嗭紙浠呭贰娓稿嚭绉燂級鈫� 闇�瑕佸鏁版嵁婧愬鍏ヨ璁�
+- 寤鸿鎺掔涓夛紙瑙勫垯寮曟搸鎵╁睍鎴愭湰楂橈紝涓斾緷璧栧閮ㄦ暟鎹彛寰勭‘璁わ級
+
+## 浜斻�佹姇璧勬ā鍧楋紙瀹㈣繍绔欏満 / 鐗╂祦鍥尯锛夆�斺�� 鐙珛寤烘ā
+
+### 鏁版嵁缁撴瀯锛堝疄鎷� `鎶曡祫/` 鐩綍锛�
+- 杈撳叆锛�17 甯傚窞 脳 2 绫伙紙瀹㈣繍绔欏満銆佺墿娴佸洯鍖猴級脳 姣忔湀涓婃姤琛紙琛�1-1 椤圭洰鏄庣粏銆佽〃1-2 寤鸿瑙勬ā/鐢熶骇鑳藉姏锛夛紝鏍蜂緥涓� 2026 骞� 6/7 鏈�
+  - **鏍煎紡涓嶇粺涓�**锛氬垪鏁� 20~26 涓嶇瓑銆佹爣棰樿浣嶇疆涓嶅悓銆佹枃浠剁被鍨嬫贩瀛橈紙.xls/.xlsx/.et/.pdf锛夛紝甯傚窞鑷濉姤
+- 杈撳嚭锛�4 寮犲浐瀹氭牸寮忔眹鎬昏〃锛堢粰棰嗗锛夛細
+  1. 2026骞村叏鐪侀瀹夋帓璁″垝杩涘害鎯呭喌姹囨�伙紙椤圭洰杩涘害銆�1-N 鏈堢疮璁°�佸疄闄呰繘搴︺�佺渷琛ヨ祫閲戯級
+  2. "鍗佷簲浜�"瑙勫垝绾冲叆鐪佸彂鏀瑰閲嶇偣椤圭洰娓呭崟鐗╂祦椤圭洰杩涘睍鎯呭喌锛堣〃1-2+鍒嗛」鐩姇璧勫畬鎴愭儏鍐碉級
+  3. 婀栧寳鐪侊紙瀹㈣揣绔欏満锛夛紙浜垮厓鎶曡祫椤圭洰锛夛細鎶曡祫鎬昏〃锛堝搴�/骞村害瀹屾垚锛�+ 瑙勪笂椤圭洰琛紙24 鍒楋級
+  4. 锛堢粡娴庡己鍘块」鐩級婀栧寳鐪佷氦閫氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃锛氬叏鐪佽揣杩愶紙鐗╂祦锛夊熀纭�璁炬柦鎶曡祫鏈堟姤锛�17 鍒楋級
+- 鍙︽湁 `鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7).xls`锛�8 鍒楋細鍗曚綅銆佹椂鏈熴�佽鍒掓�绘姇璧勩�佺疮璁″畬鎴愩�佹湰骞磋鍒掋�佽嚜骞村垵绱銆佸綋鏈堝畬鎴愶級鈥斺��**宸叉湁鎶曡祫绯荤粺鏁版嵁婧愬彲瀵规帴**
+
+### 澶嶇敤鐐�
+- 鏁版嵁妯″瀷鏄�"椤圭洰"缁村害锛堥」鐩悕绉般�佹�绘姇璧勩�佺疮璁″畬鎴愩�佹湰骞磋鍒掋�佹湰鏈堝畬鎴愩�佽繘搴︺�佸缓绛戦潰绉級锛屼笌浼佷笟/鎸囨爣缁村害涓嶅悓 鈫� 鏂板缓 `investment` 妯″潡
+- 瀵煎叆锛氬悇甯傚窞鏍煎紡瀹归敊瑙f瀽宸ヤ綔閲忔渶澶э紙寤鸿鍚屾椂鎻愪緵鍥哄畾妯℃澘+鑷姩璇嗗埆涓ょ璺緞锛屾垨鐩存帴瀵规帴鎶曡祫绯荤粺锛�
+- 瀹℃牳锛氳鍒欏皯锛堢己鏁般�佽繘搴﹀紓甯搞�佷笌绯荤粺鏁版瘮瀵逛负涓伙級
+- 鎶ヨ〃锛�4 寮犲浐瀹氭牸寮忔ā鏉匡紝澶嶇敤鎶ヨ〃瀵煎嚭妗嗘灦
+- 鏉冮檺/鎿嶄綔鏃ュ織/LLM/AI 瑙i噴搴曞骇鍙洿鎺ュ鐢�
+
+## 鍏�佸疄鏂介『搴忎笌鏋舵瀯寤鸿
+
+### 椤哄簭锛堟寜"蹇�熻鏁� + 閫愭琛ヨ兘鍔�"锛�
+1. **鍏矾鏃呭 H203-1**锛氶獙璇�"涓�濂楁鏋跺寮犺〃"锛屾垚鏈渶浣�
+2. **鑳借�� H204**锛氳ˉ榻愬鏍稿紩鎿�"璺ㄨ〃寮曠敤"鑳藉姏
+3. **鍩庡競瀹㈣繍**锛氳ˉ榻�"瓒嬪娍/鍗犳瘮"澶嶆潅瑙勫垯锛岀‘璁ょ綉绾﹁溅鏁版嵁鏉ユ簮
+4. **鎶曡祫**锛氱嫭绔嬫ā鍧楋紝鍏堝喅绛�"瀵规帴鎶曡祫绯荤粺 vs 瑙f瀽甯傚窞鎶ヨ〃"
+
+### 鏋舵瀯寤鸿锛堥伩鍏嶅爢浠g爜锛�
+1. 鎶ヨ〃绫诲瀷娉ㄥ唽琛ㄥ寲锛歚report_type`锛堢幇鏈� `audit_rule.report_type` 宸查鐣欙級鈫� 姣忕鎶ヨ〃 = 瀵煎叆瑙f瀽鍣� + 鐩爣琛� + 瑙勫垯闆� + 鎶ヨ〃妯℃澘锛岄�氳繃娉ㄥ唽琛ㄦ寕杞�
+2. 瀹℃牳寮曟搸鏂板姣旇緝绫诲瀷锛堝彲閰嶇疆銆佽鍒欒〃椹卞姩锛屼笉鍐欐锛夛細
+   - `MOM_EQ_ZERO`锛堢幆姣�=0 鏍稿疄锛夈�乣EXISTENCE`锛堟棤杞︽湁閲忕煕鐩撅級
+   - `CROSS_TABLE`锛堣法琛ㄥ紩鐢ㄥ姣旓級銆乣TREND`锛堣秼鍔夸竴鑷存�э級銆乣RATIO_CHANGE`锛堝崰姣斿彉鍖栵級
+3. 澶氭暟鎹簮瀵煎叆锛欵xcel 鏂囦欢 + 澶栭儴绯荤粺鏌ヨ瀵煎嚭锛堝鎶曡祫绯荤粺锛�+ 閮ㄧ骇涓嬪彂鏁版嵁
+4. AI 瑙i噴/鑴辨晱/鏉冮檺鍏ㄩ儴鎸夌幇鏈� `llmintegration`銆乣security` 澶嶇敤
+
+### 宸ヤ綔閲忕矖浼帮紙鍗曚汉锛�
+| 妯″潡 | 鍚庣 | 鍓嶇 | 鍚堣锛堜汉澶╋級 |
+|------|------|------|------------|
+| 鍏矾鏃呭 H203-1 | 3~5 | 1~2 | 4~7 |
+| 鑳借�� H204 | 4~6 | 1~2 | 5~8 |
+| 鍩庡競瀹㈣繍 | 6~10 | 2~3 | 8~13 |
+| 鎶曡祫 | 8~12 | 2~3 | 10~15 |
+
+> 涓嶅惈闇�姹傜‘璁ゃ�佹暟鎹彛寰勬牳瀹炪�佽仈璋冩祴璇曟椂闂淬��
diff --git "a/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\344\272\277\345\205\203\346\212\225\350\265\204\351\241\271\347\233\256.xls" "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\344\272\277\345\205\203\346\212\225\350\265\204\351\241\271\347\233\256.xls"
new file mode 100644
index 0000000..57e14ac
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\344\272\277\345\205\203\346\212\225\350\265\204\351\241\271\347\233\256.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\345\205\250\347\234\201\351\242\204\345\256\211\346\216\222\350\256\241\345\210\222\350\277\233\345\272\246\346\203\205\345\206\265\346\261\207\346\200\273.xls" "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\345\205\250\347\234\201\351\242\204\345\256\211\346\216\222\350\256\241\345\210\222\350\277\233\345\272\246\346\203\205\345\206\265\346\261\207\346\200\273.xls"
new file mode 100644
index 0000000..3ab1898
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\345\205\250\347\234\201\351\242\204\345\256\211\346\216\222\350\256\241\345\210\222\350\277\233\345\272\246\346\203\205\345\206\265\346\261\207\346\200\273.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\345\215\201\344\272\224\344\272\224\350\247\204\345\210\222\347\211\251\346\265\201\351\241\271\347\233\256\350\277\233\345\261\225\346\203\205\345\206\265.xls" "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\345\215\201\344\272\224\344\272\224\350\247\204\345\210\222\347\211\251\346\265\201\351\241\271\347\233\256\350\277\233\345\261\225\346\203\205\345\206\265.xls"
new file mode 100644
index 0000000..34cddbb
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\345\215\201\344\272\224\344\272\224\350\247\204\345\210\222\347\211\251\346\265\201\351\241\271\347\233\256\350\277\233\345\261\225\346\203\205\345\206\265.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\347\273\217\346\265\216\345\274\272\345\216\277\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250.xls" "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\347\273\217\346\265\216\345\274\272\345\216\277\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250.xls"
new file mode 100644
index 0000000..88a176f
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277/\346\250\241\346\235\277_\347\273\217\346\265\216\345\274\272\345\216\277\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\346\250\241\346\235\277_\346\237\245\350\257\242\347\273\223\346\236\234\050\346\212\225\350\265\204\347\263\273\347\273\237-\345\256\242\350\277\220+\347\211\251\346\265\201 2026.7\051.xls" "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277_\346\237\245\350\257\242\347\273\223\346\236\234\050\346\212\225\350\265\204\347\263\273\347\273\237-\345\256\242\350\277\220+\347\211\251\346\265\201 2026.7\051.xls"
new file mode 100644
index 0000000..8333dd9
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\346\250\241\346\235\277_\346\237\245\350\257\242\347\273\223\346\236\234\050\346\212\225\350\265\204\347\263\273\347\273\237-\345\256\242\350\277\220+\347\211\251\346\265\201 2026.7\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/20260623095853.pdf" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/20260623095853.pdf"
new file mode 100644
index 0000000..d71a327
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/20260623095853.pdf"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\345\222\270\345\256\201\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250\0501\051\347\234\201\350\241\250.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\345\222\270\345\256\201\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250\0501\051\347\234\201\350\241\250.xls"
new file mode 100644
index 0000000..f6258bb
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\345\222\270\345\256\201\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250\0501\051\347\234\201\350\241\250.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\346\236\227\345\214\272\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\211.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\346\236\227\345\214\272\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\211.xls"
new file mode 100644
index 0000000..3b8ba8e
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\346\236\227\345\214\272\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\211.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\345\271\264\345\233\272\346\212\225\346\234\210\346\212\245\357\274\210\346\261\211\345\217\243\343\200\201\345\215\227\345\261\261\357\274\211.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\345\271\264\345\233\272\346\212\225\346\234\210\346\212\245\357\274\210\346\261\211\345\217\243\343\200\201\345\215\227\345\261\261\357\274\211.xlsx"
new file mode 100644
index 0000000..705cc45
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\345\271\264\345\233\272\346\212\225\346\234\210\346\212\245\357\274\210\346\261\211\345\217\243\343\200\201\345\215\227\345\261\261\357\274\211.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\346\271\226\345\214\227\347\234\201\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\2455\346\234\210\050\346\201\251\346\226\275\345\267\236\051 \0501\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\346\271\226\345\214\227\347\234\201\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\2455\346\234\210\050\346\201\251\346\226\275\345\267\236\051 \0501\051.xls"
new file mode 100644
index 0000000..54ba0e2
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/2026\346\271\226\345\214\227\347\234\201\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\2455\346\234\210\050\346\201\251\346\226\275\345\267\236\051 \0501\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\346\271\226\345\214\227\347\234\201\345\256\234\346\230\214\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21106\346\234\210.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\346\271\226\345\214\227\347\234\201\345\256\234\346\230\214\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21106\346\234\210.xls"
new file mode 100644
index 0000000..a1f4619
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\346\271\226\345\214\227\347\234\201\345\256\234\346\230\214\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21106\346\234\210.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\350\215\206\351\227\250\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\2416\346\234\210.xls.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\350\215\206\351\227\250\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\2416\346\234\210.xls.xls"
new file mode 100644
index 0000000..16e5e19
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\350\215\206\351\227\250\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\2416\346\234\210.xls.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\350\245\204\351\230\263\345\270\202\351\201\223\350\267\257\350\277\220\350\276\223\346\234\215\345\212\241\344\270\255\345\277\2032026\345\271\2646\346\234\210\346\212\225\350\265\204.et" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\350\245\204\351\230\263\345\270\202\351\201\223\350\267\257\350\277\220\350\276\223\346\234\215\345\212\241\344\270\255\345\277\2032026\345\271\2646\346\234\210\346\212\225\350\265\204.et"
new file mode 100644
index 0000000..ca4c75e
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\350\245\204\351\230\263\345\270\202\351\201\223\350\267\257\350\277\220\350\276\223\346\234\215\345\212\241\344\270\255\345\277\2032026\345\271\2646\346\234\210\346\212\225\350\265\204.et"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\357\274\210\345\255\235\346\204\237\345\270\202\351\201\223\350\277\220\344\270\255\345\277\203\357\274\211202606\345\255\235\346\204\237\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250 \0501\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\357\274\210\345\255\235\346\204\237\345\270\202\351\201\223\350\277\220\344\270\255\345\277\203\357\274\211202606\345\255\235\346\204\237\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250 \0501\051.xls"
new file mode 100644
index 0000000..92e1458
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/6\346\234\210/\357\274\210\345\255\235\346\204\237\345\270\202\351\201\223\350\277\220\344\270\255\345\277\203\357\274\211202606\345\255\235\346\204\237\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250 \0501\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\345\222\270\345\256\201\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250\0501\051\347\234\201\350\241\250\0501\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\345\222\270\345\256\201\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250\0501\051\347\234\201\350\241\250\0501\051.xls"
new file mode 100644
index 0000000..6f05a23
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\345\222\270\345\256\201\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250\0501\051\347\234\201\350\241\250\0501\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\346\236\227\345\214\272\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\211.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\346\236\227\345\214\272\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\211.xls"
new file mode 100644
index 0000000..b614cca
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\346\236\227\345\214\272\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\211.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\347\234\201\347\253\231\345\234\272\346\212\225\350\265\204\350\241\250\357\274\210\351\273\204\345\206\210\357\274\211 -.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\347\234\201\347\253\231\345\234\272\346\212\225\350\265\204\350\241\250\357\274\210\351\273\204\345\206\210\357\274\211 -.xlsx"
new file mode 100644
index 0000000..c7a151e
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\347\234\201\347\253\231\345\234\272\346\212\225\350\265\204\350\241\250\357\274\210\351\273\204\345\206\210\357\274\211 -.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\347\253\231\345\234\272\345\233\272\346\212\225\357\274\210\345\215\227\345\261\261\343\200\201\346\261\211\345\217\243\347\253\231\357\274\211.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\347\253\231\345\234\272\345\233\272\346\212\225\357\274\210\345\215\227\345\261\261\343\200\201\346\261\211\345\217\243\347\253\231\357\274\211.xlsx"
new file mode 100644
index 0000000..a5575f3
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\347\253\231\345\234\272\345\233\272\346\212\225\357\274\210\345\215\227\345\261\261\343\200\201\346\261\211\345\217\243\347\253\231\357\274\211.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\346\271\226\345\214\227\347\234\201\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\2457\346\234\210\050\346\201\251\346\226\275\345\267\236\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\346\271\226\345\214\227\347\234\201\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\2457\346\234\210\050\346\201\251\346\226\275\345\267\236\051.xls"
new file mode 100644
index 0000000..b41d374
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/2026\346\271\226\345\214\227\347\234\201\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\212\2457\346\234\210\050\346\201\251\346\226\275\345\267\236\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\345\215\201\345\240\260\345\270\2022026\345\271\2647\346\234\210\345\233\272\345\256\232\350\265\204\344\272\247\346\234\210\346\212\245\347\224\265\345\255\220\347\211\210\346\212\225\350\265\204\350\241\250\346\240\274.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\345\215\201\345\240\260\345\270\2022026\345\271\2647\346\234\210\345\233\272\345\256\232\350\265\204\344\272\247\346\234\210\346\212\245\347\224\265\345\255\220\347\211\210\346\212\225\350\265\204\350\241\250\346\240\274.xlsx"
new file mode 100644
index 0000000..8a5d057
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\345\215\201\345\240\260\345\270\2022026\345\271\2647\346\234\210\345\233\272\345\256\232\350\265\204\344\272\247\346\234\210\346\212\245\347\224\265\345\255\220\347\211\210\346\212\225\350\265\204\350\241\250\346\240\274.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\346\271\226\345\214\227\347\234\201\345\256\234\346\230\214\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21107\346\234\210.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\346\271\226\345\214\227\347\234\201\345\256\234\346\230\214\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21107\346\234\210.xls"
new file mode 100644
index 0000000..86b7172
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\346\271\226\345\214\227\347\234\201\345\256\234\346\230\214\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21107\346\234\210.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\350\215\206\351\227\250\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\2417\346\234\210.xls.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\350\215\206\351\227\250\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\2417\346\234\210.xls.xls"
new file mode 100644
index 0000000..11881de
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\350\215\206\351\227\250\345\270\202\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\2417\346\234\210.xls.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\350\245\204\351\230\263\345\270\202\351\201\223\350\267\257\350\277\220\350\276\223\346\234\215\345\212\241\344\270\255\345\277\2032026\345\271\2646\346\234\210\346\212\225\350\265\204 \0501\051.et" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\350\245\204\351\230\263\345\270\202\351\201\223\350\267\257\350\277\220\350\276\223\346\234\215\345\212\241\344\270\255\345\277\2032026\345\271\2646\346\234\210\346\212\225\350\265\204 \0501\051.et"
new file mode 100644
index 0000000..51a9abe
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\350\245\204\351\230\263\345\270\202\351\201\223\350\267\257\350\277\220\350\276\223\346\234\215\345\212\241\344\270\255\345\277\2032026\345\271\2646\346\234\210\346\212\225\350\265\204 \0501\051.et"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\357\274\210\345\255\235\346\204\237\345\270\202\351\201\223\350\277\220\344\270\255\345\277\203\357\274\211202607\345\255\235\346\204\237\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\357\274\210\345\255\235\346\204\237\345\270\202\351\201\223\350\277\220\344\270\255\345\277\203\357\274\211202607\345\255\235\346\204\237\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250.xls"
new file mode 100644
index 0000000..68db00d
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/7\346\234\210/\357\274\210\345\255\235\346\204\237\345\270\202\351\201\223\350\277\220\344\270\255\345\277\203\357\274\211202607\345\255\235\346\204\237\345\270\202\351\201\223\350\267\257\345\256\242\350\277\220\347\253\231\345\234\272\346\212\225\350\265\204\347\273\237\350\256\241\346\234\210\346\212\245\350\241\250.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/\346\233\264\346\226\260_6\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/\346\233\264\346\226\260_6\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250.xlsx"
new file mode 100644
index 0000000..f450236
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/\346\233\264\346\226\260_6\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/\346\233\264\346\226\260_7\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250\357\274\210\345\270\202\345\267\236\346\261\207\346\200\273\345\244\247\350\241\250\357\274\214\347\273\231\351\242\206\345\257\274\357\274\211.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/\346\233\264\346\226\260_7\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250\357\274\210\345\270\202\345\267\236\346\261\207\346\200\273\345\244\247\350\241\250\357\274\214\347\273\231\351\242\206\345\257\274\357\274\211.xlsx"
new file mode 100644
index 0000000..6ee9b06
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/\346\233\264\346\226\260_7\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250\357\274\210\345\270\202\345\267\236\346\261\207\346\200\273\345\244\247\350\241\250\357\274\214\347\273\231\351\242\206\345\257\274\357\274\211.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/\346\240\271\346\215\2566\346\234\210\344\270\203\346\234\210\346\226\207\344\273\266\345\244\271\351\207\214\351\235\242\347\232\204\346\225\260\346\215\256\346\233\264\346\226\260\343\200\212\346\233\264\346\226\260_6\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250\343\200\213\345\222\214\343\200\212\346\233\264\346\226\260_6\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250\343\200\213\344\270\244\345\274\240\345\244\247\350\241\250\351\207\214\351\235\242\347\232\204\346\225\260\346\215\256.txt" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/\346\240\271\346\215\2566\346\234\210\344\270\203\346\234\210\346\226\207\344\273\266\345\244\271\351\207\214\351\235\242\347\232\204\346\225\260\346\215\256\346\233\264\346\226\260\343\200\212\346\233\264\346\226\260_6\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250\343\200\213\345\222\214\343\200\212\346\233\264\346\226\260_6\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250\343\200\213\344\270\244\345\274\240\345\244\247\350\241\250\351\207\214\351\235\242\347\232\204\346\225\260\346\215\256.txt"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\345\256\242\350\277\220\357\274\210\345\256\242\350\277\220\347\253\231\357\274\211/\346\240\271\346\215\2566\346\234\210\344\270\203\346\234\210\346\226\207\344\273\266\345\244\271\351\207\214\351\235\242\347\232\204\346\225\260\346\215\256\346\233\264\346\226\260\343\200\212\346\233\264\346\226\260_6\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250\343\200\213\345\222\214\343\200\212\346\233\264\346\226\260_6\346\234\210\345\256\242\350\277\220\347\253\231\346\212\225\350\265\204\346\230\216\347\273\206\350\241\250\343\200\213\344\270\244\345\274\240\345\244\247\350\241\250\351\207\214\351\235\242\347\232\204\346\225\260\346\215\256.txt"
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/202606---\351\273\204\345\206\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\350\241\250\357\274\210\346\234\200\347\273\210\347\211\210\357\274\211 \0501\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/202606---\351\273\204\345\206\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\350\241\250\357\274\210\346\234\200\347\273\210\347\211\210\357\274\211 \0501\051.xls"
new file mode 100644
index 0000000..bc13883
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/202606---\351\273\204\345\206\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\350\241\250\357\274\210\346\234\200\347\273\210\347\211\210\357\274\211 \0501\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\344\273\275\345\255\235\346\204\237\347\211\251\346\265\201\351\241\271\347\233\256\346\212\225\350\265\204\347\273\237\350\256\241\350\241\250.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\344\273\275\345\255\235\346\204\237\347\211\251\346\265\201\351\241\271\347\233\256\346\212\225\350\265\204\347\273\237\350\256\241\350\241\250.xls"
new file mode 100644
index 0000000..2da3fbc
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\344\273\275\345\255\235\346\204\237\347\211\251\346\265\201\351\241\271\347\233\256\346\212\225\350\265\204\347\273\237\350\256\241\350\241\250.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\346\212\225\350\265\204\351\241\271\347\233\256\346\230\216\347\273\206\350\241\250\357\274\210\351\273\204\347\237\263\345\270\202\357\274\211\0501\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\346\212\225\350\265\204\351\241\271\347\233\256\346\230\216\347\273\206\350\241\250\357\274\210\351\273\204\347\237\263\345\270\202\357\274\211\0501\051.xls"
new file mode 100644
index 0000000..0814601
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\346\212\225\350\265\204\351\241\271\347\233\256\346\230\216\347\273\206\350\241\250\357\274\210\351\273\204\347\237\263\345\270\202\357\274\211\0501\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\346\201\251\346\226\275\345\267\236\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\346\201\251\346\226\275\345\267\236\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206.xls"
new file mode 100644
index 0000000..2ed5f49
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\346\201\251\346\226\275\345\267\236\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\346\271\226\345\214\227\347\234\201\350\245\204\351\230\263\345\270\202\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\0502\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\346\271\226\345\214\227\347\234\201\350\245\204\351\230\263\345\270\202\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\0502\051.xls"
new file mode 100644
index 0000000..aa27d18
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\346\271\226\345\214\227\347\234\201\350\245\204\351\230\263\345\270\202\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\0502\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\345\273\272\350\256\276\346\212\225\350\265\204\346\234\210\346\212\245.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\345\273\272\350\256\276\346\212\225\350\265\204\346\234\210\346\212\245.xls"
new file mode 100644
index 0000000..f1c25de
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\345\273\272\350\256\276\346\212\225\350\265\204\346\234\210\346\212\245.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\351\204\202\345\267\236\344\274\201\344\270\232\346\234\210\346\212\245\346\261\207\346\200\273.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\351\204\202\345\267\236\344\274\201\344\270\232\346\234\210\346\212\245\346\261\207\346\200\273.xls"
new file mode 100644
index 0000000..f58ba39
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/2026\345\271\2646\346\234\210\351\204\202\345\267\236\344\274\201\344\270\232\346\234\210\346\212\245\346\261\207\346\200\273.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/6\346\234\210\345\244\251\351\227\250\344\273\275\347\211\251\346\265\201\345\233\255\344\270\255\345\277\203\345\273\272\350\256\276\351\241\271\347\233\256\0501\051.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/6\346\234\210\345\244\251\351\227\250\344\273\275\347\211\251\346\265\201\345\233\255\344\270\255\345\277\203\345\273\272\350\256\276\351\241\271\347\233\256\0501\051.xlsx"
new file mode 100644
index 0000000..d8356d1
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/6\346\234\210\345\244\251\351\227\250\344\273\275\347\211\251\346\265\201\345\233\255\344\270\255\345\277\203\345\273\272\350\256\276\351\241\271\347\233\256\0501\051.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\344\273\231\346\241\203\345\270\202\347\211\251\346\265\201\345\233\255\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210202606\357\274\211.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\344\273\231\346\241\203\345\270\202\347\211\251\346\265\201\345\233\255\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210202606\357\274\211.xls"
new file mode 100644
index 0000000..ea63f0b
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\344\273\231\346\241\203\345\270\202\347\211\251\346\265\201\345\233\255\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210202606\357\274\211.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\345\222\270\345\256\201\345\270\2022026\345\271\2646\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\345\222\270\345\256\201\345\270\2022026\345\271\2646\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206.xls"
new file mode 100644
index 0000000..400bb47
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\345\222\270\345\256\201\345\270\2022026\345\271\2646\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\212\245\350\241\250\342\200\224\346\255\246\346\261\211\345\205\255\346\234\210.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\212\245\350\241\250\342\200\224\346\255\246\346\261\211\345\205\255\346\234\210.xls"
new file mode 100644
index 0000000..c3a8e40
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\212\245\350\241\250\342\200\224\346\255\246\346\261\211\345\205\255\346\234\210.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\236\227\345\214\272\347\211\251\346\265\201\346\212\225\350\265\204\346\234\210\346\212\2452026.06.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\236\227\345\214\272\347\211\251\346\265\201\346\212\225\350\265\204\346\234\210\346\212\2452026.06.xlsx"
new file mode 100644
index 0000000..a193616
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\236\227\345\214\272\347\211\251\346\265\201\346\212\225\350\265\204\346\234\210\346\212\2452026.06.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\271\226\345\214\227\345\256\234\346\230\214\345\270\202\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21106\346\234\210.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\271\226\345\214\227\345\256\234\346\230\214\345\270\202\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21106\346\234\210.xls"
new file mode 100644
index 0000000..79735ad
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\271\226\345\214\227\345\256\234\346\230\214\345\270\202\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21106\346\234\210.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\271\226\345\214\227\347\234\201\345\215\201\345\240\260\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\261\207\346\200\273\350\241\250\357\274\210\346\234\210\346\212\245\357\274\211\357\274\2102026\345\271\2645\346\234\210\357\274\211 \0502\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\271\226\345\214\227\347\234\201\345\215\201\345\240\260\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\261\207\346\200\273\350\241\250\357\274\210\346\234\210\346\212\245\357\274\211\357\274\2102026\345\271\2645\346\234\210\357\274\211 \0502\051.xls"
new file mode 100644
index 0000000..cd83ec6
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\271\226\345\214\227\347\234\201\345\215\201\345\240\260\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\261\207\346\200\273\350\241\250\357\274\210\346\234\210\346\212\245\357\274\211\357\274\2102026\345\271\2645\346\234\210\357\274\211 \0502\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\275\234\346\261\2372026.06.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\275\234\346\261\2372026.06.xlsx"
new file mode 100644
index 0000000..2125a9a
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\346\275\234\346\261\2372026.06.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\350\215\206\345\267\236\347\211\251\346\265\201\346\212\225\350\265\204\357\274\2102026\345\271\2646\346\234\210\346\212\245\347\234\201\357\274\211 .xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\350\215\206\345\267\236\347\211\251\346\265\201\346\212\225\350\265\204\357\274\2102026\345\271\2646\346\234\210\346\212\245\347\234\201\357\274\211 .xls"
new file mode 100644
index 0000000..a2eb9c7
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\350\215\206\345\267\236\347\211\251\346\265\201\346\212\225\350\265\204\357\274\2102026\345\271\2646\346\234\210\346\212\245\347\234\201\357\274\211 .xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\350\215\206\351\227\2502026\345\271\26406\346\234\210\351\241\271\347\233\256\346\212\225\350\265\204\346\200\273\346\212\245\350\241\250\357\274\210\347\211\251\346\265\201\357\274\211 \0501\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\350\215\206\351\227\2502026\345\271\26406\346\234\210\351\241\271\347\233\256\346\212\225\350\265\204\346\200\273\346\212\245\350\241\250\357\274\210\347\211\251\346\265\201\357\274\211 \0501\051.xls"
new file mode 100644
index 0000000..74e5e6c
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/6\346\234\210/\350\215\206\351\227\2502026\345\271\26406\346\234\210\351\241\271\347\233\256\346\212\225\350\265\204\346\200\273\346\212\245\350\241\250\357\274\210\347\211\251\346\265\201\357\274\211 \0501\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/202607---\351\273\204\345\206\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\350\241\250\357\274\210\346\234\200\347\273\210\347\211\210\357\274\211.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/202607---\351\273\204\345\206\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\350\241\250\357\274\210\346\234\200\347\273\210\347\211\210\357\274\211.xls"
new file mode 100644
index 0000000..5b2e06a
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/202607---\351\273\204\345\206\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\350\241\250\357\274\210\346\234\200\347\273\210\347\211\210\357\274\211.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\344\273\275\345\255\235\346\204\237\347\211\251\346\265\201\351\241\271\347\233\256\346\212\225\350\265\204\347\273\237\350\256\241\350\241\250.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\344\273\275\345\255\235\346\204\237\347\211\251\346\265\201\351\241\271\347\233\256\346\212\225\350\265\204\347\273\237\350\256\241\350\241\250.xls"
new file mode 100644
index 0000000..7584c14
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\344\273\275\345\255\235\346\204\237\347\211\251\346\265\201\351\241\271\347\233\256\346\212\225\350\265\204\347\273\237\350\256\241\350\241\250.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\346\212\225\350\265\204\351\241\271\347\233\256\346\230\216\347\273\206\350\241\250\357\274\210\351\273\204\347\237\263\345\270\202\357\274\211 \0502\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\346\212\225\350\265\204\351\241\271\347\233\256\346\230\216\347\273\206\350\241\250\357\274\210\351\273\204\347\237\263\345\270\202\357\274\211 \0502\051.xls"
new file mode 100644
index 0000000..8c350fe
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\346\212\225\350\265\204\351\241\271\347\233\256\346\230\216\347\273\206\350\241\250\357\274\210\351\273\204\347\237\263\345\270\202\357\274\211 \0502\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\344\274\201\344\270\232\346\234\210\346\212\245\346\261\207\346\200\273\357\274\210\351\204\202\345\267\236\357\274\211.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\344\274\201\344\270\232\346\234\210\346\212\245\346\261\207\346\200\273\357\274\210\351\204\202\345\267\236\357\274\211.xls"
new file mode 100644
index 0000000..c4787ca
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\344\274\201\344\270\232\346\234\210\346\212\245\346\261\207\346\200\273\357\274\210\351\204\202\345\267\236\357\274\211.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\346\201\251\346\226\275\345\267\236\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206 .xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\346\201\251\346\226\275\345\267\236\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206 .xls"
new file mode 100644
index 0000000..a920da5
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\346\201\251\346\226\275\345\267\236\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206 .xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\346\271\226\345\214\227\347\234\201\350\245\204\351\230\263\345\270\202\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250 \0501\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\346\271\226\345\214\227\347\234\201\350\245\204\351\230\263\345\270\202\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250 \0501\051.xls"
new file mode 100644
index 0000000..400260b
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\346\271\226\345\214\227\347\234\201\350\245\204\351\230\263\345\270\202\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250 \0501\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\345\273\272\350\256\276\346\212\225\350\265\204\346\234\210\346\212\245\050\351\232\217\345\267\236\051 \0501\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\345\273\272\350\256\276\346\212\225\350\265\204\346\234\210\346\212\245\050\351\232\217\345\267\236\051 \0501\051.xls"
new file mode 100644
index 0000000..92ca576
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/2026\345\271\2647\346\234\210\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\345\273\272\350\256\276\346\212\225\350\265\204\346\234\210\346\212\245\050\351\232\217\345\267\236\051 \0501\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/7\346\234\210\344\273\275\347\211\251\346\265\201\345\233\255\344\270\255\345\277\203\345\273\272\350\256\276\351\241\271\347\233\256 \357\274\210\345\244\251\351\227\250\357\274\211.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/7\346\234\210\344\273\275\347\211\251\346\265\201\345\233\255\344\270\255\345\277\203\345\273\272\350\256\276\351\241\271\347\233\256 \357\274\210\345\244\251\351\227\250\357\274\211.xlsx"
new file mode 100644
index 0000000..afcc0c1
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/7\346\234\210\344\273\275\347\211\251\346\265\201\345\233\255\344\270\255\345\277\203\345\273\272\350\256\276\351\241\271\347\233\256 \357\274\210\345\244\251\351\227\250\357\274\211.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\344\273\231\346\241\203\345\270\202\347\211\251\346\265\201\345\233\255\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210202607\357\274\211.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\344\273\231\346\241\203\345\270\202\347\211\251\346\265\201\345\233\255\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210202607\357\274\211.xls"
new file mode 100644
index 0000000..4d8d26a
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\344\273\231\346\241\203\345\270\202\347\211\251\346\265\201\345\233\255\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210202607\357\274\211.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\345\222\270\345\256\201\345\270\2022026\345\271\2647\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206 \0501\051.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\345\222\270\345\256\201\345\270\2022026\345\271\2647\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206 \0501\051.xls"
new file mode 100644
index 0000000..9a4fdde
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\345\222\270\345\256\201\345\270\2022026\345\271\2647\346\234\210\344\273\275\347\211\251\346\265\201\357\274\210\350\264\247\350\277\220\357\274\211\351\241\271\347\233\256\346\212\225\350\265\204\346\230\216\347\273\206 \0501\051.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\212\245\350\241\250\342\200\224\346\255\246\346\261\211\344\270\203\346\234\210.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\212\245\350\241\250\342\200\224\346\255\246\346\261\211\344\270\203\346\234\210.xls"
new file mode 100644
index 0000000..bd2df6c
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\212\245\350\241\250\342\200\224\346\255\246\346\261\211\344\270\203\346\234\210.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\236\227\345\214\272\347\211\251\346\265\201\346\212\225\350\265\204\346\234\210\346\212\2452026.07.24.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\236\227\345\214\272\347\211\251\346\265\201\346\212\225\350\265\204\346\234\210\346\212\2452026.07.24.xlsx"
new file mode 100644
index 0000000..db8195f
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\236\227\345\214\272\347\211\251\346\265\201\346\212\225\350\265\204\346\234\210\346\212\2452026.07.24.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\271\226\345\214\227\345\256\234\346\230\214\345\270\202\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21107\346\234\210.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\271\226\345\214\227\345\256\234\346\230\214\345\270\202\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21107\346\234\210.xls"
new file mode 100644
index 0000000..b26d942
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\271\226\345\214\227\345\256\234\346\230\214\345\270\202\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\210\346\234\210\343\200\201\345\271\264\346\212\245\357\274\21107\346\234\210.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\271\226\345\214\227\347\234\201\345\215\201\345\240\260\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\261\207\346\200\273\350\241\250\357\274\210\346\234\210\346\212\245\357\274\211\357\274\2102026\345\271\2647\346\234\210\357\274\211.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\271\226\345\214\227\347\234\201\345\215\201\345\240\260\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\261\207\346\200\273\350\241\250\357\274\210\346\234\210\346\212\245\357\274\211\357\274\2102026\345\271\2647\346\234\210\357\274\211.xls"
new file mode 100644
index 0000000..37fbaf6
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\271\226\345\214\227\347\234\201\345\215\201\345\240\260\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\261\207\346\200\273\350\241\250\357\274\210\346\234\210\346\212\245\357\274\211\357\274\2102026\345\271\2647\346\234\210\357\274\211.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\275\234\346\261\2372026.07.xlsx" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\275\234\346\261\2372026.07.xlsx"
new file mode 100644
index 0000000..cb77d13
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\346\275\234\346\261\2372026.07.xlsx"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\350\215\206\345\267\236\347\211\251\346\265\201\346\212\225\350\265\204\357\274\2102026\345\271\2647\346\234\210\346\212\245\347\234\201\357\274\211 .xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\350\215\206\345\267\236\347\211\251\346\265\201\346\212\225\350\265\204\357\274\2102026\345\271\2647\346\234\210\346\212\245\347\234\201\357\274\211 .xls"
new file mode 100644
index 0000000..aa7d5fe
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\350\215\206\345\267\236\347\211\251\346\265\201\346\212\225\350\265\204\357\274\2102026\345\271\2647\346\234\210\346\212\245\347\234\201\357\274\211 .xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\350\215\206\351\227\2502026\345\271\26407\346\234\210\351\241\271\347\233\256\346\212\225\350\265\204\346\200\273\346\212\245\350\241\250\357\274\210\347\211\251\346\265\201\357\274\211.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\350\215\206\351\227\2502026\345\271\26407\346\234\210\351\241\271\347\233\256\346\212\225\350\265\204\346\200\273\346\212\245\350\241\250\357\274\210\347\211\251\346\265\201\357\274\211.xls"
new file mode 100644
index 0000000..833c9b9
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/7\346\234\210/\350\215\206\351\227\2502026\345\271\26407\346\234\210\351\241\271\347\233\256\346\212\225\350\265\204\346\200\273\346\212\245\350\241\250\357\274\210\347\211\251\346\265\201\357\274\211.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/\346\233\264\346\226\260_6\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/\346\233\264\346\226\260_6\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206.xls"
new file mode 100644
index 0000000..7366aea
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/\346\233\264\346\226\260_6\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/\346\233\264\346\226\260_7\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/\346\233\264\346\226\260_7\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206.xls"
new file mode 100644
index 0000000..8c467fc
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/\346\233\264\346\226\260_7\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/\346\240\271\346\215\2566\346\234\2107\346\234\210\346\226\207\344\273\266\345\244\271\351\207\214\351\235\242\347\232\204\346\225\260\346\215\256\345\210\206\345\210\253\346\233\264\346\226\260\343\200\2126\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206\343\200\213\345\222\214\343\200\2127\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206\343\200\213\351\207\214\351\235\242\347\232\204\351\241\271\347\233\256\345\222\214\346\225\260\346\215\256.txt" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/\346\240\271\346\215\2566\346\234\2107\346\234\210\346\226\207\344\273\266\345\244\271\351\207\214\351\235\242\347\232\204\346\225\260\346\215\256\345\210\206\345\210\253\346\233\264\346\226\260\343\200\2126\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206\343\200\213\345\222\214\343\200\2127\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206\343\200\213\351\207\214\351\235\242\347\232\204\351\241\271\347\233\256\345\222\214\346\225\260\346\215\256.txt"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245/\347\211\251\346\265\201\357\274\210\347\211\251\346\265\201\345\233\255\345\214\272\357\274\211/\346\240\271\346\215\2566\346\234\2107\346\234\210\346\226\207\344\273\266\345\244\271\351\207\214\351\235\242\347\232\204\346\225\260\346\215\256\345\210\206\345\210\253\346\233\264\346\226\260\343\200\2126\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206\343\200\213\345\222\214\343\200\2127\346\234\210\347\211\251\346\265\201\347\253\231\345\234\272\346\212\225\350\265\204\346\230\216\347\273\206\343\200\213\351\207\214\351\235\242\347\232\204\351\241\271\347\233\256\345\222\214\346\225\260\346\215\256.txt"
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\205\245\347\232\204\345\206\205\345\256\271\350\246\201\344\270\216\343\200\212\346\250\241\346\235\277_\346\237\245\350\257\242\347\273\223\346\236\234\050\346\212\225\350\265\204\347\263\273\347\273\237-\345\256\242\350\277\220+\347\211\251\346\265\201 2026.7\051\343\200\213\350\277\233\350\241\214\346\240\241\351\252\214\357\274\214\345\255\230\345\234\250\346\225\260\346\215\256\344\270\215\344\270\200\350\207\264\347\232\204\345\274\271\347\252\227\346\217\220\347\244\272.txt" "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245\347\232\204\345\206\205\345\256\271\350\246\201\344\270\216\343\200\212\346\250\241\346\235\277_\346\237\245\350\257\242\347\273\223\346\236\234\050\346\212\225\350\265\204\347\263\273\347\273\237-\345\256\242\350\277\220+\347\211\251\346\265\201 2026.7\051\343\200\213\350\277\233\350\241\214\346\240\241\351\252\214\357\274\214\345\255\230\345\234\250\346\225\260\346\215\256\344\270\215\344\270\200\350\207\264\347\232\204\345\274\271\347\252\227\346\217\220\347\244\272.txt"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\205\245\347\232\204\345\206\205\345\256\271\350\246\201\344\270\216\343\200\212\346\250\241\346\235\277_\346\237\245\350\257\242\347\273\223\346\236\234\050\346\212\225\350\265\204\347\263\273\347\273\237-\345\256\242\350\277\220+\347\211\251\346\265\201 2026.7\051\343\200\213\350\277\233\350\241\214\346\240\241\351\252\214\357\274\214\345\255\230\345\234\250\346\225\260\346\215\256\344\270\215\344\270\200\350\207\264\347\232\204\345\274\271\347\252\227\346\217\220\347\244\272.txt"
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/2026\345\271\264\345\205\250\347\234\201\351\242\204\345\256\211\346\216\222\350\256\241\345\210\222\350\277\233\345\272\246\346\203\205\345\206\265\346\261\207\346\200\273\357\274\2107\346\234\210\357\274\211.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/2026\345\271\264\345\205\250\347\234\201\351\242\204\345\256\211\346\216\222\350\256\241\345\210\222\350\277\233\345\272\246\346\203\205\345\206\265\346\261\207\346\200\273\357\274\2107\346\234\210\357\274\211.xls"
new file mode 100644
index 0000000..3ab1898
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/2026\345\271\264\345\205\250\347\234\201\351\242\204\345\256\211\346\216\222\350\256\241\345\210\222\350\277\233\345\272\246\346\203\205\345\206\265\346\261\207\346\200\273\357\274\2107\346\234\210\357\274\211.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\342\200\234\345\215\201\344\272\224\344\272\224\342\200\235\350\247\204\345\210\222\347\272\263\345\205\245\347\234\201\345\217\221\346\224\271\345\247\224\351\207\215\347\202\271\351\241\271\347\233\256\346\270\205\345\215\225\347\211\251\346\265\201\351\241\271\347\233\256\350\277\233\345\261\225\346\203\205\345\206\265\357\274\2107\346\234\210\357\274\2111.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\342\200\234\345\215\201\344\272\224\344\272\224\342\200\235\350\247\204\345\210\222\347\272\263\345\205\245\347\234\201\345\217\221\346\224\271\345\247\224\351\207\215\347\202\271\351\241\271\347\233\256\346\270\205\345\215\225\347\211\251\346\265\201\351\241\271\347\233\256\350\277\233\345\261\225\346\203\205\345\206\265\357\274\2107\346\234\210\357\274\2111.xls"
new file mode 100644
index 0000000..34cddbb
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\342\200\234\345\215\201\344\272\224\344\272\224\342\200\235\350\247\204\345\210\222\347\272\263\345\205\245\347\234\201\345\217\221\346\224\271\345\247\224\351\207\215\347\202\271\351\241\271\347\233\256\346\270\205\345\215\225\347\211\251\346\265\201\351\241\271\347\233\256\350\277\233\345\261\225\346\203\205\345\206\265\357\274\2107\346\234\210\357\274\2111.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\346\257\217\344\270\252\346\234\210\345\201\232\345\207\272\350\277\231\344\272\233\345\233\272\345\256\232\346\240\274\345\274\217\347\232\204\350\241\250\357\274\214\347\273\231\351\242\206\345\257\274\347\234\213.txt" "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\346\257\217\344\270\252\346\234\210\345\201\232\345\207\272\350\277\231\344\272\233\345\233\272\345\256\232\346\240\274\345\274\217\347\232\204\350\241\250\357\274\214\347\273\231\351\242\206\345\257\274\347\234\213.txt"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\346\257\217\344\270\252\346\234\210\345\201\232\345\207\272\350\277\231\344\272\233\345\233\272\345\256\232\346\240\274\345\274\217\347\232\204\350\241\250\357\274\214\347\273\231\351\242\206\345\257\274\347\234\213.txt"
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\346\271\226\345\214\227\347\234\201\357\274\210\345\256\242\350\264\247\347\253\231\345\234\272\357\274\211\357\274\210\344\272\277\345\205\203\346\212\225\350\265\204\351\241\271\347\233\256\357\274\2117\346\234\210.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\346\271\226\345\214\227\347\234\201\357\274\210\345\256\242\350\264\247\347\253\231\345\234\272\357\274\211\357\274\210\344\272\277\345\205\203\346\212\225\350\265\204\351\241\271\347\233\256\357\274\2117\346\234\210.xls"
new file mode 100644
index 0000000..eb3999a
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\346\271\226\345\214\227\347\234\201\357\274\210\345\256\242\350\264\247\347\253\231\345\234\272\357\274\211\357\274\210\344\272\277\345\205\203\346\212\225\350\265\204\351\241\271\347\233\256\357\274\2117\346\234\210.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\357\274\210\347\273\217\346\265\216\345\274\272\345\216\277\351\241\271\347\233\256\357\274\211\346\271\226\345\214\227\347\234\201\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\2107\346\234\210\346\234\210\346\212\245\357\274\211xls.xls" "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\357\274\210\347\273\217\346\265\216\345\274\272\345\216\277\351\241\271\347\233\256\357\274\211\346\271\226\345\214\227\347\234\201\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\2107\346\234\210\346\234\210\346\212\245\357\274\211xls.xls"
new file mode 100644
index 0000000..88a176f
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204/\350\276\223\345\207\272/\357\274\210\347\273\217\346\265\216\345\274\272\345\216\277\351\241\271\347\233\256\357\274\211\346\271\226\345\214\227\347\234\201\344\272\244\351\200\232\347\211\251\346\265\201\345\237\272\347\241\200\350\256\276\346\226\275\346\212\225\350\265\204\347\273\237\350\256\241\346\212\245\350\241\250\357\274\2107\346\234\210\346\234\210\346\212\245\357\274\211xls.xls"
Binary files differ
diff --git "a/docs/\346\212\225\350\265\204\346\250\241\345\235\227\345\256\236\346\226\275\346\226\271\346\241\210.md" "b/docs/\346\212\225\350\265\204\346\250\241\345\235\227\345\256\236\346\226\275\346\226\271\346\241\210.md"
new file mode 100644
index 0000000..ba1df1b
--- /dev/null
+++ "b/docs/\346\212\225\350\265\204\346\250\241\345\235\227\345\256\236\346\226\275\346\226\271\346\241\210.md"
@@ -0,0 +1,118 @@
+# 鎶曡祫妯″潡瀹炴柦鏂规锛堝杩愮珯鍦� / 鐗╂祦鍥尯锛�
+
+> 鍩轰簬 `docs/鎵╁睍琛ㄦā鏉�/鎶曡祫/` 鍏ㄩ儴鏍蜂緥瀹炴媶鏁寸悊锛�2026-08-23锛�
+> 缁撹鍏堣锛�**涓绘暟鎹簮璧�"鍏ㄧ渷姹囨�诲ぇ琛� + 甯傚窞涓婃姤琛ㄨВ鏋�"锛屾姇璧勭郴缁熷鍑轰粎浣滃鏍告瘮瀵�**锛涘缓璁垎涓ゆ湡钀藉湴锛岀涓�鏈熷厛璁� 4 寮犻瀵兼姤琛ㄨ窇閫氥��
+
+## 涓�銆佹暟鎹簮瀹炴媶缁撹
+
+### 1. 鎶曡祫绯荤粺鏌ヨ瀵煎嚭锛堟渶骞插噣锛屼絾瀛楁鏈�灏戯級
+`鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7).xls`锛�149 琛� 脳 8 鍒楋級锛�
+
+| 鍒� | 瀛楁 | 鏍蜂緥 |
+|----|------|------|
+| A | 琛屽彿 | 2 |
+| B | 鍗曚綅锛堝嵆椤圭洰鍚嶇О锛� | 姹夊彛瀹㈣繍涓績 |
+| C | 鏃舵湡 | 2026骞�7鏈� |
+| D | 璁″垝鎬绘姇璧� | 315700 |
+| E | 鑷紑濮嬪缓璁捐嚦鏈湀绱瀹屾垚鎶曡祫 | 177685.4 |
+| F | 鏈勾璁″垝鎶曡祫 | 30000 |
+| G | 鑷勾鍒濈疮璁″畬鎴愭姇璧� | 4000 |
+| H | 褰撴湀瀹屾垚鎶曡祫 | 500 |
+
+- 浼樼偣锛氱粨鏋勫浐瀹氥�佹棤闇�瀹归敊瑙f瀽銆�
+- 灞�闄愶細**鏃犲競宸炪�佹棤杩涘害/褰㈣薄杩涘害/鎵瑰鏂囧彿**锛屽彧鑳芥敮鎾� 4 寮犺緭鍑鸿〃閲岀殑鏁板�煎垪锛屼笉瓒充互鐙珛鐢熸垚棰嗗鎶ヨ〃銆�
+
+### 2. 甯傚窞涓婃姤琛紙鏍煎紡涓嶇粺涓�锛岄渶瀹归敊瑙f瀽锛�
+- 瀹㈣繍绔欏満鏍蜂緥锛氬疁鏄� 18 鍒椼�佹眹鎬诲ぇ琛� 20 鍒楋紱鐗╂祦鏍蜂緥锛氶粍鐭� 23 鍒椼�佹眹鎬昏〃 24 鍒椼��
+- 鍏叡瀛楁锛堝垪浣嶇疆鍥犺〃鑰屽紓锛岄』鎸夎〃澶村叧閿瘝鍖归厤锛夛細
+  搴忓彿 / 椤圭洰鍚嶇О鍙婄珯绾� / 鎶曡祫寤鸿鍗曚綅 / 寤鸿鎬ц川锛堟柊寤�/缁缓/鏀规墿寤猴級/ 寮�宸ユ椂闂� / 绔e伐鏃堕棿 / 璁″垝鎬绘姇璧� / 鑷紑濮嬪缓璁剧疮璁″畬鎴愭姇璧� / 鏈勾璁″垝鎶曡祫 / 鑷勾鍒濈疮璁″畬鎴愭姇璧� / 鍏朵腑鏈湀瀹屾垚鎶曡祫 / 寤鸿闃舵锛堝湪寤�/瀹屽伐/鍓嶆湡锛�/ 褰㈣薄杩涘害 / 宸ュ彲鎵瑰 / 鍒濊鎵瑰 / 寤虹瓚闈㈢Н / 鐢ㄥ伐浜烘暟銆�
+- 鍒嗙粍琛岋細`涓�銆佷娇鐢ㄤ氦閫氶儴杞﹁喘绋庤祫閲戝缓璁鹃」鐩甡銆乣浜屻�佷娇鐢ㄧ渷鐕冩补绋庤繑杩樿祫閲戝缓璁鹃」鐩甡銆乣涓夈�佸啘鏉戠患鍚堣繍杈撴湇鍔$珯/涓�鑸」鐩甡銆乣鍥涖�佸�欒溅浜�/鍐滄潙缁煎悎鏈嶅姟绔檂銆乣浜斻�佽鍒掑椤圭洰`锛涘競宸炲皬璁¤鍚�"灏忚/鍚堣"銆�
+- 鏂囦欢娣峰瓨锛歚.xls / .xlsx / .et / .pdf`锛屾爣棰樿浣嶇疆涓嶄竴锛堥儴鍒嗘棤鏍囬鐩存帴鏄〃澶达級銆�
+
+### 3. 鍏ㄧ渷姹囨�讳腑闂磋〃锛堟帹鑽愪竴鏈熶富鍏ュ彛锛�
+- `6鏈�/7鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx`锛氫袱 sheet锛堟槑缁� 121 琛� 脳 20 鍒� + 姹囨�伙級锛屽凡鏄叏鐪� 17 甯傚窞鍒嗙粍鍚堣缁撴瀯锛屾牸寮忕粺涓�銆�
+- `6鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls`锛�4 sheet锛堟湀2 寤鸿瑙勬ā琛� / 鍒嗛」鐩姇璧勫畬鎴愭儏鍐� / 鍒嗗競宸炶鍒掑唴澶栭」鐩畬鎴愭儏鍐� / 甯傚窞姹囨�伙級锛屾牸寮忕粺涓�銆�
+
+### 4. 杈撳嚭 4 寮犺〃锛堢粰棰嗗锛岄渶姹傝 `姣忎釜鏈堝仛鍑鸿繖浜涘浐瀹氭牸寮忕殑琛紝缁欓瀵肩湅.txt`锛�
+| # | 琛ㄥ悕 | 缁撴瀯 | 鍏抽敭鍙e緞 |
+|---|------|------|---------|
+| 1 | 2026骞村叏鐪侀瀹夋帓璁″垝杩涘害鎯呭喌姹囨�� | 28 鍒楋紝鍙屽眰琛ㄥご锛涘悎璁�/瀹㈣繍绔欏満/鐗╂祦 澶у潡 + 17 甯傚窞灏忚 + 椤圭洰鏄庣粏 | 搴忓彿/甯傚窞/鍘垮尯/椤圭洰/鎬ц川/寤鸿鏃堕棿/鎬绘姇璧�/绱瀹屾垚/2026骞磋鍒�(鍚堣路閮风渷路鑷路杩涘害)/2026路2027寤鸿璁″垝/鍓嶆湡宸ヤ綔/1-N鏈堢疮璁″畬鎴�/瀹為檯杩涘害/鐪佽ˉ璧勯噾/澶囨敞 |
+| 2 | "鍗佷簲浜�"瑙勫垝鐗╂祦椤圭洰杩涘睍 | 甯傚窞灏忚 + 椤圭洰鏄庣粏 | 搴忓彿/椤圭洰/浼佷笟/寮�宸�/瀹屽伐/鎬绘姇璧�/绱瀹屾垚/鏈湀瀹屾垚/杩涘害/杩涘睍鎻忚堪 |
+| 3 | 婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩� | 2 sheet锛氭姇璧勬�昏〃(6鍒楀搴﹀彛寰�) + 瑙勪笂椤圭洰琛�(24鍒�) | 瑙勪笂椤圭洰锛氱渷浠�/甯傚窞/椤圭洰/绫诲埆/寤鸿鍐呭/鐘舵��/鎬ц川/寮�宸�/鎬绘姇璧�/绱/鍚勬湀璁″垝涓庡畬鎴愶紱**绛涢�夆墺1浜垮厓椤圭洰** |
+| 4 | 锛堢粡娴庡己鍘块」鐩級浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻 | 17 鍒� | 搴忓彿/椤圭洰/浼佷笟/鎬绘姇璧�/绱瀹屾垚/鏈勾璁″垝/鑷勾鍒濈疮璁�/鏈湀瀹屾垚/杩涘害/杩涘睍/宸ュ彲鍒濊鎵瑰 |
+
+**娲炲療**锛�4 寮犺緭鍑鸿〃鏄悓涓�鎵归」鐩暟鎹殑 4 绉嶈瑙掞紙鍏ㄧ渷璁″垝鍏ㄦ櫙 / 鍗佷簲浜旂墿娴侀噸鐐� / 浜垮厓瑙勬ā浠ヤ笂 / 缁忔祹寮哄幙锛夛紝鎶戒竴濂楅」鐩富妗e嵆鍙鐢ㄣ��
+
+## 浜屻�佹暟鎹ā鍨嬶紙寤鸿 2 寮犺〃锛�
+
+### investment_project 椤圭洰涓绘。锛堟寜椤圭洰鍚�+绫诲瀷鍞竴锛�
+| 瀛楁 | 璇存槑 |
+|------|------|
+| id / project_name | 涓婚敭 / 椤圭洰鍚嶇О锛堝敮涓�閿細project_name + category锛� |
+| category | 瀹㈣繍绔欏満 / 鐗╂祦鍥尯 |
+| city / county | 甯傚窞 / 鍘匡紙甯傘�佸尯锛� |
+| construct_nature | 鏂板缓/缁缓/鏀规墿寤� |
+| start_year / end_year | 寮�宸� / 绔e伐鏃堕棿 |
+| total_investment | 璁″垝鎬绘姇璧勶紙涓囧厓锛宒ouble锛� |
+| approval_gk / approval_cs | 宸ュ彲 / 鍒濊鎵瑰鏂囧彿 |
+| planned_level | 鏄惁 2026 棰勫畨鎺掕鍒掑唴 / "鍗佷簲浜�"閲嶇偣 / 浜垮厓椤圭洰鏍囪 |
+| source | 鏁版嵁鏉ユ簮锛堝叏鐪佹眹鎬昏〃/甯傚窞鎶ヨ〃/绯荤粺瀵煎嚭锛� |
+
+### investment_monthly 鏈堝害瀹屾垚琛紙report_period + project_id锛�
+| 瀛楁 | 璇存槑 |
+|------|------|
+| report_period | 鎶ヨ〃鏈� 2026-07 |
+| project_id | 鍏宠仈涓绘。 |
+| year_plan | 鏈勾璁″垝鎶曡祫 |
+| year_cum | 鑷勾鍒濈疮璁″畬鎴愭姇璧� |
+| month_done | 褰撴湀瀹屾垚鎶曡祫 |
+| start_cum | 鑷紑濮嬪缓璁剧疮璁″畬鎴愭姇璧� |
+| progress_stage / progress_desc | 寤鸿闃舵 / 褰㈣薄杩涘害 |
+| building_area | 寤虹瓚闈㈢Н锛堝杩愯〃锛� |
+
+> 鎶ヨ〃鏈熶互椤甸潰閫夋嫨涓哄噯锛涢噾棰�/闈㈢Н缁熶竴 double 瀛樺偍锛屼笉鍋� BigDecimal銆�
+
+## 涓夈�佸鍏ヨ璁★紙涓ゆ湡锛�
+
+### 涓�鏈燂細鍏ㄧ渷姹囨�诲ぇ琛� + 绯荤粺瀵煎嚭锛堝揩閫熻窇閫� 4 寮犺〃锛�
+- 鏂板瀵煎叆绫诲瀷"鎶曡祫鏈堟姤"锛氭敮鎸� `6鏈�/7鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx`锛堟槑缁� sheet锛変笌 `6鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls`锛堝垎椤圭洰鎶曡祫瀹屾垚鎯呭喌 sheet锛変袱绫荤粺涓�鏍煎紡銆�
+- 瑙f瀽瑕佺偣锛氳烦杩囧垎缁勮/灏忚琛岋紙鍏抽敭璇嶏細甯傚窞鍚�+鏁板瓧鍒椼�乣灏忚/鍚堣`銆乣涓�銆佷簩銆佷笁鈥锛夛紝璇嗗埆椤圭洰鏄庣粏琛岋紱琛ㄥご鎸夊叧閿瘝鏄犲皠鍒椼��
+- 骞傜瓑锛氬悓鎶ヨ〃鏈熷垹闄ゆ棫鏁版嵁锛堜富妗f寜鍚� upsert锛屾湀搴﹁〃鎸� period 鍒犻櫎閲嶅缓锛夈��
+- 鍚屾椂瀵煎叆 `鏌ヨ缁撴灉(鎶曡祫绯荤粺鈥�).xls` 浣滀负瀹℃牳姣斿鍩哄噯锛堝瓨 `investment_system` 琛ㄦ垨鐩存帴瀹℃牳鏃惰鏂囦欢鈫掑缓璁瓨琛級銆�
+
+### 浜屾湡锛�17 甯傚窞鍗曡〃瀹归敊瑙f瀽
+- 瑙f瀽鍣ㄤ笁绾у閿欙細鈶犳寜鍏抽敭璇嶅畾浣嶈〃澶磋锛坄椤圭洰鍚嶇О`+`鎬绘姇璧刞锛夆啋 鈶℃寜琛ㄥご鍏抽敭璇嶆槧灏勫垪锛坄璁″垝.*鎬绘姇璧刞鈫掓�绘姇璧勭瓑锛夆啋 鈶㈠垎缁�/灏忚琛岃繃婊ゃ��
+- 鏍煎紡娉ㄥ唽琛細姣忕甯傚窞鏍煎紡 = 涓�涓В鏋愬櫒閰嶇疆锛堝垪鏄犲皠 + 鏍囬琛屽亸绉� + 璺宠繃琛屾ā寮忥級锛屾柊澧炴牸寮忓姞閰嶇疆涓嶆敼浠g爜銆�
+- 鏀寔 `.xls/.xlsx`锛沗.et`锛圵PS锛変笌 `.pdf` 鍏堟彁绀�"璇峰彟瀛樹负 xls/xlsx"銆�
+
+## 鍥涖�佸鏍歌鍒欙紙瑙勫垯琛ㄩ┍鍔紝report_type=INVEST锛�
+| 瑙勫垯 | 姣旇緝绫诲瀷 | 璇存槑 |
+|------|---------|------|
+| 涓庢姇璧勭郴缁熸暟瀵规瘮 | CROSS_DIFF | 璁″垝鎬绘姇璧�/鑷勾鍒濈疮璁�/褰撴湀瀹屾垚 涓庣郴缁熷鍑哄樊寮傝秴闃堝�硷紙濡傛�绘姇璧�>5% 鎴栫粷瀵瑰樊>100涓囷級 |
+| 瀹屾垚瓒呰鍒� | RANGE | 褰撴湀瀹屾垚 > 鏈勾璁″垝鍓╀綑锛涚疮璁″畬鎴� > 璁″垝鎬绘姇璧劽�1.05 |
+| 杩涘害鐭涚浘 | LOGIC | 闃舵=瀹屽伐浣嗗畬鎴愮巼<1锛涢樁娈�=鍦ㄥ缓浣嗙疮璁�=0 |
+| 蹇呭~缂哄け | NULL_CHECK | 椤圭洰鍚�/鎬绘姇璧�/绱瀹屾垚 涓虹┖ |
+
+## 浜斻�佹姤琛ㄧ敓鎴愶紙澶嶇敤鐜版湁瀵煎嚭妗嗘灦锛�
+- 4 涓鐐癸細`/api/report/export/investPlan`锛堥瀹夋帓璁″垝琛級銆乣investFiveYearLogistics`锛堝崄浜斾簲鐗╂祦锛夈�乣investBillion`锛堜嚎鍏冮」鐩紝鍚姇璧勬�昏〃+瑙勪笂椤圭洰琛ㄤ袱 sheet锛夈�乣investCounty`锛堢粡娴庡己鍘胯〃锛夈��
+- 鍔ㄦ�佹湀浠斤細`period=2026-07` 鈫� 鏍囬/鍒楀ご鐢� `1-7鏈堢疮璁锛涘競宸為『搴忔部鐢� `RegionUtil.cityList()`銆�
+- 浜垮厓椤圭洰琛細鎸� `total_investment 鈮� 10000 涓囧厓` 绛涢�夛紱鎶曡祫鎬昏〃鎸夊搴﹀彛寰勪粠鏈堝害瀹屾垚鎺ㄥ锛堜竴瀛e害=1-3鏈堛�佷簩瀛e害=4-6鏈堚�︼級銆�
+
+## 鍏�佸墠绔�
+- `DataImport.vue`锛氭柊澧�"鎶曡祫鏈堟姤"锛堝叏鐪佹眹鎬诲ぇ琛級涓�"鎶曡祫绯荤粺瀵煎嚭"涓ょ瀵煎叆绫诲瀷銆�
+- `ReportExport.vue`锛氭柊澧�"鎶曡祫鎶ヨ〃锛圛NVEST锛�"鍗$墖锛�4 涓敓鎴愭寜閽��
+- `AuditResult.vue`锛氭姤琛ㄧ被鍨嬪鍔�"鎶曡祫鏈堟姤"锛堝彲閫夛紝浜屾湡锛夈��
+
+## 涓冦�侀噷绋嬬涓庡伐浣滈噺锛堝崟浜猴級
+| 閲岀▼纰� | 鍐呭 | 宸ヤ綔閲� |
+|--------|------|--------|
+| M1 | 鍏ㄧ渷姹囨�诲ぇ琛ㄥ鍏� + 涓绘。/鏈堝害琛� + 4 寮犳姤琛ㄨ緭鍑� | 鍚庣 4~5 浜哄ぉ + 鍓嶇 1~2 浜哄ぉ |
+| M2 | 甯傚窞鍗曡〃瀹归敊瑙f瀽 + 绯荤粺瀵煎嚭姣斿瀹℃牳 | 鍚庣 4~6 浜哄ぉ |
+| M3 | 鍓嶇鍏ュ彛鏁村悎 + 鑱旇皟锛堜笌甯傚窞鏍稿 6/7 鏈堟牱渚嬶級 | 2~3 浜哄ぉ |
+
+> 涓嶅惈闇�姹傜‘璁や笌鏁版嵁鍙e緞鏍稿疄锛涘缓璁� M1 瀹屾垚鍗宠鐢ㄦ埛鐢� 6/7 鏈堟牱渚嬫牳瀵� 4 寮犺緭鍑鸿〃涓庣幇鏈� Excel 鏄惁涓�鑷淬��
+
+## 鍏�佸緟鐢ㄦ埛纭鐨勭偣
+1. 涓绘暟鎹簮锛氬厛浠�"鍏ㄧ渷姹囨�诲ぇ琛�"涓哄噯锛圡1锛夛紝杩樻槸鐩存帴涓�"甯傚窞鍗曡〃瑙f瀽"锛�
+2. 绯荤粺瀵煎嚭鏄惁姣忔湀閮芥湁锛堝彲浣滄瘮瀵瑰熀鍑嗭級锛熸槸鍚︽彁渚涢」鐩�-甯傚窞瀵圭収锛堢郴缁熷鍑烘棤甯傚窞瀛楁锛夛紵
+3. 浜垮厓椤圭洰绛涢�夊彛寰勶細鈮�1 浜匡紙10000 涓囷級鏄惁鍑嗙‘锛熺粡娴庡己鍘块」鐩竻鍗曟槸鍚︽湁鍥哄畾鍚嶅崟锛�
\ No newline at end of file
diff --git "a/docs/\346\212\245\350\241\250\347\224\237\346\210\220\347\225\214\351\235\242\344\274\230\345\214\226.doc" "b/docs/\346\212\245\350\241\250\347\224\237\346\210\220\347\225\214\351\235\242\344\274\230\345\214\226.doc"
new file mode 100644
index 0000000..2e72e02
--- /dev/null
+++ "b/docs/\346\212\245\350\241\250\347\224\237\346\210\220\347\225\214\351\235\242\344\274\230\345\214\226.doc"
Binary files differ
diff --git "a/docs/\346\212\245\350\241\250\347\224\237\346\210\220\347\225\214\351\235\242\344\274\230\345\214\226\346\214\207\344\273\244.docx" "b/docs/\346\212\245\350\241\250\347\224\237\346\210\220\347\225\214\351\235\242\344\274\230\345\214\226\346\214\207\344\273\244.docx"
new file mode 100644
index 0000000..fed948b
--- /dev/null
+++ "b/docs/\346\212\245\350\241\250\347\224\237\346\210\220\347\225\214\351\235\242\344\274\230\345\214\226\346\214\207\344\273\244.docx"
Binary files differ
diff --git "a/docs/\347\224\237\346\210\220_\345\221\250\350\275\254\351\207\217\346\216\222\345\220\215.xlsx" "b/docs/\347\224\237\346\210\220_\345\221\250\350\275\254\351\207\217\346\216\222\345\220\215.xlsx"
deleted file mode 100644
index 41cd347..0000000
--- "a/docs/\347\224\237\346\210\220_\345\221\250\350\275\254\351\207\217\346\216\222\345\220\215.xlsx"
+++ /dev/null
Binary files differ
diff --git "a/docs/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx" "b/docs/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
deleted file mode 100644
index 100d9ae..0000000
--- "a/docs/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
+++ /dev/null
Binary files differ
diff --git "a/docs/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\346\216\222\345\220\215.xlsx" "b/docs/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\346\216\222\345\220\215.xlsx"
deleted file mode 100644
index 0c64bf8..0000000
--- "a/docs/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\346\216\222\345\220\215.xlsx"
+++ /dev/null
Binary files differ
diff --git "a/docs/\350\203\275\350\200\227/\346\225\260\346\215\256\345\257\274\345\205\245\345\244\261\350\264\245 .png" "b/docs/\350\203\275\350\200\227/\346\225\260\346\215\256\345\257\274\345\205\245\345\244\261\350\264\245 .png"
new file mode 100644
index 0000000..79b27ee
--- /dev/null
+++ "b/docs/\350\203\275\350\200\227/\346\225\260\346\215\256\345\257\274\345\205\245\345\244\261\350\264\245 .png"
Binary files differ
diff --git "a/docs/\350\203\275\350\200\227/\346\250\241\346\235\277_\350\203\275\350\200\227\350\275\246\350\276\206\350\277\220\346\224\277\344\277\241\346\201\257.xlsx" "b/docs/\350\203\275\350\200\227/\346\250\241\346\235\277_\350\203\275\350\200\227\350\275\246\350\276\206\350\277\220\346\224\277\344\277\241\346\201\257.xlsx"
new file mode 100644
index 0000000..f9ea660
--- /dev/null
+++ "b/docs/\350\203\275\350\200\227/\346\250\241\346\235\277_\350\203\275\350\200\227\350\275\246\350\276\206\350\277\220\346\224\277\344\277\241\346\201\257.xlsx"
Binary files differ
diff --git "a/docs/\350\203\275\350\200\227/\346\250\241\346\235\277_\351\201\223\350\267\257\350\264\247\350\277\220\350\275\246\350\276\206\350\203\275\346\272\220\346\266\210\350\200\227\346\203\205\345\206\265\357\274\2102026\357\274\211.xlsx" "b/docs/\350\203\275\350\200\227/\346\250\241\346\235\277_\351\201\223\350\267\257\350\264\247\350\277\220\350\275\246\350\276\206\350\203\275\346\272\220\346\266\210\350\200\227\346\203\205\345\206\265\357\274\2102026\357\274\211.xlsx"
new file mode 100644
index 0000000..db66a33
--- /dev/null
+++ "b/docs/\350\203\275\350\200\227/\346\250\241\346\235\277_\351\201\223\350\267\257\350\264\247\350\277\220\350\275\246\350\276\206\350\203\275\346\272\220\346\266\210\350\200\227\346\203\205\345\206\265\357\274\2102026\357\274\211.xlsx"
Binary files differ
diff --git "a/docs/\350\203\275\350\200\227/\347\224\237\346\210\220_\350\203\275\350\277\220\346\261\207\346\200\273\350\241\250.xlsx" "b/docs/\350\203\275\350\200\227/\347\224\237\346\210\220_\350\203\275\350\277\220\346\261\207\346\200\273\350\241\250.xlsx"
new file mode 100644
index 0000000..437b561
--- /dev/null
+++ "b/docs/\350\203\275\350\200\227/\347\224\237\346\210\220_\350\203\275\350\277\220\346\261\207\346\200\273\350\241\250.xlsx"
Binary files differ
diff --git "a/docs/\350\203\275\350\200\227/\347\224\237\346\210\220\347\232\204\350\203\275\350\277\220\346\261\207\346\200\273\350\241\250\350\246\201\346\240\271\346\215\256\346\250\241\346\235\277\350\241\250\343\200\212\350\203\275\350\277\220\346\261\207\346\200\273\350\241\250\343\200\213\347\232\204\345\210\227\345\256\275\343\200\201\350\241\250\345\244\264\343\200\201\345\220\210\345\271\266\350\247\204\345\210\231\345\256\214\345\205\250\344\270\200\350\207\264\357\274\214\344\273\205\346\233\277\346\215\242\346\225\260\346\215\256\343\200\202\345\257\274\345\207\272\344\270\272Excel\346\240\274\345\274\217\357\274\214\347\224\237\346\210\22017\344\270\252\345\270\202\345\267\236\347\232\204\345\210\206\350\241\250\345\222\214\345\205\250\347\234\201\347\232\204\346\261\207\346\200\273\350\241\250.txt" "b/docs/\350\203\275\350\200\227/\347\224\237\346\210\220\347\232\204\350\203\275\350\277\220\346\261\207\346\200\273\350\241\250\350\246\201\346\240\271\346\215\256\346\250\241\346\235\277\350\241\250\343\200\212\350\203\275\350\277\220\346\261\207\346\200\273\350\241\250\343\200\213\347\232\204\345\210\227\345\256\275\343\200\201\350\241\250\345\244\264\343\200\201\345\220\210\345\271\266\350\247\204\345\210\231\345\256\214\345\205\250\344\270\200\350\207\264\357\274\214\344\273\205\346\233\277\346\215\242\346\225\260\346\215\256\343\200\202\345\257\274\345\207\272\344\270\272Excel\346\240\274\345\274\217\357\274\214\347\224\237\346\210\22017\344\270\252\345\270\202\345\267\236\347\232\204\345\210\206\350\241\250\345\222\214\345\205\250\347\234\201\347\232\204\346\261\207\346\200\273\350\241\250.txt"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/docs/\350\203\275\350\200\227/\347\224\237\346\210\220\347\232\204\350\203\275\350\277\220\346\261\207\346\200\273\350\241\250\350\246\201\346\240\271\346\215\256\346\250\241\346\235\277\350\241\250\343\200\212\350\203\275\350\277\220\346\261\207\346\200\273\350\241\250\343\200\213\347\232\204\345\210\227\345\256\275\343\200\201\350\241\250\345\244\264\343\200\201\345\220\210\345\271\266\350\247\204\345\210\231\345\256\214\345\205\250\344\270\200\350\207\264\357\274\214\344\273\205\346\233\277\346\215\242\346\225\260\346\215\256\343\200\202\345\257\274\345\207\272\344\270\272Excel\346\240\274\345\274\217\357\274\214\347\224\237\346\210\22017\344\270\252\345\270\202\345\267\236\347\232\204\345\210\206\350\241\250\345\222\214\345\205\250\347\234\201\347\232\204\346\261\207\346\200\273\350\241\250.txt"
diff --git "a/docs/\350\203\275\350\200\227/\350\203\275\350\200\227\347\256\200\345\214\226.docx" "b/docs/\350\203\275\350\200\227/\350\203\275\350\200\227\347\256\200\345\214\226.docx"
new file mode 100644
index 0000000..f4dd8ba
--- /dev/null
+++ "b/docs/\350\203\275\350\200\227/\350\203\275\350\200\227\347\256\200\345\214\226.docx"
Binary files differ
diff --git "a/docs/\350\203\275\350\200\227/\350\203\275\350\200\227\347\256\200\345\214\226\357\274\2102026\345\271\264\357\274\211.xlsx" "b/docs/\350\203\275\350\200\227/\350\203\275\350\200\227\347\256\200\345\214\226\357\274\2102026\345\271\264\357\274\211.xlsx"
new file mode 100644
index 0000000..d67247f
--- /dev/null
+++ "b/docs/\350\203\275\350\200\227/\350\203\275\350\200\227\347\256\200\345\214\226\357\274\2102026\345\271\264\357\274\211.xlsx"
Binary files differ
diff --git "a/docs/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237H203-2\350\241\250_\351\201\223\350\267\257\350\264\247\347\211\251\350\277\220\350\276\223\346\234\210\345\272\246\347\224\237\344\272\247\346\203\205\345\206\265.xlsx" "b/docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237H203-2\350\241\250_\351\201\223\350\267\257\350\264\247\347\211\251\350\277\220\350\276\223\346\234\210\345\272\246\347\224\237\344\272\247\346\203\205\345\206\265.xlsx"
similarity index 100%
rename from "docs/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237H203-2\350\241\250_\351\201\223\350\267\257\350\264\247\347\211\251\350\277\220\350\276\223\346\234\210\345\272\246\347\224\237\344\272\247\346\203\205\345\206\265.xlsx"
rename to "docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\344\272\244\344\274\201\347\273\237H203-2\350\241\250_\351\201\223\350\267\257\350\264\247\347\211\251\350\277\220\350\276\223\346\234\210\345\272\246\347\224\237\344\272\247\346\203\205\345\206\265.xlsx"
Binary files differ
diff --git "a/docs/\346\250\241\346\235\277_\350\247\204\344\270\212\350\247\204\344\270\213\346\213\206\345\210\206\350\277\220\350\276\223\351\207\217.xlsx" "b/docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\350\247\204\344\270\212\350\247\204\344\270\213\346\213\206\345\210\206\350\277\220\350\276\223\351\207\217.xlsx"
similarity index 100%
rename from "docs/\346\250\241\346\235\277_\350\247\204\344\270\212\350\247\204\344\270\213\346\213\206\345\210\206\350\277\220\350\276\223\351\207\217.xlsx"
rename to "docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\350\247\204\344\270\212\350\247\204\344\270\213\346\213\206\345\210\206\350\277\220\350\276\223\351\207\217.xlsx"
Binary files differ
diff --git "a/docs/\346\250\241\346\235\277_\350\264\247\350\277\220\351\207\217\345\221\250\350\275\254\351\207\217.xlsx" "b/docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\350\264\247\350\277\220\351\207\217\345\221\250\350\275\254\351\207\217.xlsx"
similarity index 100%
rename from "docs/\346\250\241\346\235\277_\350\264\247\350\277\220\351\207\217\345\221\250\350\275\254\351\207\217.xlsx"
rename to "docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\350\264\247\350\277\220\351\207\217\345\221\250\350\275\254\351\207\217.xlsx"
Binary files differ
diff --git "a/docs/\346\250\241\346\235\277_\350\275\250\350\277\271\351\207\214\347\250\213.xlsx" "b/docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\350\275\250\350\277\271\351\207\214\347\250\213.xlsx"
similarity index 100%
rename from "docs/\346\250\241\346\235\277_\350\275\250\350\277\271\351\207\214\347\250\213.xlsx"
rename to "docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\350\275\250\350\277\271\351\207\214\347\250\213.xlsx"
Binary files differ
diff --git "a/docs/\346\250\241\346\235\277_\350\277\220\346\224\277\350\275\246\350\276\206.xlsx" "b/docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\350\277\220\346\224\277\350\275\246\350\276\206.xlsx"
similarity index 100%
rename from "docs/\346\250\241\346\235\277_\350\277\220\346\224\277\350\275\246\350\276\206.xlsx"
rename to "docs/\350\264\247\350\277\220/\346\250\241\346\235\277_\350\277\220\346\224\277\350\275\246\350\276\206.xlsx"
Binary files differ
diff --git "a/docs/\350\264\247\350\277\220/\347\224\237\346\210\220_\345\221\250\350\275\254\351\207\217\346\216\222\345\220\215.xlsx" "b/docs/\350\264\247\350\277\220/\347\224\237\346\210\220_\345\221\250\350\275\254\351\207\217\346\216\222\345\220\215.xlsx"
new file mode 100644
index 0000000..b048f4e
--- /dev/null
+++ "b/docs/\350\264\247\350\277\220/\347\224\237\346\210\220_\345\221\250\350\275\254\351\207\217\346\216\222\345\220\215.xlsx"
Binary files differ
diff --git "a/docs/\350\264\247\350\277\220/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx" "b/docs/\350\264\247\350\277\220/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
new file mode 100644
index 0000000..bbc0359
--- /dev/null
+++ "b/docs/\350\264\247\350\277\220/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\345\210\206\345\270\202\345\267\236\346\230\216\347\273\206.xlsx"
Binary files differ
diff --git "a/docs/\350\264\247\350\277\220/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\346\216\222\345\220\215.xlsx" "b/docs/\350\264\247\350\277\220/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\346\216\222\345\220\215.xlsx"
new file mode 100644
index 0000000..7195313
--- /dev/null
+++ "b/docs/\350\264\247\350\277\220/\347\224\237\346\210\220_\350\264\247\350\277\220\351\207\217\346\216\222\345\220\215.xlsx"
Binary files differ
diff --git "a/docs/\350\264\247\350\277\220/\350\264\247\350\277\220\347\256\200\345\214\226.docx" "b/docs/\350\264\247\350\277\220/\350\264\247\350\277\220\347\256\200\345\214\226.docx"
new file mode 100644
index 0000000..abe04a2
--- /dev/null
+++ "b/docs/\350\264\247\350\277\220/\350\264\247\350\277\220\347\256\200\345\214\226.docx"
Binary files differ
diff --git "a/docs/\350\264\247\350\277\220\347\256\200\345\214\226\345\267\245\344\275\234.xlsx" "b/docs/\350\264\247\350\277\220/\350\264\247\350\277\220\347\256\200\345\214\226\345\267\245\344\275\234.xlsx"
similarity index 100%
rename from "docs/\350\264\247\350\277\220\347\256\200\345\214\226\345\267\245\344\275\234.xlsx"
rename to "docs/\350\264\247\350\277\220/\350\264\247\350\277\220\347\256\200\345\214\226\345\267\245\344\275\234.xlsx"
Binary files differ
diff --git "a/docs/\350\264\247\350\277\220\347\256\200\345\214\226.docx" "b/docs/\350\264\247\350\277\220\347\256\200\345\214\226.docx"
deleted file mode 100644
index 5f14306..0000000
--- "a/docs/\350\264\247\350\277\220\347\256\200\345\214\226.docx"
+++ /dev/null
Binary files differ
diff --git a/dump_cells.py b/dump_cells.py
deleted file mode 100644
index ac8ee32..0000000
--- a/dump_cells.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, os
-tmp = r'C:\Users\jcxiong\AppData\Local\Temp\ta_e2e_524cecf1'
-wb = openpyxl.load_workbook(os.path.join(tmp, 'gen_freight_rank.xlsx'))
-ws = wb[wb.sheetnames[0]]
-print('max_row:', ws.max_row, 'max_col:', ws.max_column)
-for r in [2,3,4,5,6,24,25,26,27,28,44,45,46,47]:
-    cells = [(c.coordinate, c.value) for c in ws[r] if c.value is not None]
-    print(r, '->', cells[:3])
\ No newline at end of file
diff --git a/dump_ft_mid.py b/dump_ft_mid.py
deleted file mode 100644
index b19a6ef..0000000
--- a/dump_ft_mid.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, glob, os
-docs = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs'
-f = glob.glob(os.path.join(docs, '鐢熸垚_璐ц繍閲忓懆杞噺.xlsx'))[0]
-wb = openpyxl.load_workbook(f)
-ws = wb[wb.sheetnames[0]]
-for r in range(17, 25):
-    vals = [str(ws.cell(r,c).value)[:10] if ws.cell(r,c).value is not None else '' for c in range(1, 10)]
-    print(r, '|', ' | '.join(vals))
\ No newline at end of file
diff --git a/dump_ft_sample.py b/dump_ft_sample.py
deleted file mode 100644
index 4a2cb86..0000000
--- a/dump_ft_sample.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, glob, os
-docs = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs'
-f = glob.glob(os.path.join(docs, '鐢熸垚_璐ц繍閲忓懆杞噺.xlsx'))[0]
-wb = openpyxl.load_workbook(f)
-ws = wb[wb.sheetnames[0]]
-print('SAMPLE dims:', ws.max_row, ws.max_column)
-for r in range(1, ws.max_row+1):
-    vals = []
-    for c in range(1, ws.max_column+1):
-        v = ws.cell(r,c).value
-        if v is not None:
-            vals.append(f'c{c}={str(v)[:12]}')
-    print(r, '|', ' '.join(vals))
\ No newline at end of file
diff --git a/dump_func.py b/dump_func.py
deleted file mode 100644
index 297c701..0000000
--- a/dump_func.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-import io
-path = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\traffic-audit-server\src\main\java\com\trafficaudit\reportexport\service\ReportExportService.java'
-with io.open(path, encoding='utf-8') as f:
-    lines = f.read().split('\n')
-start = next(i for i, l in enumerate(lines) if 'public byte[] exportFreightRank' in l)
-end = next(i for i in range(start+1, len(lines)) if 'public byte[] ' in lines[i])
-for i in range(start, end):
-    print(i+1, '|', lines[i])
\ No newline at end of file
diff --git a/dump_gen_rank.py b/dump_gen_rank.py
deleted file mode 100644
index 37c4c4f..0000000
--- a/dump_gen_rank.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, os
-tmp = r'C:\Users\jcxiong\AppData\Local\Temp\ta_e2e_524cecf1'
-wb = openpyxl.load_workbook(os.path.join(tmp, 'gen_freight_rank.xlsx'))
-ws = wb[wb.sheetnames[0]]
-for r in range(1, ws.max_row+1):
-    v = ws.cell(r,1).value
-    v2 = ws.cell(r,2).value
-    print(r, '|', str(v)[:24] if v is not None else '', '|', str(v2)[:10] if v2 is not None else '')
\ No newline at end of file
diff --git a/dump_rank2.py b/dump_rank2.py
deleted file mode 100644
index cabcc19..0000000
--- a/dump_rank2.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, glob, os
-docs = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs'
-for name in ['鐢熸垚_璐ц繍閲忔帓鍚�.xlsx', '鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx']:
-    f = glob.glob(os.path.join(docs, name))[0]
-    wb = openpyxl.load_workbook(f)
-    ws = wb[wb.sheetnames[0]]
-    print('='*30, name, 'dims', ws.max_row, ws.max_column)
-    for r in [1, 2, 3, 4, 23, 24, 25, 26]:
-        vals = [str(ws.cell(r,c).value)[:26] if ws.cell(r,c).value is not None else '' for c in range(1, ws.max_column+1)]
-        nonempty = [(i+1, v) for i, v in enumerate(vals) if v]
-        print(r, '|', ' | '.join(f'c{c}={v}' for c, v in nonempty))
\ No newline at end of file
diff --git a/dump_rank_sample.py b/dump_rank_sample.py
deleted file mode 100644
index 3f2631d..0000000
--- a/dump_rank_sample.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, glob, os
-docs = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs'
-f = glob.glob(os.path.join(docs, '鐢熸垚_璐ц繍閲忔帓鍚�.xlsx'))[0]
-wb = openpyxl.load_workbook(f)
-ws = wb[wb.sheetnames[0]]
-for r in range(1, ws.max_row+1):
-    vals = [str(ws.cell(r,c).value)[:12] if ws.cell(r,c).value is not None else '' for c in range(1, 6)]
-    print(r, '|', ' | '.join(vals))
\ No newline at end of file
diff --git a/dump_xml.py b/dump_xml.py
deleted file mode 100644
index 202f561..0000000
--- a/dump_xml.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-import zipfile, os, re
-tmp = r'C:\Users\jcxiong\AppData\Local\Temp\ta_e2e_524cecf1'
-zf = zipfile.ZipFile(os.path.join(tmp, 'gen_freight_rank.xlsx'))
-xml = zf.read('xl/worksheets/sheet1.xml').decode('utf-8')
-rows = re.findall(r'<row r="(\d+)"[^>]*>(.*?)</row>|<row r="(\d+)"[^>]*/>', xml)
-print('total <row> elements:', len(rows))
-for m in rows:
-    r = m[0] or m[2]
-    cells = len(re.findall(r'<c ', m[1] if m[1] else ''))
-    print('row', r, 'cells:', cells)
\ No newline at end of file
diff --git a/fix_order.py b/fix_order.py
deleted file mode 100644
index d6ade0c..0000000
--- a/fix_order.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-import io
-path = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\traffic-audit-server\src\main\java\com\trafficaudit\reportexport\service\ReportExportService.java'
-with io.open(path, encoding='utf-8') as f:
-    content = f.read()
-has_crlf = '\r\n' in content
-lines = content.split('\n')
-fixed = 0
-for i, l in enumerate(lines):
-    if 'rowIdx = writeRankHead(' in l and i+1 < len(lines) and lines[i+1].strip() == 'int rowIdx = 3;':
-        lines[i], lines[i+1] = lines[i+1], lines[i]
-        fixed += 1
-print('swapped pairs:', fixed)
-assert fixed == 2
-new_content = '\n'.join(lines)
-if has_crlf:
-    new_content = new_content.replace('\n', '\r\n')
-with io.open(path, 'w', encoding='utf-8', newline='') as f:
-    f.write(new_content)
-print('saved')
\ No newline at end of file
diff --git a/fix_rank2.py b/fix_rank2.py
deleted file mode 100644
index ca41fe8..0000000
--- a/fix_rank2.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-import io
-path = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\traffic-audit-server\src\main\java\com\trafficaudit\reportexport\service\ReportExportService.java'
-with io.open(path, encoding='utf-8') as f:
-    content = f.read()
-has_crlf = '\r\n' in content
-lines = content.split('\n')
-
-def patch_func(func, rowidx_from, rowidx_to, unit_from, unit_to):
-    start = next(i for i, l in enumerate(lines) if l.strip().startswith('public byte[] ' + func + '('))
-    end = len(lines)
-    for i in range(start+1, len(lines)):
-        if lines[i].strip().startswith('public byte[] '):
-            end = i
-            break
-    cnt_r = cnt_u = 0
-    for i in range(start, end):
-        if lines[i].strip() == 'int rowIdx = ' + rowidx_from + ';':
-            lines[i] = lines[i].replace('int rowIdx = ' + rowidx_from + ';', 'int rowIdx = ' + rowidx_to + ';')
-            cnt_r += 1
-        if '"' + unit_from + '"' in lines[i]:
-            lines[i] = lines[i].replace('"' + unit_from + '"', '"' + unit_to + '"')
-            cnt_u += 1
-    print(func, '-> rowIdx:', cnt_r, '| unit:', cnt_u)
-
-patch_func('exportFreightRank', '3', '1', '涓囧惃', '鍚�')
-patch_func('exportTurnoverRank', '3', '1', '涓囧惃鍏噷', '鍚ㄥ叕閲�')
-
-new_content = '\n'.join(lines)
-if has_crlf:
-    new_content = new_content.replace('\n', '\r\n')
-with io.open(path, 'w', encoding='utf-8', newline='') as f:
-    f.write(new_content)
-print('saved')
\ No newline at end of file
diff --git a/fix_schema.py b/fix_schema.py
deleted file mode 100644
index 5c231ee..0000000
--- a/fix_schema.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-import io
-
-sql_path = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs\init.sql'
-with io.open(sql_path, encoding='utf-8') as f:
-    sql = f.read()
-
-old = """    enterprise_code VARCHAR(50) COMMENT '缁熶竴绀句細淇$敤浠g爜',
-    enterprise_name VARCHAR(200) COMMENT '浼佷笟鍚嶇О',
-"""
-new = """    enterprise_code VARCHAR(50) COMMENT '浼佷笟浠g爜',
-    enterprise_name VARCHAR(200) COMMENT '浼佷笟鍚嶇О',
-    unified_credit_code VARCHAR(50) COMMENT '缁熶竴绀句細淇$敤浠g爜',
-    report_unit VARCHAR(200) COMMENT '濉姤鍗曚綅',
-"""
-assert old in sql, 'anchor1 not found'
-sql = sql.replace(old, new)
-
-old2 = """    -- 濉姤淇℃伅
-    contact_person VARCHAR(50) COMMENT '濉〃浜�',
-"""
-new2 = """    -- 濉姤淇℃伅
-    unit_leader VARCHAR(50) COMMENT '鍗曚綅璐熻矗浜�',
-    stats_leader VARCHAR(50) COMMENT '缁熻璐熻矗浜�',
-    contact_person VARCHAR(50) COMMENT '濉〃浜�',
-"""
-assert old2 in sql, 'anchor2 not found'
-sql = sql.replace(old2, new2)
-
-with io.open(sql_path, 'w', encoding='utf-8', newline='') as f:
-    f.write(sql)
-print('init.sql updated')
-
-md_path = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs\database.md'
-with io.open(md_path, encoding='utf-8') as f:
-    md = f.read()
-
-old3 = """| enterprise_code | varchar(50) | 浼佷笟绀句細淇$敤浠g爜 |
-| enterprise_name | varchar(200) | 浼佷笟鍚嶇О |
-"""
-new3 = """| enterprise_code | varchar(50) | 浼佷笟浠g爜 |
-| enterprise_name | varchar(200) | 浼佷笟鍚嶇О |
-| unified_credit_code | varchar(50) | 缁熶竴绀句細淇$敤浠g爜 |
-| report_unit | varchar(200) | 濉姤鍗曚綅 |
-"""
-assert old3 in md, 'md anchor1 not found'
-md = md.replace(old3, new3)
-
-old4 = """| contact_person | varchar(50) | 濉〃浜� |
-| contact_phone | varchar(20) | 鑱旂郴鐢佃瘽 |
-"""
-new4 = """| unit_leader | varchar(50) | 鍗曚綅璐熻矗浜� |
-| stats_leader | varchar(50) | 缁熻璐熻矗浜� |
-| contact_person | varchar(50) | 濉〃浜� |
-| contact_phone | varchar(20) | 鑱旂郴鐢佃瘽 |
-"""
-assert old4 in md, 'md anchor2 not found'
-md = md.replace(old4, new4)
-
-with io.open(md_path, 'w', encoding='utf-8', newline='') as f:
-    f.write(md)
-print('database.md updated')
\ No newline at end of file
diff --git a/fix_schema2.py b/fix_schema2.py
deleted file mode 100644
index 2bb3b2c..0000000
--- a/fix_schema2.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-import io
-
-base = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit'
-
-sql_path = base + r'\docs\init.sql'
-with io.open(sql_path, encoding='utf-8') as f:
-    sql = f.read()
-
-pairs = [
-    ("""    -- 瑙勪笂
-    above_scale_turnover DECIMAL(15,2) DEFAULT 0 COMMENT '瑙勪笂鍛ㄨ浆閲�',""",
-     """    -- 瑙勪笂
-    above_scale_freight DECIMAL(15,2) DEFAULT 0 COMMENT '瑙勪笂璐ц繍閲�',
-    above_scale_turnover DECIMAL(15,2) DEFAULT 0 COMMENT '瑙勪笂鍛ㄨ浆閲�',"""),
-    ("""    -- 瑙勪笅
-    below_scale_turnover DECIMAL(15,2) DEFAULT 0 COMMENT '瑙勪笅鍛ㄨ浆閲�',""",
-     """    -- 瑙勪笅
-    below_scale_freight DECIMAL(15,2) DEFAULT 0 COMMENT '瑙勪笅璐ц繍閲�',
-    below_scale_turnover DECIMAL(15,2) DEFAULT 0 COMMENT '瑙勪笅鍛ㄨ浆閲�',"""),
-    ("""    -- 鍚堣
-    total_turnover DECIMAL(15,2) DEFAULT 0 COMMENT '杩愯緭閲忥紙瑙勪笂+瑙勪笅锛�',""",
-     """    -- 鍚堣
-    total_freight DECIMAL(15,2) DEFAULT 0 COMMENT '杩愯緭閲忓悎璁�-璐ц繍閲�',
-    total_turnover DECIMAL(15,2) DEFAULT 0 COMMENT '杩愯緭閲忓悎璁�-鍛ㄨ浆閲�',"""),
-]
-for old, new in pairs:
-    assert old in sql, 'anchor missing: ' + old[:40]
-    sql = sql.replace(old, new)
-with io.open(sql_path, 'w', encoding='utf-8', newline='') as f:
-    f.write(sql)
-print('init.sql updated')
-
-md_path = base + r'\docs\database.md'
-with io.open(md_path, encoding='utf-8') as f:
-    md = f.read()
-pairs_md = [
-    ('| above_scale_turnover | decimal(15,2) | 瑙勪笂鍛ㄨ浆閲� |',
-     '| above_scale_freight | decimal(15,2) | 瑙勪笂璐ц繍閲� |\n| above_scale_turnover | decimal(15,2) | 瑙勪笂鍛ㄨ浆閲� |'),
-    ('| below_scale_turnover | decimal(15,2) | 瑙勪笅鍛ㄨ浆閲� |',
-     '| below_scale_freight | decimal(15,2) | 瑙勪笅璐ц繍閲� |\n| below_scale_turnover | decimal(15,2) | 瑙勪笅鍛ㄨ浆閲� |'),
-    ("| total_turnover | decimal(15,2) | 杩愯緭閲忥紙瑙勪笂+瑙勪笅锛� |",
-     '| total_freight | decimal(15,2) | 杩愯緭閲忓悎璁�-璐ц繍閲� |\n| total_turnover | decimal(15,2) | 杩愯緭閲忓悎璁�-鍛ㄨ浆閲� |'),
-]
-for old, new in pairs_md:
-    assert old in md, 'md anchor missing: ' + old[:40]
-    md = md.replace(old, new)
-with io.open(md_path, 'w', encoding='utf-8', newline='') as f:
-    f.write(md)
-print('database.md updated')
\ No newline at end of file
diff --git a/inspect_report.py b/inspect_report.py
deleted file mode 100644
index 3672b27..0000000
--- a/inspect_report.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# -*- coding: utf-8 -*-
-import io
-path = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\traffic-audit-server\src\main\java\com\trafficaudit\reportexport\service\ReportExportService.java'
-with io.open(path, encoding='utf-8') as f:
-    lines = f.read().split('\n')
-for i, l in enumerate(lines):
-    if 'rowIdx = writeRankHead' in l or 'for (int i = 0; i < 4' in l or '鏈熬绌鸿' in l:
-        print(i+1, '|', repr(l[:120]))
\ No newline at end of file
diff --git a/inspect_report2.py b/inspect_report2.py
deleted file mode 100644
index 075ffd7..0000000
--- a/inspect_report2.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# -*- coding: utf-8 -*-
-import io
-path = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\traffic-audit-server\src\main\java\com\trafficaudit\reportexport\service\ReportExportService.java'
-with io.open(path, encoding='utf-8') as f:
-    lines = f.read().split('\n')
-for i, l in enumerate(lines):
-    if 'rowIdx = writeRankHead' in l or l.strip() == 'int rowIdx = 3;':
-        print(i+1, '|', l.strip()[:90])
\ No newline at end of file
diff --git a/patch_report.py b/patch_report.py
deleted file mode 100644
index ec3893d..0000000
--- a/patch_report.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- coding: utf-8 -*-
-import io
-
-path = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\traffic-audit-server\src\main\java\com\trafficaudit\reportexport\service\ReportExportService.java'
-with io.open(path, encoding='utf-8') as f:
-    content = f.read()
-has_crlf = '\r\n' in content
-lines = content.split('\n')
-
-def func_span(name):
-    start = next(i for i, l in enumerate(lines) if l.strip().startswith('public byte[] ' + name + '('))
-    end = len(lines)
-    for i in range(start+1, len(lines)):
-        if lines[i].strip().startswith('public byte[] '):
-            end = i
-            break
-    return start, end
-
-def process_rank(func, metric, unit, title_suffix, city_writer, province_writer):
-    fs, fe = func_span(func)
-    i = next(i for i in range(fs, fe) if lines[i].strip() == 'int rowIdx = 3;')
-    j = i
-    while j > fs and 'Row head1 = sheet.createRow(' not in lines[j]:
-        j -= 1
-    assert 'Row head1' in lines[j], func + ' head1 not found'
-    lines[j:i] = ['        rowIdx = writeRankHead(sheet, rowIdx, "%s", "%s");' % (metric, unit)]
-    auto_i = next(m for m in range(j+1, fe) if lines[m].strip() == 'autoWidth(sheet, 18);')
-    insert = [
-        '',
-        '        // 鏈湀鍖哄潡锛堝綋鏈堟爣棰� + 绱鏁版嵁锛屼笌鏍蜂緥缁撴瀯涓�鑷达級',
-        '        sheet.createRow(rowIdx++);',
-        '        Row title2 = sheet.createRow(rowIdx++);',
-        '        title2.createCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚" + "%s");' % title_suffix,
-        '        rowIdx = writeRankHead(sheet, rowIdx, "%s", "%s");' % (metric, unit),
-        '        rowIdx = %s;' % province_writer,
-        '        for (String city : RegionUtil.cityList()) {',
-        '            rowIdx = %s;' % city_writer,
-        '        }',
-        '',
-        '        // 鏈熬绌鸿锛堜笌鏍蜂緥缁撴瀯瀵归綈锛�',
-        '        for (int i = 0; i < 4; i++) sheet.createRow(rowIdx++);',
-        '',
-    ]
-    lines[auto_i:auto_i] = insert
-    print('processed', func)
-
-process_rank('exportTurnoverRank', '鍛ㄨ浆閲�', '涓囧惃鍏噷', '鍏矾璐х墿杩愯緭鍛ㄨ浆閲忔儏鍐�',
-             'writeTurnoverRankRows(sheet, rowIdx, city, cumMap, false)',
-             'writeTurnoverRankRows(sheet, rowIdx, null, cumMap, true)')
-process_rank('exportFreightRank', '璐ц繍閲�', '涓囧惃', '鍏矾璐ц繍閲忔儏鍐�',
-             'writeRankRows(sheet, rowIdx, city, cumMap, h2032FreightCum, h2032FreightYoy, false)',
-             'writeRankRows(sheet, rowIdx, null, cumMap, h2032FreightCum, h2032FreightYoy, true)')
-
-# Change A: blank row before head2 in exportFreightTurnover
-blank = [i for i, l in enumerate(lines) if l.strip() == 'sheet.createRow(rowIdx++);']
-target = [i for i in blank if i+1 < len(lines) and 'Row head2 = sheet.createRow(rowIdx++);' in lines[i+1]]
-assert len(target) == 1, target
-del lines[target[0]]
-print('removed blank row at', target[0]+1)
-
-# helper before writeRankRows
-anchor = next(i for i, l in enumerate(lines) if l.strip().startswith('private int writeRankRows('))
-helper = [
-    '    /** 鎺掑悕琛ㄥ弻琛岃〃澶� */',
-    '    private int writeRankHead(Sheet sheet, int rowIdx, String metric, String unit) {',
-    '        Row h1 = sheet.createRow(rowIdx++);',
-    '        h1.createCell(0).setCellValue("甯傚窞");',
-    '        h1.createCell(1).setCellValue("瑙勪笂" + metric);',
-    '        h1.createCell(4).setCellValue("瑙勪笂澧為��");',
-    '        h1.createCell(6).setCellValue("瑙勪笅" + metric);',
-    '        h1.createCell(9).setCellValue("瑙勪笅澧為��");',
-    '        h1.createCell(11).setCellValue("鍚堣" + metric);',
-    '        h1.createCell(14).setCellValue("鍚堣澧為��");',
-    '        h1.createCell(16).setCellValue("鍒嗗競宸炶涓婅涓嬪崰姣�");',
-    '',
-    '        Row h2 = sheet.createRow(rowIdx++);',
-    '        h2.createCell(1).setCellValue("绱瀹屾垚    (涓�" + unit + "锛�");',
-    '        h2.createCell(2).setCellValue("鎺掑悕");',
-    '        h2.createCell(3).setCellValue("鍗犲叏鐪佹瘮閲�");',
-    '        h2.createCell(4).setCellValue("鍚屾瘮");',
-    '        h2.createCell(5).setCellValue("澧為��    鎺掑悕");',
-    '        h2.createCell(6).setCellValue("绱瀹屾垚    (涓�" + unit + "锛�");',
-    '        h2.createCell(7).setCellValue("鎺掑悕");',
-    '        h2.createCell(8).setCellValue("鍗犲叏鐪佹瘮閲�");',
-    '        h2.createCell(9).setCellValue("鍚屾瘮");',
-    '        h2.createCell(10).setCellValue("澧為�熸帓鍚�");',
-    '        h2.createCell(11).setCellValue("绱瀹屾垚    (涓�" + unit + "锛�");',
-    '        h2.createCell(12).setCellValue("鎺掑悕");',
-    '        h2.createCell(13).setCellValue("鍗犲叏鐪佹瘮閲�");',
-    '        h2.createCell(14).setCellValue("鍚屾瘮");',
-    '        h2.createCell(15).setCellValue("澧為�熸帓鍚�");',
-    '        return rowIdx;',
-    '    }',
-    '',
-    '',
-]
-lines[anchor:anchor] = helper
-print('helper inserted')
-
-new_content = '\n'.join(lines)
-if has_crlf:
-    new_content = new_content.replace('\n', '\r\n')
-with io.open(path, 'w', encoding='utf-8', newline='') as f:
-    f.write(new_content)
-print('saved')
\ No newline at end of file
diff --git a/traffic-audit-server/pom.xml b/traffic-audit-server/pom.xml
index c747c5d..65f52a1 100644
--- a/traffic-audit-server/pom.xml
+++ b/traffic-audit-server/pom.xml
@@ -34,6 +34,10 @@
             <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>
             <version>${mybatis-plus.version}</version>
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/controller/AuditController.java b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/controller/AuditController.java
index e5dc454..dd04fa2 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/controller/AuditController.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/controller/AuditController.java
@@ -3,6 +3,7 @@
 import com.trafficaudit.auditengine.entity.AuditResult;
 import com.trafficaudit.auditengine.service.AuditEngineService;
 import com.trafficaudit.common.Result;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
@@ -16,14 +17,58 @@
     private AuditEngineService auditService;
 
     @PostMapping("/execute")
-    public Result<List<AuditResult>> executeAudit(@RequestParam("period") String period) {
-        List<AuditResult> results = auditService.executeAudit(period);
+    public Result<List<AuditResult>> executeAudit(@RequestParam("period") String period,
+                                                  @RequestParam(value = "reportType", defaultValue = "H2032") String reportType) {
+        List<AuditResult> results;
+        if ("H2031".equals(reportType)) {
+            results = auditService.executePassengerAudit(period);
+        } else if ("H204".equals(reportType)) {
+            results = auditService.executeEnergyAudit(period);
+        } else if ("INVEST".equals(reportType)) {
+            results = auditService.executeInvestmentAudit(period);
+        } else if ("CITY_BUS".equals(reportType)) {
+            results = auditService.executeCityBusAudit(period);
+        } else if ("CITY_TAXI".equals(reportType)) {
+            results = auditService.executeCityTaxiAudit(period);
+        } else {
+            results = auditService.executeAudit(period);
+        }
         return Result.ok(results);
     }
 
+    /** 鏍囪璇ユ姤琛ㄦ湡+绫诲瀷涓恒�屽凡瀹℃牳銆嶏紙绾夸笅瀹℃牳閫氳繃鐨勬暟鎹紱鍏堝鏍稿啀鍑鸿〃鐨勪緷鎹紝瀵煎嚭鍓嶄笉鍐嶆彁绀烘湭瀹℃牳锛� */
+    @PostMapping("/markAudited")
+    public Result<Boolean> markAudited(@RequestParam("period") String period,
+                                       @RequestParam("reportType") String reportType) {
+        return Result.ok(auditService.markAudited(reportType, period));
+    }
+
+    @GetMapping("/ruleStats")
+    public Result<java.util.List<java.util.Map<String, Object>>> ruleStats(@RequestParam("period") String period,
+                                                                           @RequestParam(value = "reportType", defaultValue = "H2032") String reportType) {
+        return Result.ok(auditService.ruleStats(period, reportType));
+    }
+
+    @GetMapping("/latestPeriod")
+    public Result<String> latestPeriod(@RequestParam(value = "reportType", defaultValue = "H2032") String reportType) {
+        return Result.ok(auditService.latestPeriod(reportType));
+    }
+
+    @GetMapping("/export")
+    public ResponseEntity<byte[]> exportResults(@RequestParam("period") String period,
+                                                @RequestParam(value = "reportType", defaultValue = "H2032") String reportType) throws Exception {
+        byte[] bytes = auditService.exportResults(period, reportType);
+        String name = java.net.URLEncoder.encode("瀹℃牳缁撴灉_" + period + ".xlsx", "UTF-8");
+        return ResponseEntity.ok()
+            .header("Content-Disposition", "attachment; filename*=UTF-8''" + name)
+            .contentType(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM)
+            .body(bytes);
+    }
+
     @GetMapping("/results")
-    public Result<List<AuditResult>> getResults(@RequestParam("period") String period) {
-        List<AuditResult> results = auditService.queryResults(period);
+    public Result<List<AuditResult>> getResults(@RequestParam("period") String period,
+                                                @RequestParam(value = "reportType", defaultValue = "H2032") String reportType) {
+        List<AuditResult> results = auditService.queryResults(period, reportType);
         return Result.ok(results);
     }
 
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/entity/AuditRun.java b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/entity/AuditRun.java
new file mode 100644
index 0000000..6c69fb0
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/entity/AuditRun.java
@@ -0,0 +1,16 @@
+package com.trafficaudit.auditengine.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/** 鎶ヨ〃鏈熷鏍搁�氳繃鏍囪锛氭煇鎶ヨ〃绫诲瀷鏌愭姤琛ㄦ湡宸插畬鎴愬鏍革紙绾夸笅瀹℃牳閫氳繃/绯荤粺瀹℃牳鎵ц鍚庡啓鍏ワ級锛屽鍑哄墠 ready 鏍¢獙渚濇嵁 */
+@Data
+@TableName("audit_run")
+public class AuditRun {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String reportType;
+    private String reportPeriod;
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/mapper/AuditRunMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/mapper/AuditRunMapper.java
new file mode 100644
index 0000000..7f51800
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/mapper/AuditRunMapper.java
@@ -0,0 +1,6 @@
+package com.trafficaudit.auditengine.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.auditengine.entity.AuditRun;
+import org.apache.ibatis.annotations.Mapper;
+@Mapper
+public interface AuditRunMapper extends BaseMapper<AuditRun> {}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java
index 47d5fe2..29bffc1 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java
@@ -2,11 +2,33 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.trafficaudit.auditengine.entity.AuditResult;
+import com.trafficaudit.auditengine.entity.AuditRun;
 import com.trafficaudit.auditengine.mapper.AuditResultMapper;
+import com.trafficaudit.auditengine.mapper.AuditRunMapper;
+import com.trafficaudit.dataimport.entity.EnergyAuthVehicle;
+import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly;
 import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly;
+import com.trafficaudit.dataimport.entity.InvestmentMonthly;
+import com.trafficaudit.dataimport.entity.InvestmentProject;
+import com.trafficaudit.dataimport.entity.InvestmentSystem;
+import com.trafficaudit.dataimport.entity.CityBusMonthly;
+import com.trafficaudit.dataimport.entity.CityTaxiAuth;
+import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
+import com.trafficaudit.dataimport.entity.PassengerAuthVehicle;
+import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
 import com.trafficaudit.dataimport.entity.TransportAuthVehicle;
 import com.trafficaudit.dataimport.entity.VehicleTrackMileage;
+import com.trafficaudit.dataimport.mapper.EnergyAuthVehicleMapper;
+import com.trafficaudit.dataimport.mapper.EnergyVehicleQuarterlyMapper;
 import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.InvestmentMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.InvestmentProjectMapper;
+import com.trafficaudit.dataimport.mapper.InvestmentSystemMapper;
+import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.CityTaxiAuthMapper;
+import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.PassengerAuthVehicleMapper;
+import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
 import com.trafficaudit.dataimport.mapper.TransportAuthVehicleMapper;
 import com.trafficaudit.dataimport.mapper.VehicleTrackMileageMapper;
 import com.trafficaudit.rulemanage.entity.AuditRule;
@@ -14,7 +36,13 @@
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
 import javax.annotation.Resource;
+import java.io.ByteArrayOutputStream;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -45,19 +73,114 @@
         FREIGHT_TYPE_NAMES.put("freightGrain", "绮");
     }
 
+    /** H204 杞﹁締绫诲瀷鐮� -> 鍏佽鐨勮繍鏀胯溅杈嗙被鍨嬶紙宸插幓鎺夐噸/涓�/杞诲瀷鍓嶇紑锛� */
+    private static final Map<String, java.util.Set<String>> ENERGY_VEHICLE_TYPE_ALLOWED = new HashMap<>();
+
+    /** H204 鐕冩枡绫诲瀷鐮� -> 鍏佽鐨勮繍鏀跨噧鏂欑被鍨� */
+    private static final Map<String, java.util.Set<String>> ENERGY_FUEL_TYPE_ALLOWED = new HashMap<>();
+
+    static {
+        ENERGY_VEHICLE_TYPE_ALLOWED.put("01", new java.util.HashSet<>(java.util.Arrays.asList("鏅�氳揣杞�", "鏍忔澘璐ц溅", "鑷嵏璐ц溅")));
+        ENERGY_VEHICLE_TYPE_ALLOWED.put("02", new java.util.HashSet<>(java.util.Arrays.asList("骞虫澘璐ц溅", "鏍忔澘璐ц溅")));
+        ENERGY_VEHICLE_TYPE_ALLOWED.put("03", new java.util.HashSet<>(java.util.Arrays.asList("浠撴爡寮忚揣杞�")));
+        ENERGY_VEHICLE_TYPE_ALLOWED.put("04", new java.util.HashSet<>(java.util.Arrays.asList("鍘㈠紡璐ц溅")));
+        ENERGY_VEHICLE_TYPE_ALLOWED.put("06", new java.util.HashSet<>(java.util.Arrays.asList("缃愬紡璐ц溅")));
+        ENERGY_VEHICLE_TYPE_ALLOWED.put("07", new java.util.HashSet<>(java.util.Arrays.asList("鐗规畩缁撴瀯璐ц溅", "杞﹁締杩愯緭杞�")));
+        ENERGY_VEHICLE_TYPE_ALLOWED.put("08", new java.util.HashSet<>(java.util.Arrays.asList("鑷嵏璐ц溅", "鏍忔澘璐ц溅")));
+        ENERGY_VEHICLE_TYPE_ALLOWED.put("09", new java.util.HashSet<>(java.util.Arrays.asList("杞﹁締杩愯緭杞�", "涓疆杞磋溅杈嗚繍杈撹溅")));
+        ENERGY_VEHICLE_TYPE_ALLOWED.put("11", new java.util.HashSet<>(java.util.Arrays.asList("鍗婃寕鐗靛紩杞�")));
+
+        ENERGY_FUEL_TYPE_ALLOWED.put("01", new java.util.HashSet<>(java.util.Arrays.asList("姹芥补")));
+        ENERGY_FUEL_TYPE_ALLOWED.put("02", new java.util.HashSet<>(java.util.Arrays.asList("鏌存补")));
+        ENERGY_FUEL_TYPE_ALLOWED.put("03", new java.util.HashSet<>(java.util.Arrays.asList("鍘嬬缉澶╃劧姘�")));
+        ENERGY_FUEL_TYPE_ALLOWED.put("04", new java.util.HashSet<>(java.util.Arrays.asList("娑叉�佸ぉ鐒舵皵", "娑插寲澶╃劧姘�", "鍙岀噧鏂�")));
+        ENERGY_FUEL_TYPE_ALLOWED.put("07", new java.util.HashSet<>(java.util.Arrays.asList("鐢靛姩")));
+        ENERGY_FUEL_TYPE_ALLOWED.put("08", new java.util.HashSet<>(java.util.Arrays.asList("姘㈡皵")));
+    }
+
     @Resource
     private AuditRuleMapper ruleMapper;
     @Resource
     private AuditResultMapper resultMapper;
+    @Resource
+    private AuditRunMapper auditRunMapper;
     @Resource
     private H2032EnterpriseMonthlyMapper h2032Mapper;
     @Resource
     private TransportAuthVehicleMapper transportAuthMapper;
     @Resource
     private VehicleTrackMileageMapper trackMileageMapper;
+    @Resource
+    private PassengerEnterpriseMonthlyMapper passengerMapper;
+    @Resource
+    private PassengerAuthVehicleMapper passengerAuthMapper;
+    @Resource
+    private EnergyVehicleQuarterlyMapper energyMapper;
+    @Resource
+    private EnergyAuthVehicleMapper energyAuthMapper;
+    @Resource
+    private InvestmentProjectMapper investProjectMapper;
+    @Resource
+    private InvestmentMonthlyMapper investMonthlyMapper;
+    @Resource
+    private InvestmentSystemMapper investSystemMapper;
+    @Resource
+    private CityBusMonthlyMapper cityBusMapper;
+    @Resource
+    private CityTaxiMonthlyMapper cityTaxiMapper;
+    @Resource
+    private CityTaxiAuthMapper cityTaxiAuthMapper;
+
+    /** 璁板綍銆岃鏈熻绫诲瀷宸插畬鎴愬鏍搞�嶆爣璁帮紙鍏堝鏍稿啀鍑鸿〃渚濇嵁锛涙棤瑙勫垯鎴栨棤鏁版嵁涓嶆爣璁帮紝閲嶅鎵ц骞傜瓑锛� */
+    public void markAudited(String reportType, String reportPeriod, List<AuditRule> rules, boolean hasData) {
+        if (rules == null || rules.isEmpty() || !hasData) return;
+        Long cnt = auditRunMapper.selectCount(new LambdaQueryWrapper<AuditRun>()
+            .eq(AuditRun::getReportType, reportType)
+            .eq(AuditRun::getReportPeriod, reportPeriod));
+        if (cnt != null && cnt > 0) return;
+        AuditRun run = new AuditRun();
+        run.setReportType(reportType);
+        run.setReportPeriod(reportPeriod);
+        auditRunMapper.insert(run);
+    }
+
+    /** 2 鍙傛暟閲嶈浇锛氭寜鎶ヨ〃绫诲瀷鑷姩鍙栧惎鐢ㄨ鍒� + 鏍¢獙璇ユ湡鏈夋暟鎹悗鍐嶅啓鏍囪锛堢嚎涓嬪鏍搁�氳繃鏁版嵁鎵嬪姩鏍囪 / Controller 璋冪敤锛� */
+    public boolean markAudited(String reportType, String reportPeriod) {
+        List<AuditRule> rules = rulesOf(reportType);
+        if (rules.isEmpty()) {
+            log.info("markAudited skip: no enabled rules for {}", reportType);
+            return false;
+        }
+        long dataCount;
+        if ("H2031".equals(reportType)) {
+            dataCount = passengerMapper.selectCount(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+                .eq(PassengerEnterpriseMonthly::getReportPeriod, reportPeriod));
+        } else if ("H204".equals(reportType)) {
+            dataCount = energyMapper.selectCount(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+                .eq(EnergyVehicleQuarterly::getReportPeriod, reportPeriod));
+        } else if ("INVEST".equals(reportType)) {
+            dataCount = investMonthlyMapper.selectCount(new LambdaQueryWrapper<InvestmentMonthly>()
+                .eq(InvestmentMonthly::getReportPeriod, reportPeriod));
+        } else if ("CITY_BUS".equals(reportType)) {
+            dataCount = cityBusMapper.selectCount(new LambdaQueryWrapper<CityBusMonthly>()
+                .eq(CityBusMonthly::getReportPeriod, reportPeriod));
+        } else if ("CITY_TAXI".equals(reportType)) {
+            dataCount = cityTaxiMapper.selectCount(new LambdaQueryWrapper<CityTaxiMonthly>()
+                .eq(CityTaxiMonthly::getReportPeriod, reportPeriod));
+        } else {
+            dataCount = h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+                .eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod));
+        }
+        if (dataCount <= 0) {
+            log.info("markAudited skip: no data for {} / {}", reportType, reportPeriod);
+            return false;
+        }
+        markAudited(reportType, reportPeriod, rules, true);
+        return true;
+    }
 
     public List<AuditResult> executeAudit(String reportPeriod) {
-        List<AuditRule> rules = ruleMapper.selectEnabledRules();
+        List<AuditRule> rules = rulesOf("H2032");
         List<H2032EnterpriseMonthly> reports = h2032Mapper.selectList(
             new LambdaQueryWrapper<H2032EnterpriseMonthly>()
                 .eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod));
@@ -90,15 +213,1062 @@
             }
         }
         log.info("Audit complete: {} reports, {} issues for {}", reports.size(), results.size(), reportPeriod);
+        if (!rules.isEmpty() && !reports.isEmpty()) markAudited("H2032", reportPeriod);
         return results;
     }
 
-    public List<AuditResult> queryResults(String reportPeriod) {
+    public List<AuditResult> executePassengerAudit(String reportPeriod) {
+        List<AuditRule> rules = rulesOf("H2031");
+        List<PassengerEnterpriseMonthly> reports = passengerMapper.selectList(
+            new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+                .eq(PassengerEnterpriseMonthly::getReportPeriod, reportPeriod));
+
+        Map<String, PassengerAuthVehicle> authMap = loadPassengerAuthMap(reportPeriod);
+        Map<String, PassengerEnterpriseMonthly> lastMonthMap = loadPassengerLastMonthMap(reportPeriod);
+
+        // 骞傜瓑锛氬彧娓呴櫎 H2031 瑙勫垯鐨勬棫瀹℃牳缁撴灉
+        List<Long> ruleIds = new ArrayList<>();
+        for (AuditRule rule : rules) ruleIds.add(rule.getId());
+        if (!ruleIds.isEmpty()) {
+            resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, reportPeriod)
+                .in(AuditResult::getRuleId, ruleIds));
+        }
+
+        Map<Long, AuditRule> ruleMap = new HashMap<>();
+        for (AuditRule rule : rules) ruleMap.put(rule.getId(), rule);
+
+        List<AuditResult> results = new ArrayList<>();
+        for (PassengerEnterpriseMonthly report : reports) {
+            for (AuditRule rule : rules) {
+                AuditResult result = checkPassengerRule(rule, report, authMap, lastMonthMap);
+                if (result != null) {
+                    result.setRuleName(rule.getRuleName());
+                    result.setRuleCode(rule.getRuleCode());
+                    result.setAlertLevel(rule.getAlertLevel());
+                    result.setEnterpriseName(report.getEnterpriseName());
+                    result.setVerifyExplanation(report.getVerifyExplanation());
+                    resultMapper.insert(result);
+                    results.add(result);
+                }
+            }
+        }
+        log.info("Passenger audit complete: {} reports, {} issues for {}", reports.size(), results.size(), reportPeriod);
+        if (!rules.isEmpty() && !reports.isEmpty()) markAudited("H2031", reportPeriod);
+        return results;
+    }
+
+
+    // ========== H204 鑳借�楀鏍革紙杞﹁締绮掑害锛屼笌杩愭斂杞﹁締淇℃伅璺ㄨ〃瀵规瘮锛� ==========
+
+    public List<AuditResult> executeEnergyAudit(String reportPeriod) {
+        List<AuditRule> rules = rulesOf("H204");
+        List<EnergyVehicleQuarterly> vehicles = energyMapper.selectList(
+            new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+                .eq(EnergyVehicleQuarterly::getReportPeriod, reportPeriod));
+
+        Map<String, EnergyAuthVehicle> authMap = new HashMap<>();
+        List<EnergyAuthVehicle> authList = energyAuthMapper.selectList(
+            new LambdaQueryWrapper<EnergyAuthVehicle>()
+                .eq(EnergyAuthVehicle::getReportPeriod, reportPeriod));
+        for (EnergyAuthVehicle v : authList) {
+            authMap.putIfAbsent(key(v.getPlateNo()), v);
+        }
+
+        // E007 鐜瘮鍩哄噯锛氫笂涓�瀛e害鍚岃溅鐗岀櫨鍏噷鍗曡��
+        String lastPeriod = prevQuarter(reportPeriod);
+        Map<String, EnergyVehicleQuarterly> lastQuarterMap = new HashMap<>();
+        if (lastPeriod != null) {
+            for (EnergyVehicleQuarterly v : energyMapper.selectList(
+                    new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+                        .eq(EnergyVehicleQuarterly::getReportPeriod, lastPeriod))) {
+                lastQuarterMap.putIfAbsent(key(v.getPlateNo()), v);
+            }
+        }
+
+        // E006 瓒呰寖鍥村熀鍑嗭細鏈湡鍚勭噧鏂欑被鍨嬬櫨鍏噷鍗曡�椾腑浣嶆暟
+        Map<String, Double> fuelMedianMap = fuelPer100kmMedian(vehicles);
+
+        // E008/E009 瀵规瘮鍩哄噯锛氬悓鏈� H2032 浼佷笟鏈堟姤锛堟寜浼佷笟鍚嶇储寮曪級
+        Map<String, H2032EnterpriseMonthly> h2032Map = new HashMap<>();
+        for (H2032EnterpriseMonthly m : h2032Mapper.selectList(
+                new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+                    .eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod))) {
+            String ent = m.getEnterpriseName();
+            if (ent != null && !ent.trim().isEmpty()) h2032Map.putIfAbsent(ent.trim(), m);
+        }
+
+        // 骞傜瓑锛氬彧娓呴櫎 H204 瑙勫垯鐨勬棫瀹℃牳缁撴灉
+        List<Long> ruleIds = new ArrayList<>();
+        for (AuditRule rule : rules) ruleIds.add(rule.getId());
+        if (!ruleIds.isEmpty()) {
+            resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, reportPeriod)
+                .in(AuditResult::getRuleId, ruleIds));
+        }
+
+        List<AuditResult> results = new ArrayList<>();
+        for (EnergyVehicleQuarterly vehicle : vehicles) {
+            for (AuditRule rule : rules) {
+                AuditResult result = checkEnergyRule(rule, vehicle, authMap, fuelMedianMap, lastQuarterMap, h2032Map);
+                if (result != null) {
+                    result.setRuleName(rule.getRuleName());
+                    result.setRuleCode(rule.getRuleCode());
+                    result.setAlertLevel(rule.getAlertLevel());
+                    result.setEnterpriseName(vehicle.getEnterpriseName());
+                    result.setVerifyExplanation(null);
+                    resultMapper.insert(result);
+                    results.add(result);
+                }
+            }
+        }
+        log.info("Energy audit complete: {} vehicles, {} issues for {}", vehicles.size(), results.size(), reportPeriod);
+        if (!rules.isEmpty() && !vehicles.isEmpty()) markAudited("H204", reportPeriod);
+        return results;
+    }
+
+    private AuditResult checkEnergyRule(AuditRule rule, EnergyVehicleQuarterly vehicle,
+                                        Map<String, EnergyAuthVehicle> authMap,
+                                        Map<String, Double> fuelMedianMap,
+                                        Map<String, EnergyVehicleQuarterly> lastQuarterMap,
+                                        Map<String, H2032EnterpriseMonthly> h2032Map) {
+        try {
+            switch (rule.getCompareType()) {
+                case "CROSS_DIFF":
+                    return checkEnergyCrossDiff(rule, vehicle, authMap);
+                case "EXISTENCE_AUTH":
+                    return checkEnergyExistence(rule, vehicle, authMap);
+                case "LOADED_RATIO_DIFF":
+                    return checkEnergyLoadedRatio(rule, vehicle);
+                case "FUEL_RANGE":
+                    return checkEnergyFuelRange(rule, vehicle, fuelMedianMap);
+                case "FUEL_MOM":
+                    return checkEnergyFuelMom(rule, vehicle, lastQuarterMap);
+                case "FREIGHT_VS_H2032":
+                    return checkEnergyVsFreightReport(rule, vehicle, h2032Map);
+                case "AVG_DIST_VS_H2032":
+                    return checkEnergyAvgDistVsFreight(rule, vehicle, h2032Map);
+                case "LOADED_TON_RATIO":
+                    return checkEnergyLoadedTonnageRatio(rule, vehicle);
+                default:
+                    return null;
+            }
+        } catch (Exception e) {
+            log.error("Energy rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
+            return null;
+        }
+    }
+
+    /** 璺ㄨ〃瀛楁瀵规瘮锛氳溅杈嗙被鍨�/鐕冩枡绫诲瀷鎸夌爜琛ㄦ瘮瀵癸紝鏍囪鍚ㄤ綅锛堢壍寮曡溅鐢ㄥ噯鐗靛紩璐ㄩ噺锛夋寜闃堝�兼瘮瀵� */
+    private AuditResult checkEnergyCrossDiff(AuditRule rule, EnergyVehicleQuarterly vehicle,
+                                             Map<String, EnergyAuthVehicle> authMap) {
+        EnergyAuthVehicle auth = authMap.get(key(vehicle.getPlateNo()));
+        if (auth == null) return null; // 杩愭斂缂哄け鐢� EXISTENCE_AUTH 瑙勫垯鎻愮ず
+
+        String field = rule.getCheckField();
+        if ("vehicleType".equals(field)) {
+            java.util.Set<String> allowed = ENERGY_VEHICLE_TYPE_ALLOWED.get(vehicle.getVehicleTypeCode());
+            if (allowed == null || !allowed.contains(normalizeVehicleType(auth.getVehicleType()))) {
+                return buildEnergyResult(rule, vehicle,
+                    "鑳借��=" + valueOf(vehicle.getVehicleType()) + "(鐮�" + valueOf(vehicle.getVehicleTypeCode())
+                        + "), 杩愭斂=" + valueOf(auth.getVehicleType()),
+                    "杞﹁締绫诲瀷涓�鑷�", "杞﹁締绫诲瀷涓嶄竴鑷�");
+            }
+            return null;
+        }
+        if ("fuelType".equals(field)) {
+            java.util.Set<String> allowed = ENERGY_FUEL_TYPE_ALLOWED.get(vehicle.getFuelTypeCode());
+            if (allowed == null || !allowed.contains(key(auth.getFuelType()))) {
+                return buildEnergyResult(rule, vehicle,
+                    "鑳借��=" + valueOf(vehicle.getFuelType()) + "(鐮�" + valueOf(vehicle.getFuelTypeCode())
+                        + "), 杩愭斂=" + valueOf(auth.getFuelType()),
+                    "鐕冩枡绫诲瀷涓�鑷�", "鐕冩枡绫诲瀷涓嶄竴鑷�");
+            }
+            return null;
+        }
+        if ("markedTonnage".equals(field)) {
+            double actual = nvl(vehicle.getMarkedTonnage());
+            double reference = "11".equals(vehicle.getVehicleTypeCode())
+                ? nvl(auth.getTractionQuality())
+                : nvl(auth.getMarkedTonnage());
+            double threshold = Double.parseDouble(rule.getThreshold());
+            double diff = Math.abs(actual - reference);
+            if (diff > threshold) {
+                String refName = "11".equals(vehicle.getVehicleTypeCode()) ? "鍑嗙壍寮曡川閲�" : "鏍囪鍚ㄤ綅";
+                return buildEnergyResult(rule, vehicle,
+                    "鑳借�楁爣璁板惃浣�=" + fmt(actual) + ", 杩愭斂" + refName + "=" + fmt(reference),
+                    "宸窛鈮�" + fmt(threshold), "宸窛=" + fmt(diff));
+            }
+            return null;
+        }
+        return null;
+    }
+
+    /** 鑳借�楁槑缁嗚溅鐗屽湪杩愭斂杞﹁締淇℃伅涓笉瀛樺湪鏃舵彁绀� */
+    private AuditResult checkEnergyExistence(AuditRule rule, EnergyVehicleQuarterly vehicle,
+                                             Map<String, EnergyAuthVehicle> authMap) {
+        EnergyAuthVehicle auth = authMap.get(key(vehicle.getPlateNo()));
+        if (auth == null) {
+            return buildEnergyResult(rule, vehicle,
+                "杞︾墝鍙�=" + vehicle.getPlateNo(), "杩愭斂杞﹁締淇℃伅瀛樺湪", "杩愭斂杞﹁締淇℃伅缂哄け");
+        }
+        return null;
+    }
+
+    /** E005 杞借揣閲岀▼鍗犳瘮 vs 骞冲潎杩愯窛/鍗曡稛骞冲潎閲岀▼ 鐩稿樊卤10% */
+    private AuditResult checkEnergyLoadedRatio(AuditRule rule, EnergyVehicleQuarterly vehicle) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.1);
+        double total = nvl(vehicle.getTotalMileage());
+        double loaded = nvl(vehicle.getLoadedMileage());
+        double avgDist = nvl(vehicle.getAvgDistance());
+        double avgTrip = nvl(vehicle.getAvgTripMileage());
+        if (total <= 0 || avgTrip <= 0 || avgDist <= 0) return null;
+        double r1 = loaded / total;
+        double r2 = avgDist / avgTrip;
+        if (r2 <= 0) return null;
+        double diff = Math.abs(r1 - r2) / r2;
+        if (diff <= threshold) return null;
+        return buildEnergyResult(rule, vehicle,
+            "杞借揣閲岀▼/鎬昏绋�=" + fmt4(r1) + "锛屽钩鍧囪繍璺�/鍗曡稛骞冲潎閲岀▼=" + fmt4(r2),
+            "涓よ�呯浉宸墹" + fmt(threshold * 100) + "%",
+            "鐩稿樊=" + fmt(diff * 100) + "%");
+    }
+
+    /** E006 鍚岀噧鏂欑被鍨嬬櫨鍏噷鍗曡�楀亸绂讳腑浣嶆暟瓒呴槇鍊硷紙榛樿 50%锛� */
+    private AuditResult checkEnergyFuelRange(AuditRule rule, EnergyVehicleQuarterly vehicle,
+                                             Map<String, Double> fuelMedianMap) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.5);
+        Double v = vehicle.getFuelPer100km();
+        if (v == null || v <= 0 || vehicle.getFuelTypeCode() == null) return null;
+        Double median = fuelMedianMap.get(vehicle.getFuelTypeCode().trim());
+        if (median == null || median <= 0) return null;
+        double diff = Math.abs(v - median) / median;
+        if (diff <= threshold) return null;
+        return buildEnergyResult(rule, vehicle,
+            "鐧惧叕閲屽崟鑰�=" + fmt(v) + "锛�" + valueOf(vehicle.getFuelType()) + "锛夛紝鍚岀噧鏂欎腑浣嶆暟=" + fmt(median),
+            "鍋忕涓綅鏁扳墹" + fmt(threshold * 100) + "%",
+            "鍋忕=" + fmt(diff * 100) + "%");
+    }
+
+    /** E007 鍚屼竴杈嗚溅鐧惧叕閲屽崟鑰楃幆姣斿彉鍖栬秴闃堝�硷紙榛樿 卤50%锛� */
+    private AuditResult checkEnergyFuelMom(AuditRule rule, EnergyVehicleQuarterly vehicle,
+                                           Map<String, EnergyVehicleQuarterly> lastQuarterMap) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.5);
+        Double cur = vehicle.getFuelPer100km();
+        if (cur == null || cur <= 0) return null;
+        EnergyVehicleQuarterly last = lastQuarterMap.get(key(vehicle.getPlateNo()));
+        if (last == null || last.getFuelPer100km() == null || last.getFuelPer100km() <= 0) return null;
+        double diff = Math.abs(cur - last.getFuelPer100km()) / last.getFuelPer100km();
+        if (diff <= threshold) return null;
+        return buildEnergyResult(rule, vehicle,
+            "鏈湡鐧惧叕閲屽崟鑰�=" + fmt(cur) + "锛屼笂瀛e害=" + fmt(last.getFuelPer100km()),
+            "鐜瘮鍙樺寲鈮�" + fmt(threshold * 100) + "%",
+            "鐜瘮鍙樺寲=" + fmt(diff * 100) + "%");
+    }
+
+    /** E008 鑳借�楄揣杩愰噺/鍛ㄨ浆閲忎笌 H2032 鏈堟姤瀵规瘮瓒呴槇鍊硷紙榛樿 50%锛� */
+    private AuditResult checkEnergyVsFreightReport(AuditRule rule, EnergyVehicleQuarterly vehicle,
+                                                   Map<String, H2032EnterpriseMonthly> h2032Map) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.5);
+        String ent = vehicle.getEnterpriseName();
+        if (ent == null || ent.trim().isEmpty()) return null;
+        H2032EnterpriseMonthly m = h2032Map.get(ent.trim());
+        if (m == null) return null;
+        List<String> msgs = new ArrayList<>();
+        if (m.getFreightTotal() != null && m.getFreightTotal() > 0 && vehicle.getFreight() != null) {
+            double r = vehicle.getFreight() / m.getFreightTotal();
+            if (r > threshold) {
+                msgs.add("鑳借�楄揣杩愰噺=" + fmt(vehicle.getFreight()) + "锛屾湀鎶�=" + fmt(m.getFreightTotal())
+                    + "锛屽崰姣�=" + fmt4(r));
+            }
+        }
+        if (m.getTurnoverTotal() != null && m.getTurnoverTotal() > 0 && vehicle.getTurnover() != null) {
+            double r = vehicle.getTurnover() / m.getTurnoverTotal();
+            if (r > threshold) {
+                msgs.add("鑳借�楀懆杞噺=" + fmt(vehicle.getTurnover()) + "锛屾湀鎶�=" + fmt(m.getTurnoverTotal())
+                    + "锛屽崰姣�=" + fmt4(r));
+            }
+        }
+        if (msgs.isEmpty()) return null;
+        return buildEnergyResult(rule, vehicle, String.join("锛�", msgs),
+            "鑳借�楅噺鈮ゆ湀鎶�" + fmt(threshold * 100) + "%", "瓒呰繃鏈堟姤" + fmt(threshold * 100) + "%");
+    }
+
+    /** E009 杞﹁締骞冲潎杩愯窛涓� H2032 鏈堟姤骞冲潎杩愯窛鐩稿樊瓒呴槇鍊硷紙榛樿 卤60%锛� */
+    private AuditResult checkEnergyAvgDistVsFreight(AuditRule rule, EnergyVehicleQuarterly vehicle,
+                                                    Map<String, H2032EnterpriseMonthly> h2032Map) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.6);
+        String ent = vehicle.getEnterpriseName();
+        if (ent == null || ent.trim().isEmpty()) return null;
+        H2032EnterpriseMonthly m = h2032Map.get(ent.trim());
+        if (m == null || m.getAvgDistance() == null || m.getAvgDistance() <= 0) return null;
+        double cur = nvl(vehicle.getAvgDistance());
+        if (cur <= 0) return null;
+        double diff = Math.abs(cur - m.getAvgDistance()) / m.getAvgDistance();
+        if (diff <= threshold) return null;
+        return buildEnergyResult(rule, vehicle,
+            "鑳借�楀钩鍧囪繍璺�=" + fmt(cur) + "锛屾湀鎶�=" + fmt(m.getAvgDistance()),
+            "鐩稿樊鈮�" + fmt(threshold * 100) + "%",
+            "鐩稿樊=" + fmt(diff * 100) + "%");
+    }
+
+    /** E010 骞冲潎杞借揣鍚ㄤ綅/鏍囪鍚ㄤ綅 姣斿�艰秴鍑� [0.5, 1] */
+    private AuditResult checkEnergyLoadedTonnageRatio(AuditRule rule, EnergyVehicleQuarterly vehicle) {
+        double lo = 0.5, hi = 1.0;
+        String t = rule.getThreshold();
+        if (t != null && t.contains(",")) {
+            String[] parts = t.split(",");
+            try {
+                lo = Double.parseDouble(parts[0].trim());
+                hi = Double.parseDouble(parts[1].trim());
+            } catch (Exception ignore) {
+            }
+        }
+        double marked = nvl(vehicle.getMarkedTonnage());
+        double loaded = nvl(vehicle.getAvgLoadedTonnage());
+        if (marked <= 0 || loaded <= 0) return null;
+        double r = loaded / marked;
+        if (r >= lo && r <= hi) return null;
+        return buildEnergyResult(rule, vehicle,
+            "骞冲潎杞借揣鍚ㄤ綅=" + fmt(loaded) + "锛屾爣璁板惃浣�=" + fmt(marked) + "锛屾瘮鍊�=" + fmt4(r),
+            "姣斿�煎湪[" + fmt(lo) + "," + fmt(hi) + "]",
+            "姣斿��=" + fmt4(r) + " 瓒呭嚭鑼冨洿");
+    }
+
+    /** 涓婁竴瀛e害鎶ヨ〃鏈燂細2026-06 鈫� 2026-03 */
+    private String prevQuarter(String period) {
+        if (period == null || period.length() < 7) return null;
+        try {
+            int year = Integer.parseInt(period.substring(0, 4));
+            int month = Integer.parseInt(period.substring(5, 7));
+            int pm = month - 3;
+            int py = year;
+            if (pm <= 0) {
+                pm += 12;
+                py -= 1;
+            }
+            return String.format("%04d-%02d", py, pm);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /** 鏈湡鍚勭噧鏂欑被鍨嬬櫨鍏噷鍗曡�椾腑浣嶆暟 */
+    private Map<String, Double> fuelPer100kmMedian(List<EnergyVehicleQuarterly> vehicles) {
+        Map<String, List<Double>> byFuel = new HashMap<>();
+        for (EnergyVehicleQuarterly v : vehicles) {
+            if (v.getFuelTypeCode() == null || v.getFuelPer100km() == null || v.getFuelPer100km() <= 0) continue;
+            byFuel.computeIfAbsent(v.getFuelTypeCode().trim(), k -> new ArrayList<>()).add(v.getFuelPer100km());
+        }
+        Map<String, Double> med = new HashMap<>();
+        for (Map.Entry<String, List<Double>> e : byFuel.entrySet()) {
+            List<Double> list = e.getValue();
+            list.sort(null);
+            double m = list.size() % 2 == 1 ? list.get(list.size() / 2)
+                : (list.get(list.size() / 2 - 1) + list.get(list.size() / 2)) / 2.0;
+            med.put(e.getKey(), m);
+        }
+        return med;
+    }
+
+    private Map<String, EnergyAuthVehicle> loadEnergyAuthMap(String reportPeriod) {
+        Map<String, EnergyAuthVehicle> map = new HashMap<>();
+        List<EnergyAuthVehicle> list = energyAuthMapper.selectList(
+            new LambdaQueryWrapper<EnergyAuthVehicle>()
+                .eq(EnergyAuthVehicle::getReportPeriod, reportPeriod));
+        for (EnergyAuthVehicle v : list) {
+            map.putIfAbsent(key(v.getPlateNo()), v);
+        }
+        return map;
+    }
+
+    /** 杩愭斂杞﹁締绫诲瀷鍘绘帀 閲嶅瀷/涓瀷/杞诲瀷 鍓嶇紑鍚庢瘮瀵� */
+    private String normalizeVehicleType(String type) {
+        if (type == null) return "";
+        String t = type.trim();
+        for (String prefix : new String[]{"閲嶅瀷", "涓瀷", "杞诲瀷"}) {
+            if (t.startsWith(prefix)) {
+                t = t.substring(prefix.length());
+                break;
+            }
+        }
+        return t;
+    }
+
+    private String valueOf(String v) {
+        return v == null ? "绌�" : v.trim();
+    }
+
+    private AuditResult buildEnergyResult(AuditRule rule, EnergyVehicleQuarterly vehicle,
+                                          String actual, String threshold, String deviation) {
+        AuditResult result = new AuditResult();
+        result.setRuleId(rule.getId());
+        result.setReportId(vehicle.getId());
+        result.setEnterpriseCode(vehicle.getEnterpriseCode());
+        result.setReportPeriod(vehicle.getReportPeriod());
+        result.setActualValue(actual);
+        result.setThresholdValue(threshold);
+        result.setDeviation(deviation);
+        result.setStatus("PENDING");
+        return result;
+    }
+
+
+    // ========== INVEST 鎶曡祫瀹℃牳锛堟眹鎬诲ぇ琛� 涓� 鎶曡祫绯荤粺瀵煎嚭 璺ㄨ〃瀵规瘮锛� ==========
+
+    public List<AuditResult> executeInvestmentAudit(String reportPeriod) {
+        List<AuditRule> rules = rulesOf("INVEST");
+        List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList(
+            new LambdaQueryWrapper<InvestmentMonthly>()
+                .eq(InvestmentMonthly::getReportPeriod, reportPeriod));
+        Map<Long, InvestmentProject> projectMap = new HashMap<>();
+        for (InvestmentProject p : investProjectMapper.selectList(null)) {
+            projectMap.put(p.getId(), p);
+        }
+        Map<String, List<InvestmentSystem>> sysMap = new HashMap<>();
+        for (InvestmentSystem s : investSystemMapper.selectList(
+                new LambdaQueryWrapper<InvestmentSystem>()
+                    .eq(InvestmentSystem::getReportPeriod, reportPeriod))) {
+            sysMap.computeIfAbsent(normInvestName(s.getProjectName()), k -> new ArrayList<>()).add(s);
+        }
+
+        // 骞傜瓑锛氬彧娓呴櫎 INVEST 瑙勫垯鐨勬棫瀹℃牳缁撴灉
+        List<Long> ruleIds = new ArrayList<>();
+        for (AuditRule rule : rules) ruleIds.add(rule.getId());
+        if (!ruleIds.isEmpty()) {
+            resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, reportPeriod)
+                .in(AuditResult::getRuleId, ruleIds));
+        }
+
+        List<AuditResult> results = new ArrayList<>();
+        for (InvestmentMonthly monthly : monthlies) {
+            InvestmentProject project = projectMap.get(monthly.getProjectId());
+            String projectName = project == null ? "鏈煡椤圭洰" : project.getProjectName();
+            InvestmentSystem sys = findInvestSystem(sysMap, projectName);
+            for (AuditRule rule : rules) {
+                AuditResult result = checkInvestRule(rule, monthly, projectName, sys);
+                if (result != null) {
+                    result.setRuleName(rule.getRuleName());
+                    result.setRuleCode(rule.getRuleCode());
+                    result.setAlertLevel(rule.getAlertLevel());
+                    result.setEnterpriseName(projectName);
+                    result.setVerifyExplanation(null);
+                    resultMapper.insert(result);
+                    results.add(result);
+                }
+            }
+        }
+        log.info("Investment audit complete: {} projects, {} issues for {}", monthlies.size(), results.size(), reportPeriod);
+        if (!rules.isEmpty() && !monthlies.isEmpty()) markAudited("INVEST", reportPeriod);
+        return results;
+    }
+
+    private AuditResult checkInvestRule(AuditRule rule, InvestmentMonthly monthly,
+                                        String projectName, InvestmentSystem sys) {
+        try {
+            switch (rule.getCompareType()) {
+                case "CROSS_DIFF":
+                    return checkInvestCrossDiff(rule, monthly, projectName, sys);
+                case "EXISTENCE_SYS":
+                    return checkInvestExistence(rule, monthly, projectName, sys);
+                default:
+                    return null;
+            }
+        } catch (Exception e) {
+            log.error("Investment rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
+            return null;
+        }
+    }
+
+    /** 涓庢姇璧勭郴缁熷鍑烘暟鍊煎姣旓細闃堝�兼牸寮� "缁濆宸畖鐩稿宸�%" */
+    private AuditResult checkInvestCrossDiff(AuditRule rule, InvestmentMonthly monthly,
+                                             String projectName, InvestmentSystem sys) {
+        if (sys == null) return null; // 缂哄け鐢� EXISTENCE_SYS 瑙勫垯鎻愮ず
+        String field = rule.getCheckField();
+        double actual;
+        double reference;
+        String fieldName;
+        if ("startCum".equals(field)) {
+            actual = nvl(monthly.getStartCum());
+            reference = nvl(sys.getStartCum());
+            fieldName = "鑷紑濮嬪缓璁剧疮璁�";
+        } else if ("yearCum".equals(field)) {
+            actual = nvl(monthly.getYearCum());
+            reference = nvl(sys.getYearCum());
+            fieldName = "鑷勾鍒濈疮璁�";
+        } else if ("monthDone".equals(field)) {
+            actual = nvl(monthly.getMonthDone());
+            reference = nvl(sys.getMonthDone());
+            fieldName = "鏈湀瀹屾垚";
+        } else {
+            return null;
+        }
+        String[] parts = rule.getThreshold().split("\\|");
+        double absThreshold = parts.length > 0 ? parseDoubleSafe(parts[0], 50) : 50;
+        double pctThreshold = parts.length > 1 ? parseDoubleSafe(parts[1], 5) : 5;
+        double diff = Math.abs(actual - reference);
+        double base = Math.max(Math.abs(reference), 1.0);
+        double pct = diff / base * 100;
+        boolean hit = diff > absThreshold && pct > pctThreshold;
+        if (!hit && reference == 0 && actual > absThreshold) {
+            hit = true;
+        }
+        if (hit) {
+            return buildInvestResult(rule, monthly,
+                "姹囨�诲ぇ琛�" + fieldName + "=" + fmt(actual) + ", 鎶曡祫绯荤粺=" + fmt(reference),
+                "缁濆宸墹" + fmt(absThreshold) + "涓� 涓� 鐩稿宸墹" + fmt(pctThreshold) + "%",
+                "缁濆宸�=" + fmt(diff) + "涓�, 鐩稿宸�=" + fmt(Math.round(pct * 10) / 10.0) + "%");
+        }
+        return null;
+    }
+
+    /** 姹囨�诲ぇ琛ㄩ」鐩湪鎶曡祫绯荤粺瀵煎嚭涓笉瀛樺湪 */
+    private AuditResult checkInvestExistence(AuditRule rule, InvestmentMonthly monthly,
+                                             String projectName, InvestmentSystem sys) {
+        if (sys == null) {
+            return buildInvestResult(rule, monthly,
+                "椤圭洰鍚嶇О=" + projectName, "鎶曡祫绯荤粺瀵煎嚭涓瓨鍦�", "鎶曡祫绯荤粺瀵煎嚭涓笉瀛樺湪");
+        }
+        return null;
+    }
+
+    private AuditResult buildInvestResult(AuditRule rule, InvestmentMonthly monthly,
+                                          String actual, String threshold, String deviation) {
+        AuditResult result = new AuditResult();
+        result.setRuleId(rule.getId());
+        result.setReportId(monthly.getId());
+        result.setReportPeriod(monthly.getReportPeriod());
+        result.setActualValue(actual);
+        result.setThresholdValue(threshold);
+        result.setDeviation(deviation);
+        result.setStatus("PENDING");
+        return result;
+    }
+
+    // ========== CITY_BUS 鍩庡競鍏氦瀹℃牳锛�4 缁勮鍒欙紝浼佷笟绾э級 ==========
+
+    public List<AuditResult> executeCityBusAudit(String reportPeriod) {
+        List<AuditRule> rules = rulesOf("CITY_BUS");
+        List<CityBusMonthly> rows = cityBusMapper.selectList(
+            new LambdaQueryWrapper<CityBusMonthly>()
+                .eq(CityBusMonthly::getReportPeriod, reportPeriod));
+
+        // 骞傜瓑锛氬彧娓呴櫎 CITY_BUS 瑙勫垯鐨勬棫瀹℃牳缁撴灉
+        List<Long> ruleIds = new ArrayList<>();
+        for (AuditRule rule : rules) ruleIds.add(rule.getId());
+        if (!ruleIds.isEmpty()) {
+            resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, reportPeriod)
+                .in(AuditResult::getRuleId, ruleIds));
+        }
+
+        List<AuditResult> results = new ArrayList<>();
+        for (CityBusMonthly row : rows) {
+            // 鏃犲叕浜ょ粡钀ユ椿鍔ㄤ紒涓氾紙杩愯惀杞︽暟/瀹㈣繍閲�/鍛ㄨ浆閲忓潎 0锛夎烦杩�
+            if (nvl(row.getOpVehicles()) <= 0 && nvl(row.getPassengerVolume()) <= 0
+                    && nvl(row.getTurnover()) <= 0) {
+                continue;
+            }
+            for (AuditRule rule : rules) {
+                AuditResult result = checkCityBusRule(rule, row);
+                if (result != null) {
+                    result.setRuleName(rule.getRuleName());
+                    result.setRuleCode(rule.getRuleCode());
+                    result.setAlertLevel(rule.getAlertLevel());
+                    result.setEnterpriseName(row.getEnterpriseName());
+                    result.setVerifyExplanation(row.getVerifyExplanation());
+                    resultMapper.insert(result);
+                    results.add(result);
+                }
+            }
+        }
+        log.info("CityBus audit complete: {} enterprises, {} issues for {}", rows.size(), results.size(), reportPeriod);
+        if (!rules.isEmpty() && !rows.isEmpty()) markAudited("CITY_BUS", reportPeriod);
+        return results;
+    }
+
+    private AuditResult checkCityBusRule(AuditRule rule, CityBusMonthly row) {
+        try {
+            switch (rule.getCompareType()) {
+                case "TREND_CONSISTENCY":
+                    return checkCityBusTrend(rule, row);
+                case "RATIO_CHANGE":
+                    return checkCityBusRatioChange(rule, row);
+                case "AVG_DIST_CHANGE":
+                    return checkCityBusAvgDistChange(rule, row);
+                default:
+                    return null;
+            }
+        } catch (Exception e) {
+            log.error("CityBus rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
+            return null;
+        }
+    }
+
+    /** 瓒嬪娍涓�鑷达細check_field 涓垚瀵瑰瓧娈碉紙鐜瘮/鍚屾瘮锛夎秼鍔夸笉涓�鑷翠笖澧為�熻秴闃堝�奸渶鏍稿疄 */
+    private AuditResult checkCityBusTrend(AuditRule rule, CityBusMonthly row) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.02);
+        String field = rule.getCheckField() == null ? "" : rule.getCheckField();
+        List<String> msgs = new ArrayList<>();
+        if (field.contains("passengerCityMom")) {
+            checkTrendPair(msgs, "鍩庡競鍐呭杩愰噺鐜瘮", row.getRatePassengerCityMom(),
+                "鍩庨檯鍩庝埂瀹㈣繍閲忕幆姣�", row.getRateChengxiangMom(), threshold);
+        }
+        if (field.contains("passengerCityYoy")) {
+            checkTrendPair(msgs, "鍩庡競鍐呭杩愰噺鍚屾瘮", row.getRatePassengerCityYoy(),
+                "鍩庨檯鍩庝埂瀹㈣繍閲忓悓姣�", row.getRateChengxiangYoy(), threshold);
+        }
+        if (field.contains("passengerMom")) {
+            checkTrendPair(msgs, "瀹㈣繍閲忕幆姣�", row.getRatePassengerMom(),
+                "鏃呭鍛ㄨ浆閲忕幆姣�", row.getRateTurnoverMom(), threshold);
+        }
+        if (field.contains("passengerYoy")) {
+            checkTrendPair(msgs, "瀹㈣繍閲忓悓姣�", row.getRatePassengerYoy(),
+                "鏃呭鍛ㄨ浆閲忓悓姣�", row.getRateTurnoverYoy(), threshold);
+        }
+        if (msgs.isEmpty()) return null;
+        return buildCityBusResult(rule, row, String.join("锛�", msgs),
+            "涓ゆ寚鏍囩幆姣�/鍚屾瘮瓒嬪娍涓�鑷�", "瓒嬪娍涓嶄竴鑷翠笖澧為�熻秴闃堝��");
+    }
+
+    private void checkTrendPair(List<String> msgs, String aName, Double aVal,
+                                String bName, Double bVal, double threshold) {
+        double a = nvl(aVal);
+        double b = nvl(bVal);
+        boolean aUp = a > 0, bUp = b > 0;
+        boolean aDown = a < 0, bDown = b < 0;
+        if ((aUp && bDown) || (aDown && bUp)) {
+            double maxAbs = Math.max(Math.abs(a), Math.abs(b));
+            if (maxAbs > threshold) {
+                msgs.add(aName + "=" + fmt(a) + ", " + bName + "=" + fmt(b) + "锛堣秼鍔夸笉涓�鑷达級");
+            }
+        }
+    }
+
+    /** 鍗犳瘮鍙樺寲锛氭湰鏈堝崰姣� vs 鐢辩幆姣斿閫熷弽鎺ㄧ殑涓婃湀鍗犳瘮锛屽彉鍖�>1% 闇�鏍稿疄 */
+    private AuditResult checkCityBusRatioChange(AuditRule rule, CityBusMonthly row) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.01);
+        String field = rule.getCheckField() == null ? "" : rule.getCheckField();
+        List<String> msgs = new ArrayList<>();
+        if (field.contains("passengerCityRatio")) {
+            Double change = cityBusRatioChange(row.getPassengerCityRatio(),
+                row.getPassengerCity(), row.getRatePassengerCityMom(),
+                row.getPassengerVolume(), row.getRatePassengerMom());
+            if (change != null && change > threshold) {
+                msgs.add("鍩庡競鍐呭杩愰噺鍗犳瘮鍙樺寲=" + fmt4(change) + "锛�>1%锛�");
+            }
+        }
+        if (field.contains("chengxiangRatio")) {
+            Double change = cityBusRatioChange(row.getChengxiangRatio(),
+                row.getPassengerChengxiang(), row.getRateChengxiangMom(),
+                row.getPassengerVolume(), row.getRatePassengerMom());
+            if (change != null && change > threshold) {
+                msgs.add("鍩庨檯鍩庝埂瀹㈣繍閲忓崰姣斿彉鍖�=" + fmt4(change) + "锛�>1%锛�");
+            }
+        }
+        if (msgs.isEmpty()) return null;
+        return buildCityBusResult(rule, row, String.join("锛�", msgs),
+            "鍗犳瘮鍙樺寲鈮�1%", "鍗犳瘮鍙樺寲>1%");
+    }
+
+    /** 鍗犳瘮鐜瘮鍙樺寲锛氭湰鏈堝崰姣� vs 涓婃湀鍗犳瘮锛堢敱鐜瘮澧為�熷弽鎺級锛涙棤娉曟帹绠楄繑鍥� null */
+    private Double cityBusRatioChange(Double curRatio, Double curValue, Double curRate,
+                                      Double curTotal, Double totalRate) {
+        if (curRatio == null) return null;
+        double r = nvl(curRate);
+        double tr = nvl(totalRate);
+        if (r <= -1.0 || tr <= -1.0) return null;
+        double lastRatio = (nvl(curValue) / (1 + r)) / (nvl(curTotal) / (1 + tr));
+        if (Double.isNaN(lastRatio) || Double.isInfinite(lastRatio)) return null;
+        return Math.abs(curRatio - lastRatio);
+    }
+
+    /** 骞冲潎杩愯窛鍙樺寲锛氱幆姣斿鍑忥紙鍏噷锛夋垨鍩庨檯鍩庝埂鐢� 鍛ㄨ浆閲�/瀹㈣繍閲� 鐜瘮鍙嶆帹锛岃秴杩� 3 鍏噷闇�鏍稿疄 */
+    private AuditResult checkCityBusAvgDistChange(AuditRule rule, CityBusMonthly row) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 3.0);
+        String field = rule.getCheckField() == null ? "" : rule.getCheckField();
+        List<String> msgs = new ArrayList<>();
+        if (field.contains("avgDistanceMomChange")) {
+            double chg = Math.abs(nvl(row.getAvgDistanceMomChange()));
+            if (chg > threshold) {
+                msgs.add("骞冲潎杩愯窛鐜瘮澧炲噺=" + fmt(nvl(row.getAvgDistanceMomChange())) + "鍏噷");
+            }
+        }
+        if (field.contains("avgDistanceCityMomChange")) {
+            double chg = Math.abs(nvl(row.getAvgDistanceCityMomChange()));
+            if (chg > threshold) {
+                msgs.add("鍩庡競鍐呭钩鍧囪繍璺濈幆姣斿鍑�=" + fmt(nvl(row.getAvgDistanceCityMomChange())) + "鍏噷");
+            }
+        }
+        if (field.contains("avgDistanceChengxiang")) {
+            double cur = nvl(row.getAvgDistanceChengxiang());
+            double km = nvl(row.getRateChengxiangMom());
+            double kt = nvl(row.getRateChengxiangTurnoverMom());
+            if (cur > 0 && km > -1.0 && kt > -1.0) {
+                double last = cur * (1 + km) / (1 + kt);
+                double chg = Math.abs(cur - last);
+                if (chg > threshold) {
+                    msgs.add("鍩庨檯鍩庝埂骞冲潎杩愯窛鍙樺寲=" + fmt(chg) + "鍏噷锛堟湰鏈�" + fmt(cur)
+                        + " vs 涓婃湀" + fmt(last) + "锛�");
+                }
+            }
+        }
+        if (msgs.isEmpty()) return null;
+        return buildCityBusResult(rule, row, String.join("锛�", msgs),
+            "骞冲潎杩愯窛鍙樺寲鈮�" + fmt(threshold) + "鍏噷", "骞冲潎杩愯窛鍙樺寲>" + fmt(threshold) + "鍏噷");
+    }
+
+    private AuditResult buildCityBusResult(AuditRule rule, CityBusMonthly row,
+                                           String actual, String threshold, String deviation) {
+        AuditResult result = new AuditResult();
+        result.setRuleId(rule.getId());
+        result.setReportId(row.getId());
+        result.setReportPeriod(row.getReportPeriod());
+        result.setActualValue(actual);
+        result.setThresholdValue(threshold);
+        result.setDeviation(deviation);
+        result.setStatus("PENDING");
+        return result;
+    }
+
+    // ========== CITY_TAXI 宸℃父鍑虹瀹℃牳锛�7 缁勮鍒欙紝甯傚窞绾э級 ==========
+
+    public List<AuditResult> executeCityTaxiAudit(String reportPeriod) {
+        List<AuditRule> rules = rulesOf("CITY_TAXI");
+        List<CityTaxiMonthly> rows = cityTaxiMapper.selectList(
+            new LambdaQueryWrapper<CityTaxiMonthly>()
+                .eq(CityTaxiMonthly::getReportPeriod, reportPeriod));
+
+        // 骞傜瓑锛氬彧娓呴櫎 CITY_TAXI 瑙勫垯鐨勬棫瀹℃牳缁撴灉
+        List<Long> ruleIds = new ArrayList<>();
+        for (AuditRule rule : rules) ruleIds.add(rule.getId());
+        if (!ruleIds.isEmpty()) {
+            resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, reportPeriod)
+                .in(AuditResult::getRuleId, ruleIds));
+        }
+
+        // 杩愭斂杞﹁締鎸夊競宸炶鏁帮紙T001 瀵规瘮鍩哄噯锛�
+        Map<String, Long> authCount = loadTaxiAuthCount(reportPeriod);
+
+        List<AuditResult> results = new ArrayList<>();
+        for (CityTaxiMonthly row : rows) {
+            if (row.getCity() == null || row.getCity().trim().isEmpty()) continue;
+            // 鏃犵粡钀ユ椿鍔ㄥ競宸烇紙杩愯惀杞︽暟/瀹㈣繍閲�/鍛ㄨ浆閲忓潎 0锛夎烦杩�
+            if (nvl(row.getOpVehicles()) <= 0 && nvl(row.getPassengerVolume()) <= 0
+                    && nvl(row.getTurnover()) <= 0) {
+                continue;
+            }
+            for (AuditRule rule : rules) {
+                AuditResult result = checkCityTaxiRule(rule, row, authCount);
+                if (result != null) {
+                    result.setRuleName(rule.getRuleName());
+                    result.setRuleCode(rule.getRuleCode());
+                    result.setAlertLevel(rule.getAlertLevel());
+                    result.setEnterpriseName(row.getCity());
+                    result.setVerifyExplanation(row.getVerifyExplanation());
+                    resultMapper.insert(result);
+                    results.add(result);
+                }
+            }
+        }
+        log.info("CityTaxi audit complete: {} cities, {} issues for {}", rows.size(), results.size(), reportPeriod);
+        if (!rules.isEmpty() && !rows.isEmpty()) markAudited("CITY_TAXI", reportPeriod);
+        return results;
+    }
+
+    private Map<String, Long> loadTaxiAuthCount(String reportPeriod) {
+        Map<String, Long> map = new HashMap<>();
+        List<CityTaxiAuth> auths = cityTaxiAuthMapper.selectList(
+            new LambdaQueryWrapper<CityTaxiAuth>()
+                .eq(CityTaxiAuth::getReportPeriod, reportPeriod));
+        for (CityTaxiAuth a : auths) {
+            if (a.getCity() == null || a.getCity().trim().isEmpty()) continue;
+            map.merge(a.getCity(), 1L, Long::sum);
+        }
+        return map;
+    }
+
+    private AuditResult checkCityTaxiRule(AuditRule rule, CityTaxiMonthly row, Map<String, Long> authCount) {
+        try {
+            switch (rule.getCompareType()) {
+                case "CROSS_DIFF_PCT":
+                    return checkTaxiAuthDiff(rule, row, authCount);
+                case "TREND_CONSISTENCY":
+                    return checkCityTaxiTrend(rule, row);
+                case "RATIO_CHANGE":
+                    return checkCityTaxiRatioChange(rule, row);
+                case "AVG_DIST_CHANGE":
+                    return checkCityTaxiAvgDistChange(rule, row);
+                case "PAX_PER_TRIP_CHANGE":
+                    return checkTaxiPaxPerTripChange(rule, row);
+                default:
+                    return null;
+            }
+        } catch (Exception e) {
+            log.error("CityTaxi rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
+            return null;
+        }
+    }
+
+    /** T001 杩愯惀杞︽暟 vs 杩愭斂鍦ㄨ惀杞﹁締鏁帮細宸紓鐜�>3% 闇�鏍稿疄锛圧ED锛� */
+    private AuditResult checkTaxiAuthDiff(AuditRule rule, CityTaxiMonthly row, Map<String, Long> authCount) {
+        // 鏁存湡鏈鍏ヨ繍鏀挎暟鎹椂璺宠繃瀵规瘮锛堥伩鍏嶆妸鈥滆繍鏀�=0鈥濊褰撳熀鍑嗘姤宸紓锛�
+        if (authCount == null || authCount.isEmpty()) return null;
+        double threshold = parseDoubleSafe(rule.getThreshold(), 3.0);
+        double monthly = nvl(row.getOpVehicles());
+        Long auth = authCount.get(row.getCity());
+        long authV = auth == null ? 0 : auth;
+        if (monthly <= 0 && authV <= 0) return null;
+        double diffPct = authV <= 0 ? 100.0 : Math.abs(monthly - authV) / authV * 100.0;
+        if (diffPct <= threshold) return null;
+        return buildTaxiResult(rule, row,
+            "鏈堟姤杩愯惀杞﹁締鏁�=" + fmt(monthly) + "锛岃繍鏀垮湪钀ヨ溅杈嗘暟=" + authV + "锛屽樊寮傜巼=" + fmt(diffPct) + "%",
+            "宸紓鐜団墹" + fmt(threshold) + "%",
+            "宸紓鐜�=" + fmt(diffPct) + "%>" + fmt(threshold) + "%");
+    }
+
+    /** T002/T003/T007 瓒嬪娍涓�鑷达細check_field 涓垚瀵瑰瓧娈碉紙鐜瘮/鍚屾瘮锛夎秼鍔夸笉涓�鑷翠笖澧為�熻秴闃堝�奸渶鏍稿疄 */
+    private AuditResult checkCityTaxiTrend(AuditRule rule, CityTaxiMonthly row) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.02);
+        String field = rule.getCheckField() == null ? "" : rule.getCheckField();
+        List<String> msgs = new ArrayList<>();
+        if (field.contains("passengerCityMom")) {
+            checkTrendPair(msgs, "鍩庡競鍐呭杩愰噺鐜瘮", row.getRatePassengerCityMom(),
+                "鍩庨檯鍩庝埂瀹㈣繍閲忕幆姣�", row.getRatePassengerChengxiangMom(), threshold);
+        }
+        if (field.contains("turnoverMomDerived")) {
+            // 鏈堟姤鏃犵洿鎺ュ懆杞噺鐜瘮鍒楋細鍛ㄨ浆閲�=瀹㈣繍閲徝楀钩鍧囪繍璺濓紝鐢变袱鑰呯幆姣旀帹瀵�
+            Double rateTurnover = taxiTurnoverMomDerived(row);
+            if (rateTurnover != null) {
+                checkTrendPair(msgs, "瀹㈣繍閲忕幆姣�", row.getRatePassengerMom(),
+                    "鏃呭鍛ㄨ浆閲忕幆姣�(鎺ㄥ)", rateTurnover, threshold);
+            }
+        }
+        if (field.contains("tripMom")) {
+            checkTrendPair(msgs, "杞藉杞︽鎬绘暟鐜瘮", row.getRateTripMom(),
+                "瀹㈣繍閲忕幆姣�", row.getRatePassengerMom(), threshold);
+        }
+        if (field.contains("tripYoy")) {
+            checkTrendPair(msgs, "杞藉杞︽鍚屾瘮", row.getRateTripYoy(),
+                "瀹㈣繍閲忓悓姣�", row.getRatePassengerYoy(), threshold);
+        }
+        if (msgs.isEmpty()) return null;
+        return buildTaxiResult(rule, row, String.join("锛�", msgs),
+            "涓ゆ寚鏍囩幆姣�/鍚屾瘮瓒嬪娍涓�鑷�", "瓒嬪娍涓嶄竴鑷翠笖澧為�熻秴闃堝��");
+    }
+
+    /** 鎺ㄥ鍛ㄨ浆閲忕幆姣旓細rate鈮�(1+瀹㈣繍閲忕幆姣�)脳(1+骞冲潎杩愯窛鐜瘮)-1锛屽钩鍧囪繍璺濈幆姣旂敱涓婃湡杩愯窛=鏈湡鈭掑鍑忔帹绠� */
+    private Double taxiTurnoverMomDerived(CityTaxiMonthly row) {
+        double dist = nvl(row.getAvgDistance());
+        double chg = nvl(row.getAvgDistanceMomChange());
+        double rp = nvl(row.getRatePassengerMom());
+        if (dist <= 0 || dist - chg <= 0 || rp <= -1.0) return null;
+        double rd = chg / (dist - chg);
+        return (1 + rp) * (1 + rd) - 1;
+    }
+
+    /** T004 鍗犳瘮鍙樺寲锛氬煄甯傚唴瀹㈣繍閲忓崰姣斿彉鍖�>1% 闇�鏍稿疄 */
+    private AuditResult checkCityTaxiRatioChange(AuditRule rule, CityTaxiMonthly row) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.01);
+        String field = rule.getCheckField() == null ? "" : rule.getCheckField();
+        List<String> msgs = new ArrayList<>();
+        if (field.contains("passengerCityRatio")) {
+            Double change = cityBusRatioChange(row.getPassengerCityRatio(),
+                row.getPassengerCity(), row.getRatePassengerCityMom(),
+                row.getPassengerVolume(), row.getRatePassengerMom());
+            if (change != null && change > threshold) {
+                msgs.add("鍩庡競鍐呭杩愰噺鍗犳瘮鍙樺寲=" + fmt4(change) + "锛�>1%锛�");
+            }
+        }
+        if (msgs.isEmpty()) return null;
+        return buildTaxiResult(rule, row, String.join("锛�", msgs),
+            "鍗犳瘮鍙樺寲鈮�1%", "鍗犳瘮鍙樺寲>1%");
+    }
+
+    /** T005 骞冲潎杩愯窛鍙樺寲锛氱幆姣斿鍑忥紙鍏噷锛夋垨鍩庨檯鍩庝埂鐢卞钩鍧囪繍璺濆閫熷弽鎺紝瓒呰繃 3 鍏噷闇�鏍稿疄 */
+    private AuditResult checkCityTaxiAvgDistChange(AuditRule rule, CityTaxiMonthly row) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 3.0);
+        String field = rule.getCheckField() == null ? "" : rule.getCheckField();
+        List<String> msgs = new ArrayList<>();
+        if (field.contains("avgDistanceMomChange")) {
+            double chg = Math.abs(nvl(row.getAvgDistanceMomChange()));
+            if (chg > threshold) {
+                msgs.add("骞冲潎杩愯窛鐜瘮澧炲噺=" + fmt(nvl(row.getAvgDistanceMomChange())) + "鍏噷");
+            }
+        }
+        if (field.contains("avgDistanceCityMomChange")) {
+            double chg = Math.abs(nvl(row.getAvgDistanceCityMomChange()));
+            if (chg > threshold) {
+                msgs.add("鍩庡競鍐呭钩鍧囪繍璺濈幆姣斿鍑�=" + fmt(nvl(row.getAvgDistanceCityMomChange())) + "鍏噷");
+            }
+        }
+        if (field.contains("avgDistanceChengxiang")) {
+            double cur = nvl(row.getAvgDistanceChengxiang());
+            double dist = nvl(row.getAvgDistance());
+            double chg = nvl(row.getAvgDistanceMomChange());
+            if (cur > 0 && dist > 0 && dist > chg) {
+                double rd = chg / (dist - chg);
+                double last = cur / (1 + rd);
+                double diff = Math.abs(cur - last);
+                if (diff > threshold) {
+                    msgs.add("鍩庨檯鍩庝埂骞冲潎杩愯窛鍙樺寲=" + fmt(diff) + "鍏噷锛堟湰鏈�" + fmt(cur)
+                        + " vs 涓婃湀" + fmt(last) + "锛�");
+                }
+            }
+        }
+        if (msgs.isEmpty()) return null;
+        return buildTaxiResult(rule, row, String.join("锛�", msgs),
+            "骞冲潎杩愯窛鍙樺寲鈮�" + fmt(threshold) + "鍏噷", "骞冲潎杩愯窛鍙樺寲>" + fmt(threshold) + "鍏噷");
+    }
+
+    /** T006 鍗曡溅娆¤浇瀹汉鏁板彉鍖栵細鐢变笂鏈�(杞藉杞︽/瀹㈣繍閲忕幆姣�)鍙嶆帹锛屽彉鍖�>0.2浜� 闇�鏍稿疄 */
+    private AuditResult checkTaxiPaxPerTripChange(AuditRule rule, CityTaxiMonthly row) {
+        double threshold = parseDoubleSafe(rule.getThreshold(), 0.2);
+        String field = rule.getCheckField() == null ? "" : rule.getCheckField();
+        List<String> msgs = new ArrayList<>();
+        if (field.contains("passengersPerTrip")) {
+            double cur = nvl(row.getPassengersPerTrip());
+            double rp = nvl(row.getRatePassengerMom());
+            double rt = nvl(row.getRateTripMom());
+            if (cur > 0 && rp > -1.0 && rt > -1.0) {
+                double last = cur * (1 + rt) / (1 + rp);
+                double diff = Math.abs(cur - last);
+                if (diff > threshold) {
+                    msgs.add("鍗曡溅娆¤浇瀹汉鏁板彉鍖�=" + fmt(diff) + "浜猴紙鏈湀" + fmt(cur)
+                        + " vs 涓婃湀" + fmt(last) + "锛�");
+                }
+            }
+        }
+        if (msgs.isEmpty()) return null;
+        return buildTaxiResult(rule, row, String.join("锛�", msgs),
+            "鍗曡溅娆¤浇瀹汉鏁板彉鍖栤墹" + fmt(threshold) + "浜�", "鍗曡溅娆¤浇瀹汉鏁板彉鍖�>" + fmt(threshold) + "浜�");
+    }
+
+    private AuditResult buildTaxiResult(AuditRule rule, CityTaxiMonthly row,
+                                        String actual, String threshold, String deviation) {
+        AuditResult result = new AuditResult();
+        result.setRuleId(rule.getId());
+        result.setReportId(row.getId());
+        result.setReportPeriod(row.getReportPeriod());
+        result.setActualValue(actual);
+        result.setThresholdValue(threshold);
+        result.setDeviation(deviation);
+        result.setStatus("PENDING");
+        return result;
+    }
+
+    /** 椤圭洰鍚嶅綊涓�鍖栵細鍘绘嫭鍙峰瓧绗︺�佽繛鎺ョ锛岀綏椹暟瀛楃粺涓�锛屽幓鐪�/甯�/鍘�/鍖哄墠缂�涓庣┖鐧� */
+    private String normInvestName(String name) {
+        if (name == null) return "";
+        String s = name.trim();
+        s = s.replaceAll("[锛�(]", "").replaceAll("[锛�)]", "");
+        s = s.replaceAll("[路鈥⑩�旓紞_\\-]", "");
+        s = s.replaceAll("[鈪犫叀鈪⑩叄鈪も叆鈪︹収鈪ㄢ叐鈪扳叡鈪测叧鈪粹叺鈪垛叿鈪糕吂]", "I");
+        s = s.replaceAll("^(婀栧寳鐪亅姝︽眽甯倈榛勭煶甯倈鍗佸牥甯倈瀹滄槍甯倈瑗勯槼甯倈閯傚窞甯倈鑽嗛棬甯倈瀛濇劅甯倈鑽嗗窞甯倈榛勫唸甯倈鍜稿畞甯倈闅忓窞甯倈鎭╂柦宸瀨浠欐甯倈娼滄睙甯倈澶╅棬甯倈绁炲啘鏋舵灄鍖�)", "");
+        s = s.replaceAll("^(?:[\\u4e00-\\u9fa5]{2,4}(?:鍘縷甯倈鍖�))", "");
+        s = s.replaceAll("\\s+", "");
+        return s;
+    }
+
+    /** 鍚嶇О褰掍竴鍖栧悗绮剧‘鍖归厤锛堝悓鍚嶅鍊欓�夋寜鍘垮尯娑堟锛夛紝澶辫触鍐嶆寜璇勫垎鍋氬墠缂�/鍖呭惈/瀛愬簭鍒楁ā绯婂尮閰� */
+    private InvestmentSystem findInvestSystem(Map<String, List<InvestmentSystem>> sysMap, String projectName) {
+        String norm = normInvestName(projectName);
+        if (norm.isEmpty()) return null;
+        List<InvestmentSystem> exact = sysMap.get(norm);
+        if (exact != null && !exact.isEmpty()) {
+            return disambiguateInvest(exact, projectName);
+        }
+        InvestmentSystem best = null;
+        int bestScore = Integer.MIN_VALUE;
+        for (Map.Entry<String, List<InvestmentSystem>> e : sysMap.entrySet()) {
+            String key = e.getKey();
+            if (key.length() < 5 || norm.length() < 5) continue;
+            String shortSide = key.length() <= norm.length() ? key : norm;
+            String longSide = key.length() > norm.length() ? key : norm;
+            int idx = longSide.indexOf(shortSide);
+            int lenDiff = Math.abs(key.length() - norm.length());
+            int score;
+            if (idx == 0) {
+                // 鍓嶇紑寮忓寘鍚紙鐭悕=闀垮悕寮�澶达級鏈�鍙俊
+                score = 1000 - lenDiff * 2;
+            } else if (idx > 0 && shortSide.length() >= 6) {
+                // 涓悗閮ㄥ寘鍚細鐭悕瓒冲闀挎墠鍙俊
+                score = 500 - idx * 5 - lenDiff * 2 + shortSide.length();
+            } else if (idx > 0 && shortSide.length() == 5 && lenDiff <= 5) {
+                // 5瀛楃煭鍚嶄腑鍚庨儴锛氬樊寮傞儴鍒嗕笉鍚� 鍘�/甯�/宸� 鎵嶅尮閰嶏紙鎺掗櫎 缃楃敯鍘跨患鍚堢墿娴佸洯 杩欑被锛�
+                String diff = longSide.substring(0, idx) + longSide.substring(idx + shortSide.length());
+                if (diff.matches(".*(鍘縷甯倈宸�).*")) continue;
+                score = 400 - idx * 5 - lenDiff;
+            } else if (isInvestSubsequence(shortSide, longSide) && lenDiff <= 8) {
+                // 瀛愬簭鍒楀厹搴曪細鐭悕涓洪暱鍚嶅幓鎺夋彃鍏ヨ瘝鍚庣殑瀛愬簭鍒楋紙濡� 缁煎悎鏋㈢航 vs 缁煎悎瀹㈣繍鏋㈢航銆佺墿娴佷腑蹇� vs 鐗╂祦鍥腑蹇冿級
+                score = 300 - lenDiff * 3;
+            } else {
+                continue;
+            }
+            if (score > bestScore) {
+                bestScore = score;
+                best = disambiguateInvest(e.getValue(), projectName);
+            }
+        }
+        return best;
+    }
+
+    /** 澶氫釜绯荤粺椤圭洰褰掍竴鍖栧悗鍚屽悕锛堝 鍜镐赴鍘跨患鍚堢墿娴佸洯/缃楃敯鍘跨患鍚堢墿娴佸洯 鈫� 缁煎悎鐗╂祦鍥級锛屾寜鍘垮尯鍓嶇紑鍖哄垎 */
+    private InvestmentSystem disambiguateInvest(List<InvestmentSystem> candidates, String projectName) {
+        if (candidates == null || candidates.isEmpty()) return null;
+        if (candidates.size() == 1) return candidates.get(0);
+        String county = extractCounty(projectName);
+        if (!county.isEmpty()) {
+            for (InvestmentSystem c : candidates) {
+                String raw = c.getProjectName() == null ? "" : c.getProjectName();
+                if (raw.contains(county)) return c;
+            }
+        }
+        return candidates.get(0);
+    }
+
+    /** 鎻愬彇鍚嶇О涓殑 鍘�/甯�/鍖� 鍓嶇紑锛堝 鍜镐赴鍘匡級 */
+    private String extractCounty(String name) {
+        if (name == null) return "";
+        java.util.regex.Matcher m = java.util.regex.Pattern.compile("([\\u4e00-\\u9fa5]{2,4}(?:鍘縷甯倈鍖�))").matcher(name);
+        if (m.find()) return m.group(1);
+        return "";
+    }
+
+    /** 鍒ゆ柇鐭悕鏄惁涓洪暱鍚嶇殑瀛楃瀛愬簭鍒楋紙鎸夐『搴忓嚭鐜板嵆鍙紝鍏佽涓棿鎻掑叆璇嶏級 */
+    private boolean isInvestSubsequence(String shortSide, String longSide) {
+        int i = 0;
+        for (int j = 0; i < shortSide.length() && j < longSide.length(); j++) {
+            if (shortSide.charAt(i) == longSide.charAt(j)) i++;
+        }
+        return i == shortSide.length();
+    }
+
+    private double parseDoubleSafe(String v, double def) {
+        try {
+            return Double.parseDouble(v.trim());
+        } catch (Exception e) {
+            return def;
+        }
+    }
+
+    public List<AuditResult> queryResults(String reportPeriod, String reportType) {
+        String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim();
         List<AuditResult> results = resultMapper.selectList(new LambdaQueryWrapper<AuditResult>()
             .eq(AuditResult::getReportPeriod, reportPeriod)
             .orderByAsc(AuditResult::getId));
-        attachInfo(results);
-        return results;
+        List<Long> ruleIds = new ArrayList<>();
+        for (AuditRule rule : ruleMapper.selectList(null)) {
+            if (type.equals(rule.getReportType())) ruleIds.add(rule.getId());
+        }
+        List<AuditResult> filtered = new ArrayList<>();
+        for (AuditResult result : results) {
+            if (ruleIds.contains(result.getRuleId())) filtered.add(result);
+        }
+        attachInfo(filtered);
+        return filtered;
     }
 
     public void reviewResult(Long id, String status, String comment) {
@@ -114,11 +1284,43 @@
         if (results.isEmpty()) return;
         Map<Long, AuditRule> ruleMap = new HashMap<>();
         for (AuditRule rule : ruleMapper.selectList(null)) ruleMap.put(rule.getId(), rule);
-        Map<Long, String> nameMap = new HashMap<>();
-        Map<Long, String> explainMap = new HashMap<>();
+        Map<Long, String> h2032Name = new HashMap<>();
+        Map<Long, String> h2032Explain = new HashMap<>();
+        Map<Long, String> passengerName = new HashMap<>();
+        Map<Long, String> passengerExplain = new HashMap<>();
+        Map<Long, String> energyName = new HashMap<>();
+        Map<Long, String> investName = new HashMap<>();
+        Map<Long, String> busName = new HashMap<>();
+        Map<Long, String> busExplain = new HashMap<>();
+        Map<Long, String> taxiName = new HashMap<>();
+        Map<Long, String> taxiExplain = new HashMap<>();
         for (H2032EnterpriseMonthly r : h2032Mapper.selectList(null)) {
-            nameMap.putIfAbsent(r.getId(), r.getEnterpriseName());
-            explainMap.putIfAbsent(r.getId(), r.getVerifyExplanation());
+            h2032Name.putIfAbsent(r.getId(), r.getEnterpriseName());
+            h2032Explain.putIfAbsent(r.getId(), r.getVerifyExplanation());
+        }
+        for (PassengerEnterpriseMonthly r : passengerMapper.selectList(null)) {
+            passengerName.putIfAbsent(r.getId(), r.getEnterpriseName());
+            passengerExplain.putIfAbsent(r.getId(), r.getVerifyExplanation());
+        }
+        for (EnergyVehicleQuarterly r : energyMapper.selectList(null)) {
+            energyName.putIfAbsent(r.getId(), r.getEnterpriseName());
+        }
+        for (CityBusMonthly r : cityBusMapper.selectList(null)) {
+            busName.putIfAbsent(r.getId(), r.getEnterpriseName());
+            busExplain.putIfAbsent(r.getId(), r.getVerifyExplanation());
+        }
+        for (CityTaxiMonthly r : cityTaxiMapper.selectList(null)) {
+            taxiName.putIfAbsent(r.getId(), r.getCity());
+            taxiExplain.putIfAbsent(r.getId(), r.getVerifyExplanation());
+        }
+        Map<Long, Long> investProjectId = new HashMap<>();
+        for (InvestmentMonthly r : investMonthlyMapper.selectList(null)) {
+            investProjectId.putIfAbsent(r.getId(), r.getProjectId());
+        }
+        for (InvestmentProject p : investProjectMapper.selectList(null)) {
+            for (Map.Entry<Long, Long> e : investProjectId.entrySet()) {
+                if (e.getValue().equals(p.getId())) investName.putIfAbsent(e.getKey(), p.getProjectName());
+            }
         }
         for (AuditResult result : results) {
             AuditRule rule = ruleMap.get(result.getRuleId());
@@ -127,6 +1329,16 @@
                 result.setRuleCode(rule.getRuleCode());
                 result.setAlertLevel(rule.getAlertLevel());
             }
+            // 鍚勬姤琛ㄨ〃 id 鍚勮嚜鐙珛鑷锛岄渶鎸夎鍒欐墍灞炴姤琛ㄧ被鍨嬪垎琛ㄥ洖濉紒涓氬悕绉�
+            String type = rule == null ? null : rule.getReportType();
+            Map<Long, String> nameMap = "INVEST".equals(type) ? investName
+                : "H204".equals(type) ? energyName
+                : "H2031".equals(type) ? passengerName
+                : "CITY_BUS".equals(type) ? busName
+                : "CITY_TAXI".equals(type) ? taxiName : h2032Name;
+            Map<Long, String> explainMap = "H2031".equals(type) ? passengerExplain
+                : "CITY_BUS".equals(type) ? busExplain
+                : "CITY_TAXI".equals(type) ? taxiExplain : h2032Explain;
             String enterpriseName = nameMap.get(result.getReportId());
             if (enterpriseName == null) {
                 // 涓婃姤鏁版嵁琚噸鏂板鍏ュ悗鏃у鏍哥粨鏋滃凡澶辨晥锛岀粰鍑烘彁绀鸿�岄潪绌虹櫧
@@ -135,6 +1347,214 @@
             result.setEnterpriseName(enterpriseName);
             result.setVerifyExplanation(explainMap.get(result.getReportId()));
         }
+    }
+
+    /** 鏈�杩戞湁鏁版嵁鐨勬姤琛ㄦ湡锛堢敤浜庡墠绔粯璁ら�変腑锛� */
+    public String latestPeriod(String reportType) {
+        String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim();
+        String max = null;
+        switch (type) {
+            case "H2031":
+                for (PassengerEnterpriseMonthly m : passengerMapper.selectList(
+                        new LambdaQueryWrapper<PassengerEnterpriseMonthly>().select(PassengerEnterpriseMonthly::getReportPeriod))) {
+                    max = maxPeriod(max, m.getReportPeriod());
+                }
+                break;
+            case "H204":
+                for (EnergyVehicleQuarterly m : energyMapper.selectList(
+                        new LambdaQueryWrapper<EnergyVehicleQuarterly>().select(EnergyVehicleQuarterly::getReportPeriod))) {
+                    max = maxPeriod(max, m.getReportPeriod());
+                }
+                break;
+            case "INVEST":
+                for (InvestmentMonthly m : investMonthlyMapper.selectList(
+                        new LambdaQueryWrapper<InvestmentMonthly>().select(InvestmentMonthly::getReportPeriod))) {
+                    max = maxPeriod(max, m.getReportPeriod());
+                }
+                break;
+            case "CITY_BUS":
+                for (CityBusMonthly m : cityBusMapper.selectList(
+                        new LambdaQueryWrapper<CityBusMonthly>().select(CityBusMonthly::getReportPeriod))) {
+                    max = maxPeriod(max, m.getReportPeriod());
+                }
+                break;
+            case "CITY_TAXI":
+                for (CityTaxiMonthly m : cityTaxiMapper.selectList(
+                        new LambdaQueryWrapper<CityTaxiMonthly>().select(CityTaxiMonthly::getReportPeriod))) {
+                    max = maxPeriod(max, m.getReportPeriod());
+                }
+                break;
+            default:
+                for (H2032EnterpriseMonthly m : h2032Mapper.selectList(
+                        new LambdaQueryWrapper<H2032EnterpriseMonthly>().select(H2032EnterpriseMonthly::getReportPeriod))) {
+                    max = maxPeriod(max, m.getReportPeriod());
+                }
+                break;
+        }
+        return max;
+    }
+
+    private String maxPeriod(String a, String b) {
+        if (b == null || b.trim().isEmpty()) return a;
+        if (a == null || a.compareTo(b) < 0) return b;
+        return a;
+    }
+
+    /** 瀹℃牳缁撴灉瀵煎嚭 Excel锛堝惈浼佷笟鍚�/瑙勫垯鍚�/鐘舵��/瀹℃牳鎰忚锛� */
+    public byte[] exportResults(String reportPeriod, String reportType) throws Exception {
+        List<AuditResult> results = queryResults(reportPeriod, reportType);
+        try (XSSFWorkbook wb = new XSSFWorkbook()) {
+            Sheet sheet = wb.createSheet("瀹℃牳缁撴灉");
+            String[] headers = {"鎶ヨ〃鏈�", "浼佷笟/甯傚窞", "瑙勫垯缂栫爜", "瀹℃牳瑙勫垯", "绛夌骇", "瀹為檯鍊�", "闃堝��/鍩哄噯", "鍋忓樊璇存槑", "鐘舵��", "瀹℃牳鎰忚"};
+            Row head = sheet.createRow(0);
+            for (int i = 0; i < headers.length; i++) {
+                Cell cell = head.createCell(i);
+                cell.setCellValue(headers[i]);
+                org.apache.poi.ss.usermodel.CellStyle style = wb.createCellStyle();
+                org.apache.poi.ss.usermodel.Font font = wb.createFont();
+                font.setBold(true);
+                style.setFont(font);
+                cell.setCellStyle(style);
+            }
+            int r = 1;
+            for (AuditResult res : results) {
+                Row row = sheet.createRow(r++);
+                row.createCell(0).setCellValue(valueOf(res.getReportPeriod()));
+                row.createCell(1).setCellValue(valueOf(res.getEnterpriseName()));
+                row.createCell(2).setCellValue(valueOf(res.getRuleCode()));
+                row.createCell(3).setCellValue(valueOf(res.getRuleName()));
+                row.createCell(4).setCellValue(valueOf(res.getAlertLevel()));
+                row.createCell(5).setCellValue(valueOf(res.getActualValue()));
+                row.createCell(6).setCellValue(valueOf(res.getThresholdValue()));
+                row.createCell(7).setCellValue(valueOf(res.getDeviation()));
+                row.createCell(8).setCellValue(auditStatusCn(res.getStatus()));
+                row.createCell(9).setCellValue(valueOf(res.getReviewComment()));
+            }
+            int[] widths = {12, 28, 12, 28, 8, 36, 22, 36, 10, 28};
+            for (int i = 0; i < widths.length; i++) {
+                sheet.setColumnWidth(i, widths[i] * 256);
+            }
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            wb.write(out);
+            return out.toByteArray();
+        }
+    }
+
+    private String auditStatusCn(String status) {
+        if ("CONFIRMED".equals(status)) return "宸茬‘璁�";
+        if ("IGNORED".equals(status)) return "宸插拷鐣�";
+        return "寰呭鐞�";
+    }
+
+    /** 瑙勫垯鎵ц缁熻锛氭瘡鏉″惎鐢ㄨ鍒欑殑妫�鏌ュ璞℃暟涓庡懡涓暟 */
+    public List<Map<String, Object>> ruleStats(String reportPeriod, String reportType) {
+        String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim();
+        List<AuditRule> rules = rulesOf(type);
+        long checked;
+        switch (type) {
+            case "H2031":
+                checked = passengerMapper.selectCount(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+                    .eq(PassengerEnterpriseMonthly::getReportPeriod, reportPeriod));
+                break;
+            case "H204":
+                checked = energyMapper.selectCount(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+                    .eq(EnergyVehicleQuarterly::getReportPeriod, reportPeriod));
+                break;
+            case "INVEST":
+                checked = investMonthlyMapper.selectCount(new LambdaQueryWrapper<InvestmentMonthly>()
+                    .eq(InvestmentMonthly::getReportPeriod, reportPeriod));
+                break;
+            case "CITY_BUS":
+                checked = cityBusMapper.selectCount(new LambdaQueryWrapper<CityBusMonthly>()
+                    .eq(CityBusMonthly::getReportPeriod, reportPeriod));
+                break;
+            case "CITY_TAXI":
+                checked = cityTaxiMapper.selectCount(new LambdaQueryWrapper<CityTaxiMonthly>()
+                    .eq(CityTaxiMonthly::getReportPeriod, reportPeriod));
+                break;
+            default:
+                checked = h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+                    .eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod));
+                break;
+        }
+        List<AuditResult> results = resultMapper.selectList(new LambdaQueryWrapper<AuditResult>()
+            .eq(AuditResult::getReportPeriod, reportPeriod));
+        Map<Long, Long> hitMap = new HashMap<>();
+        for (AuditResult r : results) {
+            hitMap.merge(r.getRuleId(), 1L, Long::sum);
+        }
+        List<Map<String, Object>> out = new ArrayList<>();
+        for (AuditRule rule : rules) {
+            Map<String, Object> m = new HashMap<>();
+            m.put("ruleId", rule.getId());
+            m.put("ruleCode", rule.getRuleCode());
+            m.put("ruleName", rule.getRuleName());
+            m.put("alertLevel", rule.getAlertLevel());
+            m.put("checked", checked);
+            m.put("hit", hitMap.getOrDefault(rule.getId(), 0L));
+            m.put("skipped", ruleSkipped(rule, reportPeriod));
+            out.add(m);
+        }
+        return out;
+    }
+
+    /** 璺ㄨ〃/鐜瘮鍩哄噯鏁版嵁鏁存湡缂哄け鏃讹紝瑙勫垯鏍囪涓鸿烦杩囷紙閬垮厤璇姤锛� */
+    private boolean ruleSkipped(AuditRule rule, String period) {
+        String code = rule.getRuleCode();
+        if (code == null) return false;
+        switch (code) {
+            case "RULE_001":
+            case "RULE_002":
+                return transportAuthMapper.selectCount(new LambdaQueryWrapper<TransportAuthVehicle>()
+                    .eq(TransportAuthVehicle::getReportPeriod, period)) == 0;
+            case "RULE_003":
+                return trackMileageMapper.selectCount(new LambdaQueryWrapper<VehicleTrackMileage>()
+                    .eq(VehicleTrackMileage::getReportPeriod, period)) == 0;
+            case "RULE_004":
+                return h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+                    .eq(H2032EnterpriseMonthly::getReportPeriod, getLastPeriod(period))) == 0;
+            case "RULE_P001":
+            case "RULE_P002":
+                return passengerAuthMapper.selectCount(new LambdaQueryWrapper<PassengerAuthVehicle>()
+                    .eq(PassengerAuthVehicle::getReportPeriod, period)) == 0;
+            case "RULE_P003":
+            case "RULE_P004":
+            case "RULE_P005":
+            case "RULE_P006":
+                return passengerMapper.selectCount(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+                    .eq(PassengerEnterpriseMonthly::getReportPeriod, getLastPeriod(period))) == 0;
+            case "RULE_E001":
+            case "RULE_E002":
+            case "RULE_E003":
+                return energyAuthMapper.selectCount(new LambdaQueryWrapper<EnergyAuthVehicle>()
+                    .eq(EnergyAuthVehicle::getReportPeriod, period)) == 0;
+            case "RULE_E008":
+            case "RULE_E009":
+                return h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+                    .eq(H2032EnterpriseMonthly::getReportPeriod, period)) == 0;
+            case "RULE_T001":
+                return cityTaxiAuthMapper.selectCount(new LambdaQueryWrapper<CityTaxiAuth>()
+                    .eq(CityTaxiAuth::getReportPeriod, period)) == 0;
+            case "RULE_I001":
+            case "RULE_I002":
+            case "RULE_I003":
+                return investSystemMapper.selectCount(new LambdaQueryWrapper<InvestmentSystem>()
+                    .eq(InvestmentSystem::getReportPeriod, period)) == 0;
+            default:
+                return false;
+        }
+    }
+
+    private List<AuditRule> rulesOf(String reportType) {
+        List<AuditRule> all = ruleMapper.selectEnabledRules();
+        List<AuditRule> matched = new ArrayList<>();
+        for (AuditRule rule : all) {
+            String type = rule.getReportType();
+            if (reportType.equals(type) || (type == null && "H2032".equals(reportType))) {
+                matched.add(rule);
+            }
+        }
+        return matched;
     }
 
     private AuditResult checkRule(AuditRule rule, H2032EnterpriseMonthly report,
@@ -152,7 +1572,7 @@
                 case "NULL_CHECK":
                     return checkNull(rule, report);
                 case "OUT_RANGE":
-                    return checkOutRange(rule, report, authMap);
+                    return checkOutRange(rule, report);
                 default:
                     return null;
             }
@@ -187,7 +1607,7 @@
         return null;
     }
 
-    /** 涓婃姤鍛ㄨ浆閲忎笌杞ㄨ抗娴嬬畻鍛ㄨ浆閲忓姣旓紝楂樺嚭30%鏍稿疄 */
+    /** 涓婃姤鍛ㄨ浆閲忎笌杞ㄨ抗娴嬬畻鍛ㄨ浆閲忓姣旓紝楂樺嚭闃堝�兼瘮渚嬫牳瀹� */
     private AuditResult checkRatio(AuditRule rule, H2032EnterpriseMonthly report,
                                    Map<String, TransportAuthVehicle> authMap,
                                    Map<String, VehicleTrackMileage> trackMap) {
@@ -215,67 +1635,59 @@
 
         if (ratio > threshold) {
             return buildResult(rule, report,
-                "涓婃姤鍛ㄨ浆閲�=" + fmt(actual) + ", 杞ㄨ抗娴嬬畻=" + Math.round(estimatedTurnover),
-                "姣斿�尖墹" + fmt(threshold), "姣斿��=" + fmt(ratio));
+                "涓婃姤鍛ㄨ浆閲�=" + fmt(actual) + ", 杞ㄨ抗娴嬬畻=" + fmt(estimatedTurnover),
+                "鈮よ建杩规祴绠椕�" + fmt(threshold), "=杞ㄨ抗娴嬬畻脳" + fmt(ratio));
         }
         return null;
     }
 
-    /** 璐ц繍绫诲瀷鐜瘮锛氭湰鏈堟柊澧炶揣杩愮被鍨嬫牳瀹烇紙澶氱殑鏍稿疄锛屽皯鐨勪笉绠★級 */
+    /** 璐ц繍绫诲瀷鐜瘮锛氭湰鏈堟柊澧烇紙涓婃湀涓�0銆佹湰鏈�>0锛夐渶鏍稿疄锛屽皯鐨勪笉绠� */
     private AuditResult checkMom(AuditRule rule, H2032EnterpriseMonthly report,
                                  Map<String, H2032EnterpriseMonthly> lastMonthMap) {
         H2032EnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode()));
         if (lastMonth == null) return null;
-
-        String[] fields = {"freightContainer", "freightCoal", "freightOilGas", "freightCrudeOil",
-            "freightMetalOre", "freightIronOre", "freightBuilding", "freightGrain"};
-        List<String> newTypes = new ArrayList<>();
-        for (String field : fields) {
-            double current = getFieldValue(report, field);
-            double last = getFieldValue(lastMonth, field);
+        List<String> hits = new ArrayList<>();
+        for (String field : rule.getCheckField().split(",")) {
+            double current = getH2032FieldValue(report, field);
+            double last = getH2032FieldValue(lastMonth, field);
             if (current > 0 && last == 0) {
-                newTypes.add(FREIGHT_TYPE_NAMES.get(field) + "=" + fmt(current) + "鍚�");
+                hits.add(h2032FieldLabel(field) + "=" + fmt(current));
             }
         }
-        if (!newTypes.isEmpty()) {
-            return buildResult(rule, report, "鏈湀鏂板璐х被: " + String.join("銆�", newTypes),
-                "涓婃湀涓�0", "鐜瘮鏂板");
+        if (!hits.isEmpty()) {
+            return buildResult(rule, report, "鏈湀鏂板: " + String.join("銆�", hits),
+                "涓婃湀璇ョ被鍨嬩负0", "鏈湀鏂板璐ц繍绫诲瀷锛堜笂鏈堟棤锛�");
         }
         return null;
     }
 
-    /** 鏃犻泦瑁呯杞︿絾鏈夐泦瑁呯璐ц繍閲� */
+    /** 鏈夎揣杩愰噺浣嗗搴旇溅杈嗕负0 闇�鏍稿疄锛堝 鏈夐泦瑁呯璐ц繍閲忔棤闆嗚绠辫溅杈嗭級 */
     private AuditResult checkNull(AuditRule rule, H2032EnterpriseMonthly report) {
-        double containerFreight = nvl(report.getFreightContainer());
-        double containerVehicle = nvl(report.getVehicleContainer());
-        if (containerFreight > 0 && containerVehicle == 0) {
-            return buildResult(rule, report, "闆嗚绠辫揣杩愰噺=" + fmt(containerFreight),
-                "鏈夐泦瑁呯杞�", "鏃犻泦瑁呯杞︿絾鏈夐泦瑁呯璐ц繍閲�");
+        String[] fields = rule.getCheckField().split(",");
+        if (fields.length < 2) return null;
+        double volume = getH2032FieldValue(report, fields[0]);
+        double vehicle = getH2032FieldValue(report, fields[1]);
+        if (volume > 0 && vehicle == 0) {
+            return buildResult(rule, report,
+                h2032FieldLabel(fields[0]) + "=" + fmt(volume) + ", " + h2032FieldLabel(fields[1]) + "=0",
+                "鏈�" + h2032FieldLabel(fields[0]) + "搴旀湁瀵瑰簲杞﹁締", "鏈�" + h2032FieldLabel(fields[0]) + "浣嗘棤" + h2032FieldLabel(fields[1]));
         }
         return null;
     }
 
-    /** 鏁磋溅鍚ㄤ綅涓嶅湪(4,40]鑼冨洿鍐呮牳瀹� */
-    private AuditResult checkOutRange(AuditRule rule, H2032EnterpriseMonthly report,
-                                      Map<String, TransportAuthVehicle> authMap) {
-        TransportAuthVehicle auth = findAuth(authMap, report.getEnterpriseName());
-        if (auth == null) return null;
-        if (num(auth.getOtherCount()) == 0) return null;
-
-        double wholeTonnage = nvl(auth.getOtherTons()) / num(auth.getOtherCount());
-
-        String[] range = rule.getThreshold().split(",");
-        double min = Double.parseDouble(range[0]);
-        double max = Double.parseDouble(range[1]);
-
-        if (wholeTonnage < min || wholeTonnage > max) {
-            return buildResult(rule, report, "鏁磋溅鍚ㄤ綅=" + fmt(wholeTonnage),
-                fmt(min) + " 鈮� 鏁磋溅鍚ㄤ綅 鈮� " + fmt(max), "瓒呭嚭鑼冨洿");
+    /** 鏁板�艰寖鍥存鏌ワ紙濡� 鏁磋溅鍚ㄤ綅 4鈮や笖鈮�40锛� */
+    private AuditResult checkOutRange(AuditRule rule, H2032EnterpriseMonthly report) {
+        String[] bounds = rule.getThreshold().split(",");
+        if (bounds.length < 2) return null;
+        double value = getH2032FieldValue(report, rule.getCheckField());
+        double low = Double.parseDouble(bounds[0].trim());
+        double high = Double.parseDouble(bounds[1].trim());
+        if (value > 0 && (value < low || value > high)) {
+            return buildResult(rule, report, h2032FieldLabel(rule.getCheckField()) + "=" + fmt(value),
+                "鑼冨洿 " + fmt(low) + "~" + fmt(high), "瓒呭嚭鑼冨洿");
         }
         return null;
     }
-
-    // ========== 杈呭姪鏂规硶 ==========
 
     private AuditResult buildResult(AuditRule rule, H2032EnterpriseMonthly report,
                                     String actual, String threshold, String deviation) {
@@ -291,6 +1703,10 @@
         return result;
     }
 
+    private TransportAuthVehicle findAuth(Map<String, TransportAuthVehicle> authMap, String name) {
+        return authMap.get(key(name));
+    }
+
     private Map<String, TransportAuthVehicle> loadAuthMap(String reportPeriod) {
         Map<String, TransportAuthVehicle> map = new HashMap<>();
         List<TransportAuthVehicle> list = transportAuthMapper.selectList(
@@ -302,11 +1718,11 @@
         return map;
     }
 
-    private Map<String, VehicleTrackMileage> loadTrackMap(String period) {
+    private Map<String, VehicleTrackMileage> loadTrackMap(String reportPeriod) {
         Map<String, VehicleTrackMileage> map = new HashMap<>();
         List<VehicleTrackMileage> list = trackMileageMapper.selectList(
             new LambdaQueryWrapper<VehicleTrackMileage>()
-                .eq(VehicleTrackMileage::getReportPeriod, period));
+                .eq(VehicleTrackMileage::getReportPeriod, reportPeriod));
         for (VehicleTrackMileage v : list) {
             map.putIfAbsent(key(v.getEnterpriseName()), v);
         }
@@ -325,40 +1741,20 @@
         return map;
     }
 
-    private TransportAuthVehicle findAuth(Map<String, TransportAuthVehicle> authMap, String name) {
-        return authMap.get(key(name));
-    }
-
-    private String key(String s) {
-        return s == null ? "" : s.trim();
-    }
-
-    /** double 鏄剧ず鏍煎紡鍖栵細閬垮厤绉戝璁℃暟娉曪紝鏁存暟鍊间笉甯﹀皬鏁� */
-    private String fmt(double v) {
-        if (Double.isNaN(v) || Double.isInfinite(v)) return String.valueOf(v);
-        if (v == Math.rint(v) && Math.abs(v) < 1e15) {
-            return String.valueOf((long) v);
+    private String getLastPeriod(String period) {
+        String[] parts = period.split("-");
+        int year = Integer.parseInt(parts[0]);
+        int month = Integer.parseInt(parts[1]);
+        if (month == 1) {
+            year--;
+            month = 12;
+        } else {
+            month--;
         }
-        String s = String.format("%.6f", v);
-        if (s.indexOf('.') >= 0) {
-            s = s.replaceAll("0+$", "").replaceAll("\\.$", "");
-        }
-        return s;
+        return year + "-" + (month < 10 ? "0" + month : String.valueOf(month));
     }
 
-    private double nvl(Double v) {
-        return v == null ? 0.0 : v;
-    }
-
-    private double nvl(Integer v) {
-        return v == null ? 0.0 : v;
-    }
-
-    private int num(Integer v) {
-        return v == null ? 0 : v;
-    }
-
-    private double getFieldValue(H2032EnterpriseMonthly report, String fieldName) {
+    private double getH2032FieldValue(H2032EnterpriseMonthly report, String fieldName) {
         try {
             java.lang.reflect.Field field = H2032EnterpriseMonthly.class.getDeclaredField(fieldName);
             field.setAccessible(true);
@@ -372,16 +1768,257 @@
         }
     }
 
-    private String getLastPeriod(String period) {
-        String[] parts = period.split("-");
-        int year = Integer.parseInt(parts[0]);
-        int month = Integer.parseInt(parts[1]);
-        if (month == 1) {
-            year--;
-            month = 12;
-        } else {
-            month--;
-        }
-        return String.format("%d-%02d", year, month);
+    private String h2032FieldLabel(String field) {
+        String label = FREIGHT_TYPE_NAMES.get(field);
+        if (label != null) return label;
+        java.util.Map<String, String> labels = new HashMap<>();
+        labels.put("vehicleTotal", "杞﹁締鏁�");
+        labels.put("tonsTotal", "鎬诲惃浣�");
+        labels.put("vehicleContainer", "闆嗚绠辫溅杈嗘暟");
+        labels.put("tonsWhole", "鏁磋溅鍚ㄤ綅");
+        labels.put("turnoverTotal", "鍛ㄨ浆閲�");
+        labels.put("vehicleWhole", "鏁磋溅杞﹁締鏁�");
+        String v = labels.get(field);
+        return v == null ? field : v;
     }
-}
+
+    private boolean nearlyEqual(double a, double b) {
+        if (a == b) return true;
+        return Math.abs(a - b) <= 1e-6 * Math.max(1.0, Math.max(Math.abs(a), Math.abs(b)));
+    }
+
+    private String key(String s) {
+        if (s == null) return "";
+        return s.trim().replaceAll("\\s+", "");
+    }
+
+    private double nvl(Double v) {
+        return v == null ? 0.0 : v;
+    }
+
+    private double num(Integer v) {
+        return v == null ? 0.0 : v.doubleValue();
+    }
+
+    private String fmt(double v) {
+        if (v == Math.floor(v) && !Double.isInfinite(v)) {
+            return String.valueOf((long) v);
+        }
+        return String.valueOf(Math.round(v * 100.0) / 100.0);
+    }
+
+    /** 鍗犳瘮/灏忔暟鍊硷細淇濈暀 4 浣嶅皬鏁� */
+    private String fmt4(double v) {
+        if (v == Math.floor(v) && !Double.isInfinite(v)) {
+            return String.valueOf((long) v);
+        }
+        return String.valueOf(Math.round(v * 10000.0) / 10000.0);
+    }
+
+    // ========== H2031 鏃呭瀹℃牳 ==========
+
+    private AuditResult checkPassengerRule(AuditRule rule, PassengerEnterpriseMonthly report,
+                                           Map<String, PassengerAuthVehicle> authMap,
+                                           Map<String, PassengerEnterpriseMonthly> lastMonthMap) {
+        try {
+            switch (rule.getCompareType()) {
+                case "DIFF":
+                    return checkPassengerDiff(rule, report, authMap);
+                case "MOM_EQ_ZERO":
+                    return checkPassengerMomEqZero(rule, report, lastMonthMap);
+                case "MOM_PCT":
+                    return checkPassengerMomPct(rule, report, lastMonthMap);
+                case "MOM_EQ":
+                    return checkPassengerMomEq(rule, report, lastMonthMap);
+                case "EXISTENCE":
+                    return checkPassengerExistence(rule, report);
+                default:
+                    return null;
+            }
+        } catch (Exception e) {
+            log.error("Passenger rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
+            return null;
+        }
+    }
+
+    /** 杞﹁締鏁�/杞藉浣嶆暟 涓庤繍鏀垮姣旓紝宸窛瓒呴槇鍊兼牳瀹� */
+    private AuditResult checkPassengerDiff(AuditRule rule, PassengerEnterpriseMonthly report,
+                                           Map<String, PassengerAuthVehicle> authMap) {
+        PassengerAuthVehicle auth = authMap.get(key(report.getEnterpriseName()));
+        if (auth == null) return null;
+        double actual;
+        double reference;
+        if (rule.getCheckField().contains("seat")) {
+            actual = num(report.getSeatTotal());
+            reference = num(auth.getSeatCount());
+        } else {
+            actual = num(report.getVehicleTotal());
+            reference = num(auth.getVehicleCount());
+        }
+        double threshold = Double.parseDouble(rule.getThreshold());
+        double diff = Math.abs(actual - reference);
+        if (diff > threshold) {
+            return buildPassengerResult(rule, report, "涓婃姤=" + fmt(actual) + ", 杩愭斂=" + fmt(reference),
+                "宸窛鈮�" + fmt(threshold), "宸窛=" + fmt(diff));
+        }
+        return null;
+    }
+
+    /** 鐜瘮涓婁釜鏈堢瓑浜�0锛氬�间笌涓婃湀鐩稿悓闇�鏍稿疄锛堜笂鏈堜负0涓嶆牳瀹烇級 */
+    private AuditResult checkPassengerMomEqZero(AuditRule rule, PassengerEnterpriseMonthly report,
+                                                Map<String, PassengerEnterpriseMonthly> lastMonthMap) {
+        PassengerEnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode()));
+        if (lastMonth == null) return null;
+        List<String> hits = new ArrayList<>();
+        for (String field : rule.getCheckField().split(",")) {
+            double current = getPassengerFieldValue(report, field);
+            double last = getPassengerFieldValue(lastMonth, field);
+            if (last > 0 && nearlyEqual(current, last)) {
+                hits.add(fieldLabel(field) + "=" + fmt(current));
+            }
+        }
+        if (!hits.isEmpty()) {
+            return buildPassengerResult(rule, report, "鏈湀涓庝笂鏈堢浉鍚�: " + String.join("銆�", hits),
+                "鐜瘮鈮�0", "鐜瘮=0");
+        }
+        return null;
+    }
+
+    /** 骞冲潎杩愯窛鐜瘮瓒� 卤threshold% 鏍稿疄 */
+    private AuditResult checkPassengerMomPct(AuditRule rule, PassengerEnterpriseMonthly report,
+                                             Map<String, PassengerEnterpriseMonthly> lastMonthMap) {
+        PassengerEnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode()));
+        if (lastMonth == null) return null;
+        double threshold = Double.parseDouble(rule.getThreshold());
+        List<String> hits = new ArrayList<>();
+        for (String field : rule.getCheckField().split(",")) {
+            double current = getPassengerFieldValue(report, field);
+            double last = getPassengerFieldValue(lastMonth, field);
+            if (last > 0) {
+                double pct = Math.abs((current - last) / last) * 100.0;
+                if (pct > threshold) {
+                    hits.add(fieldLabel(field) + "=" + fmt(current) + "锛屼笂鏈�=" + fmt(last) + "锛岀幆姣�" + fmt(pct) + "%");
+                }
+            }
+        }
+        if (!hits.isEmpty()) {
+            return buildPassengerResult(rule, report, String.join("锛�", hits),
+                "鐜瘮鈮ぢ�" + fmt(threshold) + "%", "鐜瘮瓒呴檺");
+        }
+        return null;
+    }
+
+    /** 鍖呰溅骞冲潎杩愯窛绛変簬涓婃湀鏍稿疄 */
+    private AuditResult checkPassengerMomEq(AuditRule rule, PassengerEnterpriseMonthly report,
+                                            Map<String, PassengerEnterpriseMonthly> lastMonthMap) {
+        PassengerEnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode()));
+        if (lastMonth == null) return null;
+        List<String> hits = new ArrayList<>();
+        for (String field : rule.getCheckField().split(",")) {
+            double current = getPassengerFieldValue(report, field);
+            double last = getPassengerFieldValue(lastMonth, field);
+            if (last > 0 && nearlyEqual(current, last)) {
+                hits.add(fieldLabel(field) + "=" + fmt(current));
+            }
+        }
+        if (!hits.isEmpty()) {
+            return buildPassengerResult(rule, report, "绛変簬涓婃湀: " + String.join("銆�", hits),
+                "涓庝笂鏈堜笉鍚�", "涓庝笂鏈堢浉鍚�");
+        }
+        return null;
+    }
+
+    /** 鏃犵彮绾�/鏃犲寘杞﹀嵈鏈夊搴斿杩愰噺锛歝heck_field 棣栦釜涓鸿溅杈嗗瓧娈碉紝鍏朵綑涓哄杩愰噺瀛楁 */
+    private AuditResult checkPassengerExistence(AuditRule rule, PassengerEnterpriseMonthly report) {
+        String[] fields = rule.getCheckField().split(",");
+        if (fields.length < 2) return null;
+        String vehicleField = fields[0];
+        double vehicleCount = getPassengerFieldValue(report, vehicleField);
+        if (vehicleCount > 0) return null;
+        List<String> hits = new ArrayList<>();
+        for (int i = 1; i < fields.length; i++) {
+            double value = getPassengerFieldValue(report, fields[i]);
+            if (value > 0) {
+                hits.add(fieldLabel(fields[i]) + "=" + fmt(value));
+            }
+        }
+        if (!hits.isEmpty()) {
+            String label = "schedule".equals(vehicleField) || "vehicleSchedule".equals(vehicleField) ? "鐝嚎" : "鍖呰溅";
+            return buildPassengerResult(rule, report, String.join("銆�", hits),
+                "鏈�" + label + "杞﹁締", "鏃�" + label + "杞﹁締浣嗘湁瀹㈣繍閲�");
+        }
+        return null;
+    }
+
+    private Map<String, PassengerAuthVehicle> loadPassengerAuthMap(String reportPeriod) {
+        Map<String, PassengerAuthVehicle> map = new HashMap<>();
+        List<PassengerAuthVehicle> list = passengerAuthMapper.selectList(
+            new LambdaQueryWrapper<PassengerAuthVehicle>()
+                .eq(PassengerAuthVehicle::getReportPeriod, reportPeriod));
+        for (PassengerAuthVehicle v : list) {
+            map.putIfAbsent(key(v.getEnterpriseName()), v);
+        }
+        return map;
+    }
+
+    private Map<String, PassengerEnterpriseMonthly> loadPassengerLastMonthMap(String period) {
+        Map<String, PassengerEnterpriseMonthly> map = new HashMap<>();
+        String lastPeriod = getLastPeriod(period);
+        List<PassengerEnterpriseMonthly> list = passengerMapper.selectList(
+            new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+                .eq(PassengerEnterpriseMonthly::getReportPeriod, lastPeriod));
+        for (PassengerEnterpriseMonthly v : list) {
+            map.putIfAbsent(key(v.getEnterpriseCode()), v);
+        }
+        return map;
+    }
+
+    private double getPassengerFieldValue(PassengerEnterpriseMonthly report, String fieldName) {
+        try {
+            java.lang.reflect.Field field = PassengerEnterpriseMonthly.class.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            Object value = field.get(report);
+            if (value == null) return 0.0;
+            if (value instanceof Double) return (Double) value;
+            if (value instanceof Integer) return ((Integer) value).doubleValue();
+            return Double.parseDouble(value.toString());
+        } catch (Exception e) {
+            return 0.0;
+        }
+    }
+
+    private String fieldLabel(String field) {
+        java.util.Map<String, String> labels = new HashMap<>();
+        labels.put("passengerClass1", "涓�绫荤彮绾垮杩愰噺");
+        labels.put("passengerClass2", "浜岀被鐝嚎瀹㈣繍閲�");
+        labels.put("passengerClass3", "涓夌被鐝嚎瀹㈣繍閲�");
+        labels.put("passengerClass4", "鍥涚被鐝嚎瀹㈣繍閲�");
+        labels.put("passengerCharter", "鍖呰溅瀹㈣繍閲�");
+        labels.put("turnoverClass1", "涓�绫荤彮绾垮懆杞噺");
+        labels.put("turnoverClass2", "浜岀被鐝嚎鍛ㄨ浆閲�");
+        labels.put("turnoverClass3", "涓夌被鐝嚎鍛ㄨ浆閲�");
+        labels.put("turnoverClass4", "鍥涚被鐝嚎鍛ㄨ浆閲�");
+        labels.put("turnoverCharter", "鍖呰溅鍛ㄨ浆閲�");
+        labels.put("avgDistanceClass1", "涓�绫荤彮绾垮钩鍧囪繍璺�");
+        labels.put("avgDistanceClass2", "浜岀被鐝嚎骞冲潎杩愯窛");
+        labels.put("avgDistanceClass3", "涓夌被鐝嚎骞冲潎杩愯窛");
+        labels.put("avgDistanceClass4", "鍥涚被鐝嚎骞冲潎杩愯窛");
+        labels.put("avgDistanceCharter", "鍖呰溅骞冲潎杩愯窛");
+        String label = labels.get(field);
+        return label == null ? field : label;
+    }
+
+    private AuditResult buildPassengerResult(AuditRule rule, PassengerEnterpriseMonthly report,
+                                             String actual, String threshold, String deviation) {
+        AuditResult result = new AuditResult();
+        result.setRuleId(rule.getId());
+        result.setReportId(report.getId());
+        result.setEnterpriseCode(report.getEnterpriseCode());
+        result.setReportPeriod(report.getReportPeriod());
+        result.setActualValue(actual);
+        result.setThresholdValue(threshold);
+        result.setDeviation(deviation);
+        result.setStatus("PENDING");
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/common/GlobalExceptionHandler.java b/traffic-audit-server/src/main/java/com/trafficaudit/common/GlobalExceptionHandler.java
new file mode 100644
index 0000000..b0a6623
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/common/GlobalExceptionHandler.java
@@ -0,0 +1,22 @@
+package com.trafficaudit.common;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/** 鍏ㄥ眬寮傚父澶勭悊锛氭妸 500 閿欒鍘熷洜閫忓嚭鍒板墠绔彁绀猴紙P0锛氬鍑哄け璐ュ彲瑙佸叿浣撳師鍥狅級 */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<Result<String>> handle(Exception e) {
+        log.error("Unhandled exception", e);
+        String msg = e.getMessage();
+        if (msg == null || msg.trim().isEmpty()) msg = e.toString();
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+            .body(Result.error(500, msg));
+    }
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/common/util/RegionUtil.java b/traffic-audit-server/src/main/java/com/trafficaudit/common/util/RegionUtil.java
index 4bb5503..a804f77 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/common/util/RegionUtil.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/common/util/RegionUtil.java
@@ -44,6 +44,7 @@
 
         ALIAS_MAP.put("鎭╂柦", "鎭╂柦宸�");
         ALIAS_MAP.put("鎭╂柦甯�", "鎭╂柦宸�");
+        ALIAS_MAP.put("鎭╂柦鍦熷鏃忚嫍鏃忚嚜娌诲窞", "鎭╂柦宸�");
         ALIAS_MAP.put("澶╅棬", "澶╅棬甯�");
         ALIAS_MAP.put("鏋楀尯", "绁炲啘鏋舵灄鍖�");
         ALIAS_MAP.put("绁炲啘鏋�", "绁炲啘鏋舵灄鍖�");
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/config/TemplateDownloadController.java b/traffic-audit-server/src/main/java/com/trafficaudit/config/TemplateDownloadController.java
new file mode 100644
index 0000000..662cbb4
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/config/TemplateDownloadController.java
@@ -0,0 +1,64 @@
+package com.trafficaudit.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URLEncoder;
+
+/**
+ * 鏁版嵁瀵煎叆椤点�屼笅杞芥ā鏉裤�嶏細/api/templates/** -> docs 鐩綍涓嬬殑妯℃澘鏂囦欢銆�
+ * 鐢� Controller 鑰岄潪闈欐�佽祫婧愭槧灏勶紝渚夸簬璁剧疆涓枃鏂囦欢鍚嶇殑涓嬭浇澶村苟闃茶矾寰勭┛瓒娿��
+ */
+@RestController
+@RequestMapping("/api/templates")
+public class TemplateDownloadController {
+
+    @Value("${docs.template-root:docs}")
+    private String templateRoot;
+
+    @GetMapping("/{*path}")
+    public ResponseEntity<Resource> download(@PathVariable String path) throws IOException {
+        File file = resolveTemplateFile(path);
+        if (file == null) {
+            return ResponseEntity.notFound().build();
+        }
+        String encoded = URLEncoder.encode(file.getName(), "UTF-8").replace("+", "%20");
+        return ResponseEntity.ok()
+            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded)
+            .contentType(MediaType.APPLICATION_OCTET_STREAM)
+            .contentLength(file.length())
+            .body(new FileSystemResource(file));
+    }
+
+    /** 妯℃澘瀹氫綅锛氶厤缃洰褰� 鈫� user.dir 鈫� user.dir/.. 閫愮骇鍥為��锛堜笌鎶ヨ〃妯″潡涓�鑷达級锛屽苟闃茶矾寰勭┛瓒� */
+    private File resolveTemplateFile(String rel) throws IOException {
+        String r = rel;
+        while (r.startsWith("./")) r = r.substring(2);
+        if (r.contains("..")) {
+            return null;
+        }
+        String[] roots = {
+            templateRoot,
+            System.getProperty("user.dir") + "/" + templateRoot,
+            System.getProperty("user.dir") + "/../" + templateRoot
+        };
+        for (String root : roots) {
+            if (root == null || root.trim().isEmpty()) continue;
+            File f = new File(root, r);
+            if (f.exists() && f.isFile()) {
+                return f.getCanonicalFile();
+            }
+        }
+        return null;
+    }
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataImportController.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataImportController.java
index abd2716..5700733 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataImportController.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataImportController.java
@@ -2,8 +2,12 @@
 
 import com.trafficaudit.common.Result;
 import com.trafficaudit.dataimport.dto.ImportResult;
+import com.trafficaudit.dataimport.entity.ImportBatch;
+import com.trafficaudit.dataimport.mapper.ImportBatchMapper;
 import com.trafficaudit.dataimport.service.DataImportService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -11,6 +15,9 @@
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 
 @Slf4j
 @RestController
@@ -19,6 +26,8 @@
 
     @Resource
     private DataImportService importService;
+    @Resource
+    private ImportBatchMapper importBatchMapper;
 
     /** H203-2琛� 閬撹矾璐х墿杩愯緭鏈堝害鐢熶骇鎯呭喌 */
     @PostMapping("/h2032")
@@ -50,6 +59,239 @@
     }
 
 
+
+    /** H204 閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙瀛e害鑳借�楁槑缁� + 杩愭斂杞﹁締淇℃伅锛� */
+    @PostMapping("/energyMonthly")
+    public Result<String> importEnergyMonthly(@RequestParam("file") MultipartFile file,
+                                              @RequestParam("period") String period) {
+        try {
+            ImportResult result = importService.importEnergyMonthly(file, period);
+            String msg = "H204鑳借�楀鎶ュ鍏ュ畬鎴愶細鎴愬姛 " + result.getSuccess() + " 鏉★紝澶辫触 " + result.getFail() + " 鏉★紙鍚悓鎶ヨ〃鏈熻繍鏀胯溅杈嗕俊鎭級";
+            if (result.getFail() > 0) {
+                java.util.List<String> details = result.getFailDetails();
+                int shown = Math.min(details.size(), 100);
+                for (int i = 0; i < shown; i++) {
+                    msg += "\n" + details.get(i);
+                }
+                if (details.size() > shown) {
+                    msg += "\n鈥︹�﹀叡 " + details.size() + " 琛屽け璐�";
+                }
+            }
+            return Result.ok(msg);
+        } catch (Exception e) {
+            log.error("energyMonthly import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 鑳借�楄溅杈嗚繍鏀夸俊鎭紙鐙珛妯℃澘锛屼篃鍙殢 H204 瀛f姤绗簩涓� sheet 涓�骞跺鍏ワ級 */
+    @PostMapping("/energyAuth")
+    public Result<String> importEnergyAuth(@RequestParam("file") MultipartFile file,
+                                           @RequestParam("period") String period) {
+        try {
+            ImportResult result = importService.importEnergyAuth(file, period);
+            String msg = "鑳借�楄溅杈嗚繍鏀夸俊鎭鍏ュ畬鎴愶細鎴愬姛 " + result.getSuccess() + " 鏉★紝澶辫触 " + result.getFail() + " 鏉�";
+            if (result.getFail() > 0) {
+                java.util.List<String> details = result.getFailDetails();
+                int shown = Math.min(details.size(), 100);
+                for (int i = 0; i < shown; i++) {
+                    msg += "\n" + details.get(i);
+                }
+                if (details.size() > shown) {
+                    msg += "\n鈥︹�﹀叡 " + details.size() + " 琛屽け璐�";
+                }
+            }
+            return Result.ok(msg);
+        } catch (Exception e) {
+            log.error("energyAuth import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** H203-1琛� 鍏矾鏃呭杩愯緭鏈堝害鐢熶骇鎯呭喌锛堝惈杩愭斂杞﹁締鏁皊heet锛� */
+    @PostMapping("/passengerMonthly")
+    public Result<String> importPassengerMonthly(@RequestParam("file") MultipartFile file,
+                                                 @RequestParam("period") String period) {
+        try {
+            ImportResult result = importService.importPassengerMonthly(file, period);
+            String msg = "H2031鏃呭鏈堟姤瀵煎叆瀹屾垚锛氭垚鍔� " + result.getSuccess() + " 鏉★紝澶辫触 " + result.getFail() + " 鏉★紙鍚悓鎶ヨ〃鏈熻繍鏀胯溅杈嗘暟鎹級";
+            if (result.getFail() > 0) {
+                java.util.List<String> details = result.getFailDetails();
+                int shown = Math.min(details.size(), 100);
+                for (int i = 0; i < shown; i++) {
+                    msg += "\n" + details.get(i);
+                }
+                if (details.size() > shown) {
+                    msg += "\n鈥︹�﹀叡 " + details.size() + " 琛屽け璐�";
+                }
+            }
+            return Result.ok(msg);
+        } catch (Exception e) {
+            log.error("passengerMonthly import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+
+    /** 鍏矾鏃呭涓綋瀹㈣繍閲�/鍛ㄨ浆閲忥紙浼佷笟绾夸笅姹囨姤锛屽鍏ュ悗渚涘垎甯傚窞琛ㄤ釜浣撹濉厖锛� */
+    @PostMapping("/passengerIndividual")
+    public Result<String> importPassengerIndividual(@RequestParam("file") MultipartFile file,
+                                                    @RequestParam("period") String period) {
+        try {
+            ImportResult result = importService.importPassengerIndividual(file, period);
+            String msg = "涓綋瀹㈣繍閲忓鍏ュ畬鎴愶細鎴愬姛 " + result.getSuccess() + " 鏉★紝澶辫触 " + result.getFail() + " 鏉�";
+            if (result.getFail() > 0) {
+                java.util.List<String> details = result.getFailDetails();
+                int shown = Math.min(details.size(), 100);
+                for (int i = 0; i < shown; i++) {
+                    msg += "\n" + details.get(i);
+                }
+                if (details.size() > shown) {
+                    msg += "\n鈥︹�﹀叡 " + details.size() + " 琛屽け璐�";
+                }
+            }
+            return Result.ok(msg);
+        } catch (Exception e) {
+            log.error("passengerIndividual import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 鍏矾鏃呭杩愭斂杞﹁締鏁帮紙鐙珛妯℃澘锛屼篃鍙殢 H203-1 鏈堟姤绗簩涓� sheet 涓�骞跺鍏ワ級 */
+    @PostMapping("/passengerAuth")
+    public Result<String> importPassengerAuth(@RequestParam("file") MultipartFile file,
+                                              @RequestParam("period") String period) {
+        try {
+            ImportResult result = importService.importPassengerAuth(file, period);
+            String msg = "鏃呭杩愭斂杞﹁締鏁板鍏ュ畬鎴愶細鎴愬姛 " + result.getSuccess() + " 鏉★紝澶辫触 " + result.getFail() + " 鏉�";
+            if (result.getFail() > 0) {
+                java.util.List<String> details = result.getFailDetails();
+                int shown = Math.min(details.size(), 100);
+                for (int i = 0; i < shown; i++) {
+                    msg += "\n" + details.get(i);
+                }
+                if (details.size() > shown) {
+                    msg += "\n鈥︹�﹀叡 " + details.size() + " 琛屽け璐�";
+                }
+            }
+            return Result.ok(msg);
+        } catch (Exception e) {
+            log.error("passengerAuth import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 鍩庡競鍏氦鏈堟姤锛堝煄甯傚叕鍏变氦閫氭湀搴﹁繍钀ユ儏鍐碉紝浼佷笟绾ф槑缁嗭級 */
+    @PostMapping("/cityBus")
+    public Result<String> importCityBus(@RequestParam("file") MultipartFile file,
+                                        @RequestParam("period") String period) {
+        try {
+            ImportResult result = importService.importCityBus(file, period);
+            String msg = "鍩庡競鍏氦鏈堟姤瀵煎叆瀹屾垚锛氭垚鍔� " + result.getSuccess() + " 鏉★紝澶辫触 " + result.getFail() + " 鏉�";
+            if (result.getFail() > 0) {
+                java.util.List<String> details = result.getFailDetails();
+                int shown = Math.min(details.size(), 100);
+                for (int i = 0; i < shown; i++) {
+                    msg += "\n" + details.get(i);
+                }
+                if (details.size() > shown) {
+                    msg += "\n鈥︹�﹀叡 " + details.size() + " 琛屽け璐�";
+                }
+            }
+            return Result.ok(msg);
+        } catch (Exception e) {
+            log.error("cityBus import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 宸℃父鍑虹姹借溅杩愯惀鏈嶅姟鎯呭喌鏈堟姤锛堝競宸炵骇锛� */
+    @PostMapping("/cityTaxi")
+    public Result<String> importCityTaxi(@RequestParam("file") MultipartFile file,
+                                         @RequestParam("period") String period) {
+        try {
+            ImportResult result = importService.importCityTaxi(file, period);
+            String msg = "宸℃父鍑虹鏈堟姤瀵煎叆瀹屾垚锛氭垚鍔� " + result.getSuccess() + " 鏉★紝澶辫触 " + result.getFail() + " 鏉�";
+            if (result.getFail() > 0) {
+                java.util.List<String> details = result.getFailDetails();
+                int shown = Math.min(details.size(), 100);
+                for (int i = 0; i < shown; i++) {
+                    msg += "\n" + details.get(i);
+                }
+                if (details.size() > shown) {
+                    msg += "\n鈥︹�﹀叡 " + details.size() + " 琛屽け璐�";
+                }
+            }
+            return Result.ok(msg);
+        } catch (Exception e) {
+            log.error("cityTaxi import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 鍑虹杞﹁繍鏀胯溅杈嗕俊鎭紙杞﹁締绾ф槑缁嗭紝鐢ㄤ簬杩愯惀杞︽暟瀵规瘮锛� */
+    @PostMapping("/cityTaxiAuth")
+    public Result<String> importCityTaxiAuth(@RequestParam("file") MultipartFile file,
+                                             @RequestParam("period") String period) {
+        try {
+            int count = importService.importCityTaxiAuth(file, period);
+            return Result.ok("鍑虹杞﹁繍鏀胯溅杈嗗鍏ユ垚鍔�, 鍏� " + count + " 鏉¤褰�");
+        } catch (Exception e) {
+            log.error("cityTaxiAuth import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 鎶曡祫鏈堟姤锛堝叏鐪佹眹鎬诲ぇ琛級锛歝ategory=瀹㈣繍绔欏満/鐗╂祦鍥尯 */
+    @PostMapping("/investment")
+    public Result<String> importInvestment(@RequestParam("file") MultipartFile file,
+                                           @RequestParam("period") String period,
+                                           @RequestParam(value = "category", defaultValue = "瀹㈣繍绔欏満") String category) {
+        try {
+            ImportResult result = importService.importInvestment(file, period, category);
+            String msg = "鎶曡祫鏈堟姤瀵煎叆瀹屾垚锛�" + category + "锛夛細鎴愬姛 " + result.getSuccess() + " 鏉★紝澶辫触 " + result.getFail() + " 鏉�";
+            if (result.getFail() > 0) {
+                java.util.List<String> details = result.getFailDetails();
+                int shown = Math.min(details.size(), 100);
+                for (int i = 0; i < shown; i++) {
+                    msg += "\n" + details.get(i);
+                }
+                if (details.size() > shown) {
+                    msg += "\n鈥︹�﹀叡 " + details.size() + " 琛屽け璐�";
+                }
+            }
+            return Result.ok(msg);
+        } catch (Exception e) {
+            log.error("investment import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 鎶曡祫甯傚窞鍗曡〃鎵归噺瀵煎叆锛圡2锛夛細鎵弿 docs/鎶曡祫/杈撳叆/{绫诲埆}/{X鏈坿 鐩綍鍏ㄩ儴甯傚窞鎶ヨ〃锛宑ategory=瀹㈣繍绔欏満/鐗╂祦鍥尯 */
+    @PostMapping("/investCityBatch")
+    public Result<Object> importInvestCityBatch(@RequestParam("period") String period,
+                                                @RequestParam(value = "category", defaultValue = "瀹㈣繍绔欏満") String category) {
+        try {
+            Map<String, Object> result = importService.importInvestCityDir(period, category);
+            return Result.ok(result);
+        } catch (Exception e) {
+            log.error("investCityBatch import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 鎶曡祫绯荤粺瀵煎嚭锛堝鏍告瘮瀵瑰熀鍑嗭級 */
+    @PostMapping("/investmentSystem")
+    public Result<String> importInvestmentSystem(@RequestParam("file") MultipartFile file,
+                                                 @RequestParam("period") String period) {
+        try {
+            int count = importService.importInvestmentSystem(file, period);
+            return Result.ok("鎶曡祫绯荤粺瀵煎嚭瀵煎叆鎴愬姛, 鍏� " + count + " 鏉¤褰�");
+        } catch (Exception e) {
+            log.error("investmentSystem import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
     /** 杩愭斂杞﹁締 */
     @PostMapping("/transportAuth")
     public Result<String> importTransportAuth(@RequestParam("file") MultipartFile file,
@@ -101,4 +343,63 @@
             return Result.error("瀵煎叆澶辫触: " + e.getMessage());
         }
     }
+    /** 鐩綍娴忚鍣細path 涓虹┖杩斿洖椤圭洰 docs 鏁版嵁鐩綍锛岃繑鍥炲瓙鐩綍涓庡綋鍓嶇洰褰� Excel 鏂囦欢 */
+    @GetMapping("/dirs")
+    public Result<Map<String, Object>> dirs(@RequestParam(value = "path", required = false) String path) {
+        try {
+            Map<String, Object> result = importService.listDirs(path);
+            return Result.ok(result);
+        } catch (Exception e) {
+            log.error("list dirs error", e);
+            return Result.error("鐩綍娴忚澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 鎸夎矾寰勫鍏ュ崟涓� Excel 鏂囦欢锛堝墠绔�愭枃浠跺鍏ユ樉绀鸿繘搴︼級锛涙姤琛ㄦ湡浼樺厛浠庢枃浠跺悕璇嗗埆 */
+    @PostMapping("/fromDirFile")
+    public Result<Map<String, Object>> importFromDirFile(@RequestParam("type") String type,
+                                                         @RequestParam(value = "period", required = false) String period,
+                                                         @RequestParam(value = "skipImported", defaultValue = "true") boolean skipImported,
+                                                         @RequestParam("path") String path) {
+        try {
+            Map<String, Object> result = importService.importDirFileByPath(type, period, skipImported, path);
+            return Result.ok(result);
+        } catch (Exception e) {
+            log.error("fromDirFile import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 浠庢湇鍔$鏂囦欢澶规壒閲忓鍏ワ細鎵弿 dir 涓嬪叏閮� .xlsx/.xls锛屾寜 type 閫愪釜瀵煎叆锛涙姤琛ㄦ湡浼樺厛浠庢枃浠跺悕璇嗗埆 */
+    @PostMapping("/fromDir")
+    public Result<Object> importFromDir(@RequestParam("type") String type,
+                                        @RequestParam(value = "period", required = false) String period,
+                                        @RequestParam(value = "skipImported", defaultValue = "true") boolean skipImported,
+                                        @RequestParam("dir") String dir) {
+        try {
+            Map<String, Object> result = importService.importFromDir(type, period, skipImported, dir);
+            return Result.ok(result);
+        } catch (Exception e) {
+            log.error("fromDir import error", e);
+            return Result.error("瀵煎叆澶辫触: " + e.getMessage());
+        }
+    }
+
+    /** 瀵煎叆鎵规鍘嗗彶锛堟暟鎹鍏ラ〉灞曠ず鏈�杩戝鍏ョ粨鏋滐級 */
+    @GetMapping("/batches")
+    public Result<Map<String, Object>> batches(@RequestParam(value = "type", required = false) String type,
+                                               @RequestParam(value = "period", required = false) String period,
+                                               @RequestParam(value = "limit", defaultValue = "20") int limit) {
+        int safeLimit = Math.min(Math.max(limit, 1), 200);
+        LambdaQueryWrapper<ImportBatch> w = new LambdaQueryWrapper<ImportBatch>()
+            .eq(type != null && !type.trim().isEmpty(), ImportBatch::getImportType, type)
+            .eq(period != null && !period.trim().isEmpty(), ImportBatch::getReportPeriod, period)
+            .orderByDesc(ImportBatch::getId)
+            .last("limit " + safeLimit);
+        List<ImportBatch> list = importBatchMapper.selectList(w);
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("records", list);
+        return Result.ok(result);
+    }
+
 }
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataViewController.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataViewController.java
index c01014e..6755650 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataViewController.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataViewController.java
@@ -6,11 +6,21 @@
 import com.trafficaudit.common.Result;
 import com.trafficaudit.dataimport.entity.FreightTurnoverImport;
 import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly;
+import com.trafficaudit.dataimport.entity.PassengerAuthVehicle;
+import com.trafficaudit.dataimport.entity.CityBusMonthly;
+import com.trafficaudit.dataimport.entity.CityTaxiAuth;
+import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
+import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
 import com.trafficaudit.dataimport.entity.ScaleSplitTransport;
 import com.trafficaudit.dataimport.entity.TransportAuthVehicle;
 import com.trafficaudit.dataimport.entity.VehicleTrackMileage;
 import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper;
 import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.PassengerAuthVehicleMapper;
+import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.CityTaxiAuthMapper;
+import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
 import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper;
 import com.trafficaudit.dataimport.mapper.TransportAuthVehicleMapper;
 import com.trafficaudit.dataimport.mapper.VehicleTrackMileageMapper;
@@ -44,6 +54,16 @@
     private ScaleSplitTransportMapper scaleSplitMapper;
     @Resource
     private FreightTurnoverImportMapper freightTurnoverMapper;
+    @Resource
+    private PassengerEnterpriseMonthlyMapper passengerMapper;
+    @Resource
+    private PassengerAuthVehicleMapper passengerAuthMapper;
+    @Resource
+    private CityBusMonthlyMapper cityBusMapper;
+    @Resource
+    private CityTaxiMonthlyMapper cityTaxiMapper;
+    @Resource
+    private CityTaxiAuthMapper cityTaxiAuthMapper;
 
     @GetMapping("/list")
     public Result<Map<String, Object>> list(@RequestParam("type") String type,
@@ -113,6 +133,61 @@
                 fillPage(result, r);
                 break;
             }
+            case "h2031": {
+                LambdaQueryWrapper<PassengerEnterpriseMonthly> w = new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+                        .eq(notEmpty(period), PassengerEnterpriseMonthly::getReportPeriod, period)
+                        .like(!kw.isEmpty(), PassengerEnterpriseMonthly::getEnterpriseName, kw)
+                        .orderByDesc(PassengerEnterpriseMonthly::getId);
+                Page<PassengerEnterpriseMonthly> p = new Page<>(safePage, safeSize);
+                Page<PassengerEnterpriseMonthly> r = passengerMapper.selectPage(p, w);
+                result.put("records", toMaps(r.getRecords(), propsOf(H2031_COLUMNS)));
+                fillPage(result, r);
+                break;
+            }
+            case "cityBus": {
+                LambdaQueryWrapper<CityBusMonthly> w = new LambdaQueryWrapper<CityBusMonthly>()
+                        .eq(notEmpty(period), CityBusMonthly::getReportPeriod, period)
+                        .like(!kw.isEmpty(), CityBusMonthly::getEnterpriseName, kw)
+                        .orderByDesc(CityBusMonthly::getId);
+                Page<CityBusMonthly> p = new Page<>(safePage, safeSize);
+                Page<CityBusMonthly> r = cityBusMapper.selectPage(p, w);
+                result.put("records", toMaps(r.getRecords(), propsOf(BUS_COLUMNS)));
+                fillPage(result, r);
+                break;
+            }
+            case "cityTaxi": {
+                LambdaQueryWrapper<CityTaxiMonthly> w = new LambdaQueryWrapper<CityTaxiMonthly>()
+                        .eq(notEmpty(period), CityTaxiMonthly::getReportPeriod, period)
+                        .like(!kw.isEmpty(), CityTaxiMonthly::getCity, kw)
+                        .orderByAsc(CityTaxiMonthly::getId);
+                Page<CityTaxiMonthly> p = new Page<>(safePage, safeSize);
+                Page<CityTaxiMonthly> r = cityTaxiMapper.selectPage(p, w);
+                result.put("records", toMaps(r.getRecords(), propsOf(TAXI_COLUMNS)));
+                fillPage(result, r);
+                break;
+            }
+            case "cityTaxiAuth": {
+                LambdaQueryWrapper<CityTaxiAuth> w = new LambdaQueryWrapper<CityTaxiAuth>()
+                        .eq(notEmpty(period), CityTaxiAuth::getReportPeriod, period)
+                        .like(!kw.isEmpty(), CityTaxiAuth::getCity, kw)
+                        .orderByDesc(CityTaxiAuth::getId);
+                Page<CityTaxiAuth> p = new Page<>(safePage, safeSize);
+                Page<CityTaxiAuth> r = cityTaxiAuthMapper.selectPage(p, w);
+                result.put("records", toMaps(r.getRecords(), propsOf(TAXI_AUTH_COLUMNS)));
+                fillPage(result, r);
+                break;
+            }
+            case "passengerAuth": {
+                LambdaQueryWrapper<PassengerAuthVehicle> w = new LambdaQueryWrapper<PassengerAuthVehicle>()
+                        .eq(notEmpty(period), PassengerAuthVehicle::getReportPeriod, period)
+                        .like(!kw.isEmpty(), PassengerAuthVehicle::getEnterpriseName, kw)
+                        .orderByDesc(PassengerAuthVehicle::getId);
+                Page<PassengerAuthVehicle> p = new Page<>(safePage, safeSize);
+                Page<PassengerAuthVehicle> r = passengerAuthMapper.selectPage(p, w);
+                result.put("records", toMaps(r.getRecords(), propsOf(PAUTH_COLUMNS)));
+                fillPage(result, r);
+                break;
+            }
             case "freightTurnover": {
                 LambdaQueryWrapper<FreightTurnoverImport> w = new LambdaQueryWrapper<FreightTurnoverImport>()
                         .eq(notEmpty(period), FreightTurnoverImport::getReportPeriod, period)
@@ -171,6 +246,11 @@
             case "trackMileage": return TRACK_COLUMNS;
             case "scaleSplit": return SPLIT_COLUMNS;
             case "freightTurnover": return FREIGHT_COLUMNS;
+            case "h2031": return H2031_COLUMNS;
+            case "cityBus": return BUS_COLUMNS;
+            case "cityTaxi": return TAXI_COLUMNS;
+            case "cityTaxiAuth": return TAXI_AUTH_COLUMNS;
+            case "passengerAuth": return PAUTH_COLUMNS;
             default: return new ArrayList<>();
         }
     }
@@ -181,6 +261,7 @@
         m.put("label", label);
         m.put("width", width);
         return m;
+
     }
 
     private static final List<Map<String, Object>> H2032_COLUMNS = new ArrayList<>();
@@ -188,6 +269,11 @@
     private static final List<Map<String, Object>> TRACK_COLUMNS = new ArrayList<>();
     private static final List<Map<String, Object>> SPLIT_COLUMNS = new ArrayList<>();
     private static final List<Map<String, Object>> FREIGHT_COLUMNS = new ArrayList<>();
+    private static final List<Map<String, Object>> H2031_COLUMNS = new ArrayList<>();
+    private static final List<Map<String, Object>> PAUTH_COLUMNS = new ArrayList<>();
+    private static final List<Map<String, Object>> BUS_COLUMNS = new ArrayList<>();
+    private static final List<Map<String, Object>> TAXI_COLUMNS = new ArrayList<>();
+    private static final List<Map<String, Object>> TAXI_AUTH_COLUMNS = new ArrayList<>();
 
     static {
         H2032_COLUMNS.add(col("reportPeriod", "鎶ヨ〃鏈�", 100));
@@ -268,5 +354,96 @@
         for (int m = 1; m <= 12; m++) {
             FREIGHT_COLUMNS.add(col(String.format("lastTurnoverM%02d", m), "鍘诲勾鍛ㄨ浆閲�" + m + "鏈�", 140));
         }
+
+        H2031_COLUMNS.add(col("reportPeriod", "鎶ヨ〃鏈�", 100));
+        H2031_COLUMNS.add(col("regionCode", "鎵�灞炲湴鍖�", 100));
+        H2031_COLUMNS.add(col("enterpriseCode", "浼佷笟浠g爜", 130));
+        H2031_COLUMNS.add(col("enterpriseName", "浼佷笟鍚嶇О", 220));
+        H2031_COLUMNS.add(col("vehicleTotal", "瀹㈣繍杞﹁締鍚堣", 110));
+        H2031_COLUMNS.add(col("seatTotal", "鏍稿畾杞藉浣嶅悎璁�", 130));
+        H2031_COLUMNS.add(col("vehicleSchedule", "鐝嚎杞﹁締鏁�", 110));
+        H2031_COLUMNS.add(col("seatSchedule", "鐝嚎杞藉浣�", 110));
+        H2031_COLUMNS.add(col("vehicleCharter", "鍖呰溅杞﹁締鏁�", 110));
+        H2031_COLUMNS.add(col("seatCharter", "鍖呰溅杞藉浣�", 110));
+        H2031_COLUMNS.add(col("passengerTotal", "瀹㈣繍閲忓悎璁�", 110));
+        H2031_COLUMNS.add(col("turnoverTotal", "鍛ㄨ浆閲忓悎璁�", 110));
+        H2031_COLUMNS.add(col("passengerClass1", "瀹㈣繍閲忎竴绫�", 110));
+        H2031_COLUMNS.add(col("passengerClass2", "瀹㈣繍閲忎簩绫�", 110));
+        H2031_COLUMNS.add(col("passengerClass3", "瀹㈣繍閲忎笁绫�", 110));
+        H2031_COLUMNS.add(col("passengerClass4", "瀹㈣繍閲忓洓绫�", 110));
+        H2031_COLUMNS.add(col("passengerCharter", "瀹㈣繍閲忓寘杞�", 110));
+        H2031_COLUMNS.add(col("turnoverClass1", "鍛ㄨ浆閲忎竴绫�", 110));
+        H2031_COLUMNS.add(col("turnoverClass2", "鍛ㄨ浆閲忎簩绫�", 110));
+        H2031_COLUMNS.add(col("turnoverClass3", "鍛ㄨ浆閲忎笁绫�", 110));
+        H2031_COLUMNS.add(col("turnoverClass4", "鍛ㄨ浆閲忓洓绫�", 110));
+        H2031_COLUMNS.add(col("turnoverCharter", "鍛ㄨ浆閲忓寘杞�", 110));
+        H2031_COLUMNS.add(col("avgDistanceTotal", "骞冲潎杩愯窛鍚堣", 120));
+        H2031_COLUMNS.add(col("avgDistanceClass1", "涓�绫诲钩鍧囪繍璺�", 120));
+        H2031_COLUMNS.add(col("avgDistanceClass2", "浜岀被骞冲潎杩愯窛", 120));
+        H2031_COLUMNS.add(col("avgDistanceClass3", "涓夌被骞冲潎杩愯窛", 120));
+        H2031_COLUMNS.add(col("avgDistanceClass4", "鍥涚被骞冲潎杩愯窛", 120));
+        H2031_COLUMNS.add(col("avgDistanceCharter", "鍖呰溅骞冲潎杩愯窛", 120));
+        BUS_COLUMNS.add(col("reportPeriod", "鎶ヨ〃鏈�", 100));
+        BUS_COLUMNS.add(col("regionCode", "鎵�灞炲湴鍖�", 100));
+        BUS_COLUMNS.add(col("city", "甯傚窞", 110));
+        BUS_COLUMNS.add(col("enterpriseName", "浼佷笟鍚嶇О", 220));
+        BUS_COLUMNS.add(col("opVehicles", "杩愯惀杞︽暟", 100));
+        BUS_COLUMNS.add(col("stopTotal", "绔欑偣鎬讳釜鏁�", 110));
+        BUS_COLUMNS.add(col("stopCity", "鍩庡競鍐呯珯鐐�", 110));
+        BUS_COLUMNS.add(col("passengerVolume", "瀹㈣繍閲�", 100));
+        BUS_COLUMNS.add(col("passengerCity", "鍩庡競鍐呭杩愰噺", 120));
+        BUS_COLUMNS.add(col("turnover", "鏃呭鍛ㄨ浆閲�", 110));
+        BUS_COLUMNS.add(col("turnoverCity", "鍩庡競鍐呭懆杞噺", 120));
+        BUS_COLUMNS.add(col("avgDistance", "骞冲潎杩愯窛", 100));
+        BUS_COLUMNS.add(col("avgDistanceCity", "鍩庡競鍐呭钩鍧囪繍璺�", 130));
+        BUS_COLUMNS.add(col("passengerChengxiang", "鍩庨檯鍩庝埂瀹㈣繍閲�", 130));
+        BUS_COLUMNS.add(col("turnoverChengxiang", "鍩庨檯鍩庝埂鍛ㄨ浆閲�", 130));
+        BUS_COLUMNS.add(col("avgDistanceChengxiang", "鍩庨檯鍩庝埂骞冲潎杩愯窛", 140));
+        BUS_COLUMNS.add(col("ratePassengerMom", "瀹㈣繍閲忕幆姣�", 100));
+        BUS_COLUMNS.add(col("ratePassengerCityMom", "鍩庡競鍐呯幆姣�", 100));
+        BUS_COLUMNS.add(col("rateChengxiangMom", "鍩庨檯鍩庝埂鐜瘮", 110));
+        BUS_COLUMNS.add(col("ratePassengerYoy", "瀹㈣繍閲忓悓姣�", 100));
+        BUS_COLUMNS.add(col("passengerCityRatio", "鍩庡競鍐呭崰姣�", 100));
+        BUS_COLUMNS.add(col("chengxiangRatio", "鍩庨檯鍩庝埂鍗犳瘮", 110));
+        BUS_COLUMNS.add(col("contactPerson", "濉〃浜�", 100));
+        BUS_COLUMNS.add(col("reportDate", "鎶ュ嚭鏃ユ湡", 110));
+        BUS_COLUMNS.add(col("verifyExplanation", "鏍稿疄瑙i噴", 220));
+        TAXI_COLUMNS.add(col("reportPeriod", "鎶ヨ〃鏈�", 100));
+        TAXI_COLUMNS.add(col("regionCode", "鎵�灞炲湴鍖�", 100));
+        TAXI_COLUMNS.add(col("city", "甯傚窞", 110));
+        TAXI_COLUMNS.add(col("tripTotal", "杞藉杞︽鎬绘暟", 120));
+        TAXI_COLUMNS.add(col("tripCity", "杞藉杞︽_鍩庡競鍐�", 130));
+        TAXI_COLUMNS.add(col("passengerVolume", "瀹㈣繍閲�", 100));
+        TAXI_COLUMNS.add(col("passengerCity", "鍩庡競鍐呭杩愰噺", 120));
+        TAXI_COLUMNS.add(col("turnover", "鏃呭鍛ㄨ浆閲�", 110));
+        TAXI_COLUMNS.add(col("turnoverCity", "鍩庡競鍐呭懆杞噺", 120));
+        TAXI_COLUMNS.add(col("opVehicles", "杩愯惀杞﹁締鏁�", 110));
+        TAXI_COLUMNS.add(col("avgDistance", "骞冲潎杩愯窛", 100));
+        TAXI_COLUMNS.add(col("avgDistanceCity", "鍩庡競鍐呭钩鍧囪繍璺�", 130));
+        TAXI_COLUMNS.add(col("passengersPerTrip", "鍗曡溅娆¤浇瀹汉鏁�", 130));
+        TAXI_COLUMNS.add(col("rateTripMom", "杞藉杞︽鐜瘮", 110));
+        TAXI_COLUMNS.add(col("rateTripYoy", "杞藉杞︽鍚屾瘮", 110));
+        TAXI_COLUMNS.add(col("ratePassengerMom", "瀹㈣繍閲忕幆姣�", 100));
+        TAXI_COLUMNS.add(col("ratePassengerYoy", "瀹㈣繍閲忓悓姣�", 100));
+        TAXI_COLUMNS.add(col("passengerCityRatio", "鍩庡競鍐呭杩愰噺鍗犳瘮", 140));
+        TAXI_COLUMNS.add(col("avgDistanceChengxiang", "鍩庨檯鍩庝埂骞冲潎杩愯窛", 140));
+        TAXI_COLUMNS.add(col("verifyExplanation", "鏍稿疄瑙i噴", 220));
+        TAXI_AUTH_COLUMNS.add(col("reportPeriod", "鎶ヨ〃鏈�", 100));
+        TAXI_AUTH_COLUMNS.add(col("city", "甯傚窞", 110));
+        TAXI_AUTH_COLUMNS.add(col("plateNo", "杞︾墝鍙风爜", 120));
+        TAXI_AUTH_COLUMNS.add(col("vehicleType", "杞﹁締绫诲瀷", 110));
+        TAXI_AUTH_COLUMNS.add(col("fuelType", "鐕冩枡绫诲瀷", 100));
+        TAXI_AUTH_COLUMNS.add(col("opStatus", "钀ヨ繍鐘舵��", 100));
+        TAXI_AUTH_COLUMNS.add(col("certStatus", "璇佷欢鐘舵��", 100));
+        TAXI_AUTH_COLUMNS.add(col("licenseNo", "缁忚惀鏉冨彿", 130));
+        TAXI_AUTH_COLUMNS.add(col("archiveNo", "妗f鍙�", 130));
+        TAXI_AUTH_COLUMNS.add(col("roadCertNo", "閬撹矾杩愯緭璇佸瓧鍙�", 150));
+        TAXI_AUTH_COLUMNS.add(col("operatorName", "涓氭埛鍚嶇О", 240));
+        TAXI_AUTH_COLUMNS.add(col("operatorAddr", "涓氭埛鍦板潃", 280));
+        PAUTH_COLUMNS.add(col("reportPeriod", "鎶ヨ〃鏈�", 100));
+        PAUTH_COLUMNS.add(col("enterpriseCode", "浼佷笟浠g爜", 130));
+        PAUTH_COLUMNS.add(col("enterpriseName", "浼佷笟鍚嶇О", 240));
+        PAUTH_COLUMNS.add(col("vehicleCount", "杞﹁締鏁�", 100));
+        PAUTH_COLUMNS.add(col("seatCount", "杞藉浣嶆暟", 100));
     }
 }
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/ExplainController.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/ExplainController.java
index 586dc65..01bb9cc 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/ExplainController.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/ExplainController.java
@@ -3,10 +3,17 @@
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.trafficaudit.common.Result;
+import com.trafficaudit.dataimport.entity.CityBusMonthly;
+import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
 import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly;
+import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
+import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
 import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
@@ -18,7 +25,8 @@
 import java.util.Map;
 
 /**
- * 浼佷笟瑙i噴锛氱嫭绔嬩簬瀹℃牳缁撴灉鐨勮В閲婂垪琛ㄤ笌 AI 鍒嗘瀽鍏ュ彛
+ * 浼佷笟瑙i噴锛氱嫭绔嬩簬瀹℃牳缁撴灉鐨勮В閲婂垪琛ㄤ笌 AI 鍒嗘瀽鍏ュ彛銆�
+ * 鏀寔澶氭姤琛ㄧ被鍨嬶細H2032 璐ц繍銆丠2031 鍏矾鏃呭銆丆ITY_BUS 鍩庡競鍏氦銆丆ITY_TAXI 宸℃父鍑虹銆�
  */
 @Slf4j
 @RestController
@@ -27,47 +35,171 @@
 
     @Resource
     private H2032EnterpriseMonthlyMapper h2032Mapper;
+    @Resource
+    private PassengerEnterpriseMonthlyMapper passengerMapper;
+    @Resource
+    private CityBusMonthlyMapper cityBusMapper;
+    @Resource
+    private CityTaxiMonthlyMapper cityTaxiMapper;
 
     @GetMapping("/list")
-    public Result<Map<String, Object>> list(@RequestParam(value = "period", required = false) String period,
+    public Result<Map<String, Object>> list(@RequestParam(value = "reportType", defaultValue = "H2032") String reportType,
+                                            @RequestParam(value = "period", required = false) String period,
                                             @RequestParam(value = "keyword", required = false) String keyword,
+                                            @RequestParam(value = "hasExplanation", defaultValue = "false") boolean hasExplanation,
                                             @RequestParam(value = "page", defaultValue = "1") long page,
                                             @RequestParam(value = "size", defaultValue = "20") long size) {
         String kw = keyword == null ? "" : keyword.trim();
         boolean hasPeriod = period != null && !period.trim().isEmpty();
-
-        LambdaQueryWrapper<H2032EnterpriseMonthly> w = new LambdaQueryWrapper<H2032EnterpriseMonthly>()
-            .eq(hasPeriod, H2032EnterpriseMonthly::getReportPeriod, period)
-            .isNotNull(H2032EnterpriseMonthly::getVerifyExplanation)
-            .ne(H2032EnterpriseMonthly::getVerifyExplanation, "")
-            .like(!kw.isEmpty(), H2032EnterpriseMonthly::getEnterpriseName, kw)
-            .orderByDesc(H2032EnterpriseMonthly::getId);
-
-        Page<H2032EnterpriseMonthly> p = new Page<>(Math.max(page, 1), Math.min(Math.max(size, 1), 200));
-        Page<H2032EnterpriseMonthly> r = h2032Mapper.selectPage(p, w);
+        long p = Math.max(page, 1);
+        long s = Math.min(Math.max(size, 1), 200);
 
         List<Map<String, Object>> records = new ArrayList<>();
-        for (H2032EnterpriseMonthly e : r.getRecords()) {
-            Map<String, Object> row = new LinkedHashMap<>();
-            row.put("id", e.getId());
-            row.put("reportPeriod", e.getReportPeriod());
-            row.put("regionCode", e.getRegionCode());
-            row.put("enterpriseCode", e.getEnterpriseCode());
-            row.put("enterpriseName", e.getEnterpriseName());
-            row.put("vehicleTotal", e.getVehicleTotal());
-            row.put("tonsTotal", e.getTonsTotal());
-            row.put("freightTotal", e.getFreightTotal());
-            row.put("turnoverTotal", e.getTurnoverTotal());
-            row.put("avgDistance", e.getAvgDistance());
-            row.put("verifyExplanation", e.getVerifyExplanation());
-            records.add(row);
+        long total = 0;
+
+        if ("H2031".equals(reportType)) {
+            LambdaQueryWrapper<PassengerEnterpriseMonthly> w = new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+                .eq(hasPeriod, PassengerEnterpriseMonthly::getReportPeriod, period)
+                .isNotNull(hasExplanation, PassengerEnterpriseMonthly::getVerifyExplanation)
+                .ne(hasExplanation, PassengerEnterpriseMonthly::getVerifyExplanation, "")
+                .like(!kw.isEmpty(), PassengerEnterpriseMonthly::getEnterpriseName, kw)
+                .orderByDesc(PassengerEnterpriseMonthly::getId);
+            Page<PassengerEnterpriseMonthly> r = passengerMapper.selectPage(new Page<>(p, s), w);
+            for (PassengerEnterpriseMonthly e : r.getRecords()) {
+                Map<String, Object> row = baseRow(e.getId(), e.getReportPeriod(), e.getRegionCode(),
+                        e.getEnterpriseCode(), e.getEnterpriseName(), e.getVerifyExplanation());
+                row.put("vehicleTotal", e.getVehicleTotal());
+                row.put("seatTotal", e.getSeatTotal());
+                row.put("passengerTotal", e.getPassengerTotal());
+                row.put("turnoverTotal", e.getTurnoverTotal());
+                row.put("avgDistance", e.getAvgDistanceTotal());
+                records.add(row);
+            }
+            total = r.getTotal();
+        } else if ("CITY_BUS".equals(reportType)) {
+            LambdaQueryWrapper<CityBusMonthly> w = new LambdaQueryWrapper<CityBusMonthly>()
+                .eq(hasPeriod, CityBusMonthly::getReportPeriod, period)
+                .isNotNull(hasExplanation, CityBusMonthly::getVerifyExplanation)
+                .ne(hasExplanation, CityBusMonthly::getVerifyExplanation, "")
+                .like(!kw.isEmpty(), CityBusMonthly::getEnterpriseName, kw)
+                .orderByDesc(CityBusMonthly::getId);
+            Page<CityBusMonthly> r = cityBusMapper.selectPage(new Page<>(p, s), w);
+            for (CityBusMonthly e : r.getRecords()) {
+                Map<String, Object> row = baseRow(e.getId(), e.getReportPeriod(), e.getRegionCode(),
+                        e.getEnterpriseCode(), e.getEnterpriseName(), e.getVerifyExplanation());
+                row.put("city", e.getCity());
+                row.put("opVehicles", e.getOpVehicles());
+                row.put("passengerVolume", e.getPassengerVolume());
+                row.put("turnover", e.getTurnover());
+                row.put("avgDistance", e.getAvgDistance());
+                records.add(row);
+            }
+            total = r.getTotal();
+        } else if ("CITY_TAXI".equals(reportType)) {
+            LambdaQueryWrapper<CityTaxiMonthly> w = new LambdaQueryWrapper<CityTaxiMonthly>()
+                .eq(hasPeriod, CityTaxiMonthly::getReportPeriod, period)
+                .isNotNull(hasExplanation, CityTaxiMonthly::getVerifyExplanation)
+                .ne(hasExplanation, CityTaxiMonthly::getVerifyExplanation, "")
+                .like(!kw.isEmpty(), CityTaxiMonthly::getCity, kw)
+                .orderByDesc(CityTaxiMonthly::getId);
+            Page<CityTaxiMonthly> r = cityTaxiMapper.selectPage(new Page<>(p, s), w);
+            for (CityTaxiMonthly e : r.getRecords()) {
+                Map<String, Object> row = baseRow(e.getId(), e.getReportPeriod(), e.getRegionCode(),
+                        null, e.getCity(), e.getVerifyExplanation());
+                row.put("city", e.getCity());
+                row.put("opVehicles", e.getOpVehicles());
+                row.put("tripTotal", e.getTripTotal());
+                row.put("passengerVolume", e.getPassengerVolume());
+                row.put("turnover", e.getTurnover());
+                row.put("avgDistance", e.getAvgDistance());
+                records.add(row);
+            }
+            total = r.getTotal();
+        } else {
+            LambdaQueryWrapper<H2032EnterpriseMonthly> w = new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+                .eq(hasPeriod, H2032EnterpriseMonthly::getReportPeriod, period)
+                .isNotNull(hasExplanation, H2032EnterpriseMonthly::getVerifyExplanation)
+                .ne(hasExplanation, H2032EnterpriseMonthly::getVerifyExplanation, "")
+                .like(!kw.isEmpty(), H2032EnterpriseMonthly::getEnterpriseName, kw)
+                .orderByDesc(H2032EnterpriseMonthly::getId);
+            Page<H2032EnterpriseMonthly> r = h2032Mapper.selectPage(new Page<>(p, s), w);
+            for (H2032EnterpriseMonthly e : r.getRecords()) {
+                Map<String, Object> row = baseRow(e.getId(), e.getReportPeriod(), e.getRegionCode(),
+                        e.getEnterpriseCode(), e.getEnterpriseName(), e.getVerifyExplanation());
+                row.put("vehicleTotal", e.getVehicleTotal());
+                row.put("tonsTotal", e.getTonsTotal());
+                row.put("freightTotal", e.getFreightTotal());
+                row.put("turnoverTotal", e.getTurnoverTotal());
+                row.put("avgDistance", e.getAvgDistance());
+                records.add(row);
+            }
+            total = r.getTotal();
         }
 
         Map<String, Object> result = new LinkedHashMap<>();
+        result.put("reportType", reportType);
         result.put("records", records);
-        result.put("total", r.getTotal());
-        result.put("current", r.getCurrent());
-        result.put("size", r.getSize());
+        result.put("total", total);
+        result.put("current", p);
+        result.put("size", s);
         return Result.ok(result);
     }
-}
\ No newline at end of file
+
+    /**
+     * 淇濆瓨浼佷笟瑙i噴锛氭洿鏂板搴旀潵婧愯〃锛圚2032/H2031/CITY_BUS/CITY_TAXI锛夎鐨� verifyExplanation銆�
+     * 瀹℃牳缁撴灉椤靛唴宓岃В閲婃ā鍧椾笌銆屼紒涓氳В閲娿�嶉〉鍏辩敤姝ゆ帴鍙c��
+     */
+    @PostMapping("/save")
+    public Result<String> save(@RequestParam("reportType") String reportType,
+                               @RequestParam("reportId") Long reportId,
+                               @RequestParam(value = "explanation", required = false) String explanation) {
+        String exp = explanation == null ? "" : explanation.trim();
+        boolean ok = false;
+        String t = reportType == null ? "" : reportType;
+        try {
+            switch (t) {
+                case "H2032": {
+                    H2032EnterpriseMonthly e = h2032Mapper.selectById(reportId);
+                    if (e != null) { e.setVerifyExplanation(exp); h2032Mapper.updateById(e); ok = true; }
+                    break;
+                }
+                case "H2031": {
+                    PassengerEnterpriseMonthly e = passengerMapper.selectById(reportId);
+                    if (e != null) { e.setVerifyExplanation(exp); passengerMapper.updateById(e); ok = true; }
+                    break;
+                }
+                case "CITY_BUS": {
+                    CityBusMonthly e = cityBusMapper.selectById(reportId);
+                    if (e != null) { e.setVerifyExplanation(exp); cityBusMapper.updateById(e); ok = true; }
+                    break;
+                }
+                case "CITY_TAXI": {
+                    CityTaxiMonthly e = cityTaxiMapper.selectById(reportId);
+                    if (e != null) { e.setVerifyExplanation(exp); cityTaxiMapper.updateById(e); ok = true; }
+                    break;
+                }
+                default:
+                    throw new RuntimeException("涓嶆敮鎸佺殑鎶ヨ〃绫诲瀷锛�" + reportType);
+            }
+        } catch (Exception e) {
+            log.error("explain save error", e);
+            return Result.error("淇濆瓨澶辫触: " + e.getMessage());
+        }
+        if (!ok) {
+            return Result.error("鏈壘鍒板搴旂殑鏁版嵁琛岋紙reportId=" + reportId + "锛�");
+        }
+        return Result.ok(exp);
+    }
+
+    private Map<String, Object> baseRow(Long id, String reportPeriod, String regionCode,
+                                        String enterpriseCode, String enterpriseName, String verifyExplanation) {
+        Map<String, Object> row = new LinkedHashMap<>();
+        row.put("id", id);
+        row.put("reportPeriod", reportPeriod);
+        row.put("regionCode", regionCode);
+        row.put("enterpriseCode", enterpriseCode);
+        row.put("enterpriseName", enterpriseName);
+        row.put("verifyExplanation", verifyExplanation);
+        return row;
+    }
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityBusMonthly.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityBusMonthly.java
new file mode 100644
index 0000000..c6c6a79
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityBusMonthly.java
@@ -0,0 +1,117 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鍩庡競瀹㈣繍绗�1姝ワ細鍩庡競鍏氦浼佷笟鏈堝害鏁版嵁
+ * 鏁版嵁婧愶細瀵煎叆_鍩庡競鍏叡浜ら�氭湀搴﹁繍钀ユ儏鍐碉紙YYYY骞碝鏈堬級.xls锛堜紒涓氱骇鏄庣粏锛屼粎鍙栧叕鍏辨苯鐢佃溅瀛楁锛�
+ */
+@Data
+@TableName("city_bus_monthly")
+public class CityBusMonthly {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 鎶ヨ〃鏈� 2026-07 */
+    private String reportPeriod;
+    /** 鎵�灞炲湴鍖猴紙琛屾斂鍖哄垝浠g爜锛� */
+    private String regionCode;
+    /** 甯傚窞锛堢敱鍦板尯浠g爜鏄犲皠锛� */
+    private String city;
+    /** 浼佷笟浠g爜 */
+    private String enterpriseCode;
+    /** 浼佷笟鍚嶇О */
+    private String enterpriseName;
+
+    /** 鍏叡姹界數杞﹁繍钀ヨ溅鏁� */
+    private Double opVehicles;
+    /** 杩愯惀绔欑偣鎬讳釜鏁� */
+    private Double stopTotal;
+    /** 鍏朵腑锛氬煄甯傚唴杩愯惀绔欑偣 */
+    private Double stopCity;
+    /** 鍏叡姹界數杞﹀杩愰噺锛堜竾浜烘锛� */
+    private Double passengerVolume;
+    /** 鍏朵腑锛氬煄甯傚唴瀹㈣繍閲忥紙涓囦汉娆★級 */
+    private Double passengerCity;
+    /** 鍏叡姹界數杞︽梾瀹㈠懆杞噺锛堜竾浜哄叕閲岋級 */
+    private Double turnover;
+    /** 鍏朵腑锛氬煄甯傚唴鏃呭鍛ㄨ浆閲忥紙涓囦汉鍏噷锛� */
+    private Double turnoverCity;
+    /** 鍏叡姹界數杞﹀钩鍧囪繍璺濓紙鍏噷锛� */
+    private Double avgDistance;
+    /** 鍩庡競鍐呭钩鍧囪繍璺濓紙鍏噷锛� */
+    private Double avgDistanceCity;
+    /** 鍩庨檯鍩庝埂瀹㈣繍閲忥紙涓囦汉娆★級 */
+    private Double passengerChengxiang;
+    /** 鍩庨檯鍩庝埂鏃呭鍛ㄨ浆閲忥紙涓囦汉鍏噷锛� */
+    private Double turnoverChengxiang;
+    /** 鍩庨檯鍩庝埂骞冲潎杩愯窛锛堝叕閲岋級 */
+    private Double avgDistanceChengxiang;
+
+    /** 杩愯惀杞︽暟鐜瘮澧為�� */
+    private Double rateOpVehiclesMom;
+    /** 瀹㈣繍閲忕幆姣斿閫� */
+    private Double ratePassengerMom;
+    /** 鍩庡競鍐呭杩愰噺鐜瘮 */
+    private Double ratePassengerCityMom;
+    /** 鍩庨檯鍩庝埂瀹㈣繍閲忕幆姣斿閫� */
+    private Double rateChengxiangMom;
+    /** 鍩庨檯鍩庝埂鏃呭鍛ㄨ浆閲忕幆姣斿閫� */
+    private Double rateChengxiangTurnoverMom;
+    /** 鏃呭鍛ㄨ浆閲忕幆姣斿閫� */
+    private Double rateTurnoverMom;
+    /** 骞冲潎杩愯窛鐜瘮澧炲噺锛堝叕閲岋級 */
+    private Double avgDistanceMomChange;
+    /** 鍩庡競鍐呭钩鍧囪繍璺濈幆姣斿鍑忥紙鍏噷锛� */
+    private Double avgDistanceCityMomChange;
+    /** 鍗曡溅鏃ュ潎瀹㈣繍閲� */
+    private Double dailyPassengerPerVehicle;
+
+    /** 杩愯惀杞︽暟鍚屾瘮澧為�� */
+    private Double rateOpVehiclesYoy;
+    /** 瀹㈣繍閲忓悓姣斿閫� */
+    private Double ratePassengerYoy;
+    /** 鍩庡競鍐呭杩愰噺鍚屾瘮澧為�� */
+    private Double ratePassengerCityYoy;
+    /** 鍩庨檯鍩庝埂瀹㈣繍閲忓悓姣斿閫� */
+    private Double rateChengxiangYoy;
+    /** 鍩庨檯鍩庝埂鏃呭鍛ㄨ浆閲忓悓姣斿閫� */
+    private Double rateChengxiangTurnoverYoy;
+    /** 鏃呭鍛ㄨ浆閲忓悓姣斿閫� */
+    private Double rateTurnoverYoy;
+
+    /** 鍩庡競鍐呰繍钀ョ珯鐐瑰崰姣� */
+    private Double stopCityRatio;
+    /** 鍩庡競鍐呭杩愰噺鍗犳瘮 */
+    private Double passengerCityRatio;
+    /** 鍩庨檯鍩庝埂瀹㈣繍閲忓崰姣� */
+    private Double chengxiangRatio;
+
+    /** 濉〃浜� */
+    private String contactPerson;
+    /** 鑱旂郴鐢佃瘽 */
+    private String contactPhone;
+    /** 鎶ュ嚭鏃ユ湡 */
+    private String reportDate;
+    /** 鏍稿疄鎬у師鍥犺В閲� */
+    private String verifyExplanation;
+    /** 涓婃姤璇存槑 */
+    private String reportNote;
+    /** 淇敼璁板綍 */
+    private String modifyRecord;
+
+    /** 杞ㄩ亾浜ら�歘瀹㈣繍閲忥紙涓囦汉娆★級 */
+    private Double railPassengerVolume;
+    /** 杞ㄩ亾浜ら�歘鏃呭鍛ㄨ浆閲忥紙涓囦汉鍏噷锛� */
+    private Double railTurnover;
+    /** 鍩庡競瀹㈣繍杞浮_瀹㈣繍閲忥紙涓囦汉娆★級 */
+    private Double ferryPassengerVolume;
+    /** 鍩庡競瀹㈣繍杞浮_鏃呭鍛ㄨ浆閲忥紙涓囦汉鍏噷锛� */
+    private Double ferryTurnover;
+
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityTaxiAuth.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityTaxiAuth.java
new file mode 100644
index 0000000..45af4ae
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityTaxiAuth.java
@@ -0,0 +1,49 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鍩庡競瀹㈣繍绗�2姝ワ細鍑虹杞﹁繍鏀胯溅杈嗕俊鎭紙杞﹁締绾ф槑缁嗭級
+ * 鏁版嵁婧愶細杈撳叆_鍑虹杞︿俊鎭�(杩愭斂杞﹁締淇℃伅).xls
+ */
+@Data
+@TableName("city_taxi_auth")
+public class CityTaxiAuth {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 鎶ヨ〃鏈� */
+    private String reportPeriod;
+    /** 甯傚窞锛堢敱璇佸彿/妗f鍙�/鍦板潃鎻愬彇锛� */
+    private String city;
+    /** 杞︾墝鍙风爜 */
+    private String plateNo;
+    /** 杞﹁締绫诲瀷 */
+    private String vehicleType;
+    /** 缁忚惀绫诲瀷 */
+    private String bizType;
+    /** 杞︿富鍚嶇О */
+    private String ownerName;
+    /** 缁忚惀鏉冨彿 */
+    private String licenseNo;
+    /** 妗f鍙� */
+    private String archiveNo;
+    /** 閬撹矾杩愯緭璇佸瓧鍙� */
+    private String roadCertNo;
+    /** 鐕冩枡绫诲瀷 */
+    private String fuelType;
+    /** 涓氭埛鍚嶇О */
+    private String operatorName;
+    /** 涓氭埛鍦板潃 */
+    private String operatorAddr;
+    /** 钀ヨ繍鐘舵�� */
+    private String opStatus;
+    /** 璇佷欢鐘舵�� */
+    private String certStatus;
+
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityTaxiMonthly.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityTaxiMonthly.java
new file mode 100644
index 0000000..5b03381
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/CityTaxiMonthly.java
@@ -0,0 +1,95 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鍩庡競瀹㈣繍绗�2姝ワ細宸℃父鍑虹姹借溅杩愯惀鏈嶅姟鎯呭喌鏈堟姤锛堝競宸炵骇锛�
+ * 鏁版嵁婧愶細瀵煎叆_宸℃父鍑虹姹借溅杩愯惀鏈嶅姟鎯呭喌鏈堟姤锛圷YYY骞碝鏈堬級.xls
+ */
+@Data
+@TableName("city_taxi_monthly")
+public class CityTaxiMonthly {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 鎶ヨ〃鏈� 2026-07 */
+    private String reportPeriod;
+    /** 鎵�灞炲湴鍖轰唬鐮� */
+    private String regionCode;
+    /** 甯傚窞 */
+    private String city;
+
+    /** 杞藉杞︽鎬绘暟 */
+    private Double tripTotal;
+    /** 杞藉杞︽鎬绘暟_鍩庡競鍐� */
+    private Double tripCity;
+    /** 瀹㈣繍閲忥紙涓囦汉娆★級 */
+    private Double passengerVolume;
+    /** 瀹㈣繍閲廮鍏朵腑鍩庡競鍐� */
+    private Double passengerCity;
+    /** 鏃呭鍛ㄨ浆閲忥紙涓囦汉鍏噷锛� */
+    private Double turnover;
+    /** 鏃呭鍛ㄨ浆閲廮鍩庡競鍐� */
+    private Double turnoverCity;
+    /** 杩愯惀杞﹁締鏁� */
+    private Double opVehicles;
+    /** 宸℃父鍑虹姹借溅骞冲潎杩愯窛锛堝叕閲岋級 */
+    private Double avgDistance;
+    /** 鍩庡競鍐呭钩鍧囪繍璺濓紙鍏噷锛� */
+    private Double avgDistanceCity;
+    /** 鍗曡溅鏃ュ潎杞藉杞︽ */
+    private Double dailyTripsPerVehicle;
+    /** 鍗曡溅鏃ュ潎瀹㈣繍閲� */
+    private Double dailyPassengerPerVehicle;
+    /** 鍗曡溅娆¤浇瀹汉鏁� */
+    private Double passengersPerTrip;
+    /** 鍗曡溅娆¤浇瀹汉鏁癬鍩庡競鍐� */
+    private Double passengersPerTripCity;
+    /** 鍗曡溅娆¤浇瀹汉鏁癬鍩庨檯鍩庝埂 */
+    private Double passengersPerTripChengxiang;
+
+    /** 杩愯惀杞︽暟鐜瘮澧為�� */
+    private Double rateOpVehiclesMom;
+    /** 杩愯惀杞︽暟鍚屾瘮澧為�� */
+    private Double rateOpVehiclesYoy;
+    /** 杞藉杞︽鎬绘暟鐜瘮澧為�� */
+    private Double rateTripMom;
+    /** 杞藉杞︽鍚屾瘮澧為�� */
+    private Double rateTripYoy;
+    /** 瀹㈣繍閲忕幆姣斿閫� */
+    private Double ratePassengerMom;
+    /** 瀹㈣繍閲忓悓姣斿閫� */
+    private Double ratePassengerYoy;
+    /** 骞冲潎杩愯窛鐜瘮澧炲噺锛堝叕閲岋級 */
+    private Double avgDistanceMomChange;
+    /** 骞冲潎杩愯窛_鍩庡競鍐呯幆姣斿鍑忥紙鍏噷锛� */
+    private Double avgDistanceCityMomChange;
+    /** 杞藉杞︽鎬绘暟鐜瘮澧為�焈鍩庡競鍐� */
+    private Double rateTripCityMom;
+    /** 瀹㈣繍閲忕幆姣斿閫焈鍩庡競鍐� */
+    private Double ratePassengerCityMom;
+    /** 杞藉杞︽鎬绘暟_鍩庨檯鍩庝埂鐜瘮澧為�� */
+    private Double rateTripChengxiangMom;
+    /** 瀹㈣繍閲廮鍩庨檯鍩庝埂鐜瘮澧為�� */
+    private Double ratePassengerChengxiangMom;
+    /** 骞冲潎杩愯窛_鍩庨檯鍩庝埂锛堝叕閲岋級 */
+    private Double avgDistanceChengxiang;
+
+    /** 杞藉杞︽鎬绘暟_鍩庡競鍐呭崰姣� */
+    private Double tripCityRatio;
+    /** 瀹㈣繍閲廮鍩庡競鍐呭崰姣� */
+    private Double passengerCityRatio;
+
+    private String contactPerson;
+    private String contactPhone;
+    private String reportDate;
+    private String verifyExplanation;
+    private String reportNote;
+    private String modifyRecord;
+
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/EnergyAuthVehicle.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/EnergyAuthVehicle.java
new file mode 100644
index 0000000..d4ed344
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/EnergyAuthVehicle.java
@@ -0,0 +1,32 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 閬撹矾璐ц繍鑳借�梍杩愭斂杞﹁締淇℃伅锛堣溅鐗岀矑搴︼級
+ */
+@Data
+@TableName("h204_auth_vehicle")
+public class EnergyAuthVehicle {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 鎶ヨ〃鏈� */
+    private String reportPeriod;
+    /** 杞︾墝鍙� */
+    private String plateNo;
+    /** 杞﹁締绫诲瀷 */
+    private String vehicleType;
+    /** 鐕冩枡绫诲瀷 */
+    private String fuelType;
+    /** 鏍囪鍚ㄤ綅锛堝惃锛� */
+    private Double markedTonnage;
+    /** 鍑嗙壍寮曡川閲忥紙鐗靛紩杞︽爣璁板惃浣嶏級 */
+    private Double tractionQuality;
+    private Long importBatchId;
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/EnergyVehicleQuarterly.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/EnergyVehicleQuarterly.java
new file mode 100644
index 0000000..d3b3c5f
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/EnergyVehicleQuarterly.java
@@ -0,0 +1,70 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 浜や紒缁烪204琛細閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙杞﹁締绮掑害瀛f姤锛�
+ */
+@Data
+@TableName("h204_vehicle_quarterly")
+public class EnergyVehicleQuarterly {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 鎶ヨ〃鏈� 2026-06锛堝搴︽湯鏈堬級 */
+    private String reportPeriod;
+    /** 鎵�灞炲湴鍖猴紙琛屾斂鍖哄垝浠g爜锛� */
+    private String regionCode;
+    /** 浼佷笟浠g爜 */
+    private String enterpriseCode;
+    /** 浼佷笟鍚嶇О */
+    private String enterpriseName;
+    /** 杞︾墝鍙� */
+    private String plateNo;
+    /** 杞﹁締绫诲瀷浠g爜 */
+    private String vehicleTypeCode;
+    /** 杞﹁締绫诲瀷锛堜腑鏂囷級 */
+    private String vehicleType;
+    /** 鐕冩枡绫诲瀷浠g爜 */
+    private String fuelTypeCode;
+    /** 鐕冩枡绫诲瀷锛堜腑鏂囷級 */
+    private String fuelType;
+    /** 鍑哄巶骞翠唤 */
+    private Integer manufactureYear;
+    /** 鏍囪鍚ㄤ綅锛堝惃锛� */
+    private Double markedTonnage;
+    /** 鎬昏绋嬶紙鍏噷锛� */
+    private Double totalMileage;
+    /** 杞借揣閲岀▼锛堝叕閲岋級 */
+    private Double loadedMileage;
+    /** 绌洪┒閲岀▼锛堝叕閲岋級 */
+    private Double emptyMileage;
+    /** 璐ц繍閲忥紙鍚級 */
+    private Double freight;
+    /** 璐х墿鍛ㄨ浆閲忥紙鍚ㄥ叕閲岋級 */
+    private Double turnover;
+    /** 瓒熸鏁帮紙涓級 */
+    private Integer tripCount;
+    /** 鐕冩枡1璁¢噺鍗曚綅 */
+    private String fuelUnit;
+    /** 鐕冩枡1娑堣�楅噺 */
+    private Double fuelConsumption;
+    /** 骞冲潎杩愯窛锛堝叕閲岋級 */
+    private Double avgDistance;
+    /** 骞冲潎杞借揣鍚ㄤ綅锛堝惃锛� */
+    private Double avgLoadedTonnage;
+    /** 鍗曡稛骞冲潎閲岀▼锛堝叕閲岋級 */
+    private Double avgTripMileage;
+    /** 鐧惧叕閲屽崟鑰� */
+    @TableField("fuel_per_100km")
+    private Double fuelPer100km;
+    /** 鍛ㄨ浆閲忓崟鑰� */
+    private Double turnoverFuel;
+    private Long importBatchId;
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentMonthly.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentMonthly.java
new file mode 100644
index 0000000..3a1434b
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentMonthly.java
@@ -0,0 +1,37 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鎶曡祫椤圭洰鏈堝害瀹屾垚鎯呭喌
+ */
+@Data
+@TableName("investment_monthly")
+public class InvestmentMonthly {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 鎶ヨ〃鏈� 2026-07 */
+    private String reportPeriod;
+    /** 椤圭洰涓绘。ID */
+    private Long projectId;
+    /** 鏈勾璁″垝鎶曡祫锛堜竾鍏冿級 */
+    private Double yearPlan;
+    /** 鑷勾鍒濈疮璁″畬鎴愭姇璧勶紙涓囧厓锛� */
+    private Double yearCum;
+    /** 鍏朵腑锛氭湰鏈堝畬鎴愭姇璧勶紙涓囧厓锛� */
+    private Double monthDone;
+    /** 鑷紑濮嬪缓璁剧疮璁″畬鎴愭姇璧勶紙涓囧厓锛� */
+    private Double startCum;
+    /** 寤鸿闃舵 */
+    private String progressStage;
+    /** 褰㈣薄杩涘害 */
+    private String progressDesc;
+    /** 寤虹瓚闈㈢Н锛堝钩鏂圭背锛� */
+    private Double buildingArea;
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentProject.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentProject.java
new file mode 100644
index 0000000..5130752
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentProject.java
@@ -0,0 +1,53 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鎶曡祫椤圭洰涓绘。锛堝杩愮珯鍦� / 鐗╂祦鍥尯锛�
+ */
+@Data
+@TableName("investment_project")
+public class InvestmentProject {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 椤圭洰鍚嶇О */
+    private String projectName;
+    /** 绫诲埆锛氬杩愮珯鍦� / 鐗╂祦鍥尯 */
+    private String category;
+    /** 甯傚窞 */
+    private String city;
+    /** 鍘匡紙甯傘�佸尯锛� */
+    private String county;
+    /** 鎶曡祫寤鸿鍗曚綅 */
+    private String builderName;
+    /** 寤鸿鎬ц川 */
+    private String constructNature;
+    /** 寮�宸ユ椂闂� */
+    private String startTime;
+    /** 寤烘垚/绔e伐鏃堕棿 */
+    private String endTime;
+    /** 璁″垝鎬绘姇璧勶紙涓囧厓锛� */
+    private Double totalInvestment;
+    /** 宸ュ彲鎵瑰 */
+    private String approvalGk;
+    /** 鍒濊鎵瑰 */
+    private String approvalCs;
+    /** 寤虹瓚闈㈢Н锛堝钩鏂圭背锛� */
+    private Double buildingArea;
+    /** 浜垮厓鎶曡祫椤圭洰鏍囪 */
+    private Integer isBillion;
+    /** 缁忔祹寮哄幙椤圭洰鏍囪 */
+    private Integer isCounty;
+    /** 鍗佷簲浜旈噸鐐圭墿娴侀」鐩爣璁� */
+    private Integer isFiveYear;
+    /** 鏁版嵁鏉ユ簮 */
+    private String source;
+    private Long importBatchId;
+    private LocalDateTime createdAt;
+    private LocalDateTime updatedAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentSystem.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentSystem.java
new file mode 100644
index 0000000..f628ebf
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/InvestmentSystem.java
@@ -0,0 +1,34 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鎶曡祫绯荤粺鏌ヨ瀵煎嚭锛堝鏍告瘮瀵瑰熀鍑嗭級
+ */
+@Data
+@TableName("investment_system")
+public class InvestmentSystem {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 鎶ヨ〃鏈� 2026-07 */
+    private String reportPeriod;
+    /** 椤圭洰鍚嶇О锛堝崟浣嶏級 */
+    private String projectName;
+    /** 璁″垝鎬绘姇璧勶紙涓囧厓锛� */
+    private Double totalInvestment;
+    /** 鑷紑濮嬪缓璁捐嚦鏈湀绱瀹屾垚鎶曡祫锛堜竾鍏冿級 */
+    private Double startCum;
+    /** 鏈勾璁″垝鎶曡祫锛堜竾鍏冿級 */
+    private Double yearPlan;
+    /** 鑷勾鍒濈疮璁″畬鎴愭姇璧勶紙涓囧厓锛� */
+    private Double yearCum;
+    /** 褰撴湀瀹屾垚鎶曡祫锛堜竾鍏冿級 */
+    private Double monthDone;
+    private Long importBatchId;
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerAuthVehicle.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerAuthVehicle.java
new file mode 100644
index 0000000..2fb0b83
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerAuthVehicle.java
@@ -0,0 +1,29 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鍏矾鏃呭杩愭斂杞﹁締鏁版嵁锛堣溅杈嗘暟/杞藉浣嶆暟锛�
+ */
+@Data
+@TableName("passenger_auth_vehicle")
+public class PassengerAuthVehicle {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String reportPeriod;
+    /** 浼佷笟浠g爜 */
+    private String enterpriseCode;
+    /** 浼佷笟鍚嶇О */
+    private String enterpriseName;
+    /** 杞﹁締鏁� */
+    private Integer vehicleCount;
+    /** 杞藉浣嶆暟 */
+    private Integer seatCount;
+    private Long importBatchId;
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerEnterpriseMonthly.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerEnterpriseMonthly.java
new file mode 100644
index 0000000..1a690af
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerEnterpriseMonthly.java
@@ -0,0 +1,73 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 浜や紒缁烪203-1琛細鍏矾鏃呭杩愯緭鏈堝害鐢熶骇鎯呭喌
+ */
+@Data
+@TableName("h2031_enterprise_monthly")
+public class PassengerEnterpriseMonthly {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String reportPeriod;
+    /** 鎵�灞炲湴鍖猴紙琛屾斂鍖哄垝浠g爜锛� */
+    private String regionCode;
+    /** 浼佷笟浠g爜 */
+    private String enterpriseCode;
+    /** 浼佷笟鍚嶇О */
+    private String enterpriseName;
+
+    /** 瀹㈣繍杞﹁締_鎬昏 */
+    private Integer vehicleTotal;
+    /** 鏍稿畾杞藉浣峗鎬昏 */
+    private Integer seatTotal;
+    /** 杞﹁締鏁癬瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父瀹㈣繍瀹㈣溅锛� */
+    private Integer vehicleSchedule;
+    /** 鏍稿畾杞藉浣峗瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父瀹㈣繍瀹㈣溅锛� */
+    private Integer seatSchedule;
+    /** 杞﹁締鏁癬瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓稿杩愬杞︼級 */
+    private Integer vehicleCharter;
+    /** 鏍稿畾杞藉浣峗瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓稿杩愬杞︼級 */
+    private Integer seatCharter;
+
+    /** 瀹㈣繍閲廮鎬昏 */
+    private Double passengerTotal;
+    /** 鏃呭鍛ㄨ浆閲廮鎬昏 */
+    private Double turnoverTotal;
+
+    /** 瀹㈣繍閲廮涓�绫诲杩愮彮绾匡紙鍚畾绾挎梾娓革級 */
+    private Double passengerClass1;
+    private Double turnoverClass1;
+    /** 瀹㈣繍閲廮浜岀被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛� */
+    private Double passengerClass2;
+    private Double turnoverClass2;
+    /** 瀹㈣繍閲廮涓夌被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛� */
+    private Double passengerClass3;
+    private Double turnoverClass3;
+    /** 瀹㈣繍閲廮鍥涚被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛� */
+    private Double passengerClass4;
+    private Double turnoverClass4;
+    /** 瀹㈣繍閲廮瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓革級 */
+    private Double passengerCharter;
+    private Double turnoverCharter;
+
+    /** 瀹㈣繍骞冲潎杩愯窛 */
+    private Double avgDistanceTotal;
+    /** 涓�绫诲杩愮彮绾垮钩鍧囪繍璺� */
+    private Double avgDistanceClass1;
+    private Double avgDistanceClass2;
+    private Double avgDistanceClass3;
+    private Double avgDistanceClass4;
+    /** 鏃呮父銆佸寘杞﹀钩鍧囪繍璺� */
+    private Double avgDistanceCharter;
+
+    private String verifyExplanation;
+    private Long importBatchId;
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerIndividualMonthly.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerIndividualMonthly.java
new file mode 100644
index 0000000..aca8286
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/entity/PassengerIndividualMonthly.java
@@ -0,0 +1,31 @@
+package com.trafficaudit.dataimport.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鍏矾鏃呭涓綋瀹㈣繍閲�/鍛ㄨ浆閲忥紙浼佷笟绾夸笅姹囨姤锛岀敤鎴峰鍏ュ悗鎸夊競宸炴眹鎬诲~鍏呭垎甯傚窞琛ㄤ釜浣撹锛�
+ */
+@Data
+@TableName("passenger_individual_monthly")
+public class PassengerIndividualMonthly {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /** 鎶ヨ〃鏈� 2026-07 */
+    private String reportPeriod;
+    /** 鎵�灞炲湴鍖猴紙琛屾斂鍖哄垝浠g爜锛� */
+    private String regionCode;
+    /** 浼佷笟浠g爜 */
+    private String enterpriseCode;
+    /** 浼佷笟鍚嶇О */
+    private String enterpriseName;
+    /** 鍏朵腑涓綋瀹㈣繍閲忥紙浜猴級 */
+    private Double passengerCount;
+    /** 鍏朵腑涓綋鏃呭鍛ㄨ浆閲忥紙浜哄叕閲岋級 */
+    private Double turnover;
+    private LocalDateTime createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityBusMonthlyMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityBusMonthlyMapper.java
new file mode 100644
index 0000000..e2e4ac5
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityBusMonthlyMapper.java
@@ -0,0 +1,6 @@
+package com.trafficaudit.dataimport.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.CityBusMonthly;
+import org.apache.ibatis.annotations.Mapper;
+@Mapper
+public interface CityBusMonthlyMapper extends BaseMapper<CityBusMonthly> {}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityTaxiAuthMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityTaxiAuthMapper.java
new file mode 100644
index 0000000..e09e6cd
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityTaxiAuthMapper.java
@@ -0,0 +1,6 @@
+package com.trafficaudit.dataimport.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.CityTaxiAuth;
+import org.apache.ibatis.annotations.Mapper;
+@Mapper
+public interface CityTaxiAuthMapper extends BaseMapper<CityTaxiAuth> {}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityTaxiMonthlyMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityTaxiMonthlyMapper.java
new file mode 100644
index 0000000..70aea64
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/CityTaxiMonthlyMapper.java
@@ -0,0 +1,6 @@
+package com.trafficaudit.dataimport.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
+import org.apache.ibatis.annotations.Mapper;
+@Mapper
+public interface CityTaxiMonthlyMapper extends BaseMapper<CityTaxiMonthly> {}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/EnergyAuthVehicleMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/EnergyAuthVehicleMapper.java
new file mode 100644
index 0000000..ea18855
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/EnergyAuthVehicleMapper.java
@@ -0,0 +1,9 @@
+package com.trafficaudit.dataimport.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.EnergyAuthVehicle;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface EnergyAuthVehicleMapper extends BaseMapper<EnergyAuthVehicle> {
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/EnergyVehicleQuarterlyMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/EnergyVehicleQuarterlyMapper.java
new file mode 100644
index 0000000..792dd26
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/EnergyVehicleQuarterlyMapper.java
@@ -0,0 +1,9 @@
+package com.trafficaudit.dataimport.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface EnergyVehicleQuarterlyMapper extends BaseMapper<EnergyVehicleQuarterly> {
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentMonthlyMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentMonthlyMapper.java
new file mode 100644
index 0000000..7c126bf
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentMonthlyMapper.java
@@ -0,0 +1,9 @@
+package com.trafficaudit.dataimport.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.InvestmentMonthly;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface InvestmentMonthlyMapper extends BaseMapper<InvestmentMonthly> {
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentProjectMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentProjectMapper.java
new file mode 100644
index 0000000..5d3f36c
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentProjectMapper.java
@@ -0,0 +1,9 @@
+package com.trafficaudit.dataimport.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.InvestmentProject;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface InvestmentProjectMapper extends BaseMapper<InvestmentProject> {
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentSystemMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentSystemMapper.java
new file mode 100644
index 0000000..138b9bf
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/InvestmentSystemMapper.java
@@ -0,0 +1,9 @@
+package com.trafficaudit.dataimport.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.InvestmentSystem;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface InvestmentSystemMapper extends BaseMapper<InvestmentSystem> {
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerAuthVehicleMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerAuthVehicleMapper.java
new file mode 100644
index 0000000..9495dc7
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerAuthVehicleMapper.java
@@ -0,0 +1,6 @@
+package com.trafficaudit.dataimport.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.PassengerAuthVehicle;
+import org.apache.ibatis.annotations.Mapper;
+@Mapper
+public interface PassengerAuthVehicleMapper extends BaseMapper<PassengerAuthVehicle> {}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerEnterpriseMonthlyMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerEnterpriseMonthlyMapper.java
new file mode 100644
index 0000000..992dbb7
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerEnterpriseMonthlyMapper.java
@@ -0,0 +1,6 @@
+package com.trafficaudit.dataimport.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
+import org.apache.ibatis.annotations.Mapper;
+@Mapper
+public interface PassengerEnterpriseMonthlyMapper extends BaseMapper<PassengerEnterpriseMonthly> {}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerIndividualMonthlyMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerIndividualMonthlyMapper.java
new file mode 100644
index 0000000..8d5ac3c
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/mapper/PassengerIndividualMonthlyMapper.java
@@ -0,0 +1,6 @@
+package com.trafficaudit.dataimport.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly;
+import org.apache.ibatis.annotations.Mapper;
+@Mapper
+public interface PassengerIndividualMonthlyMapper extends BaseMapper<PassengerIndividualMonthly> {}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
index fc3edb9..deafe7a 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -4,19 +4,45 @@
 import com.trafficaudit.auditengine.entity.AuditResult;
 import com.trafficaudit.auditengine.mapper.AuditResultMapper;
 import com.trafficaudit.common.util.RegionUtil;
+import com.trafficaudit.dataimport.entity.EnergyAuthVehicle;
+import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly;
 import com.trafficaudit.dataimport.entity.FreightTurnoverImport;
 import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly;
+import com.trafficaudit.dataimport.entity.PassengerAuthVehicle;
+import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
+import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly;
 import com.trafficaudit.dataimport.entity.ImportBatch;
+import com.trafficaudit.dataimport.entity.InvestmentMonthly;
+import com.trafficaudit.dataimport.entity.InvestmentProject;
+import com.trafficaudit.dataimport.entity.InvestmentSystem;
+import com.trafficaudit.dataimport.entity.CityBusMonthly;
+import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
+import com.trafficaudit.dataimport.entity.CityTaxiAuth;
 import com.trafficaudit.dataimport.entity.ScaleSplitTransport;
 import com.trafficaudit.dataimport.entity.TransportAuthVehicle;
 import com.trafficaudit.dataimport.entity.VehicleTrackMileage;
+import com.trafficaudit.dataimport.mapper.EnergyAuthVehicleMapper;
+import com.trafficaudit.dataimport.mapper.EnergyVehicleQuarterlyMapper;
 import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper;
 import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.PassengerAuthVehicleMapper;
+import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.PassengerIndividualMonthlyMapper;
+import com.trafficaudit.rulemanage.mapper.AuditRuleMapper;
 import com.trafficaudit.dataimport.mapper.ImportBatchMapper;
+import com.trafficaudit.dataimport.mapper.InvestmentMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.InvestmentProjectMapper;
+import com.trafficaudit.dataimport.mapper.InvestmentSystemMapper;
+import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.CityTaxiAuthMapper;
 import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper;
 import com.trafficaudit.dataimport.mapper.TransportAuthVehicleMapper;
 import com.trafficaudit.dataimport.mapper.VehicleTrackMileageMapper;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellType;
 import org.apache.poi.ss.usermodel.DataFormatter;
@@ -24,10 +50,17 @@
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.ss.usermodel.WorkbookFactory;
+import cn.hutool.core.io.IoUtil;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -36,6 +69,11 @@
 import java.util.Set;
 import com.trafficaudit.dataimport.dto.ImportResult;
 import org.apache.poi.ss.usermodel.DateUtil;
+import java.util.Comparator;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.stream.Collectors;
 
 import java.text.SimpleDateFormat;
 
@@ -47,6 +85,40 @@
 public class DataImportService {
 
     private static final DataFormatter FORMATTER = new DataFormatter();
+
+    /** H204 鑳借�楁槑缁嗚溅杈嗙被鍨嬬爜 -> 涓枃 */
+    private static final Map<String, String> ENERGY_VEHICLE_TYPE_NAMES = new HashMap<>();
+
+    /** H204 鑳借�楁槑缁嗙噧鏂欑被鍨嬬爜 -> 涓枃 */
+    private static final Map<String, String> ENERGY_FUEL_TYPE_NAMES = new HashMap<>();
+
+    /** H204 鐕冩枡1璁¢噺鍗曚綅鐮� -> 涓枃鍗曚綅 */
+    private static final Map<String, String> ENERGY_FUEL_UNIT_NAMES = new HashMap<>();
+
+    static {
+        ENERGY_VEHICLE_TYPE_NAMES.put("01", "鏅�氳揣杞�");
+        ENERGY_VEHICLE_TYPE_NAMES.put("02", "骞虫澘璐ц溅");
+        ENERGY_VEHICLE_TYPE_NAMES.put("03", "浠撴爡寮忚揣杞�");
+        ENERGY_VEHICLE_TYPE_NAMES.put("04", "鍘㈠紡璐ц溅");
+        ENERGY_VEHICLE_TYPE_NAMES.put("06", "缃愬紡璐ц溅");
+        ENERGY_VEHICLE_TYPE_NAMES.put("07", "鐗规畩缁撴瀯璐ц溅");
+        ENERGY_VEHICLE_TYPE_NAMES.put("08", "鑷嵏璐ц溅");
+        ENERGY_VEHICLE_TYPE_NAMES.put("09", "杞﹁締杩愯緭杞�");
+        ENERGY_VEHICLE_TYPE_NAMES.put("11", "閲嶅瀷鍗婃寕鐗靛紩杞�");
+
+        ENERGY_FUEL_TYPE_NAMES.put("01", "姹芥补");
+        ENERGY_FUEL_TYPE_NAMES.put("02", "鏌存补");
+        ENERGY_FUEL_TYPE_NAMES.put("03", "鍘嬬缉澶╃劧姘�");
+        ENERGY_FUEL_TYPE_NAMES.put("04", "娑插寲澶╃劧姘�");
+        ENERGY_FUEL_TYPE_NAMES.put("07", "鐢靛姩");
+        ENERGY_FUEL_TYPE_NAMES.put("08", "姘㈡皵");
+
+        ENERGY_FUEL_UNIT_NAMES.put("1", "鍗�");
+        ENERGY_FUEL_UNIT_NAMES.put("2", "鍗�");
+        ENERGY_FUEL_UNIT_NAMES.put("4", "鍗冨厠");
+        ENERGY_FUEL_UNIT_NAMES.put("5", "鍗冨厠");
+        ENERGY_FUEL_UNIT_NAMES.put("7", "鍗冪摝鏃�");
+    }
 
     @Resource
     private H2032EnterpriseMonthlyMapper h2032Mapper;
@@ -62,6 +134,32 @@
     private FreightTurnoverImportMapper freightTurnoverMapper;
     @Resource
     private AuditResultMapper auditResultMapper;
+    @Resource
+    private PassengerEnterpriseMonthlyMapper passengerMapper;
+    @Resource
+    private PassengerIndividualMonthlyMapper passengerIndividualMapper;
+    @Resource
+    private PassengerAuthVehicleMapper passengerAuthMapper;
+    @Resource
+    private AuditRuleMapper ruleMapper;
+    @Resource
+    private EnergyVehicleQuarterlyMapper energyMapper;
+    @Resource
+    private EnergyAuthVehicleMapper energyAuthMapper;
+    @Resource
+    private InvestmentProjectMapper investProjectMapper;
+    @Resource
+    private InvestmentMonthlyMapper investMonthlyMapper;
+    @Resource
+    private InvestmentSystemMapper investSystemMapper;
+    @Resource
+    private CityBusMonthlyMapper cityBusMapper;
+    @Resource
+    private CityTaxiMonthlyMapper cityTaxiMapper;
+    @Resource
+    private CityTaxiAuthMapper cityTaxiAuthMapper;
+    @Resource
+    private SqlSessionFactory sqlSessionFactory;
 
     // ========== H2032 鏈堟姤 ==========
 
@@ -136,11 +234,18 @@
             throw e;
         }
 
-        // 骞傜瓑锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ワ紝鍏堝垹闄ゆ棫鏁版嵁
+        // 骞傜瓑锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ワ紝鍏堝垹闄ゆ棫鏁版嵁锛堝彧娓� H2032 瑙勫垯鐨勫鏍哥粨鏋滐級
         h2032Mapper.delete(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
             .eq(H2032EnterpriseMonthly::getReportPeriod, period));
-        auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
-            .eq(AuditResult::getReportPeriod, period));
+        List<Long> h2032RuleIds = new ArrayList<>();
+        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+            if ("H2032".equals(rule.getReportType())) h2032RuleIds.add(rule.getId());
+        }
+        if (!h2032RuleIds.isEmpty()) {
+            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, period)
+                .in(AuditResult::getRuleId, h2032RuleIds));
+        }
 
         int success = 0;
         List<String> failDetails = new ArrayList<>();
@@ -170,6 +275,439 @@
         return new ImportResult(success, failDetails.size(), failDetails);
     }
 
+
+    // ========== H203-1 鍏矾鏃呭鏈堟姤锛堝惈杩愭斂杞﹁締鏁皊heet锛� ==========
+
+    public ImportResult importPassengerMonthly(MultipartFile file, String period) throws Exception {
+        List<PassengerRow> rows = new ArrayList<>();
+        List<PassengerAuthVehicle> authList = new ArrayList<>();
+        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+            // sheet0: 鍏矾鏃呭6鏈堟姤
+            Sheet sheet = wb.getSheetAt(0);
+            Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
+            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String name = gs(row, colMap, "浼佷笟鍚嶇О");
+                if (name == null || name.trim().isEmpty()) continue;
+                PassengerEnterpriseMonthly entity = new PassengerEnterpriseMonthly();
+                entity.setReportPeriod(period);
+                entity.setRegionCode(gs(row, colMap, "鎵�灞炲湴鍖�"));
+                entity.setEnterpriseCode(gs(row, colMap, "浼佷笟浠g爜"));
+                entity.setEnterpriseName(name.trim());
+                entity.setVehicleTotal(gi(row, colMap, "瀹㈣繍杞﹁締_鎬昏"));
+                entity.setSeatTotal(gi(row, colMap, "鏍稿畾杞藉浣峗鎬昏"));
+                entity.setVehicleSchedule(gi(row, colMap, "杞﹁締鏁癬瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父瀹㈣繍瀹㈣溅锛�"));
+                entity.setSeatSchedule(gi(row, colMap, "鏍稿畾杞藉浣峗瀹㈣繍鐝溅锛堝惈瀹氱嚎鏃呮父瀹㈣繍瀹㈣溅锛�"));
+                entity.setVehicleCharter(gi(row, colMap, "杞﹁締鏁癬瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓稿杩愬杞︼級"));
+                entity.setSeatCharter(gi(row, colMap, "鏍稿畾杞藉浣峗瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓稿杩愬杞︼級"));
+                entity.setPassengerTotal(gd(row, colMap, "瀹㈣繍閲廮鎬昏"));
+                entity.setTurnoverTotal(gd(row, colMap, "鏃呭鍛ㄨ浆閲廮鎬昏"));
+                entity.setPassengerClass1(gd(row, colMap, "瀹㈣繍閲廮涓�绫诲杩愮彮绾匡紙鍚畾绾挎梾娓革級"));
+                entity.setTurnoverClass1(gd(row, colMap, "鏃呭鍛ㄨ浆閲廮涓�绫诲杩愮彮绾匡紙鍚畾绾挎梾娓革級"));
+                entity.setPassengerClass2(gd(row, colMap, "瀹㈣繍閲廮浜岀被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�"));
+                entity.setTurnoverClass2(gd(row, colMap, "鏃呭鍛ㄨ浆閲廮浜岀被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�"));
+                entity.setPassengerClass3(gd(row, colMap, "瀹㈣繍閲廮涓夌被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�"));
+                entity.setTurnoverClass3(gd(row, colMap, "鏃呭鍛ㄨ浆閲廮涓夌被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�"));
+                entity.setPassengerClass4(gd(row, colMap, "瀹㈣繍閲廮鍥涚被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�"));
+                entity.setTurnoverClass4(gd(row, colMap, "鏃呭鍛ㄨ浆閲廮鍥涚被瀹㈣繍鐝嚎锛堝惈瀹氱嚎鏃呮父锛�"));
+                entity.setPassengerCharter(gd(row, colMap, "瀹㈣繍閲廮瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓革級"));
+                entity.setTurnoverCharter(gd(row, colMap, "鏃呭鍛ㄨ浆閲廮瀹㈣繍鍖呰溅锛堝惈闈炲畾绾挎梾娓革級"));
+                entity.setAvgDistanceTotal(gd(row, colMap, "瀹㈣繍骞冲潎杩愯窛"));
+                entity.setAvgDistanceClass1(gd(row, colMap, "涓�绫诲杩愮彮绾垮钩鍧囪繍璺�"));
+                entity.setAvgDistanceClass2(gd(row, colMap, "浜岀被瀹㈣繍鐝嚎骞冲潎杩愯窛"));
+                entity.setAvgDistanceClass3(gd(row, colMap, "涓夌被瀹㈣繍鐝嚎骞冲潎杩愯窛"));
+                entity.setAvgDistanceClass4(gd(row, colMap, "鍥涚被瀹㈣繍鐝嚎骞冲潎杩愯窛"));
+                entity.setAvgDistanceCharter(gd(row, colMap, "鏃呮父銆佸寘杞﹀钩鍧囪繍璺�"));
+                entity.setVerifyExplanation(gs(row, colMap, "鏍稿疄鎬у師鍥犺В閲�"));
+                rows.add(new PassengerRow(r + 1, entity));
+            }
+            // sheet1: 杩愭斂杞﹁締鏁帮紙鍥哄畾4鍒楋細浼佷笟浠g爜/浼佷笟鍚嶇О/杞﹁締鏁�/杞藉浣嶆暟锛�
+            if (wb.getNumberOfSheets() > 1) {
+                Sheet authSheet = wb.getSheetAt(1);
+                for (int r = 1; r <= authSheet.getLastRowNum(); r++) {
+                    Row row = authSheet.getRow(r);
+                    if (row == null) continue;
+                    String name = getString(row, 1);
+                    if (name == null || name.trim().isEmpty()) continue;
+                    PassengerAuthVehicle auth = new PassengerAuthVehicle();
+                    auth.setReportPeriod(period);
+                    auth.setEnterpriseCode(getString(row, 0));
+                    auth.setEnterpriseName(name.trim());
+                    auth.setVehicleCount(getInt(row, 2));
+                    auth.setSeatCount(getInt(row, 3));
+                    authList.add(auth);
+                }
+            }
+        } catch (Exception e) {
+            log.error("Passenger monthly parse error", e);
+            throw e;
+        }
+
+        // 骞傜瓑锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ワ紝鍏堝垹闄ゆ棫鏁版嵁
+        passengerMapper.delete(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+            .eq(PassengerEnterpriseMonthly::getReportPeriod, period));
+        passengerAuthMapper.delete(new LambdaQueryWrapper<PassengerAuthVehicle>()
+            .eq(PassengerAuthVehicle::getReportPeriod, period));
+        // 鍙竻 H2031 瑙勫垯浜х敓鐨勫鏍哥粨鏋滐紝閬垮厤璇垹鍚屾湡鐨勮揣杩愬鏍哥粨鏋�
+        List<Long> h2031RuleIds = new ArrayList<>();
+        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+            if ("H2031".equals(rule.getReportType())) h2031RuleIds.add(rule.getId());
+        }
+        if (!h2031RuleIds.isEmpty()) {
+            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, period)
+                .in(AuditResult::getRuleId, h2031RuleIds));
+        }
+
+        int success = 0;
+        List<String> failDetails = new ArrayList<>();
+        for (PassengerRow item : rows) {
+            try {
+                passengerMapper.insert(item.entity);
+                success++;
+            } catch (Exception e) {
+                Throwable cause = e;
+                while (cause.getCause() != null) {
+                    cause = cause.getCause();
+                }
+                String reason = cause.getMessage();
+                if (reason == null || reason.trim().isEmpty()) {
+                    reason = e.getMessage();
+                }
+                failDetails.add("绗� " + item.excelRow + " 琛岋紙" + item.entity.getEnterpriseName() + "锛夛細" + reason);
+                log.error("Passenger insert error: row {}, {}", item.excelRow, item.entity.getEnterpriseName(), e);
+            }
+        }
+        String errorDetail = String.join("\n", failDetails);
+        if (errorDetail.length() > 4000) {
+            errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+        }
+        recordBatch(file.getOriginalFilename(), "H2031", period, rows.size(), success, failDetails.size(), errorDetail);
+
+        int authSuccess = 0;
+        for (PassengerAuthVehicle auth : authList) {
+            try {
+                passengerAuthMapper.insert(auth);
+                authSuccess++;
+            } catch (Exception e) {
+                log.error("passenger auth insert error: {}", auth.getEnterpriseName(), e);
+            }
+        }
+        recordBatch(file.getOriginalFilename(), "PASSENGER_AUTH", period, authList.size(), authSuccess, 0, null);
+        log.info("Passenger monthly imported: {} rows, auth {} rows for {}", success, authSuccess, period);
+        return new ImportResult(success, failDetails.size(), failDetails);
+    }
+
+
+
+    /** 鍏矾鏃呭涓綋瀹㈣繍閲�/鍛ㄨ浆閲忥紙浼佷笟绾夸笅姹囨姤鏁版嵁锛屾寜鎵�灞炲湴鍖哄鍏ワ紝渚涘垎甯傚窞琛ㄤ釜浣撹濉厖锛� */
+    public ImportResult importPassengerIndividual(MultipartFile file, String period) throws Exception {
+        List<PassengerIndividualMonthly> rows = new ArrayList<>();
+        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+            Sheet sheet = wb.getSheetAt(0);
+            Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
+            Integer codeCol = colMap.get("鎵�灞炲湴鍖�");
+            Integer entCol = colMap.get("浼佷笟浠g爜");
+            Integer nameCol = colMap.get("浼佷笟鍚嶇О");
+            Integer passCol = null;
+            Integer turnCol = null;
+            for (Map.Entry<String, Integer> e : colMap.entrySet()) {
+                String k = e.getKey();
+                if (k != null && k.contains("涓綋") && k.contains("瀹㈣繍閲�")) passCol = e.getValue();
+                if (k != null && k.contains("涓綋") && (k.contains("鍛ㄨ浆閲�") || k.contains("鍛ㄨ浆"))) turnCol = e.getValue();
+            }
+            if (passCol == null && turnCol == null) {
+                throw new RuntimeException("鏈壘鍒般�屼釜浣撳杩愰噺/涓綋鏃呭鍛ㄨ浆閲忋�嶅垪锛岃妫�鏌ユā鏉匡紙鍒楀悕闇�鍖呭惈鈥滀釜浣撳杩愰噺鈥濃�滀釜浣撳懆杞噺鈥濓級");
+            }
+            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String name = nameCol != null ? getString(row, nameCol) : null;
+                if (name == null || name.trim().isEmpty()) continue;
+                Double pass = passCol != null ? getDecimalOrNull(row, passCol) : null;
+                Double turn = turnCol != null ? getDecimalOrNull(row, turnCol) : null;
+                if ((pass == null || pass == 0.0) && (turn == null || turn == 0.0)) continue;
+                PassengerIndividualMonthly entity = new PassengerIndividualMonthly();
+                entity.setReportPeriod(period);
+                entity.setRegionCode(codeCol != null ? getString(row, codeCol) : null);
+                entity.setEnterpriseCode(entCol != null ? getString(row, entCol) : null);
+                entity.setEnterpriseName(name.trim());
+                entity.setPassengerCount(pass);
+                entity.setTurnover(turn);
+                rows.add(entity);
+            }
+        } catch (Exception e) {
+            log.error("Passenger individual parse error", e);
+            throw e;
+        }
+        passengerIndividualMapper.delete(new LambdaQueryWrapper<PassengerIndividualMonthly>()
+            .eq(PassengerIndividualMonthly::getReportPeriod, period));
+        int success = 0;
+        List<String> failDetails = new ArrayList<>();
+        for (PassengerIndividualMonthly item : rows) {
+            try {
+                passengerIndividualMapper.insert(item);
+                success++;
+            } catch (Exception e) {
+                Throwable cause = e;
+                while (cause.getCause() != null) {
+                    cause = cause.getCause();
+                }
+                String reason = cause.getMessage();
+                if (reason == null || reason.trim().isEmpty()) {
+                    reason = e.getMessage();
+                }
+                failDetails.add("绗� " + (rows.indexOf(item) + 2) + " 琛岋紙" + item.getEnterpriseName() + "锛夛細" + reason);
+            }
+        }
+        String errorDetail = String.join("\n", failDetails);
+        if (errorDetail.length() > 4000) {
+            errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+        }
+        recordBatch(file.getOriginalFilename(), "PASSENGER_INDIVIDUAL", period, rows.size(), success, failDetails.size(), errorDetail);
+        log.info("Passenger individual imported: {} rows for {}", success, period);
+        return new ImportResult(success, failDetails.size(), failDetails);
+    }
+
+    /** 鍏矾鏃呭杩愭斂杞﹁締鏁帮紙鐙珛妯℃澘锛氫紒涓氫唬鐮�/浼佷笟鍚嶇О/杞﹁締鏁�/杞藉浣嶆暟锛� */
+    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));
+            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String name = gs(row, colMap, "浼佷笟鍚嶇О");
+                if (name == null || name.trim().isEmpty()) continue;
+                PassengerAuthVehicle auth = new PassengerAuthVehicle();
+                auth.setReportPeriod(period);
+                auth.setEnterpriseCode(gs(row, colMap, "浼佷笟浠g爜"));
+                auth.setEnterpriseName(name.trim());
+                auth.setVehicleCount(gi(row, colMap, "杞﹁締鏁�"));
+                auth.setSeatCount(gi(row, colMap, "杞藉浣嶆暟"));
+                authList.add(auth);
+            }
+        } catch (Exception e) {
+            log.error("Passenger auth parse error", e);
+            throw e;
+        }
+        // 骞傜瓑锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ワ紝鍏堝垹闄ゆ棫鏁版嵁
+        passengerAuthMapper.delete(new LambdaQueryWrapper<PassengerAuthVehicle>()
+            .eq(PassengerAuthVehicle::getReportPeriod, period));
+        deleteRuleResults("H2031", period);
+        int success = 0;
+        List<String> failDetails = new ArrayList<>();
+        for (PassengerAuthVehicle auth : authList) {
+            try {
+                passengerAuthMapper.insert(auth);
+                success++;
+            } catch (Exception e) {
+                Throwable cause = e;
+                while (cause.getCause() != null) cause = cause.getCause();
+                String reason = cause.getMessage();
+                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
+                failDetails.add("绗� " + (authList.indexOf(auth) + 2) + " 琛岋紙" + auth.getEnterpriseName() + "锛夛細" + reason);
+                log.error("Passenger auth insert error: {}", auth.getEnterpriseName(), e);
+            }
+        }
+        String errorDetail = String.join("\n", failDetails);
+        if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+        recordBatch(file.getOriginalFilename(), "PASSENGER_AUTH", period, authList.size(), success, failDetails.size(), errorDetail);
+        log.info("Passenger auth imported: {} rows for {}", success, period);
+        return new ImportResult(success, failDetails.size(), failDetails);
+    }
+
+    // ========== H204 閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙瀛f姤锛屽惈杩愭斂杞﹁締淇℃伅sheet锛� ==========
+
+    /** 鑳借�楁槑缁嗘瘡缁勫浐瀹�18鍒楋紝杞︾墝鍙疯捣濮嬪垪锛�0-based锛夛細3,21,39,57,75 */
+    private static final int[] ENERGY_GROUP_COLS = {3, 21, 39, 57, 75};
+
+    public ImportResult importEnergyMonthly(MultipartFile file, String period) throws Exception {
+        List<EnergyRow> rows = new ArrayList<>();
+        List<EnergyAuthVehicle> authList = new ArrayList<>();
+        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+            // sheet0: 绗簩瀛e害璐ц溅鑳借�楁槑缁嗭紙姣忚宸﹀彸鍚�5缁勮溅杈嗭紝姣忕粍鍥哄畾18鍒楋級
+            Sheet sheet = wb.getSheetAt(0);
+            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String enterpriseName = getString(row, 2);
+                if (enterpriseName == null || enterpriseName.trim().isEmpty()) continue;
+                for (int group : ENERGY_GROUP_COLS) {
+                    String plate = getString(row, group);
+                    if (plate == null || plate.trim().isEmpty()) continue;
+                    EnergyVehicleQuarterly e = new EnergyVehicleQuarterly();
+                    e.setReportPeriod(period);
+                    e.setRegionCode(getString(row, 0));
+                    e.setEnterpriseCode(getString(row, 1));
+                    e.setEnterpriseName(enterpriseName.trim());
+                    e.setPlateNo(plate.trim());
+                    String vtc = getString(row, group + 1);
+                    e.setVehicleTypeCode(vtc);
+                    e.setVehicleType(vtc == null ? null : ENERGY_VEHICLE_TYPE_NAMES.getOrDefault(vtc, vtc));
+                    String ftc = getString(row, group + 2);
+                    e.setFuelTypeCode(ftc);
+                    e.setFuelType(ftc == null ? null : ENERGY_FUEL_TYPE_NAMES.getOrDefault(ftc, ftc));
+                    e.setManufactureYear(getIntOrNull(row, group + 3));
+                    e.setMarkedTonnage(getDecimalOrNull(row, group + 4));
+                    e.setTotalMileage(getDecimalOrNull(row, group + 5));
+                    e.setLoadedMileage(getDecimalOrNull(row, group + 6));
+                    e.setEmptyMileage(getDecimalOrNull(row, group + 7));
+                    e.setFreight(getDecimalOrNull(row, group + 8));
+                    e.setTurnover(getDecimalOrNull(row, group + 9));
+                    e.setTripCount(getIntOrNull(row, group + 10));
+                    String unit = getString(row, group + 11);
+                    e.setFuelUnit(unit == null ? null : ENERGY_FUEL_UNIT_NAMES.getOrDefault(unit, unit));
+                    e.setFuelConsumption(getDecimalOrNull(row, group + 12));
+                    e.setAvgDistance(getDecimalOrNull(row, group + 13));
+                    e.setAvgLoadedTonnage(getDecimalOrNull(row, group + 14));
+                    e.setAvgTripMileage(getDecimalOrNull(row, group + 15));
+                    e.setFuelPer100km(getDecimalOrNull(row, group + 16));
+                    e.setTurnoverFuel(getDecimalOrNull(row, group + 17));
+                    rows.add(new EnergyRow(r + 1, e));
+                }
+            }
+            // sheet1: 杩愭斂杞﹁締淇℃伅锛堝浐瀹�5鍒楋細杞︾墝鍙�/杞﹁締绫诲瀷/鐕冩枡绫诲瀷/鏍囪鍚ㄤ綅/鍑嗙壍寮曡川閲忥級
+            if (wb.getNumberOfSheets() > 1) {
+                Sheet authSheet = wb.getSheetAt(1);
+                for (int r = 1; r <= authSheet.getLastRowNum(); r++) {
+                    Row row = authSheet.getRow(r);
+                    if (row == null) continue;
+                    String plate = getString(row, 0);
+                    if (plate == null || plate.trim().isEmpty()) continue;
+                    EnergyAuthVehicle auth = new EnergyAuthVehicle();
+                    auth.setReportPeriod(period);
+                    auth.setPlateNo(plate.trim());
+                    auth.setVehicleType(getString(row, 1));
+                    auth.setFuelType(getString(row, 2));
+                    auth.setMarkedTonnage(getDecimal(row, 3));
+                    auth.setTractionQuality(getDecimal(row, 4));
+                    authList.add(auth);
+                }
+            }
+        } catch (Exception e) {
+            log.error("Energy monthly parse error", e);
+            throw e;
+        }
+
+        // 骞傜瓑锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ワ紝鍏堝垹闄ゆ棫鏁版嵁锛堜袱琛� + H204 瑙勫垯瀹℃牳缁撴灉锛�
+        energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+            .eq(EnergyVehicleQuarterly::getReportPeriod, period));
+        energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
+            .eq(EnergyAuthVehicle::getReportPeriod, period));
+        List<Long> h204RuleIds = new ArrayList<>();
+        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+            if ("H204".equals(rule.getReportType())) h204RuleIds.add(rule.getId());
+        }
+        if (!h204RuleIds.isEmpty()) {
+            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, period)
+                .in(AuditResult::getRuleId, h204RuleIds));
+        }
+
+        int success = 0;
+        List<String> failDetails = new ArrayList<>();
+        for (EnergyRow item : rows) {
+            try {
+                energyMapper.insert(item.entity);
+                success++;
+            } catch (Exception e) {
+                Throwable cause = e;
+                while (cause.getCause() != null) cause = cause.getCause();
+                String reason = cause.getMessage();
+                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
+                failDetails.add("绗� " + item.excelRow + " 琛岋紙" + item.entity.getEnterpriseName()
+                    + " " + item.entity.getPlateNo() + "锛夛細" + reason);
+                log.error("Energy insert error: row {}, {}", item.excelRow, item.entity.getPlateNo(), e);
+            }
+        }
+        String errorDetail = String.join("\n", failDetails);
+        if (errorDetail.length() > 4000) {
+            errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+        }
+        recordBatch(file.getOriginalFilename(), "H204", period, rows.size(), success, failDetails.size(), errorDetail);
+
+        int authSuccess = 0;
+        for (EnergyAuthVehicle auth : authList) {
+            try {
+                energyAuthMapper.insert(auth);
+                authSuccess++;
+            } catch (Exception e) {
+                log.error("energy auth insert error: {}", auth.getPlateNo(), e);
+            }
+        }
+        recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), authSuccess, 0, null);
+        log.info("Energy monthly imported: {} vehicles, auth {} rows for {}", success, authSuccess, period);
+        return new ImportResult(success, failDetails.size(), failDetails);
+    }
+
+    /** 鑳借�楄溅杈嗚繍鏀夸俊鎭紙鐙珛妯℃澘锛氳溅鐗屽彿/杞﹁締绫诲瀷/鐕冩枡绫诲瀷/鏍囪鍚ㄤ綅/鍑嗙壍寮曡川閲忥級 */
+    public ImportResult importEnergyAuth(MultipartFile file, String period) throws Exception {
+        List<EnergyAuthVehicle> authList = new ArrayList<>();
+        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+            Sheet sheet = wb.getSheetAt(0);
+            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String plate = getString(row, 0);
+                if (plate == null || plate.trim().isEmpty()) continue;
+                EnergyAuthVehicle auth = new EnergyAuthVehicle();
+                auth.setReportPeriod(period);
+                auth.setPlateNo(plate.trim());
+                auth.setVehicleType(getString(row, 1));
+                auth.setFuelType(getString(row, 2));
+                auth.setMarkedTonnage(getDecimal(row, 3));
+                auth.setTractionQuality(getDecimal(row, 4));
+                authList.add(auth);
+            }
+        } catch (Exception e) {
+            log.error("Energy auth parse error", e);
+            throw e;
+        }
+        // 骞傜瓑锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ワ紝鍏堝垹闄ゆ棫鏁版嵁
+        energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
+            .eq(EnergyAuthVehicle::getReportPeriod, period));
+        deleteRuleResults("H204", period);
+        int success = 0;
+        List<String> failDetails = new ArrayList<>();
+        for (EnergyAuthVehicle auth : authList) {
+            try {
+                energyAuthMapper.insert(auth);
+                success++;
+            } catch (Exception e) {
+                Throwable cause = e;
+                while (cause.getCause() != null) cause = cause.getCause();
+                String reason = cause.getMessage();
+                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
+                failDetails.add("绗� " + (authList.indexOf(auth) + 2) + " 琛岋紙" + auth.getPlateNo() + "锛夛細" + reason);
+                log.error("Energy auth insert error: {}", auth.getPlateNo(), e);
+            }
+        }
+        String errorDetail = String.join("\n", failDetails);
+        if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+        recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), success, failDetails.size(), errorDetail);
+        log.info("Energy auth imported: {} rows for {}", success, period);
+        return new ImportResult(success, failDetails.size(), failDetails);
+    }
+
+    /** H204 琛岃褰曪細淇濈暀 Excel 琛屽彿鐢ㄤ簬澶辫触瀹氫綅 */
+    private static class EnergyRow {
+        final int excelRow;
+        final EnergyVehicleQuarterly entity;
+
+        EnergyRow(int excelRow, EnergyVehicleQuarterly entity) {
+            this.excelRow = excelRow;
+            this.entity = entity;
+        }
+    }
+
+    /** 绌哄崟鍏冩牸杩斿洖 null锛堢敤浜庤兘鑰楁槑缁嗘湭濉啓椤逛笌 0 鍖哄垎锛� */
+    private Integer getIntOrNull(Row row, int idx) {
+        Double val = getDecimalOrNull(row, idx);
+        return val == null ? null : val.intValue();
+    }
 
     // ========== 杩愭斂杞﹁締 ==========
 
@@ -601,6 +1139,19 @@
         }
     }
 
+    /** 鏁版嵁鍙樺寲鍚庡垹闄ゆ寚瀹氭姤琛ㄧ被鍨嬫寚瀹氭姤琛ㄦ湡鐨勫鏍哥粨鏋滐紙闇�閲嶆柊鎵ц瀹℃牳锛� */
+    private void deleteRuleResults(String reportType, String period) {
+        List<Long> ruleIds = new ArrayList<>();
+        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+            if (reportType.equals(rule.getReportType())) ruleIds.add(rule.getId());
+        }
+        if (!ruleIds.isEmpty()) {
+            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, period)
+                .in(AuditResult::getRuleId, ruleIds));
+        }
+    }
+
     /** 鎸夎〃澶村悕绉板缓绔嬪垪鍙锋槧灏勶紝鍏煎鏂版棫妯℃澘鍒椾綅宸紓 */
     private Map<String, Integer> buildHeaderMap(Row header) {
         Map<String, Integer> colMap = new HashMap<>();
@@ -662,10 +1213,26 @@
         }
     }
 
+    private static class PassengerRow {
+        int excelRow;
+        PassengerEnterpriseMonthly entity;
+
+        PassengerRow(int excelRow, PassengerEnterpriseMonthly entity) {
+            this.excelRow = excelRow;
+            this.entity = entity;
+        }
+    }
+
 
     private String getString(Row row, int idx) {
         Cell cell = row.getCell(idx);
         if (cell == null) return null;
+        // 鍏紡鍗曞厓鏍硷細璇荤紦瀛樺瓧绗︿覆锛�.et/WPS 鏂囦欢甯歌锛孌ataFormatter 鏃� evaluator 鏃跺彧杩斿洖鍏紡鏂囨湰锛�
+        if (cell.getCellType() == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.STRING) {
+            String cached = cell.getStringCellValue();
+            cached = cached == null ? null : cached.trim();
+            return cached == null || cached.isEmpty() ? null : cached;
+        }
         String val = FORMATTER.formatCellValue(cell).trim();
         return val.isEmpty() ? null : val;
     }
@@ -675,6 +1242,10 @@
         if (cell == null) return 0.0;
         // 鏁板�煎崟鍏冩牸鐩存帴鍙栧師濮嬪�硷紝閬垮厤琚� Excel 鏄剧ず鏍煎紡锛堢櫨鍒嗘瘮/0_ 绛夛級褰卞搷瑙f瀽
         if (cell.getCellType() == CellType.NUMERIC) {
+            return cell.getNumericCellValue();
+        }
+        // 鍏紡鍗曞厓鏍硷細璇荤紦瀛樻暟鍊硷紙.et/WPS 璺ㄨ〃鍏紡甯歌锛孌ataFormatter 鏃� evaluator 鏃跺彧杩斿洖鍏紡鏂囨湰锛�
+        if (cell.getCellType() == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.NUMERIC) {
             return cell.getNumericCellValue();
         }
         String val = FORMATTER.formatCellValue(cell).trim();
@@ -699,6 +1270,9 @@
         if (cell.getCellType() == CellType.NUMERIC) {
             return cell.getNumericCellValue();
         }
+        if (cell.getCellType() == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.NUMERIC) {
+            return cell.getNumericCellValue();
+        }
         String val = FORMATTER.formatCellValue(cell).trim();
         if (val.isEmpty()) return null;
         try {
@@ -707,4 +1281,1347 @@
             return null;
         }
     }
+    // ========== 鎶曡祫妯″潡锛堝杩愮珯鍦� / 鐗╂祦鍥尯锛屽叏鐪佹眹鎬诲ぇ琛級 ==========
+
+    /** 浜垮厓鎶曡祫椤圭洰鍥哄畾鍚嶅崟锛�2026骞磋涓婇」鐩〃锛� */
+    private static final java.util.Set<String> INVEST_BILLION_NAMES = new LinkedHashSet<>(Arrays.asList(
+        "姹夊彛瀹㈣繍涓績", "閽熺ゥ甯傜患鍚堜氦閫氬杩愭灑绾戒竴鏈熼」鐩�", "鑸彂鏅烘収鐗╂祦鍥�", "鍥借儨鍏矾娓�",
+        "鍗佸牥鐢熶骇鏈嶅姟鍨嬪浗瀹剁墿娴佹灑绾藉伐绋嬮」鐩�", "绉綊鍘胯剱姗欎骇涓氱患鍚堢墿娴佷腑蹇�", "瀹滈兘渚涢攢鍟嗚锤鐗╂祦鍥�",
+        "闀块槼鍘垮煄涔″喎閾剧墿娴佸洯", "瀹滄槍娓叴灞辨腐鍖虹鍏按澶氬紡鑱旇繍寤鸿椤圭洰", "鐜夋箹鍐烽摼锛堣闃筹級浜ゆ槗涓績椤圭洰",
+        "姝︽眽涓囧惃鍗庝腑鍐烽摼娓簩鏈熼」鐩�", "婀栧寳闀挎睙鐜颁唬鐗╂祦浜т笟闆嗚仛绀鸿寖鍖郝风幇浠g墿娴佸洯",
+        "閯傚窞鑺辨箹鍥介檯鏈哄満绌烘腐鍨嬪浗瀹剁墿娴佹灑绾芥櫤鎱у叕鍏卞浗闄呰揣绔欓」鐩�", "鑽嗛棬鏅烘収鍐烽摼鐗╂祦鍥�",
+        "鑽嗛棬鍖楃珯椤烘磱鐗╂祦鍩哄湴锛堣崋闂ㄥ寳瀛愰櫟閾佽矾鐗╂祦鍥級", "娌欐磱澶氬紡鑱旇繍鐜颁唬鐗╂祦鍥竴鏈燂紙娌欐磱鐓ょ偔鍌ㄥ鈥㈤泦瑁呯鏁f敼闆嗗熀鍦帮級",
+        "棣栬 鍩庢櫤鎱у喎閾剧墿娴佷簩鏈熼」鐩瓽鍖�", "瀛濇槍骞挎禂鏅烘収鐗╂祦鐢靛晢浜т笟鍥�", "鐢抽�氾紙瀛濇劅锛夋櫤鎱х墿娴佺數鍟嗕骇涓氬洯锛堜笁鏈燂級",
+        "鍏畨鍘垮寮忚仈杩愮患鍚堢墿娴佸洯", "鑽嗗窞甯傞搧姘磋仈杩愮墿娴佷腑蹇�", "鑽嗗窞甯傜憺娴峰浗闄呯墿娴佸洯涓�鏈�", "鑽嗗窞缁忓紑鍖烘櫤鑳戒粨鍌ㄧ墿娴佸洯椤圭洰",
+        "閯備笢锛堥粍鍐堬級鏁版櫤鐗╂祦缁煎悎鏈嶅姟涓績",
+        "閯傛箻璧e晢璐哥墿娴佷腑蹇冧竴鏈燂紙閫氬煄鍘跨墿娴侀厤閫佷腑蹇冦�侀剛鍗楁櫤鎱х墿娴佷骇涓氬洯寤鸿銆侀剛鍗楁櫤鎱х墿娴佷骇涓氬洯寤鸿椤圭洰锛堜簩鏈燂級銆佸喎閾鹃厤閫佷腑蹇冿級椤圭洰",
+        "鍜稿畞鍥介檯闄嗘腐鐗╂祦鍥�-鍜稿畞甯傚浗闄呴檰娓患鍚堢墿娴佸洯锛堜竴鏈燂級", "鍜稿畞鍥介檯闄嗘腐鐗╂祦鍥�-鏂板缓鍜稿畞閾佽矾鐗╂祦鍩哄湴椤圭洰",
+        "宕囬槼鍘跨患鍚堢墿娴佸洯鍙婇厤濂楄鏂藉缓璁鹃」鐩�", "鎭╂柦涓冮噷鍧墿娴佸洯", "鑱屼笟宸ヨ浜т笟闆嗚仛鍖轰粨鍌ㄧ墿娴侀」鐩�",
+        "鏋佸厰閫熼�掑崕涓墿娴佹灑绾藉熀鍦伴」鐩�", "婀栧寳杩堢澘杈句緵搴旈摼鎬婚儴鐮斿彂鍙婄敓浜у熀鍦伴」鐩�", "涓滄鑸┖鍐烽摼鐗╂祦涓績",
+        "涓夊场鏅烘収鑸┖鐗╂祦浜т笟鍥�", "姹夋睙瑗勯槼锛堝皬娌筹級娓寮忚仈杩愮墿娴佸洯", "瀹忚寕鍏ㄧ悆閫氳繍椤圭洰", "姝︽眽澶╂渤缁煎悎瀹㈣繍鏋㈢航",
+        "骞挎按甯傛潹瀹跺鍏搧鐗╂祦鍥」鐩�---骞挎按甯傛潹瀵ㄩ搧璺揣鍦猴紙骞挎按甯傚崕閼喍閲戠墿娴佽揣鍦猴級", "闃虫柊缁煎悎瀹㈣繍鏋㈢航绔�",
+        "褰撻槼甯傞珮閾佺患鍚堝杩愮珯", "瀹滄槍鍖楀杩愮珯", "鍗楁汲鍘垮叕閾佹崲涔樹腑蹇�(閮戜竾楂橀搧鍗楁汲缁煎悎瀹㈣繍鏋㈢航)", "浜北鍗楀叕閾佺患鍚堝杩愭灑绾�",
+        "娌挎睙楂橀搧姹夊窛鍖楃珯绔欏墠骞垮満(缁煎悎瀹㈣繍鏋㈢航)", "姝︽澀楂橀搧榛勫唸瑗跨患鍚堝杩愭灑绾�", "榛勯粍楂橀搧娴犳按缁煎悎瀹㈣繍鏋㈢航",
+        "閮戜竾楂橀搧宸翠笢缁煎悎瀹㈣繍鏋㈢航", "涓�氫緵搴旈摼婀栧寳绠$悊涓績椤圭洰", "闃抽�绘腐閾佹按鑱旇繍浜屾湡涓鐝垪闆嗙粨涓績",
+        "瑗块┈鍥介檯锛堥粍鐭筹級鏅烘収鐗╂祦鍥�", "鍗佸牥甯傚煄鍙戜粨鍌ㄧ墿娴佷腑蹇�", "鏋濇睙甯傞【瀹跺簵浠撳偍鐗╂祦涓績锛堜竴鏈燂級", "涓夊场寤烘潗鐗╂祦鍥�",
+        "瀹滈兘甯傞檰鍩庣患鍚堢墿娴佽繍杈撳粖閬�", "瀹滄槍浼犲寲鍏矾娓簩鏈�", "姝︽眽娓埅瑗勯槼鏅烘収鐗╂祦鍥�", "璋峰煄鍘垮煄涔℃櫤鎱т粨鍌ㄧ墿娴佷骇涓氬洯",
+        "涔濆窞閫氭矁鐢板浗闄呬緵搴旈摼涓績椤圭洰", "婀栧寳鎭掑嘲鍥介檯鐗╂祦鍥�", "婀栧寳涓潚渚涘簲閾炬湁闄愬叕鍙稿寘瑁呴厤濂椼�佷粨鍌ㄨ繍杈撻」鐩�",
+        "澶ф偀鍘跨數鍟嗙墿娴佷骇涓氬洯", "瀹夐檰甯傚煄涔″喎閾剧墿娴侀厤閫佷腑蹇�", "澶у埆灞辨櫤鎱х墿娴佷骇涓氬洯", "姝︾┐甯備緵閿�绀鹃泦閲囬泦閰嶇患鍚堟湇鍔′腑蹇冨伐绋�",
+        "缃楃敯鍘跨患鍚堢墿娴佸洯", "榛戣惫鍥介檯鐗╂祦鍥紙榛勫唸缁煎悎鍥尯锛�", "楹诲煄甯傚啘浜у搧缁煎悎鐗╂祦鍥�", "宸翠笢缁忔祹寮�鍙戝尯闈掗緳妗ユ櫤鎱х墿娴佷骇涓氬洯",
+        "楣ゅ嘲姝﹂櫟灞变粨鍌ㄧ墿娴佸洯", "鍜镐赴鍘跨患鍚堢墿娴佸洯", "瀹f仼鍘垮瘎閫掔墿娴佸洯", "鏉ュ嚖鍘块緳鍑ょ墿娴佸洯浜ら�氱墿娴佷腑蹇�",
+        "浠欐鍥藉楂樻柊鍖虹墿娴佹灑绾戒腑蹇冨缓璁鹃」鐩�", "澶╅棬甯傜墿娴佷腑蹇冨缓璁鹃」鐩�"));
+
+    /** 缁忔祹寮哄幙椤圭洰鍥哄畾鍚嶅崟 */
+    private static final java.util.Set<String> INVEST_COUNTY_NAMES = new LinkedHashSet<>(Arrays.asList(
+        "瑗块┈鍥介檯锛堥粍鐭筹級鏅烘収鐗╂祦鍥�", "榛勭煶澶у喍婀栭珮鏂板尯鏅烘収鍐烽摼鐗╂祦鍥�", "澶у喍婀栭珮鏂版櫤鎱х墿娴佹腐椤圭洰锛堝煄瑗垮寳渚涘簲閾炬湇鍔′腑蹇冿級",
+        "绉綊鍘胯剱姗欎骇涓氱患鍚堢墿娴佷腑蹇�", "瀹滈兘渚涢攢鍟嗚锤鐗╂祦鍥�", "闀块槼鍘垮煄涔″喎閾剧墿娴佸洯鍙婂叾閰嶅璁炬柦寤鸿椤圭洰",
+        "瀹滄槍娓叴灞辨腐鍖虹鍏按澶氬紡鑱旇繍寤鸿椤圭洰", "鏋濇睙甯傞【瀹跺簵浠撳偍鐗╂祦涓績锛堜竴鏈燂級", "瀹滈兘甯傞檰鍩庣患鍚堢墿娴佽繍杈撳粖閬�",
+        "鑰佹渤鍙e競鍐滀骇鍝佷粨鍌ㄧ墿娴佷氦鏄撲腑蹇冨缓璁鹃」鐩�", "璋峰煄鍘垮煄涔℃櫤鎱т粨鍌ㄧ墿娴佷骇涓氬洯", "娌欐磱澶氬紡鑱旇繍鐜颁唬鐗╂祦鍥�",
+        "澶ф偀鍘跨數鍟嗙墿娴佷骇涓氬洯", "搴斿煄甯傛櫤鎱у喎閾句粨鍌ㄧ墿娴佸洯", "瀛濇槍骞挎禂鏅烘収鐗╂祦鐢靛晢浜т笟鍥缓璁鹃」鐩�", "瀹夐檰甯傚煄涔″喎閾剧墿娴侀厤閫佷腑蹇�",
+        "鏉炬粙甯傛槦缁滅墿娴佸洯", "鍏畨鍘垮寮忚仈杩愮患鍚堢墿娴佸洯", "涓浗渚涢攢鍏畨鍟嗚锤鐗╂祦鍥紙鍏畨鍘垮煙闆嗛噰闆嗛厤涓績锛�", "鍚屾床鏅烘収鐗╂祦鍥�",
+        "澶у埆灞辨櫤鎱х墿娴佷骇涓氬洯", "姝︾┐甯備緵閿�绀鹃泦閲囬泦閰嶇患鍚堟湇鍔′腑蹇冨伐绋�", "缃楃敯鍘跨患鍚堢墿娴佸洯", "閯備笢锛堥粍鍐堬級鏁版櫤鐗╂祦缁煎悎鏈嶅姟涓績",
+        "楹诲煄甯傚啘浜у搧缁煎悎鐗╂祦鍥�", "閯傛箻璧e晢璐哥墿娴佷腑蹇冿紙涓�鏈燂級", "鍢夐奔鍘挎櫤鎱т氦閫氱墿娴佹灑绾戒腑蹇�",
+        "宕囬槼鍘垮浗瀹剁骇缁煎悎鐗╂祦鏋㈢航鍙婇厤濂楄鏂藉缓璁鹃」鐩�", "搴峰崕鏅烘収鐗╂祦鍥�", "缁煎悎鐗╂祦鍥紙婀栧寳浜ゆ姇闅忓窞鏅烘収渚涘簲閾句骇涓氬洯锛�",
+        "宸翠笢缁忔祹寮�鍙戝尯闈掗緳妗ユ櫤鎱х墿娴佷骇涓氬洯", "鎭╂柦甯備竷閲屽潽鐗╂祦鍥�", "楣ゅ嘲姝﹂櫟灞变粨鍌ㄧ墿娴佸洯", "鍜镐赴鍘跨患鍚堢墿娴佸洯",
+        "鏉ュ嚖鍘块緳鍑ょ墿娴佸洯浜ら�氱墿娴佷腑蹇�", "瀹f仼鍘垮瘎閫掔墿娴佸洯", "浠欐鍥藉楂樻柊鍖虹墿娴佹灑绾戒腑蹇冨缓璁鹃」鐩�",
+        "鑱屼笟宸ヨ浜т笟闆嗚仛鍖轰粨鍌ㄧ墿娴侀」鐩�", "涓浗铏捐胺鍐烽摼浠撳偍鐗╂祦鍥�", "澶╅棬甯傜墿娴佷腑蹇冨缓璁鹃」鐩�", "绁炲啘鏋剁墿娴佸洯"));
+
+    /** "鍗佷簲浜�"閲嶇偣鐗╂祦椤圭洰鍥哄畾鍚嶅崟 */
+    private static final java.util.Set<String> INVEST_FIVE_YEAR_NAMES = new LinkedHashSet<>(Arrays.asList(
+        "鑸彂鏅烘収鐗╂祦鍥�", "涓�氫緵搴旈摼婀栧寳绠$悊涓績椤圭洰", "闃抽�绘腐閾佹按鑱旇繍浜屾湡涓鐝垪闆嗙粨涓績", "鏋佸厰閫熼�掑崕涓墿娴佹灑绾藉熀鍦伴」鐩�",
+        "涓鐝垪閰嶅澶氬紡鑱旇繍涓浆涓績椤圭洰", "鏂板紑鍙戦摱琛岃捶娆鹃暱姹熶腑娓革紙姝︽眽锛夋櫤鎱х墿娴佹灑绾介」鐩搧璺伐涓氱珯瀛愰」",
+        "榛勭煶鏂版腐鍥借儨鏅烘収鍏矾娓」鐩�", "鍗佸牥鐢熶骇鏈嶅姟鍨嬪浗瀹剁墿娴佹灑绾藉伐绋嬮」鐩�", "鍗佸牥甯傚煄鍙戜粨鍌ㄧ墿娴佷腑蹇�",
+        "瀵垮悍姘镐箰鏅鸿兘鍖栫墿娴佽澶囧崌绾ф敼閫�", "鍗佸牥姹介厤锛堝箍鍦猴級鐗╂祦涓績鎵╄兘鍗囩骇椤圭洰", "鍗佸牥姝﹀綋灞辨満鍦鸿埅绌虹墿娴佸洯椤圭洰",
+        "涓规睙鍙e競闄堝娓搧姘村叕绌鸿仈杩愬熀鍦�", "闀块槼鍘垮煄涔″喎閾剧墿娴佸洯鍙婂叾閰嶅璁炬柦寤鸿椤圭洰", "涓夊场寤烘潗鐗╂祦鍥�",
+        "瀹滈兘甯傞檰鍩庣患鍚堢墿娴佽繍杈撳粖閬�", "涓夊场鏅烘収鑸┖鐗╂祦浜т笟鍥�", "褰撻槼甯傚潩闄靛寲宸ュ洯鍖虹患鍚堢墿娴佸洯", "杩滃畨鍘跨墿娴佷腑蹇冩彁鑳藉崌绾ч」鐩�",
+        "瀹滄槍甯傛櫤鎱х墿娴侀泦鏁d腑蹇�", "瀹滈兘甯傛灊鍩庡寮忚仈杩愪复娓墿娴佸洯", "鐜夋箹鍐烽摼锛堣闃筹級浜ゆ槗涓績椤圭洰", "浼犲寲閯傝タ鍖楁櫤鎱х墿娴佷腑蹇冮」鐩�",
+        "姝︽眽娓埅瑗勯槼鏅烘収鐗╂祦鍥�", "鍞愮櫧娌虫腐鏁h揣杞繍涓績椤圭洰", "姹夋睙瑗勯槼锛堝皬娌筹級娓寮忚仈杩愮墿娴佸洯", "瑗勫窞鍖哄ぇ瀹楀晢鍝佸寮忚仈杩愪氦鏄撲腑蹇�",
+        "缁煎悎鎬т緵搴旈摼浜т笟鍥尯锛堜腑鍥芥湁鏈鸿胺渚涘簲閾炬櫤鎱т骇涓氬洯銆佽胺鍩庢櫤鎱у喎閾剧墿娴佷緵搴旈摼浜т笟鍥級", "鍗楁汲鍘跨幇浠f櫤鑳界墿娴佸洯鍖�",
+        "婀栧寳闀挎睙鐜颁唬鐗╂祦浜т笟闆嗚仛绀鸿寖鍖郝风幇浠g墿娴佸洯", "寮樹笟鐜颁唬鐗╂祦浜т笟鍥�", "瀹忚寕鍏ㄧ悆閫氳繍椤圭洰", "娌欐磱澶氬紡鑱旇繍鐜颁唬鐗╂祦鍥�",
+        "浜北甯傚叕閾佺墿娴佷腑蹇�", "鏂版潗鏂欏伐涓氬搧鏅烘収鐗╂祦鍥�", "闀胯崋閾佽矾锛堥挓绁ワ級鍏搧鑱旇繍闆嗚绠辩墿娴佸洯", "灞堝宀櫤鎱х墿娴佸洯",
+        "涓滄柟鍐烽摼鏅烘収鐗╂祦閰嶉�佷腑蹇�", "搴斿煄甯傛櫤鎱у喎閾句粨鍌ㄧ墿娴佸洯", "瀛濇槍骞挎禂鏅烘収鐗╂祦鐢靛晢浜т笟鍥缓璁鹃」鐩�",
+        "浜眽绮ゅぇ閫氶亾瀛濇劅鍥藉缁煎悎璐ц繍鏋㈢航", "鍏畨鍘垮寮忚仈杩愮患鍚堢墿娴佸洯", "鑽嗗窞缁忓紑鍖烘櫤鑳戒粨鍌ㄧ墿娴佸洯椤圭洰",
+        "涓浗渚涢攢鍏畨鍟嗚锤鐗╂祦鍥紙鍏畨鍘垮煙闆嗛噰闆嗛厤涓績锛�", "鐩戝埄甯傛腐鍙g墿娴佸洯锛堝煄鍖哄洯锛�", "閯備笢锛堥粍鍐堬級鏁版櫤鐗╂祦缁煎悎鏈嶅姟涓績",
+        "宕囬槼鍘垮浗瀹剁骇缁煎悎鐗╂祦鏋㈢航鍙婇厤濂楄鏂藉缓璁鹃」鐩�", "鍜稿畞楂樻柊鍖哄喕搴撳喎閾鹃」鐩紙涓�鏈燂級", "鍜稿畨鍖烘暟瀛楃墿娴佷骇涓氬洯鍙婂熀纭�璁炬柦寤鸿椤圭洰",
+        "閫氬北鍘跨壒鑹插啘浜у搧鐜颁唬鐗╂祦鍩哄湴寤鸿椤圭洰", "璧ゅ娌挎睙鍏搧鑱旇繍缁胯壊鐗╂祦涓績椤圭洰", "鍜稿畞鍩庝埂鏅烘収鐗╂祦鍥�",
+        "缁煎悎鐗╂祦鍥紙婀栧寳浜ゆ姇闅忓窞鏅烘収渚涘簲閾句骇涓氬洯锛�", "闅忓窞缁煎悎浠撳偍閰嶉�佷腑蹇�",
+        "骞挎按甯傛潹瀹跺鍏搧鐗╂祦鍥」鐩�---绗竴鏈熷箍姘村競鏉ㄥ閾佽矾璐у満锛堝箍姘村競鍗庨懌鍐堕噾鐗╂祦璐у満锛�",
+        "宸翠笢缁忔祹寮�鍙戝尯闈掗緳妗ユ櫤鎱х墿娴佷骇涓氬洯", "楣ゅ嘲姝﹂櫟灞变粨鍌ㄧ墿娴佸洯", "鍜镐赴鍘跨患鍚堢墿娴佸洯",
+        "浠欐鍥藉楂樻柊鍖虹墿娴佹灑绾戒腑蹇冨缓璁鹃」鐩�", "澶╅棬甯傚煙鍏叡缁煎悎鐗╂祦鍥�", "娼滄睙甯傚啘浜у搧鐜颁唬鐗╂祦鍩虹璁炬柦寤鸿椤圭洰",
+        "娼滄睙鐜颁唬鍐滀笟绉戞妧绀鸿寖鍥喎閾剧墿娴佷腑蹇冮」鐩�", "婀栧寳娼滅數鐗╂祦鏈夐檺鍏徃鐜颁唬鐗╂祦涓績锛堜竴銆佷簩銆佷笁鏈燂級"));
+
+    /** 姹囨�诲ぇ琛ㄥ競宸炵煭鍚� -> 瑙勮寖鍚� */
+    private static final Map<String, String> INVEST_CITY_SHORT_TO_FULL = new LinkedHashMap<>();
+
+    static {
+        INVEST_CITY_SHORT_TO_FULL.put("姝︽眽", "姝︽眽甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("榛勭煶", "榛勭煶甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("鍗佸牥", "鍗佸牥甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("瀹滄槍", "瀹滄槍甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("瑗勯槼", "瑗勯槼甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("閯傚窞", "閯傚窞甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("鑽嗛棬", "鑽嗛棬甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("瀛濇劅", "瀛濇劅甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("鑽嗗窞", "鑽嗗窞甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("榛勫唸", "榛勫唸甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("鍜稿畞", "鍜稿畞甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("闅忓窞", "闅忓窞甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("鎭╂柦", "鎭╂柦宸�");
+        INVEST_CITY_SHORT_TO_FULL.put("浠欐", "浠欐甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("娼滄睙", "娼滄睙甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("澶╅棬", "澶╅棬甯�");
+        INVEST_CITY_SHORT_TO_FULL.put("鏋楀尯", "绁炲啘鏋舵灄鍖�");
+    }
+
+    /**
+     * 鎶曡祫鏈堟姤瀵煎叆锛堝叏鐪佹眹鎬诲ぇ琛級銆�
+     * category: 瀹㈣繍绔欏満锛坸lsx 鏄庣粏 sheet锛�/ 鐗╂祦鍥尯锛坸ls 鍒嗛」鐩姇璧勫畬鎴愭儏鍐� sheet锛�
+     * 骞傜瓑锛氬悓鎶ヨ〃鏈熸棫鏈堝害鏁版嵁涓� INVEST 瀹℃牳缁撴灉鍏堝垹锛岄」鐩富妗f寜 椤圭洰鍚�+绫诲埆 upsert銆�
+     */
+    public ImportResult importInvestment(MultipartFile file, String period, String category) throws Exception {
+        boolean passenger = "瀹㈣繍绔欏満".equals(category);
+        List<InvestRow> rows = new ArrayList<>();
+        List<String> cityOrder = new ArrayList<>();
+        String currentCity = null;
+        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+            Sheet sheet = pickInvestmentSheet(wb, passenger);
+            // 瀹氫綅琛ㄥご琛岋細鍖呭惈"椤圭洰鍚嶇О"涓斿叾鍚庢湁鏁版嵁
+            int headerRow = -1;
+            for (int r = 0; r <= Math.min(sheet.getLastRowNum(), 8); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String a = getString(row, 0);
+                String b = getString(row, 1);
+                if (b != null && b.contains("椤圭洰鍚嶇О") && (a == null || a.contains("搴忓彿") || a.contains("搴�"))) {
+                    headerRow = r;
+                    break;
+                }
+            }
+            if (headerRow < 0) {
+                throw new RuntimeException("鏈壘鍒拌〃澶磋锛堥渶鍖呭惈 搴忓彿/椤圭洰鍚嶇О 鍒楋級");
+            }
+            for (int r = headerRow + 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String a = getString(row, 0);
+                String b = getString(row, 1);
+                if (a == null || a.trim().isEmpty()) {
+                    if (b == null || b.trim().isEmpty()) continue;
+                }
+                String aTrim = a == null ? "" : a.trim();
+                String bTrim = b == null ? "" : b.trim();
+                // 鍚堣/鏍囬琛�
+                if (aTrim.contains("鍚堣") || aTrim.contains("鎬昏")) continue;
+                // 璧勯噾绫诲埆/鍒嗙粍鏍囬琛岋紙涓�銆佷簩銆佷笁銆佸洓銆佲�︼級
+                if (aTrim.matches("^[涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+銆�.*")) continue;
+                // 甯傚窞灏忚妭琛�
+                String city = investmentCity(aTrim);
+                if (city != null) {
+                    currentCity = city;
+                    if (!cityOrder.contains(city)) cityOrder.add(city);
+                    continue;
+                }
+                // 椤圭洰琛岋細搴忓彿涓烘暟瀛椾笖椤圭洰鍚嶉潪绌�
+                boolean seqNum = aTrim.matches("\\d+(\\.\\d+)?");
+                if (seqNum && !bTrim.isEmpty()) {
+                    InvestmentProject project = new InvestmentProject();
+                    project.setProjectName(bTrim);
+                    project.setCategory(category);
+                    project.setCity(currentCity);
+                    project.setBuilderName(getString(row, 2));
+                    project.setConstructNature(getString(row, 3));
+                    project.setStartTime(investTime(getString(row, 4)));
+                    project.setEndTime(investTime(getString(row, 5)));
+                    project.setTotalInvestment(getDecimal(row, 6));
+                    project.setApprovalGk(getString(row, 18));
+                    project.setApprovalCs(getString(row, 19));
+                    project.setSource("鍏ㄧ渷姹囨�诲ぇ琛�");
+                    project.setIsBillion(INVEST_BILLION_NAMES.contains(bTrim) ? 1 : 0);
+                    project.setIsCounty(INVEST_COUNTY_NAMES.contains(bTrim) ? 1 : 0);
+                    project.setIsFiveYear(INVEST_FIVE_YEAR_NAMES.contains(bTrim) ? 1 : 0);
+
+                    InvestmentMonthly monthly = new InvestmentMonthly();
+                    monthly.setReportPeriod(period);
+                    monthly.setYearPlan(getDecimal(row, 8));
+                    monthly.setYearCum(getDecimal(row, 9));
+                    monthly.setMonthDone(getDecimal(row, 10));
+                    monthly.setStartCum(getDecimal(row, 7));
+                    monthly.setProgressStage(getString(row, 11));
+                    monthly.setProgressDesc(getString(row, 12));
+                    monthly.setBuildingArea(getDecimalOrNull(row, 15));
+                    rows.add(new InvestRow(r + 1, project, monthly));
+                }
+            }
+        } catch (Exception e) {
+            log.error("Investment import parse error: {}", category, e);
+            throw e;
+        }
+
+        // 骞傜瓑娓呯悊锛氬悓鎶ヨ〃鏈熴�佸悓绫诲埆鏈堝害鏁版嵁 + INVEST 瀹℃牳缁撴灉锛堥」鐩富妗h法鏈堜繚鐣欙紝鎸夊悕 upsert锛�
+        List<InvestmentProject> sameCat = investProjectMapper.selectList(
+            new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, category));
+        java.util.Set<Long> catProjectIds = new HashSet<>();
+        for (InvestmentProject pc : sameCat) catProjectIds.add(pc.getId());
+        if (!catProjectIds.isEmpty()) {
+            investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>()
+                .eq(InvestmentMonthly::getReportPeriod, period)
+                .in(InvestmentMonthly::getProjectId, catProjectIds));
+        }
+        List<Long> investRuleIds = new ArrayList<>();
+        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+            if ("INVEST".equals(rule.getReportType())) investRuleIds.add(rule.getId());
+        }
+        if (!investRuleIds.isEmpty()) {
+            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, period)
+                .in(AuditResult::getRuleId, investRuleIds));
+        }
+
+        int success = 0;
+        List<String> failDetails = new ArrayList<>();
+        for (InvestRow item : rows) {
+            try {
+                // 涓绘。 upsert锛堟寜 椤圭洰鍚�+绫诲埆锛�
+                InvestmentProject exist = investProjectMapper.selectOne(new LambdaQueryWrapper<InvestmentProject>()
+                    .eq(InvestmentProject::getProjectName, item.project.getProjectName())
+                    .eq(InvestmentProject::getCategory, category)
+                    .last("LIMIT 1"));
+                if (exist == null) {
+                    investProjectMapper.insert(item.project);
+                    item.monthly.setProjectId(item.project.getId());
+                } else {
+                    item.project.setId(exist.getId());
+                    item.project.setCreatedAt(exist.getCreatedAt());
+                    investProjectMapper.updateById(item.project);
+                    item.monthly.setProjectId(exist.getId());
+                }
+                investMonthlyMapper.insert(item.monthly);
+                success++;
+            } catch (Exception e) {
+                Throwable cause = e;
+                while (cause.getCause() != null) cause = cause.getCause();
+                String reason = cause.getMessage();
+                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
+                failDetails.add("绗� " + item.excelRow + " 琛岋紙" + item.project.getProjectName() + "锛夛細" + reason);
+                log.error("Investment insert error: row {}", item.excelRow, e);
+            }
+        }
+        String errorDetail = String.join("\n", failDetails);
+        if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+        recordBatch(file.getOriginalFilename(), "INVEST_" + category, period, rows.size(), success, failDetails.size(), errorDetail);
+        log.info("Investment {} imported: {} rows for {}", category, success, period);
+        return new ImportResult(success, failDetails.size(), failDetails);
+    }
+
+    // ========== 鎶曡祫妯″潡 M2锛氬競宸炲崟琛ㄦ壒閲忓鍏ワ紙瀹归敊瑙f瀽锛� ==========
+
+    /**
+     * 甯傚窞鍗曡〃鎵归噺瀵煎叆锛氭壂鎻� docs/鎶曡祫/杈撳叆/{绫诲埆鐩綍}/{X鏈坿 涓嬪叏閮� xls/xlsx/et锛堣烦杩� pdf锛夛紝
+     * 瀹归敊瑙f瀽姣忎釜鏂囦欢锛堝畾浣嶈〃澶磋 + 鍒楀悕鏄犲皠锛夛紝upsert 椤圭洰涓绘。 + 鍐欏叆鏈堝害锛�
+     * 骞朵笌銆屽崄浜斾簲銆嶆ā鏉挎竻鍗曡鑼冨寲鍖归厤鍚庢爣璁� is_five_year銆�
+     */
+    public Map<String, Object> importInvestCityDir(String period, String category) throws Exception {
+        boolean passenger = "瀹㈣繍绔欏満".equals(category);
+        String catDir = passenger ? "瀹㈣繍锛堝杩愮珯锛�" : "鐗╂祦锛堢墿娴佸洯鍖猴級";
+        String monthDir = investMonthDir(period);
+        if (monthDir.isEmpty()) throw new RuntimeException("鎶ヨ〃鏈熸牸寮忓簲涓� yyyy-MM锛屼緥濡� 2026-07");
+        File dir = resolveInvestInputDir(catDir, monthDir);
+        if (dir == null || !dir.isDirectory()) {
+            throw new RuntimeException("鏈壘鍒板競宸炲崟琛ㄧ洰褰曪細docs/鎶曡祫/杈撳叆/" + catDir + "/" + monthDir);
+        }
+        Set<String> fiveYearNames = loadFiveYearProjectNames();
+        List<InvestCityRow> rows = new ArrayList<>();
+        List<Map<String, Object>> fileResults = new ArrayList<>();
+        File[] files = dir.listFiles((d, n) -> {
+            String lower = n.toLowerCase();
+            return lower.endsWith(".xls") || lower.endsWith(".xlsx") || lower.endsWith(".et");
+        });
+        if (files == null || files.length == 0) {
+            throw new RuntimeException("鐩綍涓病鏈夊彲瑙f瀽鐨勬姤琛ㄦ枃浠讹紙xls/xlsx/et锛夛細" + dir.getAbsolutePath());
+        }
+        Arrays.sort(files, Comparator.comparing(File::getName));
+        for (File f : files) {
+            Map<String, Object> fr = new LinkedHashMap<>();
+            fr.put("fileName", f.getName());
+            try (java.io.InputStream is = new java.io.FileInputStream(f);
+                 Workbook wb = WorkbookFactory.create(is)) {
+                parseInvestCityWorkbook(wb, f.getName(), category, period, fiveYearNames, rows, fr);
+            } catch (Exception e) {
+                fr.put("success", 0);
+                fr.put("error", e.getMessage() == null ? e.toString() : e.getMessage());
+                log.error("invest city file parse error: {}", f.getName(), e);
+            }
+            fileResults.add(fr);
+        }
+        // 骞傜瓑娓呯悊锛氬悓绫诲埆鍚屾姤琛ㄦ湡鏈堝害鏁版嵁 + INVEST 瀹℃牳缁撴灉锛堥」鐩富妗h法鏈堜繚鐣欙紝鎸夊悕 upsert锛�
+        cleanInvestMonthly(category, period);
+        int success = 0;
+        List<String> failDetails = new ArrayList<>();
+        for (InvestCityRow item : rows) {
+            try {
+                saveInvestProjectRow(item.project, item.monthly);
+                success++;
+            } catch (Exception e) {
+                Throwable cause = e;
+                while (cause.getCause() != null) cause = cause.getCause();
+                String reason = cause.getMessage();
+                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
+                failDetails.add(item.fileName + " 绗� " + item.excelRow + " 琛岋紙" + item.project.getProjectName() + "锛夛細" + reason);
+                log.error("Invest city insert error: file {}, row {}", item.fileName, item.excelRow, e);
+            }
+        }
+        String errorDetail = String.join("\n", failDetails);
+        if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+        recordBatch("甯傚窞鍗曡〃鎵归噺瀵煎叆锛�" + catDir + "/" + monthDir + "锛�", "INVEST_" + category + "_CITY",
+            period, rows.size(), success, failDetails.size(), errorDetail);
+        int fiveYearMatched = 0;
+        for (InvestCityRow item : rows) {
+            if (Integer.valueOf(1).equals(item.project.getIsFiveYear())) fiveYearMatched++;
+        }
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("total", rows.size());
+        result.put("success", success);
+        result.put("fail", failDetails.size());
+        result.put("failDetails", failDetails);
+        result.put("files", fileResults);
+        result.put("fiveYearTemplateProjects", fiveYearNames.size());
+        result.put("fiveYearMatched", fiveYearMatched);
+        log.info("Invest city {} {} imported: {} rows (fiveYear matched {}/{})",
+            category, period, success, fiveYearMatched, fiveYearNames.size());
+        return result;
+    }
+
+    /** 澶� sheet 鍘嗗彶鏂囦欢锛堣闃� .et 绛夛級锛歴heet 鍚嶅惈鎶ヨ〃鏈熶紭鍏堬紝鍏舵閫夋暟鎹鏈�澶� */
+    private boolean investSheetMatchesPeriod(Sheet sh, String period) {
+        if (period == null || !period.matches("\\d{4}-\\d{2}")) return false;
+        String name = sh.getSheetName();
+        if (name == null || name.isEmpty()) return false;
+        String norm = name.replace(" ", "");
+        int month = Integer.parseInt(period.substring(5, 7));
+        return norm.contains(month + "鏈�") || norm.contains(String.format("%02d", month) + "鏈�")
+            || name.contains(period) || name.contains(period.replace("-0", "-"));
+    }
+
+    /** 瑙f瀽鍗曚釜甯傚窞鎶ヨ〃锛氶�� sheet锛堟姤琛ㄦ湡鍚嶄紭鍏� + 鏁版嵁琛屾暟锛夛紝鎸夊垪鍚嶆槧灏勬彁鍙栭」鐩 */
+    private void parseInvestCityWorkbook(Workbook wb, String fileName, String category, String period,
+                                         Set<String> fiveYearNames, List<InvestCityRow> rows, Map<String, Object> fr) {
+        Sheet best = null;
+        Map<String, Integer> bestCol = null;
+        int bestScore = -1;
+        for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+            Sheet sh = wb.getSheetAt(i);
+            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);
+            if (score > bestScore) {
+                best = sh;
+                bestCol = col;
+                bestScore = score;
+            }
+        }
+        if (best == null) {
+            fr.put("success", 0);
+            fr.put("error", "鏈壘鍒板惈銆岄」鐩悕绉般�嶇殑琛ㄥご");
+            return;
+        }
+        String city = cityFromFileName(fileName);
+        if (city == null) city = cityFromSheetHeader(best);
+        String currentCity = city;
+        Map<String, Integer> col = bestCol;
+        int nameCol = col.get("name");
+        boolean hasSeq = col.containsKey("seq");
+        int seqCol = hasSeq ? col.get("seq") : 0;
+        int parsed = 0;
+        for (int r = col.get("headerRow") + 1; r <= best.getLastRowNum(); r++) {
+            Row row = best.getRow(r);
+            if (row == null) continue;
+            String name = cellStr(row, nameCol);
+            if (name == null || name.trim().isEmpty()) continue;
+            String nt = name.trim().replaceAll("[\r\n]+", "");
+            if (nt.contains("鍚堣") || nt.contains("鎬昏")) continue;
+            if (nt.matches("^[涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+銆�.*")) continue;
+            if (nt.matches("^锛圼涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+锛�.*")) continue;
+            if (nt.replace(" ", "").matches(".*灏廫\s*璁�.*")) continue;
+            if (nt.startsWith("濉姤") || nt.startsWith("鍗曚綅璐熻矗浜�") || nt.matches("^\\d+銆�.*")) continue;
+            if (hasSeq) {
+                String seq = cellStr(row, seqCol);
+                if (seq == null || seq.trim().isEmpty()) {
+                    // 甯傚窞灏忚妭琛岋紙濡� "姝︽眽" "澶� 闂�"锛夛紝鏇存柊褰撳墠甯傚窞
+                    String c = investmentCity(nt);
+                    if (c != null) currentCity = c;
+                    continue;
+                }
+                if (!seq.trim().matches("\\d+(\\.\\d+)?")) continue;
+            } else {
+                // 鏃犲簭鍙峰垪锛堥殢宸炵瓑锛夛細椤圭洰鍚嶉潪绌哄嵆椤圭洰琛岋紝甯傚窞灏忚妭琛岀敤 investmentCity 璇嗗埆
+                String c = investmentCity(nt);
+                if (c != null) { currentCity = c; continue; }
+            }
+            InvestmentProject project = new InvestmentProject();
+            project.setProjectName(nt);
+            project.setCategory(category);
+            project.setCity(currentCity);
+            project.setBuilderName(cellStr(row, col.get("builder")));
+            project.setConstructNature(cellStr(row, col.get("nature")));
+            project.setStartTime(investTime(cellStr(row, col.get("start"))));
+            project.setEndTime(investTime(cellStr(row, col.get("end"))));
+            project.setTotalInvestment(cellNum(row, col.get("total")));
+            project.setApprovalGk(cellStr(row, col.get("gk")));
+            project.setApprovalCs(cellStr(row, col.get("cs")));
+            project.setBuildingArea(cellNumOrNull(row, col.get("area")));
+            project.setSource("甯傚窞鍗曡〃");
+            project.setIsBillion(INVEST_BILLION_NAMES.contains(nt) ? 1 : 0);
+            project.setIsCounty(INVEST_COUNTY_NAMES.contains(nt) ? 1 : 0);
+            project.setIsFiveYear(fiveYearNames.contains(normalizeProjectName(nt)) ? 1 : 0);
+            InvestmentMonthly monthly = new InvestmentMonthly();
+            monthly.setReportPeriod(period);
+            monthly.setStartCum(cellNum(row, col.get("startcum")));
+            monthly.setYearPlan(cellNum(row, col.get("yearplan")));
+            monthly.setYearCum(cellNum(row, col.get("yearcum")));
+            monthly.setMonthDone(cellNum(row, col.get("monthdone")));
+            monthly.setProgressStage(cellStr(row, col.get("stage")));
+            monthly.setProgressDesc(cellStr(row, col.get("desc")));
+            monthly.setBuildingArea(cellNumOrNull(row, col.get("area")));
+            rows.add(new InvestCityRow(r + 1, fileName, project, monthly));
+            parsed++;
+        }
+        fr.put("city", currentCity);
+        fr.put("success", parsed);
+    }
+
+    /** 瀹氫綅鍚�岄」鐩悕绉般�嶇殑琛ㄥご琛屽苟鎸夊垪鍚嶅叧閿瘝寤虹珛鍒楁槧灏勶紙鏀寔鍙屽眰琛ㄥご鍚堝苟锛� */
+    private Map<String, Integer> findInvestHeader(Sheet sh) {
+        int maxHeaderRow = Math.min(22, sh.getLastRowNum());
+        for (int r = 0; r <= maxHeaderRow; r++) {
+            Row row = sh.getRow(r);
+            if (row == null) continue;
+            boolean hasName = false;
+            for (int c = 0; c <= row.getLastCellNum(); c++) {
+                String v = cellStr(row, c);
+                if (v != null && v.contains("椤圭洰鍚嶇О")) { hasName = true; break; }
+            }
+            if (!hasName) continue;
+            Map<Integer, String> colTexts = new HashMap<>();
+            for (int rr = r; rr <= Math.min(r + 3, sh.getLastRowNum()); rr++) {
+                Row rr2 = sh.getRow(rr);
+                if (rr2 == null) continue;
+                for (int c = 0; c <= rr2.getLastCellNum(); c++) {
+                    String v = cellStr(rr2, c);
+                    if (v == null) continue;
+                    colTexts.merge(c, v, (a, b) -> a + "|" + b);
+                }
+            }
+            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("绔e伐") || 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("褰㈣薄杩涘害")) 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);
+            }
+            if (!col.containsKey("name")) continue;
+            for (int c = 0; c <= row.getLastCellNum(); c++) {
+                String v = cellStr(row, c);
+                if (v != null && v.trim().matches("搴廫\s*鍙�")) { col.put("seq", c); break; }
+            }
+            // fallback锛歽earcum 缂哄け鏃舵寜 startcum+2锛堟�绘姇璧�/鑷紑濮嬬疮璁�/鏈勾璁″垝/鑷勾鍒濈疮璁� 鐨勬爣鍑嗗垪搴忥級
+            if (!col.containsKey("yearcum") && col.containsKey("startcum")) {
+                int yc = col.get("startcum") + 2;
+                if (yc <= row.getLastCellNum()) col.put("yearcum", yc);
+            }
+            // monthdone 缂哄け鏃舵寜 yearcum+1
+            if (!col.containsKey("monthdone") && col.containsKey("yearcum")) {
+                int md = col.get("yearcum") + 1;
+                if (md <= row.getLastCellNum()) col.put("monthdone", md);
+            }
+            for (String k : new String[]{"builder", "nature", "start", "end", "total", "startcum",
+                "yearplan", "yearcum", "monthdone", "stage", "desc", "gk", "cs", "area"}) {
+                col.putIfAbsent(k, -1);
+            }
+            col.put("headerRow", r);
+            return col;
+        }
+        return null;
+    }
+
+    /** 缁熻 sheet 鍐呴」鐩暟鎹鏁帮紙鐢ㄤ簬澶� sheet 鍘嗗彶鏂囦欢閫夊綋鏈� sheet锛� */
+    private int countInvestRows(Sheet sh, Map<String, Integer> col) {
+        int nameCol = col.get("name");
+        boolean hasSeq = col.containsKey("seq");
+        int seqCol = hasSeq ? col.get("seq") : 0;
+        int count = 0;
+        for (int r = col.get("headerRow") + 1; r <= sh.getLastRowNum(); r++) {
+            Row row = sh.getRow(r);
+            if (row == null) continue;
+            String name = cellStr(row, nameCol);
+            if (name == null || name.trim().isEmpty()) continue;
+            String nt = name.trim().replaceAll("[\r\n]+", "");
+            if (nt.contains("鍚堣") || nt.contains("鎬昏")) continue;
+            if (nt.matches("^[涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+銆�.*")) continue;
+            if (nt.replace(" ", "").matches(".*灏廫\s*璁�.*")) continue;
+            if (nt.startsWith("濉姤") || nt.startsWith("鍗曚綅璐熻矗浜�") || nt.matches("^\\d+銆�.*")) continue;
+            if (!hasSeq) {
+                if (nt.length() > 4) count++;
+            } else {
+                String seq = cellStr(row, seqCol);
+                if (seq != null && seq.trim().matches("\\d+(\\.\\d+)?")) count++;
+            }
+        }
+        return count;
+    }
+
+    /** 浠庢枃浠跺悕鎻愬彇甯傚窞锛堝 "锛堝瓭鎰熷競閬撹繍涓績锛夆��" -> 瀛濇劅甯傦級 */
+    private String cityFromFileName(String fileName) {
+        for (Map.Entry<String, String> e : INVEST_CITY_SHORT_TO_FULL.entrySet()) {
+            if (fileName.contains(e.getKey())) return e.getValue();
+        }
+        return null;
+    }
+
+    /** 浠� sheet 澶撮儴銆屽~鎶ュ崟浣嶏細XX銆嶈鎻愬彇甯傚窞 */
+    private String cityFromSheetHeader(Sheet sh) {
+        for (int r = 0; r < Math.min(6, sh.getLastRowNum() + 1); r++) {
+            Row row = sh.getRow(r);
+            if (row == null) continue;
+            for (int c = 0; c <= row.getLastCellNum(); c++) {
+                String v = cellStr(row, c);
+                if (v == null || !v.contains("濉姤鍗曚綅")) continue;
+                java.util.regex.Matcher m = java.util.regex.Pattern
+                    .compile("濉姤鍗曚綅[锛�:锛�(]?(鐩栫珷|绛剧珷)?[锛�)]?[锛�:]*([^\\s锛�,锛�;]{2,14})")
+                    .matcher(v);
+                if (m.find()) {
+                    String raw = m.group(2);
+                    for (Map.Entry<String, String> e : INVEST_CITY_SHORT_TO_FULL.entrySet()) {
+                        if (raw.contains(e.getKey())) return e.getValue();
+                    }
+                    String city = investmentCity(raw);
+                    if (city != null) return city;
+                }
+            }
+        }
+        return null;
+    }
+
+    /** 瑙f瀽銆屽崄浜斾簲銆嶈鍒掔墿娴侀」鐩ā鏉挎竻鍗曪紝杩斿洖瑙勮寖鍖栭」鐩悕闆嗗悎 */
+    private Set<String> loadFiveYearProjectNames() {
+        Set<String> names = new LinkedHashSet<>();
+        File template = resolveInvestTemplate("妯℃澘_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls");
+        if (template == null || !template.exists()) {
+            log.warn("鍗佷簲浜旀ā鏉夸笉瀛樺湪锛宨s_five_year 鏍囪璺宠繃: {}", template);
+            return names;
+        }
+        try (java.io.InputStream is = new java.io.FileInputStream(template);
+             Workbook wb = WorkbookFactory.create(is)) {
+            Sheet sh = null;
+            for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+                if (wb.getSheetName(i).contains("鍒嗛」鐩姇璧�")) { sh = wb.getSheetAt(i); break; }
+            }
+            if (sh == null) sh = wb.getSheetAt(0);
+            Map<String, Integer> col = findInvestHeader(sh);
+            if (col == null) return names;
+            int nameCol = col.get("name");
+            int seqCol = col.containsKey("seq") ? col.get("seq") : 0;
+            for (int r = col.get("headerRow") + 1; r <= sh.getLastRowNum(); r++) {
+                Row row = sh.getRow(r);
+                if (row == null) continue;
+                String seq = cellStr(row, seqCol);
+                String name = cellStr(row, nameCol);
+                if (seq == null || name == null) continue;
+                if (!seq.trim().matches("\\d+")) continue;
+                names.add(normalizeProjectName(name.trim()));
+            }
+        } catch (Exception e) {
+            log.error("load five year project names error", e);
+        }
+        return names;
+    }
+
+    /** 椤圭洰鍚嶈鑼冨寲锛氬幓绌虹櫧/鎹㈣/鎷彿鍐呭/鏍囩偣/銆岄」鐩�嶅悗缂�锛岀敤浜庡崄浜斾簲娓呭崟鍖归厤 */
+    private String normalizeProjectName(String name) {
+        String s = name.replaceAll("\\s+", "").replace("\n", "");
+        s = s.replaceAll("[锛�(銆怽\[][^锛�)銆慭\]]*[锛�)銆慭\]]", "");
+        s = s.replaceAll("[锛�,銆�.銆侊紱;锛�:鈥擻\-锛峗/\\\\|]", "");
+        s = s.replace("鐗╂祦椤圭洰", "鐗╂祦").replace("椤圭洰", "");
+        return s;
+    }
+
+    /** 鎶ヨ〃鏈� "2026-07" -> "7鏈�" */
+    private String investMonthDir(String period) {
+        if (period == null || !period.matches("\\d{4}-\\d{2}")) return "";
+        int month = Integer.parseInt(period.substring(5, 7));
+        return month + "鏈�";
+    }
+
+    /** 鎶曡祫杈撳叆鐩綍瀹氫綅锛歶ser.dir -> user.dir/.. 閫愮骇鍥為��锛坰pring-boot:run 鐨� user.dir 鏄� traffic-audit-server锛� */
+    private File resolveInvestInputDir(String catDir, String monthDir) {
+        String rel = "docs/鎶曡祫/杈撳叆/" + catDir + "/" + monthDir;
+        for (String baseDir : new String[]{System.getProperty("user.dir"), System.getProperty("user.dir") + "/.."}) {
+            File f = new File(baseDir, rel);
+            if (f.isDirectory()) return f;
+        }
+        return null;
+    }
+
+    private File resolveInvestTemplate(String fileName) {
+        String rel = "docs/鎶曡祫/妯℃澘/" + fileName;
+        for (String baseDir : new String[]{System.getProperty("user.dir"), System.getProperty("user.dir") + "/.."}) {
+            File f = new File(baseDir, rel);
+            if (f.isFile()) return f;
+        }
+        return null;
+    }
+
+    /** 鍚岀被鍒悓鎶ヨ〃鏈熸湀搴︽暟鎹� + INVEST 瀹℃牳缁撴灉娓呯悊锛堝箓绛夛級 */
+    private void cleanInvestMonthly(String category, String period) {
+        List<InvestmentProject> sameCat = investProjectMapper.selectList(
+            new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, category));
+        java.util.Set<Long> catProjectIds = new HashSet<>();
+        for (InvestmentProject pc : sameCat) catProjectIds.add(pc.getId());
+        if (!catProjectIds.isEmpty()) {
+            investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>()
+                .eq(InvestmentMonthly::getReportPeriod, period)
+                .in(InvestmentMonthly::getProjectId, catProjectIds));
+        }
+        List<Long> investRuleIds = new ArrayList<>();
+        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+            if ("INVEST".equals(rule.getReportType())) investRuleIds.add(rule.getId());
+        }
+        if (!investRuleIds.isEmpty()) {
+            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, period)
+                .in(AuditResult::getRuleId, investRuleIds));
+        }
+    }
+
+    /** 椤圭洰涓绘。 upsert锛堟寜 椤圭洰鍚�+绫诲埆锛�+ 鏈堝害鍐欏叆 */
+    private void saveInvestProjectRow(InvestmentProject project, InvestmentMonthly monthly) {
+        InvestmentProject exist = investProjectMapper.selectOne(new LambdaQueryWrapper<InvestmentProject>()
+            .eq(InvestmentProject::getProjectName, project.getProjectName())
+            .eq(InvestmentProject::getCategory, project.getCategory())
+            .last("LIMIT 1"));
+        if (exist == null) {
+            investProjectMapper.insert(project);
+            monthly.setProjectId(project.getId());
+        } else {
+            project.setId(exist.getId());
+            project.setCreatedAt(exist.getCreatedAt());
+            investProjectMapper.updateById(project);
+            monthly.setProjectId(exist.getId());
+        }
+        investMonthlyMapper.insert(monthly);
+    }
+
+    private String cellStr(Row row, int idx) {
+        if (idx < 0) return null;
+        return getString(row, idx);
+    }
+
+    private Double cellNum(Row row, int idx) {
+        if (idx < 0) return 0.0;
+        return getDecimal(row, idx);
+    }
+
+    private Double cellNumOrNull(Row row, int idx) {
+        if (idx < 0) return null;
+        return getDecimalOrNull(row, idx);
+    }
+
+    /** 甯傚窞鍗曡〃琛岃褰曪細淇濈暀鏂囦欢 + Excel 琛屽彿鐢ㄤ簬澶辫触瀹氫綅 */
+    private static class InvestCityRow {
+        final int excelRow;
+        final String fileName;
+        final InvestmentProject project;
+        final InvestmentMonthly monthly;
+
+        InvestCityRow(int excelRow, String fileName, InvestmentProject project, InvestmentMonthly monthly) {
+            this.excelRow = excelRow;
+            this.fileName = fileName;
+            this.project = project;
+            this.monthly = monthly;
+        }
+    }
+    /** 鎶曡祫绯荤粺瀵煎嚭瀵煎叆锛堝鏍告瘮瀵瑰熀鍑嗭級锛屽浐瀹�8鍒楋細琛屽彿/鍗曚綅/鏃舵湡/璁″垝鎬绘姇璧�/鑷紑濮嬬疮璁�/鏈勾璁″垝/鑷勾鍒濈疮璁�/褰撴湀瀹屾垚 */
+    public int importInvestmentSystem(MultipartFile file, String period) throws Exception {
+        List<InvestmentSystem> list = new ArrayList<>();
+        int failRows = 0;
+        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+            Sheet sheet = wb.getSheetAt(0);
+            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String name = getString(row, 1);
+                if (name == null || name.trim().isEmpty()) continue;
+                InvestmentSystem sys = new InvestmentSystem();
+                sys.setReportPeriod(period);
+                sys.setProjectName(name.trim());
+                sys.setTotalInvestment(getDecimal(row, 3));
+                sys.setStartCum(getDecimal(row, 4));
+                sys.setYearPlan(getDecimal(row, 5));
+                sys.setYearCum(getDecimal(row, 6));
+                sys.setMonthDone(getDecimal(row, 7));
+                list.add(sys);
+            }
+        } catch (Exception e) {
+            log.error("Investment system parse error", e);
+            throw e;
+        }
+        investSystemMapper.delete(new LambdaQueryWrapper<InvestmentSystem>()
+            .eq(InvestmentSystem::getReportPeriod, period));
+        int success = 0;
+        for (InvestmentSystem sys : list) {
+            try {
+                investSystemMapper.insert(sys);
+                success++;
+            } catch (Exception e) {
+                failRows++;
+                log.error("Investment system insert error: {}", sys.getProjectName(), e);
+            }
+        }
+        recordBatch(file.getOriginalFilename(), "INVEST_SYSTEM", period, list.size(), success, failRows, null);
+        log.info("Investment system imported: {} rows for {}", success, period);
+        return success;
+    }
+
+    /** 鍩庡競鍏氦鏈堟姤瀵煎叆锛堝煄甯傚叕鍏变氦閫氭湀搴﹁繍钀ユ儏鍐碉紝浼佷笟绾ф槑缁嗭紝鍙栧叕鍏辨苯鐢佃溅瀛楁锛� */
+    public ImportResult importCityBus(MultipartFile file, String period) throws Exception {
+        List<CityBusRow> rows = new ArrayList<>();
+        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+            Sheet sheet = wb.getSheetAt(0);
+            Row header = sheet.getRow(0);
+            if (header == null || getString(header, 2) == null
+                    || !getString(header, 2).contains("浼佷笟鍚嶇О")) {
+                throw new RuntimeException("鏈壘鍒拌〃澶磋锛堥渶鍖呭惈 浼佷笟鍚嶇О 鍒楋級");
+            }
+            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String name = getString(row, 2);
+                if (name == null || name.trim().isEmpty()) continue;
+                CityBusMonthly item = new CityBusMonthly();
+                item.setReportPeriod(period);
+                item.setRegionCode(getString(row, 0));
+                item.setCity(RegionUtil.cityByCode(getString(row, 0)));
+                item.setEnterpriseCode(getString(row, 1));
+                item.setEnterpriseName(name.trim());
+                item.setOpVehicles(getDecimal(row, 5));
+                item.setStopTotal(getDecimal(row, 6));
+                item.setStopCity(getDecimal(row, 7));
+                item.setPassengerVolume(getDecimal(row, 8));
+                item.setPassengerCity(getDecimal(row, 9));
+                item.setTurnover(getDecimal(row, 10));
+                item.setTurnoverCity(getDecimal(row, 11));
+                item.setRailPassengerVolume(getDecimal(row, 13));
+                item.setRailTurnover(getDecimal(row, 14));
+                item.setFerryPassengerVolume(getDecimal(row, 17));
+                item.setFerryTurnover(getDecimal(row, 18));
+                item.setAvgDistance(getDecimal(row, 19));
+                item.setAvgDistanceCity(getDecimal(row, 20));
+                item.setContactPerson(getString(row, 24));
+                item.setContactPhone(getString(row, 25));
+                item.setReportDate(getDateString(row, 26));
+                item.setRateOpVehiclesMom(getDecimal(row, 29));
+                item.setRatePassengerMom(getDecimal(row, 30));
+                item.setRatePassengerCityMom(getDecimal(row, 31));
+                item.setRateChengxiangMom(getDecimal(row, 32));
+                item.setRateChengxiangTurnoverMom(getDecimal(row, 33));
+                item.setAvgDistanceMomChange(getDecimal(row, 34));
+                item.setAvgDistanceCityMomChange(getDecimal(row, 35));
+                item.setDailyPassengerPerVehicle(getDecimal(row, 36));
+                item.setRateOpVehiclesYoy(getDecimal(row, 39));
+                item.setRatePassengerYoy(getDecimal(row, 40));
+                item.setRatePassengerCityYoy(getDecimal(row, 41));
+                item.setRateChengxiangYoy(getDecimal(row, 42));
+                item.setRateChengxiangTurnoverYoy(getDecimal(row, 43));
+                item.setStopCityRatio(getDecimal(row, 44));
+                item.setPassengerCityRatio(getDecimal(row, 45));
+                item.setChengxiangRatio(getDecimal(row, 46));
+                item.setRateTurnoverYoy(getDecimal(row, 47));
+                item.setRateTurnoverMom(getDecimal(row, 48));
+                item.setPassengerChengxiang(getDecimal(row, 49));
+                item.setTurnoverChengxiang(getDecimal(row, 50));
+                item.setAvgDistanceChengxiang(getDecimal(row, 51));
+                item.setVerifyExplanation(getString(row, 65));
+                item.setReportNote(getString(row, 66));
+                item.setModifyRecord(getString(row, 67));
+                rows.add(new CityBusRow(r + 1, item));
+            }
+        } catch (Exception e) {
+            log.error("CityBus import parse error: {}", e.getMessage());
+            throw e;
+        }
+        // 骞傜瓑锛氬悓鎶ヨ〃鏈熷厛娓呮棫鏁版嵁涓� CITY_BUS 瀹℃牳缁撴灉
+        cityBusMapper.delete(new LambdaQueryWrapper<CityBusMonthly>()
+            .eq(CityBusMonthly::getReportPeriod, period));
+        List<Long> busRuleIds = new ArrayList<>();
+        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+            if ("CITY_BUS".equals(rule.getReportType())) busRuleIds.add(rule.getId());
+        }
+        if (!busRuleIds.isEmpty()) {
+            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, period)
+                .in(AuditResult::getRuleId, busRuleIds));
+        }
+        int success = 0;
+        List<String> failDetails = new ArrayList<>();
+        for (CityBusRow item : rows) {
+            try {
+                cityBusMapper.insert(item.entity);
+                success++;
+            } catch (Exception e) {
+                Throwable cause = e;
+                while (cause.getCause() != null) cause = cause.getCause();
+                String reason = cause.getMessage();
+                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
+                failDetails.add("绗� " + item.excelRow + " 琛岋紙" + item.entity.getEnterpriseName() + "锛夛細" + reason);
+                log.error("CityBus insert error: row {}", item.excelRow, e);
+            }
+        }
+        String errorDetail = String.join("\n", failDetails);
+        if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+        recordBatch(file.getOriginalFilename(), "CITY_BUS", period, rows.size(), success, failDetails.size(), errorDetail);
+        log.info("CityBus imported: {} rows for {}", success, period);
+        return new ImportResult(success, failDetails.size(), failDetails);
+    }
+
+    /** 鍩庡競鍏氦琛岃褰曪細淇濈暀 Excel 琛屽彿鐢ㄤ簬澶辫触瀹氫綅 */
+    private static class CityBusRow {
+        final int excelRow;
+        final CityBusMonthly entity;
+
+        CityBusRow(int excelRow, CityBusMonthly entity) {
+            this.excelRow = excelRow;
+            this.entity = entity;
+        }
+    }
+
+    /** 宸℃父鍑虹鏈堟姤瀵煎叆锛堝競宸炵骇锛�17 琛岋紱鍚牳瀹炶鏄�/涓婃姤璇存槑/淇敼璁板綍锛� */
+    public ImportResult importCityTaxi(MultipartFile file, String period) throws Exception {
+        List<TaxiRow> rows = new ArrayList<>();
+        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+            Sheet sheet = wb.getSheetAt(0);
+            Row header = sheet.getRow(0);
+            if (header == null || getString(header, 2) == null
+                    || !getString(header, 2).contains("浼佷笟鍚嶇О")) {
+                throw new RuntimeException("鏈壘鍒拌〃澶磋锛堥渶鍖呭惈 浼佷笟鍚嶇О 鍒楋級");
+            }
+            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String name = getString(row, 2);
+                if (name == null || name.trim().isEmpty()) continue;
+                CityTaxiMonthly item = new CityTaxiMonthly();
+                item.setReportPeriod(period);
+                item.setRegionCode(getString(row, 0));
+                item.setCity(cityOf(getString(row, 0), name));
+                item.setTripTotal(getDecimal(row, 4));
+                item.setTripCity(getDecimal(row, 5));
+                item.setPassengerVolume(getDecimal(row, 6));
+                item.setPassengerCity(getDecimal(row, 7));
+                item.setTurnover(getDecimal(row, 8));
+                item.setTurnoverCity(getDecimal(row, 9));
+                item.setOpVehicles(getDecimal(row, 10));
+                item.setAvgDistance(getDecimal(row, 11));
+                item.setAvgDistanceCity(getDecimal(row, 12));
+                item.setContactPerson(getString(row, 15));
+                item.setContactPhone(getString(row, 16));
+                item.setReportDate(getDateString(row, 17));
+                item.setDailyTripsPerVehicle(getDecimal(row, 18));
+                item.setDailyPassengerPerVehicle(getDecimal(row, 19));
+                item.setPassengersPerTrip(getDecimal(row, 20));
+                item.setRateOpVehiclesMom(getDecimal(row, 21));
+                item.setRateOpVehiclesYoy(getDecimal(row, 22));
+                item.setRateTripMom(getDecimal(row, 23));
+                item.setRateTripYoy(getDecimal(row, 24));
+                item.setRatePassengerMom(getDecimal(row, 25));
+                item.setRatePassengerYoy(getDecimal(row, 26));
+                item.setAvgDistanceMomChange(getDecimal(row, 27));
+                item.setPassengersPerTripCity(getDecimal(row, 28));
+                item.setRateTripCityMom(getDecimal(row, 29));
+                item.setRatePassengerCityMom(getDecimal(row, 30));
+                item.setAvgDistanceCityMomChange(getDecimal(row, 31));
+                item.setTripCityRatio(getDecimal(row, 32));
+                item.setPassengerCityRatio(getDecimal(row, 33));
+                item.setPassengersPerTripChengxiang(getDecimal(row, 34));
+                item.setRateTripChengxiangMom(getDecimal(row, 35));
+                item.setRatePassengerChengxiangMom(getDecimal(row, 36));
+                item.setAvgDistanceChengxiang(getDecimal(row, 37));
+                item.setVerifyExplanation(getString(row, 38));
+                item.setReportNote(getString(row, 39));
+                item.setModifyRecord(getString(row, 40));
+                rows.add(new TaxiRow(r + 1, item));
+            }
+        } catch (Exception e) {
+            log.error("CityTaxi import parse error: {}", e.getMessage());
+            throw e;
+        }
+        // 骞傜瓑锛氬悓鎶ヨ〃鏈熷厛娓呮棫鏁版嵁涓� CITY_TAXI 瀹℃牳缁撴灉
+        cityTaxiMapper.delete(new LambdaQueryWrapper<CityTaxiMonthly>()
+            .eq(CityTaxiMonthly::getReportPeriod, period));
+        deleteAuditResults(period, "CITY_TAXI");
+        int success = 0;
+        List<String> failDetails = new ArrayList<>();
+        for (TaxiRow item : rows) {
+            try {
+                cityTaxiMapper.insert(item.entity);
+                success++;
+            } catch (Exception e) {
+                Throwable cause = e;
+                while (cause.getCause() != null) cause = cause.getCause();
+                String reason = cause.getMessage();
+                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
+                failDetails.add("绗� " + item.excelRow + " 琛岋紙" + item.entity.getCity() + "锛夛細" + reason);
+                log.error("CityTaxi insert error: row {}", item.excelRow, e);
+            }
+        }
+        String errorDetail = String.join("\n", failDetails);
+        if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n鈥︹��";
+        recordBatch(file.getOriginalFilename(), "CITY_TAXI", period, rows.size(), success, failDetails.size(), errorDetail);
+        log.info("CityTaxi imported: {} rows for {}", success, period);
+        return new ImportResult(success, failDetails.size(), failDetails);
+    }
+
+    /** 鍑虹杞﹁繍鏀胯溅杈嗕俊鎭鍏ワ紙杞﹁締绾ф槑缁嗭紝鎸夊競宸炲綊灞炵粺璁$敤锛� */
+    public synchronized int importCityTaxiAuth(MultipartFile file, String period) throws Exception {
+        List<CityTaxiAuth> list = new ArrayList<>();
+        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
+            Sheet sheet = wb.getSheetAt(0);
+            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                String plate = getString(row, 0);
+                if (plate == null || plate.trim().isEmpty()) continue;
+                CityTaxiAuth item = new CityTaxiAuth();
+                item.setReportPeriod(period);
+                item.setCity(taxiCity(getString(row, 9), getString(row, 8), getString(row, 4), getString(row, 18)));
+                item.setPlateNo(plate.trim());
+                item.setVehicleType(getString(row, 1));
+                item.setBizType(getString(row, 2));
+                item.setOwnerName(getString(row, 3));
+                item.setLicenseNo(getString(row, 4));
+                item.setFuelType(getString(row, 7));
+                item.setArchiveNo(getString(row, 8));
+                item.setRoadCertNo(getString(row, 9));
+                item.setOperatorName(getString(row, 17));
+                item.setOperatorAddr(getString(row, 18));
+                item.setOpStatus(getString(row, 19));
+                item.setCertStatus(getString(row, 20));
+                list.add(item);
+            }
+        } catch (Exception e) {
+            log.error("CityTaxiAuth import parse error", e);
+            throw e;
+        }
+        // 骞傜瓑锛氬悓鎶ヨ〃鏈熷厛娓呮棫鏁版嵁锛堣繍鏀胯溅杈嗛噺澶э紝鎵归噺娓呯悊锛�
+        cityTaxiAuthMapper.delete(new LambdaQueryWrapper<CityTaxiAuth>()
+            .eq(CityTaxiAuth::getReportPeriod, period));
+        deleteAuditResults(period, "CITY_TAXI");
+        // 鎵归噺鎻掑叆锛圔ATCH 妯″紡锛夛細42636 琛岀害 20~40 绉掞紝閫愭潯鎻掑叆闇�绾� 6 鍒嗛挓浼氳Е鍙戝墠绔秴鏃�
+        int success = 0;
+        int fail = 0;
+        try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
+            CityTaxiAuthMapper batchMapper = session.getMapper(CityTaxiAuthMapper.class);
+            for (CityTaxiAuth item : list) {
+                batchMapper.insert(item);
+            }
+            session.flushStatements();
+            session.commit();
+            success = list.size();
+        } catch (Exception e) {
+            fail = list.size();
+            log.error("CityTaxiAuth batch insert error", e);
+        }
+        recordBatch(file.getOriginalFilename(), "CITY_TAXI_AUTH", period, list.size(), success, fail, null);
+        log.info("CityTaxiAuth imported: {} rows for {}", success, period);
+        return success;
+    }
+
+    /** 杩愭斂鍑虹杞﹀競宸炴彁鍙栵細閬撹矾杩愯緭璇佸瓧鍙封啋妗f鍙封啋缁忚惀鏉冨彿鈫掍笟鎴峰湴鍧�锛屽彇 6 浣嶅尯鍒掍唬鐮� */
+    private static final java.util.regex.Pattern TAXI_REGION_PAT =
+        java.util.regex.Pattern.compile("(4290\\d{2}|42(?:0[1-9]|1[0-3]|28)\\d{2})");
+
+    private String taxiCity(String... fields) {
+        for (String f : fields) {
+            if (f == null) continue;
+            java.util.regex.Matcher m = TAXI_REGION_PAT.matcher(f);
+            if (m.find()) {
+                String city = RegionUtil.cityByCode(m.group(1));
+                if (city != null) return city;
+            }
+        }
+        return null;
+    }
+
+    /** 甯傚窞褰掑睘锛氫紭鍏堟寜鍖哄垝浠g爜璇嗗埆锛屽叾娆℃寜鍚嶇О瑙勮寖鍖� */
+    private String cityOf(String regionCode, String name) {
+        String city = RegionUtil.cityByCode(regionCode);
+        if (city != null) return city;
+        return RegionUtil.normalizeCityName(name);
+    }
+
+    /** 娓呴櫎鏌愭姤琛ㄧ被鍨嬪湪鎸囧畾鎶ヨ〃鏈熺殑瀹℃牳缁撴灉锛堝鍏ュ箓绛夛級 */
+    private void deleteAuditResults(String period, String reportType) {
+        List<Long> ruleIds = new ArrayList<>();
+        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
+            if (reportType.equals(rule.getReportType())) ruleIds.add(rule.getId());
+        }
+        if (!ruleIds.isEmpty()) {
+            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
+                .eq(AuditResult::getReportPeriod, period)
+                .in(AuditResult::getRuleId, ruleIds));
+        }
+    }
+
+    /** 宸℃父鍑虹琛岃褰曪細淇濈暀 Excel 琛屽彿鐢ㄤ簬澶辫触瀹氫綅 */
+    private static class TaxiRow {
+        final int excelRow;
+        final CityTaxiMonthly entity;
+
+        TaxiRow(int excelRow, CityTaxiMonthly entity) {
+            this.excelRow = excelRow;
+            this.entity = entity;
+        }
+    }
+
+    private Sheet pickInvestmentSheet(Workbook wb, boolean passenger) {
+        if (passenger) {
+            for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+                String name = wb.getSheetName(i);
+                if (name != null && name.contains("鏄庣粏")) return wb.getSheetAt(i);
+            }
+            return wb.getSheetAt(0);
+        }
+        for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+            String name = wb.getSheetName(i);
+            if (name != null && name.contains("鍒嗛」鐩姇璧�")) return wb.getSheetAt(i);
+        }
+        return wb.getSheetAt(0);
+    }
+
+    /** 甯傚窞灏忚妭琛岃瘑鍒細鐭悕/瑙勮寖鍚� -> 瑙勮寖鍚嶏紝闈炲競宸炶杩斿洖 null */
+    private String investmentCity(String a) {
+        if (a == null) return null;
+        String full = INVEST_CITY_SHORT_TO_FULL.get(a);
+        if (full != null) return full;
+        String norm = RegionUtil.normalizeCityName(a);
+        if (RegionUtil.CITY_LIST.contains(norm)) return norm;
+        return null;
+    }
+
+    /** 寮�宸�/寤烘垚鏃堕棿锛氭暟瀛楋紙2025 鎴� 202501锛夋垨鏂囨湰缁熶竴涓� yyyy 鎴� yyyyMM */
+    private String investTime(String v) {
+        if (v == null || v.trim().isEmpty()) return null;
+        String t = v.trim();
+        if (t.matches("\\d+\\.0")) t = t.substring(0, t.length() - 2);
+        if (t.matches("\\d{4}")) return t;
+        if (t.matches("\\d{6}")) return t;
+        if (t.matches("\\d{4}-\\d{2}-\\d{2}")) return t;
+        return t;
+    }
+
+    /** 鎶曡祫琛岃褰曪細淇濈暀 Excel 琛屽彿鐢ㄤ簬澶辫触瀹氫綅 */
+    private static class InvestRow {
+        final int excelRow;
+        final InvestmentProject project;
+        final InvestmentMonthly monthly;
+
+        InvestRow(int excelRow, InvestmentProject project, InvestmentMonthly monthly) {
+            this.excelRow = excelRow;
+            this.project = project;
+            this.monthly = monthly;
+        }
+    }
+
+    // ========== 閫氱敤鏂囦欢澶规壒閲忓鍏ワ紙鏈嶅姟绔洰褰曟祻瑙堝櫒锛� ==========
+
+    /**
+     * 鐩綍娴忚鍣細path 涓虹┖鏃惰繑鍥為」鐩� docs 鏁版嵁鐩綍涓嬬殑瀛愮洰褰曪紱
+     * 杩斿洖褰撳墠鐩綍缁濆璺緞銆佷笂绾х洰褰曘�佸瓙鐩綍鍒楄〃鍙婂叾涓洿鎺ュ寘鍚殑 Excel 鏂囦欢鍚�/鏁伴噺銆�
+     */
+    public Map<String, Object> listDirs(String path) throws Exception {
+        File cur;
+        if (path == null || path.trim().isEmpty()) {
+            File docs = resolveDocsDir();
+            if (!docs.isDirectory()) {
+                throw new RuntimeException("鏈壘鍒版暟鎹洰褰� docs锛堝綋鍓嶈繍琛岀洰褰曪細" + System.getProperty("user.dir") + "锛�");
+            }
+            cur = docs;
+        } else {
+            cur = new File(path);
+            if (!cur.isDirectory()) {
+                throw new RuntimeException("鐩綍涓嶅瓨鍦ㄦ垨涓嶆槸鏂囦欢澶癸細" + path);
+            }
+        }
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("current", cur.getAbsolutePath());
+        result.put("parent", cur.getParent() == null ? "" : cur.getParent());
+        List<Map<String, Object>> dirs = new ArrayList<>();
+        File[] subs = cur.listFiles(File::isDirectory);
+        if (subs != null) {
+            Arrays.sort(subs, Comparator.comparing(File::getName));
+            for (File d : subs) {
+                if (d.isHidden()) continue;
+                Map<String, Object> m = new LinkedHashMap<>();
+                m.put("name", d.getName());
+                m.put("path", d.getAbsolutePath());
+                dirs.add(m);
+            }
+        }
+        result.put("dirs", dirs);
+        List<String> excelFiles = new ArrayList<>();
+        File[] files = cur.listFiles((d, n) -> {
+            String lower = n.toLowerCase();
+            return lower.endsWith(".xlsx") || lower.endsWith(".xls");
+        });
+        if (files != null) {
+            Arrays.sort(files, Comparator.comparing(File::getName));
+            for (File f : files) excelFiles.add(f.getName());
+        }
+        result.put("excelCount", excelFiles.size());
+        result.put("files", excelFiles);
+        return result;
+    }
+
+    /** 鍚戜笂鏌ユ壘椤圭洰 docs 鏁版嵁鐩綍锛堝吋瀹逛粠 IDEA 浠� server 妯″潡鐩綍鍚姩鐨勬儏鍐碉級 */
+    private File resolveDocsDir() {
+        File base = new File(System.getProperty("user.dir"));
+        File cur = base;
+        for (int i = 0; i < 4 && cur != null; i++) {
+            File candidate = new File(cur, "docs");
+            if (candidate.isDirectory()) return candidate;
+            cur = cur.getParentFile();
+        }
+        return new File(base, "docs");
+    }
+
+    /**
+     * 浠庢湇鍔$鏂囦欢澶规壒閲忓鍏ワ細鍙壂鎻� dir 鐩存帴鍖呭惈鐨� .xlsx/.xls锛�
+     * 鎸� type 閫愪釜璋冪敤瀵瑰簲瀵煎叆鏂规硶锛堜笌涓婁紶瀵煎叆鍚屼竴濂楄В鏋愰�昏緫锛夛紝杩斿洖閫愭枃浠剁粨鏋溿��
+     */
+    public Map<String, Object> importFromDir(String type, String period, boolean skipImported, String dir) throws Exception {
+        File folder = new File(dir);
+        if (!folder.isDirectory()) {
+            throw new RuntimeException("鐩綍涓嶅瓨鍦ㄦ垨涓嶆槸鏂囦欢澶癸細" + dir);
+        }
+        File[] files = folder.listFiles((d, n) -> {
+            String lower = n.toLowerCase();
+            return lower.endsWith(".xlsx") || lower.endsWith(".xls");
+        });
+        if (files == null || files.length == 0) {
+            throw new RuntimeException("鐩綍涓病鏈� Excel 鏂囦欢锛�.xlsx/.xls锛夛細" + folder.getAbsolutePath());
+        }
+        Arrays.sort(files, Comparator.comparing(File::getName));
+        List<Map<String, Object>> fileResults = new ArrayList<>();
+        int totalSuccess = 0, totalFail = 0;
+        for (File f : files) {
+            Map<String, Object> fr = new LinkedHashMap<>();
+            fr.put("fileName", f.getName());
+            try {
+                String filePeriod = detectPeriod(f.getName(), period);
+                if (filePeriod == null || filePeriod.trim().isEmpty()) {
+                    throw new RuntimeException("鏃犳硶浠庢枃浠跺悕璇嗗埆鎶ヨ〃鏈燂紙鏀寔 2026骞�7鏈� / 2026.7 / 2026-07 / 7鏈堬級锛屼篃鏈�夋嫨鎶ヨ〃鏈燂細" + f.getName());
+                }
+                fr.put("period", filePeriod);
+                if (skipImported && alreadyImported(filePeriod, f.getName())) {
+                    fr.put("skipped", true);
+                    fr.put("success", 0);
+                    fr.put("fail", 0);
+                    totalFail++;
+                    fileResults.add(fr);
+                    continue;
+                }
+                byte[] bytes;
+                try (InputStream is = new FileInputStream(f)) {
+                    bytes = IoUtil.readBytes(is);
+                }
+                MultipartFile mf = new ByteArrayMultipartFile(f.getName(), bytes);
+                ImportResult r = importDirFile(type, mf, filePeriod);
+                fr.put("success", r.getSuccess());
+                fr.put("fail", r.getFail());
+                if (r.getFail() > 0 && r.getFailDetails() != null && !r.getFailDetails().isEmpty()) {
+                    String d = r.getFailDetails().get(0);
+                    if (d.length() > 300) d = d.substring(0, 300);
+                    fr.put("error", d);
+                }
+                totalSuccess += r.getSuccess();
+                totalFail += r.getFail();
+            } catch (Exception e) {
+                String msg = e.getMessage() == null ? e.toString() : e.getMessage();
+                if (msg.length() > 300) msg = msg.substring(0, 300);
+                fr.put("success", 0);
+                fr.put("fail", 0);
+                fr.put("error", msg);
+                totalFail++;
+            }
+            fileResults.add(fr);
+        }
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("type", type);
+        result.put("total", fileResults.size());
+        result.put("success", totalSuccess);
+        result.put("fail", totalFail);
+        result.put("files", fileResults);
+        return result;
+    }
+
+    /**
+     * 鎸夋枃浠惰矾寰勫鍏ュ崟涓� Excel锛堜緵鍓嶇閫愭枃浠跺鍏ユ樉绀鸿繘搴︼級銆�
+     * 鎶ヨ〃鏈熶紭鍏堜粠鏂囦欢鍚嶈瘑鍒紙濡� 2026骞�7鏈� / 2026.7 / 2026-07 / 7鏈堬級锛岃瘑鍒笉鍒版墠鐢� period銆�
+     */
+    public Map<String, Object> importDirFileByPath(String type, String period, boolean skipImported, String filePath) throws Exception {
+        File f = new File(filePath);
+        if (!f.isFile()) {
+            throw new RuntimeException("鏂囦欢涓嶅瓨鍦細" + filePath);
+        }
+        String filePeriod = detectPeriod(f.getName(), period);
+        if (filePeriod == null || filePeriod.trim().isEmpty()) {
+            throw new RuntimeException("鏃犳硶浠庢枃浠跺悕璇嗗埆鎶ヨ〃鏈燂紙鏀寔 2026骞�7鏈� / 2026.7 / 2026-07 / 7鏈堬級锛屼篃鏈�夋嫨鎶ヨ〃鏈燂細" + f.getName());
+        }
+        Map<String, Object> fr = new LinkedHashMap<>();
+        fr.put("fileName", f.getName());
+        fr.put("period", filePeriod);
+        if (skipImported && alreadyImported(filePeriod, f.getName())) {
+            fr.put("skipped", true);
+            fr.put("success", 0);
+            fr.put("fail", 0);
+            fr.put("error", "璇ユ姤琛ㄦ湡宸插鍏ヨ繃姝ゆ枃浠讹紝宸茶烦杩囷紙濡傞渶瑕嗙洊璇峰彇娑堝嬀閫夆�滆烦杩囧凡瀵煎叆鏂囦欢鈥濓級");
+            return fr;
+        }
+        byte[] bytes;
+        try (InputStream is = new FileInputStream(f)) {
+            bytes = IoUtil.readBytes(is);
+        }
+        ImportResult r = importDirFile(type, new ByteArrayMultipartFile(f.getName(), bytes), filePeriod);
+        fr.put("success", r.getSuccess());
+        fr.put("fail", r.getFail());
+        if (r.getFail() > 0 && r.getFailDetails() != null && !r.getFailDetails().isEmpty()) {
+            String d = r.getFailDetails().get(0);
+            if (d.length() > 300) d = d.substring(0, 300);
+            fr.put("error", d);
+        }
+        return fr;
+    }
+
+    /** 璇ユ姤琛ㄦ湡鏄惁宸叉垚鍔熷鍏ヨ繃鍚屽悕鏂囦欢锛堢敤浜庢枃浠跺す閲嶅瀵煎叆鏃惰烦杩囷級 */
+    private boolean alreadyImported(String period, String fileName) {
+        if (period == null || fileName == null) return false;
+        Long cnt = importBatchMapper.selectCount(new LambdaQueryWrapper<ImportBatch>()
+            .eq(ImportBatch::getReportPeriod, period)
+            .eq(ImportBatch::getFileName, fileName)
+            .eq(ImportBatch::getFailRows, 0));
+        return cnt != null && cnt > 0;
+    }
+
+    /** 浠庢枃浠跺悕璇嗗埆鎶ヨ〃鏈燂細2026骞�7鏈� / 2026骞�07鏈� / 2026.7 / 2026-07 / 202607 / 7鏈堬紙鏃犲勾浠界敤 period 骞翠唤鎴栧綋鍓嶅勾浠斤級 */
+    private String detectPeriod(String fileName, String defaultPeriod) {
+        if (fileName == null) return defaultPeriod;
+        String name = fileName.replace("锛�", "(").replace("锛�", ")").replace(" ", "");
+        java.util.regex.Matcher m;
+        m = java.util.regex.Pattern.compile("(\\d{4})\\s*骞碶\s*(\\d{1,2})\\s*鏈�").matcher(name);
+        if (m.find()) return String.format("%04d-%02d", Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)));
+        m = java.util.regex.Pattern.compile("(\\d{4})[.\\-/\\s](\\d{1,2})").matcher(name);
+        if (m.find()) return String.format("%04d-%02d", Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)));
+        m = java.util.regex.Pattern.compile("(?:^|[^\\d])(\\d{4})(\\d{2})(?=[^\\d]|$)").matcher(name);
+        if (m.find()) {
+            int y = Integer.parseInt(m.group(1));
+            int mo = Integer.parseInt(m.group(2));
+            if (y >= 2000 && y <= 2100 && mo >= 1 && mo <= 12) {
+                return String.format("%04d-%02d", y, mo);
+            }
+        }
+        m = java.util.regex.Pattern.compile("(\\d{1,2})\\s*鏈�").matcher(name);
+        if (m.find()) {
+            int mo = Integer.parseInt(m.group(1));
+            if (mo >= 1 && mo <= 12) {
+                return String.format("%04d-%02d", defaultYear(defaultPeriod), mo);
+            }
+        }
+        return defaultPeriod;
+    }
+
+    private int defaultYear(String defaultPeriod) {
+        if (defaultPeriod != null && defaultPeriod.matches("\\d{4}-\\d{2}")) {
+            return Integer.parseInt(defaultPeriod.substring(0, 4));
+        }
+        return java.time.Year.now().getValue();
+    }
+
+    /** 绫诲瀷鍒嗗彂锛氫笌涓婁紶瀵煎叆鍚屼竴濂楄В鏋愭柟娉� */
+    private ImportResult importDirFile(String type, MultipartFile file, String period) throws Exception {
+        switch (type) {
+            case "h2032": return importH2032(file, period);
+            case "passengerMonthly": return importPassengerMonthly(file, period);
+            case "passengerIndividual": return importPassengerIndividual(file, period);
+            case "passengerAuth": return importPassengerAuth(file, period);
+            case "energyMonthly": return importEnergyMonthly(file, period);
+            case "energyAuth": return importEnergyAuth(file, period);
+            case "cityBus": return importCityBus(file, period);
+            case "cityTaxi": return importCityTaxi(file, period);
+            case "cityTaxiAuth": return new ImportResult(importCityTaxiAuth(file, period), 0, new ArrayList<>());
+            case "investment": return importInvestment(file, period, "瀹㈣繍绔欏満");
+            case "investmentLogistics": return importInvestment(file, period, "鐗╂祦鍥尯");
+            case "investmentSystem": return new ImportResult(importInvestmentSystem(file, period), 0, new ArrayList<>());
+            case "transportAuth": return new ImportResult(importTransportAuth(file, period), 0, new ArrayList<>());
+            case "trackMileage": return new ImportResult(importTrackMileage(file, period), 0, new ArrayList<>());
+            case "freightTurnover": return new ImportResult(importFreightTurnover(file, period), 0, new ArrayList<>());
+            case "scaleSplit": return new ImportResult(importScaleSplit(file, period), 0, new ArrayList<>());
+            default: throw new RuntimeException("涓嶆敮鎸佺殑瀵煎叆绫诲瀷锛�" + type);
+        }
+    }
+
+    /** 鎶婃湇鍔$鏂囦欢鍖呰涓� MultipartFile锛屽鐢ㄥ悇瀵煎叆鏂规硶 */
+    public static class ByteArrayMultipartFile implements MultipartFile {
+        private final String name;
+        private final byte[] content;
+
+        public ByteArrayMultipartFile(String name, byte[] content) {
+            this.name = name;
+            this.content = content;
+        }
+
+        @Override
+        public String getName() { return name; }
+
+        @Override
+        public String getOriginalFilename() { return name; }
+
+        @Override
+        public String getContentType() {
+            String lower = name.toLowerCase();
+            return lower.endsWith(".xls")
+                ? "application/vnd.ms-excel"
+                : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+        }
+
+        @Override
+        public boolean isEmpty() { return content == null || content.length == 0; }
+
+        @Override
+        public long getSize() { return content == null ? 0 : content.length; }
+
+        @Override
+        public byte[] getBytes() { return content; }
+
+        @Override
+        public InputStream getInputStream() { return new ByteArrayInputStream(content); }
+
+        @Override
+        public void transferTo(File dest) throws IOException {
+            try (FileOutputStream out = new FileOutputStream(dest)) {
+                out.write(content);
+            }
+        }
+    }
 }
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java
index 9608cab..ccd6f32 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java
@@ -95,6 +95,28 @@
                 new String[]{"report_period", "region_name", "freight_m01", "freight_m02", "freight_m03", "freight_m04",
                         "freight_m05", "freight_m06", "freight_m07", "freight_m08", "freight_m09", "freight_m10", "freight_m11",
                         "freight_m12", "turnover_m01", "turnover_m12"});
+        register("h2031_enterprise_monthly", "鍏矾鏃呭鏈堟姤H203-1琛�(閬撹矾鏃呭杩愯緭鏈堝害鐢熶骇鎯呭喌)",
+                new String[]{"id", "report_period", "region_code", "enterprise_code", "enterprise_name", "vehicle_total",
+                        "seat_total", "passenger_total", "turnover_total", "avg_distance_total", "verify_explanation"},
+                new String[]{"涓婚敭", "鎶ヨ〃鏈�", "鎵�灞炲湴鍖轰唬鐮�", "浼佷笟浠g爜", "浼佷笟鍚嶇О", "杞﹁締鏁�", "杞藉浣嶆暟",
+                        "瀹㈣繍閲�(涓囦汉)", "鏃呭鍛ㄨ浆閲�(涓囦汉鍏噷)", "骞冲潎杩愯窛(鍏噷)", "浼佷笟鏍稿疄瑙i噴"},
+                new String[]{"id", "report_period", "enterprise_name", "vehicle_total", "seat_total",
+                        "passenger_total", "turnover_total", "avg_distance_total", "verify_explanation"});
+        register("city_bus_monthly", "鍩庡競鍏氦鏈堝害杩愯惀鎯呭喌(浼佷笟绾э紝鍚建閬�/杞浮瀛楁)",
+                new String[]{"id", "report_period", "region_code", "city", "enterprise_name", "op_vehicles",
+                        "passenger_volume", "turnover", "avg_distance", "passenger_chengxiang", "turnover_chengxiang",
+                        "verify_explanation"},
+                new String[]{"涓婚敭", "鎶ヨ〃鏈�", "鎵�灞炲湴鍖轰唬鐮�", "甯傚窞", "浼佷笟鍚嶇О", "杩愯惀杞︽暟", "瀹㈣繍閲�(涓囦汉娆�)",
+                        "鏃呭鍛ㄨ浆閲�(涓囦汉鍏噷)", "骞冲潎杩愯窛(鍏噷)", "鍩庨檯鍩庝埂瀹㈣繍閲�", "鍩庨檯鍩庝埂鍛ㄨ浆閲�", "浼佷笟鏍稿疄瑙i噴"},
+                new String[]{"id", "report_period", "enterprise_name", "op_vehicles", "passenger_volume",
+                        "turnover", "avg_distance", "verify_explanation"});
+        register("city_taxi_monthly", "宸℃父鍑虹姹借溅杩愯惀鏈嶅姟鎯呭喌鏈堟姤(甯傚窞绾�)",
+                new String[]{"id", "report_period", "region_code", "city", "trip_total", "passenger_volume",
+                        "turnover", "op_vehicles", "avg_distance", "verify_explanation"},
+                new String[]{"涓婚敭", "鎶ヨ〃鏈�", "鎵�灞炲湴鍖轰唬鐮�", "甯傚窞", "杞藉杞︽鎬绘暟", "瀹㈣繍閲�(涓囦汉娆�)",
+                        "鏃呭鍛ㄨ浆閲�(涓囦汉鍏噷)", "杩愯惀杞﹁締鏁�", "骞冲潎杩愯窛(鍏噷)", "浼佷笟鏍稿疄瑙i噴"},
+                new String[]{"id", "report_period", "city", "trip_total", "passenger_volume",
+                        "turnover", "op_vehicles", "avg_distance", "verify_explanation"});
         register("audit_result", "瀹℃牳缁撴灉",
                 new String[]{"id", "rule_id", "report_id", "enterprise_code", "report_period", "actual_value",
                         "threshold_value", "deviation", "status", "review_comment", "created_at"},
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java
index cb88562..69101c4 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java
@@ -159,6 +159,12 @@
             if (ctx.get("reportId") != null) {
                 sb.append("鍏宠仈涓婃姤璁板綍ID: ").append(ctx.get("reportId")).append("\n");
             }
+            if (ctx.get("reportType") != null) {
+                sb.append("鍏宠仈涓氬姟绫诲瀷: ").append(ctx.get("reportType")).append("\n");
+            }
+            if (ctx.get("tableName") != null) {
+                sb.append("鍏宠仈鏁版嵁琛�: ").append(ctx.get("tableName")).append("锛堟煡璇㈣浼佷笟瑙i噴瀵瑰簲鏁版嵁鏃朵紭鍏堟煡姝よ〃锛屾寜 id=").append(ctx.get("reportId")).append(" 杩囨护锛塡n");
+            }
         }
         sb.append("\n浣犲彲浠ヨ皟鐢� query_database 宸ュ叿鏌ヨ鏁版嵁搴撹幏鍙栫湡瀹炴暟鎹紝濡備紒涓氳溅杈嗘暟銆佸惃浣嶃�佽揣杩愰噺銆佸懆杞噺銆佽建杩归噷绋嬨�佽涓婅涓嬫媶鍒嗐�佸幓骞村悓鏈熺瓑銆俓n\n");
         sb.append("鏁版嵁搴撹〃缁撴瀯濡備笅锛堝垪鍚嶄竴寰嬩娇鐢� camelCase锛屼緥濡� enterpriseName銆乿ehicleTotal銆乫reightTotal銆乼urnoverTotal锛沠ilters 鐨勯敭鍚屾牱浣跨敤 camelCase锛夛細\n");
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
index e5f63df..9594638 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
@@ -2,6 +2,7 @@
 
 import com.trafficaudit.common.Result;
 import com.trafficaudit.reportexport.service.ReportExportService;
+import com.trafficaudit.system.service.OperationLogService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
@@ -13,6 +14,7 @@
 
 import javax.annotation.Resource;
 import java.net.URLEncoder;
+import java.util.List;
 import java.nio.charset.StandardCharsets;
 
 @Slf4j
@@ -22,30 +24,254 @@
 
     @Resource
     private ReportExportService reportService;
+    @Resource
+    private OperationLogService operationLogService;
 
     /** 鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx */
     @GetMapping("/export/cityDetail")
-    public ResponseEntity<byte[]> exportCityDetail(@RequestParam("period") String period) throws Exception {
-        return buildResponse(reportService.exportCityDetail(period), "鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx");
+    public ResponseEntity<byte[]> exportCityDetail(@RequestParam("period") String period,
+                                                    @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx", period);
+        return buildResponse(reportService.exportCityDetail(period, mode), "鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx");
     }
 
     /** 鐢熸垚_璐ц繍閲忔帓鍚�.xlsx */
     @GetMapping("/export/freightRank")
-    public ResponseEntity<byte[]> exportFreightRank(@RequestParam("period") String period) throws Exception {
-        return buildResponse(reportService.exportFreightRank(period), "鐢熸垚_璐ц繍閲忔帓鍚�.xlsx");
+    public ResponseEntity<byte[]> exportFreightRank(@RequestParam("period") String period,
+                                                     @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_璐ц繍閲忔帓鍚�.xlsx", period);
+        return buildResponse(reportService.exportFreightRank(period, mode), "鐢熸垚_璐ц繍閲忔帓鍚�.xlsx");
     }
 
     /** 鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx */
     @GetMapping("/export/turnoverRank")
-    public ResponseEntity<byte[]> exportTurnoverRank(@RequestParam("period") String period) throws Exception {
-        return buildResponse(reportService.exportTurnoverRank(period), "鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx");
+    public ResponseEntity<byte[]> exportTurnoverRank(@RequestParam("period") String period,
+                                                      @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx", period);
+        return buildResponse(reportService.exportTurnoverRank(period, mode), "鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx");
+    }
+
+    /** 鐢熸垚_鍏矾鏃呭鍒嗗競宸炴槑缁�.xlsx锛坢onth: 鎶ヨ〃鏈� yyyy-MM锛泍ear: 骞翠唤 yyyy锛� */
+    @GetMapping("/export/passengerCityDetail")
+    public ResponseEntity<byte[]> exportPassengerCityDetail(@RequestParam("period") String period,
+                                                            @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏矾鏃呭鍒嗗競宸炴槑缁�.xlsx", period);
+        return buildResponse(reportService.exportPassengerCityDetail(period, mode), "鐢熸垚_鍏矾鏃呭鍒嗗競宸炴槑缁�.xlsx");
+    }
+
+
+    /** 鐢熸垚_鍏矾鏃呭涓彛寰勬槑缁�.xlsx */
+    @GetMapping("/export/passengerMidDetail")
+    public ResponseEntity<byte[]> exportPassengerMidDetail(@RequestParam("period") String period,
+                                                           @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏矾鏃呭涓彛寰勬槑缁�.xlsx", period);
+        return buildResponse(reportService.exportPassengerMidDetail(period, mode), "鐢熸垚_鍏矾鏃呭涓彛寰勬槑缁�.xlsx");
+    }
+
+    /** 鐢熸垚_鍏矾鏃呭涓彛寰勬帓鍚�.xlsx */
+    @GetMapping("/export/passengerMidRank")
+    public ResponseEntity<byte[]> exportPassengerMidRank(@RequestParam("period") String period,
+                                                         @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏矾鏃呭涓彛寰勬帓鍚�.xlsx", period);
+        return buildResponse(reportService.exportPassengerMidRank(period, mode), "鐢熸垚_鍏矾鏃呭涓彛寰勬帓鍚�.xlsx");
+    }
+
+    /** 鐢熸垚_鍏矾鏃呭涓彛寰勫垎鏋�.xlsx */
+    @GetMapping("/export/passengerMidAnalysis")
+    public ResponseEntity<byte[]> exportPassengerMidAnalysis(@RequestParam("period") String period,
+                                                             @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏矾鏃呭涓彛寰勫垎鏋�.xlsx", period);
+        return buildResponse(reportService.exportPassengerMidAnalysis(period, mode), "鐢熸垚_鍏矾鏃呭涓彛寰勫垎鏋�.xlsx");
+    }
+
+    /** 鐢熸垚_鑳借繍姹囨�昏〃.xlsx锛圚204锛屽叏鐪� + 鍚勫競宸� sheet锛� */
+    @GetMapping("/export/energySummary")
+    public ResponseEntity<byte[]> exportEnergySummary(@RequestParam("period") String period) throws Exception {
+        logExport("鐢熸垚_鑳借繍姹囨�昏〃.xlsx", period);
+        return buildResponse(reportService.exportEnergySummary(period), "鐢熸垚_鑳借繍姹囨�昏〃.xlsx");
+    }
+    /** 鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堝煄甯傚杩愮1姝ワ紝鍏氦锛� */
+    @GetMapping("/export/cityBusDetail")
+    public ResponseEntity<byte[]> exportCityBusDetail(@RequestParam("period") String period,
+                                                       @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", period);
+        return buildResponse(reportService.exportCityBusDetail(period, mode), "鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
+    }
+
+    /** 鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堝煄甯傚杩愮2姝ワ紝宸℃父鍑虹锛� */
+    @GetMapping("/export/cityTaxiDetail")
+    public ResponseEntity<byte[]> exportCityTaxiDetail(@RequestParam("period") String period,
+                                                        @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", period);
+        return buildResponse(reportService.exportCityTaxiDetail(period, mode), "鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
+    }
+
+    /** 鐢熸垚_2026骞村叏鐪侀瀹夋帓璁″垝杩涘害鎯呭喌姹囨��.xlsx锛堟姇璧勬ā鍧楄〃1锛� */
+    @GetMapping("/export/investPlan")
+    public ResponseEntity<byte[]> exportInvestPlan(@RequestParam("period") String period,
+                                                    @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls", period);
+        return buildResponse(reportService.investPlan(period, mode), "鐢熸垚_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls");
+    }
+
+    /** 鐢熸垚_"鍗佷簲浜�"瑙勫垝鐗╂祦椤圭洰杩涘睍鎯呭喌.xlsx锛堟姇璧勬ā鍧楄〃2锛� */
+    @GetMapping("/export/investFiveYearLogistics")
+    public ResponseEntity<byte[]> exportInvestFiveYearLogistics(@RequestParam("period") String period,
+                                                                 @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls", period);
+        return buildResponse(reportService.investFiveYearLogistics(period, mode), "鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls");
+    }
+
+    /** 鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls锛堟姇璧勬ā鍧楄〃3锛屾姇璧勬�昏〃+瑙勪笂椤圭洰琛ㄤ袱sheet锛� */
+    @GetMapping("/export/investBillion")
+    public ResponseEntity<byte[]> exportInvestBillion(@RequestParam("period") String period,
+                                                       @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls", period);
+        return buildResponse(reportService.investBillion(period, mode), "鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls");
+    }
+
+    /** 鐢熸垚_锛堢粡娴庡己鍘块」鐩級浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xlsx锛堟姇璧勬ā鍧楄〃4锛� */
+    @GetMapping("/export/investCounty")
+    public ResponseEntity<byte[]> exportInvestCounty(@RequestParam("period") String period,
+                                                      @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls", period);
+        return buildResponse(reportService.investCounty(period, mode), "鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls");
+    }
+
+    /** 鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堝煄甯傚杩愶紝杞ㄩ亾/杞浮鍙栬嚜鍏氦婧愭枃浠讹級 */
+    @GetMapping("/export/cityRailFerryDetail")
+    public ResponseEntity<byte[]> exportCityRailFerryDetail(@RequestParam("period") String period,
+                                                             @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", period);
+        return buildResponse(reportService.exportCityRailFerryDetail(period, mode), "鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
+    }
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx锛堝煄甯傚垎甯傚窞_妯℃澘搴曠锛� */
+    @GetMapping("/export/cityPassengerCitySum")
+    public ResponseEntity<byte[]> exportCityPassengerCitySum(@RequestParam("period") String period,
+                                                              @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx", period);
+        return buildResponse(reportService.exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競鍒嗗競宸�.xlsx", period, mode), "鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx");
+    }
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx锛堝煄甯傚杩愬悇甯傚窞鏄庣粏琛╛妯℃澘搴曠锛� */
+    @GetMapping("/export/cityPassengerDetailSum")
+    public ResponseEntity<byte[]> exportCityPassengerDetailSum(@RequestParam("period") String period,
+                                                                @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period);
+        return buildResponse(reportService.exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period, mode), "鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx");
+    }
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx锛堝煄甯傛眹鎬籣妯℃澘搴曠锛� */
+    @GetMapping("/export/cityPassengerSummary")
+    public ResponseEntity<byte[]> exportCityPassengerSummary(@RequestParam("period") String period,
+                                                              @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx", period);
+        return buildResponse(reportService.exportCityPassengerSummary(period, mode), "鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx");
+    }
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍鎶ヨ〃绯诲垪.zip锛堜竴閿墦鍖呭叏閮細鍏氦/鍑虹/杞ㄩ亾杞浮鏄庣粏 + 鍒嗗競宸炵疮璁� + 鍚勫競宸炴槑缁嗚〃 + 姹囨�伙級 */
+    @GetMapping("/export/cityPassengerSeries")
+    public ResponseEntity<byte[]> exportCityPassengerSeries(@RequestParam("period") String period,
+                                                             @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExportZip("鐢熸垚_鍩庡競瀹㈣繍鎶ヨ〃绯诲垪.zip", period);
+        String fileName = String.format("鐢熸垚_鍩庡競瀹㈣繍鎶ヨ〃绯诲垪_%s.zip", period);
+        String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
+        return ResponseEntity.ok()
+            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded)
+            .contentType(MediaType.parseMediaType("application/zip"))
+            .body(reportService.exportCityPassengerSeries(period, mode));
+    }
+
+    /** 鐢熸垚_璐ц繍鎶ヨ〃绯诲垪.zip锛堜竴閿墦鍖咃細鏄庣粏+鎺掑悕锛� */
+    @GetMapping("/export/freightSeries")
+    public ResponseEntity<byte[]> exportFreightSeries(@RequestParam("period") String period,
+                                                       @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExportZip("鐢熸垚_璐ц繍鎶ヨ〃绯诲垪.zip", period);
+        return zipResponse(reportService.exportFreightSeries(period, mode), String.format("鐢熸垚_璐ц繍鎶ヨ〃绯诲垪_%s.zip", period));
+    }
+
+    /** 鐢熸垚_鍏矾鏃呭鎶ヨ〃绯诲垪.zip锛堜竴閿墦鍖咃細鍒嗗競宸炴槑缁�+涓彛寰勬槑缁�/鎺掑悕/鍒嗘瀽锛� */
+    @GetMapping("/export/passengerSeries")
+    public ResponseEntity<byte[]> exportPassengerSeries(@RequestParam("period") String period,
+                                                        @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExportZip("鐢熸垚_鍏矾鏃呭鎶ヨ〃绯诲垪.zip", period);
+        return zipResponse(reportService.exportPassengerSeries(period, mode), String.format("鐢熸垚_鍏矾鏃呭鎶ヨ〃绯诲垪_%s.zip", period));
+    }
+
+    /** 鐢熸垚_鎶曡祫鎶ヨ〃绯诲垪.zip锛堜竴閿墦鍖咃細棰勫畨鎺�/鍗佷簲浜�/浜垮厓/缁忔祹寮哄幙锛� */
+    @GetMapping("/export/investSeries")
+    public ResponseEntity<byte[]> exportInvestSeries(@RequestParam("period") String period,
+                                                      @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExportZip("鐢熸垚_鎶曡祫鎶ヨ〃绯诲垪.zip", period);
+        return zipResponse(reportService.exportInvestSeries(period, mode), String.format("鐢熸垚_鎶曡祫鎶ヨ〃绯诲垪_%s.zip", period));
+    }
+
+    /** 鐢熸垚_鎵�閫夋姤琛�.zip锛歵ypes 閫楀彿鍒嗛殧锛堝墠绔�"鐢熸垚閫変腑"澶氶�夛級 */
+    @GetMapping("/export/selected")
+    public ResponseEntity<byte[]> exportSelected(@RequestParam("period") String period,
+                                                 @RequestParam(value = "mode", defaultValue = "month") String mode,
+                                                 @RequestParam("types") String types) throws Exception {
+        List<String> typeList = new java.util.ArrayList<>();
+        for (String t : types.split(",")) {
+            String s = t.trim();
+            if (!s.isEmpty()) typeList.add(s);
+        }
+        logExportZip("鐢熸垚_鎵�閫夋姤琛�.zip", period);
+        return zipResponse(reportService.exportSelectedSeries(period, mode, typeList),
+                String.format("鐢熸垚_鎵�閫夋姤琛╛%s.zip", period));
+    }
+
+    /** 瀵煎嚭鍓嶆暟鎹氨缁牎楠岋細杩斿洖姣忓紶鎶ヨ〃渚濊禆鐨勬暟鎹鏁�/缂哄け鎻愮ず锛坱ypes 閫楀彿鍒嗛殧锛� */
+    @GetMapping("/ready")
+    public Result<java.util.Map<String, Object>> exportReady(@RequestParam("period") String period,
+                                                              @RequestParam(value = "mode", defaultValue = "month") String mode,
+                                                              @RequestParam("types") String types) {
+        List<String> typeList = new java.util.ArrayList<>();
+        for (String t : types.split(",")) {
+            String s = t.trim();
+            if (!s.isEmpty()) typeList.add(s);
+        }
+        return Result.ok(reportService.checkExportReady(period, mode, typeList));
+    }
+
+    /** 鎶ヨ〃鏁版嵁棰勮锛堣交閲忕増锛夛細灏辩华 + 鏈堝害瑕嗙洊 + 鏍稿績鎸囨爣鎽樿 + 闂娓呭崟锛坱ypes 閫楀彿鍒嗛殧锛� */
+    @GetMapping("/preview")
+    public Result<java.util.Map<String, Object>> reportPreview(@RequestParam("period") String period,
+                                                                @RequestParam(value = "mode", defaultValue = "month") String mode,
+                                                                @RequestParam("types") String types) {
+        List<String> typeList = new java.util.ArrayList<>();
+        for (String t : types.split(",")) {
+            String s = t.trim();
+            if (!s.isEmpty()) typeList.add(s);
+        }
+        return Result.ok(reportService.reportPreview(period, mode, typeList));
+    }
+
+    private void logExport(String reportName, String period) {
+        operationLogService.record("鎶ヨ〃鐢熸垚", "瀵煎嚭鎶ヨ〃", reportName + "锛堟姤琛ㄦ湡锛�" + period + "锛�");
+    }
+
+    private void logExportZip(String zipName, String period) {
+        operationLogService.record("鎶ヨ〃鐢熸垚", "瀵煎嚭鎶ヨ〃绯诲垪", zipName + "锛堟姤琛ㄦ湡锛�" + period + "锛�");
+    }
+
+    private ResponseEntity<byte[]> zipResponse(byte[] data, String fileName) throws Exception {
+        String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
+        return ResponseEntity.ok()
+            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded)
+            .contentType(MediaType.parseMediaType("application/zip"))
+            .body(data);
     }
 
     private ResponseEntity<byte[]> buildResponse(byte[] data, String fileName) throws Exception {
         String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
+        String type = fileName.toLowerCase().endsWith(".xls")
+            ? "application/vnd.ms-excel"
+            : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
         return ResponseEntity.ok()
             .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded)
-            .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
+            .contentType(MediaType.parseMediaType(type))
             .body(data);
     }
 }
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
index 151f652..9415269 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -1,26 +1,66 @@
 package com.trafficaudit.reportexport.service;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 import com.trafficaudit.common.util.RegionUtil;
+import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly;
 import com.trafficaudit.dataimport.entity.FreightTurnoverImport;
 import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly;
+import com.trafficaudit.dataimport.entity.InvestmentMonthly;
+import com.trafficaudit.dataimport.entity.CityBusMonthly;
+import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
+import com.trafficaudit.dataimport.entity.InvestmentProject;
+import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
+import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly;
 import com.trafficaudit.dataimport.entity.ScaleSplitTransport;
+import com.trafficaudit.auditengine.entity.AuditResult;
+import com.trafficaudit.auditengine.entity.AuditRun;
+import com.trafficaudit.auditengine.mapper.AuditResultMapper;
+import com.trafficaudit.auditengine.mapper.AuditRunMapper;
+import com.trafficaudit.rulemanage.entity.AuditRule;
+import com.trafficaudit.rulemanage.mapper.AuditRuleMapper;
+import com.trafficaudit.dataimport.mapper.EnergyVehicleQuarterlyMapper;
 import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper;
 import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.InvestmentMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.InvestmentProjectMapper;
+import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.PassengerIndividualMonthlyMapper;
 import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
 import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellType;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 
 /**
  * 鎶ヨ〃鐢熸垚鏈嶅姟锛�3 绫昏緭鍑烘姤琛�
@@ -37,12 +77,480 @@
     private ScaleSplitTransportMapper scaleSplitMapper;
     @Resource
     private FreightTurnoverImportMapper freightTurnoverMapper;
+    @Resource
+    private PassengerEnterpriseMonthlyMapper passengerMapper;
+    @Resource
+    private PassengerIndividualMonthlyMapper passengerIndividualMapper;
+    @Resource
+    private EnergyVehicleQuarterlyMapper energyMapper;
+    @Resource
+    private InvestmentProjectMapper investProjectMapper;
+    @Resource
+    private InvestmentMonthlyMapper investMonthlyMapper;
+    @Resource
+    private CityBusMonthlyMapper cityBusMapper;
+    @Resource
+    private CityTaxiMonthlyMapper cityTaxiMapper;
+    @Resource
+    private AuditResultMapper auditResultMapper;
+    @Resource
+    private AuditRunMapper auditRunMapper;
+    @Resource
+    private AuditRuleMapper auditRuleMapper;
+    /** 鍩庡競瀹㈣繍妯℃澘鐩綍锛坅pplication.yml city-passenger.template-dir锛� */
+    @Value("${city-passenger.template-dir:docs/鍩庡競瀹㈣繍}")
+    private String templateDir;
+    /** 鑳借�楁眹鎬绘ā鏉跨洰褰曪紙application.yml energy.template-dir锛� */
+    @Value("${energy.template-dir:docs/鍏矾鏃呭+鑳借�梷")
+    private String energyTemplateDir;
+    /** 鍏矾鏃呭杈撳嚭妯℃澘鐩綍锛坅pplication.yml passenger.template-dir锛� */
+    @Value("${passenger.template-dir:docs/鍏矾鏃呭+鑳借��/杈撳嚭}")
+    private String passengerTemplateDir;
+
+    /** 璐ц繍鎶ヨ〃妯℃澘鐩綍锛坅pplication.yml freight.template-dir锛� */
+    @Value("${freight.template-dir:docs/璐ц繍}")
+    private String freightTemplateDir;
+
+    /** 鎶曡祫鎶ヨ〃妯℃澘鐩綍锛坅pplication.yml investment.template-dir锛� */
+    @Value("${investment.template-dir:docs/鎶曡祫/妯℃澘}")
+    private String investTemplateDir;
+
+    // ==================== 瀵煎嚭鍓嶆暟鎹氨缁牎楠岋紙P0锛氶槻姝㈠鍑虹┖琛級 ====================
+
+    /** 杩斿洖姣忓紶鎶ヨ〃渚濊禆鐨勬暟鎹鏁颁笌缂哄け鎻愮ず */
+    public Map<String, Object> checkExportReady(String period, String mode, List<String> types) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("period", period);
+        result.put("mode", mode);
+        List<Map<String, Object>> items = new ArrayList<>();
+        for (String t : types) {
+            Map<String, Object> it = new LinkedHashMap<>();
+            it.put("key", t);
+            List<String> notes = new ArrayList<>();
+            int rows;
+            List<String> auditTypes = new ArrayList<>();
+            switch (t) {
+                case "cityDetail":
+                case "freightRank":
+                case "turnoverRank":
+                    rows = freightReadyRows(period, mode, notes);
+                    auditTypes.add("H2032");
+                    break;
+                case "passengerCityDetail":
+                case "passengerMidDetail":
+                case "passengerMidRank":
+                case "passengerMidAnalysis":
+                    rows = passengerReadyRows(period, mode, notes);
+                    auditTypes.add("H2031");
+                    break;
+                case "cityBusDetail":
+                case "cityRailFerryDetail":
+                    rows = addReadyCount(() -> cityBusMapper.selectCount(periodQw(period, mode, CityBusMonthly::getReportPeriod)), "鍩庡競鍏氦鏈堟姤", notes);
+                    auditTypes.add("CITY_BUS");
+                    break;
+                case "cityTaxiDetail":
+                    rows = addReadyCount(() -> cityTaxiMapper.selectCount(periodQw(period, mode, CityTaxiMonthly::getReportPeriod)), "宸℃父鍑虹鏈堟姤", notes);
+                    auditTypes.add("CITY_TAXI");
+                    break;
+                case "cityPassengerCitySum":
+                case "cityPassengerDetailSum":
+                case "cityPassengerSummary":
+                    rows = addReadyCount(() -> cityBusMapper.selectCount(periodQw(period, mode, CityBusMonthly::getReportPeriod)), "鍩庡競鍏氦鏈堟姤", notes)
+                         + addReadyCount(() -> cityTaxiMapper.selectCount(periodQw(period, mode, CityTaxiMonthly::getReportPeriod)), "宸℃父鍑虹鏈堟姤", notes);
+                    auditTypes.add("CITY_BUS");
+                    auditTypes.add("CITY_TAXI");
+                    break;
+                case "investPlan":
+                case "investFiveYearLogistics":
+                case "investBillion":
+                case "investCounty":
+                    rows = addReadyCount(() -> investMonthlyMapper.selectCount(periodQw(period, mode, InvestmentMonthly::getReportPeriod)), "鎶曡祫甯傚窞鍗曡〃/姹囨�绘暟鎹�", notes);
+                    auditTypes.add("INVEST");
+                    break;
+                case "energySummary":
+                    rows = addReadyCount(() -> energyMapper.selectCount(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod)), "鑳借�楄溅杈嗗搴︽暟鎹�", notes);
+                    auditTypes.add("H204");
+                    break;
+                default:
+                    rows = 0;
+                    notes.add("鏈煡鎶ヨ〃绫诲瀷锛�" + t);
+            }
+            int auditRows = 0;
+            boolean audited = false;
+            for (String at : auditTypes) {
+                auditRows += auditRowsOf(period, mode, at);
+                audited = audited || auditedMarked(period, mode, at);
+            }
+            it.put("rows", rows);
+            it.put("ready", rows > 0);
+            it.put("auditRows", auditRows);
+            it.put("audited", audited);
+            it.put("note", String.join("锛�", notes));
+            items.add(it);
+        }
+        result.put("items", items);
+        return result;
+    }
+
+    // ==================== 鎶ヨ〃鏁版嵁棰勮锛堣交閲忕増锛夛細灏辩华 + 鏈堝害瑕嗙洊 + 鏍稿績鎽樿 + 闂娓呭崟 ====================
+
+    /** 鎶ヨ〃鏁版嵁棰勮锛氬湪瀵煎嚭鍓嶅睍绀烘瘡寮犺〃鐨勫氨缁姸鎬併�佹湀搴﹁鐩栥�佹牳蹇冩寚鏍囨憳瑕佷笌闂娓呭崟 */
+    public Map<String, Object> reportPreview(String period, String mode, List<String> types) {
+        Map<String, Object> ready = checkExportReady(period, mode, types);
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> items = (List<Map<String, Object>>) ready.get("items");
+        Map<String, Map<String, Object>> byKey = new LinkedHashMap<>();
+        for (Map<String, Object> it : items) byKey.put((String) it.get("key"), it);
+
+        String year = period != null && period.length() >= 4 ? period.substring(0, 4) : "";
+        String yearPrefix = year + "-";
+        int month = monthOf(period, mode);
+        int yearNum = 0;
+        try { yearNum = Integer.parseInt(year); } catch (Exception ignore) {}
+
+        boolean freight = false, passenger = false, cityBus = false, cityTaxi = false, invest = false, energy = false;
+        for (String t : types) {
+            if (isFreightType(t)) freight = true;
+            else if (isPassengerType(t)) passenger = true;
+            else if ("cityTaxiDetail".equals(t) || isCityPassengerType(t)) { cityTaxi = true; cityBus = true; }
+            else if (isInvestType(t)) invest = true;
+            else if ("energySummary".equals(t)) energy = true;
+        }
+
+        // 鏈堝害瑕嗙洊锛氳骞� 1..month 涓摢浜涙湀鏈夋簮鏁版嵁
+        Set<Integer> freightCover = new TreeSet<>();
+        Set<Integer> passengerCover = new TreeSet<>();
+        Set<Integer> busCover = new TreeSet<>();
+        Set<Integer> taxiCover = new TreeSet<>();
+        Set<Integer> investCover = new TreeSet<>();
+        if (freight) {
+            FreightTurnoverImport ftProv = loadFreightTurnover(period).get("婀栧寳鐪�");
+            for (int m = 1; m <= month; m++) {
+                if (ftProv != null && freightMonth(ftProv, m) != null) freightCover.add(m);
+            }
+        }
+        if (passenger) {
+            passengerCover.addAll(monthsWithData(() -> passengerMapper.selectList(new LambdaQueryWrapper<PassengerEnterpriseMonthly>().likeRight(PassengerEnterpriseMonthly::getReportPeriod, yearPrefix)),
+                o -> ((PassengerEnterpriseMonthly) o).getReportPeriod(), month));
+            passengerCover.addAll(monthsWithData(() -> passengerIndividualMapper.selectList(new LambdaQueryWrapper<PassengerIndividualMonthly>().likeRight(PassengerIndividualMonthly::getReportPeriod, yearPrefix)),
+                o -> ((PassengerIndividualMonthly) o).getReportPeriod(), month));
+        }
+        if (cityBus) {
+            busCover.addAll(monthsWithData(() -> cityBusMapper.selectList(new LambdaQueryWrapper<CityBusMonthly>().likeRight(CityBusMonthly::getReportPeriod, yearPrefix)),
+                o -> ((CityBusMonthly) o).getReportPeriod(), month));
+        }
+        if (cityTaxi) {
+            taxiCover.addAll(monthsWithData(() -> cityTaxiMapper.selectList(new LambdaQueryWrapper<CityTaxiMonthly>().likeRight(CityTaxiMonthly::getReportPeriod, yearPrefix)),
+                o -> ((CityTaxiMonthly) o).getReportPeriod(), month));
+        }
+        if (invest) {
+            investCover.addAll(monthsWithData(() -> investMonthlyMapper.selectList(new LambdaQueryWrapper<InvestmentMonthly>().likeRight(InvestmentMonthly::getReportPeriod, yearPrefix)),
+                o -> ((InvestmentMonthly) o).getReportPeriod(), month));
+        }
+
+        for (Map.Entry<String, Map<String, Object>> e : byKey.entrySet()) {
+            String t = e.getKey();
+            Map<String, Object> it = e.getValue();
+            List<Map<String, Object>> summary = new ArrayList<>();
+            List<String> problems = new ArrayList<>();
+            List<Integer> missing = new ArrayList<>();
+            List<Integer> coverage = new ArrayList<>();
+            if (Boolean.FALSE.equals(it.get("audited"))) problems.add("璇ユ姤琛ㄦ湡灏氭湭瀹℃牳閫氳繃");
+            if (isFreightType(t)) {
+                coverage.addAll(freightCover);
+                missing = missingMonths(freightCover, month);
+                freightPreview(period, month, summary, problems);
+            } else if (isPassengerType(t)) {
+                coverage.addAll(passengerCover);
+                missing = missingMonths(passengerCover, month);
+                passengerPreview(yearNum, month, t, summary, problems);
+            } else if ("cityTaxiDetail".equals(t)) {
+                coverage.addAll(taxiCover);
+                missing = missingMonths(taxiCover, month);
+                cityPassengerPreview(yearPrefix, month, t, summary, problems);
+            } else if (isCityPassengerType(t)) {
+                if ("cityBusDetail".equals(t) || "cityRailFerryDetail".equals(t)) {
+                    // 鍏氦/杞ㄩ亾杞浮鏄庣粏鍙緷璧栧叕浜ゆ湀鎶ヨ〃锛堣建閬�/杞浮鏁版嵁鍦ㄥ叕浜よ〃鍐咃級
+                    coverage.addAll(busCover);
+                    missing = missingMonths(busCover, month);
+                } else {
+                    Set<Integer> cov = new TreeSet<>();
+                    cov.addAll(busCover);
+                    cov.addAll(taxiCover);
+                    coverage.addAll(cov);
+                    missing = missingMonths(cov, month);
+                }
+                cityPassengerPreview(yearPrefix, month, t, summary, problems);
+            } else if (isInvestType(t)) {
+                coverage.addAll(investCover);
+                // 鎶曡祫涓哄綋鏈堟姤琛紙鏃� 1..N 绱鍙e緞锛夛紝鍙彁绀哄綋鏈堢己澶�
+                if (!investCover.contains(month)) missing.add(month);
+                investPreview(period, mode, summary, problems);
+            } else if ("energySummary".equals(t)) {
+                Long ec = energyMapper.selectCount(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod));
+                if (ec != null && ec > 0) coverage.add(month);
+                energyPreview(period, mode, summary, problems);
+            }
+            if (!missing.isEmpty()) {
+                problems.add("缂烘湀鏁版嵁锛�" + joinMonths(missing) + "锛�1~" + month + "鏈堢疮璁¢渶閫愭湀榻愬叏锛�");
+            }
+            it.put("monthCoverage", coverage);
+            it.put("missingMonths", missing);
+            it.put("summary", summary);
+            it.put("problems", problems);
+        }
+        return ready;
+    }
+
+    private boolean isFreightType(String t) {
+        return "cityDetail".equals(t) || "freightRank".equals(t) || "turnoverRank".equals(t);
+    }
+
+    private boolean isPassengerType(String t) {
+        return "passengerCityDetail".equals(t) || "passengerMidDetail".equals(t) || "passengerMidRank".equals(t) || "passengerMidAnalysis".equals(t);
+    }
+
+    private boolean isCityPassengerType(String t) {
+        return "cityBusDetail".equals(t) || "cityRailFerryDetail".equals(t) || "cityPassengerCitySum".equals(t)
+            || "cityPassengerDetailSum".equals(t) || "cityPassengerSummary".equals(t);
+    }
+
+    private boolean isInvestType(String t) {
+        return "investPlan".equals(t) || "investFiveYearLogistics".equals(t) || "investBillion".equals(t) || "investCounty".equals(t);
+    }
+
+    private boolean isEnergyType(String t) {
+        return "energySummary".equals(t);
+    }
+
+    /** 鎸� report_period like 'yyyy-' 缁熻 1..limit 鏈堜腑鏈夋暟鎹殑鏈堜唤锛堝崌搴忥級 */
+    private Set<Integer> monthsWithData(java.util.function.Supplier<java.util.List<?>> rows, java.util.function.Function<Object, String> periodOf, int limit) {
+        Set<Integer> set = new TreeSet<>();
+        for (Object o : rows.get()) {
+            String rp = periodOf.apply(o);
+            if (rp == null) continue;
+            String[] parts = rp.split("-");
+            if (parts.length < 2) continue;
+            try {
+                int m = Integer.parseInt(parts[1]);
+                if (m >= 1 && m <= limit) set.add(m);
+            } catch (Exception ignore) {}
+        }
+        return set;
+    }
+
+    /** 1..limit 涓己澶辩殑鏈堜唤 */
+    private List<Integer> missingMonths(Set<Integer> coverage, int limit) {
+        List<Integer> missing = new ArrayList<>();
+        for (int m = 1; m <= limit; m++) if (!coverage.contains(m)) missing.add(m);
+        return missing;
+    }
+
+    private String joinMonths(List<Integer> months) {
+        StringBuilder sb = new StringBuilder();
+        for (int m : months) {
+            if (sb.length() > 0) sb.append("銆�");
+            sb.append(m).append("鏈�");
+        }
+        return sb.toString();
+    }
+
+    private void addSum(List<Map<String, Object>> summary, String label, Double value, String unit) {
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("label", label);
+        m.put("value", value);
+        m.put("unit", unit);
+        summary.add(m);
+    }
+
+    /** 璐ц繍鎽樿锛氬叏鐪佺疮璁¤揣杩愰噺锛堝悎璁�/瑙勪笂/瑙勪笅锛�+ 鍛ㄨ浆閲忥紙鎷嗗垎琛ㄥ叏鐪佺疮璁¤锛� */
+    private void freightPreview(String period, int month, List<Map<String, Object>> summary, List<String> problems) {
+        Map<String, FreightTurnoverImport> ft = loadFreightTurnover(period);
+        Map<String, Double> h2032Cum = loadH2032FreightCumulative(period);
+        ScaleSplitTransport provCum = getProvinceCumulative(period);
+        Double total = freightCum(ft.get("婀栧寳鐪�"), month);
+        Double totalWan = total == null ? null : round(total / 10000.0, 4);
+        double above = 0.0;
+        for (Double v : h2032Cum.values()) if (v != null) above += v;
+        double aboveWan = round(above / 10000.0, 4);
+        Double belowWan = totalWan == null ? null : round(totalWan - aboveWan, 4);
+        addSum(summary, "鍏ㄧ渷绱璐ц繍閲忥紙鍚堣锛�", totalWan, "涓囧惃");
+        addSum(summary, "鍏朵腑锛氳涓�", aboveWan, "涓囧惃");
+        addSum(summary, "鍏朵腑锛氳涓�", belowWan, "涓囧惃");
+        if (provCum != null) {
+            addSum(summary, "鍏ㄧ渷绱鍛ㄨ浆閲忥紙鍚堣锛�", provCum.getTotalTurnover(), "涓囧惃鍏噷");
+            addSum(summary, "鍏朵腑锛氳涓�", provCum.getAboveScaleTurnover(), "涓囧惃鍏噷");
+            addSum(summary, "鍏朵腑锛氳涓�", provCum.getBelowScaleTurnover(), "涓囧惃鍏噷");
+        } else {
+            problems.add("鎷嗗垎琛ㄦ棤鍏ㄧ渷绱琛岋紙鍛ㄨ浆閲�/瑙勪笂瑙勪笅缂猴級");
+        }
+        if (totalWan == null) problems.add("妯℃澘_璐ц繍閲忓懆杞噺鏃犲叏鐪佺疮璁★紝绱璐ц繍閲忎负绌�");
+    }
+
+    /** 鏃呭鎽樿锛氫紒涓�+涓綋锛堝垎甯傚窞锛夋垨涓彛寰勪紒涓氾紙涓彛寰� 3 琛級 */
+    private void passengerPreview(int yearNum, int month, String type, List<Map<String, Object>> summary, List<String> problems) {
+        Map<Integer, Map<String, PassengerAgg>> data = loadPassengerAggMap();
+        Map<Integer, Map<String, double[]>> indi = loadPassengerIndividualMap();
+        double entPax = 0.0, entTurn = 0.0, indiPax = 0.0, indiTurn = 0.0;
+        boolean anyEnt = false, anyIndi = false;
+        for (int m = 1; m <= month; m++) {
+            PassengerAgg agg = aggOf(data, yearNum, m, "婀栧寳鐪�");
+            if (agg != null) {
+                entPax += agg.passengerTotal / 10000.0;
+                entTurn += agg.turnoverTotal / 10000.0;
+                anyEnt = true;
+            }
+            Map<String, double[]> mm = indi.get(yearNum * 100 + m);
+            if (mm != null) {
+                double[] pv = mm.get("婀栧寳鐪�");
+                if (pv != null) {
+                    indiPax += pv[0];
+                    indiTurn += pv[1];
+                    anyIndi = true;
+                }
+            }
+        }
+        if ("passengerCityDetail".equals(type)) {
+            addSum(summary, "浼佷笟瀹㈣繍閲忕疮璁�", round(entPax, 4), "涓囦汉娆�");
+            addSum(summary, "涓綋瀹㈣繍閲忕疮璁�", round(indiPax, 4), "涓囦汉娆�");
+            addSum(summary, "鍚堣瀹㈣繍閲忕疮璁�", round(entPax + indiPax, 4), "涓囦汉娆�");
+            addSum(summary, "鍚堣鍛ㄨ浆閲忕疮璁�", round(entTurn + indiTurn, 4), "涓囦汉鍏噷");
+            if (!anyEnt && !anyIndi) problems.add("H203-1 鏃呭鏈堟姤涓庝釜浣撴暟鎹潎鏃�");
+        } else {
+            addSum(summary, "涓彛寰勫杩愰噺绱", round(entPax, 4), "涓囦汉娆�");
+            addSum(summary, "涓彛寰勫懆杞噺绱", round(entTurn, 4), "涓囦汉鍏噷");
+            if (!anyEnt) problems.add("H203-1 鏃呭鏈堟姤鏃犱紒涓氭暟鎹�");
+        }
+    }
+
+    /** 鍩庡競瀹㈣繍鎽樿锛氬叕浜�/鍑虹/杞ㄩ亾/杞浮鍏ㄧ渷绱锛堟寚鏍� 0..7锛� */
+    private void cityPassengerPreview(String yearPrefix, int month, String type, List<Map<String, Object>> summary, List<String> problems) {
+        double[] prov = loadCityPassengerCumulative(yearPrefix, month).getOrDefault("鍏ㄧ渷", new double[8]);
+        boolean allZero = true;
+        for (double v : prov) if (v != 0.0) allZero = false;
+        switch (type) {
+            case "cityTaxiDetail":
+                addSum(summary, "鍑虹瀹㈣繍閲忕疮璁�", round(prov[2], 4), "涓囦汉娆�");
+                addSum(summary, "鍑虹鍛ㄨ浆閲忕疮璁�", round(prov[3], 4), "涓囦汉鍏噷");
+                break;
+            case "cityRailFerryDetail":
+                addSum(summary, "杞ㄩ亾瀹㈣繍閲忕疮璁�", round(prov[4], 4), "涓囦汉娆�");
+                addSum(summary, "杞ㄩ亾鍛ㄨ浆閲忕疮璁�", round(prov[5], 4), "涓囦汉鍏噷");
+                addSum(summary, "杞浮瀹㈣繍閲忕疮璁�", round(prov[6], 4), "涓囦汉娆�");
+                addSum(summary, "杞浮鍛ㄨ浆閲忕疮璁�", round(prov[7], 4), "涓囦汉鍏噷");
+                break;
+            case "cityBusDetail":
+                addSum(summary, "鍏氦瀹㈣繍閲忕疮璁�", round(prov[0], 4), "涓囦汉娆�");
+                addSum(summary, "鍏氦鍛ㄨ浆閲忕疮璁�", round(prov[1], 4), "涓囦汉鍏噷");
+                addSum(summary, "杞ㄩ亾瀹㈣繍閲忕疮璁�", round(prov[4], 4), "涓囦汉娆�");
+                addSum(summary, "杞ㄩ亾鍛ㄨ浆閲忕疮璁�", round(prov[5], 4), "涓囦汉鍏噷");
+                addSum(summary, "杞浮瀹㈣繍閲忕疮璁�", round(prov[6], 4), "涓囦汉娆�");
+                addSum(summary, "杞浮鍛ㄨ浆閲忕疮璁�", round(prov[7], 4), "涓囦汉鍏噷");
+                break;
+            default:
+                addSum(summary, "鍏氦瀹㈣繍閲忕疮璁�", round(prov[0], 4), "涓囦汉娆�");
+                addSum(summary, "鍏氦鍛ㄨ浆閲忕疮璁�", round(prov[1], 4), "涓囦汉鍏噷");
+                addSum(summary, "鍑虹瀹㈣繍閲忕疮璁�", round(prov[2], 4), "涓囦汉娆�");
+                addSum(summary, "鍑虹鍛ㄨ浆閲忕疮璁�", round(prov[3], 4), "涓囦汉鍏噷");
+                addSum(summary, "杞ㄩ亾瀹㈣繍閲忕疮璁�", round(prov[4], 4), "涓囦汉娆�");
+                addSum(summary, "杞ㄩ亾鍛ㄨ浆閲忕疮璁�", round(prov[5], 4), "涓囦汉鍏噷");
+                addSum(summary, "杞浮瀹㈣繍閲忕疮璁�", round(prov[6], 4), "涓囦汉娆�");
+                addSum(summary, "杞浮鍛ㄨ浆閲忕疮璁�", round(prov[7], 4), "涓囦汉鍏噷");
+        }
+        if (allZero) problems.add("鍏ㄧ渷绱鍊煎叏涓� 0锛堝叕浜�/鍑虹鏈堟姤鍙兘鏈鍏ワ級");
+    }
+
+    /** 鎶曡祫鎽樿锛氶」鐩暟 + 鏈湀瀹屾垚/鑷勾鍒�/鑷紑濮嬬疮璁� */
+    private void investPreview(String period, String mode, List<Map<String, Object>> summary, List<String> problems) {
+        List<InvestmentMonthly> list = investMonthlyMapper.selectList(periodQw(period, mode, InvestmentMonthly::getReportPeriod));
+        Set<Long> proj = new HashSet<>();
+        double monthDone = 0.0, yearCum = 0.0, startCum = 0.0;
+        for (InvestmentMonthly m : list) {
+            if (m.getProjectId() != null) proj.add(m.getProjectId());
+            monthDone += nz(m.getMonthDone());
+            yearCum += nz(m.getYearCum());
+            startCum += nz(m.getStartCum());
+        }
+        addSum(summary, "鏈夋湀搴﹁褰曢」鐩暟", (double) proj.size(), "涓�");
+        addSum(summary, "鏈湀瀹屾垚鎶曡祫鍚堣", round(monthDone, 2), "涓囧厓");
+        addSum(summary, "鑷勾鍒濈疮璁″畬鎴�", round(yearCum, 2), "涓囧厓");
+        addSum(summary, "鑷紑濮嬬疮璁″畬鎴�", round(startCum, 2), "涓囧厓");
+        if (list.isEmpty()) problems.add("鏃犳姇璧勬湀搴︽暟鎹�");
+    }
+
+    /** 鑳借�楁憳瑕侊細杞﹁締璁板綍鏁� + 鐕冩补娑堣�楀悎璁★紙瀛e害琛紝鎸夋墍閫夊搴︽湯鏈堬級 */
+    private void energyPreview(String period, String mode, List<Map<String, Object>> summary, List<String> problems) {
+        Long cnt = energyMapper.selectCount(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod));
+        int rows = cnt == null ? 0 : cnt.intValue();
+        double fuel = 0.0;
+        if (rows > 0) {
+            for (EnergyVehicleQuarterly e : energyMapper.selectList(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod))) {
+                fuel += nz(e.getFuelConsumption());
+            }
+        }
+        addSum(summary, "杞﹁締璁板綍鏁�", (double) rows, "鏉�");
+        addSum(summary, "鐕冩补娑堣�楀悎璁�", round(fuel, 2), "鈥�");
+        if (rows == 0) problems.add("璇ュ搴︽棤鑳借�楄溅杈嗘暟鎹�");
+    }
+
+    private int freightReadyRows(String period, String mode, List<String> notes) {
+        return addReadyCount(() -> freightTurnoverMapper.selectCount(periodQw(period, mode, FreightTurnoverImport::getReportPeriod)), "妯℃澘_璐ц繍閲忓懆杞噺", notes)
+             + addReadyCount(() -> scaleSplitMapper.selectCount(periodQw(period, mode, ScaleSplitTransport::getReportPeriod)), "瑙勪笂瑙勪笅鎷嗗垎琛�", notes)
+             + addReadyCount(() -> h2032Mapper.selectCount(periodQw(period, mode, H2032EnterpriseMonthly::getReportPeriod)), "H203-2 璐ц繍鏈堟姤", notes);
+    }
+
+    private int passengerReadyRows(String period, String mode, List<String> notes) {
+        return addReadyCount(() -> passengerMapper.selectCount(periodQw(period, mode, PassengerEnterpriseMonthly::getReportPeriod)), "H203-1 鏃呭鏈堟姤", notes)
+             + addReadyCount(() -> passengerIndividualMapper.selectCount(periodQw(period, mode, PassengerIndividualMonthly::getReportPeriod)), "涓綋瀹㈣繍閲�/鍛ㄨ浆閲�", notes);
+    }
+
+    private <T> LambdaQueryWrapper<T> periodQw(String period, String mode, SFunction<T, ?> periodGetter) {
+        LambdaQueryWrapper<T> qw = new LambdaQueryWrapper<>();
+        if ("year".equals(mode)) {
+            qw.likeRight(periodGetter, period);
+        } else {
+            qw.eq(periodGetter, period);
+        }
+        return qw;
+    }
+
+    private int addReadyCount(java.util.function.Supplier<Long> counter, String label, List<String> notes) {
+        Long cnt = counter.get();
+        if (cnt == null || cnt <= 0) notes.add("缂烘暟鎹細" + label);
+        return cnt == null ? 0 : cnt.intValue();
+    }
+
+    /** 璇ユ姤琛ㄦ湡璇ュ鏍哥被鍨嬪凡浜х敓鐨勫鏍歌褰曟暟锛堝厛瀹℃牳鍐嶅嚭琛ㄧ殑鍓嶇疆妫�鏌ワ級 */
+    private int auditRowsOf(String period, String mode, String ruleType) {
+        List<AuditRule> rules = auditRuleMapper.selectList(new LambdaQueryWrapper<AuditRule>()
+            .eq(AuditRule::getReportType, ruleType)
+            .eq(AuditRule::getIsEnabled, 1));
+        if (rules.isEmpty()) return 0;
+        List<Long> ruleIds = new ArrayList<>();
+        for (AuditRule r : rules) ruleIds.add(r.getId());
+        LambdaQueryWrapper<AuditResult> qw = new LambdaQueryWrapper<>();
+        if ("year".equals(mode)) qw.likeRight(AuditResult::getReportPeriod, period);
+        else qw.eq(AuditResult::getReportPeriod, period);
+        Long cnt = auditResultMapper.selectCount(qw.in(AuditResult::getRuleId, ruleIds));
+        return cnt == null ? 0 : cnt.intValue();
+    }
+
+    /** 璇ユ姤琛ㄦ湡璇ュ鏍哥被鍨嬫槸鍚﹀凡鏈夈�屽鏍搁�氳繃鏍囪銆嶏紙audit_run 琛紱绾夸笅瀹℃牳閫氳繃/绯荤粺瀹℃牳鎵ц鍚庡啓鍏ワ紝瀵煎嚭鍓嶄笉鍐嶆彁绀烘湭瀹℃牳锛� */
+    private boolean auditedMarked(String period, String mode, String ruleType) {
+        LambdaQueryWrapper<AuditRun> qw = new LambdaQueryWrapper<AuditRun>()
+            .eq(AuditRun::getReportType, ruleType);
+        if ("year".equals(mode)) qw.likeRight(AuditRun::getReportPeriod, period);
+        else qw.eq(AuditRun::getReportPeriod, period);
+        Long cnt = auditRunMapper.selectCount(qw);
+        return cnt != null && cnt > 0;
+    }
 
     // ==================== 1. 鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx ====================
 
-    public byte[] exportCityDetail(String period) throws Exception {
-        int currentMonth = parseMonth(period);
-        String year = period.substring(0, 4);
+    public byte[] exportCityDetail(String period, String mode) throws Exception {
+        int currentMonth = monthOf(period, mode);
+        int fillMonths = currentMonth; // 1..N 鏈堬紙N>6 鏃舵ā鏉胯嚜鍔ㄥ悜鍙虫墿鍒楀埌 12 鏈堬級
+        boolean extended = fillMonths > 6;
         Map<String, Map<Integer, ScaleSplitTransport>> monthData = loadMonthData(period);
         Map<Integer, ScaleSplitTransport> provinceMonthMap = loadProvinceMonthMap(period);
         ScaleSplitTransport provinceCum = getProvinceCumulative(period);
@@ -51,85 +559,67 @@
         Map<String, Double> h2032FreightCum = loadH2032FreightCumulative(period);
         Map<String, FreightTurnoverImport> ftMap = loadFreightTurnover(period);
 
-        XSSFWorkbook wb = new XSSFWorkbook();
-        Sheet sheet = wb.createSheet("璐ц繍閲忓垎甯傚窞鏄庣粏");
-
-        Row r1 = sheet.createRow(0);
-        r1.createCell(0).setCellValue("鎶ヨ〃鏈燂細" + year + "骞�" + currentMonth + "鏈�");
-        sheet.createRow(1);
-        Row header = sheet.createRow(2);
-        header.createCell(0).setCellValue("鍦板尯 鍚嶇О");
-        header.createCell(1).setCellValue("鎸囨爣");
-        CellStyle dateStyle = wb.createCellStyle();
-        dateStyle.setDataFormat(wb.getCreationHelper().createDataFormat().getFormat("yyyy-mm-dd"));
-        for (int m = 1; m <= currentMonth; m++) {
-            Cell c = header.createCell(2 + (m - 1) * 2);
-            c.setCellValue(java.sql.Date.valueOf(year + "-" + pad(m) + "-01"));
-            c.setCellStyle(dateStyle);
-            header.createCell(3 + (m - 1) * 2).setCellValue(m + "鏈堜笌鍘诲勾鍚屾瘮");
-        }
-        int cumCol = 2 + currentMonth * 2;
-        header.createCell(cumCol).setCellValue("绱鍛ㄨ浆閲�");
-        header.createCell(cumCol + 1).setCellValue("鍚屾瘮");
-        sheet.createRow(3);
-
-        int rowIdx = 4;
-        String[][] provinceMetrics = {
-            {"璐ц繍閲�         锛堜竾鍚級", "freight", "total"},
-            {"璐х墿鍛ㄨ浆閲�     锛堜竾鍚ㄥ叕閲岋級", "turnover", "total"},
-            {"鍏朵腑瑙勪笂璐ц繍閲�   (涓囧惃)", "freight", "above"},
-            {"鍏朵腑瑙勪笂璐х墿鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "above"},
-            {"鍏朵腑瑙勪笅璐ц繍閲�   (涓囧惃)", "freight", "below"},
-            {"鍏朵腑瑙勪笅璐х墿鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "below"}
-        };
-        FreightTurnoverImport ftProvince = ftMap.get("婀栧寳鐪�");
-        for (String[] metric : provinceMetrics) {
-            Row row = sheet.createRow(rowIdx++);
-            row.createCell(0).setCellValue("鍏ㄧ渷");
-            row.createCell(1).setCellValue(metric[0]);
-            for (int m = 1; m <= currentMonth; m++) {
-                setNumeric(row, 2 + (m - 1) * 2, getProvinceMonthValue(provinceMonthMap, ftProvince, m, metric[1], metric[2], h2032Freight));
-                setNumeric(row, 3 + (m - 1) * 2, getProvinceMonthYoy(provinceMonthMap, ftProvince, m, metric[1], metric[2]));
-            }
-            setNumeric(row, cumCol, getProvinceCumValue(provinceCum, ftProvince, metric[1], metric[2], h2032FreightCum, currentMonth));
-            setNumeric(row, cumCol + 1, getProvinceCumYoy(provinceCum, ftProvince, metric[1], metric[2], currentMonth));
-        }
-
-        for (String city : RegionUtil.cityList()) {
-            String[][] metrics = {
-                {"瑙勪笂+瑙勪笅璐ц繍閲�(涓囧惃)", "freight", "total"},
-                {"瑙勪笂璐ц繍閲�      (涓囧惃)", "freight", "above"},
-                {"瑙勪笅璐ц繍閲�      (涓囧惃)", "freight", "below"},
-                {"瑙勪笂+瑙勪笅鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "total"},
-                {"瑙勪笂璐х墿鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "above"},
-                {"瑙勪笅璐х墿鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "below"}
+        // 浠� docs/璐ц繍/鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx 涓哄簳绋匡細淇濈暀鏍囬/琛ㄥご/鍚堝苟/鍒楀/鏍峰紡锛屼粎鏇挎崲鏁版嵁鍖�
+        File template = resolveFreightTemplate("鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            if (extended) extendMonthlyColumns(sheet, 2, Integer.parseInt(period.substring(0, 4)), 4, sheet.getLastRowNum());
+            String[][] provinceMetrics = {
+                {"璐ц繍閲�         锛堜竾鍚級", "freight", "total"},
+                {"璐х墿鍛ㄨ浆閲�     锛堜竾鍚ㄥ叕閲岋級", "turnover", "total"},
+                {"鍏朵腑瑙勪笂璐ц繍閲�   (涓囧惃)", "freight", "above"},
+                {"鍏朵腑瑙勪笂璐х墿鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "above"},
+                {"鍏朵腑瑙勪笅璐ц繍閲�   (涓囧惃)", "freight", "below"},
+                {"鍏朵腑瑙勪笅璐х墿鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "below"}
             };
-            FreightTurnoverImport ftCity = ftMap.get(city);
-            ScaleSplitTransport cum = cumMap.get(city);
-            for (String[] metric : metrics) {
-                Row row = sheet.createRow(rowIdx++);
-                row.createCell(0).setCellValue(RegionUtil.shortName(city));
-                row.createCell(1).setCellValue(metric[0]);
-                for (int m = 1; m <= currentMonth; m++) {
-                    setNumeric(row, 2 + (m - 1) * 2, getCityMonthValue(monthData, ftCity, city, m, metric[1], metric[2], h2032Freight));
-                    setNumeric(row, 3 + (m - 1) * 2, getCityMonthYoy(monthData, ftCity, city, m, metric[1], metric[2]));
-                }
-                setNumeric(row, cumCol, getCityCumValue(cum, ftCity, metric[1], metric[2], h2032FreightCum, city, currentMonth));
-                setNumeric(row, cumCol + 1, getCityCumYoy(cum, ftCity, metric[1], metric[2], currentMonth));
+            FreightTurnoverImport ftProvince = ftMap.get("婀栧寳鐪�");
+            int rowIdx = 4; // 0-based锛氱5琛岃捣涓� 鍏ㄧ渷 6 鎸囨爣
+            for (String[] metric : provinceMetrics) {
+                Row row = sheet.getRow(rowIdx);
+                if (row == null) row = sheet.createRow(rowIdx);
+                fillFreightDetailRow(row, fillMonths, extended ? 26 : 14,
+                    m -> getProvinceMonthValue(provinceMonthMap, ftProvince, m, metric[1], metric[2], h2032Freight),
+                    m -> getProvinceMonthYoy(provinceMonthMap, ftProvince, m, metric[1], metric[2]),
+                    getProvinceCumValue(provinceCum, ftProvince, metric[1], metric[2], h2032FreightCum, currentMonth),
+                    getProvinceCumYoy(provinceCum, ftProvince, metric[1], metric[2], currentMonth));
+                rowIdx++;
             }
+            for (String city : RegionUtil.cityList()) {
+                String[][] metrics = {
+                    {"瑙勪笂+瑙勪笅璐ц繍閲�(涓囧惃)", "freight", "total"},
+                    {"瑙勪笂璐ц繍閲�      (涓囧惃)", "freight", "above"},
+                    {"瑙勪笅璐ц繍閲�      (涓囧惃)", "freight", "below"},
+                    {"瑙勪笂+瑙勪笅鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "total"},
+                    {"瑙勪笂璐х墿鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "above"},
+                    {"瑙勪笅璐х墿鍛ㄨ浆閲�  (涓囧惃鍏噷)", "turnover", "below"}
+                };
+                FreightTurnoverImport ftCity = ftMap.get(city);
+                ScaleSplitTransport cum = cumMap.get(city);
+                for (String[] metric : metrics) {
+                    Row row = sheet.getRow(rowIdx);
+                    if (row == null) row = sheet.createRow(rowIdx);
+                    fillFreightDetailRow(row, fillMonths, extended ? 26 : 14,
+                        m -> getCityMonthValue(monthData, ftCity, city, m, metric[1], metric[2], h2032Freight),
+                        m -> getCityMonthYoy(monthData, ftCity, city, m, metric[1], metric[2]),
+                        getCityCumValue(cum, ftCity, metric[1], metric[2], h2032FreightCum, city, currentMonth),
+                        getCityCumYoy(cum, ftCity, metric[1], metric[2], currentMonth));
+                    rowIdx++;
+                }
+            }
+            return toBytes(wb);
         }
-
-        autoWidth(sheet, cumCol + 2);
-        return toBytes(wb);
     }
     // ==================== 2. 鐢熸垚_璐ц繍閲忔帓鍚�.xlsx ====================
 
-    public byte[] exportFreightRank(String period) throws Exception {
+    public byte[] exportFreightRank(String period, String mode) throws Exception {
         String year = period.substring(0, 4);
-        int month = parseMonth(period);
+        int month = monthOf(period, mode);
         Map<String, FreightTurnoverImport> ftMap = loadFreightTurnover(period);
         Map<String, Double> h2032FreightCum = loadH2032FreightCumulative(period);
         Map<String, Double> h2032FreightYoy = computeH2032Yoy(period);
+        Map<String, Double> h2032FreightCumLast = loadH2032FreightCumulative(
+            (Integer.parseInt(period.substring(0, 4)) - 1) + period.substring(4));
 
         Map<String, Double> above = new HashMap<>();
         Map<String, Double> below = new HashMap<>();
@@ -146,6 +636,15 @@
         Double provTotal = freightCum(ftMap.get("婀栧寳鐪�"), month);
         Double provBelow = provTotal == null ? null : round(provTotal - provAboveWan, 4);
         Double provTotalYoy = freightCumYoy(ftMap.get("婀栧寳鐪�"), month);
+        double provAboveLast = 0.0;
+        for (Double v : h2032FreightCumLast.values()) {
+            if (v != null) provAboveLast += v;
+        }
+        Double provAboveYoy = provAboveLast == 0.0 ? null : round((provAbove - provAboveLast) / provAboveLast, 4);
+        Double provTotalLast = lastFreightCum(ftMap.get("婀栧寳鐪�"), month);
+        Double provBelowLast = provTotalLast == null ? null : round(provTotalLast - provAboveLast / 10000.0, 4);
+        Double provBelowYoy = provBelowLast == null || provBelowLast == 0.0 ? null
+            : round((provBelow - provBelowLast) / provBelowLast, 4);
 
         for (String city : RegionUtil.cityList()) {
             Double a = h2032FreightCum.get(city);
@@ -155,81 +654,439 @@
             total.put(city, t);
             below.put(city, t == null ? null : round(t - aWan, 4));
             aboveYoy.put(city, h2032FreightYoy.get(city));
-            belowYoy.put(city, null);
+            // 瑙勪笅鍚屾瘮 = (瑙勪笅浠婂勾 - 瑙勪笅鍘诲勾) / 瑙勪笅鍘诲勾锛涜涓嬪幓骞� = 妯℃澘鍘诲勾鍚堣 - H2032鍘诲勾瑙勪笂
+            Double tLast = lastFreightCum(ftMap.get(city), month);
+            Double aLast = h2032FreightCumLast.get(city);
+            double aLastWan = aLast == null ? 0.0 : round(aLast / 10000.0, 4);
+            Double belowLast = tLast == null ? null : round(tLast - aLastWan, 4);
+            Double b = below.get(city);
+            belowYoy.put(city, belowLast == null || belowLast == 0.0 || b == null
+                ? null : round((b - belowLast) / belowLast, 4));
             totalYoy.put(city, freightCumYoy(ftMap.get(city), month));
         }
 
-        XSSFWorkbook wb = new XSSFWorkbook();
-        Sheet sheet = wb.createSheet("璐ц繍閲忔帓鍚�");
-
-        Row title = sheet.createRow(0);
-        title.createCell(0).setCellValue(year + "骞�1-" + month + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚鍏矾璐ц繍閲忔儏鍐�");
-
-        int rowIdx = 1;
-        rowIdx = writeRankHead(sheet, rowIdx, "璐ц繍閲�", "鍚�");
-        rowIdx = writeFreightRankRows(sheet, rowIdx, null, above, below, total, aboveYoy, belowYoy, totalYoy,
-            provAboveWan, provBelow, provTotal, null, null, provTotalYoy, true);
-        for (String city : RegionUtil.cityList()) {
-            rowIdx = writeFreightRankRows(sheet, rowIdx, city, above, below, total, aboveYoy, belowYoy, totalYoy,
-                provAboveWan, provBelow, provTotal, null, null, provTotalYoy, false);
+        // 浠� docs/璐ц繍/鐢熸垚_璐ц繍閲忔帓鍚�.xlsx 涓哄簳绋匡細淇濈暀琛ㄥご/鍚堝苟/鍒楀/鏍峰紡涓� Q/R 鍗犳瘮鍏紡锛屼粎鏇挎崲鏁版嵁
+        File template = resolveFreightTemplate("鐢熸垚_璐ц繍閲忔帓鍚�.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            sheet.getRow(0).getCell(0).setCellValue(year + "骞�" + cumRange(month) + "鍏ㄧ渷鍒嗗競宸炵疮璁″畬鎴愬叕璺揣杩愰噺鎯呭喌");
+            sheet.getRow(22).getCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚鍏矾璐ц繍閲忔儏鍐�");
+            fillFreightRankBlock(sheet, 3, above, below, total, aboveYoy, belowYoy, totalYoy,
+                provAboveWan, provBelow, provTotal, provAboveYoy, provBelowYoy, provTotalYoy);
+            fillFreightRankBlock(sheet, 25, above, below, total, aboveYoy, belowYoy, totalYoy,
+                provAboveWan, provBelow, provTotal, provAboveYoy, provBelowYoy, provTotalYoy);
+            try {
+                wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
+            } catch (Exception e) {
+                log.warn("璐ц繍閲忔帓鍚嶆ā鏉垮叕寮忔眰鍊煎け璐�: {}", e.getMessage());
+            }
+            return toBytes(wb);
         }
-
-        // 鏈湀鍖哄潡锛堝綋鏈堟爣棰� + 绱鏁版嵁锛屼笌鏍蜂緥缁撴瀯涓�鑷达級
-        sheet.createRow(rowIdx++);
-        Row title2 = sheet.createRow(rowIdx++);
-        title2.createCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚" + "鍏矾璐ц繍閲忔儏鍐�");
-        rowIdx = writeRankHead(sheet, rowIdx, "璐ц繍閲�", "鍚�");
-        rowIdx = writeFreightRankRows(sheet, rowIdx, null, above, below, total, aboveYoy, belowYoy, totalYoy,
-            provAboveWan, provBelow, provTotal, null, null, provTotalYoy, true);
-        for (String city : RegionUtil.cityList()) {
-            rowIdx = writeFreightRankRows(sheet, rowIdx, city, above, below, total, aboveYoy, belowYoy, totalYoy,
-                provAboveWan, provBelow, provTotal, null, null, provTotalYoy, false);
-        }
-
-        // 鏈熬绌鸿锛堜笌鏍蜂緥缁撴瀯瀵归綈锛�
-        for (int i = 0; i < 4; i++) sheet.createRow(rowIdx++);
-
-        autoWidth(sheet, 18);
-        return toBytes(wb);
     }
 
     // ==================== 3. 鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx ====================
 
-    public byte[] exportTurnoverRank(String period) throws Exception {
+    public byte[] exportTurnoverRank(String period, String mode) throws Exception {
         String year = period.substring(0, 4);
-        int month = parseMonth(period);
+        int month = monthOf(period, mode);
         Map<String, ScaleSplitTransport> cumMap = loadCumulativeMap(period);
         ScaleSplitTransport province = getProvinceCumulative(period);
 
-        XSSFWorkbook wb = new XSSFWorkbook();
-        Sheet sheet = wb.createSheet("鍛ㄨ浆閲忔帓鍚�");
-
-        Row title = sheet.createRow(0);
-        title.createCell(0).setCellValue(year + "骞�1-" + month + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚鍏矾璐х墿杩愯緭鍛ㄨ浆閲忔儏鍐�");
-
-        int rowIdx = 1;
-        rowIdx = writeRankHead(sheet, rowIdx, "鍛ㄨ浆閲�", "鍚ㄥ叕閲�");
-        rowIdx = writeTurnoverRankRows(sheet, rowIdx, null, cumMap, province, true);
-        for (String city : RegionUtil.cityList()) {
-            rowIdx = writeTurnoverRankRows(sheet, rowIdx, city, cumMap, province, false);
+        // 浠� docs/璐ц繍/鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx 涓哄簳绋匡細淇濈暀琛ㄥご/鍚堝苟/鍒楀/鏍峰紡锛屼粎鏇挎崲鏁版嵁锛決/R 鍗犳瘮涓烘ā鏉跨紦瀛樺�硷紝鎸夋柊鏁版嵁閲嶇畻
+        File template = resolveFreightTemplate("鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            sheet.getRow(0).getCell(0).setCellValue(year + "骞�" + cumRange(month) + "鍏ㄧ渷鍒嗗競宸炵疮璁″畬鎴愬叕璺揣鐗╄繍杈撳懆杞噺鎯呭喌");
+            sheet.getRow(22).getCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚鍏矾璐х墿杩愯緭鍛ㄨ浆閲忔儏鍐�");
+            fillTurnoverRankBlock(sheet, 3, cumMap, province);
+            fillTurnoverRankBlock(sheet, 25, cumMap, province);
+            return toBytes(wb);
         }
-
-        // 鏈湀鍖哄潡锛堝綋鏈堟爣棰� + 绱鏁版嵁锛屼笌鏍蜂緥缁撴瀯涓�鑷达級
-        sheet.createRow(rowIdx++);
-        Row title2 = sheet.createRow(rowIdx++);
-        title2.createCell(0).setCellValue(year + "骞�" + month + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚" + "鍏矾璐х墿杩愯緭鍛ㄨ浆閲忔儏鍐�");
-        rowIdx = writeRankHead(sheet, rowIdx, "鍛ㄨ浆閲�", "鍚ㄥ叕閲�");
-        rowIdx = writeTurnoverRankRows(sheet, rowIdx, null, cumMap, province, true);
-        for (String city : RegionUtil.cityList()) {
-            rowIdx = writeTurnoverRankRows(sheet, rowIdx, city, cumMap, province, false);
-        }
-
-        // 鏈熬绌鸿锛堜笌鏍蜂緥缁撴瀯瀵归綈锛�
-        for (int i = 0; i < 4; i++) sheet.createRow(rowIdx++);
-
-        autoWidth(sheet, 18);
-        return toBytes(wb);
     }
+    /** 璐ц繍鎶ヨ〃妯℃澘瀹氫綅锛坉ocs/璐ц繍锛� */
+    private File resolveFreightTemplate(String fileName) throws Exception {
+        return resolveAnyTemplate(freightTemplateDir, fileName);
+    }
+
+    /** 鍐欒揣杩愭ā鏉挎暟鎹崟鍏冩牸锛氬叕寮忓垪涓嶅姩锛涙湁鍊煎厛娓呯┖鍐嶅啓锛圥OI 瀵瑰叕寮忓崟鍏冩牸鍙洿鏂扮紦瀛橈級锛屾棤鍊兼竻绌烘ā鏉挎牱渚� */
+    private void setFreightCell(Row row, int idx, Double v) {
+        Cell c = row.getCell(idx);
+        if (c != null && c.getCellType() == CellType.FORMULA) return;
+        if (c == null) c = row.createCell(idx);
+        c.setBlank();
+        if (v != null) c.setCellValue(v);
+    }
+
+    private void setFreightCell(Row row, int idx, Integer v) {
+        if (v == null) {
+            setFreightCell(row, idx, (Double) null);
+        } else {
+            setFreightCell(row, idx, v.doubleValue());
+        }
+    }
+
+    // ==================== 鏈堝害妯℃澘鑷姩鎵╁垪锛�1-6鏈� 鈫� 1-12鏈堬級 ====================
+
+    /** 鏈堝害妯℃澘鍚戝彸鎵╁垪锛氱疮璁″垪锛�14/15锛夌Щ鍒版湯灏撅紙26/27锛夛紝7..12 鏈堝垪鐢� 1 鏈堝垪鍏嬮殕锛堣〃澶�/鏍峰紡/鍏紡寮曠敤鍚屾锛� */
+    private void extendMonthlyColumns(Sheet sheet, int headerRow, int year, int dataStartRow, int dataEndRow) {
+        moveColumnPair(sheet, headerRow, 14, 26, dataEndRow); // 绱/绱鍚屾瘮 鈫� AA/AB
+        for (int m = 7; m <= 12; m++) {
+            int dst = 2 + (m - 1) * 2;
+            cloneColumnPair(sheet, headerRow, 2, 3, dst, dst + 1, year, m, dataStartRow, dataEndRow);
+        }
+        fixCumulativeFormulas(sheet, headerRow, dataEndRow); // AA 绱鍏紡 = 12 涓湀鍊间箣鍜�
+    }
+
+    /** 鎶婁竴鍒楀锛堝�煎垪+鍚屾瘮鍒楋級浠� src 绉诲埌 dst锛堝惈琛ㄥご/鏁版嵁/鏍峰紡/鍚堝苟/鍒楀锛夛紝src 鍐呭娓呯┖ */
+    private void moveColumnPair(Sheet sheet, int headerRow, int src, int dst, int lastRow) {
+        for (int r = headerRow; r <= lastRow; r++) {
+            copyCellForColumn(sheet, r, src, r, dst, false);
+            copyCellForColumn(sheet, r, src + 1, r, dst + 1, false);
+            clearCellContent(sheet, r, src);
+            clearCellContent(sheet, r, src + 1);
+        }
+        moveMergedRegion(sheet, src, dst);
+        moveMergedRegion(sheet, src + 1, dst + 1);
+        copyColumnWidth(sheet, src, dst);
+        copyColumnWidth(sheet, src + 1, dst + 1);
+    }
+
+    /** 鎶� 1 鏈堝垪瀵瑰厠闅嗕负 m 鏈堝垪瀵癸細琛ㄥご鏂囨湰/鏃ユ湡淇銆佹牱寮忓鍒躲�侀潪璺ㄧ翱鍏紡鍏嬮殕骞跺悓姝ュ垪寮曠敤 */
+    private void cloneColumnPair(Sheet sheet, int headerRow, int srcVal, int srcYoy, int dstVal, int dstYoy,
+                                 int year, int month, int dataStartRow, int dataEndRow) {
+        for (int r = headerRow; r <= dataEndRow; r++) {
+            boolean dataZone = r >= dataStartRow;
+            cloneCellForMonth(sheet, r, srcVal, dstVal, year, month, dataZone);
+            cloneCellForMonth(sheet, r, srcYoy, dstYoy, year, month, dataZone);
+        }
+        cloneMergedRegion(sheet, srcVal, dstVal);
+        cloneMergedRegion(sheet, srcYoy, dstYoy);
+        copyColumnWidth(sheet, srcVal, dstVal);
+        copyColumnWidth(sheet, srcYoy, dstYoy);
+    }
+
+    /** 澶嶅埗鍗曟牸锛歮ove=false 鍘熸牱澶嶅埗锛沵ove=true 琛ㄥご鏂囨湰/鏃ユ湡淇銆佹暟鎹尯浠呮牱寮�+闈炶法绨垮叕寮忥紙鍒楀紩鐢ㄥ悓姝ワ級 */
+    private void copyCellForColumn(Sheet sheet, int r, int srcC, int dstR, int dstC, boolean monthClone) {
+        Row srcRow = sheet.getRow(r);
+        if (srcRow == null) return;
+        Cell src = srcRow.getCell(srcC);
+        if (src == null) return;
+        Cell dst = getOrCreateCell(sheet, dstR, dstC);
+        dst.setBlank();
+        dst.setCellStyle(src.getCellStyle());
+        if (!monthClone) {
+            if (src.getCellType() == CellType.FORMULA) {
+                String f = src.getCellFormula();
+                if (f != null && !f.contains("#REF!")) dst.setCellFormula(f);
+            } else if (src.getCellType() == CellType.STRING) {
+                dst.setCellValue(src.getStringCellValue());
+            } else if (src.getCellType() == CellType.NUMERIC) {
+                dst.setCellValue(src.getNumericCellValue());
+            } else if (src.getCellType() == CellType.BOOLEAN) {
+                dst.setCellValue(src.getBooleanCellValue());
+            }
+        }
+    }
+
+    /** 鍏嬮殕鍗曟牸鍒扮洰鏍囨湀鍒楋細琛ㄥご琛岋紙dataZone=false锛変慨姝f湀浠�/骞翠唤鏂囨湰鎴栨棩鏈燂紱鏁版嵁鍖轰粎鏍峰紡+闈炶法绨垮叕寮忓厠闅� */
+    private void cloneCellForMonth(Sheet sheet, int r, int srcC, int dstC, int year, int month, boolean dataZone) {
+        Row srcRow = sheet.getRow(r);
+        if (srcRow == null) return;
+        Cell src = srcRow.getCell(srcC);
+        if (src == null) return;
+        Cell dst = getOrCreateCell(sheet, r, dstC);
+        dst.setBlank();
+        dst.setCellStyle(src.getCellStyle());
+        if (!dataZone) {
+            if (src.getCellType() == CellType.NUMERIC && org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(src)) {
+                java.util.Calendar cal = java.util.Calendar.getInstance();
+                cal.set(year, month - 1, 1, 0, 0, 0);
+                cal.clear(java.util.Calendar.MILLISECOND);
+                dst.setCellValue(cal.getTime());
+            } else if (src.getCellType() == CellType.STRING) {
+                String t = src.getStringCellValue();
+                if (t != null && !t.isEmpty()) {
+                    dst.setCellValue(t.replaceFirst("^\\d{4}骞�", year + "骞�").replaceFirst("(\\d+)鏈�", month + "鏈�"));
+                }
+            }
+            return;
+        }
+        // 鏁版嵁鍖猴細浠呭鍒舵牱寮忥紱闈炶法绨裤�侀潪 #REF! 鐨勫叕寮忓厠闅嗗苟鍚屾鏈堝�煎垪寮曠敤
+        if (src.getCellType() == CellType.FORMULA) {
+            String f = src.getCellFormula();
+            if (f != null && !f.contains("[") && !f.contains("#REF!")) {
+                dst.setCellFormula(shiftMonthFormula(f, colLetter(dstC)));
+            }
+        }
+    }
+
+    /** 鍏紡鍒楀紩鐢ㄥ钩绉伙細鏈〃鏈堝�煎垪锛圕/E/G/I/K/M/O/Q/S/U/W/Y锛夆啋 鐩爣鍒楀瓧姣嶏紱鎺掗櫎璺ㄧ翱 [..]! 寮曠敤 */
+    private String shiftMonthFormula(String formula, String dstColLetter) {
+        return formula.replaceAll("(?<![A-Za-z0-9\\[\\]!])([CEGIKMOQSUWY])(\\d+)", dstColLetter + "$2");
+    }
+
+    /** AA 绱鍒楀叕寮忛噸鍐欎负 12 涓湀鍊间箣鍜岋紙妯℃澘鍘熶负 1-6 鏈堜箣鍜岋級 */
+    private void fixCumulativeFormulas(Sheet sheet, int headerRow, int dataEndRow) {
+        for (int r = headerRow; r <= dataEndRow; r++) {
+            Row row = sheet.getRow(r);
+            if (row == null) continue;
+            Cell c = row.getCell(26);
+            if (c == null || c.getCellType() != CellType.FORMULA) continue;
+            String f = c.getCellFormula();
+            if (f == null) continue;
+            java.util.regex.Matcher m = java.util.regex.Pattern
+                .compile("(?<![A-Za-z0-9\\[\\]!])([CEGIKMOQSUWY])(\\d+)").matcher(f);
+            if (!m.find()) continue;
+            int rowNum = Integer.parseInt(m.group(2));
+            StringBuilder sb = new StringBuilder();
+            for (char col : new char[]{'C', 'E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y'}) {
+                if (sb.length() > 0) sb.append("+");
+                sb.append(col).append(rowNum);
+            }
+            c.setCellFormula(sb.toString());
+        }
+    }
+
+    /** 鍚堝苟鍗曞厓鏍硷細src 鍗曞垪鍚堝苟 鈫� dst 鍒楋紙鍚屽舰鐘讹級锛屽苟绉婚櫎鍘熷悎骞� */
+    private void moveMergedRegion(Sheet sheet, int srcCol, int dstCol) {
+        java.util.List<CellRangeAddress> moves = new java.util.ArrayList<>();
+        for (CellRangeAddress m : sheet.getMergedRegions()) {
+            if (m.getFirstColumn() == srcCol && m.getLastColumn() == srcCol) moves.add(m);
+        }
+        for (CellRangeAddress m : moves) {
+            sheet.addMergedRegion(new CellRangeAddress(m.getFirstRow(), m.getLastRow(), dstCol, dstCol));
+            for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {
+                if (sheet.getMergedRegion(i).equals(m)) {
+                    sheet.removeMergedRegion(i);
+                    break;
+                }
+            }
+        }
+    }
+
+    /** 鍚堝苟鍗曞厓鏍硷細src 鍗曞垪鍚堝苟 鈫� dst 鍒楋紙鍚屽舰鐘讹紝淇濈暀鍘熷悎骞讹級 */
+    private void cloneMergedRegion(Sheet sheet, int srcCol, int dstCol) {
+        for (CellRangeAddress m : sheet.getMergedRegions()) {
+            if (m.getFirstColumn() == srcCol && m.getLastColumn() == srcCol) {
+                sheet.addMergedRegion(new CellRangeAddress(m.getFirstRow(), m.getLastRow(), dstCol, dstCol));
+            }
+        }
+    }
+
+    private void copyColumnWidth(Sheet sheet, int src, int dst) {
+        try {
+            int w = sheet.getColumnWidth(src);
+            if (w > 0) sheet.setColumnWidth(dst, w);
+        } catch (Exception ignore) { }
+    }
+
+    private Cell getOrCreateCell(Sheet sheet, int r, int c) {
+        Row row = sheet.getRow(r);
+        if (row == null) row = sheet.createRow(r);
+        Cell cell = row.getCell(c);
+        if (cell == null) cell = row.createCell(c);
+        return cell;
+    }
+
+    private void clearCellContent(Sheet sheet, int r, int c) {
+        Row row = sheet.getRow(r);
+        if (row == null) return;
+        Cell cell = row.getCell(c);
+        if (cell != null) cell.setBlank();
+    }
+
+    private String colLetter(int colIdx) {
+        return org.apache.poi.ss.util.CellReference.convertNumToColString(colIdx);
+    }
+
+    /** 鏃呭鍒嗗競宸炴湀鍊硷紙0=瀹㈣繍閲�/1=鍛ㄨ浆閲�/2=涓綋瀹㈣繍閲�/3=涓綋鍛ㄨ浆閲忥級 */
+    private double passengerMonthVal(Map<Integer, Map<String, PassengerAgg>> data, Map<Integer, Map<String, double[]>> indi,
+                                     int year, int month, String region, int metricIdx) {
+        if (metricIdx <= 1) {
+            PassengerAgg agg = aggOf(data, year, month, region);
+            Double val = agg == null ? null
+                : (metricIdx == 0 ? agg.passengerTotal / 10000.0 : agg.turnoverTotal / 10000.0);
+            return val == null ? 0.0 : val;
+        }
+        return individualVal(indi, year, month, region, metricIdx - 2);
+    }
+
+    /** 鏃呭绱鍚屾瘮锛堝幓骞� 1..months 鏈堢疮璁★級锛氭棤鍘诲勾鏁版嵁杩斿洖 null */
+    private Double passengerCumYoy(Map<Integer, Map<String, PassengerAgg>> data, Map<Integer, Map<String, double[]>> indi,
+                                   int year, int months, String region, int metricIdx) {
+        double v = 0.0, lv = 0.0;
+        for (int m = 1; m <= months; m++) {
+            v += passengerMonthVal(data, indi, year, m, region, metricIdx);
+            lv += passengerMonthVal(data, indi, year - 1, m, region, metricIdx);
+        }
+        return lv == 0.0 ? null : (v - lv) / lv;
+    }
+
+    /** 涓彛寰勬湀鍚屾瘮锛歬=-1 琛ㄧず鎬昏锛堢彮绾�+鍏氦涔嬪拰锛屽嚭绉�/缃戠害杞︽棤婧愭暟鎹寜 0锛夛紱鏃犲幓骞存暟鎹暀绌� */
+    private void fillMidYoyCell(Cell c, Map<Integer, Map<String, double[][]>> mid, int year, int month,
+                                String area, int k, boolean volume) {
+        double v = 0.0, lv = 0.0;
+        if (k == -1) {
+            for (int kk = 1; kk <= 4; kk++) {
+                if (kk >= 3) continue;
+                v += midClassVal(mid, year, month, area, kk, volume);
+                lv += midClassVal(mid, year - 1, month, area, kk, volume);
+            }
+        } else if (k <= 2) {
+            v = midClassVal(mid, year, month, area, k, volume);
+            lv = midClassVal(mid, year - 1, month, area, k, volume);
+        }
+        c.setBlank();
+        if (v != 0.0 && lv != 0.0) c.setCellValue(round((v - lv) / lv, 4));
+    }
+
+    /** 涓彛寰勭疮璁″悓姣斿垪锛�1..months 鏈堢疮璁★紱k=-1 鎬昏 = 鐝嚎+鍏氦锛� */
+    private void fillMidCumYoyColumn(Sheet sheet, Map<Integer, Map<String, double[][]>> mid, int year,
+                                     int months, int colIdx) {
+        List<String> areas = midTemplateAreas();
+        for (int r0 = 4; r0 <= sheet.getLastRowNum(); r0++) {
+            int rr = r0 + 1;
+            int blockOffset = -1;
+            boolean volume = false;
+            if (rr >= 5 && rr <= 94) {
+                blockOffset = rr - 5;
+                volume = true;
+            } else if (rr >= 99 && rr <= 188) {
+                blockOffset = rr - 99;
+                volume = false;
+            }
+            if (blockOffset < 0 || blockOffset % 5 == 0) continue;
+            int k = blockOffset % 5;
+            String area = areas.get(blockOffset / 5);
+            double v = 0.0, lv = 0.0;
+            for (int m = 1; m <= months; m++) {
+                if (k <= 2) {
+                    v += midClassVal(mid, year, m, area, k, volume);
+                    lv += midClassVal(mid, year - 1, m, area, k, volume);
+                }
+            }
+            Row row = sheet.getRow(r0);
+            if (row == null) continue;
+            Cell c = row.getCell(colIdx);
+            if (c == null) c = row.createCell(colIdx);
+            c.setBlank();
+            if (v != 0.0 && lv != 0.0) c.setCellValue(round((v - lv) / lv, 4));
+        }
+    }
+
+    /** 璐ц繍鍒嗗競宸炴槑缁嗚濉厖锛�1..fillMonths 鏈堝��/鍚屾瘮锛堟渶澶� 12 鏈堬紝鍏朵綑娓呯┖锛�+ 绱/绱鍚屾瘮锛坈umCol 璧蜂袱鍒楋級 */
+    private void fillFreightDetailRow(Row row, int fillMonths, int cumCol,
+                                      java.util.function.IntFunction<Double> monthValue,
+                                      java.util.function.IntFunction<Double> monthYoy,
+                                      Double cumValue, Double cumYoy) {
+        for (int m = 1; m <= (fillMonths > 6 ? 12 : 6); m++) {
+            if (m <= fillMonths) {
+                setFreightCell(row, 2 + (m - 1) * 2, monthValue.apply(m));
+                setFreightCell(row, 3 + (m - 1) * 2, monthYoy.apply(m));
+            } else {
+                setFreightCell(row, 2 + (m - 1) * 2, (Double) null);
+                setFreightCell(row, 3 + (m - 1) * 2, (Double) null);
+            }
+        }
+        setFreightCell(row, cumCol, cumValue);
+        setFreightCell(row, cumCol + 1, cumYoy);
+    }
+
+    /** 璐ц繍閲忔帓鍚嶅潡濉厖锛氭ā鏉� 1-based r4 璧凤紙鍏ㄧ渷+17甯傚窞锛夛紝Q/R 鍗犳瘮鍏紡淇濈暀涓嶅姩 */
+    private void fillFreightRankBlock(Sheet sheet, int start,
+                                      Map<String, Double> above, Map<String, Double> below, Map<String, Double> total,
+                                      Map<String, Double> aboveYoy, Map<String, Double> belowYoy, Map<String, Double> totalYoy,
+                                      Double provAbove, Double provBelow, Double provTotal,
+                                      Double provAboveYoy, Double provBelowYoy, Double provTotalYoy) {
+        Row provRow = sheet.getRow(start);
+        if (provRow == null) provRow = sheet.createRow(start);
+        setFreightCell(provRow, 1, provAbove);
+        setFreightCell(provRow, 4, provAboveYoy);
+        setFreightCell(provRow, 6, provBelow);
+        setFreightCell(provRow, 9, provBelowYoy);
+        setFreightCell(provRow, 11, provTotal);
+        setFreightCell(provRow, 14, provTotalYoy);
+        int rowIdx = start + 1;
+        for (String city : RegionUtil.cityList()) {
+            Row row = sheet.getRow(rowIdx);
+            if (row == null) row = sheet.createRow(rowIdx);
+            setFreightCell(row, 1, above.get(city));
+            setFreightCell(row, 2, rankOfMap(above, city));
+            setFreightCell(row, 3, ratioOf(above.get(city), provAbove));
+            setFreightCell(row, 4, aboveYoy.get(city));
+            setFreightCell(row, 5, yoyRankOfMap(aboveYoy, city));
+            setFreightCell(row, 6, below.get(city));
+            setFreightCell(row, 7, rankOfMap(below, city));
+            setFreightCell(row, 8, ratioOf(below.get(city), provBelow));
+            setFreightCell(row, 9, belowYoy.get(city));
+            setFreightCell(row, 10, yoyRankOfMap(belowYoy, city));
+            setFreightCell(row, 11, total.get(city));
+            setFreightCell(row, 12, rankOfMap(total, city));
+            setFreightCell(row, 13, ratioOf(total.get(city), provTotal));
+            setFreightCell(row, 14, totalYoy.get(city));
+            setFreightCell(row, 15, yoyRankOfMap(totalYoy, city));
+            rowIdx++;
+        }
+    }
+
+    /** 鍛ㄨ浆閲忔帓鍚嶅潡濉厖锛氭ā鏉� 1-based r4 璧凤紙鍏ㄧ渷+17甯傚窞锛� */
+    private void fillTurnoverRankBlock(Sheet sheet, int start,
+                                       Map<String, ScaleSplitTransport> cumMap, ScaleSplitTransport province) {
+        if (province == null) province = new ScaleSplitTransport();
+        Double provAbove = province.getAboveScaleTurnover();
+        Double provBelow = province.getBelowScaleTurnover();
+        Double provTotal = province.getTotalTurnover();
+        Row provRow = sheet.getRow(start);
+        if (provRow == null) provRow = sheet.createRow(start);
+        setFreightCell(provRow, 1, provAbove);
+        setFreightCell(provRow, 4, province.getAboveScaleYoy());
+        setFreightCell(provRow, 6, provBelow);
+        setFreightCell(provRow, 9, province.getBelowScaleYoy());
+        setFreightCell(provRow, 11, provTotal);
+        setFreightCell(provRow, 14, province.getTotalYoy());
+        setFreightShare(provRow, provAbove, provTotal);
+        int rowIdx = start + 1;
+        for (String city : RegionUtil.cityList()) {
+            Row row = sheet.getRow(rowIdx);
+            if (row == null) row = sheet.createRow(rowIdx);
+            ScaleSplitTransport cum = cumMap.get(city);
+            Double above = cum == null ? null : cum.getAboveScaleTurnover();
+            Double below = cum == null ? null : cum.getBelowScaleTurnover();
+            Double total = cum == null ? null : cum.getTotalTurnover();
+            setFreightCell(row, 1, above);
+            setFreightCell(row, 2, cum == null ? null : rankOfTurnover(cumMap, city, 0));
+            setFreightCell(row, 3, ratioOf(above, provAbove));
+            setFreightCell(row, 4, cum == null ? null : cum.getAboveScaleYoy());
+            setFreightCell(row, 5, cum == null ? null : yoyRankOfTurnover(cumMap, city, 0));
+            setFreightCell(row, 6, below);
+            setFreightCell(row, 7, cum == null ? null : rankOfTurnover(cumMap, city, 1));
+            setFreightCell(row, 8, ratioOf(below, provBelow));
+            setFreightCell(row, 9, cum == null ? null : cum.getBelowScaleYoy());
+            setFreightCell(row, 10, cum == null ? null : yoyRankOfTurnover(cumMap, city, 1));
+            setFreightCell(row, 11, total);
+            setFreightCell(row, 12, cum == null ? null : rankOfTurnover(cumMap, city, 2));
+            setFreightCell(row, 13, ratioOf(total, provTotal));
+            setFreightCell(row, 14, cum == null ? null : cum.getTotalYoy());
+            setFreightCell(row, 15, cum == null ? null : yoyRankOfTurnover(cumMap, city, 2));
+            setFreightShare(row, above, total);
+            rowIdx++;
+        }
+    }
+
+    /** 鍛ㄨ浆閲忔帓鍚� Q/R 鍗犳瘮锛氭ā鏉夸负缂撳瓨鍊硷紝鎸夋柊鏁版嵁閲嶇畻锛堣涓婂崰姣� 1 浣嶅皬鏁帮級 */
+    private void setFreightShare(Row row, Double above, Double total) {
+        if (total != null && total > 0 && above != null) {
+            double share = round(above * 10.0 / total, 1);
+            setFreightCell(row, 16, share);
+            setFreightCell(row, 17, round(10.0 - share, 1));
+        } else {
+            setFreightCell(row, 16, (Double) null);
+            setFreightCell(row, 17, (Double) null);
+        }
+    }
+
     // ==================== 鏁版嵁鍔犺浇 ====================
 
     /** 褰撴湀鏁版嵁: city -> month -> record锛堜笉鍚叏鐪侊級 */
@@ -771,10 +1628,846 @@
         return rank;
     }
 
+    // ==================== 4. 鐢熸垚_鍏矾鏃呭鍒嗗競宸炴槑缁�.xlsx锛堟湀鎶�/骞存姤锛� ====================
+
+    /**
+     * 鍏矾鏃呭鍒嗗競宸炴槑缁�
+     * @param period 鏈堟姤: 2026-06锛堜粖骞�1-6鏈堬級锛涘勾鎶�: 2026锛堝叏骞�12涓湀锛�
+     * @param mode   month/year
+     */
+    /**
+     * 鐢熸垚_鍏矾鏃呭鍒嗗競宸�.xlsx锛氫互 docs/鍏矾鏃呭+鑳借��/杈撳嚭 妯℃澘涓哄簳绋匡紝
+     * 淇濈暀琛ㄥご/鍚堝苟/鍒楀/鍏紡/鏍峰紡锛屼粎鏇挎崲 2026 骞� 1..6 鏈堟湀搴﹀垪鏁版嵁锛堝杩愰噺/鍛ㄨ浆閲�/涓綋琛岋級銆�
+     * 2025/2024 鍘嗗彶鍒椾繚鐣欐ā鏉垮�硷紱绱/鍚屾瘮/鍏ㄧ渷琛屽叕寮忎繚鐣欏苟閲嶇畻銆�
+     */
+    public byte[] exportPassengerCityDetail(String period, String mode) throws Exception {
+        int maxMonth = monthOf(period, mode);
+        int currentYear = Integer.parseInt(period.substring(0, 4));
+        File template = resolvePassengerTemplate("鐢熸垚_鍏矾鏃呭鍒嗗競宸�.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            fixPassengerHeaderYear(sheet, currentYear); // 琛ㄥご骞翠唤鍔ㄦ�佸寲锛堜笉鍐欐 2026锛�
+            Map<Integer, Map<String, PassengerAgg>> data = loadPassengerAggMap();
+            Map<Integer, Map<String, double[]>> indi = loadPassengerIndividualMap();
+            int fillMonths = maxMonth; // 1..N 鏈堬紙N>6 鏃舵ā鏉胯嚜鍔ㄥ悜鍙虫墿鍒楀埌 12 鏈堬級
+            boolean extended = fillMonths > 6;
+            if (extended) extendMonthlyColumns(sheet, 1, currentYear, 3, sheet.getLastRowNum());
+            String currentRegion = null;
+            for (int r = 3; r <= sheet.getLastRowNum(); r++) { // 浠� R4 璧凤細A 鍒楀悎骞惰鏄尯鍩熸爣璁帮紙R4 鍏ㄧ渷銆丷8 姝︽眽甯傗�︼級
+                Row row = sheet.getRow(r);
+                if (row == null) continue;
+                Cell nameCell = row.getCell(0);
+                Cell metricCell = row.getCell(1);
+                if (metricCell == null) continue;
+                String areaName = cellText(nameCell);
+                if (areaName != null && !areaName.trim().isEmpty()) {
+                    currentRegion = "鍏ㄧ渷".equals(areaName.trim()) ? "婀栧寳鐪�" : RegionUtil.normalizeCityName(areaName.trim());
+                }
+                if (currentRegion == null) continue;
+                String metric = cellText(metricCell);
+                if (metric == null) continue;
+                int metricIdx = cityMetricIndexOf(metric);
+                if (metricIdx < 0) continue;
+                String region = currentRegion;
+                for (int m = 1; m <= fillMonths; m++) {
+                    int col = 2 + (m - 1) * 2; // C,E,G,I,K,M,O,Q,S,U,W,Y锛�7-12 鏈堜负鎵╁垪鍒楋級
+                    Cell c = row.getCell(col);
+                    boolean keepFormula = c != null && c.getCellType() == CellType.FORMULA; // 鍏ㄧ渷/绱绛夊叕寮忎繚鐣�
+                    double v = passengerMonthVal(data, indi, currentYear, m, region, metricIdx);
+                    if (!keepFormula) {
+                        if (c == null) c = row.createCell(col);
+                        if (v == 0.0) c.setBlank(); else c.setCellValue(round(v, 4));
+                    }
+                    // 鍚屾瘮鍒楋細搴撳唴鍘诲勾鍚屾湀鍚屾瘮锛堟ā鏉� #REF! 鍏紡鏇挎崲涓烘暟鍊硷紝鍏紡琛屼篃瑕嗗啓锛�
+                    double lv = passengerMonthVal(data, indi, currentYear - 1, m, region, metricIdx);
+                    Cell yc = row.getCell(col + 1);
+                    if (yc == null) yc = row.createCell(col + 1);
+                    if (v == 0.0 || lv == 0.0) yc.setBlank(); else yc.setCellValue(round((v - lv) / lv, 4));
+                }
+                // 绱鍚屾瘮鍒楋紙妯℃澘 #REF! 鈫� 搴撳唴鍘诲勾 1..N 鏈堢疮璁″悓姣旀暟鍊硷級
+                int cumCol = extended ? 26 : 14;
+                Double cumYoy = passengerCumYoy(data, indi, currentYear, fillMonths, region, metricIdx);
+                Cell yc2 = row.getCell(cumCol + 1);
+                if (yc2 == null) yc2 = row.createCell(cumCol + 1);
+                if (cumYoy == null) yc2.setBlank(); else yc2.setCellValue(round(cumYoy, 4));
+            }
+            recalc(wb);
+            return toBytes(wb);
+        }
+    }
+
+    /** 鍒嗗競宸炶〃鎸囨爣琛岋細0=瀹㈣繍閲� 1=鍛ㄨ浆閲� 2=涓綋瀹㈣繍閲� 3=涓綋鍛ㄨ浆閲忥紱鏃犳硶璇嗗埆杩斿洖 -1 */
+    private int cityMetricIndexOf(String metric) {
+        if (metric == null) return -1;
+        boolean indi = metric.contains("涓綋");
+        if (metric.contains("瀹㈣繍閲�")) return indi ? 2 : 0;
+        if (metric.contains("鍛ㄨ浆閲�")) return indi ? 3 : 1;
+        return -1;
+    }
+
+    private String cellText(Cell c) {
+        if (c == null) return null;
+        if (c.getCellType() == CellType.STRING) return c.getStringCellValue();
+        if (c.getCellType() == CellType.NUMERIC) return String.valueOf(c.getNumericCellValue());
+        return null;
+    }
+
+    /** 涓綋瀹㈣繍閲�/鍛ㄨ浆閲忥紙涓囦汉/涓囦汉鍏噷锛� */
+    private double individualVal(Map<Integer, Map<String, double[]>> indi, int year, int month, String region, int idx) {
+        Map<String, double[]> mm = indi.get(year * 100 + month);
+        if (mm == null) return 0;
+        double[] arr = mm.get(region);
+        if (arr == null) return 0;
+        return idx < arr.length ? arr[idx] : 0;
+    }
+
+    /** 涓綋瀹㈣繍閲�/鍛ㄨ浆閲忔暟鎹細骞�*100+鏈� -> (甯傚窞/鍏ㄧ渷 -> [涓綋瀹㈣繍閲�(涓囦汉), 涓綋鍛ㄨ浆閲�(涓囦汉鍏噷)]) */
+    private Map<Integer, Map<String, double[]>> loadPassengerIndividualMap() {
+        Map<Integer, Map<String, double[]>> data = new HashMap<>();
+        for (PassengerIndividualMonthly e : passengerIndividualMapper.selectList(null)) {
+            if (e.getReportPeriod() == null) continue;
+            String[] parts = e.getReportPeriod().split("-");
+            if (parts.length != 2) continue;
+            int y, m;
+            try {
+                y = Integer.parseInt(parts[0]);
+                m = Integer.parseInt(parts[1]);
+            } catch (NumberFormatException ex) {
+                continue;
+            }
+            String city = RegionUtil.cityByCode(e.getRegionCode());
+            if (city == null) continue;
+            int key = y * 100 + m;
+            double pass = nz(e.getPassengerCount()) / 10000.0;
+            double turn = nz(e.getTurnover()) / 10000.0;
+            double[] arr = data.computeIfAbsent(key, k -> new HashMap<>()).computeIfAbsent(city, k -> new double[2]);
+            arr[0] += pass;
+            arr[1] += turn;
+            double[] prov = data.get(key).computeIfAbsent("婀栧寳鐪�", k -> new double[2]);
+            prov[0] += pass;
+            prov[1] += turn;
+        }
+        return data;
+    }
+
+    private PassengerAgg aggOf(Map<Integer, Map<String, PassengerAgg>> data, int year, int month, String region) {
+        Map<String, PassengerAgg> monthMap = data.get(year * 100 + month);
+        return monthMap == null ? null : monthMap.get(region);
+    }
+
+    private Double cumOf(Map<Integer, Map<String, PassengerAgg>> data, int year, String region, int monthCount, int metricIdx) {
+        double sum = 0.0;
+        boolean has = false;
+        for (int m = 1; m <= monthCount; m++) {
+            Double v = metricValue(aggOf(data, year, m, region), metricIdx);
+            if (v != null) {
+                sum += v;
+                has = true;
+            }
+        }
+        return has ? sum : null;
+    }
+
+    private Double yoy(Double cur, Double base) {
+        if (cur == null || base == null || base == 0.0) return null;
+        return round((cur - base) / base, 4);
+    }
+
+    /** 鎸囨爣鍙栧�硷紙宸叉崲绠椾负涓囦汉/涓囦汉鍏噷/鍏噷锛涙棤鏁版嵁杩斿洖 null锛� */
+    private Double metricValue(PassengerAgg agg, int metric) {
+        if (agg == null) return null;
+        switch (metric) {
+            case 0: return agg.passengerTotal / 10000.0;
+            case 1: return agg.passengerTotal / 10000.0;
+            case 2: return agg.passengerClass1 / 10000.0;
+            case 3: return agg.passengerClass2 / 10000.0;
+            case 4: return agg.passengerClass3 / 10000.0;
+            case 5: return agg.passengerClass4 / 10000.0;
+            case 6: return agg.passengerCharter / 10000.0;
+            case 7:
+            case 8:
+            case 9: return null; // 鍩庡競瀹㈣繍妯″潡鏁版嵁锛屾殏鐣欑┖
+            case 10: return agg.turnoverTotal / 10000.0;
+            case 11: return agg.turnoverClass1 / 10000.0;
+            case 12: return agg.turnoverClass2 / 10000.0;
+            case 13: return agg.turnoverClass3 / 10000.0;
+            case 14: return agg.turnoverClass4 / 10000.0;
+            case 15: return agg.turnoverCharter / 10000.0;
+            case 16: return ratioOrNull(agg.turnoverTotal, agg.passengerTotal);
+            case 17: return ratioOrNull(agg.turnoverClass1, agg.passengerClass1);
+            case 18: return ratioOrNull(agg.turnoverClass2, agg.passengerClass2);
+            case 19: return ratioOrNull(agg.turnoverClass3, agg.passengerClass3);
+            case 20: return ratioOrNull(agg.turnoverClass4, agg.passengerClass4);
+            case 21: return ratioOrNull(agg.turnoverCharter, agg.passengerCharter);
+            default: return null;
+        }
+    }
+
+    private Double ratioOrNull(double numerator, double denominator) {
+        if (denominator == 0.0) return null;
+        return numerator / denominator;
+    }
+
+    /** 鍗曟湀鍏ㄥ競宸炰紒涓氭眹鎬� */
+    private static class PassengerAgg {
+        double passengerTotal;
+        double passengerClass1;
+        double passengerClass2;
+        double passengerClass3;
+        double passengerClass4;
+        double passengerCharter;
+        double turnoverTotal;
+        double turnoverClass1;
+        double turnoverClass2;
+        double turnoverClass3;
+        double turnoverClass4;
+        double turnoverCharter;
+
+        void add(PassengerEnterpriseMonthly e) {
+            passengerTotal += nz(e.getPassengerClass1()) + nz(e.getPassengerClass2()) + nz(e.getPassengerClass3())
+                + nz(e.getPassengerClass4()) + nz(e.getPassengerCharter());
+            passengerClass1 += nz(e.getPassengerClass1());
+            passengerClass2 += nz(e.getPassengerClass2());
+            passengerClass3 += nz(e.getPassengerClass3());
+            passengerClass4 += nz(e.getPassengerClass4());
+            passengerCharter += nz(e.getPassengerCharter());
+            turnoverTotal += nz(e.getTurnoverClass1()) + nz(e.getTurnoverClass2()) + nz(e.getTurnoverClass3())
+                + nz(e.getTurnoverClass4()) + nz(e.getTurnoverCharter());
+            turnoverClass1 += nz(e.getTurnoverClass1());
+            turnoverClass2 += nz(e.getTurnoverClass2());
+            turnoverClass3 += nz(e.getTurnoverClass3());
+            turnoverClass4 += nz(e.getTurnoverClass4());
+            turnoverCharter += nz(e.getTurnoverCharter());
+        }
+
+        private double nz(Double v) {
+            return v == null ? 0.0 : v;
+        }
+    }
+
+
+
+    // ==================== 涓彛寰勬槑缁�/鎺掑悕/鍒嗘瀽锛圚203-1 鍏矾鏃呭锛� ====================
+
+    /** 鍔犺浇 骞�*100+鏈� -> (甯傚窞/鍏ㄧ渷 -> 浼佷笟姹囨��) 鏁版嵁 */
+    private Map<Integer, Map<String, PassengerAgg>> loadPassengerAggMap() {
+        Map<Integer, Map<String, PassengerAgg>> data = new HashMap<>();
+        List<PassengerEnterpriseMonthly> list = passengerMapper.selectList(null);
+        for (PassengerEnterpriseMonthly e : list) {
+            if (e.getReportPeriod() == null) continue;
+            String[] parts = e.getReportPeriod().split("-");
+            if (parts.length != 2) continue;
+            int y;
+            int m;
+            try {
+                y = Integer.parseInt(parts[0]);
+                m = Integer.parseInt(parts[1]);
+            } catch (NumberFormatException ex) {
+                continue;
+            }
+            String city = RegionUtil.cityByCode(e.getRegionCode());
+            if (city == null) continue;
+            int key = y * 100 + m;
+            Map<String, PassengerAgg> monthMap = data.computeIfAbsent(key, k -> new HashMap<>());
+            monthMap.computeIfAbsent(city, k -> new PassengerAgg()).add(e);
+            monthMap.computeIfAbsent("婀栧寳鐪�", k -> new PassengerAgg()).add(e);
+        }
+        return data;
+    }
+
+    /**
+     * 涓彛寰勬槑缁嗭細瀹㈣繍閲忓潡 + 鏃呭鍛ㄨ浆閲忓潡锛堟��/鍏矾鐝嚎/鍩庨檯鍩庝埂鍏氦/宸℃父鍑虹/缃戠害杞︼級
+     */
+    /**
+     * 鐢熸垚_涓彛寰勬槑缁�.xlsx锛氫互鏍蜂緥涓哄簳绋裤�傛暟鎹尯澶栭儴寮曠敤鍏紡锛堢彮绾�/鍏氦/鍑虹/缃戠害杞︼級鏇挎崲涓哄簱鍐呮暟鍊硷紝
+     * 2025 骞村垪淇濈暀妯℃澘缂撳瓨鍊硷紙鍘诲勾鏁版嵁锛夛紝鍐呴儴鍏紡锛堢疮璁�/鍚屾瘮/姹傚拰锛変繚鐣欏苟閲嶇畻銆�
+     */
+    public byte[] exportPassengerMidDetail(String period, String mode) throws Exception {
+        int maxMonth = monthOf(period, mode);
+        int currentYear = Integer.parseInt(period.substring(0, 4));
+        File template = resolvePassengerTemplate("鐢熸垚_涓彛寰勬槑缁�.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            fixPassengerHeaderYear(sheet, currentYear); // 琛ㄥご骞翠唤鍔ㄦ�佸寲锛堜笉鍐欐 2026锛�
+            Map<Integer, Map<String, double[][]>> mid = loadMidClassMap();
+            int fillMonths = maxMonth; // 1..N 鏈堬紙N>6 鏃舵ā鏉胯嚜鍔ㄥ悜鍙虫墿鍒楀埌 12 鏈堬級
+            boolean extended = fillMonths > 6;
+            if (extended) extendMonthlyColumns(sheet, 2, currentYear, 4, sheet.getLastRowNum());
+            List<String> areas = midTemplateAreas();
+            for (Row row : sheet) {
+                if (row == null) continue;
+                for (Cell c : row) {
+                    if (c.getCellType() != CellType.FORMULA) continue;
+                    String f = c.getCellFormula();
+                    if (f == null) continue;
+                    boolean crossBook = f.contains("[");
+                    if (!crossBook && !isYoyMonthCol(c.getColumnIndex() + 1)) continue; // 鍐呴儴鍏紡锛堝悎璁�/绱锛変繚鐣欓噸绠楋紱鍚屾瘮鍒� #REF! 瑕嗗啓涓烘暟鍊�
+                    if (crossBook && !is2026MonthCol(c.getColumnIndex() + 1)) {
+                        keepCached(c); // 2025 骞村垪淇濈暀妯℃澘缂撳瓨鍊�
+                        continue;
+                    }
+                    int r = c.getRowIndex() + 1;
+                    int col = c.getColumnIndex() + 1;
+                    int blockOffset = -1;
+                    boolean volume = false;
+                    if (r >= 5 && r <= 94) {
+                        blockOffset = r - 5;
+                        volume = true;
+                    } else if (r >= 99 && r <= 188) {
+                        blockOffset = r - 99;
+                        volume = false;
+                    }
+                    if (blockOffset < 0) {
+                        keepCached(c); // r1/r2 澶囨敞绛�
+                        continue;
+                    }
+                    if (blockOffset % 5 == 0) {
+                        if (isYoyMonthCol(col)) {
+                            fillMidYoyCell(c, mid, currentYear, monthOfCol(col), areas.get(blockOffset / 5), -1, volume);
+                        } else {
+                            keepCached(c); // 鎬昏鍐呴儴鍏紡锛堥槻寰★級
+                        }
+                        continue;
+                    }
+                    int k = blockOffset % 5; // 1=鐝嚎 2=鍏氦 3=鍑虹 4=缃戠害杞�
+                    String area = areas.get(blockOffset / 5);
+                    if (is2026MonthCol(col)) {
+                        fillMidCell(c, mid, currentYear, monthOfCol(col), area, k, volume);
+                    } else if (isYoyMonthCol(col)) {
+                        fillMidYoyCell(c, mid, currentYear, monthOfCol(col), area, k, volume);
+                    } else {
+                        keepCached(c); // 2025 骞村垪淇濈暀妯℃澘缂撳瓨鍊�
+                    }
+                }
+            }
+            // 绱鍚屾瘮鍒楋紙妯℃澘 #REF! 鈫� 搴撳唴鍘诲勾 1..N 鏈堢疮璁″悓姣旀暟鍊硷級
+            fillMidCumYoyColumn(sheet, mid, currentYear, fillMonths, extended ? 27 : 15);
+            recalc(wb);
+            return toBytes(wb);
+        }
+    }
+
+    /** 涓彛寰勬槑缁嗘ā鏉垮湴鍖洪『搴忥紙涓� RegionUtil.CITY_LIST 涓�鑷达細姝︽眽甯傗�︾鍐滄灦鏋楀尯锛� */
+    private List<String> midTemplateAreas() {
+        List<String> areas = new java.util.ArrayList<>();
+        areas.add("婀栧寳鐪�");
+        areas.addAll(RegionUtil.CITY_LIST);
+        return areas;
+    }
+
+    private boolean is2026MonthCol(int col) {
+        return col >= 3 && col <= 25 && col % 2 == 1;
+    }
+
+    /** 鏈堝悓姣斿垪锛�1-based 鍋舵暟鍒� 4..26锛� */
+    private boolean isYoyMonthCol(int col) {
+        return col >= 4 && col <= 26 && col % 2 == 0;
+    }
+
+    private int monthOfCol(int col) {
+        return (col - 1) / 2;
+    }
+
+    /** 濉腑鍙e緞鏁版嵁鍗曞厓鏍硷細鍑虹/缃戠害杞�(3/4)鏄惧紡 0锛岀彮绾�/鍏氦濉簱鍐呭�硷紙鏃犲�兼竻绌猴級 */
+    private void fillMidCell(Cell c, Map<Integer, Map<String, double[][]>> mid, int year, int month, String area, int k, boolean volume) {
+        if (k == 3 || k == 4) {
+            writeExplicitZero(c); // 鍑虹/缃戠害杞︽殏鏃犳暟鎹簮锛屾樉寮� 0
+            return;
+        }
+        double v = midClassVal(mid, year, month, area, k, volume);
+        // POI setCellValue(double) 瀵瑰叕寮忓崟鍏冩牸鍙洿鏂扮紦瀛樹笉绉婚櫎鍏紡锛屽繀椤诲厛 setBlank 鍐嶅啓鍊�
+        c.setBlank();
+        if (v != 0.0) c.setCellValue(round(v, 4));
+    }
+
+    /** 鏄惧紡鍐欏叆 0锛圥OI setCellValue(0.0) 浼氳浆 blank锛岄渶鎿嶄綔搴曞眰 XML锛� */
+    private void writeExplicitZero(Cell c) {
+        c.setBlank();
+        if (c instanceof XSSFCell) {
+            try {
+                ((XSSFCell) c).getCTCell().setT(STCellType.N);
+                ((XSSFCell) c).getCTCell().setV("0");
+            } catch (Exception e) {
+                log.warn("writeExplicitZero failed: {}", e.getMessage());
+            }
+        }
+    }
+
+    private double midClassVal(Map<Integer, Map<String, double[][]>> mid, int year, int month, String area, int k, boolean volume) {
+        Map<String, double[][]> mm = mid.get(year * 100 + month);
+        if (mm == null) return 0;
+        double[][] arr = mm.get(area);
+        if (arr == null) return 0;
+        if (k < 0 || k >= arr.length) return 0;
+        return volume ? arr[k][0] : arr[k][1];
+    }
+
+    /** 鍏紡鍗曞厓鏍兼浛鎹负缂撳瓨鏁板�硷紙閬垮厤璺ㄧ翱寮曠敤鏂摼锛涢潪鏁板�肩紦瀛樼疆绌猴級 */
+    private void keepCached(Cell c) {
+        try {
+            double v = 0.0;
+            boolean has = false;
+            if (c.getCachedFormulaResultType() == CellType.NUMERIC) {
+                v = c.getNumericCellValue();
+                has = true;
+            }
+            c.setBlank();
+            if (has && v != 0.0) c.setCellValue(v);
+        } catch (Exception e) {
+            try { c.setBlank(); } catch (Exception ignore) { }
+        }
+    }
+
+    /**
+     * 涓彛寰勫垎绫绘湀搴︽暟鎹細骞�*100+鏈� -> (甯傚窞/鍏ㄧ渷 -> double[5][2])
+     * 缁村害 0=鎬婚噺 1=鍏矾鐝嚎(h2031) 2=鍩庨檯鍩庝埂鍏氦(cityBus) 3=宸℃父鍑虹 4=缃戠害杞︼紱[瀹㈣繍閲�(涓囦汉), 鍛ㄨ浆閲�(涓囦汉鍏噷)]
+     */
+    private Map<Integer, Map<String, double[][]>> loadMidClassMap() {
+        Map<Integer, Map<String, double[][]>> mid = new HashMap<>();
+        for (PassengerEnterpriseMonthly e : passengerMapper.selectList(null)) {
+            if (e.getReportPeriod() == null) continue;
+            String[] parts = e.getReportPeriod().split("-");
+            if (parts.length != 2) continue;
+            int y, m;
+            try {
+                y = Integer.parseInt(parts[0]);
+                m = Integer.parseInt(parts[1]);
+            } catch (NumberFormatException ex) {
+                continue;
+            }
+            String city = RegionUtil.cityByCode(e.getRegionCode());
+            if (city == null) continue;
+            int key = y * 100 + m;
+            double pass = (nz(e.getPassengerClass1()) + nz(e.getPassengerClass2()) + nz(e.getPassengerClass3())
+                + nz(e.getPassengerClass4()) + nz(e.getPassengerCharter())) / 10000.0;
+            double turn = (nz(e.getTurnoverClass1()) + nz(e.getTurnoverClass2()) + nz(e.getTurnoverClass3())
+                + nz(e.getTurnoverClass4()) + nz(e.getTurnoverCharter())) / 10000.0;
+            addMidClass(mid, key, city, 1, pass, turn);
+            addMidClass(mid, key, "婀栧寳鐪�", 1, pass, turn);
+        }
+        for (CityBusMonthly b : cityBusMapper.selectList(null)) {
+            if (b.getReportPeriod() == null || b.getCity() == null) continue;
+            String[] parts = b.getReportPeriod().split("-");
+            if (parts.length != 2) continue;
+            int y, m;
+            try {
+                y = Integer.parseInt(parts[0]);
+                m = Integer.parseInt(parts[1]);
+            } catch (NumberFormatException ex) {
+                continue;
+            }
+            String city = RegionUtil.normalizeCityName(b.getCity());
+            if (city == null) continue;
+            int key = y * 100 + m;
+            addMidClass(mid, key, city, 2, nz(b.getPassengerChengxiang()), nz(b.getTurnoverChengxiang()));
+            addMidClass(mid, key, "婀栧寳鐪�", 2, nz(b.getPassengerChengxiang()), nz(b.getTurnoverChengxiang()));
+        }
+        return mid;
+    }
+
+    /** 鍐欏叆鍒嗙被鍊煎苟绱姞鎬婚噺锛堢淮搴�0锛� */
+    private void addMidClass(Map<Integer, Map<String, double[][]>> mid, int key, String city, int k, double pass, double turn) {
+        double[][] arr = mid.computeIfAbsent(key, kk -> new HashMap<>()).computeIfAbsent(city, kk -> new double[5][2]);
+        arr[k][0] += pass;
+        arr[k][1] += turn;
+        arr[0][0] += pass;
+        arr[0][1] += turn;
+    }
+
+    private int writeMidBlock(Sheet sheet, Map<Integer, Map<String, PassengerAgg>> data, int[] years,
+                              int currentYear, int maxMonth, int startRow, String title, boolean volume) {
+        Row titleRow = sheet.createRow(startRow);
+        titleRow.createCell(0).setCellValue(title);
+        Row noteRow = sheet.createRow(startRow + 1);
+        noteRow.createCell(0).setCellValue("娉細涓彛寰勭敱鍏矾鐝嚎銆佸煄闄呭煄涔″叕浜ゃ�佸煄闄呭煄涔″嚭绉熴�佸煄闄呭煄涔$綉绾﹁溅鍥涢儴鍒嗘瀯鎴�");
+        Row header = sheet.createRow(startRow + 2);
+        header.createCell(0).setCellValue("鍦板尯 鍚嶇О");
+        header.createCell(1).setCellValue("鎸囨爣");
+        int col = 2;
+        for (int y : years) {
+            int monthCount = (y == currentYear) ? maxMonth : 12;
+            for (int m = 1; m <= monthCount; m++) {
+                header.createCell(col++).setCellValue(y + "骞�" + m + "鏈�");
+                header.createCell(col++).setCellValue(m + "鏈堜笌鍘诲勾鍚屾瘮");
+            }
+            header.createCell(col++).setCellValue(y + "骞寸疮璁�");
+            header.createCell(col++).setCellValue("绱涓庡幓骞村悓姣�");
+        }
+        sheet.createRow(startRow + 3);
+
+        String[] metrics = volume ? new String[]{
+            "鎬诲杩愰噺锛堜竾浜烘锛�",
+            "鍏朵腑锛氬叕璺彮绾垮杩愰噺锛堜竾浜烘锛�",
+            "鍩庨檯鍩庝埂鍏氦瀹㈣繍閲忥紙涓囦汉娆★級",
+            "鍩庨檯鍩庝埂宸℃父鍑虹瀹㈣繍閲忥紙涓囦汉娆★級",
+            "鍩庨檯鍩庝埂缃戠害杞﹀杩愰噺锛堜竾浜烘锛�"
+        } : new String[]{
+            "鎬绘梾瀹㈠懆杞噺锛堜竾浜哄叕閲岋級",
+            "鍏朵腑锛氬叕璺彮绾挎梾瀹㈠懆杞噺锛堜竾浜哄叕閲岋級",
+            "鍩庨檯鍩庝埂鍏氦鏃呭鍛ㄨ浆閲忥紙涓囦汉鍏噷锛�",
+            "鍩庨檯鍩庝埂宸℃父鍑虹鏃呭鍛ㄨ浆閲忥紙涓囦汉鍏噷锛�",
+            "鍩庨檯鍩庝埂缃戠害杞︽梾瀹㈠懆杞噺锛堜竾浜哄叕閲岋級"
+        };
+
+        int rowIdx = startRow + 4;
+        List<String> regions = new java.util.ArrayList<>();
+        regions.add("婀栧寳鐪�");
+        regions.addAll(RegionUtil.cityList());
+        for (String region : regions) {
+            String label = "婀栧寳鐪�".equals(region) ? "鍏ㄧ渷" : RegionUtil.shortName(region);
+            for (int i = 0; i < metrics.length; i++) {
+                Row row = sheet.createRow(rowIdx++);
+                row.createCell(0).setCellValue(label);
+                row.createCell(1).setCellValue(metrics[i]);
+                col = 2;
+                for (int y : years) {
+                    int monthCount = (y == currentYear) ? maxMonth : 12;
+                    for (int m = 1; m <= monthCount; m++) {
+                        Double cur = midMetricValue(aggOf(data, y, m, region), i, volume);
+                        setNumeric(row, col++, cur == null ? null : round(cur, 4));
+                        Double base = midMetricValue(aggOf(data, y - 1, m, region), i, volume);
+                        setNumeric(row, col++, yoy(cur, base));
+                    }
+                    Double cum = midCumOf(data, y, region, monthCount, i, volume);
+                    setNumeric(row, col++, cum == null ? null : round(cum, 4));
+                    Double cumBase = midCumOf(data, y - 1, region, monthCount, i, volume);
+                    setNumeric(row, col++, yoy(cum, cumBase));
+                }
+            }
+        }
+        return rowIdx;
+    }
+
+    /** 涓彛寰勬寚鏍囧彇鍊硷細0=鎬婚噺 1=鍏矾鐝嚎 2-4=鍏氦/鍑虹/缃戠害杞︼紙鍩庡競瀹㈣繍妯″潡鏈帴鍏ワ紝鏆傜┖锛� */
+    private Double midMetricValue(PassengerAgg agg, int metric, boolean volume) {
+        if (agg == null) return null;
+        if (volume) {
+            switch (metric) {
+                case 0: return agg.passengerTotal / 10000.0;
+                case 1: return agg.passengerTotal / 10000.0;
+                default: return null;
+            }
+        }
+        switch (metric) {
+            case 0: return agg.turnoverTotal / 10000.0;
+            case 1: return agg.turnoverTotal / 10000.0;
+            default: return null;
+        }
+    }
+
+    private Double midCumOf(Map<Integer, Map<String, PassengerAgg>> data, int year, String region,
+                            int monthCount, int metricIdx, boolean volume) {
+        double sum = 0.0;
+        boolean has = false;
+        for (int m = 1; m <= monthCount; m++) {
+            Double v = midMetricValue(aggOf(data, year, m, region), metricIdx, volume);
+            if (v != null) {
+                sum += v;
+                has = true;
+            }
+        }
+        return has ? sum : null;
+    }
+
+    /**
+     * 涓彛寰勬帓鍚嶏細宸﹀潡=褰撴湀锛堝杩愰噺/鍛ㄨ浆閲忓強鎺掑悕/鍚屾瘮/澧為�熸帓鍚嶏級锛屽彸鍧�=1-N鏈堢疮璁★紙鍚岀粨鏋勶級
+     */
+    /**
+     * 鐢熸垚_涓彛寰勬帓鍚�.xlsx锛氬崟鍧楃疮璁★紙閫� 1 涓湀=褰撴湀锛岄�� 1-6 鏈�=绱锛夛紝RANK/SUM 鍏紡淇濈暀銆�
+     */
+    public byte[] exportPassengerMidRank(String period, String mode) throws Exception {
+        int maxMonth = monthOf(period, mode);
+        int currentYear = Integer.parseInt(period.substring(0, 4));
+        File template = resolvePassengerTemplate("鐢熸垚_涓彛寰勬帓鍚�.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            Map<Integer, Map<String, double[][]>> mid = loadMidClassMap();
+            Row title = sheet.getRow(0);
+            if (title != null && title.getCell(0) != null) {
+                if (maxMonth == 12) {
+                    title.getCell(0).setCellValue(currentYear + "骞�1-12鏈堝叏鐪佸垎甯傚窞绱瀹屾垚閬撹矾瀹㈣繍鐢熶骇鎯呭喌");
+                } else if (maxMonth == 1) {
+                    title.getCell(0).setCellValue(currentYear + "骞�1鏈堝叏鐪佸垎甯傚窞瀹屾垚閬撹矾瀹㈣繍鐢熶骇鎯呭喌");
+                } else {
+                    title.getCell(0).setCellValue(currentYear + "骞�1-" + maxMonth + "鏈堝叏鐪佸垎甯傚窞绱瀹屾垚閬撹矾瀹㈣繍鐢熶骇鎯呭喌");
+                }
+            }
+            // 鍏ㄧ渷琛� r3锛欱3/F3 涓� SUM 鍏紡淇濈暀锛汥3/H3 鍚屾瘮濉�硷紙0-based 鍒� 3/7锛�
+            setValOrBlank(sheet, 2, 3, yoyOf(midCumClass(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, true),
+                midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, 0, true)));
+            setValOrBlank(sheet, 2, 7, yoyOf(midCumClass(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, false),
+                midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, 0, false)));
+            // 甯傚窞琛� r4-20锛欱/F 绱鍊笺�丏/H 鍚屾瘮锛汣/E/G/I 鎺掑悕鍏紡淇濈暀
+            List<String> cities = RegionUtil.CITY_LIST;
+            for (int i = 0; i < cities.size(); i++) {
+                String city = cities.get(i);
+                int r0 = 3 + i;
+                double pass = midCumClass(mid, currentYear, city, maxMonth, 0, true);
+                double turn = midCumClass(mid, currentYear, city, maxMonth, 0, false);
+                double lastPass = midCumClass(mid, currentYear - 1, city, maxMonth, 0, true);
+                double lastTurn = midCumClass(mid, currentYear - 1, city, maxMonth, 0, false);
+                setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4));
+                setValOrBlank(sheet, r0, 3, yoyOf(pass, lastPass));
+                setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
+                setValOrBlank(sheet, r0, 7, yoyOf(turn, lastTurn));
+            }
+            recalc(wb);
+            return toBytes(wb);
+        }
+    }
+
+    /** 鍐欐暟鍊兼垨娓呯┖锛堝叕寮忓崟鍏冩牸淇濈暀涓嶅姩锛� */
+    private void setValOrBlank(Sheet sheet, int r0, int c0, Double v) {
+        Row row = sheet.getRow(r0);
+        if (row == null) return;
+        Cell c = row.getCell(c0);
+        if (c == null) return;
+        if (c.getCellType() == CellType.FORMULA) return;
+        if (v == null || v == 0.0) c.setBlank(); else c.setCellValue(v);
+    }
+
+    private Double yoyOf(double cur, double base) {
+        if (base == 0.0) return null;
+        return round((cur - base) / base, 4);
+    }
+
+    /** 涓彛寰勫垎绫荤疮璁★紙1..monthCount 鏈堟眰鍜岋級 */
+    private double midCumClass(Map<Integer, Map<String, double[][]>> mid, int year, String area, int monthCount, int k, boolean volume) {
+        double sum = 0.0;
+        boolean has = false;
+        for (int m = 1; m <= monthCount; m++) {
+            Map<String, double[][]> mm = mid.get(year * 100 + m);
+            if (mm == null) continue;
+            double[][] arr = mm.get(area);
+            if (arr == null) continue;
+            sum += volume ? arr[k][0] : arr[k][1];
+            has = true;
+        }
+        return has ? sum : 0.0;
+    }
+
+    /**
+     * 鐢熸垚_涓彛寰勫垎鏋�.xlsx锛氫互鏍蜂緥涓哄簳绋匡紝濉疮璁″杩愰噺/鍛ㄨ浆閲�/鍚屾瘮锛屽崰姣斿叕寮忎繚鐣欍��
+     */
+    public byte[] exportPassengerMidAnalysis(String period, String mode) throws Exception {
+        int maxMonth = monthOf(period, mode);
+        int currentYear = Integer.parseInt(period.substring(0, 4));
+        File template = resolvePassengerTemplate("鐢熸垚_涓彛寰勫垎鏋�.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            Map<Integer, Map<String, double[][]>> mid = loadMidClassMap();
+            Row title = sheet.getRow(0);
+            if (title != null && title.getCell(0) != null) {
+                title.getCell(0).setCellValue(currentYear + "骞�" + cumRange(maxMonth) + "涓彛寰勫杩愰噺鍙婂懆杞噺");
+            }
+            // r3=鎬� r4=鐝嚎 r5=鍏氦 r6=鍑虹 r7=缃戠害杞︼紙1-based锛夛紱鍒� B/C/F/G 濉�硷紝D/H 鍗犳瘮鍏紡淇濈暀
+            String[] names = {"鎬诲杩愰噺", "鍏矾鐝嚎", "鍩庨檯鍩庝埂鍏氦", "鍩庨檯鍩庝埂宸℃父鍑虹", "鍩庨檯鍩庝埂缃戠害杞�"};
+            int[] dims = {0, 1, 2, 3, 4};
+            for (int i = 0; i < names.length; i++) {
+                int r0 = 2 + i;
+                double pass = midCumClass(mid, currentYear, "婀栧寳鐪�", maxMonth, dims[i], true);
+                double turn = midCumClass(mid, currentYear, "婀栧寳鐪�", maxMonth, dims[i], false);
+                double lastPass = midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, dims[i], true);
+                double lastTurn = midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, dims[i], false);
+                setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4));
+                setValOrBlank(sheet, r0, 2, yoyOf(pass, lastPass));
+                setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
+                setValOrBlank(sheet, r0, 6, yoyOf(turn, lastTurn));
+            }
+            recalc(wb);
+            return toBytes(wb);
+        }
+    }
+
+    private double nvlOf(Double v) {
+        return v == null ? Double.NaN : v;
+    }
+
+    private Double numOrNull(double v) {
+        return Double.isNaN(v) ? null : v;
+    }
+
+    /** 闄嶅簭鎺掑悕锛堟棤鏁版嵁 NaN 鎺掓渶鍚庯級锛岃繑鍥炲悇涓嬫爣瀵瑰簲鍚嶆 */
+    private int[] rankValues(double[] values) {
+        Integer[] order = new Integer[values.length];
+        for (int i = 0; i < values.length; i++) order[i] = i;
+        java.util.Arrays.sort(order, (a, b) -> {
+            boolean na = Double.isNaN(values[a]);
+            boolean nb = Double.isNaN(values[b]);
+            if (na && nb) return 0;
+            if (na) return 1;
+            if (nb) return -1;
+            return Double.compare(values[b], values[a]);
+        });
+        int[] ranks = new int[values.length];
+        for (int i = 0; i < order.length; i++) ranks[order[i]] = i + 1;
+        return ranks;
+    }
+
+    // ==================== 5. 鐢熸垚_鑳借繍姹囨�昏〃锛圚204 閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉級 ====================
+
+    /** 鐕冩枡绫诲瀷鐮� -> 鍛ㄨ浆閲忓崟鑰楁姌绠楃郴鏁帮紙鍗冨厠鏍囧噯鐓ゆ姌绠楋級 */
+    private static final Map<String, Double> ENERGY_FUEL_COEFF = new HashMap<>();
+
+    static {
+        ENERGY_FUEL_COEFF.put("01", 0.73 * 1.4714);      // 姹芥补
+        ENERGY_FUEL_COEFF.put("02", 0.86 * 1.4571);      // 鏌存补
+        ENERGY_FUEL_COEFF.put("03", 1.7572);             // 鍘嬬缉澶╃劧姘�
+        ENERGY_FUEL_COEFF.put("04", 1.7572);             // 娑插寲澶╃劧姘�
+        ENERGY_FUEL_COEFF.put("07", 0.1229);             // 鐢靛姩
+        ENERGY_FUEL_COEFF.put("08", 0.3329 * 12.1951);   // 鐕冩枡鐢垫睜锛堟阿姘旓級
+    }
+
+    /** 鐕冩枡鐮� -> 姹囨�昏〃琛屽悕锛堜笌妯℃澘琛岄『搴忎竴鑷达級 */
+    private static final String[][] ENERGY_FUEL_ROWS = {
+        {"鏌存补杞�", "02"},
+        {"姹芥补杞�", "01"},
+        {"娑插寲澶╃劧姘旇溅", "04"},
+        {"鍘嬬缉澶╃劧姘旇溅", "03"},
+        {"绾數鍔ㄨ溅", "07"},
+        {"鐕冩枡鐢垫睜杞�", "08"}
+    };
+
+    /**
+     * 鑳借繍姹囨�昏〃锛氭寜鐕冩枡绫诲瀷鍒嗙粍姹囨�伙紝鍏ㄧ渷 + 鍚勫競宸炲悇涓�寮� sheet
+     */
+    public byte[] exportEnergySummary(String period) throws Exception {
+        List<EnergyVehicleQuarterly> list = energyMapper.selectList(
+            new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+                .eq(EnergyVehicleQuarterly::getReportPeriod, period));
+        int quarter = (parseMonth(period) + 2) / 3;
+        String[] QUARTER_CN = {"涓�", "浜�", "涓�", "鍥�"};
+        String title = period.substring(0, 4) + "骞寸" + QUARTER_CN[quarter - 1] + "瀛e害鑳借�楁眹鎬绘儏鍐佃〃";
+
+        Map<String, FuelAgg> province = new HashMap<>();
+        Map<String, Map<String, FuelAgg>> cityMap = new HashMap<>();
+        for (EnergyVehicleQuarterly e : list) {
+            String code = e.getFuelTypeCode();
+            if (code == null || code.trim().isEmpty()) continue;
+            province.computeIfAbsent(code.trim(), k -> new FuelAgg()).add(e);
+            String city = RegionUtil.cityByCode(e.getRegionCode());
+            if (city != null) {
+                cityMap.computeIfAbsent(city, k -> new HashMap<>())
+                    .computeIfAbsent(code.trim(), k2 -> new FuelAgg()).add(e);
+            }
+        }
+
+        // 浠� 妯℃澘_鑳借繍姹囨�昏〃 .xlsx 涓哄簳绋匡細淇濈暀鏍囬/琛ㄥご/鍚堝苟/鍏紡/鍒楀/鏍峰紡锛屼粎鏇挎崲鏁版嵁鍖烘暟鍊�
+        File template = resolveAnyTemplate(energyTemplateDir, "鐢熸垚_鑳借繍姹囨�昏〃.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet base = wb.getSheetAt(0);
+            base.getRow(0).getCell(0).setCellValue(title);
+            fillEnergyTemplateSheet(base, province);
+            for (String city : RegionUtil.cityList()) {
+                Map<String, FuelAgg> agg = cityMap.get(city);
+                if (agg == null || agg.isEmpty()) continue;
+                Sheet s = wb.cloneSheet(0);
+                wb.setSheetName(wb.getSheetIndex(s), RegionUtil.shortName(city) + "鑳借�楁眹鎬�");
+                s.getRow(0).getCell(0).setCellValue(title);
+                fillEnergyTemplateSheet(s, agg);
+            }
+            recalc(wb);
+            return toBytes(wb);
+        }
+    }
+
+    /** 鑳借�楁眹鎬绘ā鏉垮~鍏咃細1-based 绗�3~8琛屼负 鏌存补/姹芥补/娑插寲澶╃劧/鍘嬬缉澶╃劧/绾數/鐕冩枡鐢垫睜锛汥/J/L/M 涓哄叕寮忓垪锛屼粎濉暟鍊煎垪 */
+    private void fillEnergyTemplateSheet(Sheet sheet, Map<String, FuelAgg> data) {
+        for (int i = 0; i < ENERGY_FUEL_ROWS.length; i++) {
+            Row row = sheet.getRow(2 + i);
+            if (row == null) row = sheet.createRow(2 + i);
+            FuelAgg agg = data.get(ENERGY_FUEL_ROWS[i][1]);
+            if (agg == null || agg.count == 0) {
+                setEnergyValue(row, 1, null);
+                setEnergyValue(row, 2, null);
+                setEnergyValue(row, 4, null);
+                setEnergyValue(row, 5, null);
+                setEnergyValue(row, 6, null);
+                setEnergyValue(row, 7, null);
+                setEnergyValue(row, 8, null);
+                setEnergyValue(row, 10, null);
+                continue;
+            }
+            setEnergyValue(row, 1, (double) agg.count);
+            setEnergyValue(row, 2, agg.totalTonnage);
+            setEnergyValue(row, 4, agg.totalMileage);
+            setEnergyValue(row, 5, agg.loadedMileage);
+            setEnergyValue(row, 6, agg.emptyMileage);
+            setEnergyValue(row, 7, agg.freight);
+            setEnergyValue(row, 8, agg.turnover);
+            setEnergyValue(row, 10, agg.fuelConsumption);
+        }
+    }
+
+    /** 鍐欒兘鑰楁暟鍊煎垪锛氭湁鍊煎啓鍏ワ紝鏃犲�兼竻绌烘ā鏉挎牱渚嬶紱鍏紡鍒楋紙D/J/L/M锛変笉鍔� */
+    private void setEnergyValue(Row row, int colIdx, Double v) {
+        Cell c = row.getCell(colIdx);
+        if (c != null && c.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) return;
+        if (c == null) c = row.createCell(colIdx);
+        if (v == null) c.setBlank(); else c.setCellValue(v);
+    }
+
+    /** 鑳借�楁眹鎬伙細鎸夌噧鏂欑被鍨嬭仛鍚� */
+    private static class FuelAgg {
+        int count;
+        double totalTonnage;
+        double totalMileage;
+        double loadedMileage;
+        double emptyMileage;
+        double freight;
+        double turnover;
+        double fuelConsumption;
+
+        void add(EnergyVehicleQuarterly e) {
+            count++;
+            totalTonnage += nz(e.getMarkedTonnage());
+            totalMileage += nz(e.getTotalMileage());
+            loadedMileage += nz(e.getLoadedMileage());
+            emptyMileage += nz(e.getEmptyMileage());
+            freight += nz(e.getFreight());
+            turnover += nz(e.getTurnover());
+            fuelConsumption += nz(e.getFuelConsumption());
+        }
+
+        private double nz(Double v) {
+            return v == null ? 0.0 : v;
+        }
+    }
+
     // ==================== 閫氱敤杈呭姪 ====================
 
     private int parseMonth(String period) {
         return Integer.parseInt(period.split("-")[1]);
+    }
+
+    private int monthOf(String period, String mode) {
+        return "year".equalsIgnoreCase(mode) ? 12 : parseMonth(period);
+    }
+
+    /** 绱鍖洪棿鏂囨锛�1 鏈堟樉绀� "1鏈�"锛屽叾浣欐樉绀� "1-N鏈�"锛堥伩鍏嶅嚭鐜� "1-1鏈�"锛� */
+    private String cumRange(int month) {
+        return month <= 1 ? "1鏈�" : "1-" + month + "鏈�";
+    }
+
+    /** 鏃呭鍒嗗競宸�/涓彛寰勬槑缁嗘ā鏉胯〃澶村勾浠藉姩鎬佸寲锛氭妸 "2026骞�1鏈�"鈥�"2026骞�6鏈�"銆�"2026骞寸疮璁�" 鐨勫勾浠芥崲鎴愭墍閫夊勾 */
+    private void fixPassengerHeaderYear(Sheet sheet, int year) {
+        java.util.regex.Pattern p = java.util.regex.Pattern.compile("^\\d{4}骞�");
+        for (int r = 1; r <= 3; r++) {
+            Row row = sheet.getRow(r);
+            if (row == null) continue;
+            for (Cell cell : row) {
+                if (cell.getCellType() != CellType.STRING) continue;
+                String v = cell.getStringCellValue();
+                if (v == null || v.isEmpty()) continue;
+                String nv = p.matcher(v).replaceFirst(year + "骞�");
+                if (!nv.equals(v)) cell.setCellValue(nv);
+            }
+        }
     }
 
     private String pad(int m) {
@@ -808,6 +2501,1372 @@
         sheet.setColumnWidth(1, 22 * 256);
     }
 
+
+    // ==================== 鍩庡競瀹㈣繍锛堝叕浜わ級鍒嗗競宸炴槑缁� ====================
+
+    /** 鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堜互 鍩庡競鍏氦_妯℃澘.xlsx 涓哄簳绋匡紝淇濈暀妯℃澘鏍煎紡涓庡叕寮忥級 */
+    public byte[] exportCityBusDetail(String period, String mode) throws Exception {
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, mode);
+        return exportCityByTemplate("瀵煎叆妯℃澘_鍩庡競鍏氦.xlsx", period, mode,
+            loadCityBusByMonth(year + "-", month),
+            loadCityBusByMonth((year - 1) + "-", month));
+    }
+    /** 鍔犺浇鏌愬勾 1..limit 鏈堝煄甯傚叕浜や紒涓氭暟鎹紝鎸� 鏈� 鈫� 甯傚窞 鈫� [瀹㈣繍閲�,鍛ㄨ浆閲�,鍩庡競鍐呭杩愰噺,鍩庡競鍐呭懆杞噺] 姹囨�伙紱"鍏ㄧ渷" 涓哄叏閮ㄥ競宸炲悎璁� */
+    private Map<Integer, Map<String, double[]>> loadCityBusByMonth(String yearPrefix, int limit) {
+        Map<Integer, Map<String, double[]>> monthCity = new java.util.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();
+            Map<String, double[]> cityMap = monthCity.computeIfAbsent(m, k -> new HashMap<>());
+            double[] arr = cityMap.computeIfAbsent(city, k -> new double[4]);
+            arr[0] += nz(r.getPassengerVolume());
+            arr[1] += nz(r.getTurnover());
+            arr[2] += nz(r.getPassengerCity());
+            arr[3] += nz(r.getTurnoverCity());
+            double[] prov = cityMap.computeIfAbsent("鍏ㄧ渷", k -> new double[4]);
+            prov[0] += nz(r.getPassengerVolume());
+            prov[1] += nz(r.getTurnover());
+            prov[2] += nz(r.getPassengerCity());
+            prov[3] += nz(r.getTurnoverCity());
+        }
+        return monthCity;
+    }
+
+    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 (Exception e) {
+            return 0;
+        }
+    }
+
+    private double cityBusVal(Map<Integer, Map<String, double[]>> monthCity, String area, int m, int idx) {
+        Map<String, double[]> cityMap = monthCity.get(m);
+        if (cityMap == null) return 0;
+        double[] arr = cityMap.get(area);
+        if (arr == null) return 0;
+        return idx < arr.length ? arr[idx] : 0;
+    }
+
+    // ==================== 鍩庡競瀹㈣繍锛堝贰娓稿嚭绉燂級鍒嗗競宸炴槑缁� ====================
+
+    /** 鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堜互 宸℃父鍑虹_妯℃澘.xlsx 涓哄簳绋匡紝淇濈暀妯℃澘鏍煎紡涓庡叕寮忥級 */
+    public byte[] exportCityTaxiDetail(String period, String mode) throws Exception {
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, mode);
+        return exportCityByTemplate("瀵煎叆妯℃澘_宸℃父鍑虹.xlsx", period, mode,
+            loadCityTaxiByMonth(year + "-", month),
+            loadCityTaxiByMonth((year - 1) + "-", month));
+    }
+    /** 鍔犺浇鏌愬勾 1..limit 鏈堝贰娓稿嚭绉熸暟鎹紝鎸� 鏈� 鈫� 甯傚窞 鈫� [瀹㈣繍閲�,鍛ㄨ浆閲�,鍩庡競鍐呭杩愰噺,鍩庡競鍐呭懆杞噺] 姹囨�伙紱"鍏ㄧ渷" 涓哄叏閮ㄥ競宸炲悎璁� */
+    private Map<Integer, Map<String, double[]>> loadCityTaxiByMonth(String yearPrefix, int limit) {
+        Map<Integer, Map<String, double[]>> monthCity = new java.util.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();
+            Map<String, double[]> cityMap = monthCity.computeIfAbsent(m, k -> new HashMap<>());
+            double[] arr = cityMap.computeIfAbsent(city, k -> new double[4]);
+            arr[0] += nz(r.getPassengerVolume());
+            arr[1] += nz(r.getTurnover());
+            arr[2] += nz(r.getPassengerCity());
+            arr[3] += nz(r.getTurnoverCity());
+            double[] prov = cityMap.computeIfAbsent("鍏ㄧ渷", k -> new double[4]);
+            prov[0] += nz(r.getPassengerVolume());
+            prov[1] += nz(r.getTurnover());
+            prov[2] += nz(r.getPassengerCity());
+            prov[3] += nz(r.getTurnoverCity());
+        }
+        return monthCity;
+    }
+
+    private double cityTaxiVal(Map<Integer, Map<String, double[]>> monthCity, String area, int m, int idx) {
+        Map<String, double[]> cityMap = monthCity.get(m);
+        if (cityMap == null) return 0;
+        double[] arr = cityMap.get(area);
+        if (arr == null) return 0;
+        return idx < arr.length ? arr[idx] : 0;
+    }
+
+    // ==================== 鍩庡競瀹㈣繍锛堣建閬�/杞浮锛夊垎甯傚窞鏄庣粏 ====================
+
+    /** 鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堜互 杞ㄩ亾銆佽疆娓妯℃澘.xlsx 涓哄簳绋匡紝杞ㄩ亾=鍏ㄧ渷/姝︽眽/榛勭煶锛岃疆娓�=鍏ㄧ渷/姝︽眽锛� */
+    public byte[] exportCityRailFerryDetail(String period, String mode) throws Exception {
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, mode);
+        Map<Integer, Map<String, double[]>> monthCity = loadCityRailFerryByMonth(year + "-", month);
+        File template = resolveTemplate("瀵煎叆妯℃澘_杞ㄩ亾銆佽疆娓�.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            dynamicTitle(sheet, year, month); // 鏍囬鎸夋姤琛ㄦ湡鍔ㄦ�佸寲锛堜笉鍐欐骞翠唤/鏈堜唤锛�
+            // 杞ㄩ亾锛氬杩愰噺琛� 4(鍏ㄧ渷)/6(姝︽眽)/8(榛勭煶)锛屽懆杞噺琛屼负 +1锛涙寚鏍� 0=杞ㄩ亾瀹㈣繍閲� 1=杞ㄩ亾鍛ㄨ浆閲�
+            fillRailFerryBlock(sheet, monthCity, month, new String[][]{{"鍏ㄧ渷", "3"}, {"姝︽眽甯�", "5"}, {"榛勭煶甯�", "7"}}, 0);
+            // 杞浮锛氬杩愰噺琛� 13(鍏ㄧ渷)/15(姝︽眽)锛涙寚鏍� 2=杞浮瀹㈣繍閲� 3=杞浮鍛ㄨ浆閲�
+            fillRailFerryBlock(sheet, monthCity, month, new String[][]{{"鍏ㄧ渷", "12"}, {"姝︽眽甯�", "14"}}, 2);
+            recalc(wb);
+            return toBytes(wb);
+        }
+    }
+
+    /** 鍔犺浇鏌愬勾 1..limit 鏈堣建閬�/杞浮鏁版嵁锛氭湀 鈫� 甯傚窞 鈫� [杞ㄩ亾瀹㈣繍閲�,杞ㄩ亾鍛ㄨ浆閲�,杞浮瀹㈣繍閲�,杞浮鍛ㄨ浆閲廬锛屽惈"鍏ㄧ渷" */
+    private Map<Integer, Map<String, double[]>> loadCityRailFerryByMonth(String yearPrefix, int limit) {
+        Map<Integer, Map<String, double[]>> monthCity = new java.util.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();
+            Map<String, double[]> cityMap = monthCity.computeIfAbsent(m, k -> new HashMap<>());
+            double[] arr = cityMap.computeIfAbsent(city, k -> new double[4]);
+            arr[0] += nz(r.getRailPassengerVolume());
+            arr[1] += nz(r.getRailTurnover());
+            arr[2] += nz(r.getFerryPassengerVolume());
+            arr[3] += nz(r.getFerryTurnover());
+            double[] prov = cityMap.computeIfAbsent("鍏ㄧ渷", k -> new double[4]);
+            prov[0] += nz(r.getRailPassengerVolume());
+            prov[1] += nz(r.getRailTurnover());
+            prov[2] += nz(r.getFerryPassengerVolume());
+            prov[3] += nz(r.getFerryTurnover());
+        }
+        return monthCity;
+    }
+
+    /** 濉厖杞ㄩ亾/杞浮鍧楋細baseIdx 涓哄杩愰噺鎸囨爣锛屽懆杞噺鎸囨爣 = baseIdx+1锛�2026 鏈堝害鍒� C..O锛�2025 鍒� T.. 鏃犳暟鎹垯娓呯┖ */
+    private void fillRailFerryBlock(Sheet sheet, Map<Integer, Map<String, double[]>> monthCity, int month,
+                                    String[][] areaRows, int baseIdx) {
+        for (String[] ar : areaRows) {
+            String area = ar[0];
+            int paxRow = Integer.parseInt(ar[1]);
+            for (int m = 1; m <= month; m++) {
+                setDataCell(sheet, paxRow, 2 + (m - 1) * 2, railFerryVal(monthCity, area, m, baseIdx));
+                setDataCell(sheet, paxRow + 1, 2 + (m - 1) * 2, railFerryVal(monthCity, area, m, baseIdx + 1));
+            }
+            for (int m = 1; m <= 12; m++) {
+                setDataCell(sheet, paxRow, 19 + (m - 1) * 2, 0);
+                setDataCell(sheet, paxRow + 1, 19 + (m - 1) * 2, 0);
+            }
+        }
+    }
+
+    private double railFerryVal(Map<Integer, Map<String, double[]>> monthCity, String area, int m, int idx) {
+        Map<String, double[]> cityMap = monthCity.get(m);
+        if (cityMap == null) return 0;
+        double[] arr = cityMap.get(area);
+        if (arr == null) return 0;
+        return idx < arr.length ? arr[idx] : 0;
+    }
+
+    /** 鍐欐暟鎹崟鍏冩牸锛氬師涓哄叕寮忓垯淇濈暀锛堝叏鐪佹眰鍜�/鎺掑悕鍏紡锛夛紱鍚﹀垯鏈夊�煎啓鍏ャ�佹棤鍊兼竻绌烘ā鏉挎牱渚� */
+    private void setDataCell(Sheet sheet, int rowIdx, int colIdx, double v) {
+        Row row = sheet.getRow(rowIdx);
+        if (row == null) row = sheet.createRow(rowIdx);
+        Cell c = row.getCell(colIdx);
+        if (c != null && c.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) return;
+        if (c == null) c = row.createCell(colIdx);
+        if (v == 0) c.setBlank(); else c.setCellValue(round(v, 2));
+    }
+
+    /** 鏄惧紡鍐欏叆 0锛坰etDataCell 瀵� 0 缃┖锛涙眹鎬昏〃缃戠害杞﹁闇�瑕佹樉绀� 0锛� */
+    private void setZeroCell(Sheet sheet, int rowIdx, int colIdx) {
+        Row row = sheet.getRow(rowIdx);
+        if (row == null) row = sheet.createRow(rowIdx);
+        Cell c = row.getCell(colIdx);
+        if (c != null && c.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) return;
+        if (c == null) c = row.createCell(colIdx);
+        c.setCellValue(0.0);
+    }
+    /** 閲嶇畻鍏紡缂撳瓨鍊硷紙淇濈暀鍏紡锛屾眰鍊煎紓甯稿拷鐣ワ級 */
+    private void recalc(XSSFWorkbook wb) {
+        try {
+            wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
+        } catch (Exception e) {
+            log.warn("鍩庡競瀹㈣繍妯℃澘鍏紡姹傚�煎け璐�: {}", e.getMessage());
+        }
+    }
+
+    // ==================== 鍩庡競瀹㈣繍 鍒嗗競宸炵疮璁★紙鍩庡競鍒嗗競宸瀇妯℃澘 / 鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃_妯℃澘锛� ====================
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx锛堜互 鍩庡競鍒嗗競宸瀇妯℃澘.xlsx / 鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃_妯℃澘.xlsx 涓哄簳绋匡紱缃戠害杞﹀垪鏃犳簮鏁版嵁鐣欑┖锛� */
+    public byte[] exportCityPassengerCitySum(String templateFileName, String period, String mode) throws Exception {
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, mode);
+        Map<String, double[]> cum = loadCityPassengerCumulative(year + "-", month);
+        File template = resolveTemplate(templateFileName);
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            dynamicTitle(sheet, year, month); // 鏍囬鎸夋姤琛ㄦ湡鍔ㄦ�佸寲
+            List<String> areas = new java.util.ArrayList<>();
+            areas.add("鍏ㄧ渷");
+            areas.addAll(RegionUtil.cityList());
+            fillCitySumBlock(sheet, cum, areas, 4, 0);   // 瀹㈣繍閲忓尯锛堝崟浣嶏細涓囦汉娆★紝POI 0 鍩猴級
+            fillCitySumBlock(sheet, cum, areas, 26, 1);  // 鍛ㄨ浆閲忓尯锛堝崟浣嶏細涓囦汉娆″叕閲岋紝POI 0 鍩猴級
+            recalc(wb);
+            return toBytes(wb);
+        }
+    }
+
+    /** 濉厖涓�涓疮璁″尯锛歴tartRow 璧锋瘡鍦板尯 1 琛岋紱paxTurnIdx=0 鐢ㄥ杩愰噺銆�1 鐢ㄥ懆杞噺 */
+    private void fillCitySumBlock(Sheet sheet, Map<String, double[]> cum, List<String> areas, int startRow, int paxTurnIdx) {
+        int rowIdx = startRow;
+        for (String area : areas) {
+            double[] v = cum.get(area);
+            double bus = v == null ? 0 : v[paxTurnIdx == 0 ? 0 : 1];
+            double taxi = v == null ? 0 : v[paxTurnIdx == 0 ? 2 : 3];
+            double rail = v == null ? 0 : v[paxTurnIdx == 0 ? 4 : 5];
+            double ferry = v == null ? 0 : v[paxTurnIdx == 0 ? 6 : 7];
+            double total = bus + taxi + rail + ferry; // 缃戠害杞︽棤婧愭暟鎹紝涓嶈
+            setDataCell(sheet, rowIdx, 1, total);   // B 鎬荤疮璁�
+            setDataCell(sheet, rowIdx, 6, bus);     // G 鍏氦
+            setDataCell(sheet, rowIdx, 11, taxi);   // L 鍑虹
+            setDataCell(sheet, rowIdx, 16, 0);      // Q 缃戠害杞︼紙鏃犳簮鏁版嵁鈫掓竻绌烘牱渚嬶級
+            setDataCell(sheet, rowIdx, 21, rail);   // V 杞ㄩ亾
+            setDataCell(sheet, rowIdx, 23, ferry);  // X 杞浮
+            // 澧為�熷垪 E/J/O/T/W/Y锛氭棤鍘诲勾鍚屾湡绱 鈫� 娓呯┖妯℃澘鏍蜂緥锛堟帓鍚�/鍗犳瘮鍏紡淇濈暀锛�
+            setDataCell(sheet, rowIdx, 4, 0);
+            setDataCell(sheet, rowIdx, 9, 0);
+            setDataCell(sheet, rowIdx, 14, 0);
+            setDataCell(sheet, rowIdx, 19, 0);
+            setDataCell(sheet, rowIdx, 22, 0);
+            setDataCell(sheet, rowIdx, 24, 0);
+            rowIdx++;
+        }
+    }
+
+    /** 鏌愬勾 1..limit 鏈堝煄甯傚杩愮疮璁★細city 鈫� [鍏氦瀹㈣繍閲�,鍏氦鍛ㄨ浆閲�,鍑虹瀹㈣繍閲�,鍑虹鍛ㄨ浆閲�,杞ㄩ亾瀹㈣繍閲�,杞ㄩ亾鍛ㄨ浆閲�,杞浮瀹㈣繍閲�,杞浮鍛ㄨ浆閲廬锛屽惈"鍏ㄧ渷" */
+    private Map<String, double[]> loadCityPassengerCumulative(String yearPrefix, int limit) {
+        Map<String, double[]> cum = new HashMap<>();
+        for (CityBusMonthly r : cityBusMapper.selectList(
+                new LambdaQueryWrapper<CityBusMonthly>().likeRight(CityBusMonthly::getReportPeriod, yearPrefix))) {
+            int m = monthOf(r.getReportPeriod());
+            if (m < 1 || m > limit) continue;
+            String city = r.getCity() == null ? "鏈煡" : r.getCity();
+            double[] a = cum.computeIfAbsent(city, k -> new double[8]);
+            a[0] += nz(r.getPassengerVolume());
+            a[1] += nz(r.getTurnover());
+            a[4] += nz(r.getRailPassengerVolume());
+            a[5] += nz(r.getRailTurnover());
+            a[6] += nz(r.getFerryPassengerVolume());
+            a[7] += nz(r.getFerryTurnover());
+            double[] p = cum.computeIfAbsent("鍏ㄧ渷", k -> new double[8]);
+            p[0] += nz(r.getPassengerVolume());
+            p[1] += nz(r.getTurnover());
+            p[4] += nz(r.getRailPassengerVolume());
+            p[5] += nz(r.getRailTurnover());
+            p[6] += nz(r.getFerryPassengerVolume());
+            p[7] += nz(r.getFerryTurnover());
+        }
+        for (CityTaxiMonthly r : cityTaxiMapper.selectList(
+                new LambdaQueryWrapper<CityTaxiMonthly>().likeRight(CityTaxiMonthly::getReportPeriod, yearPrefix))) {
+            int m = monthOf(r.getReportPeriod());
+            if (m < 1 || m > limit) continue;
+            String city = r.getCity() == null ? "鏈煡" : r.getCity();
+            double[] a = cum.computeIfAbsent(city, k -> new double[8]);
+            a[2] += nz(r.getPassengerVolume());
+            a[3] += nz(r.getTurnover());
+            double[] p = cum.computeIfAbsent("鍏ㄧ渷", k -> new double[8]);
+            p[2] += nz(r.getPassengerVolume());
+            p[3] += nz(r.getTurnover());
+        }
+        return cum;
+    }
+
+    // ==================== 鍩庡競瀹㈣繍 鍏ㄧ渷姹囨�伙紙鍩庡競姹囨�籣妯℃澘锛� ====================
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx锛堜互 鍩庡競姹囨�籣妯℃澘.xlsx 涓哄簳绋匡紱2026 娈靛~搴撳唴 1..N 鏈堢疮璁★紝鍚屾瘮娓呯┖锛�2025/2024 娈典繚鐣欐ā鏉垮巻鍙插弬鑰冨�硷級 */
+    public byte[] exportCityPassengerSummary(String period, String mode) throws Exception {
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, mode);
+        double[] prov = loadCityPassengerCumulative(year + "-", month).getOrDefault("鍏ㄧ渷", new double[8]);
+        double busPax = prov[0], busTurn = prov[1];
+        double taxiPax = prov[2], taxiTurn = prov[3];
+        double railPax = prov[4], railTurn = prov[5];
+        double ferryPax = prov[6], ferryTurn = prov[7];
+        File template = resolveTemplate("瀵煎叆妯℃澘_鍩庡競姹囨��.xlsx");
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            dynamicTitle(sheet, year, month); // 鏍囬鎸夋姤琛ㄦ湡鍔ㄦ�佸寲
+            // 琛�3 鎬诲杩愰噺=鍏氦+鍑虹杞�(宸℃父+缃戠害杞�)+杞ㄩ亾+杞浮锛涜4 鍏氦锛涜5 鍑虹杞�=宸℃父+缃戠害杞︼紱琛�6 宸℃父锛涜7 缃戠害杞︼紱琛�8 杞ㄩ亾锛涜9 杞浮
+            setDataCell(sheet, 2, 1, busPax + taxiPax + railPax + ferryPax); // B3锛圥OI 0 鍩鸿2锛�
+            setDataCell(sheet, 2, 3, busTurn + taxiTurn + railTurn + ferryTurn); // D3
+            setDataCell(sheet, 3, 1, busPax);   // B4 鍩庡競鍏氦
+            setDataCell(sheet, 3, 3, busTurn);  // D4
+            setDataCell(sheet, 4, 1, taxiPax);  // B5 鍩庡競鍑虹杞︼紙缃戠害杞︽棤婧愭暟鎹級
+            setDataCell(sheet, 4, 3, taxiTurn); // D5
+            setDataCell(sheet, 5, 1, taxiPax);  // B6 鍏朵腑宸℃父鍑虹
+            setDataCell(sheet, 5, 3, taxiTurn); // D6
+            setZeroCell(sheet, 6, 1);           // B7 鍩庡競缃戠害杞︼紙鏄惧紡 0锛宻etDataCell 瀵� 0 浼氱疆绌猴級
+            setZeroCell(sheet, 6, 3);           // D7
+            setDataCell(sheet, 7, 1, railPax);  // B8 杞ㄩ亾
+            setDataCell(sheet, 7, 3, railTurn); // D8
+            setDataCell(sheet, 8, 1, ferryPax); // B9 杞浮
+            setDataCell(sheet, 8, 3, ferryTurn);// D9
+            for (int r = 2; r <= 8; r++) {      // 鍚屾瘮 C/E锛氭棤鍘诲勾鍚屾湡 鈫� 娓呯┖鏍蜂緥
+                setDataCell(sheet, r, 2, 0);
+                setDataCell(sheet, r, 4, 0);
+            }
+            recalc(wb);
+            return toBytes(wb);
+        }
+    }
+
+    // ==================== 鍩庡競瀹㈣繍 鎶ヨ〃绯诲垪锛堜竴閿墦鍖咃級 ====================
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍鎶ヨ〃绯诲垪.zip锛氬叕浜�/鍑虹/杞ㄩ亾杞浮鏄庣粏 + 鍒嗗競宸炵疮璁� + 鍚勫競宸炴槑缁嗚〃 + 姹囨�� */
+    public byte[] exportCityPassengerSeries(String period, String mode) throws Exception {
+        java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream();
+        try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) {
+            addZipEntry(zos, "鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", exportCityBusDetail(period, mode));
+            addZipEntry(zos, "鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", exportCityTaxiDetail(period, mode));
+            addZipEntry(zos, "鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", exportCityRailFerryDetail(period, mode));
+            addZipEntry(zos, "鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx", exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競鍒嗗競宸�.xlsx", period, mode));
+            addZipEntry(zos, "鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period, mode));
+            addZipEntry(zos, "鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx", exportCityPassengerSummary(period, mode));
+        }
+        return bos.toByteArray();
+    }
+
+    /** 鐢熸垚_璐ц繍鎶ヨ〃绯诲垪.zip锛氳揣杩愰噺鍒嗗競宸炴槑缁� + 璐ц繍閲忔帓鍚� + 鍛ㄨ浆閲忔帓鍚� */
+    public byte[] exportFreightSeries(String period, String mode) throws Exception {
+        java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream();
+        try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) {
+            addZipEntry(zos, "鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx", exportCityDetail(period, mode));
+            addZipEntry(zos, "鐢熸垚_璐ц繍閲忔帓鍚�.xlsx", exportFreightRank(period, mode));
+            addZipEntry(zos, "鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx", exportTurnoverRank(period, mode));
+        }
+        return bos.toByteArray();
+    }
+
+    /** 鐢熸垚_鍏矾鏃呭鎶ヨ〃绯诲垪.zip锛氬垎甯傚窞鏄庣粏 + 涓彛寰勬槑缁�/鎺掑悕/鍒嗘瀽 */
+    public byte[] exportPassengerSeries(String period, String mode) throws Exception {
+        java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream();
+        try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) {
+            addZipEntry(zos, "鐢熸垚_鍏矾鏃呭鍒嗗競宸�.xlsx", exportPassengerCityDetail(period, mode));
+            addZipEntry(zos, "鐢熸垚_涓彛寰勬槑缁�.xlsx", exportPassengerMidDetail(period, mode));
+            addZipEntry(zos, "鐢熸垚_涓彛寰勬帓鍚�.xlsx", exportPassengerMidRank(period, mode));
+            addZipEntry(zos, "鐢熸垚_涓彛寰勫垎鏋�.xlsx", exportPassengerMidAnalysis(period, mode));
+        }
+        return bos.toByteArray();
+    }
+
+    /** 鐢熸垚_鎶曡祫鎶ヨ〃绯诲垪.zip锛氶瀹夋帓 + 鍗佷簲浜� + 浜垮厓 + 缁忔祹寮哄幙 */
+    public byte[] exportInvestSeries(String period, String mode) throws Exception {
+        java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream();
+        try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) {
+            addZipEntry(zos, "鐢熸垚_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls", investPlan(period, mode));
+            addZipEntry(zos, "鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls", investFiveYearLogistics(period, mode));
+            addZipEntry(zos, "鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls", investBillion(period, mode));
+            addZipEntry(zos, "鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls", investCounty(period, mode));
+        }
+        return bos.toByteArray();
+    }
+
+    /** 鐢熸垚_鎵�閫夋姤琛�.zip锛氭寜 type 鍒楄〃鎵撳寘锛堝墠绔�"鐢熸垚閫変腑"澶氶�夋椂鐢級 */
+    public byte[] exportSelectedSeries(String period, String mode, List<String> types) throws Exception {
+        java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream();
+        try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) {
+            if (types != null) {
+                for (String t : types) {
+                    if (t == null || t.trim().isEmpty()) continue;
+                    byte[] data = selectedReportData(period, mode, t);
+                    if (data != null) {
+                        String name = REPORT_FILE_NAMES.get(t);
+                        if (name == null) name = "鐢熸垚_" + t + ".xlsx";
+                        addZipEntry(zos, name, data);
+                    }
+                }
+            }
+        }
+        return bos.toByteArray();
+    }
+
+    private byte[] selectedReportData(String period, String mode, String type) throws Exception {
+        switch (type) {
+            case "cityDetail": return exportCityDetail(period, mode);
+            case "freightRank": return exportFreightRank(period, mode);
+            case "turnoverRank": return exportTurnoverRank(period, mode);
+            case "passengerCityDetail": return exportPassengerCityDetail(period, mode);
+            case "passengerMidDetail": return exportPassengerMidDetail(period, mode);
+            case "passengerMidRank": return exportPassengerMidRank(period, mode);
+            case "passengerMidAnalysis": return exportPassengerMidAnalysis(period, mode);
+            case "energySummary": return exportEnergySummary(period);
+            case "investPlan": return investPlan(period, mode);
+            case "investFiveYearLogistics": return investFiveYearLogistics(period, mode);
+            case "investBillion": return investBillion(period, mode);
+            case "investCounty": return investCounty(period, mode);
+            case "cityBusDetail": return exportCityBusDetail(period, mode);
+            case "cityTaxiDetail": return exportCityTaxiDetail(period, mode);
+            case "cityRailFerryDetail": return exportCityRailFerryDetail(period, mode);
+            case "cityPassengerCitySum": return exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競鍒嗗競宸�.xlsx", period, mode);
+            case "cityPassengerDetailSum": return exportCityPassengerCitySum("瀵煎叆妯℃澘_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period, mode);
+            case "cityPassengerSummary": return exportCityPassengerSummary(period, mode);
+            default: return null;
+        }
+    }
+
+    private static final Map<String, String> REPORT_FILE_NAMES = new HashMap<>();
+    static {
+        REPORT_FILE_NAMES.put("cityDetail", "鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx");
+        REPORT_FILE_NAMES.put("freightRank", "鐢熸垚_璐ц繍閲忔帓鍚�.xlsx");
+        REPORT_FILE_NAMES.put("turnoverRank", "鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx");
+        REPORT_FILE_NAMES.put("passengerCityDetail", "鐢熸垚_鍏矾鏃呭鍒嗗競宸炴槑缁�.xlsx");
+        REPORT_FILE_NAMES.put("passengerMidDetail", "鐢熸垚_鍏矾鏃呭涓彛寰勬槑缁�.xlsx");
+        REPORT_FILE_NAMES.put("passengerMidRank", "鐢熸垚_鍏矾鏃呭涓彛寰勬帓鍚�.xlsx");
+        REPORT_FILE_NAMES.put("passengerMidAnalysis", "鐢熸垚_鍏矾鏃呭涓彛寰勫垎鏋�.xlsx");
+        REPORT_FILE_NAMES.put("energySummary", "鐢熸垚_鑳借繍姹囨�昏〃.xlsx");
+        REPORT_FILE_NAMES.put("investPlan", "鐢熸垚_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls");
+        REPORT_FILE_NAMES.put("investFiveYearLogistics", "鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls");
+        REPORT_FILE_NAMES.put("investBillion", "鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls");
+        REPORT_FILE_NAMES.put("investCounty", "鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls");
+        REPORT_FILE_NAMES.put("cityBusDetail", "鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
+        REPORT_FILE_NAMES.put("cityTaxiDetail", "鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
+        REPORT_FILE_NAMES.put("cityRailFerryDetail", "鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
+        REPORT_FILE_NAMES.put("cityPassengerCitySum", "鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx");
+        REPORT_FILE_NAMES.put("cityPassengerDetailSum", "鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx");
+        REPORT_FILE_NAMES.put("cityPassengerSummary", "鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx");
+    }
+
+    private void addZipEntry(java.util.zip.ZipOutputStream zos, String name, byte[] data) throws Exception {
+        zos.putNextEntry(new java.util.zip.ZipEntry(name));
+        zos.write(data);
+        zos.closeEntry();
+    }
+
+    /** 鍩庡競瀹㈣繍妯℃澘濉厖锛氫互鐩綍涓嬫ā鏉夸负搴曠锛屼繚鐣欐爣棰�/琛ㄥご/鍚堝苟/鍏紡/鏍峰紡锛屼粎鏇挎崲鏁版嵁鍖哄競宸炴湀搴﹀�� */
+    public byte[] exportCityByTemplate(String templateFileName, String period, String mode,
+                                       Map<Integer, Map<String, double[]>> monthCity,
+                                       Map<Integer, Map<String, double[]>> lastYearMonthCity) throws Exception {
+        int month = monthOf(period, mode);
+        int year = Integer.parseInt(period.split("-")[0]);
+        File template = resolveTemplate(templateFileName);
+        try (InputStream in = new FileInputStream(template);
+             XSSFWorkbook wb = new XSSFWorkbook(in)) {
+            Sheet sheet = wb.getSheetAt(0);
+            dynamicTitle(sheet, year, month); // 鏍囬鎸夋姤琛ㄦ湡鍔ㄦ�佸寲锛堜笉鍐欐骞翠唤/鏈堜唤锛�
+            // 缁撴瀯瀹堝崼锛氭暟鎹尯搴斾负 鍏ㄧ渷+17甯傚窞 脳 4 鎸囨爣 = 72 琛岋紙1-based 绗�5琛岃捣锛�
+            if (sheet.getLastRowNum() < 75) {
+                throw new RuntimeException("鍩庡競瀹㈣繍妯℃澘鏁版嵁鍖鸿鏁颁笉瓒筹紝璇风‘璁ゆā鏉挎湭鏀圭増: " + templateFileName);
+            }
+            List<String> areas = new java.util.ArrayList<>();
+            areas.add("鍏ㄧ渷");
+            areas.addAll(RegionUtil.cityList());
+            // 妯℃澘鏁版嵁鍖猴細1-based 绗�5琛岃捣锛屾瘡鍦板尯4琛岋紙瀹㈣繍閲�/鍛ㄨ浆閲�/鍩庡競鍐呭杩愰噺/鍩庡競鍐呭懆杞噺锛�
+            int rowIdx = 4;
+            for (int a = 0; a < areas.size(); a++) {
+                String area = areas.get(a);
+                for (int k = 0; k < 4; k++) {
+                    Row row = sheet.getRow(rowIdx);
+                    if (row == null) row = sheet.createRow(rowIdx);
+                    if (a > 0) {
+                        // 2026骞� 1..N 鏈堬細搴撳唴鏈夊�煎垯濉紙2浣嶅皬鏁帮級锛屾棤鍊兼竻绌烘ā鏉挎牱渚�
+                        for (int m = 1; m <= month; m++) {
+                            double v = cityVal(monthCity, area, m, k);
+                            Cell c = row.getCell(2 + (m - 1) * 2);
+                            if (c == null) c = row.createCell(2 + (m - 1) * 2);
+                            if (v == 0) c.setBlank(); else c.setCellValue(round(v, 2));
+                        }
+                        // 2025骞� 1..12 鏈堬細搴撳唴鏈夊幓骞存暟鎹垯濉紝鍚﹀垯娓呯┖妯℃澘鏍蜂緥
+                        for (int m = 1; m <= 12; m++) {
+                            double v = cityVal(lastYearMonthCity, area, m, k);
+                            Cell c = row.getCell(19 + (m - 1) * 2);
+                            if (c == null) c = row.createCell(19 + (m - 1) * 2);
+                            if (v == 0) c.setBlank(); else c.setCellValue(round(v, 2));
+                        }
+                    }
+                    rowIdx++;
+                }
+            }
+            // 閲嶇畻鍏紡缂撳瓨鍊硷紙鍏ㄧ渷姹傚拰/鍚屾瘮/绱锛屼繚鐣欏叕寮忥紱姹傚�煎紓甯稿拷鐣ワ級
+            try {
+                wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
+            } catch (Exception e) {
+                log.warn("鍩庡競瀹㈣繍妯℃澘鍏紡姹傚�煎け璐�: {}", e.getMessage());
+            }
+            return toBytes(wb);
+        }
+    }
+
+    /** 妯℃澘鏂囦欢瀹氫綅锛氶厤缃洰褰� 鈫� user.dir 鈫� user.dir/.. 閫愮骇鍥為�� */
+    private File resolveTemplate(String fileName) throws Exception {
+        return resolveAnyTemplate(templateDir, fileName);
+    }
+
+    /** 鍏矾鏃呭杈撳嚭妯℃澘瀹氫綅锛坉ocs/鍏矾鏃呭+鑳借��/杈撳嚭锛� */
+    private File resolvePassengerTemplate(String fileName) throws Exception {
+        return resolveAnyTemplate(passengerTemplateDir, fileName);
+    }
+
+    /** 閫氱敤妯℃澘鏂囦欢瀹氫綅锛堢敤浜庡煄甯傚杩�/鑳借�楃瓑涓嶅悓妯℃澘鐩綍锛� */
+    private File resolveAnyTemplate(String dir, String fileName) throws Exception {
+        String rel = dir;
+        while (rel.startsWith("./")) rel = rel.substring(2);
+        String[] roots = {
+            dir,
+            System.getProperty("user.dir") + "/" + rel,
+            System.getProperty("user.dir") + "/../" + rel
+        };
+        for (String root : roots) {
+            if (root == null || root.trim().isEmpty()) continue;
+            File f = new File(root, fileName);
+            if (f.exists() && f.isFile()) return f;
+        }
+        throw new RuntimeException("鏈壘鍒版ā鏉挎枃浠�: " + fileName);
+    }
+
+    /** 鍩庡競瀹㈣繍妯℃澘鏍囬鍔ㄦ�佸寲锛氭壂鎻忓叏閮ㄦ枃鏈崟鍏冩牸锛屾浛鎹㈠啓姝荤殑骞翠唤涓庣疮璁″尯闂达紙濡� "2026骞�1-6鏈�" 鈫� "2027骞�1-8鏈�"锛夛紝鍒楁爣棰� "2026骞�1鏈�" 鍙崲骞翠唤锛�"1-12鏈�" 鏁村勾瀵规瘮鍧楋紙2024/2025锛夋爣棰樹繚鎸佸師鏍� */
+    private void dynamicTitle(Sheet sheet, int year, int month) {
+        java.util.regex.Pattern yearP = java.util.regex.Pattern.compile("\\d{4}骞�");
+        java.util.regex.Pattern rangeP = java.util.regex.Pattern.compile("(\\d+)-(\\d+)鏈�");
+        String cum = cumRange(month);
+        for (int r = 0; r <= sheet.getLastRowNum(); r++) {
+            Row row = sheet.getRow(r);
+            if (row == null) continue;
+            for (Cell cell : row) {
+                if (cell.getCellType() != CellType.STRING) continue;
+                String v = cell.getStringCellValue();
+                if (v == null || v.isEmpty() || v.contains("1-12鏈�")) continue; // 鏁村勾瀵规瘮鍧楁爣棰樹繚鎸佸師鏍�
+                String nv = yearP.matcher(v).replaceAll(year + "骞�");
+                nv = rangeP.matcher(nv).replaceAll(java.util.regex.Matcher.quoteReplacement(cum));
+                if (!nv.equals(v)) cell.setCellValue(nv);
+            }
+        }
+    }
+
+    /** 鍩庡競瀹㈣繍鍒嗗競宸炲彇鍊硷紙鏈� 鈫� 甯傚窞 鈫� 鎸囨爣鏁扮粍锛� */
+    private double cityVal(Map<Integer, Map<String, double[]>> monthCity, String area, int m, int idx) {
+        Map<String, double[]> cityMap = monthCity.get(m);
+        if (cityMap == null) return 0;
+        double[] arr = cityMap.get(area);
+        if (arr == null) return 0;
+        return idx < arr.length ? arr[idx] : 0;
+    }
+
+    // ==================== 鎶曡祫妯″潡 4 寮犻瀵兼姤琛紙妯℃澘搴曠鐢熸垚锛岃緭鍑� .xls锛� ====================
+
+    /** 鍔犺浇鎶曡祫 .xls 妯℃澘锛圚SSFWorkbook锛屼繚鐣欏垪瀹�/琛ㄥご/鏍峰紡/鍚堝苟锛� */
+    private HSSFWorkbook loadInvestTemplate(String fileName) throws Exception {
+        File f = resolveAnyTemplate(investTemplateDir, fileName);
+        try (InputStream in = new FileInputStream(f)) {
+            return new HSSFWorkbook(in);
+        }
+    }
+
+    /** 妯℃澘鍗曞厓鏍煎啓鏂囨湰锛氬厛娓呯┖锛堜繚鐣欐牱寮�/鍚堝苟锛夊啀鍐欙紝null/绌虹櫧鐣欑┖ */
+    private void hssfText(Row row, int idx, String v) {
+        Cell c = row.getCell(idx);
+        if (c == null) c = row.createCell(idx);
+        c.setBlank();
+        if (v != null && !v.trim().isEmpty()) c.setCellValue(v.trim());
+    }
+
+    /** 妯℃澘鍗曞厓鏍煎啓鏁板�硷細鍏堟竻绌猴紙淇濈暀鏍峰紡/鍚堝苟锛夊啀鍐� */
+    private void hssfNum(Row row, int idx, Double v) {
+        Cell c = row.getCell(idx);
+        if (c == null) c = row.createCell(idx);
+        c.setBlank();
+        if (v != null) c.setCellValue(v);
+    }
+
+    /** 鍒犻櫎鏁版嵁鍖哄浣欒锛坒romRow..lastKeepRow-1锛夛細鍏堢Щ闄ゅ悎骞跺崟鍏冩牸鍐嶅垹琛岃褰曪紱琛ㄥご鍚堝苟涓嶅彈褰卞搷 */
+    private void trimHssfTail(HSSFSheet sheet, int fromRow, int lastKeepRow) {
+        for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {
+            CellRangeAddress m = sheet.getMergedRegion(i);
+            if (m.getFirstRow() >= fromRow && m.getFirstRow() < lastKeepRow) {
+                sheet.removeMergedRegion(i);
+            }
+        }
+        for (int r = lastKeepRow - 1; r >= fromRow; r--) {
+            HSSFRow row = sheet.getRow(r);
+            if (row != null) sheet.removeRow(row);
+        }
+    }
+
+    private void trimHssfTail(HSSFSheet sheet, int fromRow) {
+        trimHssfTail(sheet, fromRow, sheet.getLastRowNum() + 1);
+    }
+
+    /** 鏂板缓琛屽苟澶嶅埗妯℃澘琛屾牱寮忥紙鍚楂橈級锛屼繚璇佷笌妯℃澘鏁版嵁鍖哄瑙備竴鑷� */
+    private HSSFRow createRowLike(HSSFSheet sheet, int rowIdx, HSSFRow styleRow) {
+        HSSFRow row = sheet.createRow(rowIdx);
+        if (styleRow != null) {
+            row.setHeight(styleRow.getHeight());
+            for (int c = 0; c < styleRow.getLastCellNum(); c++) {
+                HSSFCell sc = styleRow.getCell(c);
+                if (sc == null) continue;
+                row.createCell(c).setCellStyle(sc.getCellStyle());
+            }
+        }
+        return row;
+    }
+
+    /** 娣诲姞鍗曡鍚堝苟鍗曞厓鏍硷紙涓庢ā鏉挎暟鎹尯鍚堝苟瑙勫垯涓�鑷达級 */
+    private void addMerge(HSSFSheet sheet, int r, int firstCol, int lastCol) {
+        sheet.addMergedRegion(new CellRangeAddress(r, r, firstCol, lastCol));
+    }
+
+    /** 瑙f瀽 4 浣嶅勾浠戒负鏁板�硷紙鍐欐ā鏉垮勾浠藉垪锛夛紝鏃犳硶瑙f瀽杩斿洖 null */
+    private Double parseYearNum(String t) {
+        if (t == null || t.trim().isEmpty()) return null;
+        String s = t.trim().substring(0, Math.min(4, t.trim().length()));
+        try {
+            return Double.parseDouble(s);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /** 瑙f瀽 6 浣� YYYYMM 涓烘暟鍊硷紙鍐欐ā鏉垮紑宸ユ椂闂村垪锛� */
+    private Double parseTimeYMNum(String t) {
+        if (t == null || t.trim().isEmpty()) return null;
+        String x = padTimeYM(t.trim()).replaceAll("[^0-9]", "");
+        if (x.length() < 6) return null;
+        try {
+            return Double.parseDouble(x.substring(0, 6));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private byte[] toBytesHssf(HSSFWorkbook wb) throws Exception {
+        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            wb.write(out);
+            wb.close();
+            return out.toByteArray();
+        }
+    }
+
+    /** 鎶曡祫妯℃澘 甯傚窞灏忚妭缁撴瀯锛堣〃1/琛�2 鍔ㄦ�佹彃琛岀敤锛� */
+    private static class InvestSection {
+        String blockCategory;
+        String city;
+        int startRow;
+        int styleRowIdx = -1; // 璇ュ皬鑺傞涓」鐩锛堝鍒舵牱寮忕敤锛�
+        List<Integer> projectRows = new ArrayList<>();
+        Set<Long> matched = new HashSet<>();
+    }
+
+    /** 鍦� pos 琛屼箣鍚庢彃鍏� n 琛岋細涓嬫柟琛屼笅绉伙紙鍚堝苟鍖哄厛鎽橀櫎鍐嶆寜鏂拌鍙锋仮澶嶏級锛屾柊琛屽鍒� styleRow 鏍峰紡/琛岄珮/鍗曡鍚堝苟锛涜繑鍥為涓柊琛岀储寮� */
+    private int insertRowsDown(HSSFSheet sheet, int pos, int n, HSSFRow styleRow) {
+        if (n <= 0) return -1;
+        List<CellRangeAddress> below = new ArrayList<>();
+        for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {
+            CellRangeAddress m = sheet.getMergedRegion(i);
+            if (m.getFirstRow() > pos) {
+                below.add(m);
+                sheet.removeMergedRegion(i);
+            }
+        }
+        int last = sheet.getLastRowNum();
+        if (pos < last) {
+            sheet.shiftRows(pos + 1, last, n, true, false);
+        }
+        for (CellRangeAddress m : below) {
+            sheet.addMergedRegion(new CellRangeAddress(m.getFirstRow() + n, m.getLastRow() + n, m.getFirstColumn(), m.getLastColumn()));
+        }
+        int first = pos + 1;
+        for (int i = 0; i < n; i++) {
+            HSSFRow row = createRowLike(sheet, first + i, styleRow);
+            if (styleRow != null) copyRowMerges(sheet, styleRow.getRowNum(), first + i);
+        }
+        return first;
+    }
+
+    /** 澶嶅埗 styleRow 鎵�鍦ㄨ鐨勫崟琛屽悎骞跺尯鍒� toRow锛堟彃鍏ヨ澶栬涓庢ā鏉胯涓�鑷达級 */
+    private void copyRowMerges(HSSFSheet sheet, int fromRow, int toRow) {
+        for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
+            CellRangeAddress m = sheet.getMergedRegion(i);
+            if (m.getFirstRow() == fromRow && m.getLastRow() == fromRow) {
+                sheet.addMergedRegion(new CellRangeAddress(toRow, toRow, m.getFirstColumn(), m.getLastColumn()));
+            }
+        }
+    }
+
+    /** 鎶曡祫琛� 搴忓彿閲嶆帓锛氳〃1 鎸夊潡閲嶇疆銆佽〃2 鎸夊競宸炲皬鑺傞噸缃�佽〃4 鍏ㄨ〃杩炵画锛堟ā鏉�/鎻掑叆琛岀粺涓�鎺掑簭锛� */
+    private void renumberInvestSeq(HSSFSheet sheet, String kind) {
+        int seq = 0;
+        int lastRow = sheet.getLastRowNum();
+        for (int r = 4; r <= lastRow; r++) {
+            HSSFRow row = sheet.getRow(r);
+            if (row == null) continue;
+            String a = nvl(cellText(row.getCell(0)));
+            if ("investPlan".equals(kind)) {
+                if ("涓�銆佸杩愮珯鍦哄熀纭�璁炬柦寤鸿".equals(a) || "浜屻�佷氦閫氱墿娴佸熀纭�璁炬柦寤鸿".equals(a) || "涓夈�佽�佹棫钀ヨ繍璐ц溅鎶ュ簾鏇存柊".equals(a)) {
+                    seq = 0;
+                    continue;
+                }
+            } else if ("investFiveYear".equals(kind)) {
+                if (isCityLabel(a)) {
+                    seq = 0;
+                    continue;
+                }
+            }
+            String b = nvl(cellText(row.getCell(1)));
+            if (b.isEmpty()) continue;
+            hssfNum(row, 0, (double) (++seq));
+        }
+    }
+
+    /** 琛�1 椤圭洰琛屽~鍏咃紙妯℃澘琛�/鎻掑叆琛屽叡鐢紱cityText 涓庢ā鏉� B 鍒楁牸寮忎竴鑷达級 */
+    private void fillInvestPlanProjectRow(HSSFRow row, InvestmentProject p, InvestmentMonthly m, int seq, String cityText) {
+        hssfNum(row, 0, (double) seq);
+        hssfText(row, 1, cityText);
+        hssfText(row, 2, valueOf(p.getCounty()));
+        hssfText(row, 3, p.getProjectName());
+        hssfText(row, 4, valueOf(p.getConstructNature()));
+        hssfNum(row, 5, parseYearNum(p.getStartTime()));
+        hssfNum(row, 6, parseYearNum(p.getEndTime()));
+        hssfNum(row, 7, p.getTotalInvestment());
+        hssfText(row, 20, p.getApprovalGk());
+        hssfText(row, 21, p.getApprovalCs());
+        if (m != null) {
+            hssfNum(row, 8, m.getStartCum());
+            hssfNum(row, 9, m.getYearPlan());
+            hssfText(row, 13, m.getProgressDesc());
+            hssfNum(row, 22, m.getYearCum());
+            hssfText(row, 23, valueOf(m.getProgressStage()));
+        }
+    }
+
+    /** 琛�2 椤圭洰琛屽~鍏咃紙妯℃澘琛�/鎻掑叆琛屽叡鐢級 */
+    private void fillFiveYearProjectRow(HSSFRow row, InvestmentProject p, InvestmentMonthly m, int seq) {
+        hssfNum(row, 0, (double) seq);
+        hssfText(row, 1, p.getProjectName());
+        hssfText(row, 2, valueOf(p.getBuilderName()));
+        hssfNum(row, 3, parseYearNum(p.getStartTime()));
+        hssfNum(row, 4, parseYearNum(p.getEndTime()));
+        hssfNum(row, 5, p.getTotalInvestment());
+        if (m != null) {
+            hssfNum(row, 6, m.getStartCum());
+            hssfNum(row, 7, m.getYearCum());
+            hssfText(row, 8, valueOf(m.getProgressStage()));
+            hssfText(row, 9, m.getProgressDesc());
+        }
+    }
+
+    /** 琛�4 椤圭洰琛屽~鍏咃紙妯℃澘琛�/鎻掑叆琛屽叡鐢級 */
+    private void fillCountyProjectRow(HSSFRow row, InvestmentProject p, InvestmentMonthly m, int seq) {
+        hssfNum(row, 0, (double) seq);
+        hssfText(row, 1, p.getProjectName());
+        hssfText(row, 2, valueOf(p.getBuilderName()));
+        hssfNum(row, 3, p.getTotalInvestment());
+        if (m != null) {
+            hssfNum(row, 4, m.getStartCum());
+            hssfNum(row, 5, m.getYearPlan());
+            hssfNum(row, 6, m.getYearCum());
+            hssfNum(row, 7, m.getMonthDone());
+            hssfText(row, 8, valueOf(m.getProgressStage()));
+            hssfText(row, 9, m.getProgressDesc());
+            if (m.getBuildingArea() != null && m.getBuildingArea() > 0) hssfNum(row, 12, m.getBuildingArea());
+        }
+        hssfText(row, 15, p.getApprovalGk());
+        hssfText(row, 16, p.getApprovalCs());
+    }
+
+    /** 琛�1锛�2026骞村叏鐪侀瀹夋帓璁″垝杩涘害鎯呭喌姹囨�伙紙妯℃澘搴曠鍘熶綅鏇挎崲锛氫繚鐣欏叏閮ㄨ/鍚堝苟/鍒楀锛屼粎鏇挎崲鏁版嵁锛汥B 椤圭洰澶氫簬妯℃澘棰勭暀琛屾椂鍔ㄦ�佹彃琛岋級 */
+    public byte[] investPlan(String period, String mode) throws Exception {
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, mode);
+        List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList(
+            new LambdaQueryWrapper<InvestmentMonthly>()
+                .eq(InvestmentMonthly::getReportPeriod, period));
+        Map<Long, InvestmentProject> pmap = new HashMap<>();
+        List<InvestmentProject> projects = investProjectMapper.selectList(null);
+        for (InvestmentProject p : projects) pmap.put(p.getId(), p);
+        Map<Long, InvestmentMonthly> mByProject = new HashMap<>();
+        for (InvestmentMonthly m : monthlies) mByProject.put(m.getProjectId(), m);
+
+        HSSFWorkbook wb = loadInvestTemplate("妯℃澘_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls");
+        HSSFSheet sheet = wb.getSheetAt(0);
+        // 鏍囬/琛ㄥご骞翠唤涓庣疮璁℃湀浠�
+        hssfText(sheet.getRow(1), 0, year + "骞村叏鐪佸璐ц繍绔欏満寤鸿棰勫畨鎺掕鍒掕〃");
+        hssfText(sheet.getRow(2), 0, "钃濊壊椤圭洰涓�" + year + "骞村競宸炴嫙鐢虫姤璧勯噾椤圭洰        鍗曚綅锛氫竾鍏�");
+        hssfText(sheet.getRow(3), 22, cumRange(month) + "绱瀹屾垚鎶曡祫");
+        // 缁撴瀯鎵弿锛氬潡(瀹㈣繍/鐗╂祦/鑰佹棫) -> 甯傚窞灏忚妭 -> 椤圭洰琛�
+        List<InvestSection> sections = new ArrayList<>();
+        String blockCategory = null;
+        InvestSection cur = null;
+        int firstProjectRow = -1;
+        int lastRow = sheet.getLastRowNum();
+        for (int r = 5; r <= lastRow; r++) {
+            HSSFRow row = sheet.getRow(r);
+            if (row == null) continue;
+            String a = nvl(cellText(row.getCell(0)));
+            String b = nvl(cellText(row.getCell(1)));
+            String d = nvl(cellText(row.getCell(3)));
+            if (r == 5) continue; // 鍏ㄧ渷鍚堣琛屽崟鐙鐞�
+            if ("涓�銆佸杩愮珯鍦哄熀纭�璁炬柦寤鸿".equals(a) || "浜屻�佷氦閫氱墿娴佸熀纭�璁炬柦寤鸿".equals(a) || "涓夈�佽�佹棫钀ヨ繍璐ц溅鎶ュ簾鏇存柊".equals(a)) {
+                blockCategory = a.startsWith("涓�銆�") ? "瀹㈣繍绔欏満" : (a.startsWith("浜屻��") ? "鐗╂祦鍥尯" : null);
+                cur = null;
+                continue;
+            }
+            if (a.startsWith("涓�銆�") || a.startsWith("浜屻��")) continue; // 棰勫畨鎺掕鍒掑唴/澶� 鍒嗙粍鏍囬锛氬綊褰撳墠灏忚妭
+            if (b.isEmpty() && d.isEmpty()) {
+                cur = new InvestSection();
+                cur.blockCategory = blockCategory;
+                cur.city = templateCityFull(a);
+                cur.startRow = r;
+                sections.add(cur);
+                continue;
+            }
+            if (cur != null) {
+                cur.projectRows.add(r);
+                if (cur.styleRowIdx < 0) cur.styleRowIdx = r;
+                if (firstProjectRow < 0) firstProjectRow = r;
+            }
+        }
+        HSSFRow globalStyle = firstProjectRow >= 0 ? sheet.getRow(firstProjectRow) : null;
+        // 鍘熶綅閫愯鏇挎崲锛氬悎璁�/鍧楁爣棰�/甯傚窞灏忚妭/鍒嗙粍/椤圭洰琛岋紱鏈尮閰嶇殑妯℃澘琛屾竻绌烘牱渚嬫暟鎹紙缁撴瀯/鍚堝苟/鍒楀涓嶅彉锛�
+        int seq = 0;
+        int si = -1;
+        for (int r = 5; r <= lastRow; r++) {
+            HSSFRow row = sheet.getRow(r);
+            if (row == null) continue;
+            String a = nvl(cellText(row.getCell(0)));
+            String b = nvl(cellText(row.getCell(1)));
+            String d = nvl(cellText(row.getCell(3)));
+            if (si + 1 < sections.size() && r >= sections.get(si + 1).startRow) si++;
+            InvestSection sec = si >= 0 && si < sections.size() ? sections.get(si) : null;
+            if (r == 5) { // 鍏ㄧ渷鍚堣琛�
+                blankInvestRow(row, 25);
+                hssfText(row, 0, "鍚堣");
+                InvestAgg prov = new InvestAgg();
+                for (InvestmentMonthly m : monthlies) prov.add(m, pmap.get(m.getProjectId()));
+                writeInvestAggHssf(row, prov);
+                continue;
+            }
+            if ("涓�銆佸杩愮珯鍦哄熀纭�璁炬柦寤鸿".equals(a) || "浜屻�佷氦閫氱墿娴佸熀纭�璁炬柦寤鸿".equals(a)) {
+                blockCategory = a.startsWith("涓�銆�") ? "瀹㈣繍绔欏満" : "鐗╂祦鍥尯";
+                blankInvestRow(row, 25);
+                hssfText(row, 0, a);
+                InvestAgg agg = new InvestAgg();
+                for (InvestmentMonthly m : monthlies) {
+                    InvestmentProject p = pmap.get(m.getProjectId());
+                    if (p != null && blockCategory.equals(p.getCategory())) agg.add(m, p);
+                }
+                writeInvestAggHssf(row, agg);
+                seq = 0;
+                continue;
+            }
+            if ("涓夈�佽�佹棫钀ヨ繍璐ц溅鎶ュ簾鏇存柊".equals(a)) {
+                blockCategory = null; // 鏆傛棤鏁版嵁婧愶細鏁村潡娓呯┖
+                blankInvestRow(row, 25);
+                hssfText(row, 0, a);
+                seq = 0;
+                continue;
+            }
+            if (a.startsWith("涓�銆�") || a.startsWith("浜屻��")) {
+                // 棰勫畨鎺掕鍒掑唴/澶� 鍒嗙粍鏍囬琛岋細鏃犳暟鎹紝浠呬繚鐣欐爣绛�
+                blankInvestRow(row, 25);
+                hssfText(row, 0, a);
+                continue;
+            }
+            if (b.isEmpty() && d.isEmpty()) {
+                // 甯傚窞灏忚妭琛岋細閲嶇畻璇ュ競宸烇紙褰撳墠鍧楃被鍒級鍚堣
+                String city = templateCityFull(a);
+                blankInvestRow(row, 25);
+                hssfText(row, 0, a);
+                InvestAgg agg = new InvestAgg();
+                for (InvestmentMonthly m : monthlies) {
+                    InvestmentProject p = pmap.get(m.getProjectId());
+                    if (p != null && city.equals(p.getCity())
+                        && (blockCategory == null || blockCategory.equals(p.getCategory()))) agg.add(m, p);
+                }
+                writeInvestAggHssf(row, agg);
+                continue;
+            }
+            // 椤圭洰琛岋細鎸� 妯℃澘鍚嶇О+甯傚窞+鍧楃被鍒� 鍖归厤 DB 椤圭洰锛屾湭鍖归厤鏁磋娓呯┖
+            blankInvestRow(row, 25);
+            if (blockCategory == null) continue; // 鑰佹棫璐ц溅娈垫棤鏁版嵁婧�
+            InvestmentProject p = matchInvest(projects, d, b, blockCategory);
+            if (p == null) continue;
+            if (sec != null) sec.matched.add(p.getId());
+            InvestmentMonthly m = mByProject.get(p.getId());
+            fillInvestPlanProjectRow(row, p, m, ++seq, b);
+        }
+        // 鍔ㄦ�佹彃琛岋細DB 椤圭洰鏁� > 妯℃澘棰勭暀琛屾暟鏃讹紝鍦ㄥ競宸炲皬鑺傛湯灏捐ˉ榻愶紙鑷簳鍚戜笂鎻掕锛岄伩鍏嶈鍙烽敊浣嶏級
+        for (int i = sections.size() - 1; i >= 0; i--) {
+            InvestSection s = sections.get(i);
+            if (s.blockCategory == null || s.city == null) continue; // 鑰佹棫璐ц溅娈垫棤鏁版嵁婧�
+            List<InvestmentProject> extra = new ArrayList<>();
+            for (InvestmentProject p : projects) {
+                if (s.blockCategory.equals(p.getCategory())
+                    && s.city.equals(RegionUtil.normalizeCityName(p.getCity()))
+                    && !s.matched.contains(p.getId())) extra.add(p);
+            }
+            if (extra.isEmpty()) continue;
+            HSSFRow styleRow = s.styleRowIdx >= 0 ? sheet.getRow(s.styleRowIdx) : globalStyle;
+            int pos = s.projectRows.isEmpty() ? s.startRow : s.projectRows.get(s.projectRows.size() - 1);
+            int first = insertRowsDown(sheet, pos, extra.size(), styleRow);
+            for (int j = 0; j < extra.size(); j++) {
+                HSSFRow row = sheet.getRow(first + j);
+                if (row == null) continue;
+                InvestmentProject p = extra.get(j);
+                fillInvestPlanProjectRow(row, p, mByProject.get(p.getId()), 0, p.getCity());
+            }
+        }
+        renumberInvestSeq(sheet, "investPlan");
+        return toBytesHssf(wb);
+    }
+
+    /** 甯傚窞灏忚妭/鍚堣琛屾暟鍊硷細7鎬绘姇璧� 8鑷紑濮嬬疮璁� 9鏈勾璁″垝 22鑷勾鍒濈疮璁� 23瀹為檯杩涘害锛堥儴/鐪佽祫閲戜笌鑷鏃犳暟鎹簮锛岀暀绌猴級 */
+    private void writeInvestAggHssf(HSSFRow row, InvestAgg agg) {
+        hssfNum(row, 7, agg.total == 0 ? null : agg.total);
+        hssfNum(row, 8, agg.startCum == 0 ? null : agg.startCum);
+        hssfNum(row, 9, agg.yearPlan == 0 ? null : agg.yearPlan);
+        hssfNum(row, 22, agg.yearCum == 0 ? null : agg.yearCum);
+        if (agg.yearPlan > 0) hssfNum(row, 23, round(agg.yearCum / agg.yearPlan, 4));
+    }
+
+
+    private static class InvestAgg {
+        double total, startCum, yearPlan, yearCum;
+
+        void add(InvestmentMonthly m, InvestmentProject p) {
+            if (p != null) total += nz(p.getTotalInvestment());
+            startCum += nz(m.getStartCum());
+            yearPlan += nz(m.getYearPlan());
+            yearCum += nz(m.getYearCum());
+        }
+
+        private double nz(Double v) {
+            return v == null ? 0.0 : v;
+        }
+    }
+
+    /** 琛�2锛�"鍗佷簲浜�"瑙勫垝鐗╂祦椤圭洰杩涘睍鎯呭喌锛堟ā鏉垮簳绋垮師浣嶆浛鎹細淇濈暀鍏ㄩ儴琛�/鍚堝苟/鍒楀锛屼粎鏇挎崲鏁版嵁锛汥B 椤圭洰澶氫簬妯℃澘棰勭暀琛屾椂鍔ㄦ�佹彃琛岋級 */
+    public byte[] investFiveYearLogistics(String period, String mode) throws Exception {
+        List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList(
+            new LambdaQueryWrapper<InvestmentMonthly>()
+                .eq(InvestmentMonthly::getReportPeriod, period));
+        Map<Long, InvestmentProject> pmap = new HashMap<>();
+        List<InvestmentProject> fiveProjects = new java.util.ArrayList<>();
+        for (InvestmentProject p : investProjectMapper.selectList(null)) {
+            pmap.put(p.getId(), p);
+            if (Integer.valueOf(1).equals(p.getIsFiveYear())) fiveProjects.add(p);
+        }
+        Map<Long, InvestmentMonthly> mByProject = new HashMap<>();
+        for (InvestmentMonthly m : monthlies) mByProject.put(m.getProjectId(), m);
+        List<InvestmentMonthly> mine = new java.util.ArrayList<>();
+        for (InvestmentMonthly m : monthlies) {
+            InvestmentProject p = pmap.get(m.getProjectId());
+            if (p != null && Integer.valueOf(1).equals(p.getIsFiveYear())) mine.add(m);
+        }
+
+        HSSFWorkbook wb = loadInvestTemplate("妯℃澘_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls");
+        HSSFSheet sheet = wb.getSheetAt(1); // 銆� 鍒嗛」鐩姇璧勫畬鎴愭儏鍐点��
+        int lastRow = sheet.getLastRowNum();
+        // 缁撴瀯鎵弿锛氬競宸炲皬鑺� -> 椤圭洰琛�
+        List<InvestSection> sections = new ArrayList<>();
+        InvestSection cur = null;
+        int firstProjectRow = -1;
+        for (int r = 4; r <= lastRow; r++) {
+            HSSFRow row = sheet.getRow(r);
+            if (row == null) continue;
+            String a = nvl(cellText(row.getCell(0)));
+            String b = nvl(cellText(row.getCell(1)));
+            if (r == 4) continue; // 鍏ㄧ渷鍚堣琛屽崟鐙鐞�
+            if (b.isEmpty() && isCityLabel(a)) {
+                cur = new InvestSection();
+                cur.city = templateCityFull(a);
+                cur.startRow = r;
+                sections.add(cur);
+                continue;
+            }
+            if (cur != null) {
+                cur.projectRows.add(r);
+                if (cur.styleRowIdx < 0) cur.styleRowIdx = r;
+                if (firstProjectRow < 0) firstProjectRow = r;
+            }
+        }
+        HSSFRow globalStyle = firstProjectRow >= 0 ? sheet.getRow(firstProjectRow) : null;
+        String currentCity = null;
+        int seq = 0;
+        int si = -1;
+        for (int r = 4; r <= lastRow; r++) {
+            HSSFRow row = sheet.getRow(r);
+            if (row == null) continue;
+            String a = nvl(cellText(row.getCell(0)));
+            String b = nvl(cellText(row.getCell(1)));
+            if (si + 1 < sections.size() && r >= sections.get(si + 1).startRow) si++;
+            InvestSection sec = si >= 0 && si < sections.size() ? sections.get(si) : null;
+            if (r == 4) { // 鍏ㄧ渷鍚堣琛�
+                blankInvestRow(row, 12);
+                hssfText(row, 0, "鍚堣");
+                InvestAgg total = new InvestAgg();
+                for (InvestmentMonthly m : mine) total.add(m, pmap.get(m.getProjectId()));
+                hssfNum(row, 5, total.total == 0 ? null : total.total);
+                hssfNum(row, 6, total.startCum == 0 ? null : total.startCum);
+                hssfNum(row, 7, total.yearCum == 0 ? null : total.yearCum);
+                continue;
+            }
+            if (b.isEmpty() && isCityLabel(a)) {
+                currentCity = templateCityFull(a);
+                seq = 0;
+                blankInvestRow(row, 12);
+                hssfText(row, 0, a);
+                InvestAgg agg = new InvestAgg();
+                for (InvestmentMonthly m : mine) {
+                    InvestmentProject p = pmap.get(m.getProjectId());
+                    if (p != null && currentCity.equals(p.getCity())) agg.add(m, p);
+                }
+                hssfNum(row, 5, agg.total == 0 ? null : agg.total);
+                hssfNum(row, 6, agg.startCum == 0 ? null : agg.startCum);
+                hssfNum(row, 7, agg.yearCum == 0 ? null : agg.yearCum);
+                continue;
+            }
+            // 椤圭洰琛岋細鎸� 妯℃澘鍚嶇О+甯傚窞 鍖归厤 DB 鍗佷簲浜旈」鐩紝鏈尮閰嶆暣琛屾竻绌�
+            blankInvestRow(row, 12);
+            InvestmentProject p = matchInvest(fiveProjects, b, currentCity, null);
+            if (p == null) continue;
+            if (sec != null) sec.matched.add(p.getId());
+            InvestmentMonthly m = mByProject.get(p.getId());
+            fillFiveYearProjectRow(row, p, m, ++seq);
+        }
+        // 鍔ㄦ�佹彃琛岋細DB 鍗佷簲浜旈」鐩暟 > 妯℃澘棰勭暀琛屾暟鏃讹紝鍦ㄥ競宸炲皬鑺傛湯灏捐ˉ榻�
+        for (int i = sections.size() - 1; i >= 0; i--) {
+            InvestSection s = sections.get(i);
+            if (s.city == null) continue;
+            List<InvestmentProject> extra = new ArrayList<>();
+            for (InvestmentProject p : fiveProjects) {
+                if (s.city.equals(RegionUtil.normalizeCityName(p.getCity()))
+                    && !s.matched.contains(p.getId())) extra.add(p);
+            }
+            if (extra.isEmpty()) continue;
+            HSSFRow styleRow = s.styleRowIdx >= 0 ? sheet.getRow(s.styleRowIdx) : globalStyle;
+            int pos = s.projectRows.isEmpty() ? s.startRow : s.projectRows.get(s.projectRows.size() - 1);
+            int first = insertRowsDown(sheet, pos, extra.size(), styleRow);
+            for (int j = 0; j < extra.size(); j++) {
+                HSSFRow row = sheet.getRow(first + j);
+                if (row == null) continue;
+                InvestmentProject p = extra.get(j);
+                fillFiveYearProjectRow(row, p, mByProject.get(p.getId()), 0);
+            }
+        }
+        renumberInvestSeq(sheet, "investFiveYear");
+        return toBytesHssf(wb);
+    }
+
+    /** 琛�4锛氾紙缁忔祹寮哄幙椤圭洰锛夋箹鍖楃渷浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃锛堟ā鏉垮簳绋垮師浣嶆浛鎹細淇濈暀鍏ㄩ儴琛�/鍚堝苟/鍒楀锛屼粎鏇挎崲鏁版嵁锛汥B 椤圭洰澶氫簬妯℃澘棰勭暀琛屾椂鍔ㄦ�佹彃琛岋級 */
+    public byte[] investCounty(String period, String mode) throws Exception {
+        List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList(
+            new LambdaQueryWrapper<InvestmentMonthly>()
+                .eq(InvestmentMonthly::getReportPeriod, period));
+        Map<Long, InvestmentProject> pmap = new HashMap<>();
+        List<InvestmentProject> countyProjects = new java.util.ArrayList<>();
+        for (InvestmentProject p : investProjectMapper.selectList(null)) {
+            pmap.put(p.getId(), p);
+            if (Integer.valueOf(1).equals(p.getIsCounty())) countyProjects.add(p);
+        }
+        Map<Long, InvestmentMonthly> mByProject = new HashMap<>();
+        for (InvestmentMonthly m : monthlies) mByProject.put(m.getProjectId(), m);
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, mode);
+
+        HSSFWorkbook wb = loadInvestTemplate("妯℃澘_缁忔祹寮哄幙鎶曡祫缁熻鎶ヨ〃.xls");
+        HSSFSheet sheet = wb.getSheetAt(0);
+        hssfText(sheet.getRow(0), 0, "锛堢粡娴庡己鍘块」鐩級婀栧寳鐪佷氦閫氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃锛�" + month + "鏈堟湀鎶ワ級");
+        hssfText(sheet.getRow(1), 0, "鍏ㄧ渷璐ц繍锛堢墿娴侊級鍩虹璁炬柦寤鸿鎶曡祫鏈堟姤锛�" + year + "骞�" + month + "鏈堬級");
+        int lastRow = Math.min(sheet.getLastRowNum(), 65533);
+        int seq = 0;
+        int lastProjectRow = -1;
+        Set<Long> matched = new HashSet<>();
+        for (int r = 4; r <= lastRow; r++) {
+            HSSFRow row = sheet.getRow(r);
+            if (row == null) continue;
+            String name = nvl(cellText(row.getCell(1)));
+            if (name.isEmpty()) continue; // 妯℃澘灏鹃儴绌鸿涓嶅鐞�
+            lastProjectRow = r;
+            blankInvestRow(row, 20);
+            InvestmentProject p = matchInvest(countyProjects, name, null, null);
+            if (p == null) continue;
+            matched.add(p.getId());
+            InvestmentMonthly m = mByProject.get(p.getId());
+            fillCountyProjectRow(row, p, m, ++seq);
+        }
+        // 鍔ㄦ�佹彃琛岋細DB 缁忔祹寮哄幙椤圭洰鏁� > 妯℃澘棰勭暀琛屾暟鏃讹紝鍦ㄩ」鐩尯鏈熬琛ラ綈
+        List<InvestmentProject> extra = new ArrayList<>();
+        for (InvestmentProject p : countyProjects) {
+            if (!matched.contains(p.getId())) extra.add(p);
+        }
+        if (!extra.isEmpty()) {
+            HSSFRow styleRow = sheet.getRow(4);
+            int pos = lastProjectRow >= 4 ? lastProjectRow : 3;
+            int first = insertRowsDown(sheet, pos, extra.size(), styleRow);
+            for (int j = 0; j < extra.size(); j++) {
+                HSSFRow row = sheet.getRow(first + j);
+                if (row == null) continue;
+                InvestmentProject p = extra.get(j);
+                fillCountyProjectRow(row, p, mByProject.get(p.getId()), 0);
+            }
+        }
+        renumberInvestSeq(sheet, "investCounty");
+        return toBytesHssf(wb);
+    }
+
+
+    /** 琛�3锛氭箹鍖楃渷锛堝璐х珯鍦猴級浜垮厓鎶曡祫椤圭洰锛堟ā鏉垮簳绋� .xls锛屾姇璧勬�昏〃 + 瑙勪笂椤圭洰琛� 涓� sheet锛� */
+    public byte[] investBillion(String period, String mode) throws Exception {
+        List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList(
+            new LambdaQueryWrapper<InvestmentMonthly>()
+                .eq(InvestmentMonthly::getReportPeriod, period));
+        Map<Long, InvestmentProject> pmap = new HashMap<>();
+        for (InvestmentProject p : investProjectMapper.selectList(null)) pmap.put(p.getId(), p);
+        List<InvestmentMonthly> mine = new java.util.ArrayList<>();
+        for (InvestmentMonthly m : monthlies) {
+            InvestmentProject p = pmap.get(m.getProjectId());
+            if (p != null && Integer.valueOf(1).equals(p.getIsBillion())) mine.add(m);
+        }
+        int year = Integer.parseInt(period.split("-")[0]);
+        int month = monthOf(period, mode);
+
+        HSSFWorkbook wb = loadInvestTemplate("妯℃澘_浜垮厓鎶曡祫椤圭洰.xls");
+        // ---- sheet1 鎶曡祫鎬昏〃锛氬浐瀹氶鏋讹紝浠呭~锛堜簲锛夊叾浠� 鍏ㄥ勾棰勮锛堝叏鐪佹湰骞磋鍒掓姌浜匡級 ----
+        HSSFSheet s1 = wb.getSheetAt(0);
+        hssfText(s1.getRow(0), 0, year + "骞村浐瀹氳祫浜ф姇璧勯璁″畬鎴愭儏鍐�");
+        double provinceYearPlan = 0;
+        for (InvestmentMonthly m : monthlies) provinceYearPlan += nz(m.getYearPlan());
+        HSSFRow rowOther = s1.getRow(9);
+        for (int c = 1; c <= 5; c++) hssfNum(rowOther, c, null);
+        hssfNum(rowOther, 5, provinceYearPlan == 0 ? null : round(provinceYearPlan / 10000, 2));
+        // ---- sheet2 瑙勪笂椤圭洰琛細琛ㄥご骞翠唤 + 鍚堣 + 椤圭洰琛� ----
+        HSSFSheet s2 = wb.getSheetAt(1);
+        hssfText(s2.getRow(0), 0, year + "骞翠氦閫氬缓璁鹃」鐩偍澶囨儏鍐碉紙瑙勬ā浠ヤ笂椤圭洰锛�");
+        hssfText(s2.getRow(2), 5, "鎴鍒�" + year + "骞�" + month + "鏈堝簳寤鸿鐘舵��");
+        hssfText(s2.getRow(2), 9, "鎴嚦" + (year - 1) + "骞村勾搴曞疄闄呭畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 10, year + "骞�1-3鏈堝疄闄呭畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 11, year + "骞�4鏈堣鍒掑畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 12, year + "骞寸浜屽搴﹁鍒掑畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 13, year + "骞村叏骞磋鍒掑畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 16, year + "骞�1-4鏈堣鍒掑畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 17, year + "骞�4鏈堝疄闄呭畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 18, year + "骞�1-4鏈堢疮璁″畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 19, year + "骞�5鏈堝疄闄呭畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 21, year + "骞�6鏈堝疄闄呭畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 22, year + "骞寸浜屽搴﹀疄闄呭畬鎴愭姇璧勶紙涓囧厓锛�");
+        hssfText(s2.getRow(2), 23, year + "骞�" + month + "鏈堝疄闄呭畬鎴愭姇璧�");
+        // 鍚堣琛� R4锛坈ol0-1 灞炰簬琛ㄥご鍚堝苟鍖猴紝淇濈暀锛涙竻绌哄苟閲嶅啓鏁版嵁鍒楋級
+        HSSFRow totalRow = s2.getRow(4);
+        for (int c = 2; c <= 23; c++) hssfNum(totalRow, c, null);
+        InvestAgg total = new InvestAgg();
+        for (InvestmentMonthly m : mine) total.add(m, pmap.get(m.getProjectId()));
+        hssfNum(totalRow, 8, total.total == 0 ? null : total.total);
+        hssfNum(totalRow, 13, total.yearPlan == 0 ? null : total.yearPlan);
+        hssfNum(totalRow, 23, sumMonth(mine) == 0 ? null : sumMonth(mine));
+        // 椤圭洰琛岋細淇濈暀妯℃澘 R79 娉ㄩ噴琛�
+        HSSFRow styleProject = s2.getRow(5);
+        trimHssfTail(s2, 5, 79);
+        int rowIdx = 5;
+        for (InvestmentMonthly m : mine) {
+            InvestmentProject p = pmap.get(m.getProjectId());
+            HSSFRow row = createRowLike(s2, rowIdx++, styleProject);
+            hssfText(row, 0, "婀栧寳鐪�");
+            hssfText(row, 1, RegionUtil.shortName(p.getCity()));
+            hssfText(row, 2, p.getProjectName());
+            hssfText(row, 3, "瀹㈣繍绔欏満".equals(p.getCategory()) ? "缁煎悎瀹㈣繍鏋㈢航" : "缁煎悎璐ц繍鏋㈢航");
+            hssfText(row, 5, valueOf(m.getProgressStage()));
+            hssfText(row, 6, valueOf(p.getConstructNature()));
+            hssfNum(row, 7, parseTimeYMNum(p.getStartTime()));
+            hssfNum(row, 8, p.getTotalInvestment());
+            hssfNum(row, 13, m.getYearPlan());
+            hssfNum(row, 23, m.getMonthDone());
+        }
+        trimHssfTail(s2, rowIdx, 79);
+        return toBytesHssf(wb);
+    }
+
+    /** 绌轰覆褰掍竴锛坈ellText 鍙兘杩斿洖 null锛� */
+    private String nvl(String v) {
+        return v == null ? "" : v.trim();
+    }
+
+    /** 娓呯┖妯℃澘鏁版嵁琛屾寚瀹氳寖鍥村崟鍏冩牸锛堜繚鐣欐牱寮忎笌鍚堝苟鍗曞厓鏍硷級 */
+    private void blankInvestRow(HSSFRow row, int lastCol) {
+        if (row == null) return;
+        for (int c = 0; c <= lastCol; c++) {
+            Cell cell = row.getCell(c);
+            if (cell != null) cell.setBlank();
+        }
+    }
+
+    /** 妯℃澘甯傚窞鐭悕 -> 瑙勮寖甯傚窞鍚嶏紙姝︽眽->姝︽眽甯傘�佹灄鍖�->绁炲啘鏋舵灄鍖恒�佹仼鏂藉窞->鎭╂柦宸烇級 */
+    private String templateCityFull(String label) {
+        if (label == null) return null;
+        String t = label.trim();
+        String canon = RegionUtil.normalizeCityName(t);
+        for (String city : RegionUtil.cityList()) {
+            if (city.equals(canon)) return city;
+        }
+        for (String city : RegionUtil.cityList()) {
+            if (RegionUtil.shortName(city).equals(canon) || RegionUtil.shortName(city).equals(t)) return city;
+        }
+        return canon;
+    }
+
+    /** 鍒ゆ柇妯℃澘琛� A 鍒楁槸鍚︿负甯傚窞灏忚妭鏍囩 */
+    private boolean isCityLabel(String label) {
+        if (label == null || label.isEmpty()) return false;
+        String full = templateCityFull(label);
+        return full != null && RegionUtil.cityList().contains(full);
+    }
+
+    /** 鍦ㄥ�欓�夐」鐩睜涓寜鍚嶇О鍖归厤 DB 椤圭洰锛堝彲閫� 甯傚窞/绫诲埆 杩囨护 + 鍘垮尯鍓嶇紑娑堟锛夛紝鏈懡涓繑鍥� null */
+    private InvestmentProject matchInvest(List<InvestmentProject> pool, String name, String cityFull, String category) {
+        if (name == null || name.trim().isEmpty()) return null;
+        String norm = normInvestName(name);
+        if (norm.isEmpty()) return null;
+        String cityCanon = cityFull == null || cityFull.trim().isEmpty() ? null : RegionUtil.normalizeCityName(cityFull);
+        String county = extractInvestCounty(name);
+        List<InvestmentProject> cands = new java.util.ArrayList<>();
+        for (InvestmentProject p : pool) {
+            if (category != null && !category.equals(p.getCategory())) continue;
+            if (cityCanon != null && !cityCanon.equals(RegionUtil.normalizeCityName(p.getCity()))) continue;
+            cands.add(p);
+        }
+        if (cands.isEmpty()) return null;
+        // 褰掍竴鍖栧悓鍚嶅鍊欓�夛細鍘垮尯鍓嶇紑娑堟锛堝 缃楃敯鍘跨患鍚堢墿娴佸洯 vs 鍜镐赴鍘跨患鍚堢墿娴佸洯锛�
+        if (!county.isEmpty()) {
+            for (InvestmentProject p : cands) {
+                if (normInvestName(p.getProjectName()).equals(norm)
+                    && p.getProjectName() != null && p.getProjectName().contains(county)) {
+                    return p;
+                }
+            }
+        }
+        InvestmentProject best = null;
+        int bestScore = Integer.MIN_VALUE;
+        for (InvestmentProject p : cands) {
+            String pn = normInvestName(p.getProjectName());
+            if (pn.isEmpty()) continue;
+            int s = investNameScore(norm, pn, county, p.getProjectName());
+            if (s > bestScore) {
+                bestScore = s;
+                best = p;
+            }
+        }
+        return bestScore > 0 ? best : null;
+    }
+
+    /** 椤圭洰鍚嶅綊涓�鍖栵細鍘绘嫭鍙峰瓧绗︺�佽繛鎺ョ锛岀綏椹暟瀛楃粺涓�锛屽幓鐪�/甯�/鍘�/鍖哄墠缂�涓庣┖鐧斤紙涓庡鏍稿紩鎿庝竴鑷达級 */
+    private String normInvestName(String name) {
+        if (name == null) return "";
+        String s = name.trim();
+        s = s.replaceAll("[锛�(]", "").replaceAll("[锛�)]", "");
+        s = s.replaceAll("[路鈥⑩�旓紞_\\-]", "");
+        s = s.replaceAll("[鈪犫叀鈪⑩叄鈪も叆鈪︹収鈪ㄢ叐鈪扳叡鈪测叧鈪粹叺鈪垛叿鈪糕吂]", "I");
+        s = s.replaceAll("^(婀栧寳鐪亅姝︽眽甯倈榛勭煶甯倈鍗佸牥甯倈瀹滄槍甯倈瑗勯槼甯倈閯傚窞甯倈鑽嗛棬甯倈瀛濇劅甯倈鑽嗗窞甯倈榛勫唸甯倈鍜稿畞甯倈闅忓窞甯倈鎭╂柦宸瀨浠欐甯倈娼滄睙甯倈澶╅棬甯倈绁炲啘鏋舵灄鍖�)", "");
+        s = s.replaceAll("^(?:[\\u4e00-\\u9fa5]{2,4}(?:鍘縷甯倈鍖�))", "");
+        s = s.replaceAll("\\s+", "");
+        return s;
+    }
+
+    /** 鍚嶇О鐩镐技搴﹁瘎鍒嗭細绮剧‘>鍓嶇紑鍖呭惈>涓悗閮ㄥ寘鍚�>5瀛�>瀛愬簭鍒�>鏈�闀垮叕鍏卞瓙涓诧紱鍘垮尯鍓嶇紑涓嶄竴鑷存嫆缁�/澶ф墸鍒� */
+    private int investNameScore(String norm, String key, String county, String rawB) {
+        if (norm.equals(key)) {
+            // 褰掍竴鍖栧悗鍚屽悕锛氳嫢妯℃澘甯﹀幙鍖哄墠缂�鑰屽�欓�夊師濮嬪悕涓嶅惈璇ュ幙鍖� 鈫� 鎷掔粷锛堥槻 瀹滈兘甯� vs 褰撻槼甯� 璇厤锛�
+            if (county != null && !county.isEmpty() && (rawB == null || !rawB.contains(county))) return -1;
+            return 2000;
+        }
+        if (key.length() < 5 || norm.length() < 5) return -1;
+        String shortSide = key.length() <= norm.length() ? key : norm;
+        String longSide = key.length() > norm.length() ? key : norm;
+        int idx = longSide.indexOf(shortSide);
+        int lenDiff = Math.abs(key.length() - norm.length());
+        int base;
+        if (idx == 0) {
+            base = 1000 - lenDiff * 2;
+        } else if (idx > 0 && shortSide.length() >= 6) {
+            base = 500 - idx * 5 - lenDiff * 2 + shortSide.length();
+        } else if (idx > 0 && shortSide.length() == 5 && lenDiff <= 5) {
+            String diff = longSide.substring(0, idx) + longSide.substring(idx + shortSide.length());
+            if (diff.matches(".*(鍘縷甯倈宸�).*")) return -1;
+            base = 400 - idx * 5 - lenDiff;
+        } else if (isInvestSubsequence(shortSide, longSide) && lenDiff <= 8) {
+            base = 300 - lenDiff * 3;
+        } else {
+            int l = investLcs(norm, key);
+            if (l >= 6) {
+                base = 200 + l * 5 - lenDiff;
+            } else if (l == 5 && lenDiff <= 4 && county != null && !county.isEmpty()
+                       && rawB != null && rawB.contains(county)) {
+                base = 150 + l * 5 - lenDiff;
+            } else {
+                return -1;
+            }
+        }
+        if (county != null && !county.isEmpty() && (rawB == null || !rawB.contains(county))) {
+            base -= 400;
+        }
+        return base;
+    }
+
+    /** 鍒ゆ柇鐭悕鏄惁涓洪暱鍚嶇殑瀛楃瀛愬簭鍒� */
+    private boolean isInvestSubsequence(String shortSide, String longSide) {
+        int i = 0;
+        for (int j = 0; i < shortSide.length() && j < longSide.length(); j++) {
+            if (shortSide.charAt(i) == longSide.charAt(j)) i++;
+        }
+        return i == shortSide.length();
+    }
+
+    /** 鏈�闀垮叕鍏卞瓙涓查暱搴� */
+    private int investLcs(String a, String b) {
+        int n = a.length(), m = b.length();
+        if (n == 0 || m == 0) return 0;
+        int[][] dp = new int[n + 1][m + 1];
+        int max = 0;
+        for (int i = 1; i <= n; i++) {
+            for (int j = 1; j <= m; j++) {
+                if (a.charAt(i - 1) == b.charAt(j - 1)) {
+                    dp[i][j] = dp[i - 1][j - 1] + 1;
+                    if (dp[i][j] > max) max = dp[i][j];
+                }
+            }
+        }
+        return max;
+    }
+
+    /** 鎻愬彇鍚嶇О涓殑 鍘�/甯�/鍖� 鍓嶇紑锛堝 鍜镐赴鍘裤�佸疁閮藉競锛� */
+    private String extractInvestCounty(String name) {
+        if (name == null) return "";
+        java.util.regex.Matcher mm = java.util.regex.Pattern.compile("([\\u4e00-\\u9fa5]{2,4}(?:鍘縷甯倈鍖�))").matcher(name);
+        if (mm.find()) return mm.group(1);
+        return "";
+    }
+
+
+    private double sumMonth(List<InvestmentMonthly> list) {
+        double s = 0;
+        for (InvestmentMonthly m : list) s += nz(m.getMonthDone());
+        return s;
+    }
+
+    private String padTimeYM(String t) {
+        if (t == null) return null;
+        String x = t.trim();
+        if (x.matches("\\d{4}")) return x + "01";
+        return x;
+    }
+
+    private String valueOf(String v) {
+        return v == null || v.trim().isEmpty() ? "" : v.trim();
+    }
+    private double nz(Double v) {
+        return v == null ? 0.0 : v;
+    }
     private byte[] toBytes(XSSFWorkbook wb) throws Exception {
         try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
             wb.write(out);
@@ -815,4 +3874,4 @@
             return out.toByteArray();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/rulemanage/controller/RuleManageController.java b/traffic-audit-server/src/main/java/com/trafficaudit/rulemanage/controller/RuleManageController.java
new file mode 100644
index 0000000..1ed1c90
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/rulemanage/controller/RuleManageController.java
@@ -0,0 +1,57 @@
+package com.trafficaudit.rulemanage.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.trafficaudit.common.Result;
+import com.trafficaudit.rulemanage.entity.AuditRule;
+import com.trafficaudit.rulemanage.mapper.AuditRuleMapper;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/** 瑙勫垯绠$悊锛氭寜鎶ヨ〃绫诲瀷鏌ョ湅/鍚仠/璋冩暣闃堝�� */
+@RestController
+@RequestMapping("/api/rules")
+public class RuleManageController {
+
+    @Resource
+    private AuditRuleMapper ruleMapper;
+
+    /** 瑙勫垯鍒楄〃锛屽彲鎸夋姤琛ㄧ被鍨嬭繃婊� */
+    @GetMapping("/list")
+    public Result<List<AuditRule>> list(@RequestParam(value = "reportType", required = false) String reportType) {
+        LambdaQueryWrapper<AuditRule> qw = new LambdaQueryWrapper<>();
+        if (reportType != null && !reportType.trim().isEmpty()) {
+            qw.eq(AuditRule::getReportType, reportType.trim());
+        }
+        qw.orderByAsc(AuditRule::getSortOrder);
+        return Result.ok(ruleMapper.selectList(qw));
+    }
+
+    /** 鍚仠瑙勫垯 */
+    @PostMapping("/toggle")
+    public Result<String> toggle(@RequestParam("id") Long id,
+                                 @RequestParam("enabled") Integer enabled) {
+        AuditRule rule = ruleMapper.selectById(id);
+        if (rule == null) return Result.error("瑙勫垯涓嶅瓨鍦�");
+        rule.setIsEnabled(enabled == null || enabled == 0 ? 0 : 1);
+        rule.setUpdatedAt(LocalDateTime.now());
+        ruleMapper.updateById(rule);
+        return Result.ok("宸叉洿鏂�");
+    }
+
+    /** 鏇存柊闃堝��/绛夌骇/鎻忚堪 */
+    @PostMapping("/update")
+    public Result<String> update(@RequestBody AuditRule body) {
+        if (body.getId() == null) return Result.error("缂哄皯瑙勫垯ID");
+        AuditRule rule = ruleMapper.selectById(body.getId());
+        if (rule == null) return Result.error("瑙勫垯涓嶅瓨鍦�");
+        if (body.getThreshold() != null) rule.setThreshold(body.getThreshold());
+        if (body.getAlertLevel() != null) rule.setAlertLevel(body.getAlertLevel());
+        if (body.getDescription() != null) rule.setDescription(body.getDescription());
+        rule.setUpdatedAt(LocalDateTime.now());
+        ruleMapper.updateById(rule);
+        return Result.ok("宸蹭繚瀛�");
+    }
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/security/controller/LoginController.java b/traffic-audit-server/src/main/java/com/trafficaudit/security/controller/LoginController.java
index 5424c4e..dac86e5 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/security/controller/LoginController.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/security/controller/LoginController.java
@@ -1,7 +1,11 @@
 package com.trafficaudit.security.controller;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.trafficaudit.common.Result;
 import com.trafficaudit.security.utils.JwtUtils;
+import com.trafficaudit.system.entity.User;
+import com.trafficaudit.system.mapper.UserMapper;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
@@ -14,19 +18,40 @@
 
     @Resource
     private JwtUtils jwtUtils;
+    @Resource
+    private UserMapper userMapper;
+    @Resource
+    private PasswordEncoder passwordEncoder;
 
     @PostMapping("/login")
     public Result<Map<String, Object>> login(@RequestBody Map<String, String> params) {
         String username = params.get("username");
         String password = params.get("password");
-
-        if ("admin".equals(username) && "admin123".equals(password)) {
-            String token = jwtUtils.generateToken(username);
-            Map<String, Object> data = new HashMap<>();
-            data.put("token", token);
-            data.put("username", username);
-            return Result.ok(data);
+        if (username == null || username.trim().isEmpty() || password == null || password.isEmpty()) {
+            return Result.error(401, "鐢ㄦ埛鍚嶆垨瀵嗙爜閿欒");
         }
-        return Result.error(401, "鐢ㄦ埛鍚嶆垨瀵嗙爜閿欒");
+        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
+            .eq(User::getUsername, username.trim()));
+        if (user == null || !Integer.valueOf(1).equals(user.getStatus())) {
+            return Result.error(401, "鐢ㄦ埛鍚嶆垨瀵嗙爜閿欒");
+        }
+        String encoded = user.getPassword();
+        boolean ok;
+        try {
+            ok = encoded != null && passwordEncoder.matches(password, encoded);
+        } catch (IllegalArgumentException e) {
+            // 搴撲腑瀵嗙爜涓嶆槸鍚堟硶 BCrypt 鏃跺洖閫�涓烘槑鏂囧姣旓紙閬垮厤鏃ф暟鎹棤娉曠櫥褰曪級
+            ok = encoded != null && encoded.equals(password);
+        }
+        if (!ok) {
+            return Result.error(401, "鐢ㄦ埛鍚嶆垨瀵嗙爜閿欒");
+        }
+        String token = jwtUtils.generateToken(username.trim());
+        Map<String, Object> data = new HashMap<>();
+        data.put("token", token);
+        data.put("username", username.trim());
+        data.put("realName", user.getRealName());
+        data.put("modules", user.getModules() == null ? "" : user.getModules());
+        return Result.ok(data);
     }
-}
+}
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/system/controller/UserController.java b/traffic-audit-server/src/main/java/com/trafficaudit/system/controller/UserController.java
new file mode 100644
index 0000000..d44adf3
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/system/controller/UserController.java
@@ -0,0 +1,107 @@
+package com.trafficaudit.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.trafficaudit.common.Result;
+import com.trafficaudit.system.entity.User;
+import com.trafficaudit.system.mapper.UserMapper;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** 鐢ㄦ埛绠$悊锛堝惈璐熻矗鎶ヨ〃妯″潡閰嶇疆锛歠reight/passenger/invest/energy/cityPassenger锛� */
+@RestController
+@RequestMapping("/api/user")
+public class UserController {
+
+    @Resource
+    private UserMapper userMapper;
+    @Resource
+    private PasswordEncoder passwordEncoder;
+
+    @GetMapping("/list")
+    public Result<List<Map<String, Object>>> list(@RequestParam(value = "keyword", required = false) String keyword) {
+        LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
+        if (keyword != null && !keyword.trim().isEmpty()) {
+            String k = keyword.trim();
+            qw.and(w -> w.like(User::getUsername, k).or().like(User::getRealName, k));
+        }
+        qw.orderByAsc(User::getId);
+        List<Map<String, Object>> data = new ArrayList<>();
+        for (User u : userMapper.selectList(qw)) {
+            Map<String, Object> m = new HashMap<>();
+            m.put("id", u.getId());
+            m.put("username", u.getUsername());
+            m.put("realName", u.getRealName());
+            m.put("phone", u.getPhone());
+            m.put("status", u.getStatus());
+            m.put("modules", u.getModules());
+            m.put("createdAt", u.getCreatedAt() == null ? null : u.getCreatedAt().toString());
+            data.add(m);
+        }
+        return Result.ok(data);
+    }
+
+    /** 鏂板/缂栬緫鐢ㄦ埛锛涚紪杈戞椂 password 鐣欑┖鍒欎笉淇敼瀵嗙爜锛沵odules 涓烘ā鍧� key 鏁扮粍 */
+    @PostMapping("/save")
+    public Result<Void> save(@RequestBody Map<String, Object> params) {
+        Object idObj = params.get("id");
+        Long id = idObj == null ? null : Long.valueOf(idObj.toString());
+        String username = str(params.get("username"));
+        if (username == null || username.trim().isEmpty()) return Result.error(400, "鐢ㄦ埛鍚嶄笉鑳戒负绌�");
+        username = username.trim();
+        String password = str(params.get("password"));
+        String realName = str(params.get("realName"));
+        String phone = str(params.get("phone"));
+        Integer status = params.get("status") == null ? 1 : Integer.valueOf(params.get("status").toString());
+        List<String> moduleKeys = new ArrayList<>();
+        Object modulesObj = params.get("modules");
+        if (modulesObj instanceof List) {
+            for (Object o : (List<?>) modulesObj) if (o != null) moduleKeys.add(o.toString());
+        }
+        String modules = String.join(",", moduleKeys);
+
+        LambdaQueryWrapper<User> dup = new LambdaQueryWrapper<User>().eq(User::getUsername, username);
+        if (id != null) dup.ne(User::getId, id);
+        if (userMapper.selectCount(dup) != null && userMapper.selectCount(dup) > 0) return Result.error(400, "鐢ㄦ埛鍚嶅凡瀛樺湪");
+
+        if (id == null) {
+            if (password == null || password.isEmpty()) return Result.error(400, "鏂板鐢ㄦ埛蹇呴』璁剧疆瀵嗙爜");
+            User u = new User();
+            u.setUsername(username);
+            u.setRealName(realName);
+            u.setPhone(phone);
+            u.setStatus(status);
+            u.setModules(modules);
+            u.setPassword(passwordEncoder.encode(password));
+            userMapper.insert(u);
+        } else {
+            User u = userMapper.selectById(id);
+            if (u == null) return Result.error(404, "鐢ㄦ埛涓嶅瓨鍦�");
+            u.setRealName(realName);
+            u.setPhone(phone);
+            u.setStatus(status);
+            u.setModules(modules);
+            if (password != null && !password.isEmpty()) u.setPassword(passwordEncoder.encode(password));
+            userMapper.updateById(u);
+        }
+        return Result.ok(null);
+    }
+
+    @PostMapping("/delete")
+    public Result<Void> delete(@RequestParam("id") Long id) {
+        User u = userMapper.selectById(id);
+        if (u == null) return Result.error(404, "鐢ㄦ埛涓嶅瓨鍦�");
+        if ("admin".equals(u.getUsername())) return Result.error(400, "admin 涓嶅厑璁稿垹闄�");
+        userMapper.deleteById(id);
+        return Result.ok(null);
+    }
+
+    private String str(Object o) {
+        return o == null ? null : o.toString();
+    }
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/system/entity/OperationLog.java b/traffic-audit-server/src/main/java/com/trafficaudit/system/entity/OperationLog.java
new file mode 100644
index 0000000..5d987b5
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/system/entity/OperationLog.java
@@ -0,0 +1,22 @@
+package com.trafficaudit.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.util.Date;
+
+/** 鎿嶄綔鏃ュ織锛坰ys_operation_log锛夛細鎶ヨ〃鐢熸垚/瀵煎叆绛夊叧閿搷浣滅暀鐥� */
+@Data
+@TableName("sys_operation_log")
+public class OperationLog {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long userId;
+    private String module;
+    private String action;
+    private String detail;
+    private String ipAddress;
+    private Date createdAt;
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/system/entity/User.java b/traffic-audit-server/src/main/java/com/trafficaudit/system/entity/User.java
index 23d6ceb..6b2a781 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/system/entity/User.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/system/entity/User.java
@@ -17,4 +17,6 @@
     private String phone;
     private Long deptId;
     private Integer status;
+    /** 璐熻矗鎶ヨ〃妯″潡锛岄�楀彿鍒嗛殧锛歠reight,passenger,invest,energy,cityPassenger */
+    private String modules;
 }
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/system/mapper/OperationLogMapper.java b/traffic-audit-server/src/main/java/com/trafficaudit/system/mapper/OperationLogMapper.java
new file mode 100644
index 0000000..6b5c0fd
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/system/mapper/OperationLogMapper.java
@@ -0,0 +1,8 @@
+package com.trafficaudit.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.trafficaudit.system.entity.OperationLog;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface OperationLogMapper extends BaseMapper<OperationLog> {}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/system/service/OperationLogService.java b/traffic-audit-server/src/main/java/com/trafficaudit/system/service/OperationLogService.java
new file mode 100644
index 0000000..f610d5c
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/system/service/OperationLogService.java
@@ -0,0 +1,75 @@
+package com.trafficaudit.system.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.trafficaudit.security.utils.JwtUtils;
+import com.trafficaudit.system.entity.OperationLog;
+import com.trafficaudit.system.entity.User;
+import com.trafficaudit.system.mapper.OperationLogMapper;
+import com.trafficaudit.system.mapper.UserMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 鎿嶄綔鏃ュ織鏈嶅姟锛氳褰曞叧閿搷浣滐紙鎶ヨ〃鐢熸垚/绯诲垪鎵撳寘绛夛級锛岃嚜鍔ㄤ粠 Authorization 澶磋В鏋愬綋鍓嶇敤鎴枫��
+ * 瑙f瀽澶辫触鎴栨湭鐧诲綍鏃朵笉鎶涘紓甯革紝浠呰褰曟搷浣滄湰韬��
+ */
+@Slf4j
+@Service
+public class OperationLogService {
+
+    @Resource
+    private OperationLogMapper operationLogMapper;
+    @Resource
+    private UserMapper userMapper;
+    @Resource
+    private JwtUtils jwtUtils;
+    @Resource
+    private HttpServletRequest request;
+
+    public void record(String module, String action, String detail) {
+        try {
+            OperationLog entry = new OperationLog();
+            entry.setModule(module);
+            entry.setAction(action);
+            entry.setDetail(detail);
+            entry.setUserId(resolveUserId());
+            entry.setIpAddress(clientIp());
+            operationLogMapper.insert(entry);
+        } catch (Exception e) {
+            log.warn("鎿嶄綔鏃ュ織璁板綍澶辫触: {}", e.getMessage());
+        }
+    }
+
+    private Long resolveUserId() {
+        try {
+            String auth = request.getHeader("Authorization");
+            if (auth == null || !auth.startsWith("Bearer ")) {
+                return null;
+            }
+            String username = jwtUtils.getUsernameFromToken(auth.substring(7));
+            if (username == null || username.isEmpty()) {
+                return null;
+            }
+            User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
+                .eq(User::getUsername, username).last("limit 1"));
+            return user == null ? null : user.getId();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private String clientIp() {
+        try {
+            String ip = request.getHeader("X-Forwarded-For");
+            if (ip != null && !ip.isEmpty()) {
+                return ip.split(",")[0].trim();
+            }
+            return request.getRemoteAddr();
+        } catch (Exception e) {
+            return "";
+        }
+    }
+}
diff --git a/traffic-audit-server/src/main/resources/application.yml.example b/traffic-audit-server/src/main/resources/application.yml.example
index e1785be..e819dd2 100644
--- a/traffic-audit-server/src/main/resources/application.yml.example
+++ b/traffic-audit-server/src/main/resources/application.yml.example
@@ -4,7 +4,7 @@
 
 spring:
   datasource:
-    url: jdbc:mysql://localhost:3305/traffic_audit?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
+    url: jdbc:mysql://localhost:3308/traffic_audit?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
     username: root
     password: ${MYSQL_PASSWORD:root}
     driver-class-name: com.mysql.cj.jdbc.Driver
diff --git a/traffic-audit-web/src/App.vue b/traffic-audit-web/src/App.vue
index 20854ee..7189b2d 100644
--- a/traffic-audit-web/src/App.vue
+++ b/traffic-audit-web/src/App.vue
@@ -1,6 +1,8 @@
 <template>
   <div id="app">
-    <router-view />
+    <keep-alive include="AuditResult">
+      <router-view />
+    </keep-alive>
   </div>
 </template>
 <script>
diff --git a/traffic-audit-web/src/main.js b/traffic-audit-web/src/main.js
index 6b77a3f..6ff9576 100644
--- a/traffic-audit-web/src/main.js
+++ b/traffic-audit-web/src/main.js
@@ -7,6 +7,19 @@
 Vue.use(ElementUI)
 Vue.config.productionTip = false
 
+// 鍏ㄥ眬鐧诲嚭锛氭竻闄ょ櫥褰曟�佸苟鍥炵櫥褰曢〉锛堝悇椤甸潰渚ц竟鏍忋�岄��鍑虹櫥褰曘�嶈彍鍗曞叡鐢級
+Vue.mixin({
+  methods: {
+    logout() {
+      localStorage.removeItem('token')
+      localStorage.removeItem('username')
+      localStorage.removeItem('realName')
+      localStorage.removeItem('modules')
+      this.$router.push('/login')
+    }
+  }
+})
+
 new Vue({
   router,
   render: h => h(App)
diff --git a/traffic-audit-web/src/router/index.js b/traffic-audit-web/src/router/index.js
index 2540f38..b3727c2 100644
--- a/traffic-audit-web/src/router/index.js
+++ b/traffic-audit-web/src/router/index.js
@@ -3,7 +3,7 @@
 
 Vue.use(Router)
 
-export default new Router({
+const router = new Router({
   routes: [
     { path: '/', redirect: '/login' },
     { path: '/login', name: 'Login', component: () => import('@/views/Login.vue') },
@@ -17,3 +17,14 @@
     { path: '/users', name: 'UserManage', component: () => import('@/views/UserManage.vue') }
   ]
 })
+
+// 绠�鍗曠櫥褰曞畧鍗細鏈櫥褰曪紙鏃� token锛夎闂笟鍔¢〉璺崇櫥褰曢〉
+router.beforeEach((to, from, next) => {
+  const token = localStorage.getItem('token')
+  if (to.path === '/login') { next(); return }
+  if (!token) { next('/login'); return }
+  next()
+})
+
+export default router
+
diff --git a/traffic-audit-web/src/views/AuditResult.vue b/traffic-audit-web/src/views/AuditResult.vue
index 4fc27a0..50c989a 100644
--- a/traffic-audit-web/src/views/AuditResult.vue
+++ b/traffic-audit-web/src/views/AuditResult.vue
@@ -12,56 +12,213 @@
           <el-menu-item index="/report">鎶ヨ〃鐢熸垚</el-menu-item>
           <el-menu-item index="/rules">瑙勫垯绠$悊</el-menu-item>
           <el-menu-item index="/users">鐢ㄦ埛绠$悊</el-menu-item>
-        </el-menu>
+          <el-menu-item index="/login" @click="logout">閫�鍑虹櫥褰�</el-menu-item>
+</el-menu>
       </el-aside>
-      <el-main>
-        <h3>瀹℃牳缁撴灉</h3>
-        <el-form :inline="true">
+      <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-radio-group v-model="reportType" @change="onTypeChange">
+              <el-radio label="H2032">璐ц繍鏈堟姤</el-radio>
+              <el-radio label="H2031">鏃呭鏈堟姤</el-radio>
+              <el-radio label="CITY_BUS">鍩庡競鍏氦鏈堟姤</el-radio>
+              <el-radio label="CITY_TAXI">宸℃父鍑虹鏈堟姤</el-radio>
+              <el-radio label="INVEST">鎶曡祫鏈堟姤</el-radio>
+              <el-radio label="H204">鑳借�楀鎶�</el-radio>
+            
+            
+            
+            </el-radio-group>
+          </el-form-item>
           <el-form-item label="鎶ヨ〃鏈�">
-            <el-date-picker v-model="period" type="month" placeholder="閫夋嫨鏈堜唤" value-format="yyyy-MM"></el-date-picker>
+            <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>
             <el-button @click="queryResults">鏌ヨ缁撴灉</el-button>
+            <el-button type="success" icon="el-icon-download" @click="exportExcel">瀵煎嚭 Excel</el-button>
+            <el-button v-if="!focusMode" size="small" icon="el-icon-full-screen" @click="focusMode = true">鑱氱劍鏌ョ湅</el-button>
+            <el-button v-else type="primary" size="small" icon="el-icon-crop" @click="focusMode = false">閫�鍑鸿仛鐒�</el-button>
           </el-form-item>
         </el-form>
-        <el-form :inline="true" style="margin-top:12px">
+        <!-- 闂姒傝缁熻鍗� -->
+        <div v-if="results.length && !focusMode" class="sev-cards" style="flex:none">
+          <div class="sev-card sev-red"><b>{{ severityStats.red }}</b> 绾㈣壊闂</div>
+          <div class="sev-card sev-yellow"><b>{{ severityStats.yellow }}</b> 榛勮壊闂</div>
+          <div class="sev-card sev-pending"><b>{{ severityStats.pending }}</b> 寰呭鐞�</div>
+          <div class="sev-card sev-done"><b>{{ severityStats.done }}</b> 宸插鐞�</div>
+        </div>
+        <el-form v-if="!focusMode" :inline="true" style="margin-top:8px;flex:none">
           <el-form-item label="浼佷笟鍚嶇О">
-            <el-input v-model="enterpriseFilter" placeholder="杈撳叆浼佷笟鍚嶇О鍏抽敭瀛�" clearable style="width:220px"></el-input>
+            <el-input v-model="enterpriseFilter" placeholder="杈撳叆浼佷笟鍚嶇О鍏抽敭瀛�" clearable style="width:200px" @keyup.enter.native="queryResults"></el-input>
           </el-form-item>
           <el-form-item label="瀹℃牳瑙勫垯">
-            <el-select v-model="ruleFilter" placeholder="鍏ㄩ儴瑙勫垯" clearable style="width:200px">
+            <el-select v-model="ruleFilter" placeholder="鍏ㄩ儴瑙勫垯" clearable style="width:180px">
               <el-option v-for="rn in ruleOptions" :key="rn" :label="rn" :value="rn"></el-option>
             </el-select>
           </el-form-item>
+          <el-form-item label="瑙嗗浘">
+            <el-radio-group v-model="viewMode">
+              <el-radio label="detail">鎸夋潯鏄庣粏</el-radio>
+              <el-radio label="merged">鎸変紒涓氬悎骞�</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item v-if="viewMode === 'detail'">
+            <el-button size="small" type="success" :disabled="!batchPending.length" @click="reviewBatch('CONFIRMED')">鎵归噺纭({{ batchPending.length }})</el-button>
+            <el-button size="small" type="info" :disabled="!batchPending.length" @click="reviewBatch('IGNORED')">鎵归噺蹇界暐({{ batchPending.length }})</el-button>
+          </el-form-item>
           <el-form-item>
             <el-button size="small" @click="clearFilters">娓呯┖杩囨护</el-button>
+            <el-button size="small" plain @click="statsCollapsed = !statsCollapsed">{{ statsCollapsed ? '灞曞紑' : '鏀惰捣' }}瑙勫垯缁熻</el-button>
           </el-form-item>
           <el-form-item>
-            <span style="color:#909399;font-size:12px">鍏� {{ results.length }} 鏉★紝褰撳墠鏄剧ず {{ filteredResults.length }} 鏉�</span>
+            <span style="color:#909399;font-size:12px">鍏� {{ results.length }} 鏉¢棶棰橈紝{{ mergedResults.length }} 瀹朵紒涓�</span>
           </el-form-item>
         </el-form>
-        <div class="table-area">
-        <el-table :data="filteredResults" border v-loading="loading" height="100%">
-          <el-table-column prop="enterpriseName" label="浼佷笟鍚嶇О" min-width="200" show-overflow-tooltip></el-table-column>
-          <el-table-column prop="ruleName" label="瀹℃牳瑙勫垯" min-width="140"></el-table-column>
-          <el-table-column prop="actualValue" label="瀹為檯鍊�" min-width="180" show-overflow-tooltip></el-table-column>
-          <el-table-column prop="thresholdValue" label="闃堝��/瀵规瘮" min-width="120"></el-table-column>
-          <el-table-column prop="deviation" label="鍋忓樊璇存槑" min-width="140" show-overflow-tooltip></el-table-column>
-          <el-table-column label="绛夌骇" width="80">
+        <div v-if="ruleStats.length && !statsCollapsed && !focusMode" class="rule-stats" style="flex:none">
+          <div class="stats-cards">
+            <div v-for="rs in sortedRuleStats" :key="rs.ruleId" class="stat-card"
+                 :class="{ 'has-hit': rs.hit > 0, 'is-skipped': rs.skipped, active: ruleFilter === rs.ruleName }"
+                 :title="rs.ruleName + '锛�' + rs.ruleCode + '锛夋鏌�' + rs.checked + ' 鍛戒腑' + rs.hit + '锛堢偣鍑昏繃婊わ級'"
+                 @click="toggleRuleFilter(rs)">
+              <template v-if="rs.skipped">
+                <span class="stat-name">{{ rs.ruleName }}</span>
+                <span class="skip-label">鍩哄噯缂哄け锛屽凡璺宠繃</span>
+              </template>
+              <template v-else>
+                <span class="stat-name">{{ rs.ruleName }}</span>
+                <span class="stat-num"><b :class="{ 'hit-num': rs.hit > 0 }">{{ rs.hit }}</b> / {{ rs.checked }}</span>
+              </template>
+            </div>
+          </div>
+        </div>
+        <div v-if="focusMode" style="flex:none;padding:0 2px 6px;color:#909399;font-size:12px">
+          鍏� {{ results.length }} 鏉¢棶棰橈紝{{ mergedResults.length }} 瀹朵紒涓�
+        </div>
+        <div class="table-area" style="flex:1;min-height:0">
+        <!-- 鏄庣粏瑙嗗浘锛氫竴鏉¢棶棰樹竴寮犲崱鐗� -->
+        <div v-if="viewMode === 'detail'" class="detail-cards-wrap" v-loading="loading">
+          <div class="detail-toolbar">
+            <el-checkbox :value="allSelected" :indeterminate="someSelected" @change="toggleSelectAll">鍏ㄩ�夊綋鍓� {{ filteredResults.length }} 鏉�</el-checkbox>
+            <span class="toolbar-tip">鍕鹃�夊悗鍙壒閲忕‘璁� / 蹇界暐</span>
+          </div>
+          <div v-for="row in filteredResults" :key="row.id" class="detail-card"
+               :class="{ 'card-red': row.alertLevel === 'RED', 'card-yellow': row.alertLevel === 'YELLOW', 'card-done': row.status !== 'PENDING' }">
+            <div class="card-head">
+              <el-checkbox v-model="row._selected" @change="updateBatchSelection"></el-checkbox>
+              <div class="card-ent">
+                <div class="ent-name">{{ row.enterpriseName || row.city || '鏈煡浼佷笟' }}</div>
+                <div class="ent-sub">{{ row.reportPeriod }}</div>
+              </div>
+              <el-tag :type="row.alertLevel === 'RED' ? 'danger' : 'warning'" size="small" effect="dark">{{ row.alertLevel }}</el-tag>
+              <el-tag :type="statusType(row.status)" size="small" effect="plain" class="status-tag">
+                <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 ? '鏀惰捣瑙i噴' : '灞曞紑濉啓/鏌ョ湅瑙i噴'"
+                       @click="toggleExplain(row.reportId)">
+                {{ row.verifyExplanation ? '瑙i噴锛氬凡濉啓' : '瑙i噴锛氭湭濉啓' }}
+              </el-tag>
+              <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>
+            </div>
+            <div class="card-body">
+              <div class="rule-title">
+                <b>{{ row.ruleName }}</b>
+                <span class="rule-code">{{ row.ruleCode }}</span>
+              </div>
+              <div class="card-details">
+                <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">浼佷笟瑙i噴</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="濉啓/淇敼璇ヤ紒涓氱殑瑙i噴璇存槑鈥�"></el-input>
+              <div class="explain-actions">
+                <el-button size="mini" type="primary" plain :loading="savingExplainId === row.reportId" @click="saveExplain(row)">淇濆瓨瑙i噴</el-button>
+                <el-button v-if="(explainDrafts[row.reportId] || '') !== (row.verifyExplanation || '')" size="mini" text @click="resetDraft(row.reportId)">閲嶇疆</el-button>
+              </div>
+            </div>
+          </div>
+          <div v-if="!filteredResults.length" class="empty-tip">
+            <el-empty description="褰撳墠绛涢�夋潯浠朵笅娌℃湁闂鏁版嵁"></el-empty>
+          </div>
+        </div>
+        <!-- 鍚堝苟瑙嗗浘 -->
+        <el-table v-else :data="mergedResults" border stripe v-loading="loading" height="100%"
+          :header-cell-style="{ background: '#F5F7FA', color: '#303133', fontWeight: 600 }">
+          <el-table-column label="浼佷笟" min-width="190">
             <template slot-scope="scope">
-              <el-tag :type="scope.row.alertLevel === 'RED' ? 'danger' : 'warning'">{{ scope.row.alertLevel }}</el-tag>
+              <div style="font-weight:600">{{ scope.row.enterpriseName }}</div>
+              <div style="color:#909399;font-size:12px;margin-top:2px">鍏� {{ scope.row.items.length }} 鏉¢棶棰�</div>
             </template>
           </el-table-column>
-          <el-table-column prop="status" label="鐘舵��" width="100">
+          <el-table-column label="瀹℃牳闂鏄庣粏" min-width="520">
             <template slot-scope="scope">
-              <el-tag :type="statusType(scope.row.status)">{{ scope.row.status }}</el-tag>
+              <div v-for="(item, i) in scope.row.items" :key="item.id"
+                   style="padding:6px 0;border-bottom:1px dashed #EBEEF5"
+                   :class="{ 'no-border': i === scope.row.items.length - 1 }">
+                <div style="display:flex;align-items:center;flex-wrap:wrap;gap:6px">
+                  <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>
+                  <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>
+                </div>
+                <div style="color:#606266;font-size:12px;line-height:1.6;margin-top:2px">
+                  <span>瀹為檯锛歿{ item.actualValue }}</span>
+                  <span style="margin:0 8px">鍩哄噯锛歿{ item.thresholdValue }}</span>
+                  <span>宸紓锛歿{ item.deviation }}</span>
+                </div>
+              </div>
             </template>
           </el-table-column>
-          <el-table-column label="鎿嶄綔" width="160">
+          <el-table-column label="浼佷笟瑙i噴" width="340">
             <template slot-scope="scope">
-              <el-button v-if="scope.row.status === 'PENDING'" size="mini" type="success" @click="review(scope.row, 'CONFIRMED')">纭</el-button>
-              <el-button v-if="scope.row.status === 'PENDING'" size="mini" type="info" @click="review(scope.row, 'IGNORED')">蹇界暐</el-button>
+              <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">浼佷笟瑙i噴</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="濉啓/淇敼璇ヤ紒涓氱殑瑙i噴璇存槑鈥�"></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])">淇濆瓨瑙i噴</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>
+              <el-button v-if="hasPending(scope.row)" size="mini" type="info" plain @click="reviewAll(scope.row, 'IGNORED')">鍏ㄩ儴蹇界暐</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -72,11 +229,15 @@
 </template>
 <script>
 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']
 export default {
   name: 'AuditResult',
   data() {
     return {
-      period: '', loading: false, results: [], enterpriseFilter: '', ruleFilter: ''
+      period: '', reportType: 'H2032', loading: false, marking: false, results: [], ruleStats: [], statsCollapsed: false,
+      enterpriseFilter: '', ruleFilter: '', viewMode: 'merged', batchSelection: [], focusMode: false,
+      explainDrafts: {}, savingExplainId: null, expandedExplain: null
     }
   },
   computed: {
@@ -84,6 +245,25 @@
       const names = []
       this.results.forEach(r => { if (r.ruleName && names.indexOf(r.ruleName) < 0) names.push(r.ruleName) })
       return names
+    },
+    severityStats() {
+      const s = { red: 0, yellow: 0, pending: 0, done: 0 }
+      this.results.forEach(r => {
+        if (r.alertLevel === 'RED') s.red++
+        else if (r.alertLevel === 'YELLOW') s.yellow++
+        if (r.status === 'PENDING') s.pending++
+        else s.done++
+      })
+      return s
+    },
+    batchPending() { return this.batchSelection.filter(x => x.status === 'PENDING') },
+    allSelected() {
+      const list = this.filteredResults
+      return list.length > 0 && list.every(r => r._selected)
+    },
+    someSelected() {
+      const list = this.filteredResults
+      return list.some(r => r._selected) && !this.allSelected
     },
     filteredResults() {
       const kw = (this.enterpriseFilter || '').trim()
@@ -94,49 +274,310 @@
         if (rule && r.ruleName !== rule) return false
         return true
       })
+    },
+    sortedRuleStats() {
+      const arr = (this.ruleStats || []).slice()
+      arr.sort((a, b) => {
+        const ah = a.hit > 0 ? 1 : 0
+        const bh = b.hit > 0 ? 1 : 0
+        if (ah !== bh) return bh - ah
+        return (a.ruleId || 0) - (b.ruleId || 0)
+      })
+      return arr
+    },
+    mergedResults() {
+      const map = new Map()
+      this.filteredResults.forEach(r => {
+        const key = r.enterpriseName || r.actualValue || '鏈煡浼佷笟'
+        if (!map.has(key)) map.set(key, [])
+        map.get(key).push(r)
+      })
+      return Array.from(map.entries())
+        .map(([name, items]) => ({ enterpriseName: name, items }))
+        .sort((a, b) => b.items.length - a.items.length)
     }
   },
   methods: {
+    dataType(t) { return DATA_TYPE_MAP[t] || '' },
+    explainType(t) { return EXPLAIN_TYPES.indexOf(t) >= 0 },
+    goData(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 || '' } })
+    },
+    goExplain(row) {
+      this.$router.push({ path: '/explain', query: { reportType: this.reportType, keyword: row.enterpriseName || row.city || '', period: row.reportPeriod } })
+    },
+    onTypeChange() {
+      this.ruleFilter = ''; this.batchSelection = []; this.results = []; this.ruleStats = []
+      this.period = ''
+      this.loadLatestPeriod(true)
+    },
+    onPeriodChange() {
+      if (this.period) this.queryResults(); else { this.results = []; this.ruleStats = [] }
+    },
+    loadLatestPeriod(autoQuery) {
+      api.get('/audit/latestPeriod', { params: { reportType: this.reportType } })
+        .then(res => {
+          const p = res.data
+          if (p && !this.period) {
+            this.period = p
+            if (autoQuery) this.queryResults()
+          } else if (autoQuery && !p) {
+            this.results = []; this.ruleStats = []
+          }
+        })
+        .catch(() => {})
+    },
     executeAudit() {
       if (!this.period) { this.$message.warning('璇烽�夋嫨鎶ヨ〃鏈�'); return }
       this.loading = true
       const fd = new FormData()
       fd.append('period', this.period)
+      fd.append('reportType', this.reportType)
       api.post('/audit/execute', fd)
         .then(res => {
           const count = (res.data || []).length
-          this.$message.success('瀹℃牳瀹屾垚锛屽叡 ' + count + ' 鏉¢棶棰�')
-          return api.get('/audit/results', { params: { period: this.period } })
+          return api.get('/audit/ruleStats', { params: { period: this.period, reportType: this.reportType } })
+            .then(stats => {
+              const list = stats.data || []
+              const checked = list.reduce((s, r) => Math.max(s, Number(r.checked) || 0), 0)
+              if (list.length === 0) {
+                this.$message.warning('璇ユ姤琛ㄧ被鍨嬫病鏈夊惎鐢ㄥ鏍歌鍒欙紝鏈墽琛屽鏍�')
+              } else if (checked === 0) {
+                this.$message.warning('璇ユ姤琛ㄦ湡娌℃湁鏁版嵁锛屾湭鎵ц瀹℃牳锛堣鍏堝鍏ユ暟鎹級')
+              } else {
+                const skipped = list.filter(x => x.skipped)
+                if (skipped.length > 0) {
+                  const names = skipped.map(x => x.ruleCode).join('銆�')
+                  this.$message.warning('瀹℃牳瀹屾垚锛屽叡 ' + count + ' 鏉¢棶棰橈紱' + names + ' 鍥犲熀鍑嗘暟鎹紙杩愭斂/杞ㄨ抗/涓婃湀绛夛級缂哄け宸茶烦杩�')
+                } else {
+                  this.$message.success('瀹℃牳瀹屾垚锛屽叡 ' + count + ' 鏉¢棶棰�')
+                }
+              }
+            })
         })
-        .then(res => { this.results = res.data || [] })
+        .then(() => api.get('/audit/results', { params: { period: this.period, reportType: this.reportType } }))
+        .then(res => { this.applyResults(res.data) })
+        .then(() => this.loadRuleStats())
         .catch(() => { this.$message.error('瀹℃牳鎵ц澶辫触') })
         .finally(() => { this.loading = false })
+    },
+    markAudited() {
+      if (!this.period) { this.$message.warning('璇烽�夋嫨鎶ヨ〃鏈�'); return }
+      this.marking = true
+      const fd = new FormData()
+      fd.append('period', this.period)
+      fd.append('reportType', this.reportType)
+      api.post('/audit/markAudited', fd)
+        .then(res => {
+          if (res.data) {
+            this.$message.success('宸叉爣璁拌鏈熴��' + this.reportType + '銆嶄负宸插鏍革紝鎶ヨ〃瀵煎嚭涓嶅啀鎻愮ず鏈鏍�')
+          } else {
+            this.$message.warning('璇ョ被鍨嬫病鏈夊惎鐢ㄨ鍒欐垨璇ユ湡娌℃湁鏁版嵁锛屾湭鍐欏叆鏍囪')
+          }
+          return api.get('/audit/results', { params: { period: this.period, reportType: this.reportType } })
+        })
+        .then(res => { this.applyResults(res.data) })
+        .catch(() => { this.$message.error('鏍囪澶辫触') })
+        .finally(() => { this.marking = false })
     },
     queryResults() {
       if (!this.period) { this.$message.warning('璇烽�夋嫨鎶ヨ〃鏈�'); return }
       this.loading = true
-      api.get('/audit/results', { params: { period: this.period } })
-        .then(res => { this.results = res.data || [] })
+      api.get('/audit/results', { params: { period: this.period, reportType: this.reportType } })
+        .then(res => { this.applyResults(res.data) })
+        .then(() => this.loadRuleStats())
         .catch(() => { this.$message.error('鏌ヨ澶辫触') })
         .finally(() => { this.loading = false })
     },
+    toggleExplain(reportId) {
+      this.expandedExplain = this.expandedExplain === reportId ? null : reportId
+    },
+    applyResults(list) {
+      this.results = (list || []).map(r => Object.assign({}, r, { _selected: false }))
+      this.results.forEach(r => {
+        if (r.reportId && this.explainDrafts[r.reportId] === undefined) {
+          this.$set(this.explainDrafts, r.reportId, r.verifyExplanation || '')
+        }
+      })
+    },
+    saveExplain(row) {
+      const reportId = row.reportId
+      if (!reportId) { this.$message.warning('鏃犳硶瀹氫綅璇ヤ紒涓氱殑鏁版嵁琛�'); return }
+      const explanation = (this.explainDrafts[reportId] || '').trim()
+      this.savingExplainId = reportId
+      const fd = new FormData()
+      fd.append('reportType', this.reportType)
+      fd.append('reportId', reportId)
+      fd.append('explanation', explanation)
+      api.post('/explain/save', fd)
+        .then(() => {
+          this.$message.success('浼佷笟瑙i噴宸蹭繚瀛�')
+          this.$set(this.explainDrafts, reportId, explanation)
+          this.results.forEach(r => { if (r.reportId === reportId) r.verifyExplanation = explanation })
+        })
+        .catch(e => { this.$message.error((e.response && e.response.data && e.response.data.message) || '淇濆瓨澶辫触') })
+        .finally(() => { this.savingExplainId = null })
+    },
+    resetDraft(reportId) {
+      const row = this.results.find(r => r.reportId === reportId)
+      if (row) this.$set(this.explainDrafts, reportId, row.verifyExplanation || '')
+    },
+    loadRuleStats() {
+      if (!this.period) return
+      api.get('/audit/ruleStats', { params: { period: this.period, reportType: this.reportType } })
+        .then(res => { this.ruleStats = res.data || [] })
+        .catch(() => { this.ruleStats = [] })
+    },
+    exportExcel() {
+      if (!this.period) { this.$message.warning('璇烽�夋嫨鎶ヨ〃鏈�'); return }
+      window.open('/api/audit/export?period=' + this.period + '&reportType=' + this.reportType)
+    },
     review(row, status) {
+      if (status === 'IGNORED') {
+        this.$prompt('璇疯緭鍏ュ拷鐣ュ師鍥狅紙鏂逛究浜嬪悗杩芥函锛�', '蹇界暐闂', {
+          inputPlaceholder: '鍘熷洜锛堝彲閫夛級', inputType: 'textarea', confirmButtonText: '纭畾蹇界暐', cancelButtonText: '鍙栨秷'
+        }).then(({ value }) => this.doReview(row, status, (value || '').trim()))
+          .catch(() => {})
+      } else {
+        this.doReview(row, status)
+      }
+    },
+    doReview(row, status, comment) {
       const fd = new FormData()
       fd.append('id', row.id)
       fd.append('status', status)
+      if (comment) fd.append('comment', comment)
       api.post('/audit/review', fd)
-        .then(() => { row.status = status; this.$message.success('宸插鐞�') })
+        .then(() => { row.status = status; this.$message.success(status === 'CONFIRMED' ? '宸茬‘璁�' : '宸插拷鐣�') })
         .catch(() => { this.$message.error('鎿嶄綔澶辫触') })
     },
+    updateBatchSelection() { this.batchSelection = this.filteredResults.filter(r => r._selected) },
+    toggleSelectAll(value) {
+      this.filteredResults.forEach(r => { r._selected = value })
+      this.updateBatchSelection()
+    },
+    reviewBatch(status) {
+      const list = this.batchPending.slice()
+      if (list.length === 0) return
+      if (status === 'IGNORED') {
+        this.$prompt('璇疯緭鍏ュ拷鐣ュ師鍥狅紙搴旂敤浜庨�変腑鐨� ' + list.length + ' 鏉★級', '鎵归噺蹇界暐', {
+          inputPlaceholder: '鍘熷洜锛堝彲閫夛級', inputType: 'textarea', confirmButtonText: '纭畾蹇界暐', cancelButtonText: '鍙栨秷'
+        }).then(({ value }) => this.doReviewBatch(list, status, (value || '').trim()))
+          .catch(() => {})
+      } else {
+        this.doReviewBatch(list, status)
+      }
+    },
+    doReviewBatch(list, status, comment) {
+      let done = 0
+      list.forEach(item => {
+        const fd = new FormData()
+        fd.append('id', item.id)
+        fd.append('status', status)
+        if (comment) fd.append('comment', comment)
+        api.post('/audit/review', fd)
+          .then(() => { item.status = status; done++; if (done === list.length) this.$message.success('宸�' + (status === 'CONFIRMED' ? '纭' : '蹇界暐') + ' ' + done + ' 鏉�') })
+          .catch(() => { this.$message.error('閮ㄥ垎鎿嶄綔澶辫触') })
+      })
+    },
     clearFilters() { this.enterpriseFilter = ''; this.ruleFilter = '' },
+    toggleRuleFilter(rs) {
+      this.ruleFilter = this.ruleFilter === rs.ruleName ? '' : rs.ruleName
+    },
+    hasPending(group) { return group.items.some(x => x.status === 'PENDING') },
+    reviewAll(group, status) {
+      const pending = group.items.filter(x => x.status === 'PENDING')
+      if (pending.length === 0) return
+      if (status === 'IGNORED') {
+        this.$prompt('璇疯緭鍏ュ拷鐣ュ師鍥狅紙搴旂敤浜庤浼佷笟 ' + pending.length + ' 鏉¢棶棰橈級', '蹇界暐', {
+          inputPlaceholder: '鍘熷洜锛堝彲閫夛級', inputType: 'textarea', confirmButtonText: '纭畾蹇界暐', cancelButtonText: '鍙栨秷'
+        }).then(({ value }) => this.doReviewBatch(pending, status, (value || '').trim()))
+          .catch(() => {})
+      } else {
+        this.doReviewBatch(pending, status)
+      }
+    },
     statusType(status) {
       if (status === 'CONFIRMED') return 'success'
       if (status === 'IGNORED') return 'info'
       return 'warning'
+    },
+    statusText(status) {
+      if (status === 'CONFIRMED') return '宸茬‘璁�'
+      if (status === 'IGNORED') return '宸插拷鐣�'
+      return '寰呭鐞�'
+    },
+    statusIcon(status) {
+      if (status === 'CONFIRMED') return 'el-icon-circle-check'
+      if (status === 'IGNORED') return 'el-icon-circle-close'
+      return 'el-icon-time'
     }
+  },
+  mounted() {
+    this.loadLatestPeriod(true)
   }
 }
 </script>
 <style scoped>
 .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; }
-</style>
\ No newline at end of file
+.no-border { border-bottom: none !important; }
+.sev-cards { display: flex; gap: 10px; }
+.sev-card { flex: 1; max-width: 180px; border-radius: 6px; padding: 6px 12px; font-size: 13px; color: #606266; }
+.sev-card b { font-size: 20px; margin-right: 4px; }
+.sev-red { background: #FEF0F0; border: 1px solid #FDE2E2; }
+.sev-red b { color: #F56C6C; }
+.sev-yellow { background: #FDF6EC; border: 1px solid #FAECD8; }
+.sev-yellow b { color: #E6A23C; }
+.sev-pending { background: #F4F4F5; border: 1px solid #E9E9EB; }
+.sev-pending b { color: #606266; }
+.sev-done { background: #F0F9EB; border: 1px solid #E1F3D8; }
+.sev-done b { color: #67C23A; }
+.ent-name { font-weight: 600; color: #303133; }
+.ent-sub { color: #909399; font-size: 12px; margin-top: 2px; }
+.rule-name { font-weight: 500; color: #303133; }
+.rule-code { color: #909399; font-size: 12px; margin-top: 2px; }
+.detail-line { display: flex; align-items: baseline; line-height: 1.7; }
+.dt-label { flex: none; width: 34px; color: #909399; font-size: 12px; }
+.dt-val { flex: 1; color: #606266; word-break: break-all; }
+.dt-warn { color: #E6A23C; }
+.status-tag i { margin-right: 3px; }
+.done-text { color: #C0C4CC; font-size: 12px; }
+.explain-side { background: #FAFAFA; border: 1px solid #EBEEF5; border-radius: 6px; padding: 8px 10px; }
+.explain-cell { min-height: 40px; }
+.explain-compact .explain-preview { color: #909399; font-size: 12px; line-height: 1.5; margin: 4px 0; max-height: 40px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; word-break: break-all; }
+.explain-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
+.explain-title { font-weight: 600; color: #303133; font-size: 13px; }
+.explain-cur { display: block; color: #909399; font-size: 12px; white-space: pre-wrap; word-break: break-all; line-height: 1.5; margin-bottom: 6px; }
+.explain-actions { margin-top: 6px; }
+.main-focus { padding: 8px 12px !important; }
+.detail-cards-wrap { flex: 1; min-height: 0; overflow-y: auto; padding: 2px 4px 16px; }
+.detail-toolbar { display: flex; align-items: center; gap: 10px; padding: 0 2px 8px; }
+.toolbar-tip { color: #909399; font-size: 12px; }
+.detail-card { background: #FFF; border: 1px solid #EBEEF5; border-left: 3px solid #E6A23C; border-radius: 6px; margin-bottom: 10px; box-shadow: 0 1px 2px rgba(0,0,0,.04); }
+.detail-card.card-red { border-left-color: #F56C6C; }
+.detail-card.card-yellow { border-left-color: #E6A23C; }
+.detail-card.card-done { opacity: .72; background: #FAFAFA; }
+.card-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px 4px; }
+.card-ent { flex: 1; min-width: 0; }
+.spacer { flex: 1; }
+.card-body { padding: 2px 12px 10px; }
+.rule-title { display: flex; align-items: baseline; gap: 8px; margin: 2px 0 4px; }
+.rule-title b { font-size: 14px; color: #303133; }
+.card-details { display: flex; flex-wrap: wrap; gap: 4px 24px; background: #F8F9FB; border: 1px solid #F0F2F5; border-radius: 4px; padding: 6px 10px; }
+.card-details .detail-line { display: flex; align-items: baseline; line-height: 1.8; min-width: 200px; }
+.empty-tip { padding: 30px 0; }
+.rule-stats { margin: 0 0 8px; }
+.stats-cards { display: flex; overflow-x: auto; gap: 6px; padding: 4px 0; }
+.stat-card { flex: none; display: inline-flex; align-items: center; gap: 6px; border: 1px solid #EBEEF5; border-radius: 14px; padding: 3px 10px; background: #FAFAFA; font-size: 12px; white-space: nowrap; cursor: pointer; }
+.stat-card:hover { border-color: #C0C4CC; }
+.stat-card.has-hit { background: #FDF6EC; border-color: #E6A23C; }
+.stat-card.is-skipped { background: #F2F6FC; border-color: #DCDFE6; opacity: 0.85; }
+.stat-card.active { border-color: #409EFF; box-shadow: 0 0 0 1px #409EFF; }
+.skip-label { color: #909399; font-size: 12px; }
+.stat-name { color: #606266; max-width: 150px; overflow: hidden; text-overflow: ellipsis; }
+.stat-num { color: #909399; }
+.stat-num b { font-weight: 700; }
+.hit-num { color: #E6A23C; }
+</style>
diff --git a/traffic-audit-web/src/views/Dashboard.vue b/traffic-audit-web/src/views/Dashboard.vue
index 1054c6f..7074186 100644
--- a/traffic-audit-web/src/views/Dashboard.vue
+++ b/traffic-audit-web/src/views/Dashboard.vue
@@ -12,7 +12,8 @@
           <el-menu-item index="/report">鎶ヨ〃鐢熸垚</el-menu-item>
           <el-menu-item index="/rules">瑙勫垯绠$悊</el-menu-item>
           <el-menu-item index="/users">鐢ㄦ埛绠$悊</el-menu-item>
-        </el-menu>
+          <el-menu-item index="/login" @click="logout">閫�鍑虹櫥褰�</el-menu-item>
+</el-menu>
       </el-aside>
       <el-main>
         <div class="page-scroll">
@@ -26,9 +27,9 @@
           <el-card style="margin-top:20px">
             <div slot="header">Demo 鏁版嵁娴�</div>
             <ol>
-              <li>鏁版嵁瀵煎叆锛欻203-2鏈堟姤 / 杩愭斂杞﹁締 / 杞ㄨ抗閲岀▼ / 瑙勪笂瑙勪笅鎷嗗垎锛堟敮鎸侀噸澶嶅鍏ヨ鐩栵級</li>
-              <li>瀹℃牳寮曟搸锛�6鏉¢缃鍒欙紙杞﹁締/鍚ㄤ綅杩愭斂瀵规瘮銆佸懆杞噺杞ㄨ抗瀵规瘮銆佽揣杩愮被鍨嬬幆姣斻�侀泦瑁呯寮傚父銆佹暣杞﹀惃浣嶈寖鍥达級</li>
-              <li>鎶ヨ〃鐢熸垚锛氳揣杩愰噺鍛ㄨ浆閲忋�佸垎甯傚窞鏄庣粏銆佽揣杩愰噺鎺掑悕銆佸懆杞噺鎺掑悕锛堢偣鍑荤敓鎴愬苟涓嬭浇锛�</li>
+              <li>鏁版嵁瀵煎叆锛欻203-2璐ц繍鏈堟姤 / H203-1鏃呭鏈堟姤锛堝惈杩愭斂锛�/ 杩愭斂杞﹁締 / 杞ㄨ抗閲岀▼ / 瑙勪笂瑙勪笅鎷嗗垎锛堟敮鎸侀噸澶嶅鍏ヨ鐩栵級</li>
+              <li>瀹℃牳寮曟搸锛氳揣杩�6鏉� + 鏃呭8鏉¢缃鍒欙紙杩愭斂瀵规瘮銆佺幆姣�=0銆佸钩鍧囪繍璺濈幆姣斻�佹棤杞︽湁閲忕煕鐩剧瓑锛�</li>
+              <li>鎶ヨ〃鐢熸垚锛氳揣杩愶紙鍒嗗競宸炴槑缁�/鎺掑悕锛�+ 鏃呭鍒嗗競宸炴槑缁嗭紙鏈堟姤/骞存姤涓ょ妯″紡锛�</li>
             </ol>
           </el-card>
         </div>
diff --git a/traffic-audit-web/src/views/DataImport.vue b/traffic-audit-web/src/views/DataImport.vue
index 5b40688..f311c07 100644
--- a/traffic-audit-web/src/views/DataImport.vue
+++ b/traffic-audit-web/src/views/DataImport.vue
@@ -12,41 +12,194 @@
           <el-menu-item index="/report">鎶ヨ〃鐢熸垚</el-menu-item>
           <el-menu-item index="/rules">瑙勫垯绠$悊</el-menu-item>
           <el-menu-item index="/users">鐢ㄦ埛绠$悊</el-menu-item>
-        </el-menu>
+          <el-menu-item index="/login" @click="logout">閫�鍑虹櫥褰�</el-menu-item>
+</el-menu>
       </el-aside>
       <el-main>
         <div class="page-scroll">
-        <h3>瀵煎叆涓氬姟鏁版嵁</h3>
-        <el-card style="max-width:640px">
-          <el-form label-width="100px">
-            <el-form-item label="瀵煎叆绫诲瀷">
-              <el-radio-group v-model="importType">
-                <el-radio label="h2032">H203-2鏈堟姤</el-radio>
-                <el-radio label="transportAuth">杩愭斂杞﹁締</el-radio>
-                <el-radio label="trackMileage">杞ㄨ抗閲岀▼</el-radio>
-                <el-radio label="scaleSplit">瑙勪笂瑙勪笅鎷嗗垎</el-radio>
-                <el-radio label="freightTurnover">璐ц繍閲忓懆杞噺</el-radio>
-              </el-radio-group>
-            </el-form-item>
-            <el-form-item label="鎶ヨ〃鏈�">
-              <el-date-picker v-model="period" type="month" placeholder="閫夋嫨鏈堜唤" value-format="yyyy-MM" style="width:100%"></el-date-picker>
-            </el-form-item>
-            <el-form-item label="閫夋嫨鏂囦欢">
-              <el-upload ref="upload" :auto-upload="false" :limit="1" accept=".xlsx,.xls"
-                         :on-change="handleFileChange" :file-list="fileList">
-                <el-button slot="trigger" type="primary">閫夋嫨 Excel 鏂囦欢</el-button>
-              </el-upload>
-              <div class="tip">璇蜂娇鐢ㄥ搴旀ā鏉匡細{{ templateName }}</div>
-            </el-form-item>
-            <el-form-item>
-              <el-button type="success" @click="importData" :loading="loading">寮�濮嬪鍏�</el-button>
-            </el-form-item>
-          </el-form>
-        </el-card>
-        <el-alert v-if="result" type="success" style="margin-top:15px" closable @close="result=''">
-          <div style="white-space: pre-line">{{ result }}</div>
-        </el-alert>
-        <el-alert v-if="error" :title="error" type="error" style="margin-top:15px" closable @close="error=''"></el-alert>
+          <h3>瀵煎叆涓氬姟鏁版嵁</h3>
+          <el-card shadow="hover" class="import-card" style="margin-bottom:15px">
+            <div class="card-head">鎶ヨ〃鏈�</div>
+            <el-date-picker v-model="period" type="month" placeholder="閫夋嫨鏈堜唤" value-format="yyyy-MM"
+                            style="width:240px" @change="onPeriodChange"></el-date-picker>
+            <div class="tip">閫夋嫨鎶ヨ〃鏈熷悗锛屼笅鏂规樉绀鸿鏈堝悇妯″潡鐨勫鍏ヨ繘搴︼紱鍚勬ā鍧椾笅鎷変簰涓嶅奖鍝嶏紝鍙厛閫夊ソ绫诲瀷銆佹寕杞芥枃浠讹紝鍐嶅姞鍏ュ緟瀵煎叆闃熷垪缁熶竴鎵ц銆�</div>
+          </el-card>
+
+          <el-card shadow="hover" class="import-card" style="margin-bottom:15px">
+            <div class="card-head">鏈湀瀵煎叆杩涘害锛坽{ period || '鏈�夋嫨鎶ヨ〃鏈�' }}锛�
+              <el-button type="text" icon="el-icon-refresh" style="float:right" @click="loadOverview">鍒锋柊</el-button>
+            </div>
+            <div v-if="!period" class="tip">璇峰厛鍦ㄤ笂鏂归�夋嫨鎶ヨ〃鏈�</div>
+            <div v-else class="overview-grid">
+              <div class="ov-row" v-for="g in groups" :key="g.key">
+                <div class="ov-group">{{ g.name }}</div>
+                <div class="ov-item" v-for="t in g.types" :key="t.value" :class="overviewState(t.value).cls">
+                  <div class="ov-name">{{ t.label }}</div>
+                  <div class="ov-status">{{ overviewState(t.value).text }}</div>
+                </div>
+              </div>
+            </div>
+          </el-card>
+          <el-card shadow="hover" class="import-card" style="margin-bottom:15px">
+            <div class="card-head">閫夋嫨绫诲瀷骞舵寕杞芥枃浠�
+              <span class="tip" style="float:right;line-height:24px">鍚勬ā鍧椾簰涓嶅奖鍝嶏細鍏堥�夌被鍨嬨�佹寕杞芥枃浠讹紝鍙�岀珛鍗冲鍏ャ�嶆垨銆屽姞鍏ラ槦鍒椼�嶇粺涓�鎵ц锛涗篃鍙湪涓嬫柟绮樿创鐩綍璺緞鐐广�屽鍏ヨ鐩綍銆嶏紝鏂囦欢澶归噷澶氫釜鏈堢殑 Excel 浼氭寜鏂囦欢鍚嶈嚜鍔ㄨ瘑鍒湀浠姐�佸垎鍒鍏ュ搴旀姤琛ㄦ湡</span>
+            </div>
+            <el-tabs v-model="activeModule" class="module-tabs">
+              <el-tab-pane v-for="g in groups" :key="g.key" :label="g.name" :name="g.key">
+                <div class="module-sel">
+                  <el-select v-model="moduleSel[g.key]" placeholder="閫夋嫨瀵煎叆绫诲瀷" style="width:260px"
+                             @change="onModuleChange(g.key)" clearable>
+                    <el-option v-for="t in g.types" :key="t.value" :label="t.label" :value="t.value"></el-option>
+                  </el-select>
+                  <div v-if="isCityDirType(moduleSel[g.key])" class="module-file">
+                    <div class="tip">鏃犻渶閫夋枃浠讹細鑷姩鎵弿 docs/鎶曡祫/杈撳叆/鈥�/{{ period ? Number(period.slice(5)) : 'X' }}鏈� 鐩綍涓嬪叏閮ㄥ競宸炴姤琛紝閫愭枃浠跺閿欒В鏋愬叆搴擄紙椤圭洰涓绘。+鏈堝害锛屽惈鏈湀瀹屾垚/寤鸿闃舵/褰㈣薄杩涘害锛涘崄浜斾簲椤圭洰鑷姩鏍囪锛夈��</div>
+                    <el-button size="mini" type="primary" icon="el-icon-upload2" :loading="cityDirLoading"
+                               @click="importCityDir(moduleSel[g.key])">浠庣洰褰曟壒閲忓鍏�</el-button>
+                  </div>
+                  <div v-else-if="moduleSel[g.key]" class="module-file">
+                    <div class="file-name" :title="fileNames[moduleSel[g.key]]">{{ fileNames[moduleSel[g.key]] || '鏈�夋嫨鏂囦欢' }}</div>
+                    <div class="file-btns">
+                      <el-upload action="#" :auto-upload="false" :show-file-list="false" accept=".xlsx,.xls"
+                                 :ref="'up' + g.key" :on-change="f => onFilePicked(g.key, f)" style="display:inline-block">
+                        <el-button size="mini" type="primary" plain icon="el-icon-folder-opened">閫夋嫨鏂囦欢</el-button>
+                      </el-upload>
+                      <el-button size="mini" type="primary" icon="el-icon-upload2" :disabled="!files[moduleSel[g.key]]"
+                                 @click="importNow(g.key)">绔嬪嵆瀵煎叆</el-button>
+                      <el-button size="mini" type="success" plain icon="el-icon-plus" :disabled="!files[moduleSel[g.key]]"
+                                 @click="addToQueue(g.key)">鍔犲叆闃熷垪</el-button>
+                    </div>
+                    <div class="dir-import-bar">
+                      <el-input v-model="dirPath" size="small" placeholder="绮樿创鐩綍璺緞锛堝 D:\鏁版嵁\2026锛夛紝鐐广�屽鍏ヨ鐩綍銆�" style="width:330px"
+                                @keyup.enter.native="quickDirImport(moduleSel[g.key])" clearable></el-input>
+                      <el-button size="mini" type="warning" icon="el-icon-folder" :loading="dirImporting"
+                                 @click="quickDirImport(moduleSel[g.key])">瀵煎叆璇ョ洰褰�</el-button>
+                      <el-button size="mini" type="text" icon="el-icon-search" @click="openDirBrowser(g.key)">娴忚鈥�</el-button>
+                      <el-checkbox v-model="dirSkipImported" size="mini">璺宠繃宸插鍏ユ枃浠�</el-checkbox>
+                    </div>
+                    <div class="tip">鐩綍瀵煎叆锛氭枃浠跺す閲岀殑 Excel 浼氭寜鏂囦欢鍚嶈嚜鍔ㄨ瘑鍒湀浠斤紙鏀寔 2026骞�7鏈� / 2026.7 / 2026-07 / 7鏈堬級锛屽垎鍒鍏ュ搴旀姤琛ㄦ湡锛涜瘑鍒笉鍒版湀浠界殑鏂囦欢浼氭姤閿欐彁绀恒�備篃鍙偣銆屾祻瑙堚�︺�嶆煡鎵剧洰褰曘��</div>
+                    <div v-if="dirImportFiles.length" class="dir-progress">
+                      <div class="card-head" style="font-size:13px">鐩綍瀵煎叆杩涘害</div>
+                      <div v-for="f in dirImportFiles" :key="f.name" class="dir-progress-row">
+                        <i :class="progIcon(f.status)"></i>
+                        <span class="prog-name" :title="f.name">{{ f.name }}</span>
+                        <el-tag v-if="f.period" size="mini" type="info">{{ f.period }}</el-tag>
+                        <el-tag size="mini" :type="progTag(f.status)">{{ progText(f.status) }}</el-tag>
+                        <span v-if="f.msg" class="queue-msg" :style="f.status === 'fail' ? 'color:#F56C6C' : 'color:#67C23A'">{{ f.msg }}</span>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </el-tab-pane>
+            </el-tabs>
+            <div v-if="importType" class="type-info">
+              <div>妯℃澘锛歿{ templateName }}
+                <el-link v-if="templateUrl" type="primary" icon="el-icon-download" :underline="false"
+                         style="margin-left:8px" @click="downloadTemplate">涓嬭浇妯℃澘</el-link>
+              </div>
+              <div class="tip">{{ typeDesc }}</div>
+              <span v-if="importType === 'cityTaxiAuth'" class="slow-tip">璇ョ被鍨嬬害 4 涓�+ 琛岋紝棰勮鑰楁椂 5 鍒嗛挓宸﹀彸</span>
+            </div>
+          </el-card>
+
+
+          <el-card v-if="queue.length" ref="queueCard" shadow="hover" class="import-card" style="margin-bottom:15px">
+            <div class="card-head">
+              寰呭鍏ラ槦鍒楋紙{{ queue.length }} 椤癸級
+              <el-button type="success" size="small" icon="el-icon-video-play" style="float:right"
+                         :loading="queueLoading" :disabled="!pendingCount" @click="importQueue">鍏ㄩ儴瀵煎叆锛坽{ pendingCount }}锛�</el-button>
+            </div>
+            <el-table :data="queue" border size="small" style="width:100%">
+              <el-table-column label="妯″潡" width="90">
+                <template slot-scope="s">{{ s.row.moduleName }}</template>
+              </el-table-column>
+              <el-table-column label="瀵煎叆绫诲瀷" width="150">
+                <template slot-scope="s">{{ s.row.typeLabel }}</template>
+              </el-table-column>
+              <el-table-column label="鏂囦欢" min-width="200" show-overflow-tooltip>
+                <template slot-scope="s">{{ s.row.fileName }}</template>
+              </el-table-column>
+              <el-table-column label="鐘舵��" width="110" align="center">
+                <template slot-scope="s">
+                  <el-tag v-if="s.row.status === 'pending'" type="info" size="mini">寰呭鍏�</el-tag>
+                  <el-tag v-else-if="s.row.status === 'uploading'" type="warning" size="mini" effect="dark">瀵煎叆涓��</el-tag>
+                  <el-tag v-else-if="s.row.status === 'success'" type="success" size="mini">鎴愬姛</el-tag>
+                  <el-tag v-else type="danger" size="mini">澶辫触</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="缁撴灉" min-width="240">
+                <template slot-scope="s">
+                  <span class="queue-msg">{{ s.row.message }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="鎿嶄綔" width="130" align="center">
+                <template slot-scope="s">
+                  <el-button v-if="s.row.status === 'pending'" size="mini" type="primary" plain @click="importOne(s.row)">瀵煎叆</el-button>
+                  <el-button v-if="s.row.status !== 'uploading'" size="mini" type="danger" plain icon="el-icon-delete"
+                             @click="removeQueueItem(s.row)"></el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+            <div class="tip" style="margin-top:8px">瀵煎叆涓洪�愪釜鎵ц锛涘悓鎶ヨ〃鏈熼噸澶嶅鍏ヤ細鍏堝垹闄ゆ棫鏁版嵁鍐嶅啓鍏ワ紙骞傜瓑锛屼笉浼氶噸澶嶅瓨涓や唤锛夈��</div>
+          </el-card>
+
+          <el-dialog :title="'娴忚鐩綍 鈥� ' + (dirTypeLabel || '')" :visible.sync="dirDialog" width="640px" top="6vh" append-to-body>
+            <div class="dir-toolbar">
+              <el-button size="mini" type="text" icon="el-icon-refresh" :disabled="dirLoading" @click="loadDirs(dirCurrent)">鍒锋柊</el-button>
+              <el-button size="mini" type="text" icon="el-icon-folder-opened" @click="loadDirs('')">鏁版嵁鐩綍 docs</el-button>
+              <el-button size="mini" type="text" icon="el-icon-back" :disabled="!dirParent" @click="goDirParent">涓婄骇鐩綍</el-button>
+            </div>
+            <div class="dir-path" :title="dirCurrent">{{ dirCurrent || '锛堝姞杞戒腑鈥︼級' }}</div>
+            <div class="dir-body" v-loading="dirLoading">
+              <div v-if="!dirDirs.length" class="dir-empty">褰撳墠鐩綍涓嬫病鏈夊瓙鏂囦欢澶癸紝鍙洿鎺ラ�夋嫨褰撳墠鏂囦欢澶瑰鍏�</div>
+              <div v-for="d in dirDirs" :key="d.path" class="dir-row" :title="d.path + '锛堝弻鍑荤珛鍗冲鍏ヨ鏂囦欢澶癸級'" @click="loadDirs(d.path)" @dblclick="dblImportDir(d)">
+                <i class="el-icon-folder"></i><span>{{ d.name }}</span>
+              </div>
+            </div>
+            <div class="dir-files">
+              <div class="tip">鍗曞嚮鏂囦欢澶硅繘鍏ュ瓙鏂囦欢澶癸紱<b>鍙屽嚮鐩爣鏂囦欢澶圭珛鍗冲鍏�</b>鍏朵腑鍏ㄩ儴 Excel锛涗篃鍙繘鍏ュ悗鐐逛笅鏂规寜閽�傚鍏ユ椂浼氭寜鏂囦欢鍚嶈嚜鍔ㄨ瘑鍒湀浠姐��</div>
+              <div v-if="dirExcelCount === 0" class="tip">褰撳墠鏂囦欢澶瑰唴娌℃湁 Excel 鏂囦欢锛�.xlsx/.xls锛�</div>
+              <div v-else class="tip">褰撳墠鏂囦欢澶瑰唴 Excel锛歿{ dirExcelCount }} 涓� 鈥斺�� {{ dirFiles.slice(0, 6).join('銆�') }}{{ dirFiles.length > 6 ? ' 绛�' : '' }}锛堜粎瀵煎叆鐩存帴浣嶄簬璇ユ枃浠跺す鐨勬枃浠讹級</div>
+            </div>
+            <span slot="footer">
+              <el-button size="small" @click="dirDialog = false">鍙栨秷</el-button>
+              <el-button size="small" type="primary" :disabled="dirExcelCount === 0 || dirImporting" :loading="dirImporting" @click="confirmDirImport">瀵煎叆褰撳墠鐩綍</el-button>
+            </span>
+          </el-dialog>
+
+          <el-card shadow="never" style="margin-top:15px" class="batch-card">
+            <div slot="header" class="card-head">
+              瀵煎叆璁板綍锛坽{ period || '鍏ㄩ儴鎶ヨ〃鏈�' }}锛�
+              <el-button type="text" icon="el-icon-refresh" style="float:right" @click="loadBatches">鍒锋柊</el-button>
+            </div>
+            <el-table :data="batches" border size="small" v-loading="batchLoading" style="width:100%">
+              <el-table-column prop="importType" label="瀵煎叆绫诲瀷" width="150">
+                <template slot-scope="s">{{ typeLabel(s.row.importType) }}</template>
+              </el-table-column>
+              <el-table-column prop="reportPeriod" label="鎶ヨ〃鏈�" width="100"></el-table-column>
+              <el-table-column prop="fileName" label="鏂囦欢鍚�" min-width="220" show-overflow-tooltip></el-table-column>
+              <el-table-column prop="totalRows" label="鎬昏鏁�" width="90"></el-table-column>
+              <el-table-column prop="successRows" label="鎴愬姛" width="80">
+                <template slot-scope="s">
+                  <span style="color:#67C23A" v-if="s.row.successRows > 0">{{ s.row.successRows }}</span>
+                  <span v-else>0</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="failRows" label="澶辫触" width="70">
+                <template slot-scope="s">
+                  <el-tag v-if="s.row.failRows > 0" size="mini" type="danger">{{ s.row.failRows }}</el-tag>
+                  <span v-else style="color:#909399">0</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="createdAt" label="瀵煎叆鏃堕棿" width="165"></el-table-column>
+              <el-table-column label="鐘舵��" width="90">
+                <template slot-scope="s">
+                  <el-tag v-if="s.row.failRows > 0" size="mini" type="warning" effect="plain">閮ㄥ垎澶辫触</el-tag>
+                  <el-tag v-else-if="s.row.successRows > 0" size="mini" type="success" effect="plain">鎴愬姛</el-tag>
+                  <el-tag v-else size="mini" type="info" effect="plain">鏃犳暟鎹�</el-tag>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-card>
         </div>
       </el-main>
     </el-container>
@@ -54,63 +207,554 @@
 </template>
 <script>
 import api from '@/api'
+const TYPE_LABELS = {
+  h2032: 'H203-2鏈堟姤', transportAuth: '杩愭斂杞﹁締', trackMileage: '杞ㄨ抗閲岀▼', scaleSplit: '瑙勪笂瑙勪笅鎷嗗垎',
+  freightTurnover: '璐ц繍閲忓懆杞噺', passengerMonthly: 'H203-1鏃呭鏈堟姤', passengerIndividual: '涓綋瀹㈣繍閲�/鍛ㄨ浆閲�',
+  passengerAuth: '鏃呭杩愭斂杞﹁締鏁�', energyMonthly: 'H204鑳借�楀鎶�', energyAuth: '鑳借�楄溅杈嗚繍鏀夸俊鎭�',
+  investment: '鎶曡祫鏈堟姤锛堝杩愮珯鍦猴級', investmentLogistics: '鎶曡祫鏈堟姤锛堢墿娴佸洯鍖猴級', investmentSystem: '鎶曡祫绯荤粺瀵煎嚭',
+  investCityPassenger: '甯傚窞鍗曡〃锛堝杩愮珯鍦猴級', investCityLogistics: '甯傚窞鍗曡〃锛堢墿娴佸洯鍖猴級',
+  'INVEST_瀹㈣繍绔欏満_CITY': '甯傚窞鍗曡〃锛堝杩愮珯鍦猴級', 'INVEST_鐗╂祦鍥尯_CITY': '甯傚窞鍗曡〃锛堢墿娴佸洯鍖猴級',
+  cityBus: '鍩庡競鍏氦鏈堟姤', cityTaxi: '宸℃父鍑虹鏈堟姤', cityTaxiAuth: '鍑虹杞﹁繍鏀胯溅杈�',
+  H2032: 'H203-2鏈堟姤', H2031: 'H203-1鏃呭鏈堟姤', H204: 'H204鑳借�楀鎶�', PASSENGER_AUTH: '鏃呭杩愭斂杞﹁締鏁�',
+  PASSENGER_INDIVIDUAL: '涓綋瀹㈣繍閲�/鍛ㄨ浆閲�', ENERGY_AUTH: '鑳借�楄溅杈嗚繍鏀夸俊鎭�', TRANSPORT_AUTH: '杩愭斂杞﹁締',
+  TRACK_MILEAGE: '杞ㄨ抗閲岀▼', SCALE_SPLIT: '瑙勪笂瑙勪笅鎷嗗垎', FREIGHT_TURNOVER: '璐ц繍閲忓懆杞噺',
+  INVEST_SYSTEM: '鎶曡祫绯荤粺瀵煎嚭', CITY_BUS: '鍩庡競鍏氦鏈堟姤', CITY_TAXI: '宸℃父鍑虹鏈堟姤', CITY_TAXI_AUTH: '鍑虹杞﹁繍鏀胯溅杈�'
+}
+const BATCH_TYPE_MAP = {
+  H2032: 'h2032', TRANSPORT_AUTH: 'transportAuth', TRACK_MILEAGE: 'trackMileage',
+  SCALE_SPLIT: 'scaleSplit', FREIGHT_TURNOVER: 'freightTurnover',
+  H2031: 'passengerMonthly', PASSENGER_AUTH: 'passengerAuth', PASSENGER_INDIVIDUAL: 'passengerIndividual',
+  H204: 'energyMonthly', ENERGY_AUTH: 'energyAuth',
+  CITY_BUS: 'cityBus', CITY_TAXI: 'cityTaxi', CITY_TAXI_AUTH: 'cityTaxiAuth',
+  INVEST_SYSTEM: 'investmentSystem'
+}
 export default {
   name: 'DataImport',
   data() {
     return {
       importType: 'h2032',
-      period: '',
-      file: null,
-      fileList: [],
-      loading: false,
+      period: (() => { const d = new Date(); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') })(),
+      queue: [],
+      queueLoading: false,
+      cityDirLoading: false,
+      dirDialog: false,
+      dirKey: '',
+      dirCurrent: '',
+      dirParent: '',
+      dirDirs: [],
+      dirExcelCount: 0,
+      dirFiles: [],
+      dirLoading: false,
+      dirImporting: false,
+      dirPath: '',
+      dirImportFiles: [],
+      dirSkipImported: true,
       result: '',
-      error: ''
+      error: '',
+      batches: [],
+      batchLoading: false,
+      moduleSel: { freight: 'h2032', passenger: '', city: '', energy: '', invest: '' },
+      activeModule: 'freight',
+      files: {},
+      fileNames: {},
+      overview: {},
+      groups: [
+        {
+          key: 'freight', name: '璐ц繍', types: [
+            { value: 'h2032', label: 'H203-2鏈堟姤' },
+            { value: 'transportAuth', label: '杩愭斂杞﹁締' },
+            { value: 'trackMileage', label: '杞ㄨ抗閲岀▼' },
+            { value: 'scaleSplit', label: '瑙勪笂瑙勪笅鎷嗗垎' },
+            { value: 'freightTurnover', label: '璐ц繍閲忓懆杞噺' }
+          ]
+        },
+        {
+          key: 'passenger', name: '鍏矾鏃呭', types: [
+            { value: 'passengerMonthly', label: 'H203-1鏃呭鏈堟姤' },
+            { value: 'passengerAuth', label: '杩愭斂杞﹁締鏁�' },
+            { value: 'passengerIndividual', label: '涓綋瀹㈣繍閲�/鍛ㄨ浆閲�' }
+          ]
+        },
+        {
+          key: 'city', name: '鍩庡競瀹㈣繍', types: [
+            { value: 'cityBus', label: '鍩庡競鍏氦鏈堟姤' },
+            { value: 'cityTaxi', label: '宸℃父鍑虹鏈堟姤' },
+            { value: 'cityTaxiAuth', label: '鍑虹杞﹁繍鏀胯溅杈�' }
+          ]
+        },
+        {
+          key: 'invest', name: '鎶曡祫', types: [
+            { value: 'investment', label: '鎶曡祫鏈堟姤锛堝杩愮珯鍦猴級' },
+            { value: 'investmentLogistics', label: '鎶曡祫鏈堟姤锛堢墿娴佸洯鍖猴級' },
+            { value: 'investmentSystem', label: '鎶曡祫绯荤粺瀵煎嚭' },
+            { value: 'investCityPassenger', label: '甯傚窞鍗曡〃鎵归噺瀵煎叆锛堝杩愮珯鍦猴級' },
+            { value: 'investCityLogistics', label: '甯傚窞鍗曡〃鎵归噺瀵煎叆锛堢墿娴佸洯鍖猴級' }
+          ]
+        },
+        {
+          key: 'energy', name: '鑳借��', types: [
+            { value: 'energyMonthly', label: 'H204鑳借�楀鎶�' },
+            { value: 'energyAuth', label: '鑳借�楄溅杈嗚繍鏀夸俊鎭�' }
+          ]
+        }
+      ]
     }
   },
   computed: {
+    pendingCount() { return this.queue.filter(x => x.status === 'pending').length },
+    dirTypeLabel() {
+      const t = this.moduleSel[this.dirKey]
+      if (!t) return ''
+      for (const g of this.groups) {
+        const found = g.types.find(x => x.value === t)
+        if (found) return g.name + ' / ' + found.label
+      }
+      return t
+    },
+    typeDesc() {
+      const map = {
+        h2032: '浼佷笟绾ц揣杩愭湀鎶ワ細鐢ㄤ簬瑙勪笂璐ц繍閲忓鏍镐笌鍒嗗競宸炴眹鎬伙紙17 甯傚窞浼佷笟鏄庣粏锛夈��',
+        transportAuth: '杩愭斂杞﹁締妗f锛氱敤浜庤溅杈嗕俊鎭牳瀵癸紙鍚ㄤ綅/杞﹀瀷/鐕冩枡绫诲瀷锛夈��',
+        trackMileage: '杞ㄨ抗閲岀▼鏁版嵁锛氱敤浜庨噷绋嬩笌杩愯窛鏍¢獙銆�',
+        scaleSplit: '瑙勪笂瑙勪笅鎷嗗垎锛氬叏鐪�+17 甯傚窞璐ц繍閲�/鍛ㄨ浆閲忔媶鍒嗙粨鏋溿��',
+        freightTurnover: '璐ц繍閲忓懆杞噺锛氬叏鐪�+17 甯傚窞鏈堝害鍚堣锛堜粖骞� 1-N 鏈� + 鍘诲勾锛夈��',
+        passengerMonthly: '鍏矾鏃呭鏈堟姤锛氫紒涓氱骇鏄庣粏 + 杩愭斂杞﹁締鏁颁袱涓� sheet锛堣繍鏀胯溅杈嗘暟涔熷彲鍗曠嫭瀵煎叆锛夈��',
+        passengerAuth: '鏃呭杩愭斂杞﹁締鏁帮細鐙珛妯℃澘锛堜紒涓氫唬鐮�/浼佷笟鍚嶇О/杞﹁締鏁�/杞藉浣嶆暟锛夛紝鐢ㄤ簬杩愯惀杞﹁締鏍稿锛涗篃鍙殢 H203-1 鏈堟姤绗簩涓� sheet 涓�骞跺鍏ャ��',
+        passengerIndividual: '涓綋瀹㈣繍閲�/鍛ㄨ浆閲忥細浼佷笟绾夸笅姹囨姤鏁版嵁锛堟墍灞炲湴鍖�/浼佷笟浠g爜/浼佷笟鍚嶇О/涓綋瀹㈣繍閲�/涓綋鏃呭鍛ㄨ浆閲忥級锛屽鍏ュ悗鑷姩濉叆鍒嗗競宸炶〃鈥滃叾涓釜浣撯�濊銆�',
+        energyMonthly: '鑳借�楀鎶ワ細鑳借�楁槑缁� + 杩愭斂杞﹁締淇℃伅涓や釜 sheet锛堣繍鏀夸俊鎭篃鍙崟鐙鍏ワ紝瀛e害鏈湀鐢熸垚姹囨�昏〃锛夈��',
+        energyAuth: '鑳借�楄溅杈嗚繍鏀夸俊鎭細鐙珛妯℃澘锛堣溅鐗屽彿/杞﹁締绫诲瀷/鐕冩枡绫诲瀷/鏍囪鍚ㄤ綅/鍑嗙壍寮曡川閲忥級锛岀敤浜庤兘鑰楀鏍稿姣旓紱涔熷彲闅� H204 瀛f姤绗簩涓� sheet 涓�骞跺鍏ャ��',
+        investment: '鎶曡祫鏈堟姤锛堝杩愮珯鍦猴級锛氬競宸炴眹鎬诲ぇ琛紝瀵煎叆鍚庣敓鎴� 4 寮犻瀵兼姤琛ㄣ��',
+        investmentLogistics: '鎶曡祫鏈堟姤锛堢墿娴佸洯鍖猴級锛氬垎椤圭洰鎶曡祫瀹屾垚鎯呭喌 sheet銆�',
+        investmentSystem: '鎶曡祫绯荤粺瀵煎嚭锛氱敤浜庝笌鏈堟姤鏁版嵁璺ㄨ〃鏍¢獙銆�',
+        investCityPassenger: '鏃犻渶閫夋枃浠讹細鑷姩鎵弿 docs/鎶曡祫/杈撳叆/瀹㈣繍锛堝杩愮珯锛�/X鏈� 鐩綍鍏ㄩ儴甯傚窞鎶ヨ〃锛屽閿欒В鏋愬叆搴擄紙鍚湰鏈堝畬鎴�/寤鸿闃舵/褰㈣薄杩涘害锛屽崄浜斾簲椤圭洰鑷姩鏍囪锛夈��',
+        investCityLogistics: '鏃犻渶閫夋枃浠讹細鑷姩鎵弿 docs/鎶曡祫/杈撳叆/鐗╂祦锛堢墿娴佸洯鍖猴級/X鏈� 鐩綍鍏ㄩ儴甯傚窞鎶ヨ〃锛屽閿欒В鏋愬叆搴擄紙鍚湰鏈堝畬鎴�/寤鸿闃舵/褰㈣薄杩涘害锛屽崄浜斾簲椤圭洰鑷姩鏍囪锛夈��',
+        cityBus: '鍩庡競鍏氦鏈堟姤锛氫紒涓氱骇杩愯惀鏁版嵁锛屽惈杞ㄩ亾/杞浮瀛楁銆�',
+        cityTaxi: '宸℃父鍑虹鏈堟姤锛氬競宸炵骇 17 琛岃繍钀ユ暟鎹��',
+        cityTaxiAuth: '鍑虹杞﹁繍鏀胯溅杈嗭細杞﹁締绾ф槑缁嗭紙绾� 4 涓�+ 琛岋級锛岀敤浜庤繍钀ヨ溅鏁板姣斻��'
+      }
+      return map[this.importType] || ''
+    },
     templateName() {
       const map = {
         h2032: '妯℃澘_浜や紒缁烪203-2琛╛閬撹矾璐х墿杩愯緭鏈堝害鐢熶骇鎯呭喌.xlsx',
         transportAuth: '妯℃澘_杩愭斂杞﹁締.xlsx',
         trackMileage: '妯℃澘_杞ㄨ抗閲岀▼.xlsx',
         scaleSplit: '妯℃澘_瑙勪笂瑙勪笅鎷嗗垎杩愯緭閲�.xlsx',
-        freightTurnover: '妯℃澘_璐ц繍閲忓懆杞噺.xlsx'
+        freightTurnover: '妯℃澘_璐ц繍閲忓懆杞噺.xlsx',
+        passengerMonthly: '鍏矾鏃呭杩愯緭鏈堝害鐢熶骇鎯呭喌.xlsx锛堝惈杩愭斂杞﹁締鏁皊heet锛�',
+        passengerAuth: '妯℃澘_鍏矾鏃呭杩愭斂杞﹁締鏁�.xlsx锛堝垪锛氫紒涓氫唬鐮�/浼佷笟鍚嶇О/杞﹁締鏁�/杞藉浣嶆暟锛�',
+        passengerIndividual: '涓綋瀹㈣繍閲忔眹鎬�.xlsx锛堝垪锛氭墍灞炲湴鍖�/浼佷笟浠g爜/浼佷笟鍚嶇О/涓綋瀹㈣繍閲�/涓綋鏃呭鍛ㄨ浆閲忥級',
+        energyMonthly: '閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙2026骞达級.xlsx锛堣兘鑰楁槑缁�+杩愭斂杞﹁締淇℃伅涓heet锛�',
+        energyAuth: '妯℃澘_鑳借�楄溅杈嗚繍鏀夸俊鎭�.xlsx锛堝垪锛氳溅鐗屽彿/杞﹁締绫诲瀷/鐕冩枡绫诲瀷/鏍囪鍚ㄤ綅/鍑嗙壍寮曡川閲忥級',
+        investment: '7鏈堝杩愮珯鎶曡祫鏄庣粏琛紙甯傚窞姹囨�诲ぇ琛紝缁欓瀵硷級.xlsx',
+        investmentLogistics: '7鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls锛堝垎椤圭洰鎶曡祫瀹屾垚鎯呭喌sheet锛�',
+        investmentSystem: '鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7).xls',
+        investCityPassenger: '鈥旓紙鎵归噺鎵弿鐩綍锛屾棤闇�妯℃澘锛�',
+        investCityLogistics: '鈥旓紙鎵归噺鎵弿鐩綍锛屾棤闇�妯℃澘锛�',
+        cityBus: '瀵煎叆_鍩庡競鍏叡浜ら�氭湀搴﹁繍钀ユ儏鍐碉紙YYYY骞碝鏈堬級.xls/.xlsx锛堜紒涓氱骇鏄庣粏锛屽彇鍏叡姹界數杞﹀瓧娈碉級',
+        cityTaxi: '瀵煎叆_宸℃父鍑虹姹借溅杩愯惀鏈嶅姟鎯呭喌鏈堟姤锛圷YYY骞碝鏈堬級.xls锛堝競宸炵骇17琛岋級',
+        cityTaxiAuth: '瀵煎叆_鍑虹杞︿俊鎭�(杩愭斂杞﹁締淇℃伅).xls锛堣溅杈嗙骇鏄庣粏锛岀敤浜庤繍钀ヨ溅鏁板姣旓級'
       }
       return map[this.importType] || ''
     },
-    endpoint() {
+    templateUrl() {
+      const map = {
+        h2032: '璐ц繍/妯℃澘_浜や紒缁烪203-2琛╛閬撹矾璐х墿杩愯緭鏈堝害鐢熶骇鎯呭喌.xlsx',
+        transportAuth: '璐ц繍/妯℃澘_杩愭斂杞﹁締.xlsx',
+        trackMileage: '璐ц繍/妯℃澘_杞ㄨ抗閲岀▼.xlsx',
+        scaleSplit: '璐ц繍/妯℃澘_瑙勪笂瑙勪笅鎷嗗垎杩愯緭閲�.xlsx',
+        freightTurnover: '璐ц繍/妯℃澘_璐ц繍閲忓懆杞噺.xlsx',
+        passengerMonthly: '鍏矾鏃呭/妯℃澘_浜や紒缁烪203-1琛╛鍏矾鏃呭杩愯緭鏈堝害鐢熶骇鎯呭喌.xlsx',
+        passengerAuth: '鍏矾鏃呭/妯℃澘_鍏矾鏃呭杩愭斂杞﹁締鏁�.xlsx',
+        passengerIndividual: '',
+        energyMonthly: '鑳借��/妯℃澘_閬撹矾璐ц繍杞﹁締鑳芥簮娑堣�楁儏鍐碉紙2026锛�.xlsx',
+        energyAuth: '鑳借��/妯℃澘_鑳借�楄溅杈嗚繍鏀夸俊鎭�.xlsx',
+        cityBus: '鍩庡競瀹㈣繍/瀵煎叆妯℃澘_鍩庡競鍏氦.xlsx',
+        cityTaxi: '鍩庡競瀹㈣繍/瀵煎叆妯℃澘_宸℃父鍑虹.xlsx',
+        cityTaxiAuth: '鍩庡競瀹㈣繍/瀵煎叆妯℃澘_鍑虹杞︿俊鎭�(杩愭斂杞﹁締淇℃伅).xls',
+        investment: '鎶曡祫/杈撳叆/瀹㈣繍锛堝杩愮珯锛�/鏇存柊_7鏈堝杩愮珯鎶曡祫鏄庣粏琛紙甯傚窞姹囨�诲ぇ琛紝缁欓瀵硷級.xlsx',
+        investmentLogistics: '鎶曡祫/杈撳叆/鐗╂祦锛堢墿娴佸洯鍖猴級/鏇存柊_7鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls',
+        investmentSystem: '鎶曡祫/妯℃澘_鏌ヨ缁撴灉(鎶曡祫绯荤粺-瀹㈣繍+鐗╂祦 2026.7).xls'
+      }
+      return map[this.importType] || ''
+    }
+  },
+  methods: {
+    typeLabel(t) { return TYPE_LABELS[t] || t },
+    downloadTemplate() {
+      if (!this.templateUrl) return
+      window.open('/api/templates/' + this.templateUrl)
+    },
+    onModuleChange(key) {
+      this.importType = this.moduleSel[key] || ''
+      this.dirPath = (this.importType && localStorage.getItem('dirLast_' + this.importType)) || ''
+    },
+    onPeriodChange() { this.loadOverview(); this.loadBatches() },
+    endpoint(t) {
       const map = {
         h2032: '/import/h2032',
         transportAuth: '/import/transportAuth',
         trackMileage: '/import/trackMileage',
         scaleSplit: '/import/scaleSplit',
-        freightTurnover: '/import/freightTurnover'
+        freightTurnover: '/import/freightTurnover',
+        passengerMonthly: '/import/passengerMonthly',
+        passengerAuth: '/import/passengerAuth',
+        passengerIndividual: '/import/passengerIndividual',
+        energyMonthly: '/import/energyMonthly',
+        energyAuth: '/import/energyAuth',
+        investment: '/import/investment',
+        investmentLogistics: '/import/investment',
+        investmentSystem: '/import/investmentSystem',
+        cityBus: '/import/cityBus',
+        cityTaxi: '/import/cityTaxi',
+        cityTaxiAuth: '/import/cityTaxiAuth'
       }
-      return map[this.importType]
+      return map[t] || ''
+    },
+    openDirBrowser(key) {
+      const type = this.moduleSel[key]
+      if (!type) { this.$message.warning('璇峰厛閫夋嫨瀵煎叆绫诲瀷'); return }
+      this.dirKey = key
+      this.dirDialog = true
+      const last = localStorage.getItem('dirLast_' + type)
+      this.loadDirs(last || '')
+    },
+    loadDirs(path) {
+      this.dirLoading = true
+      api.get('/import/dirs', { params: { path: path || '' } })
+        .then(res => {
+          const d = (res && res.data) || {}
+          this.dirCurrent = d.current || ''
+          this.dirParent = d.parent || ''
+          this.dirDirs = d.dirs || []
+          this.dirExcelCount = d.excelCount || 0
+          this.dirFiles = d.files || []
+        })
+        .catch(e => { this.$message.error((e.response && e.response.data && e.response.data.message) || '鐩綍鍔犺浇澶辫触') })
+        .finally(() => { this.dirLoading = false })
+    },
+    goDirParent() { if (this.dirParent) this.loadDirs(this.dirParent) },
+    dblImportDir(d) {
+      this.dirDialog = false
+      this.runDirImport(this.moduleSel[this.dirKey], d.path)
+    },
+    confirmDirImport() {
+      if (!this.dirCurrent) return
+      this.runDirImport(this.moduleSel[this.dirKey], this.dirCurrent)
+    },
+    quickDirImport(type) {
+      if (!type) { this.$message.warning('璇峰厛閫夋嫨瀵煎叆绫诲瀷'); return }
+      this.runDirImport(type, this.dirPath)
+    },
+    runDirImport(type, dir) {
+      if (!type) { this.$message.warning('璇峰厛閫夋嫨瀵煎叆绫诲瀷'); return }
+      if (!dir || !dir.trim()) { this.$message.warning('璇峰厛濉啓鐩綍璺緞锛堟垨鐐广�屾祻瑙堚�︺�嶉�夋嫨锛�'); return }
+      dir = dir.trim()
+      this.dirImporting = true
+      this.dirImportFiles = []
+      api.get('/import/dirs', { params: { path: dir } })
+        .then(res => {
+          const d = (res && res.data) || {}
+          const files = (d.files || []).filter(n => /\.(xlsx|xls)$/i.test(n))
+          if (!files.length) { this.$message.warning('璇ョ洰褰曟病鏈� Excel 鏂囦欢锛�.xlsx/.xls锛夛細' + dir); this.dirImporting = false; return }
+          localStorage.setItem('dirLast_' + type, dir)
+          this.dirPath = dir
+          this.dirImportFiles = files.map(n => ({ name: n, status: 'pending', period: '', msg: '' }))
+          const run = (i) => {
+            if (i >= files.length) {
+              this.dirImporting = false
+              const ok = this.dirImportFiles.filter(x => x.status === 'success').length
+              const fail = this.dirImportFiles.filter(x => x.status === 'fail').length
+              const skip = this.dirImportFiles.filter(x => x.status === 'skipped').length
+              if (fail > 0) this.$message.warning('鐩綍瀵煎叆瀹屾垚锛氭垚鍔� ' + ok + '锛屽け璐� ' + fail + '锛岃烦杩� ' + skip + '锛堝け璐ュ師鍥犺鍒楄〃锛�')
+              else this.$message.success('鐩綍瀵煎叆瀹屾垚锛氭垚鍔� ' + ok + '锛岃烦杩� ' + skip)
+              this.afterImport()
+              return
+            }
+            const row = this.dirImportFiles[i]
+            row.status = 'importing'
+            const fd = new FormData()
+            fd.append('type', type)
+            fd.append('period', this.period || '')
+            fd.append('skipImported', this.dirSkipImported ? 'true' : 'false')
+            fd.append('path', dir.replace(/[\\/]+$/, '') + '\\' + files[i])
+            api.post('/import/fromDirFile', fd, { timeout: 600000 })
+              .then(res => {
+                const r = (res && res.data) || {}
+                row.period = r.period || ''
+                if (r.skipped) { row.status = 'skipped'; row.msg = r.error || '宸茶烦杩�' }
+                else if (r.fail > 0 || r.error) { row.status = 'fail'; row.msg = r.error || ('澶辫触 ' + r.fail + ' 鏉�') }
+                else { row.status = 'success'; row.msg = '瑙f瀽 ' + r.success + ' 鏉�' }
+              })
+              .catch(e => { row.status = 'fail'; row.msg = (e.response && e.response.data && e.response.data.message) || '瀵煎叆澶辫触' })
+              .finally(() => run(i + 1))
+          }
+          run(0)
+        })
+        .catch(e => {
+          this.dirImporting = false
+          this.$message.error((e.response && e.response.data && e.response.data.message) || '鐩綍涓嶅瓨鍦ㄦ垨鏃犳硶璁块棶锛�' + dir)
+        })
+    },
+    progText(s) { return s === 'pending' ? '寰呭鍏�' : s === 'importing' ? '瀵煎叆涓��' : s === 'success' ? '鎴愬姛' : s === 'skipped' ? '宸茶烦杩�' : '澶辫触' },
+    progTag(s) { return s === 'pending' ? 'info' : s === 'importing' ? 'warning' : s === 'success' ? 'success' : s === 'skipped' ? 'info' : 'danger' },
+    progIcon(s) { return s === 'pending' ? 'el-icon-time' : s === 'importing' ? 'el-icon-loading' : s === 'success' ? 'el-icon-success' : s === 'skipped' ? 'el-icon-minus' : 'el-icon-error' },
+    onFilePicked(key, file) {
+      this.$set(this.files, this.moduleSel[key], file.raw)
+      this.$set(this.fileNames, this.moduleSel[key], file.name)
+      const up = this.$refs['up' + key]
+      if (up && up.clearFiles) up.clearFiles()
+    },
+    importNow(key) {
+      const type = this.moduleSel[key]
+      if (!type) return
+      if (this.isCityDirType(type)) {
+        this.importCityDir(type)
+        this.moduleSel[key] = ''
+        this.importType = ''
+        return
+      }
+      const file = this.files[type]
+      if (!file) { this.$message.warning('璇峰厛涓鸿绫诲瀷閫夋嫨鏂囦欢'); return }
+      if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鎶ヨ〃鏈�'); return }
+      const g = this.groups.find(x => x.key === key)
+      const t = g.types.find(x => x.value === type)
+      const row = {
+        id: Date.now() + Math.random(),
+        moduleKey: key,
+        moduleName: g.name,
+        type,
+        typeLabel: t.label,
+        file,
+        fileName: this.fileNames[type] || file.name,
+        status: 'pending',
+        message: ''
+      }
+      this.importOne(row).then(() => {
+        if ((row.type === 'investment' || row.type === 'investmentLogistics') && row.status === 'success') {
+          this.checkInvest(this.period)
+        }
+        this.afterImport()
+        this.moduleSel[key] = ''
+        this.importType = ''
+        this.files[type] = null
+        this.$delete(this.fileNames, type)
+      })
+    },
+    isCityDirType(t) { return t === 'investCityPassenger' || t === 'investCityLogistics' },
+    importCityDir(type) {
+      if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鎶ヨ〃鏈�'); return }
+      const category = type === 'investCityPassenger' ? '瀹㈣繍绔欏満' : '鐗╂祦鍥尯'
+      const fd = new FormData()
+      fd.append('period', this.period)
+      fd.append('category', category)
+      this.cityDirLoading = true
+      api.post('/import/investCityBatch', fd, { timeout: 300000 })
+        .then(res => {
+          const d = (res && res.data) || {}
+          if (!d.files) {
+            this.$message.error((res && res.message) || '鎵归噺瀵煎叆澶辫触锛堣纭鎶ヨ〃鏈熺洰褰曞瓨鍦級')
+            return
+          }
+          const files = d.files || []
+          const errCount = files.filter(f => f.error || f.success === 0).length
+          const html = files.map(f =>
+            '<div style="margin:6px 0;line-height:1.6"><b>' + (f.fileName || '') + '</b>' +
+            (f.city ? '锛�' + f.city + '锛�' : '') + '锛氳В鏋� ' + (f.success || 0) + ' 涓」鐩�' +
+            (f.error ? ' <span style="color:#F56C6C">' + f.error + '</span>' : '') + '</div>'
+          ).join('')
+          this.$alert(html, '甯傚窞鍗曡〃鎵归噺瀵煎叆瀹屾垚锛氬叡 ' + d.total + ' 鏉★紝鎴愬姛 ' + d.success + ' 鏉�' +
+            (d.fail > 0 ? '锛屽け璐� ' + d.fail + ' 鏉�' : '') +
+            '锛堝崄浜斾簲椤圭洰鍖归厤 ' + d.fiveYearMatched + '/' + d.fiveYearTemplateProjects + '锛�', {
+            dangerouslyUseHTMLString: true,
+            type: errCount > 0 ? 'warning' : 'success',
+            confirmButtonText: '鐭ラ亾浜�'
+          })
+          this.afterImport()
+        })
+        .catch(e => {
+          this.$message.error((e.response && e.response.data && e.response.data.message) || '鎵归噺瀵煎叆澶辫触')
+        })
+        .finally(() => { this.cityDirLoading = false })
+    },
+    addToQueue(key) {
+      const type = this.moduleSel[key]
+      if (!type) return
+      const file = this.files[type]
+      if (!file) { this.$message.warning('璇峰厛涓鸿绫诲瀷閫夋嫨鏂囦欢'); return }
+      const g = this.groups.find(x => x.key === key)
+      const t = g.types.find(x => x.value === type)
+      this.queue.push({
+        id: Date.now() + Math.random(),
+        moduleKey: key,
+        moduleName: g.name,
+        type,
+        typeLabel: t.label,
+        file,
+        fileName: this.fileNames[type] || file.name,
+        status: 'pending',
+        message: ''
+      })
+      this.moduleSel[key] = ''
+      this.importType = ''
+      this.files[type] = null
+      this.$delete(this.fileNames, type)
+    },
+    removeQueueItem(row) {
+      const i = this.queue.indexOf(row)
+      if (i >= 0) this.queue.splice(i, 1)
+    },
+    importOne(row) {
+      if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鎶ヨ〃鏈�'); return Promise.resolve() }
+      row.status = 'uploading'
+      row.message = ''
+      const fd = new FormData()
+      fd.append('file', row.file)
+      fd.append('period', this.period)
+      if (row.type === 'investment' || row.type === 'investmentLogistics') {
+        fd.append('category', row.type === 'investment' ? '瀹㈣繍绔欏満' : '鐗╂祦鍥尯')
+      }
+      return api.post(this.endpoint(row.type), fd, { headers: { 'Content-Type': 'multipart/form-data' }, timeout: 600000 })
+        .then(res => { row.status = 'success'; row.message = res.data || '瀵煎叆鎴愬姛' })
+        .catch(e => { row.status = 'fail'; row.message = (e.response && e.response.data && e.response.data.message) || '瀵煎叆澶辫触' })
+        .finally(() => {})
+    },
+    importQueue() {
+      if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鎶ヨ〃鏈�'); return Promise.resolve(false) }
+      const pending = this.queue.filter(x => x.status === 'pending')
+      if (pending.length === 0) { this.$message.warning('闃熷垪涓病鏈夊緟瀵煎叆椤�'); return Promise.resolve(false) }
+      this.queueLoading = true
+      return new Promise(resolve => {
+        const run = (i) => {
+          if (i >= pending.length) {
+            this.queueLoading = false
+            this.afterImport()
+            resolve(true)
+            return
+          }
+          this.importOne(pending[i]).then(() => {
+            const t = pending[i].type
+            if ((t === 'investment' || t === 'investmentLogistics') && pending[i].status === 'success') {
+              this.checkInvest(this.period)
+            }
+            run(i + 1)
+          })
+        }
+        run(0)
+      })
+    },
+    afterImport() { this.loadOverview(); this.loadBatches() },
+    loadOverview() {
+      if (!this.period) return
+      api.get('/import/batches', { params: { period: this.period, limit: 200 } })
+        .then(res => {
+          const map = {}
+          const records = (res.data && res.data.records) || []
+          records.forEach(r => {
+            const key = this.batchKeyToType(r.importType)
+            if (!key) return
+            const cur = map[key]
+            if (!cur || (cur.latestId || 0) < (r.id || 0)) {
+              map[key] = { totalRows: r.totalRows, successRows: r.successRows, failRows: r.failRows, latestId: r.id }
+            }
+          })
+          this.overview = map
+        })
+        .catch(() => {})
+    },
+    batchKeyToType(k) {
+      if (BATCH_TYPE_MAP[k]) return BATCH_TYPE_MAP[k]
+      if (k && k.indexOf('INVEST_') === 0) {
+        if (k.indexOf('_CITY') >= 0) return k === 'INVEST_鐗╂祦鍥尯_CITY' ? 'investCityLogistics' : 'investCityPassenger'
+        return k === 'INVEST_鐗╂祦鍥尯' ? 'investmentLogistics' : 'investment'
+      }
+      return ''
+    },
+    overviewState(type) {
+      const o = this.overview[type]
+      if (!o) return { cls: 'ov-empty', text: '鏈鍏�' }
+      if (o.failRows > 0) return { cls: 'ov-fail', text: '閮ㄥ垎澶辫触锛堝け璐� ' + o.failRows + ' 琛岋級' }
+      if (o.successRows > 0) return { cls: 'ov-ok', text: '宸插鍏� ' + o.successRows + ' 琛�' }
+      return { cls: 'ov-empty', text: '鏃犳暟鎹�' }
+    },
+    loadBatches() {
+      this.batchLoading = true
+      const params = { limit: 50 }
+      if (this.period) params.period = this.period
+      api.get('/import/batches', { params })
+        .then(res => { this.batches = (res.data && res.data.records) || [] })
+        .catch(() => {})
+        .finally(() => { this.batchLoading = false })
+    },
+    checkInvest(period) {
+      const fd = new FormData()
+      fd.append('period', period)
+      fd.append('reportType', 'INVEST')
+      api.post('/audit/execute', fd, { timeout: 120000 })
+        .then(res => {
+          const list = res.data || []
+          const pending = list.filter(x => x.status === 'PENDING')
+          if (pending.length > 0) {
+            const html = pending.map(x => {
+              const name = x.enterpriseName || x.actualValue || ''
+              return '<div style="margin:8px 0;line-height:1.6"><b>' + (x.ruleName || '') + '</b>锛�' + name +
+                '<br/>瀹為檯锛�' + (x.actualValue || '') + '銆�鍩哄噯锛�' + (x.thresholdValue || '') +
+                '<br/>宸紓锛�' + (x.deviation || '') + '</div>'
+            }).join('')
+            this.$alert(html, '鎶曡祫鏁版嵁涓庢姇璧勭郴缁熶笉涓�鑷达紙' + pending.length + ' 鏉★級', {
+              dangerouslyUseHTMLString: true, type: 'warning', confirmButtonText: '鐭ラ亾浜�'
+            })
+          } else {
+            this.$message.success('宸蹭笌鎶曡祫绯荤粺鏍稿锛屾湰鏈堟暟鎹竴鑷�')
+          }
+        })
+        .catch(() => {})
     }
   },
-  methods: {
-    handleFileChange(file) { this.file = file.raw },
-    importData() {
-      if (!this.file) { this.error = '璇烽�夋嫨鏂囦欢'; return }
-      if (!this.period) { this.error = '璇烽�夋嫨鎶ヨ〃鏈�'; return }
-      this.loading = true; this.error = ''; this.result = ''
-      let fd = new FormData()
-      fd.append('file', this.file)
-      fd.append('period', this.period)
-      api.post(this.endpoint, fd, { headers: { 'Content-Type': 'multipart/form-data' } })
-        .then(res => {
-          this.result = res.data || '瀵煎叆鎴愬姛'
-          this.fileList = []
-          this.file = null
-        })
-        .catch(e => { this.error = (e.response && e.response.data && e.response.data.message) || '瀵煎叆澶辫触' })
-        .finally(() => { this.loading = false })
-    }
+  mounted() {
+    this.loadBatches()
+    this.loadOverview()
   }
 }
 </script>
 <style scoped>
 .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; }
-.tip { font-size: 12px; color: #909399; margin-top: 6px; }
+.import-card { margin-bottom: 0; }
+.card-head { font-weight: 600; margin-bottom: 6px; }
+.module-tabs >>> .el-tabs__header { margin-bottom: 12px; }
+.module-sel { max-width: 480px; }
+.group-label { font-size: 13px; color: #606266; font-weight: 600; margin-bottom: 6px; padding-left: 6px; border-left: 3px solid #409EFF; }
+.module-file { margin-top: 8px; }
+.file-name { font-size: 12px; color: #606266; background: #F5F7FA; border: 1px solid #EBEEF5; border-radius: 4px; padding: 4px 8px; margin-bottom: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.file-btns { display: flex; gap: 6px; }
+.type-info { margin-top: 12px; color: #606266; font-size: 13px; background: #F5F7FA; border-radius: 4px; padding: 8px 10px; }
+.tip { font-size: 12px; color: #909399; margin-top: 6px; line-height: 1.5; }
+.slow-tip { font-size: 12px; color: #E6A23C; margin-left: 8px; }
+.overview-grid { display: flex; flex-direction: column; gap: 8px; }
+.ov-row { display: flex; align-items: stretch; gap: 8px; }
+.ov-group { flex: none; width: 90px; display: flex; align-items: center; font-weight: 600; color: #606266; font-size: 13px; }
+.ov-item { flex: 1; min-width: 0; border: 1px solid #EBEEF5; border-radius: 6px; padding: 6px 8px; background: #FAFAFA; }
+.ov-item.ov-ok { background: #F0F9EB; border-color: #E1F3D8; }
+.ov-item.ov-fail { background: #FDF6EC; border-color: #FAECD8; }
+.ov-name { font-size: 12px; color: #303133; }
+.ov-status { font-size: 12px; margin-top: 2px; }
+.ov-ok .ov-status { color: #67C23A; }
+.ov-fail .ov-status { color: #E6A23C; }
+.ov-empty .ov-status { color: #C0C4CC; }
+.queue-msg { font-size: 12px; color: #606266; word-break: break-all; white-space: pre-line; }
+.dir-import-bar { display: flex; align-items: center; gap: 8px; margin: 6px 0 4px; flex-wrap: wrap; }
+.dir-progress { margin-top: 8px; border: 1px solid #EBEEF5; border-radius: 4px; padding: 8px; background: #FAFAFA; }
+.dir-progress-row { display: flex; align-items: center; gap: 8px; padding: 4px 2px; font-size: 12px; color: #303133; flex-wrap: wrap; }
+.dir-progress-row i { color: #409EFF; }
+.dir-progress-row .prog-name { min-width: 0; max-width: 380px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.dir-progress-row .queue-msg { width: 100%; padding-left: 22px; }
+.dir-toolbar { margin-bottom: 6px; }
+.dir-path { font-size: 12px; color: #606266; background: #F5F7FA; border: 1px solid #EBEEF5; border-radius: 4px; padding: 6px 8px; margin-bottom: 8px; word-break: break-all; }
+.dir-body { max-height: 320px; overflow-y: auto; border: 1px solid #EBEEF5; border-radius: 4px; }
+.dir-row { display: flex; align-items: center; gap: 6px; padding: 7px 10px; cursor: pointer; font-size: 13px; color: #303133; }
+.dir-row:hover { background: #ECF5FF; color: #409EFF; }
+.dir-row i { color: #E6A23C; }
+.dir-empty { padding: 14px; font-size: 12px; color: #909399; text-align: center; }
+.dir-files { margin-top: 8px; }
+
 </style>
diff --git a/traffic-audit-web/src/views/DataView.vue b/traffic-audit-web/src/views/DataView.vue
index 1ece0a6..e4ca041 100644
--- a/traffic-audit-web/src/views/DataView.vue
+++ b/traffic-audit-web/src/views/DataView.vue
@@ -12,7 +12,8 @@
           <el-menu-item index="/report">鎶ヨ〃鐢熸垚</el-menu-item>
           <el-menu-item index="/rules">瑙勫垯绠$悊</el-menu-item>
           <el-menu-item index="/users">鐢ㄦ埛绠$悊</el-menu-item>
-        </el-menu>
+          <el-menu-item index="/login" @click="logout">閫�鍑虹櫥褰�</el-menu-item>
+</el-menu>
       </el-aside>
       <el-main>
         <h3>瀵煎叆鏁版嵁鏌ョ湅</h3>
@@ -24,6 +25,11 @@
               <el-radio-button label="trackMileage">杞ㄨ抗閲岀▼</el-radio-button>
               <el-radio-button label="scaleSplit">瑙勪笂瑙勪笅鎷嗗垎</el-radio-button>
               <el-radio-button label="freightTurnover">璐ц繍閲忓懆杞噺</el-radio-button>
+              <el-radio-button label="h2031">H203-1鏃呭鏈堟姤</el-radio-button>
+              <el-radio-button label="passengerAuth">鏃呭杩愭斂杞﹁締</el-radio-button>
+              <el-radio-button label="cityBus">鍩庡競鍏氦鏈堟姤</el-radio-button>
+              <el-radio-button label="cityTaxi">宸℃父鍑虹鏈堟姤</el-radio-button>
+              <el-radio-button label="cityTaxiAuth">鍑虹杞﹁繍鏀胯溅杈�</el-radio-button>
             </el-radio-group>
           </el-form-item>
           <el-form-item label="鎶ヨ〃鏈�">
@@ -34,12 +40,13 @@
           </el-form-item>
           <el-form-item>
             <el-button type="primary" @click="loadData">鏌ヨ</el-button>
+            <el-button type="success" icon="el-icon-full-screen" @click="toggleFullscreen">鍏ㄥ睆鏌ョ湅</el-button>
           </el-form-item>
         </el-form>
         <div style="color:#909399;font-size:12px;margin-bottom:6px">
           鍏� {{ total }} 鏉¤褰曪紝褰撳墠绗� {{ page }} 椤�
         </div>
-        <div class="table-area">
+        <div class="table-area" ref="tableArea">
         <el-table :data="records" border v-loading="loading" size="small" style="width:100%" height="100%">
           <el-table-column v-for="c in columns" :key="c.prop" :prop="c.prop" :label="c.label" :min-width="c.width" show-overflow-tooltip>
             <template slot-scope="scope">
@@ -100,16 +107,34 @@
         this.loading = false
       })
     },
+    toggleFullscreen() {
+      const el = this.$refs.tableArea
+      if (!document.fullscreenElement) {
+        if (el && el.requestFullscreen) el.requestFullscreen()
+        else this.$message.warning("娴忚鍣ㄤ笉鏀寔鍏ㄥ睆")
+      } else if (document.exitFullscreen) {
+        document.exitFullscreen()
+      }
+    },
     formatCell(v) {
       if (v === null || v === undefined || v === '') return '-'
       return v
     }
   },
   mounted() {
+    const q = this.$route.query
+    if (q.type) this.type = q.type
+    if (q.period) this.period = q.period
+    if (q.keyword) this.keyword = q.keyword
     this.loadData()
   }
 }
 </script>
 <style scoped>
 .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; }
+#app .table-area:fullscreen {
+  padding: 20px;
+  background: #fff;
+  overflow: auto;
+}
 </style>
\ No newline at end of file
diff --git a/traffic-audit-web/src/views/ExplainReview.vue b/traffic-audit-web/src/views/ExplainReview.vue
index 767be34..a052f60 100644
--- a/traffic-audit-web/src/views/ExplainReview.vue
+++ b/traffic-audit-web/src/views/ExplainReview.vue
@@ -12,37 +12,48 @@
           <el-menu-item index="/report">鎶ヨ〃鐢熸垚</el-menu-item>
           <el-menu-item index="/rules">瑙勫垯绠$悊</el-menu-item>
           <el-menu-item index="/users">鐢ㄦ埛绠$悊</el-menu-item>
-        </el-menu>
+          <el-menu-item index="/login" @click="logout">閫�鍑虹櫥褰�</el-menu-item>
+</el-menu>
       </el-aside>
       <el-main>
         <h3>浼佷笟瑙i噴</h3>
-        <el-form :inline="true">
+        <el-form :inline="true" @submit.native.prevent>
+          <el-form-item label="鎶ヨ〃绫诲瀷">
+            <el-select v-model="reportType" style="width:190px" @change="onTypeChange">
+              <el-option label="璐ц繍锛圚203-2锛�" value="H2032"></el-option>
+              <el-option label="鍏矾鏃呭锛圚203-1锛�" value="H2031"></el-option>
+              <el-option label="鍩庡競鍏氦锛圕ITY_BUS锛�" value="CITY_BUS"></el-option>
+              <el-option label="宸℃父鍑虹锛圕ITY_TAXI锛�" value="CITY_TAXI"></el-option>
+            </el-select>
+          </el-form-item>
           <el-form-item label="鎶ヨ〃鏈�">
             <el-date-picker v-model="period" type="month" placeholder="鍏ㄩ儴" value-format="yyyy-MM" clearable style="width:140px" @change="loadData"></el-date-picker>
           </el-form-item>
-          <el-form-item label="浼佷笟鍚嶇О">
-            <el-input v-model="keyword" placeholder="杈撳叆浼佷笟鍚嶇О鍏抽敭瀛�" clearable style="width:220px" @keyup.enter.native="loadData"></el-input>
+          <el-form-item :label="keywordLabel">
+            <el-input v-model="keyword" :placeholder="keywordPlaceholder" clearable style="width:220px" @keyup.enter.native="loadData"></el-input>
           </el-form-item>
           <el-form-item>
             <el-button type="primary" @click="loadData">鏌ヨ</el-button>
           </el-form-item>
+          <el-form-item label="浠呮樉绀哄凡濉啓">
+            <el-switch v-model="hasExplanation" @change="onFilterChange"></el-switch>
+          </el-form-item>
           <el-form-item>
-            <span style="color:#909399;font-size:12px">鍏� {{ total }} 鏉¤В閲�</span>
+            <span style="color:#909399;font-size:12px">鍏� {{ total }} 鏉¤褰�<template v-if="hasExplanation">锛堝凡濉啓瑙i噴锛�</template></span>
           </el-form-item>
         </el-form>
         <div class="table-area">
-        <el-table :data="records" border v-loading="loading" size="small" style="width:100%" height="100%">
-          <el-table-column prop="reportPeriod" label="鎶ヨ〃鏈�" width="100"></el-table-column>
-          <el-table-column prop="regionCode" label="鎵�灞炲湴鍖�" width="100"></el-table-column>
-          <el-table-column prop="enterpriseCode" label="浼佷笟浠g爜" min-width="140" show-overflow-tooltip></el-table-column>
-          <el-table-column prop="enterpriseName" label="浼佷笟鍚嶇О" min-width="200" show-overflow-tooltip></el-table-column>
-          <el-table-column prop="vehicleTotal" label="杞﹁締鏁�" width="90"></el-table-column>
-          <el-table-column prop="tonsTotal" label="鍚ㄤ綅" width="100"></el-table-column>
-          <el-table-column prop="freightTotal" label="璐ц繍閲�" width="110"></el-table-column>
-          <el-table-column prop="turnoverTotal" label="鍛ㄨ浆閲�" width="120"></el-table-column>
-          <el-table-column prop="avgDistance" label="骞冲潎杩愯窛" width="100"></el-table-column>
-          <el-table-column prop="verifyExplanation" label="浼佷笟瑙i噴" min-width="280" show-overflow-tooltip></el-table-column>
-          <el-table-column label="鎿嶄綔" width="100" fixed="right">
+        <el-table :data="records" border v-loading="loading" size="mini" :row-style="{ height: '32px' }" style="width:100%" height="100%">
+          <el-table-column v-for="col in columns" :key="col.prop" v-bind="col">
+            <template slot-scope="scope">
+              <template v-if="col.prop === 'verifyExplanation'">
+                <span v-if="scope.row.verifyExplanation" class="expl-text">{{ scope.row.verifyExplanation }}</span>
+                <el-tag v-else size="mini" type="warning" effect="plain">鏈~鍐�</el-tag>
+              </template>
+              <span v-else>{{ scope.row[col.prop] }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="鎿嶄綔" width="110" fixed="right">
             <template slot-scope="scope">
               <el-button size="mini" type="primary" @click="analyze(scope.row)">AI 鍒嗘瀽</el-button>
             </template>
@@ -63,22 +74,86 @@
 <script>
 import api from '@/api'
 import AiChatDialog from '@/components/AiChatDialog'
+const COLS = {
+  H2032: [
+    { prop: 'reportPeriod', label: '鎶ヨ〃鏈�', width: 100 },
+    { prop: 'regionCode', label: '鎵�灞炲湴鍖�', width: 100 },
+    { prop: 'enterpriseCode', label: '浼佷笟浠g爜', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'enterpriseName', label: '浼佷笟鍚嶇О', minWidth: 200, showOverflowTooltip: true },
+    { prop: 'vehicleTotal', label: '杞﹁締鏁�', width: 90 },
+    { prop: 'tonsTotal', label: '鍚ㄤ綅', width: 100 },
+    { prop: 'freightTotal', label: '璐ц繍閲�(鍚�)', width: 110 },
+    { prop: 'turnoverTotal', label: '鍛ㄨ浆閲�(鍚ㄥ叕閲�)', width: 130 },
+    { prop: 'avgDistance', label: '骞冲潎杩愯窛(鍏噷)', width: 120 },
+    { prop: 'verifyExplanation', label: '浼佷笟瑙i噴', minWidth: 260, showOverflowTooltip: true }
+  ],
+  H2031: [
+    { prop: 'reportPeriod', label: '鎶ヨ〃鏈�', width: 100 },
+    { prop: 'regionCode', label: '鎵�灞炲湴鍖�', width: 100 },
+    { prop: 'enterpriseCode', label: '浼佷笟浠g爜', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'enterpriseName', label: '浼佷笟鍚嶇О', minWidth: 200, showOverflowTooltip: true },
+    { prop: 'vehicleTotal', label: '杞﹁締鏁�', width: 90 },
+    { prop: 'seatTotal', label: '杞藉浣嶆暟', width: 100 },
+    { prop: 'passengerTotal', label: '瀹㈣繍閲�(涓囦汉)', width: 110 },
+    { prop: 'turnoverTotal', label: '鍛ㄨ浆閲�(涓囦汉鍏噷)', width: 130 },
+    { prop: 'avgDistance', label: '骞冲潎杩愯窛(鍏噷)', width: 120 },
+    { prop: 'verifyExplanation', label: '浼佷笟瑙i噴', minWidth: 260, showOverflowTooltip: true }
+  ],
+  CITY_BUS: [
+    { prop: 'reportPeriod', label: '鎶ヨ〃鏈�', width: 100 },
+    { prop: 'regionCode', label: '鎵�灞炲湴鍖�', width: 100 },
+    { prop: 'city', label: '甯傚窞', width: 100 },
+    { prop: 'enterpriseName', label: '浼佷笟鍚嶇О', minWidth: 220, showOverflowTooltip: true },
+    { prop: 'opVehicles', label: '杩愯惀杞︽暟', width: 90 },
+    { prop: 'passengerVolume', label: '瀹㈣繍閲�(涓囦汉娆�)', width: 120 },
+    { prop: 'turnover', label: '鍛ㄨ浆閲�(涓囦汉鍏噷)', width: 130 },
+    { prop: 'avgDistance', label: '骞冲潎杩愯窛(鍏噷)', width: 120 },
+    { prop: 'verifyExplanation', label: '浼佷笟瑙i噴', minWidth: 260, showOverflowTooltip: true }
+  ],
+  CITY_TAXI: [
+    { prop: 'reportPeriod', label: '鎶ヨ〃鏈�', width: 100 },
+    { prop: 'regionCode', label: '鎵�灞炲湴鍖�', width: 100 },
+    { prop: 'city', label: '甯傚窞', width: 110 },
+    { prop: 'tripTotal', label: '杞藉杞︽鎬绘暟', width: 110 },
+    { prop: 'passengerVolume', label: '瀹㈣繍閲�(涓囦汉娆�)', width: 120 },
+    { prop: 'turnover', label: '鍛ㄨ浆閲�(涓囦汉鍏噷)', width: 130 },
+    { prop: 'opVehicles', label: '杩愯惀杞﹁締鏁�', width: 100 },
+    { prop: 'avgDistance', label: '骞冲潎杩愯窛(鍏噷)', width: 120 },
+    { prop: 'verifyExplanation', label: '浼佷笟瑙i噴', minWidth: 260, showOverflowTooltip: true }
+  ]
+}
+const TABLE_NAME = {
+  H2032: 'h2032_enterprise_monthly',
+  H2031: 'h2031_enterprise_monthly',
+  CITY_BUS: 'city_bus_monthly',
+  CITY_TAXI: 'city_taxi_monthly'
+}
 export default {
   name: 'ExplainReview',
   components: { AiChatDialog },
   data() {
     return {
-      period: '', keyword: '', page: 1, size: 20, total: 0, records: [], loading: false,
+      reportType: 'H2032',
+      period: '', keyword: '', hasExplanation: false, page: 1, size: 20, total: 0, records: [], loading: false,
       chatVisible: false, chatContext: {}
     }
   },
+  computed: {
+    columns() { return COLS[this.reportType] || COLS.H2032 },
+    keywordLabel() { return this.reportType === 'CITY_TAXI' ? '甯傚窞鍚嶇О' : '浼佷笟鍚嶇О' },
+    keywordPlaceholder() { return this.reportType === 'CITY_TAXI' ? '杈撳叆甯傚窞鍚嶇О鍏抽敭瀛�' : '杈撳叆浼佷笟鍚嶇О鍏抽敭瀛�' }
+  },
   methods: {
+    onTypeChange() { this.page = 1; this.loadData() },
+    onFilterChange() { this.page = 1; this.loadData() },
     loadData() {
       this.loading = true
       api.get('/explain/list', {
         params: {
+          reportType: this.reportType,
           period: this.period || undefined,
           keyword: this.keyword || undefined,
+          hasExplanation: this.hasExplanation || undefined,
           page: this.page,
           size: this.size
         }
@@ -102,17 +177,26 @@
       this.chatContext = {
         reportId: row.id,
         reportPeriod: row.reportPeriod || this.period,
-        enterpriseName: row.enterpriseName || '',
-        verifyExplanation: explanation
+        enterpriseName: row.enterpriseName || row.city || '',
+        verifyExplanation: explanation,
+        reportType: this.reportType,
+        tableName: TABLE_NAME[this.reportType] || ''
       }
       this.chatVisible = true
     }
   },
   mounted() {
+    const q = this.$route.query
+    if (q.reportType && COLS[q.reportType]) this.reportType = q.reportType
+    if (q.period) this.period = q.period
+    if (q.keyword) this.keyword = q.keyword
     this.loadData()
   }
 }
 </script>
 <style scoped>
 .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; }
-</style>
\ No newline at end of file
+.el-table td { padding: 3px 0; }
+.el-table th { padding: 4px 0; }
+.expl-text { display: inline-block; max-height: 62px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; line-height: 1.4; }
+</style>
diff --git a/traffic-audit-web/src/views/Login.vue b/traffic-audit-web/src/views/Login.vue
index 4993005..e081d22 100644
--- a/traffic-audit-web/src/views/Login.vue
+++ b/traffic-audit-web/src/views/Login.vue
@@ -1,4 +1,4 @@
-锘�<template>
+<template>
   <div class="login-container">
     <el-card class="login-card">
       <h2>浜ら�氱粺璁℃姤琛ㄥ鏍哥郴缁�</h2>
@@ -10,30 +10,97 @@
           <el-input v-model="form.password" type="password" placeholder="瀵嗙爜" @keyup.enter.native="login"></el-input>
         </el-form-item>
         <el-form-item>
-          <el-button type="primary" @click="login" style="width:100%">鐧� 褰�</el-button>
+          <div class="captcha-wrap" v-loading="captchaLoading">
+            <shape-captcha v-if="captcha.shapes.length"
+                           :shapes="captcha.shapes"
+                           :instruction="captcha.instruction"
+                           @complete="onCaptchaComplete"
+                           @refresh="loadCaptcha"
+                           @reset="onCaptchaReset"></shape-captcha>
+            <div v-else class="captcha-empty">姝e湪鍔犺浇楠岃瘉鐮佲��</div>
+          </div>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="login" :loading="loading" style="width:100%">鐧� 褰�</el-button>
         </el-form-item>
       </el-form>
     </el-card>
   </div>
 </template>
 <script>
+import api from '@/api'
+import ShapeCaptcha from '@/components/ShapeCaptcha.vue'
 export default {
   name: 'Login',
+  components: { ShapeCaptcha },
   data() {
     return {
       form: { username: '', password: '' },
+      loading: false,
+      captchaLoading: false,
+      captcha: { captchaId: '', shapes: [], instruction: '' },
+      seq: [],
       rules: {
         username: [{ required: true, message: '璇疯緭鍏ョ敤鎴峰悕', trigger: 'blur' }],
         password: [{ required: true, message: '璇疯緭鍏ュ瘑鐮�', trigger: 'blur' }]
       }
     }
   },
+  created() { this.loadCaptcha() },
   methods: {
+    loadCaptcha() {
+      this.captchaLoading = true
+      this.seq = []
+      this.captcha = { captchaId: '', shapes: [], instruction: '' }
+      api.get('/captcha/shape')
+        .then(res => {
+          const d = res && res.data
+          if (!d || !d.captchaId) { this.$message.error((res && res.message) || '楠岃瘉鐮佸姞杞藉け璐�'); return }
+          this.captcha = d
+        })
+        .catch(err => {
+          const d = err && err.response && err.response.data
+          this.$message.error((d && d.message) || '楠岃瘉鐮佸姞杞藉け璐ワ紝璇风◢鍚庨噸璇�')
+        })
+        .finally(() => { this.captchaLoading = false })
+    },
+    onCaptchaComplete(sequence) { this.seq = sequence || [] },
+    onCaptchaReset() { this.seq = [] },
     login() {
       this.$refs.form.validate(valid => {
-        if (valid) {
-          this.$router.push('/dashboard')
+        if (!valid) return
+        if (!this.captcha.captchaId) { this.$message.warning('楠岃瘉鐮佸姞杞戒腑锛岃绋嶅��'); return }
+        if (!this.seq || this.seq.length < this.captcha.total) {
+          this.$message.warning('璇峰厛鎸夋彁绀洪『搴忕偣鍑婚獙璇佺爜鍥惧舰')
+          return
         }
+        this.loading = true
+        api.post('/captcha/verify', { captchaId: this.captcha.captchaId, sequence: this.seq })
+          .then(res => {
+            if (!res || res.code !== 200) {
+              this.$message.error((res && res.message) || '楠岃瘉鐮佹牎楠屽け璐�')
+              this.loadCaptcha()
+              return
+            }
+            return api.post('/auth/login', this.form)
+          })
+          .then(res => {
+            if (!res) return
+            const d = res.data
+            if (!d || !d.token) { this.$message.error((res && res.message) || '鐧诲綍澶辫触'); return }
+            localStorage.setItem('token', d.token)
+            localStorage.setItem('username', d.username || '')
+            localStorage.setItem('realName', d.realName || '')
+            localStorage.setItem('modules', d.modules || '')
+            this.$message.success('娆㈣繋锛�' + (d.realName || d.username))
+            this.$router.push('/dashboard')
+          })
+          .catch(err => {
+            const d = err && err.response && err.response.data
+            this.$message.error((d && d.message) || '鐧诲綍澶辫触锛氳妫�鏌ョ敤鎴峰悕鎴栧瘑鐮�')
+            this.loadCaptcha()
+          })
+          .finally(() => { this.loading = false })
       })
     }
   }
@@ -41,6 +108,8 @@
 </script>
 <style scoped>
 .login-container { display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f2f5; }
-.login-card { width: 400px; }
-.login-card h2 { text-align: center; margin-bottom: 30px; }
-</style>
+.login-card { width: 420px; }
+.login-card h2 { text-align: center; margin-bottom: 20px; }
+.captcha-wrap { min-height: 168px; }
+.captcha-empty { display: flex; justify-content: center; align-items: center; height: 168px; color: #909399; font-size: 13px; }
+</style>
\ No newline at end of file
diff --git a/traffic-audit-web/src/views/ReportExport.vue b/traffic-audit-web/src/views/ReportExport.vue
index ef1f647..bcfd56b 100644
--- a/traffic-audit-web/src/views/ReportExport.vue
+++ b/traffic-audit-web/src/views/ReportExport.vue
@@ -3,7 +3,7 @@
     <el-header>浜ら�氱粺璁℃姤琛ㄥ鏍哥郴缁� - 鎶ヨ〃鐢熸垚</el-header>
     <el-container>
       <el-aside width="200px">
-        <el-menu router default-active="/report">
+        <el-menu router :default-active="activeMenu">
           <el-menu-item index="/dashboard">宸ヤ綔鍙�</el-menu-item>
           <el-menu-item index="/import">鏁版嵁瀵煎叆</el-menu-item>
           <el-menu-item index="/data">鏁版嵁鏌ョ湅</el-menu-item>
@@ -12,71 +12,475 @@
           <el-menu-item index="/report">鎶ヨ〃鐢熸垚</el-menu-item>
           <el-menu-item index="/rules">瑙勫垯绠$悊</el-menu-item>
           <el-menu-item index="/users">鐢ㄦ埛绠$悊</el-menu-item>
+          <el-menu-item index="/login" @click="logout">閫�鍑虹櫥褰�</el-menu-item>
         </el-menu>
       </el-aside>
       <el-main>
         <div class="page-scroll">
-        <h3>鐢熸垚缁熻鎶ヨ〃</h3>
-        <el-card style="max-width:640px">
-          <el-form label-width="100px">
-            <el-form-item label="鎶ヨ〃鏈�">
-              <el-date-picker v-model="period" type="month" placeholder="閫夋嫨鏈堜唤" value-format="yyyy-MM" style="width:100%"></el-date-picker>
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary" @click="exportReport('cityDetail')" :loading="loading === 'cityDetail'">鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏</el-button>
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary" @click="exportReport('freightRank')" :loading="loading === 'freightRank'">鐢熸垚_璐ц繍閲忔帓鍚�</el-button>
-              <el-button type="primary" @click="exportReport('turnoverRank')" :loading="loading === 'turnoverRank'">鐢熸垚_鍛ㄨ浆閲忔帓鍚�</el-button>
-            </el-form-item>
-          </el-form>
-        </el-card>
-        <el-alert
-          title="鎻愮ず锛氭姤琛ㄧ敱绯荤粺鏍规嵁宸插鍏ユ暟鎹嚜鍔ㄨ绠楃敓鎴愩�傝揣杩愰噺锛氬悎璁℃潵鑷ā鏉縚璐ц繍閲忓懆杞噺锛堝乏鍗�1-N鏈堬級銆佽涓婃潵鑷狧203-2鏈堟姤銆佽涓�=鍚堣-瑙勪笂锛涘懆杞噺鏉ヨ嚜瑙勪笂瑙勪笅鎷嗗垎琛ㄣ��"
-          type="info" style="margin-top:15px" :closable="false"></el-alert>
+          <!-- 绗竴琛岋細鏍囬 + 鏇存柊鏃堕棿 -->
+          <div class="topbar">
+            <h3>鐢熸垚缁熻鎶ヨ〃</h3>
+            <div class="topbar-right">
+              <span v-if="myModules.length" class="focus-user">{{ realName || username }}锛氳礋璐� {{ myModules.map(moduleTitle).join('銆�') }}</span>
+              <span class="update-time">鏇存柊鏃堕棿锛歿{ updatedAt }}</span>
+            </div>
+          </div>
+
+          <!-- 绗簩琛岋細鎶ヨ〃绫诲瀷 Tab锛堣揣杩� / 鏃呭 / 鎶曡祫 / 鑳借�� / 鍩庡競瀹㈣繍锛� -->
+          <el-tabs v-model="activeKey" class="module-tabs">
+            <el-tab-pane v-for="m in modules" :key="m.key" :name="m.key">
+              <span slot="label">{{ m.title }}<el-tag v-if="m.tag" size="mini" :type="m.tagType || 'info'" style="margin-left:6px">{{ m.tag }}</el-tag></span>
+            </el-tab-pane>
+          </el-tabs>
+
+          <!-- 绗笁琛岋細鏌ヨ鏉′欢鍖猴紙鍛ㄦ湡 + 鏈堜唤 + 鏌ヨ锛� -->
+          <section class="query-panel">
+            <span class="ctrl-label">鍛ㄦ湡</span>
+            <el-radio-group v-model="cycle" size="small" @change="onCycleChange">
+              <el-radio-button v-for="opt in cycleOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio-button>
+            </el-radio-group>
+            <span class="ctrl-label">{{ cycle === 'year' ? '骞翠唤' : '鏈堜唤' }}</span>
+            <el-select v-model="period" size="small" style="width:190px" :placeholder="periodPlaceholder" @change="onPeriodChange">
+              <el-option-group v-for="grp in periodGroups" :key="grp.label" :label="grp.label">
+                <el-option v-for="opt in grp.options" :key="opt.value" :label="opt.label" :value="opt.value"></el-option>
+              </el-option-group>
+            </el-select>
+            <el-button type="primary" size="small" icon="el-icon-search" @click="doQuery">鏌ヨ</el-button>
+          </section>
+
+          <!-- 绗洓琛岋細鎶ヨ〃鍒楄〃 -->
+          <section class="module-panel">
+            <div class="panel-head">
+              <div class="panel-title">鎶ヨ〃鍒楄〃<span class="muted">锛坽{ curModule.title }}锛�</span></div>
+              <div class="panel-controls">
+                <el-checkbox :value="allChecked" @change="toggleAll">鍏ㄩ��</el-checkbox>
+                <span class="checked-count">宸查�� {{ curModule.checked.length }} 寮�</span>
+                <el-button size="small" icon="el-icon-view" :disabled="!curModule.checked.length" @click="previewChecked()">鎵归噺棰勮</el-button>
+                <el-button size="small" type="primary" icon="el-icon-download" :disabled="!curModule.checked.length" @click="exportChecked()">鎵归噺瀵煎嚭</el-button>
+              </div>
+            </div>
+            <el-table :data="curModule.items" size="mini" class="report-table">
+              <el-table-column width="44">
+                <template slot-scope="{ row }">
+                  <el-checkbox :value="curModule.checked.indexOf(row.key) >= 0" @change="toggleChecked(row)"></el-checkbox>
+                </template>
+              </el-table-column>
+              <el-table-column label="鎶ヨ〃鍚嶇О" prop="label" min-width="220"></el-table-column>
+              <el-table-column label="鏁版嵁鐘舵��" width="180">
+                <template slot-scope="{ row }">
+                  <el-tag v-if="row.status === 'ready'" size="mini" type="success">鏁版嵁灏辩华</el-tag>
+                  <el-tag v-else-if="row.status === 'unaudited'" size="mini" type="warning">鏈鏍�</el-tag>
+                  <el-tag v-else-if="row.status === 'missing'" size="mini" type="danger">缂烘暟鎹�</el-tag>
+                  <el-tag v-else size="mini" type="info">鈥�</el-tag>
+                  <div v-if="row.statusNote" class="status-note">{{ row.statusNote }}</div>
+                </template>
+              </el-table-column>
+              <el-table-column label="澶囨敞" min-width="190">
+                <template slot-scope="{ row }">
+                  <span class="note-text">{{ noteOf(row.key) || '鐐瑰嚮鍥炬爣娣诲姞澶囨敞' }}</span>
+                  <el-button size="mini" type="text" icon="el-icon-edit" title="缂栬緫澶囨敞" @click="openNoteDialog(row)"></el-button>
+                </template>
+              </el-table-column>
+              <el-table-column label="鎿嶄綔" width="170" align="right">
+                <template slot-scope="{ row }">
+                  <el-button size="mini" icon="el-icon-view" @click="previewOne(row)">棰勮</el-button>
+                  <el-button size="mini" type="primary" icon="el-icon-download" @click="exportOne(row)">瀵煎嚭</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+            <div class="panel-footer">鍏� {{ curModule.items.length }} 寮犳姤琛�</div>
+          </section>
+
+          <el-alert title="浣跨敤鎻愮ず" type="info" style="margin-top:15px" :closable="false">
+            <div class="tip-text">
+              <p>1. 閫夋嫨鎶ヨ〃绫诲瀷 Tab銆佸懆鏈熶笌鏈堜唤鍚庣偣鍑汇�屾煡璇€�嶏紝鍒楄〃浼氭樉绀烘瘡寮犳姤琛ㄧ殑鏁版嵁鐘舵�侊紙鏁版嵁灏辩华 / 缂烘暟鎹� / 鏈鏍革級銆�</p>
+              <p>2. 鏁版嵁鏍¢獙涓庡鏍歌鍒般�屽鏍哥粨鏋溿�嶉〉鏌ョ湅锛岃鍒欏彲鍦ㄣ�岃鍒欑鐞嗐�嶉〉閰嶇疆銆�</p>
+              <p>3. 鍗曞紶鍙洿鎺ョ偣銆岄瑙� / 瀵煎嚭銆嶏紱澶氬紶鍙嬀閫夊悗銆屾壒閲忛瑙� / 鎵归噺瀵煎嚭銆嶏紝澶氬紶瀵煎嚭鑷姩鎵撳寘涓� zip銆�</p>
+              <p>4. 鏈堟姤锛氶�� 2026-07 鐢熸垚 1~7 鏈堟暟鎹紱骞存姤锛氶�夊勾浠界敓鎴愬叏骞� 1~12 鏈� + 涓婂勾鍚屾瘮銆�</p>
+            </div>
+          </el-alert>
         </div>
+        <el-dialog :title="'棰勮鏁版嵁 - ' + previewModule + '锛�' + previewPeriod + '锛�'" :visible.sync="previewVisible" width="760px">
+          <div class="preview-body">
+            <div v-for="it in previewItems" :key="it.key" class="preview-item">
+              <div class="preview-head">
+                <span class="preview-name">{{ it.label }}</span>
+                <span class="preview-rows">鍏� {{ it.rowCount || 0 }} 琛�</span>
+              </div>
+              <div class="preview-coverage">
+                <span class="cov-label">鏈堝害瑕嗙洊锛�</span>
+                <span :class="it.ready ? 'cov-ok' : 'cov-bad'">{{ coverageText(it) }}</span>
+                <span v-if="!it.ready" class="cov-bad" style="margin-left:10px">{{ it.note }}</span>
+                <span v-if="it.ready && it.audited === false" class="cov-bad" style="margin-left:10px">鏈鏍�</span>
+              </div>
+              <table class="preview-summary" v-if="it.summary && it.summary.length">
+                <tbody>
+                  <tr v-for="(s, i) in it.summary" :key="i">
+                    <td>{{ s.name }}</td>
+                    <td><span v-if="s.value === null || s.value === ''" class="cell-empty">锛堢┖锛�</span>{{ fmtVal(s.value) }}</td>
+                  </tr>
+                </tbody>
+              </table>
+              <div class="preview-problems" v-if="it.problems && it.problems.length">
+                <div v-for="(p, i) in it.problems" :key="i" class="prob-item">路 {{ p }}</div>
+              </div>
+              <div class="preview-ok" v-else-if="it.ready">鏁版嵁瀹屾暣锛屽彲姝e父鐢熸垚</div>
+            </div>
+          </div>
+          <span slot="footer">
+            <el-button size="small" @click="previewVisible = false">鍏抽棴</el-button>
+          </span>
+        </el-dialog>
+
+        <!-- 澶囨敞缂栬緫寮圭獥 -->
+        <el-dialog title="缂栬緫澶囨敞" :visible.sync="noteDialogVisible" width="480px">
+          <el-input v-model="noteDraft" type="textarea" :rows="4" placeholder="濉啓璇ユ姤琛ㄧ殑澶囨敞璇存槑锛堜繚瀛樺悗鏈湴璁板繂锛�"></el-input>
+          <span slot="footer">
+            <el-button size="small" @click="noteDialogVisible = false">鍙栨秷</el-button>
+            <el-button size="small" type="primary" @click="saveNote">淇濆瓨</el-button>
+          </span>
+        </el-dialog>
       </el-main>
     </el-container>
   </el-container>
 </template>
+
 <script>
 import api from '@/api'
+
 export default {
   name: 'ReportExport',
   data() {
-    return { period: '', loading: '' }
+    return {
+      loading: '',
+      activeMenu: '/report',
+      activeKey: '',
+      updatedAt: '',
+      // 鏌ヨ鏉′欢锛歝ycle 鍛ㄦ湡锛坢onth 鏈堟姤 / quarter 瀛f姤 / year 骞存姤锛夛紝period 鎶ヨ〃鏈燂紙鏈堜唤 yyyy-MM 鎴栧勾浠� yyyy锛�
+      cycle: 'month',
+      period: '',
+      // 棰勮寮圭獥
+      previewVisible: false,
+      previewItems: [],
+      previewModule: '',
+      previewPeriod: '',
+      // 澶囨敞缂栬緫寮圭獥
+      noteDialogVisible: false,
+      noteEditingKey: '',
+      noteDraft: '',
+      modules: [
+        {
+          key: 'freight', title: '璐ц繍鎶ヨ〃', items: [
+            { key: 'cityDetail', label: '璐ц繍閲忓垎甯傚窞鏄庣粏', fileName: '鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx', status: null, statusNote: '' },
+            { key: 'freightRank', label: '璐ц繍閲忔帓鍚�', fileName: '鐢熸垚_璐ц繍閲忔帓鍚�.xlsx', status: null, statusNote: '' },
+            { key: 'turnoverRank', label: '鍛ㄨ浆閲忔帓鍚�', fileName: '鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx', status: null, statusNote: '' }
+          ], checked: []
+        },
+        {
+          key: 'passenger', title: '鏃呭鎶ヨ〃', items: [
+            { key: 'passengerCityDetail', label: '鍏矾鏃呭鍒嗗競宸�', fileName: '鐢熸垚_鍏矾鏃呭鍒嗗競宸�.xlsx', status: null, statusNote: '' },
+            { key: 'passengerMidDetail', label: '涓彛寰勬槑缁�', fileName: '鐢熸垚_鍏矾鏃呭涓彛寰勬槑缁�.xlsx', status: null, statusNote: '' },
+            { key: 'passengerMidRank', label: '涓彛寰勬帓鍚�', fileName: '鐢熸垚_鍏矾鏃呭涓彛寰勬帓鍚�.xlsx', status: null, statusNote: '' },
+            { key: 'passengerMidAnalysis', label: '涓彛寰勫垎鏋�', fileName: '鐢熸垚_鍏矾鏃呭涓彛寰勫垎鏋�.xlsx', status: null, statusNote: '' }
+          ], checked: []
+        },        {
+          key: 'cityPassenger', title: '鍩庡競瀹㈣繍鎶ヨ〃', tag: '鍏氦/鍑虹/杞ㄩ亾杞浮/姹囨��', tagType: 'success', 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: '' }
+          ], checked: []
+        },
+        {
+          key: 'invest', title: '鎶曡祫鎶ヨ〃', items: [
+            { key: 'investPlan', label: '鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽眹鎬�', fileName: '鐢熸垚_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls', status: null, statusNote: '' },
+            { key: 'investFiveYearLogistics', label: '鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞�', fileName: '鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls', status: null, statusNote: '' },
+            { key: 'investBillion', label: '浜垮厓鎶曡祫椤圭洰', fileName: '鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls', status: null, statusNote: '' },
+            { key: 'investCounty', label: '缁忔祹寮哄幙椤圭洰鎶曡祫缁熻', fileName: '鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls', status: null, statusNote: '' }
+          ], checked: []
+        },
+        {
+          key: 'energy', title: '鑳借�楁姤琛�', items: [
+            { key: 'energySummary', label: '鑳借繍姹囨�昏〃', fileName: '鐢熸垚_鑳借繍姹囨�昏〃.xlsx', status: null, statusNote: '' }
+          ], checked: []
+        }
+      ]
+    }
   },
-  methods: {
-    exportReport(type) {
-      if (!this.period) { this.$message.warning('璇烽�夋嫨鎶ヨ〃鏈�'); return }
-      this.loading = type
-      const token = localStorage.getItem('token') || ''
-      api.get('/report/export/' + type, { params: { period: this.period }, responseType: 'blob' })
-        .then(res => {
-          const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
-          const url = window.URL.createObjectURL(blob)
-          const a = document.createElement('a')
-          a.href = url
-          a.download = this.fileName(type)
-          document.body.appendChild(a)
-          a.click()
-          window.URL.revokeObjectURL(url)
-          this.$message.success('鎶ヨ〃鐢熸垚鎴愬姛')
-        })
-        .catch(() => { this.$message.error('鎶ヨ〃鐢熸垚澶辫触锛岃纭宸插鍏ュ搴旀暟鎹�') })
-        .finally(() => { this.loading = '' })
+  computed: {
+    username() { return localStorage.getItem('username') || '' },
+    realName() { return localStorage.getItem('realName') || '' },
+    myModules() {
+      const s = localStorage.getItem('modules')
+      if (!s) return []
+      return s.split(',').filter(Boolean)
     },
-    fileName(type) {
-      const map = {
-        cityDetail: '鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx',
-        freightRank: '鐢熸垚_璐ц繍閲忔帓鍚�.xlsx',
-        turnoverRank: '鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx'
+    curModule() {
+      return this.modules.find(m => m.key === this.activeKey) || this.modules[0]
+    },
+    allChecked() {
+      const m = this.curModule
+      return m.items.length > 0 && m.checked.length === m.items.length
+    },
+    // 鍛ㄦ湡閫夐」锛氳兘鑰椾负瀛f姤锛堝搴︽湯鏈堬級锛屽叾浣欎负鏈堟姤 / 骞存姤
+    cycleOptions() {
+      if (this.activeKey === 'energy') return [{ label: '瀛f姤', value: 'quarter' }]
+      return [{ label: '鏈堟姤', value: 'month' }, { label: '骞存姤', value: 'year' }]
+    },
+    periodPlaceholder() {
+      return this.cycle === 'year' ? '閫夋嫨骞翠唤' : '閫夋嫨鏈堜唤'
+    },
+    // 鎶ヨ〃鏈熶笅鎷夊垎缁勶細骞存姤鎸夊勾浠斤紱鏈堟姤/瀛f姤鎸夊勾鍒嗙粍鍒� 1-12 鏈堬紙鑳借�楀彧鍒楀搴︽湯鏈堬級
+    periodGroups() {
+      const year = new Date().getFullYear()
+      const years = [year - 1, year, year + 1]
+      if (this.cycle === 'year') {
+        return [{ label: '閫夋嫨骞翠唤', options: years.map(y => ({ label: y + '骞�', value: String(y) })) }]
       }
-      return map[type] || 'report.xlsx'
+      const months = this.activeKey === 'energy' ? [3, 6, 9, 12] : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+      return years.map(y => ({
+        label: y + '骞�' + (this.activeKey === 'energy' ? '锛堝搴︽湯鏈堬級' : ''),
+        options: months.map(mo => ({ label: y + '骞�' + mo + '鏈�', value: y + '-' + String(mo).padStart(2, '0') }))
+      }))
+    },
+  },
+  watch: {
+    activeKey() {
+      this.ensureCycle()
+      this.refreshAll()
+    }
+  },
+  created() {
+    const mine = this.myModules.filter(k => this.modules.some(m => m.key === k))
+    this.activeKey = (mine.length ? mine : this.modules.map(m => m.key))[0]
+    this.ensureCycle()
+    this.refreshAll()
+  },  methods: {
+    moduleTitle(key) {
+      const m = this.modules.find(x => x.key === key)
+      return m ? m.title : key
+    },
+    logout() {
+      localStorage.removeItem('token'); localStorage.removeItem('username'); localStorage.removeItem('realName'); localStorage.removeItem('modules')
+      this.$router.push('/login')
+    },
+    // ==================== 鏌ヨ鏉′欢涓庣姸鎬佸埛鏂� ====================
+    buildParams() {
+      const p = { period: this.period }
+      p.mode = this.cycle === 'year' ? 'year' : 'month'
+      return p
+    },
+    // 鍛ㄦ湡涓庢姤琛ㄦ湡鍖归厤锛氬垏鎹㈠懆鏈熷悗娓呮帀涓嶅尮閰嶇殑鏃у��
+    normalizePeriod() {
+      const isYear = /^\d{4}$/.test(this.period || '')
+      const isMonth = /^\d{4}-\d{2}$/.test(this.period || '')
+      if (this.cycle === 'year' && isMonth) this.period = ''
+      if (this.cycle !== 'year' && isYear) this.period = ''
+    },
+    ensureCycle() {
+      const allowed = this.cycleOptions.map(o => o.value)
+      if (!allowed.includes(this.cycle)) this.cycle = allowed[0]
+      this.normalizePeriod()
+    },
+    onCycleChange() {
+      this.normalizePeriod()
+      this.refreshAll()
+    },
+    onPeriodChange() {
+      this.refreshAll()
+    },
+    doQuery() {
+      if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鍛ㄦ湡鍜屾湀浠�'); return }
+      this.refreshAll()
+    },
+    refreshAll() {
+      this.updatedAt = new Date().toLocaleString('zh-CN', { hour12: false })
+      this.refreshStatus()
+    },
+    refreshStatus() {
+      const m = this.curModule
+      if (!m || !this.period) return
+      const params = Object.assign(this.buildParams(), { types: m.items.map(i => i.key).join(',') })
+      api.get('/report/ready', { params })
+        .then(res => {
+          const list = (res && res.data && res.data.items) || []
+          list.forEach(r => {
+            const it = m.items.find(x => x.key === r.key)
+            if (!it) return
+            if (!r.ready) { it.status = 'missing'; it.statusNote = r.note || '缂烘暟鎹�' }
+            else if (r.audited === false) { it.status = 'unaudited'; it.statusNote = '灏氭湭瀹℃牳閫氳繃' }
+            else { it.status = 'ready'; it.statusNote = '' }
+          })
+        })
+        .catch(() => {})
+    },
+    noteOf(key) {
+      return localStorage.getItem('report_note_' + key) || ''
+    },
+    openNoteDialog(row) {
+      this.noteEditingKey = row.key
+      this.noteDraft = this.noteOf(row.key)
+      this.noteDialogVisible = true
+    },
+    saveNote() {
+      localStorage.setItem('report_note_' + this.noteEditingKey, this.noteDraft.trim())
+      this.noteDialogVisible = false
+      this.$message.success('澶囨敞宸蹭繚瀛�')
+    },
+    // ==================== 鍕鹃�� / 棰勮 / 瀵煎嚭 ====================
+    toggleChecked(row) {
+      const checked = this.curModule.checked
+      const i = checked.indexOf(row.key)
+      if (i >= 0) checked.splice(i, 1)
+      else checked.push(row.key)
+    },
+    toggleAll(v) {
+      this.curModule.checked = v ? this.curModule.items.map(i => i.key) : []
+    },
+    exportOne(item) { this.exportChecked([item]) },
+    previewOne(item) { this.previewChecked([item]) },
+    previewChecked(itemList) {
+      const m = this.curModule
+      if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鍛ㄦ湡鍜屾湀浠�'); return }
+      const items = itemList || m.items.filter(i => m.checked.includes(i.key))
+      if (!items.length) { this.$message.warning('璇烽�夋嫨瑕侀瑙堢殑鎶ヨ〃'); return }
+      this.loading = 'preview-' + m.key
+      const params = Object.assign(this.buildParams(), { types: items.map(i => i.key).join(',') })
+      api.get('/report/preview', { params })
+        .then(res => {
+          const list = (res && res.data && res.data.items) || []
+          this.previewItems = list.map(it => {
+            const item = items.find(x => x.key === it.key)
+            return Object.assign({}, it, { label: item ? item.label : it.key })
+          })
+          this.previewModule = m.title
+          this.previewPeriod = this.period
+          this.previewVisible = true
+          this.loading = ''
+        })
+        .catch(err => this.failWith(err))
+    },
+    fmtVal(v) {
+      if (v === null || v === undefined || v === '') return '鈥�'
+      if (typeof v === 'number') {
+        if (Number.isInteger(v)) return String(v)
+        return v.toFixed(4).replace(/\.?0+$/, '')
+      }
+      return String(v)
+    },
+    coverageText(it) {
+      const cov = it.monthCoverage || []
+      if (!cov.length) return '鏃犳暟鎹�'
+      const continuous = cov.length === cov[cov.length - 1]
+      if (continuous) return '1~' + cov[cov.length - 1] + '鏈堝潎鏈夋暟鎹�'
+      return cov.map(x => x + '鏈�').join('銆�') + ' 鏈夋暟鎹�'
+    },
+    exportChecked(itemList) {
+      const m = this.curModule
+      if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鍛ㄦ湡鍜屾湀浠�'); return }
+      const items = itemList || m.items.filter(i => m.checked.includes(i.key))
+      if (!items.length) { this.$message.warning('璇烽�夋嫨瑕佸鍑虹殑鎶ヨ〃'); return }
+      this.loading = 'gen-' + m.key
+      const params = this.buildParams()
+      const doExport = () => {
+        if (items.length === 1) {
+          api.get('/report/export/' + items[0].key, { params, responseType: 'blob' })
+            .then(res => { this.downloadBlob(res, items[0].fileName); this.$message.success('鎶ヨ〃瀵煎嚭鎴愬姛'); this.loading = '' })
+            .catch(err => this.failWith(err))
+          return
+        }
+        const zipParams = Object.assign({}, params, { types: items.map(i => i.key).join(',') })
+        api.get('/report/export/selected', { params: zipParams, responseType: 'blob' })
+          .then(res => { this.downloadBlob(res, '鐢熸垚_鎵�閫夋姤琛╛' + this.period + '.zip'); this.$message.success('宸插鍑� ' + items.length + ' 寮犳姤琛紙zip锛�'); this.loading = '' })
+          .catch(err => this.failWith(err))
+      }
+      const readyParams = Object.assign({}, params, { types: items.map(i => i.key).join(',') })
+      api.get('/report/ready', { params: readyParams })
+        .then(rd => {
+          const list = (rd && rd.data && rd.data.items) || []
+          const missing = list.filter(i => !i.ready)
+          const unaudited = list.filter(i => i.ready && i.audited === false)
+          const lines = []
+          missing.forEach(i => {
+            const it = items.find(x => x.key === i.key)
+            lines.push('銆愮己鏁版嵁銆�' + (it ? it.label : i.key) + '锛�' + i.note + '锛�')
+          })
+          unaudited.forEach(i => {
+            const it = items.find(x => x.key === i.key)
+            lines.push('銆愭湭瀹℃牳銆�' + (it ? it.label : i.key))
+          })
+          if (!lines.length) { doExport(); return }
+          this.$confirm('浠ヤ笅鎶ヨ〃璇锋敞鎰忥細\n' + lines.join('\n') + '\n\n浠嶈瀵煎嚭鍚楋紵', '瀵煎嚭鍓嶆彁绀�', {
+            confirmButtonText: '浠嶈瀵煎嚭', cancelButtonText: '鍙栨秷', type: 'warning'
+          }).then(() => doExport()).catch(() => { this.loading = '' })
+        })
+        .catch(() => doExport())
+    },
+    failWith(err) {
+      let msg = ''
+      const d = err && err.response && err.response.data
+      if (d && typeof d === 'object' && d.message) msg = d.message
+      this.$message.error('瀵煎嚭澶辫触锛�' + (msg || '璇风‘璁ゅ凡瀵煎叆瀵瑰簲鏁版嵁锛屾垨鏌ョ湅鍚庣鏃ュ織'))
+      this.loading = ''
+    },
+    downloadBlob(res, fileName) {
+      const isZip = /\.zip$/.test(fileName)
+      const isXls = /\.xls$/.test(fileName)
+      const blob = new Blob([res], { type: isZip ? 'application/zip' : (isXls ? 'application/vnd.ms-excel' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') })
+      const url = window.URL.createObjectURL(blob)
+      const a = document.createElement('a')
+      a.href = url
+      a.download = fileName
+      document.body.appendChild(a)
+      a.click()
+      window.URL.revokeObjectURL(url)
     }
   }
 }
-</script>
-<style scoped>
+</script><style scoped>
 .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; }
+.page-scroll { max-width: 1200px; }
+.topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
+.topbar h3 { margin: 0; }
+.topbar-right { display: flex; align-items: center; gap: 12px; font-size: 13px; color: #606266; }
+.update-time { font-size: 12px; color: #909399; }
+.module-tabs { margin-bottom: 6px; }
+.query-panel { background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 12px 16px; margin-bottom: 12px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
+.data-card { background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 14px 16px; margin-bottom: 12px; }
+.card-title { font-size: 15px; font-weight: 600; color: #303133; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
+.muted { font-size: 12px; color: #909399; font-weight: 400; }
+.module-panel { background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 14px 16px; }
+.ctrl-label { font-size: 13px; color: #606266; }
+.panel-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }
+.panel-title { font-size: 16px; font-weight: 600; color: #303133; display: flex; align-items: center; gap: 6px; }
+.panel-controls { display: flex; align-items: center; gap: 10px; }
+.report-table { width: 100%; }
+.checked-count { font-size: 12px; color: #909399; }
+.panel-footer { margin-top: 10px; font-size: 12px; color: #909399; text-align: right; }
+.status-note { font-size: 11px; color: #F56C6C; margin-top: 2px; line-height: 1.4; }
+.note-text { font-size: 12px; color: #606266; margin-right: 4px; }
+.spacer { flex: 1; }
+.preview-body { max-height: 68vh; overflow-y: auto; }
+.preview-item { border: 1px solid #ebeef5; border-radius: 6px; padding: 12px 14px; margin-bottom: 12px; }
+.preview-head { display: flex; align-items: center; margin-bottom: 8px; }
+.preview-name { font-weight: 600; font-size: 15px; margin-right: 10px; }
+.preview-rows { margin-left: auto; color: #909399; font-size: 12px; }
+.preview-coverage { font-size: 13px; margin-bottom: 8px; }
+.cov-ok { color: #67C23A; }
+.cov-bad { color: #F56C6C; }
+.cov-label { color: #909399; }
+.preview-summary { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 8px; }
+.preview-summary th, .preview-summary td { border: 1px solid #ebeef5; padding: 4px 8px; text-align: left; }
+.preview-summary th { background: #f5f7fa; }
+.preview-summary .cell-empty { color: #F56C6C; }
+.preview-problems { font-size: 13px; color: #F56C6C; }
+.preview-problems .prob-item { margin: 2px 0; }
+.preview-ok { font-size: 13px; color: #67C23A; }
+.tip-text { font-size: 13px; line-height: 1.7; }
+.tip-text p { margin: 2px 0; }
+@media (max-width: 900px) {
+  .panel-head { flex-direction: column; align-items: flex-start; }
+}
 </style>
diff --git a/traffic-audit-web/src/views/RuleManage.vue b/traffic-audit-web/src/views/RuleManage.vue
index 06ab9b4..30d2f57 100644
--- a/traffic-audit-web/src/views/RuleManage.vue
+++ b/traffic-audit-web/src/views/RuleManage.vue
@@ -1,2 +1,114 @@
-锘�<template><div>瑙勫垯绠$悊</div></template>
-<script>export default { name: 'RuleManage' }</script>
+<template>
+  <el-container>
+    <el-header>浜ら�氱粺璁℃姤琛ㄥ鏍哥郴缁� - 瑙勫垯绠$悊</el-header>
+    <el-container>
+      <el-aside width="200px">
+        <el-menu router default-active="/rules">
+          <el-menu-item index="/dashboard">宸ヤ綔鍙�</el-menu-item>
+          <el-menu-item index="/import">鏁版嵁瀵煎叆</el-menu-item>
+          <el-menu-item index="/data">鏁版嵁鏌ョ湅</el-menu-item>
+          <el-menu-item index="/explain">浼佷笟瑙i噴</el-menu-item>
+          <el-menu-item index="/audit">瀹℃牳缁撴灉</el-menu-item>
+          <el-menu-item index="/report">鎶ヨ〃鐢熸垚</el-menu-item>
+          <el-menu-item index="/rules">瑙勫垯绠$悊</el-menu-item>
+          <el-menu-item index="/users">鐢ㄦ埛绠$悊</el-menu-item>
+          <el-menu-item index="/login" @click="logout">閫�鍑虹櫥褰�</el-menu-item>
+</el-menu>
+      </el-aside>
+      <el-main>
+        <h3>瀹℃牳瑙勫垯绠$悊</h3>
+        <el-form :inline="true">
+          <el-form-item label="鎶ヨ〃绫诲瀷">
+            <el-radio-group v-model="reportType" @change="loadRules">
+              <el-radio label="H2032">璐ц繍鏈堟姤</el-radio>
+              <el-radio label="H2031">鏃呭鏈堟姤</el-radio>
+              <el-radio label="CITY_BUS">鍩庡競鍏氦鏈堟姤</el-radio>
+              <el-radio label="CITY_TAXI">宸℃父鍑虹鏈堟姤</el-radio>
+              <el-radio label="INVEST">鎶曡祫鏈堟姤</el-radio>
+              <el-radio label="H204">鑳借�楀鎶�</el-radio>
+            
+            
+            
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="loadRules" :loading="loading">鍒锋柊</el-button>
+          </el-form-item>
+        </el-form>
+        <div class="table-area">
+        <el-table :data="rules" border v-loading="loading" size="small" style="width:100%" height="100%">
+          <el-table-column prop="ruleCode" label="瑙勫垯缂栫爜" width="120"></el-table-column>
+          <el-table-column prop="ruleName" label="瑙勫垯鍚嶇О" min-width="180"></el-table-column>
+          <el-table-column prop="description" label="瑙勫垯璇存槑" min-width="260" show-overflow-tooltip></el-table-column>
+          <el-table-column label="姣旇緝鏂瑰紡" width="130">
+            <template slot-scope="scope">{{ typeLabel(scope.row.compareType) }}</template>
+          </el-table-column>
+          <el-table-column label="闃堝��" width="130">
+            <template slot-scope="scope">
+              <el-input v-model="scope.row.threshold" size="mini" placeholder="闃堝��" @change="saveThreshold(scope.row)"></el-input>
+            </template>
+          </el-table-column>
+          <el-table-column label="绛夌骇" width="110">
+            <template slot-scope="scope">
+              <el-select v-model="scope.row.alertLevel" size="mini" @change="saveThreshold(scope.row)">
+                <el-option label="绾� (RED)" value="RED"></el-option>
+                <el-option label="榛� (YELLOW)" value="YELLOW"></el-option>
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="鍚敤" width="80">
+            <template slot-scope="scope">
+              <el-switch v-model="scope.row.isEnabled" :active-value="1" :inactive-value="0"
+                @change="toggleRule(scope.row)"></el-switch>
+            </template>
+          </el-table-column>
+        </el-table>
+        </div>
+      </el-main>
+    </el-container>
+  </el-container>
+</template>
+<script>
+import api from '@/api'
+const TYPE_CN = {
+  DIFF: '宸�煎姣�', RATIO: '姣斾緥瀵规瘮', MOM: '鐜瘮鏂板', NULL_CHECK: '蹇呭~/涓�鑷存��',
+  OUT_RANGE: '鑼冨洿妫�鏌�', MOM_EQ_ZERO: '鐜瘮涓�0', MOM_PCT: '鐜瘮鐧惧垎姣�',
+  MOM_EQ: '绛変簬涓婃湀', EXISTENCE: '瀛樺湪鎬�', CROSS_DIFF: '璺ㄨ〃瀵规瘮',
+  EXISTENCE_AUTH: '杩愭斂缂哄け', EXISTENCE_SYS: '绯荤粺缂哄け', CROSS_DIFF_PCT: '璺ㄨ〃鐧惧垎姣�',
+  TREND_CONSISTENCY: '瓒嬪娍涓�鑷�', RATIO_CHANGE: '鍗犳瘮鍙樺寲', AVG_DIST_CHANGE: '骞冲潎杩愯窛鍙樺寲',
+  PAX_PER_TRIP_CHANGE: '鍗曡溅娆¤浇瀹汉鏁板彉鍖�', LOADED_RATIO_DIFF: '杞借揣閲岀▼鍗犳瘮瀵规瘮',
+  FUEL_RANGE: '鍗曡�楄秴鑼冨洿', FUEL_MOM: '鍗曡�楃幆姣�', FREIGHT_VS_H2032: '涓庤揣杩愭湀鎶ュ姣�',
+  AVG_DIST_VS_H2032: '骞冲潎杩愯窛瀵规瘮', LOADED_TON_RATIO: '杞借揣鍚ㄤ綅姣斿��'
+}
+export default {
+  name: 'RuleManage',
+  data() {
+    return { reportType: 'H2032', rules: [], loading: false }
+  },
+  methods: {
+    typeLabel(t) { return TYPE_CN[t] || t },
+    loadRules() {
+      this.loading = true
+      api.get('/rules/list', { params: { reportType: this.reportType } })
+        .then(res => { this.rules = res.data || [] })
+        .catch(() => { this.$message.error('瑙勫垯鍔犺浇澶辫触') })
+        .finally(() => { this.loading = false })
+    },
+    toggleRule(row) {
+      api.post('/rules/toggle', null, { params: { id: row.id, enabled: row.isEnabled } })
+        .then(() => { this.$message.success((row.isEnabled ? '宸插惎鐢細' : '宸插仠鐢細') + row.ruleCode) })
+        .catch(() => { row.isEnabled = row.isEnabled ? 0 : 1; this.$message.error('鎿嶄綔澶辫触') })
+    },
+    saveThreshold(row) {
+      api.post('/rules/update', {
+        id: row.id, threshold: row.threshold, alertLevel: row.alertLevel
+      }).then(() => { this.$message.success('宸蹭繚瀛橈細' + row.ruleCode) })
+        .catch(() => { this.$message.error('淇濆瓨澶辫触') })
+    }
+  },
+  mounted() { this.loadRules() }
+}
+</script>
+<style scoped>
+.el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; }
+</style>
diff --git a/traffic-audit-web/src/views/UserManage.vue b/traffic-audit-web/src/views/UserManage.vue
index 6deae10..abae3fe 100644
--- a/traffic-audit-web/src/views/UserManage.vue
+++ b/traffic-audit-web/src/views/UserManage.vue
@@ -1,2 +1,185 @@
-锘�<template><div>鐢ㄦ埛绠$悊</div></template>
-<script>export default { name: 'UserManage' }</script>
+<template>
+  <el-container>
+    <el-header>浜ら�氱粺璁℃姤琛ㄥ鏍哥郴缁� - 鐢ㄦ埛绠$悊</el-header>
+    <el-container>
+      <el-aside width="200px">
+        <el-menu router :default-active="activeMenu">
+          <el-menu-item index="/dashboard">宸ヤ綔鍙�</el-menu-item>
+          <el-menu-item index="/import">鏁版嵁瀵煎叆</el-menu-item>
+          <el-menu-item index="/data">鏁版嵁鏌ョ湅</el-menu-item>
+          <el-menu-item index="/explain">浼佷笟瑙i噴</el-menu-item>
+          <el-menu-item index="/audit">瀹℃牳缁撴灉</el-menu-item>
+          <el-menu-item index="/report">鎶ヨ〃鐢熸垚</el-menu-item>
+          <el-menu-item index="/rules">瑙勫垯绠$悊</el-menu-item>
+          <el-menu-item index="/users">鐢ㄦ埛绠$悊</el-menu-item>
+          <el-menu-item index="/login" @click="logout">閫�鍑虹櫥褰�</el-menu-item>
+        </el-menu>
+      </el-aside>
+      <el-main>
+        <div class="page-scroll">
+          <h3>鐢ㄦ埛绠$悊锛堣礋璐fā鍧楀喅瀹氭姤琛ㄧ敓鎴愰〉榛樿鏄剧ず鍝簺妯″潡锛�</h3>
+          <div class="toolbar">
+            <el-input v-model="keyword" placeholder="鐢ㄦ埛鍚� / 濮撳悕" clearable style="width:220px" @keyup.enter.native="loadList" @clear="loadList"></el-input>
+            <el-button type="primary" icon="el-icon-search" @click="loadList">鏌ヨ</el-button>
+            <el-button type="success" icon="el-icon-plus" @click="openEdit()">鏂板鐢ㄦ埛</el-button>
+          </div>
+          <div class="table-area">
+            <el-table :data="list" border stripe size="small" v-loading="loading">
+              <el-table-column prop="id" label="ID" width="60" align="center"></el-table-column>
+              <el-table-column prop="username" label="鐢ㄦ埛鍚�" width="120"></el-table-column>
+              <el-table-column prop="realName" label="濮撳悕" width="100"></el-table-column>
+              <el-table-column prop="phone" label="鎵嬫満鍙�" width="130"></el-table-column>
+              <el-table-column label="璐熻矗妯″潡" min-width="240">
+                <template slot-scope="{ row }">
+                  <el-tag v-for="mk in moduleKeys(row.modules)" :key="mk" size="mini" style="margin-right:4px">{{ moduleLabel(mk) }}</el-tag>
+                  <span v-if="!row.modules" style="color:#999">鏈厤缃紙榛樿鍏ㄩ儴鍙锛�</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="鐘舵��" width="80" align="center">
+                <template slot-scope="{ row }">
+                  <el-tag :type="row.status === 1 ? 'success' : 'info'" size="mini">{{ row.status === 1 ? '鍚敤' : '鍋滅敤' }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column prop="createdAt" label="鍒涘缓鏃堕棿" width="170"></el-table-column>
+              <el-table-column label="鎿嶄綔" width="140" align="center">
+                <template slot-scope="{ row }">
+                  <el-button type="text" size="mini" @click="openEdit(row)">缂栬緫</el-button>
+                  <el-button type="text" size="mini" style="color:#f56c6c" @click="removeUser(row)">鍒犻櫎</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </div>
+      </el-main>
+    </el-container>
+
+    <el-dialog :title="form.id ? '缂栬緫鐢ㄦ埛' : '鏂板鐢ㄦ埛'" :visible.sync="dialogVisible" width="520px" @closed="resetForm">
+      <el-form ref="userForm" :model="form" :rules="formRules" label-width="90px">
+        <el-form-item label="鐢ㄦ埛鍚�" prop="username">
+          <el-input v-model="form.username" :disabled="!!form.id" placeholder="鐧诲綍璐﹀彿"></el-input>
+        </el-form-item>
+        <el-form-item label="瀵嗙爜" :prop="form.id ? '' : 'password'">
+          <el-input v-model="form.password" type="password" :placeholder="form.id ? '鐣欑┖鍒欎笉淇敼瀵嗙爜' : '璇疯緭鍏ュ垵濮嬪瘑鐮�'"></el-input>
+        </el-form-item>
+        <el-form-item label="濮撳悕" prop="realName">
+          <el-input v-model="form.realName" placeholder="鐪熷疄濮撳悕"></el-input>
+        </el-form-item>
+        <el-form-item label="鎵嬫満鍙�">
+          <el-input v-model="form.phone" placeholder="閫夊~"></el-input>
+        </el-form-item>
+        <el-form-item label="璐熻矗妯″潡">
+          <el-checkbox-group v-model="form.modules">
+            <el-checkbox v-for="mo in moduleOptions" :key="mo.key" :label="mo.key">{{ mo.label }}</el-checkbox>
+          </el-checkbox-group>
+          <div class="form-tip">鎶ヨ〃鐢熸垚椤甸粯璁ゅ彧鏄剧ず鍕鹃�夌殑妯″潡锛涗笉鍕惧垯鍏ㄩ儴鍙</div>
+        </el-form-item>
+        <el-form-item label="鐘舵��">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="1">鍚敤</el-radio>
+            <el-radio :label="0">鍋滅敤</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+        <el-button type="primary" :loading="saving" @click="saveUser">淇濆瓨</el-button>
+      </div>
+    </el-dialog>
+  </el-container>
+</template>
+<script>
+import api from '@/api'
+const MODULE_OPTIONS = [
+  { key: 'freight', label: '璐ц繍鎶ヨ〃' },
+  { key: 'passenger', label: '鍏矾鏃呭鎶ヨ〃' },
+  { key: 'invest', label: '鎶曡祫鎶ヨ〃' },
+  { key: 'energy', label: '鑳借�楁姤琛�' },
+  { key: 'cityPassenger', label: '鍩庡競瀹㈣繍鎶ヨ〃' }
+]
+export default {
+  name: 'UserManage',
+  data() {
+    return {
+      activeMenu: '/users',
+      keyword: '',
+      list: [],
+      loading: false,
+      dialogVisible: false,
+      saving: false,
+      moduleOptions: MODULE_OPTIONS,
+      form: { id: null, username: '', password: '', realName: '', phone: '', modules: [], status: 1 },
+      formRules: {
+        username: [{ required: true, message: '璇疯緭鍏ョ敤鎴峰悕', trigger: 'blur' }],
+        password: [{ required: true, message: '璇疯緭鍏ュ垵濮嬪瘑鐮�', trigger: 'blur' }]
+      }
+    }
+  },
+  mounted() { this.loadList() },
+  methods: {
+    logout() {
+      localStorage.removeItem('token'); localStorage.removeItem('username'); localStorage.removeItem('realName'); localStorage.removeItem('modules')
+      this.$router.push('/login')
+    },
+    loadList() {
+      this.loading = true
+      api.get('/user/list', { params: { keyword: this.keyword } })
+        .then(res => { this.list = res.data || [] })
+        .catch(() => this.$message.error('鐢ㄦ埛鍒楄〃鍔犺浇澶辫触'))
+        .finally(() => { this.loading = false })
+    },
+    moduleKeys(modules) {
+      if (!modules) return []
+      return modules.split(',').filter(Boolean)
+    },
+    moduleLabel(key) {
+      const mo = MODULE_OPTIONS.find(x => x.key === key)
+      return mo ? mo.label : key
+    },
+    openEdit(row) {
+      this.dialogVisible = true
+      this.$nextTick(() => {
+        if (row && row.id) {
+          this.form = {
+            id: row.id, username: row.username, password: '', realName: row.realName || '',
+            phone: row.phone || '', modules: this.moduleKeys(row.modules), status: row.status === 0 ? 0 : 1
+          }
+        } else {
+          this.form = { id: null, username: '', password: '', realName: '', phone: '', modules: [], status: 1 }
+        }
+        this.$refs.userForm && this.$refs.userForm.clearValidate()
+      })
+    },
+    resetForm() {
+      this.form = { id: null, username: '', password: '', realName: '', phone: '', modules: [], status: 1 }
+      this.$refs.userForm && this.$refs.userForm.clearValidate()
+    },
+    saveUser() {
+      this.$refs.userForm.validate(valid => {
+        if (!valid) return
+        this.saving = true
+        api.post('/user/save', this.form)
+          .then(() => {
+            this.$message.success('淇濆瓨鎴愬姛')
+            this.dialogVisible = false
+            this.loadList()
+          })
+          .catch(err => {
+            const d = err && err.response && err.response.data
+            this.$message.error((d && d.message) || '淇濆瓨澶辫触')
+          })
+          .finally(() => { this.saving = false })
+      })
+    },
+    removeUser(row) {
+      this.$confirm('纭畾鍒犻櫎鐢ㄦ埛銆�' + (row.realName || row.username) + '銆嶅悧锛�', '鍒犻櫎纭', { type: 'warning' })
+        .then(() => api.post('/user/delete', null, { params: { id: row.id } }))
+        .then(() => { this.$message.success('宸插垹闄�'); this.loadList() })
+        .catch(() => {})
+    }
+  }
+}
+</script>
+<style scoped>
+.toolbar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
+.form-tip { font-size: 12px; color: #909399; line-height: 1.6; margin-top: 4px; }
+</style>
diff --git a/verify_final.py b/verify_final.py
deleted file mode 100644
index dcc3e75..0000000
--- a/verify_final.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-import openpyxl, glob, os
-
-tmp = r'C:\Users\jcxiong\AppData\Local\Temp\ta_e2e_524cecf1'
-docs = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs'
-
-pairs = [
-    ('gen_freight_turnover.xlsx', '鐢熸垚_璐ц繍閲忓懆杞噺.xlsx'),
-    ('gen_city_detail.xlsx', '鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx'),
-    ('gen_freight_rank.xlsx', '鐢熸垚_璐ц繍閲忔帓鍚�.xlsx'),
-    ('gen_turnover_rank.xlsx', '鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx'),
-]
-for gen_name, sample_name in pairs:
-    gen_path = os.path.join(tmp, gen_name)
-    sample_path = glob.glob(os.path.join(docs, sample_name))[0]
-    wg = openpyxl.load_workbook(gen_path)
-    ws = wg[wg.sheetnames[0]]
-    wb2 = openpyxl.load_workbook(sample_path)
-    wss = wb2[wb2.sheetnames[0]]
-    match = 'OK' if (ws.max_row, ws.max_column) == (wss.max_row, wss.max_column) else 'DIFF'
-    print(f'{gen_name}: {ws.max_row}x{ws.max_column} vs {wss.max_row}x{wss.max_column} -> {match}')
-    print('   gen title:', str(ws.cell(1,1).value)[:40])
-# detail rank row layout
-ws = openpyxl.load_workbook(os.path.join(tmp, 'gen_freight_rank.xlsx'))[openpyxl.load_workbook(os.path.join(tmp, 'gen_freight_rank.xlsx')).sheetnames[0]]
-for r in [1,2,3,4,5,6,22,23,24,25,26,27,28,44,45,46,47]:
-    a = ws.cell(r,1).value
-    b = ws.cell(r,2).value
-    print('  R%d:' % r, str(a)[:30] if a is not None else '', '|', str(b)[:16] if b is not None else '')
\ No newline at end of file
diff --git a/write_readme.py b/write_readme.py
deleted file mode 100644
index 10df37f..0000000
--- a/write_readme.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- coding: utf-8 -*-
-import io
-content = """# 浜ら�氱粺璁℃姤琛ㄥ鏍哥郴缁� - Demo 璇存槑
-
-> Demo 鑼冨洿锛氫氦浼佺粺H203-2琛紙閬撹矾璐х墿杩愯緭鏈堝害鐢熶骇鎯呭喌锛�
-> 闇�姹傛枃妗o細`璐ц繍绠�鍖�.docx`銆乣database.md`銆乣architecture.md`锛涙暟鎹簱鑴氭湰锛歚init.sql`
-
-## 涓�銆佺幆澧冭姹�
-
-| 缁勪欢 | 瑕佹眰 |
-|------|------|
-| JDK | 1.8 |
-| Maven | 3.x |
-| Node.js | 14+锛圴ue CLI锛� |
-| MySQL | 5.7锛堟湰鏈� `localhost:3305`锛岃处鍙� `root/root`锛� |
-
-## 浜屻�佸垵濮嬪寲鏁版嵁搴�
-
-```bash
-mysql --default-character-set=utf8mb4 -h localhost -P 3305 -u root -proot < docs/init.sql
-```
-
-`init.sql` 浼氶噸寤� `traffic_audit` 搴擄紙鍚棫鐗堣〃娓呯悊锛夛紝骞堕缃細
-- 閮ㄩ棬锛氭箹鍖楃渷浜ら�氳繍杈撳巺
-- 瑙掕壊锛氫笁鍛� + 鐪佸巺涓氬姟鍛�
-- 璐﹀彿锛歚admin / admin123`
-- 6 鏉″鏍歌鍒欙紙RULE_001 ~ RULE_006锛�
-
-## 涓夈�佸惎鍔ㄥ悗绔�
-
-```bash
-# 鏂瑰紡涓�锛氱洿鎺ヨ繍琛�
-mvn spring-boot:run -f traffic-audit-server/pom.xml
-
-# 鏂瑰紡浜岋細鍚庡彴杩愯锛堟棩蹇楀啓鍏� backend.log锛屽凡鎻愪緵鑴氭湰锛�
-run-backend.cmd
-```
-
-- 绔彛锛歚8090`
-- 鎺ュ彛鍓嶇紑锛歚/api/**`锛堝綋鍓嶄负 permitAll锛屼究浜� Demo锛�
-
-## 鍥涖�佸惎鍔ㄥ墠绔�
-
-```bash
-cd traffic-audit-web
-npm install
-npm run serve
-```
-
-- 绔彛锛歚8080`
-- 鐧诲綍锛歚admin / admin123`
-
-## 浜斻�丏emo 鎿嶄綔娴佺▼
-
-1. 鐧诲綍绯荤粺锛岃繘鍏ャ�屾暟鎹鍏ャ�嶉〉锛屾寜椤哄簭瀵煎叆 `docs/` 涓� 4 涓ā鏉匡細
-   - `妯℃澘_浜や紒缁烪203-2琛╛閬撹矾璐х墿杩愯緭鏈堝害鐢熶骇鎯呭喌.xlsx` 鈫� 鎶ヨ〃鏈� `2026-07`
-   - `妯℃澘_杩愭斂杞﹁締.xlsx` 鈫� 鏃犻渶鎶ヨ〃鏈�
-   - `妯℃澘_杞ㄨ抗閲岀▼.xlsx` 鈫� 鎶ヨ〃鏈� `2026-07`
-   - `妯℃澘_瑙勪笂瑙勪笅鎷嗗垎杩愯緭閲�.xlsx` 鈫� 鎶ヨ〃鏈� `2026-06`锛堟ā鏉垮唴涓� 2026 骞� 6 鏈堝綋鏈� + 1-6 鏈堢疮璁★級
-2. 杩涘叆銆屾暟鎹鏍搞�嶉〉锛岄�夋嫨鎶ヨ〃鏈� `2026-07`锛岀偣銆屾墽琛屽鏍搞�嶏紝鏌ョ湅闂鍒楄〃锛屽彲閫愭潯銆岀‘璁�/蹇界暐銆嶃��
-3. 杩涘叆銆屾姤琛ㄧ敓鎴愩�嶉〉锛岄�夋嫨鎶ヨ〃鏈燂紝涓嬭浇 4 寮犳姤琛細
-   - 鐢熸垚_璐ц繍閲忓懆杞噺.xlsx锛�38 琛� 脳 21 鍒楋級
-   - 鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx锛�112 琛� 脳 16 鍒楋級
-   - 鐢熸垚_璐ц繍閲忔帓鍚�.xlsx锛�47 琛� 脳 18 鍒楋級
-   - 鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx锛�47 琛� 脳 18 鍒楋級
-
-## 鍏�佷富瑕佹帴鍙�
-
-| 鎺ュ彛 | 璇存槑 |
-|------|------|
-| `POST /api/import/h2032` | 瀵煎叆 H203-2 鏈堟姤锛坒ile + period锛� |
-| `POST /api/import/transportAuth` | 瀵煎叆杩愭斂杞﹁締锛坒ile锛� |
-| `POST /api/import/trackMileage` | 瀵煎叆杞ㄨ抗閲岀▼锛坒ile + period锛� |
-| `POST /api/import/scaleSplit` | 瀵煎叆瑙勪笂瑙勪笅鎷嗗垎锛坒ile + period锛� |
-| `POST /api/audit/execute?period=` | 鎵ц瀹℃牳 |
-| `GET /api/audit/results?period=` | 鏌ヨ瀹℃牳缁撴灉 |
-| `POST /api/audit/review` | 瀹℃牳纭/蹇界暐锛坕d + status + comment锛� |
-| `GET /api/report/export/freightTurnover?period=` | 鐢熸垚_璐ц繍閲忓懆杞噺 |
-| `GET /api/report/export/cityDetail?period=` | 鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏 |
-| `GET /api/report/export/freightRank?period=` | 鐢熸垚_璐ц繍閲忔帓鍚� |
-| `GET /api/report/export/turnoverRank?period=` | 鐢熸垚_鍛ㄨ浆閲忔帓鍚� |
-
-## 涓冦�佹暟鎹笌鍏紡璇存槑
-
-- 瀵煎叆閲囩敤**骞傜瓑瑕嗙洊**锛氬悓鎶ヨ〃鏈熼噸鏂板鍏ヤ細鍏堝垹闄ゆ棫鏁版嵁銆�
-- 6 鏉″鏍歌鍒欙細
-  1. RULE_001 杞﹁締鏁拌繍鏀垮姣旓紙宸窛 > 5 鍙帮級
-  2. RULE_002 鍚ㄤ綅杩愭斂瀵规瘮锛堝樊璺� > 50 鍚級
-  3. RULE_003 鍛ㄨ浆閲忚建杩瑰姣旓紙涓婃姤 / 杞ㄨ抗娴嬬畻 > 1.3锛�
-  4. RULE_004 璐ц繍绫诲瀷鐜瘮锛堟湰鏈堟柊澧炶揣绫伙級
-  5. RULE_005 闆嗚绠卞紓甯革紙鏃犻泦瑁呯杞︿絾鏈夐泦瑁呯璐ц繍閲忥級
-  6. RULE_006 鏁磋溅鍚ㄤ綅鑼冨洿锛堜笉鍦� 4-40 鍚ㄤ箣闂达級
-- 杞ㄨ抗娴嬬畻鍛ㄨ浆閲� = 杞ㄨ抗閲岀▼ / 杞ㄨ抗杞﹁締鏁� 脳锛堢壍寮曡溅鏁� 脳 31 + 鏁磋溅鍚ㄤ綅锛夛紝鍏朵腑鏁磋溅鍚ㄤ綅 = 杩愭斂鍏跺畠杞﹁締鎬诲惃浣� / 鍏跺畠杞﹁締鏁般��
-- 鎶ヨ〃涓� 1-5 鏈堜负绌哄睘姝e父锛氬綋鍓嶆ā鏉垮彧鏈� 2026-07锛圚2032锛変笌 2026-06锛堟媶鍒嗭級鍗曟湀鏁版嵁锛屾寜鏈堣ˉ鍏呮暟鎹悗鍚勬湀鑷姩濉厖銆�
-
-## 鍏�佸綋鍓嶉檺鍒� / 鍚庣画瑙勫垝
-
-- 瑙勪笂璐ц繍閲忔潵鑷� H2032 姹囨�伙紙鎸夊競宸烇級锛岃涓�/鍚堣鏉ヨ嚜鎷嗗垎琛紙鍛ㄨ浆閲忓彛寰勶級銆�
-- 鎷嗗垎妯℃澘鏆備负 13 鍒楋紙鍛ㄨ浆閲�+鎺掑悕+鍚屾瘮锛夛紱浠g爜宸插吋瀹瑰甫璐ц繍閲忓垪鐨� 15 鍒楁墿灞曞竷灞�銆�
-- 鍚庣画鎸夌粺璁¤皟鏌ュ埗搴︽墿灞曞叾浠栬〃锛堣〃鍚嶆寜涓氬姟缂栧彿鍛藉悕锛屽 `h2032_enterprise_monthly`锛夛紝鍙鐢ㄦ湰妗嗘灦鐨勫鍏モ啋瀹℃牳鈫掓姤琛ㄩ摼璺��
-"""
-path = r'C:\Users\jcxiong\Documents\Codex\MyProject\trafficAudit\docs\README.md'
-with io.open(path, 'w', encoding='utf-8', newline='') as f:
-    f.write(content)
-print('written:', path, len(content), 'chars')
\ No newline at end of file
diff --git "a/\351\241\271\347\233\256\350\257\264\346\230\216.txt" "b/\351\241\271\347\233\256\350\257\264\346\230\216.txt"
new file mode 100644
index 0000000..28ab50d
--- /dev/null
+++ "b/\351\241\271\347\233\256\350\257\264\346\230\216.txt"
@@ -0,0 +1,39 @@
+浜ら�氳揣杩愮粺璁″鏍哥郴缁� 鈥斺�� 椤圭洰浜や粯鍖�
+===================================
+
+銆愰」鐩粨鏋勩��
+  traffic-audit-server   鍚庣锛圫pring Boot锛岀鍙� 8090锛�
+  traffic-audit-web      鍓嶇锛圴ue2 + Element UI锛岀鍙� 8080锛�/api 浠g悊鍒� 8090锛�
+  docs/ requirement/     闇�姹備笌璁捐鏂囨。
+  test/                  鏍蜂緥鏁版嵁
+
+銆愮幆澧冭姹傘��
+  JDK 8+銆丮aven 3.x銆丯ode.js 14~18銆丮ySQL 5.7/8.0銆丷edis
+
+銆愭暟鎹簱鍒濆鍖栥�戯紙棣栨杩愯鍓嶅繀椤绘墽琛岋級
+  1. 寤哄簱寤鸿〃 + 鍩虹鏁版嵁锛氭墽琛� docs/init.sql
+     锛堣嚜鍔ㄥ垱寤� traffic_audit 鏁版嵁搴撱�佸叏閮ㄤ笟鍔¤〃銆佺敤鎴锋潈闄愪笌鍩虹鏁版嵁锛�
+  2. 鍩庡競瀹㈣繍鎵╁睍琛紙鎸夐渶锛夛細鎵ц docs/sql_city_bus.sql銆乨ocs/sql_city_taxi.sql
+  3. 淇敼 traffic-audit-server/src/main/resources/application.yml
+     涓殑鏁版嵁搴�/Redis 璐﹀彿瀵嗙爜涓烘湰鏈哄疄闄呭��
+
+銆愬惎鍔ㄦ楠ゃ��
+  鏂瑰紡涓�锛堟帹鑽愶紝Windows PowerShell锛岄」鐩牴鐩綍鎵ц锛夛細
+    powershell -ExecutionPolicy Bypass -File start-dev.ps1
+    鑴氭湰鑷姩鍚姩鍚庣(8090)涓庡墠绔�(8080)锛屽苟绛夊緟灏辩华銆�
+
+  鏂瑰紡浜岋紙鎵嬪姩锛夛細
+    鍚庣锛歝d traffic-audit-server && mvn spring-boot:run
+    鍓嶇锛歝d traffic-audit-web && npm install && npm run serve
+
+  璁块棶锛歨ttp://localhost:8080
+  榛樿璐﹀彿锛歛dmin / 123456
+
+銆愰噸瑕侀厤缃��
+  鏁版嵁搴�/Redis 杩炴帴閰嶇疆鍦細
+    traffic-audit-server/src/main/resources/application.yml
+  锛堝唴鍚紑鍙戠幆澧冭处鍙峰瘑鐮侊紝璇锋寜瀹為檯鐜淇敼锛�
+    閰嶇疆妯℃澘瑙佸悓鐩綍 application.yml.example锛�
+
+銆愬娉ㄣ��
+  鍓嶇棣栨鍚姩鍓嶉渶 npm install锛堜緷璧栨湭闅忓寘鎻愪緵锛夈��

--
Gitblit v1.9.1