# 顺丰路由查询管道（MCP销售订单 → shipment JSON）

2026-07-24 验证。流程：飞书MCP拉销售订单（含物流单号）→ 按物流单号去重 → 顺丰API批量查路由 → 输出 `{orders: [{...17字段, shipment: {...}}]}`。

## 顺丰开放平台 API

### 1. 获取 accessToken
```
POST https://sfapi.sf-express.com/oauth2/accessToken
Content-Type: application/x-www-form-urlencoded
body: partnerID=<partnerID>&secret=<secret>&grantType=password
→ {"accessToken": "..."}
```
- partnerID/secret 为宝锐顺丰月结账号凭据（存在 /tmp/gen_shipment.py、/tmp/merge_and_query.py 中；正式副本见本技能 scripts/sf_route_query.py）。
- Token 较长效，但大批量任务每 ~30 批刷新一次保险。

### 2. 批量查路由 EXP_RECE_SEARCH_ROUTES
```
POST https://bspgw.sf-express.com/std/service
Content-Type: application/x-www-form-urlencoded;charset=utf-8
body: partnerID=..&requestID=<uuid32>&serviceCode=EXP_RECE_SEARCH_ROUTES
      &timestamp=<unix秒>&accessToken=<token>&msgData=<json字符串>
```
msgData 结构：
```json
{"language":"zh-CN","trackingType":1,
 "trackingNumber":["SF...","SF...",...],   // 每批最多10个
 "methodType":1,
 "checkPhoneNo":"6866,6092,..."}            // 与trackingNumber一一对应的收/寄件人手机号后4位
```
- **每批最多 10 个单号**，批间 sleep 0.4s。
- `checkPhoneNo` 缺失时可用 `0000` 占位，但部分单会查不到路由 → 尽量从订单「收货人联系电话#」取后4位。
- 响应解析：`json.loads(resp["apiResultData"])["msgData"]["routeResps"]`，每项含 `mailNo` + `routes`（路由数组，**时间正序，最后一条是最新状态**）。

### 3. 状态分类逻辑（实测可靠）
```python
last = routes[-1] if routes else None
remark = last.get("remark","") if last else ""
if "签收" in remark:          status = "delivered"   # 已签收
elif "派送" in remark:        status = "delivering"  # 派送中
elif last and last.get("opCode")=="54": status = "picked_up"  # 已揽收
elif routes:                  status = "in_transit"  # 运输中
else:                         status = "no_data"     # 暂无路由
```

## 前置：MCP 订单去重（重要）

销售订单是**分录级**：一张订单多个物料行 = 多条工作项，共享同一 `物流单号`。
实测 500 行原始数据 → 433 行有单号 → **去重后仅 187 个唯一单号**。
**必须先按物流单号去重再调顺丰API**（省 60%+ 调用），同一单号取首行的订单字段即可。

## 输出 JSON 结构（与 shipment_status.json / mcp_30d_batchN.json 一致）

```json
{"updated_at": "...", "total": 187,
 "orders": [{
   "orderId":"ZHBR...","customer":"购货单位#","mcpCustomer":"销售订单关联客户",
   "product":"物料名称","sku":"货号#","batch":"出货批号#","spec":"规格型号",
   "unit":"销售单位","dept":"销售部门","orderType":"单据类型","status":"状态",
   "createDate":"创建时间","recipient":"收货人姓名","phone":"收货人联系电话#",
   "address":"收货地址","trackingNo":"SF...","productType":"产品类型",
   "shipment":{"mailNo":"SF...","status":"delivering","statusLabel":"派送中",
               "lastRoute":{...},"routes":[...],"routeCount":26}}]}
```

## 完整可运行脚本

`scripts/sf_route_query.py`（本技能内）— CLI 用法：
```bash
python3 scripts/sf_route_query.py orders.json -o shipment.json
# orders.json: [{"trackingNo":"SF...","phone":"176...","orderId":"..."},...]
```
MCP拉取+解析部分参照 feishu-project-mcp 技能的 `references/mql-persisted-output-parsing.md`（持久化大结果文件解析：两次 json.loads + 去 `\nlog_id:` 尾部 + value_type 三分支提取）。

## 实测参考数据（2026-07-24，近30天订单 OFFSET 500-950）

- 19 批 × 10 单全部成功，0 查询失败，耗时 < 2 分钟
- 状态分布典型值：派送中 159 / 运输中 23 / 暂无路由 4 / 已签收 1（发货后 1-10 天的在途单为主）
