hkaraoguz commited on
Commit
2fc6ac0
·
verified ·
1 Parent(s): d70a4af

Fall back when weather forecast is unavailable

Browse files
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
- response = self.http_client.get(
36
- "https://api.open-meteo.com/v1/forecast",
37
- params={
38
- "latitude": latitude,
39
- "longitude": longitude,
40
- "daily": (
41
- "precipitation_sum,temperature_2m_max,"
42
- "et0_fao_evapotranspiration"
43
- ),
44
- "forecast_days": 16,
45
- "timezone": "auto",
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
- for index, day in enumerate(daily["time"])
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 []