decision_agent 0.3.0 → 1.0.1

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +272 -7
  3. data/lib/decision_agent/agent.rb +72 -1
  4. data/lib/decision_agent/context.rb +1 -0
  5. data/lib/decision_agent/data_enrichment/cache/memory_adapter.rb +86 -0
  6. data/lib/decision_agent/data_enrichment/cache_adapter.rb +49 -0
  7. data/lib/decision_agent/data_enrichment/circuit_breaker.rb +135 -0
  8. data/lib/decision_agent/data_enrichment/client.rb +220 -0
  9. data/lib/decision_agent/data_enrichment/config.rb +78 -0
  10. data/lib/decision_agent/data_enrichment/errors.rb +36 -0
  11. data/lib/decision_agent/decision.rb +102 -2
  12. data/lib/decision_agent/dmn/feel/evaluator.rb +28 -6
  13. data/lib/decision_agent/dsl/condition_evaluator.rb +982 -839
  14. data/lib/decision_agent/dsl/schema_validator.rb +51 -13
  15. data/lib/decision_agent/evaluators/dmn_evaluator.rb +106 -19
  16. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +69 -9
  17. data/lib/decision_agent/explainability/condition_trace.rb +83 -0
  18. data/lib/decision_agent/explainability/explainability_result.rb +52 -0
  19. data/lib/decision_agent/explainability/rule_trace.rb +39 -0
  20. data/lib/decision_agent/explainability/trace_collector.rb +24 -0
  21. data/lib/decision_agent/monitoring/alert_manager.rb +5 -1
  22. data/lib/decision_agent/simulation/errors.rb +18 -0
  23. data/lib/decision_agent/simulation/impact_analyzer.rb +498 -0
  24. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +635 -0
  25. data/lib/decision_agent/simulation/replay_engine.rb +486 -0
  26. data/lib/decision_agent/simulation/scenario_engine.rb +318 -0
  27. data/lib/decision_agent/simulation/scenario_library.rb +163 -0
  28. data/lib/decision_agent/simulation/shadow_test_engine.rb +287 -0
  29. data/lib/decision_agent/simulation/what_if_analyzer.rb +1002 -0
  30. data/lib/decision_agent/simulation.rb +17 -0
  31. data/lib/decision_agent/version.rb +1 -1
  32. data/lib/decision_agent/versioning/activerecord_adapter.rb +23 -8
  33. data/lib/decision_agent/web/public/app.js +119 -0
  34. data/lib/decision_agent/web/public/index.html +49 -0
  35. data/lib/decision_agent/web/public/simulation.html +130 -0
  36. data/lib/decision_agent/web/public/simulation_impact.html +478 -0
  37. data/lib/decision_agent/web/public/simulation_replay.html +551 -0
  38. data/lib/decision_agent/web/public/simulation_shadow.html +546 -0
  39. data/lib/decision_agent/web/public/simulation_whatif.html +532 -0
  40. data/lib/decision_agent/web/public/styles.css +65 -0
  41. data/lib/decision_agent/web/server.rb +594 -23
  42. data/lib/decision_agent.rb +60 -2
  43. metadata +53 -73
  44. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  45. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  46. data/spec/ab_testing/ab_test_spec.rb +0 -270
  47. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -655
  48. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  49. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  50. data/spec/activerecord_thread_safety_spec.rb +0 -553
  51. data/spec/advanced_operators_spec.rb +0 -3150
  52. data/spec/agent_spec.rb +0 -289
  53. data/spec/api_contract_spec.rb +0 -430
  54. data/spec/audit_adapters_spec.rb +0 -92
  55. data/spec/auth/access_audit_logger_spec.rb +0 -394
  56. data/spec/auth/authenticator_spec.rb +0 -112
  57. data/spec/auth/password_reset_spec.rb +0 -294
  58. data/spec/auth/permission_checker_spec.rb +0 -207
  59. data/spec/auth/permission_spec.rb +0 -73
  60. data/spec/auth/rbac_adapter_spec.rb +0 -778
  61. data/spec/auth/rbac_config_spec.rb +0 -82
  62. data/spec/auth/role_spec.rb +0 -51
  63. data/spec/auth/session_manager_spec.rb +0 -172
  64. data/spec/auth/session_spec.rb +0 -112
  65. data/spec/auth/user_spec.rb +0 -130
  66. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  67. data/spec/context_spec.rb +0 -127
  68. data/spec/decision_agent_spec.rb +0 -96
  69. data/spec/decision_spec.rb +0 -423
  70. data/spec/dmn/decision_graph_spec.rb +0 -282
  71. data/spec/dmn/decision_tree_spec.rb +0 -203
  72. data/spec/dmn/feel/errors_spec.rb +0 -18
  73. data/spec/dmn/feel/functions_spec.rb +0 -400
  74. data/spec/dmn/feel/simple_parser_spec.rb +0 -274
  75. data/spec/dmn/feel/types_spec.rb +0 -176
  76. data/spec/dmn/feel_parser_spec.rb +0 -489
  77. data/spec/dmn/hit_policy_spec.rb +0 -202
  78. data/spec/dmn/integration_spec.rb +0 -226
  79. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  80. data/spec/dsl_validation_spec.rb +0 -648
  81. data/spec/edge_cases_spec.rb +0 -353
  82. data/spec/evaluation_spec.rb +0 -364
  83. data/spec/evaluation_validator_spec.rb +0 -165
  84. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  85. data/spec/examples.txt +0 -1909
  86. data/spec/fixtures/dmn/complex_decision.dmn +0 -81
  87. data/spec/fixtures/dmn/invalid_structure.dmn +0 -31
  88. data/spec/fixtures/dmn/simple_decision.dmn +0 -40
  89. data/spec/issue_verification_spec.rb +0 -759
  90. data/spec/json_rule_evaluator_spec.rb +0 -587
  91. data/spec/monitoring/alert_manager_spec.rb +0 -378
  92. data/spec/monitoring/metrics_collector_spec.rb +0 -501
  93. data/spec/monitoring/monitored_agent_spec.rb +0 -225
  94. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  95. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  96. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  97. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  98. data/spec/performance_optimizations_spec.rb +0 -493
  99. data/spec/replay_edge_cases_spec.rb +0 -699
  100. data/spec/replay_spec.rb +0 -210
  101. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  102. data/spec/scoring_spec.rb +0 -225
  103. data/spec/spec_helper.rb +0 -60
  104. data/spec/testing/batch_test_importer_spec.rb +0 -693
  105. data/spec/testing/batch_test_runner_spec.rb +0 -307
  106. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  107. data/spec/testing/test_result_comparator_spec.rb +0 -392
  108. data/spec/testing/test_scenario_spec.rb +0 -113
  109. data/spec/thread_safety_spec.rb +0 -490
  110. data/spec/thread_safety_spec.rb.broken +0 -878
  111. data/spec/versioning/adapter_spec.rb +0 -156
  112. data/spec/versioning_spec.rb +0 -1030
  113. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  114. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  115. data/spec/web_ui_rack_spec.rb +0 -2134
@@ -62,12 +62,32 @@ require_relative "decision_agent/auth/rbac_config"
62
62
  require_relative "decision_agent/auth/permission_checker"
63
63
  require_relative "decision_agent/auth/access_audit_logger"
64
64
 
65
+ require_relative "decision_agent/data_enrichment/config"
66
+ require_relative "decision_agent/data_enrichment/client"
67
+ require_relative "decision_agent/data_enrichment/cache_adapter"
68
+ require_relative "decision_agent/data_enrichment/cache/memory_adapter"
69
+ require_relative "decision_agent/data_enrichment/circuit_breaker"
70
+ require_relative "decision_agent/data_enrichment/errors"
71
+
72
+ require_relative "decision_agent/simulation"
73
+
74
+ require_relative "decision_agent/explainability/condition_trace"
75
+ require_relative "decision_agent/explainability/rule_trace"
76
+ require_relative "decision_agent/explainability/trace_collector"
77
+ require_relative "decision_agent/explainability/explainability_result"
78
+
65
79
  module DecisionAgent
66
80
  # Global RBAC configuration
67
81
  @rbac_config = Auth::RbacConfig.new
82
+ # Global data enrichment configuration
83
+ @data_enrichment_config = DataEnrichment::Config.new
84
+ @data_enrichment_client = nil
85
+ @permission_checker = nil
86
+ @permission_checker_mutex = Mutex.new
87
+ @data_enrichment_client_mutex = Mutex.new
68
88
 
69
89
  class << self
70
- attr_reader :rbac_config
90
+ attr_reader :rbac_config, :data_enrichment_config
71
91
 
72
92
  # Configure RBAC adapter
73
93
  # @param adapter_type [Symbol] :default, :devise_cancan, :pundit, or :custom
@@ -85,15 +105,53 @@ module DecisionAgent
85
105
  elsif adapter_type
86
106
  @rbac_config.use(adapter_type, **options)
87
107
  end
108
+ # Initialize permission checker at configuration time (thread-safe write-once pattern)
109
+ @permission_checker = Auth::PermissionChecker.new(adapter: @rbac_config.adapter)
88
110
  @rbac_config
89
111
  end
90
112
 
91
113
  # Get the configured permission checker
114
+ # Thread-safe: uses double-checked locking for lazy initialization fallback
92
115
  def permission_checker
93
- @permission_checker ||= Auth::PermissionChecker.new(adapter: @rbac_config.adapter)
116
+ return @permission_checker if @permission_checker
117
+
118
+ @permission_checker_mutex.synchronize do
119
+ @permission_checker ||= Auth::PermissionChecker.new(adapter: @rbac_config.adapter)
120
+ end
94
121
  end
95
122
 
96
123
  # Set a custom permission checker
97
124
  attr_writer :permission_checker
125
+
126
+ # Configure data enrichment endpoints
127
+ # @yield [DataEnrichment::Config] Configuration block
128
+ # @example
129
+ # DecisionAgent.configure_data_enrichment do |config|
130
+ # config.add_endpoint(:credit_bureau,
131
+ # url: "https://api.creditbureau.com/v1/score",
132
+ # method: :post,
133
+ # auth: { type: :api_key, header: "X-API-Key" },
134
+ # cache: { ttl: 3600, adapter: :memory }
135
+ # )
136
+ # end
137
+ def configure_data_enrichment
138
+ yield @data_enrichment_config if block_given?
139
+ # Initialize client at configuration time (thread-safe write-once pattern)
140
+ @data_enrichment_client = DataEnrichment::Client.new(config: @data_enrichment_config)
141
+ @data_enrichment_config
142
+ end
143
+
144
+ # Get the data enrichment client
145
+ # Thread-safe: uses double-checked locking for lazy initialization fallback
146
+ def data_enrichment_client
147
+ return @data_enrichment_client if @data_enrichment_client
148
+
149
+ @data_enrichment_client_mutex.synchronize do
150
+ @data_enrichment_client ||= DataEnrichment::Client.new(config: @data_enrichment_config)
151
+ end
152
+ end
153
+
154
+ # Set a custom data enrichment client
155
+ attr_writer :data_enrichment_client
98
156
  end
99
157
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decision_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Aswin
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '3.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: csv
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.3'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: json-canonicalization
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -163,6 +177,20 @@ dependencies:
163
177
  - - "~>"
164
178
  - !ruby/object:Gem::Version
165
179
  version: '1.60'
180
+ - !ruby/object:Gem::Dependency
181
+ name: webmock
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '3.18'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: '3.18'
166
194
  description: A production-grade decision agent that provides deterministic rule evaluation,
167
195
  conflict resolution, and full audit replay capabilities. Framework-agnostic and
168
196
  AI-optional.
@@ -201,6 +229,12 @@ files:
201
229
  - lib/decision_agent/auth/session_manager.rb
202
230
  - lib/decision_agent/auth/user.rb
203
231
  - lib/decision_agent/context.rb
232
+ - lib/decision_agent/data_enrichment/cache/memory_adapter.rb
233
+ - lib/decision_agent/data_enrichment/cache_adapter.rb
234
+ - lib/decision_agent/data_enrichment/circuit_breaker.rb
235
+ - lib/decision_agent/data_enrichment/client.rb
236
+ - lib/decision_agent/data_enrichment/config.rb
237
+ - lib/decision_agent/data_enrichment/errors.rb
204
238
  - lib/decision_agent/decision.rb
205
239
  - lib/decision_agent/dmn/adapter.rb
206
240
  - lib/decision_agent/dmn/cache.rb
@@ -231,6 +265,10 @@ files:
231
265
  - lib/decision_agent/evaluators/dmn_evaluator.rb
232
266
  - lib/decision_agent/evaluators/json_rule_evaluator.rb
233
267
  - lib/decision_agent/evaluators/static_evaluator.rb
268
+ - lib/decision_agent/explainability/condition_trace.rb
269
+ - lib/decision_agent/explainability/explainability_result.rb
270
+ - lib/decision_agent/explainability/rule_trace.rb
271
+ - lib/decision_agent/explainability/trace_collector.rb
234
272
  - lib/decision_agent/monitoring/alert_manager.rb
235
273
  - lib/decision_agent/monitoring/dashboard/public/dashboard.css
236
274
  - lib/decision_agent/monitoring/dashboard/public/dashboard.js
@@ -248,6 +286,15 @@ files:
248
286
  - lib/decision_agent/scoring/max_weight.rb
249
287
  - lib/decision_agent/scoring/threshold.rb
250
288
  - lib/decision_agent/scoring/weighted_average.rb
289
+ - lib/decision_agent/simulation.rb
290
+ - lib/decision_agent/simulation/errors.rb
291
+ - lib/decision_agent/simulation/impact_analyzer.rb
292
+ - lib/decision_agent/simulation/monte_carlo_simulator.rb
293
+ - lib/decision_agent/simulation/replay_engine.rb
294
+ - lib/decision_agent/simulation/scenario_engine.rb
295
+ - lib/decision_agent/simulation/scenario_library.rb
296
+ - lib/decision_agent/simulation/shadow_test_engine.rb
297
+ - lib/decision_agent/simulation/what_if_analyzer.rb
251
298
  - lib/decision_agent/testing/batch_test_importer.rb
252
299
  - lib/decision_agent/testing/batch_test_runner.rb
253
300
  - lib/decision_agent/testing/test_coverage_analyzer.rb
@@ -268,6 +315,11 @@ files:
268
315
  - lib/decision_agent/web/public/dmn-editor.js
269
316
  - lib/decision_agent/web/public/index.html
270
317
  - lib/decision_agent/web/public/login.html
318
+ - lib/decision_agent/web/public/simulation.html
319
+ - lib/decision_agent/web/public/simulation_impact.html
320
+ - lib/decision_agent/web/public/simulation_replay.html
321
+ - lib/decision_agent/web/public/simulation_shadow.html
322
+ - lib/decision_agent/web/public/simulation_whatif.html
271
323
  - lib/decision_agent/web/public/styles.css
272
324
  - lib/decision_agent/web/public/users.html
273
325
  - lib/decision_agent/web/server.rb
@@ -286,78 +338,6 @@ files:
286
338
  - lib/generators/decision_agent/install/templates/performance_metric.rb
287
339
  - lib/generators/decision_agent/install/templates/rule.rb
288
340
  - lib/generators/decision_agent/install/templates/rule_version.rb
289
- - spec/ab_testing/ab_test_assignment_spec.rb
290
- - spec/ab_testing/ab_test_manager_spec.rb
291
- - spec/ab_testing/ab_test_spec.rb
292
- - spec/ab_testing/ab_testing_agent_spec.rb
293
- - spec/ab_testing/storage/adapter_spec.rb
294
- - spec/ab_testing/storage/memory_adapter_spec.rb
295
- - spec/activerecord_thread_safety_spec.rb
296
- - spec/advanced_operators_spec.rb
297
- - spec/agent_spec.rb
298
- - spec/api_contract_spec.rb
299
- - spec/audit_adapters_spec.rb
300
- - spec/auth/access_audit_logger_spec.rb
301
- - spec/auth/authenticator_spec.rb
302
- - spec/auth/password_reset_spec.rb
303
- - spec/auth/permission_checker_spec.rb
304
- - spec/auth/permission_spec.rb
305
- - spec/auth/rbac_adapter_spec.rb
306
- - spec/auth/rbac_config_spec.rb
307
- - spec/auth/role_spec.rb
308
- - spec/auth/session_manager_spec.rb
309
- - spec/auth/session_spec.rb
310
- - spec/auth/user_spec.rb
311
- - spec/comprehensive_edge_cases_spec.rb
312
- - spec/context_spec.rb
313
- - spec/decision_agent_spec.rb
314
- - spec/decision_spec.rb
315
- - spec/dmn/decision_graph_spec.rb
316
- - spec/dmn/decision_tree_spec.rb
317
- - spec/dmn/feel/errors_spec.rb
318
- - spec/dmn/feel/functions_spec.rb
319
- - spec/dmn/feel/simple_parser_spec.rb
320
- - spec/dmn/feel/types_spec.rb
321
- - spec/dmn/feel_parser_spec.rb
322
- - spec/dmn/hit_policy_spec.rb
323
- - spec/dmn/integration_spec.rb
324
- - spec/dsl/condition_evaluator_spec.rb
325
- - spec/dsl_validation_spec.rb
326
- - spec/edge_cases_spec.rb
327
- - spec/evaluation_spec.rb
328
- - spec/evaluation_validator_spec.rb
329
- - spec/examples.txt
330
- - spec/examples/feedback_aware_evaluator_spec.rb
331
- - spec/fixtures/dmn/complex_decision.dmn
332
- - spec/fixtures/dmn/invalid_structure.dmn
333
- - spec/fixtures/dmn/simple_decision.dmn
334
- - spec/issue_verification_spec.rb
335
- - spec/json_rule_evaluator_spec.rb
336
- - spec/monitoring/alert_manager_spec.rb
337
- - spec/monitoring/metrics_collector_spec.rb
338
- - spec/monitoring/monitored_agent_spec.rb
339
- - spec/monitoring/prometheus_exporter_spec.rb
340
- - spec/monitoring/storage/activerecord_adapter_spec.rb
341
- - spec/monitoring/storage/base_adapter_spec.rb
342
- - spec/monitoring/storage/memory_adapter_spec.rb
343
- - spec/performance_optimizations_spec.rb
344
- - spec/replay_edge_cases_spec.rb
345
- - spec/replay_spec.rb
346
- - spec/rfc8785_canonicalization_spec.rb
347
- - spec/scoring_spec.rb
348
- - spec/spec_helper.rb
349
- - spec/testing/batch_test_importer_spec.rb
350
- - spec/testing/batch_test_runner_spec.rb
351
- - spec/testing/test_coverage_analyzer_spec.rb
352
- - spec/testing/test_result_comparator_spec.rb
353
- - spec/testing/test_scenario_spec.rb
354
- - spec/thread_safety_spec.rb
355
- - spec/thread_safety_spec.rb.broken
356
- - spec/versioning/adapter_spec.rb
357
- - spec/versioning_spec.rb
358
- - spec/web/middleware/auth_middleware_spec.rb
359
- - spec/web/middleware/permission_middleware_spec.rb
360
- - spec/web_ui_rack_spec.rb
361
341
  homepage: https://github.com/samaswin/decision_agent
362
342
  licenses:
363
343
  - MIT
