← Retour à l'index

6. Segment et Insight

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.

10Cas d'usage
3Tables
12+Métriques par segment
3Niveaux de confiance

A. Architecture des tables

L'intelligence segment repose sur 3 tables à rôles distincts :

TableRoleÉcrituresLecture
segment_recordsDonnées brutes par traverséeMoteur (sync, step 7)Dashboard, cron stats
segment_géométriesTrace GPS PostGIS (1:1)waitUntil (async)Carte overlay
waypoint_transit_statsMoyennes flotte par paireCron quotidien 03:00 UTCETA prédictive (« hot path »)
Principe clé : Les données brutes (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.

B. Création d'un segment

Diagramme : flow de creation segment

Diagramme 1 Lieu A Garoua exited_at 08:00 Conduite + arrêts + gaps Trajet 1 Arret Trajet 2 trip_count = 2 Lieu B Maroua entered_at 14:01 WaypointEntered sequence > 1 segment_records INSERT SYNC (dans la transaction) 5 requêtes plage horaire : trips, stops, data_gaps, vehicle_events (speeding), corridor_deviations = 12+ colonnes métriques ASYNC (waitUntil) ~600 positions → PostGIS LineString → segment_géométries Midpoint-box check → route_cluster_key backfill Légende Sync (transaction) Async (waitUntil)
Figure 1 — Création d'un segment_record au step 7 du pipeline. Métriques synchrones, géométrie asynchrone.

UC-1 : Création du segment record

moteursync

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).

UC-2 : Métriques synchrones dans la transaction

sync5 requêtes

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

Colonnes du segment record

GroupeColonnesSource
Identitétenant_id, vehicle_id, mission_id, from/to_waypoint_id, from/to_visit_idContexte batch
Tempsdeparted_at, arrived_at, transit_seconds, driving_secondsVisits + trips
Conduitetrip_count, distance_m, avg_speed_kmh, max_speed_kmh, idle_secondstrips (somme)
Carburantfuel_start_pct, fuel_end_pct, fuel_consumed_liters, fuel_efficiency_l100km1er/dernier trip
Arrêtsstop_count, total_stop_secondsstops (plage)
Gapsgap_count, total_gap_secondsdata_gaps (plage)
Sécuritéspeeding_event_count, deviation_countvehicle_events, corridor_deviations
Contextevehicle_type, season (dry/rainy), route_cluster_keyVéhicule + date + async

C. Géométrie et clustering de route

UC-3 : Construction asynchrone de la géométrie

waitUntil

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.

UC-4 : Clustering d'itinéraire par midpoint-box

waitUntil

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).

Diagramme : concept de clustering par midpoint-box

Diagramme 2 Garoua (départ) Kousseri (arrivée) Box A Mora (nord) Box B Maroua (sud) Box C Box D Box E Route nord "A-C-E" Route sud "B-D-E" Route nord (A-C-E) Route sud (B-D-E)
Figure 2 — Deux itinéraires entre les mêmes lieux, différenciés par les bounding boxes traversées. Le dashboard compare les métriques par route_cluster_key.

D. Statistiques flotte et ETA prédictive

UC-5 : Rafraîchissement waypoint_transit_stats

cron 03:00 UTC

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.

UC-6 : Calcul ETA prédictive

moteurstep 7

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).

Diagramme : projection ETA à 5 lieux

Diagramme 3 A → B 6h01 réel B → C p50: 3h30 + arrêts 45min C → D p50: 4h + arrêts 1h20 D → E p50: 2h15 + arrêts 30min A Douala B Ngaoundere Position actuelle C Garoua D Maroua E Kousseri ETA projeté : +12h20 (3h30+45min) + (4h+1h20) + (2h15+30min) Comparaison avec l'échéance : ETA projeté = 14:01 + 12h20 = 02:21 (J+1) Échéance mission = 00:00 (J+1) Retard predit : +2h21 → mission_delay_predicted
Figure 3 — Projection ETA à chaque entrée de lieu. Les segments complétés utilisent le temps réel ; les restants utilisent les p50 historiques décomposés (conduite + arrêts).

E. Prédiction de retard et auto-résolution

UC-7 : Prédiction de retard mission

moteur

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).

UC-8 : Auto-resolution du retard

moteur

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.

Diagramme : tiers de confiance

Diagramme 4 FAIBLE sample_count < 5 ETA indicatif seulement. Portail : "Estimation preliminaire" "Peu de données historiques" MOYEN 5 ≤ sample_count ≤ 20 ETA raisonnablement fiable. Portail : "Estimation basée sur X traversées précédentes" ELEVE sample_count > 20 ETA a haute confiance. Portail : "Estimation fiable basée sur 35 traversées" 1-4 échantillons 5-20 échantillons 21+ échantillons
Figure 4 — Tiers de confiance dérivés du sample_count (non stocké dans la table, calcule à la requête). L'interface du portail adapte son libellé.

F. Comparaisons : véhicule, flotte et route

UC-9 : Historique par véhicule

à la demande

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.

UC-10 : Benchmark flotte + route

à la demandestats flotte

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.

Requêtes dashboard

à la demande

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

Diagramme : comparaisons véhicule / flotte / route

Diagramme 5 Historique véhicule (K12, Douala → Ngaoundere) Janvier 22 L/100km 14h, 3 arrêts Février 24 L/100km 15h, 4 arrêts Mars 27 L/100km +23% 17h, 5 arrêts Dégradation détectée : Maintenance moteur a prévoir Benchmark flotte (Garoua → Kousseri) Moyenne flotte 8h 478 km, 6 arrêts Camion K12 14h +75% 478 km, 11 arrêts, 3h idle avg flotte Comparaison de routes (Ngaoundere → Garoua) Route A (sud) "B-D-E" 6h, +15% carburant, 2 checkpoints 2h plus rapide Route B (nord) "A-C-E" 8h, -15% carburant, 5 checkpoints 15% moins de fuel Arbitrage transporteur Route A : livraison urgente Route B : economies carburant Décision basée sur les données
Figure 5 — Trois niveaux de comparaison : même camion au fil du temps, camion vs moyenne flotte, route A vs route B entre les mêmes lieux.

