Province estimates
This page gives the province estimates from the joint model, whose methods are on the Methods page. The national estimates are the sum of the provinces here. The per-province forecast is on the province forecasts page and its scoring is in the forecast by province.
This page is generated from docs/pages/estimates/province.jl. The model code it calls is in src/. See aim and origins and limitations.
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");Last updated: 23 October 2026.
Data as of: 20 October 2026.
Summary
Compute the weekly cases and reproduction number by province
# The provincial Rt trajectories, rebuilt on the same grid as the
# reproduction number by province figure further down.
province_summary_rt = reconstruct_patch_rt(
chn_joint;
n = obs.n, breakpoint = _BREAKPOINT, n_patches = N_PATCHES,
rt_start = _rt_start_plot,
rt_walk_start = clamp(_BREAKPOINT - RT_WALK_LEAD, _rt_start_plot, obs.n),
ramp = RT_INTERVENTION_RAMP
);
# The week-ahead province forecast, read from the same draws as the
# province forecasts page.
province_summary_forecast = forecast_provinces(
fit_forecast("joint"); horizon = 7, n_patches = N_PATCHES
);
province_cases_rt = cases_rt_table(
province_summary_rt, province_cases.days, province_cases.increments;
cutoff = obs.cutoff, n = obs.n, forecast = province_summary_forecast
);
province_cases_rt_fig = plot_cases_rt(province_cases_rt);
Compute the province comparison
province_headline_md = patch_headline(chn_joint, N_PATCHES)
province_headline = Markdown.parse(province_headline_md);| Province | Share of infections (%) | R at the cut-off | P(R > 1) | CFR (%) | Relative ascertainment |
|---|---|---|---|---|---|
| Ituri | 64–72 | 0.71–1.52 | 60% | 36.0–60.7 | 0.89–1.23 |
| Nord-Kivu | 22–30 | 0.72–1.58 | 64% | 39.1–63.8 | 0.76–1.08 |
| Haut-Uele | 4–5 | 0.6–1.67 | 52% | 36.1–60.4 | 0.89–1.23 |
| Sud-Kivu | 0–0 | 0.49–1.57 | 42% | 36.4–60.8 | 0.76–1.23 |
| Tshopo | 1–1 | 0.78–1.88 | 79% | 36.4–61.0 | 0.85–1.25 |
| Bas-Uele | 0–0 | 0.63–1.71 | 60% | 36.2–61.0 | 0.83–1.3 |
| Sud-Ubangi | 0–0 | 0.62–1.7 | 60% | 36.4–61.0 | 0.76–1.25 |
| Nord-Ubangi | 0–0 | 0.82–2.72 | 86% | 36.5–61.1 | 0.78–1.28 |
Ituri has the most infections with probability over 99%, and 7 of 8 provinces are more likely than not to be growing. Infections imported from another province make up 0.2–2.8% of infections to date.
Maps
Maps of infections, reproduction number and ascertainment
# The infections panel is also drawn alone for the summary page.
province_infections_panel = (;
values = province_map_summary(chn_joint, :C_T_patch, N_PATCHES).values,
title = "Infections to date", scale = log10,
colorbar_label = "Infections (median)",
)
province_map_fig = plot_province_map(
[
province_infections_panel,
(;
province_map_summary(chn_joint, :R_T_patch, N_PATCHES)...,
title = "Reproduction number at the cut-off",
diverging_at = 1.0, colorbar_label = "R (median)",
),
(;
province_map_summary(
chn_joint, :province_ascertainment, N_PATCHES
)...,
title = "Relative case ascertainment", diverging_at = 1.0,
scale = log10, colorbar_label = "Relative to national (median)",
),
]
);
Detail by province
Each province's case-fatality ratio is in the case-fatality ratio by province.
Compute the per-province tables and figure
# The tables below and the pair-plot dropdowns further down name the
# provinces in this order.
@assert PROVINCE_LABELS[1:N_PATCHES] == [
"Ituri", "Nord-Kivu", "Haut-Uele",
"Sud-Kivu", "Tshopo", "Bas-Uele", "Sud-Ubangi", "Nord-Ubangi",
]
province_detail_tables = [
patch_summary_table(chn_joint, N_PATCHES; patch = p) for p in 1:N_PATCHES
];
province_detail_fig = plot_patch_summary(chn_joint, N_PATCHES);
Ituri
| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Cumulative infections | 10008.0 | 12086.0 | 13528.0 | 16272.0 | 18172.0 | 22365.0 |
| 2 | Reproduction number | 0.71 | 0.87 | 0.97 | 1.13 | 1.25 | 1.52 |
| 3 | Daily infections at cut-off | 42.0 | 59.0 | 71.0 | 98.0 | 117.0 | 166.0 |
| 4 | log-Rt deviation from trend | -0.31 | -0.18 | -0.11 | 0.01 | 0.09 | 0.23 |
| 5 | log-Rt vs primary patch | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 6 | Rt deviation innovation sd | 0.053 | 0.071 | 0.082 | 0.104 | 0.12 | 0.154 |
| 7 | Relative case ascertainment | 0.89 | 0.96 | 1.0 | 1.05 | 1.11 | 1.23 |
Nord-Kivu
| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Cumulative infections | 3697.0 | 4510.0 | 5047.0 | 6217.0 | 6943.0 | 8606.0 |
| 2 | Reproduction number | 0.72 | 0.89 | 0.99 | 1.17 | 1.3 | 1.58 |
| 3 | Daily infections at cut-off | 33.0 | 47.0 | 57.0 | 79.0 | 94.0 | 137.0 |
| 4 | log-Rt deviation from trend | -0.32 | -0.15 | -0.07 | 0.04 | 0.11 | 0.26 |
| 5 | log-Rt vs primary patch | -0.38 | -0.16 | -0.05 | 0.12 | 0.23 | 0.42 |
| 6 | Rt deviation innovation sd | 0.047 | 0.066 | 0.078 | 0.105 | 0.126 | 0.16 |
| 7 | Relative case ascertainment | 0.76 | 0.85 | 0.9 | 0.98 | 1.01 | 1.08 |
Haut-Uele
| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Cumulative infections | 627.0 | 769.0 | 872.0 | 1070.0 | 1205.0 | 1498.0 |
| 2 | Reproduction number | 0.6 | 0.78 | 0.9 | 1.15 | 1.31 | 1.67 |
| 3 | Daily infections at cut-off | 4.0 | 5.0 | 7.0 | 10.0 | 13.0 | 20.0 |
| 4 | log-Rt deviation from trend | -0.51 | -0.3 | -0.17 | 0.03 | 0.14 | 0.35 |
| 5 | log-Rt vs primary patch | -0.54 | -0.29 | -0.14 | 0.09 | 0.22 | 0.49 |
| 6 | Rt deviation innovation sd | 0.101 | 0.123 | 0.137 | 0.165 | 0.183 | 0.22 |
| 7 | Relative case ascertainment | 0.89 | 0.97 | 1.0 | 1.06 | 1.1 | 1.23 |
Sud-Kivu
| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Cumulative infections | 2.0 | 4.0 | 5.0 | 8.0 | 10.0 | 16.0 |
| 2 | Reproduction number | 0.49 | 0.69 | 0.82 | 1.06 | 1.23 | 1.57 |
| 3 | Daily infections at cut-off | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 4 | log-Rt deviation from trend | -0.75 | -0.42 | -0.26 | -0.05 | 0.06 | 0.28 |
| 5 | log-Rt vs primary patch | -0.75 | -0.39 | -0.23 | 0.01 | 0.13 | 0.38 |
| 6 | Rt deviation innovation sd | 0.068 | 0.094 | 0.111 | 0.141 | 0.164 | 0.203 |
| 7 | Relative case ascertainment | 0.76 | 0.91 | 0.97 | 1.02 | 1.07 | 1.23 |
Tshopo
| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Cumulative infections | 129.0 | 163.0 | 186.0 | 238.0 | 272.0 | 353.0 |
| 2 | Reproduction number | 0.78 | 0.99 | 1.1 | 1.34 | 1.49 | 1.88 |
| 3 | Daily infections at cut-off | 2.0 | 3.0 | 4.0 | 6.0 | 7.0 | 11.0 |
| 4 | log-Rt deviation from trend | -0.23 | -0.06 | 0.03 | 0.17 | 0.26 | 0.44 |
| 5 | log-Rt vs primary patch | -0.33 | -0.08 | 0.05 | 0.25 | 0.38 | 0.62 |
| 6 | Rt deviation innovation sd | 0.059 | 0.077 | 0.09 | 0.113 | 0.131 | 0.162 |
| 7 | Relative case ascertainment | 0.85 | 0.95 | 0.99 | 1.04 | 1.09 | 1.25 |
Bas-Uele
| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Cumulative infections | 16.0 | 22.0 | 26.0 | 35.0 | 42.0 | 57.0 |
| 2 | Reproduction number | 0.63 | 0.84 | 0.97 | 1.19 | 1.34 | 1.71 |
| 3 | Daily infections at cut-off | 0.0 | 0.0 | 0.0 | 1.0 | 1.0 | 1.0 |
| 4 | log-Rt deviation from trend | -0.45 | -0.21 | -0.1 | 0.06 | 0.14 | 0.33 |
| 5 | log-Rt vs primary patch | -0.53 | -0.23 | -0.09 | 0.13 | 0.26 | 0.52 |
| 6 | Rt deviation innovation sd | 0.057 | 0.078 | 0.092 | 0.12 | 0.138 | 0.177 |
| 7 | Relative case ascertainment | 0.83 | 0.94 | 0.98 | 1.04 | 1.1 | 1.3 |
Sud-Ubangi
| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Cumulative infections | 2.0 | 3.0 | 5.0 | 7.0 | 10.0 | 15.0 |
| 2 | Reproduction number | 0.62 | 0.83 | 0.96 | 1.19 | 1.35 | 1.7 |
| 3 | Daily infections at cut-off | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 4 | log-Rt deviation from trend | -0.46 | -0.22 | -0.11 | 0.06 | 0.15 | 0.34 |
| 5 | log-Rt vs primary patch | -0.55 | -0.23 | -0.09 | 0.13 | 0.26 | 0.53 |
| 6 | Rt deviation innovation sd | 0.055 | 0.075 | 0.089 | 0.119 | 0.138 | 0.174 |
| 7 | Relative case ascertainment | 0.76 | 0.92 | 0.97 | 1.02 | 1.08 | 1.25 |
Nord-Ubangi
| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Cumulative infections | 4.0 | 6.0 | 8.0 | 14.0 | 19.0 | 34.0 |
| 2 | Reproduction number | 0.82 | 1.09 | 1.25 | 1.65 | 1.99 | 2.72 |
| 3 | Daily infections at cut-off | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 2.0 |
| 4 | log-Rt deviation from trend | -0.16 | 0.03 | 0.15 | 0.37 | 0.53 | 0.85 |
| 5 | log-Rt vs primary patch | -0.24 | 0.03 | 0.18 | 0.44 | 0.63 | 1.0 |
| 6 | Rt deviation innovation sd | 0.066 | 0.093 | 0.109 | 0.139 | 0.161 | 0.206 |
| 7 | Relative case ascertainment | 0.78 | 0.92 | 0.97 | 1.02 | 1.09 | 1.28 |
Isolation beds
Province bed table
province_bed_overview = province_bed_table(chn_joint, N_PATCHES);| Row | Province | Beds | Bed demand | Occupied beds | Utilisation (%) | Shortfall |
|---|---|---|---|---|---|---|
| String | String | String | String | String | String | |
| 1 | Ituri | 1074 (705–1553) | 647 (576–722) | 645 (569–720) | 60.3 (40.7–93.6) | 0 (0–0) |
| 2 | Nord-Kivu | 302 (196–439) | 399 (347–450) | 302 (196–403) | 100.0 (88.7–100.0) | 96 (0–217) |
| 3 | Haut-Uele | 147 (95–212) | 72 (53–92) | 71 (53–92) | 49.1 (29.9–80.6) | 0 (0–0) |
| 4 | Sud-Kivu | 28 (17–43) | 24 (17–34) | 23 (16–30) | 88.3 (50.8–100.0) | 0 (0–10) |
| 5 | Tshopo | 31 (20–46) | 22 (16–28) | 21 (16–28) | 69.1 (41.7–100.0) | 0 (0–3) |
| 6 | Bas-Uele | 5 (2–10) | 13 (9–18) | 5 (2–10) | 100.0 (100.0–100.0) | 8 (2–14) |
| 7 | Sud-Ubangi | 9 (4–17) | 1 (0–2) | 1 (0–2) | 9.8 (3.0–31.3) | 0 (0–0) |
| 8 | Nord-Ubangi | 1 (0–9) | 1 (0–3) | 1 (0–2) | 100.0 (13.2–100.0) | 0 (0–1) |
Size and infections
Modelled infections by province
province_infections_fig = plot_infections_patches(
chn_joint;
n = obs.n, seeding = obs.seeding, n_patches = N_PATCHES
);
The provinces are coupled by the gravity kernel in the mixing and importation Methods section. The distances between the patches' population centres run from 191 km between Sud-Ubangi and Nord-Ubangi to 1 220 km between Sud-Kivu and Sud-Ubangi. Ituri is 324 km from Nord-Kivu and 216 km from Haut-Uele. About half of what leaves Nord-Kivu lands in Sud-Kivu, 260 km away and the second most populous of the eight. Nord-Ubangi sends about a third of its exports to Sud-Ubangi, its nearest neighbour.
Importation intensity and imports by province
importation_table = summary_table(
chn_joint, [:importation_epsilon];
digits = 4,
labels = Dict(:importation_epsilon => "Importation intensity")
);
province_imports_fig = plot_imports_patches(
chn_joint;
n = obs.n, seeding = obs.seeding, n_patches = N_PATCHES
);| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Importation intensity | 0.0025 | 0.0061 | 0.0088 | 0.0142 | 0.0183 | 0.0265 |

