MET's series starts mid-today and thins toward the tail, and the parser
graded whatever samples a day happened to have: the current day was built
from the remaining afternoon hours (seen live on prod: an 'overnight low'
of 114.8F graded at the ~100th percentile) and the final day can collapse
to one sample with tmin == tmax. A day now needs its samples to span at
least 18 hours before it is emitted — full hourly days and the 0/6/12/18
6-hourly far-term shape both pass; partial today and the degenerate tail
are dropped instead of being served as false extremes.
Content pages keyed cache validity on the full-history hist_end, invalidating ~1-2x/day and paying a 45yr load even to compute the token. Add content_token(cell_id) = PAYLOAD_VER:CONTENT_VER:max_date, backed by an indexed MAX(date) (climate_store.history_max_date / climate.history_max_date), so it survives intra-day top-ups and needs no full-history load.
Steady-state usage of the Open-Meteo API is removed; it remains only as a dormant fallback. Geocoding -> local GeoNames + Nominatim; wind gusts -> Meteostat; history -> NASA POWER (curated cells seeded with keyless ERA5); recent+forecast -> NASA range + MET Norway. Plus a drift-check tool comparing NASA vs Open-Meteo. All keyless, no new infra. Backend suite green in-image (gate).