""" Geocoding service for FleetMind Handles address validation with HERE API and smart mock fallback """ import os import requests import logging from typing import Dict, Optional logger = logging.getLogger(__name__) # Common city coordinates for mock geocoding CITY_COORDINATES = { "san francisco": (37.7749, -122.4194), "sf": (37.7749, -122.4194), "new york": (40.7128, -74.0060), "nyc": (40.7128, -74.0060), "los angeles": (34.0522, -118.2437), "la": (34.0522, -118.2437), "chicago": (41.8781, -87.6298), "houston": (29.7604, -95.3698), "phoenix": (33.4484, -112.0740), "philadelphia": (39.9526, -75.1652), "san antonio": (29.4241, -98.4936), "san diego": (32.7157, -117.1611), "dallas": (32.7767, -96.7970), "austin": (30.2672, -97.7431), "seattle": (47.6062, -122.3321), "boston": (42.3601, -71.0589), "denver": (39.7392, -104.9903), "miami": (25.7617, -80.1918), "atlanta": (33.7490, -84.3880), "portland": (45.5152, -122.6784), } class GeocodingService: """Handle address geocoding with HERE API and smart mock fallback""" def __init__(self): self.here_api_key = os.getenv("HERE_API_KEY", "") self.use_mock = not self.here_api_key or self.here_api_key.startswith("your_") if self.use_mock: logger.info("Geocoding: Using mock (HERE_API_KEY not configured)") else: logger.info("Geocoding: Using HERE Maps API") def geocode(self, address: str) -> Dict: """ Geocode address, using mock if API unavailable Args: address: Street address to geocode Returns: Dict with keys: lat, lng, formatted_address, confidence """ if self.use_mock: return self._geocode_mock(address) else: try: return self._geocode_here(address) except Exception as e: logger.error(f"HERE API failed: {e}, falling back to mock") return self._geocode_mock(address) def _geocode_here(self, address: str) -> Dict: """Real HERE API geocoding""" url = "https://geocode.search.hereapi.com/v1/geocode" params = { "q": address, "apiKey": self.here_api_key } response = requests.get(url, params=params, timeout=10) response.raise_for_status() data = response.json() if not data.get("items"): # No results found, fall back to mock logger.warning(f"HERE API found no results for: {address}") return self._geocode_mock(address) # Get first result item = data["items"][0] position = item["position"] return { "lat": position["lat"], "lng": position["lng"], "formatted_address": item.get("address", {}).get("label", address), "confidence": "high (HERE API)" } def _geocode_mock(self, address: str) -> Dict: """ Smart mock geocoding for testing Tries to detect city name and use approximate coordinates """ address_lower = address.lower() # Try to find a city match for city, coords in CITY_COORDINATES.items(): if city in address_lower: logger.info(f"Mock geocoding detected city: {city}") return { "lat": coords[0], "lng": coords[1], "formatted_address": address, "confidence": f"medium (mock - {city})" } # Default to San Francisco if no city detected logger.info("Mock geocoding: Using default SF coordinates") return { "lat": 37.7749, "lng": -122.4194, "formatted_address": address, "confidence": "low (mock - default)" } def get_status(self) -> str: """Get geocoding service status""" if self.use_mock: return "⚠️ Using mock geocoding (add HERE_API_KEY for real)" else: return "✅ HERE Maps API connected"