Module: DecisionAgent::Dsl::Operators::FinancialOperators

Defined in:
lib/decision_agent/dsl/operators/financial_operators.rb

Overview

Handles financial calculation operators: compound_interest, present_value, future_value, payment

Class Method Summary collapse

Class Method Details

.compare_financial_result(actual, params) ⇒ Object

Compare financial result



231
232
233
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 231

def self.compare_financial_result(actual, params)
  ConditionEvaluator.compare_financial_result(actual, params)
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
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 8

def self.handle(op, actual_value, expected_value, param_cache: nil, param_cache_mutex: nil)
  case op
  when "compound_interest"
    # Calculates compound interest: A = P(1 + r/n)^(nt)
    return false unless actual_value.is_a?(Numeric)

    params = parse_compound_interest_params(expected_value, param_cache: param_cache, param_cache_mutex: param_cache_mutex)
    return false unless params

    principal = actual_value
    rate = params[:rate]
    periods = params[:periods]
    result = principal * ((1 + (rate / periods))**periods)

    if params[:result]
      (result.round(2) == params[:result].round(2))
    else
      compare_financial_result(result, params)
    end

  when "present_value"
    # Calculates present value: PV = FV / (1 + r)^n
    return false unless actual_value.is_a?(Numeric)

    params = parse_present_value_params(expected_value, param_cache: param_cache, param_cache_mutex: param_cache_mutex)
    return false unless params

    future_value = actual_value
    rate = params[:rate]
    periods = params[:periods]
    present_value = future_value / ((1 + rate)**periods)

    if params[:result]
      (present_value.round(2) == params[:result].round(2))
    else
      compare_financial_result(present_value, params)
    end

  when "future_value"
    # Calculates future value: FV = PV * (1 + r)^n
    return false unless actual_value.is_a?(Numeric)

    params = parse_future_value_params(expected_value, param_cache: param_cache, param_cache_mutex: param_cache_mutex)
    return false unless params

    present_value = actual_value
    rate = params[:rate]
    periods = params[:periods]
    future_value = present_value * ((1 + rate)**periods)

    if params[:result]
      (future_value.round(2) == params[:result].round(2))
    else
      compare_financial_result(future_value, params)
    end

  when "payment"
    # Calculates loan payment: PMT = P * [r(1+r)^n] / [(1+r)^n - 1]
    return false unless actual_value.is_a?(Numeric)

    params = parse_payment_params(expected_value, param_cache: param_cache, param_cache_mutex: param_cache_mutex)
    return false unless params

    principal = actual_value
    rate = params[:rate]
    periods = params[:periods]

    return false if rate <= 0 || periods <= 0

    payment = if rate.zero?
                principal / periods
              else
                principal * (rate * ((1 + rate)**periods)) / (((1 + rate)**periods) - 1)
              end

    if params[:result]
      (payment.round(2) == params[:result].round(2))
    else
      compare_financial_result(payment, params)
    end
  end
  # Returns nil if not handled by this module
end

.parse_compound_interest_params(value, param_cache: nil, param_cache_mutex: nil) ⇒ Object

Parse compound interest parameters



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 93

def self.parse_compound_interest_params(value, param_cache: nil, param_cache_mutex: nil)
  return nil unless value.is_a?(Hash)

  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, "compound_interest")
  cached = cache[cache_key]
  return cached if cached

  mutex.synchronize do
    cache[cache_key] ||= parse_compound_interest_params_impl(normalized)
  end
end

.parse_compound_interest_params_impl(value) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 114

def self.parse_compound_interest_params_impl(value)
  rate = value[:rate] || value["rate"]
  periods = value[:periods] || value["periods"]
  return nil unless rate.is_a?(Numeric) && periods.is_a?(Numeric) && periods.positive?

  {
    rate: rate.to_f,
    periods: periods.to_f,
    result: value[:result] || value["result"],
    compare: value[:compare] || value["compare"],
    threshold: value[:threshold] || value["threshold"]
  }
end

.parse_future_value_params(value, param_cache: nil, param_cache_mutex: nil) ⇒ Object

Parse future value parameters



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 163

def self.parse_future_value_params(value, param_cache: nil, param_cache_mutex: nil)
  return nil unless value.is_a?(Hash)

  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, "future_value")
  cached = cache[cache_key]
  return cached if cached

  mutex.synchronize do
    cache[cache_key] ||= parse_future_value_params_impl(normalized)
  end
end

.parse_future_value_params_impl(value) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 184

def self.parse_future_value_params_impl(value)
  rate = value[:rate] || value["rate"]
  periods = value[:periods] || value["periods"]
  return nil unless rate.is_a?(Numeric) && periods.is_a?(Numeric) && periods.positive?

  {
    rate: rate.to_f,
    periods: periods.to_f,
    result: value[:result] || value["result"]
  }
end

.parse_payment_params(value, param_cache: nil, param_cache_mutex: nil) ⇒ Object

Parse payment parameters



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 197

def self.parse_payment_params(value, param_cache: nil, param_cache_mutex: nil)
  return nil unless value.is_a?(Hash)

  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, "payment")
  cached = cache[cache_key]
  return cached if cached

  mutex.synchronize do
    cache[cache_key] ||= parse_payment_params_impl(normalized)
  end
end

.parse_payment_params_impl(value) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 218

def self.parse_payment_params_impl(value)
  rate = value[:rate] || value["rate"]
  periods = value[:periods] || value["periods"]
  return nil unless rate.is_a?(Numeric) && periods.is_a?(Numeric) && periods.positive?

  {
    rate: rate.to_f,
    periods: periods.to_f,
    result: value[:result] || value["result"]
  }
end

.parse_present_value_params(value, param_cache: nil, param_cache_mutex: nil) ⇒ Object

Parse present value parameters



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 129

def self.parse_present_value_params(value, param_cache: nil, param_cache_mutex: nil)
  return nil unless value.is_a?(Hash)

  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, "present_value")
  cached = cache[cache_key]
  return cached if cached

  mutex.synchronize do
    cache[cache_key] ||= parse_present_value_params_impl(normalized)
  end
end

.parse_present_value_params_impl(value) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
# File 'lib/decision_agent/dsl/operators/financial_operators.rb', line 150

def self.parse_present_value_params_impl(value)
  rate = value[:rate] || value["rate"]
  periods = value[:periods] || value["periods"]
  return nil unless rate.is_a?(Numeric) && periods.is_a?(Numeric) && periods.positive?

  {
    rate: rate.to_f,
    periods: periods.to_f,
    result: value[:result] || value["result"]
  }
end