| | |
| | | import com.trafficaudit.holiday.entity.HolidayCalendar; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerFlow; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleFlow; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleAdjustment; |
| | | import com.trafficaudit.holiday.mapper.HolidayPassengerFlowMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleAdjustmentMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleFlowMapper; |
| | | import lombok.Data; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | private HolidayPassengerFlowMapper passengerFlowMapper; |
| | | @Resource |
| | | private HolidayVehicleFlowMapper vehicleFlowMapper; |
| | | @Resource |
| | | private HolidayVehicleAdjustmentMapper vehicleAdjustmentMapper; |
| | | @Resource |
| | | private ImportBatchMapper importBatchMapper; |
| | | |
| | |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate = loadVehicleByDate(year); |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate = loadComparisonBaseByDate(year); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | List<Map<String, Object>> rows = new ArrayList<>(); |
| | | BigDecimal previousNationalProvincial = null; |
| | | Map<Integer, HolidayCalendar> calendarByDay = new LinkedHashMap<>(); |
| | | for (HolidayCalendar day : calendar) { |
| | | Map<String, BigDecimal> passenger = passengerByDate.getOrDefault(day.getHolidayDate(), Collections.emptyMap()); |
| | | Map<String, BigDecimal> vehicle = vehicleByDate.getOrDefault(day.getHolidayDate(), Collections.emptyMap()); |
| | | BigDecimal expressway = vehicle.get(HolidayConstants.ROAD_EXPRESSWAY); |
| | | BigDecimal nationalProvincial = vehicle.get(HolidayConstants.ROAD_NATIONAL_PROVINCIAL); |
| | | BigDecimal comparisonBase = comparisonBaseByDate.get(day.getHolidayDate()); |
| | | BigDecimal nationalProvincialRingRatio = nationalProvincialRingRatio( |
| | | nationalProvincial, previousNationalProvincial); |
| | | BigDecimal nonBusinessPersonFlow = null; |
| | | if (coefficient != null && (expressway != null || nationalProvincial != null)) { |
| | | nonBusinessPersonFlow = HolidayNumberUtils.multiply( |
| | | HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient); |
| | | } |
| | | if (day.getDayIndex() != null) calendarByDay.put(day.getDayIndex(), day); |
| | | } |
| | | List<Map<String, Object>> rows = new ArrayList<>(); |
| | | for (HolidayCalendar day : calendar) { |
| | | DailyMetrics current = buildDailyMetrics(year, holidayType, day.getDayIndex(), day.getHolidayDate(), |
| | | passengerByDate, vehicleByDate, comparisonBaseByDate, coefficient); |
| | | DailyMetrics ringRatioBase = ringRatioBaseFromMaps(year, holidayType, day.getDayIndex(), calendarByDay, |
| | | passengerByDate, vehicleByDate, comparisonBaseByDate, coefficient); |
| | | BigDecimal nationalProvincialRingRatio = ratio(current.getNationalProvincial(), |
| | | ringRatioBase == null ? null : ringRatioBase.getNationalProvincial()); |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("dayIndex", day.getDayIndex()); |
| | | row.put("date", day.getHolidayDate()); |
| | | row.put("rail", passenger.get(HolidayConstants.MODE_RAIL)); |
| | | row.put("civilAviation", passenger.get(HolidayConstants.MODE_CIVIL_AVIATION)); |
| | | row.put("road", passenger.get(HolidayConstants.MODE_ROAD)); |
| | | row.put("water", passenger.get(HolidayConstants.MODE_WATER)); |
| | | row.put("expressway", expressway); |
| | | row.put("nationalProvincial", nationalProvincial); |
| | | row.put("comparisonBase", comparisonBase); |
| | | row.put("rail", current.getRail()); |
| | | row.put("civilAviation", current.getCivilAviation()); |
| | | row.put("road", current.getRoad()); |
| | | row.put("water", current.getWater()); |
| | | row.put("expressway", current.getExpressway()); |
| | | row.put("nationalProvincial", current.getNationalProvincial()); |
| | | row.put("comparisonBase", current.getComparisonBase()); |
| | | row.put("nationalProvincialRingRatio", nationalProvincialRingRatio); |
| | | row.put("coefficient", coefficient); |
| | | row.put("nonBusinessPersonFlow", nonBusinessPersonFlow); |
| | | row.put("totalPersonFlow", totalPersonFlow(passenger, nonBusinessPersonFlow)); |
| | | row.put("coefficient", current.getCoefficient()); |
| | | row.put("nonBusinessPersonFlow", current.getNonBusinessPersonFlow()); |
| | | row.put("totalPersonFlow", current.getTotalPersonFlow()); |
| | | rows.add(row); |
| | | previousNationalProvincial = nationalProvincial; |
| | | } |
| | | return rows; |
| | | } |
| | |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate = loadPassengerByDate(year); |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate = loadVehicleByDate(year); |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate = loadComparisonBaseByDate(year); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | return buildDailyMetrics(year, holidayType, dayIndex, date, passengerByDate, vehicleByDate, |
| | | comparisonBaseByDate, coefficient); |
| | | } |
| | | |
| | | public DailyMetrics ringRatioBaseMetrics(int year, String holidayType, int dayIndex) { |
| | | if (dayIndex <= 1) return null; |
| | | List<HolidayCalendar> calendar = configService.findHolidayDays(year, holidayType); |
| | | Map<Integer, HolidayCalendar> calendarByDay = new LinkedHashMap<>(); |
| | | for (HolidayCalendar day : calendar) { |
| | | if (day.getDayIndex() != null) calendarByDay.put(day.getDayIndex(), day); |
| | | } |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate = loadPassengerByDate(year); |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate = loadVehicleByDate(year); |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate = loadComparisonBaseByDate(year); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | return ringRatioBaseFromMaps(year, holidayType, dayIndex, calendarByDay, passengerByDate, |
| | | vehicleByDate, comparisonBaseByDate, coefficient); |
| | | } |
| | | |
| | | private DailyMetrics buildDailyMetrics(int year, String holidayType, int dayIndex, LocalDate date, |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate, |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate, |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate, |
| | | BigDecimal coefficient) { |
| | | Map<String, BigDecimal> passenger = passengerByDate.getOrDefault(date, Collections.emptyMap()); |
| | | Map<String, BigDecimal> vehicle = vehicleByDate.getOrDefault(date, Collections.emptyMap()); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | BigDecimal expressway = vehicle.get(HolidayConstants.ROAD_EXPRESSWAY); |
| | | BigDecimal nationalProvincial = vehicle.get(HolidayConstants.ROAD_NATIONAL_PROVINCIAL); |
| | | BigDecimal comparisonBase = comparisonBaseByDate.get(date); |
| | | BigDecimal nonBusiness = null; |
| | | if (coefficient != null && (expressway != null || nationalProvincial != null)) { |
| | | nonBusiness = HolidayNumberUtils.multiply( |
| | | HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient); |
| | | } |
| | | BigDecimal nonBusiness = nonBusinessPersonFlow(expressway, nationalProvincial, coefficient); |
| | | |
| | | DailyMetrics metrics = new DailyMetrics(); |
| | | metrics.setYear(year); |
| | |
| | | metrics.setComparisonBase(comparisonBase); |
| | | metrics.setCoefficient(coefficient); |
| | | metrics.setNonBusinessPersonFlow(nonBusiness); |
| | | metrics.setRoadPersonFlow(sumNullable(metrics.getRoad(), nonBusiness)); |
| | | metrics.setTotalPersonFlow(totalPersonFlow(passenger, nonBusiness)); |
| | | recalculateDerived(metrics); |
| | | return metrics; |
| | | } |
| | | |
| | | private DailyMetrics ringRatioBaseFromMaps(int year, String holidayType, int dayIndex, |
| | | Map<Integer, HolidayCalendar> calendarByDay, |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate, |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate, |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate, |
| | | BigDecimal coefficient) { |
| | | if (dayIndex <= 1) return null; |
| | | HolidayCalendar previous = calendarByDay.get(dayIndex - 1); |
| | | if (previous == null || previous.getHolidayDate() == null) return null; |
| | | DailyMetrics base = buildDailyMetrics(year, holidayType, previous.getDayIndex(), previous.getHolidayDate(), |
| | | passengerByDate, vehicleByDate, comparisonBaseByDate, coefficient); |
| | | BigDecimal comparisonBase = comparisonBaseByDate.get(previous.getHolidayDate()); |
| | | if (comparisonBase != null) { |
| | | base.setNationalProvincial(comparisonBase); |
| | | recalculateDerived(base); |
| | | } |
| | | return base; |
| | | } |
| | | |
| | | private void recalculateDerived(DailyMetrics metrics) { |
| | | BigDecimal nonBusiness = nonBusinessPersonFlow(metrics.getExpressway(), metrics.getNationalProvincial(), |
| | | metrics.getCoefficient()); |
| | | metrics.setNonBusinessPersonFlow(nonBusiness); |
| | | metrics.setRoadPersonFlow(sumNullable(metrics.getRoad(), nonBusiness)); |
| | | metrics.setTotalPersonFlow(totalPersonFlow(nonBusiness, metrics.getRoad(), metrics.getRail(), |
| | | metrics.getCivilAviation(), metrics.getWater())); |
| | | } |
| | | |
| | | private BigDecimal nonBusinessPersonFlow(BigDecimal expressway, BigDecimal nationalProvincial, |
| | | BigDecimal coefficient) { |
| | | if (coefficient == null || (expressway == null && nationalProvincial == null)) return null; |
| | | return HolidayNumberUtils.multiply( |
| | | HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient); |
| | | } |
| | | |
| | | public DailyMetrics cumulativeMetrics(int year, String holidayType, int dayIndex) { |
| | |
| | | result.computeIfAbsent(row.getFlowDate(), key -> new LinkedHashMap<>()) |
| | | .put(row.getRoadCategory(), row.getVehicleCount()); |
| | | } |
| | | Map<LocalDate, BigDecimal> adjustments = loadNationalProvincialAdjustmentsByDate(year); |
| | | for (Map.Entry<LocalDate, BigDecimal> entry : adjustments.entrySet()) { |
| | | result.computeIfAbsent(entry.getKey(), key -> new LinkedHashMap<>()) |
| | | .put(HolidayConstants.ROAD_NATIONAL_PROVINCIAL, entry.getValue()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Map<LocalDate, BigDecimal> loadNationalProvincialAdjustmentsByDate(int year) { |
| | | Map<LocalDate, BigDecimal> result = new LinkedHashMap<>(); |
| | | List<HolidayVehicleAdjustment> rows = vehicleAdjustmentMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleAdjustment>().eq(HolidayVehicleAdjustment::getYear, year)); |
| | | for (HolidayVehicleAdjustment row : rows) { |
| | | if (row.getFlowDate() != null && row.getAdjustedValue() != null) { |
| | | result.put(row.getFlowDate(), row.getAdjustedValue()); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | |
| | | return result; |
| | | } |
| | | |
| | | private BigDecimal totalPersonFlow(Map<String, BigDecimal> passenger, BigDecimal nonBusinessPersonFlow) { |
| | | private BigDecimal totalPersonFlow(BigDecimal nonBusinessPersonFlow, BigDecimal road, BigDecimal rail, |
| | | BigDecimal civilAviation, BigDecimal water) { |
| | | BigDecimal total = nonBusinessPersonFlow; |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_ROAD)); |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_RAIL)); |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_CIVIL_AVIATION)); |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_WATER)); |
| | | total = sumNullable(total, road); |
| | | total = sumNullable(total, rail); |
| | | total = sumNullable(total, civilAviation); |
| | | total = sumNullable(total, water); |
| | | return total; |
| | | } |
| | | |
| | | // The national/provincial ring ratio always compares the current day with the previous day of |
| | | // the same year. The stored comparison base (Excel column C) is kept as an operator backup |
| | | // value only and never feeds this calculation. |
| | | private BigDecimal nationalProvincialRingRatio(BigDecimal current, BigDecimal previous) { |
| | | if (current == null) return null; |
| | | return previous == null ? null : ratio(current, previous); |
| | | } |
| | | |
| | | private BigDecimal ratio(BigDecimal current, BigDecimal base) { |
| | | BigDecimal quotient = HolidayNumberUtils.divide(current, base); |