Class: RailsErrorDashboard::Services::BaselineCalculator

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_error_dashboard/services/baseline_calculator.rb

Overview

Calculates baseline statistics for error types

This service analyzes historical error data to calculate statistical baselines for different time periods (hourly, daily, weekly). These baselines enable anomaly detection by establishing “normal” error behavior.

Statistical methods used:

  • Mean and Standard Deviation

  • 95th and 99th Percentiles

  • Outlier removal (> 3 std devs)

Examples:

BaselineCalculator.calculate_all_baselines
# Calculates baselines for all error types and platforms

Constant Summary collapse

HOURLY_LOOKBACK =

Lookback periods for baseline calculation

4.weeks
DAILY_LOOKBACK =
12.weeks
WEEKLY_LOOKBACK =
1.year
OUTLIER_THRESHOLD =

Outlier threshold (standard deviations)

3

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBaselineCalculator

Returns a new instance of BaselineCalculator.



36
37
38
# File 'lib/rails_error_dashboard/services/baseline_calculator.rb', line 36

def initialize
  @calculated_count = 0
end

Class Method Details

.calculate_all_baselinesObject



28
29
30
# File 'lib/rails_error_dashboard/services/baseline_calculator.rb', line 28

def self.calculate_all_baselines
  new.calculate_all_baselines
end

.calculate_for_error_type(error_type, platform) ⇒ Object



32
33
34
# File 'lib/rails_error_dashboard/services/baseline_calculator.rb', line 32

def self.calculate_for_error_type(error_type, platform)
  new.calculate_for_error_type(error_type, platform)
end

.calculate_statistics(counts) ⇒ Object

Class-level pure algorithm: calculate statistics from counts



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/rails_error_dashboard/services/baseline_calculator.rb', line 168

def self.calculate_statistics(counts)
  return default_stats if counts.empty?

  # Remove outliers
  clean_counts = remove_outliers(counts)
  return default_stats if clean_counts.empty?

  mean = clean_counts.sum.to_f / clean_counts.size
  variance = clean_counts.map { |c| (c - mean)**2 }.sum / clean_counts.size
  std_dev = Math.sqrt(variance)

  sorted = clean_counts.sort
  percentile_95 = percentile(sorted, 95)
  percentile_99 = percentile(sorted, 99)

  {
    mean: mean.round(2),
    std_dev: std_dev.round(2),
    percentile_95: percentile_95.round(2),
    percentile_99: percentile_99.round(2)
  }
end

.default_statsHash

Default statistics for empty datasets

Returns:

  • (Hash)

    Zero-valued statistics hash



230
231
232
233
234
235
236
237
# File 'lib/rails_error_dashboard/services/baseline_calculator.rb', line 230

def self.default_stats
  {
    mean: 0.0,
    std_dev: 0.0,
    percentile_95: 0.0,
    percentile_99: 0.0
  }
end

.percentile(sorted_array, pct) ⇒ Float

Calculate percentile value using linear interpolation

Parameters:

  • sorted_array (Array)

    Sorted array of numbers

  • pct (Integer)

    Percentile to calculate (0-100)

Returns:

  • (Float)

    Percentile value



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/rails_error_dashboard/services/baseline_calculator.rb', line 209

def self.percentile(sorted_array, pct)
  return 0 if sorted_array.empty?
  return sorted_array.first if sorted_array.size == 1

  rank = (pct / 100.0) * (sorted_array.size - 1)
  lower_index = rank.floor
  upper_index = rank.ceil

  if lower_index == upper_index
    sorted_array[lower_index].to_f
  else
    # Linear interpolation
    lower_value = sorted_array[lower_index]
    upper_value = sorted_array[upper_index]
    fraction = rank - lower_index
    lower_value + (upper_value - lower_value) * fraction
  end
end

.remove_outliers(counts) ⇒ Array<Integer>

Remove outliers from counts (values > 3 std devs from mean)

Parameters:

  • counts (Array<Integer>)

    Raw counts

Returns:

  • (Array<Integer>)

    Counts with outliers removed



194
195
196
197
198
199
200
201
202
203
# File 'lib/rails_error_dashboard/services/baseline_calculator.rb', line 194

def self.remove_outliers(counts)
  return counts if counts.size < 3

  mean = counts.sum.to_f / counts.size
  variance = counts.map { |c| (c - mean)**2 }.sum / counts.size
  std_dev = Math.sqrt(variance)

  # Remove values more than OUTLIER_THRESHOLD std devs from mean
  counts.select { |c| (c - mean).abs <= (OUTLIER_THRESHOLD * std_dev) }
end

Instance Method Details

#calculate_all_baselinesHash

Calculate baselines for all error types and platforms

Returns:

  • (Hash)

    Summary of calculated baselines



42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/rails_error_dashboard/services/baseline_calculator.rb', line 42

def calculate_all_baselines
  return { calculated: 0, message: "ErrorBaseline table not available" } unless can_calculate?

  # Get all unique combinations of error_type and platform
  combinations = ErrorLog.distinct.pluck(:error_type, :platform).compact

  combinations.each do |(error_type, platform)|
    calculate_for_error_type(error_type, platform)
  end

  { calculated: @calculated_count }
end

#calculate_for_error_type(error_type, platform) ⇒ Hash

Calculate baselines for a specific error type and platform

Parameters:

  • error_type (String)

    The error type

  • platform (String)

    The platform

Returns:

  • (Hash)

    Summary with hourly, daily, weekly baseline info



59
60
61
62
63
64
65
66
67
# File 'lib/rails_error_dashboard/services/baseline_calculator.rb', line 59

def calculate_for_error_type(error_type, platform)
  return {} unless can_calculate?

  {
    hourly: calculate_hourly_baseline(error_type, platform),
    daily: calculate_daily_baseline(error_type, platform),
    weekly: calculate_weekly_baseline(error_type, platform)
  }
end