Limit weather API calls to cron only, increase cache TTL to 24h
Web view was calling the OpenWeatherMap API directly on every request (home + all trip locations), causing >1000 calls/day. Now web_view.py uses cache_only=True everywhere so it never triggers live API calls — update.py (cron) is the sole API caller. Also warms home location cache in update_weather(), and increases TTL from 6h to 24h. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
04cb3e8179
commit
feaefba03c
3 changed files with 45 additions and 6 deletions
|
|
@ -14,7 +14,7 @@ def _cache_path(data_dir: str, lat: float, lon: float) -> str:
|
||||||
return os.path.join(weather_dir, f"{lat:.2f}_{lon:.2f}.json")
|
return os.path.join(weather_dir, f"{lat:.2f}_{lon:.2f}.json")
|
||||||
|
|
||||||
|
|
||||||
def _is_fresh(path: str, max_age_hours: int = 6) -> bool:
|
def _is_fresh(path: str, max_age_hours: int = 24) -> bool:
|
||||||
"""Return True if the cache file exists and is recent enough."""
|
"""Return True if the cache file exists and is recent enough."""
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
return False
|
return False
|
||||||
|
|
@ -22,14 +22,30 @@ def _is_fresh(path: str, max_age_hours: int = 6) -> bool:
|
||||||
return age < max_age_hours * 3600
|
return age < max_age_hours * 3600
|
||||||
|
|
||||||
|
|
||||||
def get_forecast(data_dir: str, api_key: str, lat: float, lon: float) -> list[dict]:
|
def get_forecast(
|
||||||
"""Return 8-day daily forecast for lat/lon, caching results for 6 hours."""
|
data_dir: str,
|
||||||
|
api_key: str,
|
||||||
|
lat: float,
|
||||||
|
lon: float,
|
||||||
|
cache_only: bool = False,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""Return 8-day daily forecast for lat/lon, caching results for 24 hours.
|
||||||
|
|
||||||
|
If cache_only=True, return cached data if available (even if stale) and
|
||||||
|
never call the API. Returns [] if no cache exists.
|
||||||
|
"""
|
||||||
cache_file = _cache_path(data_dir, lat, lon)
|
cache_file = _cache_path(data_dir, lat, lon)
|
||||||
|
|
||||||
if _is_fresh(cache_file):
|
if _is_fresh(cache_file):
|
||||||
with open(cache_file) as f:
|
with open(cache_file) as f:
|
||||||
return json.load(f) # type: ignore[no-any-return]
|
return json.load(f) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
if cache_only:
|
||||||
|
if os.path.exists(cache_file):
|
||||||
|
with open(cache_file) as f:
|
||||||
|
return json.load(f) # type: ignore[no-any-return]
|
||||||
|
return []
|
||||||
|
|
||||||
owm = pyowm.OWM(api_key)
|
owm = pyowm.OWM(api_key)
|
||||||
mgr = owm.weather_manager()
|
mgr = owm.weather_manager()
|
||||||
result = mgr.one_call(lat=lat, lon=lon)
|
result = mgr.one_call(lat=lat, lon=lon)
|
||||||
|
|
@ -67,17 +83,23 @@ def trip_latlon(trip: object) -> tuple[float, float] | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_trip_weather(data_dir: str, api_key: str, trip: object) -> dict[str, dict]:
|
def get_trip_weather(
|
||||||
|
data_dir: str,
|
||||||
|
api_key: str,
|
||||||
|
trip: object,
|
||||||
|
cache_only: bool = False,
|
||||||
|
) -> dict[str, dict]:
|
||||||
"""Return forecast for a trip keyed by date ISO string.
|
"""Return forecast for a trip keyed by date ISO string.
|
||||||
|
|
||||||
Returns an empty dict if no location is known or the API call fails.
|
Returns an empty dict if no location is known or the API call fails.
|
||||||
|
If cache_only=True, never call the API (returns stale or empty data).
|
||||||
"""
|
"""
|
||||||
latlon = trip_latlon(trip)
|
latlon = trip_latlon(trip)
|
||||||
if not latlon:
|
if not latlon:
|
||||||
return {}
|
return {}
|
||||||
lat, lon = latlon
|
lat, lon = latlon
|
||||||
try:
|
try:
|
||||||
forecasts = get_forecast(data_dir, api_key, lat, lon)
|
forecasts = get_forecast(data_dir, api_key, lat, lon, cache_only=cache_only)
|
||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
return {}
|
||||||
return {f["date"]: f for f in forecasts}
|
return {f["date"]: f for f in forecasts}
|
||||||
|
|
|
||||||
16
update.py
16
update.py
|
|
@ -410,7 +410,7 @@ def update_gandi(config: flask.config.Config) -> None:
|
||||||
|
|
||||||
|
|
||||||
def update_weather(config: flask.config.Config) -> None:
|
def update_weather(config: flask.config.Config) -> None:
|
||||||
"""Refresh weather cache for all upcoming trips."""
|
"""Refresh weather cache for home and all upcoming trips."""
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
@ -425,6 +425,20 @@ def update_weather(config: flask.config.Config) -> None:
|
||||||
|
|
||||||
seen: set[tuple[float, float]] = set()
|
seen: set[tuple[float, float]] = set()
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
|
# Always include home location
|
||||||
|
home_lat = config.get("HOME_LATITUDE")
|
||||||
|
home_lon = config.get("HOME_LONGITUDE")
|
||||||
|
if home_lat is not None and home_lon is not None:
|
||||||
|
seen.add((home_lat, home_lon))
|
||||||
|
try:
|
||||||
|
agenda.weather.get_forecast(
|
||||||
|
config["DATA_DIR"], config["OPENWEATHERMAP_API_KEY"], home_lat, home_lon
|
||||||
|
)
|
||||||
|
count += 1
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"weather update failed for home {home_lat},{home_lon}: {exc}")
|
||||||
|
|
||||||
for trip in upcoming:
|
for trip in upcoming:
|
||||||
latlon = agenda.weather.trip_latlon(trip)
|
latlon = agenda.weather.trip_latlon(trip)
|
||||||
if not latlon or latlon in seen:
|
if not latlon or latlon in seen:
|
||||||
|
|
|
||||||
|
|
@ -627,6 +627,7 @@ def get_home_weather() -> list[StrDict]:
|
||||||
app.config.get("OPENWEATHERMAP_API_KEY", ""),
|
app.config.get("OPENWEATHERMAP_API_KEY", ""),
|
||||||
app.config["HOME_LATITUDE"],
|
app.config["HOME_LATITUDE"],
|
||||||
app.config["HOME_LONGITUDE"],
|
app.config["HOME_LONGITUDE"],
|
||||||
|
cache_only=True,
|
||||||
)
|
)
|
||||||
for f in forecasts:
|
for f in forecasts:
|
||||||
f["date_obj"] = date_type.fromisoformat(f["date"])
|
f["date_obj"] = date_type.fromisoformat(f["date"])
|
||||||
|
|
@ -729,6 +730,7 @@ def trip_future_list() -> str:
|
||||||
app.config["DATA_DIR"],
|
app.config["DATA_DIR"],
|
||||||
app.config.get("OPENWEATHERMAP_API_KEY", ""),
|
app.config.get("OPENWEATHERMAP_API_KEY", ""),
|
||||||
trip,
|
trip,
|
||||||
|
cache_only=True,
|
||||||
)
|
)
|
||||||
for trip in shown
|
for trip in shown
|
||||||
}
|
}
|
||||||
|
|
@ -1049,6 +1051,7 @@ def trip_page(start: str) -> str:
|
||||||
app.config["DATA_DIR"],
|
app.config["DATA_DIR"],
|
||||||
app.config.get("OPENWEATHERMAP_API_KEY", ""),
|
app.config.get("OPENWEATHERMAP_API_KEY", ""),
|
||||||
trip,
|
trip,
|
||||||
|
cache_only=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
return flask.render_template(
|
return flask.render_template(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue