Inspiration

Every AI travel tool we'd ever used did the same thing: you type in a destination, a budget, and some interests — and it hands you a giant itinerary in one shot. You wait, you scroll, and you forget most of it before you've even left the couch.

But that's not how real travel feels. Real travellers decide one day at a time — shaped by how tired they are, what's left in the wallet, what they saw yesterday, and the small surprises along the way. We wanted to capture that feeling before anyone spends a single cent.

What it does

You enter your destination, days, budget, and travel style once. Gemini writes Day 1 in full — time, place, a five-sense story, dialogues with locals, cultural tips. Then it asks: "Where will you go tomorrow?" and offers 3–4 forward options. You pick one, and it becomes the seed for the next day. The loop continues until your journey is complete — then exports into a real, executable plan.

Crucially, every option is geographically reachable, within the remaining budget, and never a repeat of an experience you've already had. Every user's journal is genuinely their own.

How we built it

A single-gateway modular monolith. The frontend speaks to exactly one endpoint. Every request carries an X-Operation-Type header, and a gateway routes it to decoupled logical microservicesjourney, budget, geo, chat, user, waypoint — that never import each other. They communicate through an in-process async RPC registry, giving us the location transparency of a gRPC stub while staying a single deployable on Cloud Run.

  • Frontend: React + Vite — a step-by-step journey wizard with a live map.
  • Backend: Python, FastAPI (fully async), MongoDB via the Motor driver.
  • AI: Google Gemini on Vertex AI generates each day as structured JSON.
  • Data: MongoDB Atlas — GeoJSON + a 2dsphere index for reachability, Voyage AI embeddings + Vector Search for "vibe" matching and deduplication, Atlas Search over saved journals, and the Aggregation Pipeline for budget analytics.

Money is never a float. Floating-point money drifts ($0.1 + 0.2 \neq 0.3$ in IEEE-754), so every amount is an integer count of the currency's minor unit (MYR 44.00 → 4400), with the exponent looked up from ISO 4217 — never hardcoded — and serialized as a string on the wire so JavaScript clients stay safe above $2^{53}$.

The geography guard. We never let Gemini invent coordinates. A $geoNear query runs before the model reasons, returning only points where

$$ d(\text{current}, \text{candidate}) \leq d_{\max} \quad (\text{default } 200\,\text{km}). $$

The budget guard — the "× 1.5" daily allowance written as pure integer math:

$$ \text{ceiling} = \left\lfloor \frac{\text{remaining} \times 3}{\text{days} \times 2} \right\rfloor, \qquad \text{approve} \iff \text{cost} \leq \text{ceiling}. $$

Challenges we ran into

  • Geo + Vector can't share a pipeline. In Atlas, both $geoNear and $vectorSearch must be the first stage of an aggregation — so candidate retrieval became two steps: reach first, then rank by vibe.
  • Keeping the agent on a leash. Balancing "let Gemini be creative" against "never offer an unreachable or over-budget option" took several iterations of pushing the hard guards out of the prompt and into deterministic code.
  • Money that survives a JavaScript frontend. Reconciling integer minor units in the database with JS's unsafe-integer ceiling required a strict string-on-the-wire contract at every boundary.
  • One trace across many services. Propagating a single Trace ID through the async RPC layer — so a failure deep in the geo service still logs under the originating request — took a ContextVar and careful middleware ordering.

Accomplishments that we're proud of

  • A working day-by-day loop. Gemini actually writes a structured, immersive journal day, the choice engine offers reachable and on-budget options, and the selection seeds the next day — the core experience runs end-to-end.
  • Determinism we can trust. The geographic and budget guards live in code and the database, not in the prompt — so the agent cannot hallucinate an impossible trip or blow the budget, by construction.
  • Financial-grade money. Zero floats touch money anywhere in the system; every amount is exact integer arithmetic with multi-currency support.
  • Production-shaped observability. A unique Trace ID on every request, echoed in the response header and stamped on every log line across per-service folders, so one grep <trace-id> reconstructs a full request:

[2026-05-27 16:38:20] [c18578e0…] [INFO] - [minimap.journey]: ...

  • An architecture with a clean exit. One process to deploy today, real service boundaries we can split into separate processes tomorrow — with no change to any caller.

What we learned

  • Determinism belongs in code, not the prompt. Asking Gemini to "respect the budget and geography" doesn't work reliably; moving those guards into the database and Python made the whole system trustworthy. The model writes the story; the code enforces the rules.
  • MongoDB's document model fits travel like a glove. A hostel night and a sunrise hike need totally different fields — and GeoJSON + Vector Search turned "somewhere quiet, mountainous, and reachable from here" into a query instead of a research project.
  • A modular monolith buys speed and a clean exit — invaluable under a hackathon clock.

What's next for Mini-Map

  • Wire Gemini to the MongoDB MCP Server so the agent reads and writes through typed, guarded tools instead of orchestration code.
  • Seed the locations cache at city scale and turn on Vector Search dedup.
  • Ship the one-click "virtual → real" itinerary export and the Virtual Passport world map.

Built With

Share this project:

Updates