traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -5781,6 +5781,8 @@
            String curCity = null;
            Set<Long> used = new HashSet<>();
            int filled = 0, kept = 0, dup = 0;
            List<String> skipped = new ArrayList<>();
            Map<String, Integer> skippedByCity = new LinkedHashMap<>();
            for (int r = 8; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
@@ -5792,11 +5794,16 @@
                if (!a.matches("\\d+(\\.\\d+)?")) continue;
                if (b.isEmpty() || curCity == null) continue;
                InvestmentProject p = matchInvest(projects, b, curCity, "客运站场", used);
                p = preferMonthlyCandidate(projects, p, mByProject, "客运站场", used);
                InvestmentMonthly m = p == null ? null : mByProject.get(p.getId());
                if (p == null || m == null) {
                    kept++;
                    // 有库内项目但当月无数据 → 记为待核对;无匹配项目 → 结转上月
                    if (p != null) dup++;
                    // 2026-09-22:不再静默结转上月,未覆盖的行必须留痕(行号+市州+项目名+原因)
                    skipped.add("第" + (r + 1) + "行 " + curCity + " " + b
                            + (p == null ? " → 库内无匹配项目" : " → 库内无 " + period + " 月度数据"));
                    skippedByCity.merge(curCity, 1, Integer::sum);
                    continue;
                }
                used.add(p.getId());
@@ -5826,6 +5833,7 @@
            }
            log.info("客运站投资明细表 {}:写入 {} 行,结转母版原值 {} 行(其中库内有项目但当月无数据 {} 行)",
                    period, filled, kept, dup);
            logSkippedRows("客运站投资明细表", period, skipped, skippedByCity);
            return toBytes(wb);
        }
    }
@@ -5866,6 +5874,8 @@
        Map<String, double[]> cityStats = new LinkedHashMap<>();  // 全名 -> {总数, 累计, 规划内数, 规划内累计, 规划外数, 规划外累计}
        Set<Long> used = new HashSet<>();
        int filled = 0, kept = 0;
        List<String> skipped = new ArrayList<>();
        Map<String, Integer> skippedByCity = new LinkedHashMap<>();
        for (int i = 0; i < cityRows.size(); i++) {
            int cr = cityRows.get(i);
            String cityLabel = nvl(cellText(sheet.getRow(cr).getCell(0)));
@@ -5888,8 +5898,16 @@
                if (!a.matches("\\d+(\\.\\d+)?")) continue;
                if (b.isEmpty()) continue;
                InvestmentProject p = matchInvest(projects, b, city, "物流园区", used);
                p = preferMonthlyCandidate(projects, p, mByProject, "物流园区", used);
                InvestmentMonthly m = p == null ? null : mByProject.get(p.getId());
                if (p == null || m == null) { kept++; continue; }
                if (p == null || m == null) {
                    kept++;
                    // 2026-09-22:不再静默结转上月,未覆盖的行必须留痕
                    skipped.add("第" + (r + 1) + "行 " + city + " " + b
                            + (p == null ? " → 库内无匹配项目" : " → 库内无 " + period + " 月度数据"));
                    skippedByCity.merge(city, 1, Integer::sum);
                    continue;
                }
                used.add(p.getId());
                // 口径同客运站明细表:只覆盖 H 自开始建设累计 / J 自年初累计 / K 本月完成 / M 形象进度
                if (m.getStartCum() != null) putNum(row, 7, m.getStartCum());
@@ -6020,6 +6038,7 @@
            log.warn("物流站场投资明细公式求值失败: {}", e.getMessage());
        }
        log.info("物流站场投资明细 {}:写入 {} 行,结转母版原值 {} 行", period, filled, kept);
        logSkippedRows("物流站场投资明细", period, skipped, skippedByCity);
        return toBytesHssf(wb);
    }
@@ -6314,6 +6333,40 @@
        return full != null && RegionUtil.cityList().contains(full);
    }
    /**
     * 同名(归一化后一致)多档时,优先选「当月有数据」的那一档。
     * 背景(2026-09-22):十堰「快递物流产业园二期」在库内有两档 id=154(竣工 2026,月度只到 7 月)与
     * id=181(竣工 2029,有 8 月),模板行名前缀与 154 一致 → 导出取到 154 的 7 月值。
     */
    private InvestmentProject preferMonthlyCandidate(List<InvestmentProject> pool, InvestmentProject p,
                                                    Map<Long, InvestmentMonthly> mByProject,
                                                    String category, Set<Long> excludeIds) {
        if (p == null || mByProject.containsKey(p.getId())) return p;
        String norm = normInvestName(p.getProjectName());
        if (norm.isEmpty()) return p;
        InvestmentProject better = null;
        for (InvestmentProject c : pool) {
            if (c == null || c.getId().equals(p.getId())) continue;
            if (excludeIds != null && excludeIds.contains(c.getId())) continue;
            if (category != null && !category.equals(c.getCategory())) continue;
            if (!mByProject.containsKey(c.getId())) continue;
            if (!norm.equals(normInvestName(c.getProjectName()))) continue;
            if (better == null || c.getId() > better.getId()) better = c;
        }
        return better != null ? better : p;
    }
    /** 未按当月数据覆盖的行必须留痕:逐行列出行号+市州+项目名,避免「静默结转上期值」再次发生 */
    private void logSkippedRows(String reportName, String period, List<String> skipped, Map<String, Integer> byCity) {
        if (skipped == null || skipped.isEmpty()) return;
        log.warn("{} {}:{} 行未按当月数据覆盖(结转母版原值),需人工核对源文件/库内数据:",
                reportName, period, skipped.size());
        int limit = Math.min(skipped.size(), 50);
        for (int i = 0; i < limit; i++) log.warn("  [未覆盖] {}", skipped.get(i));
        if (skipped.size() > limit) log.warn("  …另有 {} 行未逐行列出", skipped.size() - limit);
        if (byCity != null && !byCity.isEmpty()) log.warn("  未覆盖行按市州统计:{}", byCity);
    }
    /** 在候选项目池中按名称匹配 DB 项目(可选 市州/类别 过滤 + 县区前缀消歧),未命中返回 null */
    private InvestmentProject matchInvest(List<InvestmentProject> pool, String name, String cityFull, String category) {
        return matchInvest(pool, name, cityFull, category, null);
@@ -6362,7 +6415,8 @@
        if (name == null) return "";
        String s = name.trim();
        s = s.replaceAll("[((]", "").replaceAll("[))]", "");
        s = s.replaceAll("[·•—-_\\-]", "");
        // 2026-09-22:补充半/全角句点、顿号、分号等(库内「中国供销.公安商贸物流园」因半角句点匹配不上模板行)
        s = s.replaceAll("[·•—-_\\-..。、,,;;::]", "");
        s = s.replaceAll("[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ]", "I");
        s = s.replaceAll("^(湖北省|武汉市|黄石市|十堰市|宜昌市|襄阳市|鄂州市|荆门市|孝感市|荆州市|黄冈市|咸宁市|随州市|恩施州|仙桃市|潜江市|天门市|神农架林区)", "");
        s = s.replaceAll("^(?:[\\u4e00-\\u9fa5]{2,4}(?:县|市|区))", "");