Files
InboundVerify/docs/2026-08-03-应到驱动差缺统计重构-design.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

7.6 KiB
Raw Permalink Blame History

应到驱动差缺统计重构 · 设计方案

日期2026-08-03 分支:refactor/undelivered-by-expected 状态:待审核 目标:差缺统计从"实到驱动(反推应到)"改为"应到驱动(以应到为统计起点)",并用「出库日」作为批次归属日,吸收"提前提交"扰动


一、背景与问题

1.1 业务诉求

  • 业务部门日常看的是应到货物数据:要"应到了哪些、实到了哪些、差缺是什么"。
  • 当前实现是实到驱动:以目标日实到扫描为锚点 → 反推交接批次 → 展开批次全量应到 → 逐单比对。
  • 方向与业务诉求相反,需重构为应到驱动

1.2 核心扰动:应到任务可能被提前提交

  • 应到数据理论当天提交(韵达固定提前 1 天),但实际可能提前 1~2 天。
  • 若完全按"下载日"统计应到,会把"提前提交、货次日才到"的批次计入当天,产生假差缺。

1.3 数据实证7 月全量)

downloads/archive/ 7 月应到文件 × PG 实到数据交叉验证:

站点 下载日=出库大头日 出库大头日=下载日+1
顺心 83/83100% 0
中通 28/3093% 2071402071902
韵达 0 29/29100%,固定提前)
安能 31/31100% 0

关键结论

  1. 出库大头日 ≈ 实到峰值日154/158 一致97.5%)——"出库日"基本等于"这批货实际到的那天"。
  2. "提前提交"的批次(中通 071402 出库 7/15、071902 出库 7/20出库时间上如实体现了真实归属日。
  3. 四站应到文件的「出库时间」字段非空率 100%,且已完整保留在 PG expected_record.raw JSONB 中(四站 100% 有值)。

二、核心口径

2.1 批次归属日 = 出库大头日

一个交接批次内,取运单「出库时间」的**日期众数(大头日)**作为该批次归属日:

批次归属日 = mode(运单.出库时间::date)
  • 顺心/安能:归属日 = 下载日(无扰动)
  • 中通:偶发提前批次自动归属次日(071402 → 7/15
  • 韵达:所有批次归属日 = 下载日 + 1与实到对齐不再依赖 expected_offset=1

2.2 统计 D 日差缺 = 取所有「出库日 = D」的应到批次

无论批次在 D / D-1 / D-2 哪天下载(business_date 为何),只要出库日 = D 即纳入 D 日统计:

目标批次 = expected_record WHERE site=? AND 出库日 = D

这样:

  • 提前提交的批次(下载于 D-1/D-2、出库于 D会被自然归入 D 日,不再遗漏也不提前计入;
  • 不再需要"实到为 0 → 抛弃/标记留存"的状态机;
  • 不再需要为韵达单独配置 expected_offset

三、统计流程compare_site_date 重构后)

flowchart TD
    S[查询 D 日差缺] --> S1
    S1["Step1 取应到批次<br/>expected_record<br/>WHERE site=? AND 出库日 = D"]
    S1 -->|无应到| X[返回:明确当日无应到]
    S1 -->|有批次| S2
    S2["Step2 展开批次全量应到<br/>waybill_no, handover_no, handover_pieces"]
    S2 --> S3
    S3["Step3 查这些运单的全量实到<br/>actual_record WHERE waybill_no = ANY(应到)"]
    S3 --> S4
    S4["Step4 逐运单比对_do_compare<br/>应到=交接件数 实到=子单号去重/SF行计数"]
    S4 --> R[CompareResult<br/>stats + 差缺明细]

3.1 与现实现的差异

环节 现状 重构后
应到来源 实到锚点反推批次 按出库日直接取应到批次
无实到表现 返回 None不产出 应到空 → 明确"无应到";应到有实到空 → 记差缺
历史批次混入 1 单命中即整批展开 按出库日隔离,天然干净
提前提交 无感知(靠实到锚定) 出库日归属,自动吸收

3.2 保留的能力

  • compare_site_batch(按交接单号精确比对)保留,供复核。
  • 实到驱动入口 compare_site_date 旧逻辑保留为对照模式(或通过配置切换),便于回溯验证差异。
  • 统计口径不变:应到=交接件数、实到=子单号去重(顺心 SF 行计数)、未到=max(0,应到−实到)、明细含已到单号。

四、数据层改造

4.1 新增列:out_date

expected_record 新增 out_date DATE(出库日,批次归属日的持久化依据):

ALTER TABLE expected_record ADD COLUMN IF NOT EXISTS out_date DATE;
CREATE INDEX IF NOT EXISTS idx_expected_out_date ON expected_record (site, out_date);
  • 入库时(store._ingest_expected):从 raw 的「出库时间」解析出日期写入 out_date
  • 历史数据回填:一次性 UPDATEraw->>'出库时间' 提取日期。
  • 出库时间缺失/解析失败 → out_date 置 NULL统计时回退 business_date(下载日),保证不丢数据。

4.2 business_date 语义保持不变

  • business_date 继续表示"下载目标日快照"(兼容就绪态派生 / 报表数据日期列 / 现有 API
  • 差缺统计改用 out_date,两者解耦,避免连锁改动。

4.3 出库时间字段来源(已验证)

站点 字段 覆盖率
顺心 出库时间 raw 100%
中通 出库时间 raw 100%
韵达 出库时间 raw 100%
安能 出库时间 raw 100%

五、边界与特殊处理

场景 处理
批次内出库日跨多天 大头日(众数);众数并列时取较早日期
出库时间缺失/解析失败 out_date 置 NULL回退 business_date
应到有、实到空 全部计入差缺(不再因"无实到"而返回 None
实到有、应到无(孤儿) 保持现状,报表/明细可另行提示,不混入应到统计
韵达 expected_offset 保留配置但重构后不再参与归属日计算(由 out_date 取代)
异常小批次 规模很小1~4 单)按常规逻辑走;如出现系统性偏差再单独讨论

六、涉及改动清单

文件 改动
schema.sql expected_recordout_date 列 + 索引
store.py _ingest_expectedout_date新增历史回填逻辑CLI
db_compare.py compare_site_date 改为按 out_date 取应到;新增"无应到"返回语义;保留批次入口与实到驱动对照
runtime.py _site_undelivered_handler 锚点日期逻辑随新口径调整
cli/server.py /compare 响应补充 out_date 语义说明;行为兼容
state_store.py 视需要暴露 out_date 相关查询
前端 dashboard 报表说明文案(批次归属=出库日);无结构变更预期

七、验证计划

  1. 单元验证out_date 回填后,抽样核对与归档 Excel 出库日一致。
  2. 回溯对照:用 7 月归档应到 + PG 实到,分别跑"旧实到驱动"与"新应到驱动",对比差缺差异,重点:
    • 韵达 7 月各日(应到归属日整体 +1 是否对齐实到)
    • 中通 7/14、7/19071402/071902 是否归入次日)
    • 顺心/安能(应无差异)
  3. 报表烟测:跑一次 __compare__/compare 全站汇总,人工核对韵达 08-02 数据。

八、决策记录(已确认)

  • 出库时间字段业务含义 = 货物实际发出时间(按此处理)。
  • 批次内出库日并列众数取法 = 取较早日期
  • "无应到"呈现 = 报表中直接写(如实呈现,无需特殊文案)。
  • 保留实到驱动入口,长期作为对照(不删除)。