{"cenalerts_event_type": "calibration_drift", "description": "Daily per-country calibration drift monitor for the 7-day forecast. For each of the top-N most-active countries, joins logged predictions against rebuilt (censorship/mixed only) observed labels and computes mean predicted vs empirical positive rate over a rolling 30-day window.", "drift_threshold_pp": 15.0, "endpoint": "/v1/sentinel/calibration-drift", "honest_caveats": ["Per-country windows are narrow. A country with fewer than 15 predictions in the 30-day window gets status='insufficient_data' and drift_alert=null.", "High-frequency-flip countries (VE, MM) produce noisy drift estimates \u2014 abs_drift_pp tends to overstate true drift when the country is bouncing between blocked and unblocked.", "Drift > 0 \u2260 broken model. A real distribution shift in the country (election, mass protest, fresh shutdown campaign) will legitimately have empirical_pos_rate > mean_predicted_prob until the next weekly retrain catches up.", "Observed labels are rebuilt from voidly_data.incidents using incident_type IN ('censorship','mixed') \u2014 matching the forecast model's post-2026-05-21 disruption-exclusion rule. The legacy sentinel_outcomes.observed column is NOT used.", "Calibrator is applied with the same WATCHED_FOR_CALIBRATION + CALIBRATOR_FLOOR gate as the live API, so this is the literal probability that would have been served at request time."], "min_predictions_for_alert": 15, "model_id": "forecast-v1-7day", "n_countries": 30, "n_drifting": 27, "n_insufficient_data": 0, "n_ok": 3, "per_country_endpoint": "/v1/sentinel/calibration-drift/{cc}", "refresh": "daily at 05:00 UTC via cron", "sidecar_last_modified": "2026-10-04T05:00:15Z", "sidecar_path": "[internal path]", "sidecar_size_bytes": 16554, "window_days": 30}