#!/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