package com.trafficaudit.holiday.service;
|
|
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.HolidayVehicleAdjustment;
|
import com.trafficaudit.holiday.entity.HolidayVehicleFlow;
|
import com.trafficaudit.holiday.mapper.HolidayPassengerFlowMapper;
|
import com.trafficaudit.holiday.mapper.HolidayVehicleAdjustmentMapper;
|
import com.trafficaudit.holiday.mapper.HolidayVehicleFlowMapper;
|
import org.junit.jupiter.api.Test;
|
|
import java.lang.reflect.Field;
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.List;
|
import java.util.Map;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.when;
|
|
/**
|
* Ring-ratio denominators are built one day at a time. The Excel comparison base (column C) is
|
* consumed on the source day and inherited by the next day's denominator, while historical
|
* adjustment records change only that historical day's same-day value.
|
*/
|
class HolidayComparisonBaseTest {
|
|
@Test
|
void comparisonBaseChangesOnlyTheNextDayRingRatioDenominator() throws Exception {
|
HolidayStatService service = serviceWithData(
|
2026,
|
passengerRows(2026, "50", "10", "6", "3"),
|
vehicleRows(2026),
|
Collections.<HolidayVehicleAdjustment>emptyList());
|
|
List<Map<String, Object>> rows = service.buildOverviewRows(
|
2026, HolidayConstants.TYPE_NATIONAL_DAY, calendar(2026));
|
|
assertEquals(new BigDecimal("323.7837"), rows.get(2).get("nationalProvincial"));
|
assertEquals(new BigDecimal("320.2418"), rows.get(2).get("comparisonBase"));
|
assertEquals(new BigDecimal("322.7695"), rows.get(3).get("nationalProvincial"));
|
|
BigDecimal nationalRatio = (BigDecimal) rows.get(3).get("nationalProvincialRingRatio");
|
assertEquals(ratio(new BigDecimal("322.7695"), new BigDecimal("320.2418")), nationalRatio);
|
assertNotEquals(ratio(new BigDecimal("322.7695"), new BigDecimal("323.7837")), nationalRatio);
|
|
HolidayStatService.DailyMetrics base = service.ringRatioBaseMetrics(
|
2026, HolidayConstants.TYPE_NATIONAL_DAY, 4);
|
assertNotNull(base);
|
assertEquals(new BigDecimal("320.2418"), base.getNationalProvincial());
|
assertDecimalEquals("1008.58032", base.getNonBusinessPersonFlow());
|
assertDecimalEquals("1058.58032", base.getRoadPersonFlow());
|
assertDecimalEquals("1077.58032", base.getTotalPersonFlow());
|
}
|
|
@Test
|
void historicalAdjustmentChangesTheHistoricalSameDayValue() throws Exception {
|
HolidayVehicleAdjustment adjustment = new HolidayVehicleAdjustment();
|
adjustment.setYear(2025);
|
adjustment.setFlowDate(LocalDate.of(2025, 10, 4));
|
adjustment.setAdjustedValue(new BigDecimal("337.5009"));
|
|
HolidayStatService service = serviceWithData(
|
2025,
|
passengerRows(2025, "50", "10", "6", "3"),
|
Arrays.asList(
|
vehicle("2025-10-03", HolidayConstants.ROAD_EXPRESSWAY, "100", null),
|
vehicle("2025-10-03", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "362.0035", null),
|
vehicle("2025-10-04", HolidayConstants.ROAD_EXPRESSWAY, "110", null),
|
vehicle("2025-10-04", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "350.0000", null)),
|
Collections.singletonList(adjustment));
|
|
List<Map<String, Object>> rows = service.buildOverviewRows(
|
2025, HolidayConstants.TYPE_NATIONAL_DAY, calendar(2025));
|
|
assertEquals(new BigDecimal("337.5009"), rows.get(3).get("nationalProvincial"));
|
}
|
|
private static HolidayStatService serviceWithData(int year,
|
List<HolidayPassengerFlow> passengerRows,
|
List<HolidayVehicleFlow> vehicleRows,
|
List<HolidayVehicleAdjustment> adjustments) throws Exception {
|
HolidayConfigService configService = mock(HolidayConfigService.class);
|
HolidayPassengerFlowMapper passengerMapper = mock(HolidayPassengerFlowMapper.class);
|
HolidayVehicleFlowMapper vehicleMapper = mock(HolidayVehicleFlowMapper.class);
|
HolidayVehicleAdjustmentMapper adjustmentMapper = mock(HolidayVehicleAdjustmentMapper.class);
|
|
when(configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY)).thenReturn(new BigDecimal("2.4"));
|
when(configService.findHolidayDays(year, HolidayConstants.TYPE_NATIONAL_DAY)).thenReturn(calendar(year));
|
when(passengerMapper.selectList(any())).thenReturn(passengerRows);
|
when(vehicleMapper.selectList(any())).thenReturn(vehicleRows);
|
when(adjustmentMapper.selectList(any())).thenReturn(adjustments);
|
|
HolidayStatService service = new HolidayStatService();
|
inject(service, "configService", configService);
|
inject(service, "passengerFlowMapper", passengerMapper);
|
inject(service, "vehicleFlowMapper", vehicleMapper);
|
inject(service, "vehicleAdjustmentMapper", adjustmentMapper);
|
return service;
|
}
|
|
private static BigDecimal ratio(BigDecimal current, BigDecimal base) {
|
return HolidayNumberUtils.normalize(HolidayNumberUtils.divide(current, base).subtract(BigDecimal.ONE));
|
}
|
|
private static void assertDecimalEquals(String expected, BigDecimal actual) {
|
assertNotNull(actual);
|
assertEquals(0, new BigDecimal(expected).compareTo(actual));
|
}
|
|
private static List<HolidayVehicleFlow> vehicleRows(int year) {
|
List<HolidayVehicleFlow> rows = new ArrayList<>();
|
rows.add(vehicle(year + "-10-03", HolidayConstants.ROAD_EXPRESSWAY, "100", null));
|
rows.add(vehicle(year + "-10-03", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "323.7837", "320.2418"));
|
rows.add(vehicle(year + "-10-04", HolidayConstants.ROAD_EXPRESSWAY, "110", null));
|
rows.add(vehicle(year + "-10-04", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "322.7695", null));
|
return rows;
|
}
|
|
private static HolidayVehicleFlow vehicle(String date, String roadCategory, String value, String base) {
|
HolidayVehicleFlow row = new HolidayVehicleFlow();
|
row.setYear(LocalDate.parse(date).getYear());
|
row.setFlowDate(LocalDate.parse(date));
|
row.setRoadCategory(roadCategory);
|
row.setVehicleType(HolidayConstants.VEHICLE_MEDIUM_SMALL_PASSENGER);
|
row.setVehicleCount(new BigDecimal(value));
|
row.setComparisonBase(base == null ? null : new BigDecimal(base));
|
return row;
|
}
|
|
private static List<HolidayPassengerFlow> passengerRows(int year, String road, String rail,
|
String civilAviation, String water) {
|
List<HolidayPassengerFlow> rows = new ArrayList<>();
|
rows.add(passenger(year + "-10-03", HolidayConstants.MODE_ROAD, road));
|
rows.add(passenger(year + "-10-03", HolidayConstants.MODE_RAIL, rail));
|
rows.add(passenger(year + "-10-03", HolidayConstants.MODE_CIVIL_AVIATION, civilAviation));
|
rows.add(passenger(year + "-10-03", HolidayConstants.MODE_WATER, water));
|
rows.add(passenger(year + "-10-04", HolidayConstants.MODE_ROAD, road));
|
rows.add(passenger(year + "-10-04", HolidayConstants.MODE_RAIL, rail));
|
rows.add(passenger(year + "-10-04", HolidayConstants.MODE_CIVIL_AVIATION, civilAviation));
|
rows.add(passenger(year + "-10-04", HolidayConstants.MODE_WATER, water));
|
return rows;
|
}
|
|
private static HolidayPassengerFlow passenger(String date, String mode, String value) {
|
HolidayPassengerFlow row = new HolidayPassengerFlow();
|
row.setYear(LocalDate.parse(date).getYear());
|
row.setFlowDate(LocalDate.parse(date));
|
row.setTransportMode(mode);
|
row.setPassengerCount(new BigDecimal(value));
|
return row;
|
}
|
|
private static List<HolidayCalendar> calendar(int year) {
|
List<HolidayCalendar> days = new ArrayList<>();
|
for (int i = 1; i <= 4; i++) {
|
HolidayCalendar day = new HolidayCalendar();
|
day.setYear(year);
|
day.setHolidayType(HolidayConstants.TYPE_NATIONAL_DAY);
|
day.setDayIndex(i);
|
day.setHolidayDate(LocalDate.of(year, 10, i));
|
days.add(day);
|
}
|
return days;
|
}
|
|
private static void inject(Object target, String field, Object value) throws Exception {
|
Field declared = target.getClass().getDeclaredField(field);
|
declared.setAccessible(true);
|
declared.set(target, value);
|
}
|
}
|