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:
nutcas3
2026-05-03 13:06:57 +03:00
parent 045dd6904b
commit ef374ce122
4 changed files with 271 additions and 572 deletions
@@ -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"`
}