LET’S TALK
← ALL WRITING
DATA / PRODUCT ENGINEERINGProject story / 8 MIN READ

A travel budget is more than a price.

How a planning calculator grew into a mixed-source budget, and why quote ownership, currency assumptions and missing data matter.

Making travel estimates explainable actual application interfaceFROM THE WORKING DEMO / INTERFACE CAPTURE
WHAT YOU’LL UNDERSTAND
  • Separate a planning estimate from a dated provider quote.
  • Follow a quote through ownership, route and freshness checks.
  • Calculate group and per-person currency conversions.
  • Understand graceful behaviour when a provider omits data.
THE PROJECT IN 69 SECONDSENGLISH AUDIO + CAPTIONS

Actual interface captures and explanatory diagrams. English narration uses the synthetic Microsoft Zira Desktop voice.

Read the full video transcript

Begin with the whole journey

A flight price is only one part of a trip. Travel Estimator brings transport, accommodation, daily spending, extras, and a contingency into one explained planning budget.

Keep the source visible

Base costs are illustrative estimates. Configured providers can return dated flight quotes and venue information. The interface distinguishes those sources instead of presenting every number as a current booking price.

Protect the selected quote

Before applying a flight, the server checks who selected it, the route, travel dates, and party size. A selection older than fifteen minutes must be refreshed. The browser cannot simply invent verified quote metadata.

Make the price basis explicit

For example, seven hundred dollars at an assumed rate of two hundred eighty Pakistani rupees becomes one hundred ninety-six thousand rupees for a group. If it is a per-person fare, two travellers double that flight amount.

Replace once, then recalculate

The selected flight replaces the sample transport line once. Other costs remain. The subtotal and contingency are recalculated, while baggage, transfers, and seller fees still need separate confirmation.

Be honest when data is missing

A provider may return no photos, no usable offers, or a quota error. The app explains that result. It does not replace missing live data with invented bargains. A saved plan is a snapshot, not a reservation.

Download transcript ↓

Start with the decision, not the calculator

Travel Estimator began as database coursework, with users, travel details, hotels, bookings and pricing records. That is a useful relational foundation, but a visitor making a trip decision does not experience tables first. They experience uncertainty: can the whole trip fit their budget, which numbers are current, and what has been left out?

The modern browser edition grew around that decision. It combines explainable estimates, destination exploration, private saved plans, group planning and configured provider integrations. Its cinematic interface is an invitation to explore, but the engineering has to make the total understandable after the animation stops. A beautiful total with unclear assumptions is still an unclear total.

This is not a booking engine. No flight or hotel is reserved and no payment is accepted. Some values are sample allowances, some are amounts entered by the traveller, and a configured provider can supply a dated flight quote. The central design problem is allowing those sources to coexist without making them look equally certain.

A budget needs a vocabulary of sources

The calculation engine returns cost lines, not only a final number. Transport, accommodation, a stay-tax allowance, food, local transport, experiences, visa and insurance budgets, other extras and contingency are separate entries. A line includes an amount and an explanation of its formula. Source labels distinguish estimates, entered amounts, a selected provider quote and calculated values.

This distinction is practical. “Your amount” means the traveller entered a whole-trip value; it does not mean a provider independently confirmed it. “Provider quote” means a particular response was retrieved under a particular search context; it does not mean a reservation exists. The contingency is calculated from the current subtotal and is not an extra fee charged by an airline.

The engine also treats explicit zero differently from missing data. If someone enters zero for an applicable cost override, the system should not accidentally replace that zero with a default allowance. That is why tests include zero-value overrides alongside negative, non-finite and unsupported inputs.

Follow a live quote through the system

A flight search has a context: origin, destination, departure, return date and traveller count. The browser sends that context to a server route. The server connector keeps the private key away from the client and normalises the provider's response into a consistent application shape.

FOLLOW THE DATA / EXPLANATORY WORKFLOW
  1. 01

    Describe the journey

    Origin, destination, dates and traveller count define the search context.

  2. 02

    Retrieve and normalise

    A private server connector resolves provider references into airline and itinerary details.

  3. 03

    Validate the selection

    The backend checks session ownership, matching route and a 15-minute selection window.

  4. 04

    Replace, then recalculate

    Replace the flight cost once, keep other budget lines, and recalculate the contingency.

Normalisation is more than renaming a price field. Providers can describe airlines, legs, places and agents through references. The application resolves supplied relationships so the interface can show carrier names, flight numbers, airports, connections and durations when the response includes them. Missing information stays missing instead of receiving a fabricated default.

The cheapest handful of results is not always the best representation of the response. The implemented normaliser retains inexpensive quotes and also preserves additional airline variety within a bounded result set. This is why asking for airline names led to work in the normaliser as well as the card layout. A filter cannot show a carrier that the application discarded before rendering.

The provider's coverage remains a constraint. A missing airline in one returned search is not evidence that the airline never operates the route. Likewise, a provider's returned price is not a promise that the same conditions will still be bookable when the traveller reaches the seller.

The browser cannot approve its own selection

Showing an offer and applying it to a budget are different actions. The server stores each selectable offer with a session-owned selection identifier and its original search context. When a traveller applies it, the server verifies ownership, checks that the route and party still match, and rejects a selection older than fifteen minutes.

