fix(weather): match prep behavior — serve cached weather regardless of age

The /api/journal/weather route was filtering out cache rows older than 24
hours via parse_weather_card_data, while journal_prep.py read the same
rows raw without freshness checking. Result: the daily prep referenced
"home" and "work" temperatures while the right-rail UI showed nothing —
two surfaces, same backing data, inconsistent visibility.

Two changes:

1. parse_weather_card_data no longer returns None for stale data.
   WeatherCard already exposes fetched_at and gracefully hides
   today_high / forecast fields when they're absent, so old data renders
   with whatever fields the cached forecast still covers.

2. The /weather route opportunistically schedules a background refresh
   for any cache row older than 4 hours. If the user's journal_config
   has lat/lon for that location_key, the refresh runs and the next
   page load gets fresh data; if no usable config, the refresh is a
   silent no-op and the stale cache is still served.

This makes prep and UI consistent. It also self-heals over time — once
locations are configured, stale caches get refreshed on the next page
load instead of waiting indefinitely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 21:19:57 -04:00
parent 3d916d7357
commit de4b1d7c7e
2 changed files with 43 additions and 6 deletions
+4 -6
View File
@@ -156,15 +156,13 @@ def parse_weather_card_data(
) -> dict | None:
"""
Parse a WeatherCache row into the metadata.weather card schema.
Returns None if the cache is stale (older than 24 hours) or unavailable.
Returns None if the cache row is missing or unparseable. Stale data is
returned as-is — the frontend uses `fetched_at` to convey freshness, and
WeatherCard handles missing today/forecast fields gracefully.
"""
from datetime import date, timedelta
if cache_row is None or cache_row.fetched_at is None:
return None
age_seconds = (datetime.now(timezone.utc) - cache_row.fetched_at).total_seconds()
if age_seconds > 86400:
if cache_row is None or cache_row.fetched_at is None or not cache_row.forecast_json:
return None
raw = cache_row.forecast_json or {}