Fix traffic data
This commit is contained in:
160
services/data/app/external/aemet.py
vendored
160
services/data/app/external/aemet.py
vendored
@@ -1,7 +1,7 @@
|
||||
# ================================================================
|
||||
# services/data/app/external/aemet.py
|
||||
# services/data/app/external/aemet.py - FIXED VERSION
|
||||
# ================================================================
|
||||
"""AEMET (Spanish Weather Service) API client - PROPER API FLOW FIX"""
|
||||
"""AEMET (Spanish Weather Service) API client - FIXED FORECAST PARSING"""
|
||||
|
||||
import math
|
||||
from typing import List, Dict, Any, Optional
|
||||
@@ -209,7 +209,7 @@ class AEMETClient(BaseAPIClient):
|
||||
return self._get_default_weather_data()
|
||||
|
||||
def _parse_forecast_data(self, data: List, days: int) -> List[Dict[str, Any]]:
|
||||
"""Parse AEMET forecast data"""
|
||||
"""Parse AEMET forecast data - FIXED VERSION"""
|
||||
forecast = []
|
||||
base_date = datetime.now().date()
|
||||
|
||||
@@ -218,31 +218,121 @@ class AEMETClient(BaseAPIClient):
|
||||
return []
|
||||
|
||||
try:
|
||||
# AEMET forecast data structure might be different
|
||||
# For now, we'll generate synthetic data based on the number of days requested
|
||||
for i in range(min(days, 14)): # Limit to reasonable forecast range
|
||||
forecast_date = base_date + timedelta(days=i)
|
||||
# AEMET forecast structure is complex - parse what we can and fill gaps with synthetic data
|
||||
logger.debug("Processing AEMET forecast data", data_length=len(data))
|
||||
|
||||
# If we have actual AEMET data, try to parse it
|
||||
if len(data) > 0 and isinstance(data[0], dict):
|
||||
aemet_data = data[0]
|
||||
logger.debug("AEMET forecast keys", keys=list(aemet_data.keys()) if isinstance(aemet_data, dict) else "not_dict")
|
||||
|
||||
# Try to extract data from AEMET response if available
|
||||
day_data = {}
|
||||
if i < len(data) and isinstance(data[i], dict):
|
||||
day_data = data[i]
|
||||
# Try to extract daily forecasts from AEMET structure
|
||||
dias = aemet_data.get('prediccion', {}).get('dia', []) if isinstance(aemet_data, dict) else []
|
||||
|
||||
forecast.append({
|
||||
"forecast_date": datetime.combine(forecast_date, datetime.min.time()),
|
||||
"generated_at": datetime.now(),
|
||||
"temperature": self._safe_float(day_data.get("temperatura"), 15.0 + (i % 10)),
|
||||
"precipitation": self._safe_float(day_data.get("precipitacion"), 0.0),
|
||||
"humidity": self._safe_float(day_data.get("humedad"), 50.0 + (i % 20)),
|
||||
"wind_speed": self._safe_float(day_data.get("viento"), 10.0 + (i % 15)),
|
||||
"description": str(day_data.get("descripcion", "Partly cloudy")),
|
||||
"source": "aemet"
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error("Error parsing forecast data", error=str(e))
|
||||
return []
|
||||
if isinstance(dias, list) and len(dias) > 0:
|
||||
# Parse AEMET daily forecast format
|
||||
for i, dia in enumerate(dias[:days]):
|
||||
if not isinstance(dia, dict):
|
||||
continue
|
||||
|
||||
forecast_date = base_date + timedelta(days=i)
|
||||
|
||||
# Extract temperature data (AEMET has complex temp structure)
|
||||
temp_data = dia.get('temperatura', {})
|
||||
if isinstance(temp_data, dict):
|
||||
temp_max = self._extract_temp_value(temp_data.get('maxima'))
|
||||
temp_min = self._extract_temp_value(temp_data.get('minima'))
|
||||
avg_temp = (temp_max + temp_min) / 2 if temp_max and temp_min else 15.0
|
||||
else:
|
||||
avg_temp = 15.0
|
||||
|
||||
# Extract precipitation probability
|
||||
precip_data = dia.get('probPrecipitacion', [])
|
||||
precip_prob = 0.0
|
||||
if isinstance(precip_data, list) and len(precip_data) > 0:
|
||||
for precip_item in precip_data:
|
||||
if isinstance(precip_item, dict) and 'value' in precip_item:
|
||||
precip_prob = max(precip_prob, self._safe_float(precip_item.get('value'), 0.0))
|
||||
|
||||
# Extract wind data
|
||||
viento_data = dia.get('viento', [])
|
||||
wind_speed = 10.0
|
||||
if isinstance(viento_data, list) and len(viento_data) > 0:
|
||||
for viento_item in viento_data:
|
||||
if isinstance(viento_item, dict) and 'velocidad' in viento_item:
|
||||
speed_values = viento_item.get('velocidad', [])
|
||||
if isinstance(speed_values, list) and len(speed_values) > 0:
|
||||
wind_speed = self._safe_float(speed_values[0], 10.0)
|
||||
break
|
||||
|
||||
# Generate description based on precipitation probability
|
||||
if precip_prob > 70:
|
||||
description = "Lluvioso"
|
||||
elif precip_prob > 30:
|
||||
description = "Parcialmente nublado"
|
||||
else:
|
||||
description = "Soleado"
|
||||
|
||||
forecast.append({
|
||||
"forecast_date": datetime.combine(forecast_date, datetime.min.time()),
|
||||
"generated_at": datetime.now(),
|
||||
"temperature": round(avg_temp, 1),
|
||||
"precipitation": precip_prob / 10, # Convert percentage to mm estimate
|
||||
"humidity": 50.0 + (i % 20), # Estimate
|
||||
"wind_speed": round(wind_speed, 1),
|
||||
"description": description,
|
||||
"source": "aemet"
|
||||
})
|
||||
|
||||
logger.debug("Parsed forecast day", day=i, temp=avg_temp, precip=precip_prob)
|
||||
|
||||
# If we successfully parsed some days, fill remaining with synthetic
|
||||
remaining_days = days - len(forecast)
|
||||
if remaining_days > 0:
|
||||
synthetic_forecast = self._generate_synthetic_forecast_sync(remaining_days, len(forecast))
|
||||
forecast.extend(synthetic_forecast)
|
||||
|
||||
# If no valid AEMET data was parsed, use synthetic
|
||||
if len(forecast) == 0:
|
||||
logger.info("No valid AEMET forecast data found, using synthetic")
|
||||
forecast = self._generate_synthetic_forecast_sync(days, 0)
|
||||
|
||||
return forecast
|
||||
except Exception as e:
|
||||
logger.error("Error parsing AEMET forecast data", error=str(e))
|
||||
# Fallback to synthetic forecast
|
||||
forecast = self._generate_synthetic_forecast_sync(days, 0)
|
||||
|
||||
# Ensure we always return the requested number of days
|
||||
if len(forecast) < days:
|
||||
remaining = days - len(forecast)
|
||||
synthetic_remaining = self._generate_synthetic_forecast_sync(remaining, len(forecast))
|
||||
forecast.extend(synthetic_remaining)
|
||||
|
||||
return forecast[:days] # Ensure we don't exceed requested days
|
||||
|
||||
def _extract_temp_value(self, temp_data) -> Optional[float]:
|
||||
"""Extract temperature value from AEMET complex temperature structure"""
|
||||
if temp_data is None:
|
||||
return None
|
||||
|
||||
if isinstance(temp_data, (int, float)):
|
||||
return float(temp_data)
|
||||
|
||||
if isinstance(temp_data, str):
|
||||
try:
|
||||
return float(temp_data)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
if isinstance(temp_data, dict) and 'valor' in temp_data:
|
||||
return self._safe_float(temp_data['valor'], None)
|
||||
|
||||
if isinstance(temp_data, list) and len(temp_data) > 0:
|
||||
first_item = temp_data[0]
|
||||
if isinstance(first_item, dict) and 'valor' in first_item:
|
||||
return self._safe_float(first_item['valor'], None)
|
||||
|
||||
return None
|
||||
|
||||
def _safe_float(self, value: Any, default: float) -> float:
|
||||
"""Safely convert value to float with fallback"""
|
||||
@@ -292,32 +382,36 @@ class AEMETClient(BaseAPIClient):
|
||||
"source": "synthetic"
|
||||
}
|
||||
|
||||
async def _generate_synthetic_forecast(self, days: int) -> List[Dict[str, Any]]:
|
||||
"""Generate synthetic forecast data"""
|
||||
def _generate_synthetic_forecast_sync(self, days: int, start_offset: int = 0) -> List[Dict[str, Any]]:
|
||||
"""Generate synthetic forecast data synchronously"""
|
||||
forecast = []
|
||||
base_date = datetime.now().date()
|
||||
|
||||
for i in range(days):
|
||||
forecast_date = base_date + timedelta(days=i)
|
||||
forecast_date = base_date + timedelta(days=start_offset + i)
|
||||
|
||||
# Seasonal temperature
|
||||
month = forecast_date.month
|
||||
base_temp = 5 + (month - 1) * 2.5
|
||||
temp_variation = (i % 7 - 3) * 2 # Weekly variation
|
||||
temp_variation = ((start_offset + i) % 7 - 3) * 2 # Weekly variation
|
||||
|
||||
forecast.append({
|
||||
"forecast_date": datetime.combine(forecast_date, datetime.min.time()),
|
||||
"generated_at": datetime.now(),
|
||||
"temperature": round(base_temp + temp_variation, 1),
|
||||
"precipitation": 2.0 if i % 5 == 0 else 0.0,
|
||||
"humidity": 50 + (i % 30),
|
||||
"wind_speed": 10 + (i % 15),
|
||||
"description": "Lluvioso" if i % 5 == 0 else "Soleado",
|
||||
"precipitation": 2.0 if (start_offset + i) % 5 == 0 else 0.0,
|
||||
"humidity": 50 + ((start_offset + i) % 30),
|
||||
"wind_speed": 10 + ((start_offset + i) % 15),
|
||||
"description": "Lluvioso" if (start_offset + i) % 5 == 0 else "Soleado",
|
||||
"source": "synthetic"
|
||||
})
|
||||
|
||||
return forecast
|
||||
|
||||
async def _generate_synthetic_forecast(self, days: int) -> List[Dict[str, Any]]:
|
||||
"""Generate synthetic forecast data (async version for compatibility)"""
|
||||
return self._generate_synthetic_forecast_sync(days, 0)
|
||||
|
||||
async def _generate_synthetic_historical(self, start_date: datetime, end_date: datetime) -> List[Dict[str, Any]]:
|
||||
"""Generate synthetic historical weather data"""
|
||||
historical_data = []
|
||||
|
||||
Reference in New Issue
Block a user