zhizhijie
2026-09-18 8719902780eadfc55d4d159d5a111b433f065137
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
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<String, FreightMetrics> month;
        private final Map<String, FreightMetrics> cumulative;
 
        public FreightMatrix(String period, Map<String, FreightMetrics> month, Map<String, FreightMetrics> cumulative) {
            this.period = period;
            this.month = month;
            this.cumulative = cumulative;
        }
 
        public String getPeriod() { return period; }
        public Map<String, FreightMetrics> getMonth() { return month; }
        public Map<String, FreightMetrics> 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<String, Map<Integer, Double>> h2032TonByCityMonth = new HashMap<>();
        List<H2032EnterpriseMonthly> h2032List = h2032Mapper.selectList(
            new LambdaQueryWrapper<H2032EnterpriseMonthly>().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<String, FreightTurnoverImport> ftMap = new HashMap<>();
        for (FreightTurnoverImport e : freightTurnoverMapper.selectList(
            new LambdaQueryWrapper<FreightTurnoverImport>()
                .eq(FreightTurnoverImport::getReportPeriod, period))) {
            String city = RegionUtil.normalizeCityName(e.getRegionName());
            if (city != null && !PROVINCE.equals(city)) ftMap.put(city, e);
        }
 
        // scale_split_transport:规上/合计周转量
        Map<String, ScaleSplitTransport> splitMonth = new HashMap<>();
        Map<String, ScaleSplitTransport> splitCumulative = new HashMap<>();
        for (ScaleSplitTransport rec : scaleSplitMapper.selectList(
            new LambdaQueryWrapper<ScaleSplitTransport>()
                .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<String, FreightMetrics> monthMap = new LinkedHashMap<>();
        Map<String, FreightMetrics> 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<String, Map<Integer, Double>> h2032TonByCityMonth,
                                        Map<String, FreightTurnoverImport> ftMap,
                                        Map<String, ScaleSplitTransport> splitMap,
                                        boolean cumulative) {
        Double aboveFreight = null;
        Map<Integer, Double> 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<String, FreightMetrics> 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; }
}