MATCH (proj:Project {id: $projectId})
-[:HAS_SPACE]->(cs:ConfinedSpace)
WHERE cs.status = 'active'
AND cs.requiresPermit = true
OPTIONAL MATCH (cs)-[:MONITORED_BY]->(sensor:Sensor)
-[:READS]->(reading:Reading)
WHERE sensor.category IN ['atmospheric', 'gas_detector', 'oxygen_monitor']
AND reading.timestamp >= datetime() - duration({minutes: $recentMinutes})
WITH cs, sensor,
collect(reading) AS readings,
size([r IN collect(reading)
WHERE r.gasType = 'O2' AND (r.value < 19.5 OR r.value > 23.5)]) AS oxygenAlerts,
size([r IN collect(reading)
WHERE r.gasType = 'CO' AND r.value > 35]) AS coAlerts,
size([r IN collect(reading)
WHERE r.gasType = 'H2S' AND r.value > 10]) AS h2sAlerts,
size([r IN collect(reading)
WHERE r.gasType = 'LEL' AND r.value > 10]) AS lelAlerts,
size([r IN collect(reading)
WHERE (r.gasType = 'O2' AND (r.value < 19.5 OR r.value > 23.5))
OR (r.gasType = 'CO' AND r.value > 35)
OR (r.gasType = 'H2S' AND r.value > 10)
OR (r.gasType = 'LEL' AND r.value > 10)]) AS totalAtmosAlerts
OPTIONAL MATCH (cs)<-[:APPLIES_TO]-(permit:EntryPermit)
WHERE permit.type = 'confined_space_entry'
AND permit.status = 'active'
AND permit.expiryTime > datetime()
WITH cs, readings, oxygenAlerts, coAlerts, h2sAlerts, lelAlerts, totalAtmosAlerts,
count(permit) AS validPermits,
collect(permit) AS permits
OPTIONAL MATCH (cs)<-[:CURRENTLY_IN]-(worker:Worker)
WHERE worker.status = 'active'
AND worker.lastSeen >= datetime() - duration({minutes: 10})
OPTIONAL MATCH (worker)-[:HAS_CERTIFICATION]->(cert:Certification)
WHERE cert.type IN ['confined_space_entry', 'gas_detection', 'rescue', 'SCBA']
AND cert.expiryDate > datetime()
WITH cs, readings, oxygenAlerts, coAlerts, h2sAlerts, lelAlerts,
totalAtmosAlerts, validPermits, permits,
count(DISTINCT worker) AS workersInside,
collect(DISTINCT worker) AS workerList,
count(DISTINCT cert) AS certifiedCount
OPTIONAL MATCH (cs)-[:HAS_VENTILATION]->(vent:Asset)
WHERE vent.category IN ['ventilation', 'air_mover', 'blower']
OPTIONAL MATCH (cs)-[:ASSIGNED_RESCUE]->(rescue:RescueTeam)
WHERE rescue.status = 'available'
AND rescue.responseTime <= $maxResponseMinutes
WITH cs, readings, oxygenAlerts, coAlerts, h2sAlerts, lelAlerts,
totalAtmosAlerts, validPermits, workersInside, workerList, certifiedCount,
count(vent) AS ventilationUnits,
size([v IN collect(vent) WHERE v.status = 'operational']) AS operationalVent,
count(rescue) AS rescueTeamsAvailable
WITH cs, readings, oxygenAlerts, coAlerts, h2sAlerts, lelAlerts,
totalAtmosAlerts, validPermits, workersInside, workerList,
ventilationUnits, operationalVent, rescueTeamsAvailable,
(totalAtmosAlerts * 5.0) +
(workersInside * 3.0) +
(CASE WHEN operationalVent = 0 THEN 4.0 ELSE 0 END) +
(CASE WHEN validPermits = 0 THEN 2.0 ELSE 0 END) +
(CASE WHEN rescueTeamsAvailable = 0 THEN 2.5 ELSE 0 END)
AS criticalityScore
WHERE criticalityScore >= $minCriticalityThreshold
RETURN
cs.id AS spaceId,
cs.name AS spaceName,
cs.type AS spaceType,
cs.volume AS volumeCubicMeters,
cs.location AS coordinates,
round(criticalityScore, 2) AS criticalityScore,
CASE
WHEN criticalityScore >= 20 THEN 'IMMÉDIATE'
WHEN criticalityScore >= 12 THEN 'CRITIQUE'
WHEN criticalityScore >= 6 THEN 'ÉLEVÉE'
ELSE 'ATTENTION'
END AS urgencyLevel,
totalAtmosAlerts,
oxygenAlerts AS oxygenDeficiency,
coAlerts AS carbonMonoxideAlerts,
h2sAlerts AS hydrogenSulfideAlerts,
lelAlerts AS explosiveAtmosphereAlerts,
workersInside,
[w IN workerList | {
id: w.id,
name: w.name,
entryTime: w.entryTimestamp,
durationInside: duration.between(w.entryTimestamp, datetime()).minutes
}] AS workersAtRisk,
validPermits,
CASE WHEN validPermits > 0 THEN true ELSE false END AS hasValidPermit,
ventilationUnits,
operationalVent,
CASE WHEN operationalVent > 0 THEN true ELSE false END AS ventilationOperational,
rescueTeamsAvailable,
datetime() AS analysisTimestamp,
CASE
WHEN criticalityScore >= 20 THEN ['ÉVACUATION IMMÉDIATE', 'ALERTER RESCUE TEAM', 'BLOQUER ACCÈS']
WHEN criticalityScore >= 12 THEN ['SURVEILLANCE CONTINUE', 'PRÉPA ÉVACUATION', 'VENTILATION MAX']
WHEN criticalityScore >= 6 THEN ['INTENSIFIER MONITORING', 'VÉRIFIER ÉQUIPEMENTS', 'BRIEFING SÉCURITÉ']
ELSE ['CONTINUER SURVEILLANCE', 'LOGS ATMOSPHÉRIQUES']
END AS recommendedActions
ORDER BY criticalityScore DESC, workersInside DESC
LIMIT $maxResults