Intelligence par segment : chaque traversée entre deux lieux génère un enregistrement avec 12+ métriques, une géométrie de route, un clustering d'itinéraire. Les moyennes flotte alimentent l'ETA prédictive. Comparaison véhicule/flotte/route en temps réel.
L'intelligence segment repose sur 3 tables à rôles distincts :
| Table | Role | Écritures | Lecture |
|---|---|---|---|
| segment_records | Données brutes par traversée | Moteur (sync, step 7) | Dashboard, cron stats |
| segment_géométries | Trace GPS PostGIS (1:1) | waitUntil (async) | Carte overlay |
| waypoint_transit_stats | Moyennes flotte par paire | Cron quotidien 03:00 UTC | ETA prédictive (« hot path ») |
segment_records) sont la source de vérité. Les stats flotte (waypoint_transit_stats) sont pré-calculées pour l'ETA en temps réel. Les stats par véhicule ne sont PAS pré-calculées — elles sont requêtées à la demande depuis segment_records.Déclencheur : WaypointEntered à la séquence > 1, ET le lieu précédent dans la mission a un exited_at renseigné.
Données : departed_at = from_visit.exited_at, arrived_at = to_visit.entered_at, transit_seconds = différence horloge murale (inclut arrêts + gaps).
Ex : Garoua → Maroua, départ 08:00, arrivée 14:01. transit_seconds = 21 660s, trip_count = 2 (1 arret mi-parcours).
5 requêtes sur la plage [departed_at, arrived_at] peuplent 12+ colonnes en une seule transaction atomique :
trips → trip_count, distance_m, driving_seconds, avg/max_speed, idle_seconds, fuel_start/end/consumed/efficiency
stops → stop_count, total_stop_seconds
data_gaps → gap_count, total_gap_seconds
vehicle_events → speeding_event_count
corridor_deviations → deviation_count
| Groupe | Colonnes | Source |
|---|---|---|
| Identité | tenant_id, vehicle_id, mission_id, from/to_waypoint_id, from/to_visit_id | Contexte batch |
| Temps | departed_at, arrived_at, transit_seconds, driving_seconds | Visits + trips |
| Conduite | trip_count, distance_m, avg_speed_kmh, max_speed_kmh, idle_seconds | trips (somme) |
| Carburant | fuel_start_pct, fuel_end_pct, fuel_consumed_liters, fuel_efficiency_l100km | 1er/dernier trip |
| Arrêts | stop_count, total_stop_seconds | stops (plage) |
| Gaps | gap_count, total_gap_seconds | data_gaps (plage) |
| Sécurité | speeding_event_count, deviation_count | vehicle_events, corridor_deviations |
| Contexte | vehicle_type, season (dry/rainy), route_cluster_key | Véhicule + date + async |
Après le commit de la transaction, un callback waitUntil :
1. Requête les positions dans la plage [departed_at, arrived_at] (~600 points pour un segment de 5h)
2. Construit un PostGIS LineString
3. Calcule le route_cluster_key par midpoint-box check
4. INSERT dans segment_géométries
5. Backfill route_cluster_key sur segment_records
~12 KB par géométrie (600 points GPS). Stockage séparé pour éviter le TOAST bloat sur la table métriques.
3 à 5 boîtes englobantes prédéfinies par paire de lieux, placées aux points de décision (embranchements, contournements de ville).
Le trace GPS est teste contre chaque boite avec ST_Intersects(path, box). Le résultat ordonne forme la cle : "A-C-E" = le camion est passe par les boîtes A, C et E.
Dashboard : GROUP BY route_cluster_key pour comparer les métriques par variante de route.
O(n x k) avec k = 3-5 boîtes. Instantané à toute échelle. Remplace ST_FréchetDistance (O(n x m), impraticable).
Fréquence : Cron quotidien à 03:00 UTC + à chaque completion de mission (entrée au dernier lieu destination).
Méthode : INSERT ... ON CONFLICT (tenant_id, from_waypoint_id, to_waypoint_id) DO UPDATE SET ...
Agrégé depuis segment_records : count, avg, p50, p90 pour transit, conduite, carburant, arrêts, gaps, vitesse.
Seuls les segment_records complets (métriques valides) contribuent aux agrégats.
Déclencheur : Chaque WaypointEntered pour un lieu de mission.
Formule :
ETA = maintenant + Σ(p50_driving_seconds) + Σ(avg_stop_seconds)
pour les segments restants dans la sequence mission.
Décision D-segment-eta-decomposition : Utilise conduite + arrêts décomposés, PAS le transit horloge murale (qui inclut les temps de frontière variables).
p50 (médiane) est plus robuste que la moyenne pour les distributions asymétriques (attentes douanières de 30 min à 14h à Garoua-Boulai).
Condition : ETA projeté > échéance mission (sur le expected_arrival_at du dernier lieu destination).
Résultat : Émission d'un événement mission_delay_predicted avec un tier de confiance basé sur le sample_count des stats de transit.
Notification : WhatsApp au propriétaire avec le retard estimé et le niveau de confiance.
Ex : Douala → Kousseri, 14h01 actuel pour A→B (moyenne 12h). ETA projeté dépasse l'échéance de 2h21. Confiance = high (35 échantillons).
Condition : Le camion rattrape son retard. À la prochaine WaypointEntered, l'ETA est recalculé et retombe sous l'échéance.
Résultat : L'événement mission_delay_predicted ouvert est auto-résolu.
Requête : UPDATE vehicle_events SET status = 'auto_resolved' WHERE vehicle_id AND event_type = 'mission_delay_predicted' AND mission_id AND status IN ('open', 'investigating')
Ex : Après le segment B→C, le chauffeur a conduit plus vite que prévu. L'ETA recalculé passe sous l'échéance. L'alerte est automatiquement levée.
Même camion, même segment, au fil du temps. Détecte la dégradation.
Requête : segment_records WHERE vehicle_id = X AND from/to_waypoint_id = Y/Z ORDER BY departed_at
Ex : K12 Douala → Ngaoundere. Janvier : 22 L/100km. Mars : 27 L/100km. +23% → maintenance a prévoir.
Tous les camions sur le même segment → moyenne flotte. Par route_cluster_key → comparaison de variantes.
Ex flotte : Garoua → Kousseri, moyenne 8h. K12 = 14h (+75%). Ex route : Route A (sud) +15% carburant, 2h plus rapide. Route B (nord) -15% carburant, 5 checkpoints.
Historique véhicule : segment_records filtre par vehicle_id + paire de lieux
Benchmark flotte : waypoint_transit_stats pour la moyenne, segment_records pour la valeur véhicule
Comparaison route : segment_records GROUP BY route_cluster_key + segment_géométries pour le rendu carte
| Décision | Choix | Raison |
|---|---|---|
| D-segment-records | Données brutes (segment_records) + agrégats flotte (waypoint_transit_stats). Pas de table stats par véhicule. | Les stats véhicule sont dérivables des données brutes à la requête. Une 3e table ajouterait de la complexité cron sans bénéfice pour la latence dashboard. |
| D-segment-route-cluster | Clustering par midpoint-box, pas ST_FréchetDistance. | O(n x k) avec k=3-5 boîtes vs O(n x m) pour Fréchet. Le corridor n'a que 2-3 choix de route par paire de lieux. Instantané à toute échelle. |
| D-segment-eta-decomposition | ETA = p50_driving + avg_stop, pas avg_transit (horloge murale). | Le transit horloge inclut les temps de frontière variables (30 min à 14h à Garoua-Boulai). Une valeur aberrante déforme l'ETA de 16%. La décomposition conduite + arrêts isole la variabilité. |
sample_count sur waypoint_transit_stats indique le tier : low (< 5), medium (5-20), high (> 20).
| # | Cas | Déclencheur | Type |
|---|---|---|---|
| 1 | Création segment record | WaypointEntered (seq > 1 + previous exited) | Moteur / sync |
| 2 | Métriques synchrones (5 requêtes) | Dans la transaction du segment record | Sync |
| 3 | Géométrie asynchrone + clustering | waitUntil après commit | Async |
| 4 | Clustering midpoint-box | Dans le waitUntil, sur le LineString | Async |
| 5 | Refresh waypoint_transit_stats | Cron 03:00 UTC + completion mission | Cron |
| 6 | Calcul ETA prédictive | WaypointEntered (lieu mission) | Moteur |
| 7 | Prédiction retard mission | ETA > échéance | Moteur |
| 8 | Auto-résolution retard | ETA recalculé < échéance | Moteur |
| 9 | Historique par véhicule | Requête dashboard à la demande | Query |
| 10 | Benchmark flotte + comparaison route | Requête dashboard à la demande | Query |
Retour à l'index ·
Source : docs/brainstorm/engine-evolution/engine-v2/ (schemas.md, pipeline.md, async-ops.md, décisions.md)