# 物流「合并发出」关联 —— 实现与教训

日期：2026-09-30 ｜ 归属：zone-b ｜ 关联技能：`sf-express-tracking`（**default profile，不可从 zone-b 修改**，故本文件为 zone-b 侧副本）

## 起因

用户报 `ZHBR260921024` 工作台物流缺失但实际已到货 → 追查后发现该单数据本身是好的，
真问题是「**无物流单号**」这一类订单的工作台呈现，以及「合并发出」信息从未进入数据管道。

## 三类成因（1580 单实测）

| 成因 | 判据 | 单数 |
|---|---|---|
| 合并发出（运单号记在对方单上） | 飞书备注写明「与XX单合并发货」 | 3 |
| 疑似合并（同客户 + 同发货日，对方带单号） | 备注未写明 → **不自动关联**，只提示 | 43 |
| 真未录单号 | 无备注或备注与物流无关 | 229 |
| 手机号校验失败 | 顺丰 20026 | 17 |
| 顺丰查无路由 | 顺丰 20023 | 27 |
| 正常 | — | 1261 |

## 关键发现：备注字段从未被拉取

飞书销售订单（`xsguanli`.`销售订单`）有两个备注字段，`数据/pull_orders.py` 的 `COLUMNS`
原来只声明 13 个字段、**这两个都没拉** → 工作台永远看不到「与XX单合并发货」。已补：

| 字段 key | 字段名 | 管道落点 |
|---|---|---|
| `field_5e49e1` | 备注(内部)# | `remarkInternal` |
| `field_a1ada1` | 备注(客户)# | `remarkCustomer` |

- 用 MQL 找字段：`mcp__feishu_project__list_workitem_field_config(project_key='xsguanli', work_item_type='xsdd', field_query='备注')`
- ⚠️ MQL 查询里工作项类型要用**中文名** `销售订单`（用 `xsdd` 报找不到）
- ⚠️ MQL 返回的 `count` 是**全空间总数、不是过滤后条数**，别信；看返回的 items 才是过滤结果

## 实现（`脚本/shipment_pipeline.py::attach_merge_links`）

```
① 掃所有订单备注，命中合并语义者抽出其中提到的 ZHBR/FO/CGDD 单号 → fwd / back 双向表
② 每个「无运单号」订单：候选 = fwd(自己点名的) + back(点名自己的)，只取其中有运单号的
③ 同一运单号常被多张订单共用（实测 84 个运单号覆盖 197 单）→ 取号最集中的那组
④ 写 mergedInto{orderId, trackingNo, status, statusLabel, lastRoute, shipDate, coMerged[]}
```

### 坑 1：合并语义正则必须排除伪命中

```
合并|一起发|一并发|同单发|并单发|上一单|上一个订单|同[^。；\n]{0,20}?(?:单|订单)[^。；\n]{0,12}?发
```

**别用 `随货` / `合发` 当关键词** ——「合同原件随货发」会被误判成合并发货（实测 6 条伪命中）。

### 坑 2：🔴 必须排序保证确定性

集合迭代顺序不稳定 → 同一份数据两次跑出**不同的并入对象**
（实测 `ZHBR260818022` 一次挂 `032`、一次挂 `033`）。

修法：候选先 `sorted(set(...))`，再按 (运单号出现次数, 单号) 排序取首选。
验证：连跑两次，比对 `mergedInto` 列表必须逐字相同。

### 坑 3：一个运单号被多单共用时别丢信息

`ZHBR260818032` / `ZHBR260818033` 是**同一单的两条记录、共用同一运单号**，
且两张备注都写「与 ZHBR260818022 合并发货」→ 022 是跟两张报告合并成一件包裹。
故 `coMerged[]` 要完整记录同运单的伙伴单，不能只留一个。

## 工具与产物

| 文件 | 用途 |
|---|---|
| `脚本/shipment_pipeline.py::attach_merge_links` | 管道内自动建关联（每轮跑） |
| `脚本/enrich_shipment_remarks.py [--apply]` | 立即回填备注 + 重算关联，**不必等下一轮全量管道** |
| `脚本/gen_shipment_gap_report.py` | 出六分类缺失清单 HTML |
| `销售分析/工作台物流信息缺失清单_<日期>.html` | 交付物 |
| `工具/index.html` + `工具/app.html`（双入口） | 无单号但有 `mergedInto`：单号列显示 `对方运单号 🔗`、状态列显示 `对方状态 🔗并入`、详情面板紫色横幅 |

## 部署

`脚本/cb_sync.sh`（不含顺丰管道步骤，只上传数据 + 部署 `工具/index.html` → `/workbench/index.html`）。
部署后**必须回读 CDN 验证**：拉 `数据/shipment_status.json` 比对 `mergedInto`、
拉 `workbench/index.html` 比对 sha256 与本地一致。

## 待办

- 备注里常出现「此订单无需发货，因客户更换抬头重新下单」「客户要求先发两包，留两包下次发」
  → 可再拆一类「**无需发货 / 部分发货**」，当前混在「未录单号 229」里
- 43 条「疑似合并」需下单人在飞书备注补一句「与XX单合并发货」，系统下轮自动挂上
