{# The speaker roster — full-page include and htmx post-mutation refresh target. Operator refusals (RosterError) arrive as roster_error and render inline; destructive forms carry hx-confirm (the plain no-JS path has no confirm — htmx is vendored and always served, so that path is a deliberate fallback). #} {% if roster_error %}{% endif %} {% for entry in overview.active %}

{% set bars = voiceprints.get(entry.speaker.id) %} {% if bars %} {# Signature: a deterministic strip derived from this speaker's own enrollment centroid — real data, same voice → same strip. #} {% endif %} {{ entry.speaker.display_name }}

{{ entry.embeddings|length }} enrollment{{ "s" if entry.embeddings|length != 1 }} ({{ entry.embeddings|map(attribute="embedding_space")|unique|join(", ") or "none" }}) · {{ entry.assignment_count }} machine proposal{{ "s" if entry.assignment_count != 1 }} · last seen {{ entry.last_seen_at.strftime("%Y-%m-%d") if entry.last_seen_at else "never" }}

{% set targets = overview.active | rejectattr("speaker.id", "equalto", entry.speaker.id) | list %} {% if targets %}
{% endif %}
{% set research = research_by_speaker.get(entry.speaker.id) %} {% if research %}{% include "fragments/research.html" %}{% endif %} {% if entry.embeddings %}
Enrollments {% for e in entry.embeddings %} {% set last_in_space = entry.embeddings | selectattr("embedding_space", "equalto", e.embedding_space) | list | length == 1 %}
{{ e.embedding_space }} {{ e.created_at.strftime("%Y-%m-%d") }} {% if e.source_diarization_label %}· label {{ e.source_diarization_label }}{% endif %} {% if e.source_pipeline_run_id %}· source run{% endif %} {% if last_in_space %} last one — removing it makes this speaker unmatchable in {{ e.embedding_space }} {% endif %}
{% endfor %}
{% endif %}
{% else %}

No speakers yet — enroll voices from the review workbench.

{% endfor %} {% if overview.inactive %}
Former speakers ({{ overview.inactive|length }}) {% for entry in overview.inactive %}
{{ entry.speaker.display_name }} {% if entry.merged_into_name %} merged into {{ entry.merged_into_name }} {% if entry.speaker.merged_at %}on {{ entry.speaker.merged_at.strftime("%Y-%m-%d") }}{% endif %} {% else %} archived {% if entry.speaker.deleted_at %}{{ entry.speaker.deleted_at.strftime("%Y-%m-%d") }}{% endif %}
{% endif %}
{% endfor %}
{% endif %}