xyc
4 天以前 8b8971e94db61fb346598a2402d65c8ddb196d6b
fix(holiday): 快报民航累计同比只与往年已覆盖日期比较
3个文件已修改
96 ■■■■■ 已修改文件
HANDOFF.md 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayBulletinService.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/test/java/com/trafficaudit/holiday/service/HolidayBulletinServiceTest.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HANDOFF.md
@@ -23,6 +23,13 @@
- **验证**:Maven 编译和测试通过;重新生成 2026-10-01 快报,正文仍为“增长2.3%”且不含“增长了”;Word 导出由 2 页恢复为 1 页,数字不再拆行。最终样报见本地 `_tmp_holiday_verify/bulletin_final_signed_styled_2026-10-01.docx`。
- **状态**:2026-10-03 用户验收通过;本次提交只包含代码、回归测试和本交接记录,不包含 `_tmp_*` 过程产物及用户未提交的数据文件重命名。
### 10-03 深夜(2):缺当日民航数据时的累计同比对比期间修正
- **快报民航累计同比对比期间修正(2026-10-03,缺当日民航数据场景)**:当某运输方式当年累计覆盖的日期短于所选日期(典型为 10 月 2 日民航数据未出、民航累计只有 10-01),累计同比改为「只和往年已经覆盖到的日期比较」——先找当年最后一个有该指标数据的日期,再取往年同日累计。修复后缺失民航的 10 月 2 日快报民航累计同比由 `-50.5%/-49.9%` 变为 `-10.0%/-0.9%`(与手工稿 -10.03%/-0.87% 的 1 位小数值一致)。实现见 `HolidayBulletinService.comparableCumulative`(仅作用于民航行),其余行与总人员流动量口径不变。
- **「累计同比 -4.65% vs 快报 -4.6%」结论(2026-10-03,无需改代码)**:Excel 显示的 -4.65% 是**两位小数**结果;底层精确比值是 `-4.64633%`(2026 累计 3082.23818、2025 累计 3232.42742),**一次四舍五入到 1 位小数就是 -4.6%**。把已经取到两位小数的 -4.65 再取一位才得到 -4.7,属二次取整,不采用。同理 2026 对 2024 为 `-1.7710%` → `-1.8%`。快报百分比统一 1 位小数口径不变。
- **「暂缺民航」提示**:用户确认由人工在生成的快报稿上修改,系统不生成该提示行,**不做代码改动**。
- **验证**:`HolidayBulletinServiceTest` 3 个用例通过(新增 2 个覆盖民航累计对比期间与无数据返回 null);端到端用库内真实 2026-10-02 数据(铁路/道路/水路/车流齐全、缺民航)重新生成快报,民航累计同比 `-10.0%/-0.9%`、民航累计 `6.75`、总计累计 `3082.24`、总计累计同比 `-4.6%/-1.8%`,与手工稿口径一致。
- **Git**:本次改动待用户「验收通过」后再提交/推送。
---
### 补充(10-03 下午):服务器部署报 “Access denied ... (using password: NO)” —— 根因与修复
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayBulletinService.java
@@ -31,6 +31,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@@ -131,6 +132,10 @@
        if (table == null) {
            throw new IllegalArgumentException("快报模板中找不到数据表");
        }
        DailyMetrics previousYearCivilCumulative = comparableCumulative(
                current, previousYear, DailyMetrics::getCivilAviation);
        DailyMetrics twoYearsAgoCivilCumulative = comparableCumulative(
                current, twoYearsAgo, DailyMetrics::getCivilAviation);
        writeTableRow(table, 2, new String[]{
                number(current.getNonBusinessPersonFlow()),
                ratio(current.getNonBusinessPersonFlow(), previousDay == null ? null : previousDay.getNonBusinessPersonFlow()),
@@ -174,8 +179,8 @@
                previousYear == null ? null : previousYear.getCivilAviation(),
                twoYearsAgo == null ? null : twoYearsAgo.getCivilAviation(),
                currentCumulative.getCivilAviation(),
                previousYearCumulative == null ? null : previousYearCumulative.getCivilAviation(),
                twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getCivilAviation()));
                previousYearCivilCumulative == null ? null : previousYearCivilCumulative.getCivilAviation(),
                twoYearsAgoCivilCumulative == null ? null : twoYearsAgoCivilCumulative.getCivilAviation()));
        writeTableRow(table, 7, transportRow(current.getWater(),
                previousDay == null ? null : previousDay.getWater(),
                previousYear == null ? null : previousYear.getWater(),
@@ -332,6 +337,21 @@
        return (percent.signum() > 0 ? "增长" : "下降") + HolidayNumberUtils.format1(percent.abs()) + "%";
    }
    private DailyMetrics comparableCumulative(DailyMetrics current, DailyMetrics comparisonYear,
                                              Function<DailyMetrics, BigDecimal> selector) {
        if (current == null || comparisonYear == null) return null;
        int coveredDay = 0;
        for (int day = current.getDayIndex(); day >= 1; day--) {
            DailyMetrics metrics = statService.dailyMetrics(current.getYear(), current.getHolidayType(), day);
            if (metrics != null && selector.apply(metrics) != null) {
                coveredDay = day;
                break;
            }
        }
        if (coveredDay == 0) return null;
        return statService.cumulativeMetrics(comparisonYear.getYear(), current.getHolidayType(), coveredDay);
    }
    private String ratio(BigDecimal current, BigDecimal base) {
        if (current == null || base == null || base.abs().compareTo(EPSILON) < 0) return "—";
        BigDecimal ratio = HolidayNumberUtils.divide(current, base);
traffic-audit-server/src/test/java/com/trafficaudit/holiday/service/HolidayBulletinServiceTest.java
@@ -1,11 +1,19 @@
package com.trafficaudit.holiday.service;
import com.trafficaudit.holiday.service.HolidayStatService.DailyMetrics;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.function.Function;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class HolidayBulletinServiceTest {
@@ -21,6 +29,63 @@
        assertEquals("—", ratio(service, "100", "0"));
    }
    @Test
    void comparesCivilAviationOnlyThroughCoveredDay() throws Exception {
        HolidayStatService stat = mock(HolidayStatService.class);
        HolidayBulletinService service = new HolidayBulletinService(stat);
        DailyMetrics current = new DailyMetrics();
        current.setYear(2026);
        current.setHolidayType("NATIONAL_DAY");
        current.setDayIndex(2);
        DailyMetrics coveredDay = new DailyMetrics();
        coveredDay.setCivilAviation(new BigDecimal("6.7482"));
        DailyMetrics missingDay = new DailyMetrics();
        when(stat.dailyMetrics(2026, "NATIONAL_DAY", 2)).thenReturn(missingDay);
        when(stat.dailyMetrics(2026, "NATIONAL_DAY", 1)).thenReturn(coveredDay);
        DailyMetrics previousYear = new DailyMetrics();
        previousYear.setYear(2025);
        DailyMetrics previousYearThroughDay1 = new DailyMetrics();
        previousYearThroughDay1.setCivilAviation(new BigDecimal("7.5003"));
        when(stat.cumulativeMetrics(2025, "NATIONAL_DAY", 1)).thenReturn(previousYearThroughDay1);
        Object result = comparableCumulative(service, current, previousYear, DailyMetrics::getCivilAviation);
        assertSame(previousYearThroughDay1, result);
        verify(stat).cumulativeMetrics(2025, "NATIONAL_DAY", 1);
        verify(stat, never()).cumulativeMetrics(2025, "NATIONAL_DAY", 2);
    }
    @Test
    void comparableCumulativeIsNullWhenCurrentYearHasNoMetricData() throws Exception {
        HolidayStatService stat = mock(HolidayStatService.class);
        HolidayBulletinService service = new HolidayBulletinService(stat);
        DailyMetrics current = new DailyMetrics();
        current.setYear(2026);
        current.setHolidayType("NATIONAL_DAY");
        current.setDayIndex(2);
        when(stat.dailyMetrics(2026, "NATIONAL_DAY", 2)).thenReturn(new DailyMetrics());
        when(stat.dailyMetrics(2026, "NATIONAL_DAY", 1)).thenReturn(new DailyMetrics());
        Object result = comparableCumulative(service, current, new DailyMetrics(), DailyMetrics::getCivilAviation);
        assertNull(result);
        verify(stat, never()).cumulativeMetrics(0, "NATIONAL_DAY", 1);
    }
    private Object comparableCumulative(HolidayBulletinService service, DailyMetrics current,
                                        DailyMetrics comparisonYear,
                                        Function<DailyMetrics, BigDecimal> selector) throws Exception {
        Method method = HolidayBulletinService.class.getDeclaredMethod(
                "comparableCumulative", DailyMetrics.class, DailyMetrics.class, Function.class);
        method.setAccessible(true);
        return method.invoke(service, current, comparisonYear, selector);
    }
    private String ratio(HolidayBulletinService service, String current, String base) throws Exception {
        Method method = HolidayBulletinService.class.getDeclaredMethod("ratio", BigDecimal.class, BigDecimal.class);
        method.setAccessible(true);