G. Schema SQL des 3 tables

segment_records (12+ métriques par traversée)

CREATE TABLE segment_records ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants(id), vehicle_id UUID NOT NULL REFERENCES vehicles(id), mission_id UUID NOT NULL REFERENCES missions(id), from_waypoint_id UUID NOT NULL REFERENCES waypoints(id), to_waypoint_id UUID NOT NULL REFERENCES waypoints(id), from_visit_id UUID NOT NULL REFERENCES waypoint_visits(id), to_visit_id UUID NOT NULL REFERENCES waypoint_visits(id), departed_at TIMESTAMPTZ NOT NULL, -- exited_at du visit d'origine arrived_at TIMESTAMPTZ NOT NULL, -- entered_at du visit destination transit_seconds INTEGER NOT NULL, -- horloge murale (conduite + arrêts + gaps) -- Conduite (agrege de tous les trips du segment) trip_count INTEGER NOT NULL DEFAULT 1, distance_m NUMERIC(12,1), driving_seconds INTEGER, avg_speed_kmh NUMERIC(5,1), max_speed_kmh NUMERIC(5,1), -- Carburant (1er trip fuel_start → dernier trip fuel_end) fuel_start_pct NUMERIC(5,2), fuel_end_pct NUMERIC(5,2), fuel_consumed_liters NUMERIC(7,2), fuel_efficiency_l100km NUMERIC(5,2), -- Arrêts et gaps dans la plage du segment stop_count INTEGER DEFAULT 0, total_stop_seconds INTEGER DEFAULT 0, gap_count INTEGER DEFAULT 0, total_gap_seconds INTEGER DEFAULT 0, idle_seconds INTEGER DEFAULT 0, -- Route et contexte route_cluster_key TEXT, -- ex: 'A-C-E' (backfill async) season TEXT CHECK (season IN ('dry', 'rainy')), vehicle_type TEXT, -- Sécurité speeding_event_count INTEGER NOT NULL DEFAULT 0, deviation_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX segment_records_segment ON segment_records (tenant_id, from_waypoint_id, to_waypoint_id); CREATE INDEX segment_records_vehicle_segment ON segment_records (tenant_id, vehicle_id, from_waypoint_id, to_waypoint_id, departed_at DESC); CREATE INDEX segment_records_mission ON segment_records (mission_id);

segment_géométries (trace GPS, 1:1)

CREATE TABLE segment_géométries ( segment_record_id UUID PRIMARY KEY REFERENCES segment_records(id) ON DELETE CASCADE, tenant_id UUID NOT NULL REFERENCES tenants(id), route_geometry GEOGRAPHY(LineString, 4326) NOT NULL, point_count INTEGER NOT NULL, -- contrôle qualité sans charger la géométrie route_cluster_key TEXT, -- midpoint-box → backfill sur segment_records created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() );

waypoint_transit_stats (moyennes flotte pré-calculées)

CREATE TABLE waypoint_transit_stats ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants(id), from_waypoint_id UUID NOT NULL REFERENCES waypoints(id), to_waypoint_id UUID NOT NULL REFERENCES waypoints(id), sample_count INTEGER NOT NULL DEFAULT 0, avg_transit_seconds INTEGER NOT NULL, p50_transit_seconds INTEGER, p90_transit_seconds INTEGER, avg_distance_m INTEGER, -- Métriques étendues (agrégées depuis segment_records) avg_driving_seconds INTEGER, avg_fuel_consumed_liters NUMERIC(7,2), avg_fuel_efficiency_l100km NUMERIC(5,2), avg_stop_count NUMERIC(4,1), avg_gap_count NUMERIC(4,1), avg_speed_kmh NUMERIC(5,1), -- Decomposition ETA (conduite + arrêts, pas horloge murale) p50_driving_seconds INTEGER, p90_driving_seconds INTEGER, avg_stop_seconds INTEGER, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(tenant_id, from_waypoint_id, to_waypoint_id) );

H. Décisions de design

DécisionChoixRaison
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é.
Émergence des benchmarks : Les comparaisons deviennent significatives à partir de 10+ segment records sur la même paire de lieux. En dessous de 10, la confiance est "low". Le sample_count sur waypoint_transit_stats indique le tier : low (< 5), medium (5-20), high (> 20).

I. Récapitulatif des 10 cas d'usage

#CasDéclencheurType
1Création segment recordWaypointEntered (seq > 1 + previous exited)Moteur / sync
2Métriques synchrones (5 requêtes)Dans la transaction du segment recordSync
3Géométrie asynchrone + clusteringwaitUntil après commitAsync
4Clustering midpoint-boxDans le waitUntil, sur le LineStringAsync
5Refresh waypoint_transit_statsCron 03:00 UTC + completion missionCron
6Calcul ETA prédictiveWaypointEntered (lieu mission)Moteur
7Prédiction retard missionETA > échéanceMoteur
8Auto-résolution retardETA recalculé < échéanceMoteur
9Historique par véhiculeRequête dashboard à la demandeQuery
10Benchmark flotte + comparaison routeRequête dashboard à la demandeQuery

Retour à l'index · Source : docs/brainstorm/engine-evolution/engine-v2/ (schemas.md, pipeline.md, async-ops.md, décisions.md)