Class: DecisionAgent::Dsl::ConditionEvaluator

Inherits:
Object
  • Object
show all
Defined in:
lib/decision_agent/dsl/condition_evaluator.rb

Overview

Evaluates conditions in the rule DSL against a context

Supports:

  • Field conditions with various operators
  • Nested field access via dot notation (e.g., "user.profile.role")
  • Logical operators (all/any)

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.date_cacheObject (readonly)

Returns the value of attribute date_cache.



55
56
57
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 55

def date_cache
  @date_cache
end

.geospatial_cacheObject (readonly)

Returns the value of attribute geospatial_cache.



55
56
57
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 55

def geospatial_cache
  @geospatial_cache
end

.param_cacheObject (readonly)

Returns the value of attribute param_cache.



55
56
57
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 55

def param_cache
  @param_cache
end

.path_cacheObject (readonly)

Returns the value of attribute path_cache.



55
56
57
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 55

def path_cache
  @path_cache
end

.regex_cacheObject (readonly)

Returns the value of attribute regex_cache.



55
56
57
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 55

def regex_cache
  @regex_cache
end

Class Method Details

.cache_statsObject

Get cache statistics



473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 473

def self.cache_stats
  stats = Helpers::CacheHelpers.cache_stats(
    regex_cache: @regex_cache,
    path_cache: @path_cache,
    date_cache: @date_cache,
    geospatial_cache: @geospatial_cache,
    param_cache: @param_cache
  )
  {
    regex_cache_size: stats[:regex],
    path_cache_size: stats[:path],
    date_cache_size: stats[:date],
    geospatial_cache_size: stats[:geospatial],
    param_cache_size: stats[:param]
  }
end

.clear_caches!Object

Clear all caches (useful for testing or memory management)



462
463
464
465
466
467
468
469
470
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 462

def self.clear_caches!
  Helpers::CacheHelpers.clear_caches!(
    regex_cache: @regex_cache,
    path_cache: @path_cache,
    date_cache: @date_cache,
    geospatial_cache: @geospatial_cache,
    param_cache: @param_cache
  )
end

.comparable?(val1, val2) ⇒ Boolean

Checks if two values can be compared with <, >, <=, >= Allows comparison between numeric types (Float, Integer, etc.) or same string types

Returns:



181
182
183
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 181

def self.comparable?(val1, val2)
  Helpers::UtilityHelpers.comparable?(val1, val2)
end

.compare_aggregation_result(actual, expected) ⇒ Object

Compare aggregation result with expected value (supports hash with comparison operators) Delegates to Base utilities for consistency



333
334
335
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 333

def self.compare_aggregation_result(actual, expected)
  Operators::Base.compare_aggregation_result(actual, expected)
end

.compare_date_result?(actual, target, params) ⇒ Boolean

Compare date result

Returns:



363
364
365
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 363

def self.compare_date_result?(actual, target, params)
  Helpers::ComparisonHelpers.compare_date_result?(actual, target, params)
end

.compare_dates(actual_value, expected_value, operator) ⇒ Object

Compare two dates with given operator Optimized: Early return if values are already Time/Date objects



272
273
274
275
276
277
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 272

def self.compare_dates(actual_value, expected_value, operator)
  Helpers::DateHelpers.compare_dates(
    actual_value, expected_value, operator,
    parse_date: method(:parse_date)
  )
end

.compare_duration_result(actual, params) ⇒ Object

Compare duration result



353
354
355
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 353

def self.compare_duration_result(actual, params)
  Helpers::ComparisonHelpers.compare_duration_result(actual, params)
end

.compare_financial_result(actual, params) ⇒ Object

Compare financial result



411
412
413
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 411

def self.compare_financial_result(actual, params)
  Helpers::ComparisonHelpers.compare_financial_result(actual, params)
end

.compare_length_result(actual, expected) ⇒ Object

Compare length result



421
422
423
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 421

def self.compare_length_result(actual, expected)
  compare_aggregation_result(actual, expected)
end

.compare_moving_window_result(actual, params) ⇒ Object

Compare moving window result



386
387
388
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 386

def self.compare_moving_window_result(actual, params)
  Helpers::ComparisonHelpers.compare_moving_window_result(actual, params)
end

.compare_percentile_result(actual, params) ⇒ Object

Compare percentile result



343
344
345
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 343

def self.compare_percentile_result(actual, params)
  Helpers::ComparisonHelpers.compare_percentile_result(actual, params)
end

.compare_rate_result(actual, expected) ⇒ Object

Compare rate result



376
377
378
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 376

def self.compare_rate_result(actual, expected)
  compare_aggregation_result(actual, expected)
