Full-stack flight price scanner built on fast-flights v3 (SOCS cookie bypass): Backend (FastAPI + SQLite): - REST API with rate limiting, Pydantic v2 validation, paginated responses - Scan pipeline: resolves airports, queries every day in the window, saves individual flights + aggregate route stats to SQLite - Background async scan processor with real-time progress tracking - Airport search endpoint backed by OpenFlights dataset - Daily scan window (all dates, not monthly samples) Frontend (React 19 + TypeScript + Tailwind CSS v4): - Dashboard with live scan status and recent scans - Create scan form: country mode or specific airports (searchable dropdown) - Scan detail page with expandable route rows showing individual flights (date, airline, departure, arrival, price) loaded on demand - AirportSearch component with debounced live search and multi-select Database: - scans → routes → flights schema with FK cascade and auto-update triggers - Migrations for schema evolution (relaxed country constraint) Tests: - 74 tests: unit + integration, isolated per-test SQLite DB - Confirmed flight fixtures in tests/confirmed_flights.json (50 real flights, BDS→FMM Ryanair + BDS→DUS Eurowings, scraped Feb 2026) - Integration tests parametrized from confirmed routes Docker: - Multi-stage builds, Compose orchestration, Nginx reverse proxy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
97 lines
6.5 KiB
JSON
97 lines
6.5 KiB
JSON
{
|
|
"_meta": {
|
|
"description": "Confirmed real flights from live Google Flights queries, scraped via fast-flights v3 with SOCS cookie. Used as ground truth for integration tests.",
|
|
"source_scan_id": 54,
|
|
"origin": "BDS",
|
|
"window": "2026-02-26 to 2026-05-27",
|
|
"scraped_at": "2026-02-25",
|
|
"total_flights": 50
|
|
},
|
|
"routes": {
|
|
"BDS-FMM": {
|
|
"origin": "BDS",
|
|
"destination": "FMM",
|
|
"airline": "Ryanair",
|
|
"flight_count": 39,
|
|
"min_price": 15.0,
|
|
"max_price": 193.0,
|
|
"flights": [
|
|
{"date": "2026-04-01", "departure": "09:20", "arrival": "11:10", "price": 15.0},
|
|
{"date": "2026-03-30", "departure": "18:45", "arrival": "20:35", "price": 21.0},
|
|
{"date": "2026-04-22", "departure": "09:20", "arrival": "11:10", "price": 24.0},
|
|
{"date": "2026-04-02", "departure": "09:40", "arrival": "11:30", "price": 26.0},
|
|
{"date": "2026-04-17", "departure": "19:35", "arrival": "21:25", "price": 27.0},
|
|
{"date": "2026-04-24", "departure": "19:35", "arrival": "21:25", "price": 27.0},
|
|
{"date": "2026-03-29", "departure": "10:05", "arrival": "11:55", "price": 29.0},
|
|
{"date": "2026-05-11", "departure": "18:45", "arrival": "20:35", "price": 30.0},
|
|
{"date": "2026-04-15", "departure": "09:20", "arrival": "11:10", "price": 31.0},
|
|
{"date": "2026-05-07", "departure": "09:40", "arrival": "11:30", "price": 32.0},
|
|
{"date": "2026-04-23", "departure": "09:40", "arrival": "11:30", "price": 34.0},
|
|
{"date": "2026-04-16", "departure": "09:40", "arrival": "11:30", "price": 35.0},
|
|
{"date": "2026-05-20", "departure": "09:20", "arrival": "11:10", "price": 35.0},
|
|
{"date": "2026-04-27", "departure": "18:45", "arrival": "20:35", "price": 40.0},
|
|
{"date": "2026-05-06", "departure": "09:20", "arrival": "11:10", "price": 40.0},
|
|
{"date": "2026-04-20", "departure": "18:45", "arrival": "20:35", "price": 41.0},
|
|
{"date": "2026-04-29", "departure": "09:20", "arrival": "11:10", "price": 41.0},
|
|
{"date": "2026-05-13", "departure": "09:20", "arrival": "11:10", "price": 44.0},
|
|
{"date": "2026-04-26", "departure": "10:05", "arrival": "11:55", "price": 45.0},
|
|
{"date": "2026-05-21", "departure": "09:40", "arrival": "11:30", "price": 46.0},
|
|
{"date": "2026-04-13", "departure": "18:45", "arrival": "20:35", "price": 48.0},
|
|
{"date": "2026-05-14", "departure": "09:40", "arrival": "11:30", "price": 48.0},
|
|
{"date": "2026-05-27", "departure": "09:20", "arrival": "11:10", "price": 48.0},
|
|
{"date": "2026-04-19", "departure": "10:05", "arrival": "11:55", "price": 51.0},
|
|
{"date": "2026-04-03", "departure": "19:35", "arrival": "21:25", "price": 55.0},
|
|
{"date": "2026-04-30", "departure": "09:40", "arrival": "11:30", "price": 58.0},
|
|
{"date": "2026-05-10", "departure": "10:05", "arrival": "11:55", "price": 63.0},
|
|
{"date": "2026-04-05", "departure": "10:05", "arrival": "11:55", "price": 65.0},
|
|
{"date": "2026-04-10", "departure": "19:35", "arrival": "21:25", "price": 72.0},
|
|
{"date": "2026-04-09", "departure": "09:40", "arrival": "11:30", "price": 78.0},
|
|
{"date": "2026-05-25", "departure": "18:45", "arrival": "20:35", "price": 81.0},
|
|
{"date": "2026-05-04", "departure": "18:45", "arrival": "20:35", "price": 82.0},
|
|
{"date": "2026-05-18", "departure": "18:45", "arrival": "20:35", "price": 84.0},
|
|
{"date": "2026-04-08", "departure": "09:20", "arrival": "11:10", "price": 96.0},
|
|
{"date": "2026-05-24", "departure": "10:05", "arrival": "11:55", "price": 108.0},
|
|
{"date": "2026-05-03", "departure": "10:05", "arrival": "11:55", "price": 134.0},
|
|
{"date": "2026-04-06", "departure": "18:45", "arrival": "20:35", "price": 144.0},
|
|
{"date": "2026-04-12", "departure": "10:05", "arrival": "11:55", "price": 146.0},
|
|
{"date": "2026-05-17", "departure": "10:05", "arrival": "11:55", "price": 193.0}
|
|
],
|
|
"notes": "Ryanair operates ~5-6x/week. Two daily slots: morning (09:20/09:40/10:05) and evening (18:45/19:35). Season starts late March 2026."
|
|
},
|
|
"BDS-DUS": {
|
|
"origin": "BDS",
|
|
"destination": "DUS",
|
|
"airline": "Eurowings",
|
|
"flight_count": 11,
|
|
"min_price": 40.0,
|
|
"max_price": 270.0,
|
|
"flights": [
|
|
{"date": "2026-04-04", "departure": "09:20", "arrival": "11:40", "price": 40.0},
|
|
{"date": "2026-05-12", "departure": "19:45", "arrival": "22:05", "price": 90.0},
|
|
{"date": "2026-04-18", "departure": "11:20", "arrival": "13:40", "price": 120.0},
|
|
{"date": "2026-04-25", "departure": "11:20", "arrival": "13:40", "price": 120.0},
|
|
{"date": "2026-05-09", "departure": "11:20", "arrival": "13:40", "price": 120.0},
|
|
{"date": "2026-05-19", "departure": "19:45", "arrival": "22:05", "price": 140.0},
|
|
{"date": "2026-05-23", "departure": "11:20", "arrival": "13:40", "price": 160.0},
|
|
{"date": "2026-05-26", "departure": "19:45", "arrival": "22:05", "price": 160.0},
|
|
{"date": "2026-05-02", "departure": "11:20", "arrival": "13:40", "price": 240.0},
|
|
{"date": "2026-04-11", "departure": "09:20", "arrival": "11:40", "price": 270.0},
|
|
{"date": "2026-05-16", "departure": "11:20", "arrival": "13:40", "price": 270.0}
|
|
],
|
|
"notes": "Eurowings operates Saturdays only (verified: all 11 dates are Saturdays). Two time slots: morning (09:20 or 11:20) and evening (19:45). Cheapest in April."
|
|
}
|
|
},
|
|
"confirmed_dates_for_testing": {
|
|
"description": "Specific (origin, destination, date) tuples confirmed to return >=1 flight from the live API. Safe to use in integration tests without risk of flakiness due to no-service days.",
|
|
"entries": [
|
|
{"origin": "BDS", "destination": "FMM", "date": "2026-04-01", "min_flights": 1, "airline": "Ryanair", "price": 15.0},
|
|
{"origin": "BDS", "destination": "FMM", "date": "2026-04-15", "min_flights": 1, "airline": "Ryanair", "price": 31.0},
|
|
{"origin": "BDS", "destination": "FMM", "date": "2026-05-07", "min_flights": 1, "airline": "Ryanair", "price": 32.0},
|
|
{"origin": "BDS", "destination": "DUS", "date": "2026-04-04", "min_flights": 1, "airline": "Eurowings", "price": 40.0},
|
|
{"origin": "BDS", "destination": "DUS", "date": "2026-04-18", "min_flights": 1, "airline": "Eurowings", "price": 120.0},
|
|
{"origin": "BDS", "destination": "DUS", "date": "2026-05-09", "min_flights": 1, "airline": "Eurowings", "price": 120.0},
|
|
{"origin": "BDS", "destination": "DUS", "date": "2026-05-23", "min_flights": 1, "airline": "Eurowings", "price": 160.0}
|
|
]
|
|
}
|
|
}
|