Occupancy Data Engine

How the Occupancy Data Engine processes device group sensor data into unified occupancy measurements

Overview

The Occupancy Data Engine is Haltian’s backend processing service that transforms raw sensor events from a device group’s member sensors into unified, accurate occupancy measurements. It takes entry/exit counts and presence detections as input and produces a single authoritative occupancy state for the space the device group represents.

The engine runs automatically — once you create a device group with supported sensors, the Occupancy Data Engine begins processing their data and publishing occupancy measurements under the device group’s identity.

Processing Pipeline

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#F6FAFA', 'primaryTextColor': '#143633', 'primaryBorderColor': '#143633', 'lineColor': '#143633', 'secondaryColor': '#C7FDE6', 'tertiaryColor': '#73F9C1', 'clusterBkg': '#ffffff', 'clusterBorder': '#143633', 'edgeLabelBackground': '#ffffff'}}}%%
graph TB
    subgraph Input["Member Sensors"]
        A[Entryway sensor<br/>entries + exits]
        C[Presence sensor<br/>occupied + movementDetections]
    end

    subgraph DG["Device Group"]
        D[Virtual Device<br/>aggregation point]
    end

    subgraph ODE["Occupancy Data Engine"]
        E[Event Correlation]
        F[Count Algorithm]
        G[Reset Logic]
    end

    subgraph Output["Output Measurements"]
        H[occupantsCount]
        I[directionalMovement]
        J[occupancyStatus]
    end

    A --> D
    C --> D
    D --> E
    E --> F
    F --> G
    G --> H
    G --> I
    G --> J

How It Processes Data

  1. Event ingestion — When a member sensor sends a measurement (entry/exit trigger, presence detection, or movement), the platform routes it to the Occupancy Data Engine via the device group relationship.
  2. Event correlation — The engine reads the latest device group measurement and correlates the new sensor event with existing state.
  3. Count algorithm — For Entryway sensors, entry and exit counts are summed across all member sensors. For Presence sensors, the engine evaluates whether any sensor detects presence. The exact logic depends on the sensor composition of the group.
  4. Reset logic — The engine applies reset strategies to correct count drift caused by sensor limitations over time.
  5. Output generation — The engine publishes standardized occupancy measurements under the device group’s UUID.

Event-Driven, No Data Duplication

The Occupancy Data Engine processes data on every sensor event — it does not poll or run on a schedule. Each time a member sensor reports a new measurement, the engine recalculates the device group’s occupancy state immediately.

Individual sensor measurements are not duplicated to the device group. Each member device’s data stays on the device itself. The device group only stores its own computed measurements — occupantsCount, directionalMovement, and occupancyStatus — which are the aggregated output of the engine. This means:

  • No storage overhead from copying raw sensor data
  • Member device measurements remain queryable on the device as usual
  • The device group’s measurements are new data derived from the aggregation logic

Output Measurements

The Occupancy Data Engine produces three measurement types, all attributed to the device group:

occupantsCount

Current number of people in the space.

Schema Type: measurementOccupantsCount

{
  "deviceId": "550e8400-e29b-41d4-a716-446655440000",
  "measuredAt": "2026-02-05T10:30:00.000Z",
  "occupantsCount": 5
}
FieldTypeDescription
measuredAtISO 8601 timestampWhen count was calculated
occupantsCountIntegerNumber of occupants (≥ 0)

directionalMovement

Entry and exit tracking.

Schema Type: measurementDirectionalMovement

{
  "deviceId": "550e8400-e29b-41d4-a716-446655440000",
  "measuredAt": "2026-02-05T10:30:00.000Z",
  "entries": 3,
  "exits": 1
}
FieldTypeDescription
entriesIntegerNumber of entries since last reset
exitsIntegerNumber of exits since last reset

occupancyStatus

Binary occupied/vacant status.

Schema Type: measurementOccupancyStatus

{
  "deviceId": "550e8400-e29b-41d4-a716-446655440000",
  "measuredAt": "2026-02-05T10:30:00.000Z",
  "isOccupied": true
}
FieldTypeDescription
isOccupiedBooleantrue if space is occupied, false if vacant

Querying Occupancy Data

Occupancy Data Engine measurements are accessed through the same APIs as any device measurement — using the device group’s UUID.

Service API (GraphQL)

query MeasurementLastOccupantsCount($device: uuid) {
  measurementLastOccupantsCount(where: { deviceId: { _eq: $device } }) {
    measuredAt
    occupantsCount
  }
}

query MeasurementLastDirectionalMovement($device: uuid) {
  measurementLastDirectionalMovement(where: { deviceId: { _eq: $device } }) {
    measuredAt
    entries
    exits
  }
}

query MeasurementLastOccupancyStatus($device: uuid) {
  measurementLastOccupancyStatus(where: { deviceId: { _eq: $device } }) {
    measuredAt
    isOccupied
  }
}

Note: Pass the device group’s UUID as the $device parameter — device groups use the same UUID structure as physical devices.

Stream API (MQTT)

Occupancy Data Engine measurements are published on the same topic structure as physical devices:

haltian-iot/events/{integration-id}/{api-key-id}/measurements/occupantsCount/{device-group-id}
haltian-iot/events/{integration-id}/{api-key-id}/measurements/directionalMovement/{device-group-id}
haltian-iot/events/{integration-id}/{api-key-id}/measurements/occupancyStatus/{device-group-id}

Data API (Parquet)

Occupancy measurements from device groups appear in Parquet exports alongside regular device measurements. The devicegroups and devicegroupdevices tables provide the group structure for joining.

Next Steps


Reset Logic

How the Occupancy Data Engine corrects count drift using inactivity-based and manual resets

Troubleshooting

Common issues and solutions when working with the Occupancy Data Engine.