Files
InboundVerify/docs/2026-07-31-四站点差缺对比逻辑审查报告.md
2026-07-31 14:48:24 +08:00

12 KiB
Raw Permalink Blame History

四站点差缺对比逻辑审查报告

审查日期2026-07-31 审查范围:顺心、中通、韵达、安能 四个站点的应到 vs 实到差缺对比逻辑 排除:百世(站点直供未到明细,不参与四站比对)


一、比对算法总览(四站共用)

compare.py:process() 对四个站点执行完全相同的算法步骤。站点间的差异仅由 domain.py:STATIONS 配置注入——列名映射 + 实到单号解析器。

步骤1: 读应到Excel → 按运单号去重keep-first → 构建 {运单号 → (交接单号, 交接件数=n)}
步骤2: 读实到Excel → 站点专用解析器 → 构建 {运单基号 → {已到单号集合}}
步骤3: 逐运单比对
        arrived_cnt >= n  → 足额到货,跳过
        arrived_cnt == 0  → 完全未到
        0 < arrived < n   → 部分未到
步骤4: 产出未到明细(交接单号 | 运单号 | 总件数 | 已到单号1 | 已到单号2 | ...

核心口径

指标 口径
应到件数 交接件数(非录单件数);按运单号去重 keep-first
实到件数 单号去重计数(每扫描一件=一个单号)
未到件数 max(0, 应到件数 实到件数)
未到率 未到件数 ÷ 应到件数

未到明细输出约定

  • 仅列出短少运单(实到 < 应到)
  • 列出该运单实际已到的单号已到单号1, 已到单号2, ...
  • 不编造缺件子单号——实到扫描顺序号乱序,无法反推缺了哪个顺序号

统计指标

指标 含义
运单数 应到运单去重数
应到件 Σ 交接件数
已到件 Σ 实到单号去重数
未到件 max(0, 应到件 已到件)
涉及运单 存在短少的运单数
完全未到 整单零到货运单数
部分未到 部分缺件运单数

二、四站配置对照

domain.py:STATIONS — 所有差异集中于此配置表,比对核心代码不感知站点差异。

维度 中通 顺心 韵达 安能
应到文件 中通-应到货物数据.xlsx 顺心-应到货物数据.xlsx 韵达-应到货物数据.xlsx 安能-应到货物数据.xlsx
实到文件 中通-实到货物数据.xlsx 顺心-实到货物数据.xlsx 韵达-实到货物数据.xlsx 安能-实到货物数据.xlsx
应到-运单号列 运单号 运单号 运单号 运单号
应到-件数列 交接件数 交接件数 交接件数 交接件数
应到-交接单号列 交接单号 交接单号 交接单号 交接单号
实到-基号列 —(从复合串推导) 运单号 主单号 所属单号
实到-单号列 运单号(复合串) 子单号 子单号 扫描单号
解析器 arrived_pieces_zhongtong arrived_pieces_by_cols arrived_pieces_by_cols arrived_pieces_by_cols

三、逐站点详细分析

3.1 中通ZTO

业务逻辑

实到货物数据中的「运单号」为复合串,由三部分构成:

┌──────────┬────────────┬──────────┐
│ 运单号    │ 录单件数    │ 顺序号    │
│ (12位)    │ (4位)      │ (4位)    │
└──────────┴────────────┴──────────┘
  总长 20 位

示例: 330953527953 0001 0001
      ├─ 运单号 ─┤├录单┤├顺序┤
  • 运单号(12位): 与应到货物数据中的运单号对齐
  • 录单件数(4位): 该运单在系统中的录单总件数0占位
  • 顺序号(4位): 0占位0001, 0002, 0003, 0004

对比逻辑:

  1. 从应到数据取运单号 + 交接件数(非录单件数
  2. 从实到数据取复合串掐尾8位得运单基号完整串为子运单号
  3. 按运单基号分组,子运单号去重得实到件数
  4. 实到件数 < 交接件数 → 差缺

重要: 录单件数仅作参考。举例:某运单录单件数=4、交接件数=2实到最多出现2条数据。如果只出现了1条我们只知道差缺了无法判断具体差缺了哪一件(顺序号乱序)。

代码实现

domain.py:17-26 — 实到解析器:

def arrived_pieces_zhongtong(df):
    res = defaultdict(set)
    for v in df["运单号"]:
        v = str(v).strip()
        if len(v) > 8 and v[-4:].isdigit():
            res[v[:-8]].add(v)    # 基号=前12位, 已到单号=完整20位复合串
    return res

domain.py:48-56 — 站点配置:

{
    "name": "中通",
    "exp_qty": "交接件数",       # 应到件数口径:交接件数(非录单件数)
    "exp_wb": "运单号",
    "exp_jd": "交接单号",
    "arrived_pieces": arrived_pieces_zhongtong,
    "columns": ["交接单号", "运单号", "总件数"],
}

对齐情况: 对齐

代码实现与业务逻辑一致。v[:-8] 掐尾8位得12位运单基号保留完整复合串作为已到单号——不解析、不推断录单件数和顺序号的具体含义。


3.2 安能Anneng

业务逻辑

与中通相同的差缺对比逻辑。

安能实到数据同样为复合串,结构:运单号(12位) + 录单件数(4位) + 顺序号(4位)20位

与中通的关键区别:安能实到表有独立的「所属单号」列干净运单基号无需像中通那样从复合串掐尾8位推导基号。

代码实现

domain.py:76-86

{
    "name": "安能",
    "arrived_pieces": arrived_pieces_by_cols("所属单号", "扫描单号"),
    ...
}

安能使用 arrived_pieces_by_cols 而非 arrived_pieces_zhongtong——直接从「所属单号」列读基号、从「扫描单号」列读完整单号,效果等价。

差异点 中通 安能
实到基号来源 从复合串解析(v[:-8] 直接读「所属单号」列
实到单号来源 复合串本身(「运单号」列) 「扫描单号」列
解析器 arrived_pieces_zhongtong arrived_pieces_by_cols
最终产出 {基号 → {完整单号集合}} 相同

数据库验证

piece_no=61003282264500140014 → waybill_no=610032822645 (12位), total=0014, seq=0014

对齐情况: 对齐


3.3 顺心Shunxin⚠️

业务逻辑

顺心站点需区分两类运单:

A. 非SF开头运单占 97%:

实到「子单号」结构为两部分:

┌──────────┬──────────┐
│ 运单号    │ 顺序号    │
│ (不定长)  │ (3位)    │
└──────────┴──────────┘

示例: S71623721115 001
      ├─ 运单号 ──┤├顺序┤

注意:顺心子单号无录单件数部分(仅两部分)

对比时从实到取「子单号」列,按「运单号」分组,子单号去重得实到件数。

B. SF开头运单占 3%:

SF订单的「子单号」为随机号码(非由运单号衍生),不能用于差缺推导。

对比逻辑:

  1. 在实到数据中按「运单号」字段查找,统计出现次数
  2. 出现次数 < 交接件数 → 差缺
  3. 将找到的子单号(虽随机但可以列出来)填入「已到单号」列

SF订单的差缺判定只基于交接件数与实到运单号出现次数的比较,不依赖子单号的结构解析。

代码实现

domain.py:57-65

{
    "name": "顺心",
    "arrived_pieces": arrived_pieces_by_cols("运单号", "子单号"),
}

SF 与非 SF 没有任何区分处理。 所有运单走同一条路径。

数据库验证

非SF正常:

子单号=S71623721115001 → 运单号=S71623721115 + 后缀=001  ✅
子单号=S71934073996002 → 运单号=S71934073996 + 后缀=002  ✅

SF异常:

运单号=SF1225002296515 的两条实到记录:
  子单号=SF2025318183224  (随机SF号码)
  子单号=SF1225002296515  (与运单号相同)

数据中有 10 个SF运单存在多条实到记录。

对齐情况:⚠️ 部分对齐SF特殊逻辑缺失

检查项 代码现状 业务要求
非SF处理 arrived_pieces_by_cols("运单号", "子单号") 一致
非SF子单号结构 运单号 + 顺序号(两部分) 一致
SF处理 与非SF完全一致使用子单号去重 不能使用子单号,只按运单号行数计数
功能影响 子单号虽随机但值唯一,按目前逻辑也能正确去重计数 但语义不正确——SF子单号不由运单号衍生

3.4 韵达Yunda

业务逻辑

去重规则: 韵达实到数据存在重复行(同一子单号出现两次)。去重依据为「交接单号」字段:

  • 保留交接单号为的行
  • 丢弃交接单号非空的行

子单号结构: 两部分——单号 + 顺序号(无录单件数部分)。

┌──────────┬──────────┐
│ 主单号    │ 顺序号    │
│ (不定长)  │ (4位)    │
└──────────┴──────────┘

示例: 713326603 0003
      ├─主单号─┤├顺序┤

对比方式: 与中通/安能同——按「主单号」分组,「子单号」去重得实到件数,与交接件数比对。

代码实现

store.py:316-320(入库过滤):

if site == "韵达":
    # 韵达业务清洗:抛弃「交接单号」为空的行(派件/签收等其他扫描无交接单号),
    # 再按子单号去重一件多扫只留一条清洗后子单号已天然唯一drop 为保险)。
    df = df[df["交接单号"].astype(str).str.strip() != ""]    # ← 保留非空
    df = df.drop_duplicates(subset=[cm["piece"]], keep="last")

domain.py:67-75(比对配置):

{
    "name": "韵达",
    "exp_wb": "运单号",
    "arrived_pieces": arrived_pieces_by_cols("主单号", "子单号"),
}

对齐情况: 交接单号过滤逻辑完全相反

检查项 代码现状 业务要求
交接单号过滤 保留 != ""非空 保留 == ""
子单号结构 7133266030003 = wb713326603 + seq0003 一致
实到解析 arrived_pieces_by_cols("主单号", "子单号") 一致
compare.py 过滤 无过滤,所有行参与比对 需要过滤

影响分析:

  1. store.py 过滤反了——入库时留下了错误的数据集
  2. compare.py 完全没有交接单号过滤——如果原始 Excel 中同时存在空和非空行,比对阶段会全部读入导致重复计数
  3. 当前数据库中韵达 3483 条记录全部为非空交接单号——说明当前 Excel 数据中空交接单号行偏少或不存在,但这不改变逻辑错误

四、差异汇总

# 站点 问题 严重程度 影响范围
1 韵达 交接单号过滤反了:!= "" 应改为 == "" 严重 store.py:319 + compare.py 需新增过滤
2 顺心 SF运单无特殊处理与非SF混用子单号 ⚠️ 中等 domain.py 需新增SF判断分支
3 中通 录单件数0占位描述与实际数据完全一致 无影响 代码不依赖此区分

五、代码位置索引

逻辑 文件 行号
单站比对 process() compare.py 61-143
站点配置 STATIONS domain.py 46-87
中通实到解析器 domain.py 17-26
通用实到解析器 domain.py 29-42
单站未到文件写入 compare.py 258-273
全量汇总报告 compare.py 293-324
未到触发编排 runtime.py 478-497
韵达入库过滤(需修) store.py 316-320
顺心实到配置(需修) domain.py 57-65