Files
telecom-platform/apps/carrier-connector/internal/smdp/selection_test_ml.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

309 lines
9.2 KiB
Go

package smdp
import (
"context"
"testing"
)
// TestMachineLearningModel tests the ML learning capabilities
func TestMachineLearningModel(t *testing.T) {
mlModel := NewLearningModel()
// Test initial state
t.Run("InitialState", func(t *testing.T) {
weights := mlModel.GetOptimizedWeights()
// Should start with base weights
if weights.Performance < 0.29 || weights.Performance > 0.31 {
t.Errorf("Expected performance weight around 0.3, got %.3f", weights.Performance)
}
stats := mlModel.GetLearningStats()
if stats["learning_rate"].(float64) <= 0 {
t.Error("Learning rate should be positive")
}
})
// Test learning updates
t.Run("LearningUpdates", func(t *testing.T) {
carrierID := "test-ml-carrier"
// Simulate multiple selections with feedback
for i := range 10 {
expectedScore := float64(70 + i*2) // Improving expected scores
actualScore := float64(65 + i*3) // Actual performance
mlModel.UpdateLearning(carrierID, actualScore, expectedScore)
}
// Check that weights have been optimized
optimizedWeights := mlModel.GetOptimizedWeights()
originalWeights := WeightVector{Performance: 0.3, Reliability: 0.3, Cost: 0.2, Region: 0.1, Capability: 0.1}
// Weights should have changed from original
if optimizedWeights.Performance == originalWeights.Performance &&
optimizedWeights.Reliability == originalWeights.Reliability {
t.Error("Expected weights to change after learning")
}
// Weights should still sum to 1
total := optimizedWeights.Performance + optimizedWeights.Reliability +
optimizedWeights.Cost + optimizedWeights.Region + optimizedWeights.Capability
if total < 0.99 || total > 1.01 {
t.Errorf("Weights should sum to 1.0, got %.3f", total)
}
})
// Test performance metrics tracking
t.Run("PerformanceMetrics", func(t *testing.T) {
carrierID := "metrics-carrier"
// Add some performance data
mlModel.UpdateLearning(carrierID, 85.0, 80.0)
mlModel.UpdateLearning(carrierID, 90.0, 85.0)
mlModel.UpdateLearning(carrierID, 78.0, 82.0)
metrics := mlModel.GetCarrierPerformance(carrierID)
if metrics == nil {
t.Fatal("Expected performance metrics for carrier")
}
if metrics.SampleCount != 3 {
t.Errorf("Expected 3 samples, got %d", metrics.SampleCount)
}
if metrics.SuccessRate < 0 || metrics.SuccessRate > 1 {
t.Errorf("Success rate should be between 0 and 1, got %.3f", metrics.SuccessRate)
}
})
// Test performance prediction
t.Run("PerformancePrediction", func(t *testing.T) {
carrierID := "prediction-carrier"
criteria := &SelectionCriteria{
Region: "US",
ProfileType: "operational",
Urgency: "medium",
CostSensitivity: 0.5,
PerformanceWeight: 0.4,
ReliabilityWeight: 0.4,
}
// Add training data
for i := range 5 {
mlModel.UpdateLearning(carrierID, 80.0+float64(i*2), 75.0+float64(i))
}
prediction := mlModel.PredictPerformance(carrierID, criteria)
if prediction < 0 || prediction > 100 {
t.Errorf("Prediction should be between 0 and 100, got %.2f", prediction)
}
// Prediction for unknown carrier should return default
unknownPrediction := mlModel.PredictPerformance("unknown-carrier", criteria)
if unknownPrediction != 75.0 {
t.Errorf("Expected default prediction for unknown carrier, got %.2f", unknownPrediction)
}
})
}
// TestMLIntegration tests the integration of ML with SelectionAlgorithm
func TestMLIntegration(t *testing.T) {
selector := NewSelectionAlgorithm()
// Create test carriers
carriers := []*Carrier{
createTestCarrier("ml-test-1", "ML Test 1", "US", 80, 85, 150, 5.0),
createTestCarrier("ml-test-2", "ML Test 2", "US", 75, 90, 120, 4.0),
}
criteria := &SelectionCriteria{
Region: "US",
ProfileType: "operational",
Urgency: "medium",
CostSensitivity: 0.5,
PerformanceWeight: 0.4,
ReliabilityWeight: 0.4,
}
// Test initial selection
t.Run("InitialSelection", func(t *testing.T) {
score, err := selector.SelectOptimalCarrier(context.Background(), carriers, criteria)
if err != nil {
t.Fatalf("Selection failed: %v", err)
}
// Provide learning feedback
selector.UpdateLearning(score.CarrierID, 85.0)
// Check learning stats
stats := selector.GetLearningStats()
if stats["total_carriers_tracked"].(int) == 0 {
t.Error("Expected carriers to be tracked in ML model")
}
})
// Test learning over multiple iterations
t.Run("IterativeLearning", func(t *testing.T) {
weightsBefore := selector.mlModel.GetOptimizedWeights()
// Perform multiple selections with feedback
for i := range 5 {
score, err := selector.SelectOptimalCarrier(context.Background(), carriers, criteria)
if err != nil {
t.Fatalf("Selection %d failed: %v", i, err)
}
// Simulate performance feedback (varying performance)
actualPerformance := 70.0 + float64(i*3)
selector.UpdateLearning(score.CarrierID, actualPerformance)
}
weightsAfter := selector.mlModel.GetOptimizedWeights()
// Weights should have been optimized
if weightsAfter.Performance == weightsBefore.Performance &&
weightsAfter.Reliability == weightsBefore.Reliability {
t.Error("Expected weights to change after learning iterations")
}
// Check performance prediction
prediction := selector.PredictPerformance("ml-test-1", criteria)
if prediction < 0 || prediction > 100 {
t.Errorf("Prediction should be between 0 and 100, got %.2f", prediction)
}
})
// Test carrier performance metrics
t.Run("CarrierPerformanceMetrics", func(t *testing.T) {
// Get performance metrics for a carrier
metrics := selector.GetCarrierPerformance("ml-test-1")
if metrics == nil {
t.Error("Expected performance metrics for carrier")
}
if metrics.SampleCount == 0 {
t.Error("Expected sample count > 0 after learning")
}
})
}
// TestMLWeightOptimization tests the weight optimization logic
func TestMLWeightOptimization(t *testing.T) {
mlModel := NewLearningModel()
t.Run("GradientDescent", func(t *testing.T) {
carrierID := "gradient-test"
// Simulate consistent poor performance to trigger weight optimization
for range 10 {
expectedScore := 80.0
actualScore := 50.0 // Consistently poor performance
mlModel.UpdateLearning(carrierID, actualScore, expectedScore)
}
weights := mlModel.GetOptimizedWeights()
// After consistent poor performance, weights should be adjusted
// This is a simplified test - in practice, the optimization is more complex
total := weights.Performance + weights.Reliability + weights.Cost + weights.Region + weights.Capability
if total < 0.99 || total > 1.01 {
t.Errorf("Weights should still sum to 1.0 after optimization, got %.3f", total)
}
// All weights should be non-negative
if weights.Performance < 0 || weights.Reliability < 0 || weights.Cost < 0 ||
weights.Region < 0 || weights.Capability < 0 {
t.Error("All weights should be non-negative")
}
})
t.Run("Regularization", func(t *testing.T) {
// Test that regularization prevents extreme weight values
carrierID := "regularization-test"
// Simulate extreme performance variations
for i := range 20 {
expectedScore := 80.0
actualScore := 20.0 + float64(i*3) // Wide range of performance
mlModel.UpdateLearning(carrierID, actualScore, expectedScore)
}
weights := mlModel.GetOptimizedWeights()
// Weights should be reasonable (not too extreme)
for _, weight := range []float64{
weights.Performance, weights.Reliability, weights.Cost,
weights.Region, weights.Capability} {
if weight < 0.05 || weight > 0.7 {
t.Errorf("Weight %.3f seems too extreme (should be between 0.05 and 0.7)", weight)
}
}
})
}
// TestMLLearningRate tests the learning rate adaptation
func TestMLLearningRate(t *testing.T) {
mlModel := NewLearningModel()
initialLearningRate := mlModel.GetLearningStats()["learning_rate"].(float64)
// Add some learning data
for range 10 {
mlModel.UpdateLearning("rate-test", 75.0, 70.0)
}
finalLearningRate := mlModel.GetLearningStats()["learning_rate"].(float64)
// Learning rate should have decayed
if finalLearningRate >= initialLearningRate {
t.Error("Learning rate should decay over time")
}
// Learning rate should not go below minimum
if finalLearningRate < 0.001 {
t.Error("Learning rate should not go below minimum threshold")
}
}
// BenchmarkMLModel benchmarks the ML model performance
func BenchmarkMLModel(b *testing.B) {
mlModel := NewLearningModel()
carrierID := "benchmark-carrier"
b.ResetTimer()
for i := 0; i < b.N; i++ {
mlModel.UpdateLearning(carrierID, 75.0, 70.0)
}
}
// BenchmarkSelectionWithML benchmarks selection with ML integration
func BenchmarkSelectionWithML(b *testing.B) {
selector := NewSelectionAlgorithm()
carriers := []*Carrier{
createTestCarrier("benchmark-1", "Benchmark 1", "US", 80, 85, 150, 5.0),
createTestCarrier("benchmark-2", "Benchmark 2", "US", 75, 90, 120, 4.0),
}
criteria := &SelectionCriteria{
Region: "US",
ProfileType: "operational",
Urgency: "medium",
CostSensitivity: 0.5,
PerformanceWeight: 0.4,
ReliabilityWeight: 0.4,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := selector.SelectOptimalCarrier(context.Background(), carriers, criteria)
if err != nil {
b.Fatalf("Selection failed: %v", err)
}
selector.UpdateLearning("benchmark-1", 75.0)
}
}