Module: DecisionAgent::Dsl::Operators::StatisticalAggregations
- Defined in:
- lib/decision_agent/dsl/operators/statistical_aggregations.rb
Overview
Handles statistical aggregation operators: min, max, sum, average, median, stddev, variance, percentile, count
Class Method Summary collapse
-
.compare_percentile_result(actual, params) ⇒ Object
Compare percentile result.
- .handle(op, actual_value, expected_value, param_cache: nil, param_cache_mutex: nil) ⇒ Object
-
.parse_percentile_params(value, param_cache: nil, param_cache_mutex: nil) ⇒ Object
Parse percentile parameters.
- .parse_percentile_params_impl(value) ⇒ Object
Class Method Details
.compare_percentile_result(actual, params) ⇒ Object
Compare percentile result
174 175 176 177 178 179 180 181 182 183 |
# File 'lib/decision_agent/dsl/operators/statistical_aggregations.rb', line 174 def self.compare_percentile_result(actual, params) result = true result &&= (actual >= params[:threshold]) if params[:threshold] result &&= (actual > params[:gt]) if params[:gt] result &&= (actual < params[:lt]) if params[:lt] result &&= (actual >= params[:gte]) if params[:gte] result &&= (actual <= params[:lte]) if params[:lte] result &&= (actual == params[:eq]) if params[:eq] result end |
.handle(op, actual_value, expected_value, param_cache: nil, param_cache_mutex: nil) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/decision_agent/dsl/operators/statistical_aggregations.rb', line 8 def self.handle(op, actual_value, expected_value, param_cache: nil, param_cache_mutex: nil) case op when "min" # Checks if min(field_value) equals expected_value return false unless actual_value.is_a?(Array) return false if actual_value.empty? return false unless expected_value.is_a?(Numeric) actual_value.min == expected_value when "max" # Checks if max(field_value) equals expected_value return false unless actual_value.is_a?(Array) return false if actual_value.empty? return false unless expected_value.is_a?(Numeric) actual_value.max == expected_value when "sum" # Checks if sum of numeric array equals expected_value return false unless actual_value.is_a?(Array) return false if actual_value.empty? # OPTIMIZE: calculate sum in single pass, filtering as we go sum_value = 0.0 found_numeric = false actual_value.each do |v| if v.is_a?(Numeric) sum_value += v found_numeric = true end end return false unless found_numeric Base.compare_aggregation_result(sum_value, expected_value) when "average", "mean" # Checks if average of numeric array equals expected_value return false unless actual_value.is_a?(Array) return false if actual_value.empty? # OPTIMIZE: calculate sum and count in single pass sum_value = 0.0 count = 0 actual_value.each do |v| if v.is_a?(Numeric) sum_value += v count += 1 end end return false if count.zero? avg_value = sum_value / count Base.compare_aggregation_result(avg_value, expected_value) when "median" # Checks if median of numeric array equals expected_value return false unless actual_value.is_a?(Array) return false if actual_value.empty? numeric_array = actual_value.select { |v| v.is_a?(Numeric) }.sort return false if numeric_array.empty? median_value = if numeric_array.size.odd? numeric_array[numeric_array.size / 2] else (numeric_array[(numeric_array.size / 2) - 1] + numeric_array[numeric_array.size / 2]) / 2.0 end Base.compare_aggregation_result(median_value, expected_value) when "stddev", "standard_deviation" # Checks if standard deviation of numeric array equals expected_value return false unless actual_value.is_a?(Array) return false if actual_value.size < 2 numeric_array = actual_value.select { |v| v.is_a?(Numeric) } return false if numeric_array.size < 2 mean = numeric_array.sum.to_f / numeric_array.size variance = numeric_array.sum { |v| (v - mean)**2 } / numeric_array.size stddev_value = Math.sqrt(variance) Base.compare_aggregation_result(stddev_value, expected_value) when "variance" # Checks if variance of numeric array equals expected_value return false unless actual_value.is_a?(Array) return false if actual_value.size < 2 numeric_array = actual_value.select { |v| v.is_a?(Numeric) } return false if numeric_array.size < 2 mean = numeric_array.sum.to_f / numeric_array.size variance_value = numeric_array.sum { |v| (v - mean)**2 } / numeric_array.size Base.compare_aggregation_result(variance_value, expected_value) when "percentile" # Checks if Nth percentile of numeric array meets threshold return false unless actual_value.is_a?(Array) return false if actual_value.empty? numeric_array = actual_value.select { |v| v.is_a?(Numeric) }.sort return false if numeric_array.empty? params = parse_percentile_params(expected_value, param_cache: param_cache, param_cache_mutex: param_cache_mutex) return false unless params percentile_index = (params[:percentile] / 100.0) * (numeric_array.size - 1) percentile_value = if percentile_index == percentile_index.to_i numeric_array[percentile_index.to_i] else lower = numeric_array[percentile_index.floor] upper = numeric_array[percentile_index.ceil] lower + ((upper - lower) * (percentile_index - percentile_index.floor)) end compare_percentile_result(percentile_value, params) when "count" # Checks if count of array elements meets threshold return false unless actual_value.is_a?(Array) count_value = actual_value.size Base.compare_aggregation_result(count_value, expected_value) end # Returns nil if not handled by this module end |
.parse_percentile_params(value, param_cache: nil, param_cache_mutex: nil) ⇒ Object
Parse percentile parameters
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/decision_agent/dsl/operators/statistical_aggregations.rb', line 136 def self.parse_percentile_params(value, param_cache: nil, param_cache_mutex: nil) return nil unless value.is_a?(Hash) # Normalize to hash (already a hash, but normalize keys) normalized = Base.normalize_params_to_hash(value, []) cache = param_cache mutex = param_cache_mutex if cache.nil? || mutex.nil? cache = ConditionEvaluator.instance_variable_get(:@param_cache) mutex = ConditionEvaluator.instance_variable_get(:@param_cache_mutex) end cache_key = Base.normalize_param_cache_key(normalized, "percentile") cached = cache[cache_key] return cached if cached mutex.synchronize do cache[cache_key] ||= parse_percentile_params_impl(normalized) end end |
.parse_percentile_params_impl(value) ⇒ Object
158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/decision_agent/dsl/operators/statistical_aggregations.rb', line 158 def self.parse_percentile_params_impl(value) percentile = value[:percentile] || value["percentile"] return nil unless percentile.is_a?(Numeric) && percentile >= 0 && percentile <= 100 { percentile: percentile.to_f, threshold: value[:threshold] || value["threshold"], gt: value[:gt] || value["gt"], lt: value[:lt] || value["lt"], gte: value[:gte] || value["gte"], lte: value[:lte] || value["lte"], eq: value[:eq] || value["eq"] } end |