Backup automatico script del 2026-02-08 07:00
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user