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 / 全站汇总切换到应到驱动

- 附现状梳理、设计、实现总结三篇文档
This commit is contained in:
Misaka
2026-08-03 22:52:28 +08:00
parent bccf7cd396
commit 36b204abe5
8 changed files with 734 additions and 20 deletions

View File

@@ -224,6 +224,83 @@ def compare_site_date(site: str, target_date: str) -> CompareResult | None:
return None
def compare_site_outdate(site: str, target_date: str) -> CompareResult | None:
"""应到驱动差缺比对以「批次归属日batch_out_date」为准取应到。
与 compare_site_date实到驱动区别
1. 应到来源 = expected_record WHERE batch_out_date = target_date
2. 不再依赖实到锚点反推;应到空时返回 None明确"当日无应到"
3. 提前提交的批次按其出库日归属,自动归入正确日期
Args:
site: 站点名("顺心"/"中通"/"韵达"/"安能"
target_date: 目标业务日期 "YYYY-MM-DD"
Returns:
CompareResult 或 None当日无应到批次
"""
cfg = SITE_COMPARE_CONFIG.get(site)
if cfg is None:
print(f"[db_compare] 不支持的站点: {site}")
return None
try:
conn = _connect()
cur = conn.cursor()
# ── Step 1: 取目标日应到批次(按批次归属日)──
cur.execute(
"""
SELECT DISTINCT handover_no FROM expected_record
WHERE site = %s AND batch_out_date = %s
ORDER BY handover_no
""",
(site, target_date),
)
batches = [r[0] for r in cur.fetchall()]
if not batches:
print(f"[db_compare] {site} {target_date}: 当日无应到批次")
conn.close()
return None
# ── Step 2: 展开批次全量应到 ──
cur.execute(
"""
SELECT waybill_no, handover_no, handover_pieces
FROM expected_record
WHERE site = %s AND handover_no = ANY(%s)
ORDER BY handover_no, waybill_no
""",
(site, batches),
)
exp_rows = cur.fetchall()
if not exp_rows:
conn.close()
return None
all_wbs = [r[0] for r in exp_rows]
# ── Step 3: 取批次全量实到 ──
cur.execute(
"""
SELECT waybill_no, piece_no FROM actual_record
WHERE site = %s AND waybill_no = ANY(%s)
ORDER BY waybill_no, piece_no
""",
(site, all_wbs),
)
act_rows = cur.fetchall()
conn.close()
# ── Step 4: 逐运单比对 ──
return _do_compare(site, target_date, batches, exp_rows, act_rows, cfg)
except Exception as e:
print(f"[db_compare] {site} {target_date} 应到驱动比对异常: {e}")
return None
def compare_site_batch(site: str, handover_no: str) -> CompareResult | None:
"""按指定交接单号执行全批次比对(不依赖实到锚点)。
@@ -499,11 +576,9 @@ def _stats_to_dict(s: CompareStats) -> dict:
def _target_date_for(site: str) -> str:
"""4 站比对锚点today - actual_offset以实到扫描日为锚与 _site_undelivered_handler 一致)。"""
from inbound_verify import state_store # 懒导入,避免成环
offset = state_store.get_offset(site, "actual")
return (date.today() - timedelta(days=offset)).strftime("%Y-%m-%d")
"""4 站比对锚点(应到驱动):批次归属日默认取今天。
各站统一以出库日batch_out_date为准不再依赖站点偏移配置。"""
return date.today().strftime("%Y-%m-%d")
def _baishi_from_pg(cur, target: str):
@@ -555,7 +630,7 @@ def _baishi_from_pg(cur, target: str):
def build_full_report(date=None) -> str:
"""DB 版全站汇总报表4 站走 DB 比对、百世走 PG复用 compare.build_summary 渲染。
产出 output/应到未到数据.xlsx/report 下载。date=None 时各站按 actual_offset 算锚点(以实到扫描日为锚)。
产出 output/应到未到数据.xlsx/report 下载。date=None 时各站取今天为批次归属锚点(应到驱动)。
返回输出路径。"""
from inbound_verify import compare # 复用 build_summary / write_station / OUTFILE
@@ -584,7 +659,7 @@ def build_full_report(date=None) -> str:
continue
target = date or _target_date_for(name)
site_targets[name] = target
result = compare_site_date(name, target)
result = compare_site_outdate(name, target)
if result is not None:
results.append((name, _stats_to_dict(result.stats)))
_write_sheet(wb.create_sheet(name), result)