---
name: travel-cost-estimator
description: "Use this skill whenever the user needs a cost estimate for a business trip — including \"what will it cost to get to X\", \"price up that customer visit\", \"I need a travel budget for the offsite\", or a manager asking for a number before approving travel. Prices flights and hotels live from the company's browser-based booking tool, benchmarks the non-bookable lines against the user's own approved expense reports, and produces a forwardable estimate. Do NOT use this skill to actually book, hold, or confirm travel."
---
# Travel cost estimator

Produce a defensible trip estimate from **live fares and rates**, not guesses, and
present it so the user can forward or approve it in seconds.

The estimate has two halves. Flights and hotels come from the corporate booking tool.
Ground transport, meals, and parking are not in the booking tool, so they get
benchmarked against what the user has actually been reimbursed before.

---

## HARD GUARDRAIL — read this first

**The corporate booking tool is a live booking system. Real money, real tickets,
real change fees, real non-refundable segments.**

You are here to **search and price only.**

**Never click:** `Continue` past fare selection · `Select hotel` into a booking
funnel · `Book` · `Reserve` · `Hold` · `Confirm` · anything on a traveller-details
or payment page.

Reading fares off a **results page** is safe — results and the booking funnel are
separate pages, and the results page is where you stop. That line is the whole
guardrail.

If a flow ever lands on a page showing traveller details, payment fields, or a
confirm button: **stop immediately, do not click anything, and tell the user where
you are.** Do not try to navigate back out by clicking through.

Every estimate you produce must state **"Nothing booked."** explicitly.

---

## Step 0 — establish the traveller's preferences

Before the first search, confirm (or reuse from a previous run):

| | |
|---|---|
| Home airport | |
| Preferred airline / alliance | |
| Preferred hotel chain | |
| Cabin class permitted by policy | |
| Trip dates and destination | |
| Rental car likely? | |

Do not assume a home airport from the user's city — many metros have several, and
the wrong one silently produces a plausible, useless number.

Record these preferences so later runs skip this step, but **re-confirm the dates
every time.**

---

## Step 1 — reach the booking tool

Use the company's SSO entry point for the booking tool (commonly a corporate
shortlink or an intranet tile) so the existing session is reused.

**Two failure modes worth knowing:**

1. **Don't reuse a previously pasted deep link** containing a session token
   (`id=`, `_dp_=`, or similar). These are session-scoped and expire, and the
   symptom is misleading — you land on a generic *"Cookies are disabled"* error
   rather than a session-expired message. Go back to the clean entry point.
2. **Don't use the vendor's direct login URL.** It presents a username/password
   form with no SSO path. **Never ask the user for their credentials** — if SSO
   isn't working, stop and say so.

---

## Step 2 — price the flight

Navigate to the flight search. The DOM patterns below are typical of enterprise
booking portals (Cytric/Amadeus, and similar Concur/Egencia-family tools) and will
save a lot of wasted time:

**1. Origin and destination are comboboxes, not text inputs.**

```js
await page.getByRole('combobox', { name: /arrival location/i }).click();
await page.keyboard.type('Orlando');
await page.waitForTimeout(3500);           // suggestion lookup is server-side and slow
await page.getByRole('option', { name: /Orlando International \(MCO\)/i }).click();
```

Pick the **airport** option, not the city option. Metro-area city entries
(e.g. a city code covering several fields) resolve to a different, less useful
inventory set for air search.

**2. Airline filter is another combobox** — type the carrier, click the option,
then press `Escape` to close the dropdown. Leaving it open blocks the search button.

**3. Date fields are hidden inputs behind a calendar widget.** Clicking the visible
date label opens a date picker that is awkward and flaky to drive. Set the hidden
inputs directly with the native setter and dispatch the events the framework
listens for:

```js
await page.evaluate(({ depart, ret }) => {
  function setVal(name, value) {
    const el = document.querySelector(`input[name="${name}"]`);
    if (!el) return;
    const setter = Object.getOwnPropertyDescriptor(
      window.HTMLInputElement.prototype, 'value'
    )?.set;
    if (!setter) return;
    setter.call(el, String(value));
    ['input', 'change', 'blur'].forEach(e =>
      el.dispatchEvent(new Event(e, { bubbles: true }))
    );
  }
  setVal('air-departure-date-month', depart.month);
  setVal('air-departure-date-day', depart.day);
  setVal('air-departure-date-year', depart.year);
  setVal('air-return-date-month', ret.month);
  setVal('air-return-date-day', ret.day);
  setVal('air-return-date-year', ret.year);
}, { depart: { month: 8, day: 26, year: 2026 }, ret: { month: 8, day: 27, year: 2026 } });
```

