# services/external/app/ingestion/adapters/madrid_adapter.py """ Madrid city data adapter - Uses existing AEMET and Madrid OpenData clients """ from typing import List, Dict, Any from datetime import datetime import structlog from ..base_adapter import CityDataAdapter from app.external.aemet import AEMETClient from app.external.apis.madrid_traffic_client import MadridTrafficClient logger = structlog.get_logger() class MadridAdapter(CityDataAdapter): """Adapter for Madrid using AEMET + Madrid OpenData""" def __init__(self, city_id: str, config: Dict[str, Any]): super().__init__(city_id, config) self.aemet_client = AEMETClient() self.traffic_client = MadridTrafficClient() self.madrid_lat = 40.4168 self.madrid_lon = -3.7038 async def fetch_historical_weather( self, start_date: datetime, end_date: datetime ) -> List[Dict[str, Any]]: """Fetch historical weather from AEMET""" try: logger.info( "Fetching Madrid historical weather", start=start_date.isoformat(), end=end_date.isoformat() ) weather_data = await self.aemet_client.get_historical_weather( self.madrid_lat, self.madrid_lon, start_date, end_date ) for record in weather_data: record['city_id'] = self.city_id record['city_name'] = 'Madrid' logger.info( "Madrid weather data fetched", records=len(weather_data) ) return weather_data except Exception as e: logger.error("Error fetching Madrid weather", error=str(e)) return [] async def fetch_historical_traffic( self, start_date: datetime, end_date: datetime ) -> List[Dict[str, Any]]: """Fetch historical traffic from Madrid OpenData""" try: logger.info( "Fetching Madrid historical traffic", start=start_date.isoformat(), end=end_date.isoformat() ) traffic_data = await self.traffic_client.get_historical_traffic( self.madrid_lat, self.madrid_lon, start_date, end_date ) for record in traffic_data: record['city_id'] = self.city_id record['city_name'] = 'Madrid' logger.info( "Madrid traffic data fetched", records=len(traffic_data) ) return traffic_data except Exception as e: logger.error("Error fetching Madrid traffic", error=str(e)) return [] async def validate_connection(self) -> bool: """Validate connection to AEMET and Madrid OpenData Note: Validation is lenient - passes if traffic API works. AEMET rate limits may cause weather validation to fail during initialization. """ try: test_traffic = await self.traffic_client.get_current_traffic( self.madrid_lat, self.madrid_lon ) # Traffic API must work (critical for operations) if test_traffic is None: logger.error("Traffic API validation failed - this is critical") return False # Try weather API, but don't fail validation if rate limited test_weather = await self.aemet_client.get_current_weather( self.madrid_lat, self.madrid_lon ) if test_weather is None: logger.warning("Weather API validation failed (likely rate limited) - proceeding anyway") else: logger.info("Weather API validation successful") # Pass validation if traffic works (weather can be fetched later) return True except Exception as e: logger.error("Madrid adapter connection validation failed", error=str(e)) return False