Spaces:
Sleeping
Sleeping
| """ | |
| Intelligent Flight Agent with Advanced Decision-Making Logic | |
| This module implements a sophisticated FlightAgent that goes beyond simple data retrieval | |
| to provide intelligent flight recommendations based on multiple factors and strategies. | |
| """ | |
| import asyncio | |
| import hashlib | |
| import json | |
| import logging | |
| import time | |
| from datetime import datetime, timedelta, timezone | |
| from decimal import Decimal | |
| from enum import Enum | |
| from typing import Dict, List, Optional, Any, Tuple, Union | |
| from dataclasses import dataclass, field | |
| from collections import defaultdict | |
| from pydantic import BaseModel, Field | |
| from ..models.flight_models import FlightOption, Airline | |
| from ..utils.config import get_settings | |
| class SearchStrategy(str, Enum): | |
| """Search strategy enumeration.""" | |
| CHEAPEST = "cheapest" | |
| FASTEST = "fastest" | |
| BEST_VALUE = "best_value" | |
| MOST_CONVENIENT = "most_convenient" | |
| LEAST_STOPS = "least_stops" | |
| class TripType(str, Enum): | |
| """Trip type enumeration.""" | |
| ONE_WAY = "one_way" | |
| ROUND_TRIP = "round_trip" | |
| MULTI_CITY = "multi_city" | |
| class FlightSearchRequest: | |
| """Comprehensive flight search request.""" | |
| departure_city: str | |
| arrival_city: str | |
| departure_date: datetime | |
| return_date: Optional[datetime] = None | |
| passengers: int = 1 | |
| travel_class: str = "Economy" | |
| strategy: SearchStrategy = SearchStrategy.BEST_VALUE | |
| trip_type: TripType = TripType.ONE_WAY | |
| max_stops: int = 2 | |
| max_duration_hours: float = 24.0 | |
| budget_max: Optional[Decimal] = None | |
| flexible_dates: bool = False | |
| date_flexibility_days: int = 3 | |
| multi_city_legs: Optional[List[Dict[str, Any]]] = None | |
| def __post_init__(self): | |
| """Validate request after initialization.""" | |
| if self.passengers < 1 or self.passengers > 9: | |
| raise ValueError("Passengers must be between 1 and 9") | |
| if self.max_stops < 0 or self.max_stops > 3: | |
| raise ValueError("Max stops must be between 0 and 3") | |
| if self.date_flexibility_days < 0 or self.date_flexibility_days > 7: | |
| raise ValueError("Date flexibility must be between 0 and 7 days") | |
| class FlightSearchResult: | |
| """Comprehensive flight search result.""" | |
| flights: List[FlightOption] | |
| search_request: FlightSearchRequest | |
| search_metadata: Dict[str, Any] = field(default_factory=dict) | |
| search_duration_ms: float = 0.0 | |
| cache_hit: bool = False | |
| total_results_found: int = 0 | |
| filtered_out: int = 0 | |
| ranking_scores: Dict[str, float] = field(default_factory=dict) | |
| class FlexibleDateGenerator: | |
| """ | |
| Generates flexible date ranges for flight searches. | |
| This class implements intelligent date flexibility logic that considers | |
| business rules, seasonal patterns, and user preferences. | |
| """ | |
| def __init__(self): | |
| self.weekend_days = {5, 6} # Friday, Saturday | |
| self.holiday_multiplier = 1.5 # More flexibility during holidays | |
| def generate_date_range( | |
| self, | |
| base_date: datetime, | |
| flexibility_days: int, | |
| include_weekends: bool = True, | |
| prefer_weekdays: bool = False | |
| ) -> List[datetime]: | |
| """ | |
| Generate a range of dates around the base date. | |
| Strategy: | |
| 1. Consider weekend vs weekday preferences | |
| 2. Apply holiday awareness | |
| 3. Optimize for common travel patterns | |
| """ | |
| dates = [] | |
| base_weekday = base_date.weekday() | |
| # Generate dates before and after base date | |
| for day_offset in range(-flexibility_days, flexibility_days + 1): | |
| candidate_date = base_date + timedelta(days=day_offset) | |
| candidate_weekday = candidate_date.weekday() | |
| # Skip weekends if not preferred | |
| if not include_weekends and candidate_weekday in self.weekend_days: | |
| continue | |
| # Prioritize weekdays if preferred | |
| if prefer_weekdays and candidate_weekday in self.weekend_days: | |
| continue | |
| dates.append(candidate_date) | |
| # Sort by proximity to base date | |
| dates.sort(key=lambda d: abs((d - base_date).days)) | |
| return dates | |
| def is_holiday_period(self, date: datetime) -> bool: | |
| """Check if date falls within a holiday period.""" | |
| # Simplified holiday detection - would be more sophisticated in production | |
| month = date.month | |
| day = date.day | |
| # Major US holidays (simplified) | |
| holidays = [ | |
| (12, 25), # Christmas | |
| (12, 31), # New Year's Eve | |
| (1, 1), # New Year's Day | |
| (7, 4), # Independence Day | |
| (11, 28), # Thanksgiving (simplified) | |
| ] | |
| return (month, day) in holidays | |
| class FlightRankingEngine: | |
| """ | |
| Advanced ranking engine for flight options. | |
| This class implements sophisticated ranking algorithms that consider | |
| multiple factors and user preferences to score flight options. | |
| """ | |
| def __init__(self): | |
| # Weight configuration for different factors | |
| self.weights = { | |
| "price": 0.3, | |
| "duration": 0.25, | |
| "stops": 0.2, | |
| "departure_time": 0.15, | |
| "airline_reliability": 0.1 | |
| } | |
| # Time preferences (0-1 score based on departure time) | |
| self.time_preferences = { | |
| "morning": (6, 11), # 6 AM - 11 AM | |
| "afternoon": (11, 17), # 11 AM - 5 PM | |
| "evening": (17, 22), # 5 PM - 10 PM | |
| "night": (22, 6) # 10 PM - 6 AM (next day) | |
| } | |
| # Airline reliability scores (based on historical data) | |
| self.airline_scores = { | |
| "Delta Air Lines": 0.9, | |
| "American Airlines": 0.85, | |
| "United Airlines": 0.8, | |
| "Southwest Airlines": 0.88, | |
| "JetBlue Airways": 0.87, | |
| "Alaska Airlines": 0.91, | |
| "Spirit Airlines": 0.6, | |
| "Frontier Airlines": 0.65, | |
| "Other": 0.7 | |
| } | |
| def calculate_composite_score( | |
| self, | |
| flight: FlightOption, | |
| strategy: SearchStrategy, | |
| preferences: Dict[str, Any] = None | |
| ) -> float: | |
| """ | |
| Calculate composite score for a flight option. | |
| Strategy: | |
| 1. Normalize individual factors to 0-1 scale | |
| 2. Apply strategy-specific weighting | |
| 3. Combine factors using weighted average | |
| 4. Apply bonus/penalty adjustments | |
| """ | |
| if preferences is None: | |
| preferences = {} | |
| # Calculate individual factor scores | |
| price_score = self._calculate_price_score(flight, strategy, preferences) | |
| duration_score = self._calculate_duration_score(flight, strategy) | |
| stops_score = self._calculate_stops_score(flight, strategy) | |
| time_score = self._calculate_departure_time_score(flight, preferences) | |
| airline_score = self._calculate_airline_score(flight) | |
| # Apply strategy-specific weights | |
| weights = self._get_strategy_weights(strategy) | |
| # Calculate weighted composite score | |
| composite_score = ( | |
| price_score * weights["price"] + | |
| duration_score * weights["duration"] + | |
| stops_score * weights["stops"] + | |
| time_score * weights["departure_time"] + | |
| airline_score * weights["airline_reliability"] | |
| ) | |
| # Apply bonus/penalty adjustments | |
| composite_score = self._apply_adjustments(flight, composite_score, preferences) | |
| return min(max(composite_score, 0.0), 1.0) # Clamp to 0-1 range | |
| def _calculate_price_score( | |
| self, | |
| flight: FlightOption, | |
| strategy: SearchStrategy, | |
| preferences: Dict[str, Any] | |
| ) -> float: | |
| """Calculate price-based score.""" | |
| if strategy == SearchStrategy.CHEAPEST: | |
| # For cheapest strategy, lower prices get higher scores | |
| # This would be normalized against all available prices | |
| return 1.0 - min(float(flight.price) / 1000, 1.0) | |
| elif strategy == SearchStrategy.BEST_VALUE: | |
| # Best value considers price per hour | |
| if flight.duration_minutes > 0: | |
| price_per_hour = float(flight.price) / flight.duration_hours | |
| return 1.0 - min(price_per_hour / 100, 1.0) | |
| # Default: moderate price preference | |
| return 1.0 - min(float(flight.price) / 2000, 1.0) | |
| def _calculate_duration_score( | |
| self, | |
| flight: FlightOption, | |
| strategy: SearchStrategy | |
| ) -> float: | |
| """Calculate duration-based score.""" | |
| if strategy == SearchStrategy.FASTEST: | |
| # For fastest strategy, shorter flights get higher scores | |
| return 1.0 - min(flight.duration_minutes / 1440, 1.0) # 24 hours max | |
| # Default: moderate duration preference | |
| return 1.0 - min(flight.duration_minutes / 720, 1.0) # 12 hours max | |
| def _calculate_stops_score( | |
| self, | |
| flight: FlightOption, | |
| strategy: SearchStrategy | |
| ) -> float: | |
| """Calculate stops-based score.""" | |
| if strategy == SearchStrategy.LEAST_STOPS or strategy == SearchStrategy.MOST_CONVENIENT: | |
| # Direct flights get highest score | |
| return 1.0 if flight.stops == 0 else 0.8 - (flight.stops * 0.2) | |
| # Default: slight preference for fewer stops | |
| return 1.0 - (flight.stops * 0.3) | |
| def _calculate_departure_time_score( | |
| self, | |
| flight: FlightOption, | |
| preferences: Dict[str, Any] | |
| ) -> float: | |
| """Calculate departure time-based score.""" | |
| departure_hour = flight.departure_time.hour | |
| # Check user time preferences | |
| preferred_time = preferences.get("preferred_departure_time", "morning") | |
| if preferred_time in self.time_preferences: | |
| start_hour, end_hour = self.time_preferences[preferred_time] | |
| if start_hour <= end_hour: | |
| # Normal time range (e.g., 6-11) | |
| if start_hour <= departure_hour < end_hour: | |
| return 1.0 | |
| else: | |
| # Overnight range (e.g., 22-6) | |
| if departure_hour >= start_hour or departure_hour < end_hour: | |
| return 1.0 | |
| # Default scoring based on time of day | |
| if 8 <= departure_hour <= 18: # Business hours | |
| return 0.8 | |
| elif 6 <= departure_hour < 8 or 18 < departure_hour <= 22: # Extended hours | |
| return 0.6 | |
| else: # Very early or late | |
| return 0.4 | |
| def _calculate_airline_score(self, flight: FlightOption) -> float: | |
| """Calculate airline reliability score.""" | |
| # Handle both enum and string values | |
| airline_name = flight.airline.value if hasattr(flight.airline, 'value') else flight.airline | |
| return self.airline_scores.get(airline_name, self.airline_scores["Other"]) | |
| def _get_strategy_weights(self, strategy: SearchStrategy) -> Dict[str, float]: | |
| """Get weights adjusted for specific strategy.""" | |
| base_weights = self.weights.copy() | |
| if strategy == SearchStrategy.CHEAPEST: | |
| base_weights.update({"price": 0.6, "duration": 0.1, "stops": 0.1}) | |
| elif strategy == SearchStrategy.FASTEST: | |
| base_weights.update({"duration": 0.5, "stops": 0.3, "price": 0.1}) | |
| elif strategy == SearchStrategy.LEAST_STOPS: | |
| base_weights.update({"stops": 0.5, "duration": 0.2, "price": 0.2}) | |
| elif strategy == SearchStrategy.MOST_CONVENIENT: | |
| base_weights.update({"departure_time": 0.3, "stops": 0.25, "duration": 0.2}) | |
| return base_weights | |
| def _apply_adjustments( | |
| self, | |
| flight: FlightOption, | |
| score: float, | |
| preferences: Dict[str, Any] | |
| ) -> float: | |
| """Apply bonus/penalty adjustments to the score.""" | |
| adjustments = 0.0 | |
| # Direct flight bonus | |
| if flight.is_direct_flight: | |
| adjustments += 0.1 | |
| # Reasonable connection time bonus (if applicable) | |
| if flight.stops > 0: | |
| # This would check connection times in a real implementation | |
| adjustments += 0.05 | |
| # Airline preference bonus | |
| preferred_airlines = preferences.get("preferred_airlines", []) | |
| airline_name = flight.airline.value if hasattr(flight.airline, 'value') else flight.airline | |
| if airline_name in preferred_airlines: | |
| adjustments += 0.15 | |
| # Avoided airlines penalty | |
| avoided_airlines = preferences.get("avoided_airlines", []) | |
| if airline_name in avoided_airlines: | |
| adjustments -= 0.2 | |
| return score + adjustments | |
| class FlightFilterEngine: | |
| """ | |
| Intelligent filtering engine for flight options. | |
| This class implements sophisticated filtering logic to remove | |
| unrealistic or undesirable flight options. | |
| """ | |
| def __init__(self): | |
| self.min_connection_time_minutes = 45 # Minimum connection time | |
| self.max_connection_time_hours = 4 # Maximum connection time | |
| self.max_layover_time_hours = 8 # Maximum layover time | |
| self.min_flight_duration_minutes = 30 # Minimum flight segment duration | |
| def filter_flights( | |
| self, | |
| flights: List[FlightOption], | |
| request: FlightSearchRequest | |
| ) -> Tuple[List[FlightOption], List[str]]: | |
| """ | |
| Filter flights based on business rules and user preferences. | |
| Returns: | |
| Tuple of (filtered_flights, filter_reasons) | |
| """ | |
| filtered_flights = [] | |
| filter_reasons = [] | |
| for flight in flights: | |
| is_valid, reasons = self._validate_flight(flight, request) | |
| if is_valid: | |
| filtered_flights.append(flight) | |
| else: | |
| filter_reasons.extend(reasons) | |
| return filtered_flights, filter_reasons | |
| def _validate_flight( | |
| self, | |
| flight: FlightOption, | |
| request: FlightSearchRequest | |
| ) -> Tuple[bool, List[str]]: | |
| """Validate individual flight against business rules.""" | |
| reasons = [] | |
| # Check stops limit | |
| if flight.stops > request.max_stops: | |
| reasons.append(f"Too many stops ({flight.stops} > {request.max_stops})") | |
| # Check duration limit | |
| max_duration_minutes = request.max_duration_hours * 60 | |
| if flight.duration_minutes > max_duration_minutes: | |
| reasons.append(f"Flight too long ({flight.duration_minutes}min > {max_duration_minutes}min)") | |
| # Check budget limit | |
| if request.budget_max and flight.price > request.budget_max: | |
| reasons.append(f"Over budget (${flight.price} > ${request.budget_max})") | |
| # Check minimum flight duration | |
| if flight.duration_minutes < self.min_flight_duration_minutes: | |
| reasons.append(f"Flight too short ({flight.duration_minutes}min < {self.min_flight_duration_minutes}min)") | |
| # Check for unrealistic prices | |
| if flight.price < Decimal("50") or flight.price > Decimal("5000"): | |
| reasons.append(f"Unrealistic price (${flight.price})") | |
| # Check for past departure times | |
| if flight.departure_time < datetime.now(timezone.utc): | |
| reasons.append("Departure time in the past") | |
| # Additional validation for connecting flights | |
| if flight.stops > 0: | |
| # This would check connection times in a real implementation | |
| # For now, we'll just flag flights with too many stops | |
| if flight.stops > 2: | |
| reasons.append("Too many connections for comfort") | |
| is_valid = len(reasons) == 0 | |
| return is_valid, reasons | |
| class FlightCacheManager: | |
| """ | |
| Intelligent caching manager for flight searches. | |
| This class implements sophisticated caching strategies with | |
| appropriate expiration times and cache invalidation logic. | |
| """ | |
| def __init__(self): | |
| self.cache = {} | |
| self.cache_metadata = {} | |
| # Cache expiration times (in minutes) | |
| self.expiration_times = { | |
| "flight_prices": 30, # Flight prices change frequently | |
| "flight_schedules": 120, # Schedules change less frequently | |
| "airline_info": 1440, # Airline info changes rarely | |
| "route_info": 720 # Route info changes moderately | |
| } | |
| def get_cache_key(self, request: FlightSearchRequest) -> str: | |
| """Generate cache key for search request.""" | |
| # Create a normalized representation of the request | |
| key_data = { | |
| "departure_city": request.departure_city, | |
| "arrival_city": request.arrival_city, | |
| "departure_date": request.departure_date.isoformat(), | |
| "return_date": request.return_date.isoformat() if request.return_date else None, | |
| "passengers": request.passengers, | |
| "travel_class": request.travel_class, | |
| "strategy": request.strategy.value, | |
| "trip_type": request.trip_type.value, | |
| "max_stops": request.max_stops | |
| } | |
| # Create hash of the normalized data | |
| key_string = json.dumps(key_data, sort_keys=True) | |
| return hashlib.md5(key_string.encode()).hexdigest() | |
| def get_cached_results(self, request: FlightSearchRequest) -> Optional[List[FlightOption]]: | |
| """Get cached results if available and valid.""" | |
| cache_key = self.get_cache_key(request) | |
| if cache_key in self.cache: | |
| metadata = self.cache_metadata[cache_key] | |
| expiration_time = metadata["timestamp"] + timedelta( | |
| minutes=self.expiration_times["flight_prices"] | |
| ) | |
| if datetime.now() < expiration_time: | |
| return self.cache[cache_key] | |
| else: | |
| # Remove expired entry | |
| del self.cache[cache_key] | |
| del self.cache_metadata[cache_key] | |
| return None | |
| def cache_results(self, request: FlightSearchRequest, results: List[FlightOption]): | |
| """Cache search results.""" | |
| cache_key = self.get_cache_key(request) | |
| self.cache[cache_key] = results | |
| self.cache_metadata[cache_key] = { | |
| "timestamp": datetime.now(), | |
| "request_hash": cache_key | |
| } | |
| def get_cache_stats(self) -> Dict[str, Any]: | |
| """Get cache statistics.""" | |
| total_entries = len(self.cache) | |
| expired_entries = 0 | |
| for metadata in self.cache_metadata.values(): | |
| expiration_time = metadata["timestamp"] + timedelta( | |
| minutes=self.expiration_times["flight_prices"] | |
| ) | |
| if datetime.now() >= expiration_time: | |
| expired_entries += 1 | |
| return { | |
| "total_entries": total_entries, | |
| "expired_entries": expired_entries, | |
| "active_entries": total_entries - expired_entries | |
| } | |
| class IntelligentFlightAgent: | |
| """ | |
| Intelligent Flight Agent with sophisticated decision-making logic. | |
| This agent goes beyond simple data retrieval to provide intelligent | |
| flight recommendations based on multiple factors and user preferences. | |
| """ | |
| def __init__(self, api_key: Optional[str] = None): | |
| """Initialize the intelligent flight agent.""" | |
| self.settings = get_settings() | |
| self.api_key = api_key or self.settings.anthropic_api_key | |
| # Initialize components | |
| self.date_generator = FlexibleDateGenerator() | |
| self.ranking_engine = FlightRankingEngine() | |
| self.filter_engine = FlightFilterEngine() | |
| self.cache_manager = FlightCacheManager() | |
| # Initialize logging | |
| self.logger = logging.getLogger(__name__) | |
| # Search engines (would integrate with your existing clients) | |
| self.search_engines = { | |
| "serpapi": None, # Would initialize SerpAPI client | |
| "tavily": None # Would initialize Tavily client | |
| } | |
| async def search_flights( | |
| self, | |
| request: FlightSearchRequest, | |
| preferences: Dict[str, Any] = None | |
| ) -> FlightSearchResult: | |
| """ | |
| Perform intelligent flight search with advanced decision-making. | |
| Strategy: | |
| 1. Check cache for recent results | |
| 2. Generate flexible date ranges if requested | |
| 3. Execute searches across multiple engines | |
| 4. Filter unrealistic options | |
| 5. Rank results based on strategy and preferences | |
| 6. Cache results for future use | |
| """ | |
| start_time = time.time() | |
| if preferences is None: | |
| preferences = {} | |
| # Check cache first | |
| cached_results = self.cache_manager.get_cached_results(request) | |
| if cached_results: | |
| self.logger.info(f"Cache hit for flight search: {request.departure_city} β {request.arrival_city}") | |
| return FlightSearchResult( | |
| flights=cached_results, | |
| search_request=request, | |
| search_metadata={"cache_hit": True}, | |
| search_duration_ms=(time.time() - start_time) * 1000, | |
| cache_hit=True, | |
| total_results_found=len(cached_results) | |
| ) | |
| # Generate search dates if flexible | |
| search_dates = self._generate_search_dates(request) | |
| # Execute searches | |
| all_flights = [] | |
| search_metadata = { | |
| "searches_performed": 0, | |
| "engines_used": [], | |
| "date_ranges_searched": len(search_dates) | |
| } | |
| for search_date in search_dates: | |
| # Create modified request for this date | |
| date_request = self._create_date_request(request, search_date) | |
| # Search across engines (would integrate with your existing clients) | |
| flights = await self._execute_search(date_request) | |
| all_flights.extend(flights) | |
| search_metadata["searches_performed"] += 1 | |
| # Filter flights | |
| filtered_flights, filter_reasons = self.filter_engine.filter_flights(all_flights, request) | |
| # Rank flights | |
| ranked_flights = self._rank_flights(filtered_flights, request, preferences) | |
| # Cache results | |
| self.cache_manager.cache_results(request, ranked_flights) | |
| # Calculate search duration | |
| search_duration_ms = (time.time() - start_time) * 1000 | |
| # Create result | |
| result = FlightSearchResult( | |
| flights=ranked_flights, | |
| search_request=request, | |
| search_metadata=search_metadata, | |
| search_duration_ms=search_duration_ms, | |
| cache_hit=False, | |
| total_results_found=len(all_flights), | |
| filtered_out=len(all_flights) - len(filtered_flights) | |
| ) | |
| self.logger.info( | |
| f"Flight search completed: {len(ranked_flights)} results from {len(all_flights)} total " | |
| f"({len(filtered_flights)} after filtering) in {search_duration_ms:.0f}ms" | |
| ) | |
| return result | |
| def _generate_search_dates(self, request: FlightSearchRequest) -> List[datetime]: | |
| """Generate dates to search based on flexibility settings.""" | |
| if not request.flexible_dates: | |
| return [request.departure_date] | |
| dates = self.date_generator.generate_date_range( | |
| request.departure_date, | |
| request.date_flexibility_days, | |
| include_weekends=True, # Could be configurable | |
| prefer_weekdays=False # Could be configurable | |
| ) | |
| return dates | |
| def _create_date_request(self, request: FlightSearchRequest, search_date: datetime) -> FlightSearchRequest: | |
| """Create a modified request for a specific search date.""" | |
| return FlightSearchRequest( | |
| departure_city=request.departure_city, | |
| arrival_city=request.arrival_city, | |
| departure_date=search_date, | |
| return_date=request.return_date, | |
| passengers=request.passengers, | |
| travel_class=request.travel_class, | |
| strategy=request.strategy, | |
| trip_type=request.trip_type, | |
| max_stops=request.max_stops, | |
| max_duration_hours=request.max_duration_hours, | |
| budget_max=request.budget_max, | |
| flexible_dates=False, # Already handled | |
| date_flexibility_days=0, | |
| multi_city_legs=request.multi_city_legs | |
| ) | |
| async def _execute_search(self, request: FlightSearchRequest) -> List[FlightOption]: | |
| """ | |
| Execute search across multiple engines. | |
| This would integrate with your existing SerpAPI and Tavily clients. | |
| """ | |
| # Placeholder implementation - would integrate with your existing clients | |
| flights = [] | |
| # Simulate search results for demonstration | |
| sample_flights = [ | |
| FlightOption( | |
| airline="Delta Air Lines", | |
| flight_number="DL1234", | |
| departure_city=request.departure_city, | |
| arrival_city=request.arrival_city, | |
| departure_time=request.departure_date.replace(hour=8, minute=30), | |
| arrival_time=request.departure_date.replace(hour=11, minute=45), | |
| price=Decimal("299.99"), | |
| duration_minutes=195, | |
| stops=0 | |
| ), | |
| FlightOption( | |
| airline="American Airlines", | |
| flight_number="AA5678", | |
| departure_city=request.departure_city, | |
| arrival_city=request.arrival_city, | |
| departure_time=request.departure_date.replace(hour=14, minute=15), | |
| arrival_time=request.departure_date.replace(hour=17, minute=30), | |
| price=Decimal("279.99"), | |
| duration_minutes=195, | |
| stops=0 | |
| ) | |
| ] | |
| return sample_flights | |
| def _rank_flights( | |
| self, | |
| flights: List[FlightOption], | |
| request: FlightSearchRequest, | |
| preferences: Dict[str, Any] | |
| ) -> List[FlightOption]: | |
| """Rank flights based on strategy and preferences.""" | |
| # Calculate scores for all flights | |
| scored_flights = [] | |
| for flight in flights: | |
| score = self.ranking_engine.calculate_composite_score( | |
| flight, request.strategy, preferences | |
| ) | |
| scored_flights.append((flight, score)) | |
| # Sort by score (descending) | |
| scored_flights.sort(key=lambda x: x[1], reverse=True) | |
| # Return sorted flights | |
| return [flight for flight, score in scored_flights] | |
| async def search_multi_city_flights( | |
| self, | |
| legs: List[Dict[str, Any]], | |
| strategy: SearchStrategy = SearchStrategy.BEST_VALUE | |
| ) -> Dict[str, FlightSearchResult]: | |
| """ | |
| Search for multi-city flight itineraries. | |
| Strategy: | |
| 1. Search each leg independently | |
| 2. Combine results intelligently | |
| 3. Apply multi-city specific ranking | |
| """ | |
| results = {} | |
| for i, leg in enumerate(legs): | |
| request = FlightSearchRequest( | |
| departure_city=leg["departure_city"], | |
| arrival_city=leg["arrival_city"], | |
| departure_date=leg["departure_date"], | |
| passengers=leg.get("passengers", 1), | |
| travel_class=leg.get("travel_class", "Economy"), | |
| strategy=strategy, | |
| trip_type=TripType.ONE_WAY | |
| ) | |
| result = await self.search_flights(request) | |
| results[f"leg_{i+1}"] = result | |
| return results | |
| async def search_round_trip_flights( | |
| self, | |
| departure_city: str, | |
| arrival_city: str, | |
| departure_date: datetime, | |
| return_date: datetime, | |
| strategy: SearchStrategy = SearchStrategy.BEST_VALUE, | |
| **kwargs | |
| ) -> Dict[str, FlightSearchResult]: | |
| """ | |
| Search for round-trip flights. | |
| Strategy: | |
| 1. Search outbound and return legs separately | |
| 2. Combine results to find best round-trip combinations | |
| 3. Apply round-trip specific logic (e.g., same airline discounts) | |
| """ | |
| # Search outbound | |
| outbound_request = FlightSearchRequest( | |
| departure_city=departure_city, | |
| arrival_city=arrival_city, | |
| departure_date=departure_date, | |
| return_date=None, | |
| strategy=strategy, | |
| trip_type=TripType.ONE_WAY, | |
| **kwargs | |
| ) | |
| outbound_result = await self.search_flights(outbound_request) | |
| # Search return | |
| return_request = FlightSearchRequest( | |
| departure_city=arrival_city, | |
| arrival_city=departure_city, | |
| departure_date=return_date, | |
| return_date=None, | |
| strategy=strategy, | |
| trip_type=TripType.ONE_WAY, | |
| **kwargs | |
| ) | |
| return_result = await self.search_flights(return_request) | |
| return { | |
| "outbound": outbound_result, | |
| "return": return_result | |
| } | |
| def get_agent_stats(self) -> Dict[str, Any]: | |
| """Get comprehensive agent statistics.""" | |
| cache_stats = self.cache_manager.get_cache_stats() | |
| return { | |
| "cache_stats": cache_stats, | |
| "ranking_weights": self.ranking_engine.weights, | |
| "filter_settings": { | |
| "min_connection_time_minutes": self.filter_engine.min_connection_time_minutes, | |
| "max_connection_time_hours": self.filter_engine.max_connection_time_hours, | |
| "min_flight_duration_minutes": self.filter_engine.min_flight_duration_minutes | |
| } | |
| } | |
| # Example usage and demonstration | |
| async def demonstrate_intelligent_flight_agent(): | |
| """Demonstrate the intelligent flight agent capabilities.""" | |
| print("π Intelligent Flight Agent Demonstration") | |
| print("=" * 60) | |
| # Initialize agent | |
| agent = IntelligentFlightAgent() | |
| # Example search requests | |
| departure_date = datetime(2024, 1, 15, 8, 0, 0, tzinfo=timezone.utc) | |
| # 1. Basic search | |
| print("\nπ 1. Basic Flight Search:") | |
| basic_request = FlightSearchRequest( | |
| departure_city="New York", | |
| arrival_city="Los Angeles", | |
| departure_date=departure_date, | |
| passengers=1, | |
| strategy=SearchStrategy.BEST_VALUE | |
| ) | |
| basic_result = await agent.search_flights(basic_request) | |
| print(f"Found {len(basic_result.flights)} flights") | |
| print(f"Search took {basic_result.search_duration_ms:.0f}ms") | |
| print(f"Cache hit: {basic_result.cache_hit}") | |
| # 2. Flexible date search | |
| print("\nπ 2. Flexible Date Search:") | |
| flexible_request = FlightSearchRequest( | |
| departure_city="New York", | |
| arrival_city="Los Angeles", | |
| departure_date=departure_date, | |
| passengers=2, | |
| strategy=SearchStrategy.CHEAPEST, | |
| flexible_dates=True, | |
| date_flexibility_days=3 | |
| ) | |
| flexible_result = await agent.search_flights(flexible_request) | |
| print(f"Found {len(flexible_result.flights)} flights with flexible dates") | |
| print(f"Searched {flexible_result.search_metadata['date_ranges_searched']} date ranges") | |
| # 3. Round-trip search | |
| print("\nπ 3. Round-Trip Search:") | |
| return_date = datetime(2024, 1, 22, 18, 0, 0, tzinfo=timezone.utc) | |
| round_trip_results = await agent.search_round_trip_flights( | |
| "New York", "Los Angeles", departure_date, return_date | |
| ) | |
| print(f"Outbound: {len(round_trip_results['outbound'].flights)} options") | |
| print(f"Return: {len(round_trip_results['return'].flights)} options") | |
| # 4. Agent statistics | |
| print("\nπ 4. Agent Statistics:") | |
| stats = agent.get_agent_stats() | |
| print(f"Cache entries: {stats['cache_stats']['active_entries']}") | |
| print(f"Ranking weights: {stats['ranking_weights']}") | |
| print("\nπ Intelligent Flight Agent demonstration complete!") | |
| if __name__ == "__main__": | |
| asyncio.run(demonstrate_intelligent_flight_agent()) | |