Backup automatico script del 2026-07-12 07:00
This commit is contained in:
@@ -25,11 +25,17 @@ def setup_logger() -> logging.Logger:
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.handlers.clear()
|
||||
|
||||
fh = RotatingFileHandler(LOG_FILE, maxBytes=1_000_000, backupCount=5, encoding="utf-8")
|
||||
fh.setLevel(logging.DEBUG)
|
||||
fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
||||
fh.setFormatter(fmt)
|
||||
logger.addHandler(fh)
|
||||
log_path = os.environ.get("ROAD_WEATHER_LOG", LOG_FILE)
|
||||
try:
|
||||
fh = RotatingFileHandler(log_path, maxBytes=1_000_000, backupCount=5, encoding="utf-8")
|
||||
fh.setLevel(logging.DEBUG)
|
||||
fh.setFormatter(fmt)
|
||||
logger.addHandler(fh)
|
||||
except OSError:
|
||||
sh = logging.StreamHandler()
|
||||
sh.setFormatter(fmt)
|
||||
logger.addHandler(sh)
|
||||
|
||||
return logger
|
||||
|
||||
@@ -121,14 +127,43 @@ WEATHER_CODES = {
|
||||
# =============================================================================
|
||||
|
||||
def get_google_maps_api_key() -> Optional[str]:
|
||||
"""Ottiene la chiave API di Google Maps da variabile d'ambiente."""
|
||||
"""Ottiene la chiave API di Google Maps da variabile d'ambiente o file condiviso."""
|
||||
api_key = os.environ.get('GOOGLE_MAPS_API_KEY', '').strip()
|
||||
if api_key:
|
||||
return api_key
|
||||
api_key = os.environ.get('GOOGLE_API_KEY', '').strip()
|
||||
if api_key:
|
||||
return api_key
|
||||
# Debug: verifica tutte le variabili d'ambiente che contengono GOOGLE
|
||||
|
||||
for path in (
|
||||
os.environ.get("GOOGLE_MAPS_API_KEY_FILE", ""),
|
||||
"/etc/google_maps_api_key",
|
||||
os.path.expanduser("~/.google_maps_api_key"),
|
||||
os.path.join(SCRIPT_DIR, ".env"),
|
||||
):
|
||||
if not path or not os.path.isfile(path):
|
||||
continue
|
||||
try:
|
||||
if path.endswith(".env"):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("GOOGLE_MAPS_API_KEY="):
|
||||
v = line.split("=", 1)[1].strip().strip("'\"")
|
||||
if v:
|
||||
return v
|
||||
elif line.startswith("GOOGLE_API_KEY="):
|
||||
v = line.split("=", 1)[1].strip().strip("'\"")
|
||||
if v:
|
||||
return v
|
||||
else:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
v = f.read().strip()
|
||||
if v:
|
||||
return v
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
if os.environ.get('DEBUG_GOOGLE_MAPS', ''):
|
||||
google_vars = {k: v[:10] + '...' if len(v) > 10 else v for k, v in os.environ.items() if 'GOOGLE' in k.upper()}
|
||||
LOGGER.debug(f"Variabili GOOGLE trovate: {google_vars}")
|
||||
@@ -433,7 +468,7 @@ def get_weather_data(lat: float, lon: float, model_slug: str) -> Optional[Dict]:
|
||||
url = f"https://api.open-meteo.com/v1/forecast"
|
||||
|
||||
# Parametri base (aggiunto soil_temperature_0cm per analisi ghiaccio più accurata)
|
||||
hourly_params = "temperature_2m,relative_humidity_2m,precipitation,rain,showers,snowfall,weathercode,visibility,wind_speed_10m,wind_gusts_10m,soil_temperature_0cm,dew_point_2m"
|
||||
hourly_params = "temperature_2m,relative_humidity_2m,precipitation,rain,showers,snowfall,snow_depth,weathercode,visibility,wind_speed_10m,wind_gusts_10m,soil_temperature_0cm,dew_point_2m"
|
||||
|
||||
# Aggiungi CAPE se disponibile (AROME Seamless o ICON)
|
||||
if model_slug in ["meteofrance_seamless", "italia_meteo_arpae_icon_2i", "icon_eu"]:
|
||||
@@ -627,6 +662,242 @@ def analyze_past_24h_conditions(weather_data: Dict) -> Dict:
|
||||
}
|
||||
|
||||
|
||||
def summarize_point_weather(weather_data: Dict) -> Dict:
|
||||
"""Manto nevoso attuale e precipitazioni previste 12h/24h per un punto."""
|
||||
result = {
|
||||
"snow_depth_cm": None,
|
||||
"rain_12h_mm": 0.0,
|
||||
"rain_24h_mm": 0.0,
|
||||
"snow_12h_cm": 0.0,
|
||||
"snow_24h_cm": 0.0,
|
||||
}
|
||||
if not weather_data or "hourly" not in weather_data:
|
||||
return result
|
||||
|
||||
hourly = weather_data["hourly"]
|
||||
times = hourly.get("time", [])
|
||||
if not times:
|
||||
return result
|
||||
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
rain = hourly.get("rain", [])
|
||||
snowfall = hourly.get("snowfall", [])
|
||||
snow_depth = hourly.get("snow_depth", [])
|
||||
|
||||
timestamps = []
|
||||
for ts_str in times:
|
||||
try:
|
||||
if "Z" in ts_str:
|
||||
ts = datetime.datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
|
||||
else:
|
||||
ts = datetime.datetime.fromisoformat(ts_str)
|
||||
if ts.tzinfo is None:
|
||||
ts = ts.replace(tzinfo=datetime.timezone.utc)
|
||||
timestamps.append(ts)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
latest_depth_cm = None
|
||||
for i, ts in enumerate(timestamps):
|
||||
if i < len(snow_depth) and snow_depth[i] is not None and ts <= now:
|
||||
latest_depth_cm = float(snow_depth[i]) * 100.0
|
||||
|
||||
if ts < now:
|
||||
continue
|
||||
hours_ahead = (ts - now).total_seconds() / 3600.0
|
||||
if hours_ahead >= 24:
|
||||
continue
|
||||
r = rain[i] if i < len(rain) and rain[i] is not None else 0.0
|
||||
snow = snowfall[i] if i < len(snowfall) and snowfall[i] is not None else 0.0
|
||||
if hours_ahead < 12:
|
||||
result["rain_12h_mm"] += float(r)
|
||||
result["snow_12h_cm"] += float(snow)
|
||||
result["rain_24h_mm"] += float(r)
|
||||
result["snow_24h_cm"] += float(snow)
|
||||
|
||||
if latest_depth_cm is not None and latest_depth_cm >= 0.05:
|
||||
result["snow_depth_cm"] = round(latest_depth_cm, 1)
|
||||
result["rain_12h_mm"] = round(result["rain_12h_mm"], 1)
|
||||
result["rain_24h_mm"] = round(result["rain_24h_mm"], 1)
|
||||
result["snow_12h_cm"] = round(result["snow_12h_cm"], 1)
|
||||
result["snow_24h_cm"] = round(result["snow_24h_cm"], 1)
|
||||
return result
|
||||
|
||||
|
||||
RISK_BADGE_MAP = {
|
||||
"neve": ("❄️", "Neve"),
|
||||
"gelicidio": ("🔴🔴", "Gelicidio"),
|
||||
"ghiaccio": ("🔴", "Ghiaccio"),
|
||||
"brina": ("🟡", "Brina"),
|
||||
"pioggia": ("🌧️", "Pioggia"),
|
||||
"temporale": ("⛈️", "Temporale"),
|
||||
"vento": ("💨", "Vento"),
|
||||
"nebbia": ("🌫️", "Nebbia"),
|
||||
"grandine": ("🌨️", "Grandine"),
|
||||
"nessuno": ("✅", "Nessun rischio"),
|
||||
}
|
||||
|
||||
|
||||
def _first_dict(series):
|
||||
"""Prende il primo valore non-nullo (dict) da una serie pandas."""
|
||||
for val in series:
|
||||
if val is not None and (isinstance(val, dict) or (isinstance(val, str) and val != "")):
|
||||
return val
|
||||
return {}
|
||||
|
||||
|
||||
def _aggregate_route_points(df: pd.DataFrame):
|
||||
"""Raggruppa punti percorso con rischi effettivi e meteo riassuntivo."""
|
||||
max_risk_per_point = df.groupby("point_index").agg({
|
||||
"max_risk_level": "max",
|
||||
"point_name": "first",
|
||||
"past_24h": _first_dict,
|
||||
"weather_summary": _first_dict,
|
||||
}).sort_values("point_index")
|
||||
|
||||
seen_names = {}
|
||||
unique_indices = []
|
||||
for idx, row in max_risk_per_point.iterrows():
|
||||
point_name = row["point_name"]
|
||||
name_key = point_name.split("(")[0].strip()
|
||||
past_24h = row.get("past_24h", {}) if isinstance(row.get("past_24h"), dict) else {}
|
||||
has_snow_ice = past_24h.get("snow_present") or past_24h.get("ice_persistence_likely")
|
||||
|
||||
if name_key not in seen_names:
|
||||
seen_names[name_key] = idx
|
||||
unique_indices.append(idx)
|
||||
else:
|
||||
existing_idx = seen_names[name_key]
|
||||
existing_row = max_risk_per_point.loc[existing_idx]
|
||||
existing_past_24h = existing_row.get("past_24h", {}) if isinstance(existing_row.get("past_24h"), dict) else {}
|
||||
existing_has_snow_ice = existing_past_24h.get("snow_present") or existing_past_24h.get("ice_persistence_likely")
|
||||
if row["max_risk_level"] > existing_row["max_risk_level"]:
|
||||
unique_indices.remove(existing_idx)
|
||||
seen_names[name_key] = idx
|
||||
unique_indices.append(idx)
|
||||
elif row["max_risk_level"] == existing_row["max_risk_level"] and has_snow_ice and not existing_has_snow_ice:
|
||||
unique_indices.remove(existing_idx)
|
||||
seen_names[name_key] = idx
|
||||
unique_indices.append(idx)
|
||||
|
||||
max_risk_per_point = max_risk_per_point.loc[unique_indices]
|
||||
|
||||
effective_risk_levels_dict = {}
|
||||
for idx, row in max_risk_per_point.iterrows():
|
||||
level = int(row["max_risk_level"])
|
||||
past_24h = row.get("past_24h", {}) if isinstance(row.get("past_24h"), dict) else {}
|
||||
if level == 0 and past_24h:
|
||||
if past_24h.get("snow_present"):
|
||||
level = 4
|
||||
elif past_24h.get("ice_persistence_likely"):
|
||||
level = 2
|
||||
effective_risk_levels_dict[idx] = level
|
||||
|
||||
max_risk_per_point = max_risk_per_point.copy()
|
||||
max_risk_per_point["effective_risk_level"] = max_risk_per_point.index.map(effective_risk_levels_dict)
|
||||
|
||||
risks_per_point = {}
|
||||
for _, row in df[df["max_risk_level"] > 0].iterrows():
|
||||
point_idx = row["point_index"]
|
||||
if point_idx not in risks_per_point:
|
||||
risks_per_point[point_idx] = {}
|
||||
risk_type = row["risk_type"]
|
||||
risk_level = row["risk_level"]
|
||||
risk_desc = row["risk_description"]
|
||||
if risk_type not in risks_per_point[point_idx] or risks_per_point[point_idx][risk_type]["level"] < risk_level:
|
||||
risks_per_point[point_idx][risk_type] = {
|
||||
"type": risk_type,
|
||||
"desc": risk_desc,
|
||||
"level": risk_level,
|
||||
}
|
||||
|
||||
for idx, row in max_risk_per_point.iterrows():
|
||||
effective_risk = row.get("effective_risk_level", 0)
|
||||
max_risk = int(row["max_risk_level"])
|
||||
if effective_risk > 0 and max_risk == 0:
|
||||
if idx not in risks_per_point:
|
||||
risks_per_point[idx] = {}
|
||||
past_24h = row.get("past_24h", {}) if isinstance(row.get("past_24h"), dict) else {}
|
||||
if effective_risk >= 4:
|
||||
risk_type, risk_desc = "neve", "Neve presente"
|
||||
elif effective_risk == 2:
|
||||
risk_type = "ghiaccio"
|
||||
min_temp = past_24h.get("min_temp_2m")
|
||||
hours_below_2c = past_24h.get("hours_below_2c", 0)
|
||||
if min_temp is not None:
|
||||
risk_desc = f"Ghiaccio persistente (Tmin: {min_temp:.1f}°C, {hours_below_2c}h <2°C)"
|
||||
else:
|
||||
risk_desc = "Ghiaccio persistente"
|
||||
elif effective_risk == 1:
|
||||
risk_type = "brina"
|
||||
min_temp = past_24h.get("min_temp_2m")
|
||||
risk_desc = f"Brina possibile (Tmin: {min_temp:.1f}°C)" if min_temp is not None else "Brina possibile"
|
||||
else:
|
||||
continue
|
||||
risks_per_point[idx][risk_type] = {"type": risk_type, "desc": risk_desc, "level": effective_risk}
|
||||
|
||||
return max_risk_per_point, risks_per_point
|
||||
|
||||
|
||||
def _primary_risk_for_point(risks: Dict, effective_risk: int) -> Tuple[str, str, List[str]]:
|
||||
"""Ritorna (badge, label, descrizioni) per il punto."""
|
||||
type_order = ["neve", "gelicidio", "ghiaccio", "brina", "temporale", "pioggia", "vento", "nebbia", "grandine"]
|
||||
risk_list = sorted(risks.values(), key=lambda x: x.get("level", 0), reverse=True) if risks else []
|
||||
descriptions = [r.get("desc", "") for r in risk_list if r.get("desc")]
|
||||
|
||||
primary_type = risk_list[0]["type"] if risk_list else "nessuno"
|
||||
for t in type_order:
|
||||
if t in risks:
|
||||
primary_type = t
|
||||
break
|
||||
|
||||
if effective_risk >= 4 and "neve" not in risks:
|
||||
primary_type = "neve"
|
||||
elif effective_risk == 3 and primary_type == "nessuno":
|
||||
primary_type = "gelicidio"
|
||||
elif effective_risk == 2 and primary_type in ("nessuno", "brina"):
|
||||
primary_type = "ghiaccio"
|
||||
elif effective_risk == 1 and primary_type == "nessuno":
|
||||
primary_type = "brina"
|
||||
|
||||
badge, label = RISK_BADGE_MAP.get(primary_type, ("⚠️", primary_type.capitalize()))
|
||||
return badge, label, descriptions
|
||||
|
||||
|
||||
def build_route_points_table(df: pd.DataFrame) -> List[Dict]:
|
||||
"""Tabella punti notevoli per WebApp."""
|
||||
if df.empty:
|
||||
return []
|
||||
|
||||
max_risk_per_point, risks_per_point = _aggregate_route_points(df)
|
||||
points = []
|
||||
|
||||
for idx, row in max_risk_per_point.iterrows():
|
||||
effective_risk = int(row.get("effective_risk_level", 0))
|
||||
risks = risks_per_point.get(idx, {})
|
||||
badge, label, descriptions = _primary_risk_for_point(risks, effective_risk)
|
||||
weather = row.get("weather_summary", {}) if isinstance(row.get("weather_summary"), dict) else {}
|
||||
|
||||
points.append({
|
||||
"name": row["point_name"],
|
||||
"risk": {
|
||||
"badge": badge,
|
||||
"label": label,
|
||||
"descriptions": descriptions[:4],
|
||||
"level": effective_risk,
|
||||
},
|
||||
"snow_depth_cm": weather.get("snow_depth_cm"),
|
||||
"precip": {
|
||||
"rain_12h_mm": weather.get("rain_12h_mm", 0.0),
|
||||
"rain_24h_mm": weather.get("rain_24h_mm", 0.0),
|
||||
"snow_12h_cm": weather.get("snow_12h_cm", 0.0),
|
||||
"snow_24h_cm": weather.get("snow_24h_cm", 0.0),
|
||||
},
|
||||
})
|
||||
|
||||
return points
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ANALISI RISCHI METEO
|
||||
# =============================================================================
|
||||
@@ -1098,12 +1369,14 @@ def analyze_route_weather_risks(city1: str, city2: str, model_slug: Optional[str
|
||||
'risk_description': 'Dati meteo non disponibili',
|
||||
'risk_value': 0.0,
|
||||
'max_risk_level': 0,
|
||||
'point_name': point_name
|
||||
'point_name': point_name,
|
||||
'weather_summary': {},
|
||||
})
|
||||
continue
|
||||
|
||||
# Analizza condizioni 24h precedenti
|
||||
past_24h = analyze_past_24h_conditions(weather_data)
|
||||
weather_summary = summarize_point_weather(weather_data)
|
||||
|
||||
# Analizza rischi (passa anche past_24h per analisi temporale evolutiva)
|
||||
risk_analysis = analyze_weather_risks(weather_data, model_slug, hours_ahead=24, past_24h_info=past_24h)
|
||||
@@ -1121,7 +1394,8 @@ def analyze_route_weather_risks(city1: str, city2: str, model_slug: Optional[str
|
||||
'risk_value': 0.0,
|
||||
'max_risk_level': 0,
|
||||
'point_name': point_name,
|
||||
'past_24h': past_24h # Aggiungi analisi 24h precedenti anche se nessun rischio
|
||||
'past_24h': past_24h,
|
||||
'weather_summary': weather_summary,
|
||||
})
|
||||
continue
|
||||
|
||||
@@ -1161,7 +1435,8 @@ def analyze_route_weather_risks(city1: str, city2: str, model_slug: Optional[str
|
||||
'risk_value': risk.get("value", 0.0),
|
||||
'max_risk_level': hour_data["max_risk_level"],
|
||||
'point_name': point_name,
|
||||
'past_24h': past_24h
|
||||
'past_24h': past_24h,
|
||||
'weather_summary': weather_summary,
|
||||
})
|
||||
else:
|
||||
all_results.append({
|
||||
@@ -1175,7 +1450,8 @@ def analyze_route_weather_risks(city1: str, city2: str, model_slug: Optional[str
|
||||
'risk_value': 0.0,
|
||||
'max_risk_level': 0,
|
||||
'point_name': point_name,
|
||||
'past_24h': past_24h
|
||||
'past_24h': past_24h,
|
||||
'weather_summary': weather_summary,
|
||||
})
|
||||
|
||||
if not all_results:
|
||||
@@ -1193,141 +1469,13 @@ def format_route_weather_report(df: pd.DataFrame, city1: str, city2: str) -> str
|
||||
"""Formatta report compatto dei rischi meteo lungo percorso."""
|
||||
if df.empty:
|
||||
return "❌ Nessun dato disponibile per il percorso."
|
||||
|
||||
# Raggruppa per punto e trova rischio massimo + analisi 24h
|
||||
# Usa funzione custom per past_24h per assicurarsi che venga preservato correttamente
|
||||
def first_dict(series):
|
||||
"""Prende il primo valore non-nullo, utile per dict."""
|
||||
for val in series:
|
||||
if val is not None and (isinstance(val, dict) or (isinstance(val, str) and val != '')):
|
||||
return val
|
||||
return {}
|
||||
|
||||
max_risk_per_point = df.groupby('point_index').agg({
|
||||
'max_risk_level': 'max',
|
||||
'point_name': 'first',
|
||||
'past_24h': first_dict # Usa funzione custom per preservare dict
|
||||
}).sort_values('point_index')
|
||||
|
||||
# Rimuovi duplicati per nome (punti con stesso nome ma indici diversi)
|
||||
# Considera anche neve/ghiaccio persistente nella scelta
|
||||
seen_names = {}
|
||||
unique_indices = []
|
||||
for idx, row in max_risk_per_point.iterrows():
|
||||
point_name = row['point_name']
|
||||
# Normalizza nome (rimuovi suffissi tra parentesi)
|
||||
name_key = point_name.split('(')[0].strip()
|
||||
past_24h = row.get('past_24h', {}) if isinstance(row.get('past_24h'), dict) else {}
|
||||
has_snow_ice = past_24h.get('snow_present') or past_24h.get('ice_persistence_likely')
|
||||
|
||||
if name_key not in seen_names:
|
||||
seen_names[name_key] = idx
|
||||
unique_indices.append(idx)
|
||||
else:
|
||||
# Se duplicato, mantieni quello con rischio maggiore O con neve/ghiaccio
|
||||
existing_idx = seen_names[name_key]
|
||||
existing_row = max_risk_per_point.loc[existing_idx]
|
||||
existing_past_24h = existing_row.get('past_24h', {}) if isinstance(existing_row.get('past_24h'), dict) else {}
|
||||
existing_has_snow_ice = existing_past_24h.get('snow_present') or existing_past_24h.get('ice_persistence_likely')
|
||||
|
||||
# Priorità: rischio maggiore, oppure neve/ghiaccio se rischio uguale
|
||||
if row['max_risk_level'] > existing_row['max_risk_level']:
|
||||
unique_indices.remove(existing_idx)
|
||||
seen_names[name_key] = idx
|
||||
unique_indices.append(idx)
|
||||
elif row['max_risk_level'] == existing_row['max_risk_level'] and has_snow_ice and not existing_has_snow_ice:
|
||||
# Stesso rischio, ma questo ha neve/ghiaccio
|
||||
unique_indices.remove(existing_idx)
|
||||
seen_names[name_key] = idx
|
||||
unique_indices.append(idx)
|
||||
|
||||
# Filtra solo punti unici
|
||||
max_risk_per_point = max_risk_per_point.loc[unique_indices]
|
||||
|
||||
# Calcola effective_risk_level per ogni punto UNICO (considerando persistenza)
|
||||
effective_risk_levels_dict = {}
|
||||
for idx, row in max_risk_per_point.iterrows():
|
||||
level = int(row['max_risk_level'])
|
||||
past_24h = row.get('past_24h', {}) if isinstance(row.get('past_24h'), dict) else {}
|
||||
|
||||
# Se livello è 0, verifica persistenza per assegnare livello appropriato
|
||||
if level == 0 and past_24h:
|
||||
if past_24h.get('snow_present'):
|
||||
level = 4 # Neve presente
|
||||
elif past_24h.get('ice_persistence_likely'):
|
||||
# Se ice_persistence_likely è True, significa che c'è ghiaccio persistente
|
||||
# (calcolato in analyze_past_24h_conditions basandosi su suolo gelato,
|
||||
# precipitazioni con temperature basse, o neve presente)
|
||||
# Quindi deve essere classificato come ghiaccio (livello 2), non brina
|
||||
level = 2 # Ghiaccio persistente
|
||||
|
||||
effective_risk_levels_dict[idx] = level
|
||||
|
||||
# Aggiungi effective_risk_level al DataFrame
|
||||
max_risk_per_point['effective_risk_level'] = max_risk_per_point.index.map(effective_risk_levels_dict)
|
||||
|
||||
# Trova rischi unici per ogni punto (raggruppa per tipo, mantieni solo il più grave)
|
||||
risks_per_point = {}
|
||||
# Prima aggiungi rischi futuri (max_risk_level > 0)
|
||||
for idx, row in df[df['max_risk_level'] > 0].iterrows():
|
||||
point_idx = row['point_index']
|
||||
if point_idx not in risks_per_point:
|
||||
risks_per_point[point_idx] = {}
|
||||
|
||||
risk_type = row['risk_type']
|
||||
risk_level = row['risk_level']
|
||||
risk_desc = row['risk_description']
|
||||
|
||||
# Raggruppa per tipo di rischio, mantieni solo quello con livello più alto
|
||||
if risk_type not in risks_per_point[point_idx] or risks_per_point[point_idx][risk_type]['level'] < risk_level:
|
||||
risks_per_point[point_idx][risk_type] = {
|
||||
'type': risk_type,
|
||||
'desc': risk_desc,
|
||||
'level': risk_level
|
||||
}
|
||||
|
||||
# Poi aggiungi punti con persistenza ma senza rischi futuri (max_risk_level == 0 ma effective_risk > 0)
|
||||
for idx, row in max_risk_per_point.iterrows():
|
||||
effective_risk = row.get('effective_risk_level', 0)
|
||||
max_risk = int(row['max_risk_level'])
|
||||
|
||||
# Se ha persistenza ma non rischi futuri, aggiungi rischio basato su persistenza
|
||||
if effective_risk > 0 and max_risk == 0:
|
||||
if idx not in risks_per_point:
|
||||
risks_per_point[idx] = {}
|
||||
|
||||
past_24h = row.get('past_24h', {}) if isinstance(row.get('past_24h'), dict) else {}
|
||||
|
||||
# Determina tipo di rischio basandosi su effective_risk_level
|
||||
if effective_risk >= 4:
|
||||
risk_type = 'neve'
|
||||
risk_desc = "Neve presente"
|
||||
elif effective_risk == 2:
|
||||
risk_type = 'ghiaccio'
|
||||
# Determina descrizione basandosi su condizioni
|
||||
min_temp = past_24h.get('min_temp_2m')
|
||||
hours_below_2c = past_24h.get('hours_below_2c', 0)
|
||||
if min_temp is not None:
|
||||
risk_desc = f"Ghiaccio persistente (Tmin: {min_temp:.1f}°C, {hours_below_2c}h <2°C)"
|
||||
else:
|
||||
risk_desc = "Ghiaccio persistente"
|
||||
elif effective_risk == 1:
|
||||
risk_type = 'brina'
|
||||
min_temp = past_24h.get('min_temp_2m')
|
||||
if min_temp is not None:
|
||||
risk_desc = f"Brina possibile (Tmin: {min_temp:.1f}°C)"
|
||||
else:
|
||||
risk_desc = "Brina possibile"
|
||||
else:
|
||||
continue # Skip se non abbiamo un tipo valido
|
||||
|
||||
# Aggiungi al dict rischi (usa idx come chiave, non point_idx)
|
||||
risks_per_point[idx][risk_type] = {
|
||||
'type': risk_type,
|
||||
'desc': risk_desc,
|
||||
'level': effective_risk
|
||||
}
|
||||
|
||||
|
||||
max_risk_per_point, risks_per_point = _aggregate_route_points(df)
|
||||
effective_risk_levels_dict = {
|
||||
idx: int(row["effective_risk_level"])
|
||||
for idx, row in max_risk_per_point.iterrows()
|
||||
}
|
||||
|
||||
# Verifica se la chiave Google Maps è disponibile
|
||||
api_key_available = get_google_maps_api_key() is not None
|
||||
|
||||
@@ -1820,3 +1968,46 @@ def generate_route_weather_map(df: pd.DataFrame, city1: str, city2: str, output_
|
||||
LOGGER.error(f"Errore salvataggio mappa: {e}")
|
||||
plt.close(fig)
|
||||
return False
|
||||
|
||||
|
||||
def build_road_json(city1: str, city2: str, maps_dir: str) -> Dict:
|
||||
"""Analisi percorso stradale per WebApp."""
|
||||
if not PANDAS_AVAILABLE:
|
||||
return {"error": "pandas/numpy non disponibili sul server"}
|
||||
|
||||
df = analyze_route_weather_risks(city1, city2, model_slug=None)
|
||||
if df is None or df.empty:
|
||||
return {"error": f"Impossibile analizzare il percorso {city1} → {city2}"}
|
||||
|
||||
report = format_route_weather_report(df, city1, city2)
|
||||
os.makedirs(maps_dir, exist_ok=True)
|
||||
import hashlib
|
||||
key = hashlib.sha256(f"{city1}|{city2}".encode()).hexdigest()[:16]
|
||||
map_name = f"road_{key}.png"
|
||||
map_path = os.path.join(maps_dir, map_name)
|
||||
map_ok = generate_route_weather_map(df, city1, city2, map_path)
|
||||
|
||||
plain = report.replace("*", "").replace("_", "").replace("`", "")
|
||||
return {
|
||||
"from": city1,
|
||||
"to": city2,
|
||||
"report": plain,
|
||||
"map_id": map_name if map_ok else None,
|
||||
"points": build_route_points_table(df),
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Road weather analysis")
|
||||
parser.add_argument("city1")
|
||||
parser.add_argument("city2")
|
||||
parser.add_argument("--json", action="store_true")
|
||||
parser.add_argument("--maps-dir", default="/tmp")
|
||||
args = parser.parse_args()
|
||||
if args.json:
|
||||
print(json.dumps(build_road_json(args.city1, args.city2, args.maps_dir), ensure_ascii=False))
|
||||
else:
|
||||
if not PANDAS_AVAILABLE:
|
||||
raise SystemExit("pandas richiesto")
|
||||
df = analyze_route_weather_risks(args.city1, args.city2)
|
||||
print(format_route_weather_report(df, args.city1, args.city2))
|
||||
|
||||
Reference in New Issue
Block a user