Initial commit - production deployment
This commit is contained in:
1
services/external/app/schemas/__init__.py
vendored
Normal file
1
services/external/app/schemas/__init__.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# services/external/app/schemas/__init__.py
|
||||
134
services/external/app/schemas/calendar.py
vendored
Normal file
134
services/external/app/schemas/calendar.py
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
# services/external/app/schemas/calendar.py
|
||||
"""
|
||||
Calendar Schemas - Request/Response types for school calendars and location context
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict, Any
|
||||
from uuid import UUID
|
||||
|
||||
|
||||
class SchoolCalendarResponse(BaseModel):
|
||||
"""School calendar information"""
|
||||
calendar_id: str
|
||||
calendar_name: str
|
||||
city_id: str
|
||||
school_type: str
|
||||
academic_year: str
|
||||
holiday_periods: List[Dict[str, Any]]
|
||||
school_hours: Dict[str, Any]
|
||||
source: Optional[str] = None
|
||||
enabled: bool = True
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"calendar_id": "madrid_primary_2024_2025",
|
||||
"calendar_name": "Madrid Primary School Calendar 2024-2025",
|
||||
"city_id": "madrid",
|
||||
"school_type": "primary",
|
||||
"academic_year": "2024-2025",
|
||||
"holiday_periods": [
|
||||
{
|
||||
"name": "Christmas Holiday",
|
||||
"start_date": "2024-12-23",
|
||||
"end_date": "2025-01-07",
|
||||
"description": "Christmas and New Year break"
|
||||
}
|
||||
],
|
||||
"school_hours": {
|
||||
"morning_start": "09:00",
|
||||
"morning_end": "14:00",
|
||||
"has_afternoon_session": False
|
||||
},
|
||||
"source": "madrid_education_dept_2024",
|
||||
"enabled": True
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SchoolCalendarListResponse(BaseModel):
|
||||
"""List of school calendars for a city"""
|
||||
city_id: str
|
||||
calendars: List[SchoolCalendarResponse]
|
||||
total: int
|
||||
|
||||
|
||||
class CalendarCheckResponse(BaseModel):
|
||||
"""Response for holiday check"""
|
||||
date: str = Field(..., description="Date checked (ISO format)")
|
||||
is_holiday: bool = Field(..., description="Whether the date is a school holiday")
|
||||
holiday_name: Optional[str] = Field(None, description="Name of the holiday if applicable")
|
||||
calendar_id: str
|
||||
calendar_name: str
|
||||
|
||||
|
||||
class TenantLocationContextResponse(BaseModel):
|
||||
"""Tenant location context with calendar details"""
|
||||
tenant_id: str
|
||||
city_id: str
|
||||
neighborhood: Optional[str] = None
|
||||
local_events: Optional[List[Dict[str, Any]]] = None
|
||||
notes: Optional[str] = None
|
||||
calendar: Optional[Dict[str, Any]] = Field(
|
||||
None,
|
||||
description="Full calendar details if assigned"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"tenant_id": "fbffcf18-d02a-4104-b6e3-0b32006e3e47",
|
||||
"city_id": "madrid",
|
||||
"neighborhood": "Chamberí",
|
||||
"local_events": [
|
||||
{
|
||||
"name": "Neighborhood Festival",
|
||||
"date": "2025-06-15",
|
||||
"impact": "high"
|
||||
}
|
||||
],
|
||||
"notes": "Bakery near primary school",
|
||||
"calendar": {
|
||||
"calendar_id": "uuid",
|
||||
"calendar_name": "Madrid Primary School Calendar 2024-2025",
|
||||
"school_type": "primary",
|
||||
"academic_year": "2024-2025",
|
||||
"holiday_periods": [],
|
||||
"school_hours": {},
|
||||
"source": "madrid_education_dept_2024"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TenantLocationContextCreateRequest(BaseModel):
|
||||
"""Request to create/update tenant location context"""
|
||||
city_id: str = Field(..., description="City ID (e.g., 'madrid')")
|
||||
school_calendar_id: Optional[UUID] = Field(
|
||||
None,
|
||||
description="School calendar ID to assign"
|
||||
)
|
||||
neighborhood: Optional[str] = Field(None, description="Neighborhood name")
|
||||
local_events: Optional[List[Dict[str, Any]]] = Field(
|
||||
None,
|
||||
description="Local events specific to this location"
|
||||
)
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"city_id": "madrid",
|
||||
"school_calendar_id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"neighborhood": "Chamberí",
|
||||
"local_events": [
|
||||
{
|
||||
"name": "Local Market Day",
|
||||
"date": "2025-05-20",
|
||||
"impact": "medium"
|
||||
}
|
||||
],
|
||||
"notes": "Bakery located near primary school entrance"
|
||||
}
|
||||
}
|
||||
36
services/external/app/schemas/city_data.py
vendored
Normal file
36
services/external/app/schemas/city_data.py
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# services/external/app/schemas/city_data.py
|
||||
"""
|
||||
City Data Schemas - New response types for city-based operations
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class CityInfoResponse(BaseModel):
|
||||
"""Information about a supported city"""
|
||||
city_id: str
|
||||
name: str
|
||||
country: str
|
||||
latitude: float
|
||||
longitude: float
|
||||
radius_km: float
|
||||
weather_provider: str
|
||||
traffic_provider: str
|
||||
enabled: bool
|
||||
|
||||
|
||||
class DataAvailabilityResponse(BaseModel):
|
||||
"""Data availability for a city"""
|
||||
city_id: str
|
||||
city_name: str
|
||||
|
||||
weather_available: bool
|
||||
weather_start_date: Optional[str] = None
|
||||
weather_end_date: Optional[str] = None
|
||||
weather_record_count: int = 0
|
||||
|
||||
traffic_available: bool
|
||||
traffic_start_date: Optional[str] = None
|
||||
traffic_end_date: Optional[str] = None
|
||||
traffic_record_count: int = 0
|
||||
106
services/external/app/schemas/traffic.py
vendored
Normal file
106
services/external/app/schemas/traffic.py
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
# services/external/app/schemas/traffic.py
|
||||
"""
|
||||
Traffic Service Pydantic Schemas
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from uuid import UUID
|
||||
|
||||
class TrafficDataBase(BaseModel):
|
||||
"""Base traffic data schema"""
|
||||
location_id: str = Field(..., max_length=100, description="Traffic monitoring location ID")
|
||||
date: datetime = Field(..., description="Date and time of traffic measurement")
|
||||
traffic_volume: Optional[int] = Field(None, ge=0, description="Vehicles per hour")
|
||||
pedestrian_count: Optional[int] = Field(None, ge=0, description="Pedestrians per hour")
|
||||
congestion_level: Optional[str] = Field(None, pattern="^(low|medium|high)$", description="Traffic congestion level")
|
||||
average_speed: Optional[float] = Field(None, ge=0, le=200, description="Average speed in km/h")
|
||||
source: str = Field("madrid_opendata", max_length=50, description="Data source")
|
||||
raw_data: Optional[str] = Field(None, description="Raw data from source")
|
||||
|
||||
class TrafficDataCreate(TrafficDataBase):
|
||||
"""Schema for creating traffic data"""
|
||||
pass
|
||||
|
||||
class TrafficDataUpdate(BaseModel):
|
||||
"""Schema for updating traffic data"""
|
||||
traffic_volume: Optional[int] = Field(None, ge=0)
|
||||
pedestrian_count: Optional[int] = Field(None, ge=0)
|
||||
congestion_level: Optional[str] = Field(None, pattern="^(low|medium|high)$")
|
||||
average_speed: Optional[float] = Field(None, ge=0, le=200)
|
||||
raw_data: Optional[str] = None
|
||||
|
||||
class TrafficDataResponseDB(TrafficDataBase):
|
||||
"""Schema for traffic data responses from database"""
|
||||
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 TrafficDataList(BaseModel):
|
||||
"""Schema for paginated traffic data responses"""
|
||||
data: List[TrafficDataResponseDB]
|
||||
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 TrafficAnalytics(BaseModel):
|
||||
"""Schema for traffic analytics"""
|
||||
location_id: str
|
||||
period_start: datetime
|
||||
period_end: datetime
|
||||
avg_traffic_volume: Optional[float] = None
|
||||
avg_pedestrian_count: Optional[float] = None
|
||||
peak_traffic_hour: Optional[int] = None
|
||||
peak_pedestrian_hour: Optional[int] = None
|
||||
congestion_distribution: dict = Field(default_factory=dict)
|
||||
avg_speed: Optional[float] = None
|
||||
|
||||
class TrafficDataResponse(BaseModel):
|
||||
"""Schema for API traffic data responses"""
|
||||
date: datetime = Field(..., description="Date and time of traffic measurement")
|
||||
traffic_volume: Optional[int] = Field(None, ge=0, description="Vehicles per hour")
|
||||
pedestrian_count: Optional[int] = Field(None, ge=0, description="Pedestrians per hour")
|
||||
congestion_level: Optional[str] = Field(None, pattern="^(low|medium|high)$", description="Traffic congestion level")
|
||||
average_speed: Optional[float] = Field(None, ge=0, le=200, description="Average speed in km/h")
|
||||
source: str = Field(..., description="Data source")
|
||||
|
||||
class Config:
|
||||
json_encoders = {
|
||||
datetime: lambda v: v.isoformat()
|
||||
}
|
||||
|
||||
class LocationRequest(BaseModel):
|
||||
latitude: float
|
||||
longitude: float
|
||||
address: Optional[str] = None
|
||||
|
||||
class DateRangeRequest(BaseModel):
|
||||
start_date: datetime
|
||||
end_date: datetime
|
||||
|
||||
class HistoricalTrafficRequest(BaseModel):
|
||||
latitude: float
|
||||
longitude: float
|
||||
start_date: datetime
|
||||
end_date: datetime
|
||||
|
||||
class TrafficForecastRequest(BaseModel):
|
||||
latitude: float
|
||||
longitude: float
|
||||
hours: int = 24
|
||||
173
services/external/app/schemas/weather.py
vendored
Normal file
173
services/external/app/schemas/weather.py
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
# 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()
|
||||
}
|
||||
Reference in New Issue
Block a user