# 实到按「归属日当天扫描」提取修正 · 设计方案 > 日期:2026-08-04 > 分支:`refactor/undelivered-by-expected` > 状态:已实施(2026-08-04 验证通过) > 目标:把差缺对比中的「实到货物数据」提取口径修正为「目标归属日当天实际扫描」,与业务口径对齐 --- ## 一、业务口径(与业务部门对齐后的最终陈述) 差缺对比在**归属日期(业务日期)D** 这一维度上进行: 1. **应到货物数据**:取**归属日 = D** 的应到批次(批次归属日 = 出库大头日,已实现为 `expected_record.batch_out_date`)。 - 站点"提前下载"(韵达固定提前 1 天、个别站点偶发提前 1~2 天)不影响取数——提前提交的批次按其**出库日**自动归属到 D 日。 2. **实到货物数据**:取 **D 日当天实际扫描**的记录(`actual_record.scan_time::date = D`),**全量、零加工**。 - "当天实际扫描"指扫描行为发生在 D 日,与运单属于哪个批次无关。 - 重复单号在入库时即被吸收(`actual_record` 唯一键 `(site, piece_no)` + upsert),对比层不再做任何加工。 3. 对取到的双方数据按运单号进行差缺对比。 **关键原则**:说"同一天"指的是**归属日期**,不是数据产生/下载的日期。 --- ## 二、现状问题 | 环节 | 现状 | 问题 | |------|------|------| | 应到提取 | `WHERE batch_out_date = D`(应到驱动入口) | ✅ 已对齐 | | 实到提取 | `WHERE waybill_no = ANY(批次运单)`,**无扫描日过滤** | ❌ 跨日扫描混入 | **实证案例(安能 08-03)**: - 08-03 实到下载文件 = 355 行,全部扫描于 08-03(获取层正确); - 今天 15:03 另有一次 08-04 实到下载(176 行),其中 **6 件扫描于 08-04、运单属于 08-03 批次**; - 对比 SQL 按运单号取实到 → 08-03 实到 = 350(当天)+ 6(次日)= **356**,与"当天实际扫描"定义不符。 根因:`db_compare.py` 三处取实到的 SQL 只按运单号匹配,未按扫描日过滤。 --- ## 三、修正方案 ### 3.1 实到提取统一为「目标日当天扫描全量」 | 函数 | 用途 | 改动 | |------|------|------| | `compare_site_outdate` | 汇总报表(应到驱动,主入口) | 实到 SQL 改为 `WHERE site=%s AND scan_time::date=%s`,取当天扫描**全量**(不再按批次运单过滤) | | `compare_site_date` | 实到驱动对照模式 | 同上(Step 1 锚点本已按当天扫描,Step 4 同步为全量口径) | | `compare_site_batch` | 单批次审计(无日期语境) | **保持全量**,docstring 注明"全批次全量实到,不受日期过滤" | 修正后统计口径: ``` 应到 = 归属日 D 的批次全量应到(交接件数,不变) 实到 = D 日当天实际扫描的全量记录(零加工) 未到 = max(0, 应到 − 实到)(件级汇总);差缺明细按运单匹配(足额/溢到跳过) ``` **去重说明**:单号去重已在入库时完成——`schema.sql` 对 `actual_record` 建 `UNIQUE(site, piece_no)`,`store.py` 以 ON CONFLICT DO UPDATE 写入(韵达另有 drop_duplicates)。对比层拿到的实到本就是去重后的数据,不再做任何额外加工。 ### 3.2 边界语义确认 | 场景 | 修正后行为 | |------|-----------| | 批次 D 的件扫描于 D+1(迟到件) | 计入 D+1 日实到(其扫描日在 D+1);D 日差缺按运单匹配判定,未到件照常呈现 | | 批次 D 的件提前扫描于 D−1 | 计入 D−1 日实到(扫描日为准),不入 D 日实到 | | 当天扫描但运单不在 D 日应到批次(孤儿) | 仍计入 D 日实到件数(实到=当天全量);差缺明细按运单匹配,无对应应到运单则不出现 | | 一件多扫(同日) | 入库唯一键 `(site, piece_no)` 已吸收重复,对比层零加工 | ### 3.3 不做的事(范围外) - 下载层各站查询日期口径已验证正确(08-03 四站文件 100% 当天扫描),**不改下载层**。 - 获取层"日期回读校验"(韵达/中通/顺心选完日期后未校验输入框值)另立改进项,不阻塞本次。 - 百世为站级日聚合(已扫/应扫,固定当天),不涉及。 --- ## 四、涉及改动清单 | 文件 | 改动 | |------|------| | `inbound_verify/db_compare.py` | `compare_site_outdate`、`compare_site_date` 两处实到 SQL 改为 `scan_time::date = 目标日` 全量取数;`compare_site_batch` docstring 注明口径差异;`_do_compare` 实到件数 = 实到记录全量 | 仅此一处文件;无需 schema 改动(去重本已由入库唯一键保证)。 --- ## 五、验证结果(2026-08-04 已重跑) 按新口径重跑 08-03 全站汇总(`build_full_report("2026-08-03")`): | 站点 | 应到 | 实到 | 未到 | |------|------|------|------| | 顺心 | 147 | 128 | 19 | | 中通 | 305 | 276 | 29 | | 韵达 | 54 | 53 | 1 | | 安能 | 351 | **355**(= 下载文件 355 行,零加工) | 0 | 08-04(同为全量口径):顺心 134、中通 214、韵达 102、安能 176。 - 安能 08-03 实到 355 与 `安能-实到货物数据.xlsx` 完全一致,验证"实到=当天扫描全量、零加工"成立。 - 差缺明细各运单"已到单号"扫描日全部 = 08-03。 - 报表已输出:`InboundVerify/output/应到未到数据.xlsx`。 --- ## 六、决策记录 - ✅ 实到 = 归属日当天实际扫描的全量(`scan_time::date = D`),对比时零加工。 - ✅ 单号去重由入库唯一键保证(`UNIQUE(site, piece_no)` + upsert),对比层不重复去重。 - ✅ `compare_site_batch` 保持全批次全量(审计工具语义,不按日过滤)。 - ✅ 差缺明细按运单匹配,足额/溢到跳过,未到件 = max(0, 应到 − 实到)。