feat(bridge): GPS→Ortsname im Standort-Präfix (Reverse-Geocode, keine Tokens)
Das lokale 8B kann Koordinaten nicht zuverlässig in eine Stadt uebersetzen (riet "Berlin" fuer Oldenburg). Claude kann's, aber das kostet Tokens. Loesung: die Bridge loest lat/lon EINMAL via Nominatim (OpenStreetMap, gratis, keyless) auf und schreibt den Namen in den Praefix: [Stefans aktueller Standort: Oldenburg, Niedersachsen (GPS 53.12, 8.22). ...] So muss KEIN Modell mehr raten — local UND Claude lesen den Ort direkt ab, Token-Ersparnis bleibt voll erhalten. - _persist_location merkt sich den letzten Ort + triggert den Geocode async (im Hintergrund, gecacht pro ~1km-Raster, nur bei Bewegung → Nominatim- Rate-Limit locker eingehalten). - _build_core_text nutzt frische ODER letzte bekannte Position (Praefix faellt beim passiven Weiterreden ohne frisches GPS nicht mehr weg) + den Ortsnamen. - NOMINATIM_URL env (Default oeffentlicher Dienst) → spaeter self-hostbar. Reiner Bridge-Fix, kein APK/Brain. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+72
-6
@@ -1175,8 +1175,12 @@ class ARIABridge:
|
||||
"lat": float(lat),
|
||||
"lon": float(lon),
|
||||
})
|
||||
# Fuer den Standort-Praefix merken (auch fuer Turns ohne frisches GPS).
|
||||
self._last_location = {"lat": float(lat), "lon": float(lon)}
|
||||
except Exception:
|
||||
return
|
||||
# Ortsname im Hintergrund aufloesen (Nominatim, gecacht) — kein Modell.
|
||||
self._maybe_reverse_geocode(float(lat), float(lon))
|
||||
# Fire-and-forget: Brain-on-demand-Tick. Wenn Brain nicht antwortet
|
||||
# oder langsam ist, blockt das nicht den GPS-Pfad.
|
||||
try:
|
||||
@@ -1184,6 +1188,57 @@ class ARIABridge:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _maybe_reverse_geocode(self, lat: float, lon: float) -> None:
|
||||
"""Loest lat/lon → Ortsname auf, aber nur bei nennenswerter Bewegung
|
||||
(~1 km Raster) und im Hintergrund. Ergebnis landet in
|
||||
self._last_place_name; der Standort-Praefix liest es dann nur ab."""
|
||||
try:
|
||||
key = (round(lat, 2), round(lon, 2)) # ~1.1 km — reicht fuer "Stadt"
|
||||
except Exception:
|
||||
return
|
||||
if key == getattr(self, "_geocoded_key", None):
|
||||
return # diese Gegend schon aufgeloest
|
||||
# Neue Gegend: alten Namen verwerfen, sonst zeigt der Praefix die falsche
|
||||
# Stadt bis der neue Geocode da ist.
|
||||
self._geocoded_key = key
|
||||
self._last_place_name = ""
|
||||
try:
|
||||
asyncio.create_task(self._do_reverse_geocode(lat, lon, key))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _do_reverse_geocode(self, lat: float, lon: float, key) -> None:
|
||||
"""Nominatim-Reverse-Geocode (keyless, gratis). Setzt self._last_place_name.
|
||||
Fehler/Timeout → still (Praefix faellt dann auf reine Koordinaten zurueck)."""
|
||||
base = os.environ.get("NOMINATIM_URL", "https://nominatim.openstreetmap.org").rstrip("/")
|
||||
url = (f"{base}/reverse?lat={lat:.5f}&lon={lon:.5f}"
|
||||
f"&format=jsonv2&zoom=10&accept-language=de")
|
||||
|
||||
def _fetch():
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={
|
||||
# Nominatim verlangt einen aussagekraeftigen User-Agent.
|
||||
"User-Agent": "ARIA-Assistant/1.0 (personal voice assistant)",
|
||||
})
|
||||
with urllib.request.urlopen(req, timeout=8) as r:
|
||||
return json.loads(r.read().decode("utf-8", "ignore"))
|
||||
except Exception as exc:
|
||||
logger.debug("[geo] reverse-geocode fehlgeschlagen: %s", exc)
|
||||
return None
|
||||
|
||||
data = await asyncio.get_event_loop().run_in_executor(None, _fetch)
|
||||
if not isinstance(data, dict):
|
||||
return
|
||||
addr = data.get("address") or {}
|
||||
place = (addr.get("city") or addr.get("town") or addr.get("village")
|
||||
or addr.get("municipality") or addr.get("county") or "")
|
||||
state = addr.get("state") or ""
|
||||
name = f"{place}, {state}" if (place and state) else (place or data.get("name") or "")
|
||||
# Nur uebernehmen wenn Stefan nicht schon weitergezogen ist (key aktuell).
|
||||
if name and getattr(self, "_geocoded_key", None) == key:
|
||||
self._last_place_name = name
|
||||
logger.info("[geo] %.5f,%.5f → %s", lat, lon, name)
|
||||
|
||||
async def _trigger_brain_check_now(self) -> None:
|
||||
"""Brain-Endpoint POST /triggers/check-now anstossen."""
|
||||
brain_url = os.environ.get("BRAIN_URL", "http://aria-brain:8080")
|
||||
@@ -1557,14 +1612,25 @@ class ARIABridge:
|
||||
"gesprochen oder gearbeitet hast. Folgendes ist eine Korrektur, "
|
||||
"Ergaenzung oder ein Themenwechsel zu deiner letzten Antwort.]"
|
||||
)
|
||||
if location and isinstance(location, dict):
|
||||
lat = location.get("lat")
|
||||
lon = location.get("lon") or location.get("lng")
|
||||
# Standort: frische GPS aus der Nachricht, sonst der zuletzt bekannte
|
||||
# (damit's beim passiven Weiterreden ohne frisches GPS nicht wegfaellt).
|
||||
loc = location if (isinstance(location, dict) and location.get("lat") is not None) \
|
||||
else getattr(self, "_last_location", None)
|
||||
if isinstance(loc, dict):
|
||||
lat = loc.get("lat")
|
||||
lon = loc.get("lon") or loc.get("lng")
|
||||
if lat is not None and lon is not None:
|
||||
# Ortsname kommt vom Reverse-Geocode (Nominatim, gecacht) — so
|
||||
# muss KEIN Modell Koordinaten raten (das lokale 8B tippt sonst
|
||||
# daneben, z.B. "Berlin" fuer Oldenburg). Noch nicht aufgeloest
|
||||
# → nur Koordinaten (Claude kann die zur Not selbst deuten).
|
||||
place = getattr(self, "_last_place_name", "")
|
||||
where = f"{place} " if place else ""
|
||||
parts.append(
|
||||
f"[Stefans aktuelle GPS-Position: {float(lat):.6f}, {float(lon):.6f}. "
|
||||
f"Nutze die nur wenn die Frage sich auf seinen Standort bezieht. "
|
||||
f"Erwaehne sie nicht von dir aus, ausser er fragt explizit danach.]"
|
||||
f"[Stefans aktueller Standort: {where}(GPS {float(lat):.5f}, "
|
||||
f"{float(lon):.5f}). Nutze das nur wenn die Frage sich auf seinen "
|
||||
f"Standort bezieht. Erwaehne ihn nicht von dir aus, ausser er "
|
||||
f"fragt explizit danach.]"
|
||||
)
|
||||
if parts:
|
||||
return " ".join(parts) + " " + text
|
||||
|
||||
Reference in New Issue
Block a user