From d0984a430b9b3f9f45106f48678d00e824ba362a Mon Sep 17 00:00:00 2001 From: kkfluous Date: Thu, 16 Apr 2026 22:32:50 +0800 Subject: [PATCH] refactor(scheduling): improve reason text, fix classification, polish detail view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Classification: qualified requires actual completionRate >= 100% (not just predicted) - Reason text: structured two-column layout (客户日均 | 考核周期剩余) - Conclusion line in red bold (预估无法达标,需替换 / 已达标,建议换上未达标车辆) - Remove verbose subtitle from candidate section - Remove redundant middle line (预估考核期里程 vs 考核里程) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/modules/scheduling/SuggestionDetail.tsx | 43 +++++++++++++++------ src/server/routes/scheduling/algorithm.ts | 17 +++++--- src/server/routes/scheduling/suggestions.ts | 2 +- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/modules/scheduling/SuggestionDetail.tsx b/src/modules/scheduling/SuggestionDetail.tsx index 37ce573..3389d34 100644 --- a/src/modules/scheduling/SuggestionDetail.tsx +++ b/src/modules/scheduling/SuggestionDetail.tsx @@ -83,26 +83,45 @@ export default function SuggestionDetail({ suggestion: s, onClose, onNotifySucce - {/* Reason */} -
- 建议: - {s.reason} + {/* Reason — structured lines */} +
+ {s.reason.split('\n').map((line, i) => { + const isConclusion = line.startsWith('!!'); + const text = isConclusion ? line.slice(2) : line; + if (isConclusion) { + return ( +
+ {text} +
+ ); + } + // Split by | for two-column layout + if (text.includes('|')) { + const parts = text.split('|').map(p => p.trim()); + return ( +
+ {parts[0]} + {parts[1]} +
+ ); + } + return ( +
+ + {text} +
+ ); + })}
{/* Candidates */}
-
+
- {isRescue ? '从库存调入替换' : '换上以下里程少的车'} + {isRescue ? '建议替换车辆' : '建议替换车辆'} {s.candidates.length} 辆可选
-
- {isRescue - ? '以下车辆快达标,换到当前客户处利用剩余天数即可冲线' - : '以下车辆里程缺口大,换到该高里程客户处可加速达标' - } -
{s.candidates.map(c => { diff --git a/src/server/routes/scheduling/algorithm.ts b/src/server/routes/scheduling/algorithm.ts index 4040524..d82b3e7 100644 --- a/src/server/routes/scheduling/algorithm.ts +++ b/src/server/routes/scheduling/algorithm.ts @@ -25,11 +25,15 @@ export function isTypeCompatible(sourceType: string, candidateType: string): boo export function classifyVehicle( currentYearIsQualified: boolean, - predictedYearEnd: number, + currentYearMileage: number, yearTarget: number, + predictedYearEnd: number, ): VehicleClassification { - if (currentYearIsQualified || predictedYearEnd / yearTarget >= 1.2) return 'qualified'; - if (predictedYearEnd / yearTarget < 0.6) return 'hopeless'; + // qualified: current year mileage already >= target (actually done, not just predicted) + const actualRate = yearTarget > 0 ? currentYearMileage / yearTarget : 0; + if (currentYearIsQualified || actualRate >= 1.0) return 'qualified'; + // hopeless: even with remaining days, predicted < 60% of target + if (yearTarget > 0 && predictedYearEnd / yearTarget < 0.6) return 'hopeless'; return 'normal'; } @@ -127,9 +131,10 @@ export function generateSuggestions( }) .slice(0, 5); - const yearRate = vehicle.yearTarget > 0 ? Math.round((vehicle.currentYearMileage / vehicle.yearTarget) * 100) : 0; const gap = Math.max(0, vehicle.yearTarget - vehicle.currentYearMileage); - const reason = `客户日均 ${Math.round(vehicle.customerAvgDaily)} km · 完成率 ${yearRate}% · 缺口 ${fmtKmSimple(gap)} km · 剩余 ${vehicle.daysLeft} 天(约 ${fmtKmSimple(Math.round(customerCanAdd))} km)`; + const dailyReq = vehicle.daysLeft > 0 ? Math.round(gap / vehicle.daysLeft) : 0; + const predictedTotal = Math.round(vehicle.currentYearMileage + customerCanAdd); + const reason = `客户日均 ${Math.round(vehicle.customerAvgDaily)} km | 考核周期剩余 ${vehicle.daysLeft} 天 · 日均需 ${fmtKmSimple(dailyReq)} km\n!!预估无法达标,需替换`; suggestions.push({ id: `hopeless-${vehicle.plateNumber}`, @@ -194,7 +199,7 @@ export function generateSuggestions( const yearRate = vehicle.yearTarget > 0 ? Math.round((vehicle.currentYearMileage / vehicle.yearTarget) * 100) : 0; const canAddKm = vehicle.customerAvgDaily * vehicle.daysLeft; - const reason = `客户日均 ${Math.round(vehicle.customerAvgDaily)} km · 完成率 ${yearRate}% · 剩余 ${vehicle.daysLeft} 天(约 ${fmtKmSimple(Math.round(canAddKm))} km)`; + const reason = `客户日均 ${Math.round(vehicle.customerAvgDaily)} km\n已完成考核(完成率 ${yearRate}%)\n考核周期剩余 ${vehicle.daysLeft} 天,可为新车贡献约 ${fmtKmSimple(Math.round(canAddKm))} km\n!!已达标,建议换上未达标车辆`; suggestions.push({ id: `qualified-${vehicle.plateNumber}`, diff --git a/src/server/routes/scheduling/suggestions.ts b/src/server/routes/scheduling/suggestions.ts index a42b34d..d0b3924 100644 --- a/src/server/routes/scheduling/suggestions.ts +++ b/src/server/routes/scheduling/suggestions.ts @@ -212,7 +212,7 @@ app.get('/', async (c) => { const predictedYearEnd = currentYearMileage + customerAvgDaily * daysLeft; const currentYearIsQualified = row.current_year_is_qualified === 1; - const classification = classifyVehicle(currentYearIsQualified, predictedYearEnd, yearTarget); + const classification = classifyVehicle(currentYearIsQualified, currentYearMileage, yearTarget, predictedYearEnd); enrichedVehicles.push({ plateNumber: plate,