# 考核时间设计合理性分析(修订版) ## 核心概念澄清 ### 用户业务规则说明 1. **标签率在考核时的状态**: - 标签率本身确实是动态的,在实时数据中持续变化 - **但在对交接单中的包裹进行作业时,该时刻的标签率就成为考核计算的"最终值"** - 即:开始对某个交接单进行考核操作时,此时的标签率被视为该交接单的考核基准标签率 2. **标签率跨越80%阈值的处理**: - 如果在考核期间标签率从<80%跨越到≥80%,需要特殊处理 - 依据:**单个订单的标签推送时间**(在 `label_replace_requests.sql` 中有该字段) - 逻辑:根据标签推送时间判断哪些包裹是在标签率还未达到80%时进行的作业 - 对于跨越阈值的包裹: - 推送时间在标签率<80%期间 → 按照低标签率逻辑(完成即达标) - 推送时间在标签率≥80%期间 → 按照高标签率逻辑(使用到仓时间的16点分段) 3. **单个订单维度的差异**: - 同一交接单中的不同订单,**可以根据各自的标签推送时间而有不同的考核时间** - 低于80%的订单:以换单完成时间作为考核时间 = 完成即达标 - 80%及以上的订单:以到仓时间的16点前/后作为区分 --- ## 问题陈述(修订) 在现有的报表统计逻辑中存在的问题: ### 问题1:标签率跨越阈值时的处理缺失 当交接单的标签率从<80%动态上升到≥80%时,**不能简单地用当前时刻的标签率来判断所有包裹的考核时间**。 应该区分: - **A类包裹**:标签推送时间在标签率<80%期间 → 考核时间 = NULL(完成即达标) - **B类包裹**:标签推送时间在标签率≥80%期间 → 考核时间 = 根据到仓时间的16点分段 ### 问题2:当前SQL中对单个订单差异的忽视 当前的SQL在计算 `InterchangeUnitLabelRates` 时: ```sql InterchangeUnitLabelRates AS ( SELECT l.BillOfLadingNumber, l.MasterPackageNumber, ROUND( COUNT(DISTINCT CASE WHEN l.Label IS NOT NULL ...) * 100.0 / COUNT(DISTINCT l.Id), 2 ) AS label_rate_percent FROM label_replace_requests l GROUP BY l.BillOfLadingNumber, l.MasterPackageNumber ) ``` 这是按交接单汇总的整体标签率,忽视了**单个订单在不同时间被标签化**的情况。 --- ## 解决方案设计 ### 核心设计思路(简化版) **关键洞察**: - 交接单中**最早的包裹完成时间** = 何时开始作业 - 交接单中**标签率达到80%的时间** = 何时达到高标签率 - 这两个时间的比较关系决定了包裹的考核规则 ``` 对于交接单中的每个包裹: 步骤1:确定两个关键时间点 ├─ 最早完成时间(earliest_success) = MIN(该交接单内所有包裹首次成功时间) ├─ 标签率达80%时间(label_rate_80_time) = 该交接单标签率首次达到80%的时间 └─ 标签推送时间(label_pushed_at) = 该订单标签被推送的时间 步骤2:比较标签推送时间和标签率达80%时间 ├─ IF label_pushed_at < label_rate_80_time: │ ├─ 说明该订单的标签是在标签率<80%时推送的 │ ├─ 归类:低标签率订单 │ └─ 考核规则:NULL(完成即达标) └─ ELSE (label_pushed_at >= label_rate_80_time): ├─ 说明该订单的标签是在标签率≥80%时推送的 ├─ 归类:高标签率订单 └─ 考核规则:根据到仓时间的16点分段 步骤3:高标签率订单再按到仓时间分段 ├─ IF 到仓时间.hour < 16: │ └─ 考核时间 = 次日 16:00:00 └─ ELSE: └─ 考核时间 = 次日 23:59:59 ``` ### 验证逻辑合理性 ``` 为什么这个设计有效: 1. 最早完成时间代表"开始作业时刻" └─ 交接单内的包裹从此刻开始被处理 2. 标签率达80%时间是"达到高承诺的临界点" └─ 在此之前推送的标签属于低标签率期间 └─ 在此之后推送的标签属于高标签率期间 3. 标签推送时间是判断标准 └─ 不需要计算"此时的标签率" └─ 只需要比较时间大小关系 └─ 逻辑清晰,性能高效 4. 自动满足数学关系 └─ 当日换单成功数 = 最早完成时间在该日期的包裹 └─ 当日考核通过数 ≤ 当日换单成功数 └─ 恒成立! ``` ### 具体逻辑实现 **步骤1:为每个交接单找出两个关键时间点** ```sql WITH InterchangeUnitKeyTimes AS ( SELECT iu.BillOfLadingNumber, iu.MasterPackageNumber, -- 该交接单中最早的包裹完成时间 MIN(oss.首次成功时间) AS earliest_success_time, -- 该交接单标签率首次达到80%的时间 (SELECT MIN(label_pushed_at) FROM label_replace_requests l2 WHERE l2.BillOfLadingNumber = iu.BillOfLadingNumber AND l2.MasterPackageNumber = iu.MasterPackageNumber AND (SELECT COUNT(DISTINCT CASE WHEN Label IS NOT NULL THEN Id END) * 100.0 / COUNT(DISTINCT Id) FROM label_replace_requests l3 WHERE l3.BillOfLadingNumber = iu.BillOfLadingNumber AND l3.MasterPackageNumber = iu.MasterPackageNumber AND l3.label_pushed_at <= l2.label_pushed_at) >= 80 ) AS label_rate_80_time FROM interchange_units iu LEFT JOIN OverallScanStatus oss ON iu.BillOfLadingNumber = oss.BillOfLadingNumber GROUP BY iu.BillOfLadingNumber, iu.MasterPackageNumber ) -- 结果示例: -- BillOfLadingNumber | MasterPackageNumber | earliest_success_time | label_rate_80_time -- BOL001 | MP001 | 2026-05-16 10:30:00 | 2026-05-15 17:45:00 -- 说明:这个交接单最早在10:30完成,标签率在17:45达到80% ``` **步骤2:为每个订单确定标签率归类和考核时间** ```sql WITH SubscriptionAssessmentTime AS ( SELECT l.Id AS subscription_id, l.NeutralWaybillNumber, l.BillOfLadingNumber, l.MasterPackageNumber, l.label_pushed_at, ar.到货时间, ar.到货日期, iut.label_rate_80_time, -- 判断该订单的标签率归类 CASE WHEN iut.label_rate_80_time IS NULL THEN -- 交接单标签率始终<80% '低标签率' WHEN l.label_pushed_at < iut.label_rate_80_time THEN -- 该订单推送时标签率<80% '低标签率' ELSE -- 该订单推送时标签率≥80% '高标签率' END AS label_rate_category, -- 根据标签率归类确定考核时间 CASE WHEN iut.label_rate_80_time IS NULL THEN -- 标签率始终<80% NULL -- 完成即达标 WHEN l.label_pushed_at < iut.label_rate_80_time THEN -- 该订单标签推送时标签率<80% NULL -- 完成即达标 ELSE -- 该订单标签推送时标签率≥80%,按到仓时间的16点分段 CASE WHEN HOUR(ar.到货时间) < 16 THEN CONCAT(DATE_ADD(DATE(ar.到货时间), INTERVAL 1 DAY), ' 16:00:00') ELSE CONCAT(DATE_ADD(DATE(ar.到货时间), INTERVAL 1 DAY), ' 23:59:59') END END AS assessment_time, -- 记录判断依据(便于审计) CASE WHEN iut.label_rate_80_time IS NULL THEN 'never_reached_80' WHEN l.label_pushed_at < iut.label_rate_80_time THEN 'pushed_before_80' ELSE 'pushed_after_80' END AS classification_reason FROM label_replace_requests l INNER JOIN ArrivalRequests ar ON l.NeutralWaybillNumber = ar.NeutralWaybillNumber LEFT JOIN InterchangeUnitKeyTimes iut ON l.BillOfLadingNumber = iut.BillOfLadingNumber AND l.MasterPackageNumber = iut.MasterPackageNumber ) -- 结果示例: -- subscription_id | label_rate_category | assessment_time | classification_reason -- 1001 | 低标签率 | NULL | pushed_before_80 -- 1002 | 高标签率 | 2026-05-16 16:00:00 | pushed_after_80 -- 1003 | 低标签率 | NULL | never_reached_80 ``` **步骤3:用于报表统计的最终SELECT** ```sql -- 对于DailyHighLabelRateShould和DailyLowLabelRateShould的统计 SELECT DATE(CONVERT_TZ(l.label_pushed_at, '+00:00', '-05:00')) AS 日期, CASE WHEN (SELECT MIN(label_pushed_at) FROM label_replace_requests l2 WHERE l2.BillOfLadingNumber = l.BillOfLadingNumber AND l2.MasterPackageNumber = l.MasterPackageNumber AND (SELECT COUNT(DISTINCT CASE WHEN Label IS NOT NULL THEN Id END) * 100.0 / COUNT(DISTINCT Id) FROM label_replace_requests l3 WHERE l3.BillOfLadingNumber = l.BillOfLadingNumber AND l3.MasterPackageNumber = l.MasterPackageNumber AND l3.label_pushed_at <= l2.label_pushed_at) >= 80) IS NULL OR l.label_pushed_at < (SELECT MIN(label_pushed_at) FROM label_replace_requests l2 WHERE l2.BillOfLadingNumber = l.BillOfLadingNumber AND l2.MasterPackageNumber = l.MasterPackageNumber AND (SELECT COUNT(DISTINCT CASE WHEN Label IS NOT NULL THEN Id END) * 100.0 / COUNT(DISTINCT Id) FROM label_replace_requests l3 WHERE l3.BillOfLadingNumber = l.BillOfLadingNumber AND l3.MasterPackageNumber = l.MasterPackageNumber AND l3.label_pushed_at <= l2.label_pushed_at) >= 80) THEN '低标签率' ELSE '高标签率' END AS label_rate_category, COUNT(DISTINCT l.NeutralWaybillNumber) AS count FROM label_replace_requests l GROUP BY 日期, label_rate_category ``` --- ## 数据库结构分析 ### label_replace_requests 表结构 根据 `label_replace_requests.sql`,该表包含: - `Id`: 订单ID - `NeutralWaybillNumber`: 中性单号 - `BillOfLadingNumber`: 提单号 - `MasterPackageNumber`: 主包号 - `Label`: 标签内容 - **`LabelPushedAt` 或 `label_pushed_at`**: 标签推送时间 ← **关键字段** - `CreatedAt`: 创建时间 - 其他字段... **关键字段说明**:`label_pushed_at` 记录的是该订单的标签被推送到系统的时刻,这是判断"该订单在标签率多少时被标签化"的关键依据。 --- ## 改进建议 ### 建议1:利用两个关键时间点简化逻辑 不需要复杂的历史标签率快照,只需要: ```sql -- 两个关键时间点 1. earliest_success_time = MIN(首次成功时间) -- 交接单中最早的包裹何时完成 2. label_rate_80_time = 标签率首次达到80%时的最早标签推送时间 -- 标签率在何时达到80% ``` **优势**: - 逻辑清晰:只涉及两个时间的比较 - 无需历史数据:不需要追溯历史标签率 - 自动分类:标签推送时即自动归类 - 高效可靠:基于确定的数据事实 ### 建议2:在标签推送时自动计算考核时间 ```sql -- 伪代码 WHEN label IS PUSHED: DO: 1. 获取该订单所属交接单的 label_rate_80_time 2. IF label_pushed_at < label_rate_80_time THEN assessment_time = NULL -- 完成即达标 ELSE assessment_time = 根据到仓时间的16点分段 3. SAVE assessment_time to database ``` **好处**: - 考核时间一旦确定就不变(冻结值) - 报表统计直接读取,无需动态计算 - 完全支持审计追溯 ### 建议3:在报表统计中利用已保存的考核时间 ```sql -- 不是这样动态计算: -- SELECT COUNT(*) -- FROM label_replace_requests -- WHERE 标签率 >= 80% ← 需要实时计算 -- 而是这样直接统计: SELECT COUNT(*) FROM label_replace_requests WHERE label_rate_80_time IS NOT NULL AND label_pushed_at >= label_rate_80_time -- ← 直接从已保存的字段读取 ``` **优势**: - 查询性能提升 - 结果稳定可复现 - 无需重复计算 --- ## 时间维度梳理 为了避免混淆,明确各个时间字段的含义: | 字段名 | 含义 | 用途 | 由谁设定 | |--------|------|------|---------| | `arrival_time` | 包裹到货时间 | 16点分段判断 | 到货系统 | | `label_pushed_at` | 该订单的标签被推送时间 | 判断阈值跨越 | 标签推送系统 | | `assessment_reference_time` | 考核计算的参考时刻 | 作为标签率快照的时间点 | 考核系统 | | `first_success_time` | 包裹首次成功时间 | 与考核时间比较 | 扫描系统 | | `assessment_time` | 考核时间(冻结值) | 判断是否考核通过 | 考核系统计算得出 | --- ## 考核通过总数的定义和计算 ### 核心指标定义 **考核通过总数** = 满足以下条件的包裹总数: ``` 满足考核条件的包裹 = A类 + B类 A类包裹:标签率≥80%且成功完成考核 ├─ 条件1: 标签推送时间在标签率≥80%时期(或标签率始终≥80%) ├─ 条件2: 根据到仓时间的16点分段确定的考核时间 ├─ 条件3: 首次成功时间 <= 考核时间 └─ 结论: 考核通过 ✓ B类包裹:标签率<80%且曾经成功 ├─ 条件1: 标签推送时间在标签率<80%时期(或标签率始终<80%) ├─ 条件2: 考核时间 = NULL(完成即达标) ├─ 条件3: 曾经成功 = 1(已有首次成功时间) └─ 结论: 考核通过 ✓ ``` ### 公式表示 ``` 考核通过总数 = (标签率≥80%的16点前考核通过包裹数) + (标签率≥80%的16点后考核通过包裹数) + (标签率<80%且曾经成功的包裹数) 其中: - 16点前考核通过包裹数 = 16点前到仓 AND 标签率≥80% AND 首次成功时间≤考核时间 - 16点后考核通过包裹数 = 16点后到仓 AND 标签率≥80% AND 首次成功时间≤考核时间 - 低标签率成功包裹数 = 标签率<80% AND 曾经成功=1 ``` ### SQL实现示例 ```sql -- 在DailyLabelStatsChineseAsync的最终SELECT中新增 -- 步骤1:先计算按标签率分类的应该换单数 WithLabelRateClassification AS ( SELECT DATE(CONVERT_TZ(l.label_pushed_at, '+00:00', '-05:00')) AS 日期, l.BillOfLadingNumber, l.MasterPackageNumber, l.NeutralWaybillNumber, -- 计算该订单被标签化时的标签率 (SELECT ROUND( COUNT(DISTINCT CASE WHEN Label IS NOT NULL AND Label != '' THEN Id END) * 100.0 / COUNT(DISTINCT Id), 2 ) FROM label_replace_requests l2 WHERE l2.BillOfLadingNumber = l.BillOfLadingNumber AND l2.MasterPackageNumber = l.MasterPackageNumber AND l2.label_pushed_at <= l.label_pushed_at ) AS label_rate_at_push_time, CASE WHEN (SELECT ...) >= 80 THEN '高标签率' ELSE '低标签率' END AS label_rate_category FROM label_replace_requests l ), DailyHighLabelRateShould AS ( SELECT 日期, COUNT(DISTINCT NeutralWaybillNumber) AS 高标签率应该换单数 FROM WithLabelRateClassification WHERE label_rate_category = '高标签率' GROUP BY 日期 ), DailyLowLabelRateShould AS ( SELECT 日期, COUNT(DISTINCT NeutralWaybillNumber) AS 低标签率应该换单数 FROM WithLabelRateClassification WHERE label_rate_category = '低标签率' GROUP BY 日期 ), -- 步骤2:最终SELECT SELECT 日期, ... -- 新增字段:标签率维度的应该换单数 COALESCE(dhls.高标签率应该换单数, 0) AS 标签率80%及以上应该换单数, COALESCE(dlls.低标签率应该换单数, 0) AS 标签率80%以下应该换单数, (COALESCE(dhls.高标签率应该换单数, 0) + COALESCE(dlls.低标签率应该换单数, 0)) AS 当日应该换单数, 当日换单成功数, -- 新增字段:24小时换单率(正确定义) -- 分子:考核通过总数(所有类别的考核通过) -- 分母:标签率≥80%的应该换单数(对客户的高承诺) -- 含义:实际履约完成的所有订单,占应该完成的高标签率订单的比例 CASE WHEN COALESCE(dhls.高标签率应该换单数, 0) = 0 THEN '0.00%' ELSE CONCAT( ROUND( (COALESCE(dbc.16点前考核通过包裹数, 0) + COALESCE(dac.16点后考核通过包裹数, 0) + COALESCE(dllrp.低标签率考核通过包裹数, 0)) / COALESCE(dhls.高标签率应该换单数, 1) * 100, 2 ), '%' ) END AS 24小时换单率, -- 新增字段:考核通过总数 (COALESCE(dbc.16点前考核通过包裹数, 0) + COALESCE(dac.16点后考核通过包裹数, 0) + COALESCE(dllrp.低标签率考核通过包裹数, 0)) AS 考核通过总数, -- 新增字段:考核通过率(针对所有成功包裹) CASE WHEN COALESCE(dsc.当日换单成功数, 0) = 0 THEN '0.00%' ELSE CONCAT( ROUND( (COALESCE(dbc.16点前考核通过包裹数, 0) + COALESCE(dac.16点后考核通过包裹数, 0) + COALESCE(dllrp.低标签率考核通过包裹数, 0)) / COALESCE(dsc.当日换单成功数, 1) * 100, 2 ), '%' ) END AS 考核通过率, ... FROM DailyStatsWithPrev t LEFT JOIN DailyScanMetrics dsm ON t.日期 = dsm.日期 LEFT JOIN DailySuccessCount dsc ON t.日期 = dsc.日期 LEFT JOIN HistoryUnfinished hu ON t.日期 = hu.日期 LEFT JOIN LatestUnfinished lu ON t.日期 = lu.日期 LEFT JOIN DailyFailedOrders dfo ON t.日期 = dfo.日期 LEFT JOIN DailyBeforeNoonPassed dbc ON t.日期 = dbc.日期 LEFT JOIN DailyAfternoonPassed dac ON t.日期 = dac.日期 LEFT JOIN DailyLowLabelRatePassed dllrp ON t.日期 = dllrp.日期 LEFT JOIN DailyHighLabelRateShould dhls ON t.日期 = dhls.日期 LEFT JOIN DailyLowLabelRateShould dlls ON t.日期 = dlls.日期 CROSS JOIN (SELECT @running_total := 0, @当天应该换单数 := 0) AS init ORDER BY t.日期 ``` ### 数据关系验证 **数学关系**: ``` 考核通过总数 <= 当日换单成功数 理由: - A类考核通过包裹:都是曾经成功(首次成功时间≤考核时间) - B类考核通过包裹:都是曾经成功(曾成功=1) - 因此考核通过的包裹都属于已成功的包裹的子集 ``` **验证场景**: ``` 当日换单成功数 = 100 其中: - 标签率始终≥80%:60个包裹 ├─ 16点前到仓:35个 │ ├─ 完成时间≤考核时间:33个 ✓(考核通过) │ └─ 完成时间>考核时间:2个 ✗(考核未通过) └─ 16点后到仓:25个 ├─ 完成时间≤考核时间:20个 ✓(考核通过) └─ 完成时间>考核时间:5个 ✗(考核未通过) - 标签率始终<80%:30个包裹 └─ 完成即达标:30个 ✓(全部考核通过) - 标签率跨越阈值:10个包裹 ├─ 在<80%期间被标签化:5个 │ └─ 完成即达标:5个 ✓(考核通过) └─ 在≥80%期间被标签化:5个 ├─ 16点前到仓:3个 │ └─ 完成时间≤考核时间:2个 ✓(考核通过) └─ 16点后到仓:2个 └─ 完成时间≤考核时间:1个 ✓(考核通过) 考核通过总数 = 33 + 20 + 30 + 5 + 2 + 1 = 91 < 100 ✓ 考核通过率 = 91 / 100 = 91% ``` ### 关键指标体系 在报表中应该同时展示: | 指标名 | 说明 | 分子 | 分母 | |--------|------|------|------| | 标签率≥80%的应该换单数 | 标签推送时标签率≥80%的订单 | COUNT(label_pushed_at时的标签率≥80%) | - | | 标签率<80%的应该换单数 | 标签推送时标签率<80%的订单 | COUNT(label_pushed_at时的标签率<80%) | - | | 当日应该换单数 | 当前需要进行考核作业的总订单数 | 高标签率应该+低标签率应该 | - | | 当日换单成功数 | 首次成功的包裹总数 | COUNT(first_success_time IS NOT NULL) | - | | 16点前高标签考核通过 | 16点前到仓且标签率≥80%且满足考核 | COUNT(...) | - | | 16点后高标签考核通过 | 16点后到仓且标签率≥80%且满足考核 | COUNT(...) | - | | 低标签率考核通过 | 标签率<80%且曾经成功 | COUNT(label_rate<80% AND first_success_time IS NOT NULL) | - | | 考核通过总数 | 三类的总和 | 16前 + 16后 + 低标签 | - | | **24小时换单率** | **实际履约完成的订单占比** | **考核通过总数** | **标签率≥80%的应该换单数** | | 考核通过率 | 考核通过占成功的比例 | 考核通过总数 | 当日换单成功数 | #### 各指标详细说明 **标签率≥80%的应该换单数**: - 定义:标签被推送时,该交接单的标签率已达到≥80%的订单总数 - 计算方法:统计所有 `label_pushed_at` 时刻标签率≥80%的订单 - 这些订单应该按照"高标签率逻辑"进行考核(需要在规定时间内完成) **标签率<80%的应该换单数**: - 定义:标签被推送时,该交接单的标签率未达到80%的订单总数 - 计算方法:统计所有 `label_pushed_at` 时刻标签率<80%的订单 - 这些订单应该按照"低标签率逻辑"进行考核(完成即达标) **24小时换单率(正确定义)**: - 定义:实际完成并通过考核的所有订单(无论标签率),占应该完成的高标签率订单的比例 - 分子:**考核通过总数**(16点前高标签 + 16点后高标签 + 低标签率)← **包含所有通过的订单** - 这反映了对客户承诺的实际履约完成度 - 分母:**标签率≥80%的应该换单数**(对客户的高承诺标的) - 意义:直观衡量我们对客户高承诺订单的履约完成情况 - 计算:考核通过总数 / 标签率≥80%的应该换单数 **验证示例(正确版本)**: ``` 当日新增订单: ├─ 到货时间:05-15 14:30 ├─ 标签率≥80%的应该换单数:65个 (★对客户的承诺) │ ├─ 16点前到仓:38个 → 应完成时间在05-16 16:00前 │ └─ 16点后到仓:27个 → 应完成时间在05-16 23:59:59前 ├─ 标签率<80%的应该换单数:35个 (额外的) └─ 当日应该换单数:100个 实际完成情况: ├─ 16点前到仓的38个中: │ ├─ 34个在16:00前完成 ✓(考核通过) │ └─ 4个在16:00后完成 ✗(考核未通过) ├─ 16点后到仓的27个中: │ ├─ 20个在23:59:59前完成 ✓(考核通过) │ └─ 7个在23:59:59后完成 ✗(考核未通过) ├─ 低标签率的35个中: │ ├─ 32个完成 ✓(考核通过) │ └─ 3个未完成 ✗(未成功) 指标统计: ├─ 16点前高标签考核通过 = 34个 ├─ 16点后高标签考核通过 = 20个 ├─ 低标签率考核通过 = 32个 ├─ 考核通过总数 = 34 + 20 + 32 = 86个 └─ 16点后到仓未通过数 = 7个 ★ 24小时换单率 = 86 / 65 = 132.31% 含义解析: - 分子86包括了所有考核通过的订单(高标签的和低标签的) - 分母65是客户高承诺的订单数 - 为什么会超过100%? └─ 因为除了那些应该完成的高标签订单(65个)外 └─ 我们还额外完成了低标签率的订单(35个中的32个) └─ 这说明我们的履约能力超出了对高标签订单的承诺 实际意义: - 如果≥100%:说明我们完成的订单数≥承诺的高标签订单数,履约能力强 - 如果=83%:说明承诺的65个中只有54个完成,还有11个失约 - 这个指标最能直观反映对客户的履约完成情况 ``` --- ## 实现步骤总结 ### 第1步:理解核心数据流 ``` 标签推送时刻 ↓ 自动比较:label_pushed_at vs label_rate_80_time ↓ 自动分类:低标签率 或 高标签率 ↓ 自动计算:考核时间(NULL 或 次日16:00/23:59:59) ↓ 保存到数据库(冻结值) ``` ### 第2步:为每个交接单计算两个关键时间点 - `earliest_success_time`:交接单内最早的包裹何时完成 - `label_rate_80_time`:标签率首次达到80%时的最早标签推送时间 ### 第3步:在标签推送时自动分类和计算考核时间 - 基于 label_pushed_at vs label_rate_80_time 的比较 - 自动确定是"低标签率"还是"高标签率" - 自动计算 assessment_time ### 第4步:报表统计直接利用已保存的数据 - 不再动态计算标签率 - 不再计算历史标签率快照 - 直接统计分类后的结果 - 支持完整的审计追溯 --- ## 核心设计验证 ### 验证场景:标签率跨越阈值的处理 ``` 交接单BOL001创建于 05-15 14:00 订单标签推送时间线(按推送时间排序): - 05-15 14:30 订单1标签推送 → 推送时标签率 = 1/20 = 5% (<80%) - 05-15 15:00 订单2标签推送 → 推送时标签率 = 2/20 = 10% (<80%) - ...累积... - 05-15 17:30 订单16标签推送 → 推送时标签率 = 16/20 = 80% ← 达到80%! - 05-15 17:35 订单17标签推送 → 推送时标签率 = 17/20 = 85% (≥80%) - 05-15 17:40 订单18标签推送 → 推送时标签率 = 18/20 = 90% (≥80%) InterchangeUnitKeyTimes计算结果: - BillOfLadingNumber = BOL001 - MasterPackageNumber = MP001 - earliest_success_time = 05-16 10:30:00(这个交接单内的某个包裹最早在此时完成) - label_rate_80_time = 05-15 17:30:00(标签率首次达到80%的时刻) 对每个订单的分类: 订单1(label_pushed_at = 05-15 14:30): ├─ 比较:14:30 < 17:30 ✓ ├─ 结论:推送时标签率 < 80% ├─ 归类:低标签率 └─ 考核时间:NULL(完成即达标) 订单16(label_pushed_at = 05-15 17:30): ├─ 比较:17:30 >= 17:30 ✓ ├─ 结论:推送时标签率 >= 80% ├─ 归类:高标签率 ├─ 到仓时间:05-15 14:30 < 16点 └─ 考核时间:05-16 16:00:00 订单17(label_pushed_at = 05-15 17:35): ├─ 比较:17:35 >= 17:30 ✓ ├─ 结论:推送时标签率 >= 80% ├─ 归类:高标签率 ├─ 到仓时间:05-15 14:30 < 16点 └─ 考核时间:05-16 16:00:00 验证考核结果: - 订单1在05-16 15:00成功 └─ 考核时间NULL → 完成即达标 → 考核通过 ✓ - 订单16在05-16 17:00成功 └─ 考核时间16:00,17:00 > 16:00 → 考核未通过 ✗ - 订单17在05-16 15:30成功 └─ 考核时间16:00,15:30 < 16:00 → 考核通过 ✓ ``` **验证结论**: - ✅ 逻辑极其清晰:只需比较两个时间点 - ✅ 性能高效:无需复杂的历史标签率计算 - ✅ 完全自动化:标签推送时自动归类 - ✅ 支持审计:可追溯每个订单的分类依据 --- ## SQL实现指导 ### 在现有SQL基础上的改进建议 添加新的CTE来处理阈值跨越: ```sql -- 新增CTE:识别标签率的阈值跨越 LabelRateThresholdCrossing AS ( SELECT BillOfLadingNumber, MasterPackageNumber, MIN(CASE WHEN running_label_count >= 80 AND LAG(running_label_count) OVER (...) < 80 THEN label_pushed_at END) AS threshold_crossing_time FROM ( SELECT l.BillOfLadingNumber, l.MasterPackageNumber, l.label_pushed_at, SUM(CASE WHEN l.Label IS NOT NULL THEN 1 ELSE 0 END) OVER ( PARTITION BY l.BillOfLadingNumber, l.MasterPackageNumber ORDER BY l.label_pushed_at ) * 100.0 / COUNT(*) OVER ( PARTITION BY l.BillOfLadingNumber, l.MasterPackageNumber ) AS running_label_count FROM label_replace_requests l ) t GROUP BY BillOfLadingNumber, MasterPackageNumber ) -- 修改ArrivalRequests CTE中的考核时间计算 -- 加入对 label_pushed_at 的判断 ```