Inspiration

Choosing a hotel by its nightly rate hides what it does to the rest of your trip: how far you travel each day, what you spend getting around, and which places you can actually fit in. I wanted a planner that chooses the hotel and the itinerary together and shows the arithmetic. I also wanted an AI feature I could defend: AI for language, code for numbers, and every AI output checked before you see it. [YOUR PLACEHOLDER: one personal sentence about why this matters to you]

What it does

Fitinerary plans your hotel and itinerary together. You describe your trip in your own words, the AI turns it into settings you review, and you pick your must-visits (sightseeing, shopping, food and drink). It compares two or three hotels by true cost (room, admissions, transport, and the value of your travel time), then builds a day-by-day schedule on a map. You can lock, move or skip stops and see a before-and-after comparison. If a request is impossible, it says why instead of inventing a plan. A request for a different city offers a switch and plans nothing. The app also works with the AI off.

It covers four cities (Tokyo, Singapore, Seoul, Kuala Lumpur) on clearly labelled sample data. The hotels are made up and travel times are estimates, so it is not for booking real trips.

How we built it

A Python/FastAPI backend with an OR-Tools CP-SAT planner, and a separate validator, written independently, that re-checks every plan against opening hours, travel time, budget and your locked stops. The interface is Vue 3 with a Leaflet map.

The AI (Claude Haiku 4.5, reached through a third-party gateway) works in three places, always on a click: reading a request into editable settings, proposing plan changes you approve, and wording an explanation. The AI never picks the schedule or sets prices. Code checks everything it returns: strict schemas, a destination check before any model call, a grounding check that rejects numbers or place names not in the computed facts, and no way for the model to set safety flags such as removing a must-visit. Place descriptions are never sent to the model. Tests: 642 backend, 260 unit and 55 browser tests.

Challenges we ran into

  • The real model behaved differently from my mocks. The first live runs failed: a parameter Haiku rejects, "the museum" filed under interests instead of asking which museum, and a faithful rewording ("2 hours 38 minutes") that my checker rejected. I reproduced each failure with a mock test first, then fixed it. The live self-check went 16/20, 18/20, 19/20, 19/20, then 20/20.
  • The live red team found a real bug. After a currency confirmation, the form showed a yen-sized default for the value of time in non-yen cities. 2 of 27 live attacks affected the final result before the fix and 0 of 27 after (one model, two runs).
  • A missed-sight charge sized in yen made the planner schedule 1 of 10 sights in Seoul. I tied it to each city's own default value of time. I tuned that after seeing the result, so it is a modelling choice, not a finding.

Accomplishments that we're proud of

  • The AI never decides the plan, and the independent validator passed every plan the solver produced in evaluation (53 of 53 feasible Tokyo plans; 0 validator failures in the other three cities).
  • Evidence with its limits stated: a 20-check live smoke test (tuned on those checks), 48 of 48 on a hand-written prompt set (small sample, one model), and 48 attacks in four languages (744 simulated runs, final result affected in 0 after fixes).
  • An honest measurement of the value: where optimised routes and the input-order baseline covered the same sights (15 of 60 Tokyo trips), routes used 14.1% less travel. The cheapest-rate hotel was not the best by true cost on only 4 of 52 Tokyo trips at the default value of time, so the benefit shows in some trips, not most. [YOUR PLACEHOLDER: your proudest moment]

What we learned

  • Mock tests show the pipeline holds even if the model misbehaves. They say nothing about the real model, and live runs found bugs mocks never would.
  • Keeping the AI away from the numbers made the system easier to test and to trust.
  • Say the weak results out loud: sample data, small samples, one model. [YOUR PLACEHOLDER: your personal takeaways]

What's next for Fitinerary

  • Human-verified data for one city. Singapore has 7 fields source-checked against official pages, 0 human-verified, and 75 still unchecked.
  • Preferences like less walking, quieter places and accessibility. These need real data first, and the app would ask you to confirm each assumption.
  • Real user tests and an independent test set written by someone other than me.
  • Transport modes, travel with family, cited explanations, and more cities, added as a data folder (see docs/CITIES.md).

Built With

Share this project:

Updates

Submission history