Add GET /tools/environment endpoint that fetches weather, forecast, sun times, and air quality data from user's volatile Qdrant collection. - Add qdrant-client dependency - Create QdrantReadClient wrapper for read-only queries - Add environment schemas and service in tools domain - Parse weather, forecast, sun times, and air quality from Qdrant payloads - Support user-specific collections via preferred_username from OIDC - Add comprehensive service tests (13 tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
186 lines
4.5 KiB
Python
186 lines
4.5 KiB
Python
"""
|
|
Environment data schemas for Tools domain.
|
|
|
|
Provides Pydantic models for weather, forecast, sun times, and air quality data
|
|
retrieved from the Qdrant volatile collection.
|
|
"""
|
|
from datetime import datetime
|
|
from typing import Optional, List, Any
|
|
from pydantic import Field
|
|
|
|
from src.shared.base import BaseSchema
|
|
|
|
|
|
class WeatherData(BaseSchema):
|
|
"""Current weather conditions."""
|
|
|
|
temperature: Optional[float] = Field(
|
|
None,
|
|
description="Current temperature in Celsius"
|
|
)
|
|
feels_like: Optional[float] = Field(
|
|
None,
|
|
description="Feels-like temperature in Celsius"
|
|
)
|
|
conditions: Optional[str] = Field(
|
|
None,
|
|
description="Weather conditions description (e.g., 'Partly Cloudy')"
|
|
)
|
|
humidity: Optional[int] = Field(
|
|
None,
|
|
ge=0,
|
|
le=100,
|
|
description="Humidity percentage"
|
|
)
|
|
wind_speed: Optional[float] = Field(
|
|
None,
|
|
description="Wind speed in km/h"
|
|
)
|
|
wind_direction: Optional[str] = Field(
|
|
None,
|
|
description="Wind direction (e.g., 'NW')"
|
|
)
|
|
pressure: Optional[float] = Field(
|
|
None,
|
|
description="Atmospheric pressure in hPa"
|
|
)
|
|
visibility: Optional[float] = Field(
|
|
None,
|
|
description="Visibility in km"
|
|
)
|
|
uv_index: Optional[float] = Field(
|
|
None,
|
|
description="UV index"
|
|
)
|
|
location: Optional[str] = Field(
|
|
None,
|
|
description="Location name"
|
|
)
|
|
icon: Optional[str] = Field(
|
|
None,
|
|
description="Weather icon code or URL"
|
|
)
|
|
|
|
|
|
class ForecastDay(BaseSchema):
|
|
"""Single day forecast data."""
|
|
|
|
date: str = Field(
|
|
...,
|
|
description="Date string (e.g., '2025-01-07')"
|
|
)
|
|
high: Optional[float] = Field(
|
|
None,
|
|
description="High temperature in Celsius"
|
|
)
|
|
low: Optional[float] = Field(
|
|
None,
|
|
description="Low temperature in Celsius"
|
|
)
|
|
conditions: Optional[str] = Field(
|
|
None,
|
|
description="Weather conditions description"
|
|
)
|
|
precipitation_chance: Optional[int] = Field(
|
|
None,
|
|
ge=0,
|
|
le=100,
|
|
description="Chance of precipitation percentage"
|
|
)
|
|
icon: Optional[str] = Field(
|
|
None,
|
|
description="Weather icon code or URL"
|
|
)
|
|
|
|
|
|
class SunTimesData(BaseSchema):
|
|
"""Sunrise and sunset times."""
|
|
|
|
sunrise: Optional[datetime] = Field(
|
|
None,
|
|
description="Sunrise time"
|
|
)
|
|
sunset: Optional[datetime] = Field(
|
|
None,
|
|
description="Sunset time"
|
|
)
|
|
daylight_minutes: Optional[int] = Field(
|
|
None,
|
|
description="Total daylight duration in minutes"
|
|
)
|
|
solar_noon: Optional[datetime] = Field(
|
|
None,
|
|
description="Solar noon time"
|
|
)
|
|
dawn: Optional[datetime] = Field(
|
|
None,
|
|
description="Civil dawn time"
|
|
)
|
|
dusk: Optional[datetime] = Field(
|
|
None,
|
|
description="Civil dusk time"
|
|
)
|
|
|
|
|
|
class AirQualityData(BaseSchema):
|
|
"""Air quality information."""
|
|
|
|
aqi: Optional[int] = Field(
|
|
None,
|
|
ge=0,
|
|
description="Air Quality Index"
|
|
)
|
|
quality: Optional[str] = Field(
|
|
None,
|
|
description="Quality category (Good, Moderate, Unhealthy, etc.)"
|
|
)
|
|
pm25: Optional[float] = Field(
|
|
None,
|
|
description="PM2.5 concentration in microg/m3"
|
|
)
|
|
pm10: Optional[float] = Field(
|
|
None,
|
|
description="PM10 concentration in microg/m3"
|
|
)
|
|
o3: Optional[float] = Field(
|
|
None,
|
|
description="Ozone concentration in ppb"
|
|
)
|
|
no2: Optional[float] = Field(
|
|
None,
|
|
description="Nitrogen dioxide concentration in ppb"
|
|
)
|
|
location: Optional[str] = Field(
|
|
None,
|
|
description="Location name"
|
|
)
|
|
|
|
|
|
class EnvironmentResponse(BaseSchema):
|
|
"""Combined environment data response."""
|
|
|
|
weather: Optional[WeatherData] = Field(
|
|
None,
|
|
description="Current weather conditions"
|
|
)
|
|
forecast: Optional[List[ForecastDay]] = Field(
|
|
None,
|
|
description="Multi-day weather forecast"
|
|
)
|
|
sun_times: Optional[SunTimesData] = Field(
|
|
None,
|
|
description="Sunrise/sunset times"
|
|
)
|
|
air_quality: Optional[AirQualityData] = Field(
|
|
None,
|
|
description="Air quality data (None if not available)"
|
|
)
|
|
updated_at: datetime = Field(
|
|
default_factory=datetime.utcnow,
|
|
description="Timestamp when data was fetched"
|
|
)
|
|
user: Optional[str] = Field(
|
|
None,
|
|
description="User identifier used for data lookup"
|
|
)
|