Spaces:
Sleeping
Sleeping
| """Popular destinations and cheap flights endpoints.""" | |
| from __future__ import annotations | |
| from datetime import date, timedelta | |
| from fastapi import APIRouter, Query | |
| from ..data_loader import get_route_graph | |
| from ..price_engine import compute_calendar_price | |
| from ..seed_utils import seeded_random | |
| router = APIRouter(prefix="/api/destinations", tags=["destinations"]) | |
| def _cheapest_in_window(origin: str, dest: str, distance_km: int, num_carriers: int, dest_continent: str) -> tuple[float, str]: | |
| """Find the cheapest economy price across the next 90 days. | |
| Returns (cheapest_price, cheapest_date_iso). | |
| """ | |
| from ..benchmark import today as _today | |
| today = _today() | |
| best_price = float("inf") | |
| best_date = today + timedelta(days=14) | |
| for day_offset in range(1, 91): | |
| target = today + timedelta(days=day_offset) | |
| rng = seeded_random("indicative", origin, dest, target.isoformat()) | |
| price = compute_calendar_price( | |
| distance_km=distance_km, | |
| cabin_class="economy", | |
| target_date=target, | |
| num_carriers=num_carriers, | |
| dest_continent=dest_continent, | |
| rng=rng, | |
| ) | |
| if price < best_price: | |
| best_price = price | |
| best_date = target | |
| return best_price, best_date.isoformat() | |
| async def popular_destinations( | |
| origin: str = Query(..., min_length=3, max_length=3), | |
| limit: int = Query(8, ge=1, le=20), | |
| ): | |
| """Return top destinations from an origin, ranked by destination hub_score.""" | |
| graph = get_route_graph() | |
| if origin.upper() not in graph.airports: | |
| return {"destinations": []} | |
| origin_upper = origin.upper() | |
| routes = graph.get_outbound_routes(origin_upper) | |
| origin_airport = graph.airports[origin_upper] | |
| scored = [] | |
| for dest_iata, route in routes.items(): | |
| dest_airport = graph.airports.get(dest_iata) | |
| if not dest_airport: | |
| continue | |
| price, cheapest_date = _cheapest_in_window( | |
| origin_upper, dest_iata, route.distance_km, | |
| len(route.carriers), dest_airport.continent, | |
| ) | |
| scored.append({ | |
| "iata": dest_iata, | |
| "city": dest_airport.city_name, | |
| "country": dest_airport.country, | |
| "country_code": dest_airport.country_code, | |
| "price_usd": round(price), | |
| "cheapest_date": cheapest_date, | |
| "hub_score": dest_airport.hub_score, | |
| "distance_km": route.distance_km, | |
| "is_domestic": dest_airport.country_code == origin_airport.country_code, | |
| }) | |
| # Sort by hub_score descending (most popular destinations first) | |
| scored.sort(key=lambda x: -x["hub_score"]) | |
| return {"origin": origin_upper, "destinations": scored[:limit]} | |
| async def cheap_flights( | |
| origin: str = Query(..., min_length=3, max_length=3), | |
| category: str = Query("popular", pattern="^(domestic|international|popular)$"), | |
| limit: int = Query(8, ge=1, le=20), | |
| ): | |
| """Return cheapest flights from an origin, filtered by category.""" | |
| graph = get_route_graph() | |
| if origin.upper() not in graph.airports: | |
| return {"flights": []} | |
| origin_upper = origin.upper() | |
| routes = graph.get_outbound_routes(origin_upper) | |
| origin_airport = graph.airports[origin_upper] | |
| items = [] | |
| for dest_iata, route in routes.items(): | |
| dest_airport = graph.airports.get(dest_iata) | |
| if not dest_airport: | |
| continue | |
| is_domestic = dest_airport.country_code == origin_airport.country_code | |
| if category == "domestic" and not is_domestic: | |
| continue | |
| if category == "international" and is_domestic: | |
| continue | |
| price, cheapest_date = _cheapest_in_window( | |
| origin_upper, dest_iata, route.distance_km, | |
| len(route.carriers), dest_airport.continent, | |
| ) | |
| items.append({ | |
| "iata": dest_iata, | |
| "city": dest_airport.city_name, | |
| "country": dest_airport.country, | |
| "country_code": dest_airport.country_code, | |
| "price_usd": round(price), | |
| "cheapest_date": cheapest_date, | |
| "distance_km": route.distance_km, | |
| "is_domestic": is_domestic, | |
| }) | |
| # Sort by price ascending (cheapest first) | |
| items.sort(key=lambda x: x["price_usd"]) | |
| return {"origin": origin_upper, "category": category, "flights": items[:limit]} | |