Commit
·
1994cae
1
Parent(s):
2575457
Add new tools for driver and order management
Browse files- Implemented `create_driver` tool for onboarding new drivers with detailed input schema.
- Added `count_orders`, `fetch_orders`, `get_order_details`, `search_orders`, and `get_incomplete_orders` tools for order management.
- Introduced `count_drivers`, `fetch_drivers`, `get_driver_details`, `search_drivers`, and `get_available_drivers` tools for driver management.
- Enhanced `execute_tool` function to handle new tools and their respective logic.
- Added database query handling for counting and fetching orders and drivers with optional filters, pagination, and sorting.
- chat/providers/claude_provider.py +106 -8
- chat/providers/gemini_provider.py +356 -5
- chat/tools.py +1230 -1
chat/providers/claude_provider.py
CHANGED
|
@@ -40,7 +40,7 @@ class ClaudeProvider(AIProvider):
|
|
| 40 |
|
| 41 |
def _get_system_prompt(self) -> str:
|
| 42 |
"""Get the system prompt for Claude"""
|
| 43 |
-
return """You are an AI assistant for FleetMind, a delivery dispatch system. Your job is to help coordinators create delivery orders efficiently.
|
| 44 |
|
| 45 |
**IMPORTANT: When a user wants to create an order, FIRST show them this order form:**
|
| 46 |
|
|
@@ -63,7 +63,7 @@ Please provide the following details:
|
|
| 63 |
|
| 64 |
---
|
| 65 |
|
| 66 |
-
**Your Workflow:**
|
| 67 |
1. **If user says "create order" or similar:** Show the form above and ask them to provide the information
|
| 68 |
2. **If they provide all/most info:** Proceed immediately with geocoding and order creation
|
| 69 |
3. **If information is missing:** Show what's missing from the form and ask for those specific fields
|
|
@@ -72,14 +72,83 @@ Please provide the following details:
|
|
| 72 |
- Use `create_order` tool to save the order
|
| 73 |
- Provide a clear confirmation with order ID
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
**Important Rules:**
|
| 76 |
- ALWAYS geocode the address BEFORE creating an order
|
| 77 |
- Be efficient - don't ask questions one at a time
|
| 78 |
- Accept information in any format (natural language, bullet points, etc.)
|
| 79 |
- Keep responses concise and professional
|
| 80 |
-
- Show enthusiasm when orders are successfully created
|
|
|
|
| 81 |
|
| 82 |
-
Remember: Dispatch coordinators are busy - help them
|
| 83 |
|
| 84 |
def is_available(self) -> bool:
|
| 85 |
return self.api_available
|
|
@@ -243,7 +312,7 @@ To use Claude:
|
|
| 243 |
|
| 244 |
return """👋 Hello! I'm your AI dispatch assistant powered by **Claude Sonnet 3.5**.
|
| 245 |
|
| 246 |
-
I can help you create delivery orders quickly and efficiently!
|
| 247 |
|
| 248 |
---
|
| 249 |
|
|
@@ -262,13 +331,42 @@ I can help you create delivery orders quickly and efficiently!
|
|
| 262 |
|
| 263 |
---
|
| 264 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
**Quick Start Examples:**
|
| 266 |
|
| 267 |
-
✅
|
|
|
|
|
|
|
| 268 |
|
| 269 |
-
✅
|
| 270 |
|
| 271 |
-
✅
|
| 272 |
|
| 273 |
---
|
| 274 |
|
|
|
|
| 40 |
|
| 41 |
def _get_system_prompt(self) -> str:
|
| 42 |
"""Get the system prompt for Claude"""
|
| 43 |
+
return """You are an AI assistant for FleetMind, a delivery dispatch system. Your job is to help coordinators create and query delivery orders efficiently.
|
| 44 |
|
| 45 |
**IMPORTANT: When a user wants to create an order, FIRST show them this order form:**
|
| 46 |
|
|
|
|
| 63 |
|
| 64 |
---
|
| 65 |
|
| 66 |
+
**Your Workflow for ORDER CREATION:**
|
| 67 |
1. **If user says "create order" or similar:** Show the form above and ask them to provide the information
|
| 68 |
2. **If they provide all/most info:** Proceed immediately with geocoding and order creation
|
| 69 |
3. **If information is missing:** Show what's missing from the form and ask for those specific fields
|
|
|
|
| 72 |
- Use `create_order` tool to save the order
|
| 73 |
- Provide a clear confirmation with order ID
|
| 74 |
|
| 75 |
+
**Your Workflow for ORDER QUERYING (INTERACTIVE):**
|
| 76 |
+
|
| 77 |
+
When user asks to "fetch orders", "show orders", or "get orders":
|
| 78 |
+
1. First call `count_orders` (with any filters user mentioned)
|
| 79 |
+
2. Tell user: "I found X orders. How many would you like to see?"
|
| 80 |
+
3. Wait for user response
|
| 81 |
+
4. Call `fetch_orders` with the limit they specify
|
| 82 |
+
5. Display the results clearly
|
| 83 |
+
|
| 84 |
+
When user asks "which orders are incomplete/not complete/pending":
|
| 85 |
+
- Call `get_incomplete_orders` directly
|
| 86 |
+
- Show results with priority and deadline
|
| 87 |
+
|
| 88 |
+
When user asks about a specific order ID:
|
| 89 |
+
- Call `get_order_details` with the order_id
|
| 90 |
+
- Display all 26 fields clearly organized
|
| 91 |
+
|
| 92 |
+
**Available Tools:**
|
| 93 |
+
|
| 94 |
+
**Order Creation:**
|
| 95 |
+
- geocode_address: Convert address to GPS coordinates
|
| 96 |
+
- create_order: Create customer delivery order (REQUIRES geocoded address)
|
| 97 |
+
|
| 98 |
+
**Order Querying:**
|
| 99 |
+
- count_orders: Count orders with optional filters
|
| 100 |
+
- fetch_orders: Fetch N orders with pagination and filters
|
| 101 |
+
- get_order_details: Get complete info about specific order by ID
|
| 102 |
+
- search_orders: Search by customer name/email/phone/order ID
|
| 103 |
+
- get_incomplete_orders: Get all pending/assigned/in_transit orders
|
| 104 |
+
|
| 105 |
+
**Driver Creation:**
|
| 106 |
+
- create_driver: Add new driver to fleet
|
| 107 |
+
|
| 108 |
+
**Driver Querying:**
|
| 109 |
+
- count_drivers: Count drivers with optional filters
|
| 110 |
+
- fetch_drivers: Fetch N drivers with pagination and filters
|
| 111 |
+
- get_driver_details: Get complete info about specific driver by ID
|
| 112 |
+
- search_drivers: Search by name/email/phone/plate/driver ID
|
| 113 |
+
- get_available_drivers: Get all active/offline drivers
|
| 114 |
+
|
| 115 |
+
**Available Order Filters:**
|
| 116 |
+
- Status: pending, assigned, in_transit, delivered, failed, cancelled
|
| 117 |
+
- Priority: standard, express, urgent
|
| 118 |
+
- Payment: pending, paid, cod
|
| 119 |
+
- Booleans: is_fragile, requires_signature, requires_cold_storage
|
| 120 |
+
- Driver: assigned_driver_id
|
| 121 |
+
|
| 122 |
+
**Available Driver Filters:**
|
| 123 |
+
- Status: active, busy, offline, unavailable
|
| 124 |
+
- Vehicle Type: van, truck, car, motorcycle, etc.
|
| 125 |
+
|
| 126 |
+
**Your Workflow for DRIVER QUERYING (INTERACTIVE):**
|
| 127 |
+
|
| 128 |
+
When user asks to "show drivers", "fetch drivers", or "get drivers":
|
| 129 |
+
1. First call `count_drivers` (with any filters user mentioned)
|
| 130 |
+
2. Tell user: "I found X drivers. How many would you like to see?"
|
| 131 |
+
3. Wait for user response
|
| 132 |
+
4. Call `fetch_drivers` with the limit they specify
|
| 133 |
+
5. Display the results clearly
|
| 134 |
+
|
| 135 |
+
When user asks "which drivers are available/free":
|
| 136 |
+
- Call `get_available_drivers` directly
|
| 137 |
+
- Show results with status and vehicle info
|
| 138 |
+
|
| 139 |
+
When user asks about a specific driver ID:
|
| 140 |
+
- Call `get_driver_details` with the driver_id
|
| 141 |
+
- Display all 15 fields clearly organized
|
| 142 |
+
|
| 143 |
**Important Rules:**
|
| 144 |
- ALWAYS geocode the address BEFORE creating an order
|
| 145 |
- Be efficient - don't ask questions one at a time
|
| 146 |
- Accept information in any format (natural language, bullet points, etc.)
|
| 147 |
- Keep responses concise and professional
|
| 148 |
+
- Show enthusiasm when orders/drivers are successfully created
|
| 149 |
+
- For order/driver queries, be interactive and helpful with summaries
|
| 150 |
|
| 151 |
+
Remember: Dispatch coordinators are busy - help them work efficiently!"""
|
| 152 |
|
| 153 |
def is_available(self) -> bool:
|
| 154 |
return self.api_available
|
|
|
|
| 312 |
|
| 313 |
return """👋 Hello! I'm your AI dispatch assistant powered by **Claude Sonnet 3.5**.
|
| 314 |
|
| 315 |
+
I can help you create and query delivery orders and drivers quickly and efficiently!
|
| 316 |
|
| 317 |
---
|
| 318 |
|
|
|
|
| 331 |
|
| 332 |
---
|
| 333 |
|
| 334 |
+
🔍 **To Query Orders:**
|
| 335 |
+
|
| 336 |
+
• "Fetch orders" or "Show orders" - I'll ask how many
|
| 337 |
+
• "Which orders are incomplete?" - See all pending/in-progress orders
|
| 338 |
+
• "Tell me about order ORD-XXX" - Get complete order details
|
| 339 |
+
• "Show me 10 urgent orders" - Filter by priority
|
| 340 |
+
• "Search for orders from John" - Find by customer name
|
| 341 |
+
|
| 342 |
+
---
|
| 343 |
+
|
| 344 |
+
🚚 **To Create a Driver:**
|
| 345 |
+
|
| 346 |
+
**Required:** Driver Name
|
| 347 |
+
**Optional:** Phone, Email, Vehicle Type, Plate, Capacity, Skills
|
| 348 |
+
|
| 349 |
+
---
|
| 350 |
+
|
| 351 |
+
👥 **To Query Drivers:**
|
| 352 |
+
|
| 353 |
+
• "Show me drivers" or "Fetch drivers" - I'll ask how many
|
| 354 |
+
• "Which drivers are available?" - See all active/offline drivers
|
| 355 |
+
• "Tell me about driver DRV-XXX" - Get complete driver details
|
| 356 |
+
• "Show 5 active drivers with vans" - Filter by status and vehicle
|
| 357 |
+
• "Search for Tom" - Find by driver name
|
| 358 |
+
|
| 359 |
+
---
|
| 360 |
+
|
| 361 |
**Quick Start Examples:**
|
| 362 |
|
| 363 |
+
✅ **Create Order:** "Create order for John Doe, 123 Main St San Francisco CA, phone 555-1234, deliver by 5 PM"
|
| 364 |
+
|
| 365 |
+
✅ **Query Orders:** "Fetch the orders" or "Show me incomplete orders"
|
| 366 |
|
| 367 |
+
✅ **Create Driver:** "Add driver Tom Wilson, phone 555-0101, drives a van, plate ABC-123"
|
| 368 |
|
| 369 |
+
✅ **Query Drivers:** "Show me drivers" or "Which drivers are available?"
|
| 370 |
|
| 371 |
---
|
| 372 |
|
chat/providers/gemini_provider.py
CHANGED
|
@@ -78,10 +78,28 @@ User: "Create order for John Doe..."
|
|
| 78 |
You: [geocode_address] → "OK geocoded, now creating..." ❌ WRONG!
|
| 79 |
|
| 80 |
**Available Tools:**
|
|
|
|
|
|
|
| 81 |
- geocode_address: Convert address to GPS coordinates
|
| 82 |
- create_order: Create customer delivery order (REQUIRES geocoded address)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
- create_driver: Add new driver/delivery man to fleet
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
**Order Fields:**
|
| 86 |
Required: customer_name, delivery_address, contact
|
| 87 |
Optional: time_window_end, priority (standard/express/urgent), special_instructions, weight_kg
|
|
@@ -90,7 +108,85 @@ Optional: time_window_end, priority (standard/express/urgent), special_instructi
|
|
| 90 |
Required: name
|
| 91 |
Optional: phone, email, vehicle_type (van/truck/car/motorcycle), vehicle_plate, capacity_kg, capacity_m3, skills (list), status (active/busy/offline)
|
| 92 |
|
| 93 |
-
**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
def _get_gemini_tools(self) -> list:
|
| 96 |
"""Convert tool schemas to Gemini function calling format"""
|
|
@@ -208,6 +304,234 @@ Optional: phone, email, vehicle_type (van/truck/car/motorcycle), vehicle_plate,
|
|
| 208 |
},
|
| 209 |
required=["name"]
|
| 210 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
)
|
| 212 |
]
|
| 213 |
)
|
|
@@ -331,7 +655,7 @@ Optional: phone, email, vehicle_type (van/truck/car/motorcycle), vehicle_plate,
|
|
| 331 |
if has_function_call:
|
| 332 |
# Handle function calls (potentially multiple in sequence)
|
| 333 |
current_response = response
|
| 334 |
-
max_iterations =
|
| 335 |
|
| 336 |
for iteration in range(max_iterations):
|
| 337 |
# Check if current response has a function call
|
|
@@ -528,28 +852,55 @@ I can help you manage your delivery fleet!
|
|
| 528 |
• Contact (Phone OR Email)
|
| 529 |
• Optional: Deadline, Priority, Special Instructions
|
| 530 |
|
| 531 |
-
**2.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
• Driver Name (required)
|
| 533 |
• Optional: Phone, Email, Vehicle Type, License Plate, Skills
|
| 534 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
---
|
| 536 |
|
| 537 |
**Examples - Just Type Naturally:**
|
| 538 |
|
| 539 |
-
📦 **Orders:**
|
| 540 |
💬 "Create order for John Doe, 123 Main St San Francisco CA, phone 555-1234, deliver by 5 PM"
|
| 541 |
💬 "New urgent delivery to Sarah at 456 Oak Ave NYC, email [email protected]"
|
| 542 |
|
| 543 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 544 |
💬 "Add new driver Tom Wilson, phone 555-0101, drives a van, plate ABC-123"
|
| 545 |
💬 "Create driver Sarah Martinez with refrigerated truck, phone 555-0202"
|
| 546 |
💬 "New driver: Mike Chen, email [email protected], motorcycle delivery"
|
| 547 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
---
|
| 549 |
|
| 550 |
🚀 **I'll automatically:**
|
| 551 |
• Geocode addresses for orders
|
| 552 |
• Generate unique IDs
|
| 553 |
• Save everything to the database
|
|
|
|
| 554 |
|
| 555 |
What would you like to do?"""
|
|
|
|
| 78 |
You: [geocode_address] → "OK geocoded, now creating..." ❌ WRONG!
|
| 79 |
|
| 80 |
**Available Tools:**
|
| 81 |
+
|
| 82 |
+
**Order Creation:**
|
| 83 |
- geocode_address: Convert address to GPS coordinates
|
| 84 |
- create_order: Create customer delivery order (REQUIRES geocoded address)
|
| 85 |
+
|
| 86 |
+
**Order Querying (INTERACTIVE):**
|
| 87 |
+
- count_orders: Count orders with optional filters
|
| 88 |
+
- fetch_orders: Fetch N orders with pagination and filters
|
| 89 |
+
- get_order_details: Get complete info about specific order by ID
|
| 90 |
+
- search_orders: Search by customer name/email/phone/order ID
|
| 91 |
+
- get_incomplete_orders: Get all pending/assigned/in_transit orders
|
| 92 |
+
|
| 93 |
+
**Driver Creation:**
|
| 94 |
- create_driver: Add new driver/delivery man to fleet
|
| 95 |
|
| 96 |
+
**Driver Querying (INTERACTIVE):**
|
| 97 |
+
- count_drivers: Count drivers with optional filters
|
| 98 |
+
- fetch_drivers: Fetch N drivers with pagination and filters
|
| 99 |
+
- get_driver_details: Get complete info about specific driver by ID
|
| 100 |
+
- search_drivers: Search by name/email/phone/plate/driver ID
|
| 101 |
+
- get_available_drivers: Get all active/offline drivers ready for dispatch
|
| 102 |
+
|
| 103 |
**Order Fields:**
|
| 104 |
Required: customer_name, delivery_address, contact
|
| 105 |
Optional: time_window_end, priority (standard/express/urgent), special_instructions, weight_kg
|
|
|
|
| 108 |
Required: name
|
| 109 |
Optional: phone, email, vehicle_type (van/truck/car/motorcycle), vehicle_plate, capacity_kg, capacity_m3, skills (list), status (active/busy/offline)
|
| 110 |
|
| 111 |
+
**Order Query Workflow (INTERACTIVE - this is DIFFERENT from creation):**
|
| 112 |
+
|
| 113 |
+
When user asks to "fetch orders", "show orders", or "get orders":
|
| 114 |
+
1. First call count_orders (with any filters user mentioned)
|
| 115 |
+
2. Tell user: "I found X orders. How many would you like to see?"
|
| 116 |
+
3. Wait for user response
|
| 117 |
+
4. Call fetch_orders with the limit they specify
|
| 118 |
+
5. Display the results clearly
|
| 119 |
+
|
| 120 |
+
When user asks "which orders are incomplete/not complete/pending":
|
| 121 |
+
- Call get_incomplete_orders directly
|
| 122 |
+
- Show results with priority and deadline
|
| 123 |
+
|
| 124 |
+
When user asks about a specific order ID:
|
| 125 |
+
- Call get_order_details with the order_id
|
| 126 |
+
- Display all 26 fields clearly organized
|
| 127 |
+
|
| 128 |
+
**Available Filters (use when user specifies):**
|
| 129 |
+
- Status: pending, assigned, in_transit, delivered, failed, cancelled
|
| 130 |
+
- Priority: standard, express, urgent
|
| 131 |
+
- Payment: pending, paid, cod
|
| 132 |
+
- Booleans: is_fragile, requires_signature, requires_cold_storage
|
| 133 |
+
- Driver: assigned_driver_id
|
| 134 |
+
|
| 135 |
+
**Example Interactions:**
|
| 136 |
+
|
| 137 |
+
User: "Fetch the orders"
|
| 138 |
+
You: [count_orders] → "I found 45 orders in the database. How many would you like to see? (I can also filter by status, priority, etc.)"
|
| 139 |
+
|
| 140 |
+
User: "Show me 10 urgent ones"
|
| 141 |
+
You: [fetch_orders with limit=10, priority=urgent] → [Display 10 urgent orders]
|
| 142 |
+
|
| 143 |
+
User: "Which orders are incomplete?"
|
| 144 |
+
You: [get_incomplete_orders] → [Display all pending/assigned/in_transit orders]
|
| 145 |
+
|
| 146 |
+
User: "Tell me about order ORD-20251114120000"
|
| 147 |
+
You: [get_order_details with order_id=ORD-20251114120000] → [Display complete order details]
|
| 148 |
+
|
| 149 |
+
**Driver Query Workflow (INTERACTIVE - same as orders):**
|
| 150 |
+
|
| 151 |
+
When user asks to "show drivers", "fetch drivers", or "get drivers":
|
| 152 |
+
1. First call count_drivers (with any filters user mentioned)
|
| 153 |
+
2. Tell user: "I found X drivers. How many would you like to see?"
|
| 154 |
+
3. Wait for user response
|
| 155 |
+
4. Call fetch_drivers with the limit they specify
|
| 156 |
+
5. Display the results clearly
|
| 157 |
+
|
| 158 |
+
When user asks "which drivers are available/free":
|
| 159 |
+
- Call get_available_drivers directly
|
| 160 |
+
- Show results with status and vehicle info
|
| 161 |
+
|
| 162 |
+
When user asks about a specific driver ID:
|
| 163 |
+
- Call get_driver_details with the driver_id
|
| 164 |
+
- Display all 15 fields clearly organized
|
| 165 |
+
|
| 166 |
+
**Available Driver Filters:**
|
| 167 |
+
- Status: active, busy, offline, unavailable (4 values)
|
| 168 |
+
- Vehicle Type: van, truck, car, motorcycle, etc.
|
| 169 |
+
- Sorting: name, status, created_at, last_location_update
|
| 170 |
+
|
| 171 |
+
**Example Driver Interactions:**
|
| 172 |
+
|
| 173 |
+
User: "Show me drivers"
|
| 174 |
+
You: [count_drivers] → "I found 15 drivers. How many would you like to see?"
|
| 175 |
+
|
| 176 |
+
User: "Show 5 active ones with vans"
|
| 177 |
+
You: [fetch_drivers with limit=5, status=active, vehicle_type=van] → [Display 5 drivers]
|
| 178 |
+
|
| 179 |
+
User: "Which drivers are available?"
|
| 180 |
+
You: [get_available_drivers] → [Display all active/offline drivers]
|
| 181 |
+
|
| 182 |
+
User: "Tell me about driver DRV-20251114163800"
|
| 183 |
+
You: [get_driver_details with driver_id=DRV-20251114163800] → [Display complete driver details]
|
| 184 |
+
|
| 185 |
+
**Your goal:**
|
| 186 |
+
- Order CREATION: Execute in ONE smooth automated flow (no stopping!)
|
| 187 |
+
- Order QUERYING: Be interactive, ask user for preferences, provide helpful summaries
|
| 188 |
+
- Driver CREATION: Single tool call, immediate response
|
| 189 |
+
- Driver QUERYING: Be interactive, ask how many, provide helpful summaries"""
|
| 190 |
|
| 191 |
def _get_gemini_tools(self) -> list:
|
| 192 |
"""Convert tool schemas to Gemini function calling format"""
|
|
|
|
| 304 |
},
|
| 305 |
required=["name"]
|
| 306 |
)
|
| 307 |
+
),
|
| 308 |
+
genai.protos.FunctionDeclaration(
|
| 309 |
+
name="count_orders",
|
| 310 |
+
description="Count total orders in the database with optional filters. Use this when user asks 'how many orders', 'fetch orders', or wants to know order statistics.",
|
| 311 |
+
parameters=genai.protos.Schema(
|
| 312 |
+
type=genai.protos.Type.OBJECT,
|
| 313 |
+
properties={
|
| 314 |
+
"status": genai.protos.Schema(
|
| 315 |
+
type=genai.protos.Type.STRING,
|
| 316 |
+
description="Filter by order status: pending, assigned, in_transit, delivered, failed, cancelled (optional)"
|
| 317 |
+
),
|
| 318 |
+
"priority": genai.protos.Schema(
|
| 319 |
+
type=genai.protos.Type.STRING,
|
| 320 |
+
description="Filter by priority level: standard, express, urgent (optional)"
|
| 321 |
+
),
|
| 322 |
+
"payment_status": genai.protos.Schema(
|
| 323 |
+
type=genai.protos.Type.STRING,
|
| 324 |
+
description="Filter by payment status: pending, paid, cod (optional)"
|
| 325 |
+
),
|
| 326 |
+
"assigned_driver_id": genai.protos.Schema(
|
| 327 |
+
type=genai.protos.Type.STRING,
|
| 328 |
+
description="Filter by assigned driver ID (optional)"
|
| 329 |
+
),
|
| 330 |
+
"is_fragile": genai.protos.Schema(
|
| 331 |
+
type=genai.protos.Type.BOOLEAN,
|
| 332 |
+
description="Filter fragile packages only (optional)"
|
| 333 |
+
),
|
| 334 |
+
"requires_signature": genai.protos.Schema(
|
| 335 |
+
type=genai.protos.Type.BOOLEAN,
|
| 336 |
+
description="Filter orders requiring signature (optional)"
|
| 337 |
+
),
|
| 338 |
+
"requires_cold_storage": genai.protos.Schema(
|
| 339 |
+
type=genai.protos.Type.BOOLEAN,
|
| 340 |
+
description="Filter orders requiring cold storage (optional)"
|
| 341 |
+
)
|
| 342 |
+
},
|
| 343 |
+
required=[]
|
| 344 |
+
)
|
| 345 |
+
),
|
| 346 |
+
genai.protos.FunctionDeclaration(
|
| 347 |
+
name="fetch_orders",
|
| 348 |
+
description="Fetch orders from the database with optional filters, pagination, and sorting. Use after counting to show specific number of orders.",
|
| 349 |
+
parameters=genai.protos.Schema(
|
| 350 |
+
type=genai.protos.Type.OBJECT,
|
| 351 |
+
properties={
|
| 352 |
+
"limit": genai.protos.Schema(
|
| 353 |
+
type=genai.protos.Type.INTEGER,
|
| 354 |
+
description="Number of orders to fetch (default: 10, max: 100)"
|
| 355 |
+
),
|
| 356 |
+
"offset": genai.protos.Schema(
|
| 357 |
+
type=genai.protos.Type.INTEGER,
|
| 358 |
+
description="Number of orders to skip for pagination (default: 0)"
|
| 359 |
+
),
|
| 360 |
+
"status": genai.protos.Schema(
|
| 361 |
+
type=genai.protos.Type.STRING,
|
| 362 |
+
description="Filter by order status: pending, assigned, in_transit, delivered, failed, cancelled (optional)"
|
| 363 |
+
),
|
| 364 |
+
"priority": genai.protos.Schema(
|
| 365 |
+
type=genai.protos.Type.STRING,
|
| 366 |
+
description="Filter by priority level: standard, express, urgent (optional)"
|
| 367 |
+
),
|
| 368 |
+
"payment_status": genai.protos.Schema(
|
| 369 |
+
type=genai.protos.Type.STRING,
|
| 370 |
+
description="Filter by payment status: pending, paid, cod (optional)"
|
| 371 |
+
),
|
| 372 |
+
"assigned_driver_id": genai.protos.Schema(
|
| 373 |
+
type=genai.protos.Type.STRING,
|
| 374 |
+
description="Filter by assigned driver ID (optional)"
|
| 375 |
+
),
|
| 376 |
+
"is_fragile": genai.protos.Schema(
|
| 377 |
+
type=genai.protos.Type.BOOLEAN,
|
| 378 |
+
description="Filter fragile packages only (optional)"
|
| 379 |
+
),
|
| 380 |
+
"requires_signature": genai.protos.Schema(
|
| 381 |
+
type=genai.protos.Type.BOOLEAN,
|
| 382 |
+
description="Filter orders requiring signature (optional)"
|
| 383 |
+
),
|
| 384 |
+
"requires_cold_storage": genai.protos.Schema(
|
| 385 |
+
type=genai.protos.Type.BOOLEAN,
|
| 386 |
+
description="Filter orders requiring cold storage (optional)"
|
| 387 |
+
),
|
| 388 |
+
"sort_by": genai.protos.Schema(
|
| 389 |
+
type=genai.protos.Type.STRING,
|
| 390 |
+
description="Field to sort by: created_at, priority, time_window_start (default: created_at)"
|
| 391 |
+
),
|
| 392 |
+
"sort_order": genai.protos.Schema(
|
| 393 |
+
type=genai.protos.Type.STRING,
|
| 394 |
+
description="Sort order: ASC, DESC (default: DESC for newest first)"
|
| 395 |
+
)
|
| 396 |
+
},
|
| 397 |
+
required=[]
|
| 398 |
+
)
|
| 399 |
+
),
|
| 400 |
+
genai.protos.FunctionDeclaration(
|
| 401 |
+
name="get_order_details",
|
| 402 |
+
description="Get complete details of a specific order by order ID. Use when user asks 'tell me about order X' or wants detailed information about a specific order.",
|
| 403 |
+
parameters=genai.protos.Schema(
|
| 404 |
+
type=genai.protos.Type.OBJECT,
|
| 405 |
+
properties={
|
| 406 |
+
"order_id": genai.protos.Schema(
|
| 407 |
+
type=genai.protos.Type.STRING,
|
| 408 |
+
description="The order ID to fetch details for (e.g., 'ORD-20251114163800')"
|
| 409 |
+
)
|
| 410 |
+
},
|
| 411 |
+
required=["order_id"]
|
| 412 |
+
)
|
| 413 |
+
),
|
| 414 |
+
genai.protos.FunctionDeclaration(
|
| 415 |
+
name="search_orders",
|
| 416 |
+
description="Search for orders by customer name, email, phone, or order ID pattern. Use when user provides partial information to find orders.",
|
| 417 |
+
parameters=genai.protos.Schema(
|
| 418 |
+
type=genai.protos.Type.OBJECT,
|
| 419 |
+
properties={
|
| 420 |
+
"search_term": genai.protos.Schema(
|
| 421 |
+
type=genai.protos.Type.STRING,
|
| 422 |
+
description="Search term to match against customer_name, customer_email, customer_phone, or order_id"
|
| 423 |
+
)
|
| 424 |
+
},
|
| 425 |
+
required=["search_term"]
|
| 426 |
+
)
|
| 427 |
+
),
|
| 428 |
+
genai.protos.FunctionDeclaration(
|
| 429 |
+
name="get_incomplete_orders",
|
| 430 |
+
description="Get all orders that are not yet completed (excludes delivered and cancelled orders). Shortcut for finding orders in progress (pending, assigned, in_transit).",
|
| 431 |
+
parameters=genai.protos.Schema(
|
| 432 |
+
type=genai.protos.Type.OBJECT,
|
| 433 |
+
properties={
|
| 434 |
+
"limit": genai.protos.Schema(
|
| 435 |
+
type=genai.protos.Type.INTEGER,
|
| 436 |
+
description="Number of orders to fetch (default: 20)"
|
| 437 |
+
)
|
| 438 |
+
},
|
| 439 |
+
required=[]
|
| 440 |
+
)
|
| 441 |
+
),
|
| 442 |
+
genai.protos.FunctionDeclaration(
|
| 443 |
+
name="count_drivers",
|
| 444 |
+
description="Count total drivers in the database with optional filters. Use this when user asks 'how many drivers', 'show drivers', or wants driver statistics.",
|
| 445 |
+
parameters=genai.protos.Schema(
|
| 446 |
+
type=genai.protos.Type.OBJECT,
|
| 447 |
+
properties={
|
| 448 |
+
"status": genai.protos.Schema(
|
| 449 |
+
type=genai.protos.Type.STRING,
|
| 450 |
+
description="Filter by driver status: active, busy, offline, unavailable (optional)"
|
| 451 |
+
),
|
| 452 |
+
"vehicle_type": genai.protos.Schema(
|
| 453 |
+
type=genai.protos.Type.STRING,
|
| 454 |
+
description="Filter by vehicle type: van, truck, car, motorcycle, etc. (optional)"
|
| 455 |
+
)
|
| 456 |
+
},
|
| 457 |
+
required=[]
|
| 458 |
+
)
|
| 459 |
+
),
|
| 460 |
+
genai.protos.FunctionDeclaration(
|
| 461 |
+
name="fetch_drivers",
|
| 462 |
+
description="Fetch drivers from the database with optional filters, pagination, and sorting. Use after counting to show specific number of drivers.",
|
| 463 |
+
parameters=genai.protos.Schema(
|
| 464 |
+
type=genai.protos.Type.OBJECT,
|
| 465 |
+
properties={
|
| 466 |
+
"limit": genai.protos.Schema(
|
| 467 |
+
type=genai.protos.Type.INTEGER,
|
| 468 |
+
description="Number of drivers to fetch (default: 10, max: 100)"
|
| 469 |
+
),
|
| 470 |
+
"offset": genai.protos.Schema(
|
| 471 |
+
type=genai.protos.Type.INTEGER,
|
| 472 |
+
description="Number of drivers to skip for pagination (default: 0)"
|
| 473 |
+
),
|
| 474 |
+
"status": genai.protos.Schema(
|
| 475 |
+
type=genai.protos.Type.STRING,
|
| 476 |
+
description="Filter by driver status: active, busy, offline, unavailable (optional)"
|
| 477 |
+
),
|
| 478 |
+
"vehicle_type": genai.protos.Schema(
|
| 479 |
+
type=genai.protos.Type.STRING,
|
| 480 |
+
description="Filter by vehicle type: van, truck, car, motorcycle, etc. (optional)"
|
| 481 |
+
),
|
| 482 |
+
"sort_by": genai.protos.Schema(
|
| 483 |
+
type=genai.protos.Type.STRING,
|
| 484 |
+
description="Field to sort by: name, status, created_at, last_location_update (default: name)"
|
| 485 |
+
),
|
| 486 |
+
"sort_order": genai.protos.Schema(
|
| 487 |
+
type=genai.protos.Type.STRING,
|
| 488 |
+
description="Sort order: ASC, DESC (default: ASC for alphabetical)"
|
| 489 |
+
)
|
| 490 |
+
},
|
| 491 |
+
required=[]
|
| 492 |
+
)
|
| 493 |
+
),
|
| 494 |
+
genai.protos.FunctionDeclaration(
|
| 495 |
+
name="get_driver_details",
|
| 496 |
+
description="Get complete details of a specific driver by driver ID. Use when user asks 'tell me about driver X' or wants detailed information about a specific driver.",
|
| 497 |
+
parameters=genai.protos.Schema(
|
| 498 |
+
type=genai.protos.Type.OBJECT,
|
| 499 |
+
properties={
|
| 500 |
+
"driver_id": genai.protos.Schema(
|
| 501 |
+
type=genai.protos.Type.STRING,
|
| 502 |
+
description="The driver ID to fetch details for (e.g., 'DRV-20251114163800')"
|
| 503 |
+
)
|
| 504 |
+
},
|
| 505 |
+
required=["driver_id"]
|
| 506 |
+
)
|
| 507 |
+
),
|
| 508 |
+
genai.protos.FunctionDeclaration(
|
| 509 |
+
name="search_drivers",
|
| 510 |
+
description="Search for drivers by name, email, phone, vehicle plate, or driver ID pattern. Use when user provides partial information to find drivers.",
|
| 511 |
+
parameters=genai.protos.Schema(
|
| 512 |
+
type=genai.protos.Type.OBJECT,
|
| 513 |
+
properties={
|
| 514 |
+
"search_term": genai.protos.Schema(
|
| 515 |
+
type=genai.protos.Type.STRING,
|
| 516 |
+
description="Search term to match against name, email, phone, vehicle_plate, or driver_id"
|
| 517 |
+
)
|
| 518 |
+
},
|
| 519 |
+
required=["search_term"]
|
| 520 |
+
)
|
| 521 |
+
),
|
| 522 |
+
genai.protos.FunctionDeclaration(
|
| 523 |
+
name="get_available_drivers",
|
| 524 |
+
description="Get all drivers that are available for assignment (active or offline status, excludes busy and unavailable). Shortcut for finding drivers ready for dispatch.",
|
| 525 |
+
parameters=genai.protos.Schema(
|
| 526 |
+
type=genai.protos.Type.OBJECT,
|
| 527 |
+
properties={
|
| 528 |
+
"limit": genai.protos.Schema(
|
| 529 |
+
type=genai.protos.Type.INTEGER,
|
| 530 |
+
description="Number of drivers to fetch (default: 20)"
|
| 531 |
+
)
|
| 532 |
+
},
|
| 533 |
+
required=[]
|
| 534 |
+
)
|
| 535 |
)
|
| 536 |
]
|
| 537 |
)
|
|
|
|
| 655 |
if has_function_call:
|
| 656 |
# Handle function calls (potentially multiple in sequence)
|
| 657 |
current_response = response
|
| 658 |
+
max_iterations = 3 # Prevent rate limit errors (Gemini free tier: 15 req/min)
|
| 659 |
|
| 660 |
for iteration in range(max_iterations):
|
| 661 |
# Check if current response has a function call
|
|
|
|
| 852 |
• Contact (Phone OR Email)
|
| 853 |
• Optional: Deadline, Priority, Special Instructions
|
| 854 |
|
| 855 |
+
**2. Query & Search Orders:**
|
| 856 |
+
• Fetch orders with filters (status, priority, payment, etc.)
|
| 857 |
+
• Get complete details of specific orders
|
| 858 |
+
• Search by customer name, phone, email, or order ID
|
| 859 |
+
• Find incomplete/pending orders
|
| 860 |
+
|
| 861 |
+
**3. Create New Drivers:**
|
| 862 |
• Driver Name (required)
|
| 863 |
• Optional: Phone, Email, Vehicle Type, License Plate, Skills
|
| 864 |
|
| 865 |
+
**4. Query & Search Drivers:**
|
| 866 |
+
• Fetch drivers with filters (status, vehicle type)
|
| 867 |
+
• Get complete details of specific drivers
|
| 868 |
+
• Search by name, phone, email, plate, or driver ID
|
| 869 |
+
• Find available/active drivers
|
| 870 |
+
|
| 871 |
---
|
| 872 |
|
| 873 |
**Examples - Just Type Naturally:**
|
| 874 |
|
| 875 |
+
📦 **Create Orders:**
|
| 876 |
💬 "Create order for John Doe, 123 Main St San Francisco CA, phone 555-1234, deliver by 5 PM"
|
| 877 |
💬 "New urgent delivery to Sarah at 456 Oak Ave NYC, email [email protected]"
|
| 878 |
|
| 879 |
+
🔍 **Query Orders:**
|
| 880 |
+
💬 "Fetch the orders" or "Show me orders"
|
| 881 |
+
💬 "Which orders are incomplete?"
|
| 882 |
+
💬 "Tell me about order ORD-20251114120000"
|
| 883 |
+
💬 "Show me 10 urgent orders"
|
| 884 |
+
💬 "Search for orders from John"
|
| 885 |
+
|
| 886 |
+
🚚 **Create Drivers:**
|
| 887 |
💬 "Add new driver Tom Wilson, phone 555-0101, drives a van, plate ABC-123"
|
| 888 |
💬 "Create driver Sarah Martinez with refrigerated truck, phone 555-0202"
|
| 889 |
💬 "New driver: Mike Chen, email [email protected], motorcycle delivery"
|
| 890 |
|
| 891 |
+
👥 **Query Drivers:**
|
| 892 |
+
💬 "Show me drivers" or "Fetch the drivers"
|
| 893 |
+
💬 "Which drivers are available?"
|
| 894 |
+
💬 "Tell me about driver DRV-20251114163800"
|
| 895 |
+
💬 "Show 5 active drivers with vans"
|
| 896 |
+
💬 "Search for Tom"
|
| 897 |
+
|
| 898 |
---
|
| 899 |
|
| 900 |
🚀 **I'll automatically:**
|
| 901 |
• Geocode addresses for orders
|
| 902 |
• Generate unique IDs
|
| 903 |
• Save everything to the database
|
| 904 |
+
• Filter and search across all order fields
|
| 905 |
|
| 906 |
What would you like to do?"""
|
chat/tools.py
CHANGED
|
@@ -11,7 +11,7 @@ import logging
|
|
| 11 |
# Add parent directory to path
|
| 12 |
sys.path.insert(0, str(Path(__file__).parent.parent))
|
| 13 |
|
| 14 |
-
from database.connection import execute_write
|
| 15 |
from chat.geocoding import GeocodingService
|
| 16 |
|
| 17 |
logger = logging.getLogger(__name__)
|
|
@@ -85,6 +85,296 @@ TOOLS_SCHEMA = [
|
|
| 85 |
},
|
| 86 |
"required": ["customer_name", "delivery_address", "delivery_lat", "delivery_lng"]
|
| 87 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
}
|
| 89 |
]
|
| 90 |
|
|
@@ -107,6 +397,26 @@ def execute_tool(tool_name: str, tool_input: dict) -> dict:
|
|
| 107 |
return handle_create_order(tool_input)
|
| 108 |
elif tool_name == "create_driver":
|
| 109 |
return handle_create_driver(tool_input)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
else:
|
| 111 |
return {
|
| 112 |
"success": False,
|
|
@@ -337,6 +647,925 @@ def handle_create_driver(tool_input: dict) -> dict:
|
|
| 337 |
}
|
| 338 |
|
| 339 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
def get_tools_list() -> list:
|
| 341 |
"""Get list of available tools"""
|
| 342 |
return [tool["name"] for tool in TOOLS_SCHEMA]
|
|
|
|
| 11 |
# Add parent directory to path
|
| 12 |
sys.path.insert(0, str(Path(__file__).parent.parent))
|
| 13 |
|
| 14 |
+
from database.connection import execute_write, execute_query
|
| 15 |
from chat.geocoding import GeocodingService
|
| 16 |
|
| 17 |
logger = logging.getLogger(__name__)
|
|
|
|
| 85 |
},
|
| 86 |
"required": ["customer_name", "delivery_address", "delivery_lat", "delivery_lng"]
|
| 87 |
}
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"name": "create_driver",
|
| 91 |
+
"description": "Create a new delivery driver in the database. Use this to onboard new drivers to the fleet.",
|
| 92 |
+
"input_schema": {
|
| 93 |
+
"type": "object",
|
| 94 |
+
"properties": {
|
| 95 |
+
"name": {
|
| 96 |
+
"type": "string",
|
| 97 |
+
"description": "Full name of the driver"
|
| 98 |
+
},
|
| 99 |
+
"phone": {
|
| 100 |
+
"type": "string",
|
| 101 |
+
"description": "Driver phone number (optional)"
|
| 102 |
+
},
|
| 103 |
+
"email": {
|
| 104 |
+
"type": "string",
|
| 105 |
+
"description": "Driver email address (optional)"
|
| 106 |
+
},
|
| 107 |
+
"vehicle_type": {
|
| 108 |
+
"type": "string",
|
| 109 |
+
"description": "Type of vehicle: van, truck, car, motorcycle (default: van)"
|
| 110 |
+
},
|
| 111 |
+
"vehicle_plate": {
|
| 112 |
+
"type": "string",
|
| 113 |
+
"description": "Vehicle license plate number (optional)"
|
| 114 |
+
},
|
| 115 |
+
"capacity_kg": {
|
| 116 |
+
"type": "number",
|
| 117 |
+
"description": "Vehicle cargo capacity in kilograms (default: 1000.0)"
|
| 118 |
+
},
|
| 119 |
+
"capacity_m3": {
|
| 120 |
+
"type": "number",
|
| 121 |
+
"description": "Vehicle cargo volume in cubic meters (default: 12.0)"
|
| 122 |
+
},
|
| 123 |
+
"skills": {
|
| 124 |
+
"type": "array",
|
| 125 |
+
"description": "List of driver skills/certifications: refrigerated, medical_certified, fragile_handler, overnight, express_delivery",
|
| 126 |
+
"items": {
|
| 127 |
+
"type": "string"
|
| 128 |
+
}
|
| 129 |
+
},
|
| 130 |
+
"status": {
|
| 131 |
+
"type": "string",
|
| 132 |
+
"enum": ["active", "busy", "offline", "unavailable"],
|
| 133 |
+
"description": "Driver status (default: active)"
|
| 134 |
+
}
|
| 135 |
+
},
|
| 136 |
+
"required": ["name"]
|
| 137 |
+
}
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"name": "count_orders",
|
| 141 |
+
"description": "Count total orders in the database with optional filters. Use this when user asks 'how many orders', 'fetch orders', or wants to know order statistics.",
|
| 142 |
+
"input_schema": {
|
| 143 |
+
"type": "object",
|
| 144 |
+
"properties": {
|
| 145 |
+
"status": {
|
| 146 |
+
"type": "string",
|
| 147 |
+
"enum": ["pending", "assigned", "in_transit", "delivered", "failed", "cancelled"],
|
| 148 |
+
"description": "Filter by order status (optional)"
|
| 149 |
+
},
|
| 150 |
+
"priority": {
|
| 151 |
+
"type": "string",
|
| 152 |
+
"enum": ["standard", "express", "urgent"],
|
| 153 |
+
"description": "Filter by priority level (optional)"
|
| 154 |
+
},
|
| 155 |
+
"payment_status": {
|
| 156 |
+
"type": "string",
|
| 157 |
+
"enum": ["pending", "paid", "cod"],
|
| 158 |
+
"description": "Filter by payment status (optional)"
|
| 159 |
+
},
|
| 160 |
+
"assigned_driver_id": {
|
| 161 |
+
"type": "string",
|
| 162 |
+
"description": "Filter by assigned driver ID (optional)"
|
| 163 |
+
},
|
| 164 |
+
"is_fragile": {
|
| 165 |
+
"type": "boolean",
|
| 166 |
+
"description": "Filter fragile packages only (optional)"
|
| 167 |
+
},
|
| 168 |
+
"requires_signature": {
|
| 169 |
+
"type": "boolean",
|
| 170 |
+
"description": "Filter orders requiring signature (optional)"
|
| 171 |
+
},
|
| 172 |
+
"requires_cold_storage": {
|
| 173 |
+
"type": "boolean",
|
| 174 |
+
"description": "Filter orders requiring cold storage (optional)"
|
| 175 |
+
}
|
| 176 |
+
},
|
| 177 |
+
"required": []
|
| 178 |
+
}
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"name": "fetch_orders",
|
| 182 |
+
"description": "Fetch orders from the database with optional filters, pagination, and sorting. Use after counting to show specific number of orders.",
|
| 183 |
+
"input_schema": {
|
| 184 |
+
"type": "object",
|
| 185 |
+
"properties": {
|
| 186 |
+
"limit": {
|
| 187 |
+
"type": "integer",
|
| 188 |
+
"description": "Number of orders to fetch (default: 10, max: 100)"
|
| 189 |
+
},
|
| 190 |
+
"offset": {
|
| 191 |
+
"type": "integer",
|
| 192 |
+
"description": "Number of orders to skip for pagination (default: 0)"
|
| 193 |
+
},
|
| 194 |
+
"status": {
|
| 195 |
+
"type": "string",
|
| 196 |
+
"enum": ["pending", "assigned", "in_transit", "delivered", "failed", "cancelled"],
|
| 197 |
+
"description": "Filter by order status (optional)"
|
| 198 |
+
},
|
| 199 |
+
"priority": {
|
| 200 |
+
"type": "string",
|
| 201 |
+
"enum": ["standard", "express", "urgent"],
|
| 202 |
+
"description": "Filter by priority level (optional)"
|
| 203 |
+
},
|
| 204 |
+
"payment_status": {
|
| 205 |
+
"type": "string",
|
| 206 |
+
"enum": ["pending", "paid", "cod"],
|
| 207 |
+
"description": "Filter by payment status (optional)"
|
| 208 |
+
},
|
| 209 |
+
"assigned_driver_id": {
|
| 210 |
+
"type": "string",
|
| 211 |
+
"description": "Filter by assigned driver ID (optional)"
|
| 212 |
+
},
|
| 213 |
+
"is_fragile": {
|
| 214 |
+
"type": "boolean",
|
| 215 |
+
"description": "Filter fragile packages only (optional)"
|
| 216 |
+
},
|
| 217 |
+
"requires_signature": {
|
| 218 |
+
"type": "boolean",
|
| 219 |
+
"description": "Filter orders requiring signature (optional)"
|
| 220 |
+
},
|
| 221 |
+
"requires_cold_storage": {
|
| 222 |
+
"type": "boolean",
|
| 223 |
+
"description": "Filter orders requiring cold storage (optional)"
|
| 224 |
+
},
|
| 225 |
+
"sort_by": {
|
| 226 |
+
"type": "string",
|
| 227 |
+
"enum": ["created_at", "priority", "time_window_start"],
|
| 228 |
+
"description": "Field to sort by (default: created_at)"
|
| 229 |
+
},
|
| 230 |
+
"sort_order": {
|
| 231 |
+
"type": "string",
|
| 232 |
+
"enum": ["ASC", "DESC"],
|
| 233 |
+
"description": "Sort order (default: DESC for newest first)"
|
| 234 |
+
}
|
| 235 |
+
},
|
| 236 |
+
"required": []
|
| 237 |
+
}
|
| 238 |
+
},
|
| 239 |
+
{
|
| 240 |
+
"name": "get_order_details",
|
| 241 |
+
"description": "Get complete details of a specific order by order ID. Use when user asks 'tell me about order X' or wants detailed information about a specific order.",
|
| 242 |
+
"input_schema": {
|
| 243 |
+
"type": "object",
|
| 244 |
+
"properties": {
|
| 245 |
+
"order_id": {
|
| 246 |
+
"type": "string",
|
| 247 |
+
"description": "The order ID to fetch details for (e.g., 'ORD-20251114163800')"
|
| 248 |
+
}
|
| 249 |
+
},
|
| 250 |
+
"required": ["order_id"]
|
| 251 |
+
}
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"name": "search_orders",
|
| 255 |
+
"description": "Search for orders by customer name, email, phone, or order ID pattern. Use when user provides partial information to find orders.",
|
| 256 |
+
"input_schema": {
|
| 257 |
+
"type": "object",
|
| 258 |
+
"properties": {
|
| 259 |
+
"search_term": {
|
| 260 |
+
"type": "string",
|
| 261 |
+
"description": "Search term to match against customer_name, customer_email, customer_phone, or order_id"
|
| 262 |
+
}
|
| 263 |
+
},
|
| 264 |
+
"required": ["search_term"]
|
| 265 |
+
}
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"name": "get_incomplete_orders",
|
| 269 |
+
"description": "Get all orders that are not yet completed (excludes delivered and cancelled orders). Shortcut for finding orders in progress (pending, assigned, in_transit).",
|
| 270 |
+
"input_schema": {
|
| 271 |
+
"type": "object",
|
| 272 |
+
"properties": {
|
| 273 |
+
"limit": {
|
| 274 |
+
"type": "integer",
|
| 275 |
+
"description": "Number of orders to fetch (default: 20)"
|
| 276 |
+
}
|
| 277 |
+
},
|
| 278 |
+
"required": []
|
| 279 |
+
}
|
| 280 |
+
},
|
| 281 |
+
{
|
| 282 |
+
"name": "count_drivers",
|
| 283 |
+
"description": "Count total drivers in the database with optional filters. Use this when user asks 'how many drivers', 'show drivers', or wants driver statistics.",
|
| 284 |
+
"input_schema": {
|
| 285 |
+
"type": "object",
|
| 286 |
+
"properties": {
|
| 287 |
+
"status": {
|
| 288 |
+
"type": "string",
|
| 289 |
+
"enum": ["active", "busy", "offline", "unavailable"],
|
| 290 |
+
"description": "Filter by driver status (optional)"
|
| 291 |
+
},
|
| 292 |
+
"vehicle_type": {
|
| 293 |
+
"type": "string",
|
| 294 |
+
"description": "Filter by vehicle type: van, truck, car, motorcycle, etc. (optional)"
|
| 295 |
+
}
|
| 296 |
+
},
|
| 297 |
+
"required": []
|
| 298 |
+
}
|
| 299 |
+
},
|
| 300 |
+
{
|
| 301 |
+
"name": "fetch_drivers",
|
| 302 |
+
"description": "Fetch drivers from the database with optional filters, pagination, and sorting. Use after counting to show specific number of drivers.",
|
| 303 |
+
"input_schema": {
|
| 304 |
+
"type": "object",
|
| 305 |
+
"properties": {
|
| 306 |
+
"limit": {
|
| 307 |
+
"type": "integer",
|
| 308 |
+
"description": "Number of drivers to fetch (default: 10, max: 100)"
|
| 309 |
+
},
|
| 310 |
+
"offset": {
|
| 311 |
+
"type": "integer",
|
| 312 |
+
"description": "Number of drivers to skip for pagination (default: 0)"
|
| 313 |
+
},
|
| 314 |
+
"status": {
|
| 315 |
+
"type": "string",
|
| 316 |
+
"enum": ["active", "busy", "offline", "unavailable"],
|
| 317 |
+
"description": "Filter by driver status (optional)"
|
| 318 |
+
},
|
| 319 |
+
"vehicle_type": {
|
| 320 |
+
"type": "string",
|
| 321 |
+
"description": "Filter by vehicle type: van, truck, car, motorcycle, etc. (optional)"
|
| 322 |
+
},
|
| 323 |
+
"sort_by": {
|
| 324 |
+
"type": "string",
|
| 325 |
+
"enum": ["name", "status", "created_at", "last_location_update"],
|
| 326 |
+
"description": "Field to sort by (default: name)"
|
| 327 |
+
},
|
| 328 |
+
"sort_order": {
|
| 329 |
+
"type": "string",
|
| 330 |
+
"enum": ["ASC", "DESC"],
|
| 331 |
+
"description": "Sort order (default: ASC for alphabetical)"
|
| 332 |
+
}
|
| 333 |
+
},
|
| 334 |
+
"required": []
|
| 335 |
+
}
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"name": "get_driver_details",
|
| 339 |
+
"description": "Get complete details of a specific driver by driver ID. Use when user asks 'tell me about driver X' or wants detailed information about a specific driver.",
|
| 340 |
+
"input_schema": {
|
| 341 |
+
"type": "object",
|
| 342 |
+
"properties": {
|
| 343 |
+
"driver_id": {
|
| 344 |
+
"type": "string",
|
| 345 |
+
"description": "The driver ID to fetch details for (e.g., 'DRV-20251114163800')"
|
| 346 |
+
}
|
| 347 |
+
},
|
| 348 |
+
"required": ["driver_id"]
|
| 349 |
+
}
|
| 350 |
+
},
|
| 351 |
+
{
|
| 352 |
+
"name": "search_drivers",
|
| 353 |
+
"description": "Search for drivers by name, email, phone, vehicle plate, or driver ID pattern. Use when user provides partial information to find drivers.",
|
| 354 |
+
"input_schema": {
|
| 355 |
+
"type": "object",
|
| 356 |
+
"properties": {
|
| 357 |
+
"search_term": {
|
| 358 |
+
"type": "string",
|
| 359 |
+
"description": "Search term to match against name, email, phone, vehicle_plate, or driver_id"
|
| 360 |
+
}
|
| 361 |
+
},
|
| 362 |
+
"required": ["search_term"]
|
| 363 |
+
}
|
| 364 |
+
},
|
| 365 |
+
{
|
| 366 |
+
"name": "get_available_drivers",
|
| 367 |
+
"description": "Get all drivers that are available for assignment (active or offline status, excludes busy and unavailable). Shortcut for finding drivers ready for dispatch.",
|
| 368 |
+
"input_schema": {
|
| 369 |
+
"type": "object",
|
| 370 |
+
"properties": {
|
| 371 |
+
"limit": {
|
| 372 |
+
"type": "integer",
|
| 373 |
+
"description": "Number of drivers to fetch (default: 20)"
|
| 374 |
+
}
|
| 375 |
+
},
|
| 376 |
+
"required": []
|
| 377 |
+
}
|
| 378 |
}
|
| 379 |
]
|
| 380 |
|
|
|
|
| 397 |
return handle_create_order(tool_input)
|
| 398 |
elif tool_name == "create_driver":
|
| 399 |
return handle_create_driver(tool_input)
|
| 400 |
+
elif tool_name == "count_orders":
|
| 401 |
+
return handle_count_orders(tool_input)
|
| 402 |
+
elif tool_name == "fetch_orders":
|
| 403 |
+
return handle_fetch_orders(tool_input)
|
| 404 |
+
elif tool_name == "get_order_details":
|
| 405 |
+
return handle_get_order_details(tool_input)
|
| 406 |
+
elif tool_name == "search_orders":
|
| 407 |
+
return handle_search_orders(tool_input)
|
| 408 |
+
elif tool_name == "get_incomplete_orders":
|
| 409 |
+
return handle_get_incomplete_orders(tool_input)
|
| 410 |
+
elif tool_name == "count_drivers":
|
| 411 |
+
return handle_count_drivers(tool_input)
|
| 412 |
+
elif tool_name == "fetch_drivers":
|
| 413 |
+
return handle_fetch_drivers(tool_input)
|
| 414 |
+
elif tool_name == "get_driver_details":
|
| 415 |
+
return handle_get_driver_details(tool_input)
|
| 416 |
+
elif tool_name == "search_drivers":
|
| 417 |
+
return handle_search_drivers(tool_input)
|
| 418 |
+
elif tool_name == "get_available_drivers":
|
| 419 |
+
return handle_get_available_drivers(tool_input)
|
| 420 |
else:
|
| 421 |
return {
|
| 422 |
"success": False,
|
|
|
|
| 647 |
}
|
| 648 |
|
| 649 |
|
| 650 |
+
def handle_count_orders(tool_input: dict) -> dict:
|
| 651 |
+
"""
|
| 652 |
+
Execute count orders tool
|
| 653 |
+
|
| 654 |
+
Args:
|
| 655 |
+
tool_input: Dict with optional filter fields
|
| 656 |
+
|
| 657 |
+
Returns:
|
| 658 |
+
Order count result with breakdown
|
| 659 |
+
"""
|
| 660 |
+
# Build WHERE clause based on filters
|
| 661 |
+
where_clauses = []
|
| 662 |
+
params = []
|
| 663 |
+
|
| 664 |
+
if "status" in tool_input:
|
| 665 |
+
where_clauses.append("status = %s")
|
| 666 |
+
params.append(tool_input["status"])
|
| 667 |
+
|
| 668 |
+
if "priority" in tool_input:
|
| 669 |
+
where_clauses.append("priority = %s")
|
| 670 |
+
params.append(tool_input["priority"])
|
| 671 |
+
|
| 672 |
+
if "payment_status" in tool_input:
|
| 673 |
+
where_clauses.append("payment_status = %s")
|
| 674 |
+
params.append(tool_input["payment_status"])
|
| 675 |
+
|
| 676 |
+
if "assigned_driver_id" in tool_input:
|
| 677 |
+
where_clauses.append("assigned_driver_id = %s")
|
| 678 |
+
params.append(tool_input["assigned_driver_id"])
|
| 679 |
+
|
| 680 |
+
if "is_fragile" in tool_input:
|
| 681 |
+
where_clauses.append("is_fragile = %s")
|
| 682 |
+
params.append(tool_input["is_fragile"])
|
| 683 |
+
|
| 684 |
+
if "requires_signature" in tool_input:
|
| 685 |
+
where_clauses.append("requires_signature = %s")
|
| 686 |
+
params.append(tool_input["requires_signature"])
|
| 687 |
+
|
| 688 |
+
if "requires_cold_storage" in tool_input:
|
| 689 |
+
where_clauses.append("requires_cold_storage = %s")
|
| 690 |
+
params.append(tool_input["requires_cold_storage"])
|
| 691 |
+
|
| 692 |
+
where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else ""
|
| 693 |
+
|
| 694 |
+
# Total count query
|
| 695 |
+
count_query = f"SELECT COUNT(*) as total FROM orders{where_sql}"
|
| 696 |
+
|
| 697 |
+
# Breakdown by status query
|
| 698 |
+
breakdown_query = f"""
|
| 699 |
+
SELECT status, COUNT(*) as count
|
| 700 |
+
FROM orders{where_sql}
|
| 701 |
+
GROUP BY status
|
| 702 |
+
ORDER BY count DESC
|
| 703 |
+
"""
|
| 704 |
+
|
| 705 |
+
# Breakdown by priority query
|
| 706 |
+
priority_query = f"""
|
| 707 |
+
SELECT priority, COUNT(*) as count
|
| 708 |
+
FROM orders{where_sql}
|
| 709 |
+
GROUP BY priority
|
| 710 |
+
ORDER BY CASE priority
|
| 711 |
+
WHEN 'urgent' THEN 1
|
| 712 |
+
WHEN 'express' THEN 2
|
| 713 |
+
WHEN 'standard' THEN 3
|
| 714 |
+
END
|
| 715 |
+
"""
|
| 716 |
+
|
| 717 |
+
try:
|
| 718 |
+
# Execute queries
|
| 719 |
+
total_result = execute_query(count_query, tuple(params) if params else None)
|
| 720 |
+
total = total_result[0]['total'] if total_result else 0
|
| 721 |
+
|
| 722 |
+
status_result = execute_query(breakdown_query, tuple(params) if params else None)
|
| 723 |
+
priority_result = execute_query(priority_query, tuple(params) if params else None)
|
| 724 |
+
|
| 725 |
+
# Format breakdown
|
| 726 |
+
status_breakdown = {row['status']: row['count'] for row in status_result} if status_result else {}
|
| 727 |
+
priority_breakdown = {row['priority']: row['count'] for row in priority_result} if priority_result else {}
|
| 728 |
+
|
| 729 |
+
logger.info(f"Counted orders: {total} total")
|
| 730 |
+
|
| 731 |
+
return {
|
| 732 |
+
"success": True,
|
| 733 |
+
"total": total,
|
| 734 |
+
"status_breakdown": status_breakdown,
|
| 735 |
+
"priority_breakdown": priority_breakdown,
|
| 736 |
+
"message": f"Found {total} order(s)"
|
| 737 |
+
}
|
| 738 |
+
except Exception as e:
|
| 739 |
+
logger.error(f"Database error counting orders: {e}")
|
| 740 |
+
return {
|
| 741 |
+
"success": False,
|
| 742 |
+
"error": f"Failed to count orders: {str(e)}"
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
|
| 746 |
+
def handle_fetch_orders(tool_input: dict) -> dict:
|
| 747 |
+
"""
|
| 748 |
+
Execute fetch orders tool
|
| 749 |
+
|
| 750 |
+
Args:
|
| 751 |
+
tool_input: Dict with filter, pagination, and sorting options
|
| 752 |
+
|
| 753 |
+
Returns:
|
| 754 |
+
List of orders matching criteria
|
| 755 |
+
"""
|
| 756 |
+
# Extract pagination and sorting
|
| 757 |
+
limit = min(tool_input.get("limit", 10), 100) # Cap at 100
|
| 758 |
+
offset = tool_input.get("offset", 0)
|
| 759 |
+
sort_by = tool_input.get("sort_by", "created_at")
|
| 760 |
+
sort_order = tool_input.get("sort_order", "DESC")
|
| 761 |
+
|
| 762 |
+
# Build WHERE clause based on filters
|
| 763 |
+
where_clauses = []
|
| 764 |
+
params = []
|
| 765 |
+
|
| 766 |
+
if "status" in tool_input:
|
| 767 |
+
where_clauses.append("status = %s")
|
| 768 |
+
params.append(tool_input["status"])
|
| 769 |
+
|
| 770 |
+
if "priority" in tool_input:
|
| 771 |
+
where_clauses.append("priority = %s")
|
| 772 |
+
params.append(tool_input["priority"])
|
| 773 |
+
|
| 774 |
+
if "payment_status" in tool_input:
|
| 775 |
+
where_clauses.append("payment_status = %s")
|
| 776 |
+
params.append(tool_input["payment_status"])
|
| 777 |
+
|
| 778 |
+
if "assigned_driver_id" in tool_input:
|
| 779 |
+
where_clauses.append("assigned_driver_id = %s")
|
| 780 |
+
params.append(tool_input["assigned_driver_id"])
|
| 781 |
+
|
| 782 |
+
if "is_fragile" in tool_input:
|
| 783 |
+
where_clauses.append("is_fragile = %s")
|
| 784 |
+
params.append(tool_input["is_fragile"])
|
| 785 |
+
|
| 786 |
+
if "requires_signature" in tool_input:
|
| 787 |
+
where_clauses.append("requires_signature = %s")
|
| 788 |
+
params.append(tool_input["requires_signature"])
|
| 789 |
+
|
| 790 |
+
if "requires_cold_storage" in tool_input:
|
| 791 |
+
where_clauses.append("requires_cold_storage = %s")
|
| 792 |
+
params.append(tool_input["requires_cold_storage"])
|
| 793 |
+
|
| 794 |
+
where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else ""
|
| 795 |
+
|
| 796 |
+
# Build query
|
| 797 |
+
query = f"""
|
| 798 |
+
SELECT
|
| 799 |
+
order_id, customer_name, customer_phone, customer_email,
|
| 800 |
+
delivery_address, delivery_lat, delivery_lng,
|
| 801 |
+
time_window_start, time_window_end,
|
| 802 |
+
priority, weight_kg, volume_m3, special_instructions,
|
| 803 |
+
status, assigned_driver_id,
|
| 804 |
+
created_at, updated_at, delivered_at,
|
| 805 |
+
order_value, payment_status,
|
| 806 |
+
requires_signature, is_fragile, requires_cold_storage
|
| 807 |
+
FROM orders
|
| 808 |
+
{where_sql}
|
| 809 |
+
ORDER BY {sort_by} {sort_order}
|
| 810 |
+
LIMIT %s OFFSET %s
|
| 811 |
+
"""
|
| 812 |
+
|
| 813 |
+
params.extend([limit, offset])
|
| 814 |
+
|
| 815 |
+
try:
|
| 816 |
+
results = execute_query(query, tuple(params))
|
| 817 |
+
|
| 818 |
+
if not results:
|
| 819 |
+
return {
|
| 820 |
+
"success": True,
|
| 821 |
+
"orders": [],
|
| 822 |
+
"count": 0,
|
| 823 |
+
"message": "No orders found matching criteria"
|
| 824 |
+
}
|
| 825 |
+
|
| 826 |
+
# Format orders for readability
|
| 827 |
+
orders = []
|
| 828 |
+
for row in results:
|
| 829 |
+
order = {
|
| 830 |
+
"order_id": row['order_id'],
|
| 831 |
+
"customer": {
|
| 832 |
+
"name": row['customer_name'],
|
| 833 |
+
"phone": row['customer_phone'],
|
| 834 |
+
"email": row['customer_email']
|
| 835 |
+
},
|
| 836 |
+
"delivery": {
|
| 837 |
+
"address": row['delivery_address'],
|
| 838 |
+
"latitude": float(row['delivery_lat']) if row['delivery_lat'] else None,
|
| 839 |
+
"longitude": float(row['delivery_lng']) if row['delivery_lng'] else None
|
| 840 |
+
},
|
| 841 |
+
"time_window": {
|
| 842 |
+
"start": str(row['time_window_start']) if row['time_window_start'] else None,
|
| 843 |
+
"end": str(row['time_window_end']) if row['time_window_end'] else None
|
| 844 |
+
},
|
| 845 |
+
"details": {
|
| 846 |
+
"priority": row['priority'],
|
| 847 |
+
"status": row['status'],
|
| 848 |
+
"weight_kg": float(row['weight_kg']) if row['weight_kg'] else None,
|
| 849 |
+
"volume_m3": float(row['volume_m3']) if row['volume_m3'] else None,
|
| 850 |
+
"special_instructions": row['special_instructions']
|
| 851 |
+
},
|
| 852 |
+
"flags": {
|
| 853 |
+
"requires_signature": row['requires_signature'],
|
| 854 |
+
"is_fragile": row['is_fragile'],
|
| 855 |
+
"requires_cold_storage": row['requires_cold_storage']
|
| 856 |
+
},
|
| 857 |
+
"payment": {
|
| 858 |
+
"order_value": float(row['order_value']) if row['order_value'] else None,
|
| 859 |
+
"payment_status": row['payment_status']
|
| 860 |
+
},
|
| 861 |
+
"assigned_driver_id": row['assigned_driver_id'],
|
| 862 |
+
"timestamps": {
|
| 863 |
+
"created_at": str(row['created_at']),
|
| 864 |
+
"updated_at": str(row['updated_at']) if row['updated_at'] else None,
|
| 865 |
+
"delivered_at": str(row['delivered_at']) if row['delivered_at'] else None
|
| 866 |
+
}
|
| 867 |
+
}
|
| 868 |
+
orders.append(order)
|
| 869 |
+
|
| 870 |
+
logger.info(f"Fetched {len(orders)} orders")
|
| 871 |
+
|
| 872 |
+
return {
|
| 873 |
+
"success": True,
|
| 874 |
+
"orders": orders,
|
| 875 |
+
"count": len(orders),
|
| 876 |
+
"message": f"Retrieved {len(orders)} order(s)"
|
| 877 |
+
}
|
| 878 |
+
except Exception as e:
|
| 879 |
+
logger.error(f"Database error fetching orders: {e}")
|
| 880 |
+
return {
|
| 881 |
+
"success": False,
|
| 882 |
+
"error": f"Failed to fetch orders: {str(e)}"
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
|
| 886 |
+
def handle_get_order_details(tool_input: dict) -> dict:
|
| 887 |
+
"""
|
| 888 |
+
Execute get order details tool
|
| 889 |
+
|
| 890 |
+
Args:
|
| 891 |
+
tool_input: Dict with order_id
|
| 892 |
+
|
| 893 |
+
Returns:
|
| 894 |
+
Complete order details
|
| 895 |
+
"""
|
| 896 |
+
order_id = tool_input.get("order_id")
|
| 897 |
+
|
| 898 |
+
if not order_id:
|
| 899 |
+
return {
|
| 900 |
+
"success": False,
|
| 901 |
+
"error": "order_id is required"
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
query = """
|
| 905 |
+
SELECT
|
| 906 |
+
order_id, customer_name, customer_phone, customer_email,
|
| 907 |
+
pickup_address, pickup_lat, pickup_lng,
|
| 908 |
+
delivery_address, delivery_lat, delivery_lng,
|
| 909 |
+
time_window_start, time_window_end,
|
| 910 |
+
priority, weight_kg, volume_m3, special_instructions,
|
| 911 |
+
status, assigned_driver_id,
|
| 912 |
+
created_at, updated_at, delivered_at,
|
| 913 |
+
order_value, payment_status,
|
| 914 |
+
requires_signature, is_fragile, requires_cold_storage
|
| 915 |
+
FROM orders
|
| 916 |
+
WHERE order_id = %s
|
| 917 |
+
"""
|
| 918 |
+
|
| 919 |
+
try:
|
| 920 |
+
results = execute_query(query, (order_id,))
|
| 921 |
+
|
| 922 |
+
if not results:
|
| 923 |
+
return {
|
| 924 |
+
"success": False,
|
| 925 |
+
"error": f"Order {order_id} not found"
|
| 926 |
+
}
|
| 927 |
+
|
| 928 |
+
row = results[0]
|
| 929 |
+
|
| 930 |
+
order = {
|
| 931 |
+
"order_id": row['order_id'],
|
| 932 |
+
"customer": {
|
| 933 |
+
"name": row['customer_name'],
|
| 934 |
+
"phone": row['customer_phone'],
|
| 935 |
+
"email": row['customer_email']
|
| 936 |
+
},
|
| 937 |
+
"pickup": {
|
| 938 |
+
"address": row['pickup_address'],
|
| 939 |
+
"latitude": float(row['pickup_lat']) if row['pickup_lat'] else None,
|
| 940 |
+
"longitude": float(row['pickup_lng']) if row['pickup_lng'] else None
|
| 941 |
+
} if row['pickup_address'] else None,
|
| 942 |
+
"delivery": {
|
| 943 |
+
"address": row['delivery_address'],
|
| 944 |
+
"latitude": float(row['delivery_lat']) if row['delivery_lat'] else None,
|
| 945 |
+
"longitude": float(row['delivery_lng']) if row['delivery_lng'] else None
|
| 946 |
+
},
|
| 947 |
+
"time_window": {
|
| 948 |
+
"start": str(row['time_window_start']) if row['time_window_start'] else None,
|
| 949 |
+
"end": str(row['time_window_end']) if row['time_window_end'] else None
|
| 950 |
+
},
|
| 951 |
+
"details": {
|
| 952 |
+
"priority": row['priority'],
|
| 953 |
+
"status": row['status'],
|
| 954 |
+
"weight_kg": float(row['weight_kg']) if row['weight_kg'] else None,
|
| 955 |
+
"volume_m3": float(row['volume_m3']) if row['volume_m3'] else None,
|
| 956 |
+
"special_instructions": row['special_instructions']
|
| 957 |
+
},
|
| 958 |
+
"flags": {
|
| 959 |
+
"requires_signature": row['requires_signature'],
|
| 960 |
+
"is_fragile": row['is_fragile'],
|
| 961 |
+
"requires_cold_storage": row['requires_cold_storage']
|
| 962 |
+
},
|
| 963 |
+
"payment": {
|
| 964 |
+
"order_value": float(row['order_value']) if row['order_value'] else None,
|
| 965 |
+
"payment_status": row['payment_status']
|
| 966 |
+
},
|
| 967 |
+
"assigned_driver_id": row['assigned_driver_id'],
|
| 968 |
+
"timestamps": {
|
| 969 |
+
"created_at": str(row['created_at']),
|
| 970 |
+
"updated_at": str(row['updated_at']) if row['updated_at'] else None,
|
| 971 |
+
"delivered_at": str(row['delivered_at']) if row['delivered_at'] else None
|
| 972 |
+
}
|
| 973 |
+
}
|
| 974 |
+
|
| 975 |
+
logger.info(f"Retrieved details for order: {order_id}")
|
| 976 |
+
|
| 977 |
+
return {
|
| 978 |
+
"success": True,
|
| 979 |
+
"order": order,
|
| 980 |
+
"message": f"Order {order_id} details retrieved"
|
| 981 |
+
}
|
| 982 |
+
except Exception as e:
|
| 983 |
+
logger.error(f"Database error getting order details: {e}")
|
| 984 |
+
return {
|
| 985 |
+
"success": False,
|
| 986 |
+
"error": f"Failed to get order details: {str(e)}"
|
| 987 |
+
}
|
| 988 |
+
|
| 989 |
+
|
| 990 |
+
def handle_search_orders(tool_input: dict) -> dict:
|
| 991 |
+
"""
|
| 992 |
+
Execute search orders tool
|
| 993 |
+
|
| 994 |
+
Args:
|
| 995 |
+
tool_input: Dict with search_term
|
| 996 |
+
|
| 997 |
+
Returns:
|
| 998 |
+
List of matching orders
|
| 999 |
+
"""
|
| 1000 |
+
search_term = tool_input.get("search_term", "").strip()
|
| 1001 |
+
|
| 1002 |
+
if not search_term:
|
| 1003 |
+
return {
|
| 1004 |
+
"success": False,
|
| 1005 |
+
"error": "search_term is required"
|
| 1006 |
+
}
|
| 1007 |
+
|
| 1008 |
+
query = """
|
| 1009 |
+
SELECT
|
| 1010 |
+
order_id, customer_name, customer_phone, customer_email,
|
| 1011 |
+
delivery_address, priority, status, created_at
|
| 1012 |
+
FROM orders
|
| 1013 |
+
WHERE
|
| 1014 |
+
order_id ILIKE %s OR
|
| 1015 |
+
customer_name ILIKE %s OR
|
| 1016 |
+
customer_email ILIKE %s OR
|
| 1017 |
+
customer_phone ILIKE %s
|
| 1018 |
+
ORDER BY created_at DESC
|
| 1019 |
+
LIMIT 50
|
| 1020 |
+
"""
|
| 1021 |
+
|
| 1022 |
+
search_pattern = f"%{search_term}%"
|
| 1023 |
+
params = (search_pattern, search_pattern, search_pattern, search_pattern)
|
| 1024 |
+
|
| 1025 |
+
try:
|
| 1026 |
+
results = execute_query(query, params)
|
| 1027 |
+
|
| 1028 |
+
if not results:
|
| 1029 |
+
return {
|
| 1030 |
+
"success": True,
|
| 1031 |
+
"orders": [],
|
| 1032 |
+
"count": 0,
|
| 1033 |
+
"message": f"No orders found matching '{search_term}'"
|
| 1034 |
+
}
|
| 1035 |
+
|
| 1036 |
+
orders = []
|
| 1037 |
+
for row in results:
|
| 1038 |
+
orders.append({
|
| 1039 |
+
"order_id": row['order_id'],
|
| 1040 |
+
"customer_name": row['customer_name'],
|
| 1041 |
+
"customer_phone": row['customer_phone'],
|
| 1042 |
+
"customer_email": row['customer_email'],
|
| 1043 |
+
"delivery_address": row['delivery_address'],
|
| 1044 |
+
"priority": row['priority'],
|
| 1045 |
+
"status": row['status'],
|
| 1046 |
+
"created_at": str(row['created_at'])
|
| 1047 |
+
})
|
| 1048 |
+
|
| 1049 |
+
logger.info(f"Search '{search_term}' found {len(orders)} orders")
|
| 1050 |
+
|
| 1051 |
+
return {
|
| 1052 |
+
"success": True,
|
| 1053 |
+
"orders": orders,
|
| 1054 |
+
"count": len(orders),
|
| 1055 |
+
"message": f"Found {len(orders)} order(s) matching '{search_term}'"
|
| 1056 |
+
}
|
| 1057 |
+
except Exception as e:
|
| 1058 |
+
logger.error(f"Database error searching orders: {e}")
|
| 1059 |
+
return {
|
| 1060 |
+
"success": False,
|
| 1061 |
+
"error": f"Failed to search orders: {str(e)}"
|
| 1062 |
+
}
|
| 1063 |
+
|
| 1064 |
+
|
| 1065 |
+
def handle_get_incomplete_orders(tool_input: dict) -> dict:
|
| 1066 |
+
"""
|
| 1067 |
+
Execute get incomplete orders tool
|
| 1068 |
+
|
| 1069 |
+
Args:
|
| 1070 |
+
tool_input: Dict with optional limit
|
| 1071 |
+
|
| 1072 |
+
Returns:
|
| 1073 |
+
List of incomplete orders (pending, assigned, in_transit)
|
| 1074 |
+
"""
|
| 1075 |
+
limit = min(tool_input.get("limit", 20), 100)
|
| 1076 |
+
|
| 1077 |
+
query = """
|
| 1078 |
+
SELECT
|
| 1079 |
+
order_id, customer_name, delivery_address,
|
| 1080 |
+
priority, status, time_window_end, created_at,
|
| 1081 |
+
assigned_driver_id
|
| 1082 |
+
FROM orders
|
| 1083 |
+
WHERE status IN ('pending', 'assigned', 'in_transit')
|
| 1084 |
+
ORDER BY
|
| 1085 |
+
CASE priority
|
| 1086 |
+
WHEN 'urgent' THEN 1
|
| 1087 |
+
WHEN 'express' THEN 2
|
| 1088 |
+
WHEN 'standard' THEN 3
|
| 1089 |
+
END,
|
| 1090 |
+
time_window_end ASC
|
| 1091 |
+
LIMIT %s
|
| 1092 |
+
"""
|
| 1093 |
+
|
| 1094 |
+
try:
|
| 1095 |
+
results = execute_query(query, (limit,))
|
| 1096 |
+
|
| 1097 |
+
if not results:
|
| 1098 |
+
return {
|
| 1099 |
+
"success": True,
|
| 1100 |
+
"orders": [],
|
| 1101 |
+
"count": 0,
|
| 1102 |
+
"message": "No incomplete orders found"
|
| 1103 |
+
}
|
| 1104 |
+
|
| 1105 |
+
orders = []
|
| 1106 |
+
for row in results:
|
| 1107 |
+
orders.append({
|
| 1108 |
+
"order_id": row['order_id'],
|
| 1109 |
+
"customer_name": row['customer_name'],
|
| 1110 |
+
"delivery_address": row['delivery_address'],
|
| 1111 |
+
"priority": row['priority'],
|
| 1112 |
+
"status": row['status'],
|
| 1113 |
+
"time_window_end": str(row['time_window_end']) if row['time_window_end'] else None,
|
| 1114 |
+
"created_at": str(row['created_at']),
|
| 1115 |
+
"assigned_driver_id": row['assigned_driver_id']
|
| 1116 |
+
})
|
| 1117 |
+
|
| 1118 |
+
logger.info(f"Retrieved {len(orders)} incomplete orders")
|
| 1119 |
+
|
| 1120 |
+
return {
|
| 1121 |
+
"success": True,
|
| 1122 |
+
"orders": orders,
|
| 1123 |
+
"count": len(orders),
|
| 1124 |
+
"message": f"Found {len(orders)} incomplete order(s)"
|
| 1125 |
+
}
|
| 1126 |
+
except Exception as e:
|
| 1127 |
+
logger.error(f"Database error getting incomplete orders: {e}")
|
| 1128 |
+
return {
|
| 1129 |
+
"success": False,
|
| 1130 |
+
"error": f"Failed to get incomplete orders: {str(e)}"
|
| 1131 |
+
}
|
| 1132 |
+
|
| 1133 |
+
|
| 1134 |
+
def handle_count_drivers(tool_input: dict) -> dict:
|
| 1135 |
+
"""
|
| 1136 |
+
Execute count drivers tool
|
| 1137 |
+
|
| 1138 |
+
Args:
|
| 1139 |
+
tool_input: Dict with optional filter fields
|
| 1140 |
+
|
| 1141 |
+
Returns:
|
| 1142 |
+
Driver count result with breakdown
|
| 1143 |
+
"""
|
| 1144 |
+
# Build WHERE clause based on filters
|
| 1145 |
+
where_clauses = []
|
| 1146 |
+
params = []
|
| 1147 |
+
|
| 1148 |
+
if "status" in tool_input:
|
| 1149 |
+
where_clauses.append("status = %s")
|
| 1150 |
+
params.append(tool_input["status"])
|
| 1151 |
+
|
| 1152 |
+
if "vehicle_type" in tool_input:
|
| 1153 |
+
where_clauses.append("vehicle_type = %s")
|
| 1154 |
+
params.append(tool_input["vehicle_type"])
|
| 1155 |
+
|
| 1156 |
+
where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else ""
|
| 1157 |
+
|
| 1158 |
+
# Total count query
|
| 1159 |
+
count_query = f"SELECT COUNT(*) as total FROM drivers{where_sql}"
|
| 1160 |
+
|
| 1161 |
+
# Breakdown by status query
|
| 1162 |
+
status_query = f"""
|
| 1163 |
+
SELECT status, COUNT(*) as count
|
| 1164 |
+
FROM drivers{where_sql}
|
| 1165 |
+
GROUP BY status
|
| 1166 |
+
ORDER BY count DESC
|
| 1167 |
+
"""
|
| 1168 |
+
|
| 1169 |
+
# Breakdown by vehicle type query
|
| 1170 |
+
vehicle_query = f"""
|
| 1171 |
+
SELECT vehicle_type, COUNT(*) as count
|
| 1172 |
+
FROM drivers{where_sql}
|
| 1173 |
+
GROUP BY vehicle_type
|
| 1174 |
+
ORDER BY count DESC
|
| 1175 |
+
"""
|
| 1176 |
+
|
| 1177 |
+
try:
|
| 1178 |
+
# Execute queries
|
| 1179 |
+
total_result = execute_query(count_query, tuple(params) if params else None)
|
| 1180 |
+
total = total_result[0]['total'] if total_result else 0
|
| 1181 |
+
|
| 1182 |
+
status_result = execute_query(status_query, tuple(params) if params else None)
|
| 1183 |
+
vehicle_result = execute_query(vehicle_query, tuple(params) if params else None)
|
| 1184 |
+
|
| 1185 |
+
# Format breakdown
|
| 1186 |
+
status_breakdown = {row['status']: row['count'] for row in status_result} if status_result else {}
|
| 1187 |
+
vehicle_breakdown = {row['vehicle_type']: row['count'] for row in vehicle_result if row['vehicle_type']} if vehicle_result else {}
|
| 1188 |
+
|
| 1189 |
+
logger.info(f"Counted drivers: {total} total")
|
| 1190 |
+
|
| 1191 |
+
return {
|
| 1192 |
+
"success": True,
|
| 1193 |
+
"total": total,
|
| 1194 |
+
"status_breakdown": status_breakdown,
|
| 1195 |
+
"vehicle_breakdown": vehicle_breakdown,
|
| 1196 |
+
"message": f"Found {total} driver(s)"
|
| 1197 |
+
}
|
| 1198 |
+
except Exception as e:
|
| 1199 |
+
logger.error(f"Database error counting drivers: {e}")
|
| 1200 |
+
return {
|
| 1201 |
+
"success": False,
|
| 1202 |
+
"error": f"Failed to count drivers: {str(e)}"
|
| 1203 |
+
}
|
| 1204 |
+
|
| 1205 |
+
|
| 1206 |
+
def handle_fetch_drivers(tool_input: dict) -> dict:
|
| 1207 |
+
"""
|
| 1208 |
+
Execute fetch drivers tool
|
| 1209 |
+
|
| 1210 |
+
Args:
|
| 1211 |
+
tool_input: Dict with filter, pagination, and sorting options
|
| 1212 |
+
|
| 1213 |
+
Returns:
|
| 1214 |
+
List of drivers matching criteria
|
| 1215 |
+
"""
|
| 1216 |
+
# Extract pagination and sorting
|
| 1217 |
+
limit = min(tool_input.get("limit", 10), 100) # Cap at 100
|
| 1218 |
+
offset = tool_input.get("offset", 0)
|
| 1219 |
+
sort_by = tool_input.get("sort_by", "name")
|
| 1220 |
+
sort_order = tool_input.get("sort_order", "ASC")
|
| 1221 |
+
|
| 1222 |
+
# Build WHERE clause based on filters
|
| 1223 |
+
where_clauses = []
|
| 1224 |
+
params = []
|
| 1225 |
+
|
| 1226 |
+
if "status" in tool_input:
|
| 1227 |
+
where_clauses.append("status = %s")
|
| 1228 |
+
params.append(tool_input["status"])
|
| 1229 |
+
|
| 1230 |
+
if "vehicle_type" in tool_input:
|
| 1231 |
+
where_clauses.append("vehicle_type = %s")
|
| 1232 |
+
params.append(tool_input["vehicle_type"])
|
| 1233 |
+
|
| 1234 |
+
where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else ""
|
| 1235 |
+
|
| 1236 |
+
# Build query
|
| 1237 |
+
query = f"""
|
| 1238 |
+
SELECT
|
| 1239 |
+
driver_id, name, phone, email,
|
| 1240 |
+
current_lat, current_lng, last_location_update,
|
| 1241 |
+
status, vehicle_type, vehicle_plate,
|
| 1242 |
+
capacity_kg, capacity_m3, skills,
|
| 1243 |
+
created_at, updated_at
|
| 1244 |
+
FROM drivers
|
| 1245 |
+
{where_sql}
|
| 1246 |
+
ORDER BY {sort_by} {sort_order}
|
| 1247 |
+
LIMIT %s OFFSET %s
|
| 1248 |
+
"""
|
| 1249 |
+
|
| 1250 |
+
params.extend([limit, offset])
|
| 1251 |
+
|
| 1252 |
+
try:
|
| 1253 |
+
results = execute_query(query, tuple(params))
|
| 1254 |
+
|
| 1255 |
+
if not results:
|
| 1256 |
+
return {
|
| 1257 |
+
"success": True,
|
| 1258 |
+
"drivers": [],
|
| 1259 |
+
"count": 0,
|
| 1260 |
+
"message": "No drivers found matching criteria"
|
| 1261 |
+
}
|
| 1262 |
+
|
| 1263 |
+
# Format drivers for readability
|
| 1264 |
+
drivers = []
|
| 1265 |
+
for row in results:
|
| 1266 |
+
# Parse skills JSON if present
|
| 1267 |
+
skills = []
|
| 1268 |
+
if row['skills']:
|
| 1269 |
+
try:
|
| 1270 |
+
import json
|
| 1271 |
+
skills = json.loads(row['skills']) if isinstance(row['skills'], str) else row['skills']
|
| 1272 |
+
except:
|
| 1273 |
+
skills = []
|
| 1274 |
+
|
| 1275 |
+
driver = {
|
| 1276 |
+
"driver_id": row['driver_id'],
|
| 1277 |
+
"name": row['name'],
|
| 1278 |
+
"contact": {
|
| 1279 |
+
"phone": row['phone'],
|
| 1280 |
+
"email": row['email']
|
| 1281 |
+
},
|
| 1282 |
+
"location": {
|
| 1283 |
+
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 1284 |
+
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
| 1285 |
+
"last_update": str(row['last_location_update']) if row['last_location_update'] else None
|
| 1286 |
+
},
|
| 1287 |
+
"status": row['status'],
|
| 1288 |
+
"vehicle": {
|
| 1289 |
+
"type": row['vehicle_type'],
|
| 1290 |
+
"plate": row['vehicle_plate'],
|
| 1291 |
+
"capacity_kg": float(row['capacity_kg']) if row['capacity_kg'] else None,
|
| 1292 |
+
"capacity_m3": float(row['capacity_m3']) if row['capacity_m3'] else None
|
| 1293 |
+
},
|
| 1294 |
+
"skills": skills,
|
| 1295 |
+
"timestamps": {
|
| 1296 |
+
"created_at": str(row['created_at']),
|
| 1297 |
+
"updated_at": str(row['updated_at']) if row['updated_at'] else None
|
| 1298 |
+
}
|
| 1299 |
+
}
|
| 1300 |
+
drivers.append(driver)
|
| 1301 |
+
|
| 1302 |
+
logger.info(f"Fetched {len(drivers)} drivers")
|
| 1303 |
+
|
| 1304 |
+
return {
|
| 1305 |
+
"success": True,
|
| 1306 |
+
"drivers": drivers,
|
| 1307 |
+
"count": len(drivers),
|
| 1308 |
+
"message": f"Retrieved {len(drivers)} driver(s)"
|
| 1309 |
+
}
|
| 1310 |
+
except Exception as e:
|
| 1311 |
+
logger.error(f"Database error fetching drivers: {e}")
|
| 1312 |
+
return {
|
| 1313 |
+
"success": False,
|
| 1314 |
+
"error": f"Failed to fetch drivers: {str(e)}"
|
| 1315 |
+
}
|
| 1316 |
+
|
| 1317 |
+
|
| 1318 |
+
def handle_get_driver_details(tool_input: dict) -> dict:
|
| 1319 |
+
"""
|
| 1320 |
+
Execute get driver details tool
|
| 1321 |
+
|
| 1322 |
+
Args:
|
| 1323 |
+
tool_input: Dict with driver_id
|
| 1324 |
+
|
| 1325 |
+
Returns:
|
| 1326 |
+
Complete driver details
|
| 1327 |
+
"""
|
| 1328 |
+
driver_id = tool_input.get("driver_id")
|
| 1329 |
+
|
| 1330 |
+
if not driver_id:
|
| 1331 |
+
return {
|
| 1332 |
+
"success": False,
|
| 1333 |
+
"error": "driver_id is required"
|
| 1334 |
+
}
|
| 1335 |
+
|
| 1336 |
+
query = """
|
| 1337 |
+
SELECT
|
| 1338 |
+
driver_id, name, phone, email,
|
| 1339 |
+
current_lat, current_lng, last_location_update,
|
| 1340 |
+
status, vehicle_type, vehicle_plate,
|
| 1341 |
+
capacity_kg, capacity_m3, skills,
|
| 1342 |
+
created_at, updated_at
|
| 1343 |
+
FROM drivers
|
| 1344 |
+
WHERE driver_id = %s
|
| 1345 |
+
"""
|
| 1346 |
+
|
| 1347 |
+
try:
|
| 1348 |
+
results = execute_query(query, (driver_id,))
|
| 1349 |
+
|
| 1350 |
+
if not results:
|
| 1351 |
+
return {
|
| 1352 |
+
"success": False,
|
| 1353 |
+
"error": f"Driver {driver_id} not found"
|
| 1354 |
+
}
|
| 1355 |
+
|
| 1356 |
+
row = results[0]
|
| 1357 |
+
|
| 1358 |
+
# Parse skills JSON if present
|
| 1359 |
+
skills = []
|
| 1360 |
+
if row['skills']:
|
| 1361 |
+
try:
|
| 1362 |
+
import json
|
| 1363 |
+
skills = json.loads(row['skills']) if isinstance(row['skills'], str) else row['skills']
|
| 1364 |
+
except:
|
| 1365 |
+
skills = []
|
| 1366 |
+
|
| 1367 |
+
driver = {
|
| 1368 |
+
"driver_id": row['driver_id'],
|
| 1369 |
+
"name": row['name'],
|
| 1370 |
+
"contact": {
|
| 1371 |
+
"phone": row['phone'],
|
| 1372 |
+
"email": row['email']
|
| 1373 |
+
},
|
| 1374 |
+
"location": {
|
| 1375 |
+
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 1376 |
+
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
| 1377 |
+
"last_update": str(row['last_location_update']) if row['last_location_update'] else None
|
| 1378 |
+
},
|
| 1379 |
+
"status": row['status'],
|
| 1380 |
+
"vehicle": {
|
| 1381 |
+
"type": row['vehicle_type'],
|
| 1382 |
+
"plate": row['vehicle_plate'],
|
| 1383 |
+
"capacity_kg": float(row['capacity_kg']) if row['capacity_kg'] else None,
|
| 1384 |
+
"capacity_m3": float(row['capacity_m3']) if row['capacity_m3'] else None
|
| 1385 |
+
},
|
| 1386 |
+
"skills": skills,
|
| 1387 |
+
"timestamps": {
|
| 1388 |
+
"created_at": str(row['created_at']),
|
| 1389 |
+
"updated_at": str(row['updated_at']) if row['updated_at'] else None
|
| 1390 |
+
}
|
| 1391 |
+
}
|
| 1392 |
+
|
| 1393 |
+
logger.info(f"Retrieved details for driver: {driver_id}")
|
| 1394 |
+
|
| 1395 |
+
return {
|
| 1396 |
+
"success": True,
|
| 1397 |
+
"driver": driver,
|
| 1398 |
+
"message": f"Driver {driver_id} details retrieved"
|
| 1399 |
+
}
|
| 1400 |
+
except Exception as e:
|
| 1401 |
+
logger.error(f"Database error getting driver details: {e}")
|
| 1402 |
+
return {
|
| 1403 |
+
"success": False,
|
| 1404 |
+
"error": f"Failed to get driver details: {str(e)}"
|
| 1405 |
+
}
|
| 1406 |
+
|
| 1407 |
+
|
| 1408 |
+
def handle_search_drivers(tool_input: dict) -> dict:
|
| 1409 |
+
"""
|
| 1410 |
+
Execute search drivers tool
|
| 1411 |
+
|
| 1412 |
+
Args:
|
| 1413 |
+
tool_input: Dict with search_term
|
| 1414 |
+
|
| 1415 |
+
Returns:
|
| 1416 |
+
List of matching drivers
|
| 1417 |
+
"""
|
| 1418 |
+
search_term = tool_input.get("search_term", "").strip()
|
| 1419 |
+
|
| 1420 |
+
if not search_term:
|
| 1421 |
+
return {
|
| 1422 |
+
"success": False,
|
| 1423 |
+
"error": "search_term is required"
|
| 1424 |
+
}
|
| 1425 |
+
|
| 1426 |
+
query = """
|
| 1427 |
+
SELECT
|
| 1428 |
+
driver_id, name, phone, email,
|
| 1429 |
+
vehicle_type, vehicle_plate, status, created_at
|
| 1430 |
+
FROM drivers
|
| 1431 |
+
WHERE
|
| 1432 |
+
driver_id ILIKE %s OR
|
| 1433 |
+
name ILIKE %s OR
|
| 1434 |
+
email ILIKE %s OR
|
| 1435 |
+
phone ILIKE %s OR
|
| 1436 |
+
vehicle_plate ILIKE %s
|
| 1437 |
+
ORDER BY name ASC
|
| 1438 |
+
LIMIT 50
|
| 1439 |
+
"""
|
| 1440 |
+
|
| 1441 |
+
search_pattern = f"%{search_term}%"
|
| 1442 |
+
params = (search_pattern, search_pattern, search_pattern, search_pattern, search_pattern)
|
| 1443 |
+
|
| 1444 |
+
try:
|
| 1445 |
+
results = execute_query(query, params)
|
| 1446 |
+
|
| 1447 |
+
if not results:
|
| 1448 |
+
return {
|
| 1449 |
+
"success": True,
|
| 1450 |
+
"drivers": [],
|
| 1451 |
+
"count": 0,
|
| 1452 |
+
"message": f"No drivers found matching '{search_term}'"
|
| 1453 |
+
}
|
| 1454 |
+
|
| 1455 |
+
drivers = []
|
| 1456 |
+
for row in results:
|
| 1457 |
+
drivers.append({
|
| 1458 |
+
"driver_id": row['driver_id'],
|
| 1459 |
+
"name": row['name'],
|
| 1460 |
+
"phone": row['phone'],
|
| 1461 |
+
"email": row['email'],
|
| 1462 |
+
"vehicle_type": row['vehicle_type'],
|
| 1463 |
+
"vehicle_plate": row['vehicle_plate'],
|
| 1464 |
+
"status": row['status'],
|
| 1465 |
+
"created_at": str(row['created_at'])
|
| 1466 |
+
})
|
| 1467 |
+
|
| 1468 |
+
logger.info(f"Search '{search_term}' found {len(drivers)} drivers")
|
| 1469 |
+
|
| 1470 |
+
return {
|
| 1471 |
+
"success": True,
|
| 1472 |
+
"drivers": drivers,
|
| 1473 |
+
"count": len(drivers),
|
| 1474 |
+
"message": f"Found {len(drivers)} driver(s) matching '{search_term}'"
|
| 1475 |
+
}
|
| 1476 |
+
except Exception as e:
|
| 1477 |
+
logger.error(f"Database error searching drivers: {e}")
|
| 1478 |
+
return {
|
| 1479 |
+
"success": False,
|
| 1480 |
+
"error": f"Failed to search drivers: {str(e)}"
|
| 1481 |
+
}
|
| 1482 |
+
|
| 1483 |
+
|
| 1484 |
+
def handle_get_available_drivers(tool_input: dict) -> dict:
|
| 1485 |
+
"""
|
| 1486 |
+
Execute get available drivers tool
|
| 1487 |
+
|
| 1488 |
+
Args:
|
| 1489 |
+
tool_input: Dict with optional limit
|
| 1490 |
+
|
| 1491 |
+
Returns:
|
| 1492 |
+
List of available drivers (active or offline)
|
| 1493 |
+
"""
|
| 1494 |
+
limit = min(tool_input.get("limit", 20), 100)
|
| 1495 |
+
|
| 1496 |
+
query = """
|
| 1497 |
+
SELECT
|
| 1498 |
+
driver_id, name, phone, vehicle_type, vehicle_plate,
|
| 1499 |
+
current_lat, current_lng, last_location_update,
|
| 1500 |
+
status, capacity_kg, capacity_m3, skills
|
| 1501 |
+
FROM drivers
|
| 1502 |
+
WHERE status IN ('active', 'offline')
|
| 1503 |
+
ORDER BY
|
| 1504 |
+
CASE status
|
| 1505 |
+
WHEN 'active' THEN 1
|
| 1506 |
+
WHEN 'offline' THEN 2
|
| 1507 |
+
END,
|
| 1508 |
+
name ASC
|
| 1509 |
+
LIMIT %s
|
| 1510 |
+
"""
|
| 1511 |
+
|
| 1512 |
+
try:
|
| 1513 |
+
results = execute_query(query, (limit,))
|
| 1514 |
+
|
| 1515 |
+
if not results:
|
| 1516 |
+
return {
|
| 1517 |
+
"success": True,
|
| 1518 |
+
"drivers": [],
|
| 1519 |
+
"count": 0,
|
| 1520 |
+
"message": "No available drivers found"
|
| 1521 |
+
}
|
| 1522 |
+
|
| 1523 |
+
drivers = []
|
| 1524 |
+
for row in results:
|
| 1525 |
+
# Parse skills JSON if present
|
| 1526 |
+
skills = []
|
| 1527 |
+
if row['skills']:
|
| 1528 |
+
try:
|
| 1529 |
+
import json
|
| 1530 |
+
skills = json.loads(row['skills']) if isinstance(row['skills'], str) else row['skills']
|
| 1531 |
+
except:
|
| 1532 |
+
skills = []
|
| 1533 |
+
|
| 1534 |
+
drivers.append({
|
| 1535 |
+
"driver_id": row['driver_id'],
|
| 1536 |
+
"name": row['name'],
|
| 1537 |
+
"phone": row['phone'],
|
| 1538 |
+
"location": {
|
| 1539 |
+
"latitude": float(row['current_lat']) if row['current_lat'] else None,
|
| 1540 |
+
"longitude": float(row['current_lng']) if row['current_lng'] else None,
|
| 1541 |
+
"last_update": str(row['last_location_update']) if row['last_location_update'] else None
|
| 1542 |
+
},
|
| 1543 |
+
"status": row['status'],
|
| 1544 |
+
"vehicle": {
|
| 1545 |
+
"type": row['vehicle_type'],
|
| 1546 |
+
"plate": row['vehicle_plate'],
|
| 1547 |
+
"capacity_kg": float(row['capacity_kg']) if row['capacity_kg'] else None,
|
| 1548 |
+
"capacity_m3": float(row['capacity_m3']) if row['capacity_m3'] else None
|
| 1549 |
+
},
|
| 1550 |
+
"skills": skills
|
| 1551 |
+
})
|
| 1552 |
+
|
| 1553 |
+
logger.info(f"Retrieved {len(drivers)} available drivers")
|
| 1554 |
+
|
| 1555 |
+
return {
|
| 1556 |
+
"success": True,
|
| 1557 |
+
"drivers": drivers,
|
| 1558 |
+
"count": len(drivers),
|
| 1559 |
+
"message": f"Found {len(drivers)} available driver(s)"
|
| 1560 |
+
}
|
| 1561 |
+
except Exception as e:
|
| 1562 |
+
logger.error(f"Database error getting available drivers: {e}")
|
| 1563 |
+
return {
|
| 1564 |
+
"success": False,
|
| 1565 |
+
"error": f"Failed to get available drivers: {str(e)}"
|
| 1566 |
+
}
|
| 1567 |
+
|
| 1568 |
+
|
| 1569 |
def get_tools_list() -> list:
|
| 1570 |
"""Get list of available tools"""
|
| 1571 |
return [tool["name"] for tool in TOOLS_SCHEMA]
|