FlightWindow helps travelers with flexible dates search across a travel window, compare promising flight options, and understand the trade-offs that matter to them.
Conventional flight search usually starts with one exact departure and return date pair. For travelers with flexible schedules, comparing alternatives means repeating searches and keeping track of differences in price, travel time, and stops.
- Find a route: search airport and city suggestions, or enter IATA codes directly.
- Search a window: choose flexible departure and return ranges, each containing up to 14 dates.
- Compare dates: explore a departure × return matrix of observed fares, with coverage information and lowest-observed highlighting.
- Explore itineraries: select a populated date pair to retrieve detailed flight options, including segments and available cabin/baggage information.
- Compare promising options: view a small, deterministic selection with factual Cheapest, Fastest, and Fewest stops labels and price/time/stop differences.
- Understand trade-offs: optionally request an AI interpretation based on a selected preference and the calculated comparison.
- Inspect all options: expand the full returned offer set, which is collapsed by default.
Detailed flight offers currently use Duffel sandbox/test inventory and are not bookable. Cached discovery prices and sandbox offer prices are separate data sources and are not expected to match.
Choose a route and acceptable dates. FlightWindow requests cached fare observations and places them in the matrix without filling missing combinations. Selecting an observed fare requests detailed itineraries for that exact date pair.
The backend then selects up to five distinct comparison candidates and calculates their numerical differences in Python. Only when the traveler asks for an explanation does it send a compact candidate/evidence set, route, dates, and preferences to OpenAI. The response is validated against the supplied candidate and evidence references before display. The factual comparison remains usable if AI is unavailable.
flowchart TD
U[Traveler] <--> R[React frontend]
R -->|Suggestions, fares, details, or optional explanation| B[FastAPI backend]
B -->|Autocomplete| P[Duffel Places]
P -->|Suggestions| B
B -->|Date-window discovery| T[Travelpayouts cached fares]
T -->|Observations| B
B -->|Selected date pair| D[Duffel detailed flight data]
D --> C[Deterministic Python comparison]
C --> S[Temporary comparison snapshot]
C -->|Offers and comparison| B
S -->|On request: compact facts and preferences| O[OpenAI qualitative interpretation]
O --> V[Structured response and grounding validation]
V --> B
B -->|Normalized results| R
Numerical comparisons are computed by the application before AI interpretation. AI does not search for flights, calculate the displayed differences, or determine a universal “best” option. Price comparisons use decimal arithmetic and do not compare or convert different currencies.
| Layer | Technologies |
|---|---|
| Frontend | React, TypeScript, Vite, CSS |
| Backend | Python, FastAPI, Pydantic, Uvicorn |
| Provider integration | HTTPX, python-dotenv |
| Testing and checks | pytest, Vitest, Testing Library, jsdom, Oxlint, TypeScript |
| Source | Role |
|---|---|
| Travelpayouts | Cached/observed fares from the v2 week-matrix endpoint; observations may be sparse or outdated. |
| Duffel Places | Airport/city suggestions normalized to names, IATA codes, and place types. |
| Duffel flight offers | Exact-date itinerary details using sandbox/test inventory in the current project setup; not bookable. |
| OpenAI | Optional, preference-aware qualitative interpretation of the supplied deterministic comparison. |
Provider credentials stay in the backend. AI requests include the traveler's optional preference text, so the interface advises against entering personal details. AI data sharing happens only when an explanation is requested.
- Python 3.10+ with
pipandvenv. - Node.js 22.22.2+, 24.15+, or 26+ with npm, matching the supported major versions of the locked development dependencies.
- A Travelpayouts token with week-matrix access and a Duffel test access token for suggestions and offer requests.
- Optionally, an OpenAI API key with access to the configured model,
gpt-5.6-luna.
Create a root .env from .env.example only if .env does not already exist:
cp .env.example .envConfigure these variables locally:
TRAVELPAYOUTS_TOKEN=
DUFFEL_ACCESS_TOKEN=
OPENAI_API_KEY=Leave OPENAI_API_KEY empty to use discovery, itineraries, and deterministic comparison without AI. The backend resolves .env relative to the repository structure, independent of the working directory. Existing shell variables take precedence, including explicitly empty values.
.env is gitignored. Never commit credentials or put them in frontend code or VITE_ variables.
From the repository root:
cd backend
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python -m uvicorn main:app --reload --host 127.0.0.1 --port 8000On Windows PowerShell, use python instead of python3 and activate with .venv\Scripts\Activate.ps1.
The independent health check is available at http://127.0.0.1:8000/api/health.
In a second terminal, from the repository root:
cd frontend
npm ci
npm run devOpen http://localhost:5173. Vite uses port 5173 with strict-port checking. The frontend calls the backend at http://127.0.0.1:8000; development CORS permits localhost:5173 and 127.0.0.1:5173.
From the repository root, with the backend virtual environment created:
cd backend
source .venv/bin/activate
python -m pip install -r requirements-dev.txt
python -m pytest -qFrom another terminal at the repository root, after npm ci:
cd frontend
npm test
npm run lint
npm exec -- tsc -b
npm run buildThe suites cover provider normalization and failures, deterministic comparison and grounding validation, date-window planning, sparse matrix construction, autocomplete, drawer interactions, and stale/error recovery. Automated provider tests use mocked responses and do not require live Travelpayouts, Duffel, or OpenAI calls. Synthetic fixtures are used only for testing, not as application data.
- Travelpayouts observations can be sparse or cached. “—” means no fare observation was returned, not that no flight exists. Missing fares are never synthesized, and blank cells do not request detailed itineraries.
- Week-matrix planning assumes an anchor covers two days before through four days after. This assumption is isolated in
frontend/src/lib/discovery.ts; sparse data cannot establish complete date coverage. - Duffel details currently use sandbox/test inventory and are not bookable. The UI requests one adult in economy; there are no end-to-end cabin or stops filters.
- Comparisons describe returned offers, not the entire market. Missing facts remain unknown, currencies are not converted, and candidate selection is capped at five.
- Comparison snapshots are process-local and in memory, expire after ten minutes, and may be evicted earlier from the 64-entry store. Restarts clear them. The current design assumes a single backend worker; expired comparisons can be refreshed from the UI.
- AI interpretation is optional and may fail validation or be unavailable. Grounding checks do not guarantee that every qualitative statement is correct.
- All offers remain accessible, but expanding hundreds of itinerary cards can be expensive to render.
- There is no booking/payment flow, database, account system, saved-trip feature, or price-alert service.
FlightWindow is a student project created at Carnegie Mellon University for an AI-building assignment. It explores how flexible-date discovery, deterministic comparison, and grounded AI interpretation can support travel decisions.
Disclaimer: This is a student project created at Carnegie Mellon University for educational purposes only. It is not affiliated with, endorsed by, or associated with Travelpayouts, Duffel, OpenAI, Carnegie Mellon University, or any airline or brand referenced. All trademarks and brand names are the property of their respective owners.