mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-28 15:42:16 +00:00
- Add OptimizationStrategy constants for revenue_maximization, market_share, profit_margin, competitive, and churn_reduction - Add OptimizationResult struct with RatePlanID, Strategy, CurrentPrice, OptimalPrice, PriceChange, ExpectedRevenue, ExpectedDemand, Confidence, Reasoning, Risks, and Recommendations fields - Add PricingMetrics struct with Period, TotalRevenue, TotalSubscribers, AR
462 lines
16 KiB
Go
462 lines
16 KiB
Go
package pricing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// OptimizationStrategy represents pricing optimization strategies
|
|
type OptimizationStrategy string
|
|
|
|
const (
|
|
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
|
|
logger *logrus.Logger
|
|
}
|
|
|
|
// NewPricingOptimizationService creates a new pricing optimization service
|
|
func NewPricingOptimizationService(db *gorm.DB, logger *logrus.Logger) *PricingOptimizationService {
|
|
return &PricingOptimizationService{
|
|
db: db,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// OptimizePricing optimizes pricing for rate plans
|
|
func (s *PricingOptimizationService) OptimizePricing(ctx context.Context, ratePlanIDs []string, strategy OptimizationStrategy) ([]*OptimizationResult, error) {
|
|
results := make([]*OptimizationResult, 0)
|
|
|
|
for _, ratePlanID := range ratePlanIDs {
|
|
result, err := s.optimizeRatePlan(ctx, ratePlanID, strategy)
|
|
if err != nil {
|
|
s.logger.WithError(err).Error("Failed to optimize rate plan", "rate_plan_id", ratePlanID)
|
|
continue
|
|
}
|
|
results = append(results, result)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// optimizeRatePlan optimizes a single rate plan
|
|
func (s *PricingOptimizationService) optimizeRatePlan(ctx context.Context, ratePlanID string, strategy OptimizationStrategy) (*OptimizationResult, error) {
|
|
// Get current rate plan data
|
|
ratePlan, err := s.getRatePlan(ctx, ratePlanID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get historical data
|
|
historicalData, err := s.getHistoricalData(ctx, ratePlanID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Calculate optimal price based on strategy
|
|
optimalPrice := s.calculateOptimalPrice(ratePlan, historicalData, strategy)
|
|
|
|
// Calculate expected outcomes
|
|
expectedRevenue, expectedDemand := s.predictOutcomes(ratePlan, optimalPrice, historicalData)
|
|
|
|
// Generate reasoning and recommendations
|
|
reasoning, risks, recommendations := s.generateAnalysis(ratePlan, optimalPrice, strategy, historicalData)
|
|
|
|
result := &OptimizationResult{
|
|
RatePlanID: ratePlanID,
|
|
Strategy: strategy,
|
|
CurrentPrice: ratePlan.BasePrice,
|
|
OptimalPrice: optimalPrice,
|
|
PriceChange: ((optimalPrice - ratePlan.BasePrice) / ratePlan.BasePrice) * 100,
|
|
ExpectedRevenue: expectedRevenue,
|
|
ExpectedDemand: expectedDemand,
|
|
Confidence: s.calculateConfidence(historicalData),
|
|
Reasoning: reasoning,
|
|
Risks: risks,
|
|
Recommendations: recommendations,
|
|
GeneratedAt: time.Now(),
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetPricingMetrics returns pricing performance metrics
|
|
func (s *PricingOptimizationService) GetPricingMetrics(ctx context.Context, period string) (*PricingMetrics, error) {
|
|
metrics := &PricingMetrics{
|
|
Period: period,
|
|
GeneratedAt: time.Now(),
|
|
}
|
|
|
|
// Calculate total revenue
|
|
var totalRevenue float64
|
|
s.db.WithContext(ctx).Table("billing_transactions").
|
|
Where("status = ? AND created_at BETWEEN ? AND ?", "completed",
|
|
s.getPeriodStart(period), s.getPeriodEnd(period)).
|
|
Select("COALESCE(SUM(amount), 0)").
|
|
Scan(&totalRevenue)
|
|
metrics.TotalRevenue = totalRevenue
|
|
|
|
// Calculate total subscribers
|
|
var totalSubs int64
|
|
s.db.WithContext(ctx).Table("profiles").
|
|
Where("status = ?", "active").
|
|
Count(&totalSubs)
|
|
metrics.TotalSubscribers = totalSubs
|
|
|
|
// Calculate ARPU
|
|
if totalSubs > 0 {
|
|
metrics.ARPU = totalRevenue / float64(totalSubs)
|
|
}
|
|
|
|
// Calculate churn rate
|
|
metrics.ChurnRate = s.calculateChurnRate(ctx, period)
|
|
|
|
// Calculate price elasticity
|
|
metrics.PriceElasticity = s.calculatePriceElasticity(ctx, period)
|
|
|
|
// Calculate competitive index
|
|
metrics.CompetitiveIndex = s.calculateCompetitiveIndex(ctx, period)
|
|
|
|
// Calculate optimization ROI
|
|
metrics.OptimizationROI = s.calculateOptimizationROI(ctx, period)
|
|
|
|
return metrics, nil
|
|
}
|
|
|
|
// ApplyOptimization applies pricing optimization
|
|
func (s *PricingOptimizationService) ApplyOptimization(ctx context.Context, result *OptimizationResult) error {
|
|
// Update rate plan price
|
|
err := s.db.WithContext(ctx).Table("rate_plans").
|
|
Where("id = ?", result.RatePlanID).
|
|
Updates(map[string]interface{}{
|
|
"base_price": result.OptimalPrice,
|
|
"updated_at": time.Now(),
|
|
}).Error
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update rate plan: %w", err)
|
|
}
|
|
|
|
// 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,
|
|
"expected_revenue": result.ExpectedRevenue,
|
|
}).Info("Pricing optimization applied")
|
|
|
|
return nil
|
|
}
|
|
|
|
// getRatePlan retrieves rate plan data
|
|
func (s *PricingOptimizationService) getRatePlan(ctx context.Context, ratePlanID string) (*RatePlan, error) {
|
|
var ratePlan RatePlan
|
|
err := s.db.WithContext(ctx).Where("id = ?", ratePlanID).First(&ratePlan).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rate plan not found: %w", err)
|
|
}
|
|
return &ratePlan, nil
|
|
}
|
|
|
|
// getHistoricalData retrieves historical pricing and demand data
|
|
func (s *PricingOptimizationService) getHistoricalData(ctx context.Context, ratePlanID 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),
|
|
}
|
|
data = append(data, point)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// calculateOptimalPrice calculates optimal price based on strategy
|
|
func (s *PricingOptimizationService) calculateOptimalPrice(ratePlan *RatePlan, data []HistoricalDataPoint, strategy OptimizationStrategy) float64 {
|
|
switch strategy {
|
|
case StrategyRevenueMax:
|
|
return s.optimizeForRevenue(ratePlan, data)
|
|
case StrategyMarketShare:
|
|
return s.optimizeForMarketShare(ratePlan, data)
|
|
case StrategyProfitMargin:
|
|
return s.optimizeForProfitMargin(ratePlan, data)
|
|
case StrategyCompetitive:
|
|
return s.optimizeForCompetitive(ratePlan, data)
|
|
case StrategyChurnReduction:
|
|
return s.optimizeForChurnReduction(ratePlan, data)
|
|
default:
|
|
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"`
|
|
}
|