CIS490/training/dashboard
Max Gorog db9f013969 deck: 9 new scenes to meet CIS-490 assignment-guide rubric
Five required + four optional slides, slotted into the existing flow
without renumbering the visible deck UI:

REQUIRED
- problem-statement (after motivation): single-sentence problem,
  three numeric stat cards, explicit task-type justification
  (multi-class classification, why not regression/ranking)
- research-questions (after problem-statement): two-column literature
  gap layout + RQ1/RQ2/RQ3
- solution-overview (after research-questions): inline-SVG block
  diagram of the pipeline (fleet hosts → receiver → episodes →
  windowing → model zoo → per-window phase → trust score →
  containment + reset)
- evaluation-setup (between chunking and models): four blocks
  covering split recipe, primary metric, baselines compared, and
  what's reported alongside accuracy. Each block leads with the
  *why*, matching the assignment's "explain not only what will be
  measured but why" requirement.
- conclusion-future (before references): two-column "what we showed"
  + unsupervised next steps (clustering / anomaly / SSL pretrain /
  embedding viz). Addresses Section 8 of the assignment guide.

OPTIONAL
- theoretical-contributions: window-centre labelling,
  schema-hashed checkpoints, cross-host as eval axis
- practical-contributions: /proc-only deployment,
  producer-agnostic dashboard, labelled dataset on disk
- design-principles: one-loop-many-models, typed events as
  contract, two-agent path ownership
- limitations: two-host fleet, synthetic profiles, 10 Hz floor,
  KNN cross-host gap

Plus references/links.md gains four real online references (PyTorch,
XGBoost, scikit-learn, proc(5)) bringing the citation count from 8
to 12 — over the assignment's 10-source minimum.

CSS additions cover the new layouts (.problem-claim, .problem-stats,
.research-grid, .pipeline-svg + .pipeline-stage / .pipeline-arrow,
.eval-blocks, .conclusion-grid). Limitations cards reuse the
motivation-card pattern with an armed-phase amber marker for the
"warning" feel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 15:32:50 -05:00
..
static deck: 9 new scenes to meet CIS-490 assignment-guide rubric 2026-05-08 15:32:50 -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