Schedule
Totals
Enrolled{{ summary.participants|length }}
{% for k in ('answered', 'expired', 'missed', 'open', 'upcoming') %}
Sessions {{ k }}{{ summary.sessions|selectattr('status', 'equalto', k)|list|length }}
{% endfor %}
{% for k in ('pending', 'sent', 'dry_run', 'failed', 'skipped') %}
Messages {{ k|replace('_', ' ') }}{{ summary.counts.get(k, 0) }}
{% endfor %}
Participants and compliance
{% if not summary.participants %}Nobody has enrolled yet. Participants enrol when they finish the regular study flow.
{% else %}| Participant | Enrolled | Time zone | Contact | Answered | Expired | Missed | Compliance | Median latency | Next session (local) | Actions |
|---|---|---|---|---|---|---|---|---|---|---|
| {{ p.participant }} | {{ utc(p.enrolled_at) }} | {{ p.timezone }} | {% if p.withdrawn %}stopped{% elif not p.consent %}no consent{% else %}{{ p.channels|join(', ') or 'none' }}{% if p.contact.email %} · email held{% endif %}{% if p.contact.phone %} · phone held{% endif %}{% endif %} | {{ p.counts.answered }} | {{ p.counts.expired }} | {{ p.counts.missed }} | {% if p.compliance is not none %}{{ (p.compliance * 100)|round|int }}% of {{ p.closed }}{% else %}none closed yet{% endif %} | {% if p.median_latency is not none %}{{ span(p.median_latency) }}{% else %}no answers yet{% endif %} | {% if p.next %}{{ p.next.title or p.next.name }}: {{ local(p.next.opens_at, p.timezone) }}{% else %}none{% endif %} | {% if p.contact.email or p.contact.phone %} {% endif %} |
{{ empty }}
{% else %}| Due | Sent | Participant | Session | Rule | Channel | Status | Detail |
|---|---|---|---|---|---|---|---|
| {{ utc(n.due_at) }} | {{ utc(n.sent_at) if n.sent_at else '' }} | {{ n.participant }} | {{ n.session_id }} | {{ n.rule }} | {{ n.channel }} | {{ n.status|replace('_', ' ') }} | {{ n.detail or '' }}{% if n.preview %}Text{{ n.preview }} |
Failed messages
{{ notes_table(summary.failed, 'schedule-failed', 'No failed messages.') }}Upcoming messages
{{ notes_table(summary.upcoming, 'schedule-upcoming', 'No messages waiting.') }}Sent messages (latest 200)
{{ notes_table(summary.sent, 'schedule-sent', 'Nothing sent yet.') }}Skipped messages (latest 100)
{{ notes_table(summary.skipped, 'schedule-skipped', 'Nothing skipped.') }}Delphi rounds
{% if summary.delphi_stop %}{% endif %}| Round | Opens | Closes | Status | Answered |
|---|---|---|---|---|
| {{ r.round }} | {{ utc(r.opens_at) }} | {{ utc(r.closed_at or r.closes_at) }} | {{ r.status }}{% if r.stop_reason %} ({{ r.stop_reason }}){% endif %} | {{ summary.sessions|selectattr('id', 'equalto', 'round:' ~ r.round)|selectattr('status', 'equalto', 'answered')|list|length }} |
Item statistics
| Round | Item | n | Median | Q1 | Q3 | IQR | Median change | Changed share | Settled |
|---|---|---|---|---|---|---|---|---|---|
| {{ st.round }} | {{ st.item }} | {{ st.n }} | {% for k in ('median', 'q1', 'q3', 'iqr', 'median_change') %}{{ '%.2f'|format(st[k]) if st[k] is not none else '' }} | {% endfor %}{{ '%.0f%%'|format(st.changed_share * 100) if st.changed_share is not none else '' }} | {{ 'yes' if st.settled else 'no' }} |
All sessions
{% if not summary.sessions %}No sessions yet.
{% else %}| Participant | Session | Kind | Opens (UTC) | Opens (local) | Closes (UTC) | Status |
|---|---|---|---|---|---|---|
| {{ s.participant }} | {{ s.id }} | {{ s.kind }} | {{ utc(s.opens_at) }} | {{ local(s.opens_at, tzs.get(s.participant, 'UTC')) }} | {{ utc(s.closes_at) }} | {{ s.status }} |