mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 11:02:54 +00:00
feat: Add carrier selection handler with optimal selection, analytics, and performance monitoring endpoints
- Add SelectionHandler struct with manager and logger fields - Add NewSelectionHandler constructor with info-level logging - Add SelectOptimalCarrier endpoint with criteria validation and scoring - Add SelectCarrier endpoint using default selection criteria - Add GetSelectionHistory endpoint with carrier ID path parameter - Add UpdateLearning endpoint with performance feedback validation - Add GetSelectionAnalytics endpoint with carrier stats
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SelectionHandler handles carrier selection API endpoints
|
||||
type SelectionHandler struct {
|
||||
manager *smdp.SMDPManager
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
// NewSelectionHandler creates a new selection handler
|
||||
func NewSelectionHandler(manager *smdp.SMDPManager) *SelectionHandler {
|
||||
logger := logrus.New()
|
||||
logger.SetLevel(logrus.InfoLevel)
|
||||
|
||||
return &SelectionHandler{
|
||||
manager: manager,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes registers selection-related routes
|
||||
func (h *SelectionHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
// Carrier selection endpoints
|
||||
mux.HandleFunc("/api/v1/selection/optimal", h.SelectOptimalCarrier)
|
||||
mux.HandleFunc("/api/v1/selection/carrier", h.SelectCarrier)
|
||||
mux.HandleFunc("/api/v1/selection/history/", h.GetSelectionHistory)
|
||||
mux.HandleFunc("/api/v1/selection/learning", h.UpdateLearning)
|
||||
|
||||
// Analytics endpoints
|
||||
mux.HandleFunc("/api/v1/selection/analytics/selection", h.GetSelectionAnalytics)
|
||||
mux.HandleFunc("/api/v1/selection/analytics/performance", h.GetPerformanceAnalytics)
|
||||
}
|
||||
|
||||
// SelectOptimalCarrierRequest represents the request for optimal carrier selection
|
||||
type SelectOptimalCarrierRequest struct {
|
||||
Region string `json:"region"`
|
||||
ProfileType string `json:"profile_type"`
|
||||
Urgency string `json:"urgency"`
|
||||
CostSensitivity float64 `json:"cost_sensitivity"`
|
||||
PerformanceWeight float64 `json:"performance_weight"`
|
||||
ReliabilityWeight float64 `json:"reliability_weight"`
|
||||
}
|
||||
|
||||
// SelectOptimalCarrierResponse represents the response for optimal carrier selection
|
||||
type SelectOptimalCarrierResponse struct {
|
||||
Success bool `json:"success"`
|
||||
CarrierScore *smdp.CarrierScore `json:"carrier_score,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SelectOptimalCarrier selects the optimal carrier based on criteria
|
||||
func (h *SelectionHandler) SelectOptimalCarrier(w http.ResponseWriter, r *http.Request) {
|
||||
var req SelectOptimalCarrierRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.writeErrorResponse(w, http.StatusBadRequest, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate request
|
||||
if req.Urgency == "" {
|
||||
req.Urgency = "medium"
|
||||
}
|
||||
if req.CostSensitivity < 0 || req.CostSensitivity > 1 {
|
||||
req.CostSensitivity = 0.5
|
||||
}
|
||||
if req.PerformanceWeight < 0 || req.PerformanceWeight > 1 {
|
||||
req.PerformanceWeight = 0.4
|
||||
}
|
||||
if req.ReliabilityWeight < 0 || req.ReliabilityWeight > 1 {
|
||||
req.ReliabilityWeight = 0.4
|
||||
}
|
||||
|
||||
criteria := &smdp.SelectionCriteria{
|
||||
Region: req.Region,
|
||||
ProfileType: req.ProfileType,
|
||||
Urgency: req.Urgency,
|
||||
CostSensitivity: req.CostSensitivity,
|
||||
PerformanceWeight: req.PerformanceWeight,
|
||||
ReliabilityWeight: req.ReliabilityWeight,
|
||||
}
|
||||
|
||||
score, err := h.manager.SelectOptimalCarrier(r.Context(), criteria)
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to select optimal carrier")
|
||||
h.writeErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := SelectOptimalCarrierResponse{
|
||||
Success: true,
|
||||
CarrierScore: score,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// SelectCarrierResponse represents the response for carrier selection
|
||||
type SelectCarrierResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Carrier *smdp.Carrier `json:"carrier,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SelectCarrier selects a carrier using default criteria
|
||||
func (h *SelectionHandler) SelectCarrier(w http.ResponseWriter, r *http.Request) {
|
||||
carrier, err := h.manager.SelectCarrier(r.Context())
|
||||
if err != nil {
|
||||
h.logger.WithError(err).Error("Failed to select carrier")
|
||||
h.writeErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := SelectCarrierResponse{
|
||||
Success: true,
|
||||
Carrier: carrier,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// SelectionHistoryResponse represents the response for selection history
|
||||
type SelectionHistoryResponse struct {
|
||||
Success bool `json:"success"`
|
||||
History []smdp.CarrierScore `json:"history,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// GetSelectionHistory returns the selection history for a carrier
|
||||
func (h *SelectionHandler) GetSelectionHistory(w http.ResponseWriter, r *http.Request) {
|
||||
// Extract carrier ID from URL path
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/v1/selection/history/")
|
||||
carrierID := strings.TrimSuffix(path, "/")
|
||||
|
||||
if carrierID == "" {
|
||||
h.writeErrorResponse(w, http.StatusBadRequest, "Carrier ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
history := h.manager.GetSelectionHistory(carrierID)
|
||||
|
||||
response := SelectionHistoryResponse{
|
||||
Success: true,
|
||||
History: history,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// UpdateLearningRequest represents the request for updating learning
|
||||
type UpdateLearningRequest struct {
|
||||
CarrierID string `json:"carrier_id"`
|
||||
ActualPerformance float64 `json:"actual_performance"`
|
||||
}
|
||||
|
||||
// UpdateLearningResponse represents the response for updating learning
|
||||
type UpdateLearningResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateLearning updates the selection algorithm with performance feedback
|
||||
func (h *SelectionHandler) UpdateLearning(w http.ResponseWriter, r *http.Request) {
|
||||
var req UpdateLearningRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.writeErrorResponse(w, http.StatusBadRequest, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate request
|
||||
if req.CarrierID == "" {
|
||||
h.writeErrorResponse(w, http.StatusBadRequest, "Carrier ID is required")
|
||||
return
|
||||
}
|
||||
if req.ActualPerformance < 0 || req.ActualPerformance > 100 {
|
||||
h.writeErrorResponse(w, http.StatusBadRequest, "Actual performance must be between 0 and 100")
|
||||
return
|
||||
}
|
||||
|
||||
h.manager.UpdateLearning(req.CarrierID, req.ActualPerformance)
|
||||
|
||||
response := UpdateLearningResponse{
|
||||
Success: true,
|
||||
Message: "Learning updated successfully",
|
||||
}
|
||||
|
||||
h.writeJSONResponse(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// SelectionAnalyticsResponse represents the response for selection analytics
|
||||
type SelectionAnalyticsResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Analytics *SelectionAnalytics `json:"analytics,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SelectionAnalytics represents carrier selection analytics
|
||||
type SelectionAnalytics struct {
|
||||
TotalSelections int `json:"total_selections"`
|
||||
CarrierStats map[string]*CarrierStat `json:"carrier_stats"`
|
||||
AveragePerformance float64 `json:"average_performance"`
|
||||
TopPerformers []string `json:"top_performers"`
|
||||
SelectionTrends map[string]int `json:"selection_trends"`
|
||||
}
|
||||
|
||||
// CarrierStat represents statistics for a carrier
|
||||
type CarrierStat struct {
|
||||
CarrierID string `json:"carrier_id"`
|
||||
SelectionCount int `json:"selection_count"`
|
||||
AverageScore float64 `json:"average_score"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
LastSelected string `json:"last_selected"`
|
||||
}
|
||||
|
||||
// GetSelectionAnalytics returns selection analytics
|
||||
func (h *SelectionHandler) GetSelectionAnalytics(w http.ResponseWriter, r *http.Request) {
|
||||
// Get carrier status
|
||||
carriers := h.manager.GetCarrierStatus()
|
||||
|
||||
analytics := &SelectionAnalytics{
|
||||
TotalSelections: 0,
|
||||
CarrierStats: make(map[string]*CarrierStat),
|
||||
AveragePerformance: 0,
|
||||
TopPerformers: []string{},
|
||||
SelectionTrends: make(map[string]int),
|
||||
}
|
||||
|
||||
totalScore := 0.0
|
||||
totalSelections := 0
|
||||
|
||||
for carrierID, carrier := range carriers {
|
||||
history := h.manager.GetSelectionHistory(carrierID)
|
||||
|
||||
stat := &CarrierStat{
|
||||
CarrierID: carrierID,
|
||||
SelectionCount: len(history),
|
||||
AverageScore: 0,
|
||||
SuccessRate: 0,
|
||||
LastSelected: "",
|
||||
}
|
||||
|
||||
if len(history) > 0 {
|
||||
var scoreSum float64
|
||||
for _, score := range history {
|
||||
scoreSum += score.TotalScore
|
||||
}
|
||||
stat.AverageScore = scoreSum / float64(len(history))
|
||||
stat.LastSelected = history[len(history)-1].SelectedAt.Format("2006-01-02 15:04:05")
|
||||
|
||||
// Calculate success rate from carrier metrics
|
||||
if carrier.Metrics.TotalRequests > 0 {
|
||||
stat.SuccessRate = float64(carrier.Metrics.SuccessfulRequests) / float64(carrier.Metrics.TotalRequests) * 100
|
||||
}
|
||||
}
|
||||
|
||||
analytics.CarrierStats[carrierID] = stat
|
||||
totalScore += stat.AverageScore
|
||||
totalSelections += stat.SelectionCount
|
||||
}
|
||||
|
||||
analytics.TotalSelections = totalSelections
|
||||
if len(carriers) > 0 {
|
||||
analytics.AveragePerformance = totalScore / float64(len(carriers))
|
||||
}
|
||||
|
||||
response := SelectionAnalyticsResponse{
|
||||
Success: true,
|
||||
Analytics: analytics,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// PerformanceAnalyticsResponse represents the response for performance analytics
|
||||
type PerformanceAnalyticsResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Analytics *PerformanceAnalytics `json:"analytics,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// PerformanceAnalytics represents performance analytics
|
||||
type PerformanceAnalytics struct {
|
||||
CarrierPerformance map[string]*PerformanceMetrics `json:"carrier_performance"`
|
||||
SystemHealth *SystemHealth `json:"system_health"`
|
||||
Recommendations []string `json:"recommendations"`
|
||||
}
|
||||
|
||||
// PerformanceMetrics represents performance metrics for a carrier
|
||||
type PerformanceMetrics struct {
|
||||
CarrierID string `json:"carrier_id"`
|
||||
ResponseTime float64 `json:"response_time_ms"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
RequestRate float64 `json:"requests_per_second"`
|
||||
HealthScore float64 `json:"health_score"`
|
||||
Recommendation string `json:"recommendation"`
|
||||
}
|
||||
|
||||
// SystemHealth represents overall system health
|
||||
type SystemHealth struct {
|
||||
HealthyCarriers int `json:"healthy_carriers"`
|
||||
DegradedCarriers int `json:"degraded_carriers"`
|
||||
UnhealthyCarriers int `json:"unhealthy_carriers"`
|
||||
OverallHealth float64 `json:"overall_health"`
|
||||
}
|
||||
|
||||
// GetPerformanceAnalytics returns performance analytics
|
||||
func (h *SelectionHandler) GetPerformanceAnalytics(w http.ResponseWriter, r *http.Request) {
|
||||
carriers := h.manager.GetCarrierStatus()
|
||||
|
||||
analytics := &PerformanceAnalytics{
|
||||
CarrierPerformance: make(map[string]*PerformanceMetrics),
|
||||
SystemHealth: &SystemHealth{
|
||||
HealthyCarriers: 0,
|
||||
DegradedCarriers: 0,
|
||||
UnhealthyCarriers: 0,
|
||||
OverallHealth: 0,
|
||||
},
|
||||
Recommendations: []string{},
|
||||
}
|
||||
|
||||
for carrierID, carrier := range carriers {
|
||||
metrics := &PerformanceMetrics{
|
||||
CarrierID: carrierID,
|
||||
ResponseTime: float64(carrier.Metrics.AverageResponseTime.Milliseconds()),
|
||||
SuccessRate: 0,
|
||||
RequestRate: carrier.Metrics.RequestRate,
|
||||
HealthScore: 0,
|
||||
Recommendation: "",
|
||||
}
|
||||
|
||||
// Calculate success rate
|
||||
if carrier.Metrics.TotalRequests > 0 {
|
||||
metrics.SuccessRate = float64(carrier.Metrics.SuccessfulRequests) / float64(carrier.Metrics.TotalRequests) * 100
|
||||
}
|
||||
|
||||
// Calculate health score
|
||||
switch carrier.HealthStatus {
|
||||
case "healthy":
|
||||
analytics.SystemHealth.HealthyCarriers++
|
||||
metrics.HealthScore = 100
|
||||
case "degraded":
|
||||
analytics.SystemHealth.DegradedCarriers++
|
||||
metrics.HealthScore = 50
|
||||
default:
|
||||
analytics.SystemHealth.UnhealthyCarriers++
|
||||
metrics.HealthScore = 0
|
||||
}
|
||||
|
||||
// Generate recommendations
|
||||
if metrics.SuccessRate < 95 {
|
||||
metrics.Recommendation = "Monitor success rate - below optimal threshold"
|
||||
} else if metrics.ResponseTime > 500 {
|
||||
metrics.Recommendation = "Consider optimizing response time"
|
||||
} else if metrics.HealthScore < 50 {
|
||||
metrics.Recommendation = "Carrier health degraded - investigate issues"
|
||||
} else {
|
||||
metrics.Recommendation = "Carrier performing well"
|
||||
}
|
||||
|
||||
analytics.CarrierPerformance[carrierID] = metrics
|
||||
}
|
||||
|
||||
// Calculate overall health
|
||||
totalCarriers := len(carriers)
|
||||
if totalCarriers > 0 {
|
||||
analytics.SystemHealth.OverallHealth = float64(analytics.SystemHealth.HealthyCarriers) / float64(totalCarriers) * 100
|
||||
}
|
||||
|
||||
// Generate system recommendations
|
||||
if analytics.SystemHealth.OverallHealth < 80 {
|
||||
analytics.Recommendations = append(analytics.Recommendations, "System health below optimal - investigate carrier issues")
|
||||
}
|
||||
if analytics.SystemHealth.UnhealthyCarriers > 0 {
|
||||
analytics.Recommendations = append(analytics.Recommendations, "Some carriers are unhealthy - consider failover")
|
||||
}
|
||||
|
||||
response := PerformanceAnalyticsResponse{
|
||||
Success: true,
|
||||
Analytics: analytics,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
func (h *SelectionHandler) writeJSONResponse(w http.ResponseWriter, statusCode int, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
func (h *SelectionHandler) writeErrorResponse(w http.ResponseWriter, statusCode int, message string) {
|
||||
response := struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error"`
|
||||
}{
|
||||
Success: false,
|
||||
Error: message,
|
||||
}
|
||||
|
||||
h.writeJSONResponse(w, statusCode, response)
|
||||
}
|
||||
Reference in New Issue
Block a user