Skip to content

Add tsunami alerts as a new global domain - #30

Draft
turbo5000c wants to merge 4 commits into
mainfrom
claude/tsunami-alerts-planning-qulusw
Draft

Add tsunami alerts as a new global domain#30
turbo5000c wants to merge 4 commits into
mainfrom
claude/tsunami-alerts-planning-qulusw

Conversation

@turbo5000c

@turbo5000c turbo5000c commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Description

Tsunami existed in this integration as one line — three strings in binary_sensor.py:154, inside SevereWeatherAlertBinarySensor._SEVERE_EVENTS, lumped in with tornadoes and thunderstorms. A user could not tell a tsunami warning from a hailstorm, and got none of the data a tsunami alert actually carries: wave arrival time, source earthquake, evacuation instruction, or which warning center issued it. The fourth alert level — Information Statement — was missing entirely.

This adds a NOAA Tsunami device: global, created once, shared across config entries, on the same ownership-transfer pattern as NOAA Hurricane.

Seven global entities, every install:

Entity State
sensor.noaa_tsunami_threat_level Warning / Advisory / Watch / Information / None
sensor.noaa_tsunami_active_alerts count nationwide
sensor.noaa_tsunami_source_earthquake magnitude, + depth/epicenter/region attrs
sensor.noaa_tsunami_last_message when the centers last issued a product
binary_sensor.noaa_tsunami_alert_active on for Warning or Advisory (safety)
binary_sensor.noaa_tsunami_data_stale on when the feed stops answering (problem)
image.noaa_tsunami_map energy propagation forecast, or DART network map

Three more for the 26 coastal offices in the new OFFICE_TSUNAMI_CENTERS table — local threat, wave arrival, evacuation status. The ten Great Lakes offices get none of these rather than permanently-dead entities.

Data sources: api.weather.gov/alerts/active filtered on the VTEC-derived codes TSW/TSA/TSY (more stable than matching event-name strings), plus the NTWC and PTWC Atom and CAP 1.2 feeds at tsunami.gov. Parsed with the standard library — no new dependency.

The local threat sensor reads the existing NWSAlertsCoordinator, which already queries alerts/active?point=lat,lon — so it costs zero additional requests.

The map

image.noaa_tsunami_map switches source based on what is actually happening. During an event it serves the energy propagation forecast from the warning center that issued the alert — the RIFT model's directional beam of wave energy across the ocean, which is the map people picture when they think "tsunami map". The centers only generate one during a real event, so the rest of the time it serves the NDBC DART buoy network map instead. An empty panel during an event reads as "nothing is happening", which is the same failure mode the data-stale sensor exists to prevent.

Sources are tried in order; a non-200 or non-image response falls through to the next rather than raising. map_type, source_url and active_center attributes report which one is on screen.

Three choices driven by this being life-safety data

  1. unknown ≠ "None". Threat level is Python None (HA unknown) when no fetch has succeeded, and the string "None" only when a fetch genuinely found nothing. Collapsing the two would let a dead feed read as an all-clear. Two tests enforce it.
  2. A staleness sensor. binary_sensor.noaa_tsunami_data_stale exists because a silent failure on life-safety data is the failure mode that actually hurts people.
  3. 2-minute polling, the fastest in the integration — a near-field tsunami can reach the coast in under fifteen minutes. Only the NWS query runs at that rate; the warning-center feeds are fetched when an alert is active or roughly half-hourly, so a quiet install is not requesting unchanged bytes from tsunami.gov 3,600 times a day.

Testability: these sensors will read None for years, which makes the feature hard to trust. Monthly NWS communications tests never move the threat level or trip the alert sensor, but do populate a last_test_message attribute. If that is updating, the pipeline works.

Back-compat: the tsunami strings in _SEVERE_EVENTS are deliberately left in place, with a regression test. Existing automations keep firing.

Security: XML is size-capped at 512 KB and refused outright if it carries a DOCTYPE, since xml.etree.ElementTree is not hardened against entity expansion and defusedxml is not a dependency here.

Fixed against a live install (commit 3)

