# services/external/app/schemas/weather.py """Weather data schemas""" from pydantic import BaseModel, Field, field_validator 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") @field_validator('id', mode='before') @classmethod 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") @field_validator('id', mode='before') @classmethod 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 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 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 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() }