""" Tool definitions and execution handlers for FleetMind chat Simulates MCP tools using Claude's tool calling feature """ import sys from pathlib import Path from datetime import datetime, timedelta import logging # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent)) from database.connection import execute_write from chat.geocoding import GeocodingService logger = logging.getLogger(__name__) # Initialize geocoding service geocoding_service = GeocodingService() # Tool schemas for Claude TOOLS_SCHEMA = [ { "name": "geocode_address", "description": "Convert a delivery address to GPS coordinates and validate the address format. Use this before creating an order to ensure the address is valid.", "input_schema": { "type": "object", "properties": { "address": { "type": "string", "description": "The full delivery address to geocode (e.g., '123 Main St, San Francisco, CA')" } }, "required": ["address"] } }, { "name": "create_order", "description": "Create a new delivery order in the database. Only call this after geocoding the address successfully.", "input_schema": { "type": "object", "properties": { "customer_name": { "type": "string", "description": "Full name of the customer" }, "customer_phone": { "type": "string", "description": "Customer phone number (optional)" }, "customer_email": { "type": "string", "description": "Customer email address (optional)" }, "delivery_address": { "type": "string", "description": "Full delivery address" }, "delivery_lat": { "type": "number", "description": "Latitude from geocoding" }, "delivery_lng": { "type": "number", "description": "Longitude from geocoding" }, "time_window_end": { "type": "string", "description": "Delivery deadline in ISO format (e.g., '2025-11-13T17:00:00'). If not specified by user, default to 6 hours from now." }, "priority": { "type": "string", "enum": ["standard", "express", "urgent"], "description": "Delivery priority. Default to 'standard' unless user specifies urgent/express." }, "special_instructions": { "type": "string", "description": "Any special delivery instructions (optional)" }, "weight_kg": { "type": "number", "description": "Package weight in kilograms (optional, default to 5.0)" } }, "required": ["customer_name", "delivery_address", "delivery_lat", "delivery_lng"] } } ] def execute_tool(tool_name: str, tool_input: dict) -> dict: """ Route tool execution to appropriate handler Args: tool_name: Name of the tool to execute tool_input: Tool input parameters Returns: Dict with tool execution results """ try: if tool_name == "geocode_address": return handle_geocode_address(tool_input) elif tool_name == "create_order": return handle_create_order(tool_input) elif tool_name == "create_driver": return handle_create_driver(tool_input) else: return { "success": False, "error": f"Unknown tool: {tool_name}" } except Exception as e: logger.error(f"Tool execution error ({tool_name}): {e}") return { "success": False, "error": str(e) } def handle_geocode_address(tool_input: dict) -> dict: """ Execute geocoding tool Args: tool_input: Dict with 'address' key Returns: Geocoding result """ address = tool_input.get("address", "") if not address: return { "success": False, "error": "Address is required" } logger.info(f"Geocoding address: {address}") result = geocoding_service.geocode(address) return { "success": True, "latitude": result["lat"], "longitude": result["lng"], "formatted_address": result["formatted_address"], "confidence": result["confidence"], "message": f"Address geocoded successfully ({result['confidence']})" } def handle_create_order(tool_input: dict) -> dict: """ Execute order creation tool Args: tool_input: Dict with order fields Returns: Order creation result """ # Extract fields with defaults customer_name = tool_input.get("customer_name") customer_phone = tool_input.get("customer_phone") customer_email = tool_input.get("customer_email") delivery_address = tool_input.get("delivery_address") delivery_lat = tool_input.get("delivery_lat") delivery_lng = tool_input.get("delivery_lng") priority = tool_input.get("priority", "standard") special_instructions = tool_input.get("special_instructions") weight_kg = tool_input.get("weight_kg", 5.0) # Validate required fields if not all([customer_name, delivery_address, delivery_lat, delivery_lng]): return { "success": False, "error": "Missing required fields: customer_name, delivery_address, delivery_lat, delivery_lng" } # Generate order ID now = datetime.now() order_id = f"ORD-{now.strftime('%Y%m%d%H%M%S')}" # Handle time window time_window_end_str = tool_input.get("time_window_end") if time_window_end_str: try: time_window_end = datetime.fromisoformat(time_window_end_str.replace('Z', '+00:00')) except: time_window_end = now + timedelta(hours=6) else: time_window_end = now + timedelta(hours=6) time_window_start = now + timedelta(hours=2) # Insert into database query = """ INSERT INTO orders ( order_id, customer_name, customer_phone, customer_email, delivery_address, delivery_lat, delivery_lng, time_window_start, time_window_end, priority, weight_kg, status, special_instructions ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ params = ( order_id, customer_name, customer_phone, customer_email, delivery_address, delivery_lat, delivery_lng, time_window_start, time_window_end, priority, weight_kg, "pending", special_instructions ) try: execute_write(query, params) logger.info(f"Order created: {order_id}") return { "success": True, "order_id": order_id, "status": "pending", "customer": customer_name, "address": delivery_address, "deadline": time_window_end.strftime("%Y-%m-%d %H:%M"), "priority": priority, "message": f"Order {order_id} created successfully!" } except Exception as e: logger.error(f"Database error creating order: {e}") return { "success": False, "error": f"Failed to create order: {str(e)}" } def handle_create_driver(tool_input: dict) -> dict: """ Execute driver creation tool Args: tool_input: Dict with driver fields Returns: Driver creation result """ # Extract fields with defaults name = tool_input.get("name") phone = tool_input.get("phone") email = tool_input.get("email") vehicle_type = tool_input.get("vehicle_type", "van") vehicle_plate = tool_input.get("vehicle_plate") capacity_kg = tool_input.get("capacity_kg", 1000.0) capacity_m3 = tool_input.get("capacity_m3", 12.0) # Convert skills to regular list (handles protobuf RepeatedComposite) skills_raw = tool_input.get("skills", []) skills = list(skills_raw) if skills_raw else [] status = tool_input.get("status", "active") # Validate required fields if not name: return { "success": False, "error": "Missing required field: name" } # Generate driver ID now = datetime.now() driver_id = f"DRV-{now.strftime('%Y%m%d%H%M%S')}" # Default location (San Francisco) current_lat = tool_input.get("current_lat", 37.7749) current_lng = tool_input.get("current_lng", -122.4194) # Insert into database query = """ INSERT INTO drivers ( driver_id, name, phone, email, current_lat, current_lng, last_location_update, status, vehicle_type, vehicle_plate, capacity_kg, capacity_m3, skills ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ # Convert skills list to JSON import json skills_json = json.dumps(skills) if skills else json.dumps([]) params = ( driver_id, name, phone, email, current_lat, current_lng, now, status, vehicle_type, vehicle_plate, capacity_kg, capacity_m3, skills_json ) try: execute_write(query, params) logger.info(f"Driver created: {driver_id}") return { "success": True, "driver_id": driver_id, "name": name, "status": status, "vehicle_type": vehicle_type, "vehicle_plate": vehicle_plate, "capacity_kg": capacity_kg, "skills": skills, "message": f"Driver {driver_id} ({name}) created successfully!" } except Exception as e: logger.error(f"Database error creating driver: {e}") return { "success": False, "error": f"Failed to create driver: {str(e)}" } def get_tools_list() -> list: """Get list of available tools""" return [tool["name"] for tool in TOOLS_SCHEMA] def get_tool_description(tool_name: str) -> str: """Get description for a specific tool""" for tool in TOOLS_SCHEMA: if tool["name"] == tool_name: return tool["description"] return ""