Initial commit
This commit is contained in:
332
agents/fleet_agent.py
Normal file
332
agents/fleet_agent.py
Normal file
@@ -0,0 +1,332 @@
|
||||
"""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}"
|
||||
Reference in New Issue
Block a user