Decide correlation rules for ambiguous product groupings #136
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot#136
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent: #22. Blocks the event correlation layer — settle this before that pass is written.
#22 asks "how to group products when the meteorological relationship is ambiguous" and "user annotate / manually merge-split groupings?". These need answers up front because they determine the data model: a user-editable grouping needs override storage that a purely derived one does not.
Questions to settle
visible_location_filteris per-location, so a cross-location event would need its own visibility semantics.)Record the decisions here, then implement them in the correlation layer issue.
Proposed correlation rules (awaiting Ryan's sign-off)
Grounded in what the data actually provides: the VTEC lifecycle id (
NWS_{office}_{phenomenon}_{significance}_{etn}) exposes significance (W warning / A watch / Y advisory / S statement),SentAlert.upgrade_fromrecords explicit upgrade lineage, every hazard has an active interval (coalesce(onset, sent_at)→coalesce(cleared_at, expires)), MCDs carry real SPC product numbers, and lightning correlates by location + time only.The organizing idea: cores vs. context
An event is a per-location cluster of impact-significance hazards (warnings, advisories, statements — significance W/Y/S) linked by time proximity. Long-lived setup products (watches, MCDs, outlooks) attach to events as context; they never glue events together. This one distinction answers most of the ambiguity below coherently.
Q1 — Tornado Warning inside a Severe Thunderstorm Watch → one event; watch attached as context
The warning is a core member; the watch (significance A) attaches as a
contextmember to every event whose core interval overlaps the watch interval. A watch with no warnings at the location still forms its own watch-only event, so "we were under a watch but nothing fired" remains visible on the calendar. What a watch never does is chain: two separate warning rounds inside one 8-hour watch stay two events, each referencing the watch — otherwise Q2's lull rule would be meaningless whenever a watch is up (which is precisely when it matters).Q2 — Lull duration → 60-minute gap rule over core members, transitive
Two core hazards merge when their active intervals overlap or the gap between them is ≤ 60 minutes (transitive closure).
upgrade_fromedges always merge regardless of gap. Rationale for 60: distinct convective rounds at a point are almost always > 1 h apart; sub-hour gaps are usually the same complex re-warning. It's a named constant (EVENT_GAP_MINUTES) baked intoderivation_version— since the layer is derived and rebuildable (#135), tuning it later is a rebuild, not a migration.Q3 — SPC outlooks → day-band context, not membership (as proposed)
Outlooks render as a day-scale band on the calendar/day views, keyed by the existing
spc_outlook_pages/spc_outlook_risk_transitionsrows. Not event members — an ENH day would otherwise collapse every warning into one mega-event. MCDs sit in between and go the watch route: attached ascontextmembers to events overlapping their valid window (they have real product numbers and hours-scale windows), and additionally shown on the day view.Q4 — Multi-location → per-location events (as proposed)
Matches the app's entire visibility model (
visible_location_filteris per-location). Cross-location affinity is still cheap to display without a cross-location entity: the same lifecycle id appearing in several locations' events lets the day view badge "also affected N other locations" — purely derived, no new visibility semantics.Q5 — Manual merge/split → defer past v2.0.0
A user-override layer that survives rebuilds contradicts the "derived and rebuildable, never source of truth" constraint from #22 and roughly doubles the design (override storage, conflict semantics on re-derivation, UI). Ship the automatic rules, see where they actually misgroup, and revisit with evidence. Cheap interim affordance: the event view can show why members grouped (gap distances, upgrade edges), which makes misgroupings diagnosable.
Lightning (not in the original list, needs a rule)
Archived clusters (#132) form lightning episodes per location via the same 60-minute gap rule. An episode overlapping an event's core interval attaches as a
contextmember (it corroborates, it doesn't extend the event). An episode with no overlapping event forms its own lightning-only event so a dry-lightning day is still explorable.Interval edge cases
expires; if bothcleared_atandexpiresare NULL, cap atsent_at + 6 hso one malformed record can't chain a whole day together.event_kind(hazard/watch_only/lightning_only) so the calendar can render them at different visual weight.If these land as-is, #135 implements:
weather_events+weather_event_members(kind, member_id, role∈{core,context}),EVENT_GAP_MINUTES=60,derivation_version=1.✅ Decisions ratified (Ryan, 2026-08-01)
The proposal above is adopted as-is with one change:
EVENT_GAP_MINUTES = 90(not 60). Final answers to the checklist:contextmember. Watches never chain events; a watch with no warnings forms a watch-only event.upgrade_fromedges always merge. Constant baked intoderivation_version; retuning = rebuild.contextmembers to overlapping events.Plus the additions from the proposal: lightning episodes (same gap rule,
contextwhen overlapping an event, else lightning-only events), interval caps for never-cleared records (expires, elsesent_at + 6h), andevent_kind ∈ {hazard, watch_only, lightning_only}.#135 is unblocked and implements:
weather_events+weather_event_members(kind, member_id, role ∈ {core, context}),EVENT_GAP_MINUTES = 90,derivation_version = 1.