Skip to content

Add: WebSocket endpoint for real-time run status (#11) - #140

Open
Ayomipo18 wants to merge 1 commit into
Climate-Vision:mainfrom
Ayomipo18:feature/issue-11-websocket-run-status
Open

Add: WebSocket endpoint for real-time run status (#11)#140
Ayomipo18 wants to merge 1 commit into
Climate-Vision:mainfrom
Ayomipo18:feature/issue-11-websocket-run-status

Conversation

@Ayomipo18

Copy link
Copy Markdown
Contributor

Closes #11

What this adds

A /ws/runs/{run_id} WebSocket so the dashboard learns a run finished the moment it does, instead of waiting up to 5s for the next poll.

Why this shape

/api/predict and /api/predict/upload run inference inline — the run is already completed/failed by the time the HTTP response returns. So status transitions always happen inside the process serving the socket, and a small in-memory pub/sub hub is enough; no broker or task queue is needed for the current architecture.

Two details that follow from that:

  • The socket subscribes before reading the database. Otherwise a transition landing between the read and the subscribe would be lost entirely.
  • On connect it sends a snapshot read from the database. A client attaching to a run that already finished gets a terminal event immediately rather than hanging until timeout. This is the common case here, given inference is synchronous.

A failed run reports its message as error; a completed run carries the result payload. The socket closes once a terminal status has been delivered.

Frontend

useRunPolling keeps its exact signature, so no page changes were needed — NewAnalysis and RunHistory pick up the push updates through the hook they already use. It now opens one socket per in-flight run and refetches on a terminal event. The 5s interval only starts when the socket path is unavailable (no WebSocket implementation, or a socket that errored), so the worst case is precisely the old behaviour.

Tests

Backendtests/test_ws_run_status.py, 16 cases: unknown run (error event + 4404 close), late attach to a completed/failed run, the fallback message when a failed run stored no error, streaming a running → terminal transition, isolation between two concurrent runs, subscription released on disconnect, hub unit tests (fan-out, no-op publish, full-queue drop, map cleanup), and the predict-to-hub wiring for both the success and the exception path.

Frontend — 18 cases across useRunWebSocket.test.tsx and useRunPolling.test.tsx: URL derivation (same-origin, httpswss, trailing slash), one socket per run, terminal/non-terminal/unparseable frames, unhealthy on error and when WebSocket is undefined, cleanup on unmount, no reconnect churn on an equal id list, no polling while healthy, fallback polling after an error, and the preserved running → completed callback.

Verification

  • pytest tests/226 passed, up from 210. The 6 failures in test_regression.py / test_onnx_inference.py / test_anomaly_detector.py are pre-existing on main (missing local ML deps) — identical set with these changes stashed.
  • flake8 src/ --select=E9,F63,F7,F82 → 0, matching the CI gate. black applied to the two new Python files only; main.py is not black-clean on main today, so I left untouched code alone rather than mixing in unrelated reformatting.
  • npm run build (tsc + vite) green.

Note on the frontend tests

They need the Vitest runner from #125, which is green but not yet merged. I deliberately kept no package.json changes here, so there is no conflict in either merge order — the specs simply start running once #125 lands. I verified all 18 pass locally against #125's exact config and dependency versions, then reverted the scaffolding so it stays out of this diff.

Deliberately out of scope

The hub is per-process. Under a multi-worker deployment a socket on worker A will not see a transition published by worker B; that client still gets the initial snapshot and the polling fallback. Making this cross-process (Redis pub/sub or Postgres LISTEN/NOTIFY) is a real follow-up, but it is a deployment-architecture change rather than part of this endpoint, so I left it out and documented the boundary in the module docstring. Happy to open a follow-up issue if you'd like it tracked.

Adds /ws/runs/{run_id} so the dashboard receives status pushes instead of
waiting up to 5s for the next poll.

Because /api/predict runs inference inline, status transitions happen in the
process serving the socket, so a small in-memory pub/sub hub is enough: the
predict handlers publish a terminal event and every socket watching that run
receives it. On connect the socket reads the current status from the database
first, so a client attaching to an already-finished run still gets a terminal
event rather than hanging.

Frontend: useRunPolling now prefers a WebSocket per in-flight run and only
starts the 5s interval when the socket path is unavailable, so behaviour
degrades to exactly what it was before.

Closes Climate-Vision#11
@Ayomipo18
Ayomipo18 requested a review from Goldokpa as a code owner August 18, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Good First Issue] Implement WebSocket real-time run status updates

1 participant