diff --git a/src/server/routes/scheduling/algorithm.ts b/src/server/routes/scheduling/algorithm.ts index 9961556..815bf53 100644 --- a/src/server/routes/scheduling/algorithm.ts +++ b/src/server/routes/scheduling/algorithm.ts @@ -76,13 +76,21 @@ export function generateSuggestions( const suggestions: SchedulingSuggestion[] = []; // --- rescue_hopeless (high priority) --- + // For this scenario: take the hopeless car away to a high-mileage customer, + // and give the low-mileage customer a replacement from inventory. + // Exclude near-qualified candidates (completionRate >= 80%) — no point swapping + // in a car that's basically already at target. + // Instead, pick cars with BIG gaps: they benefit from any mileage, even at a low customer. for (const vehicle of hopeless) { const candidates: CandidateVehicle[] = inventoryVehicles - .filter( - (inv) => - isTypeCompatible(vehicle.vehicleType, inv.vehicleType) && - inv.region === vehicle.region, - ) + .filter((inv) => { + if (!isTypeCompatible(vehicle.vehicleType, inv.vehicleType)) return false; + if (inv.region !== vehicle.region) return false; + // Exclude near-qualified: yearTarget known and already >= 80% done + const effectiveTarget = inv.yearTarget ?? vehicle.yearTarget; + if (effectiveTarget > 0 && inv.totalMileage / effectiveTarget >= 0.8) return false; + return true; + }) .map((inv) => { const effectiveTarget = inv.yearTarget ?? vehicle.yearTarget; const mileageGap = Math.max(0, effectiveTarget - inv.totalMileage); @@ -105,10 +113,8 @@ export function generateSuggestions( }; }) .sort((a, b) => { - if (a.canQualifyAfterSwap !== b.canQualifyAfterSwap) - return a.canQualifyAfterSwap ? -1 : 1; - // For hopeless: prefer already-qualified inventory, then highest completion - return b.completionRate - a.completionRate; + // Prefer biggest gap first — these benefit most from any mileage + return b.mileageGap - a.mileageGap; }) .slice(0, 5);