""" 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