# Realtime Events (SignalR)

> The WebSocket event stream powering the live dashboard — hub groups, event payloads, and prediction timestamp semantics.

Live updates travel over one SignalR hub at **`/hub/realtime`**. Authentication
uses the same JWT as the REST API, passed as `?access_token=<jwt>` (or an
`Authorization` header).

The event stream is **at-most-once and fire-and-forget** — it is never the
source of truth. Events missed during a disconnect are not replayed;
authoritative state always comes from the REST API
(`/api/inference/status`, `/api/system/status`) and the on-box database.

## Subscription groups

High-frequency streams are opt-in per group, joined only while a consuming
screen is mounted and re-joined automatically on reconnect:

| Group | Carries | Consumed by |
|---|---|---|
| `sensors` | Live sensor reading batches | Realtime Monitor |
| `predictions` | New prediction batches | Dashboard charts |
| `logs` | Full log stream (all levels) | LogViewer page only |
| `alerts` | Warning/Error log entries only | Notification bell, app-wide |

The `alerts` group exists so warnings surface everywhere without streaming the
full `logs` firehose to every dashboard.

## Events (server → client)

| Event | Payload highlights |
|---|---|
| `SensorReading` | `channelIndex`, `value`, `timestamp` |
| `NewPrediction` | `predictions`, `confidenceScores`, `modelId`, `inferenceTimeMs`, window timestamps (below) |
| `InferenceStateChanged` | `state` ∈ `idle · ready · running`, active datasource, loaded model — emitted on every lifecycle transition |
| `InferenceFaulted` | `reason`, `datasourceId` — also raises the blocking fault banner |
| `HealthMetricsUpdate` | CPU/GPU/memory, uptime |
| `ModelActivated` | `modelId`, `version`, shapes, file size |
| `ModelUploadProgress` | Upload/validation phases, error message if failed |
| `OutputWriteFailed` | Coalesced per sink — immediate first alert, then one per 30 s window |
| `LogEntryAdded` | Log entry (level, message, logger, metadata) |

## Prediction timestamp semantics

A prediction describes a *past* window of input. All timestamps are Unix epoch
milliseconds from **one clock** — the backend stamps each sample once at read
time, and that value is echoed through the pipeline, never regenerated:

| Field | Meaning |
|---|---|
| `windowStartTimestamp` | Oldest input sample in the window |
| `windowEndTimestamp` | Newest input sample — the "as-of" time the prediction is valid for |
| `timestamp` | Mirrors `windowEndTimestamp` (compatibility) |
| `emittedAt` | Backend wall-clock at emission — `emittedAt − windowEndTimestamp` ≈ end-to-end latency |

On the dashboard the prediction trace visibly **trails** the sensor trace by
the real pipeline latency. That gap is an intended operational signal, not a
rendering defect.

## Delivery guarantees

Slow consumers never throttle inference: dashboard broadcasts are
fire-and-forget, and events to a client that can't keep up are dropped and
counted (`/api/inference/backpressure`). Notification-worthy events are
deduplicated and rate-limited to keep the bell useful — see
[Notifications](/operate/notifications/).

## Next steps

  - [API Overview](/api-reference/overview/) — REST surfaces and authentication.
  - [Monitoring](/operate/monitoring/) — Where these events land in the UI.
  - [Notifications](/operate/notifications/) — Which events raise alerts, and what stays silent.
