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
+37 -61
View File
@@ -18,14 +18,10 @@ logger = logging.getLogger(__name__)
# --- CONFIGURAZIONE METEO ---
HOME_LAT = 43.9356
HOME_LON = 12.4296
HOME_NAME = "🏠 Casa (Wide View ±12km)"
HOME_NAME = "🏠 Casa"
TZ = "Europe/Berlin"
TZINFO = ZoneInfo(TZ)
# Offset ~12-15km per i 5 punti
OFFSET_LAT = 0.12
OFFSET_LON = 0.16
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"
GEOCODING_URL = "https://geocoding-api.open-meteo.com/v1/search"
HTTP_HEADERS = {"User-Agent": "loogle-bot-v10.5"}
@@ -176,12 +172,12 @@ def get_coordinates(city_name: str):
def choose_best_model(lat, lon, cc, is_home=False):
"""
Sceglie il modello meteo.
- Per Casa: usa AROME Seamless (ha snowfall)
- Per Casa: usa ICON Italia (ARPAE 2i) - migliore risoluzione spaziale per Italia/San Marino.
- Per altre località: usa best match di Open-Meteo (senza specificare models)
"""
if is_home:
# Per Casa, usa AROME Seamless (ha snowfall e dati dettagliati)
return "meteofrance_seamless", "AROME HD"
# Per Casa, usa ICON Italia (risoluzione spaziale migliore per Italia/San Marino)
return "italia_meteo_arpae_icon_2i", "ICON Italia"
else:
# Per query worldwide, usa best match (Open-Meteo sceglie automaticamente)
return None, "Best Match"
@@ -189,7 +185,7 @@ def choose_best_model(lat, lon, cc, is_home=False):
def get_forecast(lat, lon, model=None, is_home=False, timezone=None, retry_after_60s=False):
"""
Recupera forecast. Se model è None, usa best match di Open-Meteo.
Per Casa (is_home=True), usa AROME Seamless.
Per Casa (is_home=True), usa ICON Italia.
Args:
retry_after_60s: Se True, attende 10 secondi prima di riprovare (per retry)
@@ -202,21 +198,15 @@ def get_forecast(lat, lon, model=None, is_home=False, timezone=None, retry_after
logger.info("Attendo 10 secondi prima del retry...")
time.sleep(10)
# Generiamo 5 punti: Centro, N, S, E, W
lats = [lat, lat + OFFSET_LAT, lat - OFFSET_LAT, lat, lat]
lons = [lon, lon, lon, lon + OFFSET_LON, lon - OFFSET_LON]
lat_str = ",".join(map(str, lats))
lon_str = ",".join(map(str, lons))
# Singola coordinata: cielo sopra il punto richiesto (casa o località).
params = {
"latitude": lat_str, "longitude": lon_str, "timezone": tz_to_use,
"latitude": lat, "longitude": lon, "timezone": tz_to_use,
"forecast_days": 3,
"wind_speed_unit": "kmh", "precipitation_unit": "mm",
"hourly": "temperature_2m,apparent_temperature,relative_humidity_2m,cloud_cover,cloud_cover_low,cloud_cover_mid,cloud_cover_high,windspeed_10m,winddirection_10m,windgusts_10m,precipitation,rain,snowfall,weathercode,is_day,cape,visibility,uv_index"
"hourly": "temperature_2m,apparent_temperature,relative_humidity_2m,cloud_cover,cloud_cover_low,cloud_cover_mid,cloud_cover_high,windspeed_10m,winddirection_10m,windgusts_10m,precipitation,rain,showers,snowfall,weathercode,is_day,cape,visibility,uv_index"
}
# Aggiungi models solo se specificato (per Casa usa AROME, per altre località best match)
# Aggiungi models solo se specificato (per Casa usa ICON Italia, per altre località best match)
if model:
params["models"] = model
@@ -241,11 +231,7 @@ def get_forecast(lat, lon, model=None, is_home=False, timezone=None, retry_after
logger.error(f"API Error {error_details}")
return None, error_details # Restituisce anche i dettagli dell'errore
response_data = r.json()
logger.info("get_forecast ok model=%s points=5 elapsed=%.2fs", model or "best_match", time.time() - t0)
# Open-Meteo per multiple locations (lat/lon separati da virgola) restituisce
# direttamente un dict con "hourly", "daily", etc. che contiene liste di valori
# per ogni location. Per semplicità, restituiamo il dict così com'è
# e lo gestiamo nel codice chiamante
logger.info("get_forecast ok model=%s elapsed=%.2fs", model or "best_match", time.time() - t0)
return response_data, None
except requests.exceptions.Timeout as e:
error_details = f"Timeout dopo 20s: {str(e)}"
@@ -323,14 +309,14 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
# Tentativo 1: Richiesta iniziale
data_list, error_details = get_forecast(lat, lon, model_id, is_home=is_home, timezone=tz_to_use, retry_after_60s=False)
# Se fallisce e siamo a Casa con AROME, prova retry dopo 10 secondi
if not data_list and is_home and model_id == "meteofrance_seamless":
logger.warning(f"Primo tentativo AROME fallito: {error_details}. Retry dopo 10 secondi...")
# Se fallisce e siamo a Casa con ICON Italia, prova retry dopo 10 secondi
if not data_list and is_home and model_id == "italia_meteo_arpae_icon_2i":
logger.warning(f"Primo tentativo ICON Italia fallito: {error_details}. Retry dopo 10 secondi...")
data_list, error_details = get_forecast(lat, lon, model_id, is_home=is_home, timezone=tz_to_use, retry_after_60s=True)
# Se ancora fallisce e siamo a Casa, fallback a best match
if not data_list and is_home:
logger.warning(f"AROME fallito anche dopo retry: {error_details}. Fallback a best match...")
logger.warning(f"ICON Italia fallito anche dopo retry: {error_details}. Fallback a best match...")
data_list, error_details = get_forecast(lat, lon, None, is_home=False, timezone=tz_to_use, retry_after_60s=False)
if data_list:
model_name = "Best Match (fallback)"
@@ -345,7 +331,6 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
if not isinstance(data_list, list): data_list = [data_list]
# Punto centrale (Casa) per dati specifici
data_center = data_list[0]
hourly_c = data_center.get("hourly", {})
times = hourly_c.get("time", [])
@@ -353,12 +338,13 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
L = len(times)
# --- DATI LOCALI (CASA) ---
# --- DATI LOCALI ---
l_temp = safe_get_list(hourly_c, "temperature_2m", L, 0)
l_app = safe_get_list(hourly_c, "apparent_temperature", L, 0)
l_rh = safe_get_list(hourly_c, "relative_humidity_2m", L, 50)
l_prec = safe_get_list(hourly_c, "precipitation", L, 0)
l_rain = safe_get_list(hourly_c, "rain", L, 0)
l_showers = safe_get_list(hourly_c, "showers", L, 0)
l_snow = safe_get_list(hourly_c, "snowfall", L, 0)
l_wspd = safe_get_list(hourly_c, "windspeed_10m", L, 0)
l_gust = safe_get_list(hourly_c, "windgusts_10m", L, 0)
@@ -369,8 +355,8 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
l_vis = safe_get_list(hourly_c, "visibility", L, 10000)
l_uv = safe_get_list(hourly_c, "uv_index", L, 0)
# Se è Casa e AROME non fornisce visibilità (tutti None), recuperala da best match
if is_home and model_id == "meteofrance_seamless":
# Se è Casa e ICON Italia non fornisce visibilità (tutti None), recuperala da best match
if is_home and model_id == "italia_meteo_arpae_icon_2i":
vis_check = [v for v in l_vis if v is not None]
if not vis_check: # Tutti None, recupera da best match
vis_data = get_visibility_forecast(lat, lon)
@@ -383,26 +369,14 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
l_cl_mid_loc = safe_get_list(hourly_c, "cloud_cover_mid", L, 0)
l_cl_hig_loc = safe_get_list(hourly_c, "cloud_cover_high", L, 0)
# --- DATI GLOBALI (MEDIA 5 PUNTI) ---
acc_cl_tot = [0.0] * L
points_cl_tot = [ [] for _ in range(L) ]
for d in data_list:
h = d.get("hourly", {})
for i in range(L):
cc = get_val(safe_get_list(h, "cloud_cover", L)[i])
cl = get_val(safe_get_list(h, "cloud_cover_low", L)[i])
cm = get_val(safe_get_list(h, "cloud_cover_mid", L)[i])
ch = get_val(safe_get_list(h, "cloud_cover_high", L)[i])
# Calcolo robusto del totale per singolo punto
real_point_total = max(cc, cl, cm, ch)
acc_cl_tot[i] += real_point_total
points_cl_tot[i].append(real_point_total)
num_points = len(data_list)
avg_cl_tot = [x / num_points for x in acc_cl_tot]
# Nuvolosità (stesso punto della località)
avg_cl_tot = []
for i in range(L):
cc = get_val(l_cl_tot_loc[i], 0)
cl = get_val(l_cl_low_loc[i], 0)
cm = get_val(l_cl_mid_loc[i], 0)
ch = get_val(l_cl_hig_loc[i], 0)
avg_cl_tot.append(max(cc, cl, cm, ch))
# --- DEBUG MODE ---
if debug_mode:
@@ -420,8 +394,8 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
code_now = int(get_val(l_code[idx]))
output += f"Ora: {parse_time(times[idx]).strftime('%H:%M')}\n"
output += f"📍 **LOCALE (Casa)**: L:{int(loc_L)}% | H:{int(loc_H)}%\n"
output += f"🌍 **MEDIA GLOBALE**: {int(avg_cl_tot[idx])}%\n"
output += f"📍 **LOCALE**: L:{int(loc_L)}% | H:{int(loc_H)}%\n"
output += f"☁️ **Nv%**: {int(avg_cl_tot[idx])}%\n"
output += f"❄️ **NEVE**: Codice={code_now}, Accumulo={get_val(l_snow[idx])}cm\n"
decision = "H"
@@ -521,17 +495,19 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
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
# Per modelli che espongono rain+showers (es. ICON Italia), usa il totale se precipitation è assente/zero
Pr_display = max(Pr, Rain + Showers)
# Determina se è neve
is_snowing = Sn > 0 or (Code in [71, 73, 75, 77, 85, 86])
# Formattazione MM
# Formattazione MM: 0 se nulla, altrimenti il valore (i modelli danno 0 o il valore orario)
p_suffix = ""
if Code in [96, 99]: p_suffix = "G"
elif Code in [66, 67]: p_suffix = "Z"
elif is_snowing and Pr >= 0.2: p_suffix = "N"
p_s = "--" if Pr < 0.2 else f"{int(round(Pr))}{p_suffix}"
elif is_snowing and Pr_display > 0: p_suffix = "N"
p_s = "0" if Pr_display <= 0 else f"{int(round(Pr_display))}{p_suffix}"
# --- CLOUD LOGIC ---
Cl = int(get_val(l_cl_tot_loc[idx], 0))
@@ -587,10 +563,10 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
w_fmt = f"{w_txt:<5}"
# --- ICONE ---
sky, sgx = get_icon_set(Pr, Sn, Code, IsDay, Cl, Vis, T, Rain, Gust, Cape, dominant_type)
sky, sgx = get_icon_set(Pr_display, Sn, Code, IsDay, Cl, Vis, T, Rain, Gust, Cape, dominant_type)
# Se c'è precipitazione nevosa, mostra ❄️ nella colonna Sk (invece di 🌨️)
if is_snowing and Pr >= 0.2:
if is_snowing and Pr_display > 0:
sky = "❄️"
sky_fmt = f"{sky}{uv_suffix}"
@@ -615,7 +591,7 @@ if __name__ == "__main__":
args_parser = argparse.ArgumentParser()
args_parser.add_argument("--query", help="Nome città")
args_parser.add_argument("--home", action="store_true", help="Usa coordinate casa")
args_parser.add_argument("--debug", action="store_true", help="Mostra i valori dei 5 punti")
args_parser.add_argument("--debug", action="store_true", help="Mostra dettaglio debug (nuvole, neve)")
args_parser.add_argument("--chat_id", help="Chat ID Telegram per invio diretto (opzionale, può essere multiplo separato da virgola)")
args_parser.add_argument("--timezone", help="Timezone IANA (es: Europe/Rome, America/New_York)")
args = args_parser.parse_args()