Les engins mobiles (chariots élévateurs, véhicules lourds, équipements de chantier) représentent une source majeure d'accidents graves en milieu industriel. La surveillance en temps réel des quasi-collisions piétons, des excès de vitesse, du port de la ceinture, des zones à risque d'impacts et de la maintenance préventive est essentielle pour prévenir les incidents mortels.
Mobile equipment (forklifts, heavy vehicles, construction equipment) represents a major source of serious accidents in industrial environments. Real-time monitoring of pedestrian near-misses, speed violations, seatbelt compliance, impact hotspot zones, and preventive maintenance is critical to prevent fatal incidents.
// ⚠️ Détection: Quasi-collisions piéton/véhicule (proximité critique) // Detection: Pedestrian/vehicle near-misses (critical proximity) MATCH (v:Vehicle)-[:AT]->(z)-[:PART_OF]->(p {id: $projectId}) MATCH (w:Worker)-[:AT]->(z) WHERE point.distance(point(v), point(w)) < $proximityM // Distance critique (ex: 2-3m) AND w.timestamp >= $since WITH v, w, z, point.distance(point(v), point(w)) AS distanceMeters, COUNT(*) AS nearMissCount RETURN v.id AS vehicleId, v.type AS vehicleType, v.operator AS operatorName, w.id AS workerId, w.name AS workerName, z.name AS zoneName, round(distanceMeters, 2) AS distanceMeters, nearMissCount, CASE WHEN distanceMeters < 1.0 THEN 'CRITICAL' WHEN distanceMeters < 2.0 THEN 'HIGH' ELSE 'MEDIUM' END AS riskLevel ORDER BY distanceMeters ASC
{
"vehicleId": "VEH-2024-FL-0847",
"vehicleType": "Forklift / Chariot élévateur",
"operatorName": "Jean Martineau",
"workerId": "WRK-4783",
"workerName": "Sophie Dubois",
"zoneName": "Entrepôt A - Allée 5",
"distanceMeters": 0.73, // Moins de 1 mètre!
"nearMissCount": 1,
"riskLevel": "CRITICAL",
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Alerte immédiate + coaching sécurité / Immediate alert + safety coaching"
}
// 🚨 Détection: Vitesses supérieures à la limite de zone // Detection: Speeds exceeding zone speed limit MATCH (r:Reading {type: 'Speed'})-[:FROM]->(v:Vehicle)-[:PART_OF]->(p {id: $projectId}) MATCH (v)-[:AT]->(z) WHERE r.timestamp >= $since AND r.value > coalesce(z.speedLimit, $speedLimit) // Limite zone ou limite globale WITH v, z, MAX(r.value) AS maxSpeed, AVG(r.value) AS avgSpeed, COUNT(r) AS violationCount, coalesce(z.speedLimit, $speedLimit) AS limit RETURN v.id AS vehicleId, v.type AS vehicleType, v.operator AS operatorName, z.name AS zoneName, round(maxSpeed, 1) AS maxSpeedKmh, round(avgSpeed, 1) AS avgSpeedKmh, limit AS speedLimitKmh, (maxSpeed - limit) AS excessSpeedKmh, violationCount, CASE WHEN maxSpeed > (limit * 1.5) THEN 'CRITICAL' WHEN maxSpeed > (limit * 1.2) THEN 'HIGH' ELSE 'MEDIUM' END AS riskLevel ORDER BY excessSpeedKmh DESC
{
"vehicleId": "VEH-2024-TRK-1293",
"vehicleType": "Camion lourd / Heavy truck",
"operatorName": "Michel Gagnon",
"zoneName": "Zone de chargement B",
"maxSpeedKmh": 47.3, // Vitesse maximale enregistrée
"avgSpeedKmh": 38.7,
"speedLimitKmh": 20, // Limite: 20 km/h en zone de chargement
"excessSpeedKmh": 27.3, // Dépassement de 27 km/h!
"violationCount": 14, // 14 lectures au-dessus de la limite
"riskLevel": "CRITICAL",
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Suspension immédiate + formation / Immediate suspension + training"
}
// 🔒 Détection: Départs de véhicules sans ceinture attachée // Detection: Vehicle trips started without seatbelt fastened MATCH (trip:Trip)-[:BY]->(v:Vehicle)-[:PART_OF]->(p {id: $projectId}) WHERE NOT (:Event {type: 'SeatbeltOn'})-[:DURING]->(trip) WITH trip, v, duration.between(trip.startTime, trip.endTime) AS tripDuration RETURN trip.id AS tripId, trip.startTime AS startTime, trip.endTime AS endTime, tripDuration.minutes AS durationMinutes, v.id AS vehicleId, v.type AS vehicleType, v.operator AS operatorName, trip.distanceKm AS distanceKm, 'SEATBELT_NOT_FASTENED' AS violation, CASE WHEN tripDuration.minutes > 30 THEN 'CRITICAL' WHEN tripDuration.minutes > 10 THEN 'HIGH' ELSE 'MEDIUM' END AS riskLevel ORDER BY tripDuration.minutes DESC
{
"tripId": "TRIP-2024-11-04-0847",
"startTime": "2024-11-04T08:15:00Z",
"endTime": "2024-11-04T09:03:00Z",
"durationMinutes": 48, // 48 minutes sans ceinture!
"vehicleId": "VEH-2024-VAN-0523",
"vehicleType": "Camionnette / Van",
"operatorName": "Pierre Lefebvre",
"distanceKm": 23.7,
"violation": "SEATBELT_NOT_FASTENED",
"riskLevel": "CRITICAL",
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Rencontre disciplinaire + rappel réglementaire / Disciplinary meeting + regulatory reminder"
}
// 💥 Détection: Zones à forte concentration d'impacts (hotspots) // Detection: High-impact concentration zones (hotspots) MATCH (ev:Event {type: 'Impact'})-[:AT]->(z:Zone)-[:PART_OF]->(p {id: $projectId}) WHERE ev.timestamp >= $since WITH z, COUNT(ev) AS impactCount, AVG(ev.severity) AS avgSeverity, MAX(ev.severity) AS maxSeverity, COLLECT { MATCH (ev)-[:INVOLVES]->(v:Vehicle) RETURN COUNT(DISTINCT v) AS vehicleCount }[0].vehicleCount AS distinctVehicles RETURN z.id AS zoneId, z.name AS zoneName, z.type AS zoneType, impactCount, round(avgSeverity, 2) AS avgSeverity, maxSeverity, distinctVehicles, CASE WHEN impactCount >= 10 THEN 'CRITICAL_HOTSPOT' WHEN impactCount >= 5 THEN 'HIGH_HOTSPOT' WHEN impactCount >= 3 THEN 'MEDIUM_HOTSPOT' ELSE 'LOW_RISK' END AS hotspotLevel ORDER BY impactCount DESC LIMIT 10
{
"zoneId": "ZONE-INT-A-07",
"zoneName": "Intersection A-07 (sortie quai)",
"zoneType": "Intersection / Crossroad",
"impactCount": 23, // 23 impacts en 30 jours!
"avgSeverity": 3.47, // Sévérité moyenne (sur 10)
"maxSeverity": 7.2, // Sévérité maximale observée
"distinctVehicles": 8, // 8 véhicules différents impliqués
"hotspotLevel": "CRITICAL_HOTSPOT",
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Réaménagement zone + signalisation renforcée / Zone redesign + enhanced signage"
}
// 🔧 Détection: Maintenance préventive en retard (km ou temps) // Detection: Overdue preventive maintenance (km or time) MATCH (v:Vehicle)-[:PART_OF]->(p {id: $projectId}) WHERE coalesce(v.lastServiceKm, 0) + v.serviceIntervalKm < v.currentKm OR coalesce(v.lastServiceDate, date('1970-01-01')) + duration({days: v.serviceIntervalDays}) < date() WITH v, v.currentKm - (coalesce(v.lastServiceKm, 0) + v.serviceIntervalKm) AS overdueKm, duration.between( coalesce(v.lastServiceDate, date('1970-01-01')) + duration({days: v.serviceIntervalDays}), date() ) AS overdueDuration RETURN v.id AS vehicleId, v.type AS vehicleType, v.make + ' ' + v.model AS makeModel, v.currentKm AS currentKm, v.lastServiceKm AS lastServiceKm, v.serviceIntervalKm AS intervalKm, v.lastServiceDate AS lastServiceDate, v.serviceIntervalDays AS intervalDays, overdueKm, overdueDuration.days AS overdueDays, 'MAINTENANCE_OVERDUE' AS violation, CASE WHEN overdueKm > 5000 OR overdueDuration.days > 180 THEN 'CRITICAL' WHEN overdueKm > 2000 OR overdueDuration.days > 90 THEN 'HIGH' ELSE 'MEDIUM' END AS riskLevel ORDER BY overdueKm DESC, overdueDuration.days DESC
{
"vehicleId": "VEH-2024-FL-0312",
"vehicleType": "Forklift / Chariot élévateur",
"makeModel": "Toyota 8FGU32",
"currentKm": 12847, // Kilométrage actuel
"lastServiceKm": 5230, // Dernier entretien à 5230 km
"intervalKm": 2000, // Entretien requis tous les 2000 km
"lastServiceDate": "2023-11-15",
"intervalDays": 180, // Entretien requis tous les 6 mois
"overdueKm": 5617, // Retard de 5617 km!
"overdueDays": 174, // Retard de 174 jours (presque 6 mois)
"violation": "MAINTENANCE_OVERDUE",
"riskLevel": "CRITICAL",
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Retrait immédiat du service + entretien urgent / Immediate service removal + urgent maintenance"
}