What We Learned Building JalRakshak - a Water Shortage Estimator Without Real Supply Numbers
JalRakshak estimates water demand, supply and shortage risk for 66 Indian cities from groundwater, rainfall and tap-coverage data, and lets planners test what cutting pipe losses would change.
What we built
We built JalRakshak for the Environmental Hacks hackathon (Heat & Water track). It takes public groundwater, rainfall and tap-connection data for 66 Indian cities and produces, for each one:
- an estimate of water demand and supply in MLD (million liters per day)
- a shortage percentage and a risk tier: Low (under 10%), Medium (10-25%), High (25-40%) or Critical (40% and up)
- an estimate of non-revenue water (NRW), meaning water that is supplied but never billed
- a what-if simulator for cutting those losses
The backend is FastAPI. The dashboard is React with a Leaflet map of the 20 cities with the highest demand. On the 69 records in our dataset, 5 land in Critical and 24 in High.
A note on "69" and "66": the Central Ground Water Board reports Patna, Ahmedabad and Vadodara twice each, once for the shallow aquifer and once for the deeper one. That gives 69 rows and 66 distinct cities, across 21 states and union territories.
The data problem
No source we could find publishes city-level water supply or consumption. Our own data audit says so in its first finding. What we did have:
- CGWB decadal groundwater data: the share of monitoring wells where the water table rose or fell, comparing the November 2013-2022 mean against November 2023. It covers 698 wells across the 69 rows.
- IMD district rainfall for one eight-day window, 1-8 October 2026.
- Jal Jeevan Mission tap-water coverage by state. It counts rural households, and we apply it to urban cities through their state.
- A table of city populations that we typed into the preprocessing script by hand. They are 2011-era figures, not 2026 projections.
So demand and supply in this project are constructed, not measured. Demand follows the CPHEEO urban benchmark: population x 135 liters per person per day x 1.15 for non-domestic use. That gives Delhi 2,606 MLD and Kozhikode 67. Supply is a modeled proxy driven by groundwater stress and tap coverage.
We built a groundwater stress index (GSVI) from the CGWB and IMD columns: 55% weight on the share of wells with falling water levels, 35% on the share with a drop of more than 4 meters, and 10% on the rainfall deficit, capped at 0-100.
Predicting a ratio instead of MLD
Demand in the dataset runs from 10 MLD (Jaisalmer) to 2,606 MLD (Delhi). We didn't want one regression trying to cover that range, so the supply model predicts the supply ratio, supply divided by benchmark demand:
Plain text
Supply Ratio = Estimated Supply / Benchmark DemandSupply (MLD) = Demand (MLD) x Supply Ratio
A ratio of 0.7 means the same thing in Delhi as in Coimbatore. In the dataset it runs from 0.40 to 1.00, and we clip predictions to 0.10-1.0. We never ran a head-to-head against direct supply prediction, so this is a design choice, not a measured win.
For model selection we ran repeated stratified 5-fold cross-validation (5 repeats, 25 fits) on all 69 records and scored the resulting risk tiers:
Supply ratio model | Exact tier accuracy |
|---|---|
Predict the mean | 45.05% |
Hist. Gradient Boosting | 66.20% |
Random Forest | 82.31% |
Ridge | 85.85% |
Gradient Boosting | 86.68% |
Gradient Boosting also got 100% within one tier and a shortage-percentage error of 2.62 points. On a held-out set of 14 records it got 85.71% exact and an error of 0.88 points.
Demand uses Ridge regression and scores R2 0.9998 on that held-out set. That number says more about the formula than the model: demand is population times a constant, and a linear model recovers it.
The same caveat covers every accuracy figure above. Because the labels are constructed, these numbers measure how closely the models reproduce our proxy. They are not forecast accuracy against real utility records.
Loss detection
We trained an Isolation Forest on four features (unaccounted water in MLD, NRW percentage, groundwater stress and tap coverage) with a 10% contamination setting. Offline, it flags 7 of 69 records: Delhi, Bangalore, Mumbai Suburban, Patiala, Jaipur, Kota and Kolkata.
The live API doesn't call that model. Its anomaly flag is a rule: NRW above 25% or stress index above 60. And NRW itself is a formula, 15 + 0.20 x (100 - tap coverage) + 0.05 x GSVI, so the flag points at places to inspect. It does not detect leaks, and every response carries a disclaimer saying so.
The what-if simulator
The simulator takes a city, a current NRW percentage and a target, and assumes the water saved is
supply x (current - target) / 100 . For Bangalore, the dataset has demand at 1,311 MLD, supply at 882 MLD and NRW at 20%, a 32.8% shortage and a High tier. Cutting NRW to 5% recovers 132 MLD, drops the shortage to 22.7% and moves the city to Medium. Cutting it to 10% recovers 88 MLD and leaves the city in High at 26.0%.This assumes every recovered liter reaches consumers. The API labels results as a modeled scenario, not a measurement.
The rest of the stack
- API: four endpoints (health, cities, predict, simulate).
- City matching: exact match, then match with the aquifer suffix removed, then an alias table (Bengaluru/Bangalore, Gurgaon/Gurugram, Mysore/Mysuru, Allahabad/Prayagraj), then substring.
- Frontend: React 19, Vite, Tailwind CSS 4, Leaflet on OpenStreetMap tiles, ECharts.
- Hardening: an in-memory rate limit (100 requests per 60 seconds per IP), CORS limited to localhost origins by default, security headers, an optional API key, and an optional SHA-256 check on model files. The check matters because joblib files are pickles.
- CI: three GitHub Actions workflows: backend tests plus a dependency scan, an ML smoke test, and frontend lint plus build.
Where AWS fits
Everything runs locally today, and the repo contains no AWS service calls. The three trained models are small (1.5 KB, 85 KB and 664 KB), so they fit inside a container image or an S3 object without trouble.
The deployment plan in our security notes is the React build on Amplify or behind CloudFront, and the FastAPI service on App Runner or Lambda. The model files would load from S3 with the SHA-256 check switched on, and secrets would go in Secrets Manager or Parameter Store.
Limits you should know about
- Constructed labels. See above. The tier accuracy is fidelity to a proxy.
- No time dimension. The data is one snapshot per city. The predict endpoint accepts a
forecast_periodlabel like "Upcoming Month", but the models never see it.
- Rural coverage applied to cities. JJM counts rural households.
- Old populations. Demand comes from 2011-era population figures.
- One rainfall window. Eight days in October 2026.
What we'd do next
- Replace the supply proxy with real data. Our audit lists what's needed: city supply logs, metered consumption and AMRUT 2.0 water-balance plans.
- Make the preprocessing script rebuild the dataset from the raw files. Today it doesn't, because one input file is missing from the repo.
- Route the live anomaly flag through the Isolation Forest, or drop the model.
The code
The project is MIT-licensed at
github.com/anotherhalf004/Seedlings-Jalrakshak . The README has the quickstart, the API table and a sample prediction payload.Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article