Files
AI_engine/agents/fleet_agent.py
2026-06-26 16:08:31 +05:30

333 lines
15 KiB
Python

"""Fleet Agent - Manages vehicles, capacity, and real-time tracking."""
import uuid
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
from enum import Enum
from core.agent import SpecializedAgent
from core.types import AgentTask, MessageType, Vehicle
from core.logger import logger
class VehicleStatus(str, Enum):
AVAILABLE = "available"
ASSIGNED = "assigned"
IN_TRANSIT = "in_transit"
AT_HUB = "at_hub"
MAINTENANCE = "maintenance"
OFFLINE = "offline"
@dataclass
class VehicleAssignment:
assignment_id: str
vehicle_id: str
route_id: str
order_ids: List[str]
assigned_at: datetime
estimated_return: datetime
current_location: Dict[str, float]
status: str = "active"
class FleetAgent(SpecializedAgent):
"""Fleet Agent - Manages vehicle fleet and logistics operations."""
def __init__(self):
super().__init__(
agent_id="FLEET_AGENT",
domain="fleet_management",
description="Manages vehicle fleet, capacity, and real-time tracking"
)
self._vehicles = self._init_fleet()
self._assignments: Dict[str, VehicleAssignment] = {}
self._maintenance_schedule: Dict[str, datetime] = {}
self._hub_capacity: Dict[str, Dict] = {}
# BUG FIX: hub_capacity init was dead code inside _init_fleet (after return).
# Moved here so it actually runs after _vehicles is populated.
for vehicle in self._vehicles.values():
hub = vehicle["hub"]
if hub not in self._hub_capacity:
self._hub_capacity[hub] = {"available": 0, "total": 0}
self._hub_capacity[hub]["total"] += 1
if vehicle["status"] == "available":
self._hub_capacity[hub]["available"] += 1
def _init_fleet(self) -> Dict[str, Dict]:
return {
# Delhi
"DL-V-001": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "DL-HUB-01", "location": {"lat": 28.6139, "lng": 77.2090}},
"DL-V-002": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "DL-HUB-01", "location": {"lat": 28.6139, "lng": 77.2090}},
"DL-V-003": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "DL-HUB-02", "location": {"lat": 28.5355, "lng": 77.2100}},
"DL-B-001": {"type": "bike", "capacity_kg": 15, "capacity_vol": 0.5, "status": "available", "hub": "DL-HUB-01", "location": {"lat": 28.6139, "lng": 77.2090}},
"DL-B-002": {"type": "bike", "capacity_kg": 15, "capacity_vol": 0.5, "status": "available", "hub": "DL-HUB-02", "location": {"lat": 28.5355, "lng": 77.2100}},
# Mumbai
"MU-V-001": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "MU-HUB-01", "location": {"lat": 19.0760, "lng": 72.8777}},
"MU-V-002": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "MU-HUB-01", "location": {"lat": 19.0760, "lng": 72.8777}},
"MU-V-003": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "MU-HUB-02", "location": {"lat": 19.1650, "lng": 72.8500}},
"MU-B-001": {"type": "bike", "capacity_kg": 15, "capacity_vol": 0.5, "status": "available", "hub": "MU-HUB-02", "location": {"lat": 19.1650, "lng": 72.8500}},
"MU-T-001": {"type": "truck", "capacity_kg": 2000, "capacity_vol": 25, "status": "available", "hub": "MU-HUB-01", "location": {"lat": 19.0760, "lng": 72.8777}},
# Bangalore
"BL-V-001": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "BL-HUB-01", "location": {"lat": 12.9716, "lng": 77.5946}},
"BL-B-001": {"type": "bike", "capacity_kg": 15, "capacity_vol": 0.5, "status": "available", "hub": "BL-HUB-01", "location": {"lat": 12.9716, "lng": 77.5946}},
"BL-B-002": {"type": "bike", "capacity_kg": 15, "capacity_vol": 0.5, "status": "available", "hub": "BL-HUB-01", "location": {"lat": 12.9716, "lng": 77.5946}},
# Hyderabad
"HY-V-001": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "HY-HUB-01", "location": {"lat": 17.3850, "lng": 78.4867}},
"HY-T-001": {"type": "truck", "capacity_kg": 2000, "capacity_vol": 25, "status": "available", "hub": "HY-HUB-01", "location": {"lat": 17.3850, "lng": 78.4867}},
# Pune
"PU-V-001": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "PU-HUB-01", "location": {"lat": 18.5204, "lng": 73.8567}},
"PU-B-001": {"type": "bike", "capacity_kg": 15, "capacity_vol": 0.5, "status": "available", "hub": "PU-HUB-01", "location": {"lat": 18.5204, "lng": 73.8567}},
# Kolkata
"KL-V-001": {"type": "van", "capacity_kg": 500, "capacity_vol": 8, "status": "available", "hub": "KL-HUB-01", "location": {"lat": 22.5726, "lng": 88.3639}},
"KL-B-001": {"type": "bike", "capacity_kg": 15, "capacity_vol": 0.5, "status": "available", "hub": "KL-HUB-01", "location": {"lat": 22.5726, "lng": 88.3639}},
}
async def handle_task(self, task: AgentTask) -> Dict[str, Any]:
handlers = {
"assign_vehicle": self._assign_vehicle,
"release_vehicle": self._release_vehicle,
"get_availability": self._get_availability,
"track_vehicle": self._track_vehicle,
"update_location": self._update_location,
"schedule_maintenance": self._schedule_maintenance,
"get_fleet_status": self._get_fleet_status,
"optimize_allocation": self._optimize_allocation,
}
handler = handlers.get(task.task_type, self._unknown_task)
return await handler(task)
async def _assign_vehicle(self, task: AgentTask) -> Dict[str, Any]:
route_id = task.data.get("route_id")
route_type = task.data.get("route_type", "last_mile")
from_hub = task.data.get("from_hub")
order_weight = task.data.get("order_weight", 0)
logger.info(f"Fleet Agent: Assigning vehicle for route {route_id}")
suitable_vehicles = self._find_suitable_vehicles(from_hub, order_weight, route_type)
if not suitable_vehicles:
suitable_vehicles = self._find_nearest_available_vehicles(from_hub, order_weight)
if not suitable_vehicles:
logger.warning(f"No suitable vehicles available for route {route_id}")
return {"status": "no_vehicle_available", "route_id": route_id, "message": "No suitable vehicles available"}
vehicle_id = suitable_vehicles[0]
vehicle = self._vehicles[vehicle_id]
vehicle["status"] = "assigned"
if vehicle["hub"] in self._hub_capacity:
self._hub_capacity[vehicle["hub"]]["available"] -= 1
assignment_id = f"ASN-{uuid.uuid4().hex[:8].upper()}"
assignment = VehicleAssignment(
assignment_id=assignment_id,
vehicle_id=vehicle_id,
route_id=route_id,
order_ids=[task.data.get("order_id", "")],
assigned_at=datetime.now(),
estimated_return=datetime.now() + timedelta(hours=2),
current_location=vehicle["location"],
status="active"
)
self._assignments[assignment_id] = assignment
logger.info(f"Vehicle {vehicle_id} ({vehicle['type']}) assigned to route {route_id}")
await self.send_message(
recipient="DISPATCH_AGENT",
message_type=MessageType.VEHICLE_ASSIGNED,
payload={"route_id": route_id, "vehicle_id": vehicle_id, "assignment_id": assignment_id},
)
return {
"status": "assigned",
"assignment_id": assignment_id,
"vehicle_id": vehicle_id,
"vehicle_type": vehicle["type"],
"capacity_kg": vehicle["capacity_kg"],
"capacity_vol": vehicle["capacity_vol"],
"current_location": vehicle["location"],
}
async def _release_vehicle(self, task: AgentTask) -> Dict[str, Any]:
vehicle_id = task.data.get("vehicle_id")
if vehicle_id not in self._vehicles:
return {"status": "error", "message": f"Vehicle {vehicle_id} not found"}
vehicle = self._vehicles[vehicle_id]
vehicle["status"] = "available"
if vehicle["hub"] in self._hub_capacity:
self._hub_capacity[vehicle["hub"]]["available"] += 1
logger.info(f"Vehicle {vehicle_id} released")
return {"status": "released", "vehicle_id": vehicle_id, "hub": vehicle["hub"]}
async def _get_availability(self, task: AgentTask) -> Dict[str, Any]:
hub_id = task.data.get("hub_id")
vehicle_type = task.data.get("vehicle_type")
available = []
for vid, vehicle in self._vehicles.items():
if vehicle["hub"] == hub_id and vehicle["status"] == "available":
if vehicle_type is None or vehicle["type"] == vehicle_type:
available.append({
"vehicle_id": vid,
"type": vehicle["type"],
"capacity_kg": vehicle["capacity_kg"],
"capacity_vol": vehicle["capacity_vol"],
})
return {"hub_id": hub_id, "available_vehicles": available, "total_available": len(available)}
async def _track_vehicle(self, task: AgentTask) -> Dict[str, Any]:
vehicle_id = task.data.get("vehicle_id")
if vehicle_id not in self._vehicles:
return {"status": "error", "message": f"Vehicle {vehicle_id} not found"}
vehicle = self._vehicles[vehicle_id]
active_assignment = next(
(a for a in self._assignments.values() if a.vehicle_id == vehicle_id and a.status == "active"),
None,
)
return {
"vehicle_id": vehicle_id,
"type": vehicle["type"],
"status": vehicle["status"],
"location": vehicle["location"],
"hub": vehicle["hub"],
"current_assignment": {
"assignment_id": active_assignment.assignment_id,
"route_id": active_assignment.route_id,
"estimated_return": active_assignment.estimated_return.isoformat(),
} if active_assignment else None,
}
async def _update_location(self, task: AgentTask) -> Dict[str, Any]:
vehicle_id = task.data.get("vehicle_id")
new_location = task.data.get("location")
if vehicle_id not in self._vehicles:
return {"status": "error", "message": f"Vehicle {vehicle_id} not found"}
self._vehicles[vehicle_id]["location"] = new_location
for assignment in self._assignments.values():
if assignment.vehicle_id == vehicle_id and assignment.status == "active":
assignment.current_location = new_location
break
return {"status": "updated", "vehicle_id": vehicle_id, "new_location": new_location}
async def _schedule_maintenance(self, task: AgentTask) -> Dict[str, Any]:
vehicle_id = task.data.get("vehicle_id")
maintenance_date = task.data.get("date")
if vehicle_id not in self._vehicles:
return {"status": "error", "message": f"Vehicle {vehicle_id} not found"}
if maintenance_date:
self._maintenance_schedule[vehicle_id] = datetime.fromisoformat(maintenance_date)
else:
self._maintenance_schedule[vehicle_id] = datetime.now() + timedelta(days=3)
self._vehicles[vehicle_id]["status"] = "maintenance"
return {
"status": "scheduled",
"vehicle_id": vehicle_id,
"maintenance_date": self._maintenance_schedule[vehicle_id].isoformat(),
}
async def _get_fleet_status(self, task: AgentTask) -> Dict[str, Any]:
status_summary = {
"total_vehicles": len(self._vehicles),
"by_type": {},
"by_status": {},
"by_hub": {},
"vehicles": [],
}
for vehicle_id, vehicle in self._vehicles.items():
vtype = vehicle["type"]
vstatus = vehicle["status"]
hub = vehicle["hub"]
status_summary["by_type"][vtype] = status_summary["by_type"].get(vtype, 0) + 1
status_summary["by_status"][vstatus] = status_summary["by_status"].get(vstatus, 0) + 1
if hub not in status_summary["by_hub"]:
status_summary["by_hub"][hub] = {"total": 0, "available": 0}
status_summary["by_hub"][hub]["total"] += 1
if vstatus == "available":
status_summary["by_hub"][hub]["available"] += 1
status_summary["vehicles"].append({
"vehicle_id": vehicle_id,
"type": vtype,
"status": vstatus,
"hub": hub,
"location": vehicle["location"],
})
return status_summary
async def _optimize_allocation(self, task: AgentTask) -> Dict[str, Any]:
logger.info("Fleet Agent: Optimizing vehicle allocation")
recommendations = []
for hub_id, capacity in self._hub_capacity.items():
utilization = (capacity["total"] - capacity["available"]) / capacity["total"] if capacity["total"] > 0 else 0
if utilization < 0.3:
recommendations.append({
"action": "redistribute",
"from_hub": hub_id,
"reason": f"Low utilization ({utilization*100:.0f}%)",
"vehicles_to_move": 1,
})
elif utilization > 0.9:
recommendations.append({
"action": "request_reinforcement",
"to_hub": hub_id,
"reason": f"High utilization ({utilization*100:.0f}%)",
"vehicles_needed": 2,
})
return {"status": "optimized", "recommendations": recommendations}
async def _unknown_task(self, task: AgentTask) -> Dict[str, Any]:
return {"status": "error", "message": f"Unknown task: {task.task_type}"}
def _find_suitable_vehicles(self, hub_id: str, weight_kg: float, route_type: str) -> List[str]:
suitable = []
for vehicle_id, vehicle in self._vehicles.items():
if vehicle["hub"] != hub_id:
continue
if vehicle["status"] != "available":
continue
if vehicle["capacity_kg"] < weight_kg:
continue
if route_type == "last_mile" and vehicle["type"] in ["van", "truck"]:
continue
suitable.append(vehicle_id)
return suitable
def _find_nearest_available_vehicles(self, hub_id: str, weight_kg: float) -> List[str]:
available = [
vid for vid, v in self._vehicles.items()
if v["status"] == "available" and v["capacity_kg"] >= weight_kg
]
return available[:1]
async def think(self, context: str, options: List[str] = None) -> str:
return f"[FLEET_AGENT reasoning]: {context}"