Backup automatico script del 2026-07-12 07:00
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Precipitazione unificata Open-Meteo per Casa (San Marino).
|
||||
|
||||
Reference 0–72 h: ICON Italia (ARPAE 2i). Totale orario = max(precipitation, rain + showers).
|
||||
|
||||
Copia autosufficiente per il container loogle-bot (/app); la sorgente condivisa
|
||||
resta in docker/shared/open_meteo/open_meteo_precip.py.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
try:
|
||||
from open_meteo_client import open_meteo_get
|
||||
except ImportError:
|
||||
import requests
|
||||
|
||||
def open_meteo_get(url, params=None, headers=None, timeout=(5, 25), retries=3, backoff=0.8):
|
||||
return requests.get(url, params=params, headers=headers or {}, timeout=timeout)
|
||||
|
||||
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"
|
||||
DEFAULT_HEADERS = {"User-Agent": "open-meteo-precip/1.0"}
|
||||
|
||||
CASA_LAT = 43.9356
|
||||
CASA_LON = 12.4296
|
||||
CASA_TZ = "Europe/Rome"
|
||||
ICON_ITALIA_MODEL = "italia_meteo_arpae_icon_2i"
|
||||
|
||||
ICON_HOURLY_VARS = (
|
||||
"precipitation,rain,showers,snowfall,weathercode,"
|
||||
"temperature_2m,windspeed_10m,winddirection_10m"
|
||||
)
|
||||
ICON_DAILY_VARS = (
|
||||
"precipitation_sum,rain_sum,showers_sum,snowfall_sum,weathercode,"
|
||||
"temperature_2m_min,temperature_2m_max"
|
||||
)
|
||||
|
||||
PRECIP_HOURLY_KEYS = ("precipitation", "rain", "showers", "snowfall")
|
||||
PRECIP_DAILY_KEYS = ("precipitation_sum", "rain_sum", "showers_sum", "snowfall_sum", "precipitation_hours")
|
||||
|
||||
|
||||
def is_casa(lat: float, lon: float, tol: float = 0.01) -> bool:
|
||||
return abs(lat - CASA_LAT) < tol and abs(lon - CASA_LON) < tol
|
||||
|
||||
|
||||
def hourly_precip_mm(
|
||||
precipitation: Optional[float] = None,
|
||||
rain: Optional[float] = None,
|
||||
showers: Optional[float] = None,
|
||||
) -> float:
|
||||
p = float(precipitation or 0)
|
||||
r = float(rain or 0)
|
||||
s = float(showers or 0)
|
||||
return max(p, r + s)
|
||||
|
||||
|
||||
def hourly_precip_at_index(hourly: Dict, index: int) -> float:
|
||||
def g(key: str) -> Optional[float]:
|
||||
arr = hourly.get(key) or []
|
||||
if index >= len(arr):
|
||||
return None
|
||||
v = arr[index]
|
||||
return float(v) if v is not None else None
|
||||
|
||||
return hourly_precip_mm(g("precipitation"), g("rain"), g("showers"))
|
||||
|
||||
|
||||
def hourly_precip_series(hourly: Dict) -> List[float]:
|
||||
times = hourly.get("time") or []
|
||||
return [hourly_precip_at_index(hourly, i) for i in range(len(times))]
|
||||
|
||||
|
||||
def daily_precip_from_hourly(hourly: Dict) -> Dict[str, float]:
|
||||
times = hourly.get("time") or []
|
||||
out: Dict[str, float] = defaultdict(float)
|
||||
for i, t in enumerate(times):
|
||||
if not t:
|
||||
continue
|
||||
out[str(t)[:10]] += hourly_precip_at_index(hourly, i)
|
||||
return dict(out)
|
||||
|
||||
|
||||
def daily_precip_sum(daily: Dict, date_str: str) -> Optional[float]:
|
||||
times = daily.get("time") or []
|
||||
arr = daily.get("precipitation_sum") or []
|
||||
for i, t in enumerate(times):
|
||||
if str(t)[:10] == date_str[:10] and i < len(arr) and arr[i] is not None:
|
||||
return float(arr[i])
|
||||
return None
|
||||
|
||||
|
||||
def fetch_icon_italia(
|
||||
lat: float,
|
||||
lon: float,
|
||||
tz: str = CASA_TZ,
|
||||
forecast_days: int = 10,
|
||||
past_days: int = 0,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> Optional[Dict]:
|
||||
params = {
|
||||
"latitude": lat,
|
||||
"longitude": lon,
|
||||
"timezone": tz,
|
||||
"forecast_days": forecast_days,
|
||||
"models": ICON_ITALIA_MODEL,
|
||||
"hourly": ICON_HOURLY_VARS,
|
||||
"daily": ICON_DAILY_VARS,
|
||||
}
|
||||
if past_days:
|
||||
params["past_days"] = past_days
|
||||
try:
|
||||
resp = open_meteo_get(
|
||||
OPEN_METEO_URL,
|
||||
params=params,
|
||||
headers=headers or DEFAULT_HEADERS,
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _index_by_time(section: Dict) -> Dict[str, int]:
|
||||
return {str(t): i for i, t in enumerate(section.get("time") or [])}
|
||||
|
||||
|
||||
def overlay_icon_precip_on_hourly(target: Dict, icon_hourly: Dict) -> Dict:
|
||||
if not target or not icon_hourly:
|
||||
return target
|
||||
tgt_idx = _index_by_time(target)
|
||||
icon_idx = _index_by_time(icon_hourly)
|
||||
out = dict(target)
|
||||
for key in PRECIP_HOURLY_KEYS:
|
||||
arr = list(out.get(key) or [None] * len(out.get("time") or []))
|
||||
while len(arr) < len(out.get("time") or []):
|
||||
arr.append(None)
|
||||
icon_arr = icon_hourly.get(key) or []
|
||||
for t, i in tgt_idx.items():
|
||||
j = icon_idx.get(t)
|
||||
if j is not None and j < len(icon_arr):
|
||||
arr[i] = icon_arr[j]
|
||||
out[key] = arr
|
||||
times = out.get("time") or []
|
||||
out["precipitation"] = [hourly_precip_at_index(out, i) for i in range(len(times))]
|
||||
return out
|
||||
|
||||
|
||||
def overlay_icon_precip_on_daily(target: Dict, icon_daily: Dict) -> Dict:
|
||||
if not target or not icon_daily:
|
||||
return target
|
||||
tgt_idx = {str(t)[:10]: i for i, t in enumerate(target.get("time") or [])}
|
||||
icon_idx = {str(t)[:10]: i for i, t in enumerate(icon_daily.get("time") or [])}
|
||||
out = dict(target)
|
||||
for key in PRECIP_DAILY_KEYS:
|
||||
arr = list(out.get(key) or [None] * len(out.get("time") or []))
|
||||
while len(arr) < len(out.get("time") or []):
|
||||
arr.append(None)
|
||||
icon_arr = icon_daily.get(key) or []
|
||||
for d, i in tgt_idx.items():
|
||||
j = icon_idx.get(d)
|
||||
if j is not None and j < len(icon_arr):
|
||||
arr[i] = icon_arr[j]
|
||||
out[key] = arr
|
||||
times = out.get("time") or []
|
||||
psum = list(out.get("precipitation_sum") or [None] * len(times))
|
||||
while len(psum) < len(times):
|
||||
psum.append(None)
|
||||
for d, j in icon_idx.items():
|
||||
i = tgt_idx.get(d)
|
||||
if i is None:
|
||||
continue
|
||||
arr = icon_daily.get("precipitation_sum") or []
|
||||
if j < len(arr) and arr[j] is not None:
|
||||
psum[i] = float(arr[j])
|
||||
out["precipitation_sum"] = psum
|
||||
return out
|
||||
|
||||
|
||||
def icon_precip_daily_totals(icon_data: Optional[Dict]) -> Dict[str, float]:
|
||||
if not icon_data:
|
||||
return {}
|
||||
hourly = icon_data.get("hourly") or {}
|
||||
if hourly.get("time"):
|
||||
totals = daily_precip_from_hourly(hourly)
|
||||
if totals:
|
||||
return totals
|
||||
daily = icon_data.get("daily") or {}
|
||||
out: Dict[str, float] = {}
|
||||
for i, t in enumerate(daily.get("time") or []):
|
||||
arr = daily.get("precipitation_sum") or []
|
||||
if i < len(arr) and arr[i] is not None:
|
||||
out[str(t)[:10]] = float(arr[i])
|
||||
return out
|
||||
Reference in New Issue
Block a user