From 72611cc1488df132a5cd5e563733cbc508139d74 Mon Sep 17 00:00:00 2001 From: "Matt Johnson (via Claude)" Date: Wed, 10 Jun 2026 19:49:26 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20move=20Photon=20geocoder=20config=20to?= =?UTF-8?q?=20config.yaml=20=E2=80=94=20public=20komoot=20default,=20local?= =?UTF-8?q?=20override=20in=20deployment=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meshai/central_normalizer.py | 44 +++++++++++++++++++++++++----------- meshai/config.py | 11 +++++++++ meshai/main.py | 14 ++++++++++++ 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/meshai/central_normalizer.py b/meshai/central_normalizer.py index 689ce44..dc600de 100644 --- a/meshai/central_normalizer.py +++ b/meshai/central_normalizer.py @@ -25,7 +25,7 @@ import urllib.request from collections import OrderedDict from datetime import datetime, timezone from typing import Any, Optional -from meshai.adapter_config import adapter_config +# Geocoder config is set via init_geocoder_config() logger = logging.getLogger(__name__) @@ -248,13 +248,31 @@ def _is_uninformative_road(road: Optional[str]) -> bool: # 2026-06-04). It's the same Echo6-local Photon instance that backs Central's # NaviBackend reverse-geocoder. Photon takes osm_tag=place (KEY only, not # key:value with comma-list -- that returns 0 features -- per probe). -# v0.6-3b: photon endpoint settings live in adapter_config.geocoder. -# Module-level names retained as backward-compat aliases so existing -# test imports / monkeypatches still resolve. -PHOTON_BASE_URL = "http://100.64.0.24:2322" -PHOTON_TIMEOUT_S = 2.0 -PHOTON_RADIUS_KM = 80 # ≈ 50 miles -PHOTON_LIMIT = 10 +# v0.6-3b: photon geocoder config - initialized via init_geocoder_config() +# Defaults to public Komoot Photon; deployments override in config.yaml. + +class _GeocoderSettings: + url: str = "https://photon.komoot.io" + timeout_seconds: float = 2.0 + radius_km: float = 80.0 + limit: int = 10 + +_geocoder = _GeocoderSettings() + + +def init_geocoder_config(url: str = None, timeout: float = None, + radius: float = None, limit: int = None) -> None: + """Initialize geocoder settings from config.yaml values.""" + if url is not None: + _geocoder.url = url + if timeout is not None: + _geocoder.timeout_seconds = timeout + if radius is not None: + _geocoder.radius_km = radius + if limit is not None: + _geocoder.limit = limit + + # OSM place classes we accept as "town". Suburb included for metro coverage; # locality is rare but valid for tiny rural places. _TOWN_OSM_VALUES = frozenset({"city", "town", "village"}) @@ -282,13 +300,13 @@ def _photon_reverse_places(lat: float, lon: float) -> list[dict]: qs = urllib.parse.urlencode({ "lat": f"{lat:.6f}", "lon": f"{lon:.6f}", - "radius": PHOTON_RADIUS_KM, + "radius": _geocoder.radius_km, "osm_tag": "place", - "limit": PHOTON_LIMIT, + "limit": _geocoder.limit, }) - url = f"{PHOTON_BASE_URL}/reverse?{qs}" + url = f"{_geocoder.url}/reverse?{qs}" try: - with urllib.request.urlopen(url, timeout=PHOTON_TIMEOUT_S) as resp: + with urllib.request.urlopen(url, timeout=_geocoder.timeout_seconds) as resp: body = resp.read() d = json.loads(body) except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, @@ -308,7 +326,7 @@ def nearest_town(lat: float, lon: float, max_distance_mi: float = 50.0) -> Optio event is N of the town. Returns None if no town within range or if Photon is unreachable. - Calls Photon /reverse?osm_tag=place at PHOTON_BASE_URL. Results are + Calls Photon /reverse?osm_tag=place at _geocoder.url. Results are H3-cell-cached (resolution 7 ≈ 5 km cells) so the second event near the same town is free. """ diff --git a/meshai/config.py b/meshai/config.py index f0201a9..7aa291d 100644 --- a/meshai/config.py +++ b/meshai/config.py @@ -468,6 +468,16 @@ class CentralConsumerConfig: region: str = "us.id" +@dataclass +class GeocoderConfig: + """Photon reverse geocoder settings.""" + + url: str = "https://photon.komoot.io" + timeout_seconds: float = 2.0 + radius_km: float = 80.0 + limit: int = 10 + + @dataclass class EnvironmentalConfig: """Environmental feeds settings.""" @@ -486,6 +496,7 @@ class EnvironmentalConfig: wzdx: WZDxConfig = field(default_factory=WZDxConfig) firms: FIRMSConfig = field(default_factory=FIRMSConfig) central: CentralConsumerConfig = field(default_factory=CentralConsumerConfig) + geocoder: GeocoderConfig = field(default_factory=GeocoderConfig) @dataclass diff --git a/meshai/main.py b/meshai/main.py index ad1dc2d..7aff133 100644 --- a/meshai/main.py +++ b/meshai/main.py @@ -19,6 +19,7 @@ from .commands.status import set_start_time from .config import Config from .config_loader import load_config, get_config_dir_from_path from .connector import MeshConnector, MeshMessage +from .central_normalizer import init_geocoder_config from .context import MeshContext from .history import ConversationHistory from .memory import ConversationSummary @@ -245,6 +246,19 @@ class MeshAI: except Exception: logger.exception("persistence init_db failed at startup") + # v0.6-3b: Initialize geocoder config from config.yaml + try: + gc = self.config.environmental.geocoder + init_geocoder_config( + url=gc.url, + timeout=gc.timeout_seconds, + radius=gc.radius_km, + limit=gc.limit + ) + logger.info("Geocoder configured: %s", gc.url) + except Exception: + logger.exception("geocoder init failed - using defaults") + # Conversation history self.history = ConversationHistory(self.config.history) await self.history.initialize()