Since the dry/rain grading split moved to precip > 0, a sub-0.01" reanalysis
"trace" day is graded as a rain tier but its depth rounds to 0.00" / 0 mm, and
several surfaces still treated it as dry — a day would read "0.0" · Light rain,
N days since rain" at once. Align every consumer of a graded precip day with the
> 0 split so a trace day reads as the (very light) rain it was graded to be.
- Dry streak: dry_streaks and longest_dry_streak reset on any rain (> 0), not
the 0.01" rain-frequency line, so a trace day breaks the streak and its
"days since rain" no longer keeps climbing. (The rain_freq climatology stat
keeps the 0.01" measurable-rain convention.)
- Display: new fmtPrecipTier() prints "trace" for a rain-tier day whose depth
rounds to zero, rather than a bone-dry "0.00". Used on the calendar tooltip,
the day-page observation + ladder marker, and the recent/forecast table.
- weatherType() decides wet/dry from the grade class when it has it (a rain tier
means it rained even at trace depth), falling back to depth > 0; callers on the
calendar and day page pass the class.
- Recent-table and chart precip dots key their dry-vs-rain rendering on the grade
class instead of value > 0, so a trace day tints as rain, not dry.
Rain chart fan: the precipitation fan still used the pre-split colour map,
painting the whole 90-99 rain-day-percentile region one shade and 75-90 a tier
too dark. _band_stats emits a p95 mark (additive; unused by the temperature fan)
and RAIN_FAN remaps to the eight tiers -- 95-99 Severe, 90-95 Very Heavy, 60-90
Heavy -- with a p95 fallback in pget so an older cached payload still renders.
Open-Meteo's daily high/low for the current day aggregates only the hours
elapsed so far, so grading it reads a still-unfolding day as complete and
produces spurious extremes — a cool morning served as a 1st-percentile
record-low high. The MET Norway primary already guards this with its
diurnal-coverage gate; the Open-Meteo fallback had no equivalent, so a
fleet-wide failover onto it exposed the bug.
Use the utc_offset_seconds Open-Meteo reports for a timezone=auto request to
identify the cell's local today and exclude it from the bundle. Past days are
complete and future days are whole-day forecasts, so only today is dropped;
the day lands in the record once it is over. When no offset is reported the
guard is skipped rather than guessing a date.
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).