A screenshot from a real Home Assistant install running this branch showed Last Message carrying a genuine timestamp — so the tsunami.gov Atom feed is reachable and parsing correctly — while Source Earthquake sat on Unknown. Two bugs, both of which the test suite was green through:

  • The magnitude pattern was anchored on a bare M. Against "preliminary magnitude 6.2" it matched the leading letter of "magnitude" and then failed on the letters after it. The fixture said M 7.8, which matched fine — so the tests passed while the wording the centers actually use never did. Several forms are now tried in order, spelled-out first, with a 0–10 sanity bound so a message number cannot be read as an earthquake.
  • Only products[0] was inspected. The newest product is frequently a routine statement with no quake parameters, which blanked the sensor while the answer sat one entry down. find_source_earthquake now scans back.

This also reframed the sensor: the centers publish a statement for every notable earthquake and most come to nothing, so Source Earthquake reports the last quake the centers evaluated and dismissed, not only ones that caused a tsunami. That is what makes the domain worth looking at on a quiet day.

Wave Arrival also now reads No active event when the feed is healthy, rather than unknown — which on a working feed reads like a fault.

Type

  • Bugfix
  • Feature
  • Documentation
  • Other

Checklist

  • Follows Home Assistant custom integration structure
  • Passes flake8 checks (max-line-length=120)
  • Manifest is valid and complete (version bumped to 0.6.0, no new requirements)
  • README updated if required

Verification

  • python -m pytest tests/ -v858 passed (680 before, +178 new)
  • python -m flake8 --max-line-length=120 custom_components/ tests/ → clean

Two things worth a reviewer's attention

1. The map URLs are still unverified — and the map is still blank on a live install. The sandbox's egress proxy blocks every NOAA host, so nothing here was written from captured bytes. The Atom feed URLs are now confirmed working by the live install above; the CAP structure and the map image URLs remain guesses. TSUNAMI_DART_MAP_URLS is deliberately a list of candidates tried in turn, and the URL that succeeds is logged at info level:

Tsunami map served from <url> (DART Network, N bytes)

If that line appears in a real install's log, it names the working URL and the list can be trimmed to it. If it never appears, all four candidates are wrong and the real address is needed. Still outstanding for someone with network access:

  • tsunami.gov/events/xml/PAAQCAP.xml against parse_tsunami_cap, replacing the hand-written fixture with real bytes
  • TSUNAMI_ENERGY_MAP_URLS and TSUNAMI_DART_MAP_URLS

2. Pre-existing bug left alone: README.md has a duplicated ### Dashboard Card Examples heading. Out of scope for this PR, not touched.

claude added 4 commits August 14, 2026 22:41
Tsunami existed in this integration as one line: three strings inside
SevereWeatherAlertBinarySensor._SEVERE_EVENTS, lumped in with tornadoes and
thunderstorms. A user could not tell a tsunami warning from a hailstorm, and
got none of the data a tsunami alert actually carries.

This adds a NOAA Tsunami device (global, shared across config entries, on the
same ownership-transfer pattern as NOAA Hurricane) with six entities everyone
gets and three more for the 26 coastal offices in OFFICE_TSUNAMI_CENTERS. The
ten Great Lakes offices get no location-specific entities rather than
permanently-dead ones.

Data comes from api.weather.gov/alerts/active filtered on the VTEC-derived
codes TSW/TSA/TSY, plus the NTWC and PTWC Atom and CAP 1.2 feeds at
tsunami.gov for the source earthquake and per-location wave arrival times.
CAP and Atom are parsed with the standard library, so no new dependency.

Three choices are driven by this being life-safety data:

- Threat level is None (Home Assistant "unknown") when no fetch has succeeded
  and the string "None" only when a fetch genuinely found nothing. Collapsing
  the two would let a dead feed read as an all-clear.
- binary_sensor.noaa_tsunami_data_stale turns on when the feed stops
  answering, because a silent failure is the failure mode that hurts people.
- The NWS query polls every 2 minutes; a near-field tsunami can reach the
  coast in under fifteen. The warning-center feeds are only fetched when an
  alert is active or roughly half-hourly, so a quiet install is not requesting
  unchanged bytes from tsunami.gov 3,600 times a day.

Monthly NWS communications tests never move the threat level or trip the alert
sensor, but do populate a last_test_message attribute — on a normal install
that is the only traffic this domain will ever see, and it is how a user
confirms the pipeline still works between real events.

The tsunami strings in _SEVERE_EVENTS are deliberately left in place so
existing automations keep firing, with a regression test to enforce it.

XML is size-capped and DOCTYPE-refused before parsing, since
xml.etree.ElementTree is not hardened against entity expansion and defusedxml
is not a dependency.

