Why I Built a “Vibe Check” for Neighborhoods (And How I Kept the API Bill from Killing Me)
Why I Built a “Vibe Check” for Neighborhoods (And How I Kept the API Bill from Killing Me)
When I moved to a new place, I always wanted to know “what the neighborhood looks like”, which is not enough just walk around in some random hours. I realise (later in some cases) that by checking some reviews of nearby places, you could get a sense of that.
So from that question/idea, I built **Neighborhood Vibes**. You search by an address, and an AI agent synthesizes data from across the web to give you the “essence” of that spot.

The “Frankenstein” Stack
As a side note, I’m using my darkaico.com site to be a multi-explorer site, but it's worth mentioning how I typically work towards an idea:
- Ideation: I usually start with **v0** to rapidly prototype the UI. I burned through my credits when I go to deep, but it got the vision out of my head and into code.
- Refinement: I’ve been using **Google Stitch** lately to polish and improve specific elements on the page once the foundation is there. For instance, I recently updated my site buttons using Stitch.
- The Code: I used Cursor as my primary co-pilot to write almost everything. I used Claude to experiment for a short time, but I realised that with the Cursor subscription, I got enough (I also use the cursor-agent from time to time to do some specific terminal actions)
- The Infrastructure: The frontend is a Next.js app running on Vercel, but the “heavy” logic, the engine that talks to the geo-APIs, is a Go API hosted on my own Hetzner server. This keeps the backend snappy and gives me total control over the data processing.
The Tech Stack: One Pin, Four Sources
Back in the neighborhood: I didn’t want to rely on just one source of truth. Every map provider has a “personality.” To get a real vibe, I built a hybrid engine:
- OpenStreetMap (Overpass): The backbone. Great for raw geographic data and categories without the “commercial” bias.
- Google Places: The king of reviews. I use Nearby Search to find IDs, then Place Details to suck out the top 5 reviews for each.
- Foursquare: The “tastes” and “tips” specialist. It adds that layer of “local favorite” sentiment that Google sometimes misses.
- Gemini: The brain. It takes the mess of reviews and categories and synthesizes them into pros, cons, and a summary.
The Exact Coordinate trap
Initially, I had a problem. If User A dropped a pin at 41.3976, 2.1796 (lat/long) and User B dropped a pin 5 meters away at 41.3977, 2.1797, the system treated them as brand-new requests.
Result: Two calls to Google, two calls to Gemini, double the cost for the same neighborhood vibe.
To fix this, I implemented a two-tier lookup strategy based on areas rather than just points: Lets called it “The Grid” (include mysterious techno music here)
I started by rounding coordinates to 4 decimal places (~11 meters). This creates a stable area_id so that if you hit that exact cell, you get an instant Redis/PostgreSQL cache hit.
The Close Enough reuse (150 Meters)
If the exact cell is a miss, I don’t trigger the API. Instead, I search the DB for any “vibe” generated within a 150-meter radius.
- Why 150m? It’s the sweet spot. Too small (50m), and you never get a cache hit. Too large (500m) and you risk giving a “Historic District” vibe to an “Industrial Zone” two blocks away. 150m is roughly one city block.
- Why not PostGIS? I could have used a spatial extension and something like ST_DWithin to do “nearest within 150m” in one query. Instead, the backend does a bounding-box filter in plain SQL (using an index on latitude, longitude), then runs a **Haversine distance** in Go on the small result set and picks the closest within radius. For a table that only grows with unique areas people have queried, that’s plenty fast, and it avoids adding a DB extension and another moving part. If the table ever grew into the hundreds of thousands of points or I needed richer spatial queries, I’d reconsider; for this use case, simplicity won.

bien hecho: well done (thanks Te Lo Resumo)
The Request Pipeline
When you drop a pin, the backend does a detailed (but efficient) search before it spends any money:
- Check Redis: Did someone just ask for this exact spot?
- Check DB (Exact): Is there a stored vibe for this grid cell?
- Check DB (Proximity): Is there a vibe within 150m? If yes: Return it, mark it as
approximate_area: true, and backfill Redis so the next guy gets it even faster. - Generate: If all else fails, fire up the APIs.

Keeping the “Free Tier” Alive
To prevent a single “vibe check” from costing me my lunch money, I implemented a few hard constraints:
- Anonymous first taste, then sign up: The first few vibe checks are free for anonymous users, enough to see if the tool is useful and to explore. After that, creating an account is required. That barrier keeps bots and scripted abuse at bay without punishing curious visitors.
- Source Capping: I merge the sources, but cap the final list at ~50 venues. Gemini doesn’t need 200 venues to tell you a place is “artsy.”
- Review Limits: Google only gives 5 reviews per place via API anyway, so I stick to that to keep the context window small.
- Rate Limiting: To stop a single user from accidentally (or intentionally) nuking my credits, I track Parent Areas (~111m). If you spam pins in the same block within 60 seconds, the system throttles the new generation.
Honesty in the UI
One thing I’m big on: Honest UX. If the system reuses a vibe from 100 meters away, the UI tells you. It says: “Vibe for a nearby area (same neighborhood).” We aren’t pretending the AI is pinpoint-accurate to the square inch; we’re giving you the essence of the block.
LEVEL | SCALE | PURPOSE
---------------|--------|-----------------------------------------
Exact Grid | ~11m | Primary cache key & DB lookup
Parent Area | ~111m | Anti-spam / Idempotency check
Reuse Radius | 150m | Neighborhood vibe "approximation"
Search Radius | 500m | Venue discovery (OSM, Google, Foursquare)
What’s Next?
The Hybrid mode is where it really comes alive. Seeing a local spot through the lens of a Google Review versus a Foursquare Tip gives Gemini the “contextual conflict” it needs to write a truly nuanced summary. It’s no longer just a list of venues; it’s a synthesis of perspectives.
The takeaway: If you’re building something geo-based, don’t just cache by ID, cache by distance. Your API bill (and your latency) will thank you.
Want to see it in action? Try it out at https://www.darkaico.com/en/neighborhood-vibe
Note from the Author
Update: Due to the high volume of requests and API costs, I have temporarily moved the live demo into “Mock Mode” while I optimize the backend connections. You can still see the UI and caching logic in action!
메타데이터
- post_id
- 175c2f8538f6
- slug
- why-i-built-a-vibe-check-for-neighborhoods-and-how-i-kept-the-api-bill-from-killing-me-175c2f8538f6
- url
- https://medium.com/@darkaico/why-i-built-a-vibe-check-for-neighborhoods-and-how-i-kept-the-api-bill-from-killing-me-175c2f8538f6
- canonical_url
- https://medium.com/@darkaico/why-i-built-a-vibe-check-for-neighborhoods-and-how-i-kept-the-api-bill-from-killing-me-175c2f8538f6
- author_url
- https://medium.com/@darkaico
- status
- ok
- fetched_at
- 2026-06-09 15:37:30