diff --git a/scripts/raspiBackup_loogle_post.sh b/scripts/raspiBackup_loogle_post.sh index 3bd4ea4..ef78088 100755 --- a/scripts/raspiBackup_loogle_post.sh +++ b/scripts/raspiBackup_loogle_post.sh @@ -1,16 +1,50 @@ #!/bin/bash # raspiBackup post extension — notifica Loogle Casa (solo WebApp). +# +# RaspiBackup chiama il post hook PRIMA di STARTSERVICES: su Pi2 Casa è giù +# (before-stop ferma loogle-casa, STOPSERVICES ferma docker). Non notificare +# subito: systemd-run stacca dal cgroup e riprova finché l'API risponde. +# # Sourced da raspiBackup: $1 = return code backup. +# Eseguito da systemd-run: $1 = --wait-send + +LOOGLE_NOTIFY="/home/daniely/docker/loogle-casa/scripts/loogle-notify.sh" +MAX_TRIES="${LOOGLE_BACKUP_NOTIFY_TRIES:-36}" +SLEEP_SECS="${LOOGLE_BACKUP_NOTIFY_SLEEP:-5}" + +wait_send() { + local title="$1" body="$2" severity="$3" tag="$4" + local i + if [[ ! -x "$LOOGLE_NOTIFY" ]]; then + echo "loogle-notify.sh assente" >&2 + exit 0 + fi + for ((i = 1; i <= MAX_TRIES; i++)); do + if "$LOOGLE_NOTIFY" --title "$title" --body "$body" --category raspi_backup \ + --severity "$severity" --tag "$tag"; then + echo "notify ok try=$i tag=$tag" + exit 0 + fi + echo "notify retry $i/$MAX_TRIES tag=$tag" >&2 + sleep "$SLEEP_SECS" + done + echo "notify failed after ${MAX_TRIES} tries tag=$tag" >&2 + exit 1 +} + +if [[ "${1:-}" == "--wait-send" ]]; then + wait_send "${2:-}" "${3:-}" "${4:-info}" "${5:-raspi_backup}" + exit $? +fi RC="${1:-1}" -LOOGLE_NOTIFY="/home/daniely/docker/loogle-casa/scripts/loogle-notify.sh" +HOST="${HOSTNAME:-$(hostname)}" +HOST="${HOST%%.*}" if [[ ! -x "$LOOGLE_NOTIFY" ]]; then return 0 2>/dev/null || exit 0 fi -HOST="${HOSTNAME:-$(hostname)}" - if (( RC == 0 )); then TITLE="Backup completato" BODY="Backup di ${HOST} terminato con successo." @@ -21,7 +55,19 @@ else SEVERITY="error" fi -"$LOOGLE_NOTIFY" --title "$TITLE" --body "$BODY" --category raspi_backup \ - --severity "$SEVERITY" & +TAG="raspi_backup-${HOST}" +SELF="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo /usr/local/bin/raspiBackup_loogle_post.sh)" + +# Stacca dal servizio raspiBackup (KillMode=control-group) e aspetta Casa. +if command -v systemd-run >/dev/null 2>&1; then + systemd-run --quiet --collect \ + --description="Loogle Casa raspiBackup notify ${HOST}" \ + /bin/bash "$SELF" --wait-send "$TITLE" "$BODY" "$SEVERITY" "$TAG" \ + || true +else + nohup /bin/bash "$SELF" --wait-send "$TITLE" "$BODY" "$SEVERITY" "$TAG" \ + >/dev/null 2>&1 & + disown 2>/dev/null || true +fi return 0 2>/dev/null || exit 0 diff --git a/services/telegram-bot/meteo.py b/services/telegram-bot/meteo.py index 661ccc2..bde16ba 100644 --- a/services/telegram-bot/meteo.py +++ b/services/telegram-bot/meteo.py @@ -17,6 +17,7 @@ from open_meteo_precip import ( CASA_TZ, daily_precip_from_hourly, hourly_precip_mm, + hourly_table_should_show, is_casa, ) @@ -499,16 +500,15 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT", day_date = dt.date() is_new_day = (current_day is not None and day_date != current_day) - # Determina se mostrare questo timestamp in base alla posizione nelle 48h - # Prime 24h: ogni ora (step=1) - # Dalla 25a alla 48a: ogni 2 ore (step=2) - if hours_from_start < 24: - step = 1 # Prime 24h: dettaglio 1 ora - else: - step = 2 # Dalla 25a alla 48a: dettaglio 2 ore - - # Controlla se questo timestamp deve essere mostrato - should_show = (hours_from_start % step == 0) + # Prime 24h: ogni ora. Dalla 25a alla 48a: ogni 2 ore, ma mai nascondere + # un'ora con precipitazione (altrimenti il picco cade sulle ore dispari + # e la tabella 48h mostra 5 mm mentre Meteo7 ha 22.4 mm sul giorno). + Pr_early = get_val(l_prec[idx], 0) + Rain_early = get_val(l_rain[idx], 0) + Showers_early = get_val(l_showers[idx], 0) if idx < len(l_showers) else 0 + Code_early = int(get_val(l_code[idx], 0)) + Pr_display = hourly_precip_mm(Pr_early, Rain_early, Showers_early) + should_show = hourly_table_should_show(hours_from_start, Pr_display, Code_early) # Se è un nuovo giorno, chiudi il blocco precedente if is_new_day and current_block_lines: @@ -541,12 +541,11 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT", elif diff >= 2.5: t_suffix = "H" t_s = f"{int(round(T))}{t_suffix}" - Pr = get_val(l_prec[idx], 0) + Pr = Pr_early Sn = get_val(l_snow[idx], 0) - Code = int(get_val(l_code[idx], 0)) - Rain = get_val(l_rain[idx], 0) - Showers = get_val(l_showers[idx], 0) if idx < len(l_showers) else 0 - Pr_display = hourly_precip_mm(Pr, Rain, Showers) + Code = Code_early + Rain = Rain_early + Showers = Showers_early # Determina se è neve is_snowing = Sn > 0 or (Code in [71, 73, 75, 77, 85, 86]) @@ -676,7 +675,7 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT", legend = { "temp": "W=wind chill, H=heat index", - "precip": "G=grandine, Z=ghiacciato, N=neve", + "precip": "G=grandine, Z=ghiacciato, N=neve. Dalla 25ª ora: secco ogni 2h, pioggia sempre visibile", "cloud": "FOG=nebbia", "sky": "Icona condizioni (☀️🌧️⛈️…)", "sx": "☃️ neve · 🧊 ghiaccio · ⚡/🌪️ temporali · 🥵 caldo · ☔️ pioggia · 💨 vento forte", diff --git a/services/telegram-bot/open_meteo_precip.py b/services/telegram-bot/open_meteo_precip.py index 1c9fdbe..c01fa41 100644 --- a/services/telegram-bot/open_meteo_precip.py +++ b/services/telegram-bot/open_meteo_precip.py @@ -41,6 +41,9 @@ ICON_DAILY_VARS = ( PRECIP_HOURLY_KEYS = ("precipitation", "rain", "showers", "snowfall") PRECIP_DAILY_KEYS = ("precipitation_sum", "rain_sum", "showers_sum", "snowfall_sum", "precipitation_hours") +WMO_PRECIP_CODES = frozenset( + list(range(51, 68)) + list(range(71, 78)) + list(range(80, 83)) + [85, 86, 95, 96, 99] +) def is_casa(lat: float, lon: float, tol: float = 0.01) -> bool: @@ -84,6 +87,61 @@ def daily_precip_from_hourly(hourly: Dict) -> Dict[str, float]: return dict(out) +def daily_component_from_hourly(hourly: Dict, key: str) -> Dict[str, float]: + """Somma un campo orario (rain, showers, snowfall) per data locale YYYY-MM-DD.""" + times = hourly.get("time") or [] + arr = hourly.get(key) or [] + out: Dict[str, float] = defaultdict(float) + for i, t in enumerate(times): + if not t or i >= len(arr) or arr[i] is None: + continue + try: + out[str(t)[:10]] += float(arr[i]) + except (TypeError, ValueError): + continue + return dict(out) + + +def apply_hourly_daily_precip(daily: Dict, hourly: Dict) -> Dict: + """Allinea i totali daily alla somma delle ore (stessa metrica della tabella oraria).""" + daily = dict(daily) + times = daily.get("time") or [] + mapping = { + "precipitation_sum": daily_precip_from_hourly(hourly), + "rain_sum": daily_component_from_hourly(hourly, "rain"), + "showers_sum": daily_component_from_hourly(hourly, "showers"), + "snowfall_sum": daily_component_from_hourly(hourly, "snowfall"), + } + for key, totals in mapping.items(): + arr = list(daily.get(key) or []) + while len(arr) < len(times): + arr.append(None) + for i, t in enumerate(times): + d = str(t)[:10] + if d in totals: + arr[i] = round(totals[d], 2) + daily[key] = arr + return daily + + +def hourly_table_should_show(hours_from_start: int, precip_mm: float, weathercode: int = 0) -> bool: + """ + Tabella 48h: prime 24 ore tutte; dalle 25 alle 48 ogni 2 ore, + ma un'ora con precipitazione (mm o codice WMO) non viene mai omessa. + """ + if hours_from_start < 24: + return True + if hours_from_start % 2 == 0: + return True + if float(precip_mm or 0) >= 0.1: + return True + try: + code = int(weathercode or 0) + except (TypeError, ValueError): + code = 0 + return code in WMO_PRECIP_CODES + + def daily_precip_sum(daily: Dict, date_str: str) -> Optional[float]: times = daily.get("time") or [] arr = daily.get("precipitation_sum") or [] @@ -125,8 +183,19 @@ def fetch_icon_italia( return None +def _time_key(t) -> str: + if not t: + return "" + return str(t).strip()[:16] + + def _index_by_time(section: Dict) -> Dict[str, int]: - return {str(t): i for i, t in enumerate(section.get("time") or [])} + out: Dict[str, int] = {} + for i, t in enumerate(section.get("time") or []): + k = _time_key(t) + if k: + out[k] = i + return out def overlay_icon_precip_on_hourly(target: Dict, icon_hourly: Dict) -> Dict: diff --git a/services/telegram-bot/previsione7.py b/services/telegram-bot/previsione7.py index bbd1c77..e9a09cb 100755 --- a/services/telegram-bot/previsione7.py +++ b/services/telegram-bot/previsione7.py @@ -18,7 +18,7 @@ from open_meteo_precip import ( CASA_LAT, CASA_LON, CASA_TZ, - daily_precip_from_hourly, + apply_hourly_daily_precip, fetch_icon_italia, hourly_precip_at_index, hourly_precip_series, @@ -560,6 +560,7 @@ def merge_multi_model_forecast(models_data, forecast_days=10): "snowfall": [], "snow_depth": [], "rain": [], + "showers": [], "weathercode": [], "windspeed_10m": [], "winddirection_10m": [], @@ -1055,67 +1056,55 @@ def analyze_daily_events(times, codes, probs, precip, winds, temps, dewpoints, s start_ice = i ice_type = current_ice_condition - # 2. PRECIPITAZIONI + # 2. PRECIPITAZIONI — tot_mm è sempre la somma del solo blocco orario (mai il totale giorno) + def _precip_type_at(idx): + code_val = codes[idx] if idx < len(codes) and codes[idx] is not None else 0 + t_val = temps[idx] if idx < len(temps) and temps[idx] is not None else None + try: + code_val = int(code_val) if code_val is not None else 0 + except (ValueError, TypeError): + code_val = 0 + return get_precip_type(code_val, temp=t_val) + + def _emit_rain(start, end_inclusive, rain_type): + if end_inclusive < start: + return + block_precip = precip[start:end_inclusive + 1] + block_precip_clean = [p for p in block_precip if p is not None] + tot_mm = sum(float(p) for p in block_precip_clean) + if tot_mm <= 0: + return + hours_str = _format_event_hours(times, start, end_inclusive) + avg_intensity = tot_mm / len(block_precip) if block_precip else 0 + events.append( + f"{rain_type} ({get_intensity_label(avg_intensity)}):\n" + f" 🕒 {hours_str} | 💧 {tot_mm:.1f}mm" + ) + in_rain = False start_idx = 0 current_rain_type = "" - + for i in range(len(times)): p_val = precip[i] if i < len(precip) and precip[i] is not None else 0 is_raining = p_val >= MIN_MM_PER_EVENTO - + is_last = i == len(times) - 1 + if is_raining and not in_rain: in_rain = True start_idx = i - code_val = codes[i] if i < len(codes) and codes[i] is not None else 0 - t_val = temps[i] if i < len(temps) and temps[i] is not None else None - try: - code_val = int(code_val) if code_val is not None else 0 - except (ValueError, TypeError): - code_val = 0 - current_rain_type = get_precip_type(code_val, temp=t_val) - elif in_rain and is_raining and i < len(codes): - code_val = codes[i] if codes[i] is not None else 0 - t_val = temps[i] if i < len(temps) and temps[i] is not None else None - try: - code_val = int(code_val) if code_val is not None else 0 - except (ValueError, TypeError): - code_val = 0 - new_type = get_precip_type(code_val, temp=t_val) + current_rain_type = _precip_type_at(i) + elif in_rain and is_raining: + new_type = _precip_type_at(i) if new_type != current_rain_type: - end_inclusive = i - 1 - block_precip = precip[start_idx:i] if i <= len(precip) else precip[start_idx:] - block_precip_clean = [p for p in block_precip if p is not None] - tot_mm = sum(block_precip_clean) - hours_str = _format_event_hours(times, start_idx, end_inclusive) - avg_intensity = tot_mm / len(block_precip) if block_precip else 0 - - events.append( - f"{current_rain_type} ({get_intensity_label(avg_intensity)}):\n" - f" 🕒 {hours_str} | 💧 {tot_mm:.1f}mm" - ) + _emit_rain(start_idx, i - 1, current_rain_type) start_idx = i current_rain_type = new_type - elif (not is_raining and in_rain) or (in_rain and i == len(times)-1): + + if in_rain and (not is_raining or is_last): + end_inclusive = i if is_raining else i - 1 + _emit_rain(start_idx, end_inclusive, current_rain_type) in_rain = False - if not is_raining: - end_inclusive = i - 1 - end_slice = i - else: - end_inclusive = i - end_slice = i + 1 - block_precip = precip[start_idx:end_slice] if end_slice <= len(precip) else precip[start_idx:] - block_precip_clean = [p for p in block_precip if p is not None] - tot_mm = sum(block_precip_clean) - - if tot_mm > 0 and end_inclusive >= start_idx: - hours_str = _format_event_hours(times, start_idx, end_inclusive) - avg_intensity = tot_mm / len(block_precip) if block_precip else 0 - - events.append( - f"{current_rain_type} ({get_intensity_label(avg_intensity)}):\n" - f" 🕒 {hours_str} | 💧 {tot_mm:.1f}mm" - ) # 3. VENTO if winds: @@ -1249,16 +1238,7 @@ def _apply_unified_precip(hourly: Dict, daily: Dict, casa: bool) -> Tuple[Dict, if icon_d.get("time"): daily = overlay_icon_precip_on_daily(daily, icon_d) hourly["precipitation"] = hourly_precip_series(hourly) - totals = daily_precip_from_hourly(hourly) - times = daily.get("time") or [] - psum = list(daily.get("precipitation_sum") or []) - while len(psum) < len(times): - psum.append(None) - for i, t in enumerate(times): - d = str(t)[:10] - if d in totals: - psum[i] = round(totals[d], 2) - daily["precipitation_sum"] = psum + daily = apply_hourly_daily_precip(daily, hourly) return hourly, daily @@ -1806,27 +1786,36 @@ def format_weather_context_report(models_data, location_name, country_code, as_j if day_info['precip_sum'] > 0.1: # Caratterizza usando dati daily se disponibili precip_parts = [] - - # Neve (solo con aria abbastanza fredda: evita cm spurii in estate) - if day_info.get('snowfall_sum', 0) > 0.1 and day_info['t_min'] <= ICE_EVENT_MAX_AIR_TEMP: - precip_parts.append(f"❄️ {day_info['snowfall_sum']:.1f}cm") - - # Pioggia - if day_info.get('rain_sum', 0) > 0.1: - precip_parts.append(f"🌧️ {day_info['rain_sum']:.1f}mm") - - # Temporali (showers) - if day_info.get('showers_sum', 0) > 0.1: - precip_parts.append(f"⛈️ {day_info['showers_sum']:.1f}mm") - - # Se non abbiamo dati daily dettagliati, usa il tipo generale - if not precip_parts: - precip_symbol = "❄️" if day_info['precip_type'] == "snow" else "⛈️" if day_info['precip_type'] in ("hail", "thunderstorms") else "🌨️" if day_info['precip_type'] == "mixed" else "🌧️" - precip_parts.append(f"{precip_symbol} {day_info['precip_sum']:.1f}mm") - elif day_info['precip_sum'] > 0.1 and day_info.get('snowfall_sum', 0) > 0.1 and day_info['t_min'] > ICE_EVENT_MAX_AIR_TEMP: - # snowfall spurio a caldo: assicurati che l'accumulo pioggia totale sia visibile - if not any("🌧️" in p or "⛈️" in p for p in precip_parts): - precip_parts.append(f"🌧️ {day_info['precip_sum']:.1f}mm") + snow_sum = day_info.get("snowfall_sum", 0) or 0 + rain_sum = day_info.get("rain_sum", 0) or 0 + showers_sum = day_info.get("showers_sum", 0) or 0 + precip_sum = day_info.get("precip_sum", 0) or 0 + + if snow_sum > 0.1 and day_info["t_min"] <= ICE_EVENT_MAX_AIR_TEMP: + precip_parts.append(f"❄️ {snow_sum:.1f}cm") + + overlapping = ( + rain_sum > 0.1 and showers_sum > 0.1 and ( + abs(rain_sum - precip_sum) < 0.25 + or abs(showers_sum - precip_sum) < 0.25 + or (rain_sum + showers_sum) > precip_sum + 0.3 + ) + ) + if overlapping or (rain_sum <= 0.1 and showers_sum <= 0.1): + if precip_sum > 0.1: + precip_symbol = ( + "❄️" if day_info["precip_type"] == "snow" + else "⛈️" if day_info["precip_type"] in ("hail", "thunderstorms") + else "🌨️" if day_info["precip_type"] == "mixed" + else "🌧️" + ) + if not (day_info["precip_type"] == "snow" and snow_sum > 0.1 and day_info["t_min"] <= ICE_EVENT_MAX_AIR_TEMP): + precip_parts.append(f"{precip_symbol} {precip_sum:.1f}mm") + else: + if rain_sum > 0.1: + precip_parts.append(f"🌧️ {rain_sum:.1f}mm") + if showers_sum > 0.1: + precip_parts.append(f"⛈️ {showers_sum:.1f}mm") line += f" | {' + '.join(precip_parts)}" diff --git a/services/telegram-bot/test_hourly_table_show.py b/services/telegram-bot/test_hourly_table_show.py new file mode 100644 index 0000000..4bce012 --- /dev/null +++ b/services/telegram-bot/test_hourly_table_show.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Regressione visibilità ore nella tabella meteo 48h.""" +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +from open_meteo_precip import ( + apply_hourly_daily_precip, + hourly_table_should_show, + overlay_icon_precip_on_hourly, +) + + +def test_first_24h_all_shown(): + for h in range(24): + assert hourly_table_should_show(h, 0.0, 3) is True + + +def test_second_day_even_hours_shown(): + assert hourly_table_should_show(24, 0.0, 3) is True # lun 08 + assert hourly_table_should_show(32, 5.0, 63) is True # lun 16 + + +def test_second_day_odd_dry_hidden(): + assert hourly_table_should_show(31, 0.0, 3) is False # lun 15 secco + + +def test_second_day_odd_wet_shown(): + # Caso reale 17/08/2026: 15:00 = 17.4 mm (code 65) era nascosto → 48h mostrava solo 5 mm alle 16 + assert hourly_table_should_show(31, 17.4, 65) is True + assert hourly_table_should_show(31, 0.0, 65) is True # codice precip anche con mm 0 + + +def test_apply_hourly_daily_precip_matches_hours(): + hourly = { + "time": [f"2026-08-17T{h:02d}:00" for h in range(24)], + "precipitation": [0.0] * 24, + "rain": [0.0] * 24, + "showers": [0.0] * 24, + "snowfall": [0.0] * 24, + } + hourly["rain"][15] = 17.4 + hourly["rain"][16] = 5.0 + hourly["precipitation"][15] = 17.4 + hourly["precipitation"][16] = 5.0 + daily = { + "time": ["2026-08-17"], + "precipitation_sum": [18.0], + "rain_sum": [18.0], + "showers_sum": [20.0], + "snowfall_sum": [0.0], + } + out = apply_hourly_daily_precip(daily, hourly) + assert out["precipitation_sum"][0] == 22.4 + assert out["rain_sum"][0] == 22.4 + assert out["showers_sum"][0] == 0.0 + + +def test_overlay_matches_normalized_timestamps(): + target = { + "time": ["2026-08-17T15:00"], + "precipitation": [0.0], + "rain": [0.0], + "showers": [0.0], + "snowfall": [0.0], + } + icon = { + "time": ["2026-08-17T15:00:00"], + "precipitation": [17.4], + "rain": [17.4], + "showers": [0.0], + "snowfall": [0.0], + } + out = overlay_icon_precip_on_hourly(target, icon) + assert abs(out["precipitation"][0] - 17.4) < 0.01 + + +if __name__ == "__main__": + test_first_24h_all_shown() + test_second_day_even_hours_shown() + test_second_day_odd_dry_hidden() + test_second_day_odd_wet_shown() + test_apply_hourly_daily_precip_matches_hours() + test_overlay_matches_normalized_timestamps() + print("OK hourly_table_should_show") diff --git a/services/telegram-bot/test_previsione7_interpretation.py b/services/telegram-bot/test_previsione7_interpretation.py index ec03308..81d8d0f 100644 --- a/services/telegram-bot/test_previsione7_interpretation.py +++ b/services/telegram-bot/test_previsione7_interpretation.py @@ -109,6 +109,62 @@ def test_real_gelicidio_cold(): 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() @@ -116,4 +172,7 @@ if __name__ == "__main__": 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.")