Add: WebSocket endpoint for real-time run status (#11) - #140
Open
Ayomipo18 wants to merge 1 commit into
Open
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/predictand/api/predict/uploadrun inference inline — the run is alreadycompleted/failedby 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:
A
failedrun reports its message aserror; acompletedrun carries the result payload. The socket closes once a terminal status has been delivered.Frontend
useRunPollingkeeps its exact signature, so no page changes were needed —NewAnalysisandRunHistorypick 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 (noWebSocketimplementation, or a socket that errored), so the worst case is precisely the old behaviour.Tests
Backend —
tests/test_ws_run_status.py, 16 cases: unknown run (error event +4404close), late attach to a completed/failed run, the fallback message when a failed run stored no error, streaming arunning→ 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.tsxanduseRunPolling.test.tsx: URL derivation (same-origin,https→wss, trailing slash), one socket per run, terminal/non-terminal/unparseable frames, unhealthy on error and whenWebSocketis undefined, cleanup on unmount, no reconnect churn on an equal id list, no polling while healthy, fallback polling after an error, and the preservedrunning → completedcallback.Verification
pytest tests/→ 226 passed, up from 210. The 6 failures intest_regression.py/test_onnx_inference.py/test_anomaly_detector.pyare pre-existing onmain(missing local ML deps) — identical set with these changes stashed.flake8 src/ --select=E9,F63,F7,F82→ 0, matching the CI gate.blackapplied to the two new Python files only;main.pyis not black-clean onmaintoday, 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.jsonchanges 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.