solveur : le RANG (compétence principale) domine la polyvalence — répare la mauvaise répartition
Bug (Louis) : Stephane (réparation rang 1, installation secondaire) recevait 3 installations, William (installation rang 1/principale) une seule. Cause : versatility_weight=6 > rank_weight=2 → un tech moins polyvalent mais NON spécialiste rafflait les jobs hors de sa spécialité. Fix : rank_weight=20 (domine), versatility_weight=3 (léger départage entre techs de même rang → réserve les experts). Testé : William obtient les installations. eff/AM-PM/distance inchangés. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8ee4c834f8
commit
4ae2907172
|
|
@ -4858,7 +4858,7 @@ async function optimizeSuggestion () {
|
||||||
dates: suggestWindow.value, tech_ids: selTechs.map(t => t.id),
|
dates: suggestWindow.value, tech_ids: selTechs.map(t => t.id),
|
||||||
// job_names = pool filtré (+ jobs DÉJÀ assignés de la fenêtre si « ré-optimiser l'existant ») → le solveur les replace.
|
// job_names = pool filtré (+ jobs DÉJÀ assignés de la fenêtre si « ré-optimiser l'existant ») → le solveur les replace.
|
||||||
job_names: [...new Set([...suggestJobs().map(j => j.name), ...(suggestDlg.includeAssigned ? assignedNamesForWindow() : [])])],
|
job_names: [...new Set([...suggestJobs().map(j => j.name), ...(suggestDlg.includeAssigned ? assignedNamesForWindow() : [])])],
|
||||||
opts: { rank_weight: solverOpts.rankWeight, speed_kmh: solverOpts.speedKmh, max_seconds: solverOpts.maxSeconds, overtime_coef: solverOpts.overtimeCoef },
|
opts: { rank_weight: 20, versatility_weight: 3, speed_kmh: solverOpts.speedKmh, max_seconds: solverOpts.maxSeconds, overtime_coef: solverOpts.overtimeCoef }, // rang (compétence principale) DOMINE ; polyvalence = léger départage
|
||||||
dur_overrides: { ...durOverride }, job_windows,
|
dur_overrides: { ...durOverride }, job_windows,
|
||||||
})
|
})
|
||||||
if (!r || !r.ok || !Array.isArray(r.plan)) throw new Error((r && r.error) || 'réponse invalide')
|
if (!r || !r.ok || !Array.isArray(r.plan)) throw new Error((r && r.error) || 'réponse invalide')
|
||||||
|
|
@ -4919,7 +4919,7 @@ async function optimizeSuggestionLocal () {
|
||||||
}
|
}
|
||||||
if (!vehicles.length) { unassigned.push(...entries); continue } // aucun tech éligible ce jour → non assignés
|
if (!vehicles.length) { unassigned.push(...entries); continue } // aucun tech éligible ce jour → non assignés
|
||||||
let res
|
let res
|
||||||
try { res = await roster.optimizeRoutes({ jobs, vehicles, max_seconds: solverOpts.maxSeconds, rank_weight: solverOpts.rankWeight, speed_kmh: solverOpts.speedKmh, overtime_coef: solverOpts.overtimeCoef }) } catch (e) { unassigned.push(...entries); continue }
|
try { res = await roster.optimizeRoutes({ jobs, vehicles, max_seconds: solverOpts.maxSeconds, rank_weight: 20, versatility_weight: 3, speed_kmh: solverOpts.speedKmh, overtime_coef: solverOpts.overtimeCoef }) } catch (e) { unassigned.push(...entries); continue }
|
||||||
if (!res || res.status !== 'OK') { unassigned.push(...entries); continue } // solveur absent/erreur → non assignés (ne jamais perdre de jobs)
|
if (!res || res.status !== 'OK') { unassigned.push(...entries); continue } // solveur absent/erreur → non assignés (ne jamais perdre de jobs)
|
||||||
const placed = new Set()
|
const placed = new Set()
|
||||||
for (const rt of (res.routes || [])) {
|
for (const rt of (res.routes || [])) {
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ def solve_route(req: dict) -> dict:
|
||||||
eff_bias = float(req.get("eff_bias") if req.get("eff_bias") is not None else 8.0)
|
eff_bias = float(req.get("eff_bias") if req.get("eff_bias") is not None else 8.0)
|
||||||
# RÉSERVE DES EXPERTS : pénalise les techs POLYVALENTS (beaucoup de compétences) → les cas simples vont d'abord
|
# RÉSERVE DES EXPERTS : pénalise les techs POLYVALENTS (beaucoup de compétences) → les cas simples vont d'abord
|
||||||
# aux moins polyvalents (qui ne pourraient rien faire d'autre), gardant les experts pour les jobs exigeants.
|
# aux moins polyvalents (qui ne pourraient rien faire d'autre), gardant les experts pour les jobs exigeants.
|
||||||
vers_w = float(req.get("versatility_weight") if req.get("versatility_weight") is not None else 6.0)
|
vers_w = float(req.get("versatility_weight") if req.get("versatility_weight") is not None else 3.0) # TIEBREAKER léger seulement — le RANG (compétence principale) doit dominer
|
||||||
|
|
||||||
def eff_of(veh, sk):
|
def eff_of(veh, sk):
|
||||||
se = veh.get("skill_eff") or {}
|
se = veh.get("skill_eff") or {}
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ async function optimizePlan (body) {
|
||||||
...(w ? { tw_start_min: w.tw_start_min, tw_end_min: w.tw_end_min } : {}),
|
...(w ? { tw_start_min: w.tw_start_min, tw_end_min: w.tw_end_min } : {}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const res = await solveVrp({ jobs: sJobs, vehicles, max_seconds: Number(opts.max_seconds) || 8, rank_weight: opts.rank_weight != null ? Number(opts.rank_weight) : 2, speed_kmh: Number(opts.speed_kmh) || 45, overtime_coef: opts.overtime_coef != null ? Number(opts.overtime_coef) : 20 })
|
const res = await solveVrp({ jobs: sJobs, vehicles, max_seconds: Number(opts.max_seconds) || 8, rank_weight: opts.rank_weight != null ? Number(opts.rank_weight) : 20, versatility_weight: opts.versatility_weight != null ? Number(opts.versatility_weight) : 3, speed_kmh: Number(opts.speed_kmh) || 45, overtime_coef: opts.overtime_coef != null ? Number(opts.overtime_coef) : 20 })
|
||||||
if (!res || res.status !== 'OK') { for (const j of rest) unassigned.push(entryOf(j, iso, durOf(j))); continue }
|
if (!res || res.status !== 'OK') { for (const j of rest) unassigned.push(entryOf(j, iso, durOf(j))); continue }
|
||||||
const placed = new Set()
|
const placed = new Set()
|
||||||
for (const rt of (res.routes || [])) {
|
for (const rt of (res.routes || [])) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user