xyc
4 天以前 922823a3f1faaf0293256e3ffb1285eaeb8a3b23
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayBulletinService.java
@@ -42,6 +42,8 @@
    private static final String WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
    private static final BigDecimal EPSILON = new BigDecimal("0.0000001");
    private static final int EXCEL_ONE_DECIMAL_PERCENT = 1;
    private static final int EXCEL_TWO_DECIMAL_PERCENT = 2;
    @Value("${holiday.bulletin-template:docs/专项统计/重要节假日/湖北省国庆假期综合运输客流快报模板.docx}")
    private String bulletinTemplatePath;
@@ -79,12 +81,12 @@
        String currentTotal = formatNumber(current.getTotalPersonFlow());
        String ringRatioClause = previousDay == null ? "" : ",环比" + ratioPhrase(
                current.getTotalPersonFlow(), previousDay.getTotalPersonFlow());
                current.getTotalPersonFlow(), previousDay.getTotalPersonFlow(), EXCEL_TWO_DECIMAL_PERCENT);
        String paragraph1 = String.format("%d月%d日(假期第%d天),全省全社会跨区域人员流动量%s万人次%s,比2025年同期%s,比2024年同期%s。",
                date.getMonthValue(), date.getDayOfMonth(), current.getDayIndex(), currentTotal,
                ringRatioClause,
                ratioPhrase(current.getTotalPersonFlow(), previousYear == null ? null : previousYear.getTotalPersonFlow()),
                ratioPhrase(current.getTotalPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getTotalPersonFlow()));
                ratioPhrase(current.getTotalPersonFlow(), previousYear == null ? null : previousYear.getTotalPersonFlow(), EXCEL_ONE_DECIMAL_PERCENT),
                ratioPhrase(current.getTotalPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getTotalPersonFlow(), EXCEL_ONE_DECIMAL_PERCENT));
        String paragraph2 = cumulativeParagraph(currentCumulative, previousYearCumulative, twoYearsAgoCumulative);
        File template = resolveTemplateFile();
