Adds DigestScheduler class that fires digest at configured time (default 07:00)
and routes to rules with trigger_type=schedule and schedule_match=digest.
- DigestScheduler: asyncio task with start/stop lifecycle
- Config: DigestConfig dataclass with schedule and include fields
- Config: schedule_match field on NotificationRuleConfig
- Pipeline: start_pipeline/stop_pipeline async lifecycle functions
- Mesh channels get per-chunk delivery, email/webhook get full text
- 26 new tests covering schedule computation, fire behavior, lifecycle
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds DigestAccumulator tracking ACTIVE NOW and SINCE LAST DIGEST
state per toggle. Replaces StubDigestQueue in build_pipeline; the
stub class is kept for Phase 2.1 backward-compat tests.
- enqueue(): adds new events, updates in place by id, detects
resolutions (expires past, or title contains cleared/reopened/
ended/resolved/back online/recovered/lifted)
- tick(now): rolls expired actives into since_last
- render_digest(now): produces a Digest with mesh_compact (<=200
chars) and full multi-line forms; clears since_last after
- Toggle ordering and labels match the v0.3 design
- Phase 2.3b will add real scheduling on top of this
Adds inline pipeline stages between the bus and the severity router:
- Inhibitor: suppresses lower-or-equal severity events when a key
in event.inhibit_keys is already active. TTL configurable, default
30 minutes.
- Grouper: coalesces events sharing group_key within a time window
(default 60s). Most recent event wins. tick() and flush_all()
drive emission; no background timers in Phase 2.2.
- build_pipeline now wires: bus -> inhibitor -> grouper -> severity_router
Phase 2.1 dispatcher tests continue to pass unchanged.
The initial 2.1 dispatcher was a logging stub with manual backend
registration. The spec required integration with the existing
NotificationRuleConfig schema and channels.py create_channel factory.
- Dispatcher takes (config, channel_factory)
- _matching_rules iterates config.notifications.rules with severity ranking
- dispatch() builds alert dict and calls channel.deliver()
- build_pipeline(config) returns EventBus per spec
- build_pipeline_components(config) added for test introspection
Phase 1.3:
- events.py: Event dataclass with ID generation and serialization
- region_tagger.py: Coordinate/NWS zone region tagging
- categories.py: Toggle field mapping for all 31 alert categories
Phase 2.1 Pipeline Skeleton:
- pipeline/bus.py: EventBus with subscribe/emit pattern
- pipeline/severity_router.py: Routes immediate->dispatch, routine->digest
- pipeline/dispatcher.py: Delivers immediate events to configured channels
- pipeline/__init__.py: build_pipeline() factory and exports
All components tested and verified in container.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds toggle field to each ALERT_CATEGORIES entry:
- mesh_health: 18 categories (infra, power, utilization, coverage, health)
- weather: 3 categories (NWS warnings, stream flooding)
- fire: 3 categories (NIFC, FIRMS hotspots)
- rf_propagation: 3 categories (solar, geomag, ducting)
- roads: 2 categories (closures, congestion)
- avalanche: 2 categories (high danger, considerable)
Also adds helper functions:
- categories_for_toggle(toggle) -> list of category IDs
- get_toggle(category_name) -> toggle name or None
Note: seismic and tracking toggles defined but have no categories yet
(reserved for Phase 3 and Phase 7 respectively).
All toggle assignments are unambiguous - no categories defaulted to
mesh_health due to ambiguity.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds meshai/notifications/region_tagger.py with:
- haversine_distance() for great-circle distance calculation
- tag_by_coordinates() maps lat/lon to nearest region within radius
- tag_by_nws_zone() maps NWS zone codes to matching regions
Also adds nws_zones field to RegionAnchor in config.py to support
zone-based matching. Default is empty list for backward compatibility.
This is scaffolding for Phase 2 - not yet wired into any adapters.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds meshai/notifications/events.py with:
- Event dataclass with all fields for unified pipeline shape
- Stable ID generation via sha1 hash for deduplication
- make_event() factory with auto-timestamp and severity validation
- to_dict/from_dict for serialization round-trip
This is scaffolding for Phase 2 - not yet wired into any adapters.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added deep_compare function for recursive dict comparison
- Replaced shallow key-list check with full Config dataclass comparison
- Uses dataclasses.asdict for consistent dict representation
- Reports full path of mismatches (e.g. connection.tcp_host)
The previous gate only checked inline sections and missed the
include-related bugs that caused the restart loop.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Changed orchestrator to use meshtastic: include meshtastic.yaml
- Added hoisting logic to extract connection/commands from wrapper
- Fixes restart loop caused by connection.type defaulting to serial
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update .gitignore for v0.3 multi-file layout
- Add config/.env.example template for secrets
- Add config/local.yaml.example for operator values
- Wire main.py to use new config_loader
- Support both legacy and new layouts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Backup original config before migration
- Split monolithic config into domain files
- Extract operator-identifying values to local.yaml
- Extract secrets to /data/secrets/.env
- Create orchestrator with !include directives
- Post-migration verification
- Safe to run multiple times (idempotent checks)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add config_loader.py with !include directive support
- Environment variable interpolation with default syntax
- local.yaml merging for operator-identifying values
- Secret loading from /data/secrets/.env
- save_section() for dashboard write-back
- Cycle detection for include directives
- Graceful degradation when files missing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace 11 info fallbacks with routine in router.py + channels.py
- Replace 2 warning min_severity defaults with priority
- Update config.example.yaml rules to use routine/priority/immediate
- Annotate config.example.yaml notifications section as transitional pending v0.3 8-toggle rewrite Phase 1.2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix leftover severity references (info→routine in filter dropdown)
- Fix node_id int handling in connector and channels (handle both int and string)
- Add LLM-generated reports for notifications (replace raw data dumps)
- Fix health.score.composite attribute path for RF reports
- Add deterministic HF band conditions from SFI/Kp values
- Remove max_tokens from LLM calls (character limits at delivery)
- Weather feed improvements: show event_type + area, local events first
- Fix is_online to use configured offline_threshold_hours in data store
- Update stale defaults: offline 24→2h, battery_warning 20→30%
- Add TODO comments for packet_threshold scale bug
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add offline_threshold_hours parameter to MeshDataStore.__init__
- Compute is_online in _normalize_node using configured threshold
- Pass config.mesh_intelligence.offline_threshold_hours from main.py
- Removes reliance on health engine for initial is_online computation
Verification:
- Unit test confirms 2h threshold marks 3h-old node offline
- Unit test confirms 4h threshold marks same node online
- Container starts healthy with no config errors
- Health engine reports 16/16 infra online
- Event feed shows event_type + area_desc instead of timestamp headline
- First sentence of description shown as hazard summary
- Local events (matching NWS zones) pinned to top with highlight
- Nearby events grouped below, slightly dimmed
- Dedup by event_id
- Fix MeshHealth.score.composite path (was accessing wrong object)
- Add deterministic band condition calculator from SFI/Kp/time
- RF reports use structured band format, not LLM
- Fix LLM prompts for health/weather reports (max_tokens, format)
- Graceful handling when data sources not configured
- Status/report messages use LLM to generate operator-readable summaries
- RF reports interpret SFI/Kp into which bands are open
- Mesh reports highlight problems, not just numbers
- Remove meaningless [STATUS] prefix
- Alerts stay templated (no LLM, no latency)
- Reports respect 180-char limit for mesh delivery
- connector.send_message accepts int or string destination
- channels.py converts node_id to str before string operations
- Rule stats write to /data/ (Docker volume) not /opt/meshai/data/
Merged remote changes with local notification verification system:
- Kept local: channels.py, router.py, notification_routes.py, Notifications.tsx
(contains the new end-to-end verification system)
- Accepted remote: Config, Environment, Reference pages, new commands,
categories, summarizer, and other supporting files
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Channel connectivity test: SMTP, webhook, mesh with real errors
- Rule test shows live data from feeds, not canned examples
- Near-miss detection: shows events filtered by threshold
- Three send actions: current conditions, example alert, live alert
- Rule status indicators: last fired, data source health
- All errors show actual error messages
- Disabled feed detection with clear warnings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test always shows current data for the rule's feed categories
- RF rules show live SFI/Kp/R/S/G and ducting conditions
- Weather rules show active NWS alert count and headlines
- Fire rules show active fire/hotspot count
- Stream rules show current gauge readings
- Mesh rules show current health score and infra status
- Send Current Conditions delivers live snapshot through channel
- Send Test Alert delivers example through channel
- Send Live Alert available when real conditions match
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Tests check current conditions against rule categories/severity
- Shows actual alert messages that would fire right now
- Falls back to example messages from category registry if no matches
- Preview mode shows without sending, Send Test delivers with [TEST] prefix
- Mesh delivery applies real summarization so preview matches actual output
- Added test dialog UI showing conditions matched and preview messages
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Utilization pillar now reads firmware-reported channel_utilization
instead of estimating from packet counts with hardcoded 200ms/pkt
- Uses highest infra node value (busiest node = bottleneck)
- Falls back to packet count estimate only when telemetry unavailable
- Updated thresholds: 20/25/35/45% matching real Meshtastic behavior
- Per-region utilization from region nodes, not mesh-wide
- API response includes util_method, util_max_percent, util_node_count
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reference page now explains HOW each health pillar is calculated:
- Infrastructure: router online ratio
- Utilization: airtime estimation from packet counts
- Coverage: gateway redundancy with single-gw penalties
- Behavior: flagged node thresholds
- Power: battery warning ratio
Includes actual formulas and special cases.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace fixed overlay with useRef-based click-outside detection
- Add X close button in top-right corner of popover
- Click ? to toggle (open if closed, close if open)
- Click anywhere outside popover to dismiss
- Remove fixed inset-0 overlay that was blocking page interaction
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add helper text and info (?) buttons to every field in Config.tsx
- Add section descriptions at the top of each config section
- Battery thresholds now show voltage equivalents (e.g., "30% ≈ 3.60V")
- NWS severity dropdown shows descriptions per option
- Alert rules grouped by category with full explanations
- Add InfoButton popover component for detailed field documentation
- Add info buttons to Environment.tsx RF propagation panels
- VOLTAGE_MAP and getVoltageApprox helper for Li-ion voltage lookup
Researched defaults and descriptions include:
- Li-ion voltage curve (4.20V=100%, 3.60V=30%, 3.50V=15%, 3.40V=7%)
- LoRa channel utilization (firmware throttles at 25%, issues at 50%)
- Packet flood detection (normal 1-5/min, suspicious >10/min)
- NWS severity levels with actionable descriptions
- Tropospheric ducting M-units/km refractivity gradients
- NOAA Space Weather R/S/G scales
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- SFI/Kp as prominent color-coded values with trend chart
- R/S/G scales as colored severity badges
- Tropospheric ducting condition with refractivity profile
- Environmental feeds replaced with scrolling live event timeline
- Unified activity log across all 9 feed adapters
- Source icons, severity badges, chronological order
- Real-time updates via WebSocket
- SWPC adapter stores Kp/SFI history for charting
- No wasted card space
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Self-contained rules replace abstract channels
- Inline delivery config (broadcast/DM/email/webhook or none)
- quiet_hours_enabled master toggle separate from start/end times
- delivery_type="" valid: rule matches but does not deliver
- Severity dropdown with plain-English descriptions
- Example messages per alert category
- Default baseline rules: Emergency Broadcast, Infrastructure Down, Fire Alert, Severe Weather
- Condition vs Schedule trigger types
- Test and preview buttons per rule
- stream_flood_warning renamed from flood_warning (distinct from packet_flood)
- Categories display with descriptions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Each notification rule contains its own delivery config inline
- No more separate channels with abstract IDs to cross-reference
- Delivery type selector (Mesh Broadcast/DM/Email/Webhook) with
inline config fields per type
- Follows MeshMonitor trigger-action UX pattern
- Channel picker from radio for mesh broadcast
- Node picker for mesh DM
- Collapsed rule cards show readable one-line summary
- Trigger type: condition (alerts) or schedule (daily reports)
- Schedule triggers support daily, weekly, custom cron
- Message types: mesh health, RF propagation, alerts digest, custom
- Migrates old channels+rules config to new flat format on load
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add GET /api/channels endpoint for live radio channel data
- Create ChannelPicker component (single/multi-select from live channels)
- Create NodePicker component (searchable multi-select from mesh nodes)
- Replace manual inputs in Config with data-driven pickers
- Update Notifications to use pickers for mesh broadcast/DM
- Resolve node names in Alerts subscriptions display
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create standalone Notifications.tsx page with full notification config UI
- Add /notifications route in App.tsx
- Add Notifications nav item in Layout.tsx sidebar (below Alerts, BellRing icon)
- Remove notifications section from Config.tsx (keep settings sections only)
- Channels, rules, quiet hours, and dedup all configurable on dedicated page
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Notifications tab in Config sidebar with Bell icon
- Channels section: add/edit/delete channels (mesh broadcast, DM, email, webhook)
- Test button sends test alert to channel
- Rules section: create rules with category checkboxes fetched from API
- Quiet hours configurable with start/end times
- Dedup window to prevent alert spam
- Full helper text and info buttons on every field
- Category list fetched from /api/notifications/categories, not hardcoded
- Added notifications and environmental to VALID_SECTIONS in config_routes.py
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add InfoButton component with click-to-toggle popover for field help
- Add SectionDescription component for section intro paragraphs
- Add AlertRuleToggle component with grouped threshold controls
- Add detailed info and helper text for every field in all sections
- Convert Commands section to toggleable command list with descriptions
- Add dropdowns for severity_min, fire state, connection type, LLM backend
- Add region management: Add/Delete buttons with confirmation
- Group alert rules by category: Infrastructure, Power, Utilization, Health
- Remove hardcoded placeholders and Idaho-specific text
- Fix config.py DashboardConfig dataclass decorator
- Fix main.py MessageRouter initialization
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Full Alerts page with active alerts, history table, subscriptions
- Active alert cards with severity styling and acknowledge button
- Alert history table with type/severity filtering and pagination
- Subscription viewer showing mesh subscriptions
- ToastProvider for app-wide toast notifications
- Toast notifications triggered on WebSocket alert_fired messages
- Auto-dismiss toasts after 8 seconds, click to navigate
- Page titles on all pages (Dashboard/Mesh/Environment/Config/Alerts)
- Improved alert_routes.py with proper pending alert handling
- Added AlertHistoryItem, Subscription types to api.ts
- Added fetchAlertHistory, fetchSubscriptions functions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Implement FIRMSAdapter polling NASA FIRMS area API for satellite hotspots
- Cross-reference hotspots against NIFC perimeters to identify new ignitions
- Add !hotspots command with --new flag for filtering new ignitions only
- Add FIRMSConfig dataclass with map_key, source, bbox, day_range options
- Add /api/env/hotspots endpoint for dashboard integration
- Add Satellite Hotspots section to Environment.tsx with NEW badges
- Add FIRMS configuration section to Config.tsx with source/confidence options
- Update config.example.yaml with FIRMS configuration template
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>