From 471633867c5f20ffe3487e0f5df7d2bd5449ba4f Mon Sep 17 00:00:00 2001 From: rene Date: Mon, 4 May 2026 20:18:38 +0200 Subject: [PATCH] =?UTF-8?q?Feature:=20Wetter=20=E2=80=94=20Gassi-Score,=20?= =?UTF-8?q?Schn=C3=BCffel-Index,=20Hunde-Alter-Hinweis=20(SW=20by-v692)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- backend/static/js/app.js | 2 +- backend/static/js/pages/wetter.js | 232 +++++++++++++++++++++++++++++- backend/static/sw.js | 2 +- 3 files changed, 229 insertions(+), 7 deletions(-) diff --git a/backend/static/js/app.js b/backend/static/js/app.js index d2655e8..bf0f697 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '690'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '692'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.3.0'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; diff --git a/backend/static/js/pages/wetter.js b/backend/static/js/pages/wetter.js index d9fbab2..538b64f 100644 --- a/backend/static/js/pages/wetter.js +++ b/backend/static/js/pages/wetter.js @@ -325,6 +325,9 @@ window.Page_wetter = (() => { + + ${_gassiScoreBadge(d)} + ${sunriseStr && sunsetStr ? `
@@ -528,11 +531,23 @@ window.Page_wetter = (() => { if (!d) return; const _POLLEN_NAMES = { erle:'Erle', birke:'Birke', graeser:'Gräser', beifuss:'Beifuß', ambrosia:'Ambrosia' }; - let html = `

- - Hunde-Wetter -

`; + const _wl = _dogWeatherLabel(d); + let html = ` +
+
${_wl.emoji}
+
+ ${_esc(_wl.label)} +
+
+ ${_esc(_wl.sub)} +
+
+

+ + Hunde-Hinweise +

`; // Asphalt-Temperatur if (d.asphalt_temp != null) { @@ -638,6 +653,16 @@ window.Page_wetter = (() => { `; } + // Schnüffel-Index + Hunde-Alter Chips + const ageYears = _dogAgeYears(); + html += _dogAgeChip(ageYears); + + html += ` +
+ ${_schnueffelChip(d)} +
+ `; + // Wenn keine Hunde-Daten vorhanden if (!d.asphalt_temp && !d.paw_cold && !d.thunderstorm && !d.zecken && !(pollen && Object.keys(pollen).length)) { @@ -651,6 +676,160 @@ window.Page_wetter = (() => { el.innerHTML = html; } + // ---------------------------------------------------------- + // GASSI-SCORE (1–10) + // ---------------------------------------------------------- + function _gassiScore(d) { + let score = 10; + const temp = d.temp_max ?? 20; + const precip = d.precip_prob ?? 0; + const wind = d.windspeed_max ?? 0; + const asphalt = d.asphalt_temp ?? 0; + + // Temperatur (ideal: 10–20°C) + if (temp > 30) score -= 3; + else if (temp > 25) score -= 1; + else if (temp < 0) score -= 3; + else if (temp < 5) score -= 1; + + // Regen + if (precip > 70) score -= 3; + else if (precip > 40) score -= 2; + else if (precip > 20) score -= 1; + + // Wind + if (wind > 60) score -= 2; + else if (wind > 40) score -= 1; + + // Asphalt + if (asphalt > 55) score -= 2; + else if (asphalt > 45) score -= 1; + + // Gewitter + if (d.thunderstorm) score -= 3; + + return Math.max(1, Math.min(10, score)); + } + + function _gassiScoreBadge(d) { + const score = _gassiScore(d); + let color, text; + if (score >= 8) { + color = '#10B981'; + text = 'Toller Gassi-Tag!'; + } else if (score >= 5) { + color = '#F59E0B'; + text = 'Geht so'; + } else { + color = '#EF4444'; + text = 'Lieber drinbleiben'; + } + return ` +
+ 🐾 Gassi-Score + + ${score} + + / 10 + — ${_esc(text)} +
+ `; + } + + // ---------------------------------------------------------- + // SCHNÜFFEL-INDEX + // ---------------------------------------------------------- + function _schnueffelIndex(d) { + const temp = d.temp_max ?? 20; + const precip = d.precip_prob ?? 0; + + // Feuchtigkeit aus precip_prob ableiten + const feucht = precip > 60 ? 'feucht' : precip > 30 ? 'leicht-feucht' : 'trocken'; + + if (feucht === 'feucht' && temp >= 10 && temp <= 18) + return { label:'Exzellent 👃', color:'#10B981' }; + if (feucht === 'feucht' && temp > 10 && temp <= 22) + return { label:'Sehr gut 👃', color:'#34D399' }; + if (temp < 5) + return { label:'Gut (kalte Luft trägt Gerüche)', color:'#60A5FA' }; + if (feucht === 'leicht-feucht' && temp >= 10 && temp <= 22) + return { label:'Gut 👃', color:'#4CAF50' }; + if (feucht === 'trocken' && temp > 25) + return { label:'Schwach', color:'#94A3B8' }; + return { label:'Mittel', color:'#F59E0B' }; + } + + function _schnueffelChip(d) { + const s = _schnueffelIndex(d); + return ` + + + Schnüffel: ${_esc(s.label)} + + `; + } + + // ---------------------------------------------------------- + // HUNDE-ALTER aus appState + // ---------------------------------------------------------- + function _dogAgeYears() { + try { + const dog = _appState?.activeDog || _appState?.dog || _appState?.active_dog; + if (!dog) return null; + const geb = dog.geburtsdatum || dog.birthdate; + if (!geb) return null; + const birth = new Date(geb); + if (isNaN(birth)) return null; + const now = new Date(); + let age = now.getFullYear() - birth.getFullYear(); + const m = now.getMonth() - birth.getMonth(); + if (m < 0 || (m === 0 && now.getDate() < birth.getDate())) age--; + return age < 0 ? 0 : age; + } catch { return null; } + } + + function _dogAgeChip(ageYears) { + if (ageYears === null) return ''; + if (ageYears < 1) { + return ` +
+ +
+ Welpe — kurze Spaziergänge, max. 15 Min bei Hitze. + Gelenke und Pfoten besonders schonen. +
+
+ `; + } + if (ageYears >= 8) { + return ` +
+ +
+ Seniorhund — Hitze und Kälte vermeiden, kurze Runden bevorzugen. + Auf Gelenkbeschwerden achten. +
+
+ `; + } + return ''; + } + // ---------------------------------------------------------- // HILFSFUNKTIONEN — Wetter // ---------------------------------------------------------- @@ -695,6 +874,49 @@ window.Page_wetter = (() => { return '#F44336'; // level 4+ } + function _dogWeatherLabel(d) { + const temp = d.temp_max ?? 20; + const tempMin = d.temp_min ?? temp; + const precip = d.precip_prob ?? 0; + const wind = d.windspeed_max ?? 0; + const asphalt = d.asphalt_temp ?? 0; + const wcode = d.weathercode ?? 0; + const isSnow = wcode >= 71 && wcode <= 77; + if (d.thunderstorm) + return { label:'Gewitterangst-Wetter', sub:'Angsthasen lieber zu Hause lassen', emoji:'⛈️', color:'#7C3AED' }; + if (isSnow && temp < 3) + return { label:'Schnee-Toben-Wetter', sub:'Pudel im Schnee — der Klassiker', emoji:'❄️', color:'#38BDF8' }; + if (isSnow) + return { label:'Matschpfoten-Wetter', sub:'Pfoten nach der Runde gut abtrocknen', emoji:'🌨️', color:'#60A5FA' }; + if (tempMin < 0 && precip < 30) + return { label:'Kristallklare Nasenluft', sub:'Kalt aber herrlich — Schnüffeln auf Maximum', emoji:'🌡️', color:'#60A5FA' }; + if (temp < 5 && precip > 50) + return { label:'Kuschelwetter', sub:'Kurze Runde, dann ab auf das Sofa', emoji:'🏠', color:'#6B7280' }; + if (temp < 5) + return { label:'Fellkuschelwetter', sub:'Frisch und klar — ideal für aktive Rassen', emoji:'🧣', color:'#93C5FD' }; + if (temp > 28 && asphalt > 50) + return { label:'Pfoten-Alarm!', sub:'Asphalt zu heiß — früh morgens oder abends raus', emoji:'🔥', color:'#EF4444' }; + if (temp > 28) + return { label:'Schwimm-Wetter', sub:'Bach oder See suchen — Hunde überhitzen schnell', emoji:'🏊', color:'#F97316' }; + if (precip > 70 && temp < 15) + return { label:'Nass-Hund-Wetter', sub:'Handtuch bereit? Der Geruch kommt garantiert', emoji:'💧', color:'#3B82F6' }; + if (precip > 70) + return { label:'Warm-Dusch-Wetter', sub:'Wer braucht noch ein Bad — der Regen übernimmt', emoji:'🌧️', color:'#60A5FA' }; + if (precip > 30 && temp >= 10 && temp <= 20) + return { label:'Schnüffel-Wetter', sub:'Feuchte Luft = Nasenarbeit pur — Gerüche lieben das', emoji:'👃', color:'#34D399' }; + if (wind > 50) + return { label:'Sturmfrisur-Wetter', sub:'Fell in alle Richtungen — Leine gut festhalten', emoji:'🌬️', color:'#A78BFA' }; + if (wind > 30 && temp >= 15) + return { label:'Ohren-im-Wind-Wetter', sub:'Optimal für Hunde mit Schlappohren', emoji:'💨', color:'#A78BFA' }; + if (precip > 30 && precip <= 70) + return { label:'Gassiregen-Wetter', sub:'Leichte Jacke, kurze Runde — Hund findet es gut', emoji:'🌦️', color:'#60A5FA' }; + if (temp >= 18 && temp <= 26 && precip < 20) + return { label:'Perfektes Gassi-Wetter',sub:'Heute müssen alle Routen genossen werden', emoji:'🐾', color:'#10B981' }; + if (temp >= 10 && temp < 18 && precip < 30) + return { label:'Klassisches Hunde-Wetter', sub:'Nicht zu warm, nicht zu kalt — Vierbeiner-Paradies', emoji:'🐕', color:'#4CAF50' }; + return { label:'Gutes Hunde-Wetter', sub:'Raus mit dem Hund!', emoji:'🐶', color:'#10B981' }; + } + function _tickLevel(risk) { const r = (risk || '').toLowerCase(); if (r === 'niedrig') return ['niedrig', '#4CAF50']; diff --git a/backend/static/sw.js b/backend/static/sw.js index ca60ee8..894a342 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v690'; +const CACHE_VERSION = 'by-v692'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache