mirror of
https://github.com/zvx-echo6/central.git
synced 2026-05-22 18:44:40 +02:00
foundation: models, adapter ABC, config, CE wire, schema
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
36ebbcb250
commit
714971fe99
9 changed files with 1051 additions and 0 deletions
68
src/central/models.py
Normal file
68
src/central/models.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""Data models for Central event processing."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class Geo(BaseModel):
|
||||
"""Geographic context for an event."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
centroid: tuple[float, float] | None = None # (lon, lat) GeoJSON order
|
||||
bbox: tuple[float, float, float, float] | None = None # (minLon, minLat, maxLon, maxLat)
|
||||
regions: list[str] = [] # ["US-ID-Ada", "US-ID-Z033", ...]
|
||||
primary_region: str | None = None # alphabetically first region, used for subject
|
||||
|
||||
|
||||
class Event(BaseModel):
|
||||
"""Canonical event representation for all adapters."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
id: str # unique, stable across republish
|
||||
source: str # adapter identity, e.g. "central/adapters/nws"
|
||||
category: str # e.g. "wx.alert.severe_thunderstorm_warning"
|
||||
time: datetime # event-time UTC, not processing-time
|
||||
expires: datetime | None = None
|
||||
severity: int | None = None # 0..4 or None for "Unknown"
|
||||
geo: Geo
|
||||
data: dict[str, Any] # adapter-specific payload
|
||||
|
||||
|
||||
def subject_for_event(ev: Event, prefix: str = "central.wx") -> str:
|
||||
"""
|
||||
Compute the NATS subject for an alert-style event.
|
||||
|
||||
For weather alerts the subject is:
|
||||
central.wx.alert.us.<state_lower>.county.<county_lower>
|
||||
or
|
||||
central.wx.alert.us.<state_lower>.zone.<zone_lower>
|
||||
based on whether the primary_region encodes a county or a zone.
|
||||
|
||||
If primary_region is None or unparseable, returns:
|
||||
central.wx.alert.us.unknown
|
||||
"""
|
||||
if ev.geo.primary_region is None:
|
||||
return f"{prefix}.alert.us.unknown"
|
||||
|
||||
region = ev.geo.primary_region
|
||||
|
||||
# Parse US-<STATE>-<CODE> format
|
||||
# County codes are like "Ada", "Canyon" (names)
|
||||
# Zone codes start with "Z" like "Z033"
|
||||
parts = region.split("-")
|
||||
if len(parts) < 3 or parts[0] != "US":
|
||||
return f"{prefix}.alert.us.unknown"
|
||||
|
||||
state = parts[1].lower()
|
||||
code = "-".join(parts[2:]) # Handle multi-part names like "Payette-Washington"
|
||||
|
||||
if code.startswith("Z") and len(code) >= 2 and code[1:].isdigit():
|
||||
# Zone code like Z033
|
||||
return f"{prefix}.alert.us.{state}.zone.{code.lower()}"
|
||||
else:
|
||||
# County name
|
||||
return f"{prefix}.alert.us.{state}.county.{code.lower()}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue