Skip to content

Commit b1e7511

Browse files
committed
Add daily irrigation api call
1 parent ced64a2 commit b1e7511

2 files changed

Lines changed: 120 additions & 1 deletion

File tree

src/api/api_v1/endpoints/forecast.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from src.external_services.openmeteo import WeatherClientFactory
2424
from src.schemas.point import GeoJSONOut
25-
from src.schemas.history_data import HourlyObservationOut, HourlyResponse
25+
from src.schemas.history_data import DailyResponse, HourlyObservationOut, HourlyResponse
2626
from src.schemas.spray import SprayForecastResponse
2727
from src.models.spray import SprayStatus
2828
from src import utils
@@ -165,3 +165,50 @@ async def get_hourly_spray_forecast(
165165
)
166166

167167
return results
168+
169+
170+
# ---------------------------------------------------------------------------
171+
# Daily irrigation forecast
172+
# ---------------------------------------------------------------------------
173+
174+
@router.get(
175+
"/daily/irrigation/",
176+
response_model=DailyResponse,
177+
summary="Daily weather forecast for smart irrigation (7–16 days)",
178+
)
179+
async def get_daily_irrigation_forecast(
180+
lat: float = Query(..., description="Latitude", example=38.25),
181+
lon: float = Query(..., description="Longitude", example=21.74),
182+
days: int = Query(
183+
16, ge=1, le=16,
184+
description="Number of forecast days (including today)",
185+
),
186+
):
187+
"""
188+
Returns **daily** weather data optimized for smart irrigation systems.
189+
190+
Variables returned per day:
191+
- **precipitation_sum** – total precipitation (mm)
192+
- **precipitation_probability_max** – maximum precipitation probability (%)
193+
- **temperature_2m_min** – minimum temperature (°C)
194+
- **temperature_2m_max** – maximum temperature (°C)
195+
- **et0_fao_evapotranspiration** – reference evapotranspiration ET₀ (mm)
196+
197+
Data comes from the Open-Meteo Forecast API.
198+
Supports up to 16 days of forecast.
199+
"""
200+
client = WeatherClientFactory.get_provider()
201+
try:
202+
results = await client.get_daily_forecast(lat, lon, days=days)
203+
except Exception as e:
204+
logger.error(f"Error fetching daily forecast from Open-Meteo: {e}")
205+
raise HTTPException(
206+
status_code=502,
207+
detail="Could not retrieve daily forecast from Open-Meteo",
208+
) from e
209+
210+
return DailyResponse(
211+
location={"lat": lat, "lon": lon},
212+
data=results,
213+
source="open-meteo",
214+
)

src/external_services/openmeteo.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ async def get_hourly_forecast(
2727
) -> List[HourlyObservationOut]:
2828
...
2929

30+
async def get_daily_forecast(
31+
self, lat: float, lon: float, days: int = 16
32+
) -> List[DailyObservationOut]:
33+
...
34+
3035

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

167172

173+
# ---- Daily forecast (Open-Meteo Forecast API) ----
174+
# Variables for smart irrigation: precipitation, probability, temp range, ET0
175+
DAILY_FORECAST_VARIABLES = [
176+
"precipitation_sum",
177+
"precipitation_probability_max",
178+
"temperature_2m_min",
179+
"temperature_2m_max",
180+
"et0_fao_evapotranspiration",
181+
]
182+
183+
async def get_daily_forecast(
184+
self, lat: float, lon: float, days: int = 16
185+
) -> List[DailyObservationOut]:
186+
"""
187+
Fetch daily weather forecast for irrigation planning from the
188+
Open-Meteo **Forecast** API.
189+
190+
Returns one :class:`DailyObservationOut` per day with:
191+
- precipitation_sum (mm)
192+
- precipitation_probability_max (%)
193+
- temperature_2m_min (°C)
194+
- temperature_2m_max (°C)
195+
- et0_fao_evapotranspiration (mm)
196+
197+
Parameters
198+
----------
199+
lat, lon : float
200+
Location coordinates.
201+
days : int
202+
Number of forecast days (1–16, default 16).
203+
"""
204+
params = {
205+
"latitude": lat,
206+
"longitude": lon,
207+
"daily": ",".join(self.DAILY_FORECAST_VARIABLES),
208+
"timezone": "auto",
209+
"forecast_days": days,
210+
}
211+
212+
data = await self._fetch_data(params, url=self.FORECAST_URL)
213+
214+
if "daily" not in data:
215+
logger.warning("Open-Meteo returned no daily forecast data")
216+
return []
217+
218+
timestamps = data["daily"]["time"]
219+
results: List[DailyObservationOut] = []
220+
221+
for i, t in enumerate(timestamps):
222+
values: Dict[str, Union[float, None]] = {}
223+
for var in self.DAILY_FORECAST_VARIABLES:
224+
if var in data["daily"]:
225+
values[var] = data["daily"][var][i]
226+
results.append(
227+
DailyObservationOut(
228+
date=date.fromisoformat(t),
229+
values=values,
230+
)
231+
)
232+
233+
logger.info(
234+
"Open-Meteo daily forecast: %d days for (%s, %s)",
235+
len(results), lat, lon,
236+
)
237+
return results
238+
239+
168240
# Factory using environment variable
169241
class WeatherClientFactory:
170242
_provider: Optional[WeatherProvider] = None

0 commit comments

Comments
 (0)