
Clear water can still make a child sick: building Srot on AWS
How we turned 573 failed government water tests in Indian villages into Hindi voice alerts, grounded answers and cases that close only on a lab re-test, using Step Functions, Amazon Bedrock, Guardrails and Cedar. Plus the five things that broke along the way, and what fixed them.
A few weeks ago I opened a government portal I had never heard of: JJM-WQMIS, the water quality system behind India's Jal Jeevan Mission. Labs test drinking water from village taps and handpumps, and every failed test is published there. Village name, contaminant, the measured value, the lab, the date.
I scrolled through Uttar Pradesh and Rajasthan for this year and stopped counting at a few hundred. Then two things jumped out, and they became our hackathon project.
First, these results are old by the time anyone could act on them. Across the 573 failures we pulled, the median result was 105 days old. 149 villages had failed the same test again the following year.
Second, 87% of the failures were chemical: nitrate, fluoride, arsenic. That matters because the advice everyone gives for bad water is "boil it". Boiling kills bacteria. It does nothing for fluoride or arsenic, and it makes nitrate more concentrated, because the water evaporates and the nitrate stays. A family doing the "right thing" with nitrate-contaminated water makes it worse.
So the data exists, the government has done the testing, and the result sits in a portal the family drinking the water will never open. Nothing carries it to them, and nothing makes anyone fix the source.
That gap became Srot (स्रोत, Hindi for "source"), our entry for the WeMakeDevs x AWS Environmental Hacks, Heat and Water track. We were four people with about three days.
What Srot does
The idea is simple to say: every failed lab test becomes a case, and a case cannot be closed until a new lab test proves the water is safe.
- The village is warned. The health worker gets an alert on Telegram (and in a web app) in Hindi or English, with a voice note for people who cannot read, and advice for that exact contaminant. For E. coli: boil for a minute or use a chlorine tablet. For nitrate: do not boil, use another tested source, and never use this water for a baby's food.
- She can ask by voice. In Hindi, something like "can I boil this water and drink it?", and she gets a spoken answer based only on her village's lab results.
- Someone is responsible. The block engineer gets the case with a deadline. Miss it and the case escalates, and the district official gets an email listing every overdue case.
- It closes only on proof. The engineer cannot close the case himself. A field test kit only makes it "provisionally safe". Only a passing lab re-test closes it.
It runs on real data: 573 failed tests in 496 villages.
The architecture, and why each piece is there
I wanted every AWS service to do a real job, not sit in a diagram.
AWS Step Functions: one workflow per case
This was the first decision and the best one. A water case is a slow human process: warn the village, wait for the engineer, wait for a field test, wait for the lab. That can take weeks. Step Functions Standard workflows with task tokens let a case wait for a person at no cost, and
TimeoutSecondsPath gives every wait its own deadline. When the engineer taps "Chlorinated" in Telegram, the Lambda behind the bot calls SendTaskSuccess and the workflow moves on. Every case keeps a full history, which is exactly what an official needs to see.To load the real data I used a Distributed Map. It started 560 case workflows in 611 seconds with zero failures, for an estimated $0.43.
Amazon Polly: the voice
Polly's Kajal neural voice speaks both Hindi and Indian English, so one code path covers both languages. Many of the people this is for read slowly or not at all, so the voice note is not a nice-to-have.
Ask Srot: Transcribe, Translate, a Strands agent on Bedrock, Guardrails, Polly
This is the part I am proudest of, and the part that took the most rewrites.
- Telegram voice notes are OGG/Opus. Amazon Transcribe streaming accepts them as they are, in
hi-IN.
- Amazon Translate turns the question into English for the model.
- A Strands Agents agent on Amazon Bedrock (Nova Pro) answers. Its tools can only read the asking village's case and our advice library. It cannot see other villages and it cannot invent advice.
- Bedrock Guardrails checks the answer with contextual grounding against that same village data.
- A Cedar policy refuses any answer that tells people to boil water with chemical contamination.
- Translate brings it back to Hindi and Polly speaks it.
If any check fails, the person never hears the model's words. They get the official advice for their contaminant instead.
Amazon Verified Permissions with Cedar: the rules
The rules that matter for safety live in one Cedar file: only a passing lab re-test closes a case, a field kit only makes it provisional, never advise boiling for chemicals. The same file runs in Verified Permissions and locally through
cedarpy as a fail-closed fallback. If the policy engine cannot answer, the action is refused.Everything else
Lambda for every step and the bot webhook, DynamoDB for cases and timelines, S3 and CloudFront for the website and voice notes, API Gateway for the web API, Amazon Location Service for village pins and the map, Amazon SES with EventBridge Scheduler for the district email, CloudWatch (Embedded Metric Format) for business metrics, X-Ray for traces, and Systems Manager Parameter Store for every secret. The whole system is one AWS CDK stack in Python, 18 services, in ap-south-1 (Mumbai).
Five things that went wrong, and what they taught me
These are the parts I would want to read if I were starting the same project.
1. Guardrails denied topics were the wrong tool
My first idea was a denied topic for "false reassurance", so the model could never tell a village its water is fine. It blocked the correct answer too. "No, your water is not safe yet" discusses the same topic as "your water is safe now", and denied topics do not look at stance. The relevance filter was worse: it scored a correct answer to "is the problem fixed?" at 0.03.
What worked was contextual grounding through
ApplyGuardrail , with the village's record as the grounding source and the question as the query. A correct Hindi answer scored 1.0. A made-up "it's fixed, boil and drink" answer scored 0.0 and got replaced. Lesson: decide what you are actually afraid of (an answer the facts don't support) and pick the check that measures exactly that.2. Transcribe batch was too slow for a conversation
My first version uploaded the voice note to S3 and ran a batch job. A three-second Hindi question took 42 seconds. Nobody waits that long on a phone. Streaming transcription brought it to about 3 seconds, and Telegram's audio format worked without conversion. It needs the separate
amazon-transcribe SDK rather than boto3, which is worth knowing before you start.3. A new AWS account's Lambda concurrency is 10
I found this at runtime: three of my first workflows failed with
Lambda.TooManyRequestsException . The fix was a retry with backoff and jitter on every Lambda task in the state machine, plus pacing the Distributed Map so it never asks for more than the account can give. After that: 560 out of 560.4. Geocoding rural India needs a sanity check
Amazon Location returned a confident result for "Dhabla Kalayanpura, Anta, Baran, Rajasthan" that was in a different district, and for another village it returned a flour mill. Now we only accept a point if its district matches the record. Anything else is pinned at the block or district centre and drawn as a hollow "approximate" dot. Of 496 villages, only 172 have a trustworthy village-level pin, and the map says so instead of pretending.
5. The model was chosen by billing, not benchmarks
I planned to use Claude on Bedrock. On a new account it failed with an
AccessDeniedException about a Marketplace subscription and a payment instrument, even with credits. Amazon Nova Pro worked immediately, and with narrow tools, a fixed advice library and Guardrails around it, it was more than good enough for this job. I would rather ship with the model I can call than wait for the one I wanted.What we measure
A dashboard of CPU graphs would not tell anyone whether villages are safer. So the CloudWatch dashboard counts what happened to villages: cases opened, alerts sent, fixes logged, deadlines missed, cases closed by a lab re-test. For every voice question it tracks the Guardrails grounding score and the answer time. All of it comes from one structured log line per event (Embedded Metric Format), with no extra API calls.
Being honest about where this stands
The lab results are real government data. The lab re-test in our demo is simulated, because labs don't re-test on hackathon timelines, and the demo clock runs deadlines in minutes instead of days. No real villager has used Srot yet. Next: SMS and phone-call alerts for people without smartphones, and reading re-test results straight from the portal so the loop closes on real data.
The team, and how we worked
I led the build: the AWS architecture, the backend and every deploy. Reshma built the portal client and the validation that refuses partial data from a flaky government site. Faiz designed the village-first website. Umar did the phone testing, the Hindi review, the "Near me" search and our demo video. I used Claude Code as a pair programmer for much of the backend, which let me spend my own time on the decisions above instead of boilerplate. Every change went through a pull request with tests before it reached AWS.
- Demo video: https://youtu.be/oexFNyjCqKo
- Live site: https://d2735023v3xj6.cloudfront.net
- Telegram bot: @Srott_bot
The thing I keep coming back to is how little of this needed new data. The government already tests the water, and the result already exists. The missing piece was getting it to the right person, in a language they read or hear, with the right advice, and then not letting the case quietly disappear. That turned out to be a very good fit for workflows, voice and a few strict rules.
Clear water can still make a child sick. Srot is our attempt to make sure the village knows, and that someone fixes it.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article