| New file |
| | |
| | | package com.trafficaudit.reportexport.calc; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.common.util.RegionUtil; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiMonthly; |
| | | import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper; |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.CellType; |
| | | import org.apache.poi.ss.usermodel.DataFormatter; |
| | | import org.apache.poi.ss.usermodel.Row; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.apache.poi.ss.usermodel.WorkbookFactory; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.InputStream; |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * ç½çº¦è½¦æåå£å¾è®¡ç®æå¡ï¼æ±æ»å¤§è¡¨æ°æ®é©±å¨æ¹é M1-ç½çº¦è½¦ï¼2026-09-08ï¼ã |
| | | * ç®æ³ä¸ exportWycSplit / ãç½çº¦è½¦æå.xlsxãæé¡µä¸è´ï¼B5ï¼è®¢å+å
¨ç pin+巡游åºç§æåï¼ï¼ |
| | | * 客è¿é=订åå æ¯Ãå
¨çæ»éï¼åå¸å
=巡游åºç§åå¸å
å æ¯ä»£çï¼æ¦æ±ä¿æèªèº«ãå
¶ä½æå©ä½å æ¯åæå°å
¨çåå¸å
pinï¼ï¼ |
| | | * å¨è½¬é=客è¿éÃ巡游åºç§å¹³åè¿è·å æ¯åæå
¨çå¨è½¬é pinï¼åå¸å
å¨è½¬éåæ³ï¼ç¨åå¸å
å¹³åè¿è·ï¼ã |
| | | * 2026-07 ä¸ _å¤ä»½â¦_æ¸
çå_20260904.xlsx ç½çº¦è½¦å°è´¦é¡µ O ååå½ 0 å·®å¼ï¼17 å¸å·Ã客è¿é/å¨è½¬é/åå¸å
客è¿é/åå¸å
å¨è½¬éï¼ã |
| | | */ |
| | | @Service |
| | | public class WycSplitCalc { |
| | | |
| | | /** åå¸å·æåç»æï¼å«åé
å乡å£å¾ï¼ä¾ä¸åå°è´¦è¡ä½¿ç¨ï¼ */ |
| | | public static class WycMetrics { |
| | | private final double totalPax; // 客è¿éæ»éï¼ä¸äººï¼ |
| | | private final double cityPax; // åå¸å
客è¿éï¼ä¸äººï¼ |
| | | private final double suburbanPax; // åé
å乡客è¿é = totalPax - cityPax |
| | | private final double totalTurnover; // æ
客å¨è½¬éæ»éï¼ä¸äººå
¬éï¼ |
| | | private final double cityTurnover; // åå¸å
æ
客å¨è½¬éï¼ä¸äººå
¬éï¼ |
| | | private final double suburbanTurnover; |
| | | |
| | | public WycMetrics(double totalPax, double cityPax, double totalTurnover, double cityTurnover) { |
| | | this.totalPax = totalPax; |
| | | this.cityPax = cityPax; |
| | | this.suburbanPax = totalPax - cityPax; |
| | | this.totalTurnover = totalTurnover; |
| | | this.cityTurnover = cityTurnover; |
| | | this.suburbanTurnover = totalTurnover - cityTurnover; |
| | | } |
| | | |
| | | public double getTotalPax() { return totalPax; } |
| | | public double getCityPax() { return cityPax; } |
| | | public double getSuburbanPax() { return suburbanPax; } |
| | | public double getTotalTurnover() { return totalTurnover; } |
| | | public double getCityTurnover() { return cityTurnover; } |
| | | public double getSuburbanTurnover() { return suburbanTurnover; } |
| | | } |
| | | |
| | | public static class WycResult { |
| | | private final String period; |
| | | private final Map<String, WycMetrics> byCity; // key=RegionUtil è§èå¸å·å |
| | | private final WycMetrics province; // å
¨ç = pin è¾å
¥ |
| | | |
| | | public WycResult(String period, Map<String, WycMetrics> byCity, WycMetrics province) { |
| | | this.period = period; |
| | | this.byCity = byCity; |
| | | this.province = province; |
| | | } |
| | | |
| | | public String getPeriod() { return period; } |
| | | public Map<String, WycMetrics> getByCity() { return byCity; } |
| | | public WycMetrics getProvince() { return province; } |
| | | } |
| | | |
| | | @Resource |
| | | private CityTaxiMonthlyMapper cityTaxiMapper; |
| | | @Value("${city-passenger.template-dir:docs/åå¸å®¢è¿}") |
| | | private String templateDir; |
| | | |
| | | private static final String[] INPUT_CANDIDATES = {"ç½çº¦è½¦è®¢ååå
¨çæ»é.xlsx", "模æ¿_ç½çº¦è½¦è®¢ååå
¨çæ»é.xlsx"}; |
| | | |
| | | /** è®¡ç® periodï¼å¦ 2026-07ï¼ç½çº¦è½¦æåç»æ */ |
| | | public WycResult calc(String period) throws Exception { |
| | | Map<String, Object> input = loadMonthlyInput(period); |
| | | if (input == null) { |
| | | throw new RuntimeException("ãç½çº¦è½¦è®¢ååå
¨çæ»é.xlsxãç¼ºå°æ¥è¡¨æ " + period + " çæ°æ®è¡ï¼è¯·å
ç»´æ¤å½æè®¢åä¸å
¨ç pin"); |
| | | } |
| | | double pinK = (Double) input.get("pinK"); |
| | | double pinCK = (Double) input.get("pinCK"); |
| | | double pinZ = (Double) input.get("pinZ"); |
| | | double pinCZ = (Double) input.get("pinCZ"); |
| | | double orderSum = (Double) input.get("orderSum"); |
| | | double[] orderArr = (double[]) input.get("orders"); |
| | | if (pinK <= 0 || pinCK <= 0 || pinZ <= 0 || pinCZ <= 0 || orderSum <= 0) { |
| | | throw new RuntimeException("ç½çº¦è½¦è¾å
¥ " + period + " è¡ä¸å®æ´ï¼å
¨ç pin / 订å缺失ï¼è¯·æ ¸å¯¹ãç½çº¦è½¦è®¢ååå
¨çæ»é.xlsxã"); |
| | | } |
| | | List<String> cities = RegionUtil.cityList(); |
| | | int n = cities.size(); |
| | | if (orderArr == null || orderArr.length < n) { |
| | | throw new RuntimeException("ç½çº¦è½¦è®¢ååæ°ä¸è¶³ï¼åºä¸º 17 å¸å·è®¢ååï¼"); |
| | | } |
| | | Map<String, double[]> taxi = taxiMonthlyOf(period); |
| | | List<String> missingTaxi = new ArrayList<>(); |
| | | double[] Dtax = new double[n]; |
| | | double[] Etax = new double[n]; |
| | | double[] avgD = new double[n]; |
| | | double[] avgDc = new double[n]; |
| | | for (int i = 0; i < n; i++) { |
| | | double[] a = taxi.get(cities.get(i)); |
| | | if (a == null || a[0] <= 0) { |
| | | missingTaxi.add(cities.get(i)); |
| | | continue; |
| | | } |
| | | Dtax[i] = a[0]; |
| | | Etax[i] = a[2]; |
| | | avgD[i] = a[4]; |
| | | avgDc[i] = a[5]; |
| | | } |
| | | if (!missingTaxi.isEmpty()) { |
| | | throw new RuntimeException("巡游åºç§ææ¥ç¼ºå°ä»¥ä¸å¸å·æ°æ®ï¼" + String.join("ã", missingTaxi)); |
| | | } |
| | | double[] C = new double[n]; |
| | | double[] H = new double[n]; |
| | | double[] Gl = new double[n]; |
| | | double[] Hl = new double[n]; |
| | | double sumG = 0.0; |
| | | double sumDl = 0.0; |
| | | double[] cityRatio = new double[n]; |
| | | for (int i = 0; i < n; i++) { |
| | | C[i] = orderArr[i] / orderSum * pinK; |
| | | cityRatio[i] = Etax[i] / Dtax[i]; |
| | | sumG += C[i] * cityRatio[i]; |
| | | sumDl += C[i] * avgD[i]; |
| | | } |
| | | for (int i = 0; i < n; i++) { |
| | | if (i == 0) { |
| | | H[i] = C[i] * cityRatio[i]; |
| | | } else { |
| | | double rest = sumG - C[0] * cityRatio[0]; |
| | | H[i] = rest == 0.0 ? 0.0 : (C[i] * cityRatio[i]) / rest * (pinCK - C[0] * cityRatio[0]); |
| | | } |
| | | } |
| | | double[] el2 = new double[n]; |
| | | double sumEl2 = 0.0; |
| | | for (int i = 0; i < n; i++) { |
| | | el2[i] = H[i] * avgDc[i]; |
| | | sumEl2 += el2[i]; |
| | | Gl[i] = sumDl == 0.0 ? 0.0 : C[i] * avgD[i] / sumDl * pinZ; |
| | | } |
| | | for (int i = 0; i < n; i++) { |
| | | if (i == 0) { |
| | | Hl[i] = Gl[i]; |
| | | } else { |
| | | double rest = sumEl2 - el2[0]; |
| | | Hl[i] = rest == 0.0 ? 0.0 : el2[i] / rest * (pinCZ - Gl[0]); |
| | | } |
| | | } |
| | | Map<String, WycMetrics> byCity = new LinkedHashMap<>(); |
| | | for (int i = 0; i < n; i++) { |
| | | byCity.put(cities.get(i), new WycMetrics(C[i], H[i], Gl[i], Hl[i])); |
| | | } |
| | | WycMetrics province = new WycMetrics(pinK, pinCK, pinZ, pinCZ); |
| | | return new WycResult(period, byCity, province); |
| | | } |
| | | |
| | | /** 巡游åºç§ææ¥ï¼DBï¼ææå è½½ï¼å¸å· -> [客è¿é,å¨è½¬é,åå¸å
客è¿é,åå¸å
å¨è½¬é,å¹³åè¿è·,åå¸å
å¹³åè¿è·] */ |
| | | private Map<String, double[]> taxiMonthlyOf(String period) { |
| | | Map<String, double[]> map = new LinkedHashMap<>(); |
| | | List<CityTaxiMonthly> rows = cityTaxiMapper.selectList( |
| | | new LambdaQueryWrapper<CityTaxiMonthly>().eq(CityTaxiMonthly::getReportPeriod, period)); |
| | | for (CityTaxiMonthly r : rows) { |
| | | String city = RegionUtil.normalizeCityName(r.getCity()); |
| | | if (city == null || city.isEmpty()) city = "æªç¥"; |
| | | double[] a = map.computeIfAbsent(city, k -> new double[6]); |
| | | a[0] += nz(r.getPassengerVolume()); |
| | | a[1] += nz(r.getTurnover()); |
| | | a[2] += nz(r.getPassengerCity()); |
| | | a[3] += nz(r.getTurnoverCity()); |
| | | if (r.getAvgDistance() != null && r.getAvgDistance() > 0) a[4] = r.getAvgDistance(); |
| | | if (r.getAvgDistanceCity() != null && r.getAvgDistanceCity() > 0) a[5] = r.getAvgDistanceCity(); |
| | | } |
| | | for (double[] a : map.values()) { |
| | | if (a[4] <= 0 && a[0] > 0) a[4] = round(a[1] / a[0], 2); |
| | | if (a[5] <= 0 && a[2] > 0) a[5] = round(a[3] / a[2], 2); |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | /** 读å docs/åå¸å®¢è¿/ãç½çº¦è½¦è®¢ååå
¨çæ»é.xlsxãï¼æ period(yyyy-MM) è¡å pin(B-E) ä¸ 17 å¸å·è®¢å(G-W) */ |
| | | private Map<String, Object> loadMonthlyInput(String period) throws Exception { |
| | | File f = findInput(); |
| | | try (InputStream in = new FileInputStream(f); |
| | | Workbook wb = WorkbookFactory.create(in)) { |
| | | org.apache.poi.ss.usermodel.Sheet sh = wb.getSheetAt(0); |
| | | DataFormatter df = new DataFormatter(); |
| | | List<String> cities = RegionUtil.cityList(); |
| | | int n = cities.size(); |
| | | for (int i = 1; i <= sh.getLastRowNum(); i++) { |
| | | Row row = sh.getRow(i); |
| | | if (row == null) continue; |
| | | Cell c0 = row.getCell(0); |
| | | if (c0 == null) continue; |
| | | String m = df.formatCellValue(c0).trim(); |
| | | if (m.isEmpty()) continue; |
| | | if (m.length() > 7) m = m.substring(0, 7); |
| | | if (!period.equals(m)) continue; |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("pinK", cellNum(row, 1, df, 0.0)); |
| | | map.put("pinCK", cellNum(row, 2, df, 0.0)); |
| | | map.put("pinZ", cellNum(row, 3, df, 0.0)); |
| | | map.put("pinCZ", cellNum(row, 4, df, 0.0)); |
| | | double[] orders = new double[n]; |
| | | double orderSum = 0.0; |
| | | for (int k = 0; k < n; k++) { |
| | | orders[k] = cellNum(row, 6 + k, df, 0.0); |
| | | orderSum += orders[k]; |
| | | } |
| | | if (orderSum <= 0) orderSum = cellNum(row, 5, df, 0.0); |
| | | map.put("orders", orders); |
| | | map.put("orderSum", orderSum); |
| | | return map; |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private File findInput() throws Exception { |
| | | String rel = templateDir; |
| | | while (rel.startsWith("./")) rel = rel.substring(2); |
| | | String[] roots = { |
| | | templateDir, |
| | | System.getProperty("user.dir") + "/" + rel, |
| | | System.getProperty("user.dir") + "/../" + rel |
| | | }; |
| | | for (String root : roots) { |
| | | if (root == null || root.trim().isEmpty()) continue; |
| | | for (String name : INPUT_CANDIDATES) { |
| | | File f = new File(root, name); |
| | | if (f.exists() && f.isFile()) return f; |
| | | } |
| | | } |
| | | throw new RuntimeException("æªæ¾å°ãç½çº¦è½¦è®¢ååå
¨çæ»é.xlsxãï¼å« 模æ¿_ åç¼å½åï¼ï¼è¯·æ¾å° docs/åå¸å®¢è¿ ç®å½"); |
| | | } |
| | | |
| | | private double cellNum(Row row, int idx, DataFormatter df, double def) { |
| | | Cell c = row.getCell(idx); |
| | | if (c == null) return def; |
| | | if (c.getCellType() == CellType.NUMERIC) return c.getNumericCellValue(); |
| | | String s = df.formatCellValue(c).replace(",", "").trim(); |
| | | if (s.isEmpty()) return def; |
| | | try { |
| | | return Double.parseDouble(s); |
| | | } catch (NumberFormatException e) { |
| | | return def; |
| | | } |
| | | } |
| | | |
| | | private double nz(Double v) { return v == null ? 0.0 : v; } |
| | | |
| | | private double round(double v, int scale) { |
| | | double factor = Math.pow(10, scale); |
| | | return Math.round(v * factor) / factor; |
| | | } |
| | | } |