Commit
Β·
18f4b6b
1
Parent(s):
a7365ce
Add reverse geocoding functionality and enhance driver details retrieval with location information
Browse files- chat/geocoding.py +99 -0
- chat/providers/gemini_provider.py +2 -2
- chat/tools.py +18 -1
chat/geocoding.py
CHANGED
|
@@ -126,6 +126,105 @@ class GeocodingService:
|
|
| 126 |
"confidence": "low (mock - default)"
|
| 127 |
}
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
def get_status(self) -> str:
|
| 130 |
"""Get geocoding service status"""
|
| 131 |
if self.use_mock:
|
|
|
|
| 126 |
"confidence": "low (mock - default)"
|
| 127 |
}
|
| 128 |
|
| 129 |
+
def reverse_geocode(self, lat: float, lng: float) -> Dict:
|
| 130 |
+
"""
|
| 131 |
+
Reverse geocode coordinates to address
|
| 132 |
+
|
| 133 |
+
Args:
|
| 134 |
+
lat: Latitude
|
| 135 |
+
lng: Longitude
|
| 136 |
+
|
| 137 |
+
Returns:
|
| 138 |
+
Dict with keys: address, city, state, country, formatted_address
|
| 139 |
+
"""
|
| 140 |
+
if self.use_mock:
|
| 141 |
+
return self._reverse_geocode_mock(lat, lng)
|
| 142 |
+
else:
|
| 143 |
+
try:
|
| 144 |
+
return self._reverse_geocode_google(lat, lng)
|
| 145 |
+
except Exception as e:
|
| 146 |
+
logger.error(f"Google Maps reverse geocoding failed: {e}, falling back to mock")
|
| 147 |
+
return self._reverse_geocode_mock(lat, lng)
|
| 148 |
+
|
| 149 |
+
def _reverse_geocode_google(self, lat: float, lng: float) -> Dict:
|
| 150 |
+
"""Real Google Maps API reverse geocoding"""
|
| 151 |
+
try:
|
| 152 |
+
# Call Google Maps Reverse Geocoding API
|
| 153 |
+
result = self.gmaps_client.reverse_geocode((lat, lng))
|
| 154 |
+
|
| 155 |
+
if not result:
|
| 156 |
+
logger.warning(f"Google Maps API found no results for: ({lat}, {lng})")
|
| 157 |
+
return self._reverse_geocode_mock(lat, lng)
|
| 158 |
+
|
| 159 |
+
# Get first result
|
| 160 |
+
first_result = result[0]
|
| 161 |
+
|
| 162 |
+
# Extract address components
|
| 163 |
+
address_components = first_result.get('address_components', [])
|
| 164 |
+
city = ""
|
| 165 |
+
state = ""
|
| 166 |
+
country = ""
|
| 167 |
+
|
| 168 |
+
for component in address_components:
|
| 169 |
+
types = component.get('types', [])
|
| 170 |
+
if 'locality' in types:
|
| 171 |
+
city = component.get('long_name', '')
|
| 172 |
+
elif 'administrative_area_level_1' in types:
|
| 173 |
+
state = component.get('short_name', '')
|
| 174 |
+
elif 'country' in types:
|
| 175 |
+
country = component.get('long_name', '')
|
| 176 |
+
|
| 177 |
+
return {
|
| 178 |
+
"address": first_result.get('formatted_address', f"{lat}, {lng}"),
|
| 179 |
+
"city": city,
|
| 180 |
+
"state": state,
|
| 181 |
+
"country": country,
|
| 182 |
+
"formatted_address": first_result.get('formatted_address', f"{lat}, {lng}"),
|
| 183 |
+
"confidence": "high (Google Maps API)"
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
except Exception as e:
|
| 187 |
+
logger.error(f"Google Maps reverse geocoding error: {e}")
|
| 188 |
+
raise
|
| 189 |
+
|
| 190 |
+
def _reverse_geocode_mock(self, lat: float, lng: float) -> Dict:
|
| 191 |
+
"""
|
| 192 |
+
Mock reverse geocoding
|
| 193 |
+
Tries to match coordinates to known cities
|
| 194 |
+
"""
|
| 195 |
+
# Find closest city
|
| 196 |
+
min_distance = float('inf')
|
| 197 |
+
closest_city = "Unknown Location"
|
| 198 |
+
|
| 199 |
+
for city, coords in CITY_COORDINATES.items():
|
| 200 |
+
# Simple distance calculation
|
| 201 |
+
distance = ((lat - coords[0]) ** 2 + (lng - coords[1]) ** 2) ** 0.5
|
| 202 |
+
if distance < min_distance:
|
| 203 |
+
min_distance = distance
|
| 204 |
+
closest_city = city
|
| 205 |
+
|
| 206 |
+
# If very close to a known city (within ~0.1 degrees, roughly 11km)
|
| 207 |
+
if min_distance < 0.1:
|
| 208 |
+
logger.info(f"Mock reverse geocoding: Matched to {closest_city}")
|
| 209 |
+
return {
|
| 210 |
+
"address": f"Near {closest_city.title()}",
|
| 211 |
+
"city": closest_city.title(),
|
| 212 |
+
"state": "CA" if "san francisco" in closest_city or "la" in closest_city else "",
|
| 213 |
+
"country": "USA",
|
| 214 |
+
"formatted_address": f"Near {closest_city.title()}, USA",
|
| 215 |
+
"confidence": f"medium (mock - near {closest_city})"
|
| 216 |
+
}
|
| 217 |
+
else:
|
| 218 |
+
logger.info(f"Mock reverse geocoding: Unknown location at ({lat}, {lng})")
|
| 219 |
+
return {
|
| 220 |
+
"address": f"{lat}, {lng}",
|
| 221 |
+
"city": "",
|
| 222 |
+
"state": "",
|
| 223 |
+
"country": "",
|
| 224 |
+
"formatted_address": f"Coordinates: {lat}, {lng}",
|
| 225 |
+
"confidence": "low (mock - no match)"
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
def get_status(self) -> str:
|
| 229 |
"""Get geocoding service status"""
|
| 230 |
if self.use_mock:
|
chat/providers/gemini_provider.py
CHANGED
|
@@ -104,7 +104,7 @@ You: [geocode_address] β "OK geocoded, now creating..." β WRONG!
|
|
| 104 |
**Driver Querying (INTERACTIVE):**
|
| 105 |
- count_drivers: Count drivers with optional filters
|
| 106 |
- fetch_drivers: Fetch N drivers with pagination and filters
|
| 107 |
-
- get_driver_details: Get complete info about specific driver by ID
|
| 108 |
- search_drivers: Search by name/email/phone/plate/driver ID
|
| 109 |
- get_available_drivers: Get all active/offline drivers ready for dispatch
|
| 110 |
|
|
@@ -501,7 +501,7 @@ You: [get_driver_details with driver_id=DRV-20251114163800] β [Display complet
|
|
| 501 |
),
|
| 502 |
genai.protos.FunctionDeclaration(
|
| 503 |
name="get_driver_details",
|
| 504 |
-
description="Get complete details of a specific driver by driver ID. Use when user asks
|
| 505 |
parameters=genai.protos.Schema(
|
| 506 |
type=genai.protos.Type.OBJECT,
|
| 507 |
properties={
|
|
|
|
| 104 |
**Driver Querying (INTERACTIVE):**
|
| 105 |
- count_drivers: Count drivers with optional filters
|
| 106 |
- fetch_drivers: Fetch N drivers with pagination and filters
|
| 107 |
+
- get_driver_details: Get complete info about specific driver by ID (includes current location with latitude, longitude, and human-readable address)
|
| 108 |
- search_drivers: Search by name/email/phone/plate/driver ID
|
| 109 |
- get_available_drivers: Get all active/offline drivers ready for dispatch
|
| 110 |
|
|
|
|
| 501 |
),
|
| 502 |
genai.protos.FunctionDeclaration(
|
| 503 |
name="get_driver_details",
|
| 504 |
+
description="Get complete details of a specific driver by driver ID, including current location (latitude, longitude, and human-readable address), contact info, vehicle details, status, and skills. Use when user asks about a driver's location, coordinates, position, or any other driver information.",
|
| 505 |
parameters=genai.protos.Schema(
|
| 506 |
type=genai.protos.Type.OBJECT,
|
| 507 |
properties={
|
chat/tools.py
CHANGED
|
@@ -336,7 +336,7 @@ TOOLS_SCHEMA = [
|
|
| 336 |
},
|
| 337 |
{
|
| 338 |
"name": "get_driver_details",
|
| 339 |
-
"description": "Get complete details of a specific driver by driver ID. Use when user asks
|
| 340 |
"input_schema": {
|
| 341 |
"type": "object",
|
| 342 |
"properties": {
|
|
@@ -1866,6 +1866,22 @@ def handle_get_driver_details(tool_input: dict) -> dict:
|
|
| 1866 |
except:
|
| 1867 |
skills = []
|
| 1868 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1869 |
driver = {
|
| 1870 |
"driver_id": row['driver_id'],
|
| 1871 |
"name": row['name'],
|
|
@@ -1876,6 +1892,7 @@ def handle_get_driver_details(tool_input: dict) -> dict:
|
|
| 1876 |
"location": {
|
| 1877 |
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 1878 |
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
|
|
|
| 1879 |
"last_update": str(row['last_location_update']) if row['last_location_update'] else None
|
| 1880 |
},
|
| 1881 |
"status": row['status'],
|
|
|
|
| 336 |
},
|
| 337 |
{
|
| 338 |
"name": "get_driver_details",
|
| 339 |
+
"description": "Get complete details of a specific driver by driver ID, including current location (latitude, longitude, and human-readable address), contact info, vehicle details, status, and skills. Use when user asks about a driver's location, coordinates, position, or any other driver information.",
|
| 340 |
"input_schema": {
|
| 341 |
"type": "object",
|
| 342 |
"properties": {
|
|
|
|
| 1866 |
except:
|
| 1867 |
skills = []
|
| 1868 |
|
| 1869 |
+
# Reverse geocode location to get address
|
| 1870 |
+
location_address = None
|
| 1871 |
+
if row['current_lat'] and row['current_lng']:
|
| 1872 |
+
try:
|
| 1873 |
+
from chat.geocoding import GeocodingService
|
| 1874 |
+
geocoding_service = GeocodingService()
|
| 1875 |
+
reverse_result = geocoding_service.reverse_geocode(
|
| 1876 |
+
float(row['current_lat']),
|
| 1877 |
+
float(row['current_lng'])
|
| 1878 |
+
)
|
| 1879 |
+
location_address = reverse_result.get('formatted_address', None)
|
| 1880 |
+
logger.info(f"Reverse geocoded driver location: {location_address}")
|
| 1881 |
+
except Exception as e:
|
| 1882 |
+
logger.warning(f"Failed to reverse geocode driver location: {e}")
|
| 1883 |
+
location_address = None
|
| 1884 |
+
|
| 1885 |
driver = {
|
| 1886 |
"driver_id": row['driver_id'],
|
| 1887 |
"name": row['name'],
|
|
|
|
| 1892 |
"location": {
|
| 1893 |
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 1894 |
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
| 1895 |
+
"address": location_address,
|
| 1896 |
"last_update": str(row['last_location_update']) if row['last_location_update'] else None
|
| 1897 |
},
|
| 1898 |
"status": row['status'],
|