Backup automatico script del 2026-02-22 07:00
This commit is contained in:
@@ -55,11 +55,18 @@ MODEL_NAMES = {
|
||||
"italia_meteo_arpae_icon_2i": "ICON Italia (ARPAE 2i)"
|
||||
}
|
||||
|
||||
# Per Casa/Italia: forecast_days per modello lungo termine (come Agent Irrigazione / OPEN_METEO_MODELS.md)
|
||||
LONG_TERM_FORECAST_DAYS = {
|
||||
"italia_meteo_arpae_icon_2i": 10,
|
||||
"ecmwf_ifs": 10,
|
||||
"meteofrance_seamless": 4,
|
||||
}
|
||||
|
||||
def choose_models_by_country(cc, is_home=False):
|
||||
"""
|
||||
Seleziona modelli meteo ottimali.
|
||||
- Per Casa e Italia: solo ICON Italia (ARPAE 2i); AROME HD non copre San Marino.
|
||||
- Per altre località: usa best match di Open-Meteo (senza specificare models).
|
||||
- Per Casa e Italia: 0-2d mediana ICON Italia + AROME HD; 3-10d mediana ICON Italia + ECMWF IFS + ARPEGE.
|
||||
- Per altre località: best match Open-Meteo.
|
||||
Ritorna (short_term_models, long_term_models)
|
||||
"""
|
||||
cc = cc.upper() if cc else "UNKNOWN"
|
||||
@@ -67,8 +74,11 @@ def choose_models_by_country(cc, is_home=False):
|
||||
long_term_default = ["gfs_global", "ecmwf_ifs04"]
|
||||
|
||||
if is_home or cc == "IT":
|
||||
# ICON Italia (0–72h) + ECMWF IFS per i giorni successivi (dove Icon Italia non arriva)
|
||||
return ["italia_meteo_arpae_icon_2i"], ["ecmwf_ifs"]
|
||||
# 0-2d: due modelli ad alta risoluzione (mediana). 3-10d: tre modelli (mediana, come Irrigazione).
|
||||
return (
|
||||
["italia_meteo_arpae_icon_2i", "meteofrance_arome_france_hd"],
|
||||
["italia_meteo_arpae_icon_2i", "ecmwf_ifs", "meteofrance_seamless"],
|
||||
)
|
||||
else:
|
||||
return None, long_term_default
|
||||
|
||||
@@ -153,22 +163,28 @@ def get_weather_multi_model(lat, lon, short_term_models, long_term_models, forec
|
||||
except:
|
||||
results["best_match"] = None
|
||||
else:
|
||||
# Modelli specifici (per Casa: AROME + ICON, per Italia: ICON ARPAE)
|
||||
# Modelli specifici: 0-2d ICON Italia + AROME HD (mediana); AROME HD solo 2 giorni
|
||||
for model in short_term_models:
|
||||
url = "https://api.open-meteo.com/v1/forecast"
|
||||
# ICON Italia (ARPAE 2i): parametri come da API, senza precipitation_probability
|
||||
if model == "italia_meteo_arpae_icon_2i":
|
||||
hourly_params = "rain,showers,snowfall,snow_depth,precipitation,temperature_2m,weathercode,windspeed_10m,winddirection_10m"
|
||||
daily_params = "temperature_2m_max,temperature_2m_min,showers_sum,rain_sum,snowfall_sum,precipitation_sum,precipitation_hours,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
|
||||
fd_short = min(forecast_days, 7)
|
||||
elif model == "meteofrance_arome_france_hd":
|
||||
# AROME HD: 2 giorni, set variabili ridotto (no snow_depth/showers in output)
|
||||
hourly_params = "temperature_2m,precipitation,snowfall,rain,weathercode,windspeed_10m,winddirection_10m"
|
||||
daily_params = "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,snowfall_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
|
||||
fd_short = 2
|
||||
else:
|
||||
hourly_params = "temperature_2m,precipitation,snowfall,rain,snow_depth,weathercode,windspeed_10m,windgusts_10m,winddirection_10m,dewpoint_2m,relative_humidity_2m,cloud_cover,soil_temperature_0cm"
|
||||
daily_params = "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,snowfall_sum,showers_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
|
||||
fd_short = min(forecast_days, 3)
|
||||
params = {
|
||||
"latitude": lat, "longitude": lon,
|
||||
"hourly": hourly_params,
|
||||
"daily": daily_params,
|
||||
"timezone": timezone if timezone else TZ_STR, "models": model,
|
||||
"forecast_days": min(forecast_days, 7) if model == "italia_meteo_arpae_icon_2i" else min(forecast_days, 3)
|
||||
"forecast_days": fd_short
|
||||
}
|
||||
try:
|
||||
resp = open_meteo_get(url, params=params, timeout=(5, 20))
|
||||
@@ -218,21 +234,25 @@ def get_weather_multi_model(lat, lon, short_term_models, long_term_models, forec
|
||||
except:
|
||||
results[model] = None
|
||||
|
||||
# Recupera modelli a lungo termine (dopo 72h, dove Icon Italia non arriva)
|
||||
# Recupera modelli a lungo termine (3-10d): tre modelli per mediana (come Agent Irrigazione)
|
||||
for model in (long_term_models or []):
|
||||
url = "https://api.open-meteo.com/v1/forecast"
|
||||
# ECMWF IFS: parametri come da API (rain, showers, snowfall) + campi necessari per il report
|
||||
fd_long = LONG_TERM_FORECAST_DAYS.get(model, forecast_days)
|
||||
if model == "ecmwf_ifs":
|
||||
hourly_params = "rain,showers,snowfall,precipitation,temperature_2m,weathercode,windspeed_10m,winddirection_10m"
|
||||
daily_params = "temperature_2m_max,temperature_2m_min,showers_sum,rain_sum,snowfall_sum,precipitation_sum,precipitation_hours,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
|
||||
elif model == "meteofrance_seamless":
|
||||
hourly_params = "temperature_2m,precipitation,snowfall,rain,weathercode,windspeed_10m,windgusts_10m,winddirection_10m"
|
||||
daily_params = "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,snowfall_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
|
||||
else:
|
||||
hourly_params = "temperature_2m,precipitation,snowfall,rain,snow_depth,weathercode,windspeed_10m,windgusts_10m,winddirection_10m,dewpoint_2m,relative_humidity_2m,cloud_cover,soil_temperature_0cm"
|
||||
daily_params = "temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_hours,snowfall_sum,showers_sum,rain_sum,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
|
||||
# ICON Italia e altri
|
||||
hourly_params = "rain,showers,snowfall,snow_depth,precipitation,temperature_2m,weathercode,windspeed_10m,winddirection_10m"
|
||||
daily_params = "temperature_2m_max,temperature_2m_min,showers_sum,rain_sum,snowfall_sum,precipitation_sum,precipitation_hours,weathercode,winddirection_10m_dominant,windspeed_10m_max,windgusts_10m_max"
|
||||
params = {
|
||||
"latitude": lat, "longitude": lon,
|
||||
"hourly": hourly_params,
|
||||
"daily": daily_params,
|
||||
"timezone": timezone if timezone else TZ_STR, "models": model, "forecast_days": forecast_days
|
||||
"timezone": timezone if timezone else TZ_STR, "models": model, "forecast_days": fd_long
|
||||
}
|
||||
try:
|
||||
resp = open_meteo_get(url, params=params, timeout=(5, 25))
|
||||
@@ -265,6 +285,144 @@ def get_weather_multi_model(lat, lon, short_term_models, long_term_models, forec
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _normalize_time_key(t):
|
||||
"""Normalizza timestamp per confronto (YYYY-MM-DDTHH:MM)."""
|
||||
if not t or not isinstance(t, str):
|
||||
return str(t) if t else ""
|
||||
return t.strip()[:16]
|
||||
|
||||
|
||||
def _median_or_single(values):
|
||||
"""Mediana dei valori numerici; ignora None. Con 2 valori restituisce la media dei due."""
|
||||
nums = [float(v) for v in values if v is not None]
|
||||
if not nums:
|
||||
return None
|
||||
if len(nums) == 1:
|
||||
return nums[0]
|
||||
return median(nums)
|
||||
|
||||
|
||||
# Chiavi che esistono solo su ICON Italia (no merge, si tiene il valore da quel modello)
|
||||
HOURLY_KEYS_ICON_ONLY = ["snow_depth", "showers"]
|
||||
DAILY_KEYS_ICON_ONLY = ["showers_sum"]
|
||||
|
||||
|
||||
def _merge_hourly_median(hourly_by_model, single_source_keys=None, single_source_model=None):
|
||||
"""
|
||||
Unisce hourly da più modelli: mediana per ogni timestamp.
|
||||
single_source_keys: per queste chiavi si prende il valore solo da single_source_model (es. ICON Italia per snow_depth, showers).
|
||||
"""
|
||||
single_source_keys = single_source_keys or []
|
||||
time_idx = {}
|
||||
all_times = []
|
||||
for _model, h in hourly_by_model:
|
||||
times = h.get("time", []) or []
|
||||
for t in times:
|
||||
k = _normalize_time_key(str(t)) if t else ""
|
||||
if k and k not in time_idx:
|
||||
time_idx[k] = len(all_times)
|
||||
all_times.append(t if isinstance(t, str) else k)
|
||||
if not all_times:
|
||||
return {"time": [], "temperature_2m": [], "precipitation": [], "snowfall": [], "rain": [], "weathercode": [], "windspeed_10m": [], "winddirection_10m": [], "snow_depth": [], "dewpoint_2m": [], "cloud_cover": [], "soil_temperature_0cm": []}
|
||||
# Raccogli tutte le chiavi numeriche dal primo modello che le ha
|
||||
all_keys = []
|
||||
for _model, h in hourly_by_model:
|
||||
for key in (h.keys() - {"time"}):
|
||||
if key not in all_keys:
|
||||
all_keys.append(key)
|
||||
out = {"time": all_times}
|
||||
for key in all_keys:
|
||||
out[key] = []
|
||||
for ref_t in all_times:
|
||||
ref_k = _normalize_time_key(str(ref_t))
|
||||
if key in single_source_keys and single_source_model:
|
||||
val = None
|
||||
for m, h in hourly_by_model:
|
||||
if m != single_source_model:
|
||||
continue
|
||||
times = h.get("time", []) or []
|
||||
arr = h.get(key, []) or []
|
||||
for i, t in enumerate(times):
|
||||
if _normalize_time_key(str(t)) == ref_k and i < len(arr) and arr[i] is not None:
|
||||
try:
|
||||
val = float(arr[i]) if key != "weathercode" else (int(arr[i]) if arr[i] is not None else None)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
break
|
||||
break
|
||||
out[key].append(val)
|
||||
else:
|
||||
vals = []
|
||||
for _m, h in hourly_by_model:
|
||||
times = h.get("time", []) or []
|
||||
arr = h.get(key, []) or []
|
||||
for i, t in enumerate(times):
|
||||
if _normalize_time_key(str(t)) == ref_k and i < len(arr) and arr[i] is not None:
|
||||
try:
|
||||
vals.append(float(arr[i]))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
break
|
||||
out[key].append(_median_or_single(vals) if vals else None)
|
||||
return out
|
||||
|
||||
|
||||
def _merge_daily_median(daily_by_model, single_source_keys=None, single_source_model=None):
|
||||
"""Unisce daily da più modelli: mediana per data. single_source_keys: valore solo da single_source_model."""
|
||||
single_source_keys = single_source_keys or []
|
||||
time_idx = {}
|
||||
all_times = []
|
||||
for _model, d in daily_by_model:
|
||||
times = d.get("time", []) or []
|
||||
for t in times:
|
||||
key = str(t)[:10] if t else ""
|
||||
if key and key not in time_idx:
|
||||
time_idx[key] = len(all_times)
|
||||
all_times.append(key)
|
||||
if not all_times:
|
||||
return {"time": [], "temperature_2m_max": [], "temperature_2m_min": [], "precipitation_sum": [], "precipitation_hours": [], "snowfall_sum": [], "showers_sum": [], "rain_sum": [], "weathercode": [], "winddirection_10m_dominant": [], "windspeed_10m_max": [], "windgusts_10m_max": []}
|
||||
all_keys = []
|
||||
for _model, d in daily_by_model:
|
||||
for key in (d.keys() - {"time"}):
|
||||
if key not in all_keys:
|
||||
all_keys.append(key)
|
||||
out = {"time": all_times}
|
||||
for key in all_keys:
|
||||
out[key] = []
|
||||
for date_str in all_times:
|
||||
if key in single_source_keys and single_source_model:
|
||||
val = None
|
||||
for m, d in daily_by_model:
|
||||
if m != single_source_model:
|
||||
continue
|
||||
times = d.get("time", []) or []
|
||||
arr = d.get(key, []) or []
|
||||
for i, t in enumerate(times):
|
||||
if str(t)[:10] == date_str and i < len(arr) and arr[i] is not None:
|
||||
try:
|
||||
val = float(arr[i]) if key != "weathercode" else (int(arr[i]) if arr[i] is not None else None)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
break
|
||||
break
|
||||
out[key].append(val)
|
||||
else:
|
||||
vals = []
|
||||
for _m, d in daily_by_model:
|
||||
times = d.get("time", []) or []
|
||||
arr = d.get(key, []) or []
|
||||
for i, t in enumerate(times):
|
||||
if str(t)[:10] == date_str and i < len(arr) and arr[i] is not None:
|
||||
try:
|
||||
vals.append(float(arr[i]))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
break
|
||||
out[key].append(_median_or_single(vals) if vals else None)
|
||||
return out
|
||||
|
||||
|
||||
def merge_multi_model_forecast(models_data, forecast_days=10):
|
||||
"""Combina dati da modelli a breve e lungo termine in un forecast unificato"""
|
||||
merged = {
|
||||
@@ -299,183 +457,141 @@ def merge_multi_model_forecast(models_data, forecast_days=10):
|
||||
"models_used": []
|
||||
}
|
||||
|
||||
# Trova modello a breve termine disponibile (cerca tutti i modelli con type "short_term")
|
||||
# Priorità: ICON Italia per snow_depth, altrimenti primo disponibile
|
||||
short_term_data = None
|
||||
short_term_model = None
|
||||
icon_italia_data = None
|
||||
icon_italia_model = None
|
||||
cutoff_day = 2 # 0-2d alta risoluzione, 3-10d mediana tre modelli
|
||||
short_term_list = [(m, models_data[m]) for m in models_data if models_data.get(m) and models_data[m].get("model_type") == "short_term"]
|
||||
long_term_list = [(m, models_data[m]) for m in models_data if models_data.get(m) and models_data[m].get("model_type") == "long_term"]
|
||||
|
||||
# Prima cerca ICON Italia (ha snow_depth quando disponibile)
|
||||
# Cerca anche altri modelli che potrebbero avere snow_depth (icon_d2, etc.)
|
||||
for model in models_data.keys():
|
||||
if models_data[model] and models_data[model].get("model_type") == "short_term":
|
||||
# Priorità a ICON Italia, ma cerca anche altri modelli con snow_depth
|
||||
if model == "italia_meteo_arpae_icon_2i":
|
||||
icon_italia_data = models_data[model]
|
||||
icon_italia_model = model
|
||||
# ICON-D2 può avere anche snow_depth
|
||||
elif model == "icon_d2" and icon_italia_data is None:
|
||||
# Usa ICON-D2 come fallback se ICON Italia non disponibile
|
||||
hourly_data = models_data[model].get("hourly", {})
|
||||
snow_depth_values = hourly_data.get("snow_depth", []) if hourly_data else []
|
||||
# Verifica se ha dati di snow_depth validi
|
||||
has_valid_snow_depth = False
|
||||
if snow_depth_values:
|
||||
for sd in snow_depth_values[:24]:
|
||||
if sd is not None:
|
||||
try:
|
||||
if float(sd) > 0:
|
||||
has_valid_snow_depth = True
|
||||
break
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
if has_valid_snow_depth:
|
||||
icon_italia_data = models_data[model]
|
||||
icon_italia_model = model
|
||||
|
||||
# Poi cerca primo modello disponibile (per altri parametri)
|
||||
for model in models_data.keys():
|
||||
if models_data[model] and models_data[model].get("model_type") == "short_term":
|
||||
short_term_data = models_data[model]
|
||||
short_term_model = model
|
||||
break
|
||||
|
||||
# Trova modello a lungo termine disponibile (cerca tutti i modelli con type "long_term")
|
||||
long_term_data = None
|
||||
long_term_model = None
|
||||
for model in models_data.keys():
|
||||
if models_data[model] and models_data[model].get("model_type") == "long_term":
|
||||
long_term_data = models_data[model]
|
||||
long_term_model = model
|
||||
break
|
||||
|
||||
if not short_term_data and not long_term_data:
|
||||
if not short_term_list and not long_term_list:
|
||||
return None
|
||||
|
||||
# Usa dati a breve termine per primi 2-3 giorni, poi passa a lungo termine
|
||||
cutoff_day = 2 # Usa modelli ad alta risoluzione per primi 2 giorni
|
||||
daily_keys = list(merged["daily"].keys())
|
||||
hourly_keys = list(merged["hourly"].keys())
|
||||
|
||||
if short_term_data:
|
||||
# Gestisci best_match o modelli specifici
|
||||
if short_term_model == "best_match":
|
||||
model_display = "Best Match"
|
||||
else:
|
||||
model_display = MODEL_NAMES.get(short_term_model, short_term_model)
|
||||
short_daily = short_term_data.get("daily", {})
|
||||
short_hourly = short_term_data.get("hourly", {})
|
||||
# Prendi dati daily: tutti i giorni se è l'unico modello, altrimenti primi cutoff_day+1
|
||||
short_daily_times_all = short_daily.get("time", [])
|
||||
short_daily_times = short_daily_times_all[:cutoff_day+1] if long_term_data else short_daily_times_all
|
||||
|
||||
# Verifica se ICON Italia ha dati di snow_depth (controllo diretto, non solo il flag)
|
||||
has_icon_snow_depth = False
|
||||
if icon_italia_data:
|
||||
icon_hourly = icon_italia_data.get("hourly", {})
|
||||
icon_snow_depth = icon_hourly.get("snow_depth", []) if icon_hourly else []
|
||||
if icon_snow_depth:
|
||||
for sd in icon_snow_depth[:72]: # Controlla prime 72h
|
||||
if sd is not None:
|
||||
try:
|
||||
if float(sd) > 0:
|
||||
has_icon_snow_depth = True
|
||||
break
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
num_days = len(short_daily_times)
|
||||
if has_icon_snow_depth or (icon_italia_data and icon_italia_data.get("has_snow_depth_data")):
|
||||
icon_display = MODEL_NAMES.get("italia_meteo_arpae_icon_2i", "ICON Italia")
|
||||
merged["models_used"].append(f"{model_display} + {icon_display} (snow_depth) (0-{num_days}d)")
|
||||
else:
|
||||
merged["models_used"].append(f"{model_display} (0-{num_days}d)")
|
||||
|
||||
for i, day_time in enumerate(short_daily_times):
|
||||
merged["daily"]["time"].append(day_time)
|
||||
for key in ["temperature_2m_max", "temperature_2m_min", "precipitation_sum", "precipitation_hours", "snowfall_sum", "showers_sum", "rain_sum", "weathercode", "winddirection_10m_dominant", "windspeed_10m_max", "windgusts_10m_max"]:
|
||||
val = short_daily.get(key, [])[i] if i < len(short_daily.get(key, [])) else None
|
||||
merged["daily"][key].append(val)
|
||||
|
||||
# Prendi dati hourly dal modello a breve termine
|
||||
# Priorità: usa snow_depth da ICON Italia se disponibile, altrimenti dal modello principale
|
||||
short_hourly_times = short_hourly.get("time", [])
|
||||
icon_italia_hourly = icon_italia_data.get("hourly", {}) if icon_italia_data else {}
|
||||
icon_italia_hourly_times = icon_italia_hourly.get("time", []) if icon_italia_hourly else []
|
||||
icon_italia_snow_depth = icon_italia_hourly.get("snow_depth", []) if icon_italia_hourly else []
|
||||
# Crea mappa timestamp -> snow_depth per ICON Italia (per corrispondenza esatta o approssimata)
|
||||
icon_snow_depth_map = {}
|
||||
if icon_italia_hourly_times and icon_italia_snow_depth:
|
||||
for idx, ts in enumerate(icon_italia_hourly_times):
|
||||
if idx < len(icon_italia_snow_depth) and icon_italia_snow_depth[idx] is not None:
|
||||
try:
|
||||
val_cm = float(icon_italia_snow_depth[idx])
|
||||
if val_cm >= 0: # Solo valori validi (già in cm)
|
||||
icon_snow_depth_map[ts] = val_cm
|
||||
except (ValueError, TypeError):
|
||||
def ensure_merged_keys(merged, daily_times, hourly_times):
|
||||
for k in daily_keys:
|
||||
if k == "time":
|
||||
continue
|
||||
while len(merged["daily"][k]) < len(merged["daily"]["time"]):
|
||||
merged["daily"][k].append(None)
|
||||
for k in hourly_keys:
|
||||
if k == "time":
|
||||
continue
|
||||
while len(merged["hourly"][k]) < len(merged["hourly"]["time"]):
|
||||
merged["hourly"][k].append(None)
|
||||
|
||||
# ---- 0-2 giorni: uno o due modelli short-term ----
|
||||
if short_term_list:
|
||||
if len(short_term_list) >= 2:
|
||||
# Mediana ICON Italia + AROME HD; snow_depth e showers solo da ICON Italia
|
||||
short_daily_by_model = [(m, d.get("daily", {}) or {}) for m, d in short_term_list]
|
||||
short_hourly_by_model = [(m, d.get("hourly", {}) or {}) for m, d in short_term_list]
|
||||
merged_short_daily = _merge_daily_median(short_daily_by_model, single_source_keys=DAILY_KEYS_ICON_ONLY, single_source_model="italia_meteo_arpae_icon_2i")
|
||||
merged_short_hourly = _merge_hourly_median(short_hourly_by_model, single_source_keys=HOURLY_KEYS_ICON_ONLY, single_source_model="italia_meteo_arpae_icon_2i")
|
||||
short_daily_times = (merged_short_daily.get("time") or [])[:cutoff_day + 1] if long_term_list else (merged_short_daily.get("time") or [])
|
||||
short_hourly_times = merged_short_hourly.get("time") or []
|
||||
cutoff_h = (cutoff_day + 1) * 24 if long_term_list else len(short_hourly_times)
|
||||
short_hourly_times = short_hourly_times[:cutoff_h]
|
||||
names_short = " + ".join(MODEL_NAMES.get(m, m) for m, _ in short_term_list[:2])
|
||||
merged["models_used"].append(f"{names_short} (mediana) (0-{len(short_daily_times)}d)")
|
||||
for i, day_time in enumerate(short_daily_times):
|
||||
merged["daily"]["time"].append(day_time)
|
||||
for key in daily_keys:
|
||||
if key == "time":
|
||||
continue
|
||||
|
||||
cutoff_hour = (cutoff_day + 1) * 24 if long_term_data else len(short_hourly_times)
|
||||
for i, hour_time in enumerate(short_hourly_times[:cutoff_hour]):
|
||||
merged["hourly"]["time"].append(hour_time)
|
||||
for key in ["temperature_2m", "precipitation", "snowfall", "rain", "weathercode", "windspeed_10m", "winddirection_10m", "dewpoint_2m", "cloud_cover", "soil_temperature_0cm"]:
|
||||
val = short_hourly.get(key, [])[i] if i < len(short_hourly.get(key, [])) else None
|
||||
merged["hourly"][key].append(val)
|
||||
# Per snow_depth: usa ICON Italia se disponibile (corrispondenza per timestamp), altrimenti modello principale
|
||||
# NOTA: I valori sono già convertiti in cm durante il recupero dall'API
|
||||
val_snow_depth = None
|
||||
# Cerca corrispondenza esatta per timestamp
|
||||
if hour_time in icon_snow_depth_map:
|
||||
# Usa snow_depth da ICON Italia per questo timestamp (già in cm)
|
||||
val_snow_depth = icon_snow_depth_map[hour_time]
|
||||
else:
|
||||
# Fallback 1: cerca corrispondenza per ora approssimata (se i timestamp non corrispondono esattamente)
|
||||
# Estrai solo la parte ora (YYYY-MM-DDTHH) per corrispondenza approssimata
|
||||
hour_time_base = hour_time[:13] if len(hour_time) >= 13 else hour_time # "2025-01-09T12"
|
||||
for icon_ts, icon_val in icon_snow_depth_map.items():
|
||||
if icon_ts.startswith(hour_time_base):
|
||||
val_snow_depth = icon_val
|
||||
break
|
||||
# Fallback 2: se non trovato, cerca il valore più vicino nello stesso giorno
|
||||
if val_snow_depth is None and hour_time_base:
|
||||
day_date_str = hour_time[:10] if len(hour_time) >= 10 else None # "2025-01-09"
|
||||
if day_date_str:
|
||||
# Cerca tutti i valori di ICON Italia per lo stesso giorno
|
||||
same_day_values = [v for ts, v in icon_snow_depth_map.items() if ts.startswith(day_date_str)]
|
||||
if same_day_values:
|
||||
# Usa il primo valore disponibile per quel giorno (approssimazione)
|
||||
val_snow_depth = same_day_values[0]
|
||||
# Fallback 3: usa snow_depth dal modello principale se ICON Italia non disponibile
|
||||
if val_snow_depth is None and i < len(short_hourly.get("snow_depth", [])):
|
||||
val_snow_depth = short_hourly.get("snow_depth", [])[i]
|
||||
merged["hourly"]["snow_depth"].append(val_snow_depth)
|
||||
arr = merged_short_daily.get(key, [])
|
||||
merged["daily"][key].append(arr[i] if i < len(arr) else None)
|
||||
for i, hour_time in enumerate(short_hourly_times):
|
||||
merged["hourly"]["time"].append(hour_time)
|
||||
for key in hourly_keys:
|
||||
if key == "time":
|
||||
continue
|
||||
arr = merged_short_hourly.get(key, [])
|
||||
merged["hourly"][key].append(arr[i] if i < len(arr) else None)
|
||||
else:
|
||||
# Un solo modello short-term (es. best_match o fallback)
|
||||
short_term_model, short_term_data = short_term_list[0]
|
||||
short_daily = short_term_data.get("daily", {}) or {}
|
||||
short_hourly = short_term_data.get("hourly", {}) or {}
|
||||
short_daily_times_all = short_daily.get("time", []) or []
|
||||
short_daily_times = short_daily_times_all[:cutoff_day + 1] if long_term_list else short_daily_times_all
|
||||
model_display = "Best Match" if short_term_model == "best_match" else MODEL_NAMES.get(short_term_model, short_term_model)
|
||||
merged["models_used"].append(f"{model_display} (0-{len(short_daily_times)}d)")
|
||||
for i, day_time in enumerate(short_daily_times):
|
||||
merged["daily"]["time"].append(day_time)
|
||||
for key in daily_keys:
|
||||
if key == "time":
|
||||
continue
|
||||
arr = short_daily.get(key, [])
|
||||
merged["daily"][key].append(arr[i] if i < len(arr) else None)
|
||||
short_hourly_times = short_hourly.get("time", []) or []
|
||||
cutoff_h = (cutoff_day + 1) * 24 if long_term_list else len(short_hourly_times)
|
||||
for i, hour_time in enumerate(short_hourly_times[:cutoff_h]):
|
||||
merged["hourly"]["time"].append(hour_time)
|
||||
for key in hourly_keys:
|
||||
if key == "time":
|
||||
continue
|
||||
arr = short_hourly.get(key, [])
|
||||
merged["hourly"][key].append(arr[i] if i < len(arr) else None)
|
||||
ensure_merged_keys(merged, merged["daily"]["time"], merged["hourly"]["time"])
|
||||
|
||||
if long_term_data:
|
||||
merged["models_used"].append(f"{MODEL_NAMES.get(long_term_model, long_term_model)} ({cutoff_day+1}-{forecast_days}d)")
|
||||
long_daily = long_term_data.get("daily", {})
|
||||
long_hourly = long_term_data.get("hourly", {})
|
||||
|
||||
# Prendi dati daily dal modello a lungo termine per i giorni successivi
|
||||
long_daily_times = long_daily.get("time", [])
|
||||
start_idx = cutoff_day + 1
|
||||
|
||||
for i in range(start_idx, min(len(long_daily_times), forecast_days)):
|
||||
merged["daily"]["time"].append(long_daily_times[i])
|
||||
for key in ["temperature_2m_max", "temperature_2m_min", "precipitation_sum", "precipitation_hours", "snowfall_sum", "showers_sum", "rain_sum", "weathercode", "winddirection_10m_dominant", "windspeed_10m_max", "windgusts_10m_max"]:
|
||||
val = long_daily.get(key, [])[i] if i < len(long_daily.get(key, [])) else None
|
||||
merged["daily"][key].append(val)
|
||||
|
||||
# Per i dati hourly, completa con dati a lungo termine se necessario
|
||||
long_hourly_times = long_hourly.get("time", [])
|
||||
current_hourly_count = len(merged["hourly"]["time"])
|
||||
needed_hours = forecast_days * 24
|
||||
|
||||
if current_hourly_count < needed_hours:
|
||||
start_hour_idx = current_hourly_count
|
||||
# ---- 3-10 giorni: uno o più modelli long-term ----
|
||||
if long_term_list:
|
||||
if len(long_term_list) >= 2:
|
||||
long_daily_by_model = [(m, d.get("daily", {}) or {}) for m, d in long_term_list]
|
||||
long_hourly_by_model = [(m, d.get("hourly", {}) or {}) for m, d in long_term_list]
|
||||
merged_long_daily = _merge_daily_median(long_daily_by_model, single_source_keys=DAILY_KEYS_ICON_ONLY, single_source_model="italia_meteo_arpae_icon_2i")
|
||||
merged_long_hourly = _merge_hourly_median(long_hourly_by_model, single_source_keys=HOURLY_KEYS_ICON_ONLY, single_source_model="italia_meteo_arpae_icon_2i")
|
||||
long_daily_times = merged_long_daily.get("time") or []
|
||||
long_hourly_times = merged_long_hourly.get("time") or []
|
||||
names_long = " + ".join(MODEL_NAMES.get(m, m) for m, _ in long_term_list[:3])
|
||||
merged["models_used"].append(f"{names_long} (mediana) ({cutoff_day+1}-{forecast_days}d)")
|
||||
start_idx = cutoff_day + 1
|
||||
for i, day_time in enumerate(long_daily_times):
|
||||
day_num = i
|
||||
if day_num < start_idx:
|
||||
continue
|
||||
if day_num >= forecast_days:
|
||||
break
|
||||
merged["daily"]["time"].append(day_time)
|
||||
for key in daily_keys:
|
||||
if key == "time":
|
||||
continue
|
||||
arr = merged_long_daily.get(key, [])
|
||||
merged["daily"][key].append(arr[i] if i < len(arr) else None)
|
||||
start_hour_idx = (cutoff_day + 1) * 24
|
||||
needed_hours = forecast_days * 24
|
||||
for i in range(start_hour_idx, min(len(long_hourly_times), needed_hours)):
|
||||
merged["hourly"]["time"].append(long_hourly_times[i])
|
||||
for key in ["temperature_2m", "precipitation", "snowfall", "snow_depth", "rain", "weathercode", "windspeed_10m", "winddirection_10m", "dewpoint_2m", "cloud_cover", "soil_temperature_0cm"]:
|
||||
val = long_hourly.get(key, [])[i] if i < len(long_hourly.get(key, [])) else None
|
||||
merged["hourly"][key].append(val)
|
||||
for key in hourly_keys:
|
||||
if key == "time":
|
||||
continue
|
||||
arr = merged_long_hourly.get(key, [])
|
||||
merged["hourly"][key].append(arr[i] if i < len(arr) else None)
|
||||
else:
|
||||
long_term_model, long_term_data = long_term_list[0]
|
||||
long_daily = long_term_data.get("daily", {}) or {}
|
||||
long_hourly = long_term_data.get("hourly", {}) or {}
|
||||
merged["models_used"].append(f"{MODEL_NAMES.get(long_term_model, long_term_model)} ({cutoff_day+1}-{forecast_days}d)")
|
||||
long_daily_times = long_daily.get("time", []) or []
|
||||
start_idx = cutoff_day + 1
|
||||
for i in range(start_idx, min(len(long_daily_times), forecast_days)):
|
||||
merged["daily"]["time"].append(long_daily_times[i])
|
||||
for key in daily_keys:
|
||||
if key == "time":
|
||||
continue
|
||||
arr = long_daily.get(key, [])
|
||||
merged["daily"][key].append(arr[i] if i < len(arr) else None)
|
||||
long_hourly_times = long_hourly.get("time", []) or []
|
||||
current_hourly_count = len(merged["hourly"]["time"])
|
||||
needed_hours = forecast_days * 24
|
||||
for i in range(current_hourly_count, min(len(long_hourly_times), needed_hours)):
|
||||
merged["hourly"]["time"].append(long_hourly_times[i])
|
||||
for key in hourly_keys:
|
||||
if key == "time":
|
||||
continue
|
||||
arr = long_hourly.get(key, [])
|
||||
merged["hourly"][key].append(arr[i] if i < len(arr) else None)
|
||||
ensure_merged_keys(merged, merged["daily"]["time"], merged["hourly"]["time"])
|
||||
|
||||
return merged
|
||||
|
||||
|
||||
Reference in New Issue
Block a user