Improve the dahboard 6
This commit is contained in:
@@ -200,6 +200,36 @@ const WeatherContext: React.FC<WeatherContextProps> = ({ className = '' }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warning for unavailable/error weather data
|
||||||
|
if (weatherData && (weatherData.source === 'unavailable' || weatherData.source === 'error')) {
|
||||||
|
return (
|
||||||
|
<Card className={`p-6 border-yellow-200 bg-yellow-50 ${className}`}>
|
||||||
|
<div className="text-center">
|
||||||
|
<AlertTriangle className="h-12 w-12 text-yellow-600 mx-auto mb-4" />
|
||||||
|
<h3 className="text-lg font-medium text-yellow-800 mb-2">
|
||||||
|
{weatherData.source === 'unavailable' ? '⚠️ Servicio Meteorológico No Disponible' : '❌ Error en Datos Meteorológicos'}
|
||||||
|
</h3>
|
||||||
|
<p className="text-yellow-700 text-sm mb-4">
|
||||||
|
{weatherData.source === 'unavailable'
|
||||||
|
? 'AEMET está experimentando problemas temporales. Los datos meteorológicos no están disponibles en este momento.'
|
||||||
|
: 'Se ha producido un error al obtener los datos meteorológicos. Por favor, inténtalo de nuevo más tarde.'
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<div className="text-xs text-yellow-600 mb-4">
|
||||||
|
<strong>Estado:</strong> {weatherData.description}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={loadWeatherContext}
|
||||||
|
disabled={isRefreshing}
|
||||||
|
className="px-4 py-2 bg-yellow-100 hover:bg-yellow-200 text-yellow-800 rounded-lg transition-colors disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{isRefreshing ? 'Verificando AEMET...' : 'Reintentar Conexión'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Warning for synthetic data
|
// Warning for synthetic data
|
||||||
if (weatherData && weatherData.source === 'synthetic') {
|
if (weatherData && weatherData.source === 'synthetic') {
|
||||||
return (
|
return (
|
||||||
@@ -451,7 +481,7 @@ const WeatherContext: React.FC<WeatherContextProps> = ({ className = '' }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Hourly Forecast Preview */}
|
{/* Hourly Forecast Preview */}
|
||||||
{hourlyForecast.length > 0 && (
|
{hourlyForecast.length > 0 ? (
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h4 className="font-medium text-gray-800 mb-3">Pronóstico por Horas (Próximas 8 horas)</h4>
|
<h4 className="font-medium text-gray-800 mb-3">Pronóstico por Horas (Próximas 8 horas)</h4>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
@@ -475,10 +505,22 @@ const WeatherContext: React.FC<WeatherContextProps> = ({ className = '' }) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="mb-6">
|
||||||
|
<h4 className="font-medium text-gray-800 mb-3">Pronóstico por Horas</h4>
|
||||||
|
<div className="bg-gray-50 rounded-lg p-4 text-center">
|
||||||
|
<div className="text-sm text-gray-600">
|
||||||
|
⚠️ Pronóstico por horas no disponible temporalmente
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
Problemas de conectividad con AEMET
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Weather Forecast */}
|
{/* Weather Forecast */}
|
||||||
{weatherForecast.length > 0 && (
|
{weatherForecast.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium text-gray-800 mb-3">Pronóstico (Próximos Días)</h4>
|
<h4 className="font-medium text-gray-800 mb-3">Pronóstico (Próximos Días)</h4>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -505,6 +547,18 @@ const WeatherContext: React.FC<WeatherContextProps> = ({ className = '' }) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-800 mb-3">Pronóstico (Próximos Días)</h4>
|
||||||
|
<div className="bg-gray-50 rounded-lg p-4 text-center">
|
||||||
|
<div className="text-sm text-gray-600">
|
||||||
|
⚠️ Pronóstico diario no disponible temporalmente
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
Problemas de conectividad con AEMET
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Data Source Info & Controls */}
|
{/* Data Source Info & Controls */}
|
||||||
|
|||||||
13
services/external/app/api/weather.py
vendored
13
services/external/app/api/weather.py
vendored
@@ -51,7 +51,7 @@ async def get_current_weather(
|
|||||||
weather = await weather_service.get_current_weather(latitude, longitude)
|
weather = await weather_service.get_current_weather(latitude, longitude)
|
||||||
|
|
||||||
if not weather:
|
if not weather:
|
||||||
raise HTTPException(status_code=404, detail="Weather data not available")
|
raise HTTPException(status_code=503, detail="Weather service temporarily unavailable")
|
||||||
|
|
||||||
# Publish event
|
# Publish event
|
||||||
try:
|
try:
|
||||||
@@ -133,8 +133,10 @@ async def get_weather_forecast(
|
|||||||
|
|
||||||
forecast = await weather_service.get_weather_forecast(request.latitude, request.longitude, request.days)
|
forecast = await weather_service.get_weather_forecast(request.latitude, request.longitude, request.days)
|
||||||
|
|
||||||
|
# Don't return 404 for empty forecast - return empty list with 200 status
|
||||||
if not forecast:
|
if not forecast:
|
||||||
raise HTTPException(status_code=404, detail="Weather forecast not available")
|
logger.info("Weather forecast unavailable - returning empty list")
|
||||||
|
return []
|
||||||
|
|
||||||
# Publish event
|
# Publish event
|
||||||
try:
|
try:
|
||||||
@@ -180,11 +182,10 @@ async def get_hourly_weather_forecast(
|
|||||||
request.latitude, request.longitude, request.hours
|
request.latitude, request.longitude, request.hours
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Don't return 404 for empty hourly forecast - return empty list with 200 status
|
||||||
if not hourly_forecast:
|
if not hourly_forecast:
|
||||||
raise HTTPException(
|
logger.info("Hourly weather forecast unavailable - returning empty list")
|
||||||
status_code=404,
|
return []
|
||||||
detail="Hourly weather forecast not available. Please check AEMET API configuration."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Publish event
|
# Publish event
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class WeatherService:
|
|||||||
self.database_manager = database_manager
|
self.database_manager = database_manager
|
||||||
|
|
||||||
async def get_current_weather(self, latitude: float, longitude: float) -> Optional[WeatherDataResponse]:
|
async def get_current_weather(self, latitude: float, longitude: float) -> Optional[WeatherDataResponse]:
|
||||||
"""Get current weather for location"""
|
"""Get current weather for location with graceful failure handling"""
|
||||||
try:
|
try:
|
||||||
logger.debug("Getting current weather", lat=latitude, lon=longitude)
|
logger.debug("Getting current weather", lat=latitude, lon=longitude)
|
||||||
weather_data = await self.aemet_client.get_current_weather(latitude, longitude)
|
weather_data = await self.aemet_client.get_current_weather(latitude, longitude)
|
||||||
@@ -31,12 +31,32 @@ class WeatherService:
|
|||||||
logger.debug("Weather data received", source=weather_data.get('source'))
|
logger.debug("Weather data received", source=weather_data.get('source'))
|
||||||
return WeatherDataResponse(**weather_data)
|
return WeatherDataResponse(**weather_data)
|
||||||
else:
|
else:
|
||||||
logger.warning("No weather data received from AEMET client")
|
logger.warning("No weather data received from AEMET client - providing service unavailable response")
|
||||||
return None
|
# Return a response indicating service unavailable rather than None
|
||||||
|
return WeatherDataResponse(
|
||||||
|
date=datetime.utcnow().isoformat(),
|
||||||
|
temperature=None,
|
||||||
|
precipitation=None,
|
||||||
|
humidity=None,
|
||||||
|
wind_speed=None,
|
||||||
|
pressure=None,
|
||||||
|
description="Servicio meteorológico temporalmente no disponible",
|
||||||
|
source="unavailable"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to get current weather", error=str(e), lat=latitude, lon=longitude)
|
logger.error("Failed to get current weather", error=str(e), lat=latitude, lon=longitude)
|
||||||
return None
|
# Return error response rather than None to prevent 404
|
||||||
|
return WeatherDataResponse(
|
||||||
|
date=datetime.utcnow().isoformat(),
|
||||||
|
temperature=None,
|
||||||
|
precipitation=None,
|
||||||
|
humidity=None,
|
||||||
|
wind_speed=None,
|
||||||
|
pressure=None,
|
||||||
|
description="Error al obtener datos meteorológicos",
|
||||||
|
source="error"
|
||||||
|
)
|
||||||
|
|
||||||
async def get_weather_forecast(self, latitude: float, longitude: float, days: int = 7) -> List[WeatherForecastResponse]:
|
async def get_weather_forecast(self, latitude: float, longitude: float, days: int = 7) -> List[WeatherForecastResponse]:
|
||||||
"""Get weather forecast for location"""
|
"""Get weather forecast for location"""
|
||||||
|
|||||||
Reference in New Issue
Block a user