@@ -131,34 +133,46 @@
                current, twoYearsAgo, DailyMetrics::getCivilAviation);
        writeTableRow(table, 2, new String[]{
                number(current.getNonBusinessPersonFlow()),
                ratio(current.getNonBusinessPersonFlow(), previousDay == null ? null : previousDay.getNonBusinessPersonFlow()),
                ratio(current.getNonBusinessPersonFlow(), previousYear == null ? null : previousYear.getNonBusinessPersonFlow()),
                ratio(current.getNonBusinessPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getNonBusinessPersonFlow()),
                ratio(current.getNonBusinessPersonFlow(), previousDay == null ? null : previousDay.getNonBusinessPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
                ratio(current.getNonBusinessPersonFlow(), previousYear == null ? null : previousYear.getNonBusinessPersonFlow(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                ratio(current.getNonBusinessPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getNonBusinessPersonFlow(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                number(currentCumulative.getNonBusinessPersonFlow()),
                ratio(currentCumulative.getNonBusinessPersonFlow(),
                        previousYearCumulative == null ? null : previousYearCumulative.getNonBusinessPersonFlow()),
                        previousYearCumulative == null ? null : previousYearCumulative.getNonBusinessPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
                ratio(currentCumulative.getNonBusinessPersonFlow(),
                        twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getNonBusinessPersonFlow())
                        twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getNonBusinessPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT)
        });
        writeTableRow(table, 3, new String[]{
                number(current.getRoad()),
                ratio(current.getRoad(), previousDay == null ? null : previousDay.getRoad()),
                ratio(current.getRoad(), previousYear == null ? null : previousYear.getRoad()),
                ratio(current.getRoad(), twoYearsAgo == null ? null : twoYearsAgo.getRoad()),
                ratio(current.getRoad(), previousDay == null ? null : previousDay.getRoad(), EXCEL_ONE_DECIMAL_PERCENT),
                ratio(current.getRoad(), previousYear == null ? null : previousYear.getRoad(), EXCEL_ONE_DECIMAL_PERCENT),
                ratio(current.getRoad(), twoYearsAgo == null ? null : twoYearsAgo.getRoad(), EXCEL_ONE_DECIMAL_PERCENT),
                number(currentCumulative.getRoad()),
                ratio(currentCumulative.getRoad(), previousYearCumulative == null ? null : previousYearCumulative.getRoad()),
                ratio(currentCumulative.getRoad(), twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getRoad())
                ratio(currentCumulative.getRoad(), previousYearCumulative == null ? null : previousYearCumulative.getRoad(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                ratio(currentCumulative.getRoad(), twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getRoad(),
                        EXCEL_ONE_DECIMAL_PERCENT)
        });
        writeTableRow(table, 4, new String[]{
                number(current.getRoadPersonFlow()),
                ratio(current.getRoadPersonFlow(), previousDay == null ? null : previousDay.getRoadPersonFlow()),
                ratio(current.getRoadPersonFlow(), previousYear == null ? null : previousYear.getRoadPersonFlow()),
                ratio(current.getRoadPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getRoadPersonFlow()),
                ratio(current.getRoadPersonFlow(), previousDay == null ? null : previousDay.getRoadPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
                ratio(current.getRoadPersonFlow(), previousYear == null ? null : previousYear.getRoadPersonFlow(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                ratio(current.getRoadPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getRoadPersonFlow(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                number(currentCumulative.getRoadPersonFlow()),
                ratio(currentCumulative.getRoadPersonFlow(),
                        previousYearCumulative == null ? null : previousYearCumulative.getRoadPersonFlow()),
                        previousYearCumulative == null ? null : previousYearCumulative.getRoadPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
                ratio(currentCumulative.getRoadPersonFlow(),
                        twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getRoadPersonFlow())
                        twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getRoadPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT)
        });
        writeTableRow(table, 5, transportRow(current.getRail(),
                firstDayRailBase == null ? null : firstDayRailBase.getRail(),
@@ -166,41 +180,54 @@
                twoYearsAgo == null ? null : twoYearsAgo.getRail(),
                currentCumulative.getRail(),
                previousYearCumulative == null ? null : previousYearCumulative.getRail(),
                twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getRail()));
                twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getRail(),
                EXCEL_ONE_DECIMAL_PERCENT));
        writeTableRow(table, 6, transportRow(current.getCivilAviation(),
                firstDayCivilAviationBase == null ? null : firstDayCivilAviationBase.getCivilAviation(),
                previousYear == null ? null : previousYear.getCivilAviation(),
                twoYearsAgo == null ? null : twoYearsAgo.getCivilAviation(),
                currentCumulative.getCivilAviation(),
                previousYearCivilCumulative == null ? null : previousYearCivilCumulative.getCivilAviation(),
                twoYearsAgoCivilCumulative == null ? null : twoYearsAgoCivilCumulative.getCivilAviation()));
                twoYearsAgoCivilCumulative == null ? null : twoYearsAgoCivilCumulative.getCivilAviation(),
                EXCEL_ONE_DECIMAL_PERCENT));
        writeTableRow(table, 7, transportRow(current.getWater(),
                previousDay == null ? null : previousDay.getWater(),
                previousYear == null ? null : previousYear.getWater(),
                twoYearsAgo == null ? null : twoYearsAgo.getWater(),
                currentCumulative.getWater(),
                previousYearCumulative == null ? null : previousYearCumulative.getWater(),
                twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getWater()));
        writeTableRow(table, 8, transportRow(current.getTotalPersonFlow(),
                previousDay == null ? null : previousDay.getTotalPersonFlow(),
                previousYear == null ? null : previousYear.getTotalPersonFlow(),
                twoYearsAgo == null ? null : twoYearsAgo.getTotalPersonFlow(),
                currentCumulative.getTotalPersonFlow(),
                twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getWater(),
                EXCEL_TWO_DECIMAL_PERCENT));
        writeTableRow(table, 8, new String[]{
                number(current.getTotalPersonFlow()),
                ratio(current.getTotalPersonFlow(), previousDay == null ? null : previousDay.getTotalPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
                ratio(current.getTotalPersonFlow(), previousYear == null ? null : previousYear.getTotalPersonFlow(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                ratio(current.getTotalPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getTotalPersonFlow(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                number(currentCumulative.getTotalPersonFlow()),
                ratio(currentCumulative.getTotalPersonFlow(),
                previousYearCumulative == null ? null : previousYearCumulative.getTotalPersonFlow(),
                twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getTotalPersonFlow()));
                        EXCEL_TWO_DECIMAL_PERCENT),
                ratio(currentCumulative.getTotalPersonFlow(),
                        twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getTotalPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT)
        });
    }
    private String[] transportRow(BigDecimal current, BigDecimal previousDay, BigDecimal previousYear,
                                  BigDecimal twoYearsAgo, BigDecimal currentCumulative,
                                  BigDecimal previousYearCumulative, BigDecimal twoYearsAgoCumulative) {
                                  BigDecimal previousYearCumulative, BigDecimal twoYearsAgoCumulative,
                                  int ringRatioExcelScale) {
        return new String[]{
                number(current),
                ratio(current, previousDay),
                ratio(current, previousYear),
                ratio(current, twoYearsAgo),
                ratio(current, previousDay, ringRatioExcelScale),
                ratio(current, previousYear, EXCEL_ONE_DECIMAL_PERCENT),
                ratio(current, twoYearsAgo, EXCEL_ONE_DECIMAL_PERCENT),
                number(currentCumulative),
                ratio(currentCumulative, previousYearCumulative),
                ratio(currentCumulative, twoYearsAgoCumulative)
                ratio(currentCumulative, previousYearCumulative, EXCEL_ONE_DECIMAL_PERCENT),
                ratio(currentCumulative, twoYearsAgoCumulative, EXCEL_ONE_DECIMAL_PERCENT)
        };
    }
@@ -322,11 +349,10 @@
        return builder.toString();
    }
    private String ratioPhrase(BigDecimal current, BigDecimal base) {
        if (current == null || base == null || base.abs().compareTo(EPSILON) < 0) return "无可比数据";
        BigDecimal ratio = HolidayNumberUtils.divide(current, base);
        BigDecimal percent = HolidayNumberUtils.multiply(ratio.subtract(BigDecimal.ONE), BigDecimal.valueOf(100));
        if (percent == null || percent.abs().compareTo(EPSILON) < 0) return "持平";
    private String ratioPhrase(BigDecimal current, BigDecimal base, int excelPercentScale) {
        BigDecimal percent = bulletinPercent(current, base, excelPercentScale);
        if (percent == null) return "无可比数据";
        if (percent.signum() == 0) return "持平";
        return (percent.signum() > 0 ? "增长" : "下降") + HolidayNumberUtils.format1(percent.abs()) + "%";
    }
@@ -351,18 +377,30 @@
        BigDecimal total = currentCumulative == null ? null : currentCumulative.getTotalPersonFlow();
        return String.format("假期以来,全省全社会跨区域人员流动量累计%s万人次,比2025年同期%s,比2024年同期%s。",
                formatNumber(total),
                ratioPhrase(total, previousYearCumulative == null ? null : previousYearCumulative.getTotalPersonFlow()),
                ratioPhrase(total, twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getTotalPersonFlow()));
                ratioPhrase(total, previousYearCumulative == null ? null : previousYearCumulative.getTotalPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
                ratioPhrase(total, twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getTotalPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT));
    }
    private String ratio(BigDecimal current, BigDecimal base) {
        if (current == null || base == null || base.abs().compareTo(EPSILON) < 0) return "—";
        BigDecimal ratio = HolidayNumberUtils.divide(current, base);
        if (ratio == null) return "—";
        BigDecimal percent = HolidayNumberUtils.multiply(ratio.subtract(BigDecimal.ONE), BigDecimal.valueOf(100));
    private String ratio(BigDecimal current, BigDecimal base, int excelPercentScale) {
        BigDecimal percent = bulletinPercent(current, base, excelPercentScale);
        if (percent == null) return "—";
        BigDecimal rounded = percent.setScale(1, RoundingMode.HALF_UP);
        return rounded.signum() == 0 ? "0%" : rounded.toPlainString() + "%";
        return percent.signum() == 0 ? "0%" : percent.toPlainString() + "%";
    }
    /**
     * Mirrors the source Excel percentage cell format, then applies the bulletin's one-decimal rule.
     * Excel stores full precision but displays 0.0% or 0.00%; reviewers compare against that display.
     */
    private BigDecimal bulletinPercent(BigDecimal current, BigDecimal base, int excelPercentScale) {
        if (current == null || base == null || base.abs().compareTo(EPSILON) < 0) return null;
        BigDecimal ratio = HolidayNumberUtils.divide(current, base);
        if (ratio == null) return null;
        BigDecimal percent = HolidayNumberUtils.multiply(ratio.subtract(BigDecimal.ONE), BigDecimal.valueOf(100));
        if (percent == null) return null;
        BigDecimal displayed = percent.setScale(excelPercentScale, RoundingMode.HALF_UP);
        return displayed.setScale(1, RoundingMode.HALF_UP);
    }
    private String number(BigDecimal value) {