Skip to content

fix(noaa): severe weather always reported zero — /alerts/active rejects limit - #129

Open
YAMRAJ13y wants to merge 1 commit into
calesthio:masterfrom
YAMRAJ13y:fix/noaa-active-alerts-limit
Open

fix(noaa): severe weather always reported zero — /alerts/active rejects limit#129
YAMRAJ13y wants to merge 1 commit into
calesthio:masterfrom
YAMRAJ13y:fix/noaa-active-alerts-limit

Conversation

@YAMRAJ13y

Copy link
Copy Markdown

Summary

The NOAA/NWS source has been reporting zero severe weather alerts on every sweep. It sends a limit query parameter that api.weather.gov/alerts/active does not accept, so every request fails with HTTP 400 and the source silently returns an all-clear.

Why

getActiveAlerts() builds ?limit=50&status=actual&severity=Extreme,Severe. The endpoint rejects it, and says so explicitly:

$ curl -s -H 'Accept: application/geo+json' \
    'https://api.weather.gov/alerts/active?limit=50&status=actual&severity=Extreme,Severe'
{
  "parameterErrors": [
    { "parameter": "query.limit",
      "message": "Query parameter \"limit\" is not recognized" }
  ],
  "title": "Bad Request",
  "status": 400
}

safeFetch resolves to { error } instead of throwing, so alerts?.features || [] collapses to [] and briefing() produces a clean-looking payload with no error field anywhere:

// node apis/sources/noaa.mjs  — on master
{
  "source": "NOAA/NWS",
  "totalSevereAlerts": 0,
  "summary": { "hurricanes": 0, "tornadoes": 0, "floods": 0,
               "winterStorms": 0, "wildfires": 0, "other": 0 },
  "topAlerts": []
}

That is the failure mode that matters here: not a visible outage, but a dead source that looks like good news. The dashboard's noaa.totalAlerts reads 0, no map markers are drawn, and nothing in /api/health indicates a problem.

Dropping the parameter restores the source. Same command on this branch, at the time of writing:

{
  "source": "NOAA/NWS",
  "totalSevereAlerts": 33,
  "topAlerts": [
    { "event": "Severe Thunderstorm Warning", "severity": "Severe",
      "urgency": "Immediate", "lat": 43.676, "lon": -89.516,
      "headline": "Severe Thunderstorm Warning issued August 9 at 9:00AM CDT ..." },
    ...
  ]
}

33 active alerts — 26 Extreme Heat Warnings, 5 Red Flag Warnings, 1 Severe Thunderstorm Warning, 1 Extreme Heat Watch.

Notes on the approach

Why remove limit rather than apply it client-side. /alerts/active only ever returns currently-active alerts, so the response is naturally bounded. Capping client-side would under-report totalSevereAlerts during exactly the severe-weather outbreaks this source exists to catch. Nothing in the repo passes limitgetSevereAlerts() only sets severity — so removing the option is not a breaking change for any caller.

The four-line error passthrough. briefing() now includes alerts.error when the request fails, so the next upstream change surfaces instead of rendering as "no severe weather anywhere in the US". It is the same bug class rather than a second feature, and it is additive so it cannot break the dashboard's totalSevereAlerts / topAlerts consumers in dashboard/inject.mjs:459. Happy to split it into its own PR if you would rather keep this to the one-line parameter fix.

Out of scope, noticed while testing: the summary buckets have no category for heat or fire-weather alerts, so today all 33 land in other (/fire/i does not match "Red Flag Warning"). That is a taxonomy decision rather than a bug, so I left it alone — happy to open a separate issue.

Scope

  • Focused bug fix
  • Small UX improvement
  • New source
  • Dashboard change
  • Docs/config change

Validation

# before: 0 alerts, no error   after: 33 alerts
node apis/sources/noaa.mjs

# the 400 is reproducible directly against upstream
curl -s -H 'Accept: application/geo+json' \
  'https://api.weather.gov/alerts/active?limit=50&status=actual&severity=Extreme,Severe'

# forced-failure path now surfaces the error instead of a silent all-clear
node --input-type=module -e "
globalThis.fetch = async () => { throw new Error('simulated network failure'); };
const { briefing } = await import('./apis/sources/noaa.mjs');
console.log((await briefing()).error);   // -> 'simulated network failure'
"

node --check apis/sources/noaa.mjs
node --test test/*.test.mjs     # 45 pass, 1 skipped, 0 fail — unchanged

Config and Docs

  • No new environment variables
  • .env.example unchanged — NOAA needs no key
  • README.md unchanged — this restores documented behaviour

…ts `limit`

`getActiveAlerts()` always sent `limit` to `api.weather.gov/alerts/active`,
which the endpoint does not accept. The upstream reply is explicit:

    HTTP 400
    {"parameter": "query.limit",
     "message": "Query parameter \"limit\" is not recognized"}

`safeFetch` resolves to `{ error }` rather than throwing, so `alerts?.features
|| []` collapsed to an empty array and the source reported a confident
all-clear on every sweep since the parameter was added:

    "totalSevereAlerts": 0,
    "summary": { "hurricanes": 0, "tornadoes": 0, "floods": 0,
                 "winterStorms": 0, "wildfires": 0, "other": 0 }

with no error field anywhere in the payload. At the time of this commit the
same query without `limit` returns 33 active severe alerts, including a live
Severe Thunderstorm Warning and 26 Extreme Heat Warnings.

Drop the parameter. `/alerts/active` only ever returns currently-active
alerts, so the response is naturally bounded and no client-side cap is
needed — capping would under-report `totalSevereAlerts` during exactly the
severe-weather outbreaks this source exists to catch.

Also pass through `alerts.error` so a future upstream failure is visible in
the payload instead of silently rendering as "no severe weather anywhere in
the US". Same bug class, four lines; happy to split it out if preferred.

Verified: 0 -> 33 alerts against the live API; forced-failure path now
surfaces the error; `node --test test/*.test.mjs` 45 pass / 1 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@YAMRAJ13y
YAMRAJ13y requested a review from calesthio as a code owner August 9, 2026 14:18
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.

1 participant