xyc
3 小时以前 8ad75d5872872b79abada58589adad0df4614db6
fix(holiday): 走势图按年度配置截取假期天数
1个文件已添加
3个文件已修改
144 ■■■■■ 已修改文件
HANDOFF.md 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayTrendChartService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayTrendService.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/test/java/com/trafficaudit/holiday/service/HolidayTrendServiceTest.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HANDOFF.md
@@ -6,6 +6,17 @@
## 最新(2026-10-07 上午,新开会话交接)
### 10-07:走势图横轴和累计范围改为按年度配置(待 10-07 数据齐全后最终验收)
- **发现方式**:应要求对已验收的 10月6日走势图 `(12).docx` 做数值复核。15 张图与源表、数据库和现场重新生成图一致,但折线图横轴仍出现 `10/8`。核对 `holiday_calendar` 后确认 2026 年十一只配置 `10-01` 至 `10-07` 共 7 天,代码却固定遍历 8 天。
- **影响**:折线图会多一个 `10/8` 空日期位;2025 年多出的 10/8 还可能进入最终 7 天版的累计和公路出行结构对比,导致 2025 自驾由 `10,406` 变为 `9,365`、道路由 `648` 变为 `571`。
- **修复**:`HolidayTrendService` 按所选年份的 `holiday_calendar` 生成日期标签和天数,折线数据、累计、每日构成、观测站数据均按配置天数截取;`HolidayTrendChartService.lineChart` 新增显式日期标签参数,旧调用保留默认行为。
- **验证**:`mvn -q -f traffic-audit-server\pom.xml -Dtest=HolidayTrendServiceTest,HolidayTrendChartServiceTest test` 通过;后端全量 `mvn -q test -f traffic-audit-server\pom.xml` 通过;真实库只读生成 `_tmp_holiday_verify\server_trend_7day.docx`,15 张图完整;原图 + 3 倍放大 OCR 合并核对,2026/2025/2024 折线、每日构成、饼图、公路结构和观测站标签均与独立重算一致,横轴无 `10/8`。唯一 OCR 粘连是 `1.3701.31`,包含预期的 `1.37` 和 `1.31`。
- **验收副本**:`C:\Users\jcxiong\Downloads\2026年十一假期全省综合运输客流量走势图(7天版-待验收).docx`。
- **测试报告**:`docs\功能测试报告\功能测试报告_走势图横轴按年度配置_2026-10-07.md`(本地留存,不入库)。
- **数据边界**:未导入、覆盖或重导任何业务数据。
- **后续**:10-07 数据导入后重新生成最终 7 天版,确认横轴包含 10/7 且 2026-10-07 数值进入各图;随后再做一次只读核验。
### 10-07:每日客流构成图右侧标签布局修复(验收通过)
- **问题**:`image13`「每日客流构成变化」右侧外置百分比标签在相邻日期之间重叠 4 处;改为两列后引线交错,个别引线压住绿色块内数值。
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayTrendChartService.java
@@ -93,16 +93,21 @@
    }
    public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis) {
        return lineChart(yAxisLabel, series, percentAxis, 1100, 590);
        return lineChart(yAxisLabel, series, percentAxis, DAY_LABELS, 2, 1100, 590);
    }
    public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis,
                            int width, int height) {
        return lineChart(yAxisLabel, series, percentAxis, 2, width, height);
        return lineChart(yAxisLabel, series, percentAxis, DAY_LABELS, 2, width, height);
    }
    public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis,
                            int valueDecimals, int width, int height) {
        return lineChart(yAxisLabel, series, percentAxis, DAY_LABELS, valueDecimals, width, height);
    }
    public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis,
                            List<String> dayLabels, int valueDecimals, int width, int height) {
        XYSeriesCollection dataset = new XYSeriesCollection();
        for (int seriesIndex = 0; seriesIndex < series.size(); seriesIndex++) {
            ChartSeries item = series.get(seriesIndex);
@@ -132,7 +137,8 @@
        plot.setRangeGridlinesVisible(false);
        plot.setDomainTickBandPaint(null);
        plot.setRangeTickBandPaint(null);
        SymbolAxis domainAxis = new SymbolAxis(null, DAY_LABELS.toArray(new String[0]));
        List<String> axisLabels = dayLabels == null || dayLabels.isEmpty() ? DAY_LABELS : dayLabels;
        SymbolAxis domainAxis = new SymbolAxis(null, axisLabels.toArray(new String[0]));
        domainAxis.setGridBandPaint(new Color(0, 0, 0, 0));
        domainAxis.setGridBandAlternatePaint(new Color(0, 0, 0, 0));
        domainAxis.setLowerMargin(0.04);
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayTrendService.java
@@ -47,10 +47,16 @@
        if (holidayLabel == null) {
            throw new IllegalArgumentException("不支持的节假日类型");
        }
        List<HolidayCalendar> holidayDays = configService.findHolidayDays(year, holidayType);
        if (holidayDays.isEmpty()) {
            throw new IllegalArgumentException("所选年份和节假日尚未配置日期,请先在系统配置中维护节假日");
        }
        List<String> dayLabels = dayLabels(holidayDays);
        int dayCount = holidayDays.size();
        List<Integer> years = Arrays.asList(year, year - 1, year - 2);
        Map<Integer, YearData> yearData = new LinkedHashMap<>();
        for (Integer item : years) {
            yearData.put(item, buildYearData(item, holidayType));
            yearData.put(item, buildYearData(item, holidayType, dayCount));
        }
        YearData selected = yearData.get(year);
        if (selected == null || !selected.hasAnyData()) {
@@ -63,12 +69,12 @@
        blocks.add(new HolidayTrendWordService.ChartBlock(
                prefix + "全省跨区域人员流动量情况",
                chartService.lineChart("万人次",
                        series(years, yearData, item -> item.totalDaily), false, 1100, 715),
                        series(years, yearData, item -> item.totalDaily), false, dayLabels, 2, 1100, 715),
                lineImageSize(5268595L, 3424555L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期累计跨区域人员流动量走势",
                chartService.lineChart("万人次",
                        series(years, yearData, item -> item.totalCumulative), false, 1100, 590),
                        series(years, yearData, item -> item.totalCumulative), false, dayLabels, 2, 1100, 590),
                lineImageSize(5268595L, 2821305L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期各交通运输方式客运量占比情况",
@@ -79,44 +85,44 @@
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期自驾出行量情况",
                chartService.lineChart("万人次",
                        series(years, yearData, item -> item.selfDrivingPersonFlow), false, 1100, 522),
                        series(years, yearData, item -> item.selfDrivingPersonFlow), false, dayLabels, 2, 1100, 522),
                lineImageSize(5232400L, 2484120L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期铁路客运量情况",
                chartService.lineChart("万人次",
                        series(years, yearData, item -> item.rail), false, 1100, 445),
                        series(years, yearData, item -> item.rail), false, dayLabels, 2, 1100, 445),
                lineImageSize(5264785L, 2129155L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期水路客运量情况",
                chartService.lineChart("万人次",
                        series(years, yearData, item -> item.water), false, 1100, 433),
                        series(years, yearData, item -> item.water), false, dayLabels, 2, 1100, 433),
                lineImageSize(5274310L, 2075815L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期民航客运量情况",
                chartService.lineChart("万人次",
                        series(years, yearData, item -> item.civilAviation), false, 1100, 521),
                        series(years, yearData, item -> item.civilAviation), false, dayLabels, 2, 1100, 521),
                lineImageSize(5271135L, 2496185L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期道路客运量情况",
                chartService.lineChart("万人次",
                        series(years, yearData, item -> item.road), false, 1100, 590),
                        series(years, yearData, item -> item.road), false, dayLabels, 2, 1100, 590),
                lineImageSize(5268595L, 2821305L)));
        blocks.add(new HolidayTrendWordService.SectionBlock("三、车流量走势"));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期高速公路车流量情况",
                chartService.lineChart("万辆次",
                        series(years, yearData, item -> item.expressway), false, 1100, 590),
                        series(years, yearData, item -> item.expressway), false, dayLabels, 2, 1100, 590),
                lineImageSize(5268595L, 2821305L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期普通国省道车流量情况",
                chartService.lineChart("万辆次",
                        series(years, yearData, item -> item.nationalProvincial), false, 1100, 590),
                        series(years, yearData, item -> item.nationalProvincial), false, dayLabels, 2, 1100, 590),
                lineImageSize(5268595L, 2821305L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期全省车流总量情况",
                chartService.lineChart("万辆次",
                        series(years, yearData, item -> item.totalVehicleFlow), false, 1100, 590),
                        series(years, yearData, item -> item.totalVehicleFlow), false, dayLabels, 2, 1100, 590),
                lineImageSize(5268595L, 2821305L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期公路出行结构",
@@ -129,21 +135,21 @@
        blocks.add(new HolidayTrendWordService.ChartBlock(
                "“" + holidayLabel + "”假期每日客流构成变化",
                chartService.stackedBarChart("占比",
                        HolidayTrendChartService.DAY_LABELS,
                        dailyShareSeries(selected), true, 1000, 607), true,
                        dayLabels,
                        dailyShareSeries(selected, dayCount), true, 1000, 607), true,
                new HolidayTrendWordService.ImageSize(5106670L, 3097530L)));
        blocks.add(new HolidayTrendWordService.SectionBlock("附录二 普通国省道观测站路网运行"));
        ObservationData observation = observationData(year, holidayType);
        ObservationData observation = observationData(holidayDays);
        blocks.add(new HolidayTrendWordService.ChartBlock(
                prefix + "普通国省道观测站平均机动车速度",
                chartService.lineChart("公里/小时",
                        Collections.singletonList(series("平均速度", observation.speed)), false, 1100, 590),
                        Collections.singletonList(series("平均速度", observation.speed)), false, dayLabels, 2, 1100, 590),
                lineImageSize(5268595L, 2821305L)));
        blocks.add(new HolidayTrendWordService.ChartBlock(
                prefix + "普通国省道观测站平均拥挤度",
                chartService.lineChart("拥挤度",
                        Collections.singletonList(series("平均拥挤度", observation.congestion)), false, 1100, 590),
                        Collections.singletonList(series("平均拥挤度", observation.congestion)), false, dayLabels, 2, 1100, 590),
                lineImageSize(5268595L, 2821305L)));
        File template = resolveTemplateFile();
