AWS Builder Center
Two Weeks Too Late: Early Warning for Contaminated Water, Built on AWS

Two Weeks Too Late: Early Warning for Contaminated Water, Built on AWS

Indore took two weeks to connect the warning signs of its water outbreak. We built a system that joins complaints, pharmacy sales, hospital visits and real rainfall for all 243 Bengaluru wards, using AWS Lambda, RDS with PostGIS, EventBridge, SES and Amplify. Built by students, with simulated health data. In tests using simulated outbreak data, it detected 8 of 8 water outbreaks, with a median of 1.5 days to the first alert.

Two weeks from a cremation to a survey team at the door

How we built Outbreak Watch, an early warning system for contaminated drinking water in Bengaluru, on AWS. Built by students for the WeMakeDevs x AWS Environmental Hacks, Heat and Water track.
The short version
  • The problem: When drinking water becomes contaminated, warning signs can appear in different places: complaints about water quality, unusual pharmacy sales, and an increase in hospital visits. When these signals remain disconnected, a potential outbreak can go unnoticed.
  • What we built: a system that watches all 243 Bengaluru wards every day, joins those signals with real rainfall, and emails a health officer when a ward looks unusual.
  • How it did: on simulated outbreaks it found 8 of 8 water outbreaks, a median of 1.5 days to the first alert.
  • The honest limit: the health data is simulated. We are students and have no access to real hospital or pharmacy records.

Indore, December 2025

On 21 December 2025, a 50-year-old woman visiting from Bihar was cremated in Indore. The cremation ground records say she had stomach pain, vomiting and diarrhoea for 10 to 12 days.
Nobody had connected it to the water yet.
She had been staying in Bhagirathpura, a neighbourhood in Ward 11. In the days after, more people there got sick with vomiting and diarrhoea after drinking the municipal tap water. The first cases were officially reported on 24 or 27 December, depending on which report you read. The Free Press Journal later wrote:
“the first death occurred on December 21, not a few days ago as officially suggested”
On 4 January 2026, teams went door to door. They screened 9,416 people in 2,354 households and found 20 new cases that day. 142 people were in hospital, 11 of them in intensive care. A team from the National Institute for Research in Bacterial Infections came from Kolkata.
That is two weeks from a cremation to a survey team at the door.

Why we built this

When we applied for this hackathon, we started researching all three problem areas. During that research, we came across the water contamination outbreak in Indore. People were getting sick, and it took time for the full scale of the problem to become clear. The warning signs existed, but they weren’t being seen together.
I live in Bengaluru, where water contamination is a concern too. That made us wonder: could we build something that connects these warning signs and helps identify a potential outbreak earlier?
That question became Outbreak Watch.

The gap

  • When drinking water becomes contaminated, small things can change across a ward. Someone complains that the water smells strange. The pharmacy down the road sells more medicines than usual. A few more people show up at the hospital.
The problem is where these signals end up. Complaints go to one office. Pharmacy sales sit in shop billing systems. Hospital visits can arrive days after patients first become ill.
In our own test feed, 355 of the 733 rows that arrived on one morning referred to earlier dates. Most of those were hospital records.
When these signals remain separate, it becomes harder to see the bigger picture: several unusual events happening in the same ward around the same time.
Outbreak Watch brings those signals together in one place.

What we built

Outbreak Watch watches all 243 Bengaluru wards, every day, across complaints, pharmacy sales, hospital visits and real rainfall.
Here is what it did in our first end-to-end run. We planted a water outbreak in ward 18, which sits in water zone 14. The one alert the detector raised was not for ward 18. It was for ward 21, a different ward in the same zone. The alert said:
Outbreak Watch map of Bengaluru wards with alert wards in red, watch wards in gold and a suspected water zone outlined in blue

complaints 5.3x normal for a Sunday (8 vs usual 1.5) … chance of an outbreak: 93%
Complaints, pharmacy and hospital all backed it. On the map, the whole zone lights up, so the officer sees every ward on those pipes and not only one.
Outbreak Watch alert page showing an 81% outbreak chance and nine alerting wards

