Les risques électriques et arc-flash représentent l'une des causes les plus graves de blessures et décès en milieu industriel. La surveillance en temps réel des études arc-flash, du travail sous tension, des circuits temporaires non protégés, des dispositifs LOTO et des rallonges électriques est essentielle pour prévenir électrocutions et brûlures catastrophiques.
Electrical hazards and arc-flash represent one of the most serious causes of injury and death in industrial environments. Real-time monitoring of arc-flash studies, energized work, unprotected temporary circuits, LOTO devices, and extension cords is critical to prevent electrocutions and catastrophic burns.
// 📊 Détection: Tableaux électriques sans étude arc-flash ou étude périmée // Detection: Electrical panels without arc-flash study or expired study MATCH (pnl:Asset {class: 'Panel'})-[:PART_OF]->(p {id: $projectId}) WHERE NOT (:Document {type: 'ArcFlashStudy'})-[:COVERS]->(pnl) OR ANY( d IN [(:Document {type: 'ArcFlashStudy'})-[:COVERS]->(pnl) | d] WHERE d.date < $maxAge // Âge maximal accepté (ex: 5 ans) ) WITH pnl, COLLECT { MATCH (d:Document {type: 'ArcFlashStudy'})-[:COVERS]->(pnl) RETURN d.date AS studyDate, d.incidentEnergy AS energy ORDER BY d.date DESC LIMIT 1 }[0] AS lastStudy RETURN pnl.id AS panelId, pnl.name AS panelName, pnl.location AS location, pnl.voltage AS voltageKV, pnl.amperage AS amperageA, lastStudy.studyDate AS lastStudyDate, lastStudy.energy AS lastIncidentEnergy, CASE WHEN lastStudy IS NULL THEN 'NO_STUDY' ELSE 'EXPIRED_STUDY' END AS violation, CASE WHEN pnl.voltage >= 600 THEN 'CRITICAL' WHEN pnl.voltage >= 240 THEN 'HIGH' ELSE 'MEDIUM' END AS riskLevel ORDER BY pnl.voltage DESC
{
"panelId": "PANEL-MV-2024-A47",
"panelName": "Tableau principal moyenne tension / Main MV panel",
"location": "Salle électrique bâtiment A",
"voltageKV": 13.8, // 13.8 kV - Moyenne tension!
"amperageA": 2000,
"lastStudyDate": "2018-03-15", // Étude de 2018 - périmée!
"lastIncidentEnergy": 28.7, // 28.7 cal/cm² (énergie incidente)
"violation": "EXPIRED_STUDY",
"riskLevel": "CRITICAL",
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Étude arc-flash urgente + étiquetage temporaire / Urgent arc-flash study + temporary labeling"
}
// 🔧 Détection: Travail sous tension sans permis OU ÉPI arc-flash inadéquat // Detection: Energized work without permit OR inadequate arc-flash PPE MATCH (t:Task {type: 'EnergizedWork'})-[:ON]->(a:Asset)-[:PART_OF]->(p {id: $projectId}) WHERE NOT (:Permit {type: 'EnergizedWork'})-[:COVERS]->(t) OR coalesce(a.requiredArcCat, 2) > max([c IN [(t)<-[:ASSIGNED_TO]-(w)-[:WEARS]->(ppe:PPE) | ppe.arcCat] | c]) WITH t, a, EXISTS { MATCH (:Permit {type: 'EnergizedWork'})-[:COVERS]->(t) } AS hasPermit, coalesce(a.requiredArcCat, 2) AS requiredCat, [c IN [(t)<-[:ASSIGNED_TO]-(w)-[:WEARS]->(ppe:PPE) | ppe.arcCat] | c] AS workerCats RETURN t.id AS taskId, t.description AS description, t.startDate AS startDate, a.id AS assetId, a.type AS assetType, hasPermit, requiredCat, CASE WHEN SIZE(workerCats) > 0 THEN max(workerCats) ELSE 0 END AS maxWorkerCat, CASE WHEN NOT hasPermit AND SIZE(workerCats) = 0 THEN 'NO_PERMIT_NO_PPE' WHEN NOT hasPermit THEN 'NO_PERMIT' WHEN SIZE(workerCats) = 0 THEN 'NO_PPE' ELSE 'INADEQUATE_PPE' END AS violation, CASE WHEN NOT hasPermit AND SIZE(workerCats) = 0 THEN 'CRITICAL' WHEN NOT hasPermit OR SIZE(workerCats) = 0 THEN 'HIGH' ELSE 'MEDIUM' END AS riskLevel ORDER BY riskLevel, t.startDate
{
"taskId": "TASK-EW-2024-1147",
"description": "Remplacement disjoncteur 600V sous tension / Energized 600V breaker replacement",
"startDate": "2024-11-04T09:00:00Z",
"assetId": "PANEL-LV-B23",
"assetType": "Switchgear 600V",
"hasPermit": false, // Pas de permis!
"requiredCat": 3, // CAT 3 requis (25 cal/cm²)
"maxWorkerCat": 1, // Travailleur porte seulement CAT 1 (4 cal/cm²)
"violation": "NO_PERMIT_NO_PPE", // Double violation!
"riskLevel": "CRITICAL",
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Arrêt immédiat + consignation équipement / Immediate stop + equipment lockout"
}
// 🔌 Détection: Circuits temporaires sans protection GFCI (différentiel) // Detection: Temporary circuits without GFCI (differential) protection MATCH (c:Circuit {type: 'Temporary'})-[:PART_OF]->(p {id: $projectId}) WHERE coalesce(c.gfci, false) = false WITH c, duration.between(c.installDate, date()) AS ageInService RETURN c.id AS circuitId, c.description AS description, c.location AS location, c.voltage AS voltageV, c.amperage AS amperageA, c.installDate AS installDate, ageInService.days AS daysInService, c.gfci AS hasGFCI, 'NO_GFCI_PROTECTION' AS violation, CASE WHEN c.location CONTAINS 'extérieur' OR c.location CONTAINS 'outdoor' THEN 'CRITICAL' WHEN c.voltage >= 240 THEN 'HIGH' ELSE 'MEDIUM' END AS riskLevel, CASE WHEN ageInService.days > 90 THEN true ELSE false END AS exceedsTemporaryThreshold ORDER BY riskLevel, daysInService DESC
{
"circuitId": "CIRCUIT-TEMP-2024-047",
"description": "Circuit temporaire chantier extérieur / Temporary outdoor construction circuit",
"location": "Zone construction extérieure - Phase 2",
"voltageV": 120,
"amperageA": 20,
"installDate": "2024-07-15",
"daysInService": 112, // Plus de 3 mois!
"hasGFCI": false, // Pas de GFCI!
"violation": "NO_GFCI_PROTECTION",
"riskLevel": "CRITICAL", // Extérieur = CRITIQUE
"exceedsTemporaryThreshold": true, // Dépasse 90 jours - n'est plus "temporaire"
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Installation GFCI immédiate + inspection / Immediate GFCI installation + inspection"
}
// 🔒 Détection: Dispositifs d'isolement sans stock LOTO disponible // Detection: Isolation devices without available LOTO inventory MATCH (iso:Asset {class: 'Isolator'})-[:PART_OF]->(p {id: $projectId}) WHERE SIZE( [(iso)<-[:FOR]-(inv:Inventory {item: 'LockoutDevice'}) | inv] ) = 0 WITH iso, SIZE( [(iso)<-[:REQUIRES]-(t:Task {status: 'Active'}) | t] ) AS activeTasksCount RETURN iso.id AS isolatorId, iso.type AS isolatorType, iso.location AS location, iso.voltage AS voltageKV, iso.lockoutPoints AS lockoutPoints, activeTasksCount, 'NO_LOTO_INVENTORY' AS violation, CASE WHEN activeTasksCount > 0 THEN 'CRITICAL' WHEN iso.voltage >= 600 THEN 'HIGH' ELSE 'MEDIUM' END AS riskLevel, CASE WHEN iso.lockoutPoints IS NOT NULL THEN iso.lockoutPoints * 2 // Besoin: 2 dispositifs par point ELSE 4 // Par défaut: 4 dispositifs minimum END AS recommendedQuantity ORDER BY riskLevel, activeTasksCount DESC
{
"isolatorId": "ISO-MV-2024-B14",
"isolatorType": "Sectionneur moyenne tension / MV Disconnector",
"location": "Poste électrique principal",
"voltageKV": 13.8,
"lockoutPoints": 3, // 3 points de verrouillage
"activeTasksCount": 2, // 2 tâches actives nécessitant consignation!
"violation": "NO_LOTO_INVENTORY",
"riskLevel": "CRITICAL",
"recommendedQuantity": 6, // 6 dispositifs requis (3 points × 2)
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Approvisionnement urgent dispositifs LOTO / Urgent LOTO devices procurement"
}
// 🔌 Détection: Rallonges électriques dépassant longueur maximale // Detection: Extension cords exceeding maximum length MATCH (cord:Asset {class: 'ExtensionCord'})-[:PART_OF]->(p {id: $projectId}) WHERE cord.lengthM > $maxLen // Longueur max règlementaire (ex: 30m / 100ft) WITH cord, cord.lengthM - $maxLen AS excessLength, EXISTS { MATCH (cord)<-[:USES]-(:Task {status: 'Active'}) } AS currentlyInUse RETURN cord.id AS cordId, cord.type AS cordType, cord.gauge AS wireGaugeAWG, cord.lengthM AS lengthMeters, $maxLen AS maxLengthMeters, round(excessLength, 1) AS excessLengthMeters, cord.amperage AS ratedAmperageA, cord.lastInspection AS lastInspection, currentlyInUse, 'EXCESSIVE_LENGTH' AS violation, CASE WHEN currentlyInUse AND cord.amperage >= 20 THEN 'CRITICAL' WHEN currentlyInUse THEN 'HIGH' WHEN cord.amperage >= 20 THEN 'MEDIUM' ELSE 'LOW' END AS riskLevel ORDER BY riskLevel, excessLength DESC
{
"cordId": "CORD-2024-EXT-0847",
"cordType": "Heavy-duty extension cord / Rallonge industrielle",
"wireGaugeAWG": 12, // Calibre 12 AWG
"lengthMeters": 48.0, // 48 mètres!
"maxLengthMeters": 30.0, // Maximum: 30 mètres (100 pieds)
"excessLengthMeters": 18.0, // Excès de 18 mètres
"ratedAmperageA": 20,
"lastInspection": "2024-09-15",
"currentlyInUse": true, // Actuellement utilisée!
"violation": "EXCESSIVE_LENGTH",
"riskLevel": "CRITICAL", // En utilisation + haute intensité
"criticalityLevel": "🔴 CRITIQUE / CRITICAL",
"requiredAction": "Remplacement immédiat + circuit permanent / Immediate replacement + permanent circuit"
}