@@ -157,9 +163,9 @@
        }
    }
    private YearData buildYearData(int year, String holidayType) {
    private YearData buildYearData(int year, String holidayType, int dayCount) {
        List<DailyMetrics> metrics = new ArrayList<>();
        for (int day = 1; day <= HolidayTrendChartService.DAY_LABELS.size(); day++) {
        for (int day = 1; day <= dayCount; day++) {
            metrics.add(statService.dailyMetrics(year, holidayType, day));
        }
        YearData data = new YearData();
@@ -210,19 +216,19 @@
        return result;
    }
    private List<HolidayTrendChartService.ChartSeries> dailyShareSeries(YearData data) {
    private List<HolidayTrendChartService.ChartSeries> dailyShareSeries(YearData data, int dayCount) {
        List<HolidayTrendChartService.ChartSeries> result = new ArrayList<>();
        result.add(series("自驾", share(data.selfDrivingPersonFlow, data)));
        result.add(series("道路", share(data.road, data)));
        result.add(series("铁路", share(data.rail, data)));
        result.add(series("民航", share(data.civilAviation, data)));
        result.add(series("水路", share(data.water, data)));
        result.add(series("自驾", share(data.selfDrivingPersonFlow, data, dayCount)));
        result.add(series("道路", share(data.road, data, dayCount)));
        result.add(series("铁路", share(data.rail, data, dayCount)));
        result.add(series("民航", share(data.civilAviation, data, dayCount)));
        result.add(series("水路", share(data.water, data, dayCount)));
        return result;
    }
    private List<BigDecimal> share(List<BigDecimal> values, YearData data) {
    private List<BigDecimal> share(List<BigDecimal> values, YearData data, int dayCount) {
        List<BigDecimal> result = new ArrayList<>();
        for (int i = 0; i < HolidayTrendChartService.DAY_LABELS.size(); i++) {
        for (int i = 0; i < dayCount; i++) {
            BigDecimal total = dayTotal(data, i);
            BigDecimal value = valueAt(values, i);
            result.add(total == null || total.signum() == 0
@@ -260,13 +266,12 @@
                series("道路客运量", road));
    }
    private ObservationData observationData(int year, String holidayType) {
        List<HolidayCalendar> days = configService.findHolidayDays(year, holidayType);
    private ObservationData observationData(List<HolidayCalendar> days) {
        if (days.isEmpty()) {
            return new ObservationData(nulls(), nulls());
            return new ObservationData(nulls(0), nulls(0));
        }
        LocalDate start = days.get(0).getHolidayDate();
        LocalDate end = start.plusDays(HolidayTrendChartService.DAY_LABELS.size() - 1L);
        LocalDate end = days.get(days.size() - 1).getHolidayDate();
        List<ObservationDailyOperation> rows = observationStationFlowMapper.selectDailyOperations(start, end);
        Map<LocalDate, ObservationDailyOperation> byDate = new LinkedHashMap<>();
        for (ObservationDailyOperation row : rows) {
@@ -274,8 +279,8 @@
        }
        List<BigDecimal> speed = new ArrayList<>();
        List<BigDecimal> congestion = new ArrayList<>();
        for (int i = 0; i < HolidayTrendChartService.DAY_LABELS.size(); i++) {
            ObservationDailyOperation row = byDate.get(start.plusDays(i));
        for (HolidayCalendar day : days) {
            ObservationDailyOperation row = byDate.get(day.getHolidayDate());
            speed.add(row == null ? null : row.getAverageSpeed());
            congestion.add(row == null ? null : row.getAverageCongestion());
        }
@@ -319,7 +324,8 @@
    private List<BigDecimal> percentageChanges(List<BigDecimal> current, List<BigDecimal> previous) {
        List<BigDecimal> result = new ArrayList<>();
        for (int i = 0; i < HolidayTrendChartService.DAY_LABELS.size(); i++) {
        int size = Math.max(current == null ? 0 : current.size(), previous == null ? 0 : previous.size());
        for (int i = 0; i < size; i++) {
            BigDecimal currentValue = valueAt(current, i);
            BigDecimal previousValue = valueAt(previous, i);
            BigDecimal ratio = HolidayNumberUtils.divide(currentValue, previousValue);
@@ -342,11 +348,26 @@
    }
    private List<BigDecimal> nulls() {
        return nulls(HolidayTrendChartService.DAY_LABELS.size());
    }
    private List<BigDecimal> nulls(int size) {
        List<BigDecimal> result = new ArrayList<>();
        for (int i = 0; i < HolidayTrendChartService.DAY_LABELS.size(); i++) result.add(null);
        for (int i = 0; i < size; i++) result.add(null);
        return result;
    }
    static List<String> dayLabels(List<HolidayCalendar> days) {
        List<String> labels = new ArrayList<>();
        if (days == null) return labels;
        for (HolidayCalendar day : days) {
            if (day == null || day.getHolidayDate() == null) continue;
            labels.add(day.getHolidayDate().getMonthValue() + "月"
                    + day.getHolidayDate().getDayOfMonth() + "日");
        }
        return labels;
    }
    private HolidayTrendWordService.ImageSize lineImageSize(long width, long height) {
        return new HolidayTrendWordService.ImageSize(
                width, HolidayTrendChartService.scaleLineChartHeight(height));
traffic-audit-server/src/test/java/com/trafficaudit/holiday/service/HolidayTrendServiceTest.java
New file
@@ -0,0 +1,32 @@
package com.trafficaudit.holiday.service;
import com.trafficaudit.holiday.entity.HolidayCalendar;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class HolidayTrendServiceTest {
    @Test
    void buildsTrendAxisFromConfiguredHolidayDays() {
        List<HolidayCalendar> days = new ArrayList<>();
        for (int index = 1; index <= 7; index++) {
            HolidayCalendar day = new HolidayCalendar();
            day.setYear(2026);
            day.setHolidayType("NATIONAL_DAY");
            day.setDayIndex(index);
            day.setHolidayDate(LocalDate.of(2026, 10, index));
            days.add(day);
        }
        assertEquals(Arrays.asList(
                "10月1日", "10月2日", "10月3日", "10月4日",
                "10月5日", "10月6日", "10月7日"),
                HolidayTrendService.dayLabels(days));
    }
}