# 顺丰快递 opCode 状态码参考

顺丰 API `EXP_RECE_SEARCH_ROUTES` 返回的每条轨迹包含 `opCode`，是最可靠的状态信号。**禁止用文本匹配作为主判断依据**。

## 核心 opCode 映射

| opCode | firstStatusName | 映射状态 | 说明 |
|--------|----------------|---------|------|
| `54` | 已揽收 | `picked_up` | 顺丰已取件 |
| `30` | 运送中 | `delivering` | 运输/分拣中 |
| `31` | 运送中 | `delivering` | 到达中转站 |
| `36` | 运送中 | `delivering` | 离开中转站 |
| `204` | 派送中 | `delivering` | 快递员派送中 |
| `80` | 已签收 | `delivered` | 客户已签收 |

## 状态聚合（coarse mapping）

```python
COARSE = {
    'delivered':  ('delivered',  '已签收'),
    'delivering': ('in_transit', '运输中'),  # 含派送中
    'picked_up':  ('in_transit', '运输中'),  # 含已揽收
    'in_transit': ('in_transit', '运输中'),
    'no_data':    (None, None),
    'unknown':    (None, None),
    'error':      (None, None),
}
```

## 文本匹配陷阱

以下 remark 文本容易被误判，必须以 opCode 为准：

| remark 示例 | 含关键词 | opCode | 正确状态 | 文本匹配会误判为 |
|------------|---------|--------|---------|----------------|
| "已派送成功（钟颖），如有疑问..." | 派送 | 80 | 已签收 | 派送中 ❌ |
| "快件交给【吕海成】，正在派送途中" | 派送 | 204 | 派送中 | 派送中 ✓ |

## 判断函数（正确实现）

```python
def fine_status(routes, reason=''):
    if not routes: return ('no_data' if reason else 'unknown'), None
    last = routes[-1]
    op = last.get('opCode', '')
    
    # opCode 优先（最可靠）
    if op == '80':  return 'delivered', last
    if op == '204' or op == '30': return 'delivering', last
    if op == '54':  return 'picked_up', last
    
    # 文本兜底
    remark = last.get('remark', '')
    if '签收' in remark or '派送成功' in remark: return 'delivered', last
    if '派送' in remark: return 'delivering', last
    if '揽收' in remark: return 'picked_up', last
    
    return 'in_transit', last
```

## 月结卡号不匹配

顺丰 API 返回 `reasonRemark: "月结卡号不匹配"` 是**常规提示**，不是异常。表示查询需要手机号后4位做身份验证（`checkPhoneNo` 参数）。

- **有路由数据 + 该提示** → 查询成功（通过手机号验证拿到了轨迹），忽略提示
- **无路由数据 + 该提示** → 查询失败（手机号也验证失败），需核对收货人手机号末4位
