-
-
Polis live: a city map of real reports, colored by severity, with the intake panel on the left
-
Every report carries an AI drafted official letter, addressed to the right department, ready to dispatch
-
Reporting is simple: a few words and an optional photo. The agent does the rest
-
One step: the agent classified the pothole, flagged the hazard, routed it to Roads and Transport, and drafted the official request
Every city runs on a slow, invisible queue. A resident notices a broken streetlight or a dangerous pothole, then has to find the right form, guess the right department, and write a request that usually goes nowhere. The signal is there, but the city's nervous system that should carry it is missing. We wanted to build that nervous system: something that takes a raw human signal and turns it into the right action, automatically.
What it does Polis lets a resident report a street problem with a few words and an optional photo. An AI agent then does the work a skilled city dispatcher would do:
- It classifies the problem into a concrete category.
- It scores severity from 1 to 5 and flags immediate safety hazards.
- It routes the report to exactly one city department.
- It names a realistic recipient for that department in the local jurisdiction.
- It drafts a short, professional official request, ready to send.
- It plots the report on a live city map with a severity colored pin.
From the report detail, the drafted request can be dispatched as a real email to the department inbox, closing the loop from citizen signal to city action.
How we built it The app is a Next.js 16 application with a React 19 front end and Tailwind. The intake form posts the report to a server route, which calls Google Gemini through the official SDK in Vertex AI mode. We force the model to return a strict JSON schema, so every report comes back with a category, severity, hazard flag, department, recipient, summary, official letter, and an estimated resolution time. When a photo is attached, the model grounds its judgment in what the image actually shows. The map is Leaflet with dark tiles and needs no API key. Dispatch sends the letter through Resend. We deployed it on a VPS behind nginx with pm2 and a Let's Encrypt certificate, live at poliscan.xyz.
Challenges we ran into The hardest problem was networking. Our server could reach the internet, but the Google authentication call kept timing out while a simple curl worked fine. The cause was that the host advertised IPv6 addresses but had no working IPv6 route, so Node stalled on the dead path. We fixed it by pinning all outbound connections to IPv4 at server startup. We also tuned the agent's writing so its letters read like a real municipal clerk wrote them, with no machine generated tells.
What we learned Structured output turns a language model into a dependable component. By constraining Gemini to a schema, the agent's results drop straight into the map and the email with no fragile parsing. We also learned how much a small product decision matters: routing to a clearly named recipient makes the agent feel like it knows the city, not just the text.
What's next for Polis Verify a sending domain so reports can go to any real department inbox, ingest signals from city open data and sensors, cluster duplicate reports, and give departments a dashboard to track resolution.
Built With
- google-gemini
- leaflet.js
- lets-encrypt
- next.js
- nginx
- node.js
- openstreetmap
- pm2
- react
- resend
- tailwind-css
- typescript
- vertex-ai
Log in or sign up for Devpost to join the conversation.