Until today, the only shutdown forecast Voidly published was daily — a single risk number per country-day. That works for the common case (e.g. Iran has 35% risk this week) but throws away an obvious axis of structure: shutdowns happen on specific hours. Election-morning DNS blocks, midnight protest-suppression cuts, a 06:00 UTC fiber-restoration window — the daily forecast collapses 24 hours of inside-day variance into a single bucket.
We trained three within-day XGBoost classifiers, one per horizon:
K = 6, 12, 24 hours. Each predicts P(at least one
censorship-class incident starts in the next K hours) at hourly
granularity. Live at
GET /v1/forecast/{cc}/hourly?horizon=6|12|24.
Fifteen-dimensional, all observable on the country panel without leakage:
90-day evidence × 45 countries × 24 hours = 97,245 cells. Drop cells with no recent evidence (MIN_EVIDENCE_24H ≥ 1) → 75,570 training rows. Country set is the union of every country with a confirmed censorship/mixed incident in the window (36) plus the top-30 by evidence volume.
Positive rates: y_6h 1.3%, y_12h 2.6%, y_24h 5.1%. Imbalanced as expected — most hours, in most countries, are uneventful.
first_seen is itself
often midnight UTC on the day the event landed in our pipeline —
so the y target inherits the same coarseness as the features.
p_low/p_high use a static
±0.15 halfwidth around the calibrated probability. ACI-style
adaptive width is the next step.
Temporal holdout: the most recent 30 days (April 21 → May 21 2026) is held out; everything earlier is training. The model has never seen any country's hourly observations after the cutoff. Per-country AUC is graded only on countries whose holdout has both classes AND where the model produced a non-degenerate ranking (AUC ≠ 0.5 exactly — those ties come from flat predictions on country windows where the model can't separate hours, which is real but not informative for ranking quality).
| Horizon | Global AUC | Median per-country | n countries |
|---|---|---|---|
| K=6h (best) | 0.741 | 0.721 | 11 |
| K=12h | 0.724 | 0.690 | 12 |
| K=24h | 0.801 | 0.667 | 12 |
All three horizons clear the 0.60 per-country AUC floor. The promote rule is intentionally lower than for daily (0.70) because hourly granularity is noisier and the labels themselves are coarser.
Where the model has strong inside-day signal:
Where the model is at or below chance:
The pattern is clear: countries whose shutdowns concentrate at specific hours (likely tied to election windows, prayer time, or protest hours) are predictable. Countries whose shutdowns are spread evenly across the day are not — and the model honestly admits it.
GET /v1/forecast/{cc}/hourly?horizon=6|12|24GET /v1/forecast/hourly/info — full sidecar metrics + honest caveats
Response includes probability,
p_low, p_high,
the top-5 features ranked by gain × live value, and the full
honest_caveats array on every call.
Countries with no recent evidence (MIN_EVIDENCE_24H == 0) get a
404 + an explicit refusal message — the model does NOT score
no-data countries.