decision_agent 0.1.3 → 0.1.4

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/decision_agent/ab_testing/ab_test.rb +197 -0
  3. data/lib/decision_agent/ab_testing/ab_test_assignment.rb +76 -0
  4. data/lib/decision_agent/ab_testing/ab_test_manager.rb +317 -0
  5. data/lib/decision_agent/ab_testing/ab_testing_agent.rb +152 -0
  6. data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +155 -0
  7. data/lib/decision_agent/ab_testing/storage/adapter.rb +67 -0
  8. data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +116 -0
  9. data/lib/decision_agent/monitoring/metrics_collector.rb +148 -3
  10. data/lib/decision_agent/monitoring/storage/activerecord_adapter.rb +253 -0
  11. data/lib/decision_agent/monitoring/storage/base_adapter.rb +90 -0
  12. data/lib/decision_agent/monitoring/storage/memory_adapter.rb +222 -0
  13. data/lib/decision_agent/version.rb +1 -1
  14. data/lib/decision_agent.rb +7 -0
  15. data/lib/generators/decision_agent/install/install_generator.rb +37 -0
  16. data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +45 -0
  17. data/lib/generators/decision_agent/install/templates/ab_test_model.rb +54 -0
  18. data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +43 -0
  19. data/lib/generators/decision_agent/install/templates/ab_testing_tasks.rake +189 -0
  20. data/lib/generators/decision_agent/install/templates/decision_agent_tasks.rake +114 -0
  21. data/lib/generators/decision_agent/install/templates/decision_log.rb +57 -0
  22. data/lib/generators/decision_agent/install/templates/error_metric.rb +53 -0
  23. data/lib/generators/decision_agent/install/templates/evaluation_metric.rb +43 -0
  24. data/lib/generators/decision_agent/install/templates/monitoring_migration.rb +109 -0
  25. data/lib/generators/decision_agent/install/templates/performance_metric.rb +76 -0
  26. data/spec/ab_testing/ab_test_manager_spec.rb +330 -0
  27. data/spec/ab_testing/ab_test_spec.rb +270 -0
  28. data/spec/examples.txt +612 -548
  29. data/spec/issue_verification_spec.rb +95 -21
  30. data/spec/monitoring/metrics_collector_spec.rb +2 -2
  31. data/spec/monitoring/monitored_agent_spec.rb +1 -1
  32. data/spec/monitoring/prometheus_exporter_spec.rb +1 -1
  33. data/spec/monitoring/storage/activerecord_adapter_spec.rb +346 -0
  34. data/spec/monitoring/storage/memory_adapter_spec.rb +247 -0
  35. metadata +26 -2
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "decision_agent/monitoring/storage/memory_adapter"
5
+
6
+ RSpec.describe DecisionAgent::Monitoring::Storage::MemoryAdapter do
7
+ let(:adapter) { described_class.new(window_size: 3600) }
8
+
9
+ describe ".available?" do
10
+ it "is always available" do
11
+ expect(described_class.available?).to be true
12
+ end
13
+ end
14
+
15
+ describe "#record_decision" do
16
+ it "stores decision in memory" do
17
+ expect do
18
+ adapter.record_decision(
19
+ "approve_loan",
20
+ { user_id: 123, amount: 10_000 },
21
+ confidence: 0.85,
22
+ evaluations_count: 3,
23
+ duration_ms: 45.5,
24
+ status: "success"
25
+ )
26
+ end.to change { adapter.metrics_count[:decisions] }.by(1)
27
+ end
28
+ end
29
+
30
+ describe "#record_evaluation" do
31
+ it "stores evaluation in memory" do
32
+ expect do
33
+ adapter.record_evaluation(
34
+ "CreditScoreEvaluator",
35
+ score: 0.92,
36
+ success: true,
37
+ duration_ms: 12.3,
38
+ details: { credit_score: 750 }
39
+ )
40
+ end.to change { adapter.metrics_count[:evaluations] }.by(1)
41
+ end
42
+ end
43
+
44
+ describe "#record_performance" do
45
+ it "stores performance metric in memory" do
46
+ expect do
47
+ adapter.record_performance(
48
+ "database_query",
49
+ duration_ms: 150.5,
50
+ status: "success",
51
+ metadata: { query: "SELECT * FROM users" }
52
+ )
53
+ end.to change { adapter.metrics_count[:performance] }.by(1)
54
+ end
55
+ end
56
+
57
+ describe "#record_error" do
58
+ it "stores error in memory" do
59
+ expect do
60
+ adapter.record_error(
61
+ "ArgumentError",
62
+ message: "Invalid input",
63
+ stack_trace: ["line 1", "line 2"],
64
+ severity: "medium",
65
+ context: { input: "bad_value" }
66
+ )
67
+ end.to change { adapter.metrics_count[:errors] }.by(1)
68
+ end
69
+ end
70
+
71
+ describe "#statistics" do
72
+ before do
73
+ # Create test data
74
+ 5.times do |i|
75
+ adapter.record_decision(
76
+ "decision_#{i}",
77
+ { index: i },
78
+ confidence: 0.5 + (i * 0.05),
79
+ evaluations_count: 2,
80
+ duration_ms: 100,
81
+ status: i.even? ? "success" : "failure"
82
+ )
83
+ end
84
+
85
+ 3.times do |i|
86
+ adapter.record_evaluation(
87
+ "Evaluator#{i}",
88
+ score: 0.8 + (i * 0.05),
89
+ success: true
90
+ )
91
+ end
92
+
93
+ 6.times do |i|
94
+ adapter.record_performance(
95
+ "operation",
96
+ duration_ms: 100 + (i * 20),
97
+ status: "success"
98
+ )
99
+ end
100
+
101
+ 2.times do
102
+ adapter.record_error("RuntimeError", severity: "critical")
103
+ end
104
+ end
105
+
106
+ it "returns comprehensive statistics" do
107
+ stats = adapter.statistics(time_range: 3600)
108
+
109
+ expect(stats[:decisions][:total]).to eq(5)
110
+ expect(stats[:decisions][:average_confidence]).to be_within(0.01).of(0.6)
111
+ expect(stats[:decisions][:success_rate]).to eq(0.6) # 3 out of 5
112
+
113
+ expect(stats[:evaluations][:total]).to eq(3)
114
+ expect(stats[:evaluations][:average_score]).to be_within(0.01).of(0.85)
115
+
116
+ expect(stats[:performance][:total]).to eq(6)
117
+ expect(stats[:performance][:average_duration_ms]).to eq(150.0)
118
+ expect(stats[:performance][:success_rate]).to eq(1.0)
119
+
120
+ expect(stats[:errors][:total]).to eq(2)
121
+ expect(stats[:errors][:critical_count]).to eq(2)
122
+ end
123
+
124
+ it "filters by time range" do
125
+ # Record an old metric that should be filtered out
126
+ adapter.instance_variable_get(:@metrics)[:decisions] << {
127
+ decision: "old_decision",
128
+ confidence: 0.5,
129
+ timestamp: Time.now - 7200 # 2 hours ago
130
+ }
131
+
132
+ stats = adapter.statistics(time_range: 3600) # Last hour only
133
+
134
+ expect(stats[:decisions][:total]).to eq(5) # Doesn't include the old one
135
+ end
136
+ end
137
+
138
+ describe "#time_series" do
139
+ before do
140
+ # Create metrics at different times
141
+ now = Time.now
142
+ adapter.instance_variable_get(:@metrics)[:decisions] << { timestamp: now - 120 }
143
+ adapter.instance_variable_get(:@metrics)[:decisions] << { timestamp: now - 70 }
144
+ adapter.instance_variable_get(:@metrics)[:decisions] << { timestamp: now - 10 }
145
+ end
146
+
147
+ it "groups metrics into time buckets" do
148
+ series = adapter.time_series(:decisions, bucket_size: 60, time_range: 200)
149
+
150
+ expect(series[:timestamps]).to be_an(Array)
151
+ expect(series[:data]).to be_an(Array)
152
+ expect(series[:data].sum).to eq(3) # All 3 metrics
153
+ end
154
+
155
+ it "uses correct bucket size" do
156
+ series = adapter.time_series(:decisions, bucket_size: 60, time_range: 200)
157
+
158
+ # Metrics should be grouped into 60-second buckets
159
+ expect(series[:data].max).to be <= 2 # No bucket should have more than 2
160
+ end
161
+ end
162
+
163
+ describe "#metrics_count" do
164
+ before do
165
+ adapter.record_decision("test", {}, confidence: 0.8)
166
+ adapter.record_decision("test2", {}, confidence: 0.9)
167
+ adapter.record_evaluation("eval1", score: 0.85)
168
+ adapter.record_performance("perf1", duration_ms: 100)
169
+ adapter.record_error("Error1")
170
+ end
171
+
172
+ it "returns count for each metric type" do
173
+ counts = adapter.metrics_count
174
+
175
+ expect(counts[:decisions]).to eq(2)
176
+ expect(counts[:evaluations]).to eq(1)
177
+ expect(counts[:performance]).to eq(1)
178
+ expect(counts[:errors]).to eq(1)
179
+ end
180
+ end
181
+
182
+ describe "#cleanup" do
183
+ let(:long_window_adapter) { described_class.new(window_size: 30 * 24 * 3_600) } # 30 day window
184
+
185
+ before do
186
+ now = Time.now
187
+
188
+ # Add old metrics (8 days ago) to adapter with long window
189
+ long_window_adapter.instance_variable_get(:@metrics)[:decisions] << {
190
+ decision: "old",
191
+ timestamp: now - (8 * 24 * 3600)
192
+ }
193
+ long_window_adapter.instance_variable_get(:@metrics)[:evaluations] << {
194
+ evaluator_name: "old",
195
+ timestamp: now - (8 * 24 * 3600)
196
+ }
197
+
198
+ # Add recent metrics
199
+ long_window_adapter.record_decision("recent", {}, confidence: 0.8)
200
+ long_window_adapter.record_evaluation("recent", score: 0.9)
201
+ end
202
+
203
+ it "removes old metrics and returns count" do
204
+ count = long_window_adapter.cleanup(older_than: 7 * 24 * 3600) # 7 days
205
+
206
+ expect(count).to eq(2) # 2 old metrics removed
207
+ expect(long_window_adapter.metrics_count[:decisions]).to eq(1) # Only recent one
208
+ expect(long_window_adapter.metrics_count[:evaluations]).to eq(1)
209
+ end
210
+ end
211
+
212
+ describe "window-based cleanup" do
213
+ let(:short_window_adapter) { described_class.new(window_size: 60) } # 1 minute window
214
+
215
+ it "automatically removes metrics older than window_size" do
216
+ now = Time.now
217
+
218
+ # Add old metric
219
+ short_window_adapter.instance_variable_get(:@metrics)[:decisions] << {
220
+ decision: "old",
221
+ timestamp: now - 120 # 2 minutes ago
222
+ }
223
+
224
+ # Add new metric (this should trigger cleanup)
225
+ short_window_adapter.record_decision("new", {}, confidence: 0.8)
226
+
227
+ # Only the new metric should remain
228
+ expect(short_window_adapter.metrics_count[:decisions]).to eq(1)
229
+ end
230
+ end
231
+
232
+ describe "thread safety" do
233
+ it "handles concurrent writes" do
234
+ threads = 10.times.map do
235
+ Thread.new do
236
+ 100.times do |i|
237
+ adapter.record_decision("concurrent_#{i}", {}, confidence: 0.8)
238
+ end
239
+ end
240
+ end
241
+
242
+ threads.each(&:join)
243
+
244
+ expect(adapter.metrics_count[:decisions]).to eq(1000)
245
+ end
246
+ end
247
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decision_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Aswin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-24 00:00:00.000000000 Z
11
+ date: 2025-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json-canonicalization
@@ -108,6 +108,13 @@ files:
108
108
  - README.md
