mashrur950 commited on
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 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 create orders FAST!"""
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
- *Complete:* "Create order for John Doe, 123 Main St San Francisco CA, phone 555-1234, deliver by 5 PM"
 
 
268
 
269
- *Partial:* "I need a delivery for Sarah" *(I'll ask for missing details)*
270
 
271
- *Natural:* "Urgent package to [email protected] at 456 Market Street"
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
- **Your goal:** Execute tasks in ONE smooth automated flow. No stopping, no intermediate messages!"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = 10 # Allow more iterations for complex tasks
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. Add New Drivers:**
 
 
 
 
 
 
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
- 🚚 **Drivers:**
 
 
 
 
 
 
 
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]