mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
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
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user