Every month, the Karnataka State Pollution Control Board (KSPCB) publishes water-quality results for Bengaluru's lakes. Technically, that's public data. In practice, it lives in PDF tables that almost no resident will ever open, and if they did, "DO 3, BOD 7, Class E" wouldn't mean much to them.
Then in October, news outlets reported thousands of dead fish at Doddagubbi lake. We looked up the lake in the July report and found it had been rated Class E, the worst class in the report's scale. The warning was in the record. Nobody could see it.
So the three of us (college students in Bengaluru) built Jala Saakshi (ಜಲ ಸಾಕ್ಷಿ, "the water's witness"). Our goal was data accessibility: take what's already public and make it usable by the people who live next to these lakes.
What "accessible" ended up meaning
We had to keep redefining it as we went.
- Findable. Open the app, tap "discover lakes near you", and see the lakes around you on a map with their latest readings. No login.
- Readable. We translated the class system into plain sentences. "Class E" becomes something a person can act on.
- In Kannada. The whole citizen app switches to Kannada, and you can report a problem by speaking in Kannada or English. Under the hood that's the browser's built-in speech recognition, not an AI model. I'll be upfront that our Kannada strings still need a native speaker's review.
- Honest about gaps. This was the most important one. The July 2026 report covers 131 lakes, and 70 of them came back Class E. But we mapped 318 lakes from OpenStreetMap, and 206 of those have no reading at all. We show that emptiness instead of hiding it, because "nobody has tested this lake" is information too.
(The PDFs were the unglamorous part. They change layout between reports, so we wrote a parser that reads column headers instead of counting positions, and moved on. The interesting problems came after.)
Mistakes that taught us the most
We quoted a wrong number. An early version of our Doddagubbi page said BOD was 22. It's 7. The 22 was the nitrate-nitrogen value, from the neighbouring column. A public-interest tool that misreports a lake is worse than no tool, so we now trace every number on screen back to the report row.
Our pages disagreed with each other. The dashboard said 59 of something, the story page said 60. We were counting the same data separately in several places. Fixing it meant one data provider that every page reads from, so a number is computed once.
Our "lakes" included a drain and a swimming pool. OpenStreetMap tags a lot of things as water, and a storm drain showed up as a lake near someone's home. We wrote exclusion rules and checked the results by hand.
Free map tiles that weren't. We tried a dark basemap from a popular provider and got tiles that said "API KEY REQUIRED". We switched to Esri's dark canvas and added a satellite layer.
What's on AWS
Right now, the app is deployed on AWS Amplify Hosting, building from our GitHub repo. Our first build failed because Amplify's default Node version didn't satisfy our Vite version. Pinning Node 22 in amplify.yml fixed it, and I'd tell any student to do that on day one.
We've also written a backend as an AWS SAM template (Lambda, API Gateway and Amazon Bedrock for photo analysis). Photo reports currently save in the user's own browser. Our roadmap includes Amazon Transcribe for server-side Kannada speech-to-text and Sentinel-2 satellite imagery from the AWS Open Data registry, to watch lakes between monthly tests. We've labelled these as planned in the repo and on our diagram.
What I'd tell my past self
- Decide early what each number on screen is, where it came from, and what it isn't.
- Showing "no data" is a feature.
- Deploy a boring version on day one. Our build problem was easy to fix, but it would have been awful to find on submission day.
Loading article