341 lines
14 KiB
Python
341 lines
14 KiB
Python
"""
|
|
VRP Optimization API Endpoints
|
|
Endpoints for VRP optimization and metrics retrieval
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from typing import List, Dict, Any, Optional
|
|
from pydantic import BaseModel, Field
|
|
import structlog
|
|
|
|
from app.services.vrp_optimization_service import VRPOptimizationService
|
|
from app.services.distribution_service import DistributionService
|
|
from shared.auth.tenant_access import verify_tenant_permission_dep
|
|
from app.core.config import settings
|
|
|
|
logger = structlog.get_logger()
|
|
router = APIRouter()
|
|
|
|
|
|
# Pydantic models for request/response
|
|
class VRPOptimizationRequest(BaseModel):
|
|
algorithm_version: str = Field(default="v2.1", description="VRP algorithm version to use")
|
|
constraints: Optional[Dict[str, Any]] = Field(
|
|
None,
|
|
description="Optimization constraints: max_route_duration, max_route_distance, etc."
|
|
)
|
|
|
|
|
|
class VRPOptimizationResponse(BaseModel):
|
|
success: bool
|
|
route_id: str
|
|
optimization_savings: Dict[str, Any]
|
|
vrp_algorithm_version: str
|
|
vrp_optimization_timestamp: str
|
|
vrp_constraints_satisfied: bool
|
|
vrp_objective_value: float
|
|
|
|
|
|
class RouteOptimizationMetrics(BaseModel):
|
|
route_id: str
|
|
route_number: str
|
|
route_date: str
|
|
vrp_optimization_savings: Optional[Dict[str, Any]]
|
|
vrp_algorithm_version: Optional[str]
|
|
vrp_optimization_timestamp: Optional[str]
|
|
vrp_constraints_satisfied: Optional[bool]
|
|
vrp_objective_value: Optional[float]
|
|
total_distance_km: Optional[float]
|
|
estimated_duration_minutes: Optional[int]
|
|
|
|
|
|
class NetworkOptimizationSummary(BaseModel):
|
|
total_routes: int
|
|
optimized_routes: int
|
|
total_distance_saved_km: float
|
|
total_time_saved_minutes: float
|
|
total_fuel_saved_liters: float
|
|
total_co2_saved_kg: float
|
|
total_cost_saved_eur: float
|
|
optimization_rate: float
|
|
average_savings_per_route: Optional[Dict[str, Any]]
|
|
|
|
|
|
class OptimizationHistoryItem(BaseModel):
|
|
optimization_id: str
|
|
route_id: str
|
|
timestamp: str
|
|
algorithm_version: str
|
|
distance_saved_km: float
|
|
time_saved_minutes: float
|
|
fuel_saved_liters: float
|
|
co2_saved_kg: float
|
|
cost_saved_eur: float
|
|
constraints_satisfied: bool
|
|
|
|
|
|
async def get_vrp_optimization_service() -> VRPOptimizationService:
|
|
"""Dependency injection for VRPOptimizationService"""
|
|
from app.core.database import database_manager
|
|
from app.services.distribution_service import DistributionService as BusinessDistributionService
|
|
from app.repositories.delivery_route_repository import DeliveryRouteRepository
|
|
from app.repositories.shipment_repository import ShipmentRepository
|
|
from app.repositories.delivery_schedule_repository import DeliveryScheduleRepository
|
|
from shared.clients.tenant_client import TenantServiceClient
|
|
from shared.clients.inventory_client import InventoryServiceClient
|
|
from shared.clients.procurement_client import ProcurementServiceClient
|
|
from app.services.routing_optimizer import RoutingOptimizer
|
|
|
|
# Create the business distribution service with proper dependencies
|
|
route_repository = DeliveryRouteRepository(database_manager.get_session())
|
|
shipment_repository = ShipmentRepository(database_manager.get_session())
|
|
schedule_repository = DeliveryScheduleRepository(database_manager.get_session())
|
|
|
|
# Create client instances (these will be initialized with proper config)
|
|
tenant_client = TenantServiceClient()
|
|
inventory_client = InventoryServiceClient()
|
|
procurement_client = ProcurementServiceClient()
|
|
routing_optimizer = RoutingOptimizer()
|
|
|
|
distribution_service = BusinessDistributionService(
|
|
route_repository=route_repository,
|
|
shipment_repository=shipment_repository,
|
|
schedule_repository=schedule_repository,
|
|
procurement_client=procurement_client,
|
|
tenant_client=tenant_client,
|
|
inventory_client=inventory_client,
|
|
routing_optimizer=routing_optimizer
|
|
)
|
|
|
|
return VRPOptimizationService(distribution_service, database_manager)
|
|
|
|
|
|
@router.post("/tenants/{tenant_id}/routes/{route_id}/optimize",
|
|
response_model=VRPOptimizationResponse,
|
|
summary="Optimize delivery route with VRP")
|
|
async def optimize_route_with_vrp(
|
|
tenant_id: str,
|
|
route_id: str,
|
|
optimization_request: VRPOptimizationRequest,
|
|
vrp_service: VRPOptimizationService = Depends(get_vrp_optimization_service),
|
|
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
|
):
|
|
"""
|
|
Optimize a delivery route using VRP algorithm
|
|
|
|
This endpoint applies VRP optimization to a specific delivery route and stores
|
|
the optimization metrics for analysis and reporting.
|
|
"""
|
|
try:
|
|
result = await vrp_service.optimize_route_with_vrp(
|
|
route_id=route_id,
|
|
algorithm_version=optimization_request.algorithm_version,
|
|
constraints=optimization_request.constraints
|
|
)
|
|
|
|
if not result.get('success'):
|
|
raise HTTPException(status_code=500, detail="Optimization failed")
|
|
|
|
return VRPOptimizationResponse(
|
|
success=True,
|
|
route_id=result['route_id'],
|
|
optimization_savings=result['optimization_savings'],
|
|
vrp_algorithm_version=result['optimization_savings'].get('algorithm_version', optimization_request.algorithm_version),
|
|
vrp_optimization_timestamp=result['optimization_savings'].get('timestamp', datetime.now().isoformat()),
|
|
vrp_constraints_satisfied=result['optimization_savings'].get('constraints_satisfied', True),
|
|
vrp_objective_value=result['optimization_savings'].get('objective_value', 0.0)
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error("VRP optimization failed", tenant_id=tenant_id, route_id=route_id, error=str(e))
|
|
raise HTTPException(status_code=500, detail=f"VRP optimization failed: {str(e)}")
|
|
|
|
|
|
@router.get("/tenants/{tenant_id}/routes/{route_id}/optimization-metrics",
|
|
response_model=RouteOptimizationMetrics,
|
|
summary="Get VRP optimization metrics for route")
|
|
async def get_route_optimization_metrics(
|
|
tenant_id: str,
|
|
route_id: str,
|
|
vrp_service: VRPOptimizationService = Depends(get_vrp_optimization_service),
|
|
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
|
):
|
|
"""
|
|
Get VRP optimization metrics for a specific route
|
|
|
|
Retrieves stored optimization metrics including savings, algorithm version,
|
|
and constraint satisfaction status.
|
|
"""
|
|
try:
|
|
metrics = await vrp_service.get_route_optimization_metrics(route_id)
|
|
return RouteOptimizationMetrics(**metrics)
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to get route optimization metrics", tenant_id=tenant_id, route_id=route_id, error=str(e))
|
|
raise HTTPException(status_code=500, detail=f"Failed to get optimization metrics: {str(e)}")
|
|
|
|
|
|
@router.get("/tenants/{tenant_id}/vrp/optimization-summary",
|
|
response_model=NetworkOptimizationSummary,
|
|
summary="Get network-wide VRP optimization summary")
|
|
async def get_network_optimization_summary(
|
|
tenant_id: str,
|
|
vrp_service: VRPOptimizationService = Depends(get_vrp_optimization_service),
|
|
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
|
):
|
|
"""
|
|
Get aggregated VRP optimization metrics across all routes
|
|
|
|
Provides network-wide summary of optimization benefits including
|
|
total savings, optimization rate, and average improvements.
|
|
"""
|
|
try:
|
|
summary = await vrp_service.get_network_optimization_summary(tenant_id)
|
|
return NetworkOptimizationSummary(**summary)
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to get network optimization summary", tenant_id=tenant_id, error=str(e))
|
|
raise HTTPException(status_code=500, detail=f"Failed to get optimization summary: {str(e)}")
|
|
|
|
|
|
@router.post("/tenants/{tenant_id}/vrp/batch-optimize",
|
|
summary="Batch optimize multiple routes")
|
|
async def batch_optimize_routes(
|
|
tenant_id: str,
|
|
route_ids: List[str] = Query(..., description="List of route IDs to optimize"),
|
|
algorithm_version: str = Query("v2.1", description="VRP algorithm version"),
|
|
vrp_service: VRPOptimizationService = Depends(get_vrp_optimization_service),
|
|
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
|
):
|
|
"""
|
|
Batch optimize multiple delivery routes with VRP
|
|
|
|
Applies VRP optimization to multiple routes in a single request.
|
|
"""
|
|
try:
|
|
result = await vrp_service.batch_optimize_routes(tenant_id, route_ids)
|
|
|
|
return {
|
|
'success': True,
|
|
'total_routes_processed': result['total_routes_processed'],
|
|
'successful_optimizations': result['successful_optimizations'],
|
|
'failed_optimizations': result['failed_optimizations'],
|
|
'results': result['results']
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error("Batch optimization failed", tenant_id=tenant_id, error=str(e))
|
|
raise HTTPException(status_code=500, detail=f"Batch optimization failed: {str(e)}")
|
|
|
|
|
|
@router.get("/tenants/{tenant_id}/routes/{route_id}/optimization-history",
|
|
response_model=List[OptimizationHistoryItem],
|
|
summary="Get optimization history for route")
|
|
async def get_optimization_history(
|
|
tenant_id: str,
|
|
route_id: str,
|
|
limit: int = Query(10, description="Maximum number of historical records to return"),
|
|
vrp_service: VRPOptimizationService = Depends(get_vrp_optimization_service),
|
|
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
|
):
|
|
"""
|
|
Get historical optimization records for a route
|
|
|
|
Retrieves past optimization runs and their results for analysis.
|
|
"""
|
|
try:
|
|
history = await vrp_service.get_optimization_history(route_id, limit)
|
|
return [OptimizationHistoryItem(**item) for item in history]
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to get optimization history", tenant_id=tenant_id, route_id=route_id, error=str(e))
|
|
raise HTTPException(status_code=500, detail=f"Failed to get optimization history: {str(e)}")
|
|
|
|
|
|
@router.get("/tenants/{tenant_id}/vrp/constraints/validate",
|
|
summary="Validate VRP constraints")
|
|
async def validate_vrp_constraints(
|
|
tenant_id: str,
|
|
route_id: str,
|
|
max_route_duration: Optional[int] = Query(None, description="Maximum route duration in minutes"),
|
|
max_route_distance: Optional[float] = Query(None, description="Maximum route distance in km"),
|
|
vrp_service: VRPOptimizationService = Depends(get_vrp_optimization_service),
|
|
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
|
):
|
|
"""
|
|
Validate VRP constraints against a route
|
|
|
|
Checks if a route satisfies specified VRP constraints.
|
|
"""
|
|
try:
|
|
from app.services.vrp_optimization_service import VRPConstraintValidator
|
|
|
|
# Get route data
|
|
route = await vrp_service.distribution_service.get_delivery_route(route_id)
|
|
|
|
if not route:
|
|
raise HTTPException(status_code=404, detail="Route not found")
|
|
|
|
# Build constraints dict
|
|
constraints = {}
|
|
if max_route_duration is not None:
|
|
constraints['max_route_duration'] = max_route_duration
|
|
if max_route_distance is not None:
|
|
constraints['max_route_distance'] = max_route_distance
|
|
|
|
# Validate constraints
|
|
validation_result = VRPConstraintValidator.validate_constraints(route, constraints)
|
|
|
|
return {
|
|
'success': True,
|
|
'all_constraints_satisfied': validation_result['all_satisfied'],
|
|
'constraint_violations': validation_result['constraint_violations']
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to validate VRP constraints", tenant_id=tenant_id, route_id=route_id, error=str(e))
|
|
raise HTTPException(status_code=500, detail=f"Failed to validate constraints: {str(e)}")
|
|
|
|
|
|
@router.post("/tenants/{tenant_id}/vrp/simulate",
|
|
summary="Simulate VRP optimization")
|
|
async def simulate_vrp_optimization(
|
|
tenant_id: str,
|
|
route_id: str,
|
|
vrp_service: VRPOptimizationService = Depends(get_vrp_optimization_service),
|
|
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
|
):
|
|
"""
|
|
Simulate VRP optimization without saving results
|
|
|
|
Useful for testing and previewing optimization results.
|
|
"""
|
|
try:
|
|
from app.services.vrp_optimization_service import VRPOptimizationSimulator
|
|
|
|
# Get route data
|
|
route = await vrp_service.distribution_service.get_delivery_route(route_id)
|
|
|
|
if not route:
|
|
raise HTTPException(status_code=404, detail="Route not found")
|
|
|
|
# Simulate optimization
|
|
simulation_result = VRPOptimizationSimulator.simulate_optimization(route)
|
|
|
|
return {
|
|
'success': True,
|
|
'original_route': simulation_result['original_route'],
|
|
'optimized_route': simulation_result['optimized_route'],
|
|
'optimization_savings': simulation_result['optimization_savings'],
|
|
'algorithm_version': simulation_result['algorithm_version'],
|
|
'constraints_satisfied': simulation_result['constraints_satisfied'],
|
|
'objective_value': simulation_result['objective_value']
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error("VRP simulation failed", tenant_id=tenant_id, route_id=route_id, error=str(e))
|
|
raise HTTPException(status_code=500, detail=f"VRP simulation failed: {str(e)}")
|
|
|
|
|
|
# Import datetime at runtime to avoid circular imports
|
|
from datetime import datetime |