Class: DecisionAgent::ABTesting::ABTest

Inherits:
Object
  • Object
show all
Defined in:
lib/decision_agent/ab_testing/ab_test.rb

Overview

Represents an A/B test configuration for comparing rule versions

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, champion_version_id:, challenger_version_id:, **options) ⇒ ABTest

Returns a new instance of ABTest.

Parameters:

  • name (String)

    Name of the A/B test

  • champion_version_id (String, Integer)

    ID of the current/champion version

  • challenger_version_id (String, Integer)

    ID of the new/challenger version

  • options (Hash)

    Optional configuration

Options Hash (**options):

  • :traffic_split (Hash)

    Traffic distribution (default: { champion: 90, challenger: 10 })

  • :start_date (Time)

    When the test starts (defaults to now)

  • :end_date (Time)

    When the test ends (optional)

  • :status (String)

    Test status: running, completed, cancelled, scheduled

  • :id (String, Integer)

    Optional ID (for persistence)



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 21

def initialize(
  name:,
  champion_version_id:,
  challenger_version_id:,
  **options
)
  @id = options[:id]
  @name = name
  @champion_version_id = champion_version_id
  @challenger_version_id = challenger_version_id
  @traffic_split = normalize_traffic_split(options[:traffic_split] || { champion: 90, challenger: 10 })
  @start_date = options[:start_date] || Time.now.utc
  @end_date = options[:end_date]
  @status = options[:status] || "scheduled"

  validate!
end

Instance Attribute Details

#challenger_version_idObject (readonly)

Returns the value of attribute challenger_version_id.



9
10
11
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 9

def challenger_version_id
  @challenger_version_id
end

#champion_version_idObject (readonly)

Returns the value of attribute champion_version_id.



9
10
11
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 9

def champion_version_id
  @champion_version_id
end

#end_dateObject (readonly)

Returns the value of attribute end_date.



9
10
11
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 9

def end_date
  @end_date
end

#idObject (readonly)

Returns the value of attribute id.



9
10
11
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 9

def id
  @id
end

#nameObject (readonly)

Returns the value of attribute name.



9
10
11
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 9

def name
  @name
end

#start_dateObject (readonly)

Returns the value of attribute start_date.



9
10
11
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 9

def start_date
  @start_date
end

#statusObject (readonly)

Returns the value of attribute status.



9
10
11
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 9

def status
  @status
end

#traffic_splitObject (readonly)

Returns the value of attribute traffic_split.



9
10
11
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 9

def traffic_split
  @traffic_split
end

Instance Method Details

#assign_variant(user_id: nil) ⇒ Symbol

Assign a variant based on traffic split Uses consistent hashing to ensure same user gets same variant

Parameters:

  • user_id (String, nil) (defaults to: nil)

    Optional user identifier for consistent assignment

Returns:

  • (Symbol)

    :champion or :challenger

Raises:



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 43

def assign_variant(user_id: nil)
  raise TestNotRunningError, "Test '#{@name}' is not running (status: #{@status})" unless running?

  if user_id
    # Consistent hashing: same user always gets same variant
    hash_value = OpenSSL::Digest::SHA256.hexdigest("#{@id}:#{user_id}").to_i(16)
    percentage = hash_value % 100
  else
    # Random assignment
    percentage = rand(100)
  end

  percentage < @traffic_split[:champion] ? :champion : :challenger
end

#cancel!Object

Cancel the test



111
112
113
114
115
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 111

def cancel!
  raise InvalidStatusTransitionError, "Cannot cancel test from status: #{@status}" if @status == "completed"

  @status = "cancelled"
end

#complete!Object

Complete the test



103
104
105
106
107
108
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 103

def complete!
  raise InvalidStatusTransitionError, "Cannot complete test from status: #{@status}" unless can_complete?

  @status = "completed"
  @end_date = Time.now.utc
end

#completed?Boolean

Check if test is completed

Returns:

  • (Boolean)


90
91
92
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 90

def completed?
  @status == "completed" || (@end_date && Time.now.utc > @end_date)
end

#running?Boolean

Check if test is currently running

Returns:

  • (Boolean)


74
75
76
77
78
79
80
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 74

def running?
  return false unless @status == "running"
  return false if @start_date && Time.now.utc < @start_date
  return false if @end_date && Time.now.utc > @end_date

  true
end

#scheduled?Boolean

Check if test is scheduled to start

Returns:

  • (Boolean)


84
85
86
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 84

def scheduled?
  @status == "scheduled" && @start_date && Time.now.utc < @start_date
end

#start!Object

Start the test



95
96
97
98
99
100
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 95

def start!
  raise InvalidStatusTransitionError, "Cannot start test from status: #{@status}" unless can_start?

  @status = "running"
  @start_date = Time.now.utc if @start_date.nil? || @start_date > Time.now.utc
end

#to_hHash

Convert to hash representation

Returns:

  • (Hash)


119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/decision_agent/ab_testing/ab_test.rb', line 119

def to_h
  {
    id: @id,
    name: @name,
    champion_version_id: @champion_version_id,
    challenger_version_id: @challenger_version_id,
    traffic_split: @traffic_split,
    start_date: @start_date,
    end_date: @end_date,
    status: @status
  }
end

#version_for_variant(variant) ⇒ String, Integer

Get the version ID for the assigned variant

Parameters:

  • variant (Symbol)

    :champion or :challenger

Returns:

  • (String, Integer)

    The version ID



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

def version_for_variant(variant)
  case variant
  when :champion
    @champion_version_id
  when :challenger
    @challenger_version_id
  else
    raise ArgumentError, "Invalid variant: #{variant}. Must be :champion or :challenger"
  end
end