Commit
·
580a4eb
1
Parent(s):
807de16
add new field driver location
Browse files- chat/tools.py +67 -26
- database/connection.py +1 -1
- database/migrations/008_add_driver_address.py +64 -0
- server.py +11 -4
chat/tools.py
CHANGED
|
@@ -141,7 +141,7 @@ TOOLS_SCHEMA = [
|
|
| 141 |
},
|
| 142 |
{
|
| 143 |
"name": "create_driver",
|
| 144 |
-
"description": "Create a new delivery driver in the database. Use this to onboard new drivers to the fleet.",
|
| 145 |
"input_schema": {
|
| 146 |
"type": "object",
|
| 147 |
"properties": {
|
|
@@ -159,12 +159,24 @@ TOOLS_SCHEMA = [
|
|
| 159 |
},
|
| 160 |
"vehicle_type": {
|
| 161 |
"type": "string",
|
| 162 |
-
"description": "Type of vehicle: van, truck, car, motorcycle
|
| 163 |
},
|
| 164 |
"vehicle_plate": {
|
| 165 |
"type": "string",
|
| 166 |
"description": "Vehicle license plate number (optional)"
|
| 167 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
"capacity_kg": {
|
| 169 |
"type": "number",
|
| 170 |
"description": "Vehicle cargo capacity in kilograms (default: 1000.0)"
|
|
@@ -186,7 +198,7 @@ TOOLS_SCHEMA = [
|
|
| 186 |
"description": "Driver status (default: active)"
|
| 187 |
}
|
| 188 |
},
|
| 189 |
-
"required": ["name"]
|
| 190 |
}
|
| 191 |
},
|
| 192 |
{
|
|
@@ -1557,6 +1569,7 @@ def handle_create_driver(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1557 |
capacity_m3 = tool_input.get("capacity_m3", 12.0)
|
| 1558 |
current_lat = tool_input.get("current_lat") # No default - REQUIRED
|
| 1559 |
current_lng = tool_input.get("current_lng") # No default - REQUIRED
|
|
|
|
| 1560 |
|
| 1561 |
# Convert skills to regular list (handles protobuf RepeatedComposite)
|
| 1562 |
skills_raw = tool_input.get("skills", [])
|
|
@@ -1582,11 +1595,11 @@ def handle_create_driver(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1582 |
|
| 1583 |
status = tool_input.get("status", "active")
|
| 1584 |
|
| 1585 |
-
# Validate ALL required fields (name, vehicle_type, current_lat, current_lng)
|
| 1586 |
-
if not all([name, vehicle_type, current_lat is not None, current_lng is not None]):
|
| 1587 |
return {
|
| 1588 |
"success": False,
|
| 1589 |
-
"error": "Missing required fields: name, vehicle_type, current_lat, current_lng. All fields are mandatory."
|
| 1590 |
}
|
| 1591 |
|
| 1592 |
# Validate coordinates are valid numbers
|
|
@@ -1620,10 +1633,10 @@ def handle_create_driver(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1620 |
query = """
|
| 1621 |
INSERT INTO drivers (
|
| 1622 |
driver_id, name, phone, email,
|
| 1623 |
-
current_lat, current_lng, last_location_update,
|
| 1624 |
status, vehicle_type, vehicle_plate,
|
| 1625 |
capacity_kg, capacity_m3, skills, user_id
|
| 1626 |
-
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
| 1627 |
"""
|
| 1628 |
|
| 1629 |
# Convert skills list to JSON
|
|
@@ -1637,6 +1650,7 @@ def handle_create_driver(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1637 |
email,
|
| 1638 |
current_lat,
|
| 1639 |
current_lng,
|
|
|
|
| 1640 |
now,
|
| 1641 |
status,
|
| 1642 |
vehicle_type,
|
|
@@ -1655,11 +1669,17 @@ def handle_create_driver(tool_input: dict, user_id: str = None) -> dict:
|
|
| 1655 |
"success": True,
|
| 1656 |
"driver_id": driver_id,
|
| 1657 |
"name": name,
|
|
|
|
| 1658 |
"status": status,
|
| 1659 |
"vehicle_type": vehicle_type,
|
| 1660 |
"vehicle_plate": vehicle_plate,
|
| 1661 |
"capacity_kg": capacity_kg,
|
| 1662 |
"skills": skills,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1663 |
"message": f"Driver {driver_id} ({name}) created successfully!"
|
| 1664 |
}
|
| 1665 |
except Exception as e:
|
|
@@ -3187,7 +3207,7 @@ def handle_fetch_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3187 |
query = f"""
|
| 3188 |
SELECT
|
| 3189 |
driver_id, name, phone, email,
|
| 3190 |
-
current_lat, current_lng, last_location_update,
|
| 3191 |
status, vehicle_type, vehicle_plate,
|
| 3192 |
capacity_kg, capacity_m3, skills,
|
| 3193 |
created_at, updated_at
|
|
@@ -3232,6 +3252,7 @@ def handle_fetch_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3232 |
"location": {
|
| 3233 |
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 3234 |
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
|
|
|
| 3235 |
"last_update": str(row['last_location_update']) if row['last_location_update'] else None
|
| 3236 |
},
|
| 3237 |
"status": row['status'],
|
|
@@ -3302,7 +3323,7 @@ def handle_get_driver_details(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3302 |
query = """
|
| 3303 |
SELECT
|
| 3304 |
driver_id, name, phone, email,
|
| 3305 |
-
current_lat, current_lng, last_location_update,
|
| 3306 |
status, vehicle_type, vehicle_plate,
|
| 3307 |
capacity_kg, capacity_m3, skills,
|
| 3308 |
created_at, updated_at
|
|
@@ -3330,16 +3351,15 @@ def handle_get_driver_details(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3330 |
except:
|
| 3331 |
skills = []
|
| 3332 |
|
| 3333 |
-
#
|
| 3334 |
-
|
| 3335 |
-
location_address
|
| 3336 |
-
if row['current_lat'] and row['current_lng']:
|
| 3337 |
try:
|
| 3338 |
reverse_result = safe_reverse_geocode(
|
| 3339 |
float(row['current_lat']),
|
| 3340 |
float(row['current_lng'])
|
| 3341 |
)
|
| 3342 |
-
location_address = reverse_result.get('
|
| 3343 |
logger.info(f"Reverse geocoded driver location: {location_address}")
|
| 3344 |
except Exception as e:
|
| 3345 |
logger.warning(f"Failed to reverse geocode driver location: {e}")
|
|
@@ -3424,7 +3444,8 @@ def handle_search_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3424 |
query = """
|
| 3425 |
SELECT
|
| 3426 |
driver_id, name, phone, email,
|
| 3427 |
-
|
|
|
|
| 3428 |
FROM drivers
|
| 3429 |
WHERE
|
| 3430 |
user_id = %s AND (
|
|
@@ -3454,14 +3475,29 @@ def handle_search_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3454 |
|
| 3455 |
drivers = []
|
| 3456 |
for row in results:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3457 |
drivers.append({
|
| 3458 |
"driver_id": row['driver_id'],
|
| 3459 |
"name": row['name'],
|
| 3460 |
"phone": row['phone'],
|
| 3461 |
"email": row['email'],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3462 |
"vehicle_type": row['vehicle_type'],
|
| 3463 |
"vehicle_plate": row['vehicle_plate'],
|
| 3464 |
"status": row['status'],
|
|
|
|
| 3465 |
"created_at": str(row['created_at'])
|
| 3466 |
})
|
| 3467 |
|
|
@@ -3512,7 +3548,7 @@ def handle_get_available_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3512 |
query = """
|
| 3513 |
SELECT
|
| 3514 |
driver_id, name, phone, vehicle_type, vehicle_plate,
|
| 3515 |
-
current_lat, current_lng, last_location_update,
|
| 3516 |
status, capacity_kg, capacity_m3, skills
|
| 3517 |
FROM drivers
|
| 3518 |
WHERE user_id = %s AND status IN ('active', 'offline')
|
|
@@ -3554,6 +3590,7 @@ def handle_get_available_drivers(tool_input: dict, user_id: str = None) -> dict:
|
|
| 3554 |
"location": {
|
| 3555 |
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 3556 |
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
|
|
|
| 3557 |
"last_update": str(row['last_location_update']) if row['last_location_update'] else None
|
| 3558 |
},
|
| 3559 |
"status": row['status'],
|
|
@@ -5067,17 +5104,18 @@ def handle_complete_delivery(tool_input: dict, user_id: str = None) -> dict:
|
|
| 5067 |
WHERE assignment_id = %s
|
| 5068 |
""", tuple(params))
|
| 5069 |
|
| 5070 |
-
# Step 2: Update driver location to delivery address
|
| 5071 |
cursor.execute("""
|
| 5072 |
UPDATE drivers
|
| 5073 |
SET current_lat = %s,
|
| 5074 |
current_lng = %s,
|
|
|
|
| 5075 |
last_location_update = %s,
|
| 5076 |
updated_at = %s
|
| 5077 |
WHERE driver_id = %s
|
| 5078 |
-
""", (delivery_lat, delivery_lng, completion_time, completion_time, driver_id))
|
| 5079 |
|
| 5080 |
-
logger.info(f"Driver {driver_id} location updated to delivery address: ({delivery_lat}, {delivery_lng})")
|
| 5081 |
|
| 5082 |
# Step 3: Calculate delivery performance status
|
| 5083 |
delivery_status = "on_time" # Default
|
|
@@ -5210,6 +5248,7 @@ def handle_fail_delivery(tool_input: dict, user_id: str = None) -> dict:
|
|
| 5210 |
from datetime import datetime
|
| 5211 |
|
| 5212 |
assignment_id = (tool_input.get("assignment_id") or "").strip()
|
|
|
|
| 5213 |
current_lat = tool_input.get("current_lat")
|
| 5214 |
current_lng = tool_input.get("current_lng")
|
| 5215 |
failure_reason = (tool_input.get("failure_reason") or "").strip()
|
|
@@ -5229,10 +5268,10 @@ def handle_fail_delivery(tool_input: dict, user_id: str = None) -> dict:
|
|
| 5229 |
"error": "Delivery failure requires confirm=true for safety"
|
| 5230 |
}
|
| 5231 |
|
| 5232 |
-
if current_lat is None or current_lng is None:
|
| 5233 |
return {
|
| 5234 |
"success": False,
|
| 5235 |
-
"error": "Driver must provide current location (current_lat and current_lng required)"
|
| 5236 |
}
|
| 5237 |
|
| 5238 |
if not failure_reason:
|
|
@@ -5345,17 +5384,18 @@ def handle_fail_delivery(tool_input: dict, user_id: str = None) -> dict:
|
|
| 5345 |
WHERE assignment_id = %s
|
| 5346 |
""", tuple(params))
|
| 5347 |
|
| 5348 |
-
# Step 2: Update driver location to reported current location
|
| 5349 |
cursor.execute("""
|
| 5350 |
UPDATE drivers
|
| 5351 |
SET current_lat = %s,
|
| 5352 |
current_lng = %s,
|
|
|
|
| 5353 |
last_location_update = %s,
|
| 5354 |
updated_at = %s
|
| 5355 |
WHERE driver_id = %s
|
| 5356 |
-
""", (current_lat, current_lng, failure_time, failure_time, driver_id))
|
| 5357 |
|
| 5358 |
-
logger.info(f"Driver {driver_id} location updated to reported position: ({current_lat}, {current_lng})")
|
| 5359 |
|
| 5360 |
# Step 3: Calculate delivery performance status for failure
|
| 5361 |
delivery_status = "failed_on_time" # Default - failed but before deadline
|
|
@@ -5433,10 +5473,11 @@ def handle_fail_delivery(tool_input: dict, user_id: str = None) -> dict:
|
|
| 5433 |
"driver_location": {
|
| 5434 |
"lat": current_lat,
|
| 5435 |
"lng": current_lng,
|
|
|
|
| 5436 |
"updated_at": failure_time.isoformat()
|
| 5437 |
},
|
| 5438 |
"cascading_actions": cascading_actions,
|
| 5439 |
-
"message": f"Delivery failed for order {order_id}. Reason: {reason_display}. Timing: {timing_info.get('status', delivery_status)}. Driver {driver_name} location updated to ({current_lat}, {current_lng})."
|
| 5440 |
}
|
| 5441 |
|
| 5442 |
except Exception as e:
|
|
|
|
| 141 |
},
|
| 142 |
{
|
| 143 |
"name": "create_driver",
|
| 144 |
+
"description": "Create a new delivery driver in the database. Use this to onboard new drivers to the fleet. Requires driver location (address + coordinates).",
|
| 145 |
"input_schema": {
|
| 146 |
"type": "object",
|
| 147 |
"properties": {
|
|
|
|
| 159 |
},
|
| 160 |
"vehicle_type": {
|
| 161 |
"type": "string",
|
| 162 |
+
"description": "Type of vehicle: van, truck, car, motorcycle"
|
| 163 |
},
|
| 164 |
"vehicle_plate": {
|
| 165 |
"type": "string",
|
| 166 |
"description": "Vehicle license plate number (optional)"
|
| 167 |
},
|
| 168 |
+
"current_address": {
|
| 169 |
+
"type": "string",
|
| 170 |
+
"description": "Driver's current location address (e.g., '123 Main St, New York, NY')"
|
| 171 |
+
},
|
| 172 |
+
"current_lat": {
|
| 173 |
+
"type": "number",
|
| 174 |
+
"description": "Driver's current latitude coordinate"
|
| 175 |
+
},
|
| 176 |
+
"current_lng": {
|
| 177 |
+
"type": "number",
|
| 178 |
+
"description": "Driver's current longitude coordinate"
|
| 179 |
+
},
|
| 180 |
"capacity_kg": {
|
| 181 |
"type": "number",
|
| 182 |
"description": "Vehicle cargo capacity in kilograms (default: 1000.0)"
|
|
|
|
| 198 |
"description": "Driver status (default: active)"
|
| 199 |
}
|
| 200 |
},
|
| 201 |
+
"required": ["name", "vehicle_type", "current_address", "current_lat", "current_lng"]
|
| 202 |
}
|
| 203 |
},
|
| 204 |
{
|
|
|
|
| 1569 |
capacity_m3 = tool_input.get("capacity_m3", 12.0)
|
| 1570 |
current_lat = tool_input.get("current_lat") # No default - REQUIRED
|
| 1571 |
current_lng = tool_input.get("current_lng") # No default - REQUIRED
|
| 1572 |
+
current_address = tool_input.get("current_address") # No default - REQUIRED
|
| 1573 |
|
| 1574 |
# Convert skills to regular list (handles protobuf RepeatedComposite)
|
| 1575 |
skills_raw = tool_input.get("skills", [])
|
|
|
|
| 1595 |
|
| 1596 |
status = tool_input.get("status", "active")
|
| 1597 |
|
| 1598 |
+
# Validate ALL required fields (name, vehicle_type, current_address, current_lat, current_lng)
|
| 1599 |
+
if not all([name, vehicle_type, current_address, current_lat is not None, current_lng is not None]):
|
| 1600 |
return {
|
| 1601 |
"success": False,
|
| 1602 |
+
"error": "Missing required fields: name, vehicle_type, current_address, current_lat, current_lng. All fields are mandatory."
|
| 1603 |
}
|
| 1604 |
|
| 1605 |
# Validate coordinates are valid numbers
|
|
|
|
| 1633 |
query = """
|
| 1634 |
INSERT INTO drivers (
|
| 1635 |
driver_id, name, phone, email,
|
| 1636 |
+
current_lat, current_lng, current_address, last_location_update,
|
| 1637 |
status, vehicle_type, vehicle_plate,
|
| 1638 |
capacity_kg, capacity_m3, skills, user_id
|
| 1639 |
+
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
| 1640 |
"""
|
| 1641 |
|
| 1642 |
# Convert skills list to JSON
|
|
|
|
| 1650 |
email,
|
| 1651 |
current_lat,
|
| 1652 |
current_lng,
|
| 1653 |
+
current_address,
|
| 1654 |
now,
|
| 1655 |
status,
|
| 1656 |
vehicle_type,
|
|
|
|
| 1669 |
"success": True,
|
| 1670 |
"driver_id": driver_id,
|
| 1671 |
"name": name,
|
| 1672 |
+
"phone": phone,
|
| 1673 |
"status": status,
|
| 1674 |
"vehicle_type": vehicle_type,
|
| 1675 |
"vehicle_plate": vehicle_plate,
|
| 1676 |
"capacity_kg": capacity_kg,
|
| 1677 |
"skills": skills,
|
| 1678 |
+
"location": {
|
| 1679 |
+
"latitude": current_lat,
|
| 1680 |
+
"longitude": current_lng,
|
| 1681 |
+
"address": current_address
|
| 1682 |
+
},
|
| 1683 |
"message": f"Driver {driver_id} ({name}) created successfully!"
|
| 1684 |
}
|
| 1685 |
except Exception as e:
|
|
|
|
| 3207 |
query = f"""
|
| 3208 |
SELECT
|
| 3209 |
driver_id, name, phone, email,
|
| 3210 |
+
current_lat, current_lng, current_address, last_location_update,
|
| 3211 |
status, vehicle_type, vehicle_plate,
|
| 3212 |
capacity_kg, capacity_m3, skills,
|
| 3213 |
created_at, updated_at
|
|
|
|
| 3252 |
"location": {
|
| 3253 |
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 3254 |
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
| 3255 |
+
"address": row['current_address'],
|
| 3256 |
"last_update": str(row['last_location_update']) if row['last_location_update'] else None
|
| 3257 |
},
|
| 3258 |
"status": row['status'],
|
|
|
|
| 3323 |
query = """
|
| 3324 |
SELECT
|
| 3325 |
driver_id, name, phone, email,
|
| 3326 |
+
current_lat, current_lng, current_address, last_location_update,
|
| 3327 |
status, vehicle_type, vehicle_plate,
|
| 3328 |
capacity_kg, capacity_m3, skills,
|
| 3329 |
created_at, updated_at
|
|
|
|
| 3351 |
except:
|
| 3352 |
skills = []
|
| 3353 |
|
| 3354 |
+
# Use stored address from database, or reverse geocode if not available
|
| 3355 |
+
location_address = row['current_address']
|
| 3356 |
+
if not location_address and row['current_lat'] and row['current_lng']:
|
|
|
|
| 3357 |
try:
|
| 3358 |
reverse_result = safe_reverse_geocode(
|
| 3359 |
float(row['current_lat']),
|
| 3360 |
float(row['current_lng'])
|
| 3361 |
)
|
| 3362 |
+
location_address = reverse_result.get('address', None)
|
| 3363 |
logger.info(f"Reverse geocoded driver location: {location_address}")
|
| 3364 |
except Exception as e:
|
| 3365 |
logger.warning(f"Failed to reverse geocode driver location: {e}")
|
|
|
|
| 3444 |
query = """
|
| 3445 |
SELECT
|
| 3446 |
driver_id, name, phone, email,
|
| 3447 |
+
current_lat, current_lng, current_address,
|
| 3448 |
+
vehicle_type, vehicle_plate, status, skills, created_at
|
| 3449 |
FROM drivers
|
| 3450 |
WHERE
|
| 3451 |
user_id = %s AND (
|
|
|
|
| 3475 |
|
| 3476 |
drivers = []
|
| 3477 |
for row in results:
|
| 3478 |
+
# Parse skills JSON if present
|
| 3479 |
+
skills = []
|
| 3480 |
+
if row['skills']:
|
| 3481 |
+
try:
|
| 3482 |
+
import json
|
| 3483 |
+
skills = json.loads(row['skills']) if isinstance(row['skills'], str) else row['skills']
|
| 3484 |
+
except:
|
| 3485 |
+
skills = []
|
| 3486 |
+
|
| 3487 |
drivers.append({
|
| 3488 |
"driver_id": row['driver_id'],
|
| 3489 |
"name": row['name'],
|
| 3490 |
"phone": row['phone'],
|
| 3491 |
"email": row['email'],
|
| 3492 |
+
"location": {
|
| 3493 |
+
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 3494 |
+
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
| 3495 |
+
"address": row['current_address']
|
| 3496 |
+
},
|
| 3497 |
"vehicle_type": row['vehicle_type'],
|
| 3498 |
"vehicle_plate": row['vehicle_plate'],
|
| 3499 |
"status": row['status'],
|
| 3500 |
+
"skills": skills,
|
| 3501 |
"created_at": str(row['created_at'])
|
| 3502 |
})
|
| 3503 |
|
|
|
|
| 3548 |
query = """
|
| 3549 |
SELECT
|
| 3550 |
driver_id, name, phone, vehicle_type, vehicle_plate,
|
| 3551 |
+
current_lat, current_lng, current_address, last_location_update,
|
| 3552 |
status, capacity_kg, capacity_m3, skills
|
| 3553 |
FROM drivers
|
| 3554 |
WHERE user_id = %s AND status IN ('active', 'offline')
|
|
|
|
| 3590 |
"location": {
|
| 3591 |
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 3592 |
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
| 3593 |
+
"address": row['current_address'],
|
| 3594 |
"last_update": str(row['last_location_update']) if row['last_location_update'] else None
|
| 3595 |
},
|
| 3596 |
"status": row['status'],
|
|
|
|
| 5104 |
WHERE assignment_id = %s
|
| 5105 |
""", tuple(params))
|
| 5106 |
|
| 5107 |
+
# Step 2: Update driver location to delivery address (including address text)
|
| 5108 |
cursor.execute("""
|
| 5109 |
UPDATE drivers
|
| 5110 |
SET current_lat = %s,
|
| 5111 |
current_lng = %s,
|
| 5112 |
+
current_address = %s,
|
| 5113 |
last_location_update = %s,
|
| 5114 |
updated_at = %s
|
| 5115 |
WHERE driver_id = %s
|
| 5116 |
+
""", (delivery_lat, delivery_lng, delivery_address, completion_time, completion_time, driver_id))
|
| 5117 |
|
| 5118 |
+
logger.info(f"Driver {driver_id} location updated to delivery address: {delivery_address} ({delivery_lat}, {delivery_lng})")
|
| 5119 |
|
| 5120 |
# Step 3: Calculate delivery performance status
|
| 5121 |
delivery_status = "on_time" # Default
|
|
|
|
| 5248 |
from datetime import datetime
|
| 5249 |
|
| 5250 |
assignment_id = (tool_input.get("assignment_id") or "").strip()
|
| 5251 |
+
current_address = (tool_input.get("current_address") or "").strip()
|
| 5252 |
current_lat = tool_input.get("current_lat")
|
| 5253 |
current_lng = tool_input.get("current_lng")
|
| 5254 |
failure_reason = (tool_input.get("failure_reason") or "").strip()
|
|
|
|
| 5268 |
"error": "Delivery failure requires confirm=true for safety"
|
| 5269 |
}
|
| 5270 |
|
| 5271 |
+
if not current_address or current_lat is None or current_lng is None:
|
| 5272 |
return {
|
| 5273 |
"success": False,
|
| 5274 |
+
"error": "Driver must provide current location (current_address, current_lat, and current_lng required)"
|
| 5275 |
}
|
| 5276 |
|
| 5277 |
if not failure_reason:
|
|
|
|
| 5384 |
WHERE assignment_id = %s
|
| 5385 |
""", tuple(params))
|
| 5386 |
|
| 5387 |
+
# Step 2: Update driver location to reported current location (address provided by user)
|
| 5388 |
cursor.execute("""
|
| 5389 |
UPDATE drivers
|
| 5390 |
SET current_lat = %s,
|
| 5391 |
current_lng = %s,
|
| 5392 |
+
current_address = %s,
|
| 5393 |
last_location_update = %s,
|
| 5394 |
updated_at = %s
|
| 5395 |
WHERE driver_id = %s
|
| 5396 |
+
""", (current_lat, current_lng, current_address, failure_time, failure_time, driver_id))
|
| 5397 |
|
| 5398 |
+
logger.info(f"Driver {driver_id} location updated to reported position: {current_address} ({current_lat}, {current_lng})")
|
| 5399 |
|
| 5400 |
# Step 3: Calculate delivery performance status for failure
|
| 5401 |
delivery_status = "failed_on_time" # Default - failed but before deadline
|
|
|
|
| 5473 |
"driver_location": {
|
| 5474 |
"lat": current_lat,
|
| 5475 |
"lng": current_lng,
|
| 5476 |
+
"address": current_address,
|
| 5477 |
"updated_at": failure_time.isoformat()
|
| 5478 |
},
|
| 5479 |
"cascading_actions": cascading_actions,
|
| 5480 |
+
"message": f"Delivery failed for order {order_id}. Reason: {reason_display}. Timing: {timing_info.get('status', delivery_status)}. Driver {driver_name} location updated to {current_address or f'({current_lat}, {current_lng})'}."
|
| 5481 |
}
|
| 5482 |
|
| 5483 |
except Exception as e:
|
database/connection.py
CHANGED
|
@@ -46,7 +46,7 @@ def get_db_connection() -> psycopg2.extensions.connection:
|
|
| 46 |
user=DB_CONFIG['user'],
|
| 47 |
password=DB_CONFIG['password'],
|
| 48 |
cursor_factory=psycopg2.extras.RealDictCursor,
|
| 49 |
-
sslmode='
|
| 50 |
)
|
| 51 |
|
| 52 |
logger.info(f"Database connection established: {DB_CONFIG['database']}@{DB_CONFIG['host']}")
|
|
|
|
| 46 |
user=DB_CONFIG['user'],
|
| 47 |
password=DB_CONFIG['password'],
|
| 48 |
cursor_factory=psycopg2.extras.RealDictCursor,
|
| 49 |
+
sslmode='prefer'
|
| 50 |
)
|
| 51 |
|
| 52 |
logger.info(f"Database connection established: {DB_CONFIG['database']}@{DB_CONFIG['host']}")
|
database/migrations/008_add_driver_address.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Migration 008: Add current_address field to drivers table
|
| 3 |
+
This stores the address for driver locations (provided by user along with lat/lng)
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
# Add parent directory to path
|
| 10 |
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
| 11 |
+
|
| 12 |
+
from database.connection import execute_write
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def up():
|
| 16 |
+
"""Add current_address column to drivers table"""
|
| 17 |
+
|
| 18 |
+
migrations = [
|
| 19 |
+
"""
|
| 20 |
+
ALTER TABLE drivers
|
| 21 |
+
ADD COLUMN IF NOT EXISTS current_address TEXT;
|
| 22 |
+
""",
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
print("Migration 008: Adding current_address column to drivers...")
|
| 26 |
+
|
| 27 |
+
for i, sql in enumerate(migrations, 1):
|
| 28 |
+
try:
|
| 29 |
+
print(f" [{i}/{len(migrations)}] Executing: {sql.strip()[:60]}...")
|
| 30 |
+
execute_write(sql)
|
| 31 |
+
print(f" Success")
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print(f" Warning: {e}")
|
| 34 |
+
|
| 35 |
+
print("\nMigration 008 complete!")
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def down():
|
| 39 |
+
"""Remove current_address column from drivers table"""
|
| 40 |
+
|
| 41 |
+
rollback_migrations = [
|
| 42 |
+
"ALTER TABLE drivers DROP COLUMN IF EXISTS current_address;",
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
+
print("Rolling back Migration 008...")
|
| 46 |
+
|
| 47 |
+
for i, sql in enumerate(rollback_migrations, 1):
|
| 48 |
+
try:
|
| 49 |
+
print(f" [{i}/{len(rollback_migrations)}] {sql[:60]}...")
|
| 50 |
+
execute_write(sql)
|
| 51 |
+
print(f" Success")
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(f" Warning: {e}")
|
| 54 |
+
|
| 55 |
+
print("\nRollback complete!")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
if __name__ == "__main__":
|
| 59 |
+
import sys
|
| 60 |
+
|
| 61 |
+
if len(sys.argv) > 1 and sys.argv[1] == "down":
|
| 62 |
+
down()
|
| 63 |
+
else:
|
| 64 |
+
up()
|
server.py
CHANGED
|
@@ -1015,6 +1015,7 @@ def delete_order(order_id: str, confirm: bool) -> dict:
|
|
| 1015 |
def create_driver(
|
| 1016 |
name: str,
|
| 1017 |
vehicle_type: str,
|
|
|
|
| 1018 |
current_lat: float,
|
| 1019 |
current_lng: float,
|
| 1020 |
phone: str | None = None,
|
|
@@ -1031,6 +1032,7 @@ def create_driver(
|
|
| 1031 |
Args:
|
| 1032 |
name: Full name of the driver (REQUIRED)
|
| 1033 |
vehicle_type: Type of vehicle: van, truck, car, motorcycle (REQUIRED)
|
|
|
|
| 1034 |
current_lat: Driver's current latitude location (REQUIRED, -90 to 90)
|
| 1035 |
current_lng: Driver's current longitude location (REQUIRED, -180 to 180)
|
| 1036 |
phone: Driver phone number (optional)
|
|
@@ -1051,11 +1053,12 @@ def create_driver(
|
|
| 1051 |
vehicle_plate: str,
|
| 1052 |
capacity_kg: float,
|
| 1053 |
skills: list[str],
|
|
|
|
| 1054 |
message: str
|
| 1055 |
}
|
| 1056 |
"""
|
| 1057 |
from chat.tools import handle_create_driver
|
| 1058 |
-
logger.info(f"Tool: create_driver(name='{name}', vehicle_type='{vehicle_type}', location=({current_lat}, {current_lng}))")
|
| 1059 |
|
| 1060 |
# STEP 1: Authenticate user
|
| 1061 |
user = get_authenticated_user()
|
|
@@ -1078,6 +1081,7 @@ def create_driver(
|
|
| 1078 |
"capacity_m3": capacity_m3,
|
| 1079 |
"skills": skills or [],
|
| 1080 |
"status": status,
|
|
|
|
| 1081 |
"current_lat": current_lat,
|
| 1082 |
"current_lng": current_lng
|
| 1083 |
}, user_id=user['user_id'])
|
|
@@ -1915,6 +1919,7 @@ def complete_delivery(
|
|
| 1915 |
@mcp.tool()
|
| 1916 |
def fail_delivery(
|
| 1917 |
assignment_id: str,
|
|
|
|
| 1918 |
current_lat: float,
|
| 1919 |
current_lng: float,
|
| 1920 |
failure_reason: str,
|
|
@@ -1924,7 +1929,7 @@ def fail_delivery(
|
|
| 1924 |
"""
|
| 1925 |
Mark a delivery as failed with mandatory driver location and failure reason.
|
| 1926 |
|
| 1927 |
-
IMPORTANT: Driver MUST provide their current GPS
|
| 1928 |
This ensures accurate location tracking and proper failure documentation.
|
| 1929 |
|
| 1930 |
Handles all necessary updates:
|
|
@@ -1947,12 +1952,13 @@ def fail_delivery(
|
|
| 1947 |
|
| 1948 |
Requirements:
|
| 1949 |
- Assignment must be in 'active' or 'in_progress' status
|
| 1950 |
-
- Driver must provide current GPS coordinates
|
| 1951 |
- Must provide a valid failure_reason from the list above
|
| 1952 |
- Requires confirm=true
|
| 1953 |
|
| 1954 |
Args:
|
| 1955 |
assignment_id: Assignment ID to mark as failed (e.g., 'ASN-20250114123456')
|
|
|
|
| 1956 |
current_lat: Driver's current latitude (-90 to 90)
|
| 1957 |
current_lng: Driver's current longitude (-180 to 180)
|
| 1958 |
failure_reason: Reason for failure (must be from valid list)
|
|
@@ -1971,7 +1977,7 @@ def fail_delivery(
|
|
| 1971 |
failure_reason: str,
|
| 1972 |
failure_reason_display: str (human-readable),
|
| 1973 |
delivery_address: str,
|
| 1974 |
-
driver_location: {lat, lng, updated_at},
|
| 1975 |
cascading_actions: list[str],
|
| 1976 |
message: str
|
| 1977 |
}
|
|
@@ -1992,6 +1998,7 @@ def fail_delivery(
|
|
| 1992 |
# STEP 3: Execute with user_id
|
| 1993 |
return handle_fail_delivery({
|
| 1994 |
"assignment_id": assignment_id,
|
|
|
|
| 1995 |
"current_lat": current_lat,
|
| 1996 |
"current_lng": current_lng,
|
| 1997 |
"failure_reason": failure_reason,
|
|
|
|
| 1015 |
def create_driver(
|
| 1016 |
name: str,
|
| 1017 |
vehicle_type: str,
|
| 1018 |
+
current_address: str,
|
| 1019 |
current_lat: float,
|
| 1020 |
current_lng: float,
|
| 1021 |
phone: str | None = None,
|
|
|
|
| 1032 |
Args:
|
| 1033 |
name: Full name of the driver (REQUIRED)
|
| 1034 |
vehicle_type: Type of vehicle: van, truck, car, motorcycle (REQUIRED)
|
| 1035 |
+
current_address: Driver's current location address, e.g. '123 Main St, New York, NY' (REQUIRED)
|
| 1036 |
current_lat: Driver's current latitude location (REQUIRED, -90 to 90)
|
| 1037 |
current_lng: Driver's current longitude location (REQUIRED, -180 to 180)
|
| 1038 |
phone: Driver phone number (optional)
|
|
|
|
| 1053 |
vehicle_plate: str,
|
| 1054 |
capacity_kg: float,
|
| 1055 |
skills: list[str],
|
| 1056 |
+
location: {latitude, longitude, address},
|
| 1057 |
message: str
|
| 1058 |
}
|
| 1059 |
"""
|
| 1060 |
from chat.tools import handle_create_driver
|
| 1061 |
+
logger.info(f"Tool: create_driver(name='{name}', vehicle_type='{vehicle_type}', address='{current_address}', location=({current_lat}, {current_lng}))")
|
| 1062 |
|
| 1063 |
# STEP 1: Authenticate user
|
| 1064 |
user = get_authenticated_user()
|
|
|
|
| 1081 |
"capacity_m3": capacity_m3,
|
| 1082 |
"skills": skills or [],
|
| 1083 |
"status": status,
|
| 1084 |
+
"current_address": current_address,
|
| 1085 |
"current_lat": current_lat,
|
| 1086 |
"current_lng": current_lng
|
| 1087 |
}, user_id=user['user_id'])
|
|
|
|
| 1919 |
@mcp.tool()
|
| 1920 |
def fail_delivery(
|
| 1921 |
assignment_id: str,
|
| 1922 |
+
current_address: str,
|
| 1923 |
current_lat: float,
|
| 1924 |
current_lng: float,
|
| 1925 |
failure_reason: str,
|
|
|
|
| 1929 |
"""
|
| 1930 |
Mark a delivery as failed with mandatory driver location and failure reason.
|
| 1931 |
|
| 1932 |
+
IMPORTANT: Driver MUST provide their current location (address + GPS coordinates) and a valid failure reason.
|
| 1933 |
This ensures accurate location tracking and proper failure documentation.
|
| 1934 |
|
| 1935 |
Handles all necessary updates:
|
|
|
|
| 1952 |
|
| 1953 |
Requirements:
|
| 1954 |
- Assignment must be in 'active' or 'in_progress' status
|
| 1955 |
+
- Driver must provide current address and GPS coordinates
|
| 1956 |
- Must provide a valid failure_reason from the list above
|
| 1957 |
- Requires confirm=true
|
| 1958 |
|
| 1959 |
Args:
|
| 1960 |
assignment_id: Assignment ID to mark as failed (e.g., 'ASN-20250114123456')
|
| 1961 |
+
current_address: Driver's current location address (e.g., '123 Main St, New York, NY')
|
| 1962 |
current_lat: Driver's current latitude (-90 to 90)
|
| 1963 |
current_lng: Driver's current longitude (-180 to 180)
|
| 1964 |
failure_reason: Reason for failure (must be from valid list)
|
|
|
|
| 1977 |
failure_reason: str,
|
| 1978 |
failure_reason_display: str (human-readable),
|
| 1979 |
delivery_address: str,
|
| 1980 |
+
driver_location: {lat, lng, address, updated_at},
|
| 1981 |
cascading_actions: list[str],
|
| 1982 |
message: str
|
| 1983 |
}
|
|
|
|
| 1998 |
# STEP 3: Execute with user_id
|
| 1999 |
return handle_fail_delivery({
|
| 2000 |
"assignment_id": assignment_id,
|
| 2001 |
+
"current_address": current_address,
|
| 2002 |
"current_lat": current_lat,
|
| 2003 |
"current_lng": current_lng,
|
| 2004 |
"failure_reason": failure_reason,
|