Skip to content
Madhu

Case study · AI & LLMs · 2 min read

CivicPulse

Turn one photo of a local problem into a categorised report, a ready-to-send complaint letter and a pin on a community map.

Role
Full-stack: React front end, FastAPI back end, Gemini integration
Type
AI & LLMs
Stack
React, FastAPI, Gemini Vision, Firebase, Leaflet
CivicPulse report form with photo upload, description and a map for picking the location
The report form: photo, description and a map picker.

01

Problem

Potholes, broken streetlights and dumped rubbish rarely get reported, because reporting them is a hassle.

CivicPulse makes it one step. You upload a photo, drop a pin and add a line of description. The app works out the rest.

Even when people want to report something, they don't know which department handles it or how to write a complaint that gets taken seriously. And there's no easy way to see whether a neighbour has already flagged the same thing.

02

What I did

  • Gemini Vision reads the photo alongside the description, puts the issue into one of eleven categories and scores its severity as low, medium or high.
  • The category is mapped to the responsible department, for example potholes to Roads and Infrastructure, or leaks to Water and Sewerage. A formal complaint letter is drafted with a timeline that depends on severity.
  • Reports are stored in Firestore with the photo in Firebase Storage, and plotted on a Leaflet map with colour-coded severity pins. Other residents can upvote to confirm an issue, and a dashboard shows totals, resolution rate and breakdowns by category and severity.
Request flow for a new report.

Decisions along the way

Keep the department routing in code
Gemini decides the category and a plain lookup table decides the department. That keeps the routing predictable and easy to change, rather than leaving it to the model each time.
Fail soft when the AI is unavailable
If Gemini is down or the key is missing, the back end still saves the report with category "other", medium severity and a template letter. A report is never lost because a model call failed.
Tests that need no keys
The back-end tests stub Gemini and Firestore, so CI can run them and build the front end without any credentials.

Stack

Front end
React 19, Vite 8, Tailwind CSS 4, React Router 7, react-leaflet
Back end
Python, FastAPI
AI
Google Gemini via the google-genai SDK
Data
Firebase Firestore and Storage
Deploy
Docker Compose locally, Google Cloud Run

03

What I found

  • An LLM is most useful for the fuzzy step (what's in this photo?) and least useful for rules you can write down yourself.
  • Designing the fallback path first made the whole app easier to test.

04

What I'd do next

  • Add user accounts and moderation, so reports and upvotes can be trusted.
  • Host a demo so people can try it without their own keys.

05

Limitations

  • There's no hosted demo. Running it needs your own Gemini API key and Firebase project.
  • No user accounts or moderation yet, so anyone can report and upvote.
  • CI runs the back-end tests and the front-end build. It doesn't call Gemini or Firebase.

Back to top