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]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|