# SQL 指标优化计划

## 一、当前SQL逻辑分析

### 现有指标计算
1. **当日新增换单数**：当天到货并且推送了标签数据的订单数量
2. **累计要换的总单数**：历史未完成换单数 + 当日新增换单数（通过滚动求和计算）
3. **当日标签推送数**：通过标签推送时间计算的当天标签推送数量
4. **当日换单成功数**：当天完成的换单包裹数（Result = 0）
5. **当天换单完成率**：当日完成数/(当日新增换单数+累计要换的总单数)
6. **24H换单率**：24小时内完成的包裹数/当日完成数

### 现有逻辑中的关键CTE
- **ArrivalFormsWithDate**：获取所有到货交接单基础信息
- **ArrivalRequests**：关联到货单与换单请求，计算考核时间（目前：取标签推送时间和到仓时间的较晚时间）
- **OverallScanStatus**：获取每个订单的首次成功信息
- **DailyBase**：每日基础统计

---

## 二、新需求分析与实现方案

### 需求1：修改包裹考核时间逻辑

**原逻辑**：取标签推送时间与到仓时间的较晚时间

**新逻辑**：根据标签率判断
- **标签率 >= 80%**（对客承诺90%）
  - 到仓时间 < 16:00：考核时间截止为**次日16:00**
  - 到仓时间 >= 16:00：考核时间截止为**次日23:59**
  
- **标签率 < 80%**（对客承诺90%）
  - 考核时间 = **包裹换单完成时间**

**实现方案**：
1. 在 `ArrivalRequests` CTE 中需要：
   - 计算每个订单所属客户的标签率
   - 根据标签率和到仓时间计算新的考核时间

2. 需要新增CTE计算客户的标签率：
   ```
   CustomerLabelRate: 计算每个客户的标签率 = 有标签的订单数/总订单数
   ```

3. 修改 `ArrivalRequests` 中的考核时间计算逻辑

### 需求2：修改24小时换单完成率

**原逻辑**：24H内完成数/当日完成数

**新逻辑**：(包裹换单完成时间 - 包裹考核时间 <= 0 的包裹数) / 当天应该换单数

**说明**：
- 包裹换单完成时间 <= 考核时间 的包裹视为24小时内完成
- 分母改为"当天应该换单数"而不是"当日完成数"

**实现方案**：
1. 创建新CTE计算每日24小时内完成的包裹数
2. 修改分母为当天应该换单数（历史未完成数+当日新增数）

### 需求3：新增指标 - 16:00前到仓的包裹数量

**定义**：当日到仓时间在16:00之前的包裹数量

**实现方案**：
在每日统计中新增计数：
```sql
COUNT(DISTINCT CASE 
    WHEN ar.到货日期 = dd.日期 AND HOUR(ar.到货时间) < 16
    THEN ar.RequestId 
END) AS 16点前到仓包裹数
```

### 需求4：新增指标 - 16:00后到仓的包裹数量

**定义**：当日到仓时间在16:00之后的包裹数量

**实现方案**：
在每日统计中新增计数：
```sql
COUNT(DISTINCT CASE 
    WHEN ar.到货日期 = dd.日期 AND HOUR(ar.到货时间) >= 16
    THEN ar.RequestId 
END) AS 16点后到仓包裹数
```

---

## 二.五、实现方式评估：SQL实现 vs 代码实现

### 方案对比

#### 方案A：直接在SQL中完整实现（推荐）
**优点**：
- 数据库层面完成所有计算，性能最优
- 减少应用层数据传输和处理
- 逻辑清晰，便于维护和调试
- 数据一致性更好

**缺点**：
- SQL复杂度高，维护难度大
- 调试相对困难

#### 方案B：SQL + C#代码混合实现
**优点**：
- 分离关注点，部分逻辑在应用层更清晰
- 便于测试和调试

**缺点**：
- 性能相对较差（多次数据传输）
- 代码复杂度反而更高
- 数据一致性难以保证

### 最终决策：采用方案A（SQL完整实现）
**理由**：
1. 虽然SQL复杂，但逻辑清晰且一次性完成
2. 涉及大量的CASE WHEN计算，在数据库层完成更高效
3. 新增的标签率计算本质上是CTE级别的操作，适合SQL实现

---

## 三、实现步骤

### 步骤1：分析当前代码结构
- [x] 已分析 `DailyLabelStatsChineseDto` 类结构
- [x] 已确认SQL所在文件位置

### 步骤2：修改DTO类添加新字段
- 在 `DailyLabelStatsChineseDto` 类中添加：
  - `LabelRate`（标签率 %）- 用于下推到前端展示整体标签率
  - `BeforeNoonArrivedCount`（16:00前到仓包裹数）
  - `AfternoonArrivedCount`（16:00后到仓包裹数）
  - `ShouldReplaceCount`（当天应该换单数 = 历史未完成+当日新增）
  - `Rate24HourModified`（修改后的24小时换单率，分母为当天应该换单数）

### 步骤3：修改SQL实现新逻辑
SQL文件位置：`d:\EPproject\LabelReplaceServer\src\DAL\Repositories\LabelReplaceRepository.cs` (L709-1040)

