How the model works (water-balance technical reference)¶
This page explains what the DSS is actually computing: how water is routed in space, how supply and demand are reconciled per sub-watershed per month, and what each number on the dashboard physically means.
The Flask app does none of this at request time. All hydrological computation happens offline in app/data_prep/prepare_hydrosheds.py, which converts the GeoPackages and Excel from the modelling team into the CSVs and GeoJSONs that the dashboard reads. The app just slices and aggregates those pre-computed tables.
1. The spatial unit: sub-watersheds¶
The modelled area is partitioned into small drainage cells, one per sub-watershed: 1,782 across 116 WRUAs. Each cell:
- Is derived from a 30 m SRTM-based DEM via flow accumulation, at a 10,000-pixel threshold.
- Has a stable
band_id(the sub-watershed polygon identifier), unique within its WRUA. - Belongs to exactly one parent WRUA. Water never crosses a WRUA boundary (Rule 1).
- Has a mean elevation, a polygon area, and an adjacency list (which cells share an edge).
Measured from the served polygons, the median cell is 5.3 km² and the mean is 10.2 km².
See Modelling unit for per-polygon attribute details.
2. The monthly water balance per sub-watershed¶
For each sub-watershed i and each month m the model computes a balance:
available(i, m) = runoff_local(i, m) + inflow_from_upstream(i, m)
demand(i, m) = CWR(i, m) # crop water requirement
gross_deficit = max(0, demand - available)
surplus = max(0, available - demand)
The five years of input data (2019 to 2023) are aggregated into a single 12-month climatology that the dashboard plots. The 48-month time series used by the SAT pond simulation preserves the original monthly sequence.
Where each term comes from¶
| Term | Source | Resolution |
|---|---|---|
runoff_local |
Model-derived (basin hydrological model), summarized monthly | Subbasin, climatology |
CWR (crop water requirement) |
FAO-56 Kc × WaPOR evapotranspiration over cropland |
100 m, 2019 to 2023 |
| Cropland mask | WaPOR land-cover | 100 m |
| Precipitation | CHIRPS | 5 km, 2019 to 2023 |
The gross deficit is the demand the irrigation system has to deliver. The net deficit is what crops actually need at the field gate, before factoring in irrigation losses.
Gross vs net deficit (irrigation efficiency)¶
The model uses two efficiencies (constant across the basin):
- Conveyance efficiency
Ec = 0.85(water lost between source and field) - Application efficiency
Ea = 0.70(water lost during application to the crop) - Overall efficiency
E = Ec * Ea = 0.595
So:
A 1 Mm³ net crop deficit needs about 1.68 Mm³ of gross water to actually deliver. Across the served dataset the two sum to 568.95 Mm³ net and 956.22 Mm³ gross, a ratio of 1.68. The map's color ramp keys on total_deficit_Mm3, which is the gross value.
3. Scenario 1: Reallocation (gravity transfer)¶

Reallocation tries to move surplus water from upstream sub-watersheds to downstream ones using gravity only, with no new infrastructure. The algorithm walks each WRUA top-down by elevation and for every surplus cell decides where its surplus goes.
Routing rules (in priority order)¶
- Stay within the same WRUA. Water never crosses a WRUA boundary, even if a neighbour has surplus to share.
- Local reallocation only on slope < 3%. If the donor's mean slope is steeper than 3% (percent grade, not degrees), no local reallocation is attempted; deficit stays accounted for at source.
- Flow downhill to adjacent watersheds. Candidate receivers must share a polygon edge with the donor AND lie at lower mean elevation. Uphill transfer is disallowed.
- Lowest-friction path. Among eligible downhill neighbours, pick the one with the lowest cumulative friction along the connecting flow path (per-pixel landcover and slope cost surface).
- Tie-break by greatest elevation drop. When two candidates tie on friction, the steeper drop wins.
After routing, each sub-watershed has:
inflow_surplus_Mm3 # surplus received from upstream
total_available_Mm3 # = local available + inflow_surplus
deficit_met_gross_Mm3 # how much of gross deficit was closed by transfers
unmet_deficit_gross_Mm3 # residual gross deficit
surplus_transferred_down # surplus passed further downstream
Most WRUAs have little total surplus, so reallocation alone closes only a small share of the deficit: across the served dataset it meets 23.14 Mm³ of a 956.22 Mm³ gross deficit, which is 2.4%. Reallocation is the cheapest scenario but rarely sufficient on its own.
4. Scenario 2: Storage (SAT farm ponds)¶

