Skip to content

Read Path And Dashboard

The read side is deliberately thin. It hands back the state the daemon already computed and does no machine learning of its own.

The API

The API is a FastAPI app (uvicorn app.main:app), and it does very little:

  • It reads the materialized view and shapes it into the dashboard's payload.
  • It imports no ML at all. The expensive HMM and episode work already ran on the write side.
  • It makes sure the schema exists on load, so a fresh clone works whether the API or the daemon starts first.

Beyond reads, the API only does tiny writes: add or remove a tracked student, ack a trigger, add a note, toggle presence or picked, signal a reset, pause or resume polling. The API reference lists every endpoint.

The Dashboard

The React dashboard polls a few endpoints, each on its own ~1.5s timer:

flowchart LR
    dash["React dashboard<br/>polls every ~1.5s"]
    dash --> a["GET /api/student_states/"]
    dash --> b["GET /api/triggers/"]
    a --> grid["Student card grid<br/>stable order by studentID"]
    b --> col["Who-needs-help column<br/>wheel-spin · inactive · big-rewrite,<br/>colour-coded and ackable"]

It polls the roster (/api/tracked/) on the same timer too, so adding or removing a student shows up right away and the alert column can hide alerts for students who aren't tracked anymore. When you open a student's detail modal, it fetches that one student's heavier payload (the playground prompt included) and keeps it refreshed while the modal is open, which is why the cohort grid itself can stay light.

Why The Dashboard Is Fast

It's fast because it reads a precomputed materialized view: small, indexed rows. It still hits SQLite on every request, but what it's reading is cheap. The speed has nothing to do with the in-memory workers.

Note

The in-memory workers speed up the daemon, not the dashboard. The dashboard is quick purely because it reads a cheap, already-computed projection.

Payload Shape

Every student in /api/student_states/ carries their full derived state:

Field Meaning
current_state / current_label the latest HMM state (0/1/2) and its label
stuck / consecutive_stuck the wheel-spin flag and how long it's held
run_count / event_count activity counters
last_seen timestamp of the most recent event
state_sequence per-run HMM states (this drives the strategy sparkline)
hmm the full HMM blob: runs, observation labels, run count
episodes segmented episodes, pauses, and events
block the current "playground" LLM prompt and its timestamp (heavy payload only)

Using the dashboard shows how these actually render on screen.