#### 3.1 新增 `CustomerLabelRate` CTE
- 计算每个客户的标签率 = (有标签的订单数) / (总订单数)

#### 3.2 修改 `ArrivalRequests` CTE
- 添加客户标签率信息
- 根据标签率和到仓时间重新计算考核时间：
  ```
  CASE
    WHEN 标签率 >= 0.8 THEN
      CASE
        WHEN HOUR(到仓时间) < 16 THEN DATE_ADD(DATE(到仓时间), INTERVAL 1 DAY) 16:00:00
        ELSE DATE_ADD(DATE(到仓时间), INTERVAL 1 DAY) 23:59:59
      END
    ELSE
      包裹换单完成时间
  END AS 考核时间
  ```

#### 3.3 修改 `DailyBase` CTE
- 添加16:00前和16:00后到仓包裹数的统计

#### 3.4 新增/修改24小时完成数计算CTE
- 重新计算基于新考核时间的24小时内完成数

#### 3.5 修改最终SELECT语句
- 添加新的指标列输出
- 修改24小时换单率的分母

### 步骤4：修改C#代码映射新字段
- 在 `GetDailyLabelStatsChineseAsync()` 方法中添加新列的映射

### 步骤5：验证和测试
- 检查SQL语法
- 验证数据准确性
- 测试边界情况

---

## 四、新增DTO字段映射与标签率计算

### SQL输出新列
1. `标签率` → 系统整体的标签率（有标签的订单数/总订单数）
2. `16点前到仓包裹数` → `BeforeNoonArrivedCount`
3. `16点后到仓包裹数` → `AfternoonArrivedCount`
4. `当天应该换单数` → `ShouldReplaceCount`
5. `修改后的24小时换单率` → `Rate24HourModified` 或保持原 `Rate24Hour` 字段

### 标签率计算方式

**概念澄清**：
- **交接单(Handover)**：到货交接单，由 HandoverNumber 标识
- **订单(Request)**：换单请求记录(label_replace_requests)
- **关系**：一个交接单可以关联多个订单（通过 BillOfLadingNumber 或 MasterPackageNumber 匹配）
- **总订单数**：一个交接单关联的所有 label_replace_requests 的总数

**客户标签率计算逻辑**：
- 每个交接单所属一个客户
- 客户标签率 = 该客户下有标签的订单总数 / 该客户下的总订单数
- 系统标签率 = 全系统有标签的订单总数 / 全系统总订单数

**在SQL中计算系统标签率**（在最终SELECT时新增）：
```sql
CONCAT(ROUND(
  (SELECT COUNT(DISTINCT l.Id) FROM label_replace_requests l 
   INNER JOIN arrival_handover_forms a 
   ON l.BillOfLadingNumber = a.HandoverNumber OR l.MasterPackageNumber = a.HandoverNumber
   WHERE l.Label IS NOT NULL AND l.Label != '') 
  /
  (SELECT COUNT(DISTINCT l.Id) FROM label_replace_requests l 
   INNER JOIN arrival_handover_forms a 
   ON l.BillOfLadingNumber = a.HandoverNumber OR l.MasterPackageNumber = a.HandoverNumber) 
  * 100, 2
), '%') AS 系统标签率
```

### C#映射代码
在 `GetDailyLabelStatsChineseAsync()` 方法的reader映射中添加新字段：
```csharp
// 注意：系统标签率是常数（不随日期变化），在每行数据中值相同
LabelRate = reader["系统标签率"] as string ?? "0.00%",
BeforeNoonArrivedCount = reader["16点前到仓包裹数"] != DBNull.Value ? Convert.ToInt32(reader["16点前到仓包裹数"]) : 0,
AfternoonArrivedCount = reader["16点后到仓包裹数"] != DBNull.Value ? Convert.ToInt32(reader["16点后到仓包裹数"]) : 0,
ShouldReplaceCount = reader["当天应该换单数"] != DBNull.Value ? Convert.ToInt32(reader["当天应该换单数"]) : 0,
```

---

## 五、关键数据表结构回顾

- **arrival_handover_forms**: 到货交接单表
  - `ReceiptTime`: 到仓时间（UTC-5）
  - `HandoverNumber`: 交接单号
  
- **label_replace_requests**: 换单请求表
  - `NeutralWaybillNumber`: 中性运单号
  - `Label`: 标签
  - `LabelRetrievedAt`: 标签推送时间
  - `CustomerId`: 客户ID
  
- **label_scan_history**: 扫描历史表
  - `CreatedAt`: 扫描时间（UTC）
  - `Result`: 扫描结果（0=成功）

---

## 六、实现注意事项

1. **时区处理**：确保所有时间比较统一使用UTC-5时区
2. **标签率计算**：需要明确如何定义"总订单数"（所有订单还是特定条件的订单）
3. **考核时间精确性**：新逻辑涉及时间戳的精确比较，需谨慎处理
4. **向后兼容性**：可能需要在Excel导出功能中也添加新列
5. **性能考虑**：大量的CASE WHEN和时间转换可能影响性能，需监控

