mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-24 21:52:16 +00:00
- 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
332 lines
9.8 KiB
Go
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
|
|
}
|