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:153 — model.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
- Change
_iem_mos.py:239 to "model": model.upper().
- 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.
- 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
Summary
fetch_iem_mosinpackages/weather/src/mostlyright/weather/_fetchers/_iem_mos.py:239sends themodelquery parameter verbatim (lowercase, e.g.nbe). The IEM API athttps://mesonet.agron.iastate.edu/api/1/mos.jsonrejects 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.pyusehttpx.MockTransport, and there are no@pytest.mark.livetests covering this fetcher.Empirical repro
Calling the fetcher end-to-end reproduces the failure:
Affected code
_iem_mos.py:239—"model": modelin 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:42is defined over the lowercase enum so this side is fine, but worth confirming during the fix_iem_mos.py:153—model.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 slipProposed fix
_iem_mos.py:239to"model": model.upper().@pytest.mark.livetest inpackages/weather/tests/test_iem_mos_fetcher.pythat hits IEM for a known-good cycle and asserts non-empty results. Suggested fixture:KNYC/NBE/runtime=2024-01-15T01:00:00Zreturns 21 rows.temp_c,dew_point_c,forecast_hour,wind_speed_ms,precip_probability).Severity
P1 — every public call to
fetch_iem_mosand theresearch(..., 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