Ebola DRC 2026 · WASH Cluster Dashboard

Importation-risk model — the R dashboard explained

How the dashboard decides which health zones are most likely to receive the virus next from the zones that already have it. Risk travels one step — from each outbreak zone to its direct destinations — and every value is a prioritisation ranking to guide surveillance, not a prediction or a probability of infection.

Updated 31 August 2026 — validation figures from the panel rebuilt that day, covering 15 May to 31 August. The main change since the last revision: risk now also spreads one step to each outbreak zone's immediate neighbours, not only along recorded travel (see §1 step 4 and §2). Two corrections are also folded in — outbreak zones that had been silently spreading no risk at all (§2), and a case-counting error that affected rankings between 17 July and 25 August (§4).

What this page covers

This is the model shown on the R WASH-Cluster dashboard (output/drc_ebola_dashboard.html) and only that model. It is single-hop: risk flows exactly one step from each outbreak zone to the zones people travel to directly. Read it once and every colour, tier, and popup number on the map should make sense.

1How it works — six plain steps

The model estimates incoming risk for every zone, one outbreak zone at a time, then ranks the receivers.

  1. Seeds — where the risk starts. The starting points are the zones currently reporting confirmed cases (the MoH Cat A foyers). Zones with more recent cases carry more weight — and each case's weight fades smoothly over the following days (the ~5-day infectious period), so influence tapers off gradually instead of counting flat and then dropping off a cliff at the window edge. An active, growing outbreak pushes more risk outward than a quiet one.
  2. Mobility — how people actually move. From each outbreak zone we follow real population-movement data (Flowminder/Vodacom) to see where people travel. Risk flows along the routes people actually use, not just to neighbours on a map.
  3. Gravity — a fallback when we have no movement data. A handful of outbreak zones aren't covered by the mobility data. Where a second Flowminder release (April 2026) does cover them we use its figures; for the rest we fall back to a sensible estimate: people tend to move toward larger population centres and toward places that are easier to reach (shorter travel time). This keeps every outbreak zone contributing instead of leaving a blind spot.
  4. Neighbours — spread that never shows up as travel. Mobility data records journeys between zones, but an outbreak also creeps across a shared border through everyday local contact that no phone-movement figure captures. So 20% of each outbreak zone's risk goes to the zones directly bordering it regardless of recorded travel, split between them in proportion to population. The other 80% still follows the movement data. Added August 2026, after the watchlist began missing small rural zones with almost no recorded travel that sat next door to an outbreak (see §4).
  5. Single-hop — one step at a time. We look one step out from each outbreak zone — direct destinations only. We do not chain risk several jumps ahead, because each extra hop adds more uncertainty than signal (confirmed by walk-forward backtesting).
  6. Tiers — turning it into a watchlist. Every receiving zone is ranked by how much incoming risk it accumulates, then grouped into a colour-coded watchlist (see §3).

2The mechanics, precisely

For the technically minded. Implemented in scripts/01_prepare_dashboard.R · R/seed_weights.R · R/gravity.R.

Seed weight for each Cat A origin i — the confirmed cases it accrued inside the lookback window (7, 14 or 21 days), recency-weighted so recent days count more, then square-root damped so a single dominant hotspot doesn't swamp everything:

wi = √( Σd ∈ window new_casesi(d) · e−age(d)/5 )

age(d) = days before the latest SitRep; the 5-day decay length is the Ebola infectious period. A case today counts fully, one 5 days ago ~0.37, one 14 days ago ~0.06. (Setting the decay to infinity recovers the older flat "every day counts equally" window exactly.)

Why the graded recency decay (adopted July 2026)

Earlier the window counted every day inside it equally, then dropped a case's weight to zero the moment it crossed the window edge — a hard cliff that could read as a real de-escalation when a single old case simply "aged out" of the window. Weighting each day by e−age/5 lets a case fade smoothly over the infectious period instead, removing that artefact. A walk-forward backtest confirmed it never lowers the watchlist hit-rate (14-day top-20: 82.9% → 85.4% as measured on the panel available in July 2026 — not comparable with the headline figure in §4, which covers a longer panel and more zones) and flags some zones slightly earlier. It changes only how loudly each outbreak zone contributes, not where its risk flows.

Each origin becomes a destination distribution (its row sums to 1), so Flowminder rows and gravity rows live on the same scale and combine cleanly:

covered origin   Pi = normalise( Flowminder outflow rowi )
dangling origin  Pi = normalise( gravity rowi ),   gravij ∝ popj / ttij2

Currently four Cat A origins fall back to gravity because they have no Flowminder outflow row: Kilo, Mangala, Miti-Murhesa, Vuhovi. An origin counts as covered on its row's mass, not on the row merely existing.

Correction — 31 August 2026: some outbreak zones were spreading no risk at all

The mobility file carries a row for every zone. For 24 of them that row is entirely zeros: no travel recorded anywhere. The model asked "does this zone have a row?" rather than "does this row contain anything?", so those zones counted as covered, never reached the gravity fallback, and then had every one of their (zero) connections discarded. An outbreak zone in that state contributed nothing to the risk of any other zone, and nothing in the output said so.