Point prevalence
Point prevalence is the number of people infected at the cut-off who have not yet died, recovered or been detected as a suspected case. We split it into people infected but not yet symptomatic and people with symptoms who have not yet been detected. Together they are the infections in the community. The point prevalence section of the Methods gives the calculation. The table gives each rate per 100,000 residents and the number of people in the community, as medians with 90% credible intervals.
Compute point prevalence by province
province_prevalence = prevalence_overview(
patch_prevalence(chn_joint, N_PATCHES),
PROVINCE_POPULATIONS[1:N_PATCHES];
labels = PROVINCE_LABELS[1:N_PATCHES], patch = collect(1:N_PATCHES)
);
province_prevalence_fig = plot_prevalence_ranking(
province_prevalence; title = "Point prevalence by province"
);| Province | Population | Infected but not yet symptomatic per 100,000 | Symptomatic, undetected per 100,000 | In the community per 100,000 | Number in the community |
|---|---|---|---|---|---|
| Ituri | 4008000 | 13.1 (7.0–24.1) | 15.1 (5.4–32.9) | 28.4 (13.3–55.7) | 1140 (532–2231) |
| Nord-Kivu | 7574000 | 5.5 (2.9–10.4) | 6.7 (2.7–13.9) | 12.2 (6.0–23.4) | 923 (454–1773) |
| Haut-Uele | 2046000 | 2.6 (1.2–5.7) | 3.1 (0.9–7.0) | 5.8 (2.4–12.4) | 118 (49–253) |
| Tshopo | 2582000 | 1.1 (0.5–2.4) | 1.1 (0.3–2.6) | 2.1 (0.9–5.0) | 55 (23–128) |
| Bas-Uele | 1250000 | 0.2 (0.1–0.6) | 0.2 (0.1–0.7) | 0.5 (0.2–1.3) | 6 (2–17) |
| Nord-Ubangi | 1425000 | 0.1 (0.0–0.8) | 0.1 (0.0–0.6) | 0.2 (0.1–1.4) | 3 (1–19) |
| Sud-Ubangi | 2755000 | 0.0 (0.0–0.1) | 0.0 (0.0–0.1) | 0.0 (0.0–0.2) | 1 (0–4) |
| Sud-Kivu | 6565000 | 0.0 (0.0–0.0) | 0.0 (0.0–0.0) | 0.0 (0.0–0.1) | 1 (0–3) |

