feat: 汇总大表数据驱动 M1-班线包车——新增 MidCalc 计算服务(企业+个体口径)与 7 月回归测试报告
1个文件已修改
2个文件已添加
177 ■■■■■ 已修改文件
docs/功能测试报告/功能测试报告_M1-班线包车MidCalc_2026-09-08.md 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工作日结/工作日结_2026-09-08.md 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/MidCalc.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_M1-°àÏß°ü³µMidCalc_2026-09-08.md
New file
@@ -0,0 +1,44 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Šï¼šM1-班线包车(MidCalc ä¼ä¸š+个体取数)与 7 æœˆå›žå½’(2026-09-08)
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
汇总大表数据驱动改造 M1 ç¬¬äº”块:把“班线包车页”的数据库取数固化为可复用 Java è®¡ç®—服务
`MidCalc`(`traffic-audit-server/.../reportexport/calc/MidCalc.java`),输出
“月 â†’ å¸‚å·ž/全省 â†’ [客运量(含个体), å‘¨è½¬é‡(含个体), ä¸ªä½“客运量, ä¸ªä½“周转量](万人/万人公里)”。
它是中口径明细/排名/分析/道路运输周转量等派生页在数据库中唯一的公路班线数据源。
## äºŒã€æµ‹è¯•环境
- åº“:`traffic_audit`(127.0.0.1:3308),数据 = 2026-01~07 å·²å¯¼å…¥çŽ°çŠ¶ï¼›
- æ¯ç‰ˆåŸºçº¿ï¼š`docs/生成汇总大表/_备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx`(班线包车页,2026 æœˆåº¦æ•°å€¼åˆ— C/E/G/I/K/M/O å‡å·²ç¼“存);
- å·¥å…·ï¼šmysql CLI å–æ•° + Python é€æ ¼æ¯”对(`_m1_mid_regress.py`,结果 `_m1_mid_regress.txt`);后端 `mvn -q -DskipTests compile` é€šè¿‡ï¼ˆexit=0)。
## ä¸‰ã€å–数口径(经母版逐格反推确认,2026-07 ä¸Ž 1-7 æœˆå…¨éƒ¨å‘½ä¸­ï¼‰
| æ¯ç‰ˆè¡Œ | å£å¾„ | æ¥æº |
|---|---|---|
| å®¢è¿é‡ / æ—…客周转量(万人 / ä¸‡äººå…¬é‡Œï¼‰ | **企业总计 + ä¸ªä½“å¹¶å…¥** | `h2031_enterprise_monthly.passenger_total / turnover_total`(人/人公里)÷10000,叠加 `passenger_individual_monthly.passenger_count / turnover` Ã·10000 |
| å…¶ä¸­ï¼šä¸ªä½“客运量 / ä¸ªä½“旅客周转量 | ä¸ªä½“æ•° Ã·10000 | ä»…黄石/襄阳/荆门/孝感/黄冈/随州 6 å¸‚有数 |
| æ¹–北省(全省)行 | 17 å¸‚州之和 | ä¸Žæ¯ç‰ˆâ€œå…¨çœè¡Œ=Σ城市行”公式一致 |
| åŒæ¯” | ä¸è®¡ç®— | Q1:以母版缓存 2025 å‚照组为基准,由写回层填表时计算 |
## å››ã€ç”¨ä¾‹è¡¨
| # | ç”¨ä¾‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|---|
| 1 | ä¼ä¸šå®¢è¿é‡/周转量(含个体并入) | åº“ 2026-01~07 | ä¸Žæ¯ç‰ˆç­çº¿åŒ…车城市行“客运量/周转量”列逐格一致 | 350 æ ¼ diff=0 | é€šè¿‡ |
| 2 | å…¶ä¸­ä¸ªä½“客运量/周转量 | åº“ 2026-01~07 | ä¸Žæ¯ç‰ˆâ€œå…¶ä¸­ä¸ªä½“”行逐格一致(6 å¸‚) | å«äºŽä¸Šè¿° 350 æ ¼ï¼Œdiff=0 | é€šè¿‡ |
| 3 | æ¹–北省行 = Î£ 17 å¸‚å·ž | åº“汇总 | ä¸Žæ¯ç‰ˆå…¬å¼å£å¾„一致 | ä¸€è‡´ | é€šè¿‡ |
| 4 | ä¸­å£å¾„分析“公路班线”锚点 | åº“ 2026-07 / 1-7 ç´¯è®¡ | å½“月 1631.2013、累计 11204.5406(母版缓存值) | 1612.2015+18.9998=1631.2013;11062.7993+141.7413=11204.5406 | é€šè¿‡ |
| 5 | åŸŽé™…城乡公交/出租/网约派生页锚点 | æ¯ç‰ˆæºé¡µ O åˆ—/累计列 | ä¸Žä¸­å£å¾„分析缓存一致 | å…¬äº¤å½“月 1466.66、出租 509、网约 421.46 å…¨ä¸­ï¼ˆç´¯è®¡å…¬äº¤è§é—®é¢˜ 2) | éƒ¨åˆ†å‘½ä¸­ |
| 6 | Java ç¼–译 | `mvn -q -DskipTests compile` | æ— é”™è¯¯ | exit=0 | é€šè¿‡ |
## äº”、发现的问题与处置
1. åˆç‰ˆå›žå½’误把“客运量”行当作纯企业数,比对发现黄石等 6 å¸‚母版值 = ä¼ä¸š+个体(差量恰为该市个体值);
   ç»é€æ ¼åæŽ¨ç¡®è®¤**班线包车“客运量/周转量”口径含个体**,“其中个体”行为其子集 â†’ å·²æŒ‰å«ä¸ªä½“口径实现并复跑 0 å·®å¼‚。
