Sai Aryan Goswami

POTHOLE PATROL, YOLOv8 road damage detection with PostGIS dispatch


YOLOv8 road damage detection with PostGIS dispatch

Photograph a pothole from your phone. The app checks it is really a pothole, makes sure nobody already reported the same one, works out which city office owns that stretch of road, and sends it there.

Year
2025
Domain
civic tech
Stack
React Native, YOLOv8, PostGIS, Celery, Firebase

Before

A photo, GPS-tagged

1

one report, unverified

road damage nobody has logged

photo.jpg  28.5710N 77.3260E
submitted by: resident, unverified
severity: unclaimed
duplicate of an existing report: unknown
which civic body owns this road: unknown
no confidence score
no route to a ward office

After

dual-stage verification

0.70

confidence, auto-dispatched

a verified report at the right city office

On-device pass
YOLOv8-nano, TFLite
Server re-validation
YOLOv8, async via Celery
Confidence >= 0.70
auto-dispatched
Within 50m of an existing report
merged, not duplicated
Routed to
MCD / GDA / NMMC by ward polygon
Reporter
+10 points, leaderboard

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.