diff --git a/CHANGELOG.md b/CHANGELOG.md index 1481f7b..2469008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file. - **MD AI Summary Threads**: Mesoscale Discussion AI summaries now create a thread named `MD #{num}` on the MD message instead of posting a standalone message in the channel (#613, #614). The AI Analysis button detects the thread and posts inside it with an ephemeral link to the user. Falls back to channel posting when thread creation fails. ### Fixed +- **Wrong Max EF Rating in Multi-Tornado Damage Survey PNS**: `_handle_pns` stripped everything after the first `&&` marker, intended to cut the trailing "EF Scale" legend out of the search text. But NWS damage survey PNS products also use `&&` as the delimiter *between every individual tornado entry*, so on any product with more than one tornado, only the first tornado's rating was ever considered — the max EF shown could be lower than the actual strongest tornado in the survey if a later entry was stronger. Confirmed live against KPBZ's July 21, 2026 tornado event Update #3, which reported "Max: EF0" despite containing an EF1 tornado (Zanesville 1, Muskingum County, OH). Now only `EF SCALE:` / `THE ENHANCED FUJITA SCALE` truncate the search, leaving every tornado entry's rating intact. - **Tropical Product Posting Crash**: `post_tropical_product` crashed with `AttributeError: 'NoneType' object has no attribute 'splitlines'` on every real NHC product, because most product types (anything without a "SUMMARY OF..." section) parse to `summary: None`, and `dict.get(key, default)` doesn't apply the default when the key is present with a `None` value. No tropical product had ever successfully posted before this fix. - **Tropical Products Posting to Dev Channel**: `cogs/tropical.py` posted to the hardcoded dev channel unconditionally. Now posts to the production tropical channel by default. - **Wrong NHC Product Labels**: `TCP` (the actual Public Advisory — "SUMMARY OF ... ADVISORY NUMBER X") was mislabeled "PROBABILITIES", and `TCV` (the Tropical Cyclone Watch/Warning summary product) was mislabeled "ADVISORY", swapping the two most common tropical products' titles. Confirmed live against Hurricane Fausto Advisory 14 and Tropical Storm Bertha Advisory 12, both of which posted as "NHC PROBABILITIES" instead of "NHC ADVISORY". Corrected per NHC's own product descriptions (nhc.noaa.gov/aboutnhcprod.shtml). diff --git a/cogs/reports.py b/cogs/reports.py index 69154ac..13451c9 100644 --- a/cogs/reports.py +++ b/cogs/reports.py @@ -291,8 +291,12 @@ async def _handle_pns(self, product_id: str, raw_text: str): return # 1. Strip boilerplate (EF Scale key) to avoid false EF5 matches + # NOTE: "&&" is NOT used as a stop marker here — multi-tornado PNS + # products use "&&" as the delimiter *between every tornado entry*, + # not just before the trailing legend, so stopping on it would + # truncate the search after the first tornado and miss the real max. search_text = raw_text - for stop_marker in ("&&", "EF SCALE:", "THE ENHANCED FUJITA SCALE"): + for stop_marker in ("EF SCALE:", "THE ENHANCED FUJITA SCALE"): m_stop = re.search(re.escape(stop_marker), raw_text, re.I) if m_stop: search_text = raw_text[: m_stop.start()] diff --git a/tests/test_reports.py b/tests/test_reports.py index e8ce496..9b99a34 100644 --- a/tests/test_reports.py +++ b/tests/test_reports.py @@ -269,6 +269,48 @@ async def test_handle_pns_extracts_max_ef_rating(): assert "EF3" in embed.description +@pytest.mark.asyncio +async def test_handle_pns_extracts_max_ef_rating_across_ampamp_delimited_tornadoes(): + """Regression: "&&" separates every tornado entry in real PNS products, + not just the trailing EF Scale legend. The max EF must be found even when + a stronger tornado appears after an earlier "&&" than a weaker first one.""" + cog, channel = _make_cog() + + multi_pns = """\ +...NWS DAMAGE SURVEY FOR 07/21/2026 TORNADO EVENT... + +..First Tornado... + +Rating: EF0 +Estimated Peak Wind: 80 mph + +&& + +.Second Tornado... + +Rating: EF1 +Estimated Peak Wind: 105 mph + +&& + +EF Scale: The Enhanced Fujita Scale classifies tornadoes into the +following categories: + +EF0.....65 to 85 mph +EF1.....86 to 110 mph + +SUMMARY: Two tornadoes confirmed near Example City OK. +$$ +""" + with patch("cogs.reports.add_significant_event", AsyncMock()), patch( + "utils.state_store.find_matching_tornado", AsyncMock(return_value=None) + ), patch.object(cog, "_check_for_surveys", AsyncMock()): + await cog._handle_pns("202607232205-KPBZ-PNSPBZ", multi_pns) + + embed: discord.Embed = channel.send.call_args.kwargs["embed"] + assert "EF1" in embed.description + + @pytest.mark.asyncio async def test_handle_pns_parses_numerical_date_for_survey_check(): """MM/DD/YYYY date triggers _check_for_surveys with the correct ISO date."""