28 types d'événements en 5 domaines, 17 comportements système. Cycle de vie à 6 états, escalade, auto-résolution, notification 2 phases, WhatsApp bidirectionnel, heures de repos, cooldown et idempotence.
Chaque événement warning ou critical suit un cycle de vie à 6 états. Les événements info sont clos immédiatement et ne passent jamais par ce cycle.
open. L'auto-résolution cible open et investigating.| Sévérité | Cycle de vie | Notification | Heures de repos |
|---|---|---|---|
| info | Aucun. status = 'closed' immédiatement à l'insertion. |
Selon config tenant (souvent none) |
Supprimé |
| warning | Workflow complet : open → investigating → action_taken → resolved |
Selon config tenant | File d'attente → envoi à quiet_hours_end |
| critical | Même workflow, notification immédiate | Toujours immédiat | Bypass — envoi immédiat même à 3h du matin |
Chaque événement est défini par son domaine, sa sévérité par défaut, son déclencheur, son mode de notification par défaut, s'il peut s'auto-résoudre et sa fenêtre de cooldown.
| Type | Sévérité | Déclencheur | Mode | Auto-resolve | Cooldown | Source |
|---|---|---|---|---|---|---|
| Sécurité (6 types) | ||||||
| unauthorized_stop | warning | Arrêt à un lieu non enregistré. L'alerte se déclenche contextuellement : arrêt pendant déviation, arrêt nocturne + non enregistré + longue durée, ou zone restreinte. Escalade à critical après 30 min sans action. | immédiate | Oui — véhicule repart (StopClosed) | — | Consumer |
| driver_alarm_violation | critical | Mode park actif (driver app) + vitesse > 5 km/h | immédiate | Non | — | Consumer |
| towing_detected | critical | Vitesse > 10 km/h + contact OFF (multi-signal) | immédiate | Oui — contact ON ou vitesse = 0 | — | Consumer |
| power_disconnection | critical | Tension externe chute à batterie seule, véhicule toujours en émission | immédiate | Oui — tension externe restaurée | — | Consumer |
| tracker_anomaly | critical | Gap classifié tampering_suspect (contact ON, batterie OK, GSM perdu) |
immédiate | Non | — | Consumer |
| unauthorized_movement | warning | Mode park actif + vitesse > seuil, OU hors heures de conduite configurées + vitesse > seuil | immédiate | Oui — park mode désactivé, arrêt, ou heures de conduite reprennent | — | Consumer |
| Carburant (8 types) | ||||||
| fuel_drain_anomaly | critical | Dérivé de fuel_drain_detected + contexte anormal (pas en station, durée courte) |
immédiate | Non | — | Consumer |
| fuel_drain_detected | info | Niveau carburant baisse > fuel_drain_threshold_percent (10%) sur la fenêtre configurée |
none | N/A (info) | — | Consumer |
| off_station_refueling | warning | Dérivé de refueling_detected quand le lieu ne correspond pas à une station enregistrée |
immédiate | Non | — | Consumer |
| refueling_detected | info | Niveau carburant augmente > fuel_refill_threshold_liters (5L) |
none | N/A (info) | — | Consumer |
| low_fuel_driving | warning | Carburant < low_fuel_threshold_percent (15%) + vitesse > 0. Risque de dommage pompe. |
immédiate | Oui — niveau remonte au-dessus du seuil | — | Consumer |
| low_fuel | info | Carburant < seuil, véhicule à l'arrêt | none | N/A (info) | — | Consumer |
| fuel_gauge_discrepancy | warning | Écart capteur vs reçu chauffeur après ravitaillement | immédiate | Non | — | Consumer |
| fuel_consumption_anomaly | warning | L/100km dévie > 2 écarts-types du baseline véhicule sur le même segment | digest | Non (différé) | — | Consumer |
| Corridor (3 types) | ||||||
| corridor_deviation | warning | 3+ positions acceptées consécutives hors polygone corridor | immédiate | Oui — véhicule rentre dans le corridor (DeviationReturned) | 30 min | Consumer |
| deviation_with_stop | critical | Arrêt détecté pendant une déviation active. Se déclenche à CHAQUE arrêt hors corridor. | immédiate | Non | — | Consumer |
| border_crossing | info | Visite de waypoint de type border (GPS + MCC comme confirmation secondaire) |
none | N/A (info) | — | Consumer |
| Operations (7 types) | ||||||
| speeding | warning | Vitesse dépasse les seuils configurés (60 / 90 / 120 km/h, 3 paliers) | digest | Oui — 5 positions consécutives sous le seuil | 15 min | Consumer |
| excessive_idle | warning | Moteur en marche, véhicule à l'arrêt, durée excessive | digest | Oui — vitesse > 0 ou contact OFF | 30 min | Consumer |
| device_offline | warning | Cron : aucun message depuis gapAlertThresholdSeconds (1h par défaut) |
immédiate | Oui — prochaine position reçue | — | Cron |
| vehicle_offline_extended | warning | Gap ouvert > 1h. Escalade à critical si gap > 4h. | immédiate | Oui — gap ferme (données reprennent) | — | Cron |
| mission_sla_breach | critical | Cron : ETA dépasse la deadline de mission | immédiate | Non — un SLA violé est un fait | — | Cron |
| mission_delay_predicted | warning | ETA projetée dépasse la deadline, calculée à chaque entrée waypoint à partir des moyennes historiques | immédiate | Oui — véhicule rattrape son retard (mission_delay_cleared) |
— | Engine |
| night_driving | info | Vitesse > 20 km/h pendant les heures de nuit configurées | none | N/A (info) | — | Consumer |
| Logistique (9 types) | ||||||
| destination_reached | info | Visite de waypoint avec role destination |
immédiate | N/A (info) | — | Consumer |
| trip_started | info | Entité trajet créée | none | N/A (info) | — | Consumer |
| trip_completed | info | Entité trajet fermée | none | N/A (info) | — | Consumer |
| stop_labelled | info | Chauffeur labellise un arrêt via l'application mobile | none | N/A (info) | — | Driver |
| breakdown_reported | warning | Chauffeur signale une panne via l'application mobile | immédiate | N/A | — | Driver |
| delivery_confirmed | info | Chauffeur prend une photo de preuve de livraison à destination | none | N/A (info) | — | Driver |
| fuel_receipt_submitted | info | Chauffeur soumet un reçu carburant (photo + litres) | none | N/A (info) | — | Driver |
| mission_paused | info | Mission en pause (panne, route barree, attente, etc.) | immédiate | N/A (info) | — | Engine/Owner |
| mission_resumed | info | Mission reprise après pause | none | N/A (info) | — | Engine/Owner |
Chaque type d'événement est route vers un ou plusieurs canaux. Le propriétaire de flotte configuré les surcharges via tenant_notification_config.
L'auto-résolution cible les événements en statut open et investigating uniquement. Les événements en action_taken nécessitent une résolution humaine. L'auto-résolution a toujours priorité sur l'escalade.
auto_resolved quand la condition de gauche est satisfaite. Aussi inclus : power_disconnection (tension restaurée).Ces événements nécessitent une investigation manuelle. Même si la condition physique disparaît, le fait reste et demande une vérification humaine.
| Type | Raison |
|---|---|
driver_alarm_violation |
L'arrêt du véhicule n'explique pas pourquoi il a bouge avec le mode park actif. Nécessite une investigation. |
tracker_anomaly |
Nécessite une inspection physique du traceur GPS. |
fuel_drain_anomaly |
Nécessite une investigation du contexte de baisse de carburant anormale. |
off_station_refueling |
Nécessite une vérification du lieu de ravitaillement. |
deviation_with_stop |
Chaque arrêt pendant une déviation est un incident séparé nécessitant une investigation. |
mission_sla_breach |
Un SLA violé est un fait qui ne peut pas être annule. |
fuel_gauge_discrepancy |
L'écart entre capteur et reçu nécessite une vérification humaine. |
open → investigating → action_taken → resolved. Branche parallele : open ou investigating → auto_resolved. Les info vont directement en closed.open, jamais investigating ni action_taken (quelqu'un s'en occupe).driver_alarm_violation, tracker_anomaly, fuel_drain_anomaly, off_station_refueling, deviation_with_stop, mission_sla_breach, fuel_gauge_discrepancy. Faits qui nécessitent une investigation humaine.speeding 15 min, corridor_deviation 30 min, excessive_idle 30 min, low_battery 60 min.night_driving:{vehicleId}:{date}, border_crossing:{vehicleId}:{waypointId}:{tripId}, etc. ON CONFLICT DO NOTHING sur la cle d'idempotence.event_subtype. Cles indépendantes de la langue. Le titre français est assemblé côté UI à partir de la structure. Pas de reverse geocoding dans le hot path.financial_impact_fcfa, duration_seconds, fuel_delta_liters existent en colonnes réelles en plus du JSONB. JSONB = affichage, colonnes = requêtes analytics.#K{id} séquentiels par tenant. Le propriétaire repond avec le code pour commenter. action_notes en tableau JSONB append-only : [{note, by, at}].related_event_id FK, un seul niveau. Exemples : refueling_detected → off_station_refueling, fuel_drain_detected → fuel_drain_anomaly.trip_id, stop_id, gap_id, visit_id (waypoint_visit), deviation_id, fuel_event_id. Chaque FK est nullable et lie l'événement à l'entité qui l'a déclenche.unauthorized_stop (label prompt), low_fuel_driving, towing_detected, driver_alarm_violation, mission_paused, mission_resumed.| Sévérité | Pendant heures de repos | Exemple |
|---|---|---|
| critical | Immédiat — envoi même à 3h du matin | towing_detected à 2h47 → WhatsApp envoyé immédiatement |
| warning | File d'attente — envoyé à quiet_hours_end (6h par défaut) |
speeding à 23h15 → envoyé à 6h00 si toujours ouvert |
| info | Supprimé — pas de notification | night_driving → dashboard uniquement |
La configuration est par tenant : quietHoursEnabled (défaut: false), quietHoursStart (défaut: 22), quietHoursEnd (défaut: 6). Timezone: Africa/Douala (UTC+1, Cameroun et Tchad).
Après auto-résolution, un nouvel événement du même type pour le même véhicule est supprimé pendant la durée du cooldown. Le cooldown est appliqué dans le consumer, pas dans le moteur. Le moteur émet toujours ; le consumer déduplique.
| Type | Cooldown | Raison |
|---|---|---|
speeding |
15 min | Vitesse qui oscille autour du seuil |
corridor_deviation |
30 min | Retour bref au corridor puis re-sortie |
excessive_idle |
30 min | Moteur en marche/arrêt répété |
low_battery |
60 min | Tension qui oscille près du seuil |
Le consumer vérifie resolved_at sur le dernier événement résolu du même type pour le véhicule. Si now() - resolved_at < cooldown, le nouvel événement est supprimé (pas inséré). La contrainte unique idempotency_key gère la déduplication exacte ; le cooldown gère la déduplication temporelle après résolution.
Les événements info (status = 'closed') contournent l'index partiel de dedup (qui porte sur open/investigating/action_taken). Des clés de bucketing par type empechent l'inondation :
| Type | Cle de bucketing (idempotency_key) |
Portee |
|---|---|---|
night_driving |
night_driving:{vehicle_id}:{calendar_date} |
1 par véhicule par nuit |
border_crossing |
border_crossing:{vehicle_id}:{waypoint_id}:{trip_id} |
1 par véhicule par frontiere par trajet |
trip_started |
trip_started:{vehicle_id}:{trip_id} |
Naturellement unique (1 par trajet) |
trip_completed |
trip_completed:{vehicle_id}:{trip_id} |
Naturellement unique |
destination_reached |
destination_reached:{vehicle_id}:{waypoint_id}:{mission_id} |
1 par destination par mission |
refueling_detected |
refueling_detected:{fuel_event_id} |
Naturellement unique (1 par fuel_event) |
fuel_drain_detected |
fuel_drain_detected:{fuel_event_id} |
Naturellement unique |
low_fuel |
low_fuel:{vehicle_id}:{calendar_date} |
1 par véhicule par jour |
low_battery |
low_battery:{vehicle_id}:{calendar_date} |
1 par véhicule par jour |
Chaque événement warning/critical envoyé par WhatsApp inclut un code de référence séquentiel par tenant. Le propriétaire de flotte peut répondre avec ce code pour commenter l'événement.
action_notes (append-only JSONB array)| Champ | Type | Description |
|---|---|---|
note | string | Texte du commentaire |
by | string | Identifiant de l'auteur (owner, system, driver) |
at | ISO 8601 | Horodatage du commentaire |
Si la réponse ne contient pas de code #K{id}, le bot fait un fallback sur l'événement ouvert le plus récent pour ce véhicule.
Chaque type d'événement porte un contexte JSONB (EventContext) avec des clés indépendantes de la langue. Le UI assemblé les titres en français à partir de event_subtype + les champs structurés.
| Colonne | Type | Usage |
|---|---|---|
financial_impact_fcfa |
integer | Impact financier en FCFA (écart carburant, déviation, ravitaillement non autorisé) |
duration_seconds |
integer | Durée de l'événement (arrêt, déviation, idle) |
fuel_delta_liters |
numeric | Variation de carburant associée (drain, ravitaillement) |
| FK | Lie a |
|---|---|
trip_id | Trajet qui a déclenche l'événement |
stop_id | Arrêt qui a déclenche l'événement |
gap_id | Gap qui a déclenche l'événement |
visit_id | Visite de waypoint associée |
deviation_id | Déviation de corridor associée |
fuel_event_id | Événement carburant associe (fuel_events) |
related_event_id | Événement parent (1 seul niveau : refueling → off_station, drain → anomaly) |
| Colonne | Valeurs |
|---|---|
notification_status | 'pending' | 'confirmed' | 'sent' | 'suppressed' |
notification_scheduled_at | Detect time + 5 min |
notification_sent_at | Horodatage d'envoi effectif |
quiet_hours_deferred | Boolean (défaut false) |
Source : async-ops.md (section 10), state-machines.md (section 3.6), config.md (section 11.3), types.md (section 6b)
← Retour à l'index