Backup automatico script del 2026-07-12 07:00

This commit is contained in:
2026-07-12 07:00:03 +02:00
parent b5a2a814e2
commit 927f0d4184
18 changed files with 2365 additions and 422 deletions
+107 -12
View File
@@ -6,20 +6,29 @@ import sys
import logging
import os
import time
from typing import Optional, List
import json
from typing import Optional, List, Dict, Any, Union
from zoneinfo import ZoneInfo
from dateutil import parser as date_parser
from open_meteo_client import open_meteo_get
from open_meteo_precip import (
CASA_LAT,
CASA_LON,
CASA_TZ,
daily_precip_from_hourly,
hourly_precip_mm,
is_casa,
)
# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# --- CONFIGURAZIONE METEO ---
HOME_LAT = 43.9356
HOME_LON = 12.4296
HOME_LAT = CASA_LAT
HOME_LON = CASA_LON
HOME_NAME = "🏠 Casa"
TZ = "Europe/Berlin"
TZ = CASA_TZ
TZINFO = ZoneInfo(TZ)
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"
@@ -300,10 +309,10 @@ def get_visibility_forecast(lat, lon):
logger.error("Visibility request error: %s elapsed=%.2fs", e, time.time() - t0)
return None
def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT", timezone=None) -> str:
def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT", timezone=None, as_json=False) -> Union[str, Dict[str, Any]]:
t_total = time.time()
# Determina se è Casa
is_home = (abs(lat - HOME_LAT) < 0.01 and abs(lon - HOME_LON) < 0.01)
is_home = is_casa(lat, lon)
# Fuso per l'API: Casa = TZ; località = timezone esplicito/geocoding, altrimenti "auto" (Open-Meteo risolve da lat/lon)
tz_for_api = timezone if timezone else (TZ if is_home else "auto")
@@ -446,6 +455,8 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
# Separa in blocchi per giorno: cambia intestazione quando passa da 23 a 00
blocks = []
json_blocks: List[Dict[str, Any]] = []
json_block_map: Dict[str, Dict[str, Any]] = {}
header = f"{'LT':<2} {'':>4} {'h%':>3} {'mm':<3} {'Vento':<5} {'Nv%':>5} {'Sk':<2} {'Sx':<2}"
separator = "-" * 31
@@ -506,8 +517,7 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
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)
Pr_display = hourly_precip_mm(Pr, Rain, Showers)
# Determina se è neve
is_snowing = Sn > 0 or (Code in [71, 73, 75, 77, 85, 86])
@@ -582,6 +592,39 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
sky_fmt = f"{sky}{uv_suffix}"
current_block_lines.append(f"{dt.strftime('%H'):<2} {t_s:>4} {Rh:>3} {p_s:>3} {w_fmt} {cl_str:>5} {sky_fmt:<2} {sgx:<2}")
day_key = day_date.isoformat()
if day_key not in json_block_map:
day_label = f"{['Lun','Mar','Mer','Gio','Ven','Sab','Dom'][day_date.weekday()]} {day_date.day}"
json_block_map[day_key] = {"day_label": day_label, "date": day_key, "rows": []}
json_blocks.append(json_block_map[day_key])
json_block_map[day_key]["rows"].append({
"hour": dt.strftime("%H"),
"datetime": dt.isoformat(),
"temp_c": round(T, 1),
"temp_display": t_s,
"feels_offset": t_suffix,
"humidity": Rh,
"precip_mm": round(Pr_display, 1),
"precip_display": p_s,
"snow_cm": round(Sn, 1),
"weathercode": Code,
"wind_speed_kmh": round(Wspd, 0),
"wind_gust_kmh": round(Gust, 0),
"wind_display": w_txt.strip(),
"wind_cardinal": card,
"cloud_pct": cl_str,
"cloud_type": dominant_type,
"visibility_m": round(Vis, 0),
"uv_index": round(UV, 1),
"uv_suffix": uv_suffix,
"cape": round(Cape, 0),
"is_day": bool(IsDay),
"sky_icon": sky_fmt,
"side_icon": sgx,
"is_fog": is_fog,
"is_snow": is_snowing,
})
hours_from_start += 1
@@ -593,7 +636,49 @@ def generate_weather_report(lat, lon, location_name, debug_mode=False, cc="IT",
if not blocks:
return f"❌ Nessun dato da mostrare nelle prossime 48 ore (da {current_hour.strftime('%H:%M')})."
report = f"🌤️ *METEO REPORT*\n📍 {location_name}\n🧠 Fonte: {model_name}\n\n" + "\n\n".join(blocks)
daily_totals = daily_precip_from_hourly(hourly_c)
today_str = datetime.datetime.now(tz_to_use_info).date().isoformat()
tomorrow_str = (datetime.datetime.now(tz_to_use_info).date() + datetime.timedelta(days=1)).isoformat()
today_mm = daily_totals.get(today_str, 0.0)
tomorrow_mm = daily_totals.get(tomorrow_str, 0.0)
totals_line = (
f"\n\n💧 *Precip cumulata:* oggi {today_mm:.1f} mm | domani {tomorrow_mm:.1f} mm"
)
legend = {
"temp": "W=wind chill, H=heat index",
"precip": "G=grandine, Z=ghiacciato, N=neve",
"cloud": "FOG=nebbia",
"sky": "Icona condizioni (☀️🌧️⛈️…)",
"sx": "☃️ neve · 🧊 ghiaccio · ⚡/🌪️ temporali · 🥵 caldo · ☔️ pioggia · 💨 vento forte",
"uv": "E=UV estremo, H=UV alto",
}
if as_json:
return {
"location": location_name,
"model": model_name,
"timezone": tz_to_use,
"blocks": json_blocks,
"precip_totals": {"today_mm": round(today_mm, 1), "tomorrow_mm": round(tomorrow_mm, 1)},
"legend": legend,
"columns": [
{"key": "hour", "label": "LT", "title": "Ora locale"},
{"key": "temp_display", "label": "", "title": "Temperatura"},
{"key": "humidity", "label": "h%", "title": "Umidità"},
{"key": "precip_display", "label": "mm", "title": "Precipitazioni orarie"},
{"key": "wind_display", "label": "Vento", "title": "Direzione e intensità (km/h)"},
{"key": "cloud_pct", "label": "Nv%", "title": "Copertura nuvolosa"},
{"key": "sky_icon", "label": "Sk", "title": "Cielo"},
{"key": "side_icon", "label": "Sx", "title": "Indicatori secondari"},
],
}
report = (
f"🌤️ *METEO REPORT*\n📍 {location_name}\n🧠 Fonte: {model_name}\n\n"
+ "\n\n".join(blocks)
+ totals_line
)
logger.info("generate_weather_report ok elapsed=%.2fs", time.time() - t_total)
return report
@@ -604,6 +689,7 @@ if __name__ == "__main__":
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_parser.add_argument("--json", action="store_true", help="Output JSON strutturato (WebApp)")
args = args_parser.parse_args()
# Determina chat_ids se specificato
@@ -613,14 +699,21 @@ if __name__ == "__main__":
# Genera report
report = None
json_out = None
if args.home:
report = generate_weather_report(HOME_LAT, HOME_LON, HOME_NAME, args.debug, "SM")
if args.json:
json_out = generate_weather_report(HOME_LAT, HOME_LON, HOME_NAME, args.debug, "SM", as_json=True)
else:
report = generate_weather_report(HOME_LAT, HOME_LON, HOME_NAME, args.debug, "SM")
elif args.query:
coords = get_coordinates(args.query)
if coords:
lat, lon, name, cc, geo_tz = coords
tz = args.timezone or geo_tz
report = generate_weather_report(lat, lon, name, args.debug, cc, timezone=tz)
if args.json:
json_out = generate_weather_report(lat, lon, name, args.debug, cc, timezone=tz, as_json=True)
else:
report = generate_weather_report(lat, lon, name, args.debug, cc, timezone=tz)
else:
error_msg = f"❌ Città '{args.query}' non trovata."
if chat_ids:
@@ -637,7 +730,9 @@ if __name__ == "__main__":
sys.exit(1)
# Invia o stampa
if chat_ids:
if args.json and json_out:
print(json.dumps(json_out, ensure_ascii=False))
elif chat_ids:
telegram_send_markdown(report, chat_ids)
else:
print(report)