# 顺丰物流管道 · 运维参考

## 数据链路

```
MCP飞书订单
  → pull_orders.py (去重+格式转换)
    → mcp_orders_input.json
      → shipment_pipeline.py (顺丰API查轨迹)
        → shipment_status.json
          → sf_delivery_watchdog.py (刷新+diff+推送)
            → sf_watchdog_state.json (状态快照)
```

## 顺丰 API 关键点

### 认证
- URL: `https://sfapi.sf-express.com/oauth2/accessToken`
- 参数: `partnerID=BRSWK5CQ08HV`, `secret=V7JDT0azGX93pnIXRyZe9zvlzY6fk1rC`, `grantType=password`

### 轨迹查询
- URL: `https://bspgw.sf-express.com/std/service`
- Service Code: `EXP_RECE_SEARCH_ROUTES`
- 批量上限: 10 单号/次
- `checkPhoneNo`: **校验每个运单独立的收件人手机号后4位**（不是宝锐注册号6804，不是月结卡号2900）
- 数据源: MCP `收货人联系电话#` → 管线映射为 `phone`

### 时序延迟（⚠️ 2026-07-30 发现）
- 订单创建后需**数小时** SF 才生成路由
- 同一单号上午查询返回"手机号校验失败"，下午返回13条路由
- 覆盖率上限由时延+手机号准确性共同决定
- 对策: watchdog 每日3次（9:00/15:00/22:00）覆盖延迟窗口

### 批量查询限制
- 每批最多10个运单，**共享同一个 checkPhoneNo**
- 所有运单的收件人手机号必须一致，否则部分失败
- 脚本按 `phone4` 分组批量查询，同组内手机号一致

### opCode 映射（用于 fine_status()）

| opCode | 含义 | 状态 |
|--------|------|------|
| 80 | 已签收 | delivered |
| 204 | 派送中 | delivering |
| 30 | 运送中/派送中 | delivering |
| 54 | 已揽收 | picked_up |
| 31/36 | 运输中 | in_transit |

**判断优先级**: opCode > 文本匹配。文本仅做兜底。

## 去重规则

### pull_orders.py
- MCP 层: 按 `work_item_id` 去重
- 输出层: 按 `单据编号`（name）去重，保留第一条
- 原因: 一个订单多个产品行 → 多个工作项，但物流维度只需一条

### shipment_pipeline.py
- SF 查询: 按 `物流单号` 去重（避免重复 API 调用）
- 输出: 信任上游已去重，直接透传

## shipment_status.json 结构

```json
{
  "updated_at": "2026-07-30 12:58:10",
  "total": 2054,
  "orders": [
    {
      "orderId": "ZHBR260727036",
      "customer": "上海芯超生物科技有限公司",
      "trackingNo": "SF1234567890",
      "phone": "13800138000",
      "creator": "陈溪子",
      "shipment": {
        "status": "delivered",
        "statusLabel": "已签收",
        "lastRoute": { "remark": "...", "opCode": "80", "acceptTime": "..." },
        "routes": [...],
        "routeCount": 12,
        "reason": "月结卡号不匹配"
      }
    }
  ]
}
```

## 异常订单分类

| 类型 | 特征 | 处理 |
|------|------|------|
| 无运单号 | trackingNo 为空 | 从 ERP 补录单号 |
| 顺丰查不到（时序） | 查询返回空路由/手机号校验失败 | 等数小时后重查 |
| 顺丰查不到（数据） | reason=月结卡号不匹配 + phone后4位不匹配 | 核对MCP收货人联系电话# |
| 状态未知 | status=unknown, 有 routes 但无法归类 | 排查 SF 返回的 opCode |

## 关键脚本

| 脚本 | 用途 |
|------|------|
| `sf_delivery_watchdog.py` | 全流程（每天3次 cron） |
| `sf_bulk_refresh.py` | 纯查询+写入（手动批量补全） |
| `shipment_pipeline.py` | MCP → SF 数据转换 |
