From b8bc612cf8cad09609dbb01d603f9ce098a733ae Mon Sep 17 00:00:00 2001 From: Orinks Date: Thu, 5 Feb 2026 17:09:26 +0000 Subject: [PATCH 1/2] test(weather-condition-analyzer): add comprehensive tests for WeatherConditionAnalyzer Add 47 tests covering: - Weather code to category/severity mapping for all Open-Meteo codes - Temperature extreme detection (cold to hot thresholds) - Wind condition analysis (calm to extreme) - Weather alert handling and severity mapping - Priority score calculation - Template recommendation logic - Error handling for invalid data Coverage improved from 0% to 98%. Closes #254 --- tests/test_weather_condition_analyzer.py | 487 +++++++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100644 tests/test_weather_condition_analyzer.py diff --git a/tests/test_weather_condition_analyzer.py b/tests/test_weather_condition_analyzer.py new file mode 100644 index 00000000..88059948 --- /dev/null +++ b/tests/test_weather_condition_analyzer.py @@ -0,0 +1,487 @@ +"""Tests for WeatherConditionAnalyzer.""" + +from accessiweather.weather_condition_analyzer import ( + ConditionCategory, + WeatherConditionAnalyzer, + WeatherSeverity, +) + + +class TestWeatherCodeAnalysis: + """Test weather code to category/severity mapping.""" + + def test_clear_weather_codes(self): + """Clear sky codes map to CLEAR category with NORMAL severity.""" + analyzer = WeatherConditionAnalyzer() + + for code in [0, 1]: + result = analyzer.analyze_weather_conditions({"weather_code": code}) + assert result["category"] == ConditionCategory.CLEAR + assert result["severity"] == WeatherSeverity.NORMAL + + def test_cloudy_weather_codes(self): + """Cloudy codes map to CLOUDY category.""" + analyzer = WeatherConditionAnalyzer() + + for code in [2, 3]: + result = analyzer.analyze_weather_conditions({"weather_code": code}) + assert result["category"] == ConditionCategory.CLOUDY + assert result["severity"] == WeatherSeverity.NORMAL + + def test_fog_weather_codes(self): + """Fog codes map to FOG category with appropriate severity.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 45}) + assert result["category"] == ConditionCategory.FOG + assert result["severity"] == WeatherSeverity.MINOR + + result = analyzer.analyze_weather_conditions({"weather_code": 48}) + assert result["category"] == ConditionCategory.FOG + assert result["severity"] == WeatherSeverity.MODERATE + + def test_drizzle_weather_codes(self): + """Drizzle codes map to PRECIPITATION category.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 51}) + assert result["category"] == ConditionCategory.PRECIPITATION + assert result["severity"] == WeatherSeverity.MINOR + + result = analyzer.analyze_weather_conditions({"weather_code": 55}) + assert result["category"] == ConditionCategory.PRECIPITATION + assert result["severity"] == WeatherSeverity.MODERATE + + def test_freezing_drizzle_codes(self): + """Freezing drizzle codes map to FREEZING category.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 56}) + assert result["category"] == ConditionCategory.FREEZING + assert result["severity"] == WeatherSeverity.MODERATE + + result = analyzer.analyze_weather_conditions({"weather_code": 57}) + assert result["category"] == ConditionCategory.FREEZING + assert result["severity"] == WeatherSeverity.SEVERE + + def test_rain_weather_codes(self): + """Rain codes map to PRECIPITATION with increasing severity.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 61}) + assert result["category"] == ConditionCategory.PRECIPITATION + assert result["severity"] == WeatherSeverity.MINOR + + result = analyzer.analyze_weather_conditions({"weather_code": 63}) + assert result["severity"] == WeatherSeverity.MODERATE + + result = analyzer.analyze_weather_conditions({"weather_code": 65}) + assert result["severity"] == WeatherSeverity.SEVERE + + def test_freezing_rain_codes(self): + """Freezing rain codes map to FREEZING with high severity.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 66}) + assert result["category"] == ConditionCategory.FREEZING + assert result["severity"] == WeatherSeverity.SEVERE + + result = analyzer.analyze_weather_conditions({"weather_code": 67}) + assert result["severity"] == WeatherSeverity.EXTREME + + def test_snow_weather_codes(self): + """Snow codes map to PRECIPITATION with varying severity.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 71}) + assert result["category"] == ConditionCategory.PRECIPITATION + assert result["severity"] == WeatherSeverity.MODERATE + + result = analyzer.analyze_weather_conditions({"weather_code": 75}) + assert result["severity"] == WeatherSeverity.EXTREME + + # Snow grains + result = analyzer.analyze_weather_conditions({"weather_code": 77}) + assert result["severity"] == WeatherSeverity.MINOR + + def test_thunderstorm_codes(self): + """Thunderstorm codes map to THUNDERSTORM with high severity.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 95}) + assert result["category"] == ConditionCategory.THUNDERSTORM + assert result["severity"] == WeatherSeverity.SEVERE + + for code in [96, 99]: + result = analyzer.analyze_weather_conditions({"weather_code": code}) + assert result["category"] == ConditionCategory.THUNDERSTORM + assert result["severity"] == WeatherSeverity.EXTREME + + def test_unknown_weather_code_defaults_to_clear(self): + """Unknown weather codes default to CLEAR with NORMAL severity.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 999}) + assert result["category"] == ConditionCategory.CLEAR + assert result["severity"] == WeatherSeverity.NORMAL + + def test_weather_code_as_list(self): + """Weather code provided as list uses first element.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": [95, 0]}) + assert result["category"] == ConditionCategory.THUNDERSTORM + assert result["severity"] == WeatherSeverity.SEVERE + + def test_weather_code_as_tuple(self): + """Weather code provided as tuple uses first element.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": (45, 0)}) + assert result["category"] == ConditionCategory.FOG + + +class TestTemperatureAnalysis: + """Test temperature extreme detection.""" + + def test_extreme_cold(self): + """Temperatures at or below 0F are extreme cold.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": -10}) + assert result["temperature_extreme"] == "extreme_cold" + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 0}) + assert result["temperature_extreme"] == "extreme_cold" + + def test_very_cold(self): + """Temperatures between 1-20F are very cold.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 10}) + assert result["temperature_extreme"] == "very_cold" + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 20}) + assert result["temperature_extreme"] == "very_cold" + + def test_cold(self): + """Temperatures between 21-32F are cold.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 25}) + assert result["temperature_extreme"] == "cold" + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 32}) + assert result["temperature_extreme"] == "cold" + + def test_hot(self): + """Temperatures between 90-99F are hot.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 92}) + assert result["temperature_extreme"] == "hot" + + def test_very_hot(self): + """Temperatures between 100-109F are very hot.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 105}) + assert result["temperature_extreme"] == "very_hot" + + def test_extreme_hot(self): + """Temperatures at or above 110F are extreme hot.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 115}) + assert result["temperature_extreme"] == "extreme_hot" + + def test_normal_temperature(self): + """Normal temperatures (33-89F) have no extreme.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 70}) + assert result["temperature_extreme"] is None + + def test_temp_f_alternative_key(self): + """Temperature can be provided as temp_f.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp_f": -5}) + assert result["temperature_extreme"] == "extreme_cold" + + def test_no_temperature_provided(self): + """Missing temperature results in None extreme.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0}) + assert result["temperature_extreme"] is None + + +class TestWindAnalysis: + """Test wind condition analysis.""" + + def test_calm_wind(self): + """Wind speeds below 15 mph are calm.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 10}) + assert result["wind_condition"] == "calm" + + def test_light_wind(self): + """Wind speeds 15-24 mph are light.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 20}) + assert result["wind_condition"] == "light" + + def test_moderate_wind(self): + """Wind speeds 25-34 mph are moderate.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 30}) + assert result["wind_condition"] == "moderate" + + def test_strong_wind(self): + """Wind speeds 35-44 mph are strong.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 40}) + assert result["wind_condition"] == "strong" + + def test_very_strong_wind(self): + """Wind speeds 45-59 mph are very strong.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 50}) + assert result["wind_condition"] == "very_strong" + + def test_extreme_wind(self): + """Wind speeds 60+ are extreme.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 65}) + assert result["wind_condition"] == "extreme" + + def test_no_wind_provided(self): + """Missing wind speed results in None condition.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0}) + assert result["wind_condition"] is None + + +class TestAlertAnalysis: + """Test weather alert handling.""" + + def test_no_alerts(self): + """No alerts returns appropriate analysis.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=None) + assert result["has_alerts"] is False + + result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=[]) + assert result["has_alerts"] is False + + def test_alert_takes_priority(self): + """Alerts have highest priority and return immediately.""" + analyzer = WeatherConditionAnalyzer() + + alerts = [{"severity": "Moderate", "headline": "Wind Advisory"}] + result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) + + assert result["has_alerts"] is True + assert result["priority_score"] == 1000 + assert result["recommended_template"] == "alert" + + def test_highest_severity_alert_selected(self): + """When multiple alerts exist, highest severity is selected.""" + analyzer = WeatherConditionAnalyzer() + + alerts = [ + {"severity": "Minor", "headline": "Wind Advisory"}, + {"severity": "Severe", "headline": "Tornado Warning"}, + {"severity": "Moderate", "headline": "Flood Watch"}, + ] + result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) + + assert result["alert_severity"] == WeatherSeverity.SEVERE + assert result["primary_alert"]["headline"] == "Tornado Warning" + + def test_extreme_alert_severity(self): + """Extreme severity alerts are properly mapped.""" + analyzer = WeatherConditionAnalyzer() + + alerts = [{"severity": "Extreme", "headline": "Tsunami Warning"}] + result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) + + assert result["alert_severity"] == WeatherSeverity.EXTREME + + def test_unknown_alert_severity_defaults_to_normal(self): + """Unknown severity strings default to NORMAL.""" + analyzer = WeatherConditionAnalyzer() + + alerts = [{"severity": "Unknown", "headline": "Test Alert"}] + result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) + + assert result["alert_severity"] == WeatherSeverity.NORMAL + + +class TestPriorityScoreCalculation: + """Test priority score calculation.""" + + def test_base_score_from_severity(self): + """Priority score includes base from severity level.""" + analyzer = WeatherConditionAnalyzer() + + # NORMAL severity (0) = base 0 + result = analyzer.analyze_weather_conditions({"weather_code": 0}) + assert result["priority_score"] == 0 + + # SEVERE severity (3) = base 30 + result = analyzer.analyze_weather_conditions({"weather_code": 95}) + assert result["priority_score"] >= 30 + + def test_temperature_extreme_bonus(self): + """Temperature extremes add to priority score.""" + analyzer = WeatherConditionAnalyzer() + + # Extreme temperature adds 50 + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": -10}) + assert result["priority_score"] >= 50 + + # Very cold adds 30 + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 15}) + assert result["priority_score"] >= 30 + + # Cold adds 15 + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 30}) + assert result["priority_score"] >= 15 + + def test_wind_condition_bonus(self): + """Wind conditions add to priority score.""" + analyzer = WeatherConditionAnalyzer() + + # Extreme wind adds 40 + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 70}) + assert result["priority_score"] >= 40 + + # Very strong wind adds 25 + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 50}) + assert result["priority_score"] >= 25 + + # Strong wind adds 15 + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 40}) + assert result["priority_score"] >= 15 + + +class TestTemplateRecommendation: + """Test recommended template selection.""" + + def test_alert_template_for_alerts(self): + """Alert template is used when alerts are present.""" + analyzer = WeatherConditionAnalyzer() + + alerts = [{"severity": "Minor", "headline": "Test"}] + result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) + assert result["recommended_template"] == "alert" + + def test_severe_weather_template(self): + """Severe weather template for SEVERE/EXTREME conditions.""" + analyzer = WeatherConditionAnalyzer() + + # Thunderstorm with hail (code 99) = EXTREME + result = analyzer.analyze_weather_conditions({"weather_code": 99}) + assert result["recommended_template"] == "severe_weather" + + # Heavy rain (code 65) = SEVERE + result = analyzer.analyze_weather_conditions({"weather_code": 65}) + assert result["recommended_template"] == "severe_weather" + + def test_temperature_extreme_template(self): + """Temperature extreme template for extreme/very temperatures.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": -10}) + assert result["recommended_template"] == "temperature_extreme" + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 105}) + assert result["recommended_template"] == "temperature_extreme" + + def test_wind_warning_template(self): + """Wind warning template for strong+ winds.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 35}) + assert result["recommended_template"] == "wind_warning" + + def test_precipitation_template(self): + """Precipitation template for rain/snow conditions.""" + analyzer = WeatherConditionAnalyzer() + + # Light rain (not severe) + result = analyzer.analyze_weather_conditions({"weather_code": 61}) + assert result["recommended_template"] == "precipitation" + + # Freezing drizzle (moderate) + result = analyzer.analyze_weather_conditions({"weather_code": 56}) + assert result["recommended_template"] == "precipitation" + + def test_fog_template(self): + """Fog template for fog conditions.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 45}) + assert result["recommended_template"] == "fog" + + def test_default_template_for_clear(self): + """Default template for clear/cloudy conditions.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({"weather_code": 0}) + assert result["recommended_template"] == "default" + + result = analyzer.analyze_weather_conditions({"weather_code": 3}) + assert result["recommended_template"] == "default" + + +class TestErrorHandling: + """Test error handling in analysis.""" + + def test_empty_weather_data(self): + """Empty weather data is handled gracefully.""" + analyzer = WeatherConditionAnalyzer() + + result = analyzer.analyze_weather_conditions({}) + assert result["category"] == ConditionCategory.CLEAR + assert result["severity"] == WeatherSeverity.NORMAL + assert result["recommended_template"] == "default" + + def test_invalid_weather_data_returns_error(self): + """Invalid data that causes exceptions returns error analysis.""" + analyzer = WeatherConditionAnalyzer() + + # This should trigger error handling (weather_code attribute access fails) + class BadData: + def get(self, key, default=None): + raise RuntimeError("Test error") + + result = analyzer.analyze_weather_conditions(BadData()) + assert "error" in result + assert result["recommended_template"] == "default" + + +class TestEnumValues: + """Test enum definitions.""" + + def test_weather_severity_ordering(self): + """WeatherSeverity enum values are correctly ordered.""" + assert WeatherSeverity.NORMAL.value < WeatherSeverity.MINOR.value + assert WeatherSeverity.MINOR.value < WeatherSeverity.MODERATE.value + assert WeatherSeverity.MODERATE.value < WeatherSeverity.SEVERE.value + assert WeatherSeverity.SEVERE.value < WeatherSeverity.EXTREME.value + + def test_condition_category_values(self): + """ConditionCategory enum has expected values.""" + assert ConditionCategory.CLEAR.value == "clear" + assert ConditionCategory.THUNDERSTORM.value == "thunderstorm" From 40cfc38e550ae3336b74468e0ac50127251f5e37 Mon Sep 17 00:00:00 2001 From: Orinks <38449772+Orinks@users.noreply.github.com> Date: Thu, 5 Feb 2026 18:05:29 -0500 Subject: [PATCH 2/2] test: add comprehensive tests for WeatherConditionAnalyzer (#256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds comprehensive test coverage for the WeatherConditionAnalyzer module with 55 tests covering: - WEATHER_CODE_MAPPING for all weather condition codes - Temperature threshold analysis (extreme cold → extreme hot) - Wind speed threshold analysis (calm → extreme) - Alert severity analysis and mapping - Priority score calculation - Format template determination Achieves 100% coverage on `weather_condition_analyzer.py` (was 0%). Closes #254 --- tests/test_weather_condition_analyzer.py | 704 +++++++++++++---------- 1 file changed, 414 insertions(+), 290 deletions(-) diff --git a/tests/test_weather_condition_analyzer.py b/tests/test_weather_condition_analyzer.py index 88059948..e050f0dc 100644 --- a/tests/test_weather_condition_analyzer.py +++ b/tests/test_weather_condition_analyzer.py @@ -1,4 +1,16 @@ -"""Tests for WeatherConditionAnalyzer.""" +""" +Tests for weather_condition_analyzer module. + +Covers: +- WEATHER_CODE_MAPPING for different weather codes +- Temperature threshold analysis +- Wind speed threshold analysis +- Format string template generation +- Severity and category classification +- Alert analysis +- Priority score calculation +""" + from accessiweather.weather_condition_analyzer import ( ConditionCategory, @@ -7,481 +19,593 @@ ) -class TestWeatherCodeAnalysis: - """Test weather code to category/severity mapping.""" +class TestWeatherCodeMapping: + """Test WEATHER_CODE_MAPPING coverage.""" - def test_clear_weather_codes(self): - """Clear sky codes map to CLEAR category with NORMAL severity.""" + def test_clear_conditions(self): + """Clear/sunny codes map to CLEAR category with NORMAL severity.""" analyzer = WeatherConditionAnalyzer() for code in [0, 1]: - result = analyzer.analyze_weather_conditions({"weather_code": code}) - assert result["category"] == ConditionCategory.CLEAR - assert result["severity"] == WeatherSeverity.NORMAL + category, severity = analyzer.WEATHER_CODE_MAPPING[code] + assert category == ConditionCategory.CLEAR + assert severity == WeatherSeverity.NORMAL - def test_cloudy_weather_codes(self): - """Cloudy codes map to CLOUDY category.""" + def test_cloudy_conditions(self): + """Cloudy codes map to CLOUDY category with NORMAL severity.""" analyzer = WeatherConditionAnalyzer() for code in [2, 3]: - result = analyzer.analyze_weather_conditions({"weather_code": code}) - assert result["category"] == ConditionCategory.CLOUDY - assert result["severity"] == WeatherSeverity.NORMAL + category, severity = analyzer.WEATHER_CODE_MAPPING[code] + assert category == ConditionCategory.CLOUDY + assert severity == WeatherSeverity.NORMAL - def test_fog_weather_codes(self): - """Fog codes map to FOG category with appropriate severity.""" + def test_fog_conditions(self): + """Fog codes map to FOG category with varying severity.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": 45}) - assert result["category"] == ConditionCategory.FOG - assert result["severity"] == WeatherSeverity.MINOR + cat, sev = analyzer.WEATHER_CODE_MAPPING[45] + assert cat == ConditionCategory.FOG + assert sev == WeatherSeverity.MINOR - result = analyzer.analyze_weather_conditions({"weather_code": 48}) - assert result["category"] == ConditionCategory.FOG - assert result["severity"] == WeatherSeverity.MODERATE + cat, sev = analyzer.WEATHER_CODE_MAPPING[48] + assert cat == ConditionCategory.FOG + assert sev == WeatherSeverity.MODERATE - def test_drizzle_weather_codes(self): + def test_drizzle_conditions(self): """Drizzle codes map to PRECIPITATION category.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": 51}) - assert result["category"] == ConditionCategory.PRECIPITATION - assert result["severity"] == WeatherSeverity.MINOR + for code in [51, 53]: + cat, sev = analyzer.WEATHER_CODE_MAPPING[code] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.MINOR - result = analyzer.analyze_weather_conditions({"weather_code": 55}) - assert result["category"] == ConditionCategory.PRECIPITATION - assert result["severity"] == WeatherSeverity.MODERATE + cat, sev = analyzer.WEATHER_CODE_MAPPING[55] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.MODERATE - def test_freezing_drizzle_codes(self): + def test_freezing_drizzle_conditions(self): """Freezing drizzle codes map to FREEZING category.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": 56}) - assert result["category"] == ConditionCategory.FREEZING - assert result["severity"] == WeatherSeverity.MODERATE + cat, sev = analyzer.WEATHER_CODE_MAPPING[56] + assert cat == ConditionCategory.FREEZING + assert sev == WeatherSeverity.MODERATE - result = analyzer.analyze_weather_conditions({"weather_code": 57}) - assert result["category"] == ConditionCategory.FREEZING - assert result["severity"] == WeatherSeverity.SEVERE + cat, sev = analyzer.WEATHER_CODE_MAPPING[57] + assert cat == ConditionCategory.FREEZING + assert sev == WeatherSeverity.SEVERE - def test_rain_weather_codes(self): - """Rain codes map to PRECIPITATION with increasing severity.""" + def test_rain_conditions(self): + """Rain codes map to PRECIPITATION with varying severity.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": 61}) - assert result["category"] == ConditionCategory.PRECIPITATION - assert result["severity"] == WeatherSeverity.MINOR + cat, sev = analyzer.WEATHER_CODE_MAPPING[61] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.MINOR - result = analyzer.analyze_weather_conditions({"weather_code": 63}) - assert result["severity"] == WeatherSeverity.MODERATE + cat, sev = analyzer.WEATHER_CODE_MAPPING[63] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.MODERATE - result = analyzer.analyze_weather_conditions({"weather_code": 65}) - assert result["severity"] == WeatherSeverity.SEVERE + cat, sev = analyzer.WEATHER_CODE_MAPPING[65] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.SEVERE - def test_freezing_rain_codes(self): + def test_freezing_rain_conditions(self): """Freezing rain codes map to FREEZING with high severity.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": 66}) - assert result["category"] == ConditionCategory.FREEZING - assert result["severity"] == WeatherSeverity.SEVERE + cat, sev = analyzer.WEATHER_CODE_MAPPING[66] + assert cat == ConditionCategory.FREEZING + assert sev == WeatherSeverity.SEVERE - result = analyzer.analyze_weather_conditions({"weather_code": 67}) - assert result["severity"] == WeatherSeverity.EXTREME + cat, sev = analyzer.WEATHER_CODE_MAPPING[67] + assert cat == ConditionCategory.FREEZING + assert sev == WeatherSeverity.EXTREME - def test_snow_weather_codes(self): + def test_snow_conditions(self): """Snow codes map to PRECIPITATION with varying severity.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": 71}) - assert result["category"] == ConditionCategory.PRECIPITATION - assert result["severity"] == WeatherSeverity.MODERATE + cat, sev = analyzer.WEATHER_CODE_MAPPING[71] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.MODERATE - result = analyzer.analyze_weather_conditions({"weather_code": 75}) - assert result["severity"] == WeatherSeverity.EXTREME + cat, sev = analyzer.WEATHER_CODE_MAPPING[73] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.SEVERE - # Snow grains - result = analyzer.analyze_weather_conditions({"weather_code": 77}) - assert result["severity"] == WeatherSeverity.MINOR + cat, sev = analyzer.WEATHER_CODE_MAPPING[75] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.EXTREME - def test_thunderstorm_codes(self): - """Thunderstorm codes map to THUNDERSTORM with high severity.""" - analyzer = WeatherConditionAnalyzer() + cat, sev = analyzer.WEATHER_CODE_MAPPING[77] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.MINOR - result = analyzer.analyze_weather_conditions({"weather_code": 95}) - assert result["category"] == ConditionCategory.THUNDERSTORM - assert result["severity"] == WeatherSeverity.SEVERE - - for code in [96, 99]: - result = analyzer.analyze_weather_conditions({"weather_code": code}) - assert result["category"] == ConditionCategory.THUNDERSTORM - assert result["severity"] == WeatherSeverity.EXTREME - - def test_unknown_weather_code_defaults_to_clear(self): - """Unknown weather codes default to CLEAR with NORMAL severity.""" + def test_rain_showers_conditions(self): + """Rain shower codes map to PRECIPITATION.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": 999}) - assert result["category"] == ConditionCategory.CLEAR - assert result["severity"] == WeatherSeverity.NORMAL + for code, expected_sev in [(80, WeatherSeverity.MINOR), + (81, WeatherSeverity.MODERATE), + (82, WeatherSeverity.SEVERE)]: + cat, sev = analyzer.WEATHER_CODE_MAPPING[code] + assert cat == ConditionCategory.PRECIPITATION + assert sev == expected_sev - def test_weather_code_as_list(self): - """Weather code provided as list uses first element.""" + def test_snow_showers_conditions(self): + """Snow shower codes map to PRECIPITATION.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": [95, 0]}) - assert result["category"] == ConditionCategory.THUNDERSTORM - assert result["severity"] == WeatherSeverity.SEVERE + cat, sev = analyzer.WEATHER_CODE_MAPPING[85] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.MODERATE - def test_weather_code_as_tuple(self): - """Weather code provided as tuple uses first element.""" + cat, sev = analyzer.WEATHER_CODE_MAPPING[86] + assert cat == ConditionCategory.PRECIPITATION + assert sev == WeatherSeverity.SEVERE + + def test_thunderstorm_conditions(self): + """Thunderstorm codes map to THUNDERSTORM with high severity.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": (45, 0)}) - assert result["category"] == ConditionCategory.FOG + cat, sev = analyzer.WEATHER_CODE_MAPPING[95] + assert cat == ConditionCategory.THUNDERSTORM + assert sev == WeatherSeverity.SEVERE + + for code in [96, 99]: + cat, sev = analyzer.WEATHER_CODE_MAPPING[code] + assert cat == ConditionCategory.THUNDERSTORM + assert sev == WeatherSeverity.EXTREME class TestTemperatureAnalysis: - """Test temperature extreme detection.""" + """Test temperature threshold analysis.""" def test_extreme_cold(self): - """Temperatures at or below 0F are extreme cold.""" + """Temps at or below 0F are extreme cold.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": -10}) + result = analyzer._analyze_temperature({"temp": 0}) assert result["temperature_extreme"] == "extreme_cold" - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 0}) + result = analyzer._analyze_temperature({"temp": -10}) assert result["temperature_extreme"] == "extreme_cold" def test_very_cold(self): - """Temperatures between 1-20F are very cold.""" + """Temps between 1-20F are very cold.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 10}) + result = analyzer._analyze_temperature({"temp": 15}) assert result["temperature_extreme"] == "very_cold" - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 20}) + result = analyzer._analyze_temperature({"temp": 20}) assert result["temperature_extreme"] == "very_cold" def test_cold(self): - """Temperatures between 21-32F are cold.""" + """Temps between 21-32F are cold.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 25}) + result = analyzer._analyze_temperature({"temp": 25}) assert result["temperature_extreme"] == "cold" - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 32}) + result = analyzer._analyze_temperature({"temp": 32}) assert result["temperature_extreme"] == "cold" + def test_normal_temperature(self): + """Temps between 33-89F have no extreme.""" + analyzer = WeatherConditionAnalyzer() + result = analyzer._analyze_temperature({"temp": 70}) + assert result["temperature_extreme"] is None + def test_hot(self): - """Temperatures between 90-99F are hot.""" + """Temps between 90-99F are hot.""" analyzer = WeatherConditionAnalyzer() + result = analyzer._analyze_temperature({"temp": 90}) + assert result["temperature_extreme"] == "hot" - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 92}) + result = analyzer._analyze_temperature({"temp": 95}) assert result["temperature_extreme"] == "hot" def test_very_hot(self): - """Temperatures between 100-109F are very hot.""" + """Temps between 100-109F are very hot.""" analyzer = WeatherConditionAnalyzer() + result = analyzer._analyze_temperature({"temp": 100}) + assert result["temperature_extreme"] == "very_hot" - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 105}) + result = analyzer._analyze_temperature({"temp": 105}) assert result["temperature_extreme"] == "very_hot" def test_extreme_hot(self): - """Temperatures at or above 110F are extreme hot.""" + """Temps at or above 110F are extreme hot.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 115}) + result = analyzer._analyze_temperature({"temp": 110}) assert result["temperature_extreme"] == "extreme_hot" - def test_normal_temperature(self): - """Normal temperatures (33-89F) have no extreme.""" - analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 70}) - assert result["temperature_extreme"] is None + result = analyzer._analyze_temperature({"temp": 120}) + assert result["temperature_extreme"] == "extreme_hot" - def test_temp_f_alternative_key(self): - """Temperature can be provided as temp_f.""" + def test_temp_f_fallback_key(self): + """Should use temp_f if temp is not present.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp_f": -5}) + result = analyzer._analyze_temperature({"temp_f": -5}) assert result["temperature_extreme"] == "extreme_cold" - def test_no_temperature_provided(self): - """Missing temperature results in None extreme.""" + def test_missing_temperature(self): + """Should return None for missing temperature.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0}) + result = analyzer._analyze_temperature({}) assert result["temperature_extreme"] is None class TestWindAnalysis: - """Test wind condition analysis.""" + """Test wind speed threshold analysis.""" def test_calm_wind(self): - """Wind speeds below 15 mph are calm.""" + """Wind under 5 mph is calm.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 10}) + result = analyzer._analyze_wind({"wind_speed": 3}) assert result["wind_condition"] == "calm" def test_light_wind(self): - """Wind speeds 15-24 mph are light.""" + """Wind 15-24 mph is light.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 20}) + result = analyzer._analyze_wind({"wind_speed": 15}) assert result["wind_condition"] == "light" def test_moderate_wind(self): - """Wind speeds 25-34 mph are moderate.""" + """Wind 25-34 mph is moderate.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 30}) + result = analyzer._analyze_wind({"wind_speed": 25}) assert result["wind_condition"] == "moderate" def test_strong_wind(self): - """Wind speeds 35-44 mph are strong.""" + """Wind 35-44 mph is strong.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 40}) + result = analyzer._analyze_wind({"wind_speed": 35}) assert result["wind_condition"] == "strong" def test_very_strong_wind(self): - """Wind speeds 45-59 mph are very strong.""" + """Wind 45-59 mph is very strong.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 50}) + result = analyzer._analyze_wind({"wind_speed": 50}) assert result["wind_condition"] == "very_strong" def test_extreme_wind(self): - """Wind speeds 60+ are extreme.""" + """Wind 60+ mph is extreme.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 65}) + result = analyzer._analyze_wind({"wind_speed": 70}) assert result["wind_condition"] == "extreme" - def test_no_wind_provided(self): - """Missing wind speed results in None condition.""" + def test_missing_wind_speed(self): + """Should return None for missing wind speed.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0}) + result = analyzer._analyze_wind({}) assert result["wind_condition"] is None class TestAlertAnalysis: - """Test weather alert handling.""" + """Test alert severity analysis.""" def test_no_alerts(self): - """No alerts returns appropriate analysis.""" + """Should return has_alerts False for empty alerts.""" analyzer = WeatherConditionAnalyzer() - - result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=None) - assert result["has_alerts"] is False - - result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=[]) + result = analyzer._analyze_alerts([]) assert result["has_alerts"] is False + assert result["alert_severity"] is None - def test_alert_takes_priority(self): - """Alerts have highest priority and return immediately.""" + def test_single_alert(self): + """Should analyze single alert correctly.""" analyzer = WeatherConditionAnalyzer() - - alerts = [{"severity": "Moderate", "headline": "Wind Advisory"}] - result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) + alerts = [{"severity": "Severe", "event": "Tornado Warning"}] + result = analyzer._analyze_alerts(alerts) assert result["has_alerts"] is True - assert result["priority_score"] == 1000 - assert result["recommended_template"] == "alert" + assert result["alert_severity"] == WeatherSeverity.SEVERE + assert result["primary_alert"] == alerts[0] - def test_highest_severity_alert_selected(self): - """When multiple alerts exist, highest severity is selected.""" + def test_multiple_alerts_highest_severity(self): + """Should pick highest severity from multiple alerts.""" analyzer = WeatherConditionAnalyzer() - alerts = [ - {"severity": "Minor", "headline": "Wind Advisory"}, - {"severity": "Severe", "headline": "Tornado Warning"}, - {"severity": "Moderate", "headline": "Flood Watch"}, + {"severity": "Minor", "event": "Wind Advisory"}, + {"severity": "Extreme", "event": "Tornado Emergency"}, + {"severity": "Moderate", "event": "Flood Watch"}, ] - result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) - - assert result["alert_severity"] == WeatherSeverity.SEVERE - assert result["primary_alert"]["headline"] == "Tornado Warning" - - def test_extreme_alert_severity(self): - """Extreme severity alerts are properly mapped.""" - analyzer = WeatherConditionAnalyzer() - - alerts = [{"severity": "Extreme", "headline": "Tsunami Warning"}] - result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) + result = analyzer._analyze_alerts(alerts) assert result["alert_severity"] == WeatherSeverity.EXTREME + assert result["primary_alert"]["event"] == "Tornado Emergency" - def test_unknown_alert_severity_defaults_to_normal(self): - """Unknown severity strings default to NORMAL.""" + def test_unknown_alert_severity(self): + """Unknown severity should map to NORMAL.""" analyzer = WeatherConditionAnalyzer() + result = analyzer._map_alert_severity("Unknown") + assert result == WeatherSeverity.NORMAL - alerts = [{"severity": "Unknown", "headline": "Test Alert"}] - result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) + def test_all_alert_severity_mappings(self): + """Test all alert severity string mappings.""" + analyzer = WeatherConditionAnalyzer() - assert result["alert_severity"] == WeatherSeverity.NORMAL + assert analyzer._map_alert_severity("Extreme") == WeatherSeverity.EXTREME + assert analyzer._map_alert_severity("Severe") == WeatherSeverity.SEVERE + assert analyzer._map_alert_severity("Moderate") == WeatherSeverity.MODERATE + assert analyzer._map_alert_severity("Minor") == WeatherSeverity.MINOR class TestPriorityScoreCalculation: """Test priority score calculation.""" def test_base_score_from_severity(self): - """Priority score includes base from severity level.""" - analyzer = WeatherConditionAnalyzer() - - # NORMAL severity (0) = base 0 - result = analyzer.analyze_weather_conditions({"weather_code": 0}) - assert result["priority_score"] == 0 - - # SEVERE severity (3) = base 30 - result = analyzer.analyze_weather_conditions({"weather_code": 95}) - assert result["priority_score"] >= 30 - - def test_temperature_extreme_bonus(self): - """Temperature extremes add to priority score.""" - analyzer = WeatherConditionAnalyzer() - - # Extreme temperature adds 50 - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": -10}) - assert result["priority_score"] >= 50 - - # Very cold adds 30 - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 15}) - assert result["priority_score"] >= 30 - - # Cold adds 15 - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 30}) - assert result["priority_score"] >= 15 - - def test_wind_condition_bonus(self): - """Wind conditions add to priority score.""" - analyzer = WeatherConditionAnalyzer() - - # Extreme wind adds 40 - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 70}) - assert result["priority_score"] >= 40 - - # Very strong wind adds 25 - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 50}) - assert result["priority_score"] >= 25 - - # Strong wind adds 15 - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 40}) - assert result["priority_score"] >= 15 - - -class TestTemplateRecommendation: - """Test recommended template selection.""" - - def test_alert_template_for_alerts(self): - """Alert template is used when alerts are present.""" - analyzer = WeatherConditionAnalyzer() - - alerts = [{"severity": "Minor", "headline": "Test"}] - result = analyzer.analyze_weather_conditions({"weather_code": 0}, alerts_data=alerts) - assert result["recommended_template"] == "alert" + """Severity value * 10 contributes to score.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "severity": WeatherSeverity.SEVERE, # value = 3 + "temperature_extreme": None, + "wind_condition": None, + } + score = analyzer._calculate_priority_score(analysis) + assert score == 30 # 3 * 10 + + def test_extreme_temperature_bonus(self): + """Extreme temperatures add 50 points.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": "extreme_cold", + "wind_condition": None, + } + score = analyzer._calculate_priority_score(analysis) + assert score == 50 + + def test_very_temperature_bonus(self): + """Very cold/hot temperatures add 30 points.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": "very_hot", + "wind_condition": None, + } + score = analyzer._calculate_priority_score(analysis) + assert score == 30 + + def test_cold_hot_bonus(self): + """Cold/hot temperatures add 15 points.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": "cold", + "wind_condition": None, + } + score = analyzer._calculate_priority_score(analysis) + assert score == 15 + + def test_extreme_wind_bonus(self): + """Extreme wind adds 40 points.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": None, + "wind_condition": "extreme", + } + score = analyzer._calculate_priority_score(analysis) + assert score == 40 + + def test_very_strong_wind_bonus(self): + """Very strong wind adds 25 points.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": None, + "wind_condition": "very_strong", + } + score = analyzer._calculate_priority_score(analysis) + assert score == 25 + + def test_strong_wind_bonus(self): + """Strong wind adds 15 points.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": None, + "wind_condition": "strong", + } + score = analyzer._calculate_priority_score(analysis) + assert score == 15 + + def test_combined_score(self): + """Multiple factors combine correctly.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "severity": WeatherSeverity.MODERATE, # 20 + "temperature_extreme": "very_cold", # 30 + "wind_condition": "strong", # 15 + } + score = analyzer._calculate_priority_score(analysis) + assert score == 65 + + +class TestTemplateGeneration: + """Test format string template determination.""" + + def test_alert_template(self): + """Alerts should return alert template.""" + analyzer = WeatherConditionAnalyzer() + analysis = {"has_alerts": True} + template = analyzer._determine_template(analysis) + assert template == "alert" def test_severe_weather_template(self): - """Severe weather template for SEVERE/EXTREME conditions.""" + """Severe/extreme severity returns severe_weather template.""" analyzer = WeatherConditionAnalyzer() - # Thunderstorm with hail (code 99) = EXTREME - result = analyzer.analyze_weather_conditions({"weather_code": 99}) - assert result["recommended_template"] == "severe_weather" - - # Heavy rain (code 65) = SEVERE - result = analyzer.analyze_weather_conditions({"weather_code": 65}) - assert result["recommended_template"] == "severe_weather" + for severity in [WeatherSeverity.SEVERE, WeatherSeverity.EXTREME]: + analysis = { + "has_alerts": False, + "severity": severity, + "temperature_extreme": None, + "wind_condition": None, + "category": ConditionCategory.CLEAR, + } + template = analyzer._determine_template(analysis) + assert template == "severe_weather" def test_temperature_extreme_template(self): - """Temperature extreme template for extreme/very temperatures.""" + """Extreme/very temps return temperature_extreme template.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": -10}) - assert result["recommended_template"] == "temperature_extreme" - - result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 105}) - assert result["recommended_template"] == "temperature_extreme" + for temp in ["extreme_cold", "extreme_hot", "very_cold", "very_hot"]: + analysis = { + "has_alerts": False, + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": temp, + "wind_condition": None, + "category": ConditionCategory.CLEAR, + } + template = analyzer._determine_template(analysis) + assert template == "temperature_extreme" def test_wind_warning_template(self): - """Wind warning template for strong+ winds.""" + """Strong winds return wind_warning template.""" analyzer = WeatherConditionAnalyzer() - result = analyzer.analyze_weather_conditions({"weather_code": 0, "wind_speed": 35}) - assert result["recommended_template"] == "wind_warning" + for wind in ["extreme", "very_strong", "strong"]: + analysis = { + "has_alerts": False, + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": None, + "wind_condition": wind, + "category": ConditionCategory.CLEAR, + } + template = analyzer._determine_template(analysis) + assert template == "wind_warning" def test_precipitation_template(self): - """Precipitation template for rain/snow conditions.""" + """Precipitation/freezing categories return precipitation template.""" analyzer = WeatherConditionAnalyzer() - # Light rain (not severe) - result = analyzer.analyze_weather_conditions({"weather_code": 61}) - assert result["recommended_template"] == "precipitation" - - # Freezing drizzle (moderate) - result = analyzer.analyze_weather_conditions({"weather_code": 56}) - assert result["recommended_template"] == "precipitation" + for category in [ConditionCategory.PRECIPITATION, ConditionCategory.FREEZING]: + analysis = { + "has_alerts": False, + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": None, + "wind_condition": None, + "category": category, + } + template = analyzer._determine_template(analysis) + assert template == "precipitation" def test_fog_template(self): - """Fog template for fog conditions.""" + """Fog category returns fog template.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "has_alerts": False, + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": None, + "wind_condition": None, + "category": ConditionCategory.FOG, + } + template = analyzer._determine_template(analysis) + assert template == "fog" + + def test_default_template(self): + """Default conditions return default template.""" + analyzer = WeatherConditionAnalyzer() + analysis = { + "has_alerts": False, + "severity": WeatherSeverity.NORMAL, + "temperature_extreme": None, + "wind_condition": None, + "category": ConditionCategory.CLEAR, + } + template = analyzer._determine_template(analysis) + assert template == "default" + + +class TestAnalyzeWeatherConditions: + """Test main analysis function.""" + + def test_basic_clear_weather(self): + """Basic clear weather analysis.""" analyzer = WeatherConditionAnalyzer() + result = analyzer.analyze_weather_conditions({"weather_code": 0, "temp": 70}) - result = analyzer.analyze_weather_conditions({"weather_code": 45}) - assert result["recommended_template"] == "fog" + assert result["category"] == ConditionCategory.CLEAR + assert result["severity"] == WeatherSeverity.NORMAL + assert result["recommended_template"] == "default" + assert result["has_alerts"] is False - def test_default_template_for_clear(self): - """Default template for clear/cloudy conditions.""" + def test_alerts_take_priority(self): + """Alerts should override other conditions.""" analyzer = WeatherConditionAnalyzer() + alerts = [{"severity": "Severe", "event": "Test Alert"}] + result = analyzer.analyze_weather_conditions( + {"weather_code": 0, "temp": 70}, + alerts_data=alerts + ) - result = analyzer.analyze_weather_conditions({"weather_code": 0}) - assert result["recommended_template"] == "default" - - result = analyzer.analyze_weather_conditions({"weather_code": 3}) - assert result["recommended_template"] == "default" + assert result["has_alerts"] is True + assert result["recommended_template"] == "alert" + assert result["priority_score"] == 1000 + def test_weather_code_as_list(self): + """Should handle weather_code as list/tuple.""" + analyzer = WeatherConditionAnalyzer() + result = analyzer.analyze_weather_conditions({"weather_code": [95, 96]}) -class TestErrorHandling: - """Test error handling in analysis.""" + assert result["primary_condition"] == 95 + assert result["category"] == ConditionCategory.THUNDERSTORM - def test_empty_weather_data(self): - """Empty weather data is handled gracefully.""" + def test_unknown_weather_code_defaults(self): + """Unknown weather code defaults to CLEAR/NORMAL.""" analyzer = WeatherConditionAnalyzer() + result = analyzer.analyze_weather_conditions({"weather_code": 999}) - result = analyzer.analyze_weather_conditions({}) assert result["category"] == ConditionCategory.CLEAR assert result["severity"] == WeatherSeverity.NORMAL - assert result["recommended_template"] == "default" - def test_invalid_weather_data_returns_error(self): - """Invalid data that causes exceptions returns error analysis.""" + def test_exception_handling(self): + """Should handle exceptions gracefully.""" analyzer = WeatherConditionAnalyzer() + # Pass something that would cause issues + result = analyzer.analyze_weather_conditions(None) - # This should trigger error handling (weather_code attribute access fails) - class BadData: - def get(self, key, default=None): - raise RuntimeError("Test error") - - result = analyzer.analyze_weather_conditions(BadData()) assert "error" in result assert result["recommended_template"] == "default" + def test_full_analysis_with_all_conditions(self): + """Full analysis with temperature and wind.""" + analyzer = WeatherConditionAnalyzer() + result = analyzer.analyze_weather_conditions({ + "weather_code": 65, # Heavy rain + "temp": 40, + "wind_speed": 35, # Strong wind threshold + }) + + assert result["category"] == ConditionCategory.PRECIPITATION + assert result["severity"] == WeatherSeverity.SEVERE + assert result["temperature_extreme"] is None + assert result["wind_condition"] == "strong" + assert result["recommended_template"] == "severe_weather" + -class TestEnumValues: - """Test enum definitions.""" +class TestEnums: + """Test enum values.""" - def test_weather_severity_ordering(self): - """WeatherSeverity enum values are correctly ordered.""" - assert WeatherSeverity.NORMAL.value < WeatherSeverity.MINOR.value - assert WeatherSeverity.MINOR.value < WeatherSeverity.MODERATE.value - assert WeatherSeverity.MODERATE.value < WeatherSeverity.SEVERE.value - assert WeatherSeverity.SEVERE.value < WeatherSeverity.EXTREME.value + def test_weather_severity_values(self): + """Severity enum has correct ordered values.""" + assert WeatherSeverity.NORMAL.value == 0 + assert WeatherSeverity.MINOR.value == 1 + assert WeatherSeverity.MODERATE.value == 2 + assert WeatherSeverity.SEVERE.value == 3 + assert WeatherSeverity.EXTREME.value == 4 def test_condition_category_values(self): - """ConditionCategory enum has expected values.""" + """Category enum has correct string values.""" assert ConditionCategory.CLEAR.value == "clear" + assert ConditionCategory.CLOUDY.value == "cloudy" + assert ConditionCategory.PRECIPITATION.value == "precipitation" + assert ConditionCategory.SEVERE_WEATHER.value == "severe_weather" + assert ConditionCategory.FOG.value == "fog" + assert ConditionCategory.FREEZING.value == "freezing" assert ConditionCategory.THUNDERSTORM.value == "thunderstorm"