Skip to main content

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 EventsSignals
DetectsBoundary crossings (enter/exit)Behavioral patterns (dwell, policy violations)
TriggerDevice crosses geofence boundaryDevice matches anomaly conditions over time
LifecycleSingle eventStateful (started -> confirmed -> ended)
ExplainabilityEvent type + locationReason 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

Future Feature

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:

  1. Device is inside the policy's geofence
  2. Current time falls within the policy's time window (or time window is null, meaning always active)
  3. Device passes the device filter (type and metadata match)
  4. 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
StateDescription
startedInitial detection. The anomaly condition has been observed.
confirmedAnomaly confirmed. This is the default state that triggers workflows.
endedAnomaly resolved naturally (e.g., device exits the geofence, ending a dwell signal).
resolvedManually 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

Automatic 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:

FilterValuePurpose
MIN_DISTANCE_METERS25.0 mIgnores movements smaller than 25 meters
MIN_TIME_SECONDS30 sRequires 30 seconds between state transitions for the same geofence
MAX_ACCURACY_METERS100.0 mRejects location updates with GPS accuracy worse than 100 meters
COOLDOWN_SECONDS300 s5-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:

  1. Geofence trigger (geofence_dwell) -- Dwell signals route through the geofence trigger handler using the geofence_dwell event type. Configure via trigger_config.type = "geofence_dwell" with a dwell_threshold in seconds.

  2. Signal trigger (signal) -- A dedicated trigger type that matches on signal_types and signal_states. Configure via trigger_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.

tip

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