decision_agent 0.1.3 → 0.1.6

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +84 -233
  3. data/lib/decision_agent/ab_testing/ab_test.rb +197 -0
  4. data/lib/decision_agent/ab_testing/ab_test_assignment.rb +76 -0
  5. data/lib/decision_agent/ab_testing/ab_test_manager.rb +317 -0
  6. data/lib/decision_agent/ab_testing/ab_testing_agent.rb +188 -0
  7. data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +155 -0
  8. data/lib/decision_agent/ab_testing/storage/adapter.rb +67 -0
  9. data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +116 -0
  10. data/lib/decision_agent/agent.rb +5 -3
  11. data/lib/decision_agent/auth/access_audit_logger.rb +122 -0
  12. data/lib/decision_agent/auth/authenticator.rb +127 -0
  13. data/lib/decision_agent/auth/password_reset_manager.rb +57 -0
  14. data/lib/decision_agent/auth/password_reset_token.rb +33 -0
  15. data/lib/decision_agent/auth/permission.rb +29 -0
  16. data/lib/decision_agent/auth/permission_checker.rb +43 -0
  17. data/lib/decision_agent/auth/rbac_adapter.rb +278 -0
  18. data/lib/decision_agent/auth/rbac_config.rb +51 -0
  19. data/lib/decision_agent/auth/role.rb +56 -0
  20. data/lib/decision_agent/auth/session.rb +33 -0
  21. data/lib/decision_agent/auth/session_manager.rb +57 -0
  22. data/lib/decision_agent/auth/user.rb +70 -0
  23. data/lib/decision_agent/context.rb +24 -4
  24. data/lib/decision_agent/decision.rb +10 -3
  25. data/lib/decision_agent/dsl/condition_evaluator.rb +378 -1
  26. data/lib/decision_agent/dsl/schema_validator.rb +8 -1
  27. data/lib/decision_agent/errors.rb +38 -0
  28. data/lib/decision_agent/evaluation.rb +10 -3
  29. data/lib/decision_agent/evaluation_validator.rb +8 -13
  30. data/lib/decision_agent/monitoring/dashboard_server.rb +1 -0
  31. data/lib/decision_agent/monitoring/metrics_collector.rb +164 -7
  32. data/lib/decision_agent/monitoring/storage/activerecord_adapter.rb +253 -0
  33. data/lib/decision_agent/monitoring/storage/base_adapter.rb +90 -0
  34. data/lib/decision_agent/monitoring/storage/memory_adapter.rb +222 -0
  35. data/lib/decision_agent/testing/batch_test_importer.rb +373 -0
  36. data/lib/decision_agent/testing/batch_test_runner.rb +244 -0
  37. data/lib/decision_agent/testing/test_coverage_analyzer.rb +191 -0
  38. data/lib/decision_agent/testing/test_result_comparator.rb +235 -0
  39. data/lib/decision_agent/testing/test_scenario.rb +42 -0
  40. data/lib/decision_agent/version.rb +10 -1
  41. data/lib/decision_agent/versioning/activerecord_adapter.rb +1 -1
  42. data/lib/decision_agent/versioning/file_storage_adapter.rb +96 -28
  43. data/lib/decision_agent/web/middleware/auth_middleware.rb +45 -0
  44. data/lib/decision_agent/web/middleware/permission_middleware.rb +94 -0
  45. data/lib/decision_agent/web/public/app.js +184 -29
  46. data/lib/decision_agent/web/public/batch_testing.html +640 -0
  47. data/lib/decision_agent/web/public/index.html +37 -9
  48. data/lib/decision_agent/web/public/login.html +298 -0
  49. data/lib/decision_agent/web/public/users.html +679 -0
  50. data/lib/decision_agent/web/server.rb +873 -7
  51. data/lib/decision_agent.rb +59 -0
  52. data/lib/generators/decision_agent/install/install_generator.rb +37 -0
  53. data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +45 -0
  54. data/lib/generators/decision_agent/install/templates/ab_test_model.rb +54 -0
  55. data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +43 -0
  56. data/lib/generators/decision_agent/install/templates/ab_testing_tasks.rake +189 -0
  57. data/lib/generators/decision_agent/install/templates/decision_agent_tasks.rake +114 -0
  58. data/lib/generators/decision_agent/install/templates/decision_log.rb +57 -0
  59. data/lib/generators/decision_agent/install/templates/error_metric.rb +53 -0
  60. data/lib/generators/decision_agent/install/templates/evaluation_metric.rb +43 -0
  61. data/lib/generators/decision_agent/install/templates/monitoring_migration.rb +109 -0
  62. data/lib/generators/decision_agent/install/templates/performance_metric.rb +76 -0
  63. data/lib/generators/decision_agent/install/templates/rule_version.rb +1 -1
  64. data/spec/ab_testing/ab_test_assignment_spec.rb +253 -0
  65. data/spec/ab_testing/ab_test_manager_spec.rb +612 -0
  66. data/spec/ab_testing/ab_test_spec.rb +270 -0
  67. data/spec/ab_testing/ab_testing_agent_spec.rb +481 -0
  68. data/spec/ab_testing/storage/adapter_spec.rb +64 -0
  69. data/spec/ab_testing/storage/memory_adapter_spec.rb +485 -0
  70. data/spec/advanced_operators_spec.rb +1003 -0
  71. data/spec/agent_spec.rb +40 -0
  72. data/spec/audit_adapters_spec.rb +18 -0
  73. data/spec/auth/access_audit_logger_spec.rb +394 -0
  74. data/spec/auth/authenticator_spec.rb +112 -0
  75. data/spec/auth/password_reset_spec.rb +294 -0
  76. data/spec/auth/permission_checker_spec.rb +207 -0
  77. data/spec/auth/permission_spec.rb +73 -0
  78. data/spec/auth/rbac_adapter_spec.rb +550 -0
  79. data/spec/auth/rbac_config_spec.rb +82 -0
  80. data/spec/auth/role_spec.rb +51 -0
  81. data/spec/auth/session_manager_spec.rb +172 -0
  82. data/spec/auth/session_spec.rb +112 -0
  83. data/spec/auth/user_spec.rb +130 -0
  84. data/spec/context_spec.rb +43 -0
  85. data/spec/decision_agent_spec.rb +96 -0
  86. data/spec/decision_spec.rb +423 -0
  87. data/spec/dsl/condition_evaluator_spec.rb +774 -0
  88. data/spec/evaluation_spec.rb +364 -0
  89. data/spec/evaluation_validator_spec.rb +165 -0
  90. data/spec/examples.txt +1542 -548
  91. data/spec/issue_verification_spec.rb +95 -21
  92. data/spec/monitoring/metrics_collector_spec.rb +221 -3
  93. data/spec/monitoring/monitored_agent_spec.rb +1 -1
  94. data/spec/monitoring/prometheus_exporter_spec.rb +1 -1
  95. data/spec/monitoring/storage/activerecord_adapter_spec.rb +498 -0
  96. data/spec/monitoring/storage/base_adapter_spec.rb +61 -0
  97. data/spec/monitoring/storage/memory_adapter_spec.rb +247 -0
  98. data/spec/performance_optimizations_spec.rb +486 -0
  99. data/spec/spec_helper.rb +23 -0
  100. data/spec/testing/batch_test_importer_spec.rb +693 -0
  101. data/spec/testing/batch_test_runner_spec.rb +307 -0
  102. data/spec/testing/test_coverage_analyzer_spec.rb +292 -0
  103. data/spec/testing/test_result_comparator_spec.rb +392 -0
  104. data/spec/testing/test_scenario_spec.rb +113 -0
  105. data/spec/versioning/adapter_spec.rb +156 -0
  106. data/spec/versioning_spec.rb +253 -0
  107. data/spec/web/middleware/auth_middleware_spec.rb +133 -0
  108. data/spec/web/middleware/permission_middleware_spec.rb +247 -0
  109. data/spec/web_ui_rack_spec.rb +1705 -0
  110. metadata +123 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c8a831b21d5bb62dbb4948827cc0b4bc9bcf021b52f04a2d9141d1339287274