A province rate is an average over zones with different levels of transmission. The health-zone estimates give the same rates zone by zone.
Reproduction number by province
The national reproduction number is in reproduction number over time.
Reproduction number by province
province_rt_fig = plot_rt_patches(
chn_joint;
n = obs.n, breakpoint = _BREAKPOINT,
n_patches = N_PATCHES,
rt_start = _rt_start_plot,
rt_walk_start = clamp(_BREAKPOINT - RT_WALK_LEAD, _rt_start_plot, obs.n),
display_start = _rt_start_plot,
as_of_date = string(obs.cutoff), seeding = obs.seeding,
ramp = RT_INTERVENTION_RAMP
);
The spread of the provinces' log-Rt deviations is the spatial diagnostic. Its prior gives a 26% probability that the Ituri to Nord-Kivu ratio moves by more than 25% over the window. With eight patches and five of them carrying little signal, the cross-province correlation is not identified and tracks its prior.
Spatial hyperparameter summary table
# Labels for the spatial hyperparameters, shared by this table and the pair
# plot against their prior below.
spatial_labels = Dict(
:region_sd => "Rt deviation spread",
:region_halflife => "Rt deviation half-life (days)",
:region_corr_primary_secondary => "Ituri-N.Kivu Rt correlation",
:province_ascertainment_sd => "Ascertainment spread",
:importation_epsilon => "Importation intensity",
:importation_destination_sd => "Importation destination spread",
:importation_origin_destination_correlation =>
"Origin-destination correlation",
:importation_flow_sd => "Importation flow spread",
:importation_reciprocity => "Importation reciprocity",
:province_cfr_sd => "Lethality spread (logit)",
:province_death_ascertainment_sd => "Death-confirmation spread"
)
spatial_hyper_table = summary_table(
chn_joint,
[
:region_sd, :region_halflife, :region_corr_primary_secondary,
:province_ascertainment_sd,
];
digits = 3, labels = spatial_labels
);| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Rt deviation spread | 0.007 | 0.033 | 0.065 | 0.137 | 0.187 | 0.294 |
| 2 | Rt deviation half-life (days) | 25.716 | 36.649 | 44.297 | 63.739 | 80.475 | 120.579 |
| 3 | Ituri-N.Kivu Rt correlation | -0.727 | -0.489 | -0.309 | 0.028 | 0.226 | 0.523 |
| 4 | Ascertainment spread | 0.014 | 0.047 | 0.078 | 0.154 | 0.204 | 0.338 |
Case-fatality ratio by province
The case-fatality ratio of each province is set against the national ratios in the confirmed case-fatality ratio. Only the product of a province's lethality and death confirmation is identified, and the province compositions Methods section says how the priors split it.
Province case-fatality spread
province_cfr_spread = summary_table(
chn_joint,
[:province_cfr_sd, :province_death_ascertainment_sd];
digits = 3, labels = spatial_labels
);| Row | Quantity | Lower 90% | Lower 60% | Lower 30% | Upper 30% | Upper 60% | Upper 90% |
|---|---|---|---|---|---|---|---|
| String | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | Lethality spread (logit) | 0.011 | 0.046 | 0.078 | 0.15 | 0.2 | 0.294 |
| 2 | Death-confirmation spread | 0.008 | 0.03 | 0.052 | 0.1 | 0.133 | 0.189 |
Province case-fatality table
confirmed_cfr = delay_corrected_confirmed_cfr(
chn_joint;
obs_confirmed = obs.confirmed_cases,
obs_confirmed_deaths = obs.confirmed_deaths
);
province_cfr = province_cfr_table(
chn_joint, confirmed_cfr;
province_cases = vec(sum(province_cases.increments; dims = 2)),
province_deaths = vec(sum(province_deaths.increments; dims = 2)),
n_patches = N_PATCHES
);| Row | Province | Naive observed confirmed ratio | Delay-corrected confirmed CFR | Structural (infection-based) CFR |
|---|---|---|---|---|
| String | String | String | String | |
| 1 | Ituri | 46.5% | 43.6% (36.5–52.1%) | 48.1% (36.0–60.7%) |
| 2 | Nord-Kivu | 55.9% | 56.4% (47.4–67.9%) | 50.9% (39.1–63.8%) |
| 3 | Haut-Uele | 43.6% | 43.9% (36.2–52.8%) | 48.5% (36.1–60.4%) |
| 4 | Sud-Kivu | 33.3% | 46.9% (34.3–65.3%) | 48.9% (36.4–60.8%) |
| 5 | Tshopo | 35.5% | 45.6% (34.9–58.3%) | 48.7% (36.4–61.0%) |
| 6 | Bas-Uele | 30.8% | 45.6% (32.8–61.2%) | 48.8% (36.2–61.0%) |
| 7 | Sud-Ubangi | 50.0% | 47.4% (34.5–65.2%) | 49.2% (36.4–61.0%) |
| 8 | Nord-Ubangi | 33.3% | 46.7% (33.2–63.8%) | 48.8% (36.5–61.1%) |
Province parameters against their priors
The pair plots below set the province parameters against their prior, drawn from the province model.
Draw from the patch model's prior
prior_patch_chn = patch_prior_draws(obs);Spatial hyperparameter pair plot (prior overlaid)
spatial_pair_fig = plot_pair(
chn_joint,
[
:region_sd, :region_halflife, :region_corr_primary_secondary,
:province_ascertainment_sd, :importation_epsilon,
:importation_destination_sd,
:importation_origin_destination_correlation, :importation_flow_sd,
:importation_reciprocity, :province_cfr_sd,
:province_death_ascertainment_sd,
];
prior = prior_patch_chn, labels = spatial_labels
);
Each province's pair plot sets the reproduction number at the cut-off against the relative case ascertainment, the case-fatality ratio against the relative death confirmation, and the importation intensity against the export weight relative to Ituri. Each pair is identified only as a product, so a ridge between the two is expected and the prior sets the position along it.
Compute the per-province pair plots
province_pair_labels = Dict(
:R_T_patch => "Reproduction number",
:province_ascertainment => "Case ascertainment",
:CFR_patch => "Case-fatality ratio",
:province_death_ascertainment => "Death confirmation",
:importation_epsilon_patch => "Importation intensity",
:export_weight => "Export weight"
)
# Ituri is the export reference, its weight fixed at one, so its pair plot
# leaves the weight out.
province_pair_figs = [
plot_pair(
chn_joint,
[
:R_T_patch, :province_ascertainment,
:CFR_patch, :province_death_ascertainment,
:importation_epsilon_patch,
(p == 1 ? () : (:export_weight,))...,
];
patch = p, prior = prior_patch_chn, labels = province_pair_labels
)
for p in 1:N_PATCHES
];Ituri pair plot (prior overlaid)

