turash/bugulma/backend/internal/service/transportation_service.go

186 lines
6.0 KiB
Go

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