2. æ¯ç‰ˆå†…部不一致(非本次代码问题,记入待办):中口径分析页“累计城际城乡公交=10194.09”,
   ä¸Žæ¯ç‰ˆå…¬äº¤å°è´¦é¡µ 1-7 æœˆåŸŽé™…城乡逐月求和 8676.57 ä¸ç¬¦ï¼ˆå·® 1517.52),疑为该页旧计算残值;
   å½“月各派生页锚点与源页一致。处置:M2 ä»¥æºé¡µï¼ˆæœ¬æœåŠ¡+已验收的 Pax/Wyc æœåŠ¡ï¼‰æ•°å€¼é‡ç®—æˆå“é¡µåŽå¤éªŒã€‚
3. ä¸­å£å¾„排名页当月/累计数值无法用母版源页当前数值解释(部分市州差量恒定),判定同为旧计算残值,
   éš M2 æˆå“é¡µé‡ç®—一并核对,不在 M1 å–数层处理。
## å…­ã€ç»“论与下一步
- ç»“论:班线包车页可从库完整复算(350 æ ¼ 0 å·®å¼‚),口径锁定;`MidCalc` å·²å…¥åº“并通过编译。
- ä¸‹ä¸€æ­¥ï¼šâ‘  M1 å‰©ä½™æ´¾ç”Ÿé¡µå£å¾„核对(排名/分析/道路运输周转量,建议随 M2 é‡ç®—实现);② M2 SummaryFiller
  å†™å›žå°è´¦+成品页并逐格 diff(含本报告问题 2/3 çš„修复验证);③ 8 æœˆå®žå¼¹ã€‚
docs/¹¤×÷ÈÕ½á/¹¤×÷ÈÕ½á_2026-09-08.md
@@ -6,15 +6,22 @@
   - æ‰§è¡Œé¡ºåºæŒ‰ E1:先回归 7 æœˆã€å†å¡« 8 æœˆï¼›
   - è´§è¿/公路旅客/城市客运三类独立小表前端全部隐藏,内部中间表保留;
   - ç¡®ç«‹çºªå¾‹ï¼šä»¥å›žæ‰§æ–‡æ¡£ä¸ºå”¯ä¸€å†³ç­–基线,已确认项不再重复提问。
2. å¯åЍ M0(按需扩列)实现准备:分析母版台账页“2026-07 â†’ 2026-08 æ‰©åˆ—”影响面(待补充结果)。
2. M0 å½±å“é¢å®žæµ‹ä¸Žå®žçŽ°ç­–ç•¥ï¼ˆæŒ‰éœ€æ‰©åˆ—ï¼Œ10 é•¿è¡¨é¡µé€æœˆæ‰©åˆ—、成品页不扩)——见 `docs/汇总大表数据驱动改造方案…` é™„录。
3. M1 è®¡ç®—服务抽取与 7 æœˆå›žå½’(逐块测试报告见 `docs/功能测试报告/`,均 0 å·®å¼‚):
   - M1-货运:新增 `FreightCalc`(规上/规下/合计 Ã— è´§è¿é‡/周转量 Ã— å½“月/累计);
   - M1-城市客运:新增 `PaxCalc`(公交/出租/轨道轮渡台账页);
   - M1-网约车:新增 `WycSplitCalc`(17 å¸‚州拆分 + å…¨çœ pin);
   - M1-城市客运整链回归(城市汇总页当月 r14-20、累计 r3-9 0 å·®å¼‚);
   - M1-班线包车:新增 `MidCalc`(h2031 ä¼ä¸šæ€»è®¡ + ä¸ªä½“并入口径,350 æ ¼ 0 å·®å¼‚)。
