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>
route_solver.py :
- dimension Temps PAR VÉHICULE (AddDimensionWithVehicleTransits) : service × efficacité(tech,skill)
→ un tech rapide (facteur bas) tient plus de jobs.
- coût d'arc : + efficacité (rapide récompensé) + pénalité de POLYVALENCE (nb de compétences) → les cas
simples vont d'abord aux techs les moins polyvalents, réservant les experts. eff_bias=8, versatility_weight=6.
roster.js : passe skill_eff + efficiency par véhicule au solveur.
Testé en conteneur prod : tech rapide+mono-compétence rafle les jobs, polyvalent réservé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- default strategy = optimize → the FIRST Générer runs the VRP (real
techs, no more greedy placeholders on first attempt).
- occupation bar is now PER-DAY (worst day), so a 2-day window shows /8h
not /16h; overload shows in red.
- solver: each vehicle may go up to +20% overtime (≈120% cap, hard) but
time past the nominal shift is soft-penalized → overflow spreads to
techs still under 100% before anyone does overtime. Verified: 20 jobs /
2 techs → 9h+9h (balanced, ≤9.6h), overflow dropped; under capacity it
still concentrates (no forced equalization).
- priority medium flag = amber (yellow/orange) per request.
Verified live: default optimize on, 16 real techs / 0 placeholders,
per-day /8h occupation, none over, medium flag amber.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Time-aware dispatch in the VRP optimizer:
- solver: per-job urgent_weight → SetCumulVarSoftUpperBound(idx, 0, w),
a soft cost on arrival time that pulls urgent jobs to the START of the
route (conditions the tech's day start). Verified: a far urgent job is
served first instead of last. AM/PM handled via existing tw_start/end.
- SPA: per-entry AM / PM / ⚡ chips (session map suggestDlg.jobTime,
survives re-solves) + a "Ré-optimiser" button. optimizeSuggestion maps
AM→[480,720], PM→[720,960] (hard window) and urgency (job priority OR
manual ⚡) → urgent_weight. Verified: chips render, toggle persists
across re-optimize, OSRM still engaged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Skill capability stays a hard filter, so a multi-skill tech (e.g. an
installer who also has the repair skill) can take those jobs. But the
specialist bias (skill-order rank) was too strong (6 virtual min/rank),
so a far repair-specialist beat a nearby installer-who-repairs. Lower
rank_weight to 2 → distance dominates; specialty only breaks near-ties.
Verified: repair job near a rank-3 multi-skill installer (4 min) vs a
far rank-0 specialist (14 min) — old weight picked the far specialist
(bug), new weight picks the nearby installer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real vehicle-routing optimization behind the Suggérer UI, reusing our
existing OR-Tools solver service (no new stack). Fixes the greedy's
structural limits (sector-splitting, no global optimization).
- roster-solver: new route_solver.py (OR-Tools Routing / VRPTW).
Minimizes real travel; skills = hard filter (VehicleVar ∈ allowed∪{-1};
SetAllowedVehiclesForIndex has a broken Span typemap in ortools 9.15);
on-site service time within each tech's shift window; optional per-job
time windows; unfittable jobs left unassigned (drop penalty) instead of
infeasible; specialist bias via skill order (per-vehicle arc cost).
New POST /route endpoint. Dockerfile now COPYs route_solver.py.
Unit-tested: skills respected, sectors consolidated, edge cases safe.
- hub: POST /roster/optimize-routes → proxies to solver /route.
- ops SPA: "⚡ Optimiser" strategy. Greedy buckets jobs into days +
placeholders, then the solver re-optimizes each day (assignment +
routes) among shifted techs; result maps into the same review dialog
(occupation bars, route map, swap/merge reused). Graceful fallback to
greedy if the solver is unreachable (never drops jobs). shiftWindowMin
derives each vehicle's shift from templates.
Phase 2 (later): OSRM/Mapbox road-time matrix for exact travel times.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>