xyc
3 天以前 dc0082bfbf706d94de4509db2338b32012f1aa3e
fix(holiday): 快报累计值与道路同比按Excel显示精度处理
4个文件已修改
75 ■■■■■ 已修改文件
AGENTS.md 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HANDOFF.md 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayBulletinService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/test/java/com/trafficaudit/holiday/service/HolidayBulletinServiceTest.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
AGENTS.md
@@ -33,9 +33,11 @@
## 验证注意
- 沙箱内 `Get-NetTCPConnection` 会因权限“拒绝访问”而静默失败,**不要**据此判定端口未监听;判断服务状态以 backend.log / frontend.log 为准。
- 用户要求:临时脚本/文件未经同意不删;导入失败要在页面显示行号;AI 分析结果要给结构化排版 + 逐字输出动画。
- **数据导入权限(2026-10-04 用户明确)**:除非用户明确要求助手导入,否则不要执行任何数据导入、覆盖或重导操作;用户自行导入的数据默认视为已完成,助手只做只读核对。
## 重要节假日快报与分析报告口径(2026-10-04 起)
- 快报百分比必须先按导入的 `2026年十一数据.xlsx` 对应单元格的数字格式生成显示值,再按快报统一 1 位小数取整。当前源表不是全局同一精度:多数单元格是 `0.0%`;`人员流动量汇总` 的当日环比 D/P/J、累计同比 V/W/Y/Z/AB/AC,以及水路当日环比 D 是 `0.00%`。
- 快报百分比必须先按导入的 `2026年十一数据.xlsx` 对应单元格的数字格式生成显示值,再按快报统一 1 位小数取整。当前源表不是全局同一精度:多数单元格是 `0.0%`;`人员流动量汇总` 的当日环比 D/P/J、累计同比 V/W/Y/Z/AB/AC,道路 `H`(同比2024年),以及水路当日环比 D 是 `0.00%`。
- `人员流动量汇总` 的累计值 U/X/AA 存的是亿单位,单元格格式为 `0.0000000_`;快报必须在换算万人次前先按亿单位 7 位小数生成显示值,再乘 10000 并按快报 2 位输出。对应总计、公路合计、非营业性小客车三项。
- 核对领导分析报告时,按该分析报告自身显示的小数位核对,不套用快报的 1 位小数口径;目标是复现人工在文档/Excel 上看到的值。
## 工作规范(2026-09-02 起;2026-09-27 按同事提交方式调整)
HANDOFF.md
@@ -5,6 +5,33 @@
> **协作约定(2026-08-31)**:说「提交」= 本地 commit + 推送到远端 git(origin main,Gitblit),不再只做本地 commit。
## 最新(2026-10-04)
### 10-04 上午:10 月 3 日数据导入并生成快报
- **源文件**:`D:\03_公司\02_项目\27_湖北省道路运输事业发展中心\10_业务\99_专项统计\01_重要节假日\2026\十一\10月3日\2026年十一数据10月3日.xlsx`(2026-10-04 10:11 更新);观测站原始数据为同目录 `观测站分日调查数据2026.10.3.xls`。
- **导入过程(修正)**:用户先于 10:13:34 导入主数据;助手随后在 10:17:41 又用同一文件重复导入了一次。接口返回 `passengerRows=14, vehicleRows=6, skippedRows=24, successRows=20, failRows=0`,未检测到 10 月 1、2 日历史数据变化。唯一键命中后仅更新 `updated_at`,没有新增重复行。
- **10 月 3 日入库值**:铁路 72.4、民航 5.7571、道路 73.12、水路 5.5668 万人次;高速 193.3634、普通国省道 323.7837 万辆次;合计人员流动量 1397.99694 万人次。
- **观测站数据(修正)**:用户先导入;助手重复导入时按同日覆盖重插,当前 2026-10-03 为 691 行 / 691 个唯一键,无重复;中小客流量合计 3,237,837 辆次(323.7837 万辆次),与普通国省道入库值一致。
- **快报**:已生成 `C:\Users\jcxiong\Downloads\湖北省国庆假期综合运输客流快报(10月3日).docx`。正文:当日 1398.00 万人次,环比下降 3.3%,同比 2025 下降 10.1%,同比 2024 下降 5.0%;累计 4486.63 万人次,同比 2025 下降 6.3%,同比 2024 下降 2.7%。
- **人工稿比对(10-04 更新)**:手工稿与系统修正版提取文本逐行一致;系统原版仅有两处累计数差异,已随本次代码修正消除。今日走势图功能暂停。
### 10-04 上午(2):快报累计数按 Excel 亿单位显示精度后再换算
- **问题**:系统原版与手工稿仅两处差异:非营业性小客车累计 `3961.76` 应为 `3961.77`;公路合计累计 `4189.31` 应为 `4189.32`。
- **根因**:源表 `人员流动量汇总!X5/AA5`(总计对应 `U5`)存的是亿单位,单元格格式为 `0.0000000_`。人先看到 7 位小数的显示值,再换算成万人次并保留 2 位;旧代码直接拿原始累计值换算后取 2 位,漏掉了 Excel 显示这一步。
- **修复**:`HolidayBulletinService` 新增 `displayedCumulativeWan(...)`,先按源单元格 7 位小数取整,再乘 10000,最后按快报 2 位输出;应用于总计、非营业性小客车、公路合计三处累计数。
- **验证**:新增单测覆盖 `3961.76472 -> 3961.77`、`4189.31472 -> 4189.32`、`4486.62652 -> 4486.63`;`mvn test` 全通过。重启后基于现有库重新生成 10 月 3 日快报,与手工稿提取文本逐行比对结果为 `NO DIFFERENCES`。本次未执行任何数据导入。
- **产物**:`_tmp_holiday_verify/bulletin_cumulative_display_2026-10-03.docx`。
- **状态**:待用户验收/提交;走势图功能仍暂停。
### 10-04 上午(3):快报其余 Excel 显示精度路径审计
- **审计范围**:逐项映射快报正文和表格的所有数值来源,核对源工作簿的实际单元格格式、Excel 显示值和代码传入的显示精度。
- **结论**:除已修复的累计值 `U/X/AA` 外,没有发现其他未处理的快报取值路径。源表直接参与快报的 `0.00%` 单元格共 11 处:`人员流动量汇总` D/P/J、V/W/Y/Z/AB/AC,道路 H(同比2024年),水路 D(当日环比);代码均先按源格式取 2 位,再按快报 1 位处理。
- **补修边界**:道路 H 原按 1 位源格式处理,10 月 3 日碰巧仍为 `6.6%`;改为先按 `0.00%` 得到 `6.62%` 后再取 1 位,边界值不会差 0.1 个百分点。
- **未纳入直接快报路径的格式**:`普通国省道` E/N 虽为 `0.00%`,快报使用的是 `人员流动量汇总` 汇总后的 P/R/T,不使用该两列;高速公路/普通国省道内部 4 位小数人员流动量同理只作为源公式中间列,不直接输出。
- **验证**:`mvn test` 全通过;重启后端后重新生成 10 月 3 日快报,与手工稿逐块、逐单元格比较无任何差异。10 月 2 日与手工稿的内容和表格文本一致,仅有 Word run 分段格式差异。`_tmp_holiday_verify/bulletin_after_road_format_2026-10-01/02/03.docx` 为本轮核对产物。
- **数据操作**:只读核对,未导入、覆盖或重导任何数据。
- **状态**:待用户验收/提交。
### 10-04:快报百分比按 Excel 单元格显示精度后再取 1 位
traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayBulletinService.java
@@ -44,6 +44,8 @@
    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;
@@ -139,7 +141,7 @@
                        EXCEL_ONE_DECIMAL_PERCENT),
                ratio(current.getNonBusinessPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getNonBusinessPersonFlow(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                number(currentCumulative.getNonBusinessPersonFlow()),
                numberCumulative(currentCumulative.getNonBusinessPersonFlow()),
                ratio(currentCumulative.getNonBusinessPersonFlow(),
                        previousYearCumulative == null ? null : previousYearCumulative.getNonBusinessPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
@@ -151,7 +153,7 @@
                number(current.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),
                ratio(current.getRoad(), twoYearsAgo == null ? null : twoYearsAgo.getRoad(), EXCEL_TWO_DECIMAL_PERCENT),
                number(currentCumulative.getRoad()),
                ratio(currentCumulative.getRoad(), previousYearCumulative == null ? null : previousYearCumulative.getRoad(),
                        EXCEL_ONE_DECIMAL_PERCENT),
@@ -166,7 +168,7 @@
                        EXCEL_ONE_DECIMAL_PERCENT),
                ratio(current.getRoadPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getRoadPersonFlow(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                number(currentCumulative.getRoadPersonFlow()),
                numberCumulative(currentCumulative.getRoadPersonFlow()),
                ratio(currentCumulative.getRoadPersonFlow(),
                        previousYearCumulative == null ? null : previousYearCumulative.getRoadPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
@@ -206,7 +208,7 @@
                        EXCEL_ONE_DECIMAL_PERCENT),
                ratio(current.getTotalPersonFlow(), twoYearsAgo == null ? null : twoYearsAgo.getTotalPersonFlow(),
                        EXCEL_ONE_DECIMAL_PERCENT),
                number(currentCumulative.getTotalPersonFlow()),
                numberCumulative(currentCumulative.getTotalPersonFlow()),
                ratio(currentCumulative.getTotalPersonFlow(),
                        previousYearCumulative == null ? null : previousYearCumulative.getTotalPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
@@ -376,7 +378,7 @@
                                       DailyMetrics twoYearsAgoCumulative) {
        BigDecimal total = currentCumulative == null ? null : currentCumulative.getTotalPersonFlow();
        return String.format("假期以来,全省全社会跨区域人员流动量累计%s万人次,比2025年同期%s,比2024年同期%s。",
                formatNumber(total),
                formatNumber(displayedCumulativeWan(total)),
                ratioPhrase(total, previousYearCumulative == null ? null : previousYearCumulative.getTotalPersonFlow(),
                        EXCEL_TWO_DECIMAL_PERCENT),
                ratioPhrase(total, twoYearsAgoCumulative == null ? null : twoYearsAgoCumulative.getTotalPersonFlow(),
@@ -407,6 +409,20 @@
        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);
traffic-audit-server/src/test/java/com/trafficaudit/holiday/service/HolidayBulletinServiceTest.java
@@ -26,6 +26,8 @@
        assertEquals("-4.5%", ratio(service, "95.551398", "100", 2));
        assertEquals("-0.1%", ratio(service, "99.955", "100", 2));
        assertEquals("0%", ratio(service, "99.955", "100", 1));
        assertEquals("6.7%", ratio(service, "106.649", "100", 2));
        assertEquals("6.6%", ratio(service, "106.649", "100", 1));
        assertEquals("1.0%", ratio(service, "101", "100", 1));
        assertEquals("0%", ratio(service, "100", "100", 1));
        assertEquals("0%", ratio(service, "10004", "10000", 1));
@@ -102,6 +104,22 @@
                text);
    }
    @Test
    void cumulativeAmountsUseExcelYiDisplayBeforeWanRounding() throws Exception {
        HolidayBulletinService service = new HolidayBulletinService(null);
        assertEquals("3961.77", cumulativeNumber(service, "3961.76472"));
        assertEquals("4189.32", cumulativeNumber(service, "4189.31472"));
        assertEquals("4486.63", cumulativeNumber(service, "4486.62652"));
    }
    private String cumulativeNumber(HolidayBulletinService service, String value) throws Exception {
        Method method = HolidayBulletinService.class.getDeclaredMethod("numberCumulative", BigDecimal.class);
        method.setAccessible(true);
        BigDecimal number = value == null ? null : new BigDecimal(value);
        return (String) method.invoke(service, number);
    }
    private Object comparableCumulative(HolidayBulletinService service, DailyMetrics current,
                                        DailyMetrics comparisonYear,
                                        Function<DailyMetrics, BigDecimal> selector) throws Exception {