Files

199 lines
6.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Precipitazione unificata Open-Meteo per Casa (San Marino).
Reference 072 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