modified: app.py

modified:   static/css/style.css
This commit is contained in:
simon
2026-04-24 11:40:50 +02:00
parent 7c7c1a7208
commit 919908e4c2
2 changed files with 34 additions and 3 deletions

35
app.py
View File

@@ -72,6 +72,7 @@ MOSMIX_PARAMS = [
"hourly/large/sunshine_duration", "hourly/large/sunshine_duration",
"hourly/large/probability_precipitation_height_gt_0_1mm_last_1h", "hourly/large/probability_precipitation_height_gt_0_1mm_last_1h",
"hourly/large/uv_index", "hourly/large/uv_index",
"hourly/large/visibility",
] ]
def _get_berlin(): def _get_berlin():
@@ -335,14 +336,24 @@ def _parse_warning_datetime(value):
# regen heavy rain # regen heavy rain
# schnee snow # schnee snow
# blitz thunderstorm (needs additional MOSMIX parameter) # blitz thunderstorm (needs additional MOSMIX parameter)
# wolkig_nebel_sonne fog / mist # nebel dense fog (visibility < 1000 m)
# nebel_wolkig foggy + overcast (1000-5000 m, cloud > 60 %)
# wolkig_nebel_sonne patchy fog / haze (1000-5000 m, less cloud)
# nacht clear night (moon) # nacht clear night (moon)
# nacht(1) moon behind cloud # nacht(1) moon behind cloud
# nacht(2) moon+cloud+rain # nacht(2) moon+cloud+rain
# nacht(3) moon+cloud+snow # nacht(3) moon+cloud+snow
def weather_icon(cloud_pct, precip_mm, rain_prob, temp_c, is_night=False): def weather_icon(cloud_pct, precip_mm, rain_prob, temp_c, is_night=False, visibility_m=None):
"""Return the icon key for static/icons/{key}.png.""" """Return the icon key for static/icons/{key}.png."""
# Fog (takes priority; fog unlikely when precipitating heavily)
if visibility_m is not None and visibility_m < 5000 and not (precip_mm and precip_mm > 0.5):
if visibility_m < 1000:
return "nebel"
if cloud_pct is not None and cloud_pct > 60:
return "nebel_wolkig"
return "wolkig_nebel_sonne"
# Snow / sleet (day and night) # Snow / sleet (day and night)
if temp_c is not None and temp_c <= 2 and ( if temp_c is not None and temp_c <= 2 and (
(precip_mm and precip_mm > 0.1) or (rain_prob and rain_prob >= 40) (precip_mm and precip_mm > 0.1) or (rain_prob and rain_prob >= 40)
@@ -375,6 +386,21 @@ def pick_daily_icon(hours):
"""Choose the most representative icon key for a whole day.""" """Choose the most representative icon key for a whole day."""
if not hours: if not hours:
return "sonne" return "sonne"
# Fog: majority of hours with reduced visibility and no heavy precipitation
fog_hours = [
h for h in hours
if h.get("visibility_m") is not None
and h["visibility_m"] < 5000
and (h.get("precip_mm") or 0) <= 0.5
]
if len(fog_hours) >= len(hours) // 2 and fog_hours:
avg_vis = sum(h["visibility_m"] for h in fog_hours) / len(fog_hours)
avg_cloud_fog = sum(h.get("cloud_pct") or 0 for h in fog_hours) / len(fog_hours)
if avg_vis < 1000:
return "nebel"
if avg_cloud_fog > 60:
return "nebel_wolkig"
return "wolkig_nebel_sonne"
# Snow / sleet # Snow / sleet
if any( if any(
h.get("temp_c") is not None and h["temp_c"] <= 2 h.get("temp_c") is not None and h["temp_c"] <= 2
@@ -590,6 +616,8 @@ def get_mosmix_forecast(lat, lon, hours=72):
sun_min = round(float(sun)/60) if not _isnan(sun) else 0 sun_min = round(float(sun)/60) if not _isnan(sun) else 0
wd = p.get("wind_direction") wd = p.get("wind_direction")
wind_dir = float(wd) if not _isnan(wd) else None wind_dir = float(wd) if not _isnan(wd) else None
vis_raw = p.get("visibility")
visibility_m = round(float(vis_raw)) if not _isnan(vis_raw) else None
uv_raw = p.get("uv_index") uv_raw = p.get("uv_index")
dt_local = pd.Timestamp(date_val).tz_convert(berlin).tz_localize(None) dt_local = pd.Timestamp(date_val).tz_convert(berlin).tz_localize(None)
# Determine day/night for icon selection # Determine day/night for icon selection
@@ -624,7 +652,8 @@ def get_mosmix_forecast(lat, lon, hours=72):
"confidence": confidence_score, "confidence": confidence_score,
"confidence_label": confidence_label, "confidence_label": confidence_label,
"activity_score": a_score, "activity_score": a_score,
"icon": weather_icon(clouds, precip, rain_prob, temp_c, is_night=is_night), "visibility_m": visibility_m,
"icon": weather_icon(clouds, precip, rain_prob, temp_c, is_night=is_night, visibility_m=visibility_m),
}) })
result_data = (forecast, station_info) result_data = (forecast, station_info)
_forecast_cache[cache_key] = result_data _forecast_cache[cache_key] = result_data

View File

@@ -441,6 +441,8 @@ main { flex: 1; }
.wx-icon-wrap[data-icon="schnee"] { background: linear-gradient(135deg, #3070a8 0%, #70b8e0 100%); } .wx-icon-wrap[data-icon="schnee"] { background: linear-gradient(135deg, #3070a8 0%, #70b8e0 100%); }
.wx-icon-wrap[data-icon="blitz"] { background: linear-gradient(135deg, #1e1040 0%, #5030a0 100%); } .wx-icon-wrap[data-icon="blitz"] { background: linear-gradient(135deg, #1e1040 0%, #5030a0 100%); }
.wx-icon-wrap[data-icon="wolkig_nebel_sonne"] { background: linear-gradient(135deg, #706858 0%, #a09880 100%); } .wx-icon-wrap[data-icon="wolkig_nebel_sonne"] { background: linear-gradient(135deg, #706858 0%, #a09880 100%); }
.wx-icon-wrap[data-icon="nebel"] { background: linear-gradient(135deg, #7a8a96 0%, #b0c0cc 100%); }
.wx-icon-wrap[data-icon="nebel_wolkig"] { background: linear-gradient(135deg, #505a64 0%, #8898a8 100%); }
/* Night icons */ /* Night icons */
.wx-icon-wrap[data-icon="nacht"] { background: linear-gradient(135deg, #080c20 0%, #141c40 100%); } .wx-icon-wrap[data-icon="nacht"] { background: linear-gradient(135deg, #080c20 0%, #141c40 100%); }
.wx-icon-wrap[data-icon="nacht(1)"] { background: linear-gradient(135deg, #0c1228 0%, #1e2848 100%); } .wx-icon-wrap[data-icon="nacht(1)"] { background: linear-gradient(135deg, #0c1228 0%, #1e2848 100%); }