Nord-Kivu pair plot (prior overlaid)

Haut-Uele pair plot (prior overlaid)

Sud-Kivu pair plot (prior overlaid)

Tshopo pair plot (prior overlaid)

Bas-Uele pair plot (prior overlaid)

Sud-Ubangi pair plot (prior overlaid)

Nord-Ubangi pair plot (prior overlaid)

Composition checks
Whether the model reproduces each province's observed share of the national total is on the in-sample checks page.
Data currency
The province blocks are the provincial tables of the situation reports: confirmed cases, confirmed deaths and the 24-hour laboratory volumes. A block that stops updating shows its last date before the cut-off rather than a flat series.
Province block currency
# Every province block whose last vintage falls before the cut-off, from
# the shared stream registry rather than a per-page list of dates. The
# grace the national table allows is not applied here: one vintage behind
# is already worth reading.
province_currency = let
status = stream_report_status(obs; stratum = :province)
behind = status[[ismissing(d) || d > 0 for d in status.days_since], :]
isempty(behind) ?
Markdown.parse(
"Every province block reports to the cut-off, $(obs.cutoff)."
) :
MarkdownTable(
DataFrame(
"Block" => behind.label,
"Last reported" => [
ismissing(d) ? "never" : string(d)
for d in behind.last_date
],
"Days before cut-off" => [
ismissing(d) ? "-" : string(d)
for d in behind.days_since
]
)
)
end;Every province block reports to the cut-off, 2026-10-20.
Province fit diagnostics
The national fit diagnostics summarise the same joint fit as a whole, and its breakdown by parameter covers every parameter.
Province-level parameter diagnostics
# The quantities the sections above hold one per province: the
# reproduction number and infections to date from the renewal equation,
# the case-fatality ratio and the two ascertainment ratios the province
# compositions identify, and the importation intensity and export weight
# the per-province pair plots show.
province_diagnostic_quantities = [
:R_T_patch, :C_T_patch, :CFR_patch, :province_ascertainment,
:province_death_ascertainment, :importation_epsilon_patch, :export_weight,
]
province_diagnostics_labels = merge(
province_pair_labels, Dict(:C_T_patch => "Infections to date")
)
province_diagnostics_frame = let df = parameter_diagnostics(chn_joint)
df[in.(Symbol.(df.parameter), Ref(province_diagnostic_quantities)), :]
end
province_diagnostics_table = MarkdownTable(
family_diagnostics_table(
province_diagnostics_frame;
n = length(province_diagnostic_quantities),
labels = province_diagnostics_labels
)
);| parameter | elements | max_rhat | min_ess_bulk | above_1.1 |
|---|---|---|---|---|
| Importation intensity | 8 | 1.006 | 282 | 0 |
| Infections to date | 8 | 1.006 | 554 | 0 |
| Reproduction number | 8 | 1.006 | 606 | 0 |
| Case-fatality ratio | 8 | 1.009 | 666 | 0 |
| Death confirmation | 8 | 1.003 | 887 | 0 |
| Case ascertainment | 8 | 1.003 | 958 | 0 |
| Export weight | 7 | 1.001 | 1586 | 0 |
Saving province assets
The summary dashboard shows the province comparison, the province map, the reproduction number by province and the weekly cases against the reproduction number, so they are written here.
Write the province dashboard assets
dashboard_dir = joinpath(
pkgdir(BVDOutbreakSize), "docs", "src", "summary_assets"
)
mkpath(dashboard_dir)
CairoMakie.save(joinpath(dashboard_dir, "rt_provinces.png"), province_rt_fig)
# The summary's cases against R figure is drawn at the page width with
# larger labels.
CairoMakie.save(
joinpath(dashboard_dir, "cases_rt_provinces.png"),
plot_cases_rt(province_cases_rt; size = (680, 560), fontsize = 13)
)
# The summary's province map: infections to date alone.
CairoMakie.save(
joinpath(dashboard_dir, "province_map.png"),
plot_province_map(
[province_infections_panel]; ncols = 1, panel_size = (680, 500)
)
)
open(joinpath(dashboard_dir, "provinces.md"), "w") do io
print(io, province_headline_md)
end
# The summary's compact province table: infections to date, R at the
# cut-off and the confirmed cases over the latest week of reports, the
# same week the cases against R figure ends on.
province_recent_cases = let last_day = min(last(province_cases.days), obs.n)
weekly_count_table(
province_cases.days, province_cases.increments[1:N_PATCHES, :],
PROVINCE_LABELS[1:N_PATCHES];
cutoff = obs.cutoff - Day(obs.n - last_day), n = last_day, weeks = 1
).count
end
open(joinpath(dashboard_dir, "provinces_summary.md"), "w") do io
print(
io,
province_summary_table(
chn_joint, N_PATCHES; recent_cases = province_recent_cases
)
)
endThe full analysis code, data and model definitions are in the epiforecasts/BVDOutbreakSize repository.