Spaces:
Runtime error
Runtime error
Fall back when weather forecast is unavailable
Browse files- README.md +2 -0
- tests/test_adapters.py +11 -0
- waterleaf/services/open_meteo.py +26 -24
README.md
CHANGED
|
@@ -254,6 +254,8 @@ available at `scripts/smoke_modal.py`.
|
|
| 254 |
- Public pages use opaque slugs but are intentionally public for calendar use.
|
| 255 |
- Plant identification and watering dates are suggestions, not horticultural
|
| 256 |
guarantees.
|
|
|
|
|
|
|
| 257 |
- Dates after the 16-day forecast are labeled seasonal estimates.
|
| 258 |
- ICS `ATTACH` support varies by calendar client; every event also includes a
|
| 259 |
portable public profile link.
|
|
|
|
| 254 |
- Public pages use opaque slugs but are intentionally public for calendar use.
|
| 255 |
- Plant identification and watering dates are suggestions, not horticultural
|
| 256 |
guarantees.
|
| 257 |
+
- If the weather API is unavailable, Waterleaf still generates dates from the
|
| 258 |
+
care baseline and labels them as seasonal estimates.
|
| 259 |
- Dates after the 16-day forecast are labeled seasonal estimates.
|
| 260 |
- ICS `ATTACH` support varies by calendar client; every event also includes a
|
| 261 |
portable public profile link.
|
tests/test_adapters.py
CHANGED
|
@@ -211,6 +211,17 @@ def test_open_meteo_geocodes_and_parses_forecast():
|
|
| 211 |
assert forecast[1].precipitation_mm == 7.2
|
| 212 |
|
| 213 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
def test_llama_cpp_sends_all_images_and_constrained_schema(tmp_path):
|
| 215 |
image_paths: list[Path] = []
|
| 216 |
for index in range(2):
|
|
|
|
| 211 |
assert forecast[1].precipitation_mm == 7.2
|
| 212 |
|
| 213 |
|
| 214 |
+
def test_open_meteo_uses_seasonal_schedule_when_forecast_is_rate_limited():
|
| 215 |
+
def handler(request: httpx.Request) -> httpx.Response:
|
| 216 |
+
return httpx.Response(429, text="Too Many Requests")
|
| 217 |
+
|
| 218 |
+
client = OpenMeteoClient(
|
| 219 |
+
http_client=httpx.Client(transport=httpx.MockTransport(handler))
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
assert client.forecast(59.33, 18.07) == []
|
| 223 |
+
|
| 224 |
+
|
| 225 |
def test_llama_cpp_sends_all_images_and_constrained_schema(tmp_path):
|
| 226 |
image_paths: list[Path] = []
|
| 227 |
for index in range(2):
|
waterleaf/services/open_meteo.py
CHANGED
|
@@ -32,28 +32,30 @@ class OpenMeteoClient:
|
|
| 32 |
)
|
| 33 |
|
| 34 |
def forecast(self, latitude: float, longitude: float) -> list[WeatherDay]:
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
"
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
response.raise_for_status()
|
| 49 |
-
daily = response.json()["daily"]
|
| 50 |
-
return [
|
| 51 |
-
WeatherDay(
|
| 52 |
-
date=date.fromisoformat(day),
|
| 53 |
-
precipitation_mm=float(daily["precipitation_sum"][index] or 0),
|
| 54 |
-
max_temperature_c=float(daily["temperature_2m_max"][index] or 0),
|
| 55 |
-
et0_mm=float(daily["et0_fao_evapotranspiration"][index] or 0),
|
| 56 |
)
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
)
|
| 33 |
|
| 34 |
def forecast(self, latitude: float, longitude: float) -> list[WeatherDay]:
|
| 35 |
+
try:
|
| 36 |
+
response = self.http_client.get(
|
| 37 |
+
"https://api.open-meteo.com/v1/forecast",
|
| 38 |
+
params={
|
| 39 |
+
"latitude": latitude,
|
| 40 |
+
"longitude": longitude,
|
| 41 |
+
"daily": (
|
| 42 |
+
"precipitation_sum,temperature_2m_max,"
|
| 43 |
+
"et0_fao_evapotranspiration"
|
| 44 |
+
),
|
| 45 |
+
"forecast_days": 16,
|
| 46 |
+
"timezone": "auto",
|
| 47 |
+
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
)
|
| 49 |
+
response.raise_for_status()
|
| 50 |
+
daily = response.json()["daily"]
|
| 51 |
+
return [
|
| 52 |
+
WeatherDay(
|
| 53 |
+
date=date.fromisoformat(day),
|
| 54 |
+
precipitation_mm=float(daily["precipitation_sum"][index] or 0),
|
| 55 |
+
max_temperature_c=float(daily["temperature_2m_max"][index] or 0),
|
| 56 |
+
et0_mm=float(daily["et0_fao_evapotranspiration"][index] or 0),
|
| 57 |
+
)
|
| 58 |
+
for index, day in enumerate(daily["time"])
|
| 59 |
+
]
|
| 60 |
+
except (httpx.HTTPError, KeyError, TypeError, ValueError):
|
| 61 |
+
return []
|