From add3c08cee1e18c25f5ee4ebfdfca50172153b45 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Mon, 6 Jul 2026 08:47:31 -0400 Subject: [PATCH] =?UTF-8?q?feat(ops):=20Sugg=C3=A9rer=20=E2=80=94=20re-opt?= =?UTF-8?q?imize=20existing=20(dispatched)=20jobs=20as=20a=20simulation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggérer peut servir de SIMULATION sur des jobs DÉJÀ dispatchés : nouveau toggle « Ré-optimiser aussi les jobs déjà assignés » dans la config. Quand activé, le solveur VRP reçoit AUSSI les noms des jobs assignés de la fenêtre (assignedNamesForWindow, depuis occByTechDay) en plus du pool → repropose une répartition de TOUT (existant + nouveaux jobs ajoutés). Force le solveur (l'heuristique locale n'a que le pool en mémoire). Aperçu seulement — rien n'est écrit avant « Appliquer » (qui réassigne). Vérifié : toggle rendu dans la config Suggérer, 0 nouvelle erreur. Le résultat de ré-optimisation (solveur roster-solver) est à confirmer en prod (comme les autres runs solveur). Co-Authored-By: Claude Opus 4.8 --- apps/ops/src/pages/PlanificationPage.vue | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue index d3e0835..e10efd5 100644 --- a/apps/ops/src/pages/PlanificationPage.vue +++ b/apps/ops/src/pages/PlanificationPage.vue @@ -1038,6 +1038,11 @@ {{ ({ optimize: 'solveur VRP (OR-Tools) : routes réelles minimisées + compétences + temps sur place — regroupe les secteurs, spécialistes d\'abord', smart: 'proximité + compétence + charge', best: 'concentre le travail sur les meilleurs (cascade)', balance: 'répartit également entre tous (round-robin)', enough: 'qualifié mais le moins sur-qualifié → réserve les experts (distance + temps pris en compte)' })[suggestDlg.strategy] }} + +
+ + Inclut les {{ assignedNamesForWindow().length }} job(s) déjà assigné(s) du/des jour(s) → le solveur repropose une répartition de TOUT (existant + nouveaux). Force le solveur VRP. Aperçu seulement — « Appliquer » réassigne. +
Coûts : @@ -4242,7 +4247,13 @@ function selectAllSector (city) { if (assignPanel.showMap) refreshAssignMap() } // ── D : SUGGESTION de répartition (glouton proximité + charge + compétence), REVUE en surcouche avant d'appliquer ── -const suggestDlg = reactive({ open: false, mode: 'config', plan: [], building: false, applying: false, fromSel: false, techSel: {}, makeShifts: true, strategy: 'optimize', view: 'list', jobTime: {} }) // défaut = solveur VRP (1re génération optimise déjà, plus de placeholders) · strategy: optimize|smart|best|balance|enough +const suggestDlg = reactive({ open: false, mode: 'config', plan: [], building: false, applying: false, fromSel: false, techSel: {}, makeShifts: true, strategy: 'optimize', view: 'list', jobTime: {}, includeAssigned: false }) // défaut = solveur VRP · strategy: optimize|smart|best|balance|enough · includeAssigned = simulation re-optimisant AUSSI les jobs déjà assignés +// Noms des jobs DÉJÀ assignés sur la fenêtre (pour la simulation « ré-optimiser l'existant ») — depuis l'occupation. +function assignedNamesForWindow () { + const win = new Set(suggestWindow.value); const out = new Set() + for (const key in occByTechDay.value) { if (!win.has(key.split('|')[1])) continue; for (const n of (occByTechDay.value[key].jobs || [])) if (n) out.add(n) } + return [...out] +} const prevOpen = reactive({}) // revue : sections « déjà assigné(s) » dépliées (clé techId|iso) // ── Revue : détail du ticket au clic + temps révisable + temps de route ENTRE arrêts (OSRM) ── const durOverride = reactive({}) // jobName → heures révisées par le dispatcher (survit aux Ré-optimiser de la session) @@ -4637,6 +4648,9 @@ function techQuality (t) { const suggestTechsRanked = computed(() => [...(visibleTechs.value || [])].sort((a, b) => techQuality(a) - techQuality(b))) async function runSuggestion () { if (!suggestSelCount.value) { $q.notify({ type: 'warning', message: 'Choisis au moins un technicien disponible.', timeout: 2500 }); return } + // « Ré-optimiser l'existant » : le solveur récupère les données des jobs assignés par leur nom (l'heuristique locale + // n'a que le pool en mémoire) → on force le solveur VRP. + if (suggestDlg.includeAssigned) suggestDlg.strategy = 'optimize' suggestDlg.building = true try { if (suggestDlg.strategy === 'optimize') await optimizeSuggestion() // solveur VRP OR-Tools (routes + compétences + temps sur place) @@ -4678,7 +4692,8 @@ async function optimizeSuggestion () { try { const r = await roster.optimizePlanHub({ dates: suggestWindow.value, tech_ids: selTechs.map(t => t.id), - job_names: suggestJobs().map(j => j.name), // ne répartir QUE les jobs des jours cochés (ou la sélection lasso) + // 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() : [])])], opts: { rank_weight: solverOpts.rankWeight, speed_kmh: solverOpts.speedKmh, max_seconds: solverOpts.maxSeconds, overtime_coef: solverOpts.overtimeCoef }, dur_overrides: { ...durOverride }, job_windows, })