From 7b6f684b6622355000292b44be2f8e6c818ab518 Mon Sep 17 00:00:00 2001 From: zvx-echo6 Date: Tue, 19 May 2026 07:08:15 +0000 Subject: [PATCH] fix(2-E): use canonical removed-event subject pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per handoff ยง9 the removed-event convention is central...removed. -- WFIGS uses central.fire.incident.removed.. GDACS tombstones were emitting central.disaster.removed. with the eventtype only in the category (disaster.removed.wf), which would silently miss type-filtered subscribers (e.g. central.disaster.wf.> would not see WF removals). Fix: - poll() iscurrent=false branch and missing-from-feed loop both set category=f"disaster.{eventtype.lower()}.removed" (eventtype before the .removed token, matching the live-event subject hierarchy). - subject_for() detects parts[-1] == "removed" and emits central.disaster..removed.. Tests updated: test_fall_off_iscurrent_false now asserts category disaster.wf.removed and subject central.disaster.wf.removed.greece. test_fall_off_missing_from_feed adds the category assertion. Both tombstone-collection filters flip from startswith("disaster.removed") to endswith(".removed") for general-shape coverage. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/central/adapters/gdacs.py | 10 ++++++---- tests/test_gdacs.py | 11 ++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/central/adapters/gdacs.py b/src/central/adapters/gdacs.py index f0af386..9ff6d6e 100644 --- a/src/central/adapters/gdacs.py +++ b/src/central/adapters/gdacs.py @@ -238,9 +238,11 @@ class GDACSAdapter(SourceAdapter): return count def subject_for(self, event: Event) -> str: + parts = event.category.split(".") country_subj = subject_for_country(event.data.get("country")) - if event.category.startswith("disaster.removed"): - return f"central.disaster.removed.{country_subj}" + if len(parts) >= 3 and parts[-1] == "removed": + eventtype = parts[1] + return f"central.disaster.{eventtype}.removed.{country_subj}" eventtype = (event.data.get("eventtype") or "").lower() or "unknown" return f"central.disaster.{eventtype}.{country_subj}" @@ -395,7 +397,7 @@ class GDACSAdapter(SourceAdapter): tombstone = Event( id=f"{guid}:removed", adapter=self.name, - category=f"disaster.removed.{eventtype.lower()}", + category=f"disaster.{eventtype.lower()}.removed", time=datetime.now(timezone.utc), severity=0, geo=geo, @@ -449,7 +451,7 @@ class GDACSAdapter(SourceAdapter): tombstone = Event( id=tombstone_id, adapter=self.name, - category=f"disaster.removed.{(prior_eventtype or '').lower()}", + category=f"disaster.{(prior_eventtype or '').lower()}.removed", time=now, severity=0, geo=geo, diff --git a/tests/test_gdacs.py b/tests/test_gdacs.py index b9c29d0..e87b493 100644 --- a/tests/test_gdacs.py +++ b/tests/test_gdacs.py @@ -308,14 +308,14 @@ class TestGDACSAdapter: second_pass = [e async for e in adapter.poll()] await adapter.shutdown() - tombstones = [e for e in second_pass if e.category.startswith("disaster.removed")] + tombstones = [e for e in second_pass if e.category.endswith(".removed")] assert len(tombstones) == 1 ts = tombstones[0] assert ts.id == "WF2002001:removed" - assert ts.category == "disaster.removed.wf" + assert ts.category == "disaster.wf.removed" assert ts.data["reason"] == "iscurrent_false" - # Subject form: central.disaster.removed. - assert adapter.subject_for(ts) == "central.disaster.removed.greece" + # Subject form: central.disaster..removed. + assert adapter.subject_for(ts) == "central.disaster.wf.removed.greece" @pytest.mark.asyncio async def test_fall_off_missing_from_feed(self, tmp_path: Path): @@ -332,9 +332,10 @@ class TestGDACSAdapter: second_pass = [e async for e in adapter.poll()] await adapter.shutdown() - tombstones = [e for e in second_pass if e.category.startswith("disaster.removed")] + tombstones = [e for e in second_pass if e.category.endswith(".removed")] assert len(tombstones) == 1 assert tombstones[0].id == "WF2002001:removed" + assert tombstones[0].category == "disaster.wf.removed" assert tombstones[0].data["reason"] == "missing_from_feed" @pytest.mark.asyncio