Files
InboundVerify/docs/2026-08-03-应到驱动差缺统计重构-实现总结.md
Misaka 36b204abe5 refactor(db_compare): 差缺统计改为应到驱动,以出库日为批次归属日
将未到统计从实到驱动(实到锚点反推批次)改为应到驱动(batch_out_date 归属日直接取应到批次),以吸收应到任务提前 1~2 天提交的扰动(韵达固定 +1、中通偶发 +1)。

- expected_record 新增 out_date / batch_out_date + 索引;入库解析出库时间并聚合批次归属日,支持历史回填

- 新增 compare_site_outdate 应到驱动入口,保留 compare_site_date 实到驱动作对照

- 未到任务 / POST /compare / 全站汇总切换到应到驱动

- 附现状梳理、设计、实现总结三篇文档
2026-08-03 22:52:28 +08:00

121 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 应到驱动差缺统计重构 · 实现总结(备忘录)
> 完成日期2026-08-03
> 分支:`refactor/undelivered-by-expected`
> 关联文档:`docs/2026-08-02-未到差缺统计逻辑现状梳理.md`、`docs/2026-08-03-应到驱动差缺统计重构-design.md`
> 状态:已完成并验证,待提交
---
## 一、背景
原差缺统计为**实到驱动**:以目标日实到扫描为锚点 → 反推交接批次 → 展开批次全量应到 → 逐单比对。
业务部门日常以**应到数据**为依据,且应到任务可能被**提前提交 1~2 天**(韵达固定提前 1 天),导致:
- 完全按应到统计会出现假差缺;
- 实到驱动会把历史批次混入当天报表1 单命中即整批展开)。
重构目标:改为**应到驱动**,并用「出库日」作为批次归属日,自动吸收提前提交。
## 二、核心口径
- **批次归属日 = 批次内运单「出库时间」日期众数,并列取较早**(字段:`expected_record.batch_out_date`)。
- **统计 D 日差缺 = 取所有 `batch_out_date = D` 的应到批次**,展开全量应到 → 查全量实到 → 逐运单比对。
- 无论批次在 D / D-1 / D-2 哪天下载(`business_date` 为何),只要出库日 = D 即纳入 D 日统计。
## 三、数据实证7 月全量)
| 站点 | 下载日=出库大头日 | 出库大头日=下载日+1 |
|------|-----------------|-------------------|
| 顺心 | 83/83100% | 0 |
| 中通 | 28/3093% | 2`071402``071902` |
| 韵达 | 0 | 29/29100%,固定提前) |
| 安能 | 31/31100% | 0 |
关键结论:
- **出库大头日 ≈ 实到峰值日**154/158 一致97.5%)——"出库日"基本等于"这批货实际到的那天"。
- "提前提交"的批次(中通 `071402` 出库 7/15、`071902` 出库 7/20在出库时间上如实体现真实归属日。
- 四站应到文件的「出库时间」字段非空率 100%,且已完整保留在 PG `expected_record.raw` JSONB 中。
## 四、代码改动
### 4.1 数据层
- `schema.sql``expected_record` 新增 `out_date DATE`(运单出库日)、`batch_out_date DATE`(批次归属日)+ `idx_expected_out_date` / `idx_expected_batch_out_date` 索引。
- `store.py`
- `_ingest_expected`:解析「出库时间」写 `out_date`;按交接单号聚合出库日众数(并列取较早)写 `batch_out_date`
- 新增 `_batch_out_date_map()` 辅助函数。
- 新增 `backfill_out_date()` + CLI 子命令 `backfill-out-date`,历史数据一次性回填。
### 4.2 比对层
- `db_compare.py`
- 新增 `compare_site_outdate(site, target_date)`:应到驱动入口,`WHERE batch_out_date = target_date` 取批次 → 展开 → 比对。
- 保留 `compare_site_date()`(实到驱动)作对照,不删除。
- `_target_date_for()` 改为默认取今天(不再依赖 actual_offset
- `build_full_report()` 改用 `compare_site_outdate`
- `runtime.py``_site_undelivered_handler` 切到应到驱动,锚点日期默认今天。
- `cli/server.py``POST /compare` 切到应到驱动。
## 五、实施与验证
### 5.1 数据迁移
```
python -m inbound_verify.store init # 建表/补列(幂等)
python -m inbound_verify.store backfill-out-date # 历史回填
```
回填结果:
- `out_date`:顺心 3290 / 中通 5419 / 韵达 1640 / 安能 3459 条,共 13808 条。
- `batch_out_date`:顺心 93 / 中通 35 / 韵达 32 / 安能 35 个批次。
- 抽样核对 PG `out_date` vs 归档 Excel 出库日一致率 96~100%。
### 5.2 批次归属验证
| 批次 | 下载日 | batch_out_date | 预期 |
|------|--------|----------------|------|
| 中通 `...071401` | 7/14 | 7/14 | 正常 |
| 中通 `...071402` | 7/14 | **7/15** | 提前提交归位 |
| 中通 `...071901` | 7/19 | 7/19 | 正常 |
| 中通 `...071902` | 7/19 | **7/20** | 提前提交归位 |
| 韵达 `...07312001` | 7/31 | 8/1 | 固定 +1 |
| 顺心/安能 | — | = 下载日 | 无扰动 |
### 5.3 回溯对照7/02~7/31
新应到驱动 vs 旧实到驱动,差异方向符合设计:
- 旧驱动混入历史批次(如中通 7/12 旧 236 件 vs 新 11 件;顺心 7/13 旧 59 vs 新 1
- 新驱动只统计出库日=当天批次,数字更聚焦。
- 个别日期新驱动未到偏大(如韵达 7/03、安能 7/29属"当天出库、次日扫描"的真实差缺口径。
### 5.4 接口联调(真实后端)
| 用例 | 结果 |
|------|------|
| `POST /compare` 韵达 2026-08-02 | 批次 1 个(`...08012001`),差缺 2 件/2 单(`988350756``988415586`),历史批次不再混入 |
| `POST /compare` 中通 2026-07-15 | 提前提交批次 `...071402` 正确归位到 7/15 |
| `__compare__/compare` 全站汇总 2026-08-01 | 顺心 17 件 / 中通 23 件 / 韵达 0 件 / 安能 0 件,合计 40 件,报表正常生成 |
## 六、待确认 / 遗留事项
- `out_date` / `batch_out_date` 依赖站点「出库时间」字段语义(当前按"货物实际发出时间"处理,已与业务确认)。
- 批次内出库日并列众数取较早(已确认)。
- "无应到"时报表直接写(如实呈现,无特殊文案,已确认)。
- 实到驱动入口保留作对照(已确认)。
- `docs/2026-08-02-未到统计重构讨论纪要与下一步.md` 中记录的 18:28 直入入库等链路疑点,本重构未处理,留待后续。
## 七、附:涉及文件
| 文件 | 说明 |
|------|------|
| `schema.sql` | 表结构:新增 `out_date` / `batch_out_date` |
| `inbound_verify/store.py` | 入库解析 + 历史回填 |
| `inbound_verify/db_compare.py` | 应到驱动比对入口(保留实到驱动对照) |
| `inbound_verify/runtime.py` | 未到任务切到应到驱动 |
| `inbound_verify/cli/server.py` | `/compare` API 切到应到驱动 |