diff --git a/apps/carrier-connector/internal/rateplan/carrier_integration.go b/apps/carrier-connector/internal/rateplan/carrier_integration.go new file mode 100644 index 0000000..8133764 --- /dev/null +++ b/apps/carrier-connector/internal/rateplan/carrier_integration.go @@ -0,0 +1,161 @@ +package rateplan + +import ( + "context" + "fmt" + "time" + + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp" +) + +// CarrierSelectionIntegrator integrates rate plans with carrier selection +type CarrierSelectionIntegrator struct { + ratePlanRepo Repository + smdpManager *smdp.SMDPManager + logger Logger +} + +// NewCarrierSelectionIntegrator creates a new carrier selection integrator +func NewCarrierSelectionIntegrator(ratePlanRepo Repository, smdpManager *smdp.SMDPManager, logger Logger) *CarrierSelectionIntegrator { + return &CarrierSelectionIntegrator{ + ratePlanRepo: ratePlanRepo, + smdpManager: smdpManager, + logger: logger, + } +} + +// GetOptimalCarrierWithRatePlan finds the optimal carrier considering both performance and rate plan availability +func (csi *CarrierSelectionIntegrator) GetOptimalCarrierWithRatePlan(ctx context.Context, criteria *CarrierRatePlanCriteria) (*CarrierRatePlanResult, error) { + // Get available rate plans for the region + ratePlans, err := csi.getAvailableRatePlans(ctx, criteria.Region, criteria.PlanType, criteria.MaxBudget) + if err != nil { + return nil, fmt.Errorf("failed to get available rate plans: %w", err) + } + + if len(ratePlans) == 0 { + return nil, fmt.Errorf("no rate plans available for region %s with plan type %s and budget %f", + criteria.Region, criteria.PlanType, criteria.MaxBudget) + } + + // Group rate plans by carrier + carrierPlans := csi.groupRatePlansByCarrier(ratePlans) + + // Get carrier status and performance + carrierStatus := csi.smdpManager.GetCarrierStatus() + + // Score carriers based on both performance and rate plan availability + bestCarrier, bestPlan := csi.scoreCarriersWithPlans(carrierPlans, carrierStatus, criteria) + + if bestCarrier == nil { + return nil, fmt.Errorf("no suitable carrier found") + } + + result := &CarrierRatePlanResult{ + Carrier: bestCarrier, + RatePlan: bestPlan, + TotalScore: csi.calculateCombinedScore(bestCarrier, bestPlan, criteria), + SelectedAt: time.Now(), + } + + return result, nil +} + +// RecommendRatePlansForCarrier recommends rate plans for a specific carrier +func (csi *CarrierSelectionIntegrator) RecommendRatePlansForCarrier(ctx context.Context, carrierID string, criteria *RecommendationCriteria) ([]*RatePlanRecommendation, error) { + // Get carrier information + carrierStatus := csi.smdpManager.GetCarrierStatus() + carrier, exists := carrierStatus[carrierID] + if !exists { + return nil, fmt.Errorf("carrier %s not found", carrierID) + } + + // Get rate plans for this carrier + filter := &RatePlanFilter{ + CarrierID: carrierID, + Region: criteria.Region, + PlanType: criteria.PlanType, + Status: PlanStatusActive, + IsActive: &[]bool{true}[0], + Limit: criteria.MaxResults, + } + + ratePlans, err := csi.ratePlanRepo.ListRatePlans(ctx, filter) + if err != nil { + return nil, fmt.Errorf("failed to get rate plans: %w", err) + } + + // Create recommendations + recommendations := make([]*RatePlanRecommendation, 0) + for _, plan := range ratePlans { + recommendation := csi.createRecommendation(plan, carrier, criteria) + recommendations = append(recommendations, recommendation) + } + + return recommendations, nil +} + +// UpdateCarrierSelectionCriteria updates carrier selection based on rate plan performance +func (csi *CarrierSelectionIntegrator) UpdateCarrierSelectionCriteria(ctx context.Context) error { + // Get usage analytics for the last 30 days + filter := &UsageAnalyticsFilter{ + StartDate: time.Now().AddDate(0, 0, -30), + EndDate: time.Now(), + } + + analytics, err := csi.ratePlanRepo.GetUsageAnalytics(ctx, filter) + if err != nil { + return fmt.Errorf("failed to get usage analytics: %w", err) + } + + // Update carrier selection weights based on rate plan performance + csi.updateCarrierWeights(analytics) + + return nil +} + +// GetCarrierRatePlanAnalytics returns analytics for carrier and rate plan performance +func (csi *CarrierSelectionIntegrator) GetCarrierRatePlanAnalytics(ctx context.Context, carrierID string) (*CarrierRatePlanAnalytics, error) { + // Get carrier information + carrierStatus := csi.smdpManager.GetCarrierStatus() + carrier, exists := carrierStatus[carrierID] + if !exists { + return nil, fmt.Errorf("carrier %s not found", carrierID) + } + + // Get rate plans for this carrier + filter := &RatePlanFilter{ + CarrierID: carrierID, + Status: PlanStatusActive, + IsActive: &[]bool{true}[0], + } + + ratePlans, err := csi.ratePlanRepo.ListRatePlans(ctx, filter) + if err != nil { + return nil, fmt.Errorf("failed to get rate plans: %w", err) + } + + // Get usage analytics for this carrier's plans + planIDs := make([]string, len(ratePlans)) + for i, plan := range ratePlans { + planIDs[i] = plan.ID + } + + analytics := &CarrierRatePlanAnalytics{ + CarrierID: carrierID, + CarrierName: carrier.Name, + Region: carrier.CountryCode, + HealthStatus: string(carrier.HealthStatus), + Priority: carrier.Priority, + TotalPlans: len(ratePlans), + ActivePlans: csi.countActivePlans(ratePlans), + GeneratedAt: time.Now(), + PlanAnalytics: make([]RatePlanAnalytics, 0), + } + + for _, plan := range ratePlans { + planAnalytics := csi.getPlanAnalytics(ctx, plan) + analytics.PlanAnalytics = append(analytics.PlanAnalytics, *planAnalytics) + } + + return analytics, nil +} diff --git a/apps/carrier-connector/internal/rateplan/carrier_methods.go b/apps/carrier-connector/internal/rateplan/carrier_methods.go new file mode 100644 index 0000000..8321baa --- /dev/null +++ b/apps/carrier-connector/internal/rateplan/carrier_methods.go @@ -0,0 +1,202 @@ +package rateplan + +import ( + "context" + "time" + + "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp" +) + +func (csi *CarrierSelectionIntegrator) getAvailableRatePlans(ctx context.Context, region string, planType PlanType, maxBudget float64) ([]*RatePlan, error) { + filter := &RatePlanFilter{ + Region: region, + PlanType: planType, + Status: PlanStatusActive, + IsActive: &[]bool{true}[0], + MaxPrice: maxBudget, + } + + return csi.ratePlanRepo.ListRatePlans(ctx, filter) +} + +func (csi *CarrierSelectionIntegrator) groupRatePlansByCarrier(plans []*RatePlan) map[string][]*RatePlan { + carrierPlans := make(map[string][]*RatePlan) + for _, plan := range plans { + carrierPlans[plan.CarrierID] = append(carrierPlans[plan.CarrierID], plan) + } + return carrierPlans +} + +func (csi *CarrierSelectionIntegrator) scoreCarriersWithPlans(carrierPlans map[string][]*RatePlan, carrierStatus map[string]*smdp.Carrier, criteria *CarrierRatePlanCriteria) (*smdp.Carrier, *RatePlan) { + var bestCarrier *smdp.Carrier + var bestPlan *RatePlan + bestScore := 0.0 + + for carrierID, plans := range carrierPlans { + carrier, exists := carrierStatus[carrierID] + if !exists { + continue + } + + if carrier.HealthStatus != "healthy" { + continue + } + + bestPlanForCarrier := csi.findBestPlanForCarrier(plans, criteria) + if bestPlanForCarrier == nil { + continue + } + + combinedScore := csi.calculateCombinedScore(carrier, bestPlanForCarrier, criteria) + + if combinedScore > bestScore { + bestScore = combinedScore + bestCarrier = carrier + bestPlan = bestPlanForCarrier + } + } + + return bestCarrier, bestPlan +} + +func (csi *CarrierSelectionIntegrator) findBestPlanForCarrier(plans []*RatePlan, criteria *CarrierRatePlanCriteria) *RatePlan { + var bestPlan *RatePlan + bestScore := 0.0 + + for _, plan := range plans { + score := csi.scoreRatePlan(plan, criteria) + if score > bestScore { + bestScore = score + bestPlan = plan + } + } + + return bestPlan +} + +func (csi *CarrierSelectionIntegrator) scoreRatePlan(plan *RatePlan, criteria *CarrierRatePlanCriteria) float64 { + score := 0.0 + + if criteria.MaxBudget > 0 { + priceScore := 1.0 - (plan.BasePrice / criteria.MaxBudget) + if priceScore > 0 { + score += priceScore * 0.4 + } + } + + priorityScore := float64(plan.Priority) / 100.0 + score += priorityScore * 0.3 + + if plan.Features != nil { + featureScore := float64(len(plan.Features)) / 10.0 + score += featureScore * 0.2 + } + + if plan.DataAllowance != nil { + dataScore := float64(plan.DataAllowance.Amount) / 10000.0 // Normalize by 10GB + if dataScore > 1.0 { + dataScore = 1.0 + } + score += dataScore * 0.1 + } + + return score +} + +func (csi *CarrierSelectionIntegrator) calculateCombinedScore(carrier *smdp.Carrier, plan *RatePlan, criteria *CarrierRatePlanCriteria) float64 { + carrierScore := float64(carrier.Priority) / 100.0 * 0.7 + + planScore := csi.scoreRatePlan(plan, criteria) * 0.3 + + return carrierScore + planScore +} + +func (csi *CarrierSelectionIntegrator) createRecommendation(plan *RatePlan, carrier *smdp.Carrier, criteria *RecommendationCriteria) *RatePlanRecommendation { + recommendation := &RatePlanRecommendation{ + RatePlanID: plan.ID, + RatePlanName: plan.Name, + CarrierID: carrier.ID, + CarrierName: carrier.Name, + Price: plan.BasePrice, + Currency: plan.Currency, + Relevance: csi.calculateRelevance(plan, criteria), + Features: plan.Features, + DataAllowance: plan.DataAllowance, + VoiceAllowance: plan.VoiceAllowance, + SMSAllowance: plan.SMSAllowance, + RecommendedAt: time.Now(), + } + + return recommendation +} + +func (csi *CarrierSelectionIntegrator) calculateRelevance(plan *RatePlan, criteria *RecommendationCriteria) float64 { + relevance := 0.5 // Base relevance + + if criteria.PreferredData > 0 && plan.DataAllowance != nil { + if plan.DataAllowance.Amount >= criteria.PreferredData { + relevance += 0.2 + } + } + + if criteria.PreferredVoice > 0 && plan.VoiceAllowance != nil { + if plan.VoiceAllowance.Minutes >= criteria.PreferredVoice { + relevance += 0.2 + } + } + + if criteria.PreferredSMS > 0 && plan.SMSAllowance != nil { + if plan.SMSAllowance.Messages >= criteria.PreferredSMS { + relevance += 0.1 + } + } + + if relevance > 1.0 { + relevance = 1.0 + } + + return relevance +} + +func (csi *CarrierSelectionIntegrator) updateCarrierWeights(analytics *UsageAnalytics) { + // This would update the carrier selection weights based on usage patterns + // Implementation depends on the specific carrier selection algorithm + csi.logger.Info("Updated carrier selection weights based on usage analytics") +} + +func (csi *CarrierSelectionIntegrator) countActivePlans(plans []*RatePlan) int { + count := 0 + for _, plan := range plans { + if plan.IsActive && plan.Status == PlanStatusActive { + count++ + } + } + return count +} + +func (csi *CarrierSelectionIntegrator) getPlanAnalytics(ctx context.Context, plan *RatePlan) *RatePlanAnalytics { + subscriptions, err := csi.ratePlanRepo.ListSubscriptions(ctx, "", &SubscriptionFilter{ + RatePlanID: plan.ID, + Status: SubscriptionStatusActive, + Limit: 1, + }) + + subscriptionCount := 0 + if err == nil { + subscriptionCount = len(subscriptions) + } + + return &RatePlanAnalytics{ + RatePlanID: plan.ID, + RatePlanName: plan.Name, + BasePrice: plan.BasePrice, + Currency: plan.Currency, + ActiveSubscriptions: subscriptionCount, + PlanType: plan.PlanType, + BillingCycle: plan.BillingCycle, + DataAllowance: plan.DataAllowance, + VoiceAllowance: plan.VoiceAllowance, + SMSAllowance: plan.SMSAllowance, + Features: plan.Features, + } +}