Data and Business Logic
Data model themes
The migration history shows the system has grown from core places/devices into a broader safety and operations platform.
Core entities
- buildings, floors, zones
- receivers/gateways
- transmitters/beacons/assets/personnel-linked devices
- current positions and historical raddec/ambient events
Operational domains added over time
- emergency events, contacts, escalation, notification requests, rules
- RBAC roles and permissions
- audit logging and retention
- analytics dashboards
- weather schema
- phone-as-beacon and phone enrollment tables
- desktop gateway runtime metadata
- BLE runtime tuning and positioning settings
Positioning pipeline
flowchart LR
A[Beacon observations] --> B[iotHub.js / ingest routes]
B --> C[RSSI filtering]
C --> D[Receiver selection + thresholds]
D --> E[Trilateration or centroid]
E --> F[Boundary / zone attribution]
F --> G[Position smoothing]
G --> H[current_positions + history]
H --> I[Centrifugo + Nuxt floorplan]What the code currently does
- filters RSSI with Kalman logic
- limits weak/noisy observations with configurable thresholds
- supports trilateration, weighted centroid, and dominant/nearest receiver decisions
- reconciles floor dimensions and percent-based map coordinates
- smooths positions before writing/displaying them
- attributes building/floor/zone after position resolution
Emergency workflow
sequenceDiagram
participant User as Operator
participant UI as Nuxt emergency UI
participant API as emergency.js
participant DB as PostgreSQL
participant Orch as notificationOrchestrator
participant RT as Centrifugo
User->>UI: Trigger event
UI->>API: POST /emergency/trigger
API->>DB: Create event + query people/contacts
API->>Orch: Start scenario notifications
Orch->>DB: Queue/send/escalate
Orch->>RT: Broadcast updates
RT-->>UI: Live emergency updates
UI->>API: Refresh muster / confirmations / statusCurrent emergency business rules visible in code
- scenarios such as manual, power outage, fire, medical, weather, shelter-in-place, drill
- per-scenario channel choices and escalation timing
- employee notification and escalation tracking
- CSV export and history views
- service-status visibility for SMS/email/voice providers
Additional ingestion paths
MikroTik KNOT ingestion (knotIngest.js)
MikroTik KNOT routers can POST BLE observations in their native JSON format directly to /api/knot/ingest. The route authenticates via X-Api-Key header (KNOTs cannot handle Bearer JWTs). The payload includes:
uid— gateway MAC address (becomesgatewayId)timestamp— KNOT-format timestamp string (parsed viatimestamps.js)beacons[]— array of observed BLE devices with RSSI, raw ad packets, optional battery/temperature
After normalization, observations feed into the same positioning pipeline as Aruba Event Hub and Desktop Gateway input.
To configure on a KNOT device:
/iot/knot/set url=http://<api-host>:7071/api/knot/ingest
/iot/knot/set http-method=post
/iot/knot/set header="X-Api-Key: <KNOT_API_KEY value>"Aruba ingestion (arubaIngest.js)
Accepts WiFi-derived location data from Aruba infrastructure in Aruba's native format. Normalizes and routes observations into the shared positioning pipeline.
RPA resolution (rpaResolver.js)
Phones with Bluetooth enabled rotate their MAC addresses using Resolvable Private Addresses (RPAs) to preserve user privacy. The backend resolves RPAs against stored Identity Resolving Keys (IRKs) using the Bluetooth Core Spec ah() function (AES-128-ECB). This links rotating phone addresses back to a registered phone beacon identity, so position tracking continues even when the MAC changes.
Outage detection
outageDetectionService.js monitors gateway heartbeat timestamps on a 30-second interval and classifies failures:
| Pattern | Threshold | Classification |
|---|---|---|
| One gateway offline, rest of building online | — | Single gateway failure |
| 25–75% of building gateways offline simultaneously | BUILDING_PARTIAL = 0.25 | Building partial outage |
| ≥75% of building gateways offline simultaneously | BUILDING_FULL = 0.75 | Building full outage (likely power) |
| Multiple buildings with full outages | — | Campus-level outage |
A minimum of 2 gateways per building is required before outage patterns are classified. Gateway offline threshold is 90 seconds since last heartbeat.
Emergency IVR voice flow
ivrStateMachineService.js manages multi-level interactive voice response for emergency escalation calls:
main_menu— initial menu with options: confirm safe, request assistance, or transfer to safety officerassistance_floor— captures floor number when assistance is requestedtransfer— routes call to safety officer lineconfirmed— records safety confirmationassistance_requested— records assistance request and logs in emergency event
The IVR integrates with employeeEscalationService.js to update escalation records based on keypad responses.
Desktop gateway business flow
flowchart LR
A[Windows machine] --> B[Capture SSID/BSSID/DNS/subnet]
B --> C[SiteDetectionEngine]
C -->|ON_SITE| D[BLE scan + advertise]
D --> E[Batch observations]
E --> F[POST /desktop-gateway/heartbeat]
E --> G[POST raddec observations]
F --> H[gateway_health + receivers]
G --> I[standard positioning pipeline]Mobile and phone-beacon logic
- Capacitor wraps the same Nuxt application instead of maintaining a second mobile codebase.
- Phone badge onboarding and station flows live in the web app.
- Phone beacon enrollment is public-token driven and uses native Bluetooth pairing.
- The current UI explicitly shows native app requirement messaging and disabled store buttons when opened in a browser.
Data stewardship notes
- Schema changes are additive and heavily migration-driven; do not edit production assumptions directly in old SQL files.
- Receiver and transmitter identifiers appear in multiple ingestion paths, so normalization rules matter.
- Many business flows depend on settings tables and runtime-config rows, not hard-coded constants alone.