{% comment %} The live figures (SPEC §13.2). Polled every three seconds; the view answers 304 when none of these numbers moved, so an idle broadcast page costs a request line. Every number is computed in apps/broadcasts/services.py::counters from the recipient rows, which is what makes `queued = sent + failed + cancelled + skipped` reconcile by construction rather than by a running total somebody has to keep correct. `delivered` and `read` are joined off the message row that apps/messaging/ingest.py advances when a delivery receipt arrives — this app has no receipt path of its own. {% endcomment %}
Progress {# Polled, so it follows the broadcast through scheduled → sending → sent. #} {{ broadcast.get_status_display }} {% if broadcast.is_live %}
{% csrf_token %}
{% endif %}
{% comment %} While fanout is still expanding the audience, `queued` counts the recipients written so far rather than the total — so a percentage against it retreats every time a chunk of five hundred lands. The bar carries no value until the denominator is real, and says what it is doing instead. {% endcomment %} {% if expanding %}

Working out who this reaches…

{% else %}
{% endif %}
{{ counters.queued }}
Queued
{{ counters.sent }}
Sent
{{ counters.delivered }}
Delivered
{{ counters.failed }}
Failed
{{ counters.skipped }}
Skipped
{% if counters.cancelled %}
{{ counters.cancelled }}
Cancelled
{% endif %} {% if counters.pending %}
{{ counters.pending }}
Waiting
{% endif %}
{% if reasons %}

Why people were skipped

{% for reason in reasons %}
{{ reason.label }} {{ reason.count }}
{% endfor %}
{% endif %}