The map shows alerting wards in red, wards on watch in gold, and the suspected water zone outlined in blue, based on the real BWSSB zone map.
When an alert is raised, Amazon SES sends an email to the officer with the affected ward, supporting evidence and a likely cause. The cause is labelled a triage hint, not a diagnosis. The officer can open the alert in the web app, acknowledge it, add notes and resolve it. Every action is logged with the officer’s name.
An open alert in the demo shows an estimated 81% chance of an outbreak, nine alerting wards in the same zone, and eight weeks of historical signals compared with the normal pattern for that weekday. The “Simulated” labels are there deliberately.
Citizens can contribute reports through the Report page. Our first test complaint read: “Tap water smells of sewage since Monday”.
In Bhagirathpura, the first sign in this story was a cremation record. We wanted Bengaluru’s warning signs to reach the right people sooner,before more families were affected.

How it works

Normal for a Tuesday. For each ward and signal, we take the median of the same weekday over the last 8 weeks. A median, because one old spike should not drag “normal” up. Today’s score is how many spreads it sits above that.
A small example from our maths notes. Past days for that weekday: 38, 41, 40 and a freak 95. Normal is 40.5, and the 95 barely moves it. Today is 120. That is 12.49 spreads above normal. Very loud.
A rise that keeps going. Some outbreaks do not spike. They creep. CUSUM keeps a running total of how unusual each day is, minus a little slack. Four days at a score of 2 build up 1.5, 3.0, 4.5, and the third day crosses the line of 4. One mild day on its own fades back to zero.
Fused Bayes. Each signal’s score becomes a weight of evidence, and we multiply them together. Complaints at 5 and pharmacy at 4 give 73% in our test settings. If the neighbouring wards are also up, it goes to 86%. Neighbours count, but only at a quarter strength.
There is one rule we like a lot. Bayes only fires if at least two different signals are up. Complaints alone at score 5 give 20%, and no alert. One feed can break, or be gamed.
The cause hint. Simple rules score each cause: whether the alerting wards share a water zone, how fast the rise was, and whether there was heavy rain in the last 10 days. In our worked example, all 12 wards of one zone alerting, a one-day rise and 42.5 mm of rain three days earlier give water 78%.
It is a hint. In the run above, the planted water outbreak was first hinted as food, 49%. On simulated outbreaks the hint names water only 21% of the time at the first alert, and 47% three days later. That is why an officer has to check in the field.

Built on AWS

Outbreak Watch architecture on AWS: Amplify, API Gateway, Lambda, EventBridge, RDS, S3, SES and CloudWatch

Outbreak Watch architecture on AWS
The backend runs in ap-south-1, Mumbai. Real rain comes in every hour. Pharmacy and hospital counts come in every morning at 06:00 through our own webhooks. Complaints come from the Report form. All of it lands in one table, and every 5 minutes the detector reads it.
  • AWS Amplify Hosting serves the React web app: the map, alerts, notes and the Report form.
  • Amazon EventBridge runs three schedules. The detector runs every 5 minutes, so a hospital row that turns up at 3 p.m. counts the same day.
  • AWS Lambda (five functions, Node 22), because the work comes in short bursts and we only pay while it runs.
  • Amazon API Gateway puts one HTTPS front door on the API for the web app, the Report form and the webhooks.
  • Amazon RDS for PostgreSQL + PostGIS holds the real ward and water zone boundaries. PostGIS turns a complaint’s location into a ward.
  • Amazon S3 keeps a raw copy of every rain answer and every feed batch, by day. Each Lambda can only write to its own folder.
  • Amazon SES sends the alert email.
  • Amazon CloudWatch shows detector runs, alerts sent and errors. An alarm fires if rain cannot be fetched for 3 hours in a row.
  • AWS Secrets Manager and SSM Parameter Store hold the database login and our secrets. None of them are in the code.
  • AWS CDK describes the whole backend in one TypeScript file.  npm run deploy  stands it all up, with a $30 budget alert included.
We built this on student AWS credits, so cost was a real design rule: short Lambda bursts, one small single-zone database, and 7-day log retention.

Did it work?

