# 冻结标签率设计修正 - v2.0 更新

**更新日期**: 2026-05-16  
**版本**: v2.0 - 核心逻辑修正  
**状态**: ✅ 编译通过，已更新文档

---

## 核心修正

### 原问题
用户发现前一版本的设计逻辑有问题。

**前一版逻辑**（v1.0）：
- 分割线 = 最早的**包裹完成时间** （OverallScanStatus.首次成功时间）
- 比较 = `标签推送时间 > 最早完成时间`
- 含义 = "在作业过程中被标签化的包裹"

### 用户的核心洞察
"以最早的扫描时间作为作业开始时间，那么以这个时间作为分割线去比较标签推送时间，大于的部分的包裹数 除以100单，那么我就可以知道现场再作业时的标签率冻结值是多少了。"

**关键理解**：
- 分割线应该是 **最早的扫描时间** 而不是完成时间
- 比较逻辑应该是 **最早扫描时间 > 标签推送时间**（而不是相反）
- 含义转变为 **标签推送早于或同时作业开始时，该标签处于可用状态**

---

## 修正的核心变化

### 1. 时间点定义的修正

**变更前**：
```
分割线时间 = MIN(OverallScanStatus.首次成功时间)  -- 包裹完成时间
```

**变更后**：
```
分割线时间 = MIN(label_scan_history.CreatedAt)    -- 扫描时间
```

**理由**：
- 扫描时间更准确地代表"开始作业"的时刻
- 完成时间是作业的结束点，不适合作为作业开始的参考
- 扫描历史直接记录了作业时间，更加客观

### 2. 比较逻辑的修正

**变更前**：
```
标签推送时间 > 最早完成时间
→ 解释：在作业过程中被标签化
```

**变更后**：
```
最早扫描时间 > 标签推送时间
→ 解释：标签推送早于作业开始，标签已可用（高标签率）
```

**逻辑对比**：
| 场景 | v1.0 理解 | v2.0 理解 | v2.0 判断 |
|------|---------|---------|---------|
| 标签推送于09:00，开始扫描于10:00 | 高标签率（作业中被标签化） | 高标签率 | `10:00 > 09:00` ✓ |
| 标签推送于10:00，开始扫描于09:00 | 低标签率（作业前就有标签） | 低标签率 | `09:00 > 10:00` ✗ |
| 标签推送于10:00，开始扫描于10:00 | 低标签率（作业前就有标签） | 低标签率 | `10:00 > 10:00` ✗ |

**结论**：v2.0 的逻辑更清晰、更容易理解

---

## 具体修改清单

### 修改的代码片段

#### 1. InterchangeUnitLabelRates CTE（第735-765行）

**修改内容**：
```diff
- AND l.LabelRetrievedAt > (
-     SELECT MIN(oss2.首次成功时间)
-     FROM label_replace_requests l2
-     INNER JOIN OverallScanStatus oss2 ON l2.NeutralWaybillNumber = oss2.NeutralWaybillNumber
-     WHERE l2.BillOfLadingNumber = l.BillOfLadingNumber
-       AND l2.MasterPackageNumber = l.MasterPackageNumber
-       AND oss2.曾成功 = 1
- )
+ AND (
+     SELECT MIN(ls.CreatedAt)
+     FROM label_scan_history ls
+     WHERE ls.NeutralWaybillNumber = l.NeutralWaybillNumber
+ ) > l.LabelRetrievedAt
```

**变更说明**：
- 改为从 `label_scan_history` 获取最早扫描时间
- 比较关系反转：由 `推送时间 > 完成时间` 改为 `扫描时间 > 推送时间`

#### 2. DailyHighLabelRateShould CTE（第1026-1055行）

**修改内容**：
```diff
- SELECT 
-     lrr2.BillOfLadingNumber,
-     lrr2.MasterPackageNumber,
-     MIN(oss.首次成功时间) AS earliest_success_time
- FROM label_replace_requests lrr2
- INNER JOIN OverallScanStatus oss ON lrr2.NeutralWaybillNumber = oss.NeutralWaybillNumber
- WHERE oss.曾成功 = 1 AND oss.首次成功时间 IS NOT NULL
+ SELECT 
+     lrr2.BillOfLadingNumber,
+     lrr2.MasterPackageNumber,
+     MIN(ls.CreatedAt) AS earliest_scan_time
+ FROM label_replace_requests lrr2
+ INNER JOIN label_scan_history ls ON lrr2.NeutralWaybillNumber = ls.NeutralWaybillNumber
  GROUP BY lrr2.BillOfLadingNumber, lrr2.MasterPackageNumber
```

```diff
- WHERE lrr.LabelRetrievedAt > earliest_times.earliest_success_time
+ WHERE earliest_times.earliest_scan_time > lrr.LabelRetrievedAt
```

