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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '095f5b6604126a4288548c648efbfb3f88cc94bf2a51ba84b21f422286c9c0f3'
4
- data.tar.gz: 0f5475b14f32d3f7524ba63d21152bda4d088312dc1b154a6c15dd73f8e038e0
3
+ metadata.gz: a016bb964d8daeb5676d84ba597f07af9e7b4816a8f075d3ca9feb6f1ddd2f44
4
+ data.tar.gz: ccc960d23a5c863b8e9be429b08188f32abe630d83e2008a3e70534401779d92
5
5
  SHA512:
6
- metadata.gz: 60f10c33779fac9cf26ed698a704b91e4ff65ca200d76870fa593a9074506b10a7791a471ab5cbb0fcdc9603145dc965131508619f0f1356f163cdaf069f49c9
7
- data.tar.gz: d60e5eaf6d7350a55047594481c00360fa188e66db11e84cf863c7c734d1210c777e7fb358a689470d84256dd225b22376deb50648443dd0257a5041816086c1
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,26 +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
110
  - **DMN 1.3 Support** - Industry-standard Decision Model and Notation with full FEEL expression language
84
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
85
113
 
86
114
  ### Production Features
87
115
  - **Real-time Monitoring** - Live dashboard with WebSocket updates
116
+ - **Persistent Monitoring** - Database storage for long-term analytics (PostgreSQL, MySQL, SQLite)
88
117
  - **Prometheus Export** - Industry-standard metrics format
89
118
  - **Intelligent Alerting** - Anomaly detection with customizable rules
90
119
  - **Grafana Integration** - Pre-built dashboards and alert rules
91
- - **Version Control** - Full rule version control and rollback
120
+ - **Version Control** - Full rule version control, rollback, and history ([Versioning Guide](docs/VERSIONING.md))
92
121
  - **Thread-Safe** - Safe for multi-threaded servers and background jobs
93
122
  - **High Performance** - 10,000+ decisions/second, ~0.1ms latency
94
123
 
@@ -156,6 +185,57 @@ result = agent.decide(context: { amount: 50000, credit_score: 750 })
156
185
 
157
186
  See [DMN Guide](docs/DMN_GUIDE.md) for complete documentation and [DMN Examples](examples/dmn/README.md) for working examples.
158
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
+
159
239
  ## Monitoring & Analytics
160
240
 
161
241
  Real-time monitoring, metrics, and alerting for production environments.