SAT = Storage Attenuation Technology. Each pond is a fixed 1000 m³ tank that captures wet-season runoff and releases it during dry-season irrigation.
Pond count per sub-watershed¶
Across every one of the 1,782 sub-watersheds in the served dataset, the relation that holds exactly is:
NP_needed is the unconstrained count: what the peak monthly deficit asks for,
assuming the basin can absorb all the runoff capture.
Design factor
The dashboard's scenario tooltips describe a 1.1x design factor on top of
this count. No such factor is present in the served figures, which follow the
plain ceiling above. Treat NP_needed as the unfactored count until the
modelling team confirms which is intended.
Environmental check (the 20% rule)¶
If every needed pond were built, the basin might capture so much runoff that downstream flows collapse. To prevent this, the model simulates 48 months of pond operation and checks the basin-annual retained fraction:
basin_annual_retained_frac =
sum(R_captured over all ponds, all months over a year)
/ sum(natural_runoff over the whole basin, same year)
If this fraction exceeds 0.20 (20%), pond counts are scaled down per sub-watershed until the basin-annual cap holds. The resulting count is NP_built. The dashboard always shows both:
NP_needed(unconstrained, what the deficit asks for)NP_built(constrained, what passes the 20% environmental check)
The 48-month simulation¶
For each sub-watershed and each of 48 months the pipeline writes:
| Field | Meaning |
|---|---|
H |
Pond water level at month end (fraction of pond capacity) |
R |
Runoff inflow into ponds that month |
overflow |
Spillover when ponds reached capacity |
balance |
Net balance (R, ET losses, irrigation withdrawals) |
Deficit |
Residual unmet demand that month, even with ponds operating |
retainedfrac |
Per-watershed annual retained share, recomputed monthly |
/api/simulation/<wrua>?scenario=sat returns the timeseries for any WRUA, optionally filtered to a single band_id with ?band=. The dashboard's pond charts plot it once a sub-watershed is selected.
5. Scenario 3: Combined¶

Combined runs reallocation first, then builds ponds only for the residual unmet deficit:
combined_deficit_to_pond = unmet_deficit_after_reallocation
NP_combined = ceil( combined_deficit_to_pond / 1000 )
Combined saves very few ponds across the study area. Measured on the served
tables, pond counts fall from 108,986 under Storage to 108,553 under Combined, a
reduction of 0.4%. In most WRUAs there is too little upstream surplus for
reallocation to remove any pond at all, so the two scenarios call for the same
ponds and Combined differs only in coverage. /api/compare/<wrua> computes this
figure per request, so the dashboard reports the measured value rather than a
rule of thumb.
6. What each dashboard chart actually shows¶
| Chart | Underlying quantity | Aggregation |
|---|---|---|
| Map polygon color | total_deficit_Mm3 per sub-watershed (annual gross deficit) |
Single sub-watershed |
| Monthly Water Balance (right panel bars) | vol_surplus_Mm3 (green, up) and vol_deficit_gross_Mm3 (orange, down) |
The selected sub-watershed. At basin scope the panel prompts for a selection rather than plotting |
| Deficit Intensity heatmap | vol_deficit_gross_Mm3 per (sub-watershed, month) |
Top 10 sub-watersheds in the WRUA by peak monthly deficit. The endpoint returns them all; the panel shows the top 10 |
| Recommended Interventions | max_deficit_m3 (single-month peak in m³, not Mm³) and NP_needed, NP_built |
Top 15 sub-watersheds by max_deficit_m3 |
| Comparison view bars | Annual total_deficit, deficit_met, unmet, ponds, % addressed |
Per scenario, per WRUA |
7. Constants and assumptions¶
| Constant | Value | Source |
|---|---|---|
| Pond volume | 1,000 m³ | Reviewer convention; matches typical farm-pond size in Kenya |
Conveyance efficiency Ec |
0.85 | Pipeline transmission |
Application efficiency Ea |
0.70 | Surface irrigation |
Overall efficiency E |
0.595 | Ec × Ea |
| Basin-annual retained-fraction cap | 0.20 | Environmental flow protection |
| Local reallocation slope cap | 3% | Reviewer rule |
| Years aggregated | 2019 to 2023 (5y) | Input data window |
| Simulation length | 48 months | Reviewer convention |
8. What the model does not do¶
- It does not route water across WRUA boundaries, even when one WRUA has surplus and a neighbour has deficit. This is a policy choice baked into the rules.
- It does not account for groundwater. All transfers are surface.
- It does not model crop choice. CWR is taken as observed from WaPOR ET; the question "what if farmers grew a less thirsty crop?" is out of scope.
- It does not dynamically recompute as you change scenarios in the UI. Every scenario's outputs are pre-computed offline and stored as static CSV.
9. Where to look in the code¶
| Concern | File |
|---|---|
| Data pipeline (Excel and GPKG → CSV and GeoJSON) | app/data_prep/prepare_hydrosheds.py |
| Authoritative routing rules | Reallocation rules |
| Flask aggregation endpoints | app/api/, one module per concern; see the API reference |
| Model constants (pond volume, thresholds, converged year) | app/model_constants.py |
| Per-WRUA data on disk | app/data/reallocation.csv, sat_bands.csv, combined_bands.csv, simulation_*.csv |
| GeoJSON polygons | app/data/elevation_bands.geojson (sub-watershed polygons) |