@@ -1,253 +0,0 @@
1
- require "spec_helper"
2
- require "decision_agent/ab_testing/ab_test_assignment"
3
-
4
- RSpec.describe DecisionAgent::ABTesting::ABTestAssignment do
5
- describe "#initialize" do
6
- it "creates an assignment with required fields" do
7
- assignment = described_class.new(
8
- ab_test_id: "test_1",
9
- variant: :champion,
10
- version_id: "v1"
11
- )
12
-
13
- expect(assignment.ab_test_id).to eq("test_1")
14
- expect(assignment.variant).to eq(:champion)
15
- expect(assignment.version_id).to eq("v1")
16
- expect(assignment.timestamp).to be_a(Time)
17
- end
18
-
19
- it "accepts optional user_id" do
20
- assignment = described_class.new(
21
- ab_test_id: "test_1",
22
- variant: :champion,
23
- version_id: "v1",
24
- user_id: "user_123"
25
- )
26
-
27
- expect(assignment.user_id).to eq("user_123")
28
- end
29
-
30
- it "accepts optional timestamp" do
31
- custom_time = Time.new(2024, 1, 1, 12, 0, 0, "+00:00")
32
- assignment = described_class.new(
33
- ab_test_id: "test_1",
34
- variant: :champion,
35
- version_id: "v1",
36
- timestamp: custom_time
37
- )
38
-
39
- expect(assignment.timestamp).to eq(custom_time)
40
- end
41
-
42
- it "accepts optional decision_result and confidence" do
43
- assignment = described_class.new(
44
- ab_test_id: "test_1",
45
- variant: :champion,
46
- version_id: "v1",
47
- decision_result: "approve",
48
- confidence: 0.95
49
- )
50
-
51
- expect(assignment.decision_result).to eq("approve")
52
- expect(assignment.confidence).to eq(0.95)
53
- end
54
-
55
- it "accepts optional context" do
56
- context = { user_type: "premium", region: "us" }
57
- assignment = described_class.new(
58
- ab_test_id: "test_1",
59
- variant: :champion,
60
- version_id: "v1",
61
- context: context
62
- )
63
-
64
- expect(assignment.context).to eq(context)
65
- end
66
-
67
- it "defaults context to empty hash" do
68
- assignment = described_class.new(
69
- ab_test_id: "test_1",
70
- variant: :champion,
71
- version_id: "v1"
72
- )
73
-
74
- expect(assignment.context).to eq({})
75
- end
76
-
77
- it "accepts optional id" do
78
- assignment = described_class.new(
79
- ab_test_id: "test_1",
80
- variant: :champion,
81
- version_id: "v1",
82
- id: "assign_123"
83
- )
84
-
85
- expect(assignment.id).to eq("assign_123")
86
- end
87
-
88
- it "raises error if ab_test_id is nil" do
89
- expect do
90
- described_class.new(
91
- ab_test_id: nil,
92
- variant: :champion,
93
- version_id: "v1"
94
- )
95
- end.to raise_error(DecisionAgent::ValidationError, /AB test ID is required/)
96
- end
97
-
98
- it "raises error if variant is nil" do
99
- expect do
100
- described_class.new(
101
- ab_test_id: "test_1",
102
- variant: nil,
103
- version_id: "v1"
104
- )
105
- end.to raise_error(DecisionAgent::ValidationError, /Variant is required/)
106
- end
107
-
108
- it "raises error if version_id is nil" do
109
- expect do
110
- described_class.new(
111
- ab_test_id: "test_1",
112
- variant: :champion,
113
- version_id: nil
114
- )
115
- end.to raise_error(DecisionAgent::ValidationError, /Version ID is required/)
116
- end
117
-
118
- it "raises error if variant is not :champion or :challenger" do
119
- expect do
120
- described_class.new(
121
- ab_test_id: "test_1",
122
- variant: :invalid,
123
- version_id: "v1"
124
- )
125
- end.to raise_error(DecisionAgent::ValidationError, /Variant must be :champion or :challenger/)
126
- end
127
-
128
- it "raises error if confidence is negative" do
129
- expect do
130
- described_class.new(
131
- ab_test_id: "test_1",
132
- variant: :champion,
133
- version_id: "v1",
134
- confidence: -0.1
135
- )
136
- end.to raise_error(DecisionAgent::ValidationError, /Confidence must be between 0 and 1/)
137
- end
138
-
139
- it "raises error if confidence is greater than 1" do
140
- expect do
141
- described_class.new(
142
- ab_test_id: "test_1",
143
- variant: :champion,
144
- version_id: "v1",
145
- confidence: 1.5
146
- )
147
- end.to raise_error(DecisionAgent::ValidationError, /Confidence must be between 0 and 1/)
148
- end
149
-
150
- it "accepts confidence of 0" do
151
- assignment = described_class.new(
152
- ab_test_id: "test_1",
153
- variant: :champion,
154
- version_id: "v1",
155
- confidence: 0.0
156
- )
157
-
158
- expect(assignment.confidence).to eq(0.0)
159
- end
160
-
161
- it "accepts confidence of 1" do
162
- assignment = described_class.new(
163
- ab_test_id: "test_1",
164
- variant: :champion,
165
- version_id: "v1",
166
- confidence: 1.0
167
- )
168
-
169
- expect(assignment.confidence).to eq(1.0)
170
- end
171
-
172
- it "accepts challenger variant" do
173
- assignment = described_class.new(
174
- ab_test_id: "test_1",
175
- variant: :challenger,
176
- version_id: "v2"
177
- )
178
-
179
- expect(assignment.variant).to eq(:challenger)
180
- end
181
- end
182
-
183
- describe "#record_decision" do
184
- let(:assignment) do
185
- described_class.new(
186
- ab_test_id: "test_1",
187
- variant: :champion,
188
- version_id: "v1"
189
- )
190
- end
191
-
192
- it "updates decision_result and confidence" do
193
- assignment.record_decision("approve", 0.95)
194
-
195
- expect(assignment.decision_result).to eq("approve")
196
- expect(assignment.confidence).to eq(0.95)
197
- end
198
-
199
- it "can update multiple times" do
200
- assignment.record_decision("approve", 0.95)
201
- assignment.record_decision("reject", 0.85)
202
-
203
- expect(assignment.decision_result).to eq("reject")
204
- expect(assignment.confidence).to eq(0.85)
205
- end
206
- end
207
-
208
- describe "#to_h" do
209
- it "converts assignment to hash with all fields" do
210
- assignment = described_class.new(
211
- ab_test_id: "test_1",
212
- variant: :champion,
213
- version_id: "v1",
214
- id: "assign_123",
215
- user_id: "user_456",
216
- decision_result: "approve",
217
- confidence: 0.95,
218
- context: { region: "us" },
219
- timestamp: Time.new(2024, 1, 1, 12, 0, 0, "+00:00")
220
- )
221
-
222
- hash = assignment.to_h
223
-
224
- expect(hash).to eq({
225
- id: "assign_123",
226
- ab_test_id: "test_1",
227
- user_id: "user_456",
228
- variant: :champion,
229
- version_id: "v1",
230
- timestamp: Time.new(2024, 1, 1, 12, 0, 0, "+00:00"),
231
- decision_result: "approve",
232
- confidence: 0.95,
233
- context: { region: "us" }
234
- })
235
- end
236
-
237
- it "includes nil values in hash" do
238
- assignment = described_class.new(
239
- ab_test_id: "test_1",
240
- variant: :champion,
241
- version_id: "v1"
242
- )
243
-
244
- hash = assignment.to_h
245
-
246
- expect(hash[:id]).to be_nil
247
- expect(hash[:user_id]).to be_nil
248
- expect(hash[:decision_result]).to be_nil
249
- expect(hash[:confidence]).to be_nil
250
- expect(hash[:context]).to eq({})
251
- end
252
- end
253
- end