Du capteur au reçu : automate 3 états (normal / draining / refilling), filtre median, détection de drain anormal, ravitaillement hors station, reconciliation reçu/capteur, bilan mission en FCFA. 22 cas d'usage.
Trois états, détection FLS uniquement. CAN utilise un compteur cumulatif aux bornes de trajet — pas de machine à états.
Automate 3 états du module carburant. Draining et refilling sont mutuellement exclusifs. Join intervals fusionnent les oscillations capteur.
normal → draining
Le niveau FLS median chute au-delà de min_drain_liters (défaut 5L). INSERT fuel_events (type=drain, state=active, initial_level).
Réservoir 600L : chute de 85% a 76% = -54L → transition immediate
draining → normal
Le niveau se stabilise pendant fuelDrainJoinSeconds (300s). UPDATE fuel_events (state=closed, final_level, volume, duration).
Plus de chute depuis 5 min → clôture du drain
normal → refilling
Le niveau FLS median monte au-delà de min_filling_liters (défaut 5L). INSERT fuel_events (type=filling, state=active).
Niveau passe de 42% a 58% = +96L → remplissage détecté
refilling → normal
Niveau stable pendant fuelFillingJoinSeconds (300s). UPDATE fuel_events (state=closed, final_level, volume).
Plein termine a 95% → événement clôture
join
2 événements du même type en < 5 min → fusionnés en un seul. Drain+drain = 1 événement. Drain+remplissage = 2 événements séparés. Évite les duplications par oscillation capteur.
Pompe s'arrête 2 min puis reprend → un seul remplissage
invariant
Draining et refilling ne peuvent pas etre actifs simultanement. Un signal de remplissage pendant un drain actif est ignore jusqu'a la clôture du drain.
Glitch capteur monte pendant un drain → ignore
pre-traitement
Fenêtre de 15 messages (7.5 min a 30s/msg). fuelFilterLevel 0=3 msgs, 3 (défaut)=15 msgs. Lisse les oscillations de carburant en mouvement. Applique au FLS uniquement, pas au CAN (l'ECU pre-filtre).
Route de montagne → niveau oscille ±3% → median elimine le bruit
skip
Si fuel_level_percent est NULL sur toutes les positions du batch, l'étape 8b (detectFuelState) est entièrement sautee. Aucun événement. fuelState reste normal.
Camion sans sonde → seuls les reçus chauffeur sont disponibles
cold start
Si fuelLevelHistory contient moins de lectures que la fenêtre du filtre median (fuelFilterLevel + 1), toutes les transitions sont supprimees. Le moteur collecte les lectures sans evaluer les seuils.
Véhicule équipé depuis 3 min → 6 lectures < 15 → pas de transition
reset
Saut soudain >30 points de pourcentage entre lectures consécutives (sans drain/remplissage en cours) → reset de fuelLevelHistory + mode démarrage a froid. Empêche les fausses détections par recalibration.
Ancien capteur 45% → nouveau capteur 82% → reset + 15 lectures avant evaluation
sécurité
>20 événements/heure → alerte sensor_fault_suspected (warning). Les événements carburant sont suspendus jusqu'a ce que le taux retombe. Protege contre un capteur defaillant qui inonde le systeme.
Sonde cassee oscille 40 fois/heure → suspension + avertissement fleet owner
Simulation d'une journee type : consommation normale, drain suspect, ravitaillement, conduite avec niveau bas.
Journee type : consommation normale → drain suspect (54L, 40 500 FCFA) → plat → ravitaillement (+95L) → consommation → seuil bas franchi
fuel_events. La couche de publication crée les jugements dans vehicle_events, lies via fuel_event_id + related_event_id. Fait (fuel_events) → Jugement (vehicle_events).CRITICAL
Drain rapide (<30 min) + hors station-service enregistrée + volume > seuil. Ne s'auto-résout jamais — investigation humaine obligatoire.
54L en 25 min, pas à une station → 40 500 FCFA de perte suspecte
WARNING
Remplissage détecté hors d'une station-service enregistrée. Événement dérivé de refueling_détectéd via related_event_id. Ne s'auto-résout jamais.
+80L détecté près de Meiganga, pas de station connue → à vérifier
INFO
Événement de base à chaque clôture de remplissage. Status = closed immédiatement. Pas de cycle de vie. Alimente le journal véhicule et les métriques trajet.
+95L a Station Total Garoua, 14:22
INFO
Événement de base à chaque clôture de drain. Alimente le journal véhicule. Si les critères isAbnormalDrain sont remplis, un fuel_drain_anomaly dérivé est créé.
-12L en 45 min pres d'une station → info seulement (maintenance possible)
WARNING
Niveau <15% ET vitesse > 0. Auto-résolution quand le niveau remonte. Évaluateur position par position, pas d'entité fuel_events.
Niveau 12%, vitesse 65 km/h → alerte conducteur + fleet owner
INFO
Niveau <15% ET véhicule à l'arrêt. Status closed immédiatement. Un seul par véhicule par jour calendaire (idempotency key: low_fuel:{vehicle_id}:{calendar_date}).
Niveau 8% stationne pour la nuit → info pour planification du lendemain
Arbre de décision isAbnormalDrain : les 3 conditions sont cumulatives — hors station + rapide + volume significatif
Deux flux convergent : le capteur automatique (fuel_events) et le reçu chauffeur (fuel_transactions). Fenêtre de ±30 min pour le matching.
RECONCILIATION
Le chauffeur soumet un reçu (POST /mobile/fuel-receipt). Le systeme trouve un fuel_event de type filling dans la fenêtre ±30 min. matched_fuel_event_id est positionne, discrepancy_liters calcule.
Reçu 100L a 14:30 → filling détecté a 14:22 (95L) → écart 5L
WARNING
Écart déclaré ≠ mesuré au-delà du seuil → événement warning. Ne s'auto-résout JAMAIS — vérification humaine requise. L'écart est exprimé en FCFA pour le fleet owner.
Déclaré 100L, mesuré 95L → écart 5L = 3 750 FCFA
NON MATCH
Aucun fuel_event dans ±30 min → stocké avec matched_fuel_event_id = NULL, signalé pour vérification. Jamais de rejet des données chauffeur. Si tankCapacityLiters est NULL (pas de capteur), pas de vérification d'écart.
"Reçu soumis (80L, Station Garoua) mais aucun ravitaillement détecté par le capteur — à vérifier."
METRIQUES
Au trajet : fuel_start_pct / fuel_end_pct (FLS) + can_fuel_consumed (CAN). Efficacite L/100km calculee depuis CAN si disponible, sinon delta FLS × tank_capacity_liters.
Trip Garoua→Maroua : CAN 45L, FLS 43.2L, efficacité 28.5 L/100km
GARDE
Si le delta CAN est négatif à la clôture du trajet (reset ECU ou débordement compteur) → can_fuel_consumed = null, fallback sur FLS : (fuel_start_pct - fuel_end_pct) * tank_capacity_liters. Jamais traiter un delta négatif comme zéro.
CAN actuel 12 450ml, CAN départ 13 200ml → delta -750ml → null, fallback FLS
trips (D-trip-fuel-metrics)| Colonne | Type | Source | Description |
|---|---|---|---|
| fuel_start_pct | NUMERIC(5,2) | FLS | Niveau % au départ du trajet |
| fuel_end_pct | NUMERIC(5,2) | FLS | Niveau % à la fin du trajet |
| fuel_consumed_liters | NUMERIC(7,2) | CAN ou FLS | CAN delta prioritaire, sinon FLS delta × capacite |
| fuel_efficiency_l100km | NUMERIC(5,2) | calcule | consumed / (distance_m / 100000) |
| can_fuel_consumed | NUMERIC(7,2) | CAN | Delta compteur cumulatif ECU (null si reset) |
| idle_fuel_liters | NUMERIC(7,2) | CAN | Consommation au ralenti (si disponible) |
| fillings_count | INTEGER | engine | Nombre de remplissages pendant le trajet |
| fillings_volume_liters | NUMERIC(7,2) | engine | Volume total des remplissages pendant le trajet |
Exemple : mission Douala → N'Djamena, camion K12, réservoir 600L.
Bilan FCFA d'une mission Douala → N'Djamena : 58L d'écart reçus (43 500 FCFA) + 54L drain anormal (40 500 FCFA) = 84 000 FCFA de pertes totales
fuel_events (D-fuel-entity)Entité de données calculées — même niveau que trips, stops, gaps. Les faits (drain, filling) sont ici. Les jugements (fuel_drain_anomaly, off_station_refueling) sont dans vehicle_events.
| Colonne | Type | Description |
|---|---|---|
| id | UUID PK | gen_random_uuid() |
| tenant_id | UUID NOT NULL | Isolation multi-tenant, RLS |
| vehicle_id | UUID NOT NULL | FK vehicles |
| event_type | TEXT | 'filling' | 'drain' |
| state | TEXT | 'active' | 'closed' |
| started_at | TIMESTAMPTZ | Début de l'événement |
| ended_at | TIMESTAMPTZ | Fin (null si actif) |
| duration_seconds | INTEGER | Durée totale |
| initial_level_pct | NUMERIC(5,2) | Niveau FLS au debut |
| final_level_pct | NUMERIC(5,2) | Niveau FLS à la fin |
| volume_liters | NUMERIC(7,2) | Delta × tank_capacity (null si capacite inconnue) |
| position | GEOGRAPHY(Point) | Localisation au debut |
| is_at_fuel_station | BOOLEAN | Proximité d'une station enregistrée |
| detection_source | TEXT | 'fls' | 'can' | 'both' |
| confidence | TEXT | 'high' | 'medium' | 'low' |
UNIQUE (vehicle_id, event_type) WHERE state = 'active' — un seul drain actif et un seul remplissage actif par véhicule à tout moment.fuel_transactions (D-fuel-transactions)Reçus chauffeur pour la reconciliation financiere et le suivi par pays.
| Colonne | Type | Description |
|---|---|---|
| id | UUID PK | gen_random_uuid() |
| tenant_id | UUID NOT NULL | RLS |
| vehicle_id | UUID NOT NULL | FK vehicles |
| driver_id | UUID | FK users (nullable) |
| transaction_at | TIMESTAMPTZ | Horodatage du reçu |
| volume_liters | NUMERIC(7,2) | Volume déclaré par le chauffeur |
| cost_fcfa | NUMERIC(10,2) | Cout total en FCFA |
| station_name | TEXT | Nom de la station |
| receipt_photo_url | TEXT | Photo du reçu (R2) |
| matched_fuel_event_id | UUID | FK fuel_events (null si pas de match) |
| discrepancy_liters | NUMERIC(7,2) | transaction.volume - fuel_event.volume |
| country_code | TEXT | CM ou TD (depuis border_crossing) |
| fuel_price_per_liter | NUMERIC(7,2) | Prix réglementé au moment de l'achat |
| Paramètre | Niveau | Défaut | Description |
|---|---|---|---|
| tank_capacity_liters | véhicule | null | Capacité réservoir. Si null, volume_liters = null sur fuel_events |
| min_drain_liters | véhicule | 5L | Seuil de détection de perte (en litres absolus) |
| min_filling_liters | véhicule | 5L | Seuil de détection de remplissage |
| fuel_filter_level | véhicule | 3 | 0=3 msg, 1=5 msg, 2=9 msg, 3=15 msg (fenêtre median) |
| fuel_drain_join_seconds | véhicule | 300 | Fenêtre de fusion pour drains consécutifs |
| fuel_filling_join_seconds | véhicule | 300 | Fenêtre de fusion pour remplissages consécutifs |
| fuel_drain_threshold_percent | tenant | — | Seuil d'alerte (tenant_event_config) |
| low_fuel_threshold_percent | tenant | 15% | Seuil low_fuel (tenant_event_config) |
| Type | Sévérité | Auto-resolve | Table source | Déclencheur |
|---|---|---|---|---|
| fuel_drain_anomaly | CRITICAL | Jamais | fuel_events (drain) | isAbnormalDrain = true |
| off_station_refueling | WARNING | Jamais | fuel_events (filling) | !is_at_fuel_station |
| fuel_gauge_discrepancy | WARNING | Jamais | fuel_transactions | discrepancy > seuil |
| low_fuel_driving | WARNING | Oui (niveau remonte) | position stream | fuel < 15% + speed > 0 |
| low_fuel | INFO | N/A (clos immédiatement) | position stream | fuel < 15% + stationnaire |
| refueling_détectéd | INFO | N/A (clos immédiatement) | fuel_events (filling) | FuelFillingEnded |
| fuel_drain_détectéd | INFO | N/A (clos immédiatement) | fuel_events (drain) | FuelDrainEnded |
fuel_drain_anomaly, off_station_refueling et fuel_gauge_discrepancy ne s'auto-résolvent JAMAIS. Chaque incident de carburant nécessite une investigation humaine. Le propriétaire de flotte décide si c'est un vol, une erreur capteur ou un transfert légitime.fuel_events est une entité de données calculées (même niveau que trips, stops, gaps). Les jugements (anomalies, écarts) vivent dans vehicle_events et sont dérivés via related_event_id.
detection_source sur fuel_events enregistre la contribution de chaque capteur.
tank_capacity_liters convertit les % en litres. Nullable si inconnu — le moteur travaille alors en pourcentages uniquement.
country_code depuis le dernier border_crossing. Écart en FCFA pour le fleet owner. Les reçus ne sont jamais rejetes.
trips. Priorité : CAN > FLS > taux configuré. Le garde CAN reset (delta négatif → null) empêche les fausses métriques.
22 cas d'usage documentes. Source : state-machines.md §3.7, pipeline.md step 8b, async-ops.md §10, schemas.md §§2.8-2.9, décisions.md D-fuel-*
← Retour à l'index