package service import ( "errors" "fmt" "math" "sort" "bugulma/backend/internal/domain" "bugulma/backend/internal/geospatial" ) // TransportationService handles transportation cost calculations and route optimization type TransportationService struct { geoCalc geospatial.Calculator } // NewTransportationService creates a new transportation service func NewTransportationService(geoCalc geospatial.Calculator) *TransportationService { return &TransportationService{ geoCalc: geoCalc, } } // Default transport profiles (simplified for Bugulma region) var transportProfiles = map[domain.TransportMode]domain.TransportProfile{ domain.TransportModeTruck: { CostPerKm: 0.12, // €0.12 per km for truck transport SpeedKmH: 60.0, // 60 km/h average speed MaxCapacity: 25.0, // 25 tons EnvironmentalFactor: 1.0, // Baseline }, domain.TransportModeRail: { CostPerKm: 0.08, // €0.08 per km (more efficient) SpeedKmH: 40.0, // 40 km/h average speed MaxCapacity: 200.0, // 200 tons (updated to better reflect rail capacity) EnvironmentalFactor: 0.7, // Better for environment }, domain.TransportModePipe: { CostPerKm: 0.05, // €0.05 per km (fixed infrastructure) SpeedKmH: 100.0, // 100 km/h (fluid transport) MaxCapacity: 1000.0, // 1000 tons (continuous flow) EnvironmentalFactor: 0.5, // Excellent for environment }, } // CalculateTransportCost calculates transportation cost between two points func (t *TransportationService) CalculateTransportCost( fromLat, fromLng, toLat, toLng float64, mode domain.TransportMode, volume float64, ) (*TransportCost, error) { // Calculate distances straightResult, err := t.geoCalc.CalculateDistance( geospatial.Point{Latitude: fromLat, Longitude: fromLng}, geospatial.Point{Latitude: toLat, Longitude: toLng}, ) if err != nil { return nil, err } // Estimate road distance (1.3x straight-line as approximation) roadDistance := straightResult.DistanceKm * 1.3 profile, exists := transportProfiles[mode] if !exists { return nil, ErrInvalidTransportMode } // Check capacity if volume > profile.MaxCapacity { return nil, ErrVolumeExceedsCapacity } // Calculate costs transportCost := roadDistance * profile.CostPerKm timeToDeliver := (roadDistance / profile.SpeedKmH) cost := &TransportCost{ TransportMode: mode, StraightDistanceKm: straightResult.DistanceKm, RoadDistanceKm: roadDistance, CostEur: transportCost, TimeHours: timeToDeliver, EnvironmentalFactor: profile.EnvironmentalFactor, CapacityUtilization: (volume / profile.MaxCapacity) * 100, } return cost, nil } // FindOptimalTransportRoutes finds the most cost-effective transportation routes func (t *TransportationService) FindOptimalTransportRoutes( fromLat, fromLng, toLat, toLng float64, volume float64, ) ([]*domain.TransportOption, error) { var options []*domain.TransportOption for mode, profile := range transportProfiles { // Check if the transport mode is feasible for this volume if volume > profile.MaxCapacity { continue } // Calculate distances result, err := t.geoCalc.CalculateDistance( geospatial.Point{Latitude: fromLat, Longitude: fromLng}, geospatial.Point{Latitude: toLat, Longitude: toLng}, ) if err != nil { continue } // Estimate road distance roadDistance := result.DistanceKm * 1.3 // Calculate costs transportCost := roadDistance * profile.CostPerKm timeToDeliver := (roadDistance / profile.SpeedKmH) // Environmental score (higher is better) environmentalScore := 10.0 / profile.EnvironmentalFactor option := &domain.TransportOption{ TransportMode: mode, DistanceKm: roadDistance, CostEur: transportCost, TimeHours: timeToDeliver, EnvironmentalScore: environmentalScore, CapacityUtilization: (volume / profile.MaxCapacity) * 100, } // Calculate overall efficiency score option.OverallScore = t.calculateTransportEfficiency(option) options = append(options, option) } // Sort by overall score (highest first) sort.Slice(options, func(i, j int) bool { return options[i].OverallScore > options[j].OverallScore }) return options, nil } // GetTransportProfile returns the profile for a transport mode func (t *TransportationService) GetTransportProfile(mode domain.TransportMode) (domain.TransportProfile, error) { profile, exists := transportProfiles[mode] if !exists { return domain.TransportProfile{}, fmt.Errorf("invalid transport mode: %s", mode) } return profile, nil } // calculateTransportEfficiency computes an overall efficiency score for transport options func (t *TransportationService) calculateTransportEfficiency(option *domain.TransportOption) float64 { // Multi-criteria scoring: cost, time, environment, capacity utilization costEfficiency := math.Max(0, 1.0-(option.CostEur/1000.0)) // Better under €1000 timeEfficiency := math.Max(0, 1.0-(option.TimeHours/24.0)) // Better under 24 hours envEfficiency := option.EnvironmentalScore / 10.0 // 0-1 scale capacityEfficiency := math.Min(option.CapacityUtilization/100.0, 1.0) // Optimal around 80-100% // Weighted average return (costEfficiency * 0.4) + (timeEfficiency * 0.3) + (envEfficiency * 0.2) + (capacityEfficiency * 0.1) } // TransportCost represents detailed transportation cost analysis type TransportCost struct { TransportMode domain.TransportMode `json:"transport_mode"` StraightDistanceKm float64 `json:"straight_distance_km"` RoadDistanceKm float64 `json:"road_distance_km"` CostEur float64 `json:"cost_eur"` TimeHours float64 `json:"time_hours"` EnvironmentalFactor float64 `json:"environmental_factor"` CapacityUtilization float64 `json:"capacity_utilization_percent"` } // Errors var ( ErrInvalidTransportMode = errors.New("invalid transport mode specified") ErrVolumeExceedsCapacity = errors.New("transport volume exceeds capacity") )