mirror of
https://github.com/zvx-echo6/central.git
synced 2026-05-21 18:14:44 +02:00
refactor(adapters): self-describing adapter pattern with auto-discovery
- Add stream_name, subject_for(), and settings_schema() to SourceAdapter ABC - Implement all three methods in NWSAdapter, FIRMSAdapter, USGSQuakeAdapter - Replace manual _ADAPTER_REGISTRY with pkgutil.iter_modules auto-discovery - Remove subject_for_event from models.py (each adapter owns its subject logic) - Update supervisor to use adapter.subject_for(event) instead of helper - Fix quake events going to wrong stream (was publishing to CENTRAL_WX) - Update test files to use adapter methods This fixes the quake stream bug where events were published to central.wx.alert.us.unknown instead of central.quake.event.<tier>. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
38b23f2a25
commit
4573bf6ee2
9 changed files with 185 additions and 118 deletions
|
|
@ -10,7 +10,6 @@ from central.adapters.firms import (
|
|||
FIRMSAdapter,
|
||||
CONFIDENCE_MAP,
|
||||
SATELLITE_SHORT,
|
||||
subject_for_fire_hotspot,
|
||||
)
|
||||
from central.config_models import AdapterConfig, RegionConfig
|
||||
from central.models import Event, Geo
|
||||
|
|
@ -285,7 +284,14 @@ class TestDeduplication:
|
|||
class TestSubjectGeneration:
|
||||
"""Test subject generation for fire hotspots."""
|
||||
|
||||
def test_subject_format(self):
|
||||
@pytest.mark.asyncio
|
||||
async def test_subject_format(self, temp_db_path, mock_config_store):
|
||||
config = make_adapter_config()
|
||||
adapter = FIRMSAdapter(
|
||||
config=config,
|
||||
config_store=mock_config_store,
|
||||
cursor_db_path=temp_db_path,
|
||||
)
|
||||
event = Event(
|
||||
id="test",
|
||||
adapter="firms",
|
||||
|
|
@ -296,10 +302,17 @@ class TestSubjectGeneration:
|
|||
data={},
|
||||
)
|
||||
|
||||
subject = subject_for_fire_hotspot(event)
|
||||
subject = adapter.subject_for(event)
|
||||
assert subject == "central.fire.hotspot.viirs_snpp.high"
|
||||
|
||||
def test_subject_nominal_confidence(self):
|
||||
@pytest.mark.asyncio
|
||||
async def test_subject_nominal_confidence(self, temp_db_path, mock_config_store):
|
||||
config = make_adapter_config()
|
||||
adapter = FIRMSAdapter(
|
||||
config=config,
|
||||
config_store=mock_config_store,
|
||||
cursor_db_path=temp_db_path,
|
||||
)
|
||||
event = Event(
|
||||
id="test",
|
||||
adapter="firms",
|
||||
|
|
@ -310,7 +323,7 @@ class TestSubjectGeneration:
|
|||
data={},
|
||||
)
|
||||
|
||||
subject = subject_for_fire_hotspot(event)
|
||||
subject = adapter.subject_for(event)
|
||||
assert subject == "central.fire.hotspot.viirs_noaa20.nominal"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from datetime import datetime, timezone
|
|||
|
||||
import pytest
|
||||
|
||||
from central.models import Event, Geo, subject_for_event
|
||||
from central.models import Event, Geo
|
||||
from central.config import NWSAdapterConfig, CloudEventsConfig, NATSConfig, PostgresConfig, Config
|
||||
from central.cloudevents_wire import wrap_event
|
||||
|
||||
|
|
@ -57,47 +57,6 @@ def sample_config() -> Config:
|
|||
)
|
||||
|
||||
|
||||
class TestSubjectForEvent:
|
||||
"""Tests for subject_for_event helper."""
|
||||
|
||||
def test_county_subject(self, sample_event: Event) -> None:
|
||||
"""County codes produce county subject."""
|
||||
subject = subject_for_event(sample_event)
|
||||
assert subject == "central.wx.alert.us.id.county.ada"
|
||||
|
||||
def test_zone_subject(self, sample_geo: Geo) -> None:
|
||||
"""Zone codes produce zone subject."""
|
||||
geo = Geo(
|
||||
centroid=sample_geo.centroid,
|
||||
bbox=sample_geo.bbox,
|
||||
regions=["US-ID-Z033"],
|
||||
primary_region="US-ID-Z033",
|
||||
)
|
||||
event = Event(
|
||||
id="test-zone",
|
||||
adapter="nws",
|
||||
category="wx.alert.winter_storm_warning",
|
||||
time=datetime(2024, 1, 15, 12, 0, 0, tzinfo=timezone.utc),
|
||||
geo=geo,
|
||||
data={},
|
||||
)
|
||||
subject = subject_for_event(event)
|
||||
assert subject == "central.wx.alert.us.id.zone.z033"
|
||||
|
||||
def test_unknown_subject(self, sample_event: Event) -> None:
|
||||
"""Missing primary_region produces unknown subject."""
|
||||
geo = Geo(regions=[], primary_region=None)
|
||||
event = Event(
|
||||
id="test-unknown",
|
||||
adapter="nws",
|
||||
category="wx.alert.test",
|
||||
time=datetime(2024, 1, 15, 12, 0, 0, tzinfo=timezone.utc),
|
||||
geo=geo,
|
||||
data={},
|
||||
)
|
||||
subject = subject_for_event(event)
|
||||
assert subject == "central.wx.alert.us.unknown"
|
||||
|
||||
|
||||
class TestCloudEventsWire:
|
||||
"""Tests for CloudEvents wire format."""
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ from central.adapters.nws import (
|
|||
SEVERITY_MAP,
|
||||
)
|
||||
from central.config_models import AdapterConfig
|
||||
from central.models import subject_for_event
|
||||
|
||||
|
||||
# Sample NWS GeoJSON features for testing
|
||||
|
|
@ -272,7 +271,7 @@ class TestSubjectDerivation:
|
|||
def test_county_subject(self, adapter: NWSAdapter) -> None:
|
||||
event = adapter._normalize_feature(SAMPLE_FEATURE_ID)
|
||||
assert event is not None
|
||||
subject = subject_for_event(event)
|
||||
subject = adapter.subject_for(event)
|
||||
# Primary region should be alphabetically first
|
||||
# Could be county or zone depending on sort order
|
||||
assert subject.startswith("central.wx.alert.us.id.")
|
||||
|
|
@ -294,7 +293,7 @@ class TestSubjectDerivation:
|
|||
}
|
||||
event = adapter._normalize_feature(feature)
|
||||
assert event is not None
|
||||
subject = subject_for_event(event)
|
||||
subject = adapter.subject_for(event)
|
||||
assert "zone" in subject
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue