diff --git a/backend/database.py b/backend/database.py index ab82594..01dfc17 100644 --- a/backend/database.py +++ b/backend/database.py @@ -189,13 +189,6 @@ def init_db(): ); CREATE INDEX IF NOT EXISTS idx_route_walks_user ON route_walks(user_id); - CREATE TABLE IF NOT EXISTS route_dogs ( - route_id INTEGER NOT NULL REFERENCES routes(id) ON DELETE CASCADE, - dog_id INTEGER NOT NULL REFERENCES dogs(id) ON DELETE CASCADE, - PRIMARY KEY (route_id, dog_id) - ); - CREATE INDEX IF NOT EXISTS idx_route_dogs_dog ON route_dogs(dog_id); - CREATE TABLE IF NOT EXISTS exercise_progress ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, @@ -1981,38 +1974,6 @@ def _migrate(conn_factory): """) logger.info("Migration: futter_profil bereit.") - # Futter-Einträge & Reaktionen (Verträglichkeits-Tracking) - try: - conn.executescript(""" - CREATE TABLE IF NOT EXISTS futter_eintraege ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - dog_id INTEGER NOT NULL REFERENCES dogs(id) ON DELETE CASCADE, - datum TEXT NOT NULL, - uhrzeit TEXT NOT NULL, - futter_name TEXT NOT NULL, - futter_typ TEXT NOT NULL DEFAULT 'trockenfutter', - menge_g INTEGER, - notiz TEXT, - created_at TEXT NOT NULL DEFAULT (datetime('now')) - ); - CREATE INDEX IF NOT EXISTS idx_futter_eintraege_dog ON futter_eintraege(dog_id, datum DESC); - - CREATE TABLE IF NOT EXISTS futter_reaktionen ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - dog_id INTEGER NOT NULL REFERENCES dogs(id) ON DELETE CASCADE, - datum TEXT NOT NULL, - uhrzeit TEXT NOT NULL, - reaktion_typ TEXT NOT NULL, - intensitaet INTEGER NOT NULL DEFAULT 3, - notiz TEXT, - created_at TEXT NOT NULL DEFAULT (datetime('now')) - ); - CREATE INDEX IF NOT EXISTS idx_futter_reaktionen_dog ON futter_reaktionen(dog_id, datum DESC); - """) - logger.info("Migration: futter_eintraege + futter_reaktionen bereit.") - except Exception as e: - logger.warning(f"Migration futter_eintraege/reaktionen: {e}") - # Wiederkehrende Ausgaben (Daueraufträge) conn.executescript(""" CREATE TABLE IF NOT EXISTS recurring_expenses ( @@ -2143,85 +2104,6 @@ def _migrate(conn_factory): except Exception: pass # Spalte existiert bereits - # exercise_progress + training_plan_progress: dog_id ergänzen - existing_ep = [r[1] for r in conn.execute("PRAGMA table_info(exercise_progress)").fetchall()] - if 'dog_id' not in existing_ep: - try: - # Neue Tabelle mit dog_id erstellen - conn.execute(""" - CREATE TABLE exercise_progress_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - dog_id INTEGER REFERENCES dogs(id) ON DELETE CASCADE, - exercise_id TEXT NOT NULL, - status TEXT, - updated_at TEXT NOT NULL DEFAULT (datetime('now')), - UNIQUE(dog_id, exercise_id) - ) - """) - # Bestehende Daten migrieren: dog_id = erster Hund des Users - conn.execute(""" - INSERT INTO exercise_progress_new (user_id, dog_id, exercise_id, status, updated_at) - SELECT ep.user_id, - (SELECT id FROM dogs WHERE user_id=ep.user_id ORDER BY id LIMIT 1), - ep.exercise_id, ep.status, ep.updated_at - FROM exercise_progress ep - """) - conn.execute("DROP TABLE exercise_progress") - conn.execute("ALTER TABLE exercise_progress_new RENAME TO exercise_progress") - conn.execute("CREATE INDEX IF NOT EXISTS idx_exercise_progress_user ON exercise_progress(user_id)") - conn.execute("CREATE INDEX IF NOT EXISTS idx_exercise_progress_dog ON exercise_progress(dog_id)") - logger.info("Migration: exercise_progress.dog_id hinzugefügt.") - except Exception as e: - logger.warning(f"Migration exercise_progress.dog_id fehlgeschlagen: {e}") - - existing_tp = [r[1] for r in conn.execute("PRAGMA table_info(training_plan_progress)").fetchall()] - if 'dog_id' not in existing_tp: - try: - conn.execute(""" - CREATE TABLE training_plan_progress_new ( - user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - dog_id INTEGER REFERENCES dogs(id) ON DELETE CASCADE, - item_key TEXT NOT NULL, - checked INTEGER NOT NULL DEFAULT 1, - checked_at TEXT NOT NULL DEFAULT (datetime('now')), - PRIMARY KEY (dog_id, item_key) - ) - """) - conn.execute(""" - INSERT INTO training_plan_progress_new (user_id, dog_id, item_key, checked, checked_at) - SELECT tp.user_id, - (SELECT id FROM dogs WHERE user_id=tp.user_id ORDER BY id LIMIT 1), - tp.item_key, tp.checked, tp.checked_at - FROM training_plan_progress tp - """) - conn.execute("DROP TABLE training_plan_progress") - conn.execute("ALTER TABLE training_plan_progress_new RENAME TO training_plan_progress") - logger.info("Migration: training_plan_progress.dog_id hinzugefügt.") - except Exception as e: - logger.warning(f"Migration training_plan_progress.dog_id fehlgeschlagen: {e}") - - # verstorben_am: Hund als verstorben markierbar - try: - conn.execute("ALTER TABLE dogs ADD COLUMN verstorben_am TEXT") - logger.info("Migration: dogs.verstorben_am hinzugefügt.") - except Exception: - pass - - # route_dogs: bestehende Routen allen Hunden des Users zuweisen - try: - existing = conn.execute("SELECT COUNT(*) FROM route_dogs").fetchone()[0] - if existing == 0: - conn.execute(""" - INSERT OR IGNORE INTO route_dogs (route_id, dog_id) - SELECT r.id, d.id - FROM routes r - JOIN dogs d ON d.user_id = r.user_id - """) - logger.info("Migration: route_dogs mit bestehenden Routen befüllt.") - except Exception as e: - logger.warning(f"Migration route_dogs fehlgeschlagen: {e}") - def _seed_help_articles(conn): """Befüllt help_articles mit Starter-FAQs — nur wenn die Tabelle noch leer ist.""" diff --git a/backend/main.py b/backend/main.py index 00027f4..dc86828 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1414,7 +1414,7 @@ async def ausweis_page(dog_id: int, request: Request):
- `; - UI.bindDogChip(_container, _appState); _container.querySelector('#diary-milestone-filter') ?.addEventListener('click', async () => { diff --git a/backend/static/js/pages/dog-profile.js b/backend/static/js/pages/dog-profile.js index 6de34d5..85ce02d 100644 --- a/backend/static/js/pages/dog-profile.js +++ b/backend/static/js/pages/dog-profile.js @@ -1060,12 +1060,6 @@ window.Page_dog_profile = (() => {
-
@@ -1285,11 +1279,6 @@ window.Page_dog_profile = (() => { document.getElementById('dp-form-cancel') ?.addEventListener('click', UI.modal.close); - document.getElementById('dp-gedenken-btn')?.addEventListener('click', async () => { - UI.modal.close(); - _openGedenkenFlow(dog); - }); - document.getElementById('dp-delete-btn')?.addEventListener('click', async () => { const ok = await UI.modal.confirm({ title : `${dog.name} löschen?`, @@ -2425,178 +2414,6 @@ window.Page_dog_profile = (() => { // ---------------------------------------------------------- // PUBLIC // ---------------------------------------------------------- - // ---------------------------------------------------------- - // GEDENKEN-FLOW - // ---------------------------------------------------------- - async function _openGedenkenFlow(dog) { - // Schritt 1: Würdevoller Übergangsdialog mit Datum-Eingabe - UI.modal.open({ - title: `Abschied von ${dog.name}`, - body: ` -
- -

- ${dog.name} hinterlässt eine riesige Lücke.
- Die gemeinsamen Erinnerungen bleiben für immer. -

-
-
- - -
`, - footer: ` -
- - -
`, - }); - - document.getElementById('gedenken-form')?.addEventListener('submit', async e => { - e.preventDefault(); - const btn = document.getElementById('gedenken-save-btn'); - const datum = document.getElementById('gedenken-datum').value; - await UI.asyncButton(btn, async () => { - await API.post(`/dogs/${dog.id}/gedenken`, { verstorben_am: datum }); - // Aus aktiver Hundeliste entfernen - _appState.dogs = _appState.dogs.filter(d => d.id !== dog.id); - _appState.activeDog = _appState.dogs[0] || null; - UI.modal.close(); - // Gedenkseite öffnen - await _openGedenkseite(dog.id, dog.name); - await _render(); - }); - }); - } - - async function _openGedenkseite(dogId, dogName) { - UI.modal.open({ title: `Erinnerungen an ${dogName}`, body: ` -
- - - -
` }); - - let data; - try { data = await API.get(`/dogs/${dogId}/gedenkseite`); } - catch { UI.modal.close(); return; } - - const d = data; - const av = d.dog.foto_url - ? `` - : `
`; - - const photoGrid = d.photos.length ? ` -
- ${d.photos.map(url => ``).join('')} -
` : ''; - - const statsHtml = ` -
- ${d.km_total ? `
- -
${d.km_total}
-
km zusammen
-
` : ''} - ${d.diary_count ? `
- -
${d.diary_count}
-
Tagebucheinträge
-
` : ''} - ${d.media_count ? `
- -
${d.media_count}
-
Fotos
-
` : ''} - ${d.gemeinsam_tage ? `
- -
${d.gemeinsam_tage}
-
gemeinsame Tage
-
` : ''} -
`; - - // Trauer-Support-Texte - const supportHtml = ` -
-
- - Für dich in dieser Zeit -
-

- Der Schmerz über den Verlust eines Hundes ist real und tief. Du musst nicht stark sein. - Lass dich trauern — so lange du brauchst. Die Erinnerungen bleiben immer bei dir. -

-
-
-
- - Sprich mit Freunden oder der Familie über ${d.dog.name} — Geschichten lebendig halten hilft. -
-
- - Das Tagebuch bleibt erhalten — es ist ein kostbares Stück gemeinsamer Geschichte. -
-
- - Professionelle Hilfe bei Tiertrauer: Tiertrauer-Hotline 0800 111 0 111 (kostenlos) -
-
-
- -
`; - - const modal = UI.modal.open({ - title: `🌈 Erinnerungen an ${UI.escape(d.dog.name)}`, - body: ` -
- ${av} -
${UI.escape(d.dog.name)}
- ${d.dog.rasse ? `
${UI.escape(d.dog.rasse)}
` : ''} - ${d.dog.verstorben_am ? `
- - Über die Regenbogenbrücke am ${new Date(d.dog.verstorben_am).toLocaleDateString('de-DE')} -
` : ''} -
- ${photoGrid} - ${statsHtml} - ${supportHtml}`, - }); - - document.getElementById('gedenk-ki-btn')?.addEventListener('click', async () => { - const btn = document.getElementById('gedenk-ki-btn'); - await UI.asyncButton(btn, async () => { - const result = await API.post('/ki/abschied', { - dog_id: dogId, - name: d.dog.name, - rasse: d.dog.rasse, - km_total: d.km_total, - diary_count: d.diary_count, - gemeinsam_tage: d.gemeinsam_tage, - }); - const wrap = document.getElementById('gedenk-ki-wrap'); - if (wrap) wrap.innerHTML = ` -
- "${UI.escape(result.text)}" -
`; - }); - }); - } - return { init, refresh, onDogChange, addNew: _openCreateModal }; })(); diff --git a/backend/static/js/pages/health.js b/backend/static/js/pages/health.js index b984515..91f082f 100644 --- a/backend/static/js/pages/health.js +++ b/backend/static/js/pages/health.js @@ -50,6 +50,10 @@ window.Page_health = (() => { async function refresh() { if (!_appState.activeDog) return; + if (_appState.dogs.length > 1) { + _renderDogPicker(); + return; + } _data = {}; await _renderHealth(); } @@ -77,7 +81,52 @@ window.Page_health = (() => { return; } - await _renderHealth(); + if (_appState.dogs.length > 1) { + _renderDogPicker(); + } else { + await _renderHealth(); + } + } + + // ---------------------------------------------------------- + // HUNDE-PICKER + // ---------------------------------------------------------- + function _renderDogPicker() { + const activeDogId = _appState.activeDog?.id; + + const cards = _appState.dogs.map(dog => { + const isActive = dog.id === activeDogId; + const av = dog.foto_url + ? `${_esc(dog.name)}` + : `${UI.icon('dog')}`; + return ` +
+
${av}
+
${_esc(dog.name)}
+ ${dog.rasse ? `
${_esc(dog.rasse)}
` : ''} +
`; + }).join(''); + + _container.innerHTML = ` +
+

Wessen Gesundheitsakte?

+
${cards}
+
`; + + _container.querySelectorAll('.diary-picker-card').forEach(el => { + el.addEventListener('click', async () => { + const id = parseInt(el.dataset.dogId); + if (id === _appState.activeDog?.id) { + // Bereits aktiver Hund → direkt Health laden + _data = {}; + await _renderHealth(); + } else { + App.setActiveDog(id); + // onDogChange() → _renderHealth() via _notifyDogChange() + } + }); + }); } // ---------------------------------------------------------- @@ -98,7 +147,6 @@ window.Page_health = (() => {
`; _container.innerHTML = ` - ${UI.dogChip(_appState)}
- - `; - UI.modal.open({ title: `${UI.icon('dog')} Hunde bearbeiten`, body, footer }); - - // Checkbox-Pill Styling - document.querySelectorAll('.rd-dog-cb').forEach(cb => { - const label = cb.closest('label'); - cb.addEventListener('change', () => { - label.style.borderColor = cb.checked ? 'var(--c-primary)' : 'var(--c-border)'; - label.style.background = cb.checked ? 'var(--c-primary-subtle)' : ''; - label.style.color = cb.checked ? 'var(--c-primary)' : ''; - }); - }); - - document.getElementById('rd-dogs-cancel')?.addEventListener('click', UI.modal.close); - - document.getElementById('rd-dogs-save')?.addEventListener('click', async () => { - const btn = document.getElementById('rd-dogs-save'); - await UI.asyncButton(btn, async () => { - const dogIds = [...document.querySelectorAll('.rd-dog-cb:checked')].map(c => parseInt(c.value)); - await API.routes.updateDogs(route.id, dogIds); - route.dog_ids = dogIds; - UI.modal.close(); - UI.toast.success('Hunde aktualisiert.'); - }); - }); - } - // Richtungspfeile gleichmäßig entlang des Tracks platzieren function _addRouteArrows(map, track, color = '#fff') { if (track.length < 2) return; diff --git a/backend/static/js/pages/trainingsplaene.js b/backend/static/js/pages/trainingsplaene.js index 346a4ab..35383f3 100644 --- a/backend/static/js/pages/trainingsplaene.js +++ b/backend/static/js/pages/trainingsplaene.js @@ -40,8 +40,7 @@ window.Page_trainingsplaene = (() => { } function _lsKey(planId, goalIdx) { - const dogId = _dogId() || 'x'; - return `tp_d${dogId}_${planId}_${goalIdx}`; + return `tp_${planId}_${goalIdx}`; } function _saveGoal(key, checked) { @@ -538,8 +537,6 @@ window.Page_trainingsplaene = (() => { // BIND EVENTS // ---------------------------------------------------------- function _bindEvents() { - UI.bindDogChip(_container, _appState); - // Notiz-Button const dogId = _dogId(); _container.querySelector('#tp-note-btn')?.addEventListener('click', e => { @@ -615,9 +612,8 @@ window.Page_trainingsplaene = (() => { : `Erwachsener Hund – ${_activeAdultTab}`; _container.innerHTML = ` -
- ${UI.dogChip(_appState)} -
+
+

${_icon('clipboard-text')} Trainingspläne

diff --git a/backend/static/js/pages/uebungen.js b/backend/static/js/pages/uebungen.js index c6ba308..d8639e6 100644 --- a/backend/static/js/pages/uebungen.js +++ b/backend/static/js/pages/uebungen.js @@ -75,7 +75,6 @@ window.Page_uebungen = (() => { // In-memory cache (loaded from API on init) let _progressCache = {}; // key → statusId - let _progressLoaded = false; let _exerciseStats = {}; // exercise_id → {recent_avg, session_count, trend} function _progressKey(tab, name) { @@ -84,13 +83,17 @@ window.Page_uebungen = (() => { function _getStatus(tab, name) { const k = _progressKey(tab, name); - return _progressCache[k] ?? null; + // Fallback to localStorage while API loads + return _progressCache[k] !== undefined + ? _progressCache[k] + : localStorage.getItem(_statusKey(tab, name)) || null; } function _setStatus(tab, name, statusId) { const k = _progressKey(tab, name); _progressCache[k] = statusId; - API.training.setProgress(k, statusId, _dogId()).catch(() => {}); + localStorage.setItem(_statusKey(tab, name), statusId || ''); // keep localStorage in sync + API.training.setProgress(k, statusId).catch(() => {}); } function _nextStatus(currentId) { @@ -501,19 +504,28 @@ window.Page_uebungen = (() => { _scrollTarget = { exercise_id: params.exercise_id || '', name: params.name || '' }; } - // Progress vom Server laden (hund-spezifisch) - const _did = _dogId(); - _progressLoaded = false; - API.training.getProgress(_did) - .then(rows => { - _progressCache = {}; - rows.forEach(r => { _progressCache[r.exercise_id] = r.status; }); - _progressLoaded = true; - _renderContent(); - }).catch(() => { _progressLoaded = true; _renderContent(); }); + // Progress vom Server laden + API.training.getProgress().then(rows => { + rows.forEach(r => { _progressCache[r.exercise_id] = r.status; }); + // localStorage-Daten migrieren falls noch nicht im Backend + Object.keys(localStorage).filter(k => k.startsWith('ub_status_')).forEach(lsKey => { + const parts = lsKey.replace('ub_status_', '').split('_'); + const tab = parts[0]; + const name = parts.slice(1).join('_'); + const apiKey = `${tab}_${name}`; + if (_progressCache[apiKey] === undefined) { + const val = localStorage.getItem(lsKey); + if (val) { + _progressCache[apiKey] = val; + API.training.setProgress(apiKey, val).catch(() => {}); + } + } + }); + _renderContent(); // Re-render with loaded progress + }).catch(() => {}); // Empfehlungen laden - API.training.getSuggestions(_did).then(suggestions => { + API.training.getSuggestions().then(suggestions => { if (suggestions.length) _showSuggestions(suggestions); }).catch(() => {}); @@ -544,7 +556,6 @@ window.Page_uebungen = (() => { _statsData = null; _badgesData = null; _progressCache = {}; - _progressLoaded = false; _exerciseStats = {}; _render(); _loadStatsAndBadges(); @@ -557,7 +568,6 @@ window.Page_uebungen = (() => { function _render() { _container.innerHTML = `
-
${UI.dogChip(_appState)}
@@ -594,7 +604,6 @@ window.Page_uebungen = (() => {
`; - UI.bindDogChip(_container, _appState); _container.querySelector('#ueb-quicksetup-btn').addEventListener('click', _openQuickSetupModal); _container.querySelector('#ueb-tabs')?.style.setProperty('--ueb-tab-cols', Math.ceil(TABS.length / 2)); _container.querySelector('#ueb-search')?.addEventListener('input', e => { @@ -604,12 +613,7 @@ window.Page_uebungen = (() => { _renderContent(); }); _bindTabs(); - if (_progressLoaded) { - _renderContent(); - } else { - const el = _container.querySelector('#ueb-content'); - if (el) el.innerHTML = `
`; - } + _renderContent(); _renderStatsBanner(); } @@ -778,18 +782,7 @@ window.Page_uebungen = (() => { // ---------------------------------------------------------- // SCHNELL-SETUP: Stand aller Übungen erfassen // ---------------------------------------------------------- - async function _openQuickSetupModal() { - // Sicherstellen dass Progress geladen ist bevor das Modal öffnet - if (!_progressLoaded) { - const did = _dogId(); - try { - const rows = await API.training.getProgress(did); - _progressCache = {}; - rows.forEach(r => { _progressCache[r.exercise_id] = r.status; }); - _progressLoaded = true; - _renderContent(); - } catch { _progressLoaded = true; } - } + function _openQuickSetupModal() { const ALL = [ { group: 'Grundkommandos', tab: 'grundkommandos', items: GRUNDKOMMANDOS }, { group: 'Tricks', tab: 'tricks', items: TRICKS }, @@ -890,8 +883,11 @@ window.Page_uebungen = (() => { // Alle geänderten Status speichern const parts = Object.entries(pending).map(([key, val]) => { + const [tab, ...rest] = key.split('_'); + const name = rest.join('_').replace(/_/g, ' '); _progressCache[key] = val || null; - return API.training.setProgress(key, val || null, _dogId()); + localStorage.setItem(`ub_status_${key}`, val || ''); + return API.training.setProgress(key, val || null); }); await Promise.allSettled(parts); diff --git a/backend/static/js/pages/wetter.js b/backend/static/js/pages/wetter.js index 2dceccd..8eae462 100644 --- a/backend/static/js/pages/wetter.js +++ b/backend/static/js/pages/wetter.js @@ -397,9 +397,7 @@ window.Page_wetter = (() => { : 0; } - const locName = _data.location_name ? `
${_esc(_data.location_name)}
` : ''; el.innerHTML = ` - ${locName}
${_wmoIcon(d.weathercode, '3.5rem')}
diff --git a/backend/static/js/ui.js b/backend/static/js/ui.js index 38d6528..5a580f4 100644 --- a/backend/static/js/ui.js +++ b/backend/static/js/ui.js @@ -995,30 +995,6 @@ const UI = (() => { _load(); } - function dogChip(appState) { - const dog = appState?.activeDog; - const dogs = appState?.dogs || []; - if (!dog) return ''; - const av = dog.foto_url - ? `` - : ``; - const sw = dogs.length > 1 - ? `` : ''; - return `
${av}${escape(dog.name)}${sw}
`; - } - - function bindDogChip(container, appState) { - if ((appState?.dogs?.length || 0) < 2) return; - container.querySelector('[data-dog-chip]')?.addEventListener('click', () => { - const dogs = appState.dogs; - const next = dogs.find(d => d.id !== appState.activeDog?.id) || dogs[0]; - if (next) App.setActiveDog(next.id); - }); - } - // Öffentliche API return { toast, modal, @@ -1033,10 +1009,6 @@ const UI = (() => { leafletMarker, locationPicker, ratingStars, - dogChip, - bindDogChip, - dogChip, - bindDogChip, }; })(); diff --git a/backend/static/js/worlds.js b/backend/static/js/worlds.js index 52dddee..e7ab1ab 100644 --- a/backend/static/js/worlds.js +++ b/backend/static/js/worlds.js @@ -1114,7 +1114,6 @@ window.Worlds = (() => { ${gassiScore ? `/10` : ''}
${w ? `
- ${w.location_name ? `
${w.location_name}
` : ''}
${Math.round(w.temp_c ?? 0)}° · ${w.precip_prob ?? 0}% Regen
${w.rain_warning_time ? `
⚠ Umschwung ab ${w.rain_warning_time}
` : w.next_rain_time ? `
ab ${w.next_rain_time} Uhr
` : ''}
` : ''} diff --git a/backend/weather.py b/backend/weather.py index cdf09d8..afde8a2 100644 --- a/backend/weather.py +++ b/backend/weather.py @@ -4,7 +4,6 @@ BAN YARO — Wetter via Open-Meteo - get_weather_for_location(): API-Endpoint, beliebiger Standort mit TTL-Cache """ -import asyncio import time import logging import httpx @@ -63,28 +62,9 @@ async def get_weather_for_location(lat: float, lon: float) -> dict: "&timezone=Europe%2FBerlin&forecast_days=1" ) async with httpx.AsyncClient(timeout=8.0) as client: - resp, geo_resp = await asyncio.gather( - client.get(url), - client.get( - f"https://nominatim.openstreetmap.org/reverse?lat={lat}&lon={lon}&format=json&zoom=10", - headers={"User-Agent": "BanYaro/1.0 support@banyaro.app"} - ), - return_exceptions=True, - ) - resp.raise_for_status() - raw = resp.json() - - # Ortsname aus Reverse-Geocoding - location_name = None - try: - if not isinstance(geo_resp, Exception) and geo_resp.status_code == 200: - geo = geo_resp.json() - addr = geo.get("address", {}) - location_name = (addr.get("city") or addr.get("town") or - addr.get("village") or addr.get("municipality") or - addr.get("county") or geo.get("name")) - except Exception: - pass + resp = await client.get(url) + resp.raise_for_status() + raw = resp.json() cur = raw.get('current', {}) daily = raw.get('daily', {}) @@ -150,10 +130,9 @@ async def get_weather_for_location(lat: float, lon: float) -> dict: 'precip_prob': precip, 'uv_index': uv, 'is_day': bool(is_day), - 'zecken_warnung': zecken, - 'next_rain_time': next_rain_time, + 'zecken_warnung': zecken, + 'next_rain_time': next_rain_time, 'rain_warning_time': rain_warning_time, - 'location_name': location_name, } _location_cache[key] = (now, data) return data @@ -293,23 +272,9 @@ async def get_forecast(lat: float, lon: float) -> dict: async with httpx.AsyncClient(timeout=10.0) as client: forecast_task = client.get(forecast_url) pollen_task = client.get(pollen_url) - geo_task = client.get( - f"https://nominatim.openstreetmap.org/reverse?lat={lat}&lon={lon}&format=json&zoom=10", - headers={"User-Agent": "BanYaro/1.0 support@banyaro.app"} + forecast_resp, pollen_resp = await asyncio.gather( + forecast_task, pollen_task, return_exceptions=True ) - forecast_resp, pollen_resp, geo_resp_fc = await asyncio.gather( - forecast_task, pollen_task, geo_task, return_exceptions=True - ) - - location_name_fc = None - try: - if not isinstance(geo_resp_fc, Exception) and geo_resp_fc.status_code == 200: - addr = geo_resp_fc.json().get("address", {}) - location_name_fc = (addr.get("city") or addr.get("town") or - addr.get("village") or addr.get("municipality") or - addr.get("county")) - except Exception: - pass # --- Forecast (required) --- if isinstance(forecast_resp, Exception): @@ -456,7 +421,7 @@ async def get_forecast(lat: float, lon: float) -> dict: 'hourly': _hourly_by_day.get(date_str, []), }) - result = {'timezone': timezone, 'days': days, 'location_name': location_name_fc} + result = {'timezone': timezone, 'days': days} _forecast_cache[key] = (now, result) _log_forecast(round(lat, 1), round(lon, 1), days) return result