# 没有首次扫描时间的包裹处理逻辑 - 说明文档

**更新日期**: 2026-05-16  
**主题**: 边界情况处理 - 当包裹没有扫描记录时  
**状态**: ✅ 编译通过

---

## 问题说明

**场景**：如果一个交接单标签率达到了80%，但其中有些包裹**从未被扫描过**（即没有首次扫描时间），应该如何处理？

**当前SQL逻辑**：
```sql
WHERE l.Label IS NOT NULL AND l.Label != '' 
  AND (
    SELECT MIN(ls.CreatedAt)
    FROM label_scan_history ls
    WHERE ls.NeutralWaybillNumber = l.NeutralWaybillNumber
  ) > l.LabelRetrievedAt
```

**问题分析**：
- 当 `label_scan_history` 中没有记录时，`MIN(ls.CreatedAt)` 返回 **NULL**
- `NULL > l.LabelRetrievedAt` 的比较结果是 **NULL（未知）**
- 在 CASE WHEN 中，NULL 被视为 FALSE
- 这些包裹**不被计入"高标签率"的分子**
- 导致整个交接单的冻结标签率会**偏低**

---

## 处理方案

### 方案选择

**选择**：当没有首次扫描时间时，按**低标签率处理**

### 原因

1. **业务逻辑**：
   - 如果一个包裹从未被扫描，说明它**还未开始作业**
   - 标签的作用是在**作业过程中发挥作用**
   - 如果作业未开始，标签的可用性无法判断
   - 保守处理：默认认为标签在此时**不可用**

2. **风险规避**：
   - 避免高估标签率
   - 确保考核时间的合理性

3. **数据质量**：
   - 如果包裹从未被扫描，可能说明：
     - 包裹尚未送达仓库
     - 包裹信息不完整
     - 系统记录有缺失

### SQL实现细节

**当前逻辑**：
```sql
冻结标签率 = (最早扫描时间 > 标签推送时间的包裹数) / 总包裹数

处理规则：
IF 最早扫描时间 IS NOT NULL THEN
  IF 最早扫描时间 > 标签推送时间 THEN
    → 计入高标签率分子
  ELSE
    → 不计入高标签率分子
  END IF
ELSE
  -- 没有扫描时间
  → 默认不计入高标签率分子（按低标签率处理）
END IF
```

---

## 具体场景示例

### 场景1：交接单中的所有包裹都没有扫描记录

```
交接单A：
├─ 总包裹数：100
├─ 有标签的包裹：80
├─ 扫描过的包裹：0（全部未扫描）
├─ 最早扫描时间：NULL
└─ 冻结标签率计算：
   分子 = 0（因为没有满足"最早扫描时间 > 标签推送时间"的包裹）
   分母 = 100
   冻结标签率 = 0%（低标签率）

考核规则：按低标签率处理（完成即达标）
```

### 场景2：交接单中的部分包裹没有扫描记录

```
交接单B：
├─ 总包裹数：100
├─ 有标签的包裹：90
├─ 扫描过的包裹：70
│  ├─ 其中：标签推送时间 < 扫描时间的包裹：60
│  └─ 其中：标签推送时间 >= 扫描时间的包裹：10
└─ 未扫描的包裹：30
   └─ 这30个包裹不计入高标签率分子

冻结标签率计算：
分子 = 60（符合"最早扫描时间 > 标签推送时间"的包裹）
分母 = 100
冻结标签率 = 60%（低标签率）

考核规则：按低标签率处理（完成即达标）
```

### 场景3：交接单中大部分包裹有扫描记录，少数没有

```
交接单C：
├─ 总包裹数：100
├─ 有标签的包裹：95
├─ 扫描过的包裹：99
│  ├─ 其中：最早扫描时间 > 标签推送时间的包裹：85
│  └─ 其中：最早扫描时间 <= 标签推送时间的包裹：14
└─ 未扫描的包裹：1
   └─ 这1个包裹不计入高标签率分子

冻结标签率计算：
分子 = 85（满足条件的包裹）
分母 = 100
冻结标签率 = 85%（高标签率）

考核规则：按高标签率处理（16点分段）
```

---

## 对各个CTE的影响

### InterchangeUnitLabelRates

**计算逻辑**：
```sql
labeled_requests = COUNT(DISTINCT CASE 
                    WHEN l.Label IS NOT NULL 
                      AND MIN(ls.CreatedAt) > l.LabelRetrievedAt
                    THEN l.Id 
                  END)
```

**对没有扫描时间的包裹**：
- ✅ 自动不计入 labeled_requests
- ✅ 因此不会虚高冻结标签率

### DailyHighLabelRateShould

**逻辑**：根据 `label_rate_percent >= 80` 判断

**对影响**：
- 如果因为没有扫描记录导致冻结标签率 < 80%
- 该交接单会被分类到"低标签率"
- 所有包裹都按低标签率规则处理

### DailyLowLabelRateShould

**逻辑**：根据 `label_rate_percent < 80` 判断

**对影响**：
- 包含没有扫描记录的交接单
- 这些包裹按完成即达标处理
- 符合保守处理的原则

---

## 数据质量检查建议

### 建议1：监控未扫描的包裹

在报表中补充一个指标：
```
未扫描包裹数 = COUNT(DISTINCT 订单) WHERE 首次扫描时间 IS NULL
未扫描率 = 未扫描包裹数 / 总包裹数
```

### 建议2：定期检查异常

```
IF 未扫描率 > 某个阈值（例如5%） THEN
  → 报警，检查系统是否有问题
  → 检查数据导入是否完整
END IF
```

### 建议3：与现场对账

定期与现场对账，确认：
- 是否真的有包裹未被扫描
- 还是系统记录有缺失

---

## 总结

### 当前处理方式

```
没有首次扫描时间 
  ↓
不被计入高标签率分子
  ↓
冻结标签率偏低（或保持原来的水平）
  ↓
按低标签率规则处理（完成即达标）
```

### 优点

1. ✅ **安全保守**：避免高估标签率
2. ✅ **符合业务逻辑**：作业未开始时，标签不可用
3. ✅ **自动处理**：无需特殊编码，SQL逻辑自动应对
4. ✅ **考核公平**：低标签率的包裹按更宽松的规则考核

### 可能的改进

如果后续发现有频繁的未扫描现象，可以：
1. 加强数据导入的完整性检查
2. 增加数据质量监控指标
3. 与现场沟通，了解根本原因
4. 根据实际情况调整处理策略

---

## 编译状态

✅ **编译成功** (exit code 0)
- SQL逻辑已加注释，明确说明处理方式
- 无编译错误
- 业务逻辑清晰