4
- data.tar.gz: 97d80cab6513385dc9e9b4bddb7555da1e8629488ce358319aa0842e74cf41e1
3
+ metadata.gz: a6adecd350c20f336995ee4954e26f9be65dee45fb9d31838be996eda5928d3d
4
+ data.tar.gz: cb55474c53415d83a4d8a66598b8a4fcfd526472c5419612d9491df4ea02832f
5
5
  SHA512:
6
- metadata.gz: dcfa8f3cc0a5931a1a163b97caf7586c17eddfee38850b52b832fa1a94d720342868c6823158597d0dafa14bf7df82b9d9b394fdffac3c74d06a556eca2b5c18
7
- data.tar.gz: f183fc1b8c021e589571d0d9a97c3b2679fec3b529d2e73a76a4b7752a9792dcba77f4914de876c5448711416179dcbd0ba35b515fe1c4f2a74bcab27c1a873e
6
+ metadata.gz: f2f14271db7d5ba25d3e3604396c221cce07ed62824025d1686c4950a06cf2f064cd5498053aa49f613d09e8a0da62fccdc880f293209cb6e30dfe3119fe567c
7
+ data.tar.gz: b32ddb86d61e4588d4db68e6a4f441b2a35688918ddde831ef6b47a19782dfd36f68931d1cd9e1885f59fae1f63af96f683401c6f382e119c7fb23477940075c
data/README.md CHANGED
@@ -11,11 +11,13 @@ A production-grade, deterministic, explainable, and auditable decision engine fo
11
11
 
12
12
  ## Why DecisionAgent?
13
13
 
14
+ DecisionAgent is designed for applications that require **deterministic, explainable, and auditable** decision-making:
15
+
14
16
  - ✅ **Deterministic** - Same input always produces same output
