Province forecasts
This page gives the one-week-ahead forecast for each province from the joint model. The projection is defined in the province forecast Methods section. The national forecast is on the forecasts page. How these forecasts have scored against what each province went on to report is in the forecast by province evaluation. How well the model reproduces each province's share is on the in-sample checks page.
Load packages, data and fitted chains
# Shared setup: packages, observations and the fit registry. See
# `docs/pages/_setup.jl`.
using BVDOutbreakSize
include(joinpath(pkgdir(BVDOutbreakSize), "docs", "pages", "_setup.jl"))validation_forecast_from (generic function with 1 method)# The fits this page reads, loaded from the cache here.
chn_joint = load_fit("joint");Summary
The table gives each province's forecast for the week to
Project each province a week ahead
# Read from the same draws as the national forecast page, so the provinces
# add up to the national forecast shown there.
province_draws = fit_forecast("joint");
province_projection = forecast_provinces(
province_draws; horizon = 7, n_patches = N_PATCHES
);
province_forecast = province_forecast_table(
province_draws, province_projection;
n_patches = N_PATCHES
);
province_forecast_fig = plot_province_forecast(
province_draws, province_projection;
n_patches = N_PATCHES
);
province_week_end = obs.cutoff + Day(7);
province_forecast_md = province_forecast_headline(
province_projection; n_patches = N_PATCHES
);| Province | New confirmed cases | New confirmed deaths | Patients in isolation | Isolation beds | New admissions | New infections | R at T+7 | P(R > 1) |
|---|---|---|---|---|---|---|---|---|
| Ituri | 164–433 | 66–201 | 299–553 | 1015–1247 | 374–663 | 189–946 | 0.5–1.37 | 31% |
| Nord-Kivu | 85–282 | 54–186 | 188–354 | 354–404 | 240–432 | 111–751 | 0.44–1.45 | 29% |
| Haut-Uele | 8–70 | 1–37 | 30–70 | 138–170 | 39–89 | 17–156 | 0.47–1.72 | 41% |
| Other provinces | 2–31 | 0–15 | 19–30 | 28–32 | 23–36 | 8–97 | 0.59–2.01 | 64% |
Most new confirmed cases: Ituri, with probability 88%.
Most new confirmed deaths: Ituri, with probability 60%.
Growing at T+7: 1 of 4 provinces are more likely than not to be growing (Other provinces).
Beds full at T+7: no province is more likely than not to be at or above its beds.
One-week-ahead forecast by province
Each panel is one forecast target, the provinces side by side, for the week to province_week_end.

