modified: app.py
modified: static/css/style.css modified: templates/weather.html
This commit is contained in:
50
app.py
50
app.py
@@ -226,6 +226,8 @@ def get_dwd_warnings(lat, lon, state_hint=None):
|
|||||||
"description": w.get("description", ""),
|
"description": w.get("description", ""),
|
||||||
"state": w.get("state", ""),
|
"state": w.get("state", ""),
|
||||||
"state_short": w.get("stateShort", ""),
|
"state_short": w.get("stateShort", ""),
|
||||||
|
"onset": w.get("onset", ""),
|
||||||
|
"expires": w.get("expires", ""),
|
||||||
})
|
})
|
||||||
filtered = []
|
filtered = []
|
||||||
if state_hint:
|
if state_hint:
|
||||||
@@ -238,8 +240,10 @@ def get_dwd_warnings(lat, lon, state_hint=None):
|
|||||||
"type": w["type"],
|
"type": w["type"],
|
||||||
"headline": w["headline"],
|
"headline": w["headline"],
|
||||||
"description": w["description"],
|
"description": w["description"],
|
||||||
|
"onset": w.get("onset", ""),
|
||||||
|
"expires": w.get("expires", ""),
|
||||||
}
|
}
|
||||||
for w in result_source[:3]
|
for w in result_source[:5]
|
||||||
]
|
]
|
||||||
_warn_cache[key] = result
|
_warn_cache[key] = result
|
||||||
return result
|
return result
|
||||||
@@ -339,6 +343,49 @@ def get_mosmix_forecast(lat, lon, hours=72):
|
|||||||
app.logger.exception("MOSMIX forecast loading failed")
|
app.logger.exception("MOSMIX forecast loading failed")
|
||||||
return [], {}
|
return [], {}
|
||||||
|
|
||||||
|
def filter_unrealistic_warnings(warnings, forecast):
|
||||||
|
"""
|
||||||
|
Filter out warnings that contradict the actual forecast.
|
||||||
|
E.g., frost warning when min temp is > 0°C in next 48 hours.
|
||||||
|
"""
|
||||||
|
if not warnings or not forecast:
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
filtered = []
|
||||||
|
frost_keywords = {"frost", "glatte", "glatt", "eis", "schnee"}
|
||||||
|
rain_keywords = {"regen", "starkregen", "dauerregen"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
temps_48h = [h.get("temp_c") for h in forecast[:48] if h.get("temp_c") is not None]
|
||||||
|
precip_48h = [h.get("precip_mm") for h in forecast[:48] if h.get("precip_mm") is not None]
|
||||||
|
|
||||||
|
min_temp_48h = min(temps_48h) if temps_48h else None
|
||||||
|
max_precip_48h = max(precip_48h) if precip_48h else 0
|
||||||
|
|
||||||
|
for w in warnings:
|
||||||
|
warn_type = _normalize_text(w.get("type", ""))
|
||||||
|
headline = _normalize_text(w.get("headline", ""))
|
||||||
|
|
||||||
|
skip = False
|
||||||
|
|
||||||
|
if min_temp_48h is not None and min_temp_48h > 2:
|
||||||
|
if any(kw in warn_type or kw in headline for kw in frost_keywords):
|
||||||
|
app.logger.info("Filtered frost warning: min_temp %.1f°C", min_temp_48h)
|
||||||
|
skip = True
|
||||||
|
|
||||||
|
if max_precip_48h < 0.1:
|
||||||
|
if any(kw in warn_type or kw in headline for kw in rain_keywords):
|
||||||
|
app.logger.info("Filtered rain warning: max_precip %.1f mm", max_precip_48h)
|
||||||
|
skip = True
|
||||||
|
|
||||||
|
if not skip:
|
||||||
|
filtered.append(w)
|
||||||
|
except Exception:
|
||||||
|
app.logger.exception("Error filtering unrealistic warnings")
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
@@ -395,6 +442,7 @@ def wetter():
|
|||||||
forecast = forecast[current_idx:]
|
forecast = forecast[current_idx:]
|
||||||
sunrise, sunset, dawn, dusk = get_sun_times(lat, lon)
|
sunrise, sunset, dawn, dusk = get_sun_times(lat, lon)
|
||||||
warnings = get_dwd_warnings(lat, lon, state_hint=state_hint)
|
warnings = get_dwd_warnings(lat, lon, state_hint=state_hint)
|
||||||
|
warnings = filter_unrealistic_warnings(warnings, forecast)
|
||||||
daily = {}
|
daily = {}
|
||||||
for h in forecast:
|
for h in forecast:
|
||||||
dt = h["datetime"]
|
dt = h["datetime"]
|
||||||
|
|||||||
@@ -502,6 +502,7 @@ main { flex: 1; }
|
|||||||
.warn-icon { font-size: 1.1rem; flex-shrink: 0; line-height: 1.3; }
|
.warn-icon { font-size: 1.1rem; flex-shrink: 0; line-height: 1.3; }
|
||||||
.warn-item strong { color: inherit; }
|
.warn-item strong { color: inherit; }
|
||||||
.warn-item div { display: flex; flex-direction: column; gap: 0.2rem; }
|
.warn-item div { display: flex; flex-direction: column; gap: 0.2rem; }
|
||||||
|
.warn-time { color: #888; font-size: 0.7rem; margin: 0; margin-bottom: 0.25rem; font-weight: 500; }
|
||||||
.warn-desc { color: var(--muted2); font-size: 0.78rem; margin: 0; }
|
.warn-desc { color: var(--muted2); font-size: 0.78rem; margin: 0; }
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -76,14 +76,8 @@
|
|||||||
<span class="warn-icon">{{ '⚠️' if w.level == 1 else ('🟠' if w.level == 2 else ('🔴' if w.level >= 3 else '⚠️')) }}</span>
|
<span class="warn-icon">{{ '⚠️' if w.level == 1 else ('🟠' if w.level == 2 else ('🔴' if w.level >= 3 else '⚠️')) }}</span>
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ w.headline }}</strong>
|
<strong>{{ w.headline }}</strong>
|
||||||
{% if w.description %}<p class="warn-desc">{{ w.description[:140] }}{% if w.description|length > 140 %}…{% endif %}</p>{% endif %}
|
{% if w.onset %}<p class="warn-time">{% if w.onset is string %}{{ w.onset[8:10] }}.{{ w.onset[5:7] }}. {{ w.onset[11:16] }}{% else %}{{ w.onset }}{% endif %}{% if w.expires %} – {% if w.expires is string %}{{ w.expires[8:10] }}.{{ w.expires[5:7] }}. {{ w.expires[11:16] }}{% else %}{{ w.expires }}{% endif %}{% endif %}</p>{% endif %}
|
||||||
</div>
|
{% if w.description %}<p class="warn-desc">{{ w.description[:250] }}{% if w.description|length > 250 %}…{% endif %}</p>{% endif %}
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- STÜNDLICH -->
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<h2 class="section-title">Stundenweise</h2>
|
<h2 class="section-title">Stundenweise</h2>
|
||||||
<div class="hourly-strip-wrap">
|
<div class="hourly-strip-wrap">
|
||||||
|
|||||||
Reference in New Issue
Block a user