2025-08-12 18:17:30 +02:00
|
|
|
# services/external/app/schemas/weather.py
|
2025-08-08 09:08:41 +02:00
|
|
|
"""Weather data schemas"""
|
|
|
|
|
|
2025-08-10 17:31:38 +02:00
|
|
|
from pydantic import BaseModel, Field, field_validator
|
2025-08-08 09:08:41 +02:00
|
|
|
from datetime import datetime
|
|
|
|
|
from typing import Optional, List
|
|
|
|
|
from uuid import UUID
|
|
|
|
|
|
|
|
|
|
class WeatherDataBase(BaseModel):
|
|
|
|
|
"""Base weather data schema"""
|
|
|
|
|
location_id: str = Field(..., max_length=100, description="Weather monitoring location ID")
|
|
|
|
|
date: datetime = Field(..., description="Date and time of weather measurement")
|
|
|
|
|
temperature: Optional[float] = Field(None, ge=-50, le=60, description="Temperature in Celsius")
|
|
|
|
|
precipitation: Optional[float] = Field(None, ge=0, description="Precipitation in mm")
|
|
|
|
|
humidity: Optional[float] = Field(None, ge=0, le=100, description="Humidity percentage")
|
|
|
|
|
wind_speed: Optional[float] = Field(None, ge=0, le=200, description="Wind speed in km/h")
|
|
|
|
|
pressure: Optional[float] = Field(None, ge=800, le=1200, description="Atmospheric pressure in hPa")
|
|
|
|
|
description: Optional[str] = Field(None, max_length=200, description="Weather description")
|
|
|
|
|
source: str = Field("aemet", max_length=50, description="Data source")
|
|
|
|
|
raw_data: Optional[str] = Field(None, description="Raw data from source")
|
|
|
|
|
|
|
|
|
|
class WeatherDataCreate(WeatherDataBase):
|
|
|
|
|
"""Schema for creating weather data"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
class WeatherDataUpdate(BaseModel):
|
|
|
|
|
"""Schema for updating weather data"""
|
|
|
|
|
temperature: Optional[float] = Field(None, ge=-50, le=60)
|
|
|
|
|
precipitation: Optional[float] = Field(None, ge=0)
|
|
|
|
|
humidity: Optional[float] = Field(None, ge=0, le=100)
|
|
|
|
|
wind_speed: Optional[float] = Field(None, ge=0, le=200)
|
|
|
|
|
pressure: Optional[float] = Field(None, ge=800, le=1200)
|
|
|
|
|
description: Optional[str] = Field(None, max_length=200)
|
|
|
|
|
raw_data: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
class WeatherDataResponse(WeatherDataBase):
|
|
|
|
|
"""Schema for weather data responses"""
|
|
|
|
|
id: str = Field(..., description="Unique identifier")
|
|
|
|
|
created_at: datetime = Field(..., description="Creation timestamp")
|
|
|
|
|
updated_at: datetime = Field(..., description="Last update timestamp")
|
|
|
|
|
|
2025-08-10 17:31:38 +02:00
|
|
|
@field_validator('id', mode='before')
|
|
|
|
|
@classmethod
|
2025-08-08 09:08:41 +02:00
|
|
|
def convert_uuid_to_string(cls, v):
|
|
|
|
|
if isinstance(v, UUID):
|
|
|
|
|
return str(v)
|
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
from_attributes = True
|
|
|
|
|
json_encoders = {
|
|
|
|
|
datetime: lambda v: v.isoformat()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class WeatherForecastBase(BaseModel):
|
|
|
|
|
"""Base weather forecast schema"""
|
|
|
|
|
location_id: str = Field(..., max_length=100, description="Location ID")
|
|
|
|
|
forecast_date: datetime = Field(..., description="Date for forecast")
|
|
|
|
|
temperature: Optional[float] = Field(None, ge=-50, le=60, description="Forecasted temperature")
|
|
|
|
|
precipitation: Optional[float] = Field(None, ge=0, description="Forecasted precipitation")
|
|
|
|
|
humidity: Optional[float] = Field(None, ge=0, le=100, description="Forecasted humidity")
|
|
|
|
|
wind_speed: Optional[float] = Field(None, ge=0, le=200, description="Forecasted wind speed")
|
|
|
|
|
description: Optional[str] = Field(None, max_length=200, description="Forecast description")
|
|
|
|
|
source: str = Field("aemet", max_length=50, description="Data source")
|
|
|
|
|
raw_data: Optional[str] = Field(None, description="Raw forecast data")
|
|
|
|
|
|
|
|
|
|
class WeatherForecastCreate(WeatherForecastBase):
|
|
|
|
|
"""Schema for creating weather forecasts"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
class WeatherForecastResponse(WeatherForecastBase):
|
|
|
|
|
"""Schema for weather forecast responses"""
|
|
|
|
|
id: str = Field(..., description="Unique identifier")
|
|
|
|
|
generated_at: datetime = Field(..., description="When forecast was generated")
|
|
|
|
|
created_at: datetime = Field(..., description="Creation timestamp")
|
|
|
|
|
updated_at: datetime = Field(..., description="Last update timestamp")
|
|
|
|
|
|
2025-08-10 17:31:38 +02:00
|
|
|
@field_validator('id', mode='before')
|
|
|
|
|
@classmethod
|
2025-08-08 09:08:41 +02:00
|
|
|
def convert_uuid_to_string(cls, v):
|
|
|
|
|
if isinstance(v, UUID):
|
|
|
|
|
return str(v)
|
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
from_attributes = True
|
|
|
|
|
json_encoders = {
|
|
|
|
|
datetime: lambda v: v.isoformat()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class WeatherDataList(BaseModel):
|
|
|
|
|
"""Schema for paginated weather data responses"""
|
|
|
|
|
data: List[WeatherDataResponse]
|
|
|
|
|
total: int = Field(..., description="Total number of records")
|
|
|
|
|
page: int = Field(..., description="Current page number")
|
|
|
|
|
per_page: int = Field(..., description="Records per page")
|
|
|
|
|
has_next: bool = Field(..., description="Whether there are more pages")
|
|
|
|
|
has_prev: bool = Field(..., description="Whether there are previous pages")
|
|
|
|
|
|
|
|
|
|
class WeatherForecastList(BaseModel):
|
|
|
|
|
"""Schema for paginated weather forecast responses"""
|
|
|
|
|
forecasts: List[WeatherForecastResponse]
|
|
|
|
|
total: int = Field(..., description="Total number of forecasts")
|
|
|
|
|
page: int = Field(..., description="Current page number")
|
|
|
|
|
per_page: int = Field(..., description="Forecasts per page")
|
|
|
|
|
|
|
|
|
|
class WeatherAnalytics(BaseModel):
|
|
|
|
|
"""Schema for weather analytics"""
|
|
|
|
|
location_id: str
|
|
|
|
|
period_start: datetime
|
|
|
|
|
period_end: datetime
|
|
|
|
|
avg_temperature: Optional[float] = None
|
|
|
|
|
min_temperature: Optional[float] = None
|
|
|
|
|
max_temperature: Optional[float] = None
|
|
|
|
|
total_precipitation: Optional[float] = None
|
|
|
|
|
avg_humidity: Optional[float] = None
|
|
|
|
|
avg_wind_speed: Optional[float] = None
|
|
|
|
|
avg_pressure: Optional[float] = None
|
|
|
|
|
weather_conditions: dict = Field(default_factory=dict)
|
|
|
|
|
rainy_days: int = 0
|
2025-08-12 18:17:30 +02:00
|
|
|
sunny_days: int = 0
|
|
|
|
|
|
|
|
|
|
class LocationRequest(BaseModel):
|
|
|
|
|
latitude: float
|
|
|
|
|
longitude: float
|
|
|
|
|
address: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
class DateRangeRequest(BaseModel):
|
|
|
|
|
start_date: datetime
|
|
|
|
|
end_date: datetime
|
|
|
|
|
|
|
|
|
|
class HistoricalWeatherRequest(BaseModel):
|
|
|
|
|
latitude: float
|
|
|
|
|
longitude: float
|
|
|
|
|
start_date: datetime
|
|
|
|
|
end_date: datetime
|
|
|
|
|
|
|
|
|
|
class WeatherForecastRequest(BaseModel):
|
|
|
|
|
latitude: float
|
|
|
|
|
longitude: float
|
2025-08-18 20:50:41 +02:00
|
|
|
days: int
|
|
|
|
|
|
|
|
|
|
class HourlyForecastRequest(BaseModel):
|
|
|
|
|
latitude: float
|
|
|
|
|
longitude: float
|
|
|
|
|
hours: int = Field(default=48, ge=1, le=48, description="Number of hours to forecast (1-48)")
|
|
|
|
|
|
|
|
|
|
class HourlyForecastResponse(BaseModel):
|
|
|
|
|
forecast_datetime: datetime
|
|
|
|
|
generated_at: datetime
|
|
|
|
|
temperature: Optional[float]
|
|
|
|
|
precipitation: Optional[float]
|
|
|
|
|
humidity: Optional[float]
|
|
|
|
|
wind_speed: Optional[float]
|
|
|
|
|
description: Optional[str]
|
|
|
|
|
source: str
|
2025-10-09 14:11:02 +02:00
|
|
|
hour: int
|
|
|
|
|
|
|
|
|
|
class WeatherForecastAPIResponse(BaseModel):
|
|
|
|
|
"""Simplified schema for API weather forecast responses (without database fields)"""
|
|
|
|
|
forecast_date: datetime = Field(..., description="Date for forecast")
|
|
|
|
|
generated_at: datetime = Field(..., description="When forecast was generated")
|
|
|
|
|
temperature: Optional[float] = Field(None, ge=-50, le=60, description="Forecasted temperature")
|
|
|
|
|
precipitation: Optional[float] = Field(None, ge=0, description="Forecasted precipitation")
|
|
|
|
|
humidity: Optional[float] = Field(None, ge=0, le=100, description="Forecasted humidity")
|
|
|
|
|
wind_speed: Optional[float] = Field(None, ge=0, le=200, description="Forecasted wind speed")
|
|
|
|
|
description: Optional[str] = Field(None, max_length=200, description="Forecast description")
|
|
|
|
|
source: str = Field("aemet", max_length=50, description="Data source")
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
json_encoders = {
|
|
|
|
|
datetime: lambda v: v.isoformat()
|
|
|
|
|
}
|