Sky-Guardian / app.py
ganeshkumar383's picture
Create app.py
a27b239 verified
"""
SkyGuardian AI - Real-Time Flight Price Intelligence Engine
============================================================
Production-ready flight price monitoring and optimization system.
Architecture:
- Modular provider-agnostic design
- Real API integration (RapidAPI Skyscanner, AviationStack, Kiwi)
- SQLite historical tracking
- Surge detection and volatility analysis
- Nearby airport arbitrage
- Emergency booking mode
- Gradio UI for HuggingFace Spaces deployment
"""
import os
import sqlite3
import requests
import json
import math
from datetime import datetime, timedelta
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
import gradio as gr
# =========================================================
# DATA MODELS
# =========================================================
@dataclass
class Airport:
"""Airport data structure"""
code: str
name: str
city: str
country: str
lat: float
lon: float
@dataclass
class PriceData:
"""Flight price data structure"""
origin: str
destination: str
price: float
currency: str
departure_date: str
carrier: Optional[str] = None
route_type: str = "direct"
@dataclass
class AnalysisResult:
"""Analysis result structure"""
current_price: float
historical_avg: float
surge_index: float
volatility: float
drop_probability: float
nearby_savings: Optional[Dict] = None
recommendation: str = ""
explanation: str = ""
# =========================================================
# AIRPORT INTELLIGENCE AGENT
# =========================================================
class AirportAgent:
"""Manages airport data and geographic calculations"""
def __init__(self):
self.airports = self._load_airports()
def _load_airports(self) -> Dict[str, Airport]:
"""Load airports for Singapore, India, Malaysia, Thailand, Sri Lanka"""
airports_data = {
# Singapore
"SIN": Airport("SIN", "Singapore Changi", "Singapore", "Singapore", 1.3644, 103.9915),
# India - Major Cities
"DEL": Airport("DEL", "Indira Gandhi Intl", "New Delhi", "India", 28.5665, 77.1031),
"BOM": Airport("BOM", "Chhatrapati Shivaji Intl", "Mumbai", "India", 19.0896, 72.8656),
"BLR": Airport("BLR", "Kempegowda Intl", "Bangalore", "India", 13.1986, 77.7066),
"MAA": Airport("MAA", "Chennai Intl", "Chennai", "India", 12.9941, 80.1709),
"HYD": Airport("HYD", "Rajiv Gandhi Intl", "Hyderabad", "India", 17.2403, 78.4294),
"CCU": Airport("CCU", "Netaji Subhas Chandra Bose Intl", "Kolkata", "India", 22.6547, 88.4467),
"GOI": Airport("GOI", "Dabolim Airport", "Goa", "India", 15.3808, 73.8314),
"COK": Airport("COK", "Cochin Intl", "Kochi", "India", 10.1520, 76.4019),
# Malaysia
"KUL": Airport("KUL", "Kuala Lumpur Intl", "Kuala Lumpur", "Malaysia", 2.7456, 101.7072),
"PEN": Airport("PEN", "Penang Intl", "Penang", "Malaysia", 5.2971, 100.2769),
"JHB": Airport("JHB", "Senai Intl", "Johor Bahru", "Malaysia", 1.6411, 103.6697),
# Thailand
"BKK": Airport("BKK", "Suvarnabhumi", "Bangkok", "Thailand", 13.6900, 100.7501),
"DMK": Airport("DMK", "Don Mueang Intl", "Bangkok", "Thailand", 13.9126, 100.6067),
"HKT": Airport("HKT", "Phuket Intl", "Phuket", "Thailand", 8.1132, 98.3169),
"CNX": Airport("CNX", "Chiang Mai Intl", "Chiang Mai", "Thailand", 18.7668, 98.9628),
# Sri Lanka
"CMB": Airport("CMB", "Bandaranaike Intl", "Colombo", "Sri Lanka", 7.1808, 79.8841),
}
return airports_data
def get_airport_list(self) -> List[Tuple[str, str]]:
"""Get formatted airport list for dropdown"""
return [(f"{apt.code} - {apt.city}, {apt.country}", apt.code)
for apt in sorted(self.airports.values(), key=lambda x: x.city)]
def haversine_distance(self, lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""Calculate distance between two points in kilometers"""
R = 6371 # Earth's radius in km
lat1_rad = math.radians(lat1)
lat2_rad = math.radians(lat2)
delta_lat = math.radians(lat2 - lat1)
delta_lon = math.radians(lon2 - lon1)
a = math.sin(delta_lat/2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(delta_lon/2)**2
c = 2 * math.asin(math.sqrt(a))
return R * c
def find_nearby_airports(self, airport_code: str, radius_km: int = 300) -> List[str]:
"""Find airports within specified radius"""
if airport_code not in self.airports:
return []
origin = self.airports[airport_code]
nearby = []
for code, apt in self.airports.items():
if code != airport_code:
distance = self.haversine_distance(origin.lat, origin.lon, apt.lat, apt.lon)
if distance <= radius_km:
nearby.append(code)
return nearby
# =========================================================
# PRICE PROVIDER INTERFACE
# =========================================================
class PriceProvider:
"""Abstract base class for price providers"""
def get_prices(self, origin: str, destination: str, departure_date: str, **kwargs) -> Optional[PriceData]:
"""Fetch flight prices - to be implemented by subclasses"""
raise NotImplementedError
class SkyscannerProvider(PriceProvider):
"""Skyscanner API via RapidAPI"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://skyscanner-api.p.rapidapi.com/v3"
def get_prices(self, origin: str, destination: str, departure_date: str, **kwargs) -> Optional[PriceData]:
"""Fetch prices from Skyscanner"""
try:
headers = {
"X-RapidAPI-Key": self.api_key,
"X-RapidAPI-Host": "skyscanner-api.p.rapidapi.com"
}
# Search for flights
url = f"{self.base_url}/flights/live/search/create"
payload = {
"query": {
"market": "SG",
"locale": "en-GB",
"currency": "USD",
"queryLegs": [{
"originPlaceId": {"iata": origin},
"destinationPlaceId": {"iata": destination},
"date": {"year": int(departure_date[:4]),
"month": int(departure_date[5:7]),
"day": int(departure_date[8:10])}
}],
"adults": 1,
"cabinClass": "CABIN_CLASS_ECONOMY"
}
}
response = requests.post(url, json=payload, headers=headers, timeout=10)
if response.status_code == 200:
data = response.json()
# Extract cheapest price from response
if "content" in data and "results" in data["content"]:
results = data["content"]["results"]
if "itineraries" in results and results["itineraries"]:
cheapest = min(results["itineraries"].values(),
key=lambda x: x["pricingOptions"][0]["price"]["amount"])
price_info = cheapest["pricingOptions"][0]["price"]
return PriceData(
origin=origin,
destination=destination,
price=price_info["amount"] / 1000, # Convert from smallest unit
currency=price_info.get("unit", "USD"),
departure_date=departure_date,
carrier=cheapest["legs"][0]["carriers"]["marketing"][0]["name"] if cheapest["legs"][0]["carriers"]["marketing"] else None
)
return None
except Exception as e:
print(f"Skyscanner API error: {str(e)}")
return None
class AviationStackProvider(PriceProvider):
"""AviationStack free API (flight data, not pricing - fallback)"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "http://api.aviationstack.com/v1"
def get_prices(self, origin: str, destination: str, departure_date: str, **kwargs) -> Optional[PriceData]:
"""Note: AviationStack doesn't provide pricing, returns estimated baseline"""
try:
# Check if route exists
params = {
"access_key": self.api_key,
"dep_iata": origin,
"arr_iata": destination
}
response = requests.get(f"{self.base_url}/routes", params=params, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get("data"):
# Return baseline estimated price (not real-time pricing)
return PriceData(
origin=origin,
destination=destination,
price=self._estimate_baseline_price(origin, destination),
currency="USD",
departure_date=departure_date,
route_type="estimated"
)
return None
except Exception as e:
print(f"AviationStack API error: {str(e)}")
return None
def _estimate_baseline_price(self, origin: str, destination: str) -> float:
"""Rough baseline estimate based on distance (fallback only)"""
# This is a fallback estimation - real providers should be used
airport_agent = AirportAgent()
airports = airport_agent.airports
if origin in airports and destination in airports:
dist = airport_agent.haversine_distance(
airports[origin].lat, airports[origin].lon,
airports[destination].lat, airports[destination].lon
)
# Rough estimate: $0.10-0.15 per km
return round(dist * 0.12, 2)
return 300.0 # Default fallback
class KiwiProvider(PriceProvider):
"""Kiwi.com Tequila API"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.tequila.kiwi.com"
def get_prices(self, origin: str, destination: str, departure_date: str, **kwargs) -> Optional[PriceData]:
"""Fetch prices from Kiwi"""
try:
headers = {"apikey": self.api_key}
params = {
"fly_from": origin,
"fly_to": destination,
"date_from": departure_date,
"date_to": departure_date,
"adults": 1,
"curr": "USD",
"limit": 1
}
response = requests.get(f"{self.base_url}/v2/search",
headers=headers, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get("data"):
flight = data["data"][0]
return PriceData(
origin=origin,
destination=destination,
price=flight["price"],
currency=flight.get("currency", "USD"),
departure_date=departure_date,
carrier=flight["airlines"][0] if flight.get("airlines") else None
)
return None
except Exception as e:
print(f"Kiwi API error: {str(e)}")
return None
# =========================================================
# PRICE LOGGER
# =========================================================
class PriceLogger:
"""SQLite-based historical price logging"""
def __init__(self, db_path: str = "flight_prices.db"):
self.db_path = db_path
self._init_database()
def _init_database(self):
"""Initialize SQLite database"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS price_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
origin TEXT NOT NULL,
destination TEXT NOT NULL,
departure_date TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
price REAL NOT NULL,
currency TEXT DEFAULT 'USD',
carrier TEXT,
route_type TEXT DEFAULT 'direct'
)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_route
ON price_history(origin, destination, departure_date)
""")
conn.commit()
conn.close()
def log_price(self, price_data: PriceData):
"""Log a price query"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO price_history
(origin, destination, departure_date, price, currency, carrier, route_type)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (
price_data.origin,
price_data.destination,
price_data.departure_date,
price_data.price,
price_data.currency,
price_data.carrier,
price_data.route_type
))
conn.commit()
conn.close()
def get_historical_prices(self, origin: str, destination: str,
departure_date: str, days_lookback: int = 30) -> List[Tuple]:
"""Retrieve historical prices for route"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
lookback_date = (datetime.now() - timedelta(days=days_lookback)).strftime("%Y-%m-%d %H:%M:%S")
cursor.execute("""
SELECT price, timestamp
FROM price_history
WHERE origin = ? AND destination = ?
AND departure_date = ?
AND timestamp >= ?
ORDER BY timestamp DESC
""", (origin, destination, departure_date, lookback_date))
results = cursor.fetchall()
conn.close()
return results
# =========================================================
# SURGE ANALYZER
# =========================================================
class SurgeAnalyzer:
"""Analyzes price surges and volatility"""
def analyze(self, current_price: float, historical_prices: List[Tuple]) -> Dict:
"""Analyze surge behavior"""
if not historical_prices:
return {
"surge_index": 0.0,
"volatility": 0.0,
"historical_avg": current_price,
"is_surge": False
}
prices = [p[0] for p in historical_prices]
# Calculate statistics
avg_price = sum(prices) / len(prices)
variance = sum((p - avg_price) ** 2 for p in prices) / len(prices)
std_dev = math.sqrt(variance)
# Surge index: how many standard deviations above mean
surge_index = (current_price - avg_price) / std_dev if std_dev > 0 else 0.0
# Volatility: coefficient of variation
volatility = (std_dev / avg_price) if avg_price > 0 else 0.0
# Detect surge (>1.5 standard deviations above mean)
is_surge = surge_index > 1.5
return {
"surge_index": round(surge_index, 2),
"volatility": round(volatility, 3),
"historical_avg": round(avg_price, 2),
"is_surge": is_surge,
"std_dev": round(std_dev, 2)
}
def estimate_drop_probability(self, surge_index: float, volatility: float,
days_to_departure: int) -> float:
"""Estimate probability of price drop (deterministic logic)"""
# Base probability on surge level
if surge_index < 0.5:
base_prob = 0.2 # Already low
elif surge_index < 1.5:
base_prob = 0.4 # Moderate
else:
base_prob = 0.6 # High surge
# Adjust for volatility (higher volatility = more likely to drop)
volatility_factor = min(volatility * 0.3, 0.25)
# Adjust for days to departure (closer = less likely to drop significantly)
if days_to_departure < 3:
time_penalty = -0.3
elif days_to_departure < 7:
time_penalty = -0.15
elif days_to_departure < 14:
time_penalty = 0.0
else:
time_penalty = 0.1
probability = base_prob + volatility_factor + time_penalty
return max(0.0, min(1.0, probability)) # Clamp between 0 and 1
# =========================================================
# ARBITRAGE AGENT
# =========================================================
class ArbitrageAgent:
"""Detects nearby airport savings opportunities"""
def __init__(self, airport_agent: AirportAgent, price_provider: PriceProvider):
self.airport_agent = airport_agent
self.price_provider = price_provider
def find_arbitrage(self, destination: str, departure_date: str,
current_price: float, origin: str) -> Optional[Dict]:
"""Find cheaper nearby airports"""
nearby = self.airport_agent.find_nearby_airports(destination, radius_km=300)
if not nearby:
return None
best_saving = None
for nearby_code in nearby[:3]: # Check top 3 nearest
try:
nearby_price_data = self.price_provider.get_prices(
origin, nearby_code, departure_date
)
if nearby_price_data and nearby_price_data.price < current_price:
savings = current_price - nearby_price_data.price
savings_pct = (savings / current_price) * 100
if not best_saving or savings > best_saving["savings"]:
airport_info = self.airport_agent.airports.get(nearby_code)
best_saving = {
"airport_code": nearby_code,
"airport_name": airport_info.name if airport_info else nearby_code,
"city": airport_info.city if airport_info else "Unknown",
"price": nearby_price_data.price,
"savings": round(savings, 2),
"savings_pct": round(savings_pct, 1)
}
except Exception as e:
print(f"Error checking nearby airport {nearby_code}: {str(e)}")
continue
return best_saving
# =========================================================
# DECISION ENGINE
# =========================================================
class DecisionEngine:
"""Makes booking recommendations based on analysis"""
def decide(self, analysis: AnalysisResult, days_to_departure: int,
emergency_mode: bool = False) -> Tuple[str, str]:
"""Generate recommendation and explanation"""
if emergency_mode:
return self._emergency_decision(analysis)
# Decision logic
if analysis.surge_index > 2.0 and analysis.drop_probability > 0.5 and days_to_departure > 7:
recommendation = "⏳ WAIT 24-48H"
explanation = f"""
**High surge detected** (Surge Index: {analysis.surge_index})
Current price is {analysis.surge_index:.1f} standard deviations above historical average.
Drop probability: {analysis.drop_probability*100:.0f}%
**Recommendation:** Wait 24-48 hours. Price likely to normalize.
Check again before {days_to_departure - 2} days to departure.
"""
elif analysis.nearby_savings and analysis.nearby_savings["savings_pct"] > 20:
recommendation = "🛫 TRY NEARBY AIRPORT"
nb = analysis.nearby_savings
explanation = f"""
**Significant arbitrage opportunity detected**
Fly to {nb['city']} ({nb['airport_code']}) instead: ${nb['price']:.2f}
**Save ${nb['savings']:.2f} ({nb['savings_pct']:.0f}%)**
Distance: ~{self._estimate_distance(nb)}km from destination
Consider: Ground transport cost + time vs savings
"""
elif analysis.surge_index < 0.5:
recommendation = "✅ BOOK NOW"
explanation = f"""
**Excellent price detected**
Current price: ${analysis.current_price:.2f}
Below historical average: ${analysis.historical_avg:.2f}
**Recommendation:** Book immediately. Price is at historical low.
Volatility: {analysis.volatility:.1%} - stable pricing
"""
elif days_to_departure <= 3:
recommendation = "⚠️ BOOK NOW (URGENT)"
explanation = f"""
**Time-sensitive booking**
{days_to_departure} days to departure - prices typically spike closer to date.
Current price: ${analysis.current_price:.2f}
Waiting risk: High
**Recommendation:** Book now to avoid last-minute surge.
"""
elif analysis.surge_index > 1.5 and analysis.volatility > 0.15:
recommendation = "📊 MONITOR CLOSELY"
explanation = f"""
**Moderate surge with high volatility**
Surge Index: {analysis.surge_index}
Volatility: {analysis.volatility:.1%}
Drop probability: {analysis.drop_probability*100:.0f}%
**Recommendation:** Check back in 12-24 hours.
Set price alert if available.
"""
else:
recommendation = "✅ BOOK NOW"
explanation = f"""
**Fair market price**
Current price: ${analysis.current_price:.2f}
Historical average: ${analysis.historical_avg:.2f}
Surge Index: {analysis.surge_index}
**Recommendation:** Price is reasonable. Book when ready.
"""
return recommendation, explanation.strip()
def _emergency_decision(self, analysis: AnalysisResult) -> Tuple[str, str]:
"""Emergency mode - always recommend booking"""
if analysis.nearby_savings and analysis.nearby_savings["savings_pct"] > 15:
nb = analysis.nearby_savings
recommendation = "🛫 BOOK NEARBY AIRPORT"
explanation = f"""
**EMERGENCY MODE: Best available option**
Cheapest confirmed option: {nb['city']} ({nb['airport_code']})
Price: ${nb['price']:.2f} (Save ${nb['savings']:.2f})
Book immediately. Arrange ground transport separately.
"""
else:
recommendation = "✅ BOOK NOW"
explanation = f"""
**EMERGENCY MODE: Book immediately**
Direct flight price: ${analysis.current_price:.2f}
No time for price monitoring.
This is your confirmed option - book now.
"""
return recommendation, explanation.strip()
def _estimate_distance(self, nearby_savings: Dict) -> int:
"""Estimate distance to nearby airport"""
return 150 # Placeholder - could calculate actual distance
# =========================================================
# ORCHESTRATOR
# =========================================================
class Orchestrator:
"""Main orchestration engine"""
def __init__(self, api_key: str = None, provider_type: str = "skyscanner"):
self.airport_agent = AirportAgent()
self.price_logger = PriceLogger()
self.surge_analyzer = SurgeAnalyzer()
# Initialize price provider
if api_key:
if provider_type == "skyscanner":
self.price_provider = SkyscannerProvider(api_key)
elif provider_type == "kiwi":
self.price_provider = KiwiProvider(api_key)
elif provider_type == "aviationstack":
self.price_provider = AviationStackProvider(api_key)
else:
self.price_provider = AviationStackProvider(api_key)
else:
# Fallback provider for demo
self.price_provider = AviationStackProvider("demo_key")
self.arbitrage_agent = ArbitrageAgent(self.airport_agent, self.price_provider)
self.decision_engine = DecisionEngine()
def analyze_flight(self, origin: str, destination: str, departure_date: str,
emergency_mode: bool = False) -> Dict:
"""Main analysis pipeline"""
# Validation
if origin not in self.airport_agent.airports:
return {"error": f"Unknown origin airport: {origin}"}
if destination not in self.airport_agent.airports:
return {"error": f"Unknown destination airport: {destination}"}
try:
departure_dt = datetime.strptime(departure_date, "%Y-%m-%d")
days_to_departure = (departure_dt - datetime.now()).days
if days_to_departure < 0:
return {"error": "Departure date is in the past"}
except ValueError:
return {"error": "Invalid date format. Use YYYY-MM-DD"}
# Fetch current price
price_data = self.price_provider.get_prices(origin, destination, departure_date)
if not price_data:
return {
"error": "Unable to fetch price data. Please check API key or try again later.",
"suggestion": "Verify RapidAPI key for Skyscanner or use alternative provider."
}
# Log price
self.price_logger.log_price(price_data)
# Get historical data
historical = self.price_logger.get_historical_prices(
origin, destination, departure_date, days_lookback=30
)
# Analyze surge
surge_analysis = self.surge_analyzer.analyze(price_data.price, historical)
# Estimate drop probability
drop_prob = self.surge_analyzer.estimate_drop_probability(
surge_analysis["surge_index"],
surge_analysis["volatility"],
days_to_departure
)
# Check arbitrage (unless emergency mode)
nearby_savings = None
if not emergency_mode:
nearby_savings = self.arbitrage_agent.find_arbitrage(
destination, departure_date, price_data.price, origin
)
# Create analysis result
analysis_result = AnalysisResult(
current_price=price_data.price,
historical_avg=surge_analysis["historical_avg"],
surge_index=surge_analysis["surge_index"],
volatility=surge_analysis["volatility"],
drop_probability=drop_prob,
nearby_savings=nearby_savings
)
# Generate decision
recommendation, explanation = self.decision_engine.decide(
analysis_result, days_to_departure, emergency_mode
)
# Return comprehensive result
return {
"success": True,
"origin": f"{self.airport_agent.airports[origin].city} ({origin})",
"destination": f"{self.airport_agent.airports[destination].city} ({destination})",
"departure_date": departure_date,
"days_to_departure": days_to_departure,
"current_price": f"${price_data.price:.2f}",
"currency": price_data.currency,
"carrier": price_data.carrier or "Multiple carriers",
"historical_avg": f"${analysis_result.historical_avg:.2f}",
"surge_index": f"{analysis_result.surge_index:.2f}",
"volatility": f"{analysis_result.volatility:.1%}",
"drop_probability": f"{analysis_result.drop_probability*100:.0f}%",
"nearby_savings": self._format_nearby_savings(nearby_savings),
"recommendation": recommendation,
"explanation": explanation,
"data_points": len(historical),
"emergency_mode": emergency_mode
}
def _format_nearby_savings(self, nearby_savings: Optional[Dict]) -> str:
"""Format nearby savings for display"""
if not nearby_savings:
return "None detected"
return f"{nearby_savings['city']} ({nearby_savings['airport_code']}): ${nearby_savings['price']:.2f} - Save ${nearby_savings['savings']:.2f} ({nearby_savings['savings_pct']:.0f}%)"
# =========================================================
# GRADIO UI
# =========================================================
def create_ui():
"""Create Gradio interface"""
# Global orchestrator (will be reinitialized with API key)
orchestrator = None
def analyze_flight_wrapper(origin, destination, departure_date, emergency_mode, api_key, provider_type):
"""Wrapper for Gradio interface"""
nonlocal orchestrator
# Initialize orchestrator with API key
if api_key and api_key.strip():
orchestrator = Orchestrator(api_key=api_key.strip(), provider_type=provider_type)
else:
orchestrator = Orchestrator() # Use fallback
result = orchestrator.analyze_flight(origin, destination, departure_date, emergency_mode)
if "error" in result:
error_msg = result["error"]
if "suggestion" in result:
error_msg += f"\n\n💡 {result['suggestion']}"
return (
"❌ Error",
error_msg,
"", "", "", "", "", "", ""
)
# Format output
route_info = f"**{result['origin']}** → **{result['destination']}**\n"
route_info += f"Departure: {result['departure_date']} ({result['days_to_departure']} days)\n"
route_info += f"Carrier: {result['carrier']}"
price_info = f"💰 **${result['current_price']}** {result['currency']}"
stats = f"""
**Historical Average:** {result['historical_avg']}
**Surge Index:** {result['surge_index']}
**Volatility:** {result['volatility']}
**Drop Probability:** {result['drop_probability']}
**Data Points:** {result['data_points']} historical records
"""
return (
result['recommendation'],
route_info,
price_info,
stats,
result['nearby_savings'],
result['explanation'],
f"✅ Analysis complete. Emergency Mode: {'ON' if result['emergency_mode'] else 'OFF'}",
"",
""
)
# Build UI
with gr.Blocks(title="SkyGuardian AI - Flight Price Intelligence", theme=gr.themes.Soft()) as app:
gr.Markdown("""
# 🛫 SkyGuardian AI
## Real-Time Flight Price Intelligence Engine
Monitor flight prices, detect surges, find arbitrage opportunities, and get AI-powered booking recommendations.
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Flight Details")
# Get airport choices
temp_agent = AirportAgent()
airport_choices = temp_agent.get_airport_list()
origin = gr.Dropdown(
choices=airport_choices,
label="Origin Airport",
value="SIN",
interactive=True
)
destination = gr.Dropdown(
choices=airport_choices,
label="Destination Airport",
value="BOM",
interactive=True
)
departure_date = gr.Textbox(
label="Departure Date (YYYY-MM-DD)",
value=(datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d"),
placeholder="2024-03-15"
)
emergency_mode = gr.Checkbox(
label="🚨 Emergency Mode (Book Immediately)",
value=False
)
gr.Markdown("### API Configuration")
provider_type = gr.Radio(
choices=["skyscanner", "kiwi", "aviationstack"],
label="Price Provider",
value="skyscanner",
info="Skyscanner (RapidAPI) recommended for real pricing"
)
api_key = gr.Textbox(
label="API Key (RapidAPI for Skyscanner/Kiwi)",
placeholder="Enter your RapidAPI key",
type="password",
info="Get free key at rapidapi.com"
)
analyze_btn = gr.Button("🔍 Analyze Flight", variant="primary", size="lg")
with gr.Column(scale=2):
gr.Markdown("### Analysis Results")
recommendation_box = gr.Textbox(
label="AI Recommendation",
lines=1,
interactive=False
)
route_box = gr.Markdown(label="Route Information")
price_box = gr.Markdown(label="Current Price")
stats_box = gr.Markdown(label="Price Statistics")
nearby_box = gr.Textbox(
label="Nearby Airport Savings",
lines=2,
interactive=False
)
explanation_box = gr.Markdown(label="Detailed Explanation")
status_box = gr.Textbox(
label="Status",
lines=1,
interactive=False
)
gr.Markdown("""
---
### How It Works
1. **Real-Time Pricing**: Fetches current flight prices from live APIs
2. **Historical Tracking**: Logs all queries to build price history database
3. **Surge Detection**: Analyzes current price vs historical average
4. **Volatility Analysis**: Calculates price stability and drop probability
5. **Arbitrage Detection**: Finds cheaper nearby airports within 300km
6. **Smart Recommendations**: AI-powered booking strategy based on data
### Emergency Mode
When enabled, system recommends **immediate booking** with best available option (no wait suggestions).
### API Setup
- **Skyscanner**: Get free RapidAPI key at [rapidapi.com/skyscanner](https://rapidapi.com/skyscanner)
- **Kiwi**: Sign up at [tequila.kiwi.com](https://tequila.kiwi.com)
- **Fallback**: System uses estimation if no API key provided (demo only)
""")
# Connect components
analyze_btn.click(
fn=analyze_flight_wrapper,
inputs=[origin, destination, departure_date, emergency_mode, api_key, provider_type],
outputs=[
recommendation_box,
route_box,
price_box,
stats_box,
nearby_box,
explanation_box,
status_box,
gr.Textbox(visible=False), # Hidden outputs
gr.Textbox(visible=False)
]
)
return app
# =========================================================
# MAIN ENTRY POINT
# =========================================================
if __name__ == "__main__":
app = create_ui()
app.launch(share=False)