fix(scheduling): exclude near-qualified vehicles from rescue candidates

For rescue_hopeless (换走) scenario, filter out inventory candidates
where totalMileage/yearTarget >= 80%. These are already near target
and swapping them in adds no value.

Instead, prioritize candidates with biggest mileage gaps — they benefit
most from accumulating any mileage, even at a low-mileage customer.

Before: showed 粤AGR6869 (93% done, 缺口 1990) as "可达标" — pointless
After:  shows 浙FF58720 (0% done, 缺口 60000) — genuinely needs mileage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-16 21:34:05 +08:00
parent 81305be2df
commit 1d1f8901aa

View File

@@ -76,13 +76,21 @@ export function generateSuggestions(
const suggestions: SchedulingSuggestion[] = []; const suggestions: SchedulingSuggestion[] = [];
// --- rescue_hopeless (high priority) --- // --- 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) { for (const vehicle of hopeless) {
const candidates: CandidateVehicle[] = inventoryVehicles const candidates: CandidateVehicle[] = inventoryVehicles
.filter( .filter((inv) => {
(inv) => if (!isTypeCompatible(vehicle.vehicleType, inv.vehicleType)) return false;
isTypeCompatible(vehicle.vehicleType, inv.vehicleType) && if (inv.region !== vehicle.region) return false;
inv.region === vehicle.region, // 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) => { .map((inv) => {
const effectiveTarget = inv.yearTarget ?? vehicle.yearTarget; const effectiveTarget = inv.yearTarget ?? vehicle.yearTarget;
const mileageGap = Math.max(0, effectiveTarget - inv.totalMileage); const mileageGap = Math.max(0, effectiveTarget - inv.totalMileage);
@@ -105,10 +113,8 @@ export function generateSuggestions(
}; };
}) })
.sort((a, b) => { .sort((a, b) => {
if (a.canQualifyAfterSwap !== b.canQualifyAfterSwap) // Prefer biggest gap first — these benefit most from any mileage
return a.canQualifyAfterSwap ? -1 : 1; return b.mileageGap - a.mileageGap;
// For hopeless: prefer already-qualified inventory, then highest completion
return b.completionRate - a.completionRate;
}) })
.slice(0, 5); .slice(0, 5);