141 new tests; 821 pass, flake8 clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0154K7X9gXPjnvkaYrQVQc4a
Adds image.noaa_tsunami_map to the NOAA Tsunami device, on the existing
image.py ImageEntity pattern with the same added-once ownership transfer the
hurricane images use.

The map switches source based on what is actually happening. During an event
it serves the energy propagation forecast from the warning center that issued
the alert — the RIFT model's directional beam of wave energy across the ocean,
which is the map people picture. The centers only generate one during a real
event, so the rest of the time it serves the NDBC DART buoy network map
instead. An empty panel during an event reads as "nothing is happening", which
is the same failure mode the data-stale sensor exists to prevent.

Sources are tried in order and a non-200 or non-image response falls through
to the next rather than raising, so a wrong URL degrades to the fallback
instead of a broken tile.

The image URLs could not be verified against live NOAA — the development
sandbox blocks every NOAA host — so they are isolated in const.py behind a
comment saying so. Correcting one is a single-line change and no logic moves.

Center attribution reads the Atom products first and falls back to the alert
sender name. That fallback needed a spelled-out hint table: alerts identify
the sender as "NWS National Tsunami Warning Center", so matching on the
literal "NTWC" never fired. Caught by a test before it shipped.

20 new tests; 841 pass, flake8 clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0154K7X9gXPjnvkaYrQVQc4a
A live install showed the NOAA Tsunami device with Last Message carrying a
real timestamp — so the tsunami.gov Atom feed was being fetched and parsed
correctly — while Source Earthquake sat on Unknown. Two separate bugs.

The magnitude pattern was anchored on a bare "M", so against "preliminary
magnitude 6.2" it matched the leading letter of "magnitude" and then failed on
the letters after it. That spelled-out form is how the centers usually write
it, so the common case never matched while the fixture's "M 7.8" did. Several
wordings are now tried in order, spelled-out first, with a 0-10 sanity bound
so a message number cannot be read as an earthquake.

Only products[0] was inspected. The newest product is frequently a routine
statement carrying no quake parameters at all, which blanked the sensor while
the answer sat one entry further down. find_source_earthquake now scans back
through recent products and falls back to the newest entry's region and link,
so the sensor is never a dead end.

Both of these mattered more than they look: the centers publish a statement
for every notable earthquake and most come to nothing, so this sensor is what
the domain has to say on a quiet day. Reporting the last quake the centers
looked at and dismissed is the difference between an interesting panel and a
column of Unknowns.

Also:
- Wave Arrival said "unknown" whenever no event was in progress, which reads
  like a fault. It now says "No active event" when the feed is healthy and
  reserves unknown for "nothing fetched" — the same distinction the threat
  level already makes.
- The quiet-day map is now a list of candidate URLs tried in turn rather than
  one unverified guess, and the URL that works is logged at info level so it
  can be identified from a real install's log and the list trimmed.

858 pass, flake8 clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0154K7X9gXPjnvkaYrQVQc4a
The map never rendered. Six guessed URLs across two revisions — RIFT energy
propagation forecasts and NDBC DART network maps — and every one 404'd on a
live install. Web search could not surface the real ones either, and the
tsunami.gov homepage map appears to be an interactive viewer with no single
image behind it.

The user supplied a URL that actually exists:

  https://www.tsunami.gov/previous.events/08-29-2018_LoyaltyIslands/Images/Location.jpg

The warning centers archive every past tsunami under previous.events/ with a
location map at a predictable path, and list them at /recent_tsunamis/. So the
map is now the location map of the most recent tsunami in that archive, and
every guessed URL is deleted rather than kept as a fallback — an unverifiable
fallback only makes the real failure harder to diagnose.

Adds sensor.noaa_tsunami_latest_event alongside it. This is the one entity in
the domain with something to say every single day: the last tsunami that
actually happened anywhere in the world. It changes only when the centers add
an event, which makes it a usable automation trigger in a way the alert
sensors — correctly silent for years at a time — are not.

parse_recent_tsunami_events reads the listing with a regex over the HTML, the
same approach ForecastDiscussionCoordinator already uses for AFD text. Only
dated event directories match, so a layout change degrades to an empty list
and a logged warning rather than to nonsense. The listing rides the slow
half-hourly cadence, not the 2-minute alert poll.

Also removes TSUNAMI_CENTER_SENDER_HINTS, which only existed to attribute an
energy map to a center and is dead now.

870 pass, flake8 clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0154K7X9gXPjnvkaYrQVQc4a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants