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,282 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "decision_agent/dmn/decision_graph"
5
-
6
- RSpec.describe DecisionAgent::Dmn::DecisionGraph do
7
- describe "graph construction" do
8
- let(:graph) do
9
- described_class.new(id: "graph1", name: "Test Graph")
10
- end
11
-
12
- it "creates an empty graph" do
13
- expect(graph.decisions).to be_empty
14
- end
15
-
16
- it "adds decisions to the graph" do
17
- decision = DecisionAgent::Dmn::DecisionNode.new(
18
- id: "decision1",
19
- name: "First Decision"
20
- )
21
- graph.add_decision(decision)
22
-
23
- expect(graph.decisions["decision1"]).to eq(decision)
24
- end
25
-
26
- it "retrieves decisions by id" do
27
- decision = DecisionAgent::Dmn::DecisionNode.new(id: "decision1", name: "Test")
28
- graph.add_decision(decision)
29
-
30
- retrieved = graph.get_decision("decision1")
31
- expect(retrieved).to eq(decision)
32
- end
33
- end
34
-
35
- describe "decision dependencies" do
36
- let(:graph) do
37
- graph = described_class.new(id: "dep_graph", name: "Dependency Graph")
38
-
39
- # Create decisions
40
- decision1 = DecisionAgent::Dmn::DecisionNode.new(
41
- id: "base_rate",
42
- name: "Base Rate",
43
- decision_logic: 0.05
44
- )
45
-
46
- decision2 = DecisionAgent::Dmn::DecisionNode.new(
47
- id: "risk_adjustment",
48
- name: "Risk Adjustment",
49
- decision_logic: 0.02
50
- )
51
-
52
- decision3 = DecisionAgent::Dmn::DecisionNode.new(
53
- id: "final_rate",
54
- name: "Final Rate",
55
- decision_logic: ->(context) { context["base_rate"] + context["risk_adjustment"] }
56
- )
57
-
58
- # Add dependencies
59
- decision3.add_dependency("base_rate")
60
- decision3.add_dependency("risk_adjustment")
61
-
62
- graph.add_decision(decision1)
63
- graph.add_decision(decision2)
64
- graph.add_decision(decision3)
65
-
66
- graph
67
- end
68
-
69
- it "tracks decision dependencies" do
70
- decision = graph.get_decision("final_rate")
71
- expect(decision.information_requirements.length).to eq(2)
72
- expect(decision.depends_on?("base_rate")).to be true
73
- expect(decision.depends_on?("risk_adjustment")).to be true
74
- end
75
-
76
- it "evaluates decision with dependencies" do
77
- result = graph.evaluate("final_rate", {})
78
- expect(result).to eq(0.07) # 0.05 + 0.02
79
- end
80
- end
81
-
82
- describe "topological ordering" do
83
- let(:graph) do
84
- graph = described_class.new(id: "topo_graph", name: "Topological Graph")
85
-
86
- # Create a dependency chain: A -> B -> C
87
- decision_a = DecisionAgent::Dmn::DecisionNode.new(id: "a", name: "A", decision_logic: 1)
88
- decision_b = DecisionAgent::Dmn::DecisionNode.new(id: "b", name: "B", decision_logic: ->(ctx) { ctx["a"] + 1 })
89
- decision_c = DecisionAgent::Dmn::DecisionNode.new(id: "c", name: "C", decision_logic: ->(ctx) { ctx["b"] + 1 })
90
-
91
- decision_b.add_dependency("a")
92
- decision_c.add_dependency("b")
93
-
94
- graph.add_decision(decision_a)
95
- graph.add_decision(decision_b)
96
- graph.add_decision(decision_c)
97
-
98
- graph
99
- end
100
-
101
- it "returns decisions in topological order" do
102
- order = graph.topological_order
103
- expect(order.index("a")).to be < order.index("b")
104
- expect(order.index("b")).to be < order.index("c")
105
- end
106
-
107
- it "identifies root decisions" do
108
- roots = graph.root_decisions
109
- expect(roots).to eq(["a"])
110
- end
111
-
112
- it "identifies leaf decisions" do
113
- leaves = graph.leaf_decisions
114
- expect(leaves).to eq(["c"])
115
- end
116
- end
117
-
118
- describe "circular dependency detection" do
119
- it "detects circular dependencies" do
120
- graph = described_class.new(id: "circular", name: "Circular Graph")
121
-
122
- decision_a = DecisionAgent::Dmn::DecisionNode.new(id: "a", name: "A")
123
- decision_b = DecisionAgent::Dmn::DecisionNode.new(id: "b", name: "B")
124
-
125
- decision_a.add_dependency("b")
126
- decision_b.add_dependency("a")
127
-
128
- graph.add_decision(decision_a)
129
- graph.add_decision(decision_b)
130
-
131
- expect(graph.circular_dependencies?).to be true
132
- end
133
-
134
- it "raises error when evaluating circular dependencies" do
135
- graph = described_class.new(id: "circular", name: "Circular Graph")
136
-
137
- decision_a = DecisionAgent::Dmn::DecisionNode.new(id: "a", name: "A")
138
- decision_b = DecisionAgent::Dmn::DecisionNode.new(id: "b", name: "B")
139
-
140
- decision_a.add_dependency("b")
141
- decision_b.add_dependency("a")
142
-
143
- graph.add_decision(decision_a)
144
- graph.add_decision(decision_b)
145
-
146
- expect { graph.topological_order }.to raise_error(DecisionAgent::Dmn::DmnError, /Circular dependency/)
147
- end
148
- end
149
-
150
- describe "complex graph evaluation" do
151
- let(:graph) do
152
- # Build a loan approval graph
153
- # Decisions: income_check -> credit_check -> final_decision
154
- graph = described_class.new(id: "loan_graph", name: "Loan Approval Graph")
155
-
156
- income_check = DecisionAgent::Dmn::DecisionNode.new(
157
- id: "income_check",
158
- name: "Income Check",
159
- decision_logic: ->(ctx) { ctx["income"] >= 50_000 ? "sufficient" : "insufficient" }
160
- )
161
-
162
- credit_check = DecisionAgent::Dmn::DecisionNode.new(
163
- id: "credit_check",
164
- name: "Credit Check",
165
- decision_logic: ->(ctx) { ctx["credit_score"] >= 650 ? "good" : "poor" }
166
- )
167
-
168
- final_decision = DecisionAgent::Dmn::DecisionNode.new(
169
- id: "final_decision",
170
- name: "Final Decision",
171
- decision_logic: lambda do |ctx|
172
- if ctx["income_check"] == "sufficient" && ctx["credit_check"] == "good"
173
- "Approved"
174
- else
175
- "Rejected"
176
- end
177
- end
178
- )
179
-
180
- final_decision.add_dependency("income_check", "income_check")
181
- final_decision.add_dependency("credit_check", "credit_check")
182
-
183
- graph.add_decision(income_check)
184
- graph.add_decision(credit_check)
185
- graph.add_decision(final_decision)
186
-
187
- graph
188
- end
189
-
190
- it "evaluates graph with all dependencies for approved case" do
191
- context = { income: 60_000, credit_score: 700 }
192
- result = graph.evaluate("final_decision", context)
193
- expect(result).to eq("Approved")
194
- end
195
-
196
- it "evaluates graph with all dependencies for rejected case" do
197
- context = { income: 40_000, credit_score: 600 }
198
- result = graph.evaluate("final_decision", context)
199
- expect(result).to eq("Rejected")
200
- end
201
-
202
- it "evaluates all decisions in graph" do
203
- context = { income: 60_000, credit_score: 700 }
204
- results = graph.evaluate_all(context)
205
-
206
- expect(results["income_check"]).to eq("sufficient")
207
- expect(results["credit_check"]).to eq("good")
208
- expect(results["final_decision"]).to eq("Approved")
209
- end
210
- end
211
-
212
- describe "graph analysis" do
213
- let(:graph) do
214
- graph = described_class.new(id: "analysis", name: "Analysis Graph")
215
-
216
- d1 = DecisionAgent::Dmn::DecisionNode.new(id: "d1", name: "D1", decision_logic: 1)
217
- d2 = DecisionAgent::Dmn::DecisionNode.new(id: "d2", name: "D2", decision_logic: 2)
218
- d3 = DecisionAgent::Dmn::DecisionNode.new(id: "d3", name: "D3", decision_logic: 3)
219
-
220
- d3.add_dependency("d1")
221
- d3.add_dependency("d2")
222
-
223
- graph.add_decision(d1)
224
- graph.add_decision(d2)
225
- graph.add_decision(d3)
226
-
227
- graph
228
- end
229
-
230
- it "exports dependency graph structure" do
231
- dep_graph = graph.dependency_graph
232
- expect(dep_graph["d1"]).to eq([])
233
- expect(dep_graph["d2"]).to eq([])
234
- expect(dep_graph["d3"]).to contain_exactly("d1", "d2")
235
- end
236
-
237
- it "exports graph to hash representation" do
238
- hash = graph.to_h
239
- expect(hash[:id]).to eq("analysis")
240
- expect(hash[:name]).to eq("Analysis Graph")
241
- expect(hash[:decisions].keys).to contain_exactly("d1", "d2", "d3")
242
- expect(hash[:dependency_graph]).to be_a(Hash)
243
- end
244
- end
245
-
246
- describe DecisionAgent::Dmn::DecisionNode do
247
- it "creates decision node with basic attributes" do
248
- node = described_class.new(id: "test", name: "Test Decision")
249
- expect(node.id).to eq("test")
250
- expect(node.name).to eq("Test Decision")
251
- expect(node.information_requirements).to be_empty
252
- end
253
-
254
- it "adds dependencies" do
255
- node = described_class.new(id: "test", name: "Test")
256
- node.add_dependency("dep1", "variable1")
257
-
258
- expect(node.information_requirements.length).to eq(1)
259
- expect(node.information_requirements.first[:decision_id]).to eq("dep1")
260
- expect(node.information_requirements.first[:variable_name]).to eq("variable1")
261
- end
262
-
263
- it "checks if node depends on another decision" do
264
- node = described_class.new(id: "test", name: "Test")
265
- node.add_dependency("dep1")
266
-
267
- expect(node.depends_on?("dep1")).to be true
268
- expect(node.depends_on?("dep2")).to be false
269
- end
270
-
271
- it "resets evaluation state" do
272
- node = described_class.new(id: "test", name: "Test")
273
- node.value = "some value"
274
- node.evaluated = true
275
-
276
- node.reset!
277
-
278
- expect(node.value).to be_nil
279
- expect(node.evaluated).to be false
280
- end
281
- end
282
- end
@@ -1,203 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "decision_agent/dmn/decision_tree"
5
-
6
- RSpec.describe DecisionAgent::Dmn::DecisionTree do
7
- describe "basic tree structure" do
8
- let(:tree) do
9
- described_class.new(id: "tree1", name: "Loan Approval Tree")
10
- end
11
-
12
- it "creates a tree with root node" do
13
- expect(tree.root).to be_a(DecisionAgent::Dmn::TreeNode)
14
- expect(tree.root.id).to eq("root")
15
- end
16
-
17
- it "allows adding children to nodes" do
18
- child1 = DecisionAgent::Dmn::TreeNode.new(id: "child1", label: "Check Age")
19
- tree.root.add_child(child1)
20
-
21
- expect(tree.root.children).to include(child1)
22
- expect(child1.parent).to eq(tree.root)
23
- end
24
- end
25
-
26
- describe "tree evaluation" do
27
- let(:tree) do
28
- # Build a simple decision tree for loan approval
29
- tree = described_class.new(id: "loan_tree", name: "Loan Approval")
30
-
31
- # Root node
32
- root = tree.root
33
-
34
- # First level - check age
35
- age_check = DecisionAgent::Dmn::TreeNode.new(
36
- id: "age_check",
37
- label: "Age >= 18?",
38
- condition: "age >= 18"
39
- )
40
- root.add_child(age_check)
41
-
42
- # Second level under age check - check credit score
43
- good_credit = DecisionAgent::Dmn::TreeNode.new(
44
- id: "good_credit",
45
- label: "Credit Score >= 650?",
46
- condition: "credit_score >= 650"
47
- )
48
- age_check.add_child(good_credit)
49
-
50
- # Leaf nodes - decisions
51
- approved = DecisionAgent::Dmn::TreeNode.new(
52
- id: "approved",
53
- label: "Approved",
54
- decision: "Approved"
55
- )
56
- good_credit.add_child(approved)
57
-
58
- rejected_credit = DecisionAgent::Dmn::TreeNode.new(
59
- id: "rejected_credit",
60
- label: "Rejected - Poor Credit",
61
- decision: "Rejected - Poor Credit"
62
- )
63
- good_credit.add_child(rejected_credit)
64
-
65
- # Rejected for age
66
- rejected_age = DecisionAgent::Dmn::TreeNode.new(
67
- id: "rejected_age",
68
- label: "Rejected - Too Young",
69
- decision: "Rejected - Too Young"
70
- )
71
- root.add_child(rejected_age)
72
-
73
- tree
74
- end
75
-
76
- it "evaluates tree with context matching approved path" do
77
- context = { age: 25, credit_score: 700 }
78
- result = tree.evaluate(context)
79
- expect(result).to eq("Approved")
80
- end
81
-
82
- it "evaluates tree with poor credit" do
83
- context = { age: 25, credit_score: 600 }
84
- result = tree.evaluate(context)
85
- expect(result).to eq("Rejected - Poor Credit")
86
- end
87
-
88
- it "evaluates tree with age too young" do
89
- context = { age: 16, credit_score: 700 }
90
- result = tree.evaluate(context)
91
- expect(result).to eq("Rejected - Too Young")
92
- end
93
-
94
- it "returns nil when no path matches" do
95
- context = {}
96
- result = tree.evaluate(context)
97
- expect(result).to be_nil
98
- end
99
- end
100
-
101
- describe "tree serialization" do
102
- let(:tree_hash) do
103
- {
104
- id: "test_tree",
105
- name: "Test Tree",
106
- root: {
107
- id: "root",
108
- label: "Root",
109
- condition: nil,
110
- decision: nil,
111
- children: [
112
- {
113
- id: "node1",
114
- label: "Node 1",
115
- condition: "x > 5",
116
- decision: nil,
117
- children: [
118
- {
119
- id: "leaf1",
120
- label: "Leaf 1",
121
- condition: nil,
122
- decision: "Result A",
123
- children: []
124
- }
125
- ]
126
- }
127
- ]
128
- }
129
- }
130
- end
131
-
132
- it "converts tree to hash" do
133
- tree = described_class.new(id: "tree1", name: "Tree 1")
134
- node1 = DecisionAgent::Dmn::TreeNode.new(id: "node1", condition: "x > 5")
135
- tree.root.add_child(node1)
136
-
137
- hash = tree.to_h
138
- expect(hash[:id]).to eq("tree1")
139
- expect(hash[:name]).to eq("Tree 1")
140
- expect(hash[:root][:children].length).to eq(1)
141
- end
142
-
143
- it "builds tree from hash" do
144
- tree = described_class.from_hash(tree_hash)
145
-
146
- expect(tree.id).to eq("test_tree")
147
- expect(tree.name).to eq("Test Tree")
148
- expect(tree.root.children.length).to eq(1)
149
- expect(tree.root.children.first.id).to eq("node1")
150
- end
151
- end
152
-
153
- describe "tree analysis" do
154
- let(:tree) do
155
- tree = described_class.new(id: "analysis_tree", name: "Analysis Tree")
156
-
157
- level1 = DecisionAgent::Dmn::TreeNode.new(id: "level1")
158
- tree.root.add_child(level1)
159
-
160
- level2a = DecisionAgent::Dmn::TreeNode.new(id: "level2a")
161
- level2b = DecisionAgent::Dmn::TreeNode.new(id: "level2b")
162
- level1.add_child(level2a)
163
- level1.add_child(level2b)
164
-
165
- leaf1 = DecisionAgent::Dmn::TreeNode.new(id: "leaf1", decision: "Result 1")
166
- leaf2 = DecisionAgent::Dmn::TreeNode.new(id: "leaf2", decision: "Result 2")
167
- level2a.add_child(leaf1)
168
- level2b.add_child(leaf2)
169
-
170
- tree
171
- end
172
-
173
- it "collects all leaf nodes" do
174
- leaves = tree.leaf_nodes
175
- expect(leaves.length).to eq(2)
176
- expect(leaves.map(&:id)).to include("leaf1", "leaf2")
177
- end
178
-
179
- it "calculates tree depth" do
180
- expect(tree.depth).to eq(3) # root -> level1 -> level2 -> leaf (depth 3)
181
- end
182
-
183
- it "collects all paths from root to leaves" do
184
- paths = tree.paths
185
- expect(paths.length).to eq(2)
186
- expect(paths.first.length).to be >= 3
187
- end
188
- end
189
-
190
- describe DecisionAgent::Dmn::TreeNode do
191
- it "identifies leaf nodes correctly" do
192
- node = DecisionAgent::Dmn::TreeNode.new(id: "test", decision: "Decision")
193
- expect(node.leaf?).to be true
194
- end
195
-
196
- it "identifies non-leaf nodes correctly" do
197
- node = DecisionAgent::Dmn::TreeNode.new(id: "test")
198
- child = DecisionAgent::Dmn::TreeNode.new(id: "child")
199
- node.add_child(child)
200
- expect(node.leaf?).to be false
201
- end
202
- end
203
- end
@@ -1,18 +0,0 @@
1
- require "spec_helper"
2
- require "decision_agent/dmn/errors"
3
-
4
- RSpec.describe "FEEL Errors" do
5
- describe DecisionAgent::Dmn::FeelParseError do
6
- it "creates error with message only" do
7
- error = DecisionAgent::Dmn::FeelParseError.new("Parse failed")
8
- expect(error.message).to eq("Parse failed")
9
- end
10
- end
11
-
12
- describe DecisionAgent::Dmn::FeelEvaluationError do
13
- it "creates error with message only" do
14
- error = DecisionAgent::Dmn::FeelEvaluationError.new("Evaluation failed")
15
- expect(error.message).to eq("Evaluation failed")
16
- end
17
- end
18
- end