from fastapi import FastAPI, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from pydantic import BaseModel import requests import pandas as pd from datetime import datetime, date import google.generativeai as genai import os import json from dotenv import load_dotenv from sheets_client import add_subscriber_to_sheet import random load_dotenv() app = FastAPI( title="Aussie Backpacker Flow API", description="Provides flight demand analysis and AI-powered insights for Australian hostels.", version="1.0.0" ) origins = ["*"] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) FLIGHT_API_KEY = os.getenv("FLIGHT_API_KEY") GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") if GEMINI_API_KEY: try: genai.configure(api_key=GEMINI_API_KEY) except Exception as e: print(f"Could not configure Gemini API: {e}") AUSTRALIAN_CITY_CODES = { "Sydney": "SYD", "Melbourne": "MEL", "Brisbane": "BNE", "Perth": "PER", "Adelaide": "ADL", "Canberra": "CBR", "Gold Coast": "OOL", "Cairns": "CNS", "Hobart": "HBA", "Darwin": "DRW" } class NewsletterPayload(BaseModel): email: str favDestinations: list[str] = [] travelOrigin: str = "" dob: str = "" class ChatPayload(BaseModel): message: str def fetch_flight_data(api_key, departure_airport, arrival_airport, date_str): url = f"https://api.flightapi.io/onewaytrip/{api_key}/{departure_airport}/{arrival_airport}/{date_str}/1/0/0/Economy/AUD" try: response = requests.get(url, timeout=30) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: print(f"Error fetching flight data: {e}") return None def parse_and_process_data(data): if not data or 'itineraries' not in data or not data.get('itineraries'): return pd.DataFrame() carriers = {c['id']: c for c in data.get('carriers', [])} flight_options = [] for i, itinerary in enumerate(data.get('itineraries', [])): price_info = itinerary.get('pricing_options', [{}])[0].get('price', {}) price = price_info.get('amount') leg_id = itinerary.get('leg_ids', [None])[0] leg = next((l for l in data.get('legs', []) if l['id'] == leg_id), None) if not all([price, leg]): continue marketing_carrier_id = leg.get('marketing_carrier_ids', [None])[0] carrier_info = carriers.get(marketing_carrier_id, {}) airline_name = carrier_info.get('name', "Unknown Airline") flight_number = "N/A" if leg.get('segment_ids'): segment_id = leg['segment_ids'][0] segment = next((s for s in data.get('segments', []) if s['id'] == segment_id), None) if segment: carrier_code = carrier_info.get('code', "XX") flight_num_part = segment.get('marketing_flight_number', leg['id'][:3]) flight_number = f"{carrier_code}{flight_num_part}" flight_options.append({ "id": i, "airline": airline_name, "flight": flight_number, "departure": pd.to_datetime(leg['departure']).strftime('%H:%M'), "arrival": pd.to_datetime(leg['arrival']).strftime('%H:%M'), "price": price }) return pd.DataFrame(flight_options).sort_values(by="price").reset_index(drop=True) def get_dashboard_ai_analysis(df, origin, destination, date_str): if not GEMINI_API_KEY or df.empty: return "AI analysis could not be performed due to a configuration issue or lack of data." cheapest_flight = df.iloc[0] data_summary = f"""Context: Flight search from {origin} to {destination} for {date_str}. Total flights: {len(df)}. Price Range: ${df['price'].min():.2f} to ${df['price'].max():.2f}. Cheapest: {cheapest_flight['airline']} for ${cheapest_flight['price']:.2f}.""" prompt = f"You are a sharp, concise market analyst for 'Aussie Backpacker Flow'. Based on the following summary, provide actionable insights in markdown bullet points: {data_summary}. Focus on: a one-sentence market snapshot, top budget carriers, demand interpretation, and one specific marketing tip for a hostel manager." try: model = genai.GenerativeModel('gemini-1.5-flash-latest') response = model.generate_content(prompt) report_header = f"Based on the data for **{origin} to {destination}** on **{date_str}**, here are the key insights:" return f"{report_header}\n\n{response.text}" except Exception as e: return f"An error occurred during AI analysis: {e}" def generate_dynamic_trend_data(origin, destination): price_tiers = { "short": (90, 180), "medium": (150, 300), "long": (250, 450) } long_haul_cities = ["Perth"] tier = "long" if origin in long_haul_cities or destination in long_haul_cities else "short" if origin in ["Sydney", "Melbourne", "Brisbane"] and destination in ["Cairns", "Adelaide", "Hobart"]: tier = "medium" min_base, max_base = price_tiers[tier] data = {} routes_to_generate = {f"{origin[:3].upper()}-{destination[:3].upper()}": (min_base, max_base)} other_routes = list(price_tiers.keys()) random.shuffle(other_routes) for i in range(2): tier_key = other_routes[i] mock_origin, mock_dest = ("SYD","MEL") if tier_key == "short" else ("BNE","CNS") if tier_key == "medium" else ("PER","SYD") routes_to_generate[f"{mock_origin}-{mock_dest}"] = price_tiers[tier_key] for route, (min_b, max_b) in routes_to_generate.items(): route_data = [] for i in range(1, 31): day_factor = (i % 7 - 3) * 5 min_price = round(min_b + day_factor + random.uniform(-10, 10)) max_price = round(max_b + day_factor + random.uniform(-10, 20)) avg_price = round((min_price + max_price) / 2) route_data.append({"day": i, "minPrice": min_price, "avgPrice": avg_price, "maxPrice": max_price}) data[route] = route_data return data def generate_mock_heatmap_data(): airports = list(AUSTRALIAN_CITY_CODES.keys()) routes = [] for _ in range(10): from_city, to_city = random.sample(airports, 2) price = random.randint(70, 400) status = 'green' if price < 150 else 'yellow' if price < 280 else 'red' routes.append({"from": from_city, "to": to_city, "status": status, "price": f"${price}"}) return routes def generate_mock_topmovers_data(): routes = ["SYD → MEL", "BNE → CNS", "MEL → ADL", "SYD → CBR", "SYD → OOL"] cities = ["Gold Coast (OOL)", "Cairns (CNS)", "Sydney (SYD)"] price_drops = [{"route": r, "airline": random.choice(["Jetstar", "Rex", "Virgin"]), "drop": f"{random.randint(15, 35)}%", "price": f"${random.randint(70, 120)}"} for r in random.sample(routes, 5)] high_demand = [{"city": c, "flights": random.randint(30, 120), "change": f"+{random.randint(8, 25)}%"} for c in cities] return {"priceDrops": price_drops, "highDemand": high_demand} @app.get("/", response_class=HTMLResponse) def read_root(): html_content = """
API is Online
This is the backend server for the flight demand analysis tool.
To see the interactive API documentation, visit /docs.