typescript / IMPLEMENTATION EXCERPT
export async function selectFlight(b:Record<string,unknown>,u:TravelUser){const input=b.input as TripInput;
if(!input||input.transport!=='flight')throw Error('Choose flight transport before applying a flight quote.');
const d=db(),row=d.prepare('SELECT * FROM flight_search_selections WHERE id=? AND user_id=?').get(String(b.selectionId),u.id);
if(!row)throw Error('Flight quote not found for this session. Search again.');
if(canonical(route(input))!==canonical(JSON.parse(String(row.trip))))throw Error('The quote belongs to different dates, route or travellers. Search again.');
if(Date.now()-Date.parse(String(row.retrieved_at))>900000)throw Error('This search is over 15 minutes old. Refresh flight prices before selecting.');
if(!['group','per_person'].includes(String(b.basis)))throw Error('Choose whether the provider quote is a group or per-person amount.');
// Further currency validation and persistence follow in the source.

That boundary prevents a subtle mistake: trusting a complete quote object merely because the browser sent one. A caller could otherwise edit its amount, exchange rate or traveller count. The implemented path stores a quote snapshot and validates its metadata against the stored session-owned version when an estimate is used or saved.

Changing the trip is therefore meaningful. If dates, destination, party size or transport mode change, a selected flight should not silently survive as if it still described the new journey. The engine also rejects incompatible quote input. The client can help by clearing an obsolete selection, but the server remains responsible for enforcing its own checks.

This teaching playground intentionally exercises calculation only. It does not call a provider or impersonate that selection endpoint. The real application adds the ownership and stored-metadata checks described above.

Work the conversion by hand

Suppose a fictional example quote is USD 700 and the chosen conversion assumption is PKR 280 per USD. If USD 700 is the group total, the flight amount is 700 × 280 = 196,000 rupees. If it is per person for two travellers, the flight amount is 700 × 280 × 2 = 392,000 rupees.

Neither interpretation should be guessed from the size of the number. The interface asks the user to choose a basis and records the assumption. The reference-rate option and a manually entered rate are also different sources, with their own timestamps and limitations. A reference rate is not automatically the exact rate a card issuer or seller will apply.

WORK IT THROUGH / INTERACTIVE EXAMPLE

Replace the flight, not the whole budget.

700 USD × 280 PKR/USD = PKR 196,000
With sample flightPKR 591,177
With example quotePKR 597,777

New subtotal: PKR 543,434. Recalculated contingency: PKR 54,343. Baggage and supplier charges still require confirmation.

This uses the application's actual estimate() function with fixed teaching dates and a fictional quote. No provider calls are made. The real selection route additionally checks session ownership, freshness and stored metadata.

Now follow the rest of the total. The selected flight replaces the transport line once; it is not added on top of the sample flight fare. Accommodation, daily allowances and entered extras remain in the estimate. The subtotal changes, then the contingency is recalculated from that changed subtotal. A per-person headline derived from the total is a presentation of the same budget, not a second charge.

The real calculation function performs validation before returning those numbers. It checks valid calendar dates, a stay associated with the selected destination, room capacity, bounded numeric inputs and supported transport modes. A rejected input is better than a confident-looking total computed from an impossible room arrangement or a non-finite conversion rate.

A provider failure is part of the product

During refinement, the flight screen could show an optional sample table while the visitor expected airline results. Making the live search prominent and collapsing the illustrative tools made the distinction easier to understand. Another visible issue was the local daily safeguard: once used, a new upstream search was blocked before the provider was contacted.

That message must not be confused with the provider account's actual credit balance. A local limit protects the demo from repeated calls; it does not prove the provider has run out of credit. Recently cached matching searches may still be available. The implementation's limit window starts from its first counted request rather than resetting automatically at midnight.

The same honesty applies to restaurant discovery. The configured Places key returned venue details and ratings in the checked responses, while some photo and review fields were absent. The app can offer the venue's Google Maps link, but it should not substitute an unrelated stock image and label it a returned restaurant photo.

The actual Travel Estimator interface. Its base costs remain illustrative; provider results are retrieved separately in the workspace.
travel enlarged interface

The actual Travel Estimator interface. Its base costs remain illustrative; provider results are retrieved separately in the workspace.

Empty, stale, unavailable and quota-protected states all need usable messages. They should help a person decide whether to retry, adjust the search, inspect a source or continue planning with clearly labelled assumptions. They should not manufacture fresh-looking results to preserve the aesthetic of a full screen.

Save what was decided, not a moving target

A saved plan should explain the assumptions used when it was saved. If exchange rates, sample prices or provider responses change later, silently rewriting the old total would make comparison unreliable. Stored snapshots preserve a decision context; reopening a plan can then make any refreshed assumptions explicit.

Private guest and traveller sessions scope access to plans. Group boards add a separate membership boundary with viewer and editor responsibilities. A plan belonging to one account should not become accessible simply because another visitor guesses an identifier. Those checks are part of the usefulness of saved planning, not an optional layer added after the interface is complete.

The group features also distinguish commitments, expenses and remaining budget. Splitting an indivisible number of rupees needs a reproducible rounding rule so the shares sum to the original amount. A well-presented division that loses or invents a rupee is still a calculation defect. The existing tests exercise that conservation property.

Test the boundaries that change the answer

The travel checks cover arithmetic totals, room capacity, date validation, quoted-flight replacement, currency assumptions, route mismatch and malformed provider data. Tests also preserve airline details during normalisation and ensure a missing price never becomes an invented zero-price bargain.

For a student, a productive exercise is to change one assumption at a time. Switch the quote from group to per person. Enter zero for food. Change the party size after selecting a flight. Remove a usable exchange rate. For each action, predict whether the system should calculate, clear a selection or refuse the operation before trying it in the interface.

The project does not need to claim a trained machine-learning model to be technically interesting. Its current contribution is making a mixed-source planning budget understandable, keeping selected data tied to its context, and treating provider uncertainty as a first-class state. The original MySQL repository remains the coursework foundation; the local modern edition supplies the browser application described here.