15
17
  - ✅ **Explainable** - Every decision includes human-readable reasoning
16
18
  - ✅ **Auditable** - Reproduce any historical decision exactly
17
19
  - ✅ **Framework-agnostic** - Pure Ruby, works anywhere
18
- - ✅ **Production-ready** - Comprehensive testing, error handling, and versioning
20
+ - ✅ **Production-ready** - Comprehensive testing ([Coverage Report](coverage.md)), error handling, and versioning
19
21
 
20
22
  ## Installation
21
23
 
@@ -24,6 +26,7 @@ gem install decision_agent
24
26
  ```
25
27
 
26
28
  Or add to your Gemfile:
29
+
27
30
  ```ruby
28
31
  gem 'decision_agent'
29
32
  ```
@@ -57,11 +60,38 @@ puts result.confidence # => 0.9
57
60
  puts result.explanations # => ["High value transaction"]
58
61
  ```
59
62
 
60
- ## Web UI - Visual Rule Builder
63
+ See [Code Examples](docs/CODE_EXAMPLES.md) for more comprehensive examples.
64
+
65
+ ## Key Features
66
+
67
+ ### Decision Making
68
+ - **Multiple Evaluators** - Combine rule-based, ML, and custom logic
69
+ - **Conflict Resolution** - Weighted average, consensus, threshold, max weight
70
+ - **Rich Context** - Nested data, dot notation, flexible operators
71
+ - **Advanced Operators** - String, numeric, date/time, collection, and geospatial operators
72
+
73
+ ### Auditability & Compliance
74
+ - **Complete Audit Trails** - Every decision fully logged
75
+ - **Deterministic Replay** - Reproduce historical decisions exactly
76
+ - **RFC 8785 Canonical JSON** - Industry-standard deterministic hashing
77
+ - **Compliance Ready** - HIPAA, SOX, regulatory compliance support
78
+
79
+ ### Developer Experience
80
+ - **Pluggable Architecture** - Custom evaluators, scoring, audit adapters
81
+ - **Framework Agnostic** - Works with Rails, Sinatra, or standalone
82
+ - **JSON Rule DSL** - Non-technical users can write rules
83
+ - **Visual Rule Builder** - Web UI for rule management
61
84
 
62
- The DecisionAgent Web UI provides a visual interface for building and testing rules.
85
+ ### Production Features
86
+ - **Real-time Monitoring** - Live dashboard with WebSocket updates
87
+ - **Prometheus Export** - Industry-standard metrics format
88
+ - **Intelligent Alerting** - Anomaly detection with customizable rules
89
+ - **Grafana Integration** - Pre-built dashboards and alert rules
90
+ - **Version Control** - Full rule version control and rollback
91
+ - **Thread-Safe** - Safe for multi-threaded servers and background jobs
92
+ - **High Performance** - 10,000+ decisions/second, ~0.1ms latency
63
93
 
64
- ### Standalone Usage
94
+ ## Web UI - Visual Rule Builder
65
95
 
66
96
  Launch the visual rule builder:
67
97
 
@@ -71,246 +101,50 @@ decision_agent web
71
101
 