end

.epsilon_equal?(value1, value2, epsilon = 1e-10) ⇒ Boolean

Floating point comparison with epsilon threshold

Returns:



186
187
188
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 186

def self.epsilon_equal?(value1, value2, epsilon = 1e-10)
  Helpers::UtilityHelpers.epsilon_equal?(value1, value2, epsilon)
end

.evaluate(condition, context, enriched_context_hash: nil, trace_collector: nil) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 58

def self.evaluate(condition, context, enriched_context_hash: nil, trace_collector: nil)
  return false unless condition.is_a?(Hash)

  # Use enriched context hash if provided, otherwise create mutable copy
  # This ensures all conditions in the same evaluation share the same enriched hash
  enriched = enriched_context_hash
  enriched ||= context.to_h.dup

  if condition.key?("all")
    evaluate_all(condition["all"], context, enriched_context_hash: enriched, trace_collector: trace_collector)
  elsif condition.key?("any")
    evaluate_any(condition["any"], context, enriched_context_hash: enriched, trace_collector: trace_collector)
  elsif condition.key?("field")
    evaluate_field_condition(condition, context, enriched_context_hash: enriched, trace_collector: trace_collector)
  else
    false
  end
end

.evaluate_all(conditions, context, enriched_context_hash: nil, trace_collector: nil) ⇒ Object

Evaluates 'all' condition - returns true only if ALL sub-conditions are true Empty array returns true (vacuous truth)



79
80
81
82
83
84
85
86
87
88
89
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 79

def self.evaluate_all(conditions, context, enriched_context_hash: nil, trace_collector: nil)
  return true if conditions.is_a?(Array) && conditions.empty?
  return false unless conditions.is_a?(Array)

  # Use enriched context hash if provided, otherwise create mutable copy
  # All conditions share the same enriched hash so data enrichment persists
  enriched = enriched_context_hash
  enriched ||= context.to_h.dup

  conditions.all? { |cond| evaluate(cond, context, enriched_context_hash: enriched, trace_collector: trace_collector) }
end

.evaluate_any(conditions, context, enriched_context_hash: nil, trace_collector: nil) ⇒ Object

Evaluates 'any' condition - returns true if AT LEAST ONE sub-condition is true Empty array returns false (no options to match)



93
94
95
96
97
98
99
100
101
102
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 93

def self.evaluate_any(conditions, context, enriched_context_hash: nil, trace_collector: nil)
  return false unless conditions.is_a?(Array)

  # Use enriched context hash if provided, otherwise create mutable copy
  # All conditions share the same enriched hash so data enrichment persists
  enriched = enriched_context_hash
  enriched ||= context.to_h.dup

  conditions.any? { |cond| evaluate(cond, context, enriched_context_hash: enriched, trace_collector: trace_collector) }
end

.evaluate_field_condition(condition, context, enriched_context_hash: nil, trace_collector: nil) ⇒ Object



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
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 104

def self.evaluate_field_condition(condition, context, enriched_context_hash: nil, trace_collector: nil)
  field = condition["field"]
  op = condition["op"]
  expected_value = condition["value"]

  # Special handling for "don't care" conditions (from DMN "-" entries)
  result = handle_dont_care_condition(field, op, expected_value, trace_collector)
  return result if result == true

  # Use enriched context hash if provided, otherwise create mutable copy
  # This ensures all conditions in the same evaluation share the same enriched hash
  context_hash = enriched_context_hash || context.to_h.dup
  actual_value = get_nested_value(context_hash, field)

  # Try operator mixins to handle the operator
  result = evaluate_operator(op, actual_value, expected_value, context_hash)

  # Add trace if collector is provided
  trace_collector&.add_trace(Explainability::ConditionTrace.new(
    field: field,
    operator: op,
    expected_value: expected_value,
    actual_value: actual_value,
    result: result
  ))

  result
end

.expand_template_params(params, context_hash) ⇒ Object

Expand template parameters (e.g., "{customer{customer.ssn}") from context



191
192
193
194
195
196
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 191

def self.expand_template_params(params, context_hash)
  Helpers::TemplateHelpers.expand_template_params(
    params, context_hash,
    get_nested_value: method(:get_nested_value)
  )
end

.expand_template_value(value, context_hash) ⇒ Object

Expand a single template value



199
200
201
202
203
204
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 199

def self.expand_template_value(value, context_hash)
  Helpers::TemplateHelpers.expand_template_value(
    value, context_hash,
    get_nested_value: method(:get_nested_value)
  )
end

.get_cached_date(date_string) ⇒ Object

Get cached parsed date with fast-path for common formats



446
447
448
449
450
451
452
453
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 446

def self.get_cached_date(date_string)
  Helpers::CacheHelpers.get_cached_date(
    date_string,
    date_cache: @date_cache,
    date_cache_mutex: @date_cache_mutex,
    parse_date_fast: ->(str) { Helpers::DateHelpers.parse_date_fast(str) }
  )
end

.get_cached_distance(point1, point2) ⇒ Object

Get cached distance between two points (with precision rounding for cache key)



315
316
317
318
319
320
321
322
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 315

def self.get_cached_distance(point1, point2)
  Helpers::CacheHelpers.get_cached_distance(
    point1, point2,
    geospatial_cache: @geospatial_cache,
    geospatial_cache_mutex: @geospatial_cache_mutex,
    haversine_distance: method(:haversine_distance)
  )
end

.get_cached_path(key_path) ⇒ Object

Get cached split path



437
438
439
440
441
442
443
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 437

def self.get_cached_path(key_path)
  Helpers::CacheHelpers.get_cached_path(
    key_path,
    path_cache: @path_cache,
    path_cache_mutex: @path_cache_mutex
  )
end

.get_cached_regex(pattern) ⇒ Object

Get or compile regex with caching



428
429
430
431
432
433
434
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 428

def self.get_cached_regex(pattern)
  Helpers::CacheHelpers.get_cached_regex(
    pattern,
    regex_cache: @regex_cache,
    regex_cache_mutex: @regex_cache_mutex
  )
end

.get_nested_value(hash, key_path) ⇒ Object

Retrieves nested values from a hash using dot notation

Examples:

get_nested_value({user: {role: "admin"}}, "user.role") # => "admin"
get_nested_value({user: {role: "admin"}}, "user.missing") # => nil
get_nested_value({user: nil}, "user.role") # => nil

Supports both string and symbol keys in the hash



172
173
174
175
176
177
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 172

def self.get_nested_value(hash, key_path)
  Helpers::UtilityHelpers.get_nested_value(
    hash, key_path,
    get_cached_path: method(:get_cached_path)
  )
end

.haversine_distance(point1, point2) ⇒ Object

Calculate distance between two points using Haversine formula Returns distance in kilometers



310
311
312
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 310

def self.haversine_distance(point1, point2)
  Helpers::GeospatialHelpers.haversine_distance(point1, point2)
end

.normalize_day_of_week(value) ⇒ Object

Normalize day of week to 0-6 (Sunday=0)



280
281
282
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 280

def self.normalize_day_of_week(value)
  Helpers::DateHelpers.normalize_day_of_week(value)
end

.normalize_param_cache_key(value, prefix) ⇒ Object

Normalize parameter value for cache key generation Converts hash keys to symbols for consistency Delegates to Base utilities for consistency



493
494
495
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 493

def self.normalize_param_cache_key(value, prefix)
  Operators::Base.normalize_param_cache_key(value, prefix)
end

.parse_atan2_params(value) ⇒ Object

Parse atan2 parameters Accepts [y, result] or x, result: y Normalizes arrays to hash for better performance with large params



251
252
253
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 251

def self.parse_atan2_params(value)
  Helpers::ParameterParsingHelpers.parse_atan2_params(value)
end

.parse_compound_interest_params(value) ⇒ Object

Parse compound interest parameters



391
392
393
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 391

def self.parse_compound_interest_params(value)
  Helpers::ParameterParsingHelpers.parse_compound_interest_params(value)
end

.parse_coordinates(value) ⇒ Object

Parse coordinates from hash or array Accepts y, lon: x, y, longitude: x, or [lat, lon]



286
287
288
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 286

def self.parse_coordinates(value)
  Helpers::GeospatialHelpers.parse_coordinates(value)
end

.parse_date(value) ⇒ Object

Parse date from string, Time, Date, or DateTime (with caching)



263
264
265
266
267
268
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 263

def self.parse_date(value)
  Helpers::DateHelpers.parse_date(
    value,
    get_cached_date: ->(date_string) { get_cached_date(date_string) }
  )
end

.parse_date_arithmetic_params(value, unit = :days) ⇒ Object

Parse date arithmetic parameters



358
359
360
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 358

def self.parse_date_arithmetic_params(value, unit = :days)
  Helpers::ParameterParsingHelpers.parse_date_arithmetic_params(value, unit)
end

.parse_date_fast(date_string) ⇒ Object

Fast-path date parsing for common formats (ISO8601, etc.) Falls back to Time.parse for other formats



457
458
459
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 457

def self.parse_date_fast(date_string)
  Helpers::DateHelpers.parse_date_fast(date_string)
end

