package com.trafficaudit.reportexport.calc; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.trafficaudit.common.util.RegionUtil; import com.trafficaudit.dataimport.entity.FreightTurnoverImport; import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly; import com.trafficaudit.dataimport.entity.ScaleSplitTransport; import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper; import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper; import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper; 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)。 * * 口径(决策基线 docs/确认回执汇总…_2026-09-07.md B1;2026-07 与已修正母版两张成品页 * 规上/规下 当月+累计 逐格回归 0 差异): * 1. 规上货运量 = h2032_enterprise_monthly.freight_total(吨)按 17 市州汇总 ÷10000(万吨)。 * 2. 合计货运量 = freight_turnover_import.freight_mMM(当月)/ freight_m01..mMM(累计)。 * 3. 规下货运量 = 合计 − 规上(拆分表不存货运量列,不能直接取规下)。 * 4. 规上/合计周转量 = scale_split_transport MONTH|CUMULATIVE 的 above_scale_turnover / total_turnover * (合计与 freight_turnover_import.turnover_mMM 同源,已核对一致);规下周转 = 合计 − 规上。 * 5. 同比(去年同口径)不在本服务计算:按 Q1 以母版缓存 2025 参照组为基准,由写回层填表时计算。 * * 输出 key = RegionUtil 规范市州名(武汉市…神农架林区)+ PROVINCE(湖北省,全省行 = 17 市州之和, * 与母版“全省行=Σ城市行”公式一致)。 */ @Service public class FreightCalc { public static final String PROVINCE = "湖北省"; /** 单个期间(当月或累计)的货运量/周转量三口径指标;无数据为 null */ public static class FreightMetrics { private final Double aboveFreight; // 规上货运量(万吨) private final Double belowFreight; // 规下货运量(万吨) private final Double totalFreight; // 规上+规下货运量(万吨) private final Double aboveTurnover; // 规上货物周转量(万吨公里) private final Double belowTurnover; // 规下货物周转量(万吨公里) private final Double totalTurnover; // 规上+规下货物周转量(万吨公里) public FreightMetrics(Double aboveFreight, Double belowFreight, Double totalFreight, Double aboveTurnover, Double belowTurnover, Double totalTurnover) { this.aboveFreight = aboveFreight; this.belowFreight = belowFreight; this.totalFreight = totalFreight; this.aboveTurnover = aboveTurnover; this.belowTurnover = belowTurnover; this.totalTurnover = totalTurnover; } public Double getAboveFreight() { return aboveFreight; } public Double getBelowFreight() { return belowFreight; } public Double getTotalFreight() { return totalFreight; } public Double getAboveTurnover() { return aboveTurnover; } public Double getBelowTurnover() { return belowTurnover; } public Double getTotalTurnover() { return totalTurnover; } } /** 全量货运矩阵:month=当月,cumulative=截至 period 月累计 */ public static class FreightMatrix { private final String period; private final Map month; private final Map cumulative; public FreightMatrix(String period, Map month, Map cumulative) { this.period = period; this.month = month; this.cumulative = cumulative; } public String getPeriod() { return period; } public Map getMonth() { return month; } public Map getCumulative() { return cumulative; } } @Resource private H2032EnterpriseMonthlyMapper h2032Mapper; @Resource private FreightTurnoverImportMapper freightTurnoverMapper; @Resource private ScaleSplitTransportMapper scaleSplitMapper; /** 计算 period(如 2026-07)的当月与累计货运矩阵 */ public FreightMatrix calc(String period) { int year = Integer.parseInt(period.substring(0, 4)); int month = Integer.parseInt(period.substring(5, 7)); // H2032 规上货运量(吨):市州 -> 月桶 Map> h2032TonByCityMonth = new HashMap<>(); List h2032List = h2032Mapper.selectList( new LambdaQueryWrapper().likeRight(H2032EnterpriseMonthly::getReportPeriod, year)); for (H2032EnterpriseMonthly rec : h2032List) { String rp = rec.getReportPeriod(); if (rp == null || rp.compareTo(period) > 0) continue; String city = RegionUtil.cityByCode(rec.getRegionCode()); if (city == null) continue; double freight = rec.getFreightTotal() == null ? 0.0 : rec.getFreightTotal(); h2032TonByCityMonth.computeIfAbsent(city, k -> new HashMap<>()) .merge(Integer.parseInt(rp.substring(5, 7)), freight, Double::sum); } // freight_turnover_import:合计货运量/周转量(仅存城市行;湖北省行由求和得出) Map ftMap = new HashMap<>(); for (FreightTurnoverImport e : freightTurnoverMapper.selectList( new LambdaQueryWrapper() .eq(FreightTurnoverImport::getReportPeriod, period))) { String city = RegionUtil.normalizeCityName(e.getRegionName()); if (city != null && !PROVINCE.equals(city)) ftMap.put(city, e); } // scale_split_transport:规上/合计周转量 Map splitMonth = new HashMap<>(); Map splitCumulative = new HashMap<>(); for (ScaleSplitTransport rec : scaleSplitMapper.selectList( new LambdaQueryWrapper() .eq(ScaleSplitTransport::getReportPeriod, period))) { String city = RegionUtil.normalizeCityName(rec.getRegionName()); if (city == null || PROVINCE.equals(city)) continue; if ("MONTH".equals(rec.getPeriodType())) splitMonth.put(city, rec); else if ("CUMULATIVE".equals(rec.getPeriodType())) splitCumulative.put(city, rec); } Map monthMap = new LinkedHashMap<>(); Map cumulativeMap = new LinkedHashMap<>(); for (String city : RegionUtil.cityList()) { monthMap.put(city, buildMetrics(city, month, h2032TonByCityMonth, ftMap, splitMonth, false)); cumulativeMap.put(city, buildMetrics(city, month, h2032TonByCityMonth, ftMap, splitCumulative, true)); } monthMap.put(PROVINCE, sumOf(monthMap)); cumulativeMap.put(PROVINCE, sumOf(cumulativeMap)); return new FreightMatrix(period, monthMap, cumulativeMap); } /** 单城市指标。cumulative=true 时 H2032 取 1..month 全桶、合计取 m01..mMM 之和;false 时只取当月。 */ private FreightMetrics buildMetrics(String city, int month, Map> h2032TonByCityMonth, Map ftMap, Map splitMap, boolean cumulative) { Double aboveFreight = null; Map h2032Buckets = h2032TonByCityMonth.get(city); if (h2032Buckets != null) { double ton; if (cumulative) { ton = 0.0; for (int m = 1; m <= month; m++) ton += h2032Buckets.getOrDefault(m, 0.0); } else { ton = h2032Buckets.getOrDefault(month, 0.0); } aboveFreight = ton / 10000.0; } FreightTurnoverImport ft = ftMap.get(city); Double totalFreight = null; Double totalTurnoverFromFt = null; if (ft != null) { if (cumulative) { double f = 0.0, t = 0.0; for (int m = 1; m <= month; m++) { f += monthField(ft, m, true); t += monthField(ft, m, false); } totalFreight = f; totalTurnoverFromFt = t; } else { totalFreight = monthField(ft, month, true); totalTurnoverFromFt = monthField(ft, month, false); } } ScaleSplitTransport split = splitMap.get(city); Double aboveTurnover = split == null ? null : split.getAboveScaleTurnover(); Double totalTurnover = split != null && split.getTotalTurnover() != null ? split.getTotalTurnover() : totalTurnoverFromFt; Double belowFreight = totalFreight == null || aboveFreight == null ? null : totalFreight - aboveFreight; Double belowTurnover = totalTurnover == null ? null : totalTurnover - (aboveTurnover == null ? 0.0 : aboveTurnover); return new FreightMetrics(aboveFreight, belowFreight, totalFreight, aboveTurnover, belowTurnover, totalTurnover); } /** 湖北省行 = 17 市州之和(与母版全省行 Σ 公式一致;全市州无数据时输出 0) */ private FreightMetrics sumOf(Map byCity) { double af = 0.0, bf = 0.0, tf = 0.0, at = 0.0, bt = 0.0, tt = 0.0; for (String city : RegionUtil.cityList()) { FreightMetrics m = byCity.get(city); if (m == null) continue; af += nz(m.getAboveFreight()); bf += nz(m.getBelowFreight()); tf += nz(m.getTotalFreight()); at += nz(m.getAboveTurnover()); bt += nz(m.getBelowTurnover()); tt += nz(m.getTotalTurnover()); } return new FreightMetrics(af, bf, tf, at, bt, tt); } private double monthField(FreightTurnoverImport e, int m, boolean freight) { switch (m) { case 1: return freight ? nz(e.getFreightM01()) : nz(e.getTurnoverM01()); case 2: return freight ? nz(e.getFreightM02()) : nz(e.getTurnoverM02()); case 3: return freight ? nz(e.getFreightM03()) : nz(e.getTurnoverM03()); case 4: return freight ? nz(e.getFreightM04()) : nz(e.getTurnoverM04()); case 5: return freight ? nz(e.getFreightM05()) : nz(e.getTurnoverM05()); case 6: return freight ? nz(e.getFreightM06()) : nz(e.getTurnoverM06()); case 7: return freight ? nz(e.getFreightM07()) : nz(e.getTurnoverM07()); case 8: return freight ? nz(e.getFreightM08()) : nz(e.getTurnoverM08()); case 9: return freight ? nz(e.getFreightM09()) : nz(e.getTurnoverM09()); case 10: return freight ? nz(e.getFreightM10()) : nz(e.getTurnoverM10()); case 11: return freight ? nz(e.getFreightM11()) : nz(e.getTurnoverM11()); case 12: return freight ? nz(e.getFreightM12()) : nz(e.getTurnoverM12()); default: return 0.0; } } private double nz(Double v) { return v == null ? 0.0 : v; } }