Detects when two or more distinct conflict sub-types converge in the same district × ISO-week on ACLED event data, then routes structured alerts through a hardened multi-channel pipeline.
Open-source. Reproducible. Built from open data. Not a forecast. A signal.
{
"geo_key": "BF-Sahel-Soum",
"admin2": "Soum",
"admin1": "Sahel",
"country": "Burkina Faso",
"region": "Central Sahel",
"composite_types": 5,
"baseline_multiplier": 4.2,
"surprise_score": 0.87,
"empirical_probability": 0.018,
"is_novel": true,
"severity": "critical",
"function_tag": "CONCURRENT",
"detected_week": "2026-W16",
"status": "active",
"expires_at": "2026-04-26T00:00:00Z"
}
TREMOR does not predict violence. It detects a specific, historically rare pattern: the convergence of two or more conflict sub-types in a single district-week. It measures how reliably that pattern precedes next-week escalation.
attacksi,t+1 = exp( αi + γct
+ β · compositei,t
+ δ · L.non_outcomei,t )
αi district fixed effects
γct country × year-quarter fixed effects
composite 1 if ≥2 event sub-types in (district × ISO-week)
L.non_outcome lagged count of non-outcome events (t-1)
SE clustering district (geo_key)
Outcome is next week’s attack count, giving a T+1 ISO-week evaluation horizon. Fixed effects absorb time-invariant district characteristics and country-specific seasonality. District-clustered SEs correct for the serial correlation standard in conflict count data.
A district-week fires when ≥2 distinct ACLED event sub-types are observed in the same admin2 × ISO-week cell. Severity scales with three signals on the alert payload:
A spot-check at the minimum composite (N=2 sub-types) reproduces an 8.8× lift in next-week attack incidence versus district-weeks without composite co-occurrence. This is one point on the detection-performance curve, not a headline statistic, and is reported with the honest limitations below.
Counts pending identification-robustness tab (Tab 9 of the workbench). Lift 8.8× corresponds to TP / (TP+FN) relative to base rate in no-composite cells.
Every alert enters a structured five-step workflow from dispatch to dossier. No orphan alerts; every decision is captured in the audit log. The workflow is shipped in the TREMOR dashboard.
Alert routes to on-duty operator. Timeout ladder: primary → secondary → escalation. No orphans.
Operator confirms which programmes, assets, or staff intersect the flagged district-week.
Structured capture of what happened: verified incidents, no incidents, access change, de-escalation. Ground truth generator.
Structured capture of operator action: paused activity, rerouted logistics, issued sitrep, no action (with rationale).
Longitudinal record per district, joining alert → exposure → outcome → response. Handover state flips ownership on operator rotation.
The workflow is not an add-on. It closes the loop between detection and action, and it generates the labelled outcome data needed to evaluate the detector itself over time.
Five Supabase edge functions dispatch alerts across redundant channels. SMS via Africa’s Talking is the durability tier; push via ntfy.sh is best-effort; USSD is infrastructure-ready pending per-country MNO agreements. A dead-man-switch treats pipeline silence as an outage.
alert-processor
Shipped
Dispatches per-user with severity threshold filtering. Enforces a 20/hr global rate limit.
Known: global rate-limit keying; per-sender keying is P2.
push-sender
Shipped
Push notifications via ntfy.sh with priority tags and click-through to dashboard.
Tier: best-effort. No SLA. SMS is the durability fallback.
ussd-handler
Sandbox-validated
Serves alerts over USSD menus for feature phones and low-bandwidth field staff.
Live dispatch contingent on per-country MNO aggregator agreements (shortcode + regulator).
dead-man-switch
Shipped
Heartbeat: pipeline ETL completion. Timeout: 6 hours. Destination: SMS to maintainer.
Escalation: ntfy priority-5 if SMS fails. Silence = failure.
send-test-alert
Shipped
Reproducible test harness: inserts canonical alert + dispatches + writes to audit_log.
No mocks. Real insert, real dispatch, real audit trail.
All dispatch outcomes (success, failure, partial) are recorded in notification_log with per-channel status. The audit_log table is append-only and mirrors every dispatch, form submission, and decision.
ACLED API
↓ weekly pull · ISO-week cadence
pipeline.py
↓ admin-2 boundary join
↓ Poisson PPML + composite-type detector
Parquet / CSV staging
↓
Supabase (Postgres + RLS)
tables: alerts, notification_log, audit_log
↓
5 edge functions
↓ ↓ ↓
ntfy.sh Africa’s USSD
(push) Talking (sandbox)
(SMS)
↓
Dashboard (React + i18n, 9 stations)
admin-2 × ISO-week resolution. ACLED geocoding is consistent at admin-2; ISO-week matches the ACLED update cycle and avoids day-of-week noise.
Named RLS policies. alerts SELECT is scoped by country ∈ user.countries; all writes are via edge functions using SERVICE_ROLE_KEY; audit_log is append-only.
First-class audit. audit_log and notification_log are first-class tables, not side effects.
alerts SELECT scoped by country ∈ user.countries. Writes via edge functions using service role. audit_log append-only.audit_log. Append-only, retained for the operating lifetime of the deployment.CONTRIBUTING.md.