decision_agent 0.2.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +313 -8
  3. data/bin/decision_agent +104 -0
  4. data/lib/decision_agent/agent.rb +72 -1
  5. data/lib/decision_agent/context.rb +1 -0
  6. data/lib/decision_agent/data_enrichment/cache/memory_adapter.rb +86 -0
  7. data/lib/decision_agent/data_enrichment/cache_adapter.rb +49 -0
  8. data/lib/decision_agent/data_enrichment/circuit_breaker.rb +135 -0
  9. data/lib/decision_agent/data_enrichment/client.rb +220 -0
  10. data/lib/decision_agent/data_enrichment/config.rb +78 -0
  11. data/lib/decision_agent/data_enrichment/errors.rb +36 -0
  12. data/lib/decision_agent/decision.rb +102 -2
  13. data/lib/decision_agent/dmn/adapter.rb +135 -0
  14. data/lib/decision_agent/dmn/cache.rb +306 -0
  15. data/lib/decision_agent/dmn/decision_graph.rb +327 -0
  16. data/lib/decision_agent/dmn/decision_tree.rb +192 -0
  17. data/lib/decision_agent/dmn/errors.rb +30 -0
  18. data/lib/decision_agent/dmn/exporter.rb +217 -0
  19. data/lib/decision_agent/dmn/feel/evaluator.rb +819 -0
  20. data/lib/decision_agent/dmn/feel/functions.rb +420 -0
  21. data/lib/decision_agent/dmn/feel/parser.rb +349 -0
  22. data/lib/decision_agent/dmn/feel/simple_parser.rb +276 -0
  23. data/lib/decision_agent/dmn/feel/transformer.rb +372 -0
  24. data/lib/decision_agent/dmn/feel/types.rb +276 -0
  25. data/lib/decision_agent/dmn/importer.rb +77 -0
  26. data/lib/decision_agent/dmn/model.rb +197 -0
  27. data/lib/decision_agent/dmn/parser.rb +191 -0
  28. data/lib/decision_agent/dmn/testing.rb +333 -0
  29. data/lib/decision_agent/dmn/validator.rb +315 -0
  30. data/lib/decision_agent/dmn/versioning.rb +229 -0
  31. data/lib/decision_agent/dmn/visualizer.rb +513 -0
  32. data/lib/decision_agent/dsl/condition_evaluator.rb +984 -838
  33. data/lib/decision_agent/dsl/schema_validator.rb +53 -14
  34. data/lib/decision_agent/evaluators/dmn_evaluator.rb +308 -0
  35. data/lib/decision_agent/evaluators/json_rule_evaluator.rb +69 -9
  36. data/lib/decision_agent/explainability/condition_trace.rb +83 -0
  37. data/lib/decision_agent/explainability/explainability_result.rb +52 -0
  38. data/lib/decision_agent/explainability/rule_trace.rb +39 -0
  39. data/lib/decision_agent/explainability/trace_collector.rb +24 -0
  40. data/lib/decision_agent/monitoring/alert_manager.rb +5 -1
  41. data/lib/decision_agent/simulation/errors.rb +18 -0
  42. data/lib/decision_agent/simulation/impact_analyzer.rb +498 -0
  43. data/lib/decision_agent/simulation/monte_carlo_simulator.rb +635 -0
  44. data/lib/decision_agent/simulation/replay_engine.rb +486 -0
  45. data/lib/decision_agent/simulation/scenario_engine.rb +318 -0
  46. data/lib/decision_agent/simulation/scenario_library.rb +163 -0
  47. data/lib/decision_agent/simulation/shadow_test_engine.rb +287 -0
  48. data/lib/decision_agent/simulation/what_if_analyzer.rb +1002 -0
  49. data/lib/decision_agent/simulation.rb +17 -0
  50. data/lib/decision_agent/version.rb +1 -1
  51. data/lib/decision_agent/versioning/activerecord_adapter.rb +23 -8
  52. data/lib/decision_agent/web/dmn_editor.rb +426 -0
  53. data/lib/decision_agent/web/public/app.js +119 -0
  54. data/lib/decision_agent/web/public/dmn-editor.css +596 -0
  55. data/lib/decision_agent/web/public/dmn-editor.html +250 -0
  56. data/lib/decision_agent/web/public/dmn-editor.js +553 -0
  57. data/lib/decision_agent/web/public/index.html +52 -0
  58. data/lib/decision_agent/web/public/simulation.html +130 -0
  59. data/lib/decision_agent/web/public/simulation_impact.html +478 -0
  60. data/lib/decision_agent/web/public/simulation_replay.html +551 -0
  61. data/lib/decision_agent/web/public/simulation_shadow.html +546 -0
  62. data/lib/decision_agent/web/public/simulation_whatif.html +532 -0
  63. data/lib/decision_agent/web/public/styles.css +86 -0
  64. data/lib/decision_agent/web/server.rb +1059 -23
  65. data/lib/decision_agent.rb +60 -2
  66. metadata +105 -61
  67. data/spec/ab_testing/ab_test_assignment_spec.rb +0 -253
  68. data/spec/ab_testing/ab_test_manager_spec.rb +0 -612
  69. data/spec/ab_testing/ab_test_spec.rb +0 -270
  70. data/spec/ab_testing/ab_testing_agent_spec.rb +0 -481
  71. data/spec/ab_testing/storage/adapter_spec.rb +0 -64
  72. data/spec/ab_testing/storage/memory_adapter_spec.rb +0 -485
  73. data/spec/activerecord_thread_safety_spec.rb +0 -553
  74. data/spec/advanced_operators_spec.rb +0 -3150
  75. data/spec/agent_spec.rb +0 -289
  76. data/spec/api_contract_spec.rb +0 -430
  77. data/spec/audit_adapters_spec.rb +0 -92
  78. data/spec/auth/access_audit_logger_spec.rb +0 -394
  79. data/spec/auth/authenticator_spec.rb +0 -112
  80. data/spec/auth/password_reset_spec.rb +0 -294
  81. data/spec/auth/permission_checker_spec.rb +0 -207
  82. data/spec/auth/permission_spec.rb +0 -73
  83. data/spec/auth/rbac_adapter_spec.rb +0 -550
  84. data/spec/auth/rbac_config_spec.rb +0 -82
  85. data/spec/auth/role_spec.rb +0 -51
  86. data/spec/auth/session_manager_spec.rb +0 -172
  87. data/spec/auth/session_spec.rb +0 -112
  88. data/spec/auth/user_spec.rb +0 -130
  89. data/spec/comprehensive_edge_cases_spec.rb +0 -1777
  90. data/spec/context_spec.rb +0 -127
  91. data/spec/decision_agent_spec.rb +0 -96
  92. data/spec/decision_spec.rb +0 -423
  93. data/spec/dsl/condition_evaluator_spec.rb +0 -774
  94. data/spec/dsl_validation_spec.rb +0 -648
  95. data/spec/edge_cases_spec.rb +0 -353
  96. data/spec/evaluation_spec.rb +0 -364
  97. data/spec/evaluation_validator_spec.rb +0 -165
  98. data/spec/examples/feedback_aware_evaluator_spec.rb +0 -460
  99. data/spec/examples.txt +0 -1633
  100. data/spec/issue_verification_spec.rb +0 -759
  101. data/spec/json_rule_evaluator_spec.rb +0 -587
  102. data/spec/monitoring/alert_manager_spec.rb +0 -378
  103. data/spec/monitoring/metrics_collector_spec.rb +0 -499
  104. data/spec/monitoring/monitored_agent_spec.rb +0 -222
  105. data/spec/monitoring/prometheus_exporter_spec.rb +0 -242
  106. data/spec/monitoring/storage/activerecord_adapter_spec.rb +0 -498
  107. data/spec/monitoring/storage/base_adapter_spec.rb +0 -61
  108. data/spec/monitoring/storage/memory_adapter_spec.rb +0 -247
  109. data/spec/performance_optimizations_spec.rb +0 -486
  110. data/spec/replay_edge_cases_spec.rb +0 -699
  111. data/spec/replay_spec.rb +0 -210
  112. data/spec/rfc8785_canonicalization_spec.rb +0 -215
  113. data/spec/scoring_spec.rb +0 -225
  114. data/spec/spec_helper.rb +0 -60
  115. data/spec/testing/batch_test_importer_spec.rb +0 -693
  116. data/spec/testing/batch_test_runner_spec.rb +0 -307
  117. data/spec/testing/test_coverage_analyzer_spec.rb +0 -292
  118. data/spec/testing/test_result_comparator_spec.rb +0 -392
  119. data/spec/testing/test_scenario_spec.rb +0 -113
  120. data/spec/thread_safety_spec.rb +0 -482
  121. data/spec/thread_safety_spec.rb.broken +0 -878
  122. data/spec/versioning/adapter_spec.rb +0 -156
  123. data/spec/versioning_spec.rb +0 -1030
  124. data/spec/web/middleware/auth_middleware_spec.rb +0 -133
  125. data/spec/web/middleware/permission_middleware_spec.rb +0 -247
  126. data/spec/web_ui_rack_spec.rb +0 -1840
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9fa9e4bed91c21f5402c19a902132014c30ab88b2acae6416d51831a544a0da
4
- data.tar.gz: df95ffc19c834fc4a6c6bfe9fa72544d958e6715fef217e6ca680171c095caa8
3
+ metadata.gz: a016bb964d8daeb5676d84ba597f07af9e7b4816a8f075d3ca9feb6f1ddd2f44
4
+ data.tar.gz: ccc960d23a5c863b8e9be429b08188f32abe630d83e2008a3e70534401779d92
5
5
  SHA512:
6
- metadata.gz: baf0c6e8b0a5895883cf6d3186f9f6576417a7bf943032aded6c5a989617a3dace19cb5f8d949d5488b10499d437f62519842ea22cc05751b9e5111ba3bc3860
7
- data.tar.gz: 4196b121c6c061e0271278fba1f2861c830b6b358d09e0cf8cd29859aa05b7049b6eb3272f6ba961b9f9bc55a39786e1f1eb43f8728fb9039a82186da3ba9ae0
6
+ metadata.gz: 4f84301ecb298b3fdc3d39b2ca02e850345d2df13eb0c8834555f1b3f08241f243dee54fd044dfd0a37bd2fa32e690dca7583c69049af68d0e951572ca222ad0
7
+ data.tar.gz: a52598d8c6358ea03ed16a3fd23d68fbea1ef9619f1c4dec613359427a11e9290cd6f4b9f8816fb6673d436b59bdd23b986f816a23ecac185eae1e27b86e6dee
data/README.md CHANGED
@@ -14,8 +14,8 @@ A production-grade, deterministic, explainable, and auditable decision engine fo
14
14
  DecisionAgent is designed for applications that require **deterministic, explainable, and auditable** decision-making:
15
15
 
16
16
  - ✅ **Deterministic** - Same input always produces same output
17
- - ✅ **Explainable** - Every decision includes human-readable reasoning
18
- - ✅ **Auditable** - Reproduce any historical decision exactly
17
+ - ✅ **Explainable** - Every decision includes human-readable reasoning and machine-readable condition traces
18
+ - ✅ **Auditable** - Reproduce any historical decision exactly with complete explainability
19
19
  - ✅ **Framework-agnostic** - Pure Ruby, works anywhere
20
20
  - ✅ **Production-ready** - Comprehensive testing ([Coverage Report](coverage.md)), error handling, and versioning
21
21
 
@@ -55,9 +55,12 @@ agent = DecisionAgent::Agent.new(evaluators: [evaluator])
55
55
  # Make decision
56
56
  result = agent.decide(context: { amount: 1500 })
57
57
 
58
- puts result.decision # => "approve"
59
- puts result.confidence # => 0.9
60
- puts result.explanations # => ["High value transaction"]
58
+ puts result.decision # => "approve"
59
+ puts result.confidence # => 0.9
60
+ puts result.explanations # => ["High value transaction"]
61
+ puts result.because # => ["amount > 1000"]
62
+ puts result.failed_conditions # => []
63
+ puts result.explainability # => { decision: "approve", because: [...], failed_conditions: [] }
61
64
  ```
62
65
 
63
66
  See [Code Examples](docs/CODE_EXAMPLES.md) for more comprehensive examples.
@@ -69,25 +72,52 @@ See [Code Examples](docs/CODE_EXAMPLES.md) for more comprehensive examples.
69
72
  - **Conflict Resolution** - Weighted average, consensus, threshold, max weight
70
73
  - **Rich Context** - Nested data, dot notation, flexible operators
71
74
  - **Advanced Operators** - String, numeric, date/time, collection, and geospatial operators
75
+ - **REST API Data Enrichment** - Fetch external data during decision-making with caching and circuit breaker
72
76
 
73
77
  ### Auditability & Compliance
74
78
  - **Complete Audit Trails** - Every decision fully logged
79
+ - **Explainability Layer** - Machine-readable condition traces for every decision
80
+ - `result.because` - Conditions that led to the decision
81
+ - `result.failed_conditions` - Conditions that failed
82
+ - `result.explainability` - Complete machine-readable explainability data
75
83
  - **Deterministic Replay** - Reproduce historical decisions exactly
76
84
  - **RFC 8785 Canonical JSON** - Industry-standard deterministic hashing
77
85
  - **Compliance Ready** - HIPAA, SOX, regulatory compliance support
78
86
 
87
+ ### Testing & Simulation
88
+ - **Simulation & What-If Analysis** - Test rule changes before deployment
89
+ - **Historical Replay / Backtesting** - Replay past decisions with new rules (CSV, JSON, database import)
90
+ - **What-If Analysis** - Simulate scenarios and sensitivity analysis with decision boundary visualization
91
+ - **Impact Analysis** - Quantify rule change effects (decision distribution, confidence shifts, performance impact)
92
+ - **Shadow Testing** - Compare new rules against production without affecting outcomes
93
+ - **Monte Carlo Simulation** - Model probabilistic inputs and understand decision outcome probabilities
94
+ - **Batch Testing** - Test rules against large datasets with CSV/Excel import, coverage analysis, and resume capability
95
+ - **A/B Testing** - Champion/Challenger testing with statistical significance analysis
96
+
97
+ ### Security & Access Control
98
+ - **Role-Based Access Control (RBAC)** - Enterprise-grade authentication and authorization
99
+ - Built-in user/role system with bcrypt password hashing
100
+ - Configurable adapters for Devise, CanCanCan, Pundit, or custom auth systems
101
+ - 5 default roles (Admin, Editor, Viewer, Auditor, Approver) with 7 permissions
102
+ - Password reset functionality with secure token management
103
+ - Comprehensive access audit logging for compliance
104
+ - Web UI integration with login and user management pages
105
+
79
106
  ### Developer Experience
80
107
  - **Pluggable Architecture** - Custom evaluators, scoring, audit adapters
81
108
  - **Framework Agnostic** - Works with Rails, Sinatra, or standalone
82
109
  - **JSON Rule DSL** - Non-technical users can write rules
83
- - **Visual Rule Builder** - Web UI for rule management
110
+ - **DMN 1.3 Support** - Industry-standard Decision Model and Notation with full FEEL expression language
111
+ - **Visual Rule Builder** - Web UI for rule management and DMN modeler
112
+ - **CLI Tools** - Command-line interface for DMN import/export and web server
84
113
 
85
114
  ### Production Features
86
115
  - **Real-time Monitoring** - Live dashboard with WebSocket updates
116
+ - **Persistent Monitoring** - Database storage for long-term analytics (PostgreSQL, MySQL, SQLite)
87
117
  - **Prometheus Export** - Industry-standard metrics format
88
118
  - **Intelligent Alerting** - Anomaly detection with customizable rules
89
119
  - **Grafana Integration** - Pre-built dashboards and alert rules
90
- - **Version Control** - Full rule version control and rollback
120
+ - **Version Control** - Full rule version control, rollback, and history ([Versioning Guide](docs/VERSIONING.md))
91
121
  - **Thread-Safe** - Safe for multi-threaded servers and background jobs
92
122
  - **High Performance** - 10,000+ decisions/second, ~0.1ms latency
93
123
 
@@ -121,6 +151,91 @@ end
121
151
 
122
152
  See [Web UI Integration Guide](docs/WEB_UI_RAILS_INTEGRATION.md) for detailed setup.
123
153
 
154
+ ## DMN (Decision Model and Notation) Support
155
+
156
+ DecisionAgent includes full support for **DMN 1.3**, the industry standard for decision modeling:
157
+
158
+ ```ruby
159
+ require 'decision_agent'
160
+ require 'decision_agent/dmn/importer'
161
+ require 'decision_agent/evaluators/dmn_evaluator'
162
+
163
+ # Import DMN XML file
164
+ importer = DecisionAgent::Dmn::Importer.new
165
+ result = importer.import('path/to/model.dmn', created_by: 'user@example.com')
166
+
167
+ # Create DMN evaluator
168
+ evaluator = DecisionAgent::Evaluators::DmnEvaluator.new(
169
+ model: result[:model],
170
+ decision_id: 'loan_approval'
171
+ )
172
+
173
+ # Use with Agent
174
+ agent = DecisionAgent::Agent.new(evaluators: [evaluator])
175
+ result = agent.decide(context: { amount: 50000, credit_score: 750 })
176
+ ```
177
+
178
+ **Features:**
179
+ - **DMN 1.3 Standard** - Full OMG DMN 1.3 compliance
180
+ - **FEEL Expressions** - Complete FEEL 1.3 language support (arithmetic, logical, functions)
181
+ - **All Hit Policies** - UNIQUE, FIRST, PRIORITY, ANY, COLLECT
182
+ - **Import/Export** - Round-trip conversion with other DMN tools (Camunda, Drools, IBM ODM)
183
+ - **Visual Modeler** - Web-based DMN editor at `/dmn/editor`
184
+ - **CLI Commands** - `decision_agent dmn import` and `decision_agent dmn export`
185
+
186
+ See [DMN Guide](docs/DMN_GUIDE.md) for complete documentation and [DMN Examples](examples/dmn/README.md) for working examples.
187
+
188
+ ## REST API Data Enrichment
189
+
190
+ DecisionAgent supports fetching external data during decision-making without manual context assembly:
191
+
192
+ ```ruby
193
+ require 'decision_agent'
194
+
195
+ # Configure data enrichment endpoints
196
+ DecisionAgent.configure_data_enrichment do |config|
197
+ config.add_endpoint(:credit_bureau,
198
+ url: "https://api.creditbureau.com/v1/score",
199
+ method: :post,
200
+ auth: { type: :api_key, header: "X-API-Key" },
201
+ cache: { ttl: 3600, adapter: :memory }
202
+ )
203
+ end
204
+
205
+ # Use in rules with fetch_from_api operator
206
+ rules = {
207
+ version: "1.0",
208
+ ruleset: "loan_approval",
209
+ rules: [{
210
+ id: "check_credit",
211
+ if: {
212
+ field: "credit_score",
213
+ op: "fetch_from_api",
214
+ value: {
215
+ endpoint: "credit_bureau",
216
+ params: { ssn: "{{customer.ssn}}" },
217
+ mapping: { score: "credit_score" }
218
+ }
219
+ },
220
+ then: { decision: "approve", weight: 0.8 }
221
+ }]
222
+ }
223
+
224
+ evaluator = DecisionAgent::Evaluators::JsonRuleEvaluator.new(rules_json: rules)
225
+ agent = DecisionAgent::Agent.new(evaluators: [evaluator])
226
+ result = agent.decide(context: { customer: { ssn: "123-45-6789" } })
227
+ ```
228
+
229
+ **Features:**
230
+ - **HTTP Client** - Support for GET, POST, PUT, DELETE methods
231
+ - **Response Caching** - Configurable TTL per endpoint with memory adapter
232
+ - **Circuit Breaker** - Fail-fast after N failures to prevent cascading failures
233
+ - **Authentication** - API key, Basic Auth, and Bearer token support
234
+ - **Template Parameters** - Use `{{path}}` syntax to reference context values
235
+ - **Error Handling** - Graceful degradation with cached data fallback
236
+
237
+ See [Data Enrichment Guide](docs/DATA_ENRICHMENT.md) for complete documentation and [Data Enrichment Example](examples/data_enrichment_example.rb) for working examples.
238
+
124
239
  ## Monitoring & Analytics
125
240
 
126
241
  Real-time monitoring, metrics, and alerting for production environments.
@@ -140,11 +255,155 @@ Open [http://localhost:4568](http://localhost:4568) for the monitoring dashboard
140
255
 
141
256
  **Features:**
142
257
  - Real-time dashboard with WebSocket updates
258
+ - **Persistent Storage** - Database storage for long-term analytics (PostgreSQL, MySQL, SQLite)
143
259
  - Prometheus metrics export
144
260
  - Intelligent alerting with anomaly detection
145
261
  - Grafana integration with pre-built dashboards
146
262
 
147
- See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) for complete documentation.
263
+ See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) and [Persistent Monitoring Guide](docs/PERSISTENT_MONITORING.md) for complete documentation.
264
+
265
+ ## Simulation & What-If Analysis
266
+
267
+ DecisionAgent provides comprehensive simulation capabilities to test rule changes before deployment:
268
+
269
+ ```ruby
270
+ require 'decision_agent/simulation/replay_engine'
271
+
272
+ # Replay historical decisions with new rules
273
+ replay_engine = DecisionAgent::Simulation::ReplayEngine.new(
274
+ agent: agent,
275
+ version_manager: version_manager
276
+ )
277
+
278
+ results = replay_engine.replay(historical_data: "decisions.csv")
279
+
280
+ # What-if analysis
281
+ whatif = DecisionAgent::Simulation::WhatIfAnalyzer.new(agent: agent)
282
+ analysis = whatif.analyze(
283
+ base_context: { credit_score: 700, amount: 50000 },
284
+ scenarios: [
285
+ { credit_score: 750 },
286
+ { credit_score: 650 }
287
+ ]
288
+ )
289
+
290
+ # Impact analysis
291
+ impact = DecisionAgent::Simulation::ImpactAnalyzer.new
292
+ comparison = impact.compare(
293
+ baseline: baseline_evaluator,
294
+ proposed: proposed_evaluator,
295
+ contexts: test_contexts
296
+ )
297
+ ```
298
+
299
+ **Features:**
300
+ - **Historical Replay** - Replay past decisions with CSV/JSON/database import
301
+ - **What-If Analysis** - Scenario simulation with decision boundary visualization
302
+ - **Impact Analysis** - Quantify rule change effects (decisions, confidence, performance)
303
+ - **Shadow Testing** - Test new rules in production without affecting outcomes
304
+ - **Monte Carlo Simulation** - Probabilistic decision modeling
305
+ - **Web UI** - Complete simulation dashboard at `/simulation`
306
+
307
+ See [Simulation Guide](docs/SIMULATION.md) for complete documentation and [Simulation Example](examples/simulation_example.rb) for working examples.
308
+
309
+ ## Role-Based Access Control (RBAC)
310
+
311
+ Enterprise-grade authentication and authorization system:
312
+
313
+ ```ruby
314
+ require 'decision_agent'
315
+
316
+ # Configure RBAC (works with any auth system)
317
+ DecisionAgent.configure_rbac(:devise_cancan, ability_class: Ability)
318
+
319
+ # Or use built-in RBAC
320
+ authenticator = DecisionAgent::Auth::Authenticator.new
321
+ admin = authenticator.create_user(
322
+ email: "admin@example.com",
323
+ password: "secure_password",
324
+ roles: [:admin]
325
+ )
326
+
327
+ session = authenticator.login("admin@example.com", "secure_password")
328
+
329
+ # Permission checks
330
+ checker = DecisionAgent.permission_checker
331
+ checker.can?(admin, :write) # => true
332
+ checker.can?(admin, :approve) # => true
333
+ ```
334
+
335
+ **Features:**
336
+ - **Built-in User System** - User management with bcrypt password hashing
337
+ - **5 Default Roles** - Admin, Editor, Viewer, Auditor, Approver
338
+ - **Configurable Adapters** - Devise, CanCanCan, Pundit, or custom
339
+ - **Password Reset** - Secure token-based password reset
340
+ - **Access Audit Logging** - Comprehensive audit trail for compliance
341
+ - **Web UI Integration** - Login page and user management interface
342
+
343
+ See [RBAC Configuration Guide](docs/RBAC_CONFIGURATION.md) for complete documentation and [RBAC Examples](examples/rbac_configuration_examples.rb) for integration examples.
344
+
345
+ ## Batch Testing
346
+
347
+ Test rules against large datasets with comprehensive analysis:
348
+
349
+ ```ruby
350
+ require 'decision_agent/testing/batch_test_runner'
351
+
352
+ runner = DecisionAgent::Testing::BatchTestRunner.new(agent: agent)
353
+
354
+ # Import from CSV or Excel
355
+ importer = DecisionAgent::Testing::BatchTestImporter.new
356
+ scenarios = importer.import_csv("test_data.csv", {
357
+ context_fields: ["credit_score", "amount"],
358
+ expected_fields: ["expected_decision"]
359
+ })
360
+
361
+ # Run batch test
362
+ results = runner.run(scenarios: scenarios)
363
+
364
+ puts "Total: #{results[:total]}"
365
+ puts "Passed: #{results[:passed]}"
366
+ puts "Failed: #{results[:failed]}"
367
+ puts "Coverage: #{results[:coverage]}"
368
+ ```
369
+
370
+ **Features:**
371
+ - **CSV/Excel Import** - Import test scenarios from files
372
+ - **Database Import** - Load test data from databases
373
+ - **Coverage Analysis** - Identify untested rule combinations
374
+ - **Resume Capability** - Continue interrupted tests from checkpoint
375
+ - **Progress Tracking** - Real-time progress updates for large imports
376
+ - **Web UI** - Complete batch testing interface with file upload
377
+
378
+ See [Batch Testing Guide](docs/BATCH_TESTING.md) for complete documentation.
379
+
380
+ ## A/B Testing
381
+
382
+ Compare rule versions with statistical analysis:
383
+
384
+ ```ruby
385
+ require 'decision_agent/testing/ab_test_manager'
386
+
387
+ ab_manager = DecisionAgent::Testing::AbTestManager.new(version_manager: version_manager)
388
+
389
+ test = ab_manager.create_test(
390
+ name: "loan_approval_v2",
391
+ champion_version: champion_version_id,
392
+ challenger_version: challenger_version_id,
393
+ traffic_split: 0.5
394
+ )
395
+
396
+ results = ab_manager.run_test(test_id: test.id, contexts: test_contexts)
397
+ ab_manager.analyze_results(test_id: test.id)
398
+ ```
399
+
400
+ **Features:**
401
+ - **Champion/Challenger Testing** - Compare baseline vs proposed rules
402
+ - **Statistical Significance** - P-value calculation and confidence intervals
403
+ - **Traffic Splitting** - Configurable split ratios
404
+ - **Decision Distribution Comparison** - Visualize differences in outcomes
405
+
406
+ See [A/B Testing Guide](docs/AB_TESTING.md) for complete documentation.
148
407
 
149
408
  ## When to Use DecisionAgent
150
409
 
@@ -169,18 +428,31 @@ See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) for complet
169
428
  - [Examples Directory](examples/README.md) - Working examples with explanations
170
429
 
171
430
  ### Core Features
431
+ - [Explainability Layer](docs/EXPLAINABILITY.md) - Machine-readable decision explanations with condition-level tracing
172
432
  - [Advanced Operators](docs/ADVANCED_OPERATORS.md) - String, numeric, date/time, collection, and geospatial operators
433
+ - [Data Enrichment](docs/DATA_ENRICHMENT.md) - REST API data enrichment with caching and circuit breaker
434
+ - [DMN Guide](docs/DMN_GUIDE.md) - Complete DMN 1.3 support guide
435
+ - [DMN API Reference](docs/DMN_API.md) - DMN API documentation
436
+ - [FEEL Reference](docs/FEEL_REFERENCE.md) - FEEL expression language reference
437
+ - [DMN Migration Guide](docs/DMN_MIGRATION_GUIDE.md) - Migrating from JSON to DMN
438
+ - [DMN Best Practices](docs/DMN_BEST_PRACTICES.md) - DMN modeling best practices
173
439
  - [Versioning System](docs/VERSIONING.md) - Version control for rules
440
+ - [Simulation & What-If Analysis](docs/SIMULATION.md) - Historical replay, what-if analysis, impact analysis, and shadow testing
174
441
  - [A/B Testing](docs/AB_TESTING.md) - Compare rule versions with statistical analysis
442
+ - [Batch Testing](docs/BATCH_TESTING.md) - Test rules against large datasets with CSV/Excel import
443
+ - [RBAC Configuration](docs/RBAC_CONFIGURATION.md) - Role-based access control setup and integration
444
+ - [RBAC Quick Reference](docs/RBAC_QUICK_REFERENCE.md) - Quick reference for RBAC configuration
175
445
  - [Web UI](docs/WEB_UI.md) - Visual rule builder
176
446
  - [Web UI Setup](docs/WEB_UI_SETUP.md) - Setup guide
177
447
  - [Web UI Rails Integration](docs/WEB_UI_RAILS_INTEGRATION.md) - Mount in Rails/Rack apps
178
448
  - [Monitoring & Analytics](docs/MONITORING_AND_ANALYTICS.md) - Real-time monitoring, metrics, and alerting
179
449
  - [Monitoring Architecture](docs/MONITORING_ARCHITECTURE.md) - System architecture and design
450
+ - [Persistent Monitoring](docs/PERSISTENT_MONITORING.md) - Database storage for long-term analytics
180
451
 
181
452
  ### Performance & Thread-Safety
182
453
  - [Performance & Thread-Safety Summary](docs/PERFORMANCE_AND_THREAD_SAFETY.md) - Benchmarks and production readiness
183
454
  - [Thread-Safety Implementation](docs/THREAD_SAFETY.md) - Technical implementation guide
455
+ - [Benchmarks](benchmarks/README.md) - Comprehensive benchmark suite and performance testing
184
456
 
185
457
  ### Reference
186
458
  - [API Contract](docs/API_CONTRACT.md) - Full API reference
@@ -204,6 +476,39 @@ All data structures are deeply frozen to prevent mutation, ensuring safe concurr
204
476
 
205
477
  See [Thread-Safety Guide](docs/THREAD_SAFETY.md) and [Performance Analysis](docs/PERFORMANCE_AND_THREAD_SAFETY.md) for details.
206
478
 
479
+ **Run Benchmarks:**
480
+ ```bash
481
+ # Run all benchmarks
482
+ rake benchmark:all
483
+
484
+ # Run specific benchmarks
485
+ rake benchmark:basic # Basic decision performance
486
+ rake benchmark:threads # Thread-safety and scalability
487
+ rake benchmark:regression # Compare against baseline
488
+
489
+ # See [Benchmarks Guide](benchmarks/README.md) for complete documentation
490
+ ```
491
+
492
+ ### Latest Benchmark Results
493
+
494
+ **Last Updated:** 2026-01-06T04:03:29Z
495
+
496
+ #### Performance Comparison
497
+
498
+ | Metric | Latest (2026-01-06) | Previous (2026-01-06) | Change |
499
+ |--------|--------------------------------------------------|------------------------------------------------------|--------|
500
+ | Basic Throughput | 8966.04 decisions/sec | 9751.42 decisions/sec | ↓ 8.05% (degraded) |
501
+ | Basic Latency | 0.1115 ms | 0.1025 ms | ↑ 8.78% (degraded) |
502
+ | Multi-threaded (50 threads) Throughput | 8560.69 decisions/sec | 8849.86 decisions/sec | ↓ 3.27% (degraded) |
503
+ | Multi-threaded (50 threads) Latency | 0.1168 ms | 0.113 ms | ↑ 3.36% (degraded) |
504
+
505
+ **Environment:**
506
+ - Ruby Version: 3.3.5
507
+ - Hardware: x86_64
508
+ - OS: Darwin
509
+ - Git Commit: `aba46af5`
510
+
511
+ > 💡 **Note:** Run `rake benchmark:regression` to generate new benchmark results. This section is automatically updated with the last 2 benchmark runs.
207
512
  ## Contributing
208
513
 
209
514
  1. Fork the repository
data/bin/decision_agent CHANGED
@@ -3,6 +3,8 @@
3
3
  require "bundler/setup"
4
4
  require_relative "../lib/decision_agent"
5
5
  require_relative "../lib/decision_agent/web/server"
6
+ require_relative "../lib/decision_agent/dmn/importer"
7
+ require_relative "../lib/decision_agent/dmn/exporter"
6
8
 
7
9
  def print_help
8
10
  puts <<~HELP
@@ -14,6 +16,8 @@ def print_help
14
16
  Commands:
15
17
  web [PORT] Start the web UI rule builder (default port: 4567)
16
18
  validate FILE Validate a rules JSON file
19
+ dmn import FILE Import a DMN XML file
20
+ dmn export RULESET OUTPUT Export a ruleset to DMN XML
17
21
  version Show version
18
22
  help Show this help message
19
23
 
@@ -21,6 +25,8 @@ def print_help
21
25
  decision_agent web # Start web UI on port 4567
22
26
  decision_agent web 8080 # Start web UI on port 8080
23
27
  decision_agent validate rules.json
28
+ decision_agent dmn import loan_decision.dmn
29
+ decision_agent dmn export loan_rules loan_export.dmn
24
30
  decision_agent version
25
31
 
26
32
  For more information, visit:
@@ -74,6 +80,75 @@ def validate_file(filepath)
74
80
  end
75
81
  end
76
82
 
83
+ def dmn_import(filepath, ruleset_name: nil)
84
+ unless File.exist?(filepath)
85
+ puts "❌ Error: File not found: #{filepath}"
86
+ exit 1
87
+ end
88
+
89
+ begin
90
+ puts "📥 Importing DMN file: #{filepath}..."
91
+
92
+ importer = DecisionAgent::Dmn::Importer.new
93
+ result = importer.import(
94
+ filepath,
95
+ ruleset_name: ruleset_name,
96
+ created_by: ENV["USER"] || "cli_user"
97
+ )
98
+
99
+ puts "✅ Import successful!"
100
+ puts " Model: #{result[:model].name}"
101
+ puts " Decisions imported: #{result[:decisions_imported]}"
102
+ puts " Namespace: #{result[:model].namespace}"
103
+
104
+ result[:model].decisions.each do |decision|
105
+ puts " - Decision: #{decision.name} (#{decision.id})"
106
+ if decision.decision_table
107
+ puts " Rules: #{decision.decision_table.rules.size}"
108
+ puts " Hit Policy: #{decision.decision_table.hit_policy}"
109
+ end
110
+ end
111
+
112
+ if result[:versions].any?
113
+ puts ""
114
+ puts " Versions created:"
115
+ result[:versions].each do |version|
116
+ puts " - #{version[:rule_id]}: version #{version[:version]}"
117
+ end
118
+ end
119
+ rescue DecisionAgent::Dmn::InvalidDmnModelError, DecisionAgent::Dmn::DmnParseError => e
120
+ puts "❌ DMN Import Error:"
121
+ puts " #{e.message}"
122
+ exit 1
123
+ rescue StandardError => e
124
+ puts "❌ Unexpected Error:"
125
+ puts " #{e.message}"
126
+ puts " #{e.backtrace.first}" if ENV["DEBUG"]
127
+ exit 1
128
+ end
129
+ end
130
+
131
+ def dmn_export(ruleset_id, output_path)
132
+ puts "📤 Exporting ruleset: #{ruleset_id}..."
133
+
134
+ exporter = DecisionAgent::Dmn::Exporter.new
135
+ dmn_xml = exporter.export(ruleset_id, output_path: output_path)
136
+
137
+ puts "✅ Export successful!"
138
+ puts " Ruleset: #{ruleset_id}"
139
+ puts " Output: #{output_path}"
140
+ puts " Size: #{dmn_xml.bytesize} bytes"
141
+ rescue DecisionAgent::Dmn::InvalidDmnModelError => e
142
+ puts "❌ Export Error:"
143
+ puts " #{e.message}"
144
+ exit 1
145
+ rescue StandardError => e
146
+ puts "❌ Unexpected Error:"
147
+ puts " #{e.message}"
148
+ puts " #{e.backtrace.first}" if ENV["DEBUG"]
149
+ exit 1
150
+ end
151
+
77
152
  # Main CLI handler
78
153
  command = ARGV[0] || "help"
79
154
 
@@ -90,6 +165,35 @@ when "validate"
90
165
  end
91
166
  validate_file(ARGV[1])
92
167
 
168
+ when "dmn"
169
+ subcommand = ARGV[1]
170
+ case subcommand
171
+ when "import"
172
+ if ARGV[2].nil?
173
+ puts "❌ Error: Please provide a DMN file path"
174
+ puts "Usage: decision_agent dmn import <file.xml>"
175
+ exit 1
176
+ end
177
+ ruleset_name = ARGV[3] # Optional ruleset name
178
+ dmn_import(ARGV[2], ruleset_name: ruleset_name)
179
+
180
+ when "export"
181
+ if ARGV[2].nil? || ARGV[3].nil?
182
+ puts "❌ Error: Please provide ruleset ID and output file path"
183
+ puts "Usage: decision_agent dmn export <ruleset> <output.xml>"
184
+ exit 1
185
+ end
186
+ dmn_export(ARGV[2], ARGV[3])
187
+
188
+ else
189
+ puts "❌ Unknown DMN subcommand: #{subcommand || '(none)'}"
190
+ puts ""
191
+ puts "DMN Commands:"
192
+ puts " import FILE Import a DMN XML file"
193
+ puts " export RULESET OUTPUT Export a ruleset to DMN XML"
194
+ exit 1
195
+ end
196
+
93
197
  when "version"
94
198
  puts "DecisionAgent version #{DecisionAgent::VERSION}"
95
199
 
@@ -6,6 +6,17 @@ module DecisionAgent
6
6
  class Agent
7
7
  attr_reader :evaluators, :scoring_strategy, :audit_adapter
8
8
 
9
+ # Thread-safe cache for deterministic hash computation
10
+ # This significantly improves performance when the same context/evaluations
11
+ # are processed multiple times (common in benchmarks and high-throughput scenarios)
12
+ @hash_cache = {}
13
+ @hash_cache_mutex = Mutex.new
14
+ @hash_cache_max_size = 1000 # Limit cache size to prevent memory bloat
15
+
16
+ class << self
17
+ attr_reader :hash_cache, :hash_cache_mutex, :hash_cache_max_size
18
+ end
19
+
9
20
  def initialize(evaluators:, scoring_strategy: nil, audit_adapter: nil, validate_evaluations: nil)
10
21
  @evaluators = Array(evaluators)
11
22
  @scoring_strategy = scoring_strategy || Scoring::WeightedAverage.new
@@ -129,8 +140,68 @@ module DecisionAgent
129
140
 
130
141
  def compute_deterministic_hash(payload)
131
142
  hashable = payload.slice(:context, :evaluations, :decision, :confidence, :scoring_strategy)
143
+
144
+ # Use fast hash (MD5) as cache key to avoid expensive canonicalization on cache hits
145
+ # Optimized: Use direct JSON.to_json instead of recursive sorting for speed
146
+ # The cache key doesn't need perfect determinism, just good enough to catch duplicates
147
+ # This avoids the expensive sort_hash_keys recursion on every call
148
+ json_str = hashable.to_json
149
+ fast_key = Digest::MD5.hexdigest(json_str)
150
+
151
+ # Fast path: check cache without lock first (unsafe read, but acceptable for cache)
152
+ # This allows concurrent reads without mutex overhead
153
+ cache = self.class.hash_cache
154
+ cached_hash = cache[fast_key]
155
+ return cached_hash if cached_hash
156
+
157
+ # Cache miss - compute canonical JSON (required for deterministic hashing)
158
+ # This is expensive, but only happens on cache misses
132
159
  canonical = canonical_json(hashable)
133
- Digest::SHA256.hexdigest(canonical)
160
+
161
+ # Compute SHA256 hash (also expensive, but only on cache misses)
162
+ computed_hash = Digest::SHA256.hexdigest(canonical)
163
+
164
+ # Store in cache (thread-safe, with size limit)
165
+ # Only lock when we need to write
166
+ self.class.hash_cache_mutex.synchronize do
167
+ # Double-check after acquiring lock (another thread may have added it)
168
+ return self.class.hash_cache[fast_key] if self.class.hash_cache[fast_key]
169
+
170
+ # Clear cache if it gets too large (simple FIFO eviction)
171
+ if self.class.hash_cache.size >= self.class.hash_cache_max_size
172
+ # Remove oldest 10% of entries (simple approximation)
173
+ keys_to_remove = self.class.hash_cache.keys.first(self.class.hash_cache_max_size / 10)
174
+ keys_to_remove.each { |key| self.class.hash_cache.delete(key) }
175
+ end
176
+ self.class.hash_cache[fast_key] = computed_hash
177
+ end
178
+
179
+ computed_hash
180
+ end
181
+
182
+ # Fast hash key generation using MD5 (much faster than canonical JSON + SHA256)
183
+ # Used as cache key to avoid expensive canonicalization on cache hits
184
+ # MD5 is sufficient for cache keys (collision resistance not critical, speed is)
185
+ def fast_hash_key(hashable)
186
+ # Create a deterministic string representation for hashing
187
+ # Use sorted JSON to ensure determinism (though not RFC 8785 canonical)
188
+ json_str = sort_hash_keys(hashable).to_json
189
+ Digest::MD5.hexdigest(json_str)
190
+ end
191
+
192
+ # Recursively sort hash keys for deterministic hashing
193
+ # This is faster than canonical JSON but still deterministic
194
+ # Note: This is still used by canonical_json indirectly, but fast_hash_key avoids it
195
+ def sort_hash_keys(obj)
196
+ case obj
197
+ when Hash
198
+ sorted = obj.sort.to_h
199
+ sorted.transform_values { |v| sort_hash_keys(v) }
200
+ when Array
201
+ obj.map { |v| sort_hash_keys(v) }
202
+ else
203
+ obj
204
+ end
134
205
  end
135
206
 
136
207
  # Uses RFC 8785 (JSON Canonicalization Scheme) for deterministic JSON serialization
@@ -4,6 +4,7 @@ module DecisionAgent
4
4
 
5
5
  def initialize(data)
6
6
  # Create a deep copy before freezing to avoid mutating the original
7
+ # This is necessary for thread-safety even if it adds some overhead
7
8
  data_hash = data.is_a?(Hash) ? data : {}
8
9
  @data = deep_freeze(deep_dup(data_hash))
9
10
  end