Imporve enterprise
This commit is contained in:
@@ -302,4 +302,23 @@ class DistributionService:
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating delivery schedule: {e}")
|
||||
raise
|
||||
raise
|
||||
|
||||
# VRP Optimization Service Methods
|
||||
async def get_route_by_id(self, route_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get a specific delivery route by ID
|
||||
"""
|
||||
return await self.route_repository.get_route_by_id(route_id)
|
||||
|
||||
async def update_route_vrp_metrics(self, route_id: str, vrp_metrics: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Update VRP optimization metrics for a route
|
||||
"""
|
||||
return await self.route_repository.update_route_vrp_metrics(route_id, vrp_metrics)
|
||||
|
||||
async def get_routes_by_tenant(self, tenant_id: str, limit: int = None, offset: int = None, order_by: str = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all routes for a specific tenant with pagination and ordering
|
||||
"""
|
||||
return await self.route_repository.get_routes_by_tenant(tenant_id, limit, offset, order_by)
|
||||
357
services/distribution/app/services/vrp_optimization_service.py
Normal file
357
services/distribution/app/services/vrp_optimization_service.py
Normal file
@@ -0,0 +1,357 @@
|
||||
"""
|
||||
VRP Optimization Service
|
||||
Business logic for VRP optimization and metrics management
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
import structlog
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.repositories.delivery_route_repository import DeliveryRouteRepository
|
||||
from app.services.routing_optimizer import RoutingOptimizer
|
||||
from app.core.database import get_db
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class VRPOptimizationService:
|
||||
"""
|
||||
Service for VRP optimization operations
|
||||
"""
|
||||
|
||||
def __init__(self, distribution_service: "DistributionService", database_manager: Any):
|
||||
"""
|
||||
Initialize VRP optimization service
|
||||
|
||||
Args:
|
||||
distribution_service: Distribution service instance
|
||||
database_manager: Database manager for session management
|
||||
"""
|
||||
self.distribution_service = distribution_service
|
||||
self.database_manager = database_manager
|
||||
self.routing_optimizer = RoutingOptimizer()
|
||||
|
||||
async def optimize_route(
|
||||
self,
|
||||
tenant_id: str,
|
||||
route_id: str,
|
||||
optimization_params: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Optimize a specific delivery route using VRP
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
route_id: Route ID to optimize
|
||||
optimization_params: Optimization parameters
|
||||
|
||||
Returns:
|
||||
Optimization result with metrics
|
||||
"""
|
||||
try:
|
||||
# Get the current route using distribution service
|
||||
route = await self.distribution_service.get_route_by_id(route_id)
|
||||
if not route:
|
||||
raise ValueError(f"Route {route_id} not found")
|
||||
|
||||
# Extract deliveries from route sequence
|
||||
deliveries = self._extract_deliveries_from_route(route)
|
||||
|
||||
# Perform VRP optimization
|
||||
depot_location = optimization_params.get('depot_location', (0.0, 0.0))
|
||||
vehicle_capacity = optimization_params.get('vehicle_capacity_kg', 1000.0)
|
||||
time_limit = optimization_params.get('time_limit_seconds', 30.0)
|
||||
|
||||
optimization_result = await self.routing_optimizer.optimize_daily_routes(
|
||||
deliveries=deliveries,
|
||||
depot_location=depot_location,
|
||||
vehicle_capacity_kg=vehicle_capacity,
|
||||
time_limit_seconds=time_limit
|
||||
)
|
||||
|
||||
# Update route with optimization metrics
|
||||
vrp_metrics = {
|
||||
'vrp_optimization_savings': {
|
||||
'distance_saved_km': optimization_result.get('distance_savings_km', 0.0),
|
||||
'time_saved_minutes': optimization_result.get('time_savings_minutes', 0.0),
|
||||
'cost_saved': optimization_result.get('cost_savings', 0.0)
|
||||
},
|
||||
'vrp_algorithm_version': 'or-tools-v1.0',
|
||||
'vrp_optimization_timestamp': datetime.utcnow(),
|
||||
'vrp_constraints_satisfied': optimization_result.get('constraints_satisfied', True),
|
||||
'vrp_objective_value': optimization_result.get('objective_value', 0.0)
|
||||
}
|
||||
|
||||
# Update the route with VRP metrics using distribution service
|
||||
await self.distribution_service.update_route_vrp_metrics(route_id, vrp_metrics)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'route_id': route_id,
|
||||
'optimization_metrics': vrp_metrics,
|
||||
'optimized_route': optimization_result.get('optimized_route', [])
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("vrp_optimization_failed", error=str(e), route_id=route_id)
|
||||
raise
|
||||
|
||||
def _extract_deliveries_from_route(self, route: Any) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extract deliveries from route sequence
|
||||
|
||||
Args:
|
||||
route: Delivery route object
|
||||
|
||||
Returns:
|
||||
List of delivery dictionaries
|
||||
"""
|
||||
deliveries = []
|
||||
route_sequence = route.route_sequence or []
|
||||
|
||||
for stop in route_sequence:
|
||||
deliveries.append({
|
||||
'id': stop.get('id', ''),
|
||||
'location': (stop.get('lat', 0.0), stop.get('lng', 0.0)),
|
||||
'weight_kg': stop.get('weight_kg', 0.0),
|
||||
'time_window': stop.get('time_window')
|
||||
})
|
||||
|
||||
return deliveries
|
||||
|
||||
async def get_route_optimization_metrics(
|
||||
self,
|
||||
tenant_id: str,
|
||||
route_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get VRP optimization metrics for a specific route
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
route_id: Route ID
|
||||
|
||||
Returns:
|
||||
VRP optimization metrics
|
||||
"""
|
||||
route = await self.route_repository.get_route_by_id(route_id)
|
||||
if not route:
|
||||
raise ValueError(f"Route {route_id} not found")
|
||||
|
||||
return {
|
||||
'vrp_optimization_savings': route.vrp_optimization_savings,
|
||||
'vrp_algorithm_version': route.vrp_algorithm_version,
|
||||
'vrp_optimization_timestamp': route.vrp_optimization_timestamp,
|
||||
'vrp_constraints_satisfied': route.vrp_constraints_satisfied,
|
||||
'vrp_objective_value': route.vrp_objective_value
|
||||
}
|
||||
|
||||
async def get_network_optimization_summary(
|
||||
self,
|
||||
tenant_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get VRP optimization summary across all routes for a tenant
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
|
||||
Returns:
|
||||
Network optimization summary
|
||||
"""
|
||||
routes = await self.route_repository.get_routes_by_tenant(tenant_id)
|
||||
|
||||
total_optimized = 0
|
||||
total_distance_saved = 0.0
|
||||
total_time_saved = 0.0
|
||||
total_cost_saved = 0.0
|
||||
|
||||
for route in routes:
|
||||
if route.vrp_optimization_timestamp:
|
||||
total_optimized += 1
|
||||
savings = route.vrp_optimization_savings or {}
|
||||
total_distance_saved += savings.get('distance_saved_km', 0.0)
|
||||
total_time_saved += savings.get('time_saved_minutes', 0.0)
|
||||
total_cost_saved += savings.get('cost_saved', 0.0)
|
||||
|
||||
return {
|
||||
'total_routes': len(routes),
|
||||
'total_optimized_routes': total_optimized,
|
||||
'optimization_rate': total_optimized / len(routes) if routes else 0.0,
|
||||
'total_distance_saved_km': total_distance_saved,
|
||||
'total_time_saved_minutes': total_time_saved,
|
||||
'total_cost_saved': total_cost_saved,
|
||||
'average_savings_per_route': {
|
||||
'distance_km': total_distance_saved / total_optimized if total_optimized > 0 else 0.0,
|
||||
'time_minutes': total_time_saved / total_optimized if total_optimized > 0 else 0.0,
|
||||
'cost': total_cost_saved / total_optimized if total_optimized > 0 else 0.0
|
||||
}
|
||||
}
|
||||
|
||||
async def batch_optimize_routes(
|
||||
self,
|
||||
tenant_id: str,
|
||||
route_ids: List[str],
|
||||
optimization_params: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Batch optimize multiple routes
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
route_ids: List of route IDs to optimize
|
||||
optimization_params: Optimization parameters
|
||||
|
||||
Returns:
|
||||
Batch optimization results
|
||||
"""
|
||||
results = []
|
||||
|
||||
for route_id in route_ids:
|
||||
try:
|
||||
result = await self.optimize_route(tenant_id, route_id, optimization_params)
|
||||
results.append({
|
||||
'route_id': route_id,
|
||||
'success': True,
|
||||
'metrics': result['optimization_metrics']
|
||||
})
|
||||
except Exception as e:
|
||||
results.append({
|
||||
'route_id': route_id,
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
return {
|
||||
'total_routes': len(route_ids),
|
||||
'successful_optimizations': sum(1 for r in results if r['success']),
|
||||
'failed_optimizations': sum(1 for r in results if not r['success']),
|
||||
'results': results
|
||||
}
|
||||
|
||||
async def validate_optimization_constraints(
|
||||
self,
|
||||
tenant_id: str,
|
||||
route_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate VRP optimization constraints for a route
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
route_id: Route ID
|
||||
|
||||
Returns:
|
||||
Constraint validation results
|
||||
"""
|
||||
route = await self.route_repository.get_route_by_id(route_id)
|
||||
if not route:
|
||||
raise ValueError(f"Route {route_id} not found")
|
||||
|
||||
# Check if route has been optimized
|
||||
if not route.vrp_optimization_timestamp:
|
||||
return {
|
||||
'route_id': route_id,
|
||||
'is_optimized': False,
|
||||
'constraints_valid': False,
|
||||
'message': 'Route has not been optimized yet'
|
||||
}
|
||||
|
||||
# Validate constraints
|
||||
constraints_valid = route.vrp_constraints_satisfied or False
|
||||
|
||||
return {
|
||||
'route_id': route_id,
|
||||
'is_optimized': True,
|
||||
'constraints_valid': constraints_valid,
|
||||
'vrp_algorithm_version': route.vrp_algorithm_version,
|
||||
'optimization_timestamp': route.vrp_optimization_timestamp
|
||||
}
|
||||
|
||||
async def get_optimization_history(
|
||||
self,
|
||||
tenant_id: str,
|
||||
limit: int = 50,
|
||||
offset: int = 0
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get VRP optimization history for a tenant
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
limit: Maximum number of records to return
|
||||
offset: Pagination offset
|
||||
|
||||
Returns:
|
||||
Optimization history
|
||||
"""
|
||||
routes = await self.route_repository.get_routes_by_tenant(
|
||||
tenant_id,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
order_by='vrp_optimization_timestamp DESC'
|
||||
)
|
||||
|
||||
history = []
|
||||
for route in routes:
|
||||
if route.vrp_optimization_timestamp:
|
||||
history.append({
|
||||
'route_id': str(route.id),
|
||||
'route_number': route.route_number,
|
||||
'optimization_timestamp': route.vrp_optimization_timestamp,
|
||||
'algorithm_version': route.vrp_algorithm_version,
|
||||
'constraints_satisfied': route.vrp_constraints_satisfied,
|
||||
'objective_value': route.vrp_objective_value,
|
||||
'savings': route.vrp_optimization_savings
|
||||
})
|
||||
|
||||
return {
|
||||
'total_records': len(history),
|
||||
'history': history
|
||||
}
|
||||
|
||||
async def simulate_optimization(
|
||||
self,
|
||||
tenant_id: str,
|
||||
route_data: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Simulate VRP optimization without saving results
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
route_data: Route data for simulation
|
||||
|
||||
Returns:
|
||||
Simulation results
|
||||
"""
|
||||
try:
|
||||
deliveries = route_data.get('deliveries', [])
|
||||
depot_location = route_data.get('depot_location', (0.0, 0.0))
|
||||
vehicle_capacity = route_data.get('vehicle_capacity_kg', 1000.0)
|
||||
time_limit = route_data.get('time_limit_seconds', 30.0)
|
||||
|
||||
simulation_result = await self.routing_optimizer.optimize_daily_routes(
|
||||
deliveries=deliveries,
|
||||
depot_location=depot_location,
|
||||
vehicle_capacity_kg=vehicle_capacity,
|
||||
time_limit_seconds=time_limit
|
||||
)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'simulation_results': simulation_result,
|
||||
'estimated_savings': {
|
||||
'distance_km': simulation_result.get('distance_savings_km', 0.0),
|
||||
'time_minutes': simulation_result.get('time_savings_minutes', 0.0),
|
||||
'cost': simulation_result.get('cost_savings', 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("vrp_simulation_failed", error=str(e))
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
Reference in New Issue
Block a user