What it changed in practice. Very little so far, which is why it went unnoticed. Across the whole outbreak only two such zones have ever had confirmed cases: Biena and Manguredjipa, both from 26 August 2026, one case each, together about 1.6% of the weight driving the model. The rankings you have been reading are not materially affected.

Why it was worth fixing anyway. Those 24 zones hold 5.65 million people and include Katoyi, Ibanda, Kadutu, Masisi, Birambizo and Kabare, mostly around Bukavu and western North Kivu. Had the outbreak reached one of them, a major source of onward risk would have gone quiet with no error and no warning.

Two things changed. Coverage is now judged on whether a row carries any travel at all. And where the April 2026 Flowminder release has real figures for a zone the March file left empty, those figures are used instead of an estimate: that fills 21 of the 24, the remaining three falling back to gravity as before. The dashboard now also reports, on every build, how each outbreak zone's movement was sourced.

Contiguity blend (adopted 31 August 2026) — a mobility-covered origin's distribution is mixed with an even-handed neighbour distribution before it is used:

Pi = (1 − α) · Piflow  +  α · Pineighbours,   α = 0.20

Neighbours are the zones sharing a border (the same 100 m tolerance that defines the MoH limitrophe Cat B ring, so the model and the published category agree on what "neighbour" means), and α is split between them in proportion to population. Both halves sum to 1 across an origin, so the blend does too — this redistributes an origin's risk, it does not add any. Neighbours with no mobility data at all enter here as new destinations, which is half the point. Gravity-fallback origins are left alone.

Importation score for a receiving zone j — sum the weighted one-step contributions from every outbreak origin:

importationj = Σi wi · Pi[j]
Why single-hop is a deliberate choice, not a shortcut

The score is exactly one matrix step from seeds — there is no iteration, damping factor, or teleport vector. Walk-forward backtesting found that chaining risk through additional hops added essentially zero ranking value for importation here (most newly affected zones turn out to be one hop from a seed), while single-hop is simpler to explain and audit. The gravity destination mass uses INS 2026 population estimates for the 104 mappable eastern-province zones, WorldPop elsewhere.

Caveat — the July 2026 shapefile migration

Upstream redrew the health-zone polygons in July 2026. The gravity terms (popj and the travel-time distances) are not directly comparable before and after the migration for zones whose footprint changed a lot — notably the Cat A zones Miti Murhesa and Makiso Kisangani. This affects the four gravity-fallback origins only; Flowminder-covered origins are unchanged.

3Reading the watchlist tiers

Receivers are ranked by incoming Cat A flow, then banded. Bands are rank-based, not equal-volume cuts — so the map colour matches the rank in the popup.

TierRuleOperational read
ÉlevéRank 1–20The active watchlist — prioritise for pre-positioning & surveillance.
MoyenRank 21–50Moderate incoming flow — monitor.
FaibleRank 51 → the 97.5% coverage cutoffLow but notable — on the radar.
Non significatifThe remaining low-inflow tailNegligible flow (greyed). Stops gravity's tiny everywhere-flows from flooding "Faible".
Cat A zones, the rank, and the window switch

4How we know it works — validation

The dashboard carries its own longitudinal check, independent of any other model. Implemented in R/importation_history.R.

Every time the case totals change, the dashboard snapshots the whole watchlist to output/importation_history.csv. That lets us ask, in hindsight, a simple question: when a zone eventually became a foyer, had the watchlist flagged it first?

MetricLatest readingWhat it means
14-day top-20 hit-rate48 / 57 (84%)Of the zones that became foyers and could have been flagged beforehand, 84% were already on the Élevé watchlist first.
Lead timemedian 13.5 days
(range 1–89)
How far ahead of confirmation the zone first entered the top-20 — days to pre-position.
14-day top-50 hit-rate57 / 57 (100%)Every zone that became a foyer had appeared somewhere in the top 50 beforehand.

Panel of 76 SitRep snapshots, 15 May – 31 August 2026; 60 zones have become foyers over that period.

An honest denominator

Some zones were already foyers the first time the panel ever saw them (Goma, Miti Murhesa, Rwampara) — there was no earlier window in which the watchlist could have flagged them, so they are excluded from the ratio (they stay listed, greyed, with a "—" lead time). Counting them as misses would understate the model: the honest figure is 48/57 (84%), not 48/60 (80%).

Why the neighbour term was added — 31 August 2026

Through August the watchlist started missing zones it would have caught earlier in the outbreak. Grouped by the month a zone became a foyer, the hit-rate ran 15/18 in May, 13/14 in June, 10/13 in July, then 6/11 in August.

The tempting explanation was that the mobility data is ageing — it dates from March 2026. We tested that and it is not the cause. Re-running the whole history against a fresher April matrix changed nothing that mattered, and 12 zones were missed by both. What predicts a miss is not when a zone was infected but how little recorded travel it has: the August misses carry roughly a seventh of the mobility volume of the August catches, and five of the six bordered an already-infected zone.

