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
17 changes: 16 additions & 1 deletion src/accessiweather/weather_client_nws.py
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,9 @@ def parse_nws_forecast(data: dict) -> Forecast:
"""Parse NWS forecast payload into a Forecast model."""
periods = []

for period_data in data.get("properties", {}).get("periods", []):
raw_periods = data.get("properties", {}).get("periods", [])

for period_data in raw_periods:
temperature, temperature_unit = _extract_temperature(
period_data.get("temperature"), period_data.get("temperatureUnit")
)
Expand Down Expand Up @@ -1205,6 +1207,19 @@ def parse_nws_forecast(data: dict) -> Forecast:
)
periods.append(period)

# Pair daytime/nighttime periods to populate high/low temperatures.
# NWS returns alternating day/night periods (isDaytime flag). Set the
# nighttime temperature as temperature_low on the preceding daytime period.
for i, period_data in enumerate(raw_periods):
if period_data.get("isDaytime") and i + 1 < len(raw_periods):
next_data = raw_periods[i + 1]
if not next_data.get("isDaytime"):
night_temp, _ = _extract_temperature(
next_data.get("temperature"), next_data.get("temperatureUnit")
)
if night_temp is not None:
periods[i].temperature_low = night_temp

return Forecast(periods=periods, generated_at=datetime.now())


Expand Down
85 changes: 85 additions & 0 deletions tests/test_nws_high_low.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Tests for NWS high/low temperature pairing in parse_nws_forecast."""

from accessiweather.weather_client_nws import parse_nws_forecast


def _make_period(name, temp, unit="F", is_daytime=True):
return {
"name": name,
"temperature": temp,
"temperatureUnit": unit,
"isDaytime": is_daytime,
"shortForecast": "Sunny",
"detailedForecast": "",
"windSpeed": "10 mph",
"windDirection": "N",
"icon": "",
"startTime": "2026-02-12T06:00:00-05:00",
"endTime": "2026-02-12T18:00:00-05:00",
}


def _wrap(periods):
return {"properties": {"periods": periods}}


class TestNwsHighLowPairing:
def test_daytime_gets_nighttime_low(self):
data = _wrap(
[
_make_period("Today", 34, is_daytime=True),
_make_period("Tonight", 16, is_daytime=False),
]
)
forecast = parse_nws_forecast(data)
assert forecast.periods[0].temperature == 34
assert forecast.periods[0].temperature_low == 16
assert forecast.periods[1].temperature == 16
assert forecast.periods[1].temperature_low is None

def test_multiple_day_night_pairs(self):
data = _wrap(
[
_make_period("Today", 34, is_daytime=True),
_make_period("Tonight", 16, is_daytime=False),
_make_period("Friday", 36, is_daytime=True),
_make_period("Friday Night", 20, is_daytime=False),
]
)
forecast = parse_nws_forecast(data)
assert forecast.periods[0].temperature_low == 16
assert forecast.periods[2].temperature_low == 20

def test_nighttime_first_no_crash(self):
"""When forecast starts at night (evening request), no pairing for first period."""
data = _wrap(
[
_make_period("Tonight", 16, is_daytime=False),
_make_period("Friday", 36, is_daytime=True),
_make_period("Friday Night", 20, is_daytime=False),
]
)
forecast = parse_nws_forecast(data)
assert forecast.periods[0].temperature_low is None
assert forecast.periods[1].temperature_low == 20

def test_single_daytime_period_no_crash(self):
data = _wrap([_make_period("Today", 34, is_daytime=True)])
forecast = parse_nws_forecast(data)
assert forecast.periods[0].temperature_low is None

def test_empty_periods(self):
forecast = parse_nws_forecast({"properties": {"periods": []}})
assert len(forecast.periods) == 0

def test_consecutive_daytime_no_pairing(self):
"""Two daytime periods in a row shouldn't pair."""
data = _wrap(
[
_make_period("Today", 34, is_daytime=True),
_make_period("Tomorrow", 36, is_daytime=True),
]
)
forecast = parse_nws_forecast(data)
assert forecast.periods[0].temperature_low is None
assert forecast.periods[1].temperature_low is None
Loading