meshai/tests/test_central_region_routing.py

88 lines
3.6 KiB
Python
Raw Normal View History

feat(central): v0.5.4 -- region-aware subscriptions using Central v0.9.20 regional subjects Pre-v0.5.4 every Central subscription used a bare wildcard (central.wx.>, central.fire.>, central.traffic.>, central.quake.>, central.hydro.>, central.space.>), so a Magic Valley operator flipping nws -> central was in fact subscribing to the all-US firehose and discarding 95% of events locally. Central v0.9.20 (2026-05-28) added per-region subject suffixes so the firehose can be filtered server-side. This wires meshai to use them. Backend (meshai/central/consumer.py): - New _subjects_for(adapter, region) replaces the static ADAPTER_SUBJECTS dict. ADAPTER_SUBJECTS is retained as an alias to _SUBJECTS_BARE for any legacy importers; the dispatcher path is unchanged. - Per-adapter subject patterns (region='us.id' default): nws -> central.wx.alert.us.id.> (region BEFORE wildcard) usgs_quake -> central.quake.event.>.us.id (region AFTER wildcard) firms -> central.fire.hotspot.>.us.id fires -> central.fire.incident.id.> (state token at fixed depth) central.fire.perimeter.id.> traffic -> central.traffic.>.id (bare state, no us. prefix) roads511 -> central.traffic.>.id (shared with traffic, sub-adapter routing) usgs -> central.hydro.>.us.id central.hydro.>.unknown (workaround until v0.9.20.1) swpc -> central.space.> (planetary; region ignored) - Empty/None region falls back to bare wildcards (pre-v0.9.20 behaviour). - _subject_owned() pulls region from env.central.region and routes through _subjects_for; v0.5.3 sub-adapter routing (owned-sources set) still applies on shared subjects like central.traffic.>.id. - start() logs the active region at connect-time for ops visibility. Config (meshai/config.py): - CentralConsumerConfig.region: str = "us.id". One region per consumer applies to every central-flipped adapter; per-adapter overrides can land in v0.6 when there is a real use case. Frontend (dashboard-frontend/src/pages/Environment.tsx): - Central Connection panel gets a Region text input next to URL/Durable. - EnvConfig.central type extended with region: string. - Static bundle rebuilt; index-DCFmSeOM.js -> index-B24tHcYj.js. Tests: - tests/test_central_region_routing.py (new, 9 cases): asserts the exact v0.9.20 subject string for each adapter at region='us.id', the SWPC global-stays-global rule, the USGS .unknown workaround, the empty-region backward-compat fallback for all 8 adapters, and integration through CentralConsumer._subject_owned() with the default region. - tests/test_central_consumer.py + tests/test_central_sub_adapter_routing.py: the two tests that asserted bare-wildcard subjects now set env.central.region = "" explicitly to preserve their original concern (no region semantics — backward-compat path only). Why swpc stays global: space weather is planetary -- a CME is detected on the sun, the geomagnetic response is hemispheric. There is no Idaho-only solar event; subscribing per-region would only drop events we want. Why hydro has the .unknown workaround: Central v0.9.20 leaves gauges whose USGS state can't be inferred on central.hydro.>.unknown. Until v0.9.20.1 backfills the state tag we subscribe to both filters to avoid silently losing those rows. Idaho downstream-filtering on data['_enriched']['usgs_site']['state'] is future v0.6 work. Orthogonal to v0.5.2 dispatcher guards (staleness / cooldown / dedup) and v0.5.3 sub-adapter routing: the region filter operates at the NATS subscription layer (server-side), upstream of everything else. Verified: pytest 327 passed (318 prior + 9 new region-routing tests); py_compile clean; frontend build clean. Safe-mode preserved -- no toggle enabled, no master enabled, no central enabled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 02:30:33 +00:00
"""v0.5.4: Central v0.9.20 region-aware subject building.
Exercises `_subjects_for(adapter, region)` and the wiring through
`CentralConsumer._subject_owned()`. The spec is hard-coded in the test
strings on purpose so a future drift in the v0.9.20 subject scheme
fails noisily here instead of silently shipping wrong filters.
"""
from meshai.central.consumer import (
CentralConsumer,
_subjects_for,
_SUBJECTS_BARE,
)
from meshai.config import EnvironmentalConfig
# --------------------------------------------------------------------- per-adapter
def test_subjects_for_nws_us_id():
"""NWS: region BEFORE wildcard (matches alert.<region>.<...>)."""
assert _subjects_for("nws", "us.id") == ["central.wx.alert.us.id.>"]
def test_subjects_for_usgs_quake_us_id():
"""USGS quake: region AFTER wildcard."""
assert _subjects_for("usgs_quake", "us.id") == ["central.quake.event.>.us.id"]
def test_subjects_for_firms_us_id():
"""FIRMS hotspots: region AFTER wildcard, hotspot domain explicit."""
assert _subjects_for("firms", "us.id") == ["central.fire.hotspot.>.us.id"]
def test_subjects_for_fires_us_id_uses_state_token():
"""NIFC fires: state-only token at depth-4 for both incident + perimeter."""
assert _subjects_for("fires", "us.id") == [
"central.fire.incident.id.>",
"central.fire.perimeter.id.>",
]
def test_subjects_for_traffic_and_roads511_share_state_token():
"""Traffic family: bare-state suffix (no us. prefix), shared by both adapters."""
assert _subjects_for("traffic", "us.id") == ["central.traffic.>.id"]
assert _subjects_for("roads511", "us.id") == ["central.traffic.>.id"]
def test_subjects_for_usgs_includes_unknown_workaround():
"""USGS hydro: subscribes to BOTH the region-tagged filter and the
".unknown" filter to cover gauges whose state Central v0.9.20 can't
infer yet (workaround until v0.9.20.1 backfills the tag)."""
assert _subjects_for("usgs", "us.id") == [
"central.hydro.>.us.id",
"central.hydro.>.unknown",
]
def test_subjects_for_swpc_stays_global():
"""SWPC: space weather is planetary; region argument is ignored."""
assert _subjects_for("swpc", "us.id") == ["central.space.>"]
assert _subjects_for("swpc", "us.mt") == ["central.space.>"] # same regardless
assert _subjects_for("swpc", "") == ["central.space.>"]
# --------------------------------------------------------------------- backward compat
def test_subjects_for_empty_region_falls_back_to_bare_wildcards():
"""Empty/None region = pre-v0.9.20 behaviour for every adapter, byte-identical
to the legacy _SUBJECTS_BARE map. Adapters absent from the map return []."""
for adapter, expected in _SUBJECTS_BARE.items():
assert _subjects_for(adapter, "") == expected, f"empty region mismatch for {adapter}"
assert _subjects_for(adapter, None) == expected, f"None region mismatch for {adapter}"
# Unknown adapters return empty regardless of region.
assert _subjects_for("ducting", "us.id") == []
assert _subjects_for("avalanche", "") == []
# --------------------------------------------------------------------- integration
def test_central_region_default_propagates_to_consumer_subjects():
"""Default region = 'us.id': flipping nws to central → consumer subscribes
to the region-aware subject, not the bare wildcard."""
env = EnvironmentalConfig()
assert env.central.region == "us.id" # spec default
env.nws.feed_source = "central"
so = CentralConsumer(env, None)._subject_owned()
assert list(so.keys()) == ["central.wx.alert.us.id.>"]
assert so["central.wx.alert.us.id.>"] == {"nws"}