Skip to content

Phase 2: scheduled supplier risk digest by email - #37

Merged
alikahwaji merged 1 commit into
developfrom
feat/scheduled-reports
Oct 22, 2026
Merged

alikahwaji merged 1 commit into
developfrom
feat/scheduled-reports

Conversation

@alikahwaji

Copy link
Copy Markdown
Contributor

Phase 2, fourth item: scheduled reports.

What's new

Administration → Integrations → Scheduled reports (admins): a supplier risk digest by email.

  • When: daily, weekly on a chosen day, or monthly on the 1st, at an hour in UTC. The picker also shows each hour in local time.
  • Contents: what's new since the last report:
    • public records naming your suppliers (one line per recall event, linked to the source)
    • disaster exposures near a site, or in the country
      It carries the same caution as the app ("matched by name or location, not findings").
  • Recipients: members of the organization only, checked when saved and again when sent, so leavers stop receiving it.
  • Options: "Don't send when there is nothing new"; send now (within about a minute); pause/resume; delete. Each schedule shows its next run and last result.
  • Audit: creating and deleting schedules is audited.

How it works

  • Migration 021: report_schedules, with forced RLS.
  • Data source: the digest reads the new-match events the webhook detector (Phase 2: signed webhooks when new public records match a supplier #35) already records for every organization.
  • Runner: the coordinator sends due reports on its 30-second loop. Each schedule is claimed with FOR UPDATE SKIP LOCKED and moved forward before sending, so replicas never double-send.
  • Windows: (since, until] in database time, so an event on the boundary appears in exactly one report.
  • Email: HTML and plain text over SMTP (SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_FROM, SMTP_STARTTLS). All record text is HTML-escaped.
  • Without SMTP: nothing is sent and the page says so.
  • Compose: now includes Mailpit (UI on 127.0.0.1:8025), so development email never leaves the machine.
  • Staging: reads optional smtp-* keys from scirm-secrets, so pods start without them. PUBLIC_APP_URL on the coordinator provides the link back to SCIRM.
  • Docs: docs/scheduled-reports.md.

Testing

  • New tests/security/test_reports.py (18 tests):
    • next-run arithmetic: daily, weekly, monthly and year rollover
    • digest counts, content and HTML escaping (a supplier named <script>); "nothing new"
    • admin-only; non-members and another organization's members refused as recipients; bad timing refused; another organization gets 404 on change and delete
    • "send now" needs SMTP
    • runner: sent once to members with the period's events; a recipient who left isn't mailed (the failure is recorded); skip-empty; a boundary event is in exactly one report
  • tests/db: report_schedules has forced RLS.
  • Suites: 535 in-memory; security + db + evidence on PostgreSQL: 333. Frontend: 35, tsc clean, vite build OK. Strict docs build OK.
  • Found while testing: the first version used [since, until). On Windows the clock ticks every ~15 ms, so an event could share the run's timestamp and be dropped. That's fixed with (since, until] and database time, and covered by a test.
  • Live on the gate stack:
    • scheduled a weekly digest in the UI and pressed "Send now"
    • Mailpit received it within seconds: to the member, subject "1 new record(s) naming your suppliers", listing the Manila Pharma Fillers warning letter, HTML table and link back
    • the schedule then showed "Sent (1 items)", with the next run on Monday 07:00 UTC

Not built

  • Attachments: no CSV attachment.
  • Time zones: no per-organization zone (times are UTC, shown locally in the picker).
  • Recipients: no external recipients or distribution lists (members only, by design for now).

🤖 Generated with Claude Code

Phase 2, fourth item: scheduled reports.

- Migration 021: report_schedules (daily / weekly on a day / monthly on the
  1st, hour in UTC, member recipients, skip-empty, active, next and last
  run, last status), forced RLS.
- The digest lists the period's new matches recorded by the webhook
  detector: public records naming suppliers (one line per recall event,
  linked to the source) and disaster exposures, with the same caution as
  the app. HTML and plain text; all record text is escaped.
- The coordinator sends due reports on its 30 s loop: claimed with
  FOR UPDATE SKIP LOCKED and moved forward before sending, so replicas
  never double-send. Windows are (since, until] in database time, so an
  event on the boundary is in exactly one report.
- Recipients must be active members, checked when saved and when sent.
- SMTP settings (SMTP_HOST, ...). Compose gets Mailpit (127.0.0.1:8025) so
  development email never leaves the machine; staging reads optional
  smtp-* keys from scirm-secrets.
- Administration > Integrations > Scheduled reports: create, send now,
  pause/resume, delete, next run and last result. Audited.
- docs/scheduled-reports.md. Also fixes a "sql =(" left by an earlier edit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alikahwaji
alikahwaji merged commit c741851 into develop Oct 22, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant