{# Operator dashboard metrics (issue #13; summary cards + mini-bars #91). Rendered standalone on the htmx 15s poll (HX-Request) and included by dashboard.html for the full page. Pure read-only render over a SystemStats snapshot — no aggregation here; that all lives in stats_query.collect_stats(). ALL markup stays in this fragment so the innerHTML poll swaps identical structure (initial render == refresh). #}

Snapshot at . Throughput window since .

{# The invalid-?since= notice is rendered by dashboard.html OUTSIDE the "Show run details" disclosure (issue #117 Phase C): an error the operator must see is never hidden, and it must not vanish on the 15s poll (the poll drops the bad value and re-takes the default, so the fragment never carries the notice). #} {# Curated hardware strip (W3): warnings + neutral GPU activity, refreshed on the same 15s poll as the run figures below. The fuller live view is one click away. #} {% include "fragments/resource_strip.html" %}

View hardware resources

{# Summary-first stat cards (issue #91). The exact figures the tables below detail, surfaced at a glance; each card keeps its unit as text. The review backlog is deliberately NOT here (issue #117 Phase C): it is promoted to the static "Continue review (N)" task card on the full page. A second copy inside this 15s-polled fragment would refresh independently and could show a count that contradicts the static card, so this fragment carries throughput and stage detail only. #}
Runs in window
{{ stats.runs_created_count }}
created this window
Completed
{{ stats.status_counts.get("completed", 0) }}
run(s) transcribed
Roster
{{ stats.roster_size }}
enrolled speaker(s)

Runs by status

{% for status in run_statuses %} {# Humanized LABEL only — the CSS class stays the raw enum so the pill keeps its colour, and /metrics + JSON + `voxint stats` keep the raw string. #} {% endfor %}
StatusRuns
{{ humanize_status(status.value) }} {{ stats.status_counts.get(status.value, 0) }}

Stage timing

{% if stats.stage_durations %} {# Relative mini-bar scaled to the slowest stage's average; the exact seconds + attempt count stay visible so the bar is supplemental, never the only cue. avg_seconds is a SQL AVG of (finished_at - started_at) durations, so it is finite and >= 0 and never exceeds max_avg — the ratio is naturally in [0,100]; `if max_avg` guards the all-zero set (no div-by-zero). #} {% set max_avg = stats.stage_durations | map(attribute="avg_seconds") | max %}
{% for d in stats.stage_durations %} {% endfor %}
StageAvg durationFinished attempts
{{ humanize_stage(d.stage) }} {{ "%.2f"|format(d.avg_seconds) }}s {{ d.attempt_count }}
{% else %}

No finished stage attempts yet.

{% endif %}

Stage failures

{% if stats.stage_failure_counts %}
{% for f in stats.stage_failure_counts %} {% endfor %}
StageFailed attempts
{{ humanize_stage(f.stage) }}{{ f.attempt_count }}
{% else %}

No stage failures recorded.

{% endif %}