package com.trafficaudit.holiday.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.trafficaudit.dataimport.entity.ImportBatch;
|
import com.trafficaudit.dataimport.mapper.ImportBatchMapper;
|
import com.trafficaudit.holiday.HolidayConstants;
|
import com.trafficaudit.holiday.HolidayNumberUtils;
|
import com.trafficaudit.holiday.entity.HolidayCalendar;
|
import com.trafficaudit.holiday.entity.HolidayPassengerFlow;
|
import com.trafficaudit.holiday.entity.HolidayVehicleFlow;
|
import com.trafficaudit.holiday.mapper.HolidayPassengerFlowMapper;
|
import com.trafficaudit.holiday.mapper.HolidayVehicleFlowMapper;
|
import lombok.Data;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.web.multipart.MultipartFile;
|
|
import javax.annotation.Resource;
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
@Service
|
public class HolidayStatService {
|
|
@Resource
|
private HolidayConfigService configService;
|
@Resource
|
private HolidayWorkbookService workbookService;
|
@Resource
|
private HolidayPassengerFlowMapper passengerFlowMapper;
|
@Resource
|
private HolidayVehicleFlowMapper vehicleFlowMapper;
|
@Resource
|
private ImportBatchMapper importBatchMapper;
|
|
public Map<String, Object> overview(int year, String holidayType) {
|
List<HolidayCalendar> calendar = configService.findHolidayDays(year, holidayType);
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("year", year);
|
result.put("holidayType", holidayType);
|
result.put("holidayTypeLabel", HolidayConstants.HOLIDAY_TYPE_LABELS.get(holidayType));
|
result.put("days", calendar);
|
result.put("rows", buildOverviewRows(year, holidayType, calendar));
|
result.put("templateFileName", workbookService.referenceFileName());
|
return result;
|
}
|
|
@Transactional
|
public Map<String, Object> importWorkbook(MultipartFile file, int year, String holidayType, boolean allowHistoricalOverwrite) {
|
HolidayWorkbookService.ImportSummary summary = workbookService.importWorkbook(file, year, holidayType, allowHistoricalOverwrite);
|
if (summary.isConfirmationRequired()) {
|
return importResult(summary);
|
}
|
ImportBatch batch = new ImportBatch();
|
batch.setFileName(file == null ? null : file.getOriginalFilename());
|
batch.setImportType("HOLIDAY_FLOW");
|
batch.setReportPeriod(year + "-" + holidayType);
|
batch.setTotalRows(summary.getSuccessRows() + summary.getFailRows());
|
batch.setSuccessRows(summary.getSuccessRows());
|
batch.setFailRows(summary.getFailRows());
|
batch.setErrorDetail(joinErrors(summary.getErrors()));
|
batch.setCreatedAt(LocalDateTime.now());
|
importBatchMapper.insert(batch);
|
|
return importResult(summary);
|
}
|
|
private Map<String, Object> importResult(HolidayWorkbookService.ImportSummary summary) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("passengerRows", summary.getPassengerRows());
|
result.put("vehicleRows", summary.getVehicleRows());
|
result.put("skippedRows", summary.getSkippedRows());
|
result.put("successRows", summary.getSuccessRows());
|
result.put("failRows", summary.getFailRows());
|
result.put("errors", summary.getErrors());
|
result.put("confirmationRequired", summary.isConfirmationRequired());
|
result.put("historicalChanges", summary.getHistoricalChanges());
|
return result;
|
}
|
|
public byte[] exportWorkbook(int year, String holidayType) {
|
return workbookService.exportWorkbook(year, holidayType);
|
}
|
|
public List<Map<String, Object>> buildOverviewRows(int year, String holidayType, List<HolidayCalendar> calendar) {
|
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);
|
List<Map<String, Object>> rows = new ArrayList<>();
|
BigDecimal previousNationalProvincial = null;
|
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(
|
year, nationalProvincial, previousNationalProvincial, comparisonBase);
|
BigDecimal nonBusinessPersonFlow = null;
|
if (coefficient != null && (expressway != null || nationalProvincial != null)) {
|
nonBusinessPersonFlow = HolidayNumberUtils.multiply(
|
HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient);
|
}
|
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("nationalProvincialRingRatio", nationalProvincialRingRatio);
|
row.put("coefficient", coefficient);
|
row.put("nonBusinessPersonFlow", nonBusinessPersonFlow);
|
row.put("totalPersonFlow", totalPersonFlow(passenger, nonBusinessPersonFlow));
|
rows.add(row);
|
previousNationalProvincial = nationalProvincial;
|
}
|
return rows;
|
}
|
|
public DailyMetrics dailyMetrics(int year, String holidayType, int dayIndex) {
|
List<HolidayCalendar> calendar = configService.findHolidayDays(year, holidayType);
|
HolidayCalendar target = null;
|
for (HolidayCalendar day : calendar) {
|
if (day.getDayIndex() != null && day.getDayIndex() == dayIndex) {
|
target = day;
|
break;
|
}
|
}
|
if (target == null) return null;
|
|
Map<LocalDate, Map<String, BigDecimal>> passengerByDate = loadPassengerByDate(year);
|
Map<LocalDate, Map<String, BigDecimal>> vehicleByDate = loadVehicleByDate(year);
|
Map<LocalDate, BigDecimal> comparisonBaseByDate = loadComparisonBaseByDate(year);
|
Map<String, BigDecimal> passenger = passengerByDate.getOrDefault(target.getHolidayDate(), Collections.emptyMap());
|
Map<String, BigDecimal> vehicle = vehicleByDate.getOrDefault(target.getHolidayDate(), 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(target.getHolidayDate());
|
BigDecimal nonBusiness = null;
|
if (coefficient != null && (expressway != null || nationalProvincial != null)) {
|
nonBusiness = HolidayNumberUtils.multiply(
|
HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient);
|
}
|
|
DailyMetrics metrics = new DailyMetrics();
|
metrics.setYear(year);
|
metrics.setHolidayType(holidayType);
|
metrics.setDayIndex(dayIndex);
|
metrics.setDate(target.getHolidayDate());
|
metrics.setRail(passenger.get(HolidayConstants.MODE_RAIL));
|
metrics.setCivilAviation(passenger.get(HolidayConstants.MODE_CIVIL_AVIATION));
|
metrics.setRoad(passenger.get(HolidayConstants.MODE_ROAD));
|
metrics.setWater(passenger.get(HolidayConstants.MODE_WATER));
|
metrics.setExpressway(expressway);
|
metrics.setNationalProvincial(nationalProvincial);
|
metrics.setComparisonBase(comparisonBase);
|
metrics.setCoefficient(coefficient);
|
metrics.setNonBusinessPersonFlow(nonBusiness);
|
metrics.setRoadPersonFlow(sumNullable(metrics.getRoad(), nonBusiness));
|
metrics.setTotalPersonFlow(totalPersonFlow(passenger, nonBusiness));
|
return metrics;
|
}
|
|
public DailyMetrics cumulativeMetrics(int year, String holidayType, int dayIndex) {
|
DailyMetrics cumulative = new DailyMetrics();
|
cumulative.setYear(year);
|
cumulative.setHolidayType(holidayType);
|
cumulative.setDayIndex(dayIndex);
|
for (int day = 1; day <= dayIndex; day++) {
|
DailyMetrics item = dailyMetrics(year, holidayType, day);
|
if (item == null) continue;
|
cumulative.setRail(sumNullable(cumulative.getRail(), item.getRail()));
|
cumulative.setCivilAviation(sumNullable(cumulative.getCivilAviation(), item.getCivilAviation()));
|
cumulative.setRoad(sumNullable(cumulative.getRoad(), item.getRoad()));
|
cumulative.setWater(sumNullable(cumulative.getWater(), item.getWater()));
|
cumulative.setExpressway(sumNullable(cumulative.getExpressway(), item.getExpressway()));
|
cumulative.setNationalProvincial(sumNullable(cumulative.getNationalProvincial(), item.getNationalProvincial()));
|
cumulative.setNonBusinessPersonFlow(sumNullable(cumulative.getNonBusinessPersonFlow(), item.getNonBusinessPersonFlow()));
|
cumulative.setRoadPersonFlow(sumNullable(cumulative.getRoadPersonFlow(), item.getRoadPersonFlow()));
|
cumulative.setTotalPersonFlow(sumNullable(cumulative.getTotalPersonFlow(), item.getTotalPersonFlow()));
|
}
|
return cumulative;
|
}
|
|
|
public DailyMetrics findMetricsByDate(LocalDate date) {
|
HolidayCalendar calendar = configService.findHolidayByDate(date);
|
if (calendar == null) return null;
|
return dailyMetrics(calendar.getYear(), calendar.getHolidayType(), calendar.getDayIndex());
|
}
|
|
private Map<LocalDate, Map<String, BigDecimal>> loadPassengerByDate(int year) {
|
Map<LocalDate, Map<String, BigDecimal>> result = new LinkedHashMap<>();
|
List<HolidayPassengerFlow> rows = passengerFlowMapper.selectList(
|
new LambdaQueryWrapper<HolidayPassengerFlow>().eq(HolidayPassengerFlow::getYear, year));
|
for (HolidayPassengerFlow row : rows) {
|
result.computeIfAbsent(row.getFlowDate(), key -> new LinkedHashMap<>())
|
.put(row.getTransportMode(), row.getPassengerCount());
|
}
|
return result;
|
}
|
|
private Map<LocalDate, Map<String, BigDecimal>> loadVehicleByDate(int year) {
|
Map<LocalDate, Map<String, BigDecimal>> result = new LinkedHashMap<>();
|
List<HolidayVehicleFlow> rows = vehicleFlowMapper.selectList(
|
new LambdaQueryWrapper<HolidayVehicleFlow>().eq(HolidayVehicleFlow::getYear, year));
|
for (HolidayVehicleFlow row : rows) {
|
result.computeIfAbsent(row.getFlowDate(), key -> new LinkedHashMap<>())
|
.put(row.getRoadCategory(), row.getVehicleCount());
|
}
|
return result;
|
}
|
|
private Map<LocalDate, BigDecimal> loadComparisonBaseByDate(int year) {
|
Map<LocalDate, BigDecimal> result = new LinkedHashMap<>();
|
List<HolidayVehicleFlow> rows = vehicleFlowMapper.selectList(
|
new LambdaQueryWrapper<HolidayVehicleFlow>()
|
.eq(HolidayVehicleFlow::getYear, year)
|
.eq(HolidayVehicleFlow::getRoadCategory, HolidayConstants.ROAD_NATIONAL_PROVINCIAL));
|
for (HolidayVehicleFlow row : rows) {
|
if (row.getComparisonBase() != null) result.put(row.getFlowDate(), row.getComparisonBase());
|
}
|
return result;
|
}
|
|
private BigDecimal totalPersonFlow(Map<String, BigDecimal> passenger, BigDecimal nonBusinessPersonFlow) {
|
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));
|
return total;
|
}
|
|
private BigDecimal nationalProvincialRingRatio(int year, BigDecimal current,
|
BigDecimal previous, BigDecimal comparisonBase) {
|
if (current == null) return null;
|
if (HolidayConstants.supportsComparisonBase(year) && comparisonBase != null) {
|
return ratio(current, comparisonBase);
|
}
|
return previous == null ? null : ratio(current, previous);
|
}
|
|
private BigDecimal ratio(BigDecimal current, BigDecimal base) {
|
BigDecimal quotient = HolidayNumberUtils.divide(current, base);
|
return quotient == null ? null : HolidayNumberUtils.normalize(quotient.subtract(BigDecimal.ONE));
|
}
|
|
private BigDecimal sumNullable(BigDecimal left, BigDecimal right) {
|
return HolidayNumberUtils.sumNullable(left, right);
|
}
|
|
private String joinErrors(List<String> errors) {
|
if (errors == null || errors.isEmpty()) return null;
|
StringBuilder builder = new StringBuilder();
|
int limit = Math.min(errors.size(), 200);
|
for (int i = 0; i < limit; i++) {
|
if (i > 0) builder.append('\n');
|
builder.append(errors.get(i));
|
}
|
if (errors.size() > limit) {
|
builder.append("\n... ").append(errors.size() - limit).append(" more");
|
}
|
return builder.toString();
|
}
|
|
@Data
|
public static class DailyMetrics {
|
private int year;
|
private String holidayType;
|
private int dayIndex;
|
private LocalDate date;
|
private BigDecimal rail;
|
private BigDecimal civilAviation;
|
private BigDecimal road;
|
private BigDecimal water;
|
private BigDecimal expressway;
|
private BigDecimal nationalProvincial;
|
private BigDecimal comparisonBase;
|
private BigDecimal coefficient;
|
private BigDecimal nonBusinessPersonFlow;
|
private BigDecimal roadPersonFlow;
|
private BigDecimal totalPersonFlow;
|
}
|
}
|