In other words the outbreak had shifted from spreading along major travel routes, which the mobility data sees well, to creeping into small rural zones next door, which it barely sees at all. Sending 20% of each outbreak zone's risk to its neighbours addressed exactly that. On the full history it lifted the 14-day top-20 rate from 44/56 (79%) to 48/57 (84%), newly catching Adja, Biena, Kilo, Manguredjipa and Viadana while losing only Butembo.

The trade-off, stated plainly. Median lead time falls from 17 days to 13.5. The watchlist now catches more of the zones that were invisible to it, but on average flags them somewhat later. We judged the coverage worth the days; if your planning depends on the longest possible lead time, that is the number to watch.

Honest limitation: the 20% share was chosen by testing several values against this same outbreak's history, so it is tuned to the data it is measured on. Every value we tried (10%, 20%, 30%, 50%) beat having no neighbour term at all, at every window length, so the direction is solid; the precise 20% is a reasonable pick rather than a proven optimum.

Correction — 25 August 2026: a counting error, now fixed

A bug in how the model counted "new cases in the window" was found and corrected on 25 August 2026. If you used the watchlist between 17 July and 25 August, a few zones were ranked too high. Here is what happened, in plain terms.

To work out how many new cases a zone had in the last 14 days, the model subtracts two running totals: the total today, minus the total just before the window opened. To find that second number it looked for a SitRep entry dated exactly on the day before the window started. When it found one, all was well. When it did not, it assumed the zone's total had been zero — as if the zone had never had a single case — and counted the zone's entire case history as new.

It is the same mistake as working out this month's spending by subtracting last month's closing bank balance, then — when you cannot find a statement dated exactly the 31st — assuming your balance was zero and booking your whole life savings as this month's spending.

This happened often because the SitRep is not published every day. The day before the window opens usually falls on a date with no publication at all, so there was no entry to find.

What it changed, and what it did not. Because nearly every outbreak zone was over-counted at the same time, and nearly all of them sit in Ituri, the overall order of the watchlist mostly survived — the walk-forward track record was unchanged by this fix (43/53 at 14-day top-20 before and after, on the panel as it then stood; the 48/57 in §4 is the later panel, rebuilt when the neighbour term was adopted). It broke down only for receiving zones whose incoming risk depended on one or two stale outbreak zones rather than on the broad Ituri flow. Three zones moved sharply once corrected:

ZoneRank beforeRank after
Laybo433
Ibanda1848
Kadutu2055
Jiba2117
Linga2318
Faradje2520

Laybo is the one to note. Most of its incoming risk came from Adja, a zone whose case total had sat unchanged at 11 for a month — no new cases at all. Because Adja had no SitRep entry on the anchor day, the model read those 11 old cases as 11 brand-new ones and pushed that phantom risk down the road to Laybo. Corrected, Laybo's incoming flow falls by a factor of about 25, and it leaves the watchlist. If Laybo was prioritised on this basis, that decision rested on a miscount. Jiba, Linga and Faradje take its place and are worth a look.

Both affected code paths are fixed, and the fix is covered by tests that reproduce exactly this situation (a zone with cases before the window and no entry on the anchor day), so it cannot come back silently. The whole history panel was rebuilt from scratch — every past snapshot recalculated — rather than only new ones being added on top.

5Where the ideas come from

The model adapts established methods from the human-mobility and epidemic-modelling literature. The load-bearing reference is Bengtsson et al. (2015).

SourceIdea taken into the R model
Bengtsson et al. (2015)
Sci. Reports 5:8923 — Haiti cholera
The core score. Their infectious pressure — risk ∝ cases in a source area × mobility to a destination — is exactly importationj = Σ wi·Pi[j]. Also their headline finding (observed mobility beats gravity and needs no retrospective tuning) is why we use Flowminder first and gravity only as a fallback.
Truscott & Ferguson (2012)
PLoS Comput. Biol.
The gravity fallback (popj/ttij2) for zones with no movement data — and its known caveat that plain gravity under-weights rare long-range travel between big centres.
Matson et al. (2020)
Emerg. Microbes & Infect.
Why recent cases drive the seed weights: detection delay (median ~44 days for Ebola) means a stale seed set would over-state spread. Motivates the windowed weighting — and the graded within-window recency decay, a case's influence tapering over the infectious period.
Bengtsson et al. (2011)
PLOS Medicine — Haiti
Treating operator mobility data as a trustworthy primary signal, plus the ownership-bias caveat (under-represents the poorest, who tend to move with phone-owning family).
González, Hidalgo & Barabási (2008)
Nature 453:779
Foundational evidence that human movement is regular and predictable — the justification for building a risk model on mobility matrices at all.
Possible future refinement (from Truscott & Ferguson)

The gravity fallback currently uses a single Newtonian kernel (popj/tt2). A matched kernel (different power-law for short vs. long range) would stop it under-estimating rare long-distance Cat A → distant-city importation. Not yet implemented.

One-sentence brief for partners: the dashboard colours each zone by how much movement reaches it, in one step, from zones that currently have cases — weighted by how recent and how large those outbreaks are — and turns that into a ranked watchlist. Every value is a prioritisation ranking to guide surveillance, not a prediction or a probability.