511 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			511 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package services | |||
|  | 
 | |||
|  | import ( | |||
|  | 	"context" | |||
|  | 	"fmt" | |||
|  | 	"math" | |||
|  | 	"time" | |||
|  | 
 | |||
|  | 	"go.uber.org/zap" | |||
|  | 
 | |||
|  | 	"tyapi-server/internal/domains/statistics/entities" | |||
|  | 	"tyapi-server/internal/domains/statistics/repositories" | |||
|  | ) | |||
|  | 
 | |||
|  | // StatisticsCalculationService 统计计算服务接口 | |||
|  | // 负责各种统计计算和分析 | |||
|  | type StatisticsCalculationService interface { | |||
|  | 	// 基础统计计算 | |||
|  | 	CalculateTotal(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) | |||
|  | 	CalculateAverage(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) | |||
|  | 	CalculateMax(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) | |||
|  | 	CalculateMin(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) | |||
|  | 	 | |||
|  | 	// 高级统计计算 | |||
|  | 	CalculateGrowthRate(ctx context.Context, metricType, metricName string, currentPeriod, previousPeriod time.Time) (float64, error) | |||
|  | 	CalculateTrend(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (string, error) | |||
|  | 	CalculateCorrelation(ctx context.Context, metricType1, metricName1, metricType2, metricName2 string, startDate, endDate time.Time) (float64, error) | |||
|  | 	 | |||
|  | 	// 业务指标计算 | |||
|  | 	CalculateSuccessRate(ctx context.Context, startDate, endDate time.Time) (float64, error) | |||
|  | 	CalculateConversionRate(ctx context.Context, startDate, endDate time.Time) (float64, error) | |||
|  | 	CalculateRetentionRate(ctx context.Context, startDate, endDate time.Time) (float64, error) | |||
|  | 	 | |||
|  | 	// 时间序列分析 | |||
|  | 	CalculateMovingAverage(ctx context.Context, metricType, metricName string, startDate, endDate time.Time, windowSize int) ([]float64, error) | |||
|  | 	CalculateSeasonality(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (map[string]float64, error) | |||
|  | } | |||
|  | 
 | |||
|  | // StatisticsCalculationServiceImpl 统计计算服务实现 | |||
|  | type StatisticsCalculationServiceImpl struct { | |||
|  | 	metricRepo repositories.StatisticsRepository | |||
|  | 	logger     *zap.Logger | |||
|  | } | |||
|  | 
 | |||
|  | // NewStatisticsCalculationService 创建统计计算服务 | |||
|  | func NewStatisticsCalculationService( | |||
|  | 	metricRepo repositories.StatisticsRepository, | |||
|  | 	logger *zap.Logger, | |||
|  | ) StatisticsCalculationService { | |||
|  | 	return &StatisticsCalculationServiceImpl{ | |||
|  | 		metricRepo: metricRepo, | |||
|  | 		logger:     logger, | |||
|  | 	} | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateTotal 计算总值 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateTotal(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return 0, fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.String("metric_name", metricName), | |||
|  | 			zap.Error(err)) | |||
|  | 		return 0, fmt.Errorf("查询指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	var total float64 | |||
|  | 	for _, metric := range metrics { | |||
|  | 		total += metric.Value | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("计算总值完成", | |||
|  | 		zap.String("metric_type", metricType), | |||
|  | 		zap.String("metric_name", metricName), | |||
|  | 		zap.Float64("total", total)) | |||
|  | 
 | |||
|  | 	return total, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateAverage 计算平均值 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateAverage(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return 0, fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.String("metric_name", metricName), | |||
|  | 			zap.Error(err)) | |||
|  | 		return 0, fmt.Errorf("查询指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if len(metrics) == 0 { | |||
|  | 		return 0, nil | |||
|  | 	} | |||
|  | 
 | |||
|  | 	var total float64 | |||
|  | 	for _, metric := range metrics { | |||
|  | 		total += metric.Value | |||
|  | 	} | |||
|  | 
 | |||
|  | 	average := total / float64(len(metrics)) | |||
|  | 
 | |||
|  | 	s.logger.Info("计算平均值完成", | |||
|  | 		zap.String("metric_type", metricType), | |||
|  | 		zap.String("metric_name", metricName), | |||
|  | 		zap.Float64("average", average)) | |||
|  | 
 | |||
|  | 	return average, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateMax 计算最大值 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateMax(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return 0, fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.String("metric_name", metricName), | |||
|  | 			zap.Error(err)) | |||
|  | 		return 0, fmt.Errorf("查询指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if len(metrics) == 0 { | |||
|  | 		return 0, nil | |||
|  | 	} | |||
|  | 
 | |||
|  | 	max := metrics[0].Value | |||
|  | 	for _, metric := range metrics { | |||
|  | 		if metric.Value > max { | |||
|  | 			max = metric.Value | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("计算最大值完成", | |||
|  | 		zap.String("metric_type", metricType), | |||
|  | 		zap.String("metric_name", metricName), | |||
|  | 		zap.Float64("max", max)) | |||
|  | 
 | |||
|  | 	return max, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateMin 计算最小值 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateMin(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (float64, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return 0, fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.String("metric_name", metricName), | |||
|  | 			zap.Error(err)) | |||
|  | 		return 0, fmt.Errorf("查询指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if len(metrics) == 0 { | |||
|  | 		return 0, nil | |||
|  | 	} | |||
|  | 
 | |||
|  | 	min := metrics[0].Value | |||
|  | 	for _, metric := range metrics { | |||
|  | 		if metric.Value < min { | |||
|  | 			min = metric.Value | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("计算最小值完成", | |||
|  | 		zap.String("metric_type", metricType), | |||
|  | 		zap.String("metric_name", metricName), | |||
|  | 		zap.Float64("min", min)) | |||
|  | 
 | |||
|  | 	return min, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateGrowthRate 计算增长率 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateGrowthRate(ctx context.Context, metricType, metricName string, currentPeriod, previousPeriod time.Time) (float64, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return 0, fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取当前周期的总值 | |||
|  | 	currentTotal, err := s.CalculateTotal(ctx, metricType, metricName, currentPeriod, currentPeriod.Add(24*time.Hour)) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("计算当前周期总值失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取上一周期的总值 | |||
|  | 	previousTotal, err := s.CalculateTotal(ctx, metricType, metricName, previousPeriod, previousPeriod.Add(24*time.Hour)) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("计算上一周期总值失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 计算增长率 | |||
|  | 	if previousTotal == 0 { | |||
|  | 		if currentTotal > 0 { | |||
|  | 			return 100, nil // 从0增长到正数,增长率为100% | |||
|  | 		} | |||
|  | 		return 0, nil // 都是0,增长率为0% | |||
|  | 	} | |||
|  | 
 | |||
|  | 	growthRate := ((currentTotal - previousTotal) / previousTotal) * 100 | |||
|  | 
 | |||
|  | 	s.logger.Info("计算增长率完成", | |||
|  | 		zap.String("metric_type", metricType), | |||
|  | 		zap.String("metric_name", metricName), | |||
|  | 		zap.Float64("growth_rate", growthRate)) | |||
|  | 
 | |||
|  | 	return growthRate, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateTrend 计算趋势 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateTrend(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (string, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return "", fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.String("metric_name", metricName), | |||
|  | 			zap.Error(err)) | |||
|  | 		return "", fmt.Errorf("查询指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if len(metrics) < 2 { | |||
|  | 		return "insufficient_data", nil // 数据不足 | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 按时间排序 | |||
|  | 	sortMetricsByDateCalc(metrics) | |||
|  | 
 | |||
|  | 	// 计算趋势 | |||
|  | 	firstValue := metrics[0].Value | |||
|  | 	lastValue := metrics[len(metrics)-1].Value | |||
|  | 
 | |||
|  | 	var trend string | |||
|  | 	if lastValue > firstValue { | |||
|  | 		trend = "increasing" // 上升趋势 | |||
|  | 	} else if lastValue < firstValue { | |||
|  | 		trend = "decreasing" // 下降趋势 | |||
|  | 	} else { | |||
|  | 		trend = "stable" // 稳定趋势 | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("计算趋势完成", | |||
|  | 		zap.String("metric_type", metricType), | |||
|  | 		zap.String("metric_name", metricName), | |||
|  | 		zap.String("trend", trend)) | |||
|  | 
 | |||
|  | 	return trend, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateCorrelation 计算相关性 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateCorrelation(ctx context.Context, metricType1, metricName1, metricType2, metricName2 string, startDate, endDate time.Time) (float64, error) { | |||
|  | 	if metricType1 == "" || metricName1 == "" || metricType2 == "" || metricName2 == "" { | |||
|  | 		return 0, fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取两个指标的数据 | |||
|  | 	metrics1, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType1, metricName1, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("查询指标1失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics2, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType2, metricName2, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("查询指标2失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if len(metrics1) != len(metrics2) || len(metrics1) < 2 { | |||
|  | 		return 0, fmt.Errorf("数据点数量不足或不对称") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 计算皮尔逊相关系数 | |||
|  | 	correlation := s.calculatePearsonCorrelation(metrics1, metrics2) | |||
|  | 
 | |||
|  | 	s.logger.Info("计算相关性完成", | |||
|  | 		zap.String("metric1", metricType1+"."+metricName1), | |||
|  | 		zap.String("metric2", metricType2+"."+metricName2), | |||
|  | 		zap.Float64("correlation", correlation)) | |||
|  | 
 | |||
|  | 	return correlation, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateSuccessRate 计算成功率 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateSuccessRate(ctx context.Context, startDate, endDate time.Time) (float64, error) { | |||
|  | 	// 获取成功调用次数 | |||
|  | 	successTotal, err := s.CalculateTotal(ctx, "api_calls", "success_count", startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("计算成功调用次数失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取总调用次数 | |||
|  | 	totalCalls, err := s.CalculateTotal(ctx, "api_calls", "total_count", startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("计算总调用次数失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if totalCalls == 0 { | |||
|  | 		return 0, nil | |||
|  | 	} | |||
|  | 
 | |||
|  | 	successRate := (successTotal / totalCalls) * 100 | |||
|  | 
 | |||
|  | 	s.logger.Info("计算成功率完成", | |||
|  | 		zap.Float64("success_rate", successRate)) | |||
|  | 
 | |||
|  | 	return successRate, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateConversionRate 计算转化率 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateConversionRate(ctx context.Context, startDate, endDate time.Time) (float64, error) { | |||
|  | 	// 获取认证用户数 | |||
|  | 	certifiedUsers, err := s.CalculateTotal(ctx, "users", "certified_count", startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("计算认证用户数失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取总用户数 | |||
|  | 	totalUsers, err := s.CalculateTotal(ctx, "users", "total_count", startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("计算总用户数失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if totalUsers == 0 { | |||
|  | 		return 0, nil | |||
|  | 	} | |||
|  | 
 | |||
|  | 	conversionRate := (certifiedUsers / totalUsers) * 100 | |||
|  | 
 | |||
|  | 	s.logger.Info("计算转化率完成", | |||
|  | 		zap.Float64("conversion_rate", conversionRate)) | |||
|  | 
 | |||
|  | 	return conversionRate, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateRetentionRate 计算留存率 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateRetentionRate(ctx context.Context, startDate, endDate time.Time) (float64, error) { | |||
|  | 	// 获取活跃用户数 | |||
|  | 	activeUsers, err := s.CalculateTotal(ctx, "users", "active_count", startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("计算活跃用户数失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取总用户数 | |||
|  | 	totalUsers, err := s.CalculateTotal(ctx, "users", "total_count", startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("计算总用户数失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if totalUsers == 0 { | |||
|  | 		return 0, nil | |||
|  | 	} | |||
|  | 
 | |||
|  | 	retentionRate := (activeUsers / totalUsers) * 100 | |||
|  | 
 | |||
|  | 	s.logger.Info("计算留存率完成", | |||
|  | 		zap.Float64("retention_rate", retentionRate)) | |||
|  | 
 | |||
|  | 	return retentionRate, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateMovingAverage 计算移动平均 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateMovingAverage(ctx context.Context, metricType, metricName string, startDate, endDate time.Time, windowSize int) ([]float64, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return nil, fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 	if windowSize <= 0 { | |||
|  | 		return nil, fmt.Errorf("窗口大小必须大于0") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.String("metric_name", metricName), | |||
|  | 			zap.Error(err)) | |||
|  | 		return nil, fmt.Errorf("查询指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if len(metrics) < windowSize { | |||
|  | 		return nil, fmt.Errorf("数据点数量不足") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 按时间排序 | |||
|  | 	sortMetricsByDateCalc(metrics) | |||
|  | 
 | |||
|  | 	// 计算移动平均 | |||
|  | 	var movingAverages []float64 | |||
|  | 	for i := windowSize - 1; i < len(metrics); i++ { | |||
|  | 		var sum float64 | |||
|  | 		for j := i - windowSize + 1; j <= i; j++ { | |||
|  | 			sum += metrics[j].Value | |||
|  | 		} | |||
|  | 		average := sum / float64(windowSize) | |||
|  | 		movingAverages = append(movingAverages, average) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("计算移动平均完成", | |||
|  | 		zap.String("metric_type", metricType), | |||
|  | 		zap.String("metric_name", metricName), | |||
|  | 		zap.Int("window_size", windowSize), | |||
|  | 		zap.Int("result_count", len(movingAverages))) | |||
|  | 
 | |||
|  | 	return movingAverages, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateSeasonality 计算季节性 | |||
|  | func (s *StatisticsCalculationServiceImpl) CalculateSeasonality(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (map[string]float64, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return nil, fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.String("metric_name", metricName), | |||
|  | 			zap.Error(err)) | |||
|  | 		return nil, fmt.Errorf("查询指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if len(metrics) < 7 { | |||
|  | 		return nil, fmt.Errorf("数据点数量不足,至少需要7个数据点") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 按星期几分组 | |||
|  | 	weeklyAverages := make(map[string][]float64) | |||
|  | 	for _, metric := range metrics { | |||
|  | 		weekday := metric.Date.Weekday().String() | |||
|  | 		weeklyAverages[weekday] = append(weeklyAverages[weekday], metric.Value) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 计算每个星期几的平均值 | |||
|  | 	seasonality := make(map[string]float64) | |||
|  | 	for weekday, values := range weeklyAverages { | |||
|  | 		var sum float64 | |||
|  | 		for _, value := range values { | |||
|  | 			sum += value | |||
|  | 		} | |||
|  | 		seasonality[weekday] = sum / float64(len(values)) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("计算季节性完成", | |||
|  | 		zap.String("metric_type", metricType), | |||
|  | 		zap.String("metric_name", metricName), | |||
|  | 		zap.Int("weekday_count", len(seasonality))) | |||
|  | 
 | |||
|  | 	return seasonality, nil | |||
|  | } | |||
|  | 
 | |||
|  | // calculatePearsonCorrelation 计算皮尔逊相关系数 | |||
|  | func (s *StatisticsCalculationServiceImpl) calculatePearsonCorrelation(metrics1, metrics2 []*entities.StatisticsMetric) float64 { | |||
|  | 	n := len(metrics1) | |||
|  | 	if n < 2 { | |||
|  | 		return 0 | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 计算均值 | |||
|  | 	var sum1, sum2 float64 | |||
|  | 	for i := 0; i < n; i++ { | |||
|  | 		sum1 += metrics1[i].Value | |||
|  | 		sum2 += metrics2[i].Value | |||
|  | 	} | |||
|  | 	mean1 := sum1 / float64(n) | |||
|  | 	mean2 := sum2 / float64(n) | |||
|  | 
 | |||
|  | 	// 计算协方差和方差 | |||
|  | 	var numerator, denominator1, denominator2 float64 | |||
|  | 	for i := 0; i < n; i++ { | |||
|  | 		diff1 := metrics1[i].Value - mean1 | |||
|  | 		diff2 := metrics2[i].Value - mean2 | |||
|  | 		numerator += diff1 * diff2 | |||
|  | 		denominator1 += diff1 * diff1 | |||
|  | 		denominator2 += diff2 * diff2 | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 计算相关系数 | |||
|  | 	if denominator1 == 0 || denominator2 == 0 { | |||
|  | 		return 0 | |||
|  | 	} | |||
|  | 
 | |||
|  | 	correlation := numerator / math.Sqrt(denominator1*denominator2) | |||
|  | 	return correlation | |||
|  | } | |||
|  | 
 | |||
|  | // sortMetricsByDateCalc 按日期排序指标 | |||
|  | func sortMetricsByDateCalc(metrics []*entities.StatisticsMetric) { | |||
|  | 	// 简单的冒泡排序 | |||
|  | 	n := len(metrics) | |||
|  | 	for i := 0; i < n-1; i++ { | |||
|  | 		for j := 0; j < n-i-1; j++ { | |||
|  | 			if metrics[j].Date.After(metrics[j+1].Date) { | |||
|  | 				metrics[j], metrics[j+1] = metrics[j+1], metrics[j] | |||
|  | 			} | |||
|  | 		} | |||
|  | 	} | |||
|  | } |