Skip to content

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
julia
# 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)
julia
# 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   as a 90% predictive interval, and the bullets under it compare the provinces draw by draw. The provinces add up to the national forecast.

Project each province a week ahead
julia
# 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
);
ProvinceNew confirmed casesNew confirmed deathsPatients in isolationIsolation bedsNew admissionsNew infectionsR at T+7P(R > 1)
Ituri164–43366–201299–5531015–1247374–663189–9460.5–1.3731%
Nord-Kivu85–28254–186188–354354–404240–432111–7510.44–1.4529%
Haut-Uele8–701–3730–70138–17039–8917–1560.47–1.7241%
Other provinces2–310–1519–3028–3223–368–970.59–2.0164%
  • 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
julia
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
ProvinceQuantityLower 90%Lower 60%Lower 30%Upper 30%Upper 60%Upper 90%
IturiNew confirmed cases by T+7164217250314352433
IturiNew confirmed deaths by T+76692110142164201
IturiPatients in isolation at T+7299348378435475553
IturiIsolation beds at T+7101510151030109111401247
IturiNew admissions by T+7374431464533577663
IturiNew infections by T+7189283357515625946
IturiReproduction number at T+70.50.660.770.971.091.37
Nord-KivuNew confirmed cases by T+785124146188216282
Nord-KivuNew confirmed deaths by T+7547794124142186
Nord-KivuPatients in isolation at T+7188222243282307354
Nord-KivuIsolation beds at T+7354354354357372404
Nord-KivuNew admissions by T+7240277301347375432
Nord-KivuNew infections by T+7111182241374476751
Nord-KivuReproduction number at T+70.440.620.740.961.091.45
Haut-UeleNew confirmed cases by T+781622364670
Haut-UeleNew confirmed deaths by T+7157152137
Haut-UelePatients in isolation at T+7303843525870
Haut-UeleIsolation beds at T+7138138141150156170
Haut-UeleNew admissions by T+7394753647289
Haut-UeleNew infections by T+71730416589156
Haut-UeleReproduction number at T+70.470.660.781.051.231.72
Other provincesNew confirmed cases by T+7258142031
Other provincesNew confirmed deaths by T+70125815
Other provincesPatients in isolation at T+7192326282830
Other provincesIsolation beds at T+7282828282932
Other provincesNew admissions by T+7232728313336
Other provincesNew infections by T+781520355197
Other provincesReproduction number at T+70.590.830.991.291.512.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
julia
# 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
julia
# 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.