| | |
| | | |
| | | 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; |
| | | private static final int EXCEL_SEVEN_DECIMAL_YI = 7; |
| | | private static final BigDecimal TEN_THOUSAND = new BigDecimal("10000"); |
| | | |
| | | @Value("${holiday.bulletin-template:docs/专项统计/重要节假日/湖北省国庆假期综合运输客流快报模板.docx}") |
| | | private String bulletinTemplatePath; |
| | |
| | | throw new IllegalArgumentException("所选日期不在已配置的节假日范围内"); |
| | | } |
| | | DailyMetrics previousDay = current.getDayIndex() > 1 |
| | | ? statService.dailyMetrics(current.getYear(), current.getHolidayType(), current.getDayIndex() - 1) : null; |
| | | ? statService.ringRatioBaseMetrics(current.getYear(), current.getHolidayType(), current.getDayIndex()) : null; |
| | | HolidayCalendar previousDayConfig = configService.findPreviousDay(current.getYear(), current.getHolidayType()); |
| | | LocalDate previousDayDate = previousDayConfig == null || previousDayConfig.getHolidayDate() == null |
| | | ? current.getDate().minusDays(1) : previousDayConfig.getHolidayDate(); |
| | |
| | | } |
| | | |
| | | String currentTotal = formatNumber(current.getTotalPersonFlow()); |
| | | String cumulativeTotalYi = formatNumber(currentCumulative.getTotalPersonFlow() == null |
| | | ? null : HolidayNumberUtils.divide(currentCumulative.getTotalPersonFlow(), BigDecimal.valueOf(10000))); |
| | | 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())); |
| | | String paragraph2 = String.format("假期以来,全省全社会跨区域人员流动量累计%s亿人次,比2025年同期%s,比2024年同期%s。", |
| | | cumulativeTotalYi, |
| | | ratioPhrase(currentCumulative.getTotalPersonFlow(), |
| | | previousYearCumulative == null ? null : previousYearCumulative.getTotalPersonFlow()), |
| | | ratioPhrase(currentCumulative.getTotalPersonFlow(), |
| | | twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.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(); |
| | | if (!template.exists() || !template.isFile()) { |
| | |
| | | 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()), |
| | | number(currentCumulative.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), |
| | | numberCumulative(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_TWO_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()), |
| | | number(currentCumulative.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), |
| | | numberCumulative(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(), |
| | |
| | | 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(), |
| | | previousYearCumulative == null ? null : previousYearCumulative.getTotalPersonFlow(), |
| | | twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.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), |
| | | numberCumulative(currentCumulative.getTotalPersonFlow()), |
| | | ratio(currentCumulative.getTotalPersonFlow(), |
| | | previousYearCumulative == null ? null : previousYearCumulative.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) |
| | | }; |
| | | } |
| | | |
| | |
| | | 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()) + "%"; |
| | | } |
| | | |
| | |
| | | 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); |
| | | if (ratio == null) return "—"; |
| | | BigDecimal percent = HolidayNumberUtils.multiply(ratio.subtract(BigDecimal.ONE), BigDecimal.valueOf(100)); |
| | | private String cumulativeParagraph(DailyMetrics currentCumulative, |
| | | DailyMetrics previousYearCumulative, |
| | | DailyMetrics twoYearsAgoCumulative) { |
| | | BigDecimal total = currentCumulative == null ? null : currentCumulative.getTotalPersonFlow(); |
| | | return String.format("假期以来,全省全社会跨区域人员流动量累计%s万人次,比2025年同期%s,比2024年同期%s。", |
| | | formatNumber(displayedCumulativeWan(total)), |
| | | 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, 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) { |
| | | return value == null ? "—" : formatNumber(value); |
| | | } |
| | | |
| | | private String numberCumulative(BigDecimal value) { |
| | | return value == null ? "—" : formatNumber(displayedCumulativeWan(value)); |
| | | } |
| | | |
| | | /** |
| | | * Source cumulative cells U5/X5/AA5 are stored in 100-million units and displayed with seven decimals. |
| | | * Mirror that displayed precision before the bulletin converts the value to 10-thousand units. |
| | | */ |
| | | private BigDecimal displayedCumulativeWan(BigDecimal cumulativeWan) { |
| | | if (cumulativeWan == null) return null; |
| | | BigDecimal yi = cumulativeWan.divide(TEN_THOUSAND, EXCEL_SEVEN_DECIMAL_YI, RoundingMode.HALF_UP); |
| | | return HolidayNumberUtils.normalize(yi.multiply(TEN_THOUSAND)); |
| | | } |
| | | |
| | | private String formatNumber(BigDecimal value) { |
| | | if (value == null) return ""; |
| | | return HolidayNumberUtils.format2(value); |