Files
telecom-platform/apps/carrier-connector/internal/smdp/selection_learning.go
T
nutcas3 699ac9f879 feat: Add intelligent carrier selection algorithm with machine learning optimization and performance tracking
- Add SelectionAlgorithm with scoring, weighting, and ML-based carrier selection
- Add SelectionCriteria struct with region, profile type, urgency, and cost/performance weights
- Add CarrierScore with performance, reliability, cost, region, and capability scores
- Add calculateWeightedScore combining all scores with ML-optimized weights
- Add getWeights with urgency-based adjustments
2026-05-01 23:28:10 +03:00

332 lines
9.8 KiB
Go

package smdp
import (
"math"
"time"
)
// LearningModel represents the machine learning model for carrier selection
type LearningModel struct {
// Performance tracking
carrierPerformance map[string]*PerformanceMetrics
learningRate float64
adaptationThreshold float64
// Weight optimization
baseWeights WeightVector
optimizedWeights WeightVector
// Historical data
performanceHistory map[string][]float64
selectionHistory map[string][]SelectionRecord
// Model parameters
momentum float64
regularization float64
decayRate float64
}
// PerformanceMetrics tracks carrier performance over time
type PerformanceMetrics struct {
SuccessRate float64
AverageResponseTime float64
Reliability float64
CostEfficiency float64
LastUpdated time.Time
SampleCount int
}
// WeightVector represents the selection weights
type WeightVector struct {
Performance float64
Reliability float64
Cost float64
Region float64
Capability float64
}
// SelectionRecord tracks individual selection outcomes
type SelectionRecord struct {
CarrierID string
SelectedAt time.Time
ExpectedScore float64
ActualScore float64
Performance float64
ResponseTime time.Duration
Success bool
SelectionReason string
}
// NewLearningModel creates a new machine learning model
func NewLearningModel() *LearningModel {
return &LearningModel{
carrierPerformance: make(map[string]*PerformanceMetrics),
performanceHistory: make(map[string][]float64),
selectionHistory: make(map[string][]SelectionRecord),
learningRate: 0.01,
adaptationThreshold: 0.05,
momentum: 0.9,
regularization: 0.001,
decayRate: 0.995,
baseWeights: WeightVector{
Performance: 0.3,
Reliability: 0.3,
Cost: 0.2,
Region: 0.1,
Capability: 0.1,
},
optimizedWeights: WeightVector{
Performance: 0.3,
Reliability: 0.3,
Cost: 0.2,
Region: 0.1,
Capability: 0.1,
},
}
}
// UpdateLearning updates the model with performance feedback using ML techniques
func (lm *LearningModel) UpdateLearning(carrierID string, actualPerformance float64, expectedScore float64) {
// Record the performance outcome
record := SelectionRecord{
CarrierID: carrierID,
SelectedAt: time.Now(),
ExpectedScore: expectedScore,
ActualScore: actualPerformance,
Performance: actualPerformance,
Success: actualPerformance >= 70.0, // 70% threshold for success
}
// Add to selection history
history := lm.selectionHistory[carrierID]
history = append(history, record)
// Keep only last 100 records per carrier
if len(history) > 100 {
history = history[1:]
}
lm.selectionHistory[carrierID] = history
// Update carrier performance metrics
lm.updatePerformanceMetrics(carrierID, record)
// Calculate performance prediction error
predictionError := actualPerformance - expectedScore
// If error is significant, trigger weight optimization
if math.Abs(predictionError) > lm.adaptationThreshold {
lm.optimizeWeights(carrierID, predictionError)
}
// Apply learning rate decay
lm.learningRate *= lm.decayRate
if lm.learningRate < 0.001 {
lm.learningRate = 0.001 // Minimum learning rate
}
}
// updatePerformanceMetrics updates the performance metrics for a carrier
func (lm *LearningModel) updatePerformanceMetrics(carrierID string, record SelectionRecord) {
metrics, exists := lm.carrierPerformance[carrierID]
if !exists {
metrics = &PerformanceMetrics{
LastUpdated: time.Now(),
}
lm.carrierPerformance[carrierID] = metrics
}
// Update metrics with exponential moving average
alpha := 0.1 // Smoothing factor
if metrics.SampleCount == 0 {
// First sample
metrics.SuccessRate = 1.0
if !record.Success {
metrics.SuccessRate = 0.0
}
metrics.AverageResponseTime = float64(record.ResponseTime.Milliseconds())
metrics.Reliability = record.Performance / 100.0
} else {
// Exponential moving average update
if record.Success {
metrics.SuccessRate = alpha*1.0 + (1-alpha)*metrics.SuccessRate
} else {
metrics.SuccessRate = alpha*0.0 + (1-alpha)*metrics.SuccessRate
}
responseTimeMs := float64(record.ResponseTime.Milliseconds())
metrics.AverageResponseTime = alpha*responseTimeMs + (1-alpha)*metrics.AverageResponseTime
metrics.Reliability = alpha*(record.Performance/100.0) + (1-alpha)*metrics.Reliability
}
metrics.CostEfficiency = 1.0 - (float64(record.ResponseTime.Milliseconds()) / 1000.0) // Simple cost proxy
metrics.LastUpdated = time.Now()
metrics.SampleCount++
}
// optimizeWeights uses gradient descent to optimize selection weights
func (lm *LearningModel) optimizeWeights(carrierID string, error float64) {
// Calculate gradients based on performance error
gradients := lm.calculateGradients(carrierID, error)
// Apply gradient descent with momentum
learningRate := lm.learningRate
// Update optimized weights
lm.optimizedWeights.Performance -= learningRate * gradients.Performance
lm.optimizedWeights.Reliability -= learningRate * gradients.Reliability
lm.optimizedWeights.Cost -= learningRate * gradients.Cost
lm.optimizedWeights.Region -= learningRate * gradients.Region
lm.optimizedWeights.Capability -= learningRate * gradients.Capability
// Apply regularization to prevent overfitting
lm.applyRegularization()
// Ensure weights sum to 1 and are non-negative
lm.normalizeWeights()
}
// calculateGradients calculates the gradient for each weight based on error
func (lm *LearningModel) calculateGradients(carrierID string, error float64) WeightVector {
metrics := lm.carrierPerformance[carrierID]
if metrics == nil {
return WeightVector{}
}
// Calculate gradients based on how each factor contributed to the error
gradients := WeightVector{}
// Performance gradient: higher error suggests need to adjust performance weight
gradients.Performance = error * (metrics.Reliability - 0.5) * 2.0
// Reliability gradient: adjust based on success rate
gradients.Reliability = error * (metrics.SuccessRate - 0.5) * 2.0
// Cost gradient: adjust based on cost efficiency
gradients.Cost = error * (metrics.CostEfficiency - 0.5) * 2.0
// Region and capability gradients (simplified)
gradients.Region = error * 0.1
gradients.Capability = error * 0.1
return gradients
}
// applyRegularization applies L2 regularization to prevent overfitting
func (lm *LearningModel) applyRegularization() {
regFactor := lm.regularization
lm.optimizedWeights.Performance *= (1.0 - regFactor)
lm.optimizedWeights.Reliability *= (1.0 - regFactor)
lm.optimizedWeights.Cost *= (1.0 - regFactor)
lm.optimizedWeights.Region *= (1.0 - regFactor)
lm.optimizedWeights.Capability *= (1.0 - regFactor)
}
// normalizeWeights ensures weights sum to 1 and are non-negative
func (lm *LearningModel) normalizeWeights() {
// Ensure non-negative
if lm.optimizedWeights.Performance < 0 {
lm.optimizedWeights.Performance = 0
}
if lm.optimizedWeights.Reliability < 0 {
lm.optimizedWeights.Reliability = 0
}
if lm.optimizedWeights.Cost < 0 {
lm.optimizedWeights.Cost = 0
}
if lm.optimizedWeights.Region < 0 {
lm.optimizedWeights.Region = 0
}
if lm.optimizedWeights.Capability < 0 {
lm.optimizedWeights.Capability = 0
}
// Normalize to sum to 1
total := lm.optimizedWeights.Performance + lm.optimizedWeights.Reliability +
lm.optimizedWeights.Cost + lm.optimizedWeights.Region + lm.optimizedWeights.Capability
if total > 0 {
lm.optimizedWeights.Performance /= total
lm.optimizedWeights.Reliability /= total
lm.optimizedWeights.Cost /= total
lm.optimizedWeights.Region /= total
lm.optimizedWeights.Capability /= total
} else {
// Fallback to base weights if all weights become zero
lm.optimizedWeights = lm.baseWeights
}
}
// GetOptimizedWeights returns the current optimized weights for selection
func (lm *LearningModel) GetOptimizedWeights() WeightVector {
return lm.optimizedWeights
}
// GetCarrierPerformance returns performance metrics for a carrier
func (lm *LearningModel) GetCarrierPerformance(carrierID string) *PerformanceMetrics {
return lm.carrierPerformance[carrierID]
}
// PredictPerformance predicts the expected performance for a carrier
func (lm *LearningModel) PredictPerformance(carrierID string, criteria *SelectionCriteria) float64 {
metrics := lm.carrierPerformance[carrierID]
if metrics == nil {
return 75.0 // Default prediction for new carriers
}
// Use weighted combination of performance metrics
weights := lm.optimizedWeights
prediction := metrics.SuccessRate*weights.Reliability*100 +
(1.0-metrics.AverageResponseTime/1000.0)*weights.Performance*100 +
metrics.CostEfficiency*weights.Cost*100 +
0.5*weights.Region*100 + // Simplified region prediction
0.5*weights.Capability*100 // Simplified capability prediction
return math.Min(100.0, math.Max(0.0, prediction))
}
// GetLearningStats returns statistics about the learning model
func (lm *LearningModel) GetLearningStats() map[string]any {
stats := make(map[string]any)
stats["learning_rate"] = lm.learningRate
stats["total_carriers_tracked"] = len(lm.carrierPerformance)
stats["optimized_weights"] = lm.optimizedWeights
stats["base_weights"] = lm.baseWeights
// Calculate overall performance improvement
totalImprovement := 0.0
sampleCount := 0
for _, history := range lm.selectionHistory {
if len(history) > 10 { // Only consider carriers with sufficient data
firstHalf := history[:len(history)/2]
secondHalf := history[len(history)/2:]
firstAvg := 0.0
for _, record := range firstHalf {
firstAvg += record.ActualScore
}
firstAvg /= float64(len(firstHalf))
secondAvg := 0.0
for _, record := range secondHalf {
secondAvg += record.ActualScore
}
secondAvg /= float64(len(secondHalf))
improvement := secondAvg - firstAvg
totalImprovement += improvement
sampleCount++
}
}
if sampleCount > 0 {
stats["average_performance_improvement"] = totalImprovement / float64(sampleCount)
}
return stats
}