Google Cloud · System Architecture

How FixMyCity routes a photo to a department

One agentic pipeline, from a citizen's camera to a trust-scored, routed ticket — and which Google Cloud service does each step.

Your browser doesn't support embedded SVG. View the architecture diagram directly.

Under the hood

What each layer actually does

Citizen app flow

The user opens the app and starts a new complaint. The camera opens and the user photographs the issue — a pothole, garbage, a broken streetlight. A live on-device object-detection overlay (MediaPipe/TFLite, via the object_detection Flutter package) runs during framing purely as a "something's in frame" confirmation — it only recognizes generic COCO classes (person, car, ...), not civic-issue categories, so it never claims to identify the actual issue. The captured photo is sent to the backend for real classification.

The backend (Genkit flow + Gemini Vision) detects the issue, classifies category and severity, and returns a presubmit structured report. The app shows this data to the user, editable — category, severity, description, location — before anything is finalized. Once the user approves, the ticket is created.

Data store

Prisma/Postgres (Cloud SQL) is the store for reports, tickets, and Better Auth's user/session tables — one GCP-native database, no third-party hosted service. Citizen photo uploads go straight to GCS, not through the database. Duplicate-by-location checks are a haversine-distance filter over tickets fetched via Prisma — no PostGIS or geospatial index needed at this scale.

Auth

Citizens log in via Google social sign-in (Better Auth's socialProviders.google) — live end-to-end: a real GCP OAuth client, with GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET set on Cloud Run, and tickets scoped per signed-in citizen. The admin dashboard has no login gate — unauthenticated by design, per its own oRPC router.

What's actually live

  • Backend + admin dashboard on Google Cloud Run, auto- deploying via Cloud Build on every push to main.
  • The full pipeline: photo upload → GCS → Eventarc- triggered Genkit flow → Gemini Vision classification → presubmit draft → citizen review/edit → ticket creation.
  • Trust score, duplicate detection, and department routing, all computed and shown to citizens today.
  • A self-improvement feedback loop, early stage: every Ticket stores Gemini's original category/severity/description suggestion alongside the citizen's final, possibly corrected, values — no retraining pipeline yet, but the data needed to refine the classification prompt is being captured on every ticket.

Why it's shaped this way

Five decisions, made on purpose

Two Cloud Run services, not one

Classification runs on a separate, IAM-locked service — the public API never runs untrusted-latency AI work, and the AI pipeline is never internet-reachable.

Postgres, not Convex

One GCP-native database instead of a second platform, at the cost of trading live dashboard updates for a 5-second poll.

Haversine, not PostGIS

A distance formula in application code beats a spatial index nobody needs yet at this data volume.

The model decides when to check for duplicates

findNearbyReports is a real tool call Gemini chooses to make, not a hard-coded step in the pipeline.

It calibrates in context, not by retraining

Every ticket stores what Gemini originally said next to what the citizen filed. The five most recent disagreements go into the next prompt as few-shot examples — no fine-tuning pipeline exists.

Common questions

What judges usually ask

Is FixMyCity already self-improving?

Yes, in a specific and honest sense: not by retraining the model. The database stores Gemini's original category, severity, and description alongside the values the citizen finally approves — every correction is captured.

Before classifying a new report, the agent (runReportPipeline) pulls the most recent corrections and folds them into its own prompt as few-shot examples: "you previously guessed X here, the citizen corrected it to Y, don't repeat that." That's in-context learning from the system's own mistakes, on every single report.

There is still no automated fine-tuning or retraining pipeline, and no claim here that the underlying model changes. Calling it self-training would overstate the implementation. The accurate claim is that FixMyCity actively feeds its own correction history back into the agent's reasoning, in real time, without any offline training step.