Backup automatico script del 2026-02-08 07:00
This commit is contained in:
@@ -19,15 +19,14 @@ from open_meteo_client import configure_open_meteo_session
|
||||
# snow_radar.py
|
||||
#
|
||||
# Scopo:
|
||||
# Analizza la nevicata in una griglia di località in un raggio di 40km da San Marino.
|
||||
# Per ciascuna località mostra:
|
||||
# - Nome della località
|
||||
# - Somma dello snowfall orario nelle 12 ore precedenti
|
||||
# - Somma dello snowfall previsto nelle 12 ore successive
|
||||
# - Somma dello snowfall previsto nelle 24 ore successive
|
||||
# Analizza la neve in una griglia di località in un raggio di 40km da San Marino.
|
||||
# Combina due parametri Open-Meteo:
|
||||
# - snowfall: precipitazione nevosa (cm/h) - neve che cade
|
||||
# - snow_depth: spessore manto al suolo (m) - neve accumulata, incluso residuo
|
||||
# senza precipitazione (es. giorni successivi a nevicata)
|
||||
#
|
||||
# Modello meteo:
|
||||
# meteofrance_seamless (AROME) per dati dettagliati
|
||||
# italia_meteo_arpae_icon_2i (supporta snowfall e snow_depth)
|
||||
#
|
||||
# Token Telegram:
|
||||
# Nessun token in chiaro. Lettura in ordine:
|
||||
@@ -88,8 +87,14 @@ LOCATIONS = [
|
||||
TZ = "Europe/Berlin"
|
||||
TZINFO = ZoneInfo(TZ)
|
||||
|
||||
# Modello meteo
|
||||
MODEL_AROME = "meteofrance_seamless"
|
||||
# Modello meteo: italia_meteo_arpae_icon_2i supporta snowfall e snow_depth
|
||||
# - snowfall: precipitazione nevosa (cm/h)
|
||||
# - snow_depth: spessore manto al suolo (m), include neve residua anche senza precipitazione
|
||||
MODEL_SNOW = "italia_meteo_arpae_icon_2i"
|
||||
|
||||
# Soglia minima (cm) per considerare "neve presente" - evita falsi positivi da rumore/modello
|
||||
# Valori < 1 cm sono tracce trascurabili (dew, frost, errori numerici) - non neve reale
|
||||
SNOW_THRESHOLD_CM = 1.0
|
||||
|
||||
# File di log
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -189,14 +194,16 @@ def calculate_distance_km(lat1: float, lon1: float, lat2: float, lon2: float) ->
|
||||
def get_forecast(session: requests.Session, lat: float, lon: float) -> Optional[Dict]:
|
||||
"""
|
||||
Recupera previsioni meteo per una località.
|
||||
Inclusi: snowfall (precipitazione nevosa cm/h), snow_depth (manto al suolo m).
|
||||
"""
|
||||
params = {
|
||||
"latitude": lat,
|
||||
"longitude": lon,
|
||||
"hourly": "snowfall,weathercode",
|
||||
"hourly": "snowfall,snow_depth,weathercode",
|
||||
"timezone": TZ,
|
||||
"forecast_days": 2,
|
||||
"models": MODEL_AROME,
|
||||
"past_days": 7,
|
||||
"forecast_days": 7,
|
||||
"models": MODEL_SNOW,
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -217,22 +224,25 @@ def get_forecast(session: requests.Session, lat: float, lon: float) -> Optional[
|
||||
|
||||
def analyze_snowfall_for_location(data: Dict, now: datetime.datetime) -> Optional[Dict]:
|
||||
"""
|
||||
Analizza snowfall per una località.
|
||||
Analizza snowfall e snow_depth per una località.
|
||||
|
||||
Nota: Open-Meteo fornisce principalmente dati futuri. Per i dati passati,
|
||||
includiamo anche le ore appena passate se disponibili nei dati hourly.
|
||||
Combina:
|
||||
- snowfall: precipitazione nevosa (cm/h) - neve che cade
|
||||
- snow_depth: spessore manto al suolo (m) - neve accumulata, incluso residuo senza precipitazione
|
||||
|
||||
Returns:
|
||||
Dict con:
|
||||
- snow_past_12h: somma snowfall ultime 12 ore (cm) - se disponibile nei dati
|
||||
- snow_next_12h: somma snowfall prossime 12 ore (cm)
|
||||
- snow_next_24h: somma snowfall prossime 24 ore (cm)
|
||||
Dict con valori in cm:
|
||||
- snow_past_12h: max(somma snowfall ultime 12h, snow_depth attuale)
|
||||
- snow_next_12h: somma snowfall prossime 12h
|
||||
- snow_next_24h: max(somma snowfall prossime 24h, snow_depth max previsto)
|
||||
- snow_depth_now_cm: manto attuale al suolo (cm)
|
||||
"""
|
||||
hourly = data.get("hourly", {}) or {}
|
||||
times = hourly.get("time", []) or []
|
||||
snowfall = hourly.get("snowfall", []) or []
|
||||
snow_depth_raw = hourly.get("snow_depth", []) or [] # in metri (m)
|
||||
|
||||
if not times or not snowfall:
|
||||
if not times:
|
||||
return None
|
||||
|
||||
# Converti timestamps
|
||||
@@ -243,29 +253,54 @@ def analyze_snowfall_for_location(data: Dict, now: datetime.datetime) -> Optiona
|
||||
next_12h_end = now + datetime.timedelta(hours=12)
|
||||
next_24h_end = now + datetime.timedelta(hours=24)
|
||||
|
||||
snow_past_12h = 0.0
|
||||
snow_next_12h = 0.0
|
||||
snow_next_24h = 0.0
|
||||
snowfall_past_12h = 0.0
|
||||
snowfall_next_12h = 0.0
|
||||
snowfall_next_24h = 0.0
|
||||
snow_depth_now_m = 0.0
|
||||
snow_depth_max_past_12h_m = 0.0
|
||||
snow_depth_max_next_24h_m = 0.0
|
||||
|
||||
# Rumore numerico: valori < 0.01 cm (snowfall) o < 0.0001 m (snow_depth) → 0
|
||||
NOISE_FLOOR_SNOWFALL_CM = 0.01
|
||||
NOISE_FLOOR_SNOW_DEPTH_M = 0.0001
|
||||
|
||||
for i, dt in enumerate(dt_list):
|
||||
snow_val = float(snowfall[i]) if i < len(snowfall) and snowfall[i] is not None else 0.0
|
||||
depth_val = float(snow_depth_raw[i]) if i < len(snow_depth_raw) and snow_depth_raw[i] is not None else 0.0
|
||||
if snow_val < NOISE_FLOOR_SNOWFALL_CM:
|
||||
snow_val = 0.0
|
||||
if depth_val < NOISE_FLOOR_SNOW_DEPTH_M:
|
||||
depth_val = 0.0
|
||||
|
||||
# Ultime 12 ore (passato) - solo se i dati includono il passato
|
||||
# Snowfall nelle finestre temporali
|
||||
if dt < now and dt >= past_12h_start:
|
||||
snow_past_12h += snow_val
|
||||
|
||||
# Prossime 12 ore
|
||||
snowfall_past_12h += snow_val
|
||||
snow_depth_max_past_12h_m = max(snow_depth_max_past_12h_m, depth_val)
|
||||
if now <= dt < next_12h_end:
|
||||
snow_next_12h += snow_val
|
||||
|
||||
# Prossime 24 ore
|
||||
snowfall_next_12h += snow_val
|
||||
if now <= dt < next_24h_end:
|
||||
snow_next_24h += snow_val
|
||||
snowfall_next_24h += snow_val
|
||||
snow_depth_max_next_24h_m = max(snow_depth_max_next_24h_m, depth_val)
|
||||
|
||||
# snow_depth attuale: usa valore più vicino a "now" (ultima ora passata o prima futura)
|
||||
if dt <= now:
|
||||
snow_depth_now_m = depth_val
|
||||
|
||||
# snow_depth da m a cm
|
||||
snow_depth_now_cm = snow_depth_now_m * 100.0
|
||||
snow_depth_max_past_12h_cm = snow_depth_max_past_12h_m * 100.0
|
||||
snow_depth_max_next_24h_cm = snow_depth_max_next_24h_m * 100.0
|
||||
|
||||
# Combina precipitazione + manto: per passato usa max(somma precipitazione, manto attuale)
|
||||
# per futuro usa max(somma precipitazione, manto max previsto)
|
||||
snow_past_12h = max(snowfall_past_12h, snow_depth_now_cm)
|
||||
snow_next_24h = max(snowfall_next_24h, snow_depth_max_next_24h_cm)
|
||||
|
||||
return {
|
||||
"snow_past_12h": snow_past_12h,
|
||||
"snow_next_12h": snow_next_12h,
|
||||
"snow_next_12h": snowfall_next_12h,
|
||||
"snow_next_24h": snow_next_24h,
|
||||
"snow_depth_now_cm": snow_depth_now_cm,
|
||||
}
|
||||
|
||||
|
||||
@@ -314,6 +349,9 @@ def generate_snow_map(results: List[Dict], center_lat: float, center_lon: float,
|
||||
totals = [r.get(data_field, 0.0) for r in results]
|
||||
max_total = max(totals) if totals else 1.0
|
||||
min_total = min(totals) if totals else 0.0
|
||||
# Evita vmin==vmax (divisione per zero nel colormap) - tutti 0 → scala 0..1
|
||||
if max_total <= min_total:
|
||||
max_total = max(min_total + 0.1, 1.0)
|
||||
|
||||
# Estrai coordinate
|
||||
lats = [r["lat"] for r in results]
|
||||
@@ -457,9 +495,10 @@ def generate_snow_map(results: List[Dict], center_lat: float, center_lon: float,
|
||||
ax.legend(handles=legend_elements, loc='lower left', fontsize=10,
|
||||
framealpha=0.95, edgecolor='black', fancybox=True, shadow=True)
|
||||
|
||||
# Info timestamp spostata in alto a destra
|
||||
# Info timestamp spostata in alto a destra (Località con neve = solo quelle con neve sopra soglia)
|
||||
now = now_local()
|
||||
info_text = f"Aggiornamento: {now.strftime('%d/%m/%Y %H:%M')}\nLocalità con neve: {len(results)}"
|
||||
num_with_snow = sum(1 for r in results if r.get("has_snow", False))
|
||||
info_text = f"Aggiornamento: {now.strftime('%d/%m/%Y %H:%M')}\nLocalità con neve: {num_with_snow}"
|
||||
ax.text(0.98, 0.98, info_text, transform=ax.transAxes,
|
||||
fontsize=9, verticalalignment='top', horizontalalignment='right',
|
||||
bbox=dict(boxstyle='round,pad=0.5', facecolor='white', alpha=0.9,
|
||||
@@ -660,11 +699,13 @@ def main(chat_ids: Optional[List[str]] = None, debug_mode: bool = False, chat_id
|
||||
continue
|
||||
|
||||
# Aggiungi sempre Casa, anche se non c'è neve
|
||||
# Per le altre località, aggiungi solo se c'è neve (passata o prevista)
|
||||
# Per le altre località, aggiungi solo se c'è neve sopra soglia (precipitazione o manto residuo)
|
||||
is_casa = loc["name"] == "Casa (Strada Cà Toro)"
|
||||
has_snow = (snow_analysis["snow_past_12h"] > 0.0 or
|
||||
snow_analysis["snow_next_12h"] > 0.0 or
|
||||
snow_analysis["snow_next_24h"] > 0.0)
|
||||
has_snow = (
|
||||
snow_analysis["snow_past_12h"] >= SNOW_THRESHOLD_CM or
|
||||
snow_analysis["snow_next_12h"] >= SNOW_THRESHOLD_CM or
|
||||
snow_analysis["snow_next_24h"] >= SNOW_THRESHOLD_CM
|
||||
)
|
||||
|
||||
if is_casa or has_snow:
|
||||
results.append({
|
||||
@@ -672,6 +713,7 @@ def main(chat_ids: Optional[List[str]] = None, debug_mode: bool = False, chat_id
|
||||
"lat": loc["lat"],
|
||||
"lon": loc["lon"],
|
||||
"distance_km": distance_km,
|
||||
"has_snow": has_snow,
|
||||
**snow_analysis
|
||||
})
|
||||
|
||||
@@ -687,6 +729,7 @@ def main(chat_ids: Optional[List[str]] = None, debug_mode: bool = False, chat_id
|
||||
|
||||
# Genera e invia DUE mappe separate
|
||||
now_str = now.strftime('%d/%m/%Y %H:%M')
|
||||
num_with_snow = sum(1 for r in results if r.get("has_snow", False))
|
||||
|
||||
# 1. Mappa snowfall passato (12h precedenti)
|
||||
map_path_past = os.path.join(BASE_DIR, "snow_radar_past.png")
|
||||
@@ -698,7 +741,7 @@ def main(chat_ids: Optional[List[str]] = None, debug_mode: bool = False, chat_id
|
||||
f"❄️ *SNOW RADAR - Ultime 12h*\n"
|
||||
f"📍 Centro: San Marino\n"
|
||||
f"🕒 {now_str}\n"
|
||||
f"📊 Località con neve: {len(results)}/{len(LOCATIONS)}"
|
||||
f"📊 Località con neve: {num_with_snow}/{len(LOCATIONS)}"
|
||||
)
|
||||
telegram_send_photo(map_path_past, caption_past, chat_ids=chat_ids)
|
||||
# Pulisci file temporaneo
|
||||
@@ -718,7 +761,7 @@ def main(chat_ids: Optional[List[str]] = None, debug_mode: bool = False, chat_id
|
||||
f"❄️ *SNOW RADAR - Prossime 24h*\n"
|
||||
f"📍 Centro: San Marino\n"
|
||||
f"🕒 {now_str}\n"
|
||||
f"📊 Località con neve: {len(results)}/{len(LOCATIONS)}"
|
||||
f"📊 Località con neve: {num_with_snow}/{len(LOCATIONS)}"
|
||||
)
|
||||
telegram_send_photo(map_path_future, caption_future, chat_ids=chat_ids)
|
||||
# Pulisci file temporaneo
|
||||
|
||||
Reference in New Issue
Block a user