# 24H换单率超过100% - 最终根本原因与修复计划 **日期**:2026-05-16 **核心问题**:24H换单率远超100%(4745%等),根本原因是分母计算错误 --- ## 业务逻辑最终澄清(用户确认) ### 包裹的属性关系 ``` 考核时间 ↑ 决定者:作业时的标签率(交接单维度) ↓ 标签率(两种) 1. 作业时标签率(固定) = 最早扫描时间之前的有标签包裹 / 该交接单所有包裹 用途:决定考核时间、决定是否纳入24H换单率 2. 统计的标签率(最终) = 当前有标签的包裹 / 该交接单所有包裹 用途:业务报表展示 ``` ### 标签率与考核时间的关系 ``` 对于交接单中的每个包裹: - 作业时标签率≥80% → 有考核时间 → 需要在规定时间内完成 - 作业时标签率<80% → 无考核时间 → 完成即达标 关键:考核时间是交接单维度确定的,然后应用到该交接单的所有包裹 ``` ### 分母的精确定义(用户最终确认) ``` 高标签率应该换单数 = 当日到货 且 作业时标签率≥80% 的交接单中的有标签包裹数 具体计算方式: 1. 找出当日到货的所有交接单 2. 对每个交接单: a. 计算作业时标签率 = 最早扫描之前有标签的数 / 总数 b. 如果作业时标签率≥80%: → 计算这个交接单有多少个有标签的包裹 → 这些有标签包裹加入应该换单数 c. 如果作业时标签率<80%: → 跳过这个交接单(不加入应该换单数) 3. 求和得到总的高标签率应该换单数 ``` --- ## 当前代码的根本问题 ### 问题1:InterchangeUnitLabelRatesAtFirstScan的计算 **当前代码**(第761-791行): ```sql SELECT l.BillOfLadingNumber, l.MasterPackageNumber, COUNT(DISTINCT l.Id) AS total_requests, COUNT(DISTINCT CASE WHEN l.Label IS NOT NULL AND l.Label != '' AND l.LabelRetrievedAt < (...) THEN l.Id END) AS labeled_at_first_scan, ... FROM label_replace_requests l GROUP BY l.BillOfLadingNumber, l.MasterPackageNumber ``` **问题**: - COUNT(DISTINCT l.Id) 统计的是"所有包裹"的数量 - 但根据用户规则,应该统计的是"有标签的包裹"的数量 **正确做法**: - 分子应该是:最早扫描时间之前有标签的包裹数 - 分母应该是:该交接单所有有标签的包裹数(不是所有包裹) ### 问题2:DailyHighLabelRateShould的计算 **当前代码**(第1100-1117行): ```sql DailyHighLabelRateShould AS ( SELECT DATE(CONVERT_TZ(ar.到货时间, '+00:00', '-05:00')) AS 日期, COUNT(DISTINCT CONCAT(ar.BillOfLadingNumber, '|', ar.MasterPackageNumber)) AS 高标签率应该换单数 FROM ArrivalRequests ar INNER JOIN InterchangeUnitLabelRatesAtFirstScan... WHERE label_rate_at_first_scan >= 80 ``` **问题**: - 这里统计的是交接单级别的计数 - 但应该统计的是"有标签的包裹"数量 - 如果一个交接单有10个包裹,其中8个有标签,标签率80% - 应该换单数应该是8,不是1 **导致的后果**: - 分母被严重低估(每个交接单只算1而不是该交接单的有标签包裹数) - 分子/分母比率就会巨大 --- ## 修复方案 ### 第1步:修正InterchangeUnitLabelRatesAtFirstScan **改进思路**: - 保留交接单维度的标签率计算(用于决定考核时间) - 但同时记录"有标签包裹数"用于分母计算 ```sql InterchangeUnitLabelRatesAtFirstScan AS ( SELECT l.BillOfLadingNumber, l.MasterPackageNumber, -- 统计有标签的包裹数(用于后续分母计算) COUNT(DISTINCT CASE WHEN l.Label IS NOT NULL AND l.Label != '' THEN l.Id END) AS total_labeled_at_any_time, -- 作业时有标签的包裹数 COUNT(DISTINCT CASE WHEN l.Label IS NOT NULL AND l.Label != '' AND l.LabelRetrievedAt < ( SELECT MIN(lsh.CreatedAt) FROM label_scan_history lsh WHERE lsh.NeutralWaybillNumber = l.NeutralWaybillNumber ) THEN l.Id END) AS labeled_at_first_scan, -- 作业时标签率 = 作业时有标签数 / 所有有标签数 ROUND( COUNT(DISTINCT CASE WHEN l.Label IS NOT NULL AND l.Label != '' AND l.LabelRetrievedAt < ( SELECT MIN(lsh.CreatedAt) FROM label_scan_history lsh WHERE lsh.NeutralWaybillNumber = l.NeutralWaybillNumber ) THEN l.Id END) * 100.0 / COUNT(DISTINCT CASE WHEN l.Label IS NOT NULL AND l.Label != '' THEN l.Id END), 2 ) AS label_rate_at_first_scan FROM label_replace_requests l GROUP BY l.BillOfLadingNumber, l.MasterPackageNumber ) ``` ### 第2步:修正DailyHighLabelRateShould **改进思路**: - 统计的是"有标签的包裹数",而不是交接单数 ```sql DailyHighLabelRateShould AS ( SELECT DATE(CONVERT_TZ(ar.到货时间, '+00:00', '-05:00')) AS 日期, -- 改为统计:有标签的包裹数 SUM(CASE WHEN iulr_first.label_rate_at_first_scan >= 80 THEN iulr_first.total_labeled_at_any_time ELSE 0 END) AS 高标签率应该换单数 FROM ArrivalRequests ar INNER JOIN InterchangeUnitLabelRatesAtFirstScan iulr_first ON ar.BillOfLadingNumber = iulr_first.BillOfLadingNumber AND ar.MasterPackageNumber = iulr_first.MasterPackageNumber WHERE ar.到货日期 IS NOT NULL GROUP BY DATE(CONVERT_TZ(ar.到货时间, '+00:00', '-05:00')) ) ``` ### 第3步:确保分子与分母维度一致 **分子的逻辑**(DailyBeforeNoonPassed + DailyAfternoonPassed): - 已经是按"订单"(NeutralWaybillNumber)计数 - 这是正确的 **但需要确保**: - 只统计有标签的订单 - 只统计作业时标签率≥80%的订单 - 只统计在考核时间内完成的订单 --- ## 实施步骤 ### 步骤1:修改InterchangeUnitLabelRatesAtFirstScan CTE - 位置:第761-791行 - 任务:添加total_labeled_at_any_time字段,调整label_rate_at_first_scan计算逻辑 - 验证:确保新增字段计算正确 ### 步骤2:修改DailyHighLabelRateShould CTE - 位置:第1100-1117行 - 任务:改为SUM统计有标签包裹数而不是COUNT交接单 - 验证:结果应该更大(因为统计包裹而不是交接单) ### 步骤3:编译验证 - 运行:`dotnet build src/DAL/DAL.csproj` - 预期:编译成功 ### 步骤4:测试和验证 **测试内容**(不要求≤100%,因为这是可能的正常现象): 测试1:验证分母计算逻辑 ```sql SELECT 日期, 高标签率应该换单数, (SELECT COUNT(*) FROM ...) as 应该的包裹数 FROM DailyHighLabelRateShould WHERE 日期 = '2026-05-15' ``` 测试2:验证分子分母是否合理对应 ```sql SELECT 日期, (SELECT SUM(...) FROM DailyBeforeNoonPassed WHERE 日期='2026-05-15') as 分子_16点前, (SELECT SUM(...) FROM DailyAfternoonPassed WHERE 日期='2026-05-15') as 分子_16点后, (SELECT 高标签率应该换单数 FROM DailyHighLabelRateShould WHERE 日期='2026-05-15') as 分母 ``` 测试3:验证数据是否能手工解释 - 选择某一天的数据 - 查看具体的订单数和包裹数 - 确认分子分母的计算逻辑是否符合业务规则 - **不要求分子≤分母或24H换单率≤100%,因为这是可能的正常现象** --- ## 预期结果 修复后: - ✅ 分母正确反映"有标签包裹数"而不是"交接单数" - ✅ 数据逻辑一致、可解释 - ✅ 24H换单率数据合理(虽然可能>100%,但不会是4745%这样极端的值) - ✅ 分子分母的对应关系清晰