#### 3. DailyLowLabelRateShould CTE（第1058-1089行）

**修改内容**：
```diff
- SELECT 
-     lrr2.BillOfLadingNumber,
-     lrr2.MasterPackageNumber,
-     MIN(oss.首次成功时间) AS earliest_success_time
- FROM label_replace_requests lrr2
- INNER JOIN OverallScanStatus oss ON lrr2.NeutralWaybillNumber = oss.NeutralWaybillNumber
- WHERE oss.曾成功 = 1 AND oss.首次成功时间 IS NOT NULL
+ SELECT 
+     lrr2.BillOfLadingNumber,
+     lrr2.MasterPackageNumber,
+     MIN(ls.CreatedAt) AS earliest_scan_time
+ FROM label_replace_requests lrr2
+ INNER JOIN label_scan_history ls ON lrr2.NeutralWaybillNumber = ls.NeutralWaybillNumber
  GROUP BY lrr2.BillOfLadingNumber, lrr2.MasterPackageNumber
```

```diff
- WHERE (earliest_times.earliest_success_time IS NULL OR 
-        lrr.LabelRetrievedAt <= earliest_times.earliest_success_time)
+ WHERE (earliest_times.earliest_scan_time IS NULL OR 
+        earliest_times.earliest_scan_time <= lrr.LabelRetrievedAt)
```

---

## 文档更新

### 已更新的文档

1. **frozen_label_rate_design.md** - 详细设计文档
   - ✅ 核心概念：时间点定义改为扫描时间
   - ✅ 公式：改为 `(最早扫描时间 > 标签推送时间的包裹数) / 总包裹数`
   - ✅ SQL实现：所有CTE的比较逻辑已更新
   - ✅ 验证场景：示例已更新为新逻辑
   - ✅ 字段映射：字段对应关系已修正

2. **frozen_label_rate_implementation_summary.md** - 实施总结文档
   - ✅ 根本原因：已更新
   - ✅ 解决方案：已更新为扫描时间作为分割线
   - ✅ 核心修改：新逻辑的解释已更新
   - ✅ DailyHighLabelRateShould 和 DailyLowLabelRateShould 的说明已更新
   - ✅ 验证场景示例：已更新为新数值和说明

---

## 新旧逻辑的对比

### 示例：100个订单在同一交接单中

**场景数据**：
- 最早扫描时间（作业开始）= 2026-05-16 10:00:00
- 标签推送分布：
  - 09:00-10:00：30个（早于作业开始）
  - 10:00-11:00：50个（同时作业开始）
  - 11:00-12:00：20个（晚于作业开始）

**v1.0 逻辑** (错误的):
```
分割线 = 首次成功时间（设为10:30）
高标签率 = 推送时间 > 10:30 的包裹数 = 20
冻结标签率 = 20/100 = 20%
```

**v2.0 逻辑** (正确的):
```
分割线 = 最早扫描时间 = 10:00
高标签率 = 扫描时间 > 推送时间 的包裹数 = 30（09:00-10:00的）
冻结标签率 = 30/100 = 30%
```

**说明**：
- 30个包裹在作业开始前就已推送标签，属于高标签率
- 50+20=70个包裹在作业开始后或正在进行时推送，属于低标签率
- 冻结标签率 = 30%，按低标签率规则考核

---

## 编译验证

✅ **DAL项目编译成功**
- 编译时间：00:00:07.55
- 编译结果：已成功生成
- 错误数：0
- 警告数：25个（既存问题，与本修改无关）

---

## 关键字段更新

| 业务概念 | 旧数据源 | 新数据源 | 变更说明 |
|---------|--------|--------|--------|
| 作业开始时间 | OverallScanStatus.首次成功时间 | label_scan_history.CreatedAt | 改为直接使用扫描时间 |
| 高/低标签率判断 | `推送时间 > 完成时间` | `扫描时间 > 推送时间` | 逻辑反转，更符合业务 |
| 高标签率应该换单数 | 条件改变 | 条件改变 | 重新计算，更加准确 |
| 低标签率应该换单数 | 条件改变 | 条件改变 | 重新计算，更加准确 |

---

## 下一步

### 待测试项目
1. ✅ 代码编译成功
2. 在测试环境中验证新逻辑的准确性
3. 对比新旧数据，确保逻辑改进
4. 与现场实际情况的符合度验证

### 预期改进
- 📊 逻辑更清晰，更容易理解
- 📈 更准确地反映现场标签供应情况
- ✅ 彻底消除"成功数 < 考核通过数"的矛盾
- 🎯 为客户提供更有说服力的数据

---

## 相关文档链接

- [冻结标签率设计文档](./frozen_label_rate_design.md)
- [冻结标签率实施总结](./frozen_label_rate_implementation_summary.md)
- [考核时间设计分析](./assessment_time_design_analysis.md)
