179 lines
6.2 KiB
Python
179 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Regression checks for Meteo7 interpretation (weathercode mode, ice gates, hours, trend max/min)."""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
|
from previsione7 import (
|
|
_merge_weathercode,
|
|
_format_event_hours,
|
|
analyze_daily_events,
|
|
analyze_temperature_trend,
|
|
format_detailed_trend_explanation,
|
|
get_precip_type,
|
|
)
|
|
|
|
|
|
def test_weathercode_mode_not_median():
|
|
# 61 (rain) + 71 (snow) → mediana numerica sarebbe 66 (FZRA); moda deve evitare 66
|
|
assert _merge_weathercode([61, 71], preferred=61) == 61
|
|
assert _merge_weathercode([61, 71], preferred=71) == 71
|
|
assert _merge_weathercode([61, 61, 71]) == 61
|
|
assert _merge_weathercode([66, 66, 61]) == 66
|
|
print("OK weathercode mode")
|
|
|
|
|
|
def test_get_precip_type_temp_gate():
|
|
assert "Congelantesi" not in get_precip_type(67, temp=28.0)
|
|
assert "Pioggia" in get_precip_type(67, temp=28.0)
|
|
assert "Congelantesi" in get_precip_type(67, temp=0.5)
|
|
assert "Neve" not in get_precip_type(71, temp=20.0)
|
|
assert "Neve" in get_precip_type(71, temp=-1.0)
|
|
print("OK precip type temp gate")
|
|
|
|
|
|
def test_gelicidio_hot_air_no_event():
|
|
times = [f"2026-07-21T{h:02d}:00" for h in range(24)]
|
|
codes = [0] * 24
|
|
codes[20] = 67 # FZRA spurio
|
|
precip = [0.0] * 24
|
|
precip[20] = 1.2
|
|
temps = [28.0] * 24
|
|
dewpoints = [18.0] * 24
|
|
winds = [5.0] * 24
|
|
events = analyze_daily_events(
|
|
times, codes, None, precip, winds, temps, dewpoints,
|
|
snowfalls=[0.0] * 24, rains=precip[:], soil_temps=[25.0] * 24,
|
|
cloud_covers=[50.0] * 24, wind_speeds=winds,
|
|
)
|
|
ice_like = [e for e in events if "GELICIDIO" in e or "BRINA" in e or "Gelata" in e or "Black Ice" in e]
|
|
assert not ice_like, f"unexpected ice events in heat: {ice_like}"
|
|
rain_like = [e for e in events if "Pioggia" in e or "Congelantesi" in e]
|
|
assert rain_like, "expected rain event"
|
|
assert all("Congelantesi" not in e for e in rain_like)
|
|
print("OK no gelicidio at 28°C")
|
|
|
|
|
|
def test_single_hour_event_range():
|
|
times = [f"2026-07-21T{h:02d}:00" for h in range(24)]
|
|
assert _format_event_hours(times, 20, 20) == "20:00-21:00"
|
|
assert _format_event_hours(times, 20, 21) == "20:00-22:00"
|
|
# last hour of day
|
|
assert _format_event_hours(times, 23, 23) == "23:00-00:00"
|
|
|
|
codes = [0] * 24
|
|
precip = [0.0] * 24
|
|
precip[20] = 2.0
|
|
temps = [22.0] * 24
|
|
events = analyze_daily_events(
|
|
times, codes, None, precip, [5.0] * 24, temps, [12.0] * 24,
|
|
snowfalls=[0.0] * 24, rains=precip[:],
|
|
)
|
|
rain_ev = [e for e in events if "🕒" in e]
|
|
assert rain_ev, events
|
|
assert "20:00-21:00" in rain_ev[0]
|
|
assert "20:00-20:00" not in rain_ev[0]
|
|
print("OK single-hour 20:00-21:00")
|
|
|
|
|
|
def test_trend_max_min_not_media():
|
|
# Heatwave: max stay high, slight drop → calo_termico not fronte_freddo
|
|
tmax = [34, 35, 36, 33, 30, 29, 28, 27, 26, 25]
|
|
tmin = [22, 23, 24, 21, 20, 19, 18, 17, 16, 15]
|
|
trend = analyze_temperature_trend(tmax, tmin, days=10)
|
|
assert trend is not None
|
|
assert "first_max" in trend and "first_min" in trend
|
|
assert trend["type"] == "calo_termico"
|
|
text = format_detailed_trend_explanation(trend, daily_time_list=[f"2026-07-{21+i:02d}" for i in range(10)])
|
|
assert "media" not in text.lower()
|
|
assert "Massime:" in text and "Minime:" in text
|
|
assert "Fronte Freddo" not in text
|
|
print("OK trend max/min calo_termico")
|
|
|
|
|
|
def test_real_gelicidio_cold():
|
|
times = [f"2026-01-10T{h:02d}:00" for h in range(24)]
|
|
codes = [0] * 24
|
|
codes[8] = 67
|
|
precip = [0.0] * 24
|
|
precip[8] = 0.5
|
|
temps = [5.0] * 24
|
|
temps[8] = -0.5
|
|
events = analyze_daily_events(
|
|
times, codes, None, precip, [3.0] * 24, temps, [-1.0] * 24,
|
|
snowfalls=[0.0] * 24, rains=precip[:], soil_temps=[-1.0] * 24,
|
|
cloud_covers=[80.0] * 24, wind_speeds=[3.0] * 24,
|
|
)
|
|
assert any("GELICIDIO" in e for e in events), events
|
|
print("OK real gelicidio at subzero")
|
|
|
|
|
|
def test_monday_storm_event_mm_is_hourly_sum():
|
|
"""15:00=17.4 + 16:00=5.0 → un evento 22.4 mm, non 5 mm né 22.4 ripetuto."""
|
|
times = [f"2026-08-17T{h:02d}:00" for h in range(24)]
|
|
precip = [0.0] * 24
|
|
precip[15] = 17.4
|
|
precip[16] = 5.0
|
|
codes = [3] * 24
|
|
codes[15] = 65
|
|
codes[16] = 63
|
|
temps = [26.0] * 24
|
|
events = analyze_daily_events(
|
|
times, codes, None, precip, [18.0] * 24, temps, [16.0] * 24,
|
|
snowfalls=[0.0] * 24, rains=precip[:],
|
|
)
|
|
rain_ev = [e for e in events if "🕒" in e]
|
|
assert len(rain_ev) == 1, rain_ev
|
|
assert "15:00-17:00" in rain_ev[0]
|
|
assert "22.4mm" in rain_ev[0]
|
|
assert rain_ev[0].count("22.4mm") == 1
|
|
print("OK lun 17 15-17h = 22.4mm")
|
|
|
|
|
|
def test_three_pulses_keep_own_mm_not_daily_total():
|
|
times = [f"2026-08-16T{h:02d}:00" for h in range(24)]
|
|
precip = [0.0] * 24
|
|
precip[10] = 18.0
|
|
precip[14] = 19.0
|
|
precip[18] = 20.0
|
|
events = analyze_daily_events(
|
|
times, [63] * 24, None, precip, [10.0] * 24, [24.0] * 24, [14.0] * 24,
|
|
snowfalls=[0.0] * 24, rains=precip[:],
|
|
)
|
|
rain_ev = [e for e in events if "🕒" in e]
|
|
assert len(rain_ev) == 3, rain_ev
|
|
assert "18.0mm" in rain_ev[0]
|
|
assert "19.0mm" in rain_ev[1]
|
|
assert "20.0mm" in rain_ev[2]
|
|
assert all("57.0mm" not in e for e in rain_ev)
|
|
print("OK tre fasce con mm propri")
|
|
|
|
|
|
def test_last_hour_rain_is_emitted():
|
|
times = [f"2026-08-17T{h:02d}:00" for h in range(24)]
|
|
precip = [0.0] * 24
|
|
precip[23] = 4.2
|
|
events = analyze_daily_events(
|
|
times, [61] * 24, None, precip, [8.0] * 24, [20.0] * 24, [12.0] * 24,
|
|
snowfalls=[0.0] * 24, rains=precip[:],
|
|
)
|
|
rain_ev = [e for e in events if "🕒" in e]
|
|
assert rain_ev, events
|
|
assert "23:00-00:00" in rain_ev[0]
|
|
assert "4.2mm" in rain_ev[0]
|
|
print("OK pioggia ultima ora del giorno")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_weathercode_mode_not_median()
|
|
test_get_precip_type_temp_gate()
|
|
test_gelicidio_hot_air_no_event()
|
|
test_single_hour_event_range()
|
|
test_trend_max_min_not_media()
|
|
test_real_gelicidio_cold()
|
|
test_monday_storm_event_mm_is_hourly_sum()
|
|
test_three_pulses_keep_own_mm_not_daily_total()
|
|
test_last_hour_rain_is_emitted()
|
|
print("\nAll interpretation regression checks passed.")
|