Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
6 changes: 5 additions & 1 deletion cogs/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()]
Expand Down
42 changes: 42 additions & 0 deletions tests/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
Loading