Field names vary by tool — inspect once and reuse. The **technique** transfers even
when the names don't: assigning `.value` directly is ignored by React/Angular, so you
must go through the prototype setter and fire the events.

**4. Search, then wait ~20 s.** Results are genuinely slow. Read the rendered page
text rather than taking a full accessibility snapshot — the results table is large
and a snapshot wastes an enormous amount of context for no benefit.

**Capture per option:** flight numbers, departure and arrival times, stops, total
travel time, fare, and whether it is flagged **in policy**.

---

## Step 3 — price the hotel

Start from a **fresh session at the entry point** rather than back-navigating out of
flight results — back-navigation frequently restores a half-populated form.

**DOM patterns:**

1. **The location control is often a select2-style widget.** The real
   `select[name="location"]` is `aria-hidden` and cannot be clicked. Drive the
   visible proxy instead:

   ```js
   await page.locator('span.select2-selection[role="combobox"]').first().click();
   await page.keyboard.type('Orlando');
   await page.waitForTimeout(4500);
   await page.locator('li[role="option"], .select2-results__option').first().click();
   ```

2. **Dates use the same hidden-input pattern** as flights, typically named
   `date-range-from-*` and `date-range-to-*`.

3. **The search click may time out waiting for navigation.** That's usually fine —
   the form submitted and the page updated in place. Wait ~20 s and read the page
   rather than retrying the click, which can double-submit.

4. **Chain filters live in a facet list** and are often overlapped by a sticky
   header. Click with `{ force: true }`, then wait ~12 s for the results to refilter.

**Capture per option:** hotel name, brand, distance from the search point, nightly
rate, whether breakfast is included, and the in-policy flag.

---

## Step 4 — fill the non-bookable lines

Ground transport, meals, and parking aren't in the booking tool. **Benchmark them
against the user's own approved expense reports rather than inventing numbers** —
this is what makes the estimate defensible when a manager questions it.

Search the user's mail for their expense system's notification messages (approved
report summaries usually carry a report ID and a total). Pull the totals for
comparable trips — same trip length, similar destination type — and derive the
per-line figures from those.

Then state each non-bookable line **as an explicit assumption**, e.g.:

- Ground transport (rideshare, both directions)
- Meals (per diem x days)
- Airport parking (x days) — or omit if arriving by other means

Never present these as if they were priced. Label them "assumption" in the table.

**If a rental car is plausible, say so.** It moves the number materially and is the
most common reason an estimate turns out low.

---

## Step 5 — build the estimate

Write the full working to a file so it can be revisited, and keep the chat response
short.

Structure:

1. **Header** — customer or purpose, traveller, route, dates, and the date priced
2. **Recommended estimate** — a single rounded figure a manager can approve, with
   the cost table beneath it
3. **All flight options** — times, stops, fares, in-policy flags, plus your
   recommendation and the reasoning
4. **All hotel options** — preferred chain first, alternatives after
5. **Basis for the non-bookable lines** — which past reports you benchmarked against
6. **Caveats** — live pricing is not held, fares drift daily, **nothing booked**

### Two judgement rules

**Always surface a materially cheaper alternative, even when it breaks the stated
preference.** Give the trade-off — cost vs. proximity vs. loyalty status — and let
the traveller choose. Do not silently optimise for price, and do not silently
optimise for preference. Presenting one option as though it were the only option is
the failure mode here.

**Recommend the non-stop when the time saved is significant.** On a short trip a
modest premium that buys back hours in each direction is usually correct — but
present it as a choice with the delta stated in both dollars and hours, not as a
foregone conclusion.

---

## Step 6 — send it on (only when asked)

If the user asks you to forward the estimate to their manager or approver:

**Resolve the recipient by UPN, not by display alias.** A vanity SMTP alias
(`First.Last@company.com`) usually works for mail but **fails for Teams chat
creation with `user_not_found`** — Teams resolves on the UPN
(`flast@company.com`). Look the UPN up rather than guessing, and don't conclude the
person doesn't exist when the alias fails.

**Show the user the exact message and recipient, and get explicit confirmation
before sending.** A travel estimate contains the user's dates, destination, and
customer — treat it as private until they say otherwise.

Message shape — keep it to a screen:

- One line of context (purpose, dates)
- **The headline number, in bold**
- The cost table
- The one or two judgement calls, stated as explicit questions rather than buried
  in prose
- "Fares are live as of today and in policy. **Nothing booked yet.**"

Approvers want the number and the options, not the process.

---

## Output style

Lead with the number. Put the trade-offs where they cannot be missed. Never present
a single option as if it were the only one. Always state that nothing was booked.