From ef5a27097d06ebdfe2f81f99b556ea6bdc8799b0 Mon Sep 17 00:00:00 2001 From: domverse Date: Fri, 27 Feb 2026 21:04:46 +0100 Subject: [PATCH] fix: enrich route destination names from airport DB when not stored MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specific-airports mode scans never resolved full airport names — they stored the IATA code as destination_name. Fixed in two places: - airports.py: add lookup_airport(iata) cached helper - api_server.py: enrich destination_name/city on the fly in the routes endpoint when the stored value equals the IATA code (fixes all past scans) - scan_processor.py: resolve airport names at scan time in specific-airports mode using lookup_airport (fixes future scans at the DB level) Co-Authored-By: Claude Sonnet 4.6 --- flight-comparator/airports.py | 20 ++++++++++++++++++++ flight-comparator/api_server.py | 25 +++++++++++++++++++++---- flight-comparator/scan_processor.py | 7 +++++-- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/flight-comparator/airports.py b/flight-comparator/airports.py index 48a6502..08b785c 100644 --- a/flight-comparator/airports.py +++ b/flight-comparator/airports.py @@ -6,6 +6,7 @@ Handles loading and filtering airport data from OpenFlights dataset. import json import csv +from functools import lru_cache from pathlib import Path from typing import Optional import urllib.request @@ -225,6 +226,25 @@ def resolve_airport_list(country: Optional[str], from_airports: Optional[str]) - raise ValueError("Either --country or --from must be provided") +@lru_cache(maxsize=1) +def _all_airports_by_iata() -> dict: + """Return {iata: airport_dict} for every airport. Cached after first load.""" + if not AIRPORTS_JSON_PATH.exists(): + download_and_build_airport_data() + with open(AIRPORTS_JSON_PATH, 'r', encoding='utf-8') as f: + airports_by_country = json.load(f) + return { + a['iata']: a + for airports in airports_by_country.values() + for a in airports + } + + +def lookup_airport(iata: str) -> dict | None: + """Look up a single airport by IATA code. Returns None if not found.""" + return _all_airports_by_iata().get(iata.upper()) + + if __name__ == "__main__": # Build the dataset if run directly download_and_build_airport_data(force_rebuild=True) diff --git a/flight-comparator/api_server.py b/flight-comparator/api_server.py index e43b049..2e49566 100644 --- a/flight-comparator/api_server.py +++ b/flight-comparator/api_server.py @@ -1430,7 +1430,8 @@ async def get_scan_routes( rows = cursor.fetchall() conn.close() - # Convert to Route models + # Convert to Route models, enriching name/city from airport DB when missing + lookup = _iata_lookup() routes = [] for row in rows: # Parse airlines JSON @@ -1439,12 +1440,22 @@ async def get_scan_routes( except: airlines = [] + dest = row[2] + dest_name = row[3] or dest + dest_city = row[4] or '' + + # If name was never resolved (stored as IATA code), look it up now + if dest_name == dest: + airport = lookup.get(dest, {}) + dest_name = airport.get('name', dest) + dest_city = airport.get('city', dest_city) + routes.append(Route( id=row[0], scan_id=row[1], - destination=row[2], - destination_name=row[3], - destination_city=row[4], + destination=dest, + destination_name=dest_name, + destination_city=dest_city, flight_count=row[5], airlines=airlines, min_price=row[7], @@ -1712,6 +1723,12 @@ def get_airport_data(): return airports +@lru_cache(maxsize=1) +def _iata_lookup() -> dict: + """Return {iata: airport_dict} built from get_airport_data(). Cached.""" + return {a['iata']: a for a in get_airport_data()} + + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/flight-comparator/scan_processor.py b/flight-comparator/scan_processor.py index 4789586..f7e77c5 100644 --- a/flight-comparator/scan_processor.py +++ b/flight-comparator/scan_processor.py @@ -15,7 +15,7 @@ from datetime import datetime, date, timedelta import json from database import get_connection -from airports import get_airports_for_country +from airports import get_airports_for_country, lookup_airport from searcher_v3 import search_multiple_routes @@ -180,7 +180,10 @@ async def process_scan(scan_id: int): else: # Specific airports mode: parse comma-separated list destination_codes = [code.strip() for code in country_or_airports.split(',')] - destinations = [] # No pre-fetched airport details; fallback to IATA code as name + destinations = [ + lookup_airport(code) or {'iata': code, 'name': code, 'city': ''} + for code in destination_codes + ] logger.info(f"[Scan {scan_id}] Mode: Specific airports ({len(destination_codes)} destinations: {destination_codes})") except Exception as e: