xyc
昨天 b425da0611b39f20fc868645c59e17f72b70d148
traffic-audit-server/src/test/java/com/trafficaudit/holiday/service/HolidayComparisonBaseTest.java
@@ -4,8 +4,10 @@
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;
@@ -13,79 +15,159 @@
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;
/**
 * The Excel comparison base column (C) is an operator backup value only: it must never change the
 * national/provincial ring ratio. This locks in that behaviour.
 * 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 ringRatioUsesPreviousDayEvenWhenComparisonBaseIsStored() throws Exception {
    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(2026, HolidayConstants.CATEGORY_HOLIDAY)).thenReturn(new BigDecimal("2.4"));
        when(passengerMapper.selectList(any())).thenReturn(new ArrayList<HolidayPassengerFlow>());
        when(vehicleMapper.selectList(any())).thenReturn(vehicleRows());
        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);
        List<Map<String, Object>> rows = service.buildOverviewRows(2026, HolidayConstants.TYPE_NATIONAL_DAY, calendar());
        BigDecimal ringRatio = (BigDecimal) rows.get(3).get("nationalProvincialRingRatio");
        assertEquals(ratio(new BigDecimal("322.7695"), new BigDecimal("323.7837")), ringRatio);
        assertNotEquals(ratio(new BigDecimal("322.7695"), new BigDecimal("320.2418")), ringRatio);
        assertEquals(new BigDecimal("320.2418"), rows.get(2).get("comparisonBase"));
    }
    @Test
    void exportFormulaComparesPreviousRowInsteadOfComparisonBaseColumn() {
        assertEquals("=(D6-D5)/D5", HolidayWorkbookService.ringRatioFormula(6, 5));
        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 List<HolidayVehicleFlow> vehicleRows() {
    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("2026-10-03", "323.7837", "320.2418"));
        rows.add(vehicle("2026-10-04", "322.7695", null));
        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 value, String base) {
    private static HolidayVehicleFlow vehicle(String date, String roadCategory, String value, String base) {
        HolidayVehicleFlow row = new HolidayVehicleFlow();
        row.setYear(2026);
        row.setYear(LocalDate.parse(date).getYear());
        row.setFlowDate(LocalDate.parse(date));
        row.setRoadCategory(HolidayConstants.ROAD_NATIONAL_PROVINCIAL);
        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<HolidayCalendar> calendar() {
    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(2026);
            day.setYear(year);
            day.setHolidayType(HolidayConstants.TYPE_NATIONAL_DAY);
            day.setDayIndex(i);
            day.setHolidayDate(LocalDate.of(2026, 10, i));
            day.setHolidayDate(LocalDate.of(year, 10, i));
            days.add(day);
        }
        return days;