Backup automatico script del 2026-02-08 07:00

This commit is contained in:
2026-02-08 07:00:03 +01:00
parent d79431ed28
commit 812bcd002c
8 changed files with 1911 additions and 446 deletions
+48 -70
View File
@@ -47,6 +47,7 @@ MODEL_NAMES = {
"icon_d2": "ICON-D2",
"gfs_global": "GFS",
"ecmwf_ifs04": "ECMWF",
"ecmwf_ifs": "ECMWF IFS",
"jma_msm": "JMA MSM",
"metno_nordic": "Yr.no",
"ukmo_global": "UK MetOffice",
@@ -57,26 +58,18 @@ MODEL_NAMES = {
def choose_models_by_country(cc, is_home=False):
"""
Seleziona modelli meteo ottimali.
- Per Casa: usa AROME Seamless e ICON-D2 (alta risoluzione)
- Per Italia: usa italia_meteo_arpae_icon_2i (include snow_depth quando > 0)
- Per altre località: usa best match di Open-Meteo (senza specificare models)
- Per Casa e Italia: solo ICON Italia (ARPAE 2i); AROME HD non copre San Marino.
- Per altre locali: usa best match di Open-Meteo (senza specificare models).
Ritorna (short_term_models, long_term_models)
"""
cc = cc.upper() if cc else "UNKNOWN"
# Modelli a lungo termine (sempre globali, funzionano ovunque)
long_term_default = ["gfs_global", "ecmwf_ifs04"]
if is_home:
# Per Casa, usa AROME Seamless, ICON-D2 e ICON Italia (alta risoluzione europea)
# ICON Italia include snow_depth quando disponibile (> 0)
return ["meteofrance_seamless", "icon_d2", "italia_meteo_arpae_icon_2i"], long_term_default
elif cc == "IT":
# Per Italia, usa ICON Italia (ARPAE 2i) che include snow_depth quando disponibile
return ["italia_meteo_arpae_icon_2i"], long_term_default
if is_home or cc == "IT":
# ICON Italia (072h) + ECMWF IFS per i giorni successivi (dove Icon Italia non arriva)
return ["italia_meteo_arpae_icon_2i"], ["ecmwf_ifs"]
else:
# Per query worldwide, usa best match (Open-Meteo sceglie automaticamente)
# Ritorna None per indicare best match
return None, long_term_default
def get_bot_token():
@@ -127,9 +120,9 @@ def get_weather_multi_model(lat, lon, short_term_models, long_term_models, forec
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": lat, "longitude": lon,
"hourly": "temperature_2m,precipitation_probability,precipitation,snowfall,rain,snow_depth,weathercode,windspeed_10m,windgusts_10m,winddirection_10m,dewpoint_2m,relative_humidity_2m,cloud_cover,soil_temperature_0cm",
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,precipitation_probability_max,snowfall_sum,showers_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max",
"timezone": timezone if timezone else TZ_STR, "forecast_days": min(forecast_days, 3) # Limita a 3 giorni per modelli ad alta risoluzione
"hourly": "temperature_2m,precipitation,snowfall,rain,snow_depth,weathercode,windspeed_10m,windgusts_10m,winddirection_10m,dewpoint_2m,relative_humidity_2m,cloud_cover,soil_temperature_0cm",
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,snowfall_sum,showers_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max",
"timezone": timezone if timezone else TZ_STR, "forecast_days": min(forecast_days, 3)
}
try:
resp = open_meteo_get(url, params=params, timeout=(5, 20))
@@ -163,14 +156,19 @@ def get_weather_multi_model(lat, lon, short_term_models, long_term_models, forec
# Modelli specifici (per Casa: AROME + ICON, per Italia: ICON ARPAE)
for model in short_term_models:
url = "https://api.open-meteo.com/v1/forecast"
# Per italia_meteo_arpae_icon_2i, includi sempre snow_depth (supportato quando > 0)
hourly_params = "temperature_2m,precipitation_probability,precipitation,snowfall,rain,snow_depth,weathercode,windspeed_10m,windgusts_10m,winddirection_10m,dewpoint_2m,relative_humidity_2m,cloud_cover,soil_temperature_0cm"
# ICON Italia (ARPAE 2i): parametri come da API, senza precipitation_probability
if model == "italia_meteo_arpae_icon_2i":
hourly_params = "rain,showers,snowfall,snow_depth,precipitation,temperature_2m,weathercode,windspeed_10m,winddirection_10m"
daily_params = "temperature_2m_max,temperature_2m_min,showers_sum,rain_sum,snowfall_sum,precipitation_sum,precipitation_hours,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
else:
hourly_params = "temperature_2m,precipitation,snowfall,rain,snow_depth,weathercode,windspeed_10m,windgusts_10m,winddirection_10m,dewpoint_2m,relative_humidity_2m,cloud_cover,soil_temperature_0cm"
daily_params = "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,snowfall_sum,showers_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
params = {
"latitude": lat, "longitude": lon,
"hourly": hourly_params,
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,precipitation_probability_max,snowfall_sum,showers_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max",
"timezone": timezone if timezone else TZ_STR, "models": model, "forecast_days": min(forecast_days, 3) # Limita a 3 giorni per modelli ad alta risoluzione
"daily": daily_params,
"timezone": timezone if timezone else TZ_STR, "models": model,
"forecast_days": min(forecast_days, 7) if model == "italia_meteo_arpae_icon_2i" else min(forecast_days, 3)
}
try:
resp = open_meteo_get(url, params=params, timeout=(5, 20))
@@ -220,13 +218,20 @@ def get_weather_multi_model(lat, lon, short_term_models, long_term_models, forec
except:
results[model] = None
# Recupera modelli a lungo termine (globale, fino a 10 giorni)
for model in long_term_models:
# Recupera modelli a lungo termine (dopo 72h, dove Icon Italia non arriva)
for model in (long_term_models or []):
url = "https://api.open-meteo.com/v1/forecast"
# ECMWF IFS: parametri come da API (rain, showers, snowfall) + campi necessari per il report
if model == "ecmwf_ifs":
hourly_params = "rain,showers,snowfall,precipitation,temperature_2m,weathercode,windspeed_10m,winddirection_10m"
daily_params = "temperature_2m_max,temperature_2m_min,showers_sum,rain_sum,snowfall_sum,precipitation_sum,precipitation_hours,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
else:
hourly_params = "temperature_2m,precipitation,snowfall,rain,snow_depth,weathercode,windspeed_10m,windgusts_10m,winddirection_10m,dewpoint_2m,relative_humidity_2m,cloud_cover,soil_temperature_0cm"
daily_params = "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,snowfall_sum,showers_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
params = {
"latitude": lat, "longitude": lon,
"hourly": "temperature_2m,precipitation_probability,precipitation,snowfall,rain,snow_depth,weathercode,windspeed_10m,windgusts_10m,winddirection_10m,dewpoint_2m,relative_humidity_2m,cloud_cover,soil_temperature_0cm",
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,precipitation_probability_max,snowfall_sum,showers_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max",
"hourly": hourly_params,
"daily": daily_params,
"timezone": timezone if timezone else TZ_STR, "models": model, "forecast_days": forecast_days
}
try:
@@ -269,11 +274,11 @@ def merge_multi_model_forecast(models_data, forecast_days=10):
"temperature_2m_min": [],
"precipitation_sum": [],
"precipitation_hours": [],
"precipitation_probability_max": [],
"snowfall_sum": [],
"showers_sum": [],
"rain_sum": [],
"weathercode": [],
"winddirection_10m_dominant": [],
"windspeed_10m_max": [],
"windgusts_10m_max": []
},
@@ -288,7 +293,6 @@ def merge_multi_model_forecast(models_data, forecast_days=10):
"windspeed_10m": [],
"winddirection_10m": [],
"dewpoint_2m": [],
"precipitation_probability": [],
"cloud_cover": [],
"soil_temperature_0cm": []
},
@@ -358,36 +362,37 @@ def merge_multi_model_forecast(models_data, forecast_days=10):
model_display = "Best Match"
else:
model_display = MODEL_NAMES.get(short_term_model, short_term_model)
short_daily = short_term_data.get("daily", {})
short_hourly = short_term_data.get("hourly", {})
# Prendi dati daily: tutti i giorni se è l'unico modello, altrimenti primi cutoff_day+1
short_daily_times_all = short_daily.get("time", [])
short_daily_times = short_daily_times_all[:cutoff_day+1] if long_term_data else short_daily_times_all
# Verifica se ICON Italia ha dati di snow_depth (controllo diretto, non solo il flag)
has_icon_snow_depth = False
if icon_italia_data:
icon_hourly = icon_italia_data.get("hourly", {})
icon_snow_depth = icon_hourly.get("snow_depth", []) if icon_hourly else []
# Verifica se ci sono dati non-null di snow_depth
if icon_snow_depth:
for sd in icon_snow_depth[:72]: # Controlla prime 72h
if sd is not None:
try:
if float(sd) > 0: # Anche valori piccoli
if float(sd) > 0:
has_icon_snow_depth = True
break
except (ValueError, TypeError):
continue
# Se ICON Italia ha dati di snow_depth, aggiungilo ai modelli usati
num_days = len(short_daily_times)
if has_icon_snow_depth or (icon_italia_data and icon_italia_data.get("has_snow_depth_data")):
icon_display = MODEL_NAMES.get("italia_meteo_arpae_icon_2i", "ICON Italia")
merged["models_used"].append(f"{model_display} + {icon_display} (snow_depth) (0-{cutoff_day+1}d)")
merged["models_used"].append(f"{model_display} + {icon_display} (snow_depth) (0-{num_days}d)")
else:
merged["models_used"].append(f"{model_display} (0-{cutoff_day+1}d)")
short_daily = short_term_data.get("daily", {})
short_hourly = short_term_data.get("hourly", {})
merged["models_used"].append(f"{model_display} (0-{num_days}d)")
# Prendi dati daily dai primi giorni del modello a breve termine
short_daily_times = short_daily.get("time", [])[:cutoff_day+1]
for i, day_time in enumerate(short_daily_times):
merged["daily"]["time"].append(day_time)
for key in ["temperature_2m_max", "temperature_2m_min", "precipitation_sum", "precipitation_hours", "precipitation_probability_max", "snowfall_sum", "showers_sum", "rain_sum", "weathercode", "windspeed_10m_max", "windgusts_10m_max"]:
for key in ["temperature_2m_max", "temperature_2m_min", "precipitation_sum", "precipitation_hours", "snowfall_sum", "showers_sum", "rain_sum", "weathercode", "winddirection_10m_dominant", "windspeed_10m_max", "windgusts_10m_max"]:
val = short_daily.get(key, [])[i] if i < len(short_daily.get(key, [])) else None
merged["daily"][key].append(val)
@@ -409,10 +414,10 @@ def merge_multi_model_forecast(models_data, forecast_days=10):
except (ValueError, TypeError):
continue
cutoff_hour = (cutoff_day + 1) * 24
cutoff_hour = (cutoff_day + 1) * 24 if long_term_data else len(short_hourly_times)
for i, hour_time in enumerate(short_hourly_times[:cutoff_hour]):
merged["hourly"]["time"].append(hour_time)
for key in ["temperature_2m", "precipitation", "snowfall", "rain", "weathercode", "windspeed_10m", "winddirection_10m", "dewpoint_2m", "precipitation_probability", "cloud_cover", "soil_temperature_0cm"]:
for key in ["temperature_2m", "precipitation", "snowfall", "rain", "weathercode", "windspeed_10m", "winddirection_10m", "dewpoint_2m", "cloud_cover", "soil_temperature_0cm"]:
val = short_hourly.get(key, [])[i] if i < len(short_hourly.get(key, [])) else None
merged["hourly"][key].append(val)
# Per snow_depth: usa ICON Italia se disponibile (corrispondenza per timestamp), altrimenti modello principale
@@ -455,7 +460,7 @@ def merge_multi_model_forecast(models_data, forecast_days=10):
for i in range(start_idx, min(len(long_daily_times), forecast_days)):
merged["daily"]["time"].append(long_daily_times[i])
for key in ["temperature_2m_max", "temperature_2m_min", "precipitation_sum", "precipitation_hours", "precipitation_probability_max", "snowfall_sum", "showers_sum", "rain_sum", "weathercode", "windspeed_10m_max", "windgusts_10m_max"]:
for key in ["temperature_2m_max", "temperature_2m_min", "precipitation_sum", "precipitation_hours", "snowfall_sum", "showers_sum", "rain_sum", "weathercode", "winddirection_10m_dominant", "windspeed_10m_max", "windgusts_10m_max"]:
val = long_daily.get(key, [])[i] if i < len(long_daily.get(key, [])) else None
merged["daily"][key].append(val)
@@ -468,7 +473,7 @@ def merge_multi_model_forecast(models_data, forecast_days=10):
start_hour_idx = current_hourly_count
for i in range(start_hour_idx, min(len(long_hourly_times), needed_hours)):
merged["hourly"]["time"].append(long_hourly_times[i])
for key in ["temperature_2m", "precipitation", "snowfall", "snow_depth", "rain", "weathercode", "windspeed_10m", "winddirection_10m", "dewpoint_2m", "precipitation_probability", "cloud_cover", "soil_temperature_0cm"]:
for key in ["temperature_2m", "precipitation", "snowfall", "snow_depth", "rain", "weathercode", "windspeed_10m", "winddirection_10m", "dewpoint_2m", "cloud_cover", "soil_temperature_0cm"]:
val = long_hourly.get(key, [])[i] if i < len(long_hourly.get(key, [])) else None
merged["hourly"][key].append(val)
@@ -792,16 +797,13 @@ def analyze_daily_events(times, codes, probs, precip, winds, temps, dewpoints, s
block_precip = precip[start_idx:end_idx] if end_idx <= 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)
prob_block = probs[start_idx:end_idx] if end_idx <= len(probs) else probs[start_idx:]
prob_block_clean = [p for p in prob_block if p is not None]
max_prob = max(prob_block_clean) if prob_block_clean else 0
start_time = times[start_idx].split("T")[1][:5]
end_time = times[end_idx].split("T")[1][:5] if end_idx < len(times) else times[-1].split("T")[1][:5]
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" 🕒 {start_time}-{end_time} | 💧 {tot_mm:.1f}mm | Prob: {max_prob}%"
f" 🕒 {start_time}-{end_time} | 💧 {tot_mm:.1f}mm"
)
start_idx = i
current_rain_type = new_type
@@ -813,16 +815,13 @@ def analyze_daily_events(times, codes, probs, precip, winds, temps, dewpoints, s
tot_mm = sum(block_precip_clean)
if tot_mm > 0:
prob_block = probs[start_idx:end_idx] if end_idx <= len(probs) else probs[start_idx:]
prob_block_clean = [p for p in prob_block if p is not None]
max_prob = max(prob_block_clean) if prob_block_clean else 0
start_time = times[start_idx].split("T")[1][:5]
end_time = times[min(end_idx-1, len(times)-1)].split("T")[1][:5]
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" 🕒 {start_time}-{end_time} | 💧 {tot_mm:.1f}mm | Prob: {max_prob}%"
f" 🕒 {start_time}-{end_time} | 💧 {tot_mm:.1f}mm"
)
# 3. VENTO
@@ -1225,12 +1224,7 @@ def format_weather_context_report(models_data, location_name, country_code):
wcode = int(wcode) if wcode is not None else 0
# Calcola probabilità e tipo precipitazione per il giorno (PRIMA di determinare icona)
precip_prob = 0
precip_type = None
if d_probs and any(p is not None for p in d_probs):
prob_values = [p for p in d_probs if p is not None]
precip_prob = max(prob_values) if prob_values else 0
# Determina tipo precipitazione usando dati daily (più affidabili)
# Usa snowfall_sum, rain_sum, showers_sum per caratterizzazione precisa
# PRIORITÀ: Se snow_depth > 0 (modello italia_meteo_arpae_icon_2i), usa questi dati per determinare neve
@@ -1369,14 +1363,6 @@ def format_weather_context_report(models_data, location_name, country_code):
else:
weather_icon = "☁️" # Nuvoloso
# Recupera probabilità max daily se disponibile
prob_max_list = daily.get('precipitation_probability_max', [])
precip_prob_max = None
if count < len(prob_max_list) and prob_max_list[count] is not None:
precip_prob_max = int(prob_max_list[count])
elif precip_prob > 0:
precip_prob_max = int(precip_prob)
# Calcola spessore manto nevoso (snow_depth) per questo giorno
# Nota: snow_depth è disponibile solo per alcuni modelli (es. ICON Italia, ICON-D2)
# Se il modello non supporta snow_depth, tutti i valori saranno None o mancanti
@@ -1449,7 +1435,6 @@ def format_weather_context_report(models_data, location_name, country_code):
"t_min": t_min,
"t_max": t_max,
"precip_sum": precip_sum,
"precip_prob": precip_prob_max if precip_prob_max is not None else precip_prob,
"precip_type": precip_type,
"snowfall_sum": snowfall_sum,
"rain_sum": rain_sum,
@@ -1496,13 +1481,6 @@ def format_weather_context_report(models_data, location_name, country_code):
precip_parts.append(f"{precip_symbol} {day_info['precip_sum']:.1f}mm")
line += f" | {' + '.join(precip_parts)}"
# Aggiungi probabilità se disponibile
if day_info['precip_prob'] and day_info['precip_prob'] > 0:
line += f" ({int(day_info['precip_prob'])}%)"
elif day_info['precip_prob'] > 50:
# Probabilità alta ma nessuna precipitazione prevista (può essere un errore del modello)
line += f" | 💧 Possibile ({int(day_info['precip_prob'])}%)"
# Aggiungi vento (sempre se disponibile, formattato come direzione intensità)
if day_info['wind_max'] > 0: