Class: DecisionAgent::ABTesting::ABTestManager

Inherits:
Object
  • Object
show all
Includes:
MonitorMixin
Defined in:
lib/decision_agent/ab_testing/ab_test_manager.rb

Overview

Manages A/B tests and provides high-level orchestration

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(storage_adapter: nil, version_manager: nil) ⇒ ABTestManager



15
16
17
18
19
20
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 15

def initialize(storage_adapter: nil, version_manager: nil)
  super()
  @storage_adapter = storage_adapter || default_storage_adapter
  @version_manager = version_manager || Versioning::VersionManager.new
  @active_tests_cache = {}
end

Instance Attribute Details

#storage_adapterObject (readonly)

Returns the value of attribute storage_adapter.



11
12
13
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 11

def storage_adapter
  @storage_adapter
end

#version_managerObject (readonly)

Returns the value of attribute version_manager.



11
12
13
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 11

def version_manager
  @version_manager
end

Instance Method Details

#active_testsArray<ABTest>

Get all active A/B tests



62
63
64
65
66
67
68
69
70
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 62

def active_tests
  synchronize do
    return @active_tests_cache[:tests] if cache_valid?

    tests = @storage_adapter.list_tests(status: "running")
    @active_tests_cache = { tests: tests, timestamp: Time.now.utc }
    tests
  end
end

#assign_variant(test_id:, user_id: nil) ⇒ Hash

Assign a variant for a request

Raises:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 76

def assign_variant(test_id:, user_id: nil)
  test = get_test(test_id)
  raise TestNotFoundError, "Test not found: #{test_id}" unless test

  variant = test.assign_variant(user_id: user_id)
  version_id = test.version_for_variant(variant)

  assignment = ABTestAssignment.new(
    ab_test_id: test_id,
    user_id: user_id,
    variant: variant,
    version_id: version_id
  )

  saved_assignment = @storage_adapter.save_assignment(assignment)

  {
    test_id: test_id,
    variant: variant,
    version_id: version_id,
    assignment_id: saved_assignment.id
  }
end

#cancel_test(test_id) ⇒ Object

Cancel a test



158
159
160
161
162
163
164
165
166
167
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 158

def cancel_test(test_id)
  synchronize do
    test = get_test(test_id)
    raise TestNotFoundError, "Test not found: #{test_id}" unless test

    test.cancel!
    @storage_adapter.update_test(test_id, status: "cancelled")
    invalidate_cache!
  end
end

#complete_test(test_id) ⇒ Object

Complete a running test



145
146
147
148
149
150
151
152
153
154
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 145

def complete_test(test_id)
  synchronize do
    test = get_test(test_id)
    raise TestNotFoundError, "Test not found: #{test_id}" unless test

    test.complete!
    @storage_adapter.update_test(test_id, status: "completed", end_date: test.end_date)
    invalidate_cache!
  end
end

#create_test(name:, champion_version_id:, challenger_version_id:, traffic_split: { champion: 90, challenger: 10 }, start_date: nil, end_date: nil) ⇒ ABTest

Create a new A/B test



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 30

def create_test(name:, champion_version_id:, challenger_version_id:, traffic_split: { champion: 90, challenger: 10 }, start_date: nil,
                end_date: nil)
  synchronize do
    # Validate that both versions exist
    validate_version_exists!(champion_version_id, "champion")
    validate_version_exists!(challenger_version_id, "challenger")

    test = ABTest.new(
      name: name,
      champion_version_id: champion_version_id,
      challenger_version_id: challenger_version_id,
      traffic_split: traffic_split,
      start_date: start_date || Time.now.utc,
      end_date: end_date,
      status: start_date && start_date > Time.now.utc ? "scheduled" : "running"
    )

    saved_test = @storage_adapter.save_test(test)
    invalidate_cache!
    saved_test
  end
end

#get_results(test_id) ⇒ Hash

Get results comparison for an A/B test

Raises:



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 111

def get_results(test_id)
  test = get_test(test_id)
  raise TestNotFoundError, "Test not found: #{test_id}" unless test

  assignments = @storage_adapter.get_assignments(test_id)

  champion_assignments = assignments.select { |a| a.variant == :champion }
  challenger_assignments = assignments.select { |a| a.variant == :challenger }

  {
    test: test.to_h,
    champion: calculate_variant_stats(champion_assignments, "Champion"),
    challenger: calculate_variant_stats(challenger_assignments, "Challenger"),
    comparison: compare_variants(champion_assignments, challenger_assignments),
    total_assignments: assignments.size,
    timestamp: Time.now.utc
  }
end

#get_test(test_id) ⇒ ABTest?

Get an A/B test by ID



56
57
58
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 56

def get_test(test_id)
  @storage_adapter.get_test(test_id)
end

#list_tests(status: nil, limit: nil) ⇒ Array<ABTest>

List all tests with optional filtering



173
174
175
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 173

def list_tests(status: nil, limit: nil)
  @storage_adapter.list_tests(status: status, limit: limit)
end

#record_decision(assignment_id:, decision:, confidence:) ⇒ Object

Record the decision result for an assignment



104
105
106
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 104

def record_decision(assignment_id:, decision:, confidence:)
  @storage_adapter.update_assignment(assignment_id, decision_result: decision, confidence: confidence)
end

#start_test(test_id) ⇒ Object

Start a scheduled test



132
133
134
135
136
137
138
139
140
141
# File 'lib/decision_agent/ab_testing/ab_test_manager.rb', line 132

def start_test(test_id)
  synchronize do
    test = get_test(test_id)
    raise TestNotFoundError, "Test not found: #{test_id}" unless test

    test.start!
    @storage_adapter.update_test(test_id, status: "running", start_date: test.start_date)
    invalidate_cache!
  end
end