fix(scheduling): fix vehicle type classification and algorithm candidate matching
- classifyVehicleType now parses dic_type.dic_name (e.g. "4.5吨冷链车") instead of raw model code - Remove overly strict completionRate >= 0.8 filter for hopeless candidates - Use vehicle's yearTarget as fallback when inventory has no assessment target - Filter out suggestions with no candidates (not actionable) - estimatedGain counts rescue_hopeless suggestions as potential gains Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,15 +71,14 @@ export function generateSuggestions(
|
||||
.filter(
|
||||
(inv) =>
|
||||
isTypeCompatible(vehicle.vehicleType, inv.vehicleType) &&
|
||||
inv.region === vehicle.region &&
|
||||
inv.completionRate >= 0.8,
|
||||
inv.region === vehicle.region,
|
||||
)
|
||||
.map((inv) => {
|
||||
const mileageGap = (inv.yearTarget ?? 0) - inv.totalMileage;
|
||||
const effectiveTarget = inv.yearTarget ?? vehicle.yearTarget;
|
||||
const mileageGap = Math.max(0, effectiveTarget - inv.totalMileage);
|
||||
const predictedAfterSwap =
|
||||
inv.totalMileage + vehicle.customerAvgDaily * vehicle.daysLeft;
|
||||
const canQualifyAfterSwap =
|
||||
inv.yearTarget != null && predictedAfterSwap >= inv.yearTarget;
|
||||
const canQualifyAfterSwap = predictedAfterSwap >= effectiveTarget;
|
||||
return {
|
||||
plateNumber: inv.plateNumber,
|
||||
targetId: inv.targetId,
|
||||
@@ -87,7 +86,7 @@ export function generateSuggestions(
|
||||
vehicleType: inv.vehicleType,
|
||||
totalMileage: inv.totalMileage,
|
||||
completionRate: inv.completionRate,
|
||||
yearTarget: inv.yearTarget,
|
||||
yearTarget: inv.yearTarget ?? vehicle.yearTarget,
|
||||
region: inv.region,
|
||||
province: inv.province,
|
||||
mileageGap,
|
||||
@@ -98,6 +97,7 @@ 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;
|
||||
})
|
||||
.slice(0, 5);
|
||||
@@ -125,11 +125,11 @@ export function generateSuggestions(
|
||||
inv.region === vehicle.region,
|
||||
)
|
||||
.map((inv) => {
|
||||
const mileageGap = (inv.yearTarget ?? 0) - inv.totalMileage;
|
||||
const effectiveTarget = inv.yearTarget ?? vehicle.yearTarget;
|
||||
const mileageGap = Math.max(0, effectiveTarget - inv.totalMileage);
|
||||
const predictedAfterSwap =
|
||||
inv.totalMileage + vehicle.customerAvgDaily * vehicle.daysLeft;
|
||||
const canQualifyAfterSwap =
|
||||
inv.yearTarget != null && predictedAfterSwap >= inv.yearTarget;
|
||||
const canQualifyAfterSwap = predictedAfterSwap >= effectiveTarget;
|
||||
return {
|
||||
plateNumber: inv.plateNumber,
|
||||
targetId: inv.targetId,
|
||||
@@ -137,7 +137,7 @@ export function generateSuggestions(
|
||||
vehicleType: inv.vehicleType,
|
||||
totalMileage: inv.totalMileage,
|
||||
completionRate: inv.completionRate,
|
||||
yearTarget: inv.yearTarget,
|
||||
yearTarget: inv.yearTarget ?? vehicle.yearTarget,
|
||||
region: inv.region,
|
||||
province: inv.province,
|
||||
mileageGap,
|
||||
@@ -164,22 +164,27 @@ export function generateSuggestions(
|
||||
});
|
||||
}
|
||||
|
||||
// Remove suggestions with no candidates
|
||||
const filteredSuggestions = suggestions.filter((s) => s.candidates.length > 0);
|
||||
|
||||
// Sort: high priority first
|
||||
suggestions.sort((a, b) => {
|
||||
filteredSuggestions.sort((a, b) => {
|
||||
if (a.priority === b.priority) return 0;
|
||||
return a.priority === 'high' ? -1 : 1;
|
||||
});
|
||||
|
||||
const estimatedGain = suggestions.filter((s) =>
|
||||
s.candidates.some((c) => c.canQualifyAfterSwap),
|
||||
// estimatedGain: count suggestions where at least one candidate canQualifyAfterSwap,
|
||||
// plus rescue_hopeless suggestions (each rescued car can potentially qualify at a new customer)
|
||||
const estimatedGain = filteredSuggestions.filter((s) =>
|
||||
s.candidates.some((c) => c.canQualifyAfterSwap) || s.type === 'rescue_hopeless',
|
||||
).length;
|
||||
|
||||
const summary: SchedulingSummary = {
|
||||
qualifiedCount: qualified.length,
|
||||
hopelessCount: hopeless.length,
|
||||
suggestionCount: suggestions.length,
|
||||
suggestionCount: filteredSuggestions.length,
|
||||
estimatedGain,
|
||||
};
|
||||
|
||||
return { suggestions, summary };
|
||||
return { suggestions: filteredSuggestions, summary };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user