The problem
A pothole in Delhi NCR gets reported, if it gets reported at all, by whoever is annoyed enough that day to find the right municipal number and call it. Most never get logged anywhere. The ones that do land with whichever civic body the caller guessed was responsible, which is often the wrong one, because jurisdiction in the NCR is split between the MCD, the GDA and the NMMC along boundaries no resident is expected to know.
Pothole Patrol turns that into a photograph and a few seconds: point a phone at the damage, and the system worries about whether it is real, whether it has already been reported, and which office actually owns that stretch of road.
Dual-stage verification
A single ML pass on a phone camera is not trustworthy enough to auto-dispatch on, and a server round trip for every frame is too slow to feel instant. So verification runs twice, at two different costs.
On-device, a YOLOv8-nano model compiled to TFLite gives the reporter immediate feedback: yes, this looks like road damage, submit it. Server side, the full YOLOv8 model re-validates asynchronously through Celery, because a phone-class model trades accuracy for speed and the report needs the more careful pass before anything is dispatched anywhere.
The two passes do not just agree or disagree. Confidence is banded:
- 0.70 and above auto-verifies and dispatches straight to the relevant civic body
- 0.50 to 0.69 queues for a human moderator
- below 0.50 auto-rejects
A single threshold would force a choice between too many false positives reaching a civic body's inbox or too many real reports sitting in a review queue. Three bands let confident cases move immediately while genuinely ambiguous ones, which are the minority, are the only ones that cost a person's time.
Deduplication before dispatch
The same pothole gets photographed by more than one resident, especially
somewhere visibly bad. PostGIS's ST_DWithin checks every new verified
report against existing ones within 50 metres; a match becomes an upvote on
the original report rather than a second entry in the queue. This is what
keeps the live heatmap honest: without it, a single bad stretch of road
would look like several unrelated ones, and a civic body would see
duplicate work orders instead of one report with rising urgency.
Getting it to the right desk
Jurisdiction is a geometry problem before it is anything else. Verified
reports are checked against ward boundary polygons via PostGIS spatial
queries, so a report is routed to whichever of the MCD, GDA or NMMC actually
owns that stretch of road, and dispatched by email and a REST webhook rather
than left for someone to forward manually. A staff-accessible CSV export at
/v1/civic/export/ exists for the offices that are not yet wired into
anything more automated than a spreadsheet, which in practice is most of
them.
Getting people to keep reporting
Firebase Phone OTP and Google Sign-In handle authentication with no passwords to manage. Verified reports earn 10 points on a global leaderboard, with badge thresholds and a pull-to-refresh view, and FCM push notifications tell a reporter when their own report's status changes. None of this is the hard engineering in this system, and none of it is optional either: a reporting tool nobody keeps using is a database, not a civic service.
Decisions and trade-offs
Confidence bands over a single threshold. More states to build and reason about, in exchange for a system that does not have to choose between spamming civic bodies and burying real reports in review.
On-device plus server, rather than server-only. Two models to maintain and keep roughly in sync, for a reporter who gets an answer before they have put their phone away and a civic body that still gets the more careful pass before anything is dispatched in their name.
Geometry-based routing over a manual jurisdiction lookup. Ward boundaries as polygons are more setup than a lookup table, and they are the only version of this that stays correct when a boundary changes without someone remembering to update a spreadsheet.
Outcome
The backend, ML pipeline and dispatch logic are built and running; the Swagger docs on the live backend describe every endpoint above as callable today. What this case study does not claim is official adoption by any of the three civic bodies it routes to: nothing here has been formally taken up by the MCD, GDA or NMMC, and the honest description of this project is a working civic-tech system looking for that partner, not one that already has it.