109
109
  - bin/decision_agent
110
110
  - lib/decision_agent.rb
111
+ - lib/decision_agent/ab_testing/ab_test.rb
112
+ - lib/decision_agent/ab_testing/ab_test_assignment.rb
113
+ - lib/decision_agent/ab_testing/ab_test_manager.rb
114
+ - lib/decision_agent/ab_testing/ab_testing_agent.rb
115
+ - lib/decision_agent/ab_testing/storage/activerecord_adapter.rb
116
+ - lib/decision_agent/ab_testing/storage/adapter.rb
117
+ - lib/decision_agent/ab_testing/storage/memory_adapter.rb
111
118
  - lib/decision_agent/agent.rb
112
119
  - lib/decision_agent/audit/adapter.rb
113
120
  - lib/decision_agent/audit/logger_adapter.rb
@@ -131,6 +138,9 @@ files:
131
138
  - lib/decision_agent/monitoring/metrics_collector.rb
132
139
  - lib/decision_agent/monitoring/monitored_agent.rb
133
140
  - lib/decision_agent/monitoring/prometheus_exporter.rb
141
+ - lib/decision_agent/monitoring/storage/activerecord_adapter.rb
142
+ - lib/decision_agent/monitoring/storage/base_adapter.rb
143
+ - lib/decision_agent/monitoring/storage/memory_adapter.rb
134
144
  - lib/decision_agent/replay/replay.rb