The maps shade each province by its patch's forecast, so the pooled patch shades all its provinces alike. The reproduction number is centred on one, and a province whose 90% predictive interval spans one is washed out.
Build the forecast maps
province_forecast_draws(col) = [
float.(province_projection[province_projection.patch .== p, col])
for p in 1:N_PATCHES
]
province_forecast_map = plot_province_map(
filter(
!isnothing,
[
(;
province_map_summary(province_forecast_draws(:rt_forecast))...,
title = "Reproduction number at T+7", diverging_at = 1.0,
colorbar_label = "R (median)",
),
(;
values = province_map_summary(
province_forecast_draws(:confirmed_new)
).values,
title = "New confirmed cases by T+7",
scale = CairoMakie.Makie.pseudolog10,
colorbar_label = "Confirmed cases (median)",
),
:isolation_level in propertynames(province_projection) ? (;
values = province_map_summary(
province_forecast_draws(:isolation_level)
).values,
title = "Patients in isolation at T+7",
scale = CairoMakie.Makie.pseudolog10,
colorbar_label = "Patients (median)",
) : nothing,
]
)
);
Forecast summary table by province
| Province | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| Ituri | New confirmed cases by T+7 | 164 | 217 | 250 | 314 | 352 | 433 |
| Ituri | New confirmed deaths by T+7 | 66 | 92 | 110 | 142 | 164 | 201 |
| Ituri | Patients in isolation at T+7 | 299 | 348 | 378 | 435 | 475 | 553 |
| Ituri | Isolation beds at T+7 | 1015 | 1015 | 1030 | 1091 | 1140 | 1247 |
| Ituri | New admissions by T+7 | 374 | 431 | 464 | 533 | 577 | 663 |
| Ituri | New infections by T+7 | 189 | 283 | 357 | 515 | 625 | 946 |
| Ituri | Reproduction number at T+7 | 0.5 | 0.66 | 0.77 | 0.97 | 1.09 | 1.37 |
| Nord-Kivu | New confirmed cases by T+7 | 85 | 124 | 146 | 188 | 216 | 282 |
| Nord-Kivu | New confirmed deaths by T+7 | 54 | 77 | 94 | 124 | 142 | 186 |
| Nord-Kivu | Patients in isolation at T+7 | 188 | 222 | 243 | 282 | 307 | 354 |
| Nord-Kivu | Isolation beds at T+7 | 354 | 354 | 354 | 357 | 372 | 404 |
| Nord-Kivu | New admissions by T+7 | 240 | 277 | 301 | 347 | 375 | 432 |
| Nord-Kivu | New infections by T+7 | 111 | 182 | 241 | 374 | 476 | 751 |
| Nord-Kivu | Reproduction number at T+7 | 0.44 | 0.62 | 0.74 | 0.96 | 1.09 | 1.45 |
| Haut-Uele | New confirmed cases by T+7 | 8 | 16 | 22 | 36 | 46 | 70 |
| Haut-Uele | New confirmed deaths by T+7 | 1 | 5 | 7 | 15 | 21 | 37 |
| Haut-Uele | Patients in isolation at T+7 | 30 | 38 | 43 | 52 | 58 | 70 |
| Haut-Uele | Isolation beds at T+7 | 138 | 138 | 141 | 150 | 156 | 170 |
| Haut-Uele | New admissions by T+7 | 39 | 47 | 53 | 64 | 72 | 89 |
| Haut-Uele | New infections by T+7 | 17 | 30 | 41 | 65 | 89 | 156 |
| Haut-Uele | Reproduction number at T+7 | 0.47 | 0.66 | 0.78 | 1.05 | 1.23 | 1.72 |
| Other provinces | New confirmed cases by T+7 | 2 | 5 | 8 | 14 | 20 | 31 |
| Other provinces | New confirmed deaths by T+7 | 0 | 1 | 2 | 5 | 8 | 15 |
| Other provinces | Patients in isolation at T+7 | 19 | 23 | 26 | 28 | 28 | 30 |
| Other provinces | Isolation beds at T+7 | 28 | 28 | 28 | 28 | 29 | 32 |
| Other provinces | New admissions by T+7 | 23 | 27 | 28 | 31 | 33 | 36 |
| Other provinces | New infections by T+7 | 8 | 15 | 20 | 35 | 51 | 97 |
| Other provinces | Reproduction number at T+7 | 0.59 | 0.83 | 0.99 | 1.29 | 1.51 | 2.01 |
Forecast for each province
Each province has its own forecast figure, one histogram per target with its 90% predictive interval shaded. The dashed rule on the case and death panels is the count the province reported over its most recent week in the spatial tables.
Build the per-province figures
# The spatial tables' most recent week in each province, clamped as the
# compositions are, so a downward revision reads as no new cases.
recent_cases = province_recent_counts(
obs.province_confirmed_history, PROVINCE_NAMES, N_PATCHES
)
recent_deaths = province_recent_counts(
obs.province_death_history, PROVINCE_NAMES, N_PATCHES
)
function forecast_province_observed(p)
o = (;)
recent_cases === nothing ||
(o = merge(o, (; confirmed_new = recent_cases.counts[p])))
recent_deaths === nothing ||
(o = merge(o, (; confirmed_deaths_new = recent_deaths.counts[p])))
return o
end
forecast_province_fig(p) = plot_province_forecast_detail(
province_draws, province_projection;
province = p, n_patches = N_PATCHES,
observed = forecast_province_observed(p)
)
# The province blocks below are written out one per patch.
@assert N_PATCHES == 4 && PROVINCE_LABELS[1:4] ==
["Ituri", "Nord-Kivu", "Haut-Uele", "Other provinces"]
# Cases and deaths come from separate tables, so each stream's observed
# week is stated on its own.
function forecast_province_recent_week(r, stream)
r === nothing &&
return "The spatial tables carry no recent week of $(stream)."
return string(
"The observed week of $(stream) runs from ",
grid_date(r.start_day), " to ", grid_date(r.last_day), "."
)
end
recent_note = join(
[
forecast_province_recent_week(recent_cases, "confirmed cases"),
forecast_province_recent_week(recent_deaths, "confirmed deaths"),
"Each ends at its last spatial vintage, which can be earlier than " *
"the cut-off.",
], " "
);The observed week of confirmed cases runs from 2026-09-19 to 2026-09-26. The observed week of confirmed deaths runs from 2026-09-19 to 2026-09-26. Each ends at its last spatial vintage, which can be earlier than the cut-off.
Ituri

Nord-Kivu

Haut-Uele

Other provinces
The other provinces are pooled into one patch, so their forecast is for the pool.

Past forecasts against what was observed
Only province forecast projections are shown, so the figure fills in as releases accumulate. Each panel is one province stream at one horizon. The x-axis is the cut-off each forecast was made from. Each forecast shows its median and 90% predictive interval, beside the persistence baseline and the count the province went on to report. A window holding a harmonisation-break day is left out, because that day's backfill is published for the country and not by province.
Load the archived province forecasts and their outcomes
# Written by `scripts/score_releases.jl` from each release's
# `province_forecast.csv`, in the national overlay's schema. A missing
# file reads as an empty table, which the figure reports as nothing scored.
province_overlay_df = _release_data(
joinpath("province", "forecast_overlay.csv"),
(;
release = String, made_date = Date, stream = String, horizon = Int,
target_date = Date, fit = String, observed = Float64,
median = Float64, lo30 = Float64, hi30 = Float64, lo60 = Float64,
hi60 = Float64, lo90 = Float64, hi90 = Float64,
)
)
province_overlay_fig = plot_forecast_overlay(
scored_overlay(province_overlay_df);
empty_message = "No projection forecast has been scored yet. " *
"This fills in as releases accumulate."
);
The full analysis code, data and model definitions are in the epiforecasts/BVDOutbreakSize repository.