M07 — Spatial Correlation, Fire Panel, and Alerts

One-line goal: when the fire panel reports a zone alarm, the system identifies all cameras with line-of-sight to that zone via PostGIS, runs Tier 2 priority analysis on those frames, and surfaces a correlated alert with evidence to the operator within 30 seconds.

This is the customer-visible feature that justifies the platform. Everything before M07 is plumbing; M07 is the demo moment.


Tracks involved

  • Backend — primary. Alert engine, spatial correlation queries, alert routing, evidence packaging.
  • Edge — fire-panel adapter (driver #1 for the pilot’s specific panel).
  • AI Worker — priority-queue support for alarm-correlated escalations (jumps ahead of routine queue).
  • Frontend — 3D digital twin scene, camera placement UI, alert console with evidence rendering.

Dependencies

  • M03 (alarm_event, alert, alert_action, floor_plan, zone_geometry tables exist).
  • M04 (cameras streaming).
  • M05 (edge producing escalations to the cloud).
  • M06 (cloud Tier-2 LLM producing AnalysisEvent verdicts).
  • External: fire panel model identified at site survey (reminders.md Q1); 3D model delivered from the modeler.

Deliverables

1. Fire-panel adapter

A driver in apps/edge-app/drivers/fire_panel/ implementing the FirePanelAdapter interface.

Pilot driver is TBD on panel model but most likely one of:

  • BacnetIpAdapter — using BAC0 or bacpypes3. Connects to BACnet/IP fire panels (Honeywell, Siemens, Johnson Controls). Subscribes to alarm objects via COV (Change-Of-Value). Maps panel-native zone IDs to our canonical zone_id via a per-building mapping config.
  • ModbusTcpAdapter — using pymodbus. Polls alarm registers; emits events on state change.
  • ContactClosureAdapter — universal fallback per 05-contingencies.md §5. USB-to-GPIO board connected to the panel’s alarm-relay outputs; reads state at 1 Hz.

The driver:

  1. Connects to the panel using the configured protocol.
  2. Subscribes to alarm events (COV or polling-and-diff).
  3. Normalizes each event to the canonical AlarmEvent shape (per contracts package).
  4. Maps panel-native zone identifier to canonical zone_id via the per-building config — failed mapping logs a warning but the event is still forwarded with mapped_zone_id = null and the alert is built from protocol_attributes.
  5. Forwards AlarmEvent over the edge tunnel to the cloud alarm-events Pub/Sub topic.
  6. Reports panel-health telemetry separately (loss of comm to panel is itself an alert).

2. Zone-to-panel mapping

A configuration step in the building-onboarding flow:

  • Admin views a list of zones from the panel (the adapter calls list_zones()).
  • Admin maps each panel zone to a zone_id in our system.
  • Mapping stored in system_config keyed by building_id + panel_zone_native_idzone_id.
  • Mapping can be edited later; an audit log entry records each change.

3. Spatial correlation engine

A service module in apps/api/src/spatial-correlation/ (running as part of the alert engine).

When an AlarmEvent arrives:

-- pseudo-SQL; real implementation uses Prisma + raw PostGIS
SELECT c.id, c.name,
       ST_Distance(zg.centroid, c.position::geometry) AS distance,
       /* coverage_score, viewing_angle_score — application-side */
FROM camera c
JOIN zone_geometry zg ON zg.zone_id = (SELECT mapped_zone_id FROM alarm_event WHERE id = $1)
WHERE c.status = 'online'
  AND ST_DWithin(zg.boundary, c.position::geometry, 15.0)  -- 15 m line-of-sight radius
ORDER BY distance ASC
LIMIT 6;

Each candidate camera gets a relevance score combining:

  • 40% coverage fraction (how much of the alarm zone polygon is in the camera’s view frustum, computed from camera.position, orientation_deg, fov_deg, and the zone polygon)
  • 30% distance inverse
  • 30% viewing angle (cameras pointing directly at the zone outrank ones with the zone at the edge of FOV)

Top 6 cameras are returned. If fewer than 3 cameras are spatially relevant, the correlation falls back to “all cameras on the same floor as the alarm zone” with a flag indicating the fallback is in use.

4. Alert engine

Service in apps/api/src/alert-engine/. Subscribes to alarm-events, analysis-events, and tier2-events Pub/Sub topics.

4.1 Alarm-driven alert flow

  1. AlarmEvent arrives.
  2. Engine creates an Alert row with status new, severity from the alarm type.
  3. Spatial correlation produces top-6 cameras.
  4. Engine asks AI Worker for priority Tier 2 inference on the most recent frame from each of those cameras (priority bypasses the routine Tier 2 queue).
  5. As each Tier 2 verdict arrives, engine attaches it to the alert as an EvidenceItem.
  6. Alert is updated in real time on the WebSocket fan-out (operators see new evidence appearing as it lands).
  7. Once all 6 verdicts (or a 20 s timeout) complete, the alert moves to status investigating if there is consensus, or escalated if any verdict is REAL FIRE.

4.2 AI-driven alert flow

  1. Tier2AnalysisEvent arrives with threat_detected = true.
  2. Engine creates (or updates) an Alert, severity based on the LLM’s severity.
  3. Evidence is attached: the source frame, the LLM verdict, related recent AnalysisEvents on the same camera.
  4. Alert published on WebSocket.

4.3 Alert rules

alert_rule rows (M03) provide configurable thresholds:

  • Crowd excess: person_count > zone.max_occupancy × 0.9 → severity HIGH.
  • Restricted zone entry: any person detection in zone.is_restricted = true outside operating hours → severity MEDIUM.
  • Equipment fault: camera offline > 10 min → severity LOW.
  • Connectivity: edge gateway offline > 5 min → severity HIGH.

Per-building / per-zone overrides allowed. Rules editable by admins; changes audit-logged.

4.4 Cooldown

Each rule has a cooldown_seconds. Once an alert fires from a rule, subsequent matching events don’t create new alerts for the cooldown period (they append to the existing one as additional evidence). Prevents flap.

5. Evidence-package generation

For each alert, an EvidenceItem collection is built:

  • Camera snapshots — JPEG frames from each correlated camera at the moment of the event, with per-camera Tier 2 verdict overlaid as caption text.
  • Tier 2 LLM verdicts — the full structured response from each camera analysis.
  • Tier 1 detections — recent AnalysisEvents (last 60 s) for each correlated camera.
  • Spatial view — the digital-twin scene rendered into a static image with the alarm zone and cameras highlighted (server-side render using a headless Three.js or a pre-rendered template).
  • Audit trail — who has been notified, when, and any operator actions taken so far.

Evidence is stored in GCS with the rest of the tenant’s data; the alert references it by URL.

6. Alert routing

For the pilot, routing is minimal:

  • All alerts visible to operators authorized for the building.
  • FD-view (M08) is automatically populated with active alerts (no SMS or external integrations needed for the pilot).
  • Escalation to specific roles based on severity (CRITICAL → all roles; LOW → operator only).
  • Configurable per building if Civil Defence needs different routing.

Mobile alerting and dispatch-system integration deferred — call it M07.5 if it becomes urgent post-pilot.

7. Digital twin integration

The 3D scene is the platform’s spatial-awareness surface — see M08 for full polish. M07 delivers the minimum:

  • 3D model file loaded into a Three.js scene (driver from 04-abstractions §5.10GlbLoader expected).
  • Camera positions placed in the scene (one marker per camera; click to drill into the camera).
  • Zone polygons rendered as semi-transparent volumes.
  • During an alert: affected zone flashes red; relevant cameras’ markers highlight.
  • Smooth camera-control (orbit, pan, zoom) with @react-three/drei controls.

8. Camera placement UI

Admin tool to position cameras in the 3D scene:

  • Load building’s 3D model.
  • Drag-and-drop camera markers onto positions.
  • Rotate orientation, set FOV angle, adjust placement height.
  • Saves position, orientation_deg, fov_deg to the camera table.
  • Visual check: camera’s view frustum is rendered as a cone; admin can verify it points at the right zones.

9. Operator alert console

apps/dashboard/src/views/Alerts.tsx:

  • Active alerts list (real-time via WebSocket).
  • Filters: severity, building, zone, alert type, time range.
  • Click an alert → detail panel with all evidence items, the Tier 2 verdict, the spatial view, and action buttons (acknowledge / dismiss / confirm / escalate / add note / mark false alarm / resolve).
  • Each action writes an AlertAction row; alert status moves through the lifecycle.
  • Resolved alerts archive after 30 days (configurable).

Verification

  1. End-to-end alarm flow. Trigger a simulated FACP zone alarm (via the panel adapter’s test-mode or a manually-published AlarmEvent). Within 30 s: alert appears on the dashboard with the zone highlighted in the 3D twin, top-6 cameras shown with snapshots, Tier 2 verdicts visible, severity coded.
  2. Spatial correlation finds the right cameras. With cameras placed in the 3D scene, trigger a zone alarm. The top-6 cameras returned are the ones a human would point at as “those have line-of-sight to that zone.” Verified visually plus by an automated test against a seeded scene.
  3. False-alarm verdict correctly down-weights. Trigger an alarm in a zone where the only nearby camera shows visible bakhoor smoke. Tier 2 returns PROBABLE FALSE ALARM; the alert’s banner reads accordingly; operator can mark it as false alarm.
  4. Real-fire verdict escalates correctly. Same setup with simulated fire imagery. Tier 2 returns REAL FIRE; the alert is severity CRITICAL; the FD-view sees it.
  5. Acknowledgement updates the lifecycle. Operator acknowledges; status → acknowledged; AlertAction row created; audit log entry exists.
  6. Cooldown works. Two alarms from the same rule within the cooldown window produce one alert with multiple evidence items, not two alerts.
  7. Camera placement saves and renders correctly. Move a camera marker in the admin UI; refresh; position persists. Operator view sees the camera at the new position.
  8. Panel offline = its own alert. Disconnect the fire panel’s network cable; within 1 min an alert of severity HIGH appears noting “fire panel disconnected — fire correlation degraded”.
  9. Concurrent alerts handled. Trigger two alarms in different zones within 5 s of each other. Both alerts appear independently, each with its own evidence package and spatial highlighting.
  10. Restricted-zone rule fires. Place a test person in a is_restricted=true zone outside hours; within ~5 s a restricted_zone_entry alert appears.

Risks

RiskLikelihoodMitigation
Fire panel model unknown until site surveyHighThree protocol fallback options ready; contact-closure as universal last resort
3D model arrives late or in wrong formatMediumAsset spec given to modeler in week 1; fallback drivers (OBJ, 2D extrusion) ready
Spatial correlation incorrectly excludes a relevant cameraMediumFallback to “all on same floor”; manual override per-zone available
Tier 2 latency makes 30s SLO tightMediumPriority queue for alarm-correlated escalations bypasses routine queue
Operator overwhelmed by too many alerts during peakMediumCooldowns; per-rule rate limits; severity-based filtering by default
False-alarm mark feeds back wrong signalLowOperator marking is audit-logged; used for prompt-template improvement, not autonomous model retraining

Open questions

  • Specific fire panel model at the Arafat site — reminders.md Q1. Affects driver implementation; should be confirmed week 1 of M07.
  • Zone-to-panel-zone mapping data source. Does the panel expose human-readable zone names, or just numeric IDs? Depends on panel.
  • Should restricted-zone rules respect prayer-time variants? Prayer halls have very different normal-occupancy patterns at prayer times vs other times. A time_schedule table linked to alert rules would solve this elegantly — deferred to M08 if time permits, M11+ otherwise.

Exit criteria

All 10 verification items pass. The signature demo flow (alarm → correlated evidence on operator screen in < 30 s) recorded on video as the customer demo artifact. M07 sign-off entry in docs/plan/COMPLETION_LOG.md.

WiqAIa+ Docs · Rizoma