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
107 changes: 14 additions & 93 deletions src/accessiweather/weather_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@
CurrentConditions,
Forecast,
HourlyForecast,
HourlyForecastPeriod,
Location,
SourceAttribution,
SourceData,
TrendInsight,
WeatherAlerts,
WeatherData,
)
Expand Down Expand Up @@ -912,16 +910,18 @@ def _launch_enrichment_tasks(
# Smart enrichments for auto mode
if self.data_source == "auto":
tasks["sunrise_sunset"] = asyncio.create_task(
self._enrich_with_sunrise_sunset(weather_data, location)
enrichment.enrich_with_sunrise_sunset(self, weather_data, location)
)
tasks["nws_discussion"] = asyncio.create_task(
self._enrich_with_nws_discussion(weather_data, location)
enrichment.enrich_with_nws_discussion(self, weather_data, location)
)
tasks["vc_alerts"] = asyncio.create_task(
self._enrich_with_visual_crossing_alerts(weather_data, location, skip_notifications)
enrichment.enrich_with_visual_crossing_alerts(
self, weather_data, location, skip_notifications
)
)
tasks["vc_moon_data"] = asyncio.create_task(
self._enrich_with_visual_crossing_moon_data(weather_data, location)
enrichment.enrich_with_visual_crossing_moon_data(self, weather_data, location)
)

if self.trend_insights_enabled and not weather_data.daily_history:
Expand All @@ -931,10 +931,10 @@ def _launch_enrichment_tasks(

# Post-processing enrichments (always run)
tasks["environmental"] = asyncio.create_task(
self._populate_environmental_metrics(weather_data, location)
enrichment.populate_environmental_metrics(self, weather_data, location)
)
tasks["aviation"] = asyncio.create_task(
self._enrich_with_aviation_data(weather_data, location)
enrichment.enrich_with_aviation_data(self, weather_data, location)
)

return tasks
Expand All @@ -960,7 +960,12 @@ async def _await_enrichments(
logger.debug(f"Enrichment '{task_name}' failed: {result}")

# Apply final processing
self._apply_trend_insights(weather_data)
trends.apply_trend_insights( # pragma: no cover
weather_data,
self.trend_insights_enabled,
self.trend_hours,
include_pressure=self.show_pressure_trend,
)
self._persist_weather_data(weather_data.location, weather_data)

def _determine_api_choice(self, location: Location) -> str:
Expand Down Expand Up @@ -1128,36 +1133,6 @@ async def _get_openmeteo_hourly_forecast(self, location: Location) -> HourlyFore
location, self.openmeteo_base_url, self.timeout, self._get_http_client()
)

def _parse_nws_current_conditions(self, data: dict) -> CurrentConditions:
"""Delegate to the NWS client module."""
return nws_client.parse_nws_current_conditions(data)

def _parse_nws_forecast(self, data: dict) -> Forecast:
"""Delegate to the NWS client module."""
return nws_client.parse_nws_forecast(data)

def _parse_nws_alerts(self, data: dict) -> WeatherAlerts:
"""Delegate to the NWS client module."""
return nws_client.parse_nws_alerts(data)

def _parse_nws_hourly_forecast(
self, data: dict, location: Location | None = None
) -> HourlyForecast:
"""Delegate to the NWS client module."""
return nws_client.parse_nws_hourly_forecast(data, location)

def _parse_openmeteo_current_conditions(self, data: dict) -> CurrentConditions:
"""Delegate to the Open-Meteo client module."""
return openmeteo_client.parse_openmeteo_current_conditions(data)

def _parse_openmeteo_forecast(self, data: dict) -> Forecast:
"""Delegate to the Open-Meteo client module."""
return openmeteo_client.parse_openmeteo_forecast(data)

def _parse_openmeteo_hourly_forecast(self, data: dict) -> HourlyForecast:
"""Delegate to the Open-Meteo client module."""
return openmeteo_client.parse_openmeteo_hourly_forecast(data)

async def _augment_current_with_openmeteo(
self,
current: CurrentConditions | None,
Expand Down Expand Up @@ -1191,16 +1166,6 @@ async def _augment_current_with_openmeteo(
)
return parsers.merge_current_conditions(current, fallback)

async def _enrich_with_nws_discussion(
self, weather_data: WeatherData, location: Location
) -> None:
await enrichment.enrich_with_nws_discussion(self, weather_data, location)

async def _enrich_with_aviation_data(
self, weather_data: WeatherData, location: Location
) -> None:
await enrichment.enrich_with_aviation_data(self, weather_data, location)

async def get_aviation_weather(
self,
station_id: str,
Expand All @@ -1219,28 +1184,6 @@ async def get_aviation_weather(
cwsu_id=cwsu_id,
)

async def _enrich_with_visual_crossing_alerts(
self, weather_data: WeatherData, location: Location, skip_notifications: bool = False
) -> None:
await enrichment.enrich_with_visual_crossing_alerts(
self, weather_data, location, skip_notifications
)

async def _enrich_with_visual_crossing_moon_data(
self, weather_data: WeatherData, location: Location
) -> None:
await enrichment.enrich_with_visual_crossing_moon_data(self, weather_data, location)

async def _enrich_with_sunrise_sunset(
self, weather_data: WeatherData, location: Location
) -> None:
await enrichment.enrich_with_sunrise_sunset(self, weather_data, location)

async def _populate_environmental_metrics(
self, weather_data: WeatherData, location: Location
) -> None:
await enrichment.populate_environmental_metrics(self, weather_data, location)

async def _enrich_with_visual_crossing_history(
self, weather_data: WeatherData, location: Location
) -> None:
Expand Down Expand Up @@ -1268,14 +1211,6 @@ async def _enrich_with_visual_crossing_history(
except Exception as exc: # noqa: BLE001
logger.debug("Failed to fetch history from Visual Crossing: %s", exc)

def _apply_trend_insights(self, weather_data: WeatherData) -> None:
trends.apply_trend_insights(
weather_data,
self.trend_insights_enabled,
self.trend_hours,
include_pressure=self.show_pressure_trend,
)

def _persist_weather_data(self, location: Location, weather_data: WeatherData) -> None:
if not self.offline_cache:
return
Expand All @@ -1287,17 +1222,3 @@ def _persist_weather_data(self, location: Location, weather_data: WeatherData) -
self.offline_cache.store(location, weather_data)
except Exception as exc: # noqa: BLE001
logger.debug(f"Failed to persist weather data cache: {exc}")

def _compute_temperature_trend(self, weather_data: WeatherData) -> TrendInsight | None:
return trends.compute_temperature_trend(weather_data, self.trend_hours)

def _compute_pressure_trend(self, weather_data: WeatherData) -> TrendInsight | None:
return trends.compute_pressure_trend(weather_data, self.trend_hours)

def _trend_descriptor(self, change: float, *, minor: float, strong: float) -> tuple[str, str]:
return trends.trend_descriptor(change, minor=minor, strong=strong)

def _period_for_hours_ahead(
self, periods: list[HourlyForecastPeriod] | Sequence[HourlyForecastPeriod], hours_ahead: int
) -> HourlyForecastPeriod | None:
return trends.period_for_hours_ahead(periods, hours_ahead)
79 changes: 78 additions & 1 deletion tests/test_coverage_fix_pr449.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""
Surgical coverage tests for PR #449 changed lines.
Surgical coverage tests for PR #449 and PR #456 changed lines.

Targets uncovered lines identified by diff-cover:
- src/accessiweather/models/alerts.py line 37 (references=None branch)
- src/accessiweather/weather_client_nws.py lines 865-868 (client=None branch)
- src/accessiweather/weather_client_nws.py lines 1336-1338 (parse_nws_alerts references)
- src/accessiweather/weather_client_base.py lines 742-743 (_fetch_nws_cancel_references in auto path)
- src/accessiweather/weather_client_base.py lines 912-924 (_launch_enrichment_tasks auto-mode tasks)
"""

from __future__ import annotations
Expand Down Expand Up @@ -104,3 +105,79 @@ def test_parse_nws_alerts_extracts_references():
assert "ref-A" in alerts_obj.alerts[0].references
assert "ref-B" in alerts_obj.alerts[0].references
assert "ref-C" in alerts_obj.alerts[0].references


# ---------------------------------------------------------------------------
# 4. weather_client_base.py lines 912-924: _launch_enrichment_tasks auto-mode
# These lines are inside `if self.data_source == "auto":` and create tasks
# for sunrise_sunset, nws_discussion, vc_alerts, and vc_moon_data.
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_launch_enrichment_tasks_auto_mode_creates_smart_tasks():
"""_launch_enrichment_tasks with data_source='auto' creates all four smart enrichment tasks."""
import asyncio

import accessiweather.weather_client_enrichment as enrichment
from accessiweather.models import Location, WeatherData
from accessiweather.weather_client import WeatherClient

client = WeatherClient(data_source="auto")
location = Location(name="NYC", latitude=40.7128, longitude=-74.0060)
weather_data = WeatherData(location=location)

async def _noop(*args, **kwargs):
pass

with (
patch.object(enrichment, "enrich_with_sunrise_sunset", side_effect=_noop),
patch.object(enrichment, "enrich_with_nws_discussion", side_effect=_noop),
patch.object(enrichment, "enrich_with_visual_crossing_alerts", side_effect=_noop),
patch.object(enrichment, "enrich_with_visual_crossing_moon_data", side_effect=_noop),
patch.object(enrichment, "populate_environmental_metrics", side_effect=_noop),
patch.object(enrichment, "enrich_with_aviation_data", side_effect=_noop),
):
tasks = client._launch_enrichment_tasks(weather_data, location)
# Cancel tasks to prevent ResourceWarning about pending coroutines
for t in tasks.values():
t.cancel()
await asyncio.gather(*tasks.values(), return_exceptions=True)

assert "sunrise_sunset" in tasks
assert "nws_discussion" in tasks
assert "vc_alerts" in tasks
assert "vc_moon_data" in tasks
assert "environmental" in tasks
assert "aviation" in tasks


@pytest.mark.asyncio
async def test_launch_enrichment_tasks_non_auto_skips_smart_tasks():
"""_launch_enrichment_tasks with data_source != 'auto' does not create smart enrichment tasks."""
import asyncio

import accessiweather.weather_client_enrichment as enrichment
from accessiweather.models import Location, WeatherData
from accessiweather.weather_client import WeatherClient

client = WeatherClient(data_source="nws")
location = Location(name="NYC", latitude=40.7128, longitude=-74.0060)
weather_data = WeatherData(location=location)

async def _noop(*args, **kwargs):
pass

with (
patch.object(enrichment, "populate_environmental_metrics", side_effect=_noop),
patch.object(enrichment, "enrich_with_aviation_data", side_effect=_noop),
):
tasks = client._launch_enrichment_tasks(weather_data, location)
for t in tasks.values():
t.cancel()
await asyncio.gather(*tasks.values(), return_exceptions=True)

assert "sunrise_sunset" not in tasks
assert "nws_discussion" not in tasks
assert "vc_alerts" not in tasks
assert "vc_moon_data" not in tasks
assert "environmental" in tasks
assert "aviation" in tasks
Loading