Skip to content

IEM MOS fetcher sends lowercase model param; live API returns HTTP 422 #17

Description

@helloiamvu

Summary

fetch_iem_mos in packages/weather/src/mostlyright/weather/_fetchers/_iem_mos.py:239 sends the model query parameter verbatim (lowercase, e.g. nbe). The IEM API at https://mesonet.agron.iastate.edu/api/1/mos.json rejects this with HTTP 422 — its validation regex is ^(AVN|GFS|ETA|NAM|NBS|NBE|ECM|LAV|MEX)$ (uppercase only).

Every live MOS fetch is currently broken. The bug is hidden because all unit tests in packages/weather/tests/test_iem_mos_fetcher.py use httpx.MockTransport, and there are no @pytest.mark.live tests covering this fetcher.

Empirical repro

import httpx
url = "https://mesonet.agron.iastate.edu/api/1/mos.json"

# What the fetcher sends today (lowercase model):
r = httpx.get(url, params={"station": "KNYC", "model": "nbe", "runtime": "2024-01-15T01:00:00+00:00"})
print(r.status_code, r.json())
# 422 {"detail":[{"type":"string_pattern_mismatch","loc":["query","model"],
#  "msg":"String should match pattern '^(AVN|GFS|ETA|NAM|NBS|NBE|ECM|LAV|MEX)$'","input":"nbe", ...}]}

# What IEM accepts (uppercase):
r = httpx.get(url, params={"station": "KNYC", "model": "NBE", "runtime": "2024-01-15T01:00:00+00:00"})
print(r.status_code, len(r.json()["data"]))
# 200 21

Calling the fetcher end-to-end reproduces the failure:

from mostlyright.weather._fetchers._iem_mos import fetch_iem_mos
fetch_iem_mos("KNYC", "2024-01-15", "2024-01-15", model="nbe")
# httpx.HTTPStatusError: Client error '422 Unprocessable Content' ...

Affected code

  • _iem_mos.py:239"model": model in the GET params dict
  • _iem_mos.py:227_runtime_hours_for(model, from_dt, to_dt) is called with lowercase too; the helper at _iem_mos.py:42 is defined over the lowercase enum so this side is fine, but worth confirming during the fix
  • _iem_mos.py:153model.upper() is already applied to the row's schema column, so the inconsistency between request param (lowercase) and stored column (uppercase) is what made this slip

Proposed fix

  1. Change _iem_mos.py:239 to "model": model.upper().
  2. Add an @pytest.mark.live test in packages/weather/tests/test_iem_mos_fetcher.py that hits IEM for a known-good cycle and asserts non-empty results. Suggested fixture: KNYC / NBE / runtime=2024-01-15T01:00:00Z returns 21 rows.
  3. Verify the canonical schema columns are populated end-to-end on a live response (temp_c, dew_point_c, forecast_hour, wind_speed_ms, precip_probability).

Severity

P1 — every public call to fetch_iem_mos and the research(..., include_forecast=True) path that depends on it is broken against the real API. Caught only because mocks shielded CI from the upstream contract.

Discovered while

Empirically inspecting the forecast surface — needed to call IEM MOS to confirm what columns/cadence the IEM forecast returns.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions