mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- Add comprehensive geographical data models (GeographicalFeature, TransportMode, TransportProfile, TransportOption) - Implement geographical feature repository with PostGIS support and spatial queries - Create transportation service for cost calculation and route optimization - Build spatial resource matcher for geographical resource matching - Develop environmental impact service for site environmental scoring - Implement facility location optimizer with multi-criteria analysis - Add geographical data migration service for SQLite to PostgreSQL migration - Create database migrations for geographical features and site footprints - Update geospatial service integration and server initialization - Add CLI command for geographical data synchronization - Implement complete test coverage for all geographical components (28 test cases) - Update test infrastructure for geographical table creation and PostGIS handling This implements advanced geospatial capabilities including transportation cost modeling, environmental impact assessment, and facility location optimization for the Turash platform.
211 lines
7.1 KiB
Go
211 lines
7.1 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
|
|
|
|
// Filter sites that can provide the resource
|
|
for _, site := range nearbySites {
|
|
if m.siteProvidesResource(site, resourceType) {
|
|
metrics, err := m.calculateSpatialMetrics(ctx, requesterLat, requesterLng, site, preferredTransport)
|
|
if err != nil {
|
|
continue // Skip sites where we can't calculate metrics
|
|
}
|
|
|
|
// Get resource flows for this site
|
|
allFlows, err := m.resourceFlowRepo.GetBySiteID(ctx, site.ID)
|
|
if err != nil {
|
|
continue // Skip if no flows found
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
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
|
|
func (m *SpatialResourceMatcher) siteProvidesResource(site *domain.Site, resourceType domain.ResourceType) bool {
|
|
// This is a simplified check - in practice, you'd check the site's resource flows
|
|
// For now, assume sites provide resources if they have any resource flows
|
|
return true // Placeholder - implement proper logic
|
|
}
|
|
|
|
// 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) {
|
|
|
|
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
|
|
}
|