meshai/tests
Matt Johnson (via Claude) 3a410d5087 feat(v0.6-phase3): reminder system + schema split + NWS dedup relaxation
Third broadcast type Active: clock-driven re-broadcasts of still-live
events at human-scale cadences. WFIGS fires 8h, itd_511 work zones daily
8 AM Mountain, SWPC G-storms 8h. NWS is NOT a clock reminder -- instead
the per-CAP-id dedup is relaxed to allow re-broadcast if >3h since last.
Schema split first_broadcast_at + last_broadcast_at on all reminder-
eligible tables. Wire prefix logic: New (first sight), Update (WFIGS
material change), Active (clock reminder). All cadences, channels, day-
of-week patterns, timezones, and termination conditions GUI-editable
from day one via the existing adapter_config editor. Termination:
tombstone OR containment_100 OR end_date_passed (no max-count). Quiet
hours not respected -- ripped out in Phase 2.

Schema (v11.sql):
  - ALTER TABLE fires|nws_alerts|traffic_events|quake_events|swpc_events|
    gauge_readings ADD COLUMN first_broadcast_at REAL
  - Backfill: UPDATE ... SET first_broadcast_at = last_broadcast_at
    WHERE last_broadcast_at IS NOT NULL
  - ALTER TABLE adapter_meta ADD COLUMN reminder_enabled INTEGER NOT NULL
    DEFAULT 0
  - UPDATE adapter_meta SET reminder_enabled=1 WHERE adapter IN
    ('wfigs', 'swpc') -- itd_511_work_zone is a new meta row seeded
    with reminder_enabled=1
  - SCHEMA_VERSION 10 -> 11

Handler commit-callbacks (wfigs/nws/quake/swpc/incident):
  - UPDATE ... SET last_broadcast_at=?, first_broadcast_at=COALESCE(
    first_broadcast_at, ?) -- first_broadcast_at stamped once, never
    overwritten

NWS handler (meshai/central/nws_handler.py):
  - _render() gains a prefix kwarg
  - After-first-broadcast branch: when (now - last_broadcast_at) >=
    adapter_config.nws.duplicate_allowed_after_seconds (default 10800
    = 3h), allow the re-broadcast with prefix=Active. Under the
    window, suppress as before. The commit callback continues to
    update last_broadcast_at.

ReminderScheduler (meshai/notifications/reminders/__init__.py):
  - Async loop, ticks every 60s
  - Each tick: SELECT adapter FROM adapter_meta WHERE reminder_enabled=1
  - Per adapter, load reminders_<adapter> config from adapter_config
    (cadence_kind, cadence_value, channels, terminate_when, dow_mask,
    timezone)
  - Interval cadence: rows where last_broadcast_at <= now - cadence_value
  - Clock cadence: localizes now to configured tz, finds slots that
    just passed in the last tick window, gated by dow_mask
  - Termination conditions checked per adapter:
      wfigs.containment_100      -> current_contained_pct >= 100
      wfigs.last_event_age_24h   -> last_event_at older than 24h
      swpc.end_date_passed       -> payload_json end_time in past
      itd_511_work_zone.end_date_passed -> traffic_events.end_at in past
  - Active: prefix on every emitted wire; dispatcher.dispatch_scheduled_
    broadcast() honors cold-start grace, bypasses toggle path
  - On success, last_broadcast_at = now; first_broadcast_at preserved

Launched from notifications/pipeline/__init__.py:start_pipeline()
alongside BandConditionsScheduler.

adapter_config registry (+15 new keys, 43 -> 58):
  - reminders_wfigs.cadence_kind/cadence_value/channels/terminate_when
  - reminders_swpc.cadence_kind/cadence_value/channels/terminate_when
  - reminders_itd_511_work_zone.cadence_kind/cadence_value/channels/
    dow_mask/timezone/terminate_when
  - nws.duplicate_allowed_after_seconds

adapter_meta (+4 rows, 15 -> 19):
  - reminders_wfigs, reminders_swpc, reminders_itd_511_work_zone
    (pseudo-adapters carrying the reminder config)
  - itd_511_work_zone (reminder target row; reminder_enabled=1)
  - reminder_enabled flag added to wfigs/swpc (existing rows updated by
    v11.sql) and to itd_511_work_zone seed.

