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);