feat(meteoalarm): merge MeteoFrance forecast days into one episode - #70
Merged
Conversation
MeteoFrance publishes one warning per calendar day, each running roughly 00:00 to 00:00 local, and the next day's bulletin goes live alongside the current day's for most of the day — live sampling found the overlap in 170 of 227 samples, spread across every hour rather than confined to an afternoon window. Because the MeteoFrance content key from #41 includes a forecast-day component, a single multi-day heat or storm episode became one entity per day and the id rolled over at midnight, breaking whatever referenced it. That is the defect #37 reports. Collapse a run of consecutive forecast days into one alert, keyed without the day component so it survives midnight. Region-picker mode explodes the bulletin first, into one alert per configured department, using the <area> blocks that already carry one areaDesc and one NUTS3 code each. The episode key becomes (sender, phenomenon, one department), which is what makes it stable: the set of departments a bulletin covers moves overnight — a thunderstorm bulletin was measured going from 83 departments to 54 — so a set-derived key, or its intersection with a multi-department config, splits the episode anyway. It also replaces an area_desc listing up to 83 departments with the one the user actually selected. The most severe day supplies the content wholesale, tie-broken to the earliest onset, and the window widens to span the run. Blending fields would let the record contradict itself, since severity_normalized comes from awareness_level and the icon from event. Per-day truth moves to a new episode_days attribute, absent on single-day runs where it would only restate the alert's own fields. Finished days are dropped before merging: otherwise a finished run and an upcoming run share the day-free id and the alert store, which keys by id, silently drops one. This makes the provider clock-dependent, so async_fetch takes an injectable now. A gap of more than one calendar day starts a new run; second and later runs re-add their first day to the key so two live runs can never collide, churning only the pending entity and never the one in effect. That gap case has never been observed live (0 of 227 samples), so getting the reading wrong costs an extra entity rather than a lost warning. Country-wide mode keeps the full-set key and still splits an episode when the footprint moves. Documented, not silent: exploding per department there would turn France into roughly 150 entities. _filter_by_regions goes back to pure filtering — identity is now owned by the merge, which runs after it — and normalize exports SEVERITY_RANK and meteoalarm_awareness_severity so the provider ranks days on the same ladder normalization applies, rather than a second copy that can drift. Measured against a live France feed at a fixed instant: 256 entities across 88 departments become 149, the most any one department carries drops from 6 to 3, and no (department, phenomenon) cell is lost. MeteoFrance entity ids change once on upgrade; stale entities are safe to delete. Other authorities and providers are untouched. Refs #37 Assisted-by: Claude:claude-opus-5
Review of the episode merge found three places where the words no longer matched the code, plus one framing gap. No behavior change. - events.md claimed an episode losing its earliest day is invisible. It usually is not: the dominant-day tie-break prefers the earliest day, so when that day finishes the content flips to the next day's headline and description — both allowlisted — and incident_updated fires. Describe the actual cadence, and scope the incident_removed sentence to the whole episode finishing rather than any single day. - The pre-merge identity docstrings (_forecast_window_key, _meteofrance_id, _compute_alert_id) still described forecast days as distinct entities. Shipped MeteoFrance ids are minted by the merge with an empty window_key; the day component survives only as the collision tie-breaker for a second live run. Say so, and mark the parse-time MeteoFrance id as provisional. - test_distinct_forecast_day_distinct_id's comment said the 4-day outlook "is not collapsed" — the opposite of shipped behavior, and an explicit acceptance item in the plan. Reword to per-warning parsing before the merge. - architecture.md now acknowledges per-day publication is MeteoFrance's deliberate product model (the two-panel vigilance map), not a feed quirk: the defect was this integration's 1:1 mapping of that model onto durable HA entities, and the merge re-maps it rather than corrects it. Refs #37 Assisted-by: Claude:claude-fable-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #37. Follows #68, which removed the green markers that would otherwise collide inside this merge.
Problem
MeteoFrance publishes one warning per calendar day, each running roughly
00:00 → 00:00local, and the next day's bulletin goes live alongside the current day's for most of the day — live sampling found the overlap in 170 of 227 samples, spread across every hour rather than confined to an afternoon window.The MeteoFrance content key from #41 includes a forecast-day component, so one multi-day heat or storm episode became one entity per day, with the id rolling over at midnight. That is what the reporter sees: two
Vigilance jaune caniculeentities, contiguous at the midnight boundary, and a fresh entity every morning.Fix
Collapse a run of consecutive forecast days into one alert, keyed without the day component.
Region-picker mode explodes the bulletin first, into one alert per configured department, using the
<area>blocks that already carry oneareaDescand one NUTS3 code each. The key becomes(sender, phenomenon, one department).That choice is the crux, and it is measured rather than assumed. The department set a bulletin covers moves overnight:
awareness_typeSo a set-derived key — or its intersection with a multi-department config — splits the episode anyway. Per-department scoping is stable, and is safe because live sampling shows at most one live warning per (department, phenomenon, day) across 203 samples. It also replaces an
area_desclisting up to 83 departments with the one the user selected.The most severe day supplies the content wholesale, tie-broken to the earliest onset; the window widens to span the run. Blending fields would let the record contradict itself, since
severity_normalizedderives fromawareness_leveland the icon fromevent— a blend could read "Vigilance jaune canicule" while carrying an orange level. Per-day truth moves to a newepisode_daysattribute.Result on live data
Replayed against a real France feed at a fixed instant:
Nothing is lost in the process: 88 → 88 departments covered, 149 → 149
(department, phenomenon)cells, and zero departments losing a phenomenon in region mode. I checked this explicitly because a wholesale dominant-day copy could plausibly have dropped coverage.Two things the plan got wrong, found while building
episode_dayson single-day runs was pure noise restating the alert's own fields. Now empty unless something actually merged, keeping attributes sparse.Scope and limitations
_filter_by_polygonfails loud first. The branch is defensive only.Breaking change
MeteoFrance entity ids change once on upgrade; stale entities are safe to delete. Multi-department configurations also get a granularity change — one entity per configured department instead of one per bulletin — so they will see more entities than before, each stably named and identified. Both need announcing in the release notes and on #37.
Every other authority and provider is untouched, guarded by pass-through tests.
Also in this PR
_filter_by_regionsreturns to pure filtering; identity is owned by the merge that runs after it.normalizeexportsSEVERITY_RANKandmeteoalarm_awareness_severityso the provider ranks days on the same ladder normalization applies, instead of a second copy that can drift.Verification
pytest tests -qruff check/ruff format --diffmypy custom_components/cap_alerts17 new tests in
tests/test_meteoalarm_episodes.py. Payloads are built rather than stored as JSON fixtures — the shapes differ from one another by a field or two, so a builder keeps the difference legible where five near-identical fixture files would bury it. Every test injectsnow, since the merge is now clock-dependent.Two existing tests changed, both anticipated:
test_region_scope_stable_across_set_churnbecametest_region_filter_is_pure_filtering(its scope-stability assertion is now covered end-to-end bytest_episode_stable_across_footprint_churn), andtest_region_picker_intersects_nuts3pinsnowinside its fixture's window.Refs #37
Assisted-by: Claude:claude-opus-5