fix: enrich route destination names from airport DB when not stored
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 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ Handles loading and filtering airport data from OpenFlights dataset.
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import csv
|
import csv
|
||||||
|
from functools import lru_cache
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
import urllib.request
|
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")
|
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__":
|
if __name__ == "__main__":
|
||||||
# Build the dataset if run directly
|
# Build the dataset if run directly
|
||||||
download_and_build_airport_data(force_rebuild=True)
|
download_and_build_airport_data(force_rebuild=True)
|
||||||
|
|||||||
@@ -1430,7 +1430,8 @@ async def get_scan_routes(
|
|||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
# Convert to Route models
|
# Convert to Route models, enriching name/city from airport DB when missing
|
||||||
|
lookup = _iata_lookup()
|
||||||
routes = []
|
routes = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
# Parse airlines JSON
|
# Parse airlines JSON
|
||||||
@@ -1439,12 +1440,22 @@ async def get_scan_routes(
|
|||||||
except:
|
except:
|
||||||
airlines = []
|
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(
|
routes.append(Route(
|
||||||
id=row[0],
|
id=row[0],
|
||||||
scan_id=row[1],
|
scan_id=row[1],
|
||||||
destination=row[2],
|
destination=dest,
|
||||||
destination_name=row[3],
|
destination_name=dest_name,
|
||||||
destination_city=row[4],
|
destination_city=dest_city,
|
||||||
flight_count=row[5],
|
flight_count=row[5],
|
||||||
airlines=airlines,
|
airlines=airlines,
|
||||||
min_price=row[7],
|
min_price=row[7],
|
||||||
@@ -1712,6 +1723,12 @@ def get_airport_data():
|
|||||||
return airports
|
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__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from datetime import datetime, date, timedelta
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from database import get_connection
|
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
|
from searcher_v3 import search_multiple_routes
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,10 @@ async def process_scan(scan_id: int):
|
|||||||
else:
|
else:
|
||||||
# Specific airports mode: parse comma-separated list
|
# Specific airports mode: parse comma-separated list
|
||||||
destination_codes = [code.strip() for code in country_or_airports.split(',')]
|
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})")
|
logger.info(f"[Scan {scan_id}] Mode: Specific airports ({len(destination_codes)} destinations: {destination_codes})")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user