decision_agent 0.3.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +234 -14
  3. data/lib/decision_agent/ab_testing/ab_test.rb +5 -1
  4. data/lib/decision_agent/ab_testing/ab_test_assignment.rb +2 -0
  5. data/lib/decision_agent/ab_testing/ab_test_manager.rb +2 -0
  6. data/lib/decision_agent/ab_testing/ab_testing_agent.rb +2 -0
  7. data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +2 -13
  8. data/lib/decision_agent/ab_testing/storage/adapter.rb +2 -0
  9. data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +2 -0
  10. data/lib/decision_agent/agent.rb +78 -9
  11. data/lib/decision_agent/audit/adapter.rb +2 -0
  12. data/lib/decision_agent/audit/logger_adapter.rb +2 -0
  13. data/lib/decision_agent/audit/null_adapter.rb +2 -0
  14. data/lib/decision_agent/auth/access_audit_logger.rb +2 -0
  15. data/lib/decision_agent/auth/authenticator.rb +2 -0
  16. data/lib/decision_agent/auth/password_reset_manager.rb +2 -0
  17. data/lib/decision_agent/auth/password_reset_token.rb +2 -0
  18. data/lib/decision_agent/auth/permission.rb +2 -0
  19. data/lib/decision_agent/auth/permission_checker.rb +2 -0
  20. data/lib/decision_agent/auth/rbac_adapter.rb +2 -0
  21. data/lib/decision_agent/auth/rbac_config.rb +2 -0
  22. data/lib/decision_agent/auth/role.rb +2 -0
  23. data/lib/decision_agent/auth/session.rb +2 -0
  24. data/lib/decision_agent/auth/session_manager.rb +2 -0
  25. data/lib/decision_agent/auth/user.rb +2 -0
  26. data/lib/decision_agent/context.rb +14 -0
  27. data/lib/decision_agent/decision.rb +113 -4
  28. data/lib/decision_agent/dmn/adapter.rb +2 -0
  29. data/lib/decision_agent/dmn/cache.rb +2 -2
  30. data/lib/decision_agent/dmn/decision_graph.rb +7 -7
  31. data/lib/decision_agent/dmn/decision_tree.rb +16 -8
  32. data/lib/decision_agent/dmn/errors.rb +2 -0
  33. data/lib/decision_agent/dmn/exporter.rb +2 -0
  34. data/lib/decision_agent/dmn/feel/evaluator.rb +130 -114
  35. data/lib/decision_agent/dmn/feel/functions.rb +2 -0
  36. data/lib/decision_agent/dmn/feel/parser.rb +2 -0
  37. data/lib/decision_agent/dmn/feel/simple_parser.rb +98 -77
  38. data/lib/decision_agent/dmn/feel/transformer.rb +56 -102
  39. data/lib/decision_agent/dmn/feel/types.rb +2 -0
  40. data/lib/decision_agent/dmn/importer.rb +2 -0
  41. data/lib/decision_agent/dmn/model.rb +2 -4
  42. data/lib/decision_agent/dmn/parser.rb +2 -0
  43. data/lib/decision_agent/dmn/testing.rb +3 -2
  44. data/lib/decision_agent/dmn/validator.rb +5 -3
  45. data/lib/decision_agent/dmn/visualizer.rb +7 -6
  46. data/lib/decision_agent/dsl/condition_evaluator.rb +242 -1375
  47. data/lib/decision_agent/dsl/helpers/cache_helpers.rb +82 -0
  48. data/lib/decision_agent/dsl/helpers/comparison_helpers.rb +98 -0
  49. data/lib/decision_agent/dsl/helpers/date_helpers.rb +91 -0
  50. data/lib/decision_agent/dsl/helpers/geospatial_helpers.rb +85 -0
  51. data/lib/decision_agent/dsl/helpers/operator_evaluation_helpers.rb +160 -0
  52. data/lib/decision_agent/dsl/helpers/parameter_parsing_helpers.rb +206 -0
  53. data/lib/decision_agent/dsl/helpers/template_helpers.rb +39 -0
  54. data/lib/decision_agent/dsl/helpers/utility_helpers.rb +45 -0
  55. data/lib/decision_agent/dsl/operators/base.rb +70 -0
  56. data/lib/decision_agent/dsl/operators/basic_comparison_operators.rb +80 -0
  57. data/lib/decision_agent/dsl/operators/collection_operators.rb +60 -0
  58. data/lib/decision_agent/dsl/operators/date_arithmetic_operators.rb +206 -0
  59. data/lib/decision_agent/dsl/operators/date_time_operators.rb +47 -0
  60. data/lib/decision_agent/dsl/operators/duration_operators.rb +149 -0
  61. data/lib/decision_agent/dsl/operators/financial_operators.rb +237 -0
  62. data/lib/decision_agent/dsl/operators/geospatial_operators.rb +106 -0
  63. data/lib/decision_agent/dsl/operators/mathematical_operators.rb +234 -0
  64. data/lib/decision_agent/dsl/operators/moving_window_operators.rb +135 -0
  65. data/lib/decision_agent/dsl/operators/numeric_operators.rb +120 -0
  66. data/lib/decision_agent/dsl/operators/rate_operators.rb +65 -0
  67. data/lib/decision_agent/dsl/operators/statistical_aggregations.rb +187 -0
  68. data/lib/decision_agent/dsl/operators/string_aggregations.rb +84 -0
  69. data/lib/decision_agent/dsl/operators/string_operators.rb +72 -0
  70. data/lib/decision_agent/dsl/operators/time_component_operators.rb +72 -0
  71. data/lib/decision_agent/dsl/rule_parser.rb +2 -0
  72. data/lib/decision_agent/dsl/schema_validator.rb +37 -14
  73. data/lib/decision_agent/errors.rb +2 -0
  74. data/lib/decision_agent/evaluation.rb +14 -2
  75. data/lib/decision_agent/evaluators/base.rb +2 -0
  76. data/lib/decision_agent/evaluators/dmn_evaluator.rb +108 -19
  77. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +56 -11
  78. data/lib/decision_agent/evaluators/static_evaluator.rb +2 -0
  79. data/lib/decision_agent/explainability/condition_trace.rb +85 -0
  80. data/lib/decision_agent/explainability/explainability_result.rb +50 -0
  81. data/lib/decision_agent/explainability/rule_trace.rb +41 -0
  82. data/lib/decision_agent/explainability/trace_collector.rb +26 -0
  83. data/lib/decision_agent/monitoring/alert_manager.rb +7 -16
  84. data/lib/decision_agent/monitoring/dashboard_server.rb +383 -250
  85. data/lib/decision_agent/monitoring/metrics_collector.rb +2 -0
  86. data/lib/decision_agent/monitoring/monitored_agent.rb +2 -0
  87. data/lib/decision_agent/monitoring/prometheus_exporter.rb +3 -1
  88. data/lib/decision_agent/replay/replay.rb +4 -1
  89. data/lib/decision_agent/scoring/base.rb +2 -0
  90. data/lib/decision_agent/scoring/consensus.rb +2 -0
  91. data/lib/decision_agent/scoring/max_weight.rb +2 -0
  92. data/lib/decision_agent/scoring/threshold.rb +2 -0
  93. data/lib/decision_agent/scoring/weighted_average.rb +2 -0
  94. data/lib/decision_agent/simulation/errors.rb +20 -0
  95. data/lib/decision_agent/simulation/impact_analyzer.rb +500 -0
  96. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +638 -0
  97. data/lib/decision_agent/simulation/replay_engine.rb +488 -0
  98. data/lib/decision_agent/simulation/scenario_engine.rb +320 -0
  99. data/lib/decision_agent/simulation/scenario_library.rb +165 -0
  100. data/lib/decision_agent/simulation/shadow_test_engine.rb +274 -0
  101. data/lib/decision_agent/simulation/what_if_analyzer.rb +1008 -0
  102. data/lib/decision_agent/simulation.rb +19 -0
  103. data/lib/decision_agent/testing/batch_test_importer.rb +6 -2
  104. data/lib/decision_agent/testing/batch_test_runner.rb +5 -2
  105. data/lib/decision_agent/testing/test_coverage_analyzer.rb +2 -0
  106. data/lib/decision_agent/testing/test_result_comparator.rb +2 -0
  107. data/lib/decision_agent/testing/test_scenario.rb +2 -0
  108. data/lib/decision_agent/version.rb +3 -1
  109. data/lib/decision_agent/versioning/activerecord_adapter.rb +108 -43
  110. data/lib/decision_agent/versioning/adapter.rb +9 -0
  111. data/lib/decision_agent/versioning/file_storage_adapter.rb +19 -6
  112. data/lib/decision_agent/versioning/version_manager.rb +9 -0
  113. data/lib/decision_agent/web/dmn_editor/serialization.rb +74 -0
  114. data/lib/decision_agent/web/dmn_editor/xml_builder.rb +107 -0
  115. data/lib/decision_agent/web/dmn_editor.rb +8 -67
  116. data/lib/decision_agent/web/middleware/auth_middleware.rb +2 -0
  117. data/lib/decision_agent/web/middleware/permission_middleware.rb +3 -1
  118. data/lib/decision_agent/web/public/app.js +186 -26
  119. data/lib/decision_agent/web/public/batch_testing.html +80 -6
  120. data/lib/decision_agent/web/public/dmn-editor.html +2 -2
  121. data/lib/decision_agent/web/public/dmn-editor.js +74 -8
  122. data/lib/decision_agent/web/public/index.html +69 -3
  123. data/lib/decision_agent/web/public/login.html +1 -1
  124. data/lib/decision_agent/web/public/sample_batch.csv +11 -0
  125. data/lib/decision_agent/web/public/sample_impact.csv +11 -0
  126. data/lib/decision_agent/web/public/sample_replay.csv +11 -0
  127. data/lib/decision_agent/web/public/sample_rules.json +118 -0
  128. data/lib/decision_agent/web/public/sample_shadow.csv +11 -0
  129. data/lib/decision_agent/web/public/sample_whatif.csv +11 -0
  130. data/lib/decision_agent/web/public/simulation.html +146 -0
  131. data/lib/decision_agent/web/public/simulation_impact.html +495 -0
  132. data/lib/decision_agent/web/public/simulation_replay.html +547 -0
  133. data/lib/decision_agent/web/public/simulation_shadow.html +561 -0
  134. data/lib/decision_agent/web/public/simulation_whatif.html +549 -0
  135. data/lib/decision_agent/web/public/styles.css +65 -0
  136. data/lib/decision_agent/web/public/users.html +1 -1
  137. data/lib/decision_agent/web/rack_helpers.rb +106 -0
  138. data/lib/decision_agent/web/rack_request_helpers.rb +196 -0
  139. data/lib/decision_agent/web/server.rb +2126 -1374
  140. data/lib/decision_agent.rb +19 -1
  141. data/lib/generators/decision_agent/install/install_generator.rb +2 -0
  142. data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +2 -0
  143. data/lib/generators/decision_agent/install/templates/ab_test_model.rb +2 -0
  144. data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +2 -0
  145. data/lib/generators/decision_agent/install/templates/migration.rb +2 -0
  146. data/lib/generators/decision_agent/install/templates/rule.rb +2 -0
  147. data/lib/generators/decision_agent/install/templates/rule_version.rb +2 -0
  148. metadata +103 -89
  149. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  150. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  151. data/spec/ab_testing/ab_test_spec.rb +0 -270
  152. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -655
  153. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  154. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  155. data/spec/activerecord_thread_safety_spec.rb +0 -553
  156. data/spec/advanced_operators_spec.rb +0 -3150
  157. data/spec/agent_spec.rb +0 -289
  158. data/spec/api_contract_spec.rb +0 -430
  159. data/spec/audit_adapters_spec.rb +0 -92
  160. data/spec/auth/access_audit_logger_spec.rb +0 -394
  161. data/spec/auth/authenticator_spec.rb +0 -112
  162. data/spec/auth/password_reset_spec.rb +0 -294
  163. data/spec/auth/permission_checker_spec.rb +0 -207
  164. data/spec/auth/permission_spec.rb +0 -73
  165. data/spec/auth/rbac_adapter_spec.rb +0 -778
  166. data/spec/auth/rbac_config_spec.rb +0 -82
  167. data/spec/auth/role_spec.rb +0 -51
  168. data/spec/auth/session_manager_spec.rb +0 -172
  169. data/spec/auth/session_spec.rb +0 -112
  170. data/spec/auth/user_spec.rb +0 -130
  171. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  172. data/spec/context_spec.rb +0 -127
  173. data/spec/decision_agent_spec.rb +0 -96
  174. data/spec/decision_spec.rb +0 -423
  175. data/spec/dmn/decision_graph_spec.rb +0 -282
  176. data/spec/dmn/decision_tree_spec.rb +0 -203
  177. data/spec/dmn/feel/errors_spec.rb +0 -18
  178. data/spec/dmn/feel/functions_spec.rb +0 -400
  179. data/spec/dmn/feel/simple_parser_spec.rb +0 -274
  180. data/spec/dmn/feel/types_spec.rb +0 -176
  181. data/spec/dmn/feel_parser_spec.rb +0 -489
  182. data/spec/dmn/hit_policy_spec.rb +0 -202
  183. data/spec/dmn/integration_spec.rb +0 -226
  184. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  185. data/spec/dsl_validation_spec.rb +0 -648
  186. data/spec/edge_cases_spec.rb +0 -353
  187. data/spec/evaluation_spec.rb +0 -364
  188. data/spec/evaluation_validator_spec.rb +0 -165
  189. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  190. data/spec/examples.txt +0 -1909
  191. data/spec/fixtures/dmn/complex_decision.dmn +0 -81
  192. data/spec/fixtures/dmn/invalid_structure.dmn +0 -31
  193. data/spec/fixtures/dmn/simple_decision.dmn +0 -40
  194. data/spec/issue_verification_spec.rb +0 -759
  195. data/spec/json_rule_evaluator_spec.rb +0 -587
  196. data/spec/monitoring/alert_manager_spec.rb +0 -378
  197. data/spec/monitoring/metrics_collector_spec.rb +0 -501
  198. data/spec/monitoring/monitored_agent_spec.rb +0 -225
  199. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  200. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  201. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  202. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  203. data/spec/performance_optimizations_spec.rb +0 -493
  204. data/spec/replay_edge_cases_spec.rb +0 -699
  205. data/spec/replay_spec.rb +0 -210
  206. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  207. data/spec/scoring_spec.rb +0 -225
  208. data/spec/spec_helper.rb +0 -60
  209. data/spec/testing/batch_test_importer_spec.rb +0 -693
  210. data/spec/testing/batch_test_runner_spec.rb +0 -307
  211. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  212. data/spec/testing/test_result_comparator_spec.rb +0 -392
  213. data/spec/testing/test_scenario_spec.rb +0 -113
  214. data/spec/thread_safety_spec.rb +0 -490
  215. data/spec/thread_safety_spec.rb.broken +0 -878
  216. data/spec/versioning/adapter_spec.rb +0 -156
  217. data/spec/versioning_spec.rb +0 -1030
  218. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  219. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  220. data/spec/web_ui_rack_spec.rb +0 -2134
@@ -1,400 +0,0 @@
1
- require "spec_helper"
2
- require "decision_agent/dmn/feel/functions"
3
-
4
- RSpec.describe DecisionAgent::Dmn::Feel::Functions do
5
- describe "String Functions" do
6
- describe "substring" do
7
- it "extracts substring with start and length" do
8
- result = described_class.execute("substring", ["hello world", 1, 5])
9
- expect(result).to eq("hello")
10
- end
11
-
12
- it "extracts substring from start to end" do
13
- result = described_class.execute("substring", ["hello world", 7])
14
- expect(result).to eq("world")
15
- end
16
-
17
- it "handles 1-based indexing" do
18
- result = described_class.execute("substring", ["abc", 2, 1])
19
- expect(result).to eq("b")
20
- end
21
- end
22
-
23
- describe "string length" do
24
- it "returns length of string" do
25
- result = described_class.execute("string length", ["hello"])
26
- expect(result).to eq(5)
27
- end
28
-
29
- it "returns 0 for empty string" do
30
- result = described_class.execute("string length", [""])
31
- expect(result).to eq(0)
32
- end
33
- end
34
-
35
- describe "upper case" do
36
- it "converts to uppercase" do
37
- result = described_class.execute("upper case", ["hello"])
38
- expect(result).to eq("HELLO")
39
- end
40
- end
41
-
42
- describe "lower case" do
43
- it "converts to lowercase" do
44
- result = described_class.execute("lower case", ["HELLO"])
45
- expect(result).to eq("hello")
46
- end
47
- end
48
-
49
- describe "contains" do
50
- it "returns true when substring is found" do
51
- result = described_class.execute("contains", ["hello world", "world"])
52
- expect(result).to be true
53
- end
54
-
55
- it "returns false when substring is not found" do
56
- result = described_class.execute("contains", ["hello world", "xyz"])
57
- expect(result).to be false
58
- end
59
- end
60
-
61
- describe "starts with" do
62
- it "returns true when string starts with prefix" do
63
- result = described_class.execute("starts with", ["hello world", "hello"])
64
- expect(result).to be true
65
- end
66
-
67
- it "returns false when string does not start with prefix" do
68
- result = described_class.execute("starts with", ["hello world", "world"])
69
- expect(result).to be false
70
- end
71
- end
72
-
73
- describe "ends with" do
74
- it "returns true when string ends with suffix" do
75
- result = described_class.execute("ends with", ["hello world", "world"])
76
- expect(result).to be true
77
- end
78
-
79
- it "returns false when string does not end with suffix" do
80
- result = described_class.execute("ends with", ["hello world", "hello"])
81
- expect(result).to be false
82
- end
83
- end
84
-
85
- describe "substring before" do
86
- it "returns substring before match" do
87
- result = described_class.execute("substring before", ["hello world", " "])
88
- expect(result).to eq("hello")
89
- end
90
-
91
- it "returns empty string when match not found" do
92
- result = described_class.execute("substring before", %w[hello x])
93
- expect(result).to eq("")
94
- end
95
- end
96
-
97
- describe "substring after" do
98
- it "returns substring after match" do
99
- result = described_class.execute("substring after", ["hello world", " "])
100
- expect(result).to eq("world")
101
- end
102
-
103
- it "returns empty string when match not found" do
104
- result = described_class.execute("substring after", %w[hello x])
105
- expect(result).to eq("")
106
- end
107
- end
108
-
109
- describe "replace" do
110
- it "replaces all occurrences" do
111
- result = described_class.execute("replace", ["hello world", "l", "L"])
112
- expect(result).to eq("heLLo worLd")
113
- end
114
- end
115
- end
116
-
117
- describe "Numeric Functions" do
118
- describe "abs" do
119
- it "returns absolute value of positive number" do
120
- result = described_class.execute("abs", [5])
121
- expect(result).to eq(5.0)
122
- end
123
-
124
- it "returns absolute value of negative number" do
125
- result = described_class.execute("abs", [-5])
126
- expect(result).to eq(5.0)
127
- end
128
- end
129
-
130
- describe "floor" do
131
- it "rounds down to integer" do
132
- result = described_class.execute("floor", [3.7])
133
- expect(result).to eq(3)
134
- end
135
-
136
- it "handles negative numbers" do
137
- result = described_class.execute("floor", [-3.2])
138
- expect(result).to eq(-4)
139
- end
140
- end
141
-
142
- describe "ceiling" do
143
- it "rounds up to integer" do
144
- result = described_class.execute("ceiling", [3.2])
145
- expect(result).to eq(4)
146
- end
147
-
148
- it "handles negative numbers" do
149
- result = described_class.execute("ceiling", [-3.7])
150
- expect(result).to eq(-3)
151
- end
152
- end
153
-
154
- describe "round" do
155
- it "rounds to nearest integer" do
156
- result = described_class.execute("round", [3.7])
157
- expect(result).to eq(4)
158
- end
159
-
160
- it "rounds to specified precision" do
161
- result = described_class.execute("round", [3.14159, 2])
162
- expect(result).to be_within(0.001).of(3.14)
163
- end
164
- end
165
-
166
- describe "sqrt" do
167
- it "calculates square root" do
168
- result = described_class.execute("sqrt", [16])
169
- expect(result).to eq(4.0)
170
- end
171
- end
172
-
173
- describe "modulo" do
174
- it "calculates remainder" do
175
- result = described_class.execute("modulo", [10, 3])
176
- expect(result).to eq(1.0)
177
- end
178
- end
179
-
180
- describe "odd" do
181
- it "returns true for odd numbers" do
182
- result = described_class.execute("odd", [5])
183
- expect(result).to be true
184
- end
185
-
186
- it "returns false for even numbers" do
187
- result = described_class.execute("odd", [4])
188
- expect(result).to be false
189
- end
190
- end
191
-
192
- describe "even" do
193
- it "returns true for even numbers" do
194
- result = described_class.execute("even", [4])
195
- expect(result).to be true
196
- end
197
-
198
- it "returns false for odd numbers" do
199
- result = described_class.execute("even", [5])
200
- expect(result).to be false
201
- end
202
- end
203
- end
204
-
205
- describe "List Functions" do
206
- describe "count" do
207
- it "returns length of list" do
208
- result = described_class.execute("count", [[1, 2, 3, 4, 5]])
209
- expect(result).to eq(5)
210
- end
211
-
212
- it "returns 0 for empty list" do
213
- result = described_class.execute("count", [[]])
214
- expect(result).to eq(0)
215
- end
216
-
217
- it "returns 0 for non-array" do
218
- result = described_class.execute("count", [42])
219
- expect(result).to eq(0)
220
- end
221
- end
222
-
223
- describe "sum" do
224
- it "calculates sum of list" do
225
- result = described_class.execute("sum", [[1, 2, 3, 4, 5]])
226
- expect(result).to eq(15.0)
227
- end
228
-
229
- it "returns 0 for empty list" do
230
- result = described_class.execute("sum", [[]])
231
- expect(result).to eq(0)
232
- end
233
- end
234
-
235
- describe "mean" do
236
- it "calculates average of list" do
237
- result = described_class.execute("mean", [[1, 2, 3, 4, 5]])
238
- expect(result).to eq(3.0)
239
- end
240
-
241
- it "returns 0 for empty list" do
242
- result = described_class.execute("mean", [[]])
243
- expect(result).to eq(0)
244
- end
245
- end
246
-
247
- describe "min" do
248
- it "returns minimum value from list" do
249
- result = described_class.execute("min", [[5, 2, 8, 1, 9]])
250
- expect(result).to eq(1.0)
251
- end
252
-
253
- it "returns minimum from multiple arguments" do
254
- result = described_class.execute("min", [5, 2, 8, 1, 9])
255
- expect(result).to eq(1.0)
256
- end
257
-
258
- it "returns nil for empty list" do
259
- result = described_class.execute("min", [[]])
260
- expect(result).to be_nil
261
- end
262
- end
263
-
264
- describe "max" do
265
- it "returns maximum value from list" do
266
- result = described_class.execute("max", [[5, 2, 8, 1, 9]])
267
- expect(result).to eq(9.0)
268
- end
269
-
270
- it "returns maximum from multiple arguments" do
271
- result = described_class.execute("max", [5, 2, 8, 1, 9])
272
- expect(result).to eq(9.0)
273
- end
274
- end
275
-
276
- describe "append" do
277
- it "appends items to list" do
278
- result = described_class.execute("append", [[1, 2], 3, 4])
279
- expect(result).to eq([1, 2, 3, 4])
280
- end
281
- end
282
-
283
- describe "reverse" do
284
- it "reverses list" do
285
- result = described_class.execute("reverse", [[1, 2, 3, 4, 5]])
286
- expect(result).to eq([5, 4, 3, 2, 1])
287
- end
288
- end
289
-
290
- describe "index of" do
291
- it "returns 1-based index of element" do
292
- result = described_class.execute("index of", [[10, 20, 30], 20])
293
- expect(result).to eq(2)
294
- end
295
-
296
- it "returns -1 when element not found" do
297
- result = described_class.execute("index of", [[10, 20, 30], 40])
298
- expect(result).to eq(-1)
299
- end
300
- end
301
-
302
- describe "distinct values" do
303
- it "removes duplicates" do
304
- result = described_class.execute("distinct values", [[1, 2, 2, 3, 3, 3]])
305
- expect(result).to eq([1, 2, 3])
306
- end
307
- end
308
- end
309
-
310
- describe "Boolean Functions" do
311
- describe "not" do
312
- it "negates true" do
313
- result = described_class.execute("not", [true])
314
- expect(result).to be false
315
- end
316
-
317
- it "negates false" do
318
- result = described_class.execute("not", [false])
319
- expect(result).to be true
320
- end
321
- end
322
-
323
- describe "all" do
324
- it "returns true when all items are true" do
325
- result = described_class.execute("all", [[true, true, true]])
326
- expect(result).to be true
327
- end
328
-
329
- it "returns false when any item is false" do
330
- result = described_class.execute("all", [[true, false, true]])
331
- expect(result).to be false
332
- end
333
- end
334
-
335
- describe "any" do
336
- it "returns true when any item is true" do
337
- result = described_class.execute("any", [[false, true, false]])
338
- expect(result).to be true
339
- end
340
-
341
- it "returns false when all items are false" do
342
- result = described_class.execute("any", [[false, false, false]])
343
- expect(result).to be false
344
- end
345
- end
346
- end
347
-
348
- describe "Date/Time Functions" do
349
- describe "date" do
350
- it "parses ISO 8601 date string" do
351
- result = described_class.execute("date", ["2024-01-15T10:30:00Z"])
352
- expect(result).to be_a(DecisionAgent::Dmn::Feel::Types::Date)
353
- end
354
- end
355
-
356
- describe "time" do
357
- it "parses ISO 8601 time string" do
358
- result = described_class.execute("time", ["2024-01-15T10:30:00Z"])
359
- expect(result).to be_a(DecisionAgent::Dmn::Feel::Types::Time)
360
- end
361
- end
362
-
363
- describe "duration" do
364
- it "parses ISO 8601 duration" do
365
- result = described_class.execute("duration", ["P1Y2M3DT4H5M6S"])
366
- expect(result).to be_a(DecisionAgent::Dmn::Feel::Types::Duration)
367
- expect(result.years).to eq(1)
368
- expect(result.months).to eq(2)
369
- end
370
- end
371
- end
372
-
373
- describe "Function Registry" do
374
- it "lists all registered functions" do
375
- functions = described_class.list
376
- expect(functions).to include("substring")
377
- expect(functions).to include("sum")
378
- expect(functions).to include("abs")
379
- end
380
-
381
- it "gets function by name" do
382
- func = described_class.get("substring")
383
- expect(func).not_to be_nil
384
- end
385
-
386
- it "raises error for unknown function" do
387
- expect do
388
- described_class.execute("unknown_func", [])
389
- end.to raise_error(DecisionAgent::Dmn::FeelFunctionError, /Unknown function/)
390
- end
391
- end
392
-
393
- describe "Argument Validation" do
394
- it "raises error for wrong argument count" do
395
- expect do
396
- described_class.execute("substring", ["hello"])
397
- end.to raise_error(DecisionAgent::Dmn::FeelFunctionError, /Wrong number of arguments/)
398
- end
399
- end
400
- end
@@ -1,274 +0,0 @@
1
- require "spec_helper"
2
- require "decision_agent/dmn/feel/simple_parser"
3
-
4
- RSpec.describe DecisionAgent::Dmn::Feel::SimpleParser do
5
- let(:parser) { described_class.new }
6
-
7
- describe ".can_parse?" do
8
- it "returns true for simple arithmetic" do
9
- expect(described_class.can_parse?("age + 5")).to be true
10
- expect(described_class.can_parse?("price * 1.1")).to be true
11
- end
12
-
13
- it "returns true for logical expressions" do
14
- expect(described_class.can_parse?("age >= 18 and status = active")).to be true
15
- end
16
-
17
- it "returns false for lists" do
18
- expect(described_class.can_parse?("[1, 2, 3]")).to be false
19
- end
20
-
21
- it "returns false for contexts" do
22
- expect(described_class.can_parse?("{x: 10}")).to be false
23
- end
24
-
25
- it "returns false for functions" do
26
- expect(described_class.can_parse?("sum(scores)")).to be false
27
- end
28
-
29
- it "returns false for quantified expressions" do
30
- expect(described_class.can_parse?("some x in list satisfies x > 5")).to be false
31
- end
32
- end
33
-
34
- describe "#parse" do
35
- describe "literals" do
36
- it "parses integer" do
37
- result = parser.parse("42")
38
- expect(result[:type]).to eq(:literal)
39
- expect(result[:value]).to eq(42)
40
- end
41
-
42
- it "parses float" do
43
- result = parser.parse("3.14")
44
- expect(result[:type]).to eq(:literal)
45
- expect(result[:value]).to eq(3.14)
46
- end
47
-
48
- it "parses negative number" do
49
- result = parser.parse("-10")
50
- expect(result[:type]).to eq(:literal)
51
- expect(result[:value]).to eq(-10)
52
- end
53
-
54
- it "parses string" do
55
- result = parser.parse('"hello"')
56
- expect(result[:type]).to eq(:literal)
57
- expect(result[:value]).to eq("hello")
58
- end
59
-
60
- it "parses boolean true" do
61
- result = parser.parse("true")
62
- expect(result[:type]).to eq(:boolean)
63
- expect(result[:value]).to be true
64
- end
65
-
66
- it "parses boolean false" do
67
- result = parser.parse("false")
68
- expect(result[:type]).to eq(:boolean)
69
- expect(result[:value]).to be false
70
- end
71
- end
72
-
73
- describe "field references" do
74
- it "parses field name" do
75
- result = parser.parse("age")
76
- expect(result[:type]).to eq(:field)
77
- expect(result[:name]).to eq("age")
78
- end
79
- end
80
-
81
- describe "arithmetic operators" do
82
- it "parses addition" do
83
- result = parser.parse("5 + 3")
84
- expect(result[:type]).to eq(:arithmetic)
85
- expect(result[:operator]).to eq("+")
86
- expect(result[:left][:value]).to eq(5)
87
- expect(result[:right][:value]).to eq(3)
88
- end
89
-
90
- it "parses subtraction" do
91
- result = parser.parse("10 - 3")
92
- expect(result[:type]).to eq(:arithmetic)
93
- expect(result[:operator]).to eq("-")
94
- end
95
-
96
- it "parses multiplication" do
97
- result = parser.parse("4 * 5")
98
- expect(result[:type]).to eq(:arithmetic)
99
- expect(result[:operator]).to eq("*")
100
- end
101
-
102
- it "parses division" do
103
- result = parser.parse("20 / 4")
104
- expect(result[:type]).to eq(:arithmetic)
105
- expect(result[:operator]).to eq("/")
106
- end
107
-
108
- it "parses exponentiation" do
109
- result = parser.parse("2 ** 3")
110
- expect(result[:type]).to eq(:arithmetic)
111
- expect(result[:operator]).to eq("**")
112
- end
113
-
114
- it "parses modulo" do
115
- result = parser.parse("10 % 3")
116
- expect(result[:type]).to eq(:arithmetic)
117
- expect(result[:operator]).to eq("%")
118
- end
119
- end
120
-
121
- describe "operator precedence" do
122
- it "respects multiplication before addition" do
123
- result = parser.parse("2 + 3 * 4")
124
- # Should be: 2 + (3 * 4)
125
- expect(result[:operator]).to eq("+")
126
- expect(result[:left][:value]).to eq(2)
127
- expect(result[:right][:operator]).to eq("*")
128
- end
129
-
130
- it "respects exponentiation before multiplication" do
131
- result = parser.parse("2 * 3 ** 2")
132
- # Should be: 2 * (3 ** 2)
133
- expect(result[:operator]).to eq("*")
134
- expect(result[:right][:operator]).to eq("**")
135
- end
136
-
137
- it "handles parentheses" do
138
- result = parser.parse("(2 + 3) * 4")
139
- # Should be: (2 + 3) * 4
140
- expect(result[:operator]).to eq("*")
141
- expect(result[:left][:operator]).to eq("+")
142
- end
143
- end
144
-
145
- describe "comparison operators" do
146
- it "parses greater than or equal" do
147
- result = parser.parse("age >= 18")
148
- expect(result[:type]).to eq(:comparison)
149
- expect(result[:operator]).to eq(">=")
150
- expect(result[:left][:name]).to eq("age")
151
- expect(result[:right][:value]).to eq(18)
152
- end
153
-
154
- it "parses less than or equal" do
155
- result = parser.parse("score <= 100")
156
- expect(result[:type]).to eq(:comparison)
157
- expect(result[:operator]).to eq("<=")
158
- end
159
-
160
- it "parses greater than" do
161
- result = parser.parse("price > 0")
162
- expect(result[:type]).to eq(:comparison)
163
- expect(result[:operator]).to eq(">")
164
- end
165
-
166
- it "parses less than" do
167
- result = parser.parse("age < 65")
168
- expect(result[:type]).to eq(:comparison)
169
- expect(result[:operator]).to eq("<")
170
- end
171
-
172
- it "parses not equal" do
173
- result = parser.parse("status != pending")
174
- expect(result[:type]).to eq(:comparison)
175
- expect(result[:operator]).to eq("!=")
176
- end
177
-
178
- it "parses equal" do
179
- result = parser.parse("status = active")
180
- expect(result[:type]).to eq(:comparison)
181
- expect(result[:operator]).to eq("=")
182
- end
183
- end
184
-
185
- describe "logical operators" do
186
- it "parses AND" do
187
- result = parser.parse("age >= 18 and score > 700")
188
- expect(result[:type]).to eq(:logical)
189
- expect(result[:operator]).to eq("and")
190
- expect(result[:left][:type]).to eq(:comparison)
191
- expect(result[:right][:type]).to eq(:comparison)
192
- end
193
-
194
- it "parses OR" do
195
- result = parser.parse("status = active or status = pending")
196
- expect(result[:type]).to eq(:logical)
197
- expect(result[:operator]).to eq("or")
198
- end
199
-
200
- it "parses NOT" do
201
- result = parser.parse("not active")
202
- expect(result[:type]).to eq(:logical)
203
- expect(result[:operator]).to eq("not")
204
- expect(result[:operand][:name]).to eq("active")
205
- end
206
-
207
- it "respects AND precedence over OR" do
208
- result = parser.parse("a or b and c")
209
- # Should be: a or (b and c)
210
- expect(result[:operator]).to eq("or")
211
- expect(result[:right][:operator]).to eq("and")
212
- end
213
- end
214
-
215
- describe "unary operators" do
216
- it "parses unary minus" do
217
- result = parser.parse("-5")
218
- expect(result[:type]).to eq(:literal)
219
- expect(result[:value]).to eq(-5)
220
- end
221
-
222
- it "parses unary minus with field" do
223
- result = parser.parse("-age")
224
- expect(result[:type]).to eq(:arithmetic)
225
- expect(result[:operator]).to eq("negate")
226
- expect(result[:operand][:name]).to eq("age")
227
- end
228
-
229
- it "parses unary plus (ignored)" do
230
- result = parser.parse("+5")
231
- expect(result[:value]).to eq(5)
232
- end
233
- end
234
-
235
- describe "complex expressions" do
236
- it "parses arithmetic with comparison" do
237
- result = parser.parse("age + 5 >= 18")
238
- expect(result[:type]).to eq(:comparison)
239
- expect(result[:left][:type]).to eq(:arithmetic)
240
- end
241
-
242
- it "parses multiple logical operations" do
243
- result = parser.parse("age >= 18 and age <= 65 and status = active")
244
- expect(result[:type]).to eq(:logical)
245
- expect(result[:operator]).to eq("and")
246
- end
247
-
248
- it "parses nested parentheses" do
249
- result = parser.parse("((age + 5) * 2) >= 40")
250
- expect(result[:type]).to eq(:comparison)
251
- end
252
- end
253
-
254
- describe "error handling" do
255
- it "raises error for empty expression" do
256
- expect do
257
- parser.parse("")
258
- end.to raise_error(DecisionAgent::Dmn::FeelParseError, /Empty expression/)
259
- end
260
-
261
- it "raises error for unbalanced parentheses" do
262
- expect do
263
- parser.parse("(age + 5")
264
- end.to raise_error(DecisionAgent::Dmn::FeelParseError)
265
- end
266
-
267
- it "raises error for unexpected character" do
268
- expect do
269
- parser.parse("age @ 5")
270
- end.to raise_error(DecisionAgent::Dmn::FeelParseError, /Unexpected character/)
271
- end
272
- end
273
- end
274
- end