.parse_duration_params(value) ⇒ Object

Parse duration parameters



348
349
350
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 348

def self.parse_duration_params(value)
  Helpers::ParameterParsingHelpers.parse_duration_params(value)
end

.parse_future_value_params(value) ⇒ Object

Parse future value parameters



401
402
403
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 401

def self.parse_future_value_params(value)
  Helpers::ParameterParsingHelpers.parse_future_value_params(value)
end

.parse_gcd_lcm_params(value) ⇒ Object

Parse gcd/lcm parameters Accepts [other, result] or x, result: y Normalizes arrays to hash for better performance with large params



258
259
260
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 258

def self.parse_gcd_lcm_params(value)
  Helpers::ParameterParsingHelpers.parse_gcd_lcm_params(value)
end

.parse_join_params(value) ⇒ Object

Parse join parameters



416
417
418
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 416

def self.parse_join_params(value)
  Helpers::ParameterParsingHelpers.parse_join_params(value)
end

.parse_modulo_params(value) ⇒ Object

Parse modulo parameters Accepts [divisor, remainder] or x, remainder: y Normalizes arrays to hash for better performance with large params



229
230
231
232
233
234
235
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 229

def self.parse_modulo_params(value)
  Helpers::ParameterParsingHelpers.parse_modulo_params(
    value,
    param_cache: @param_cache,
    param_cache_mutex: @param_cache_mutex
  )
end

.parse_modulo_params_impl(value) ⇒ Object



237
238
239
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 237

def self.parse_modulo_params_impl(value)
  Helpers::ParameterParsingHelpers.parse_modulo_params_impl(value)
end

.parse_moving_window_params(value) ⇒ Object

Parse moving window parameters



381
382
383
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 381

def self.parse_moving_window_params(value)
  Helpers::ParameterParsingHelpers.parse_moving_window_params(value)
end

.parse_payment_params(value) ⇒ Object

Parse payment parameters



406
407
408
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 406

def self.parse_payment_params(value)
  Helpers::ParameterParsingHelpers.parse_payment_params(value)
end

.parse_percentile_params(value) ⇒ Object

Parse percentile parameters



338
339
340
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 338

def self.parse_percentile_params(value)
  Helpers::ParameterParsingHelpers.parse_percentile_params(value)
end

.parse_polygon(value) ⇒ Object

Parse polygon vertices Accepts array of coordinate hashes or arrays



301
302
303
304
305
306
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 301

def self.parse_polygon(value)
  Helpers::GeospatialHelpers.parse_polygon(
    value,
    parse_coordinates: method(:parse_coordinates)
  )
end

.parse_power_params(value) ⇒ Object

Parse power parameters Accepts [exponent, result] or x, result: y Normalizes arrays to hash for better performance with large params



244
245
246
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 244

def self.parse_power_params(value)
  Helpers::ParameterParsingHelpers.parse_power_params(value)
end

.parse_present_value_params(value) ⇒ Object

Parse present value parameters



396
397
398
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 396

def self.parse_present_value_params(value)
  Helpers::ParameterParsingHelpers.parse_present_value_params(value)
end

.parse_radius_params(value) ⇒ Object

Parse radius parameters expected_value: {lat: y, lon: x, radius: distance_in_km}



292
293
294
295
296
297
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 292

def self.parse_radius_params(value)
  Helpers::GeospatialHelpers.parse_radius_params(
    value,
    parse_coordinates: method(:parse_coordinates)
  )
end

.parse_range(value) ⇒ Object

Parse range for 'between' operator Accepts [min, max] or x, max: y Normalizes arrays to hash for better performance with large params



214
215
216
217
218
219
220
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 214

def self.parse_range(value)
  Helpers::ParameterParsingHelpers.parse_range(
    value,
    param_cache: @param_cache,
    param_cache_mutex: @param_cache_mutex
  )
end

.parse_range_impl(value) ⇒ Object



222
223
224
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 222

def self.parse_range_impl(value)
  Helpers::ParameterParsingHelpers.parse_range_impl(value)
end

.point_in_polygon?(point, polygon) ⇒ Boolean

Check if point is inside polygon using ray casting algorithm

Returns:



325
326
327
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 325

def self.point_in_polygon?(point, polygon)
  Helpers::GeospatialHelpers.point_in_polygon?(point, polygon)
end

.string_operator?(actual_value, expected_value) ⇒ Boolean

String operator validation

Returns:



207
208
209
# File 'lib/decision_agent/dsl/condition_evaluator.rb', line 207

def self.string_operator?(actual_value, expected_value)
  Helpers::UtilityHelpers.string_operator?(actual_value, expected_value)
end