135
145
  - lib/decision_agent/scoring/base.rb
136
146
  - lib/decision_agent/scoring/consensus.rb
@@ -148,9 +158,21 @@ files:
148
158
  - lib/decision_agent/web/server.rb
149
159
  - lib/generators/decision_agent/install/install_generator.rb
150
160
  - lib/generators/decision_agent/install/templates/README
161
+ - lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb
162
+ - lib/generators/decision_agent/install/templates/ab_test_model.rb
163
+ - lib/generators/decision_agent/install/templates/ab_testing_migration.rb
164
+ - lib/generators/decision_agent/install/templates/ab_testing_tasks.rake
165
+ - lib/generators/decision_agent/install/templates/decision_agent_tasks.rake
166
+ - lib/generators/decision_agent/install/templates/decision_log.rb
167
+ - lib/generators/decision_agent/install/templates/error_metric.rb
168
+ - lib/generators/decision_agent/install/templates/evaluation_metric.rb
151
169
  - lib/generators/decision_agent/install/templates/migration.rb
170
+ - lib/generators/decision_agent/install/templates/monitoring_migration.rb
171
+ - lib/generators/decision_agent/install/templates/performance_metric.rb
152
172
  - lib/generators/decision_agent/install/templates/rule.rb
153
173
  - lib/generators/decision_agent/install/templates/rule_version.rb
174
+ - spec/ab_testing/ab_test_manager_spec.rb
175
+ - spec/ab_testing/ab_test_spec.rb
154
176
  - spec/activerecord_thread_safety_spec.rb
155
177
  - spec/agent_spec.rb
156
178
  - spec/api_contract_spec.rb
@@ -167,6 +189,8 @@ files:
167
189
  - spec/monitoring/metrics_collector_spec.rb
168
190
  - spec/monitoring/monitored_agent_spec.rb
169
191
  - spec/monitoring/prometheus_exporter_spec.rb
192
+ - spec/monitoring/storage/activerecord_adapter_spec.rb
193
+ - spec/monitoring/storage/memory_adapter_spec.rb
170
194
  - spec/replay_edge_cases_spec.rb
171
195
  - spec/replay_spec.rb
172
196
  - spec/rfc8785_canonicalization_spec.rb