@@ -175,11 +255,155 @@ Open [http://localhost:4568](http://localhost:4568) for the monitoring dashboard
175
255
 
176
256
  **Features:**
177
257
  - Real-time dashboard with WebSocket updates
258
+ - **Persistent Storage** - Database storage for long-term analytics (PostgreSQL, MySQL, SQLite)
178
259
  - Prometheus metrics export
179
260
  - Intelligent alerting with anomaly detection
180
261
  - Grafana integration with pre-built dashboards
181
262
 
182
- 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.
183
407
 
184
408
  ## When to Use DecisionAgent
185
409
 
@@ -204,23 +428,31 @@ See [Monitoring & Analytics Guide](docs/MONITORING_AND_ANALYTICS.md) for complet
204
428
  - [Examples Directory](examples/README.md) - Working examples with explanations
205
429
 
206
430
  ### Core Features
431
+ - [Explainability Layer](docs/EXPLAINABILITY.md) - Machine-readable decision explanations with condition-level tracing
207
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
208
434
  - [DMN Guide](docs/DMN_GUIDE.md) - Complete DMN 1.3 support guide
209
435
  - [DMN API Reference](docs/DMN_API.md) - DMN API documentation
210
436
  - [FEEL Reference](docs/FEEL_REFERENCE.md) - FEEL expression language reference
211
437
  - [DMN Migration Guide](docs/DMN_MIGRATION_GUIDE.md) - Migrating from JSON to DMN
212
438
  - [DMN Best Practices](docs/DMN_BEST_PRACTICES.md) - DMN modeling best practices
213
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
214
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
215
445
  - [Web UI](docs/WEB_UI.md) - Visual rule builder
216
446
  - [Web UI Setup](docs/WEB_UI_SETUP.md) - Setup guide
217
447
  - [Web UI Rails Integration](docs/WEB_UI_RAILS_INTEGRATION.md) - Mount in Rails/Rack apps
218
448
  - [Monitoring & Analytics](docs/MONITORING_AND_ANALYTICS.md) - Real-time monitoring, metrics, and alerting
219
449
  - [Monitoring Architecture](docs/MONITORING_ARCHITECTURE.md) - System architecture and design
450
+ - [Persistent Monitoring](docs/PERSISTENT_MONITORING.md) - Database storage for long-term analytics
220
451
 
221
452
  ### Performance & Thread-Safety
222
453
  - [Performance & Thread-Safety Summary](docs/PERFORMANCE_AND_THREAD_SAFETY.md) - Benchmarks and production readiness
223
454
  - [Thread-Safety Implementation](docs/THREAD_SAFETY.md) - Technical implementation guide
455
+ - [Benchmarks](benchmarks/README.md) - Comprehensive benchmark suite and performance testing
224
456
 
225
457
  ### Reference
226
458
  - [API Contract](docs/API_CONTRACT.md) - Full API reference
@@ -244,6 +476,39 @@ All data structures are deeply frozen to prevent mutation, ensuring safe concurr
244
476
 
245
477
  See [Thread-Safety Guide](docs/THREAD_SAFETY.md) and [Performance Analysis](docs/PERFORMANCE_AND_THREAD_SAFETY.md) for details.
246
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.
247
512
  ## Contributing
248
513
 
249
514
  1. Fork the repository
@@ -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
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../cache_adapter"
4
+ require "monitor"
5
+
6
+ module DecisionAgent
7
+ module DataEnrichment
8
+ module Cache
9
+ # In-memory cache adapter (default, no dependencies)
10
+ class MemoryAdapter < CacheAdapter
11
+ include MonitorMixin
12
+
13
+ def initialize
14
+ super
15
+ @cache = {}
16
+ end
17
+
18
+ # Get cached value
19
+ #
20
+ # @param key [String] Cache key
21
+ # @return [Hash, nil] Cached data or nil if not found/expired
22
+ def get(key)
23
+ synchronize do
24
+ entry = @cache[key]
25
+ return nil unless entry
26
+
27
+ # Check if expired
28
+ if entry[:expires_at] < Time.now
29
+ @cache.delete(key)
30
+ return nil
31
+ end
32
+
33
+ entry[:value]
34
+ end
35
+ end
36
+
37
+ # Set cached value
38
+ #
39
+ # @param key [String] Cache key
40
+ # @param value [Hash] Data to cache
41
+ # @param ttl [Integer] Time to live in seconds
42
+ def set(key, value, ttl)
43
+ synchronize do
44
+ @cache[key] = {
45
+ value: value,
46
+ expires_at: Time.now + ttl
47
+ }
48
+ end
49
+ end
50
+
51
+ # Delete cached value
52
+ #
53
+ # @param key [String] Cache key
54
+ def delete(key)
55
+ synchronize do
56
+ @cache.delete(key)
57
+ end
58
+ end
59
+
60
+ # Clear all cached values
61
+ def clear
62
+ synchronize do
63
+ @cache.clear
64
+ end
65
+ end
66
+
67
+ # Get cache statistics
68
+ #
69
+ # @return [Hash] Cache statistics
70
+ def stats
71
+ synchronize do
72
+ now = Time.now
73
+ valid_entries = @cache.count { |_k, v| v[:expires_at] >= now }
74
+ expired_entries = @cache.size - valid_entries
75
+
76
+ {
77
+ size: @cache.size,
78
+ valid: valid_entries,
79
+ expired: expired_entries
80
+ }
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DecisionAgent
4
+ module DataEnrichment
5
+ # Base class for cache adapters
6
+ class CacheAdapter
7
+ # Get cached value
8
+ #
9
+ # @param key [String] Cache key
10
+ # @return [Hash, nil] Cached data or nil if not found/expired
11
+ def get(key)
12
+ raise NotImplementedError, "#{self.class} must implement #get"
13
+ end
14
+
15
+ # Set cached value
16
+ #
17
+ # @param key [String] Cache key
18
+ # @param value [Hash] Data to cache
19
+ # @param ttl [Integer] Time to live in seconds
20
+ def set(key, value, ttl)
21
+ raise NotImplementedError, "#{self.class} must implement #set"
22
+ end
23
+
24
+ # Delete cached value
25
+ #
26
+ # @param key [String] Cache key
27
+ def delete(key)
28
+ raise NotImplementedError, "#{self.class} must implement #delete"
29
+ end
30
+
31
+ # Clear all cached values
32
+ def clear
33
+ raise NotImplementedError, "#{self.class} must implement #clear"
34
+ end
35
+
36
+ # Generate cache key from request parameters
37
+ #
38
+ # @param endpoint_name [Symbol] Endpoint identifier
39
+ # @param params [Hash] Request parameters
40
+ # @return [String] Cache key
41
+ def cache_key(endpoint_name, params)
42
+ # Sort params for consistent key generation
43
+ sorted_params = params.sort.to_h
44
+ param_string = sorted_params.map { |k, v| "#{k}=#{v}" }.join("&")
45
+ "#{endpoint_name}:#{Digest::SHA256.hexdigest(param_string)}"
46
+ end
47
+ end
48
+ end
49
+ end