## æµ‹è¯•结果摘要
- æ— ä»£ç æ”¹åŠ¨ï¼ˆæ–‡æ¡£ä¿®è®¢é˜¶æ®µï¼‰ã€‚
- äº”个 M1 å—各自 7 æœˆå›žå½’ 0 å·®å¼‚并通过 `mvn -q -DskipTests compile`(exit=0)。
- å›žå½’中发现母版成品页旧计算残值(记待办):中口径分析“累计城际城乡公交 10194.09”与公交台账页 1-7 æœˆæ±‚å’Œ 8676.57 ä¸ç¬¦ï¼›
  ä¸­å£å¾„排名页数值亦与母版源页当前数值不一致——判定 M2 é‡ç®—成品页时一并修复核对。
## é—留问题与待办
- M0:按需扩列实现(当月值列+同比列插入累计前、累计滚动、2025+ å‚照组右移)——影响面分析中;
- M1:计算服务抽取与 7 æœˆå¯¹è´¦ï¼›M2:SummaryFiller å›žå½’ 7 æœˆï¼›M3:8 æœˆå®žå¼¹ï¼›M4:小表前端隐藏+删备份还原。
### è¿½åŠ ï¼šM0 å½±å“é¢å®žæµ‹ä¸Žå®žçŽ°ç­–ç•¥ï¼ˆæ™šé—´ï¼‰
- å®žæµ‹ç¡®è®¤éœ€é€æœˆæ‰©åˆ—的页=10 ä¸ªé•¿è¡¨é¡µï¼ˆè´§è¿/班线包车/公路总客运/城市客运/公交/出租/网约车/轨道轮渡/中口径明细/中口径客运量);6 ä¸ªæˆå“é¡µ+计算页不扩列。
- M0 å®šæ¡ˆï¼šå®žçŽ°â€œæœˆåº¦ç‰ˆå¼é‡å»ºå™¨â€ï¼ˆåˆ—å¸ƒå±€å‚æ•°åŒ–æŒ‰ç›®æ ‡æœˆé‡å»ºï¼Œéž POI å°±åœ°å¹³ç§»ï¼‰ï¼Œè¯¦è§æ–¹æ¡ˆé™„录。
- M0:月度版式重建器(按需扩列)实现;
- M1:派生页(中口径明细/排名/分析/道路运输周转量/公路总客运)口径核对建议并入 M2 æˆå“é¡µé‡ç®—一并验证;
- M2:SummaryFiller å†™å›žå°è´¦é¡µ+成品页,逐格 diff(含本日“旧计算残值”修复验证);
- M3:8 æœˆå®žå¼¹ï¼ˆå…ˆæ‰©åˆ— 2026-08,再导入 8 æœˆæ•°æ®ç”Ÿæˆï¼‰ï¼›
- M4:小表前端隐藏 + å¤‡ä»½è¿˜åŽŸ/清理。
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/MidCalc.java
New file
@@ -0,0 +1,110 @@
package com.trafficaudit.reportexport.calc;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.trafficaudit.common.util.RegionUtil;
import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly;
import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
import com.trafficaudit.dataimport.mapper.PassengerIndividualMonthlyMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
 * å…¬è·¯ç­çº¿ï¼ˆå«ä¸ªä½“)口径计算服务(汇总大表数据驱动改造 M1-班线包车,2026-09-08)。
 *
 * å£å¾„(2026-07 ä¸Ž _备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx ç­çº¿åŒ…车页
 * ä¼ä¸š/个体行 1-7 æœˆé€æ ¼å›žå½’ 0 å·®å¼‚,350 æ ¼å…¨ä¸­ï¼‰ï¼š
 * 1. ç­çº¿åŒ…车页“客运量/旅客周转量”行 = h2031_enterprise_monthly æŒ‰ 17 å¸‚州汇总
 *    ï¼ˆpassenger_total / turnover_total,人 / äººå…¬é‡Œï¼‰é™¤ä»¥ 10000,再并入个体客运
 *    ï¼ˆpassenger_individual_monthly åŒå£å¾„);“其中个体客运量/周转量”行 = ä¸ªä½“数除以 10000
 *    ï¼ˆå½“前仅黄石/襄阳/荆门/孝感/黄冈/随州有数)。
 * 2. å…¨çœè¡Œ = 17 å¸‚州之和,与母版“全省行=城市行求和”公式一致;中口径分析页“公路班线”
 *    = æœ¬å£å¾„全省合计(当月 1631.2013、累计 11204.5406 ä¸Žæ¯ç‰ˆ 0 å·®å¼‚)。
 * 3. åŒæ¯”以母版缓存 2025 å‚照组为基准(Q1),不在本服务计算。
 *
 * è¾“出:月 â†’ å¸‚州(含 PROVINCE=湖北省) â†’ double[4] =
 * [客运量(含个体), å‘¨è½¬é‡(含个体), ä¸ªä½“客运量, ä¸ªä½“周转量],
 * å•位均为母版展示口径(万人 / ä¸‡äººå…¬é‡Œï¼‰ï¼›ä¼ä¸šå•独量 = arr[0]-arr[2] ä¸Ž arr[1]-arr[3]。
 */
@Service
public class MidCalc {
    public static final String PROVINCE = "湖北省";
    @Resource
    private PassengerEnterpriseMonthlyMapper passengerMapper;
    @Resource
    private PassengerIndividualMonthlyMapper individualMapper;
    /** ç­çº¿åŒ…车页数值矩阵:月 â†’ å¸‚å·ž/全省 â†’ [客运量,周转量,个体客运量,个体周转量] */
    public Map<Integer, Map<String, double[]>> banxianMonthly(String yearPrefix, int limit) {
        Map<Integer, Map<String, double[]>> result = new LinkedHashMap<>();
        List<PassengerEnterpriseMonthly> entRows = passengerMapper.selectList(
            new LambdaQueryWrapper<PassengerEnterpriseMonthly>().likeRight(
                PassengerEnterpriseMonthly::getReportPeriod, yearPrefix));
        for (PassengerEnterpriseMonthly r : entRows) {
            int m = monthOf(r.getReportPeriod());
            if (m < 1 || m > limit) continue;
            String city = RegionUtil.cityByCode(r.getRegionCode());
            if (city == null) continue;
            double pax = nz(r.getPassengerTotal());
            double turn = nz(r.getTurnoverTotal());
            if (pax == 0.0 && turn == 0.0) {
                pax = nz(r.getPassengerClass1()) + nz(r.getPassengerClass2())
                    + nz(r.getPassengerClass3()) + nz(r.getPassengerClass4())
                    + nz(r.getPassengerCharter());
                turn = nz(r.getTurnoverClass1()) + nz(r.getTurnoverClass2())
                    + nz(r.getTurnoverClass3()) + nz(r.getTurnoverClass4())
                    + nz(r.getTurnoverCharter());
            }
            accumulate(result, m, city, pax / 10000.0, turn / 10000.0, 0.0, 0.0);
        }
        List<PassengerIndividualMonthly> indRows = individualMapper.selectList(
            new LambdaQueryWrapper<PassengerIndividualMonthly>().likeRight(
                PassengerIndividualMonthly::getReportPeriod, yearPrefix));
        for (PassengerIndividualMonthly r : indRows) {
            int m = monthOf(r.getReportPeriod());
            if (m < 1 || m > limit) continue;
            String city = RegionUtil.cityByCode(r.getRegionCode());
            if (city == null) continue;
            double pax = nz(r.getPassengerCount()) / 10000.0;
            double turn = nz(r.getTurnover()) / 10000.0;
            accumulate(result, m, city, pax, turn, pax, turn);
        }
        return result;
    }
    private void accumulate(Map<Integer, Map<String, double[]>> result, int m, String city,
                            double paxAll, double turnAll, double paxIndi, double turnIndi) {
        Map<String, double[]> monthMap = result.computeIfAbsent(m, k -> new HashMap<>());
        add(monthMap.computeIfAbsent(city, k -> new double[4]), paxAll, turnAll, paxIndi, turnIndi);
        add(monthMap.computeIfAbsent(PROVINCE, k -> new double[4]), paxAll, turnAll, paxIndi, turnIndi);
    }
    private void add(double[] arr, double paxAll, double turnAll, double paxIndi, double turnIndi) {
        arr[0] += paxAll;
        arr[1] += turnAll;
        arr[2] += paxIndi;
        arr[3] += turnIndi;
    }
    private int monthOf(String reportPeriod) {
        if (reportPeriod == null) return 0;
        String[] parts = reportPeriod.split("-");
        if (parts.length < 2) return 0;
        try {
            return Integer.parseInt(parts[1]);
        } catch (NumberFormatException e) {
            return 0;
        }
    }
    private double nz(Double v) {
        return v == null ? 0.0 : v;
    }
}