mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
234 lines
7.6 KiB
Go
234 lines
7.6 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
|
|
"bugulma/backend/internal/domain"
|
|
"bugulma/backend/internal/geospatial"
|
|
)
|
|
|
|
// SpatialResourceMatcher enhances resource matching with geographical intelligence
|
|
type SpatialResourceMatcher struct {
|
|
geoRepo domain.GeographicalFeatureRepository
|
|
siteRepo domain.SiteRepository
|
|
resourceFlowRepo domain.ResourceFlowRepository
|
|
geospatialSvc *GeospatialService
|
|
transportSvc *TransportationService
|
|
geoCalc geospatial.Calculator
|
|
}
|
|
|
|
// NewSpatialResourceMatcher creates a new spatial resource matcher
|
|
func NewSpatialResourceMatcher(
|
|
geoRepo domain.GeographicalFeatureRepository,
|
|
siteRepo domain.SiteRepository,
|
|
resourceFlowRepo domain.ResourceFlowRepository,
|
|
geospatialSvc *GeospatialService,
|
|
transportSvc *TransportationService,
|
|
geoCalc geospatial.Calculator,
|
|
) *SpatialResourceMatcher {
|
|
return &SpatialResourceMatcher{
|
|
geoRepo: geoRepo,
|
|
siteRepo: siteRepo,
|
|
resourceFlowRepo: resourceFlowRepo,
|
|
geospatialSvc: geospatialSvc,
|
|
transportSvc: transportSvc,
|
|
geoCalc: geoCalc,
|
|
}
|
|
}
|
|
|
|
// SpatialMatchResult represents a resource match with spatial metadata
|
|
type SpatialMatchResult struct {
|
|
ResourceFlow *domain.ResourceFlow `json:"resource_flow"`
|
|
ProviderSite *domain.Site `json:"provider_site"`
|
|
RequesterSite *domain.Site `json:"requester_site"`
|
|
SpatialMetrics *SpatialMetrics `json:"spatial_metrics"`
|
|
MatchScore float64 `json:"match_score"`
|
|
}
|
|
|
|
// SpatialMetrics contains geographical analysis for a match
|
|
type SpatialMetrics struct {
|
|
StraightLineDistance float64 `json:"straight_line_distance_km"`
|
|
RoadDistance float64 `json:"road_distance_km,omitempty"`
|
|
TransportCost float64 `json:"transport_cost_eur_month"`
|
|
EnvironmentalScore float64 `json:"environmental_score"`
|
|
InfrastructureScore float64 `json:"infrastructure_score"`
|
|
TimeToDeliver float64 `json:"time_to_deliver_hours,omitempty"`
|
|
}
|
|
|
|
// FindNearbyResourceProviders finds resource providers within geographical constraints
|
|
func (m *SpatialResourceMatcher) FindNearbyResourceProviders(
|
|
ctx context.Context,
|
|
resourceType domain.ResourceType,
|
|
requesterLat, requesterLng float64,
|
|
maxDistanceKm float64,
|
|
preferredTransport domain.TransportMode,
|
|
) ([]*SpatialMatchResult, error) {
|
|
|
|
// Find sites within radius that offer the requested resource
|
|
nearbySites, err := m.siteRepo.GetWithinRadius(ctx, requesterLat, requesterLng, maxDistanceKm)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find nearby sites: %w", err)
|
|
}
|
|
|
|
var results []*SpatialMatchResult
|
|
|
|
// Process each site and check for matching resource flows
|
|
for _, site := range nearbySites {
|
|
if site == nil {
|
|
continue
|
|
}
|
|
// Get resource flows for this site
|
|
allFlows, err := m.resourceFlowRepo.GetBySiteID(ctx, site.ID)
|
|
if err != nil {
|
|
continue // Skip if no flows found or error
|
|
}
|
|
|
|
// Filter for output flows of the requested resource type
|
|
var flows []*domain.ResourceFlow
|
|
for _, flow := range allFlows {
|
|
if flow.Direction == domain.DirectionOutput && flow.Type == resourceType {
|
|
flows = append(flows, flow)
|
|
}
|
|
}
|
|
|
|
// Skip sites that don't have matching flows
|
|
if len(flows) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Calculate spatial metrics once per site (reused for all flows)
|
|
metrics, err := m.calculateSpatialMetrics(ctx, requesterLat, requesterLng, site, preferredTransport)
|
|
if err != nil {
|
|
continue // Skip sites where we can't calculate metrics
|
|
}
|
|
|
|
// Create match results for each matching flow
|
|
for _, flow := range flows {
|
|
matchScore := m.calculateMatchScore(metrics, flow)
|
|
result := &SpatialMatchResult{
|
|
ResourceFlow: flow,
|
|
ProviderSite: site,
|
|
SpatialMetrics: metrics,
|
|
MatchScore: matchScore,
|
|
}
|
|
results = append(results, result)
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// siteProvidesResource checks if a site provides a specific resource type
|
|
// This method is deprecated - resource flow checking is now done directly in FindNearbyResourceProviders
|
|
// for better efficiency. Kept for backward compatibility if needed elsewhere.
|
|
func (m *SpatialResourceMatcher) siteProvidesResource(ctx context.Context, site *domain.Site, resourceType domain.ResourceType) (bool, error) {
|
|
// Check if site has output flows of the requested resource type
|
|
flows, err := m.resourceFlowRepo.GetBySiteID(ctx, site.ID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, flow := range flows {
|
|
if flow.Direction == domain.DirectionOutput && flow.Type == resourceType {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// calculateSpatialMetrics calculates spatial metrics between requester and provider
|
|
func (m *SpatialResourceMatcher) calculateSpatialMetrics(
|
|
ctx context.Context,
|
|
fromLat, fromLng float64,
|
|
toSite *domain.Site,
|
|
preferredTransport domain.TransportMode,
|
|
) (*SpatialMetrics, error) {
|
|
if toSite == nil {
|
|
return nil, fmt.Errorf("toSite cannot be nil")
|
|
}
|
|
|
|
metrics := &SpatialMetrics{}
|
|
|
|
// Calculate straight-line distance
|
|
result, err := m.geoCalc.CalculateDistance(
|
|
geospatial.Point{Latitude: fromLat, Longitude: fromLng},
|
|
geospatial.Point{Latitude: toSite.Latitude, Longitude: toSite.Longitude},
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to calculate distance: %w", err)
|
|
}
|
|
|
|
metrics.StraightLineDistance = result.DistanceKm
|
|
|
|
// Estimate road distance (simplified approximation)
|
|
metrics.RoadDistance = metrics.StraightLineDistance * 1.3 // 30% longer due to roads
|
|
|
|
// Calculate transportation cost using dedicated service
|
|
transportCost, err := m.transportSvc.CalculateTransportCost(
|
|
fromLat, fromLng, toSite.Latitude, toSite.Longitude,
|
|
preferredTransport, 10.0, // Assume 10 tons for cost calculation
|
|
)
|
|
if err != nil {
|
|
// Use fallback calculation if transport service fails
|
|
metrics.TransportCost = metrics.RoadDistance * 0.1 // €0.10 per km fallback
|
|
metrics.TimeToDeliver = metrics.RoadDistance / 50.0 // 50 km/h fallback
|
|
} else {
|
|
metrics.TransportCost = transportCost.CostEur
|
|
metrics.TimeToDeliver = transportCost.TimeHours
|
|
}
|
|
|
|
// Environmental score for the destination
|
|
envScore, err := m.geospatialSvc.CalculateSiteEnvironmentalScore(ctx, toSite.Latitude, toSite.Longitude)
|
|
if err != nil {
|
|
metrics.EnvironmentalScore = 5.0 // Default neutral score
|
|
} else {
|
|
metrics.EnvironmentalScore = envScore
|
|
}
|
|
|
|
// Infrastructure score (simplified)
|
|
metrics.InfrastructureScore = m.calculateInfrastructureScore(toSite)
|
|
|
|
return metrics, nil
|
|
}
|
|
|
|
// calculateMatchScore calculates an overall match score
|
|
func (m *SpatialResourceMatcher) calculateMatchScore(metrics *SpatialMetrics, flow *domain.ResourceFlow) float64 {
|
|
// Multi-criteria scoring
|
|
distanceScore := math.Max(0, 10.0-(metrics.StraightLineDistance/10.0)) // Better closer, max 10km
|
|
costScore := math.Max(0, 10.0-(metrics.TransportCost/100.0)) // Better cheaper, max €100
|
|
envScore := metrics.EnvironmentalScore // 0-10 scale
|
|
infraScore := metrics.InfrastructureScore // 0-10 scale
|
|
|
|
// Weighted average
|
|
return (distanceScore*0.3 + costScore*0.3 + envScore*0.2 + infraScore*0.2)
|
|
}
|
|
|
|
// calculateInfrastructureScore calculates infrastructure quality score
|
|
func (m *SpatialResourceMatcher) calculateInfrastructureScore(site *domain.Site) float64 {
|
|
score := 5.0 // Base score
|
|
|
|
// Check available utilities
|
|
if len(site.AvailableUtilities) > 0 {
|
|
var utilities []string
|
|
if err := json.Unmarshal(site.AvailableUtilities, &utilities); err == nil {
|
|
score += float64(len(utilities)) * 0.5 // +0.5 per utility
|
|
}
|
|
}
|
|
|
|
// Check parking spaces
|
|
if site.ParkingSpaces > 0 {
|
|
score += 1.0
|
|
}
|
|
|
|
// Check loading docks
|
|
if site.LoadingDocks > 0 {
|
|
score += 1.0
|
|
}
|
|
|
|
return math.Min(10.0, score) // Cap at 10
|
|
}
|