diff --git a/work/config.example.yaml b/work/config.example.yaml index 0bdb8b8..78dd49d 100644 --- a/work/config.example.yaml +++ b/work/config.example.yaml @@ -30,9 +30,6 @@ history: database: /data/conversations.db max_messages_per_user: 50 # Messages to keep per user conversation_timeout: 86400 # Conversation expiry (seconds, 86400=24h) - auto_cleanup: true # Auto-delete old conversations - cleanup_interval_hours: 24 # How often to run cleanup - max_age_days: 30 # Delete conversations older than this # === MEMORY OPTIMIZATION === memory: diff --git a/work/meshai/adapter_config/_accessor.py b/work/meshai/adapter_config/_accessor.py index c06a277..650f67e 100644 --- a/work/meshai/adapter_config/_accessor.py +++ b/work/meshai/adapter_config/_accessor.py @@ -5,7 +5,7 @@ Handlers use the singleton `adapter_config` from this package: from meshai.adapter_config import adapter_config cooldown_s = adapter_config.wfigs.cooldown_seconds # int - severities = adapter_config.nws.broadcast_severities # list[str] + tombstones = adapter_config.nws.tombstone_msgtypes # list[str] Reads are dict-cached. The cache invalidates on `invalidate_cache()` (called from the REST API's PUT handler in v0.6-3c). The cache is diff --git a/work/meshai/adapter_config/defaults.py b/work/meshai/adapter_config/defaults.py index 2bd76dc..31f7ec9 100644 --- a/work/meshai/adapter_config/defaults.py +++ b/work/meshai/adapter_config/defaults.py @@ -73,23 +73,16 @@ REGISTRY: dict[tuple[str, str], dict[str, Any]] = { }, # ================================================================= - # NWS -- 3 settings (severity gate, tombstone msgTypes, suffix-promote toggle) + # NWS -- tombstone msgTypes (severity gate + suffix-promote were removed + # in the v0.x config-schema cleanup: both were [DEPRECATED — no longer + # enforced]; NWS breadth is governed by the per-toggle dispatcher + # severity threshold) # ================================================================= - ("nws", "broadcast_severities"): { - "default": ["Extreme", "Severe"], # nws_handler.py:43 - "type": "json", - "description": "CAP severity strings allowed onto the mesh. [DEPRECATED — no longer enforced; NWS breadth is governed by the per-toggle dispatcher severity threshold]", - }, ("nws", "tombstone_msgtypes"): { "default": ["Cancel", "Expire"], # nws_handler.py:46 "type": "json", "description": "CAP msgType values that mark an alert as gone.", }, - ("nws", "warning_suffix_promotes"): { - "default": True, # nws_handler.py:172 - "type": "bool", - "description": "Promote category-name-ending-in-_warning to Severe when CAP severity is missing. [DEPRECATED — no longer enforced; NWS breadth is governed by the per-toggle dispatcher severity threshold]", - }, # ================================================================= # USGS_QUAKE -- 6 settings (regional geography + 3 mag floors + PAGER set) @@ -112,12 +105,12 @@ REGISTRY: dict[tuple[str, str], dict[str, Any]] = { ("usgs_quake", "global_mag_floor"): { "default": 3.0, # quake_handler.py:69 "type": "float", - "description": "Global magnitude floor for unconditional broadcasts.", + "description": "Global magnitude floor for unconditional broadcasts. CENTRAL-PATH ONLY (central/quake_handler.py); the native feed_source gates on environmental.usgs_quake.min_magnitude instead.", }, ("usgs_quake", "regional_mag_floor"): { "default": 2.5, # quake_handler.py:70 "type": "float", - "description": "Reduced magnitude floor for quakes within regional_radius_mi of centroid.", + "description": "Reduced magnitude floor for quakes within regional_radius_mi of centroid. CENTRAL-PATH ONLY (central/quake_handler.py); the native feed_source gates on environmental.usgs_quake.min_magnitude instead.", }, ("usgs_quake", "escalate_mag_floor"): { "default": 5.0, # quake_handler.py:76 diff --git a/work/meshai/central/nws_handler.py b/work/meshai/central/nws_handler.py index a05db44..09b04ff 100644 --- a/work/meshai/central/nws_handler.py +++ b/work/meshai/central/nws_handler.py @@ -44,8 +44,10 @@ from meshai.persistence import get_db logger = logging.getLogger(__name__) -# v0.6-3b: severity gate + tombstone msgTypes live in adapter_config.nws -# (broadcast_severities, tombstone_msgtypes). Read at handler call time. +# v0.6-3b: tombstone msgTypes live in adapter_config.nws +# (tombstone_msgtypes), read at handler call time. The old broadcast_severities +# severity gate was removed in the config-schema cleanup (no longer enforced; +# NWS breadth is governed by the per-toggle dispatcher severity threshold). # Ordered (substring, emoji) checks; first match wins. _EVENT_EMOJI = [ diff --git a/work/meshai/config.py b/work/meshai/config.py index e35cba8..9140492 100644 --- a/work/meshai/config.py +++ b/work/meshai/config.py @@ -63,11 +63,6 @@ class HistoryConfig: max_messages_per_user: int = 50 conversation_timeout: int = 86400 # 24 hours - # Cleanup settings - auto_cleanup: bool = True - cleanup_interval_hours: int = 24 - max_age_days: int = 30 # Delete conversations older than this - @dataclass class MemoryConfig: @@ -75,13 +70,6 @@ class MemoryConfig: enabled: bool = True # Enable memory optimization - # MQTT-specific fields (type=mqtt only) - host: str = "" # MQTT broker hostname - port: int = 1883 # MQTT broker port (1883 plain, 8883 TLS) - username: str = "" # MQTT username (optional) - password: str = "" # MQTT password (optional, supports ) - topic_root: str = "msh/US" # Topic root to subscribe to - use_tls: bool = False # Enable TLS for MQTT connection window_size: int = 4 # Recent message pairs to keep in full summarize_threshold: int = 8 # Messages before re-summarizing @@ -92,13 +80,6 @@ class ContextConfig: enabled: bool = True - # MQTT-specific fields (type=mqtt only) - host: str = "" # MQTT broker hostname - port: int = 1883 # MQTT broker port (1883 plain, 8883 TLS) - username: str = "" # MQTT username (optional) - password: str = "" # MQTT password (optional, supports ) - topic_root: str = "msh/US" # Topic root to subscribe to - use_tls: bool = False # Enable TLS for MQTT connection observe_channels: list[int] = field(default_factory=list) # Empty = all channels ignore_nodes: list[str] = field(default_factory=list) # Node IDs to ignore max_age: int = 1_209_600 # 14 days in seconds @@ -120,13 +101,6 @@ class CommandsConfig: enabled: bool = True - # MQTT-specific fields (type=mqtt only) - host: str = "" # MQTT broker hostname - port: int = 1883 # MQTT broker port (1883 plain, 8883 TLS) - username: str = "" # MQTT username (optional) - password: str = "" # MQTT password (optional, supports ) - topic_root: str = "msh/US" # Topic root to subscribe to - use_tls: bool = False # Enable TLS for MQTT connection prefix: str = "!" disabled_commands: list[str] = field(default_factory=list) custom_commands: dict = field(default_factory=dict) @@ -253,7 +227,6 @@ class RegionAnchor: description: str = "" # e.g., "Twin Falls, Burley, Jerome along I-84/US-93" aliases: list[str] = field(default_factory=list) # e.g., ["southern Idaho", "magic valley"] cities: list[str] = field(default_factory=list) # e.g., ["Twin Falls", "Burley", "Jerome"] - nws_zones: list[str] = field(default_factory=list) # NWS zone codes (e.g., ["IDZ016", "IDZ030"]) @dataclass @@ -273,10 +246,6 @@ class AlertRulesConfig: battery_warning_threshold: int = 30 battery_critical_threshold: int = 15 battery_emergency_threshold: int = 5 - # Voltage-based thresholds (more accurate than percentage) - battery_warning_voltage: float = 3.60 - battery_critical_voltage: float = 3.50 - battery_emergency_voltage: float = 3.40 power_source_change: bool = True solar_not_charging: bool = True @@ -314,7 +283,6 @@ class MeshIntelligenceConfig: # Alert settings critical_nodes: list[str] = field(default_factory=list) # Short names of critical nodes (e.g., ["MHR", "HPR"]) alert_channel: int = -1 # Channel to broadcast alerts on. -1 = disabled, 0+ = channel index - alert_cooldown_minutes: int = 30 # Min minutes between repeated alerts for same condition alert_rules: AlertRulesConfig = field(default_factory=AlertRulesConfig) @@ -337,13 +305,6 @@ class NWSConfig(_SourcedFeed): enabled: bool = True - # MQTT-specific fields (type=mqtt only) - host: str = "" # MQTT broker hostname - port: int = 1883 # MQTT broker port (1883 plain, 8883 TLS) - username: str = "" # MQTT username (optional) - password: str = "" # MQTT password (optional, supports ) - topic_root: str = "msh/US" # Topic root to subscribe to - use_tls: bool = False # Enable TLS for MQTT connection tick_seconds: int = 60 areas: list = field(default_factory=lambda: ["ID"]) severity_min: str = "moderate" @@ -356,14 +317,6 @@ class SWPCConfig(_SourcedFeed): enabled: bool = True - # MQTT-specific fields (type=mqtt only) - host: str = "" # MQTT broker hostname - port: int = 1883 # MQTT broker port (1883 plain, 8883 TLS) - username: str = "" # MQTT username (optional) - password: str = "" # MQTT password (optional, supports ) - topic_root: str = "msh/US" # Topic root to subscribe to - use_tls: bool = False # Enable TLS for MQTT connection - @dataclass class DuctingConfig(_SourcedFeed): @@ -371,13 +324,6 @@ class DuctingConfig(_SourcedFeed): enabled: bool = True - # MQTT-specific fields (type=mqtt only) - host: str = "" # MQTT broker hostname - port: int = 1883 # MQTT broker port (1883 plain, 8883 TLS) - username: str = "" # MQTT username (optional) - password: str = "" # MQTT password (optional, supports ) - topic_root: str = "msh/US" # Topic root to subscribe to - use_tls: bool = False # Enable TLS for MQTT connection tick_seconds: int = 10800 # 3 hours latitude: float = 42.56 # Twin Falls area default longitude: float = -114.47 @@ -419,6 +365,11 @@ class USGSQuakeConfig(_SourcedFeed): enabled: bool = False tick_seconds: int = 300 feed_url: str = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson" + # Native-path broadcast magnitude floor: the native adapter + # (env/usgs_quake.py) gates on this. THIS is the GUI-editable quake + # magnitude floor for the native feed_source. (The adapter_config + # REGISTRY keys usgs_quake.global_mag_floor / regional_mag_floor apply + # only to the Central-firehose path in central/quake_handler.py.) min_magnitude: float = 2.5 # [west, south, east, north] -- Magic Valley -> Borah Peak -> Yellowstone bbox: list = field(default_factory=lambda: [-115.5, 42.0, -110.0, 45.2]) @@ -758,7 +709,18 @@ class DangerZoneHazardConfig: enabled: bool = True buffer_mi: float = 5.0 - min_acres: float = 0.0 # fire-only; ignored by other families + + +@dataclass +class DangerZoneFireConfig(DangerZoneHazardConfig): + """Fire-family danger-zone tuning; adds an acreage floor. + + ``min_acres`` is fire-only (read at danger_zone_correlator.py in the + ``fam == "fire"`` branch); the non-fire families use the plain + DangerZoneHazardConfig, which no longer carries it. + """ + + min_acres: float = 0.0 # skip fires smaller than this (0 = no floor) @dataclass @@ -778,7 +740,7 @@ class DangerZonesConfig: # Per-family sub-configs. snow->weather, flood->seismic resolved in the # correlator; both still exposed here for distinct GUI tuning. - fire: DangerZoneHazardConfig = field(default_factory=DangerZoneHazardConfig) + fire: DangerZoneFireConfig = field(default_factory=DangerZoneFireConfig) weather: DangerZoneHazardConfig = field(default_factory=DangerZoneHazardConfig) snow: DangerZoneHazardConfig = field(default_factory=DangerZoneHazardConfig) flood: DangerZoneHazardConfig = field(default_factory=DangerZoneHazardConfig) diff --git a/work/meshai/config_loader.py b/work/meshai/config_loader.py index 69f5ac9..a2b61f2 100644 --- a/work/meshai/config_loader.py +++ b/work/meshai/config_loader.py @@ -79,6 +79,8 @@ SECRET_FIELDS: set[str] = { "mesh_sources.*.password", "environmental.traffic.api_key", "environmental.firms.map_key", + "environmental.roads511.api_key", + "environmental.wzdx.api_key", "notifications.rules.*.smtp_password", "notifications.toggles.*.smtp_password", "danger_zones.webhook_url", diff --git a/work/tests/test_adapter_config_api.py b/work/tests/test_adapter_config_api.py index 357f21c..759e3d2 100644 --- a/work/tests/test_adapter_config_api.py +++ b/work/tests/test_adapter_config_api.py @@ -36,7 +36,9 @@ def test_list_returns_all_59_keys(client): # 14 adapters with at least one key (itd_511 has zero -- not in the # grouped dict because the SQL only returns rows that exist). total = sum(len(v) for v in body.values()) - assert total == 96 + # was 96; config-schema cleanup removed the two deprecated nws keys + # (broadcast_severities, warning_suffix_promotes) -> 94. + assert total == 94 def test_list_grouped_by_adapter(client): @@ -184,12 +186,14 @@ def test_put_str_validation(client): def test_put_json_accepts_list(client): + # broadcast_severities was removed in the config-schema cleanup; use the + # surviving nws json-list key tombstone_msgtypes to exercise list PUTs. r = client.put( - "/api/adapter-config/nws/broadcast_severities", - json={"value": ["Extreme"]}, + "/api/adapter-config/nws/tombstone_msgtypes", + json={"value": ["Cancel"]}, ) assert r.status_code == 200 - assert r.json()["value"] == ["Extreme"] + assert r.json()["value"] == ["Cancel"] def test_put_json_accepts_dict(client): diff --git a/work/tests/test_adapter_config_foundation.py b/work/tests/test_adapter_config_foundation.py index 8c616a3..840c602 100644 --- a/work/tests/test_adapter_config_foundation.py +++ b/work/tests/test_adapter_config_foundation.py @@ -75,7 +75,9 @@ def test_adapter_config_type_check_constrains_vocabulary(fresh_db): def test_registry_at_59_entries(): """v0.6-3a.1 trim: 43 CONFIG-only keys (was 77 in v0.6-3a draft).""" - assert len(REGISTRY) == 96, ( + # was 96; config-schema cleanup removed the two deprecated nws keys + # (broadcast_severities, warning_suffix_promotes) -> 94. + assert len(REGISTRY) == 94, ( f"REGISTRY drift guard; got {len(REGISTRY)}. " f"If a sentence template / emoji / heuristic snuck in, it belongs in CODE not config." ) @@ -225,7 +227,9 @@ def test_accessor_returns_bool(fresh_db): def test_accessor_returns_json_list(fresh_db): invalidate_cache() - assert adapter_config.nws.broadcast_severities == ["Extreme", "Severe"] + # broadcast_severities was removed in the config-schema cleanup (deprecated, + # no longer enforced); tombstone_msgtypes is the surviving nws json-list key. + assert adapter_config.nws.tombstone_msgtypes == ["Cancel", "Expire"] def test_accessor_returns_json_dict(fresh_db):