From ef374ce1228e7b747565ea63622f2cf75922649a Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sun, 3 May 2026 13:06:57 +0300 Subject: [PATCH] feat: Refactor pricing optimization service by extracting helper functions to separate file - Move calculateElasticity, calculateCompetitiveIndex, and calculateOptimizationROI to optimization_opt_service.go - Move generateAnalysis, calculateConfidence, and calculateChurnRate to optimization_opt_service.go - Simplify calculatePriceElasticity by removing comments and consolidating logic - Simplify optimizeForRevenue, optimizeForMarketShare, optimizeForProfitMargin, optimizeForCompetitive, and optimizeForChurn --- .../pricing/optimization_opt_service.go | 199 +++++++++++ .../pricing/optimization_optimize_service.go | 331 ++---------------- .../pricing/optimization_period_service.go | 27 ++ .../internal/pricing/optimization_service.go | 286 +-------------- 4 files changed, 271 insertions(+), 572 deletions(-) create mode 100644 apps/carrier-connector/internal/pricing/optimization_opt_service.go create mode 100644 apps/carrier-connector/internal/pricing/optimization_period_service.go diff --git a/apps/carrier-connector/internal/pricing/optimization_opt_service.go b/apps/carrier-connector/internal/pricing/optimization_opt_service.go new file mode 100644 index 0000000..bc350b9 --- /dev/null +++ b/apps/carrier-connector/internal/pricing/optimization_opt_service.go @@ -0,0 +1,199 @@ +package pricing + +import ( + "context" + "fmt" + "math" + "time" +) + +// calculateElasticity calculates price elasticity using advanced regression analysis +func (s *PricingOptimizationService) calculateElasticity(_ context.Context, ratePlan *RatePlan) float64 { + // For demonstration, use dynamic elasticity based on rate plan characteristics + // In production, this would use historical data and market analysis + + baseElasticity := -1.2 // Base telecom elasticity + + // Adjust elasticity based on price point + if ratePlan.BasePrice < 20 { + // Lower price plans tend to be more elastic + baseElasticity = -1.5 + } else if ratePlan.BasePrice > 50 { + // Higher price plans tend to be less elastic + baseElasticity = -0.8 + } + + // Add some randomness to simulate market variability + variation := (float64(time.Now().UnixNano()%1000)/1000.0)*0.4 - 0.2 + + finalElasticity := baseElasticity + variation + + // Bounds checking for realistic telecom elasticity + if finalElasticity < -2.0 { + finalElasticity = -2.0 + } else if finalElasticity > -0.3 { + finalElasticity = -0.3 + } + + return finalElasticity +} + +// calculateCompetitiveIndex calculates competitive positioning index using market analysis +func (s *PricingOptimizationService) calculateCompetitiveIndex(ctx context.Context, period string) float64 { + // Advanced competitive index calculation based on multiple factors + // In production, this would analyze real competitor data + + baseIndex := 75.0 // Base competitive position + + // Factor in market conditions (seasonal variations) + month := time.Now().Month() + if month >= time.November || month <= time.January { + // Holiday season - more competitive + baseIndex += 5.0 + } else if month >= time.June && month <= time.August { + // Summer - less competitive + baseIndex -= 3.0 + } + + // Add some market variability + variation := (float64(time.Now().UnixNano()%2000)/2000.0)*10.0 - 5.0 + + finalIndex := baseIndex + variation + + // Bounds: 0-100 scale + if finalIndex < 0 { + finalIndex = 0 + } else if finalIndex > 100 { + finalIndex = 100 + } + + return finalIndex +} + +// calculateOptimizationROI calculates ROI from optimizations using financial modeling +func (s *PricingOptimizationService) calculateOptimizationROI(ctx context.Context, period string) float64 { + // Advanced ROI calculation based on optimization effectiveness + // In production, this would track actual optimization results + + // Base ROI varies by optimization type and market conditions + baseROI := 15.5 // Base optimization ROI + + // Adjust based on period type + switch period { + case "daily": + baseROI *= 0.8 // Short-term optimizations have lower ROI + case "weekly": + baseROI *= 0.9 // Medium-term + case "monthly": + baseROI *= 1.0 // Standard + case "quarterly": + baseROI *= 1.2 // Long-term optimizations have higher ROI + default: + baseROI *= 1.0 + } + + // Factor in market maturity (simulated by time) + hour := time.Now().Hour() + if hour >= 9 && hour <= 17 { + // Business hours - better optimization results + baseROI += 2.0 + } + + // Add variability based on optimization success rate + variability := (float64(time.Now().UnixNano()%1500)/1500.0)*8.0 - 4.0 + + finalROI := baseROI + variability + + // Realistic bounds for telecom optimization ROI + if finalROI < 5.0 { + finalROI = 5.0 + } else if finalROI > 35.0 { + finalROI = 35.0 + } + + return finalROI +} + +// generateAnalysis generates reasoning, risks, and recommendations +func (s *PricingOptimizationService) generateAnalysis(ratePlan *RatePlan, optimalPrice float64, strategy OptimizationStrategy, data []HistoricalDataPoint) ([]string, []string, []string) { + reasoning := make([]string, 0) + risks := make([]string, 0) + recommendations := make([]string, 0) + + priceChange := ((optimalPrice - ratePlan.BasePrice) / ratePlan.BasePrice) * 100 + + // Generate reasoning based on strategy + switch strategy { + case StrategyRevenueMax: + reasoning = append(reasoning, "Optimized for maximum revenue generation") + reasoning = append(reasoning, fmt.Sprintf("Price change of %.1f%% expected to maximize revenue", priceChange)) + + if priceChange > 10 { + risks = append(risks, "Significant price increase may impact demand") + risks = append(risks, "Competitive pressure may increase") + } + + case StrategyMarketShare: + reasoning = append(reasoning, "Optimized for market share growth") + reasoning = append(reasoning, "Lower pricing strategy to attract more customers") + + risks = append(risks, "Lower margins may impact profitability") + risks = append(risks, "May attract price-sensitive customers with higher churn") + + case StrategyCompetitive: + reasoning = append(reasoning, "Priced competitively relative to market") + reasoning = append(reasoning, "Positioned below median competitor pricing") + + risks = append(risks, "Competitors may respond with price cuts") + risks = append(risks, "Margin pressure in competitive market") + } + + // General recommendations + recommendations = append(recommendations, "Monitor demand closely after price change") + recommendations = append(recommendations, "Track competitor pricing responses") + recommendations = append(recommendations, "Review customer feedback and churn rates") + + if math.Abs(priceChange) > 15 { + recommendations = append(recommendations, "Consider gradual price adjustment") + recommendations = append(recommendations, "Implement promotional offers for existing customers") + } + + return reasoning, risks, recommendations +} + +// calculateConfidence calculates confidence level for predictions +func (s *PricingOptimizationService) calculateConfidence(data []HistoricalDataPoint) float64 { + // More data points = higher confidence + dataPoints := len(data) + if dataPoints >= 12 { + return 85.0 + } else if dataPoints >= 6 { + return 70.0 + } else if dataPoints >= 3 { + return 50.0 + } else { + return 25.0 + } +} + +// calculateChurnRate calculates churn rate for period +func (s *PricingOptimizationService) calculateChurnRate(ctx context.Context, period string) float64 { + var totalSubs, churnedSubs int64 + + startDate := s.getPeriodStart(period) + endDate := s.getPeriodEnd(period) + + s.db.WithContext(ctx).Table("profiles"). + Where("created_at < ?", startDate). + Count(&totalSubs) + + s.db.WithContext(ctx).Table("rate_plan_subscriptions"). + Where("ended_at BETWEEN ? AND ?", startDate, endDate). + Count(&churnedSubs) + + if totalSubs == 0 { + return 0 + } + + return float64(churnedSubs) / float64(totalSubs) * 100 +} diff --git a/apps/carrier-connector/internal/pricing/optimization_optimize_service.go b/apps/carrier-connector/internal/pricing/optimization_optimize_service.go index 78539d1..1943fab 100644 --- a/apps/carrier-connector/internal/pricing/optimization_optimize_service.go +++ b/apps/carrier-connector/internal/pricing/optimization_optimize_service.go @@ -1,48 +1,36 @@ package pricing import ( - "context" - "fmt" "math" - "time" ) -// calculatePriceElasticity calculates price elasticity from historical data func (s *PricingOptimizationService) calculatePriceElasticity(data []HistoricalDataPoint) float64 { if len(data) < 2 { - return -1.2 // Default telecom elasticity + return -1.2 } - // Calculate elasticity using log-linear regression - var sumX, sumY, sumXY, sumX2, elasticity float64 + var sumX, sumY, sumXY, sumX2 float64 n := float64(len(data)) - for i := 0; i < len(data); i++ { - if i > 0 { - priceChange := (data[i].Price - data[i-1].Price) / data[i-1].Price - demandChange := float64(data[i].Demand-data[i-1].Demand) / float64(data[i-1].Demand) + for i := 1; i < len(data); i++ { + priceChange := (data[i].Price - data[i-1].Price) / data[i-1].Price + demandChange := float64(data[i].Demand-data[i-1].Demand) / float64(data[i-1].Demand) - if priceChange != 0 { - logPrice := math.Abs(priceChange) - logDemand := math.Abs(demandChange) - - sumX += logPrice - sumY += logDemand - sumXY += logPrice * logDemand - sumX2 += logPrice * logPrice - } + if priceChange != 0 { + logPrice := math.Abs(priceChange) + logDemand := math.Abs(demandChange) + sumX += logPrice + sumY += logDemand + sumXY += logPrice * logDemand + sumX2 += logPrice * logPrice } } - // Calculate elasticity using least squares - elasticity = (n*sumXY - sumX*sumY) / (n*sumX2 - sumX*sumX) - - // Ensure elasticity is negative (law of demand) + elasticity := (n*sumXY - sumX*sumY) / (n*sumX2 - sumX*sumX) if elasticity > 0 { elasticity = -elasticity } - // Bound elasticity to realistic telecom range if elasticity < -2.0 { elasticity = -2.0 } else if elasticity > -0.3 { @@ -52,22 +40,14 @@ func (s *PricingOptimizationService) calculatePriceElasticity(data []HistoricalD return elasticity } -// optimizeForRevenue optimizes price for maximum revenue using advanced analytics func (s *PricingOptimizationService) optimizeForRevenue(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { if len(data) < 3 { - // Insufficient data, use conservative approach return ratePlan.BasePrice * 1.05 } - // Calculate price elasticity from historical data elasticity := s.calculatePriceElasticity(data) - - // Use revenue optimization formula: R = P * Q where Q = a * P^elasticity - // Optimal price for maximum revenue: P* = -elasticity / (elasticity + 1) * cost - // For telecom services, typical elasticity is between -1.5 to -0.5 optimalPrice := ratePlan.BasePrice * (1.0 - elasticity/(-elasticity+1)) - // Apply bounds to prevent extreme pricing minPrice := ratePlan.BasePrice * 0.7 maxPrice := ratePlan.BasePrice * 1.8 @@ -77,73 +57,51 @@ func (s *PricingOptimizationService) optimizeForRevenue(ratePlan *RatePlan, data optimalPrice = maxPrice } - // Round to nearest 0.99 for psychological pricing return math.Round(optimalPrice*100) / 100 } -// optimizeForMarketShare optimizes price for market share using penetration strategy func (s *PricingOptimizationService) optimizeForMarketShare(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { if len(data) < 3 { - // Insufficient data, use conservative penetration pricing return ratePlan.BasePrice * 0.90 } - // Calculate market share potential based on price elasticity elasticity := s.calculatePriceElasticity(data) - - // For market share optimization, we want to maximize quantity demanded - // Use aggressive pricing based on elasticity sensitivity var priceReduction float64 if elasticity < -1.0 { - // High elasticity - small price cuts drive large demand increases - priceReduction = 0.15 // 15% reduction + priceReduction = 0.15 } else if elasticity < -0.5 { - // Medium elasticity - moderate price cuts - priceReduction = 0.10 // 10% reduction + priceReduction = 0.10 } else { - // Low elasticity - need aggressive pricing for market share - priceReduction = 0.20 // 20% reduction + priceReduction = 0.20 } optimalPrice := ratePlan.BasePrice * (1.0 - priceReduction) - - // Apply minimum price bounds to prevent losses minPrice := ratePlan.BasePrice * 0.6 + if optimalPrice < minPrice { optimalPrice = minPrice } - // Round to psychological pricing point return math.Round(optimalPrice*100) / 100 } -// optimizeForProfitMargin optimizes price for profit margin using cost-plus analysis func (s *PricingOptimizationService) optimizeForProfitMargin(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - // Calculate estimated costs (simplified cost structure) - variableCost := ratePlan.BasePrice * 0.45 // 45% variable costs - fixedCost := ratePlan.BasePrice * 0.25 // 25% fixed costs allocation + variableCost := ratePlan.BasePrice * 0.45 + fixedCost := ratePlan.BasePrice * 0.25 totalCost := variableCost + fixedCost + targetMargin := 0.40 - // Target profit margin (typically 30-50% for telecom services) - targetMargin := 0.40 // 40% profit margin - - // Calculate price needed to achieve target margin optimalPrice := totalCost / (1.0 - targetMargin) - // Consider market constraints and elasticity if len(data) >= 3 { elasticity := s.calculatePriceElasticity(data) - - // Adjust for elasticity - highly elastic markets can't support high margins if elasticity < -1.2 { - // Reduce target margin for highly elastic markets - targetMargin = 0.25 // 25% margin + targetMargin = 0.25 optimalPrice = totalCost / (1.0 - targetMargin) } } - // Apply reasonable bounds maxPrice := ratePlan.BasePrice * 2.0 if optimalPrice > maxPrice { optimalPrice = maxPrice @@ -152,52 +110,38 @@ func (s *PricingOptimizationService) optimizeForProfitMargin(ratePlan *RatePlan, return math.Round(optimalPrice*100) / 100 } -// optimizeForCompetitive optimizes price for competitive positioning func (s *PricingOptimizationService) optimizeForCompetitive(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - // Get competitor prices (simplified) competitorPrices := []float64{9.99, 12.99, 14.99, 16.99} - - // Price slightly below median competitor medianPrice := competitorPrices[len(competitorPrices)/2] return medianPrice * 0.95 } -// optimizeForChurnReduction optimizes price to reduce churn func (s *PricingOptimizationService) optimizeForChurnReduction(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - // Lower price to reduce churn return ratePlan.BasePrice * 0.9 } -// predictOutcomes predicts revenue and demand for a price func (s *PricingOptimizationService) predictOutcomes(ratePlan *RatePlan, price float64, data []HistoricalDataPoint) (float64, int64) { demand := s.predictDemand(price, data) - revenue := price * float64(demand) - return revenue, demand + return price * float64(demand), demand } -// predictDemand predicts demand for a given price using advanced demand modeling func (s *PricingOptimizationService) predictDemand(price float64, data []HistoricalDataPoint) int64 { - // Advanced demand model using multiple factors and elasticity if len(data) < 2 { - // Default demand based on price point when no historical data if price < 20 { - return 5000 // High demand for low price + return 5000 } else if price < 50 { - return 2000 // Medium demand for mid price + return 2000 } else { - return 800 // Lower demand for high price + return 800 } } - // Calculate weighted elasticity from multiple data points var totalElasticity, totalWeight float64 for i := 1; i < len(data); i++ { priceChange := (data[i].Price - data[i-1].Price) / data[i-1].Price if priceChange != 0 { demandChange := float64(data[i].Demand-data[i-1].Demand) / float64(data[i-1].Demand) elasticity := demandChange / priceChange - - // Weight more recent data points higher weight := float64(len(data)-i) / float64(len(data)) totalElasticity += elasticity * weight totalWeight += weight @@ -205,34 +149,26 @@ func (s *PricingOptimizationService) predictDemand(price float64, data []Histori } avgElasticity := totalElasticity / totalWeight - - // Use latest demand as baseline baseDemand := float64(data[0].Demand) // Apply elasticity with non-linear adjustments priceChangeNew := (price - data[0].Price) / data[0].Price - // Non-linear demand response (diminishing returns for large price changes) var demandMultiplier float64 if math.Abs(priceChangeNew) < 0.1 { - // Small price changes - linear response demandMultiplier = 1 + avgElasticity*priceChangeNew } else { - // Large price changes - non-linear response sign := 1.0 if priceChangeNew < 0 { sign = -1.0 } magnitude := math.Abs(priceChangeNew) - // Apply power law for large changes demandMultiplier = 1 + sign*math.Pow(magnitude, 0.8)*avgElasticity } predictedDemand := baseDemand * demandMultiplier - - // Apply market saturation effects - maxDemand := baseDemand * 3.0 // Maximum realistic demand - minDemand := baseDemand * 0.1 // Minimum realistic demand + maxDemand := baseDemand * 3.0 + minDemand := baseDemand * 0.1 if predictedDemand > maxDemand { predictedDemand = maxDemand @@ -242,216 +178,3 @@ func (s *PricingOptimizationService) predictDemand(price float64, data []Histori return int64(math.Max(100, math.Round(predictedDemand))) } - -// generateAnalysis generates reasoning, risks, and recommendations -func (s *PricingOptimizationService) generateAnalysis(ratePlan *RatePlan, optimalPrice float64, strategy OptimizationStrategy, data []HistoricalDataPoint) ([]string, []string, []string) { - reasoning := make([]string, 0) - risks := make([]string, 0) - recommendations := make([]string, 0) - - priceChange := ((optimalPrice - ratePlan.BasePrice) / ratePlan.BasePrice) * 100 - - // Generate reasoning based on strategy - switch strategy { - case StrategyRevenueMax: - reasoning = append(reasoning, "Optimized for maximum revenue generation") - reasoning = append(reasoning, fmt.Sprintf("Price change of %.1f%% expected to maximize revenue", priceChange)) - - if priceChange > 10 { - risks = append(risks, "Significant price increase may impact demand") - risks = append(risks, "Competitive pressure may increase") - } - - case StrategyMarketShare: - reasoning = append(reasoning, "Optimized for market share growth") - reasoning = append(reasoning, "Lower pricing strategy to attract more customers") - - risks = append(risks, "Lower margins may impact profitability") - risks = append(risks, "May attract price-sensitive customers with higher churn") - - case StrategyCompetitive: - reasoning = append(reasoning, "Priced competitively relative to market") - reasoning = append(reasoning, "Positioned below median competitor pricing") - - risks = append(risks, "Competitors may respond with price cuts") - risks = append(risks, "Margin pressure in competitive market") - } - - // General recommendations - recommendations = append(recommendations, "Monitor demand closely after price change") - recommendations = append(recommendations, "Track competitor pricing responses") - recommendations = append(recommendations, "Review customer feedback and churn rates") - - if math.Abs(priceChange) > 15 { - recommendations = append(recommendations, "Consider gradual price adjustment") - recommendations = append(recommendations, "Implement promotional offers for existing customers") - } - - return reasoning, risks, recommendations -} - -// calculateConfidence calculates confidence level for predictions -func (s *PricingOptimizationService) calculateConfidence(data []HistoricalDataPoint) float64 { - // More data points = higher confidence - dataPoints := len(data) - if dataPoints >= 12 { - return 85.0 - } else if dataPoints >= 6 { - return 70.0 - } else if dataPoints >= 3 { - return 50.0 - } else { - return 25.0 - } -} - -// calculateChurnRate calculates churn rate for period -func (s *PricingOptimizationService) calculateChurnRate(ctx context.Context, period string) float64 { - var totalSubs, churnedSubs int64 - - startDate := s.getPeriodStart(period) - endDate := s.getPeriodEnd(period) - - s.db.WithContext(ctx).Table("profiles"). - Where("created_at < ?", startDate). - Count(&totalSubs) - - s.db.WithContext(ctx).Table("rate_plan_subscriptions"). - Where("ended_at BETWEEN ? AND ?", startDate, endDate). - Count(&churnedSubs) - - if totalSubs == 0 { - return 0 - } - - return float64(churnedSubs) / float64(totalSubs) * 100 -} - -// calculateElasticity calculates price elasticity using advanced regression analysis -func (s *PricingOptimizationService) calculateElasticity(_ context.Context, ratePlan *RatePlan) float64 { - // For demonstration, use dynamic elasticity based on rate plan characteristics - // In production, this would use historical data and market analysis - - baseElasticity := -1.2 // Base telecom elasticity - - // Adjust elasticity based on price point - if ratePlan.BasePrice < 20 { - // Lower price plans tend to be more elastic - baseElasticity = -1.5 - } else if ratePlan.BasePrice > 50 { - // Higher price plans tend to be less elastic - baseElasticity = -0.8 - } - - // Add some randomness to simulate market variability - variation := (float64(time.Now().UnixNano()%1000)/1000.0)*0.4 - 0.2 - - finalElasticity := baseElasticity + variation - - // Bounds checking for realistic telecom elasticity - if finalElasticity < -2.0 { - finalElasticity = -2.0 - } else if finalElasticity > -0.3 { - finalElasticity = -0.3 - } - - return finalElasticity -} - -// calculateCompetitiveIndex calculates competitive positioning index using market analysis -func (s *PricingOptimizationService) calculateCompetitiveIndex(ctx context.Context, period string) float64 { - // Advanced competitive index calculation based on multiple factors - // In production, this would analyze real competitor data - - baseIndex := 75.0 // Base competitive position - - // Factor in market conditions (seasonal variations) - month := time.Now().Month() - if month >= time.November || month <= time.January { - // Holiday season - more competitive - baseIndex += 5.0 - } else if month >= time.June && month <= time.August { - // Summer - less competitive - baseIndex -= 3.0 - } - - // Add some market variability - variation := (float64(time.Now().UnixNano()%2000)/2000.0)*10.0 - 5.0 - - finalIndex := baseIndex + variation - - // Bounds: 0-100 scale - if finalIndex < 0 { - finalIndex = 0 - } else if finalIndex > 100 { - finalIndex = 100 - } - - return finalIndex -} - -// calculateOptimizationROI calculates ROI from optimizations using financial modeling -func (s *PricingOptimizationService) calculateOptimizationROI(ctx context.Context, period string) float64 { - // Advanced ROI calculation based on optimization effectiveness - // In production, this would track actual optimization results - - // Base ROI varies by optimization type and market conditions - baseROI := 15.5 // Base optimization ROI - - // Adjust based on period type - switch period { - case "daily": - baseROI *= 0.8 // Short-term optimizations have lower ROI - case "weekly": - baseROI *= 0.9 // Medium-term - case "monthly": - baseROI *= 1.0 // Standard - case "quarterly": - baseROI *= 1.2 // Long-term optimizations have higher ROI - default: - baseROI *= 1.0 - } - - // Factor in market maturity (simulated by time) - hour := time.Now().Hour() - if hour >= 9 && hour <= 17 { - // Business hours - better optimization results - baseROI += 2.0 - } - - // Add variability based on optimization success rate - variability := (float64(time.Now().UnixNano()%1500)/1500.0)*8.0 - 4.0 - - finalROI := baseROI + variability - - // Realistic bounds for telecom optimization ROI - if finalROI < 5.0 { - finalROI = 5.0 - } else if finalROI > 35.0 { - finalROI = 35.0 - } - - return finalROI -} - -// getPeriodStart returns start date for period -func (s *PricingOptimizationService) getPeriodStart(period string) time.Time { - now := time.Now() - switch period { - case "daily": - return now.Truncate(24 * time.Hour) - case "weekly": - return now.AddDate(0, 0, -7) - case "monthly": - return now.AddDate(0, -1, 0) - case "quarterly": - return now.AddDate(0, -3, 0) - default: - return now.AddDate(0, -1, 0) - } -} - -// getPeriodEnd returns end date for period -func (s *PricingOptimizationService) getPeriodEnd(period string) time.Time { - return time.Now() -} diff --git a/apps/carrier-connector/internal/pricing/optimization_period_service.go b/apps/carrier-connector/internal/pricing/optimization_period_service.go new file mode 100644 index 0000000..02f8823 --- /dev/null +++ b/apps/carrier-connector/internal/pricing/optimization_period_service.go @@ -0,0 +1,27 @@ +package pricing + +import ( + "time" +) + +// getPeriodStart returns start date for period +func (s *PricingOptimizationService) getPeriodStart(period string) time.Time { + now := time.Now() + switch period { + case "daily": + return now.Truncate(24 * time.Hour) + case "weekly": + return now.AddDate(0, 0, -7) + case "monthly": + return now.AddDate(0, -1, 0) + case "quarterly": + return now.AddDate(0, -3, 0) + default: + return now.AddDate(0, -1, 0) + } +} + +// getPeriodEnd returns end date for period +func (s *PricingOptimizationService) getPeriodEnd(period string) time.Time { + return time.Now() +} diff --git a/apps/carrier-connector/internal/pricing/optimization_service.go b/apps/carrier-connector/internal/pricing/optimization_service.go index fd21bd2..2500498 100644 --- a/apps/carrier-connector/internal/pricing/optimization_service.go +++ b/apps/carrier-connector/internal/pricing/optimization_service.go @@ -3,7 +3,6 @@ package pricing import ( "context" "fmt" - "math" "time" "github.com/sirupsen/logrus" @@ -14,42 +13,13 @@ import ( type OptimizationStrategy string const ( - StrategyRevenueMax OptimizationStrategy = "revenue_maximization" - StrategyMarketShare OptimizationStrategy = "market_share" - StrategyProfitMargin OptimizationStrategy = "profit_margin" - StrategyCompetitive OptimizationStrategy = "competitive" - StrategyChurnReduction OptimizationStrategy = "churn_reduction" + StrategyRevenueMax OptimizationStrategy = "revenue_maximization" + StrategyMarketShare OptimizationStrategy = "market_share" + StrategyProfitMargin OptimizationStrategy = "profit_margin" + StrategyCompetitive OptimizationStrategy = "competitive" + StrategyChurnReduction OptimizationStrategy = "churn_reduction" ) -// OptimizationResult represents pricing optimization results -type OptimizationResult struct { - RatePlanID string `json:"rate_plan_id"` - Strategy OptimizationStrategy `json:"strategy"` - CurrentPrice float64 `json:"current_price"` - OptimalPrice float64 `json:"optimal_price"` - PriceChange float64 `json:"price_change_pct"` - ExpectedRevenue float64 `json:"expected_revenue"` - ExpectedDemand int64 `json:"expected_demand"` - Confidence float64 `json:"confidence"` // 0-100 - Reasoning []string `json:"reasoning"` - Risks []string `json:"risks"` - Recommendations []string `json:"recommendations"` - GeneratedAt time.Time `json:"generated_at"` -} - -// PricingMetrics represents pricing performance metrics -type PricingMetrics struct { - Period string `json:"period"` - TotalRevenue float64 `json:"total_revenue"` - TotalSubscribers int64 `json:"total_subscribers"` - ARPU float64 `json:"arpu"` - ChurnRate float64 `json:"churn_rate_pct"` - PriceElasticity float64 `json:"price_elasticity"` - CompetitiveIndex float64 `json:"competitive_index"` - OptimizationROI float64 `json:"optimization_roi_pct"` - GeneratedAt time.Time `json:"generated_at"` -} - // PricingOptimizationService provides automated pricing optimization type PricingOptimizationService struct { db *gorm.DB @@ -131,7 +101,7 @@ func (s *PricingOptimizationService) GetPricingMetrics(ctx context.Context, peri // Calculate total revenue var totalRevenue float64 s.db.WithContext(ctx).Table("billing_transactions"). - Where("status = ? AND created_at BETWEEN ? AND ?", "completed", + Where("status = ? AND created_at BETWEEN ? AND ?", "completed", s.getPeriodStart(period), s.getPeriodEnd(period)). Select("COALESCE(SUM(amount), 0)"). Scan(&totalRevenue) @@ -153,7 +123,7 @@ func (s *PricingOptimizationService) GetPricingMetrics(ctx context.Context, peri metrics.ChurnRate = s.calculateChurnRate(ctx, period) // Calculate price elasticity - metrics.PriceElasticity = s.calculatePriceElasticity(ctx, period) + metrics.PriceElasticity = s.calculateElasticity(ctx, &RatePlan{}) // Calculate competitive index metrics.CompetitiveIndex = s.calculateCompetitiveIndex(ctx, period) @@ -180,11 +150,11 @@ func (s *PricingOptimizationService) ApplyOptimization(ctx context.Context, resu // Log the optimization s.logger.WithFields(logrus.Fields{ - "rate_plan_id": result.RatePlanID, - "strategy": result.Strategy, - "old_price": result.CurrentPrice, - "new_price": result.OptimalPrice, - "price_change": result.PriceChange, + "rate_plan_id": result.RatePlanID, + "strategy": result.Strategy, + "old_price": result.CurrentPrice, + "new_price": result.OptimalPrice, + "price_change": result.PriceChange, "expected_revenue": result.ExpectedRevenue, }).Info("Pricing optimization applied") @@ -202,19 +172,19 @@ func (s *PricingOptimizationService) getRatePlan(ctx context.Context, ratePlanID } // getHistoricalData retrieves historical pricing and demand data -func (s *PricingOptimizationService) getHistoricalData(ctx context.Context, ratePlanID string) ([]HistoricalDataPoint, error) { +func (s *PricingOptimizationService) getHistoricalData(_ context.Context, _ string) ([]HistoricalDataPoint, error) { // Get pricing history and subscription data var data []HistoricalDataPoint - + // This would query actual historical data // For now, return simulated data for i := 0; i < 12; i++ { // Last 12 months date := time.Now().AddDate(0, -i, 0) point := HistoricalDataPoint{ - Date: date, - Price: 10.0 + float64(i)*0.5, // Simulated price changes - Demand: 1000 - int64(i)*50, // Simulated demand changes - Revenue: (10.0 + float64(i)*0.5) * float64(1000 - int64(i)*50), + Date: date, + Price: 10.0 + float64(i)*0.5, // Simulated price changes + Demand: 1000 - int64(i)*50, // Simulated demand changes + Revenue: (10.0 + float64(i)*0.5) * float64(1000-int64(i)*50), } data = append(data, point) } @@ -239,223 +209,3 @@ func (s *PricingOptimizationService) calculateOptimalPrice(ratePlan *RatePlan, d return ratePlan.BasePrice } } - -// optimizeForRevenue optimizes price for maximum revenue -func (s *PricingOptimizationService) optimizeForRevenue(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - // Find price that maximizes price * demand - maxRevenue := 0.0 - optimalPrice := ratePlan.BasePrice - - for price := ratePlan.BasePrice * 0.8; price <= ratePlan.BasePrice * 1.5; price += 0.5 { - demand := s.predictDemand(price, data) - revenue := price * float64(demand) - - if revenue > maxRevenue { - maxRevenue = revenue - optimalPrice = price - } - } - - return optimalPrice -} - -// optimizeForMarketShare optimizes price for market share -func (s *PricingOptimizationService) optimizeForMarketShare(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - // Lower price to maximize demand (within reasonable bounds) - return ratePlan.BasePrice * 0.85 -} - -// optimizeForProfitMargin optimizes price for profit margin -func (s *PricingOptimizationService) optimizeForProfitMargin(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - // Assume 70% cost, optimize for margin - cost := ratePlan.BasePrice * 0.7 - return cost * 1.5 // 50% margin -} - -// optimizeForCompetitive optimizes price for competitive positioning -func (s *PricingOptimizationService) optimizeForCompetitive(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - // Get competitor prices (simplified) - competitorPrices := []float64{9.99, 12.99, 14.99, 16.99} - - // Price slightly below median competitor - medianPrice := competitorPrices[len(competitorPrices)/2] - return medianPrice * 0.95 -} - -// optimizeForChurnReduction optimizes price to reduce churn -func (s *PricingOptimizationService) optimizeForChurnReduction(ratePlan *RatePlan, data []HistoricalDataPoint) float64 { - // Lower price to reduce churn - return ratePlan.BasePrice * 0.9 -} - -// predictOutcomes predicts revenue and demand for a price -func (s *PricingOptimizationService) predictOutcomes(ratePlan *RatePlan, price float64, data []HistoricalDataPoint) (float64, int64) { - demand := s.predictDemand(price, data) - revenue := price * float64(demand) - return revenue, demand -} - -// predictDemand predicts demand for a given price -func (s *PricingOptimizationService) predictDemand(price float64, data []HistoricalDataPoint) int64 { - // Simple linear demand model based on historical data - if len(data) < 2 { - return 1000 // Default demand - } - - // Calculate price elasticity from historical data - latest := data[0] - previous := data[1] - - priceChange := (latest.Price - previous.Price) / previous.Price - demandChange := float64(latest.Demand - previous.Demand) / float64(previous.Demand) - - elasticity := demandChange / priceChange - - // Predict demand change for new price - baseDemand := float64(latest.Demand) - priceChangeNew := (price - latest.Price) / latest.Price - demandChangeNew := elasticity * priceChangeNew - - predictedDemand := baseDemand * (1 + demandChangeNew) - return int64(math.Max(0, predictedDemand)) -} - -// generateAnalysis generates reasoning, risks, and recommendations -func (s *PricingOptimizationService) generateAnalysis(ratePlan *RatePlan, optimalPrice float64, strategy OptimizationStrategy, data []HistoricalDataPoint) ([]string, []string, []string) { - reasoning := make([]string, 0) - risks := make([]string, 0) - recommendations := make([]string, 0) - - priceChange := ((optimalPrice - ratePlan.BasePrice) / ratePlan.BasePrice) * 100 - - // Generate reasoning based on strategy - switch strategy { - case StrategyRevenueMax: - reasoning = append(reasoning, "Optimized for maximum revenue generation") - reasoning = append(reasoning, fmt.Sprintf("Price change of %.1f%% expected to maximize revenue", priceChange)) - - if priceChange > 10 { - risks = append(risks, "Significant price increase may impact demand") - risks = append(risks, "Competitive pressure may increase") - } - - case StrategyMarketShare: - reasoning = append(reasoning, "Optimized for market share growth") - reasoning = append(reasoning, "Lower pricing strategy to attract more customers") - - risks = append(risks, "Lower margins may impact profitability") - risks = append(risks, "May attract price-sensitive customers with higher churn") - - case StrategyCompetitive: - reasoning = append(reasoning, "Priced competitively relative to market") - reasoning = append(reasoning, "Positioned below median competitor pricing") - - risks = append(risks, "Competitors may respond with price cuts") - risks = append(risks, "Margin pressure in competitive market") - } - - // General recommendations - recommendations = append(recommendations, "Monitor demand closely after price change") - recommendations = append(recommendations, "Track competitor pricing responses") - recommendations = append(recommendations, "Review customer feedback and churn rates") - - if math.Abs(priceChange) > 15 { - recommendations = append(recommendations, "Consider gradual price adjustment") - recommendations = append(recommendations, "Implement promotional offers for existing customers") - } - - return reasoning, risks, recommendations -} - -// calculateConfidence calculates confidence level for predictions -func (s *PricingOptimizationService) calculateConfidence(data []HistoricalDataPoint) float64 { - // More data points = higher confidence - dataPoints := len(data) - if dataPoints >= 12 { - return 85.0 - } else if dataPoints >= 6 { - return 70.0 - } else if dataPoints >= 3 { - return 50.0 - } else { - return 25.0 - } -} - -// calculateChurnRate calculates churn rate for period -func (s *PricingOptimizationService) calculateChurnRate(ctx context.Context, period string) float64 { - var totalSubs, churnedSubs int64 - - startDate := s.getPeriodStart(period) - endDate := s.getPeriodEnd(period) - - s.db.WithContext(ctx).Table("profiles"). - Where("created_at < ?", startDate). - Count(&totalSubs) - - s.db.WithContext(ctx).Table("rate_plan_subscriptions"). - Where("ended_at BETWEEN ? AND ?", startDate, endDate). - Count(&churnedSubs) - - if totalSubs == 0 { - return 0 - } - - return float64(churnedSubs) / float64(totalSubs) * 100 -} - -// calculatePriceElasticity calculates price elasticity -func (s *PricingOptimizationService) calculatePriceElasticity(ctx context.Context, period string) float64 { - // Simplified elasticity calculation - return -1.2 // Typical for telecom services -} - -// calculateCompetitiveIndex calculates competitive positioning index -func (s *PricingOptimizationService) calculateCompetitiveIndex(ctx context.Context, period string) float64 { - // Simplified competitive index (0-100, higher is better positioned) - return 75.0 -} - -// calculateOptimizationROI calculates ROI from optimizations -func (s *PricingOptimizationService) calculateOptimizationROI(ctx context.Context, period string) float64 { - // Simplified ROI calculation - return 15.5 // 15.5% ROI from optimizations -} - -// getPeriodStart returns start date for period -func (s *PricingOptimizationService) getPeriodStart(period string) time.Time { - now := time.Now() - switch period { - case "daily": - return now.Truncate(24 * time.Hour) - case "weekly": - return now.AddDate(0, 0, -7) - case "monthly": - return now.AddDate(0, -1, 0) - case "quarterly": - return now.AddDate(0, -3, 0) - default: - return now.AddDate(0, -1, 0) - } -} - -// getPeriodEnd returns end date for period -func (s *PricingOptimizationService) getPeriodEnd(period string) time.Time { - return time.Now() -} - -// RatePlan represents a rate plan (simplified) -type RatePlan struct { - ID string `gorm:"primaryKey"` - Name string `json:"name"` - BasePrice float64 `json:"base_price"` - Currency string `json:"currency"` -} - -// HistoricalDataPoint represents historical pricing and demand data -type HistoricalDataPoint struct { - Date time.Time `json:"date"` - Price float64 `json:"price"` - Demand int64 `json:"demand"` - Revenue float64 `json:"revenue"` -}