Technically, yes, on simulated data. As students, we don’t have access to hospital records or government pharmacy data. But the detector reads one row per ward, signal and day, and does not care where that row comes from, so real feeds can take the place of the simulated ones. To test it, we planted outbreaks: water, food and person to person, hidden inside a simulated Bengaluru with all 243 real wards, a seasonal wave and hospital data that arrives late. We made 20 different versions of that city.The rules came first, before any run. Settings are tuned on 2022 and 2023 only. Then they are locked and run once on 2024 and 2025. Every method gets the same false alarm allowance: 0.25 per ward per year, which is one false alarm per ward every four years.
On the realistic setting, the median of the 20 runs:
Outbreak type
Found
Median days to first alert
Water
8 of 8
1.5
Food
7 of 8
2
Person to person
5 of 6
7.5
To check that this was not luck, we ran the same matching rule on fake outbreaks where nothing happens. It “finds” 18 to 36% of them. Real detection is far above that. The false alarm rate in the test years came in at 0.24 per ward per year.
On water outbreaks outside the seasonal wave, a plain threshold did as well as our fused method. The results file flags every case like that, and we left the flags in.
Compare 1.5 days with two weeks.

What went wrong

During development, everything seemed to be going well. We were researching, learning, coding and working with AI, sometimes even at 2 AM. We felt good about our progress.
Then everyone finished their parts and we tried to connect them, and we were completely lost. We had agreed on the names, variables, types and parameters, but somehow the parts refused to work together. Seeing so many errors after all that work was scary and frustrating.
With the help of AI, repeated debugging, long chats and countless attempts, we finally got everything connected. Seeing the whole system work together was a huge relief. It taught us that building the individual parts is one thing, and making them work together is a completely different challenge.
The facts, from our status notes. We were three people building three parts. One part had 174 passing tests, but they tested shapes we had guessed, and nothing in the backend called that code yet. The backend ran on a placeholder detector with a 6-ward city. Each part passed its own tests, and none of them talked to each other.
Joining them up found real bugs. A citizen’s complaint and the simulated complaint for the same ward and day were overwriting each other, and which one won depended on row order. Our lockfile had come from a Windows machine and carried the wrong esbuild binaries. Near the deadline, our new AWS account was slow to activate, so we moved the deploy to a teammate’s account.
What we would do differently: connect every part on day one, even with fake data inside. And keep one shared contract as code. Ours is now  packages/contract , and every part imports its types from there.
We used Claude and Antigravity, and logged each use in  docs/ai-tools.md .

What is next, and what is honest

We built Outbreak Watch as students, without access to private hospital or pharmacy records. That meant simulating the health data needed to test our detector. Here’s what the system actually uses today:
  • Health data: Pharmacy and hospital records are simulated. Complaints are simulated too, except for reports submitted through the Report form.
  • Geographic and rainfall data: Rainfall data and ward and water zone maps are real.
  • Cause identification: The system suggests a possible cause. This is a triage hint, not a diagnosis.
  • Access control: Officer login is not implemented yet. The demo uses an open officer role, with a key required for Reset and Inject operations.
  • Database security: The database is reachable from the internet in the demo. A real pilot would require a private network and stronger security controls.
These are not minor details. They define what Outbreak Watch can and cannot do today.
The next step would be to work with authorised public health departments to evaluate the system using real data. Our detector expects one row per ward, signal and day, but connecting real feeds would still require integration, validation and testing. We would also need to establish how reliably it identifies actual outbreaks and how often it raises false alarms.
We started this project with a question: what if the warning signs of an outbreak could be connected before more people got sick? We haven’t answered that question for the real world yet. Our health data is simulated, our detector needs testing against real outbreaks, and the system needs stronger security before it could be used in practice. We want to be clear about that. A public health system cannot run on good intentions alone; it needs evidence, reliability and the trust of the people who use it.
But we built something we can test, challenge and improve. We learned how difficult it is to connect separate systems into one that works, how easily a promising result can hide a weakness, and why being honest about what failed matters as much as showing what worked. We came to this hackathon wanting to build a solution. We leave understanding more clearly what it would take to make one useful.
In Bhagirathpura, the warning signs were scattered across time and place. We cannot change what happened there, and we have not proved that Outbreak Watch would have caught it earlier. But we can work toward a future where complaints, pharmacy trends and hospital visits are no longer isolated signals ,and someone can see the pattern sooner, investigate sooner, and act before more families are affected.
We built Outbreak Watch because the first warning should not have to be the last thing someone notices.

Links

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article