Tests (tests/test_reminders.py, 10 cases):
  - wfigs reminder fires past 8h cadence, stamps last_broadcast_at,
    preserves first_broadcast_at
  - reminder skipped within cadence
  - reminder skipped when containment_100, last_event_age_24h
  - swpc reminder fires (interval)
  - work_zone clock reminder fires at 08:00 Mountain on enabled DOW
  - work_zone reminder skipped when end_date_passed
  - work_zone reminder skipped outside slot window
  - reminder_enabled=0 suppresses all reminders for that adapter

tests/test_nws_dedup_relaxation.py (5 cases):
  - First sighting renders without Active: prefix
  - Re-broadcast within 3h suppressed
  - Re-broadcast after 3h allowed with Active: prefix
  - adapter_config.nws.duplicate_allowed_after_seconds override takes
    effect (1h window verified)
  - First sighting stamps first_broadcast_at=committed_at,
    last_broadcast_at=committed_at; 4h later broadcast stamps
    last_broadcast_at only, first_broadcast_at preserved

Test count: 829 -> 844 (+15 new, 0 regressions). Foundation tests
updated for new counts (REGISTRY=58, ADAPTER_META=19, schema=v11).
2026-06-05 21:11:32 +00:00
..
fixtures/central_envelopes feat(content): v0.5.8-state_511_atis -- central_normalizer with Photon nearest_town + composer bypass + SB->S route normalization 2026-06-04 21:38:40 +00:00
conftest.py feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor 2026-06-05 17:06:51 +00:00
test_adapter_avalanche.py
test_adapter_config_api.py feat(v0.6-phase3): reminder system + schema split + NWS dedup relaxation 2026-06-05 21:11:32 +00:00
test_adapter_config_foundation.py feat(v0.6-phase3): reminder system + schema split + NWS dedup relaxation 2026-06-05 21:11:32 +00:00
test_adapter_ducting.py
test_adapter_fires.py
test_adapter_firms.py
test_adapter_nws.py
test_adapter_roads511.py
test_adapter_swpc.py
test_adapter_traffic.py
test_adapter_usgs.py
test_adapter_usgs_quake.py
test_avalanche_v057.py fix(avalanche): v0.5.7-avalanche -- Central avalanche check + categories audit 2026-06-04 06:55:27 +00:00
test_band_conditions.py feat(v0.5.11): band conditions scheduled broadcaster (3x/day HF propagation) 2026-06-05 07:38:51 +00:00
test_central_consumer.py feat(v0.5.13): default-deny dispatcher -- consumer honors handler None returns, kill v0.5.7 regression at the root 2026-06-05 14:17:41 +00:00
test_central_envelope_to_wire_v057.py feat(v0.5.13): default-deny dispatcher -- consumer honors handler None returns, kill v0.5.7 regression at the root 2026-06-05 14:17:41 +00:00
test_central_normalizer.py feat(v0.5.9): unified incident pipeline + state_511_atis Idaho cutover + two-sided freshness gate 2026-06-05 06:41:21 +00:00
test_central_region_routing.py fix(water): v0.5.7-water -- USGS NWIS hydro NATS pattern + categories audit 2026-06-04 06:42:06 +00:00
test_central_sub_adapter_routing.py feat(v0.5.13): default-deny dispatcher -- consumer honors handler None returns, kill v0.5.7 regression at the root 2026-06-05 14:17:41 +00:00
test_channel_rendering.py
test_cold_start_grace.py feat(v0.5.8b): persistence foundation + WFIGS handler + universal cold-start grace 2026-06-05 03:54:04 +00:00
test_config_loader.py
test_config_source_field.py
test_consumer_default_deny.py feat(v0.6-1): FIRMS handler -- storage-only, closes silent-drop on central.fire.hotspot.> 2026-06-05 15:50:33 +00:00
test_curation.py feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD 2026-06-05 20:19:13 +00:00
test_dashboard_config_save.py
test_dispatcher_persistence.py feat(v0.6-2): dispatcher state persistence -- cold-start, cooldowns, dedup LRU to SQLite 2026-06-05 16:35:40 +00:00
test_env_reporter.py feat(v0.6-5): env_reporter + router wiring + include_in_llm_context per-adapter toggle -- LLM gains read access to every adapter table via the existing mesh_reporter pre-rendered prompt-injection pattern 2026-06-05 20:11:40 +00:00
test_fire_v057.py feat(v0.5.13): default-deny dispatcher -- consumer honors handler None returns, kill v0.5.7 regression at the root 2026-06-05 14:17:41 +00:00
test_firms_handler.py feat(v0.6-1): FIRMS handler -- storage-only, closes silent-drop on central.fire.hotspot.> 2026-06-05 15:50:33 +00:00
test_incident_handler.py feat(v0.5.9): unified incident pipeline + state_511_atis Idaho cutover + two-sided freshness gate 2026-06-05 06:41:21 +00:00
test_itd_511_work_zone.py feat(v0.5.9): unified incident pipeline + state_511_atis Idaho cutover + two-sided freshness gate 2026-06-05 06:41:21 +00:00
test_notification_toggles.py feat(v0.6-phase2): rip out quiet hours entirely -- dashboard toggle, config schema, pipeline checks. Per Matt's repeated feedback (saved as feedback-quiet-hours-trash.md): silent is better than ugly, mesh users who need a fire alert at 3 AM need it at 3 AM. No replacement. 2026-06-05 20:39:36 +00:00
test_nwis_handler.py feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD 2026-06-05 20:19:13 +00:00
test_nws_dedup_relaxation.py feat(v0.6-phase3): reminder system + schema split + NWS dedup relaxation 2026-06-05 21:11:32 +00:00
test_nws_handler.py feat(v0.5.10): nws + usgs_quake + swpc handlers 2026-06-05 07:27:01 +00:00
test_persistence.py feat(v0.5.8b): persistence foundation + WFIGS handler + universal cold-start grace 2026-06-05 03:54:04 +00:00
test_pipeline_digest.py
test_pipeline_grouper.py
test_pipeline_inhibitor_grouper.py
test_pipeline_persistence.py feat(v0.6-6): inhibit_state + grouper_held persistence + ToggleFilter live-reload + Inhibitor/Grouper config knobs 2026-06-05 20:23:34 +00:00
test_pipeline_scheduler.py
test_pipeline_skeleton.py
test_pipeline_toggle_filter.py
test_quake_handler.py feat(v0.5.10): nws + usgs_quake + swpc handlers 2026-06-05 07:27:01 +00:00
test_reminders.py feat(v0.6-phase3): reminder system + schema split + NWS dedup relaxation 2026-06-05 21:11:32 +00:00
test_renderers.py fix(notifications): v0.5.7-regression -- consumer title fallback uses registry name, mesh renderer drops [Family] prefix 2026-06-04 16:06:47 +00:00
test_rf_v057.py feat(v0.5.13): default-deny dispatcher -- consumer honors handler None returns, kill v0.5.7 regression at the root 2026-06-05 14:17:41 +00:00
test_router_env_scope.py feat(v0.6-5): env_reporter + router wiring + include_in_llm_context per-adapter toggle -- LLM gains read access to every adapter table via the existing mesh_reporter pre-rendered prompt-injection pattern 2026-06-05 20:11:40 +00:00
test_save_section_secret_preserve.py
test_seismic_v057.py fix(seismic): v0.5.7-seismic -- USGS quake NATS pattern + severity=5 great-quake clamp + categories audit 2026-06-04 06:33:31 +00:00
test_swpc_handler.py feat(v0.5.10): nws + usgs_quake + swpc handlers 2026-06-05 07:27:01 +00:00
test_tracking_v057.py fix(tracking): v0.5.7-tracking -- Central tracking check + categories audit 2026-06-04 07:00:22 +00:00
test_traffic_v057.py
test_v052_dispatcher.py feat(v0.5.8b): persistence foundation + WFIGS handler + universal cold-start grace 2026-06-05 03:54:04 +00:00
test_water_v057.py fix(water): v0.5.7-water -- USGS NWIS hydro NATS pattern + categories audit 2026-06-04 06:42:06 +00:00
test_weather_v057.py
test_wfigs_handler.py feat(v0.5.8b): persistence foundation + WFIGS handler + universal cold-start grace 2026-06-05 03:54:04 +00:00
test_work_zone_renderer.py feat(content): v0.5.8-state_511_atis -- central_normalizer with Photon nearest_town + composer bypass + SB->S route normalization 2026-06-04 21:38:40 +00:00