72
102
  Open [http://localhost:4567](http://localhost:4567) in your browser.
73
103
 
74
- ### Mount in Rails
75
-
76
- Add to your `config/routes.rb`:
104
+ ### Integration
77
105
 
106
+ **Rails:**
78
107
  ```ruby
79
108
  require 'decision_agent/web/server'
80
-
81
109
  Rails.application.routes.draw do
82
- # Mount DecisionAgent Web UI
83
- mount DecisionAgent::Web::Server, at: '/decision_agent'
84
- end
85
- ```
86
-
87
- Then visit `http://localhost:3000/decision_agent` in your browser.
88
-
89
- **With Authentication:**
90
-
91
- ```ruby
92
- authenticate :user, ->(user) { user.admin? } do
93
110
  mount DecisionAgent::Web::Server, at: '/decision_agent'
94
111
  end
95
112
  ```
96
113
 
97
- ### Mount in Rack/Sinatra Apps
98
-
114
+ **Rack/Sinatra:**
99
115
  ```ruby
100
- # config.ru
101
116
  require 'decision_agent/web/server'
102
-
103
117
  map '/decision_agent' do
104
118
  run DecisionAgent::Web::Server
105
119
  end
106
120
  ```
107
121
 
108
- <img width="1622" height="820" alt="Screenshot" src="https://github.com/user-attachments/assets/687e9ff6-669a-40f9-be27-085c614392d4" />
109
-
110
- See [Web UI Rails Integration Guide](wiki/WEB_UI_RAILS_INTEGRATION.md) for detailed setup instructions.
122
+ See [Web UI Integration Guide](docs/WEB_UI_RAILS_INTEGRATION.md) for detailed setup.
111
123
 
112
124
  ## Monitoring & Analytics
113
125
 
114
126
  Real-time monitoring, metrics, and alerting for production environments.
115
127
 
116
- ### Quick Start
117
-
118
128
  ```ruby
119
129
  require 'decision_agent/monitoring/metrics_collector'
120
130
  require 'decision_agent/monitoring/dashboard_server'
121
131
 
122
- # Initialize metrics collection
123
132
  collector = DecisionAgent::Monitoring::MetricsCollector.new(window_size: 3600)
124
-
125
- # Start real-time dashboard
126
133
  DecisionAgent::Monitoring::DashboardServer.start!(
127
134
  port: 4568,
128
135
  metrics_collector: collector
129
136
  )
130
-
131
- # Record decisions
132
- agent = DecisionAgent::Agent.new(evaluators: [evaluator])
133
- result = agent.decide(context: { amount: 1500 })
134
- collector.record_decision(result, context, duration_ms: 25.5)
135
137
  ```
136
138
 
137
139
  Open [http://localhost:4568](http://localhost:4568) for the monitoring dashboard.
138
140
 
139
- ### Features
140
-
141
- - **Real-time Dashboard** - Live metrics with WebSocket updates
142
- - **Prometheus Export** - Industry-standard metrics format
143
- - **Intelligent Alerting** - Anomaly detection with customizable rules
144
- - **Grafana Integration** - Pre-built dashboards and alert rules
145
- - **Custom KPIs** - Track business-specific metrics
146
- - **Thread-Safe** - Production-ready performance
147
-
148
- ### Prometheus & Grafana
149
-
150
- ```yaml
151
- # prometheus.yml
152
- scrape_configs:
153
- - job_name: 'decision_agent'
154
- static_configs:
155
- - targets: ['localhost:4568']
156
- metrics_path: '/metrics'
157
- ```
158
-
159
- Import the pre-built Grafana dashboard from [grafana/decision_agent_dashboard.json](grafana/decision_agent_dashboard.json).
160
-
161
- ### Alert Management
162
-
163
- ```ruby
164
- alert_manager = DecisionAgent::Monitoring::AlertManager.new(
165
- metrics_collector: collector
166
- )
141
+ **Features:**
142
+ - Real-time dashboard with WebSocket updates
143
+ - Prometheus metrics export
144
+ - Intelligent alerting with anomaly detection
145
+ - Grafana integration with pre-built dashboards
167
146
 
168
- # Add alert rules
169
- alert_manager.add_rule(
170
- name: 'High Error Rate',
171
- condition: AlertManager.high_error_rate(threshold: 0.1),
172
- severity: :critical
173
- )
174
-
175
- # Register alert handlers
176
- alert_manager.add_handler do |alert|
177
- SlackNotifier.notify("🚨 #{alert[:message]}")
178
- end
179
-
180
- # Start monitoring
181
- alert_manager.start_monitoring(interval: 60)
182
- ```
183
-
184
- See [Monitoring & Analytics Guide](wiki/MONITORING_AND_ANALYTICS.md) for complete documentation.
185
-
186
-
187
- ## Key Features
188
-
189
- ### Decision Making
190
- - **Multiple Evaluators** - Combine rule-based, ML, and custom logic
191
- - **Conflict Resolution** - Weighted average, consensus, threshold, max weight
192
- - **Rich Context** - Nested data, dot notation, flexible operators
193
-
194
- ### Auditability
195
- - **Complete Audit Trails** - Every decision fully logged
196
- - **Deterministic Replay** - Reproduce historical decisions exactly
197
- - **Compliance Ready** - HIPAA, SOX, regulatory compliance support
198
-
199
- ### Flexibility
200
- - **Pluggable Architecture** - Custom evaluators, scoring, audit adapters
201
- - **Framework Agnostic** - Works with Rails, Sinatra, or standalone
202
- - **JSON Rule DSL** - Non-technical users can write rules
203
- - **Visual Rule Builder** - Web UI for rule management
204
-
205
- ### Monitoring & Observability
206
- - **Real-time Metrics** - Live dashboard with WebSocket updates (<1 second latency)
207
- - **Prometheus Export** - Industry-standard metrics format at `/metrics` endpoint
208
- - **Intelligent Alerting** - Anomaly detection with customizable rules and severity levels
209
- - **Grafana Integration** - Pre-built dashboards and alert configurations in `grafana/` directory
210
- - **Custom KPIs** - Track business-specific metrics with thread-safe operations
211
- - **MonitoredAgent** - Drop-in replacement that auto-records all metrics
212
- - **AlertManager** - Built-in anomaly detection (error rates, latency spikes, low confidence)
213
-
214
- ### Production Ready
215
- - **Comprehensive Testing** - 90%+ code coverage
216
- - **Error Handling** - Clear, actionable error messages
217
- - **Versioning** - Full rule version control and rollback
218
- - **Performance** - Fast, zero external dependencies
219
- - **Thread-Safe** - Safe for multi-threaded servers and background jobs
220
-
221
- ## Examples
222
-
223
- ```ruby
224
- # Multiple evaluators with conflict resolution
225
- agent = DecisionAgent::Agent.new(
226
- evaluators: [rule_evaluator, ml_evaluator],
227
- scoring_strategy: DecisionAgent::Scoring::Consensus.new(minimum_agreement: 0.7),
228
- audit_adapter: DecisionAgent::Audit::LoggerAdapter.new
229
- )
230
-
231
- # Complex rules with nested conditions
232
- rules = {
233
- version: "1.0",
234
- ruleset: "fraud_detection",
235
- rules: [{
236
- id: "suspicious_activity",
237
- if: {
238
- all: [
239
- { field: "amount", op: "gt", value: 10000 },
240
- { any: [
241
- { field: "user.country", op: "in", value: ["XX", "YY"] },
242
- { field: "velocity", op: "gt", value: 5 }
243
- ]}
244
- ]
245
- },
246
- then: { decision: "flag_for_review", weight: 0.95, reason: "Suspicious patterns detected" }
247
- }]
248
- }
249
- ```
250
-
251
- See [examples/](examples/) for complete working examples.
252
-
253
- ## Thread-Safety Guarantees
254
-
255
- DecisionAgent is designed to be **thread-safe and FAST** for use in multi-threaded environments:
256
-
257
- ### Performance
258
- - **10,000+ decisions/second** throughput
259
- - **~0.1ms average latency** per decision
260
- - **Zero performance overhead** from thread-safety
261
- - **Linear scalability** with thread count
262
-
263
- ### Safe Concurrent Usage
264
- - **Agent instances** can be shared across threads safely
265
- - **Evaluators** are immutable after initialization
266
- - **Decisions and Evaluations** are deeply frozen
267
- - **File storage** uses mutex-protected operations
268
-
269
- ### Best Practices
270
- ```ruby
271
- # Safe: Reuse agent instance across threads
272
- agent = DecisionAgent::Agent.new(evaluators: [evaluator])
273
-
274
- Thread.new { agent.decide(context: { user_id: 1 }) }
275
- Thread.new { agent.decide(context: { user_id: 2 }) }
276
-
277
- # Safe: Share evaluators across agent instances
278
- evaluator = DecisionAgent::Evaluators::JsonRuleEvaluator.new(rules_json: rules)
279
- agent1 = DecisionAgent::Agent.new(evaluators: [evaluator])
280
- agent2 = DecisionAgent::Agent.new(evaluators: [evaluator])
281
- ```
282
-
283
- ### What's Frozen
284
- All data structures are deeply frozen to prevent mutation:
285
- - Decision objects (decision, confidence, explanations, evaluations)
286
- - Evaluation objects (decision, weight, reason, metadata)
287
- - Context data
288
- - Rule definitions in evaluators
289
-
290
- This ensures safe concurrent access without race conditions.
291
-
292
- ### RFC 8785 Canonical JSON
293
- DecisionAgent uses **RFC 8785 (JSON Canonicalization Scheme)** for deterministic audit hashing:
294
-
295
- - **Industry Standard** - Official IETF specification for canonical JSON
296
- - **Cryptographically Sound** - Ensures deterministic hashing of decision payloads
297
- - **Reproducible** - Same decision always produces same audit hash
298
- - **Interoperable** - Compatible with other systems using RFC 8785
299
-
300
- Every decision includes a deterministic SHA-256 hash in the audit payload, enabling:
301
- - Tamper detection in audit logs
302
- - Exact replay verification
303
- - Regulatory compliance documentation
304
-
305
- Learn more: [RFC 8785 Specification](https://datatracker.ietf.org/doc/html/rfc8785)
306
-
307
- ### Performance Benchmark
308
- Run the included benchmark to verify zero overhead:
309
- ```bash
310
- ruby examples/thread_safe_performance.rb
311
- ```
312
-
313
- See [THREAD_SAFETY.md](wiki/THREAD_SAFETY.md) for detailed implementation guide and [PERFORMANCE_AND_THREAD_SAFETY.md](wiki/PERFORMANCE_AND_THREAD_SAFETY.md) for detailed performance analysis.
147
+ See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) for complete documentation.
314
148
 
315
149
  ## When to Use DecisionAgent
316
150
 
@@ -328,31 +162,48 @@ See [THREAD_SAFETY.md](wiki/THREAD_SAFETY.md) for detailed implementation guide
328
162
 
329
163
  ## Documentation
330
164
 
331
- **Getting Started**
165
+ ### Getting Started
332
166
  - [Installation](#installation)
333
167
  - [Quick Start](#quick-start)
334
- - [Examples](examples/README.md)
335
-
336
- **Core Features**
337
- - [Versioning System](wiki/VERSIONING.md) - Version control for rules
338
- - [Web UI](wiki/WEB_UI.md) - Visual rule builder
339
- - [Web UI Setup](wiki/WEB_UI_SETUP.md) - Setup guide
340
- - [Web UI Rails Integration](wiki/WEB_UI_RAILS_INTEGRATION.md) - Mount in Rails/Rack apps
341
- - [Monitoring & Analytics](wiki/MONITORING_AND_ANALYTICS.md) - Real-time monitoring, metrics, and alerting
342
- - [Monitoring Architecture](wiki/MONITORING_ARCHITECTURE.md) - System architecture and design
343
-
344
- **Performance & Thread-Safety**
345
- - [Performance & Thread-Safety Summary](wiki/PERFORMANCE_AND_THREAD_SAFETY.md) - Benchmarks and production readiness
346
- - [Thread-Safety Implementation](wiki/THREAD_SAFETY.md) - Technical implementation guide
347
-
348
- **Reference**
349
- - [API Contract](wiki/API_CONTRACT.md) - Full API reference
350
- - [Changelog](wiki/CHANGELOG.md) - Version history
351
-
352
- **More Resources**
353
- - [Wiki Home](wiki/README.md) - Documentation index
168
+ - [Code Examples](docs/CODE_EXAMPLES.md) - Comprehensive code snippets
169
+ - [Examples Directory](examples/README.md) - Working examples with explanations
170
+
171
+ ### Core Features
172
+ - [Advanced Operators](docs/ADVANCED_OPERATORS.md) - String, numeric, date/time, collection, and geospatial operators
173
+ - [Versioning System](docs/VERSIONING.md) - Version control for rules
174
+ - [A/B Testing](docs/AB_TESTING.md) - Compare rule versions with statistical analysis
175
+ - [Web UI](docs/WEB_UI.md) - Visual rule builder
176
+ - [Web UI Setup](docs/WEB_UI_SETUP.md) - Setup guide
177
+ - [Web UI Rails Integration](docs/WEB_UI_RAILS_INTEGRATION.md) - Mount in Rails/Rack apps
178
+ - [Monitoring & Analytics](docs/MONITORING_AND_ANALYTICS.md) - Real-time monitoring, metrics, and alerting
179
+ - [Monitoring Architecture](docs/MONITORING_ARCHITECTURE.md) - System architecture and design
180
+
181
+ ### Performance & Thread-Safety
182
+ - [Performance & Thread-Safety Summary](docs/PERFORMANCE_AND_THREAD_SAFETY.md) - Benchmarks and production readiness
183
+ - [Thread-Safety Implementation](docs/THREAD_SAFETY.md) - Technical implementation guide
184
+
185
+ ### Reference
186
+ - [API Contract](docs/API_CONTRACT.md) - Full API reference
187
+ - [Changelog](docs/CHANGELOG.md) - Version history
188
+ - [Code Coverage Report](coverage.md) - Test coverage statistics
189
+
190
+ ### More Resources
191
+ - [Documentation Home](docs/README.md) - Documentation index
354
192
  - [GitHub Issues](https://github.com/samaswin87/decision_agent/issues) - Report bugs or request features
355
193
 
194
+ ## Thread-Safety & Performance
195
+
196
+ DecisionAgent is designed to be **thread-safe and FAST** for use in multi-threaded environments:
197
+
198
+ - **10,000+ decisions/second** throughput
199
+ - **~0.1ms average latency** per decision
200
+ - **Zero performance overhead** from thread-safety
201
+ - **Linear scalability** with thread count
202
+
203
+ All data structures are deeply frozen to prevent mutation, ensuring safe concurrent access without race conditions.
204
+
205
+ See [Thread-Safety Guide](docs/THREAD_SAFETY.md) and [Performance Analysis](docs/PERFORMANCE_AND_THREAD_SAFETY.md) for details.
206
+
356
207
  ## Contributing
357
208
 
358
209
  1. Fork the repository
@@ -363,8 +214,8 @@ See [THREAD_SAFETY.md](wiki/THREAD_SAFETY.md) for detailed implementation guide
363
214
  ## Support
364
215
 
365
216
  - **Issues**: [GitHub Issues](https://github.com/samaswin87/decision_agent/issues)
366
- - **Documentation**: [Wiki](wiki/README.md)
367
- - **Examples**: [examples/](examples/)
217
+ - **Documentation**: [Documentation](docs/README.md)
218
+ - **Examples**: [Examples Directory](examples/README.md)
368
219
 
369
220
  ## License
370
221
 
@@ -0,0 +1,197 @@
1
+ module DecisionAgent
2
+ module ABTesting
3
+ # Represents an A/B test configuration for comparing rule versions
4
+ class ABTest
5
+ attr_reader :id, :name, :champion_version_id, :challenger_version_id,
6
+ :traffic_split, :start_date, :end_date, :status
7
+
8
+ # @param name [String] Name of the A/B test
9
+ # @param champion_version_id [String, Integer] ID of the current/champion version
10
+ # @param challenger_version_id [String, Integer] ID of the new/challenger version
11
+ # @param options [Hash] Optional configuration
12
+ # @option options [Hash] :traffic_split Traffic distribution (default: { champion: 90, challenger: 10 })
13
+ # @option options [Time] :start_date When the test starts (defaults to now)
14
+ # @option options [Time] :end_date When the test ends (optional)
15
+ # @option options [String] :status Test status: running, completed, cancelled, scheduled
16
+ # @option options [String, Integer] :id Optional ID (for persistence)
17
+ def initialize(
18
+ name:,
19
+ champion_version_id:,
20
+ challenger_version_id:,
21
+ **options
22
+ )
23
+ @id = options[:id]
24
+ @name = name
25
+ @champion_version_id = champion_version_id
26
+ @challenger_version_id = challenger_version_id
27
+ @traffic_split = normalize_traffic_split(options[:traffic_split] || { champion: 90, challenger: 10 })
28
+ @start_date = options[:start_date] || Time.now.utc
29
+ @end_date = options[:end_date]
30
+ @status = options[:status] || "scheduled"
31
+
32
+ validate!
33
+ end
34
+
35
+ # Assign a variant based on traffic split
36
+ # Uses consistent hashing to ensure same user gets same variant
37
+ # @param user_id [String, nil] Optional user identifier for consistent assignment
38
+ # @return [Symbol] :champion or :challenger
39
+ def assign_variant(user_id: nil)
40
+ raise TestNotRunningError, "Test '#{@name}' is not running (status: #{@status})" unless running?
41
+
42
+ if user_id
43
+ # Consistent hashing: same user always gets same variant
44
+ hash_value = Digest::SHA256.hexdigest("#{@id}:#{user_id}").to_i(16)
45
+ percentage = hash_value % 100
46
+ else
47
+ # Random assignment
48
+ percentage = rand(100)
49
+ end
50
+
51
+ percentage < @traffic_split[:champion] ? :champion : :challenger
52
+ end
53
+
54
+ # Get the version ID for the assigned variant
55
+ # @param variant [Symbol] :champion or :challenger
56
+ # @return [String, Integer] The version ID
57
+ def version_for_variant(variant)
58
+ case variant
59
+ when :champion
60
+ @champion_version_id
61
+ when :challenger
62
+ @challenger_version_id
63
+ else
64
+ raise ArgumentError, "Invalid variant: #{variant}. Must be :champion or :challenger"
65
+ end
66
+ end
67
+
68
+ # Check if test is currently running
69
+ # @return [Boolean]
70
+ def running?
71
+ return false unless @status == "running"
72
+ return false if @start_date && Time.now.utc < @start_date
73
+ return false if @end_date && Time.now.utc > @end_date
74
+
75
+ true
76
+ end
77
+
78
+ # Check if test is scheduled to start
79
+ # @return [Boolean]
80
+ def scheduled?
81
+ @status == "scheduled" && @start_date && Time.now.utc < @start_date
82
+ end
83
+
84
+ # Check if test is completed
85
+ # @return [Boolean]
86
+ def completed?
87
+ @status == "completed" || (@end_date && Time.now.utc > @end_date)
88
+ end
89
+
90
+ # Start the test
91
+ def start!
92
+ raise InvalidStatusTransitionError, "Cannot start test from status: #{@status}" unless can_start?
93
+
94
+ @status = "running"
95
+ @start_date = Time.now.utc if @start_date.nil? || @start_date > Time.now.utc
96
+ end
97
+
98
+ # Complete the test
99
+ def complete!
100
+ raise InvalidStatusTransitionError, "Cannot complete test from status: #{@status}" unless can_complete?
101
+
102
+ @status = "completed"
103
+ @end_date = Time.now.utc
104
+ end
105
+
106
+ # Cancel the test
107
+ def cancel!
108
+ raise InvalidStatusTransitionError, "Cannot cancel test from status: #{@status}" if @status == "completed"
109
+
110
+ @status = "cancelled"
111
+ end
112
+
113
+ # Convert to hash representation
114
+ # @return [Hash]
115
+ def to_h
116
+ {
117
+ id: @id,
118
+ name: @name,
119
+ champion_version_id: @champion_version_id,
120
+ challenger_version_id: @challenger_version_id,
121
+ traffic_split: @traffic_split,
122
+ start_date: @start_date,
123
+ end_date: @end_date,
124
+ status: @status
125
+ }
126
+ end
127
+
128
+ private
129
+
130
+ def validate!
131
+ raise ValidationError, "Test name is required" if @name.nil? || @name.strip.empty?
132
+ raise ValidationError, "Champion version ID is required" if @champion_version_id.nil?
133
+ raise ValidationError, "Challenger version ID is required" if @challenger_version_id.nil?
134
+ raise ValidationError, "Champion and challenger must be different versions" if @champion_version_id == @challenger_version_id
135
+
136
+ validate_traffic_split!
137
+ validate_dates!
138
+ validate_status!
139
+ end
140
+
141
+ def validate_traffic_split!
142
+ raise ValidationError, "Traffic split must be a Hash" unless @traffic_split.is_a?(Hash)
143
+
144
+ unless @traffic_split.key?(:champion) && @traffic_split.key?(:challenger)
145
+ raise ValidationError,
146
+ "Traffic split must have :champion and :challenger keys"
147
+ end
148
+
149
+ total = @traffic_split[:champion] + @traffic_split[:challenger]
150
+ raise ValidationError, "Traffic split must sum to 100, got #{total}" unless total == 100
151
+
152
+ raise ValidationError, "Traffic percentages must be non-negative" if @traffic_split.values.any?(&:negative?)
153
+ end
154
+
155
+ def validate_dates!
156
+ return unless @start_date && @end_date
157
+
158
+ raise ValidationError, "End date must be after start date" if @end_date <= @start_date
159
+ end
160
+
161
+ def validate_status!
162
+ valid_statuses = %w[scheduled running completed cancelled]
163
+ return if valid_statuses.include?(@status)
164
+
165
+ raise ValidationError, "Invalid status: #{@status}. Must be one of: #{valid_statuses.join(', ')}"
166
+ end
167
+
168
+ def normalize_traffic_split(split)
169
+ case split
170
+ when Hash
171
+ # Handle both string and symbol keys
172
+ {
173
+ champion: (split[:champion] || split["champion"] || 50).to_i,
174
+ challenger: (split[:challenger] || split["challenger"] || 50).to_i
175
+ }
176
+ when Array
177
+ # Handle array format [90, 10]
178
+ { champion: split[0].to_i, challenger: split[1].to_i }
179
+ else
180
+ raise ValidationError, "Traffic split must be a Hash or Array"
181
+ end
182
+ end
183
+
184
+ def can_start?
185
+ %w[scheduled].include?(@status)
186
+ end
187
+
188
+ def can_complete?
189
+ %w[running].include?(@status)
190
+ end
191
+ end
192
+
193
+ # Custom errors
194
+ class TestNotRunningError < StandardError; end
195
+ class InvalidStatusTransitionError < StandardError; end
196
+ end
197
+ end
@@ -0,0 +1,76 @@
1
+ module DecisionAgent
2
+ module ABTesting
3
+ # Tracks individual assignments of users/requests to A/B test variants
4
+ class ABTestAssignment
5
+ attr_reader :id, :ab_test_id, :user_id, :variant, :version_id,
6
+ :timestamp, :decision_result, :confidence, :context
7
+
8
+ # @param ab_test_id [String, Integer] The A/B test ID
9
+ # @param variant [Symbol] :champion or :challenger
10
+ # @param version_id [String, Integer] The rule version ID that was used
11
+ # @param options [Hash] Optional configuration
12
+ # @option options [String] :user_id User identifier (optional)
13
+ # @option options [Time] :timestamp When the assignment occurred
14
+ # @option options [String] :decision_result The decision outcome
15
+ # @option options [Float] :confidence Confidence score of the decision
16
+ # @option options [Hash] :context Additional context for the decision
17
+ # @option options [String, Integer] :id Optional ID (for persistence)
18
+ def initialize(
19
+ ab_test_id:,
20
+ variant:,
21
+ version_id:,
22
+ **options
23
+ )
24
+ @id = options[:id]
25
+ @ab_test_id = ab_test_id
26
+ @user_id = options[:user_id]
27
+ @variant = variant
28
+ @version_id = version_id
29
+ @timestamp = options[:timestamp] || Time.now.utc
30
+ @decision_result = options[:decision_result]
31
+ @confidence = options[:confidence]
32
+ @context = options[:context] || {}
33
+
34
+ validate!
35
+ end
36
+
37
+ # Update the assignment with decision results
38
+ # @param decision [String] The decision result
39
+ # @param confidence [Float] The confidence score
40
+ def record_decision(decision, confidence)
41
+ @decision_result = decision
42
+ @confidence = confidence
43
+ end
44
+
45
+ # Convert to hash representation
46
+ # @return [Hash]
47
+ def to_h
48
+ {
49
+ id: @id,
50
+ ab_test_id: @ab_test_id,
51
+ user_id: @user_id,
52
+ variant: @variant,
53
+ version_id: @version_id,
54
+ timestamp: @timestamp,
55
+ decision_result: @decision_result,
56
+ confidence: @confidence,
57
+ context: @context
58
+ }
59
+ end
60
+
61
+ private
62
+
63
+ def validate!
64
+ raise ValidationError, "AB test ID is required" if @ab_test_id.nil?
65
+ raise ValidationError, "Variant is required" if @variant.nil?
66
+ raise ValidationError, "Version ID is required" if @version_id.nil?
67
+
68
+ raise ValidationError, "Variant must be :champion or :challenger, got: #{@variant}" unless %i[champion challenger].include?(@variant)
69
+
70
+ return unless @confidence && (@confidence.negative? || @confidence > 1)
71
+
72
+ raise ValidationError, "Confidence must be between 0 and 1, got: #{@confidence}"
73
+ end
74
+ end
75
+ end
76
+ end