Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion src/api/api_v1/endpoints/forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from src.external_services.openmeteo import WeatherClientFactory
from src.schemas.point import GeoJSONOut
from src.schemas.history_data import HourlyObservationOut, HourlyResponse
from src.schemas.history_data import DailyResponse, HourlyObservationOut, HourlyResponse
from src.schemas.spray import SprayForecastResponse
from src.models.spray import SprayStatus
from src import utils
Expand Down Expand Up @@ -165,3 +165,50 @@ async def get_hourly_spray_forecast(
)

return results


# ---------------------------------------------------------------------------
# Daily irrigation forecast
# ---------------------------------------------------------------------------

@router.get(
"/daily/irrigation/",
response_model=DailyResponse,
summary="Daily weather forecast for smart irrigation (7–16 days)",
)
async def get_daily_irrigation_forecast(
lat: float = Query(..., description="Latitude", example=38.25),
lon: float = Query(..., description="Longitude", example=21.74),
days: int = Query(
16, ge=1, le=16,
description="Number of forecast days (including today)",
),
):
"""
Returns **daily** weather data optimized for smart irrigation systems.

Variables returned per day:
- **precipitation_sum** – total precipitation (mm)
- **precipitation_probability_max** – maximum precipitation probability (%)
- **temperature_2m_min** – minimum temperature (°C)
- **temperature_2m_max** – maximum temperature (°C)
- **et0_fao_evapotranspiration** – reference evapotranspiration ET₀ (mm)

Data comes from the Open-Meteo Forecast API.
Supports up to 16 days of forecast.
"""
client = WeatherClientFactory.get_provider()
try:
results = await client.get_daily_forecast(lat, lon, days=days)
except Exception as e:
logger.error(f"Error fetching daily forecast from Open-Meteo: {e}")
raise HTTPException(
status_code=502,
detail="Could not retrieve daily forecast from Open-Meteo",
) from e

return DailyResponse(
location={"lat": lat, "lon": lon},
data=results,
source="open-meteo",
)
72 changes: 72 additions & 0 deletions src/external_services/openmeteo.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ async def get_hourly_forecast(
) -> List[HourlyObservationOut]:
...

async def get_daily_forecast(
self, lat: float, lon: float, days: int = 16
) -> List[DailyObservationOut]:
...


class OpenMeteoClient:
BASE_URL = "https://archive-api.open-meteo.com/v1/archive"
Expand Down Expand Up @@ -165,6 +170,73 @@ async def get_hourly_forecast(
return results


# ---- Daily forecast (Open-Meteo Forecast API) ----
# Variables for smart irrigation: precipitation, probability, temp range, ET0
DAILY_FORECAST_VARIABLES = [
"precipitation_sum",
"precipitation_probability_max",
"temperature_2m_min",
"temperature_2m_max",
"et0_fao_evapotranspiration",
]

async def get_daily_forecast(
self, lat: float, lon: float, days: int = 16
) -> List[DailyObservationOut]:
"""
Fetch daily weather forecast for irrigation planning from the
Open-Meteo **Forecast** API.

Returns one :class:`DailyObservationOut` per day with:
- precipitation_sum (mm)
- precipitation_probability_max (%)
- temperature_2m_min (°C)
- temperature_2m_max (°C)
- et0_fao_evapotranspiration (mm)

Parameters
----------
lat, lon : float
Location coordinates.
days : int
Number of forecast days (1–16, default 16).
"""
params = {
"latitude": lat,
"longitude": lon,
"daily": ",".join(self.DAILY_FORECAST_VARIABLES),
"timezone": "auto",
"forecast_days": days,
}

data = await self._fetch_data(params, url=self.FORECAST_URL)

if "daily" not in data:
logger.warning("Open-Meteo returned no daily forecast data")
return []

timestamps = data["daily"]["time"]
results: List[DailyObservationOut] = []

for i, t in enumerate(timestamps):
values: Dict[str, Union[float, None]] = {}
for var in self.DAILY_FORECAST_VARIABLES:
if var in data["daily"]:
values[var] = data["daily"][var][i]
results.append(
DailyObservationOut(
date=date.fromisoformat(t),
values=values,
)
)

logger.info(
"Open-Meteo daily forecast: %d days for (%s, %s)",
len(results), lat, lon,
)
return results


# Factory using environment variable
class WeatherClientFactory:
_provider: Optional[WeatherProvider] = None
Expand Down