Signals
Signals are SpatialFlow's anomaly detection layer. While geofence events detect simple boundary crossings (enter/exit), signals detect behavioral patterns -- prolonged dwell inside a zone, policy violations during restricted hours, and route deviations. Signals give you visibility into why something happened, not just that it happened.
What is a Signal?
A signal represents a detected spatial anomaly. Each signal is stored as a SignalEvent record that captures the anomaly type, its lifecycle state, the device and geofence involved, and structured explainability data that describes exactly what triggered it.
Geofence events vs. signals:
| Geofence Events | Signals | |
|---|---|---|
| Detects | Boundary crossings (enter/exit) | Behavioral patterns (dwell, policy violations) |
| Trigger | Device crosses geofence boundary | Device matches anomaly conditions over time |
| Lifecycle | Single event | Stateful (started -> confirmed -> ended) |
| Explainability | Event type + location | Reason codes, thresholds applied, structured explanation |
Geofence enter/exit events are dual-written as both a GeofenceEvent and a SignalEvent for backward compatibility. Signal events persist independently of location data cleanup -- even if older DeviceLocation records are purged, the signal history remains.
Signal Types
SpatialFlow supports five signal types across three anomaly detection categories:
Boundary Crossing Signals
geofence_enter and geofence_exit -- These are boundary crossing signals, dual-written alongside the legacy GeofenceEvent model. They ensure that all spatial events flow through the unified signal pipeline for consistent workflow triggering and explainability.
Dwell Detection
dwell -- Fires when a device stays inside a geofence beyond a configured time threshold.
Dwell detection is powered by DwellDetectionService, which tracks per-device dwell state in the cache. Each workflow can define its own dwell threshold via trigger_config.dwell_threshold (in seconds), with a default of 300 seconds (5 minutes). Multiple workflows can monitor the same geofence with different thresholds.
{
"signal_type": "dwell",
"state": "confirmed",
"metadata": {
"dwell_duration_s": 612.5,
"dwell_threshold": 300,
"location": {
"latitude": 37.7749,
"longitude": -122.4194
},
"accuracy": 8.2
},
"reason_codes": ["dwell"]
}
Route Deviation
route_deviation exists as a signal type but no detection service is active yet. It is reserved for a future release that will detect when a device deviates from a planned route corridor.
Policy Violation
policy_violation -- Fires when a device's location matches a policy's geofence condition combined with time window, device type, and role filters.
Policies are workspace-level rules evaluated by PolicyEvaluationService on every processed location update. A violation is raised when all conditions match simultaneously:
- Device is inside the policy's geofence
- Current time falls within the policy's time window (or time window is null, meaning always active)
- Device passes the device filter (type and metadata match)
- Device owner's role passes the role filter
Valid roles for role filters: field_worker, manager, owner.
Signal Lifecycle
Every signal progresses through a state machine with four states:
started ──> confirmed ──> ended
│
└──────> resolved
| State | Description |
|---|---|
started | Initial detection. The anomaly condition has been observed. |
confirmed | Anomaly confirmed. This is the default state that triggers workflows. |
ended | Anomaly resolved naturally (e.g., device exits the geofence, ending a dwell signal). |
resolved | Manually or automatically resolved through external action. |
Key detail: Workflows default to triggering on the confirmed state. If you need to react to other lifecycle transitions (e.g., when a signal ends), configure signal_states in the workflow trigger to include the desired states.
Signal Explainability
Each signal carries three JSON fields that provide full transparency into what triggered the detection:
metadata
Location context and detection-specific data. Contents vary by signal type.
{
"dwell_duration_s": 612.5,
"dwell_threshold": 300,
"location": {
"latitude": 37.7749,
"longitude": -122.4194
},
"accuracy": 8.2
}
reason_codes
Machine-readable list of causes. Used for programmatic filtering and categorization.
["dwell"]
Common reason codes include geofence_enter, geofence_exit, dwell, and policy_violation.
explanation
Structured debug information with two sub-objects:
inputs-- The entities involved (device, geofence, workflow)thresholds_applied-- The specific threshold values that were evaluated
{
"inputs": {
"device_id": "truck-005",
"geofence_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"geofence_name": "Warehouse A",
"workflow_id": "wf-789"
},
"thresholds_applied": {
"dwell_threshold": 300
}
}
This explainability data is available in workflow template variables via {{trigger.explanation}} and {{trigger.reason_codes}}, allowing you to include detection context in notifications and webhook payloads.
GPS Jitter Filtering
SpatialFlow's LocationFilterService applies several filters before any signal detection runs. These filters prevent false signal generation from noisy GPS data. You do not need to implement your own filtering.
The following thresholds are applied to all incoming location updates:
| Filter | Value | Purpose |
|---|---|---|
MIN_DISTANCE_METERS | 25.0 m | Ignores movements smaller than 25 meters |
MIN_TIME_SECONDS | 30 s | Requires 30 seconds between state transitions for the same geofence |
MAX_ACCURACY_METERS | 100.0 m | Rejects location updates with GPS accuracy worse than 100 meters |
COOLDOWN_SECONDS | 300 s | 5-minute cooldown per device x geofence x event type combination |
These filters run before geofence containment checks. A location update that fails any filter is silently dropped and does not affect signal state.
Workflow Integration
Signals trigger workflows through two paths:
-
Geofence trigger (
geofence_dwell) -- Dwell signals route through the geofence trigger handler using thegeofence_dwellevent type. Configure viatrigger_config.type = "geofence_dwell"with adwell_thresholdin seconds. -
Signal trigger (
signal) -- A dedicated trigger type that matches onsignal_typesandsignal_states. Configure viatrigger_config.type = "signal"with a list of signal types to monitor.
Both trigger types support geofence filtering (geofence_ids) and device filtering (device_filters). The signal trigger defaults to signal_states: ["confirmed"], meaning only confirmed signals fire the workflow.
For dwell detection, the geofence_dwell trigger type is the primary path since it integrates directly with the DwellDetectionService threshold system. The signal trigger type is more general-purpose and useful for policy violations and other signal types.
See the Signal Configuration Guide for detailed setup instructions, threshold tuning, and common patterns.
Next Steps
- Signal Configuration Guide -- Set up dwell thresholds, policy rules, and workflow triggers
- Workflows -- Automation engine that executes actions when signals fire
- Geofences -- Spatial boundaries that signals monitor
- Devices -- Tracked entities that generate location updates
- API Reference -- Complete API documentation