CIS490/training/dashboard
Max c2a71de4b2 scene 9 bars: paint full zoo + 0–1 visible scale
- multi_model_metrics: publish gbt / mlp / cnn / knn_semi /
  gru / lstm / bert (knn handled by knn streamer); read both
  *_train.json and *_eval.json with macro_f1.point fallback
- dashboard.css: add palette gradients for the four
  non-canonical names so the bars render with a fill colour
- dashboard.js: open the bar's visible scale to the full 0–1
  range so honest-low cross-host F1s show as a bar instead of
  clamping to 0%
- ship lambda-live-detection-loop.py + dashboard request docs
  (scenes 7/8/12, sticky cache, lambda-inference-demo)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:18:00 -05:00
..
static scene 9 bars: paint full zoo + 0–1 visible scale 2026-05-08 17:18:00 -05:00
__init__.py training/dashboard: live deck at dashboard.wg, fed by receiver 2026-05-07 21:26:07 -05:00
__main__.py training/dashboard: live deck at dashboard.wg, fed by receiver 2026-05-07 21:26:07 -05:00
app.py training/dashboard(references): description sidebar + better space use 2026-05-08 12:40:32 -05:00
client.py training/dashboard: events.py — typed producer interface 2026-05-08 11:59:03 -05:00
dashboard.caddy training/dashboard: live deck at dashboard.wg, fed by receiver 2026-05-07 21:26:07 -05:00
events.py live scene: per-host swim lanes + latest-detection callout 2026-05-08 14:03:32 -05:00
feeder.py baseline: phase mix from sampled dataset, not 5-min window 2026-05-08 13:04:36 -05:00
PRODUCERS.md training/dashboard: events.py — typed producer interface 2026-05-08 11:59:03 -05:00
README.md training/dashboard: PRODUCERS.md + client.py for the model session 2026-05-07 21:34:54 -05:00

training/dashboard/

Live web display served at https://dashboard.wg. A Starlette app on 127.0.0.1:8447 behind Caddy; messages from Python are pushed to connected browsers over a WebSocket.

This is intentionally a blank slate — the default page just appends every received JSON message to a scrolling log. Build the real widgets on top of window.dashboard.onMessage.

Run locally

uv run python -m training.dashboard
# → open http://127.0.0.1:8447

Push live data from Python

Same process (e.g. a notebook driving the page):

from training.dashboard.app import broadcaster
await broadcaster.publish({"type": "metric", "name": "loss", "value": 0.42})

Different process (orchestrator, receiver, ad-hoc shell):

curl -s http://127.0.0.1:8447/publish \
     -H 'content-type: application/json' \
     -d '{"type":"hello","msg":"from cron"}'

The /publish endpoint is loopback-only (403 otherwise) and is not reverse-proxied by Caddy, so it cannot be hit from the WG mesh.

Producing events from your own code

If you're writing code that should drive a dashboard widget (model inference, training-loop metrics, profile envelopes), see PRODUCERS.md — it documents every event type the widgets subscribe to, the loopback-only /publish contract, the reconnect gotcha, and the systemd hardening that constrains producers running on the Pi. There's a stdlib-only Python helper at client.py:

from training.dashboard.client import Publisher
Publisher().publish({"type": "model_metric", "model": "lstm", "accuracy": 0.928})

Customizing the page

The default static/index.html exposes window.dashboard:

window.dashboard.onMessage = (msg) => {
  if (msg.type === 'metric') updateChart(msg.name, msg.value);
};
window.dashboard.send({type: 'request-snapshot'});  // browser → server

Override onMessage to dispatch to your own widgets. The blank-slate log renderer is just there so a fresh deploy is observably alive.

Deploying to the Pi

  1. sudo cp etc/cis490-dashboard.service /etc/systemd/system/
  2. sudo cp training/dashboard/dashboard.caddy /etc/caddy/Caddyfile.d/
  3. sudo systemctl daemon-reload && sudo systemctl enable --now cis490-dashboard.service
  4. sudo systemctl reload caddy