verdict 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/Gemfile +4 -0
- data/README.md +56 -33
- data/lib/verdict.rb +2 -1
- data/lib/verdict/experiment.rb +15 -7
- data/lib/verdict/segmenters.rb +4 -0
- data/lib/verdict/segmenters/base_segmenter.rb +84 -0
- data/lib/verdict/segmenters/fixed_percentage_segmenter.rb +56 -0
- data/lib/verdict/segmenters/rollout_segmenter.rb +55 -0
- data/lib/verdict/segmenters/static_segmenter.rb +27 -0
- data/lib/verdict/storage.rb +3 -138
- data/lib/verdict/storage/memory_storage.rb +40 -0
- data/lib/verdict/storage/mock_storage.rb +38 -0
- data/lib/verdict/storage/redis_storage.rb +62 -0
- data/lib/verdict/tasks.rake +8 -8
- data/lib/verdict/version.rb +1 -1
- data/test/assignment_test.rb +4 -4
- data/test/conversion_test.rb +1 -1
- data/test/event_logger_test.rb +1 -1
- data/test/experiment_test.rb +12 -3
- data/test/experiments_repository_test.rb +1 -1
- data/test/group_test.rb +2 -2
- data/test/metadata_test.rb +1 -1
- data/test/{fixed_percentage_segmenter_test.rb → segmenters/fixed_percentage_segmenter_test.rb} +11 -11
- data/test/{rollout_segmenter_test.rb → segmenters/rollout_segmenter_test.rb} +2 -2
- data/test/{static_segmenter_test.rb → segmenters/static_segmenter_test.rb} +2 -2
- data/test/{memory_subject_storage_test.rb → storage/memory_subject_storage_test.rb} +1 -1
- data/test/{redis_subject_storage_test.rb → storage/redis_subject_storage_test.rb} +2 -2
- data/test/test_helper.rb +8 -0
- data/verdict.gemspec +6 -6
- metadata +24 -19
- data/lib/verdict/fixed_percentage_segmenter.rb +0 -52
- data/lib/verdict/rollout_segmenter.rb +0 -53
- data/lib/verdict/segmenter.rb +0 -87
- data/lib/verdict/static_segmenter.rb +0 -24
data/test/metadata_test.rb
CHANGED
data/test/{fixed_percentage_segmenter_test.rb → segmenters/fixed_percentage_segmenter_test.rb}
RENAMED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class FixedPercentageSegmenterTest <
|
3
|
+
class FixedPercentageSegmenterTest < Minitest::Test
|
4
4
|
|
5
5
|
def test_add_up_to_100_percent
|
6
|
-
s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
6
|
+
s = Verdict::Segmenters::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
7
7
|
s.group :segment1, 1
|
8
8
|
s.group :segment2, 54
|
9
9
|
s.group :segment3, 27
|
@@ -18,7 +18,7 @@ class FixedPercentageSegmenterTest < MiniTest::Unit::TestCase
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def test_definition_ofhalf_and_rest
|
21
|
-
s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
21
|
+
s = Verdict::Segmenters::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
22
22
|
s.group :first_half, :half
|
23
23
|
s.group :second_half, :rest
|
24
24
|
s.verify!
|
@@ -30,7 +30,7 @@ class FixedPercentageSegmenterTest < MiniTest::Unit::TestCase
|
|
30
30
|
|
31
31
|
def test_raises_if_less_than_100_percent
|
32
32
|
assert_raises(Verdict::SegmentationError) do
|
33
|
-
s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
33
|
+
s = Verdict::Segmenters::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
34
34
|
s.group :too_little, 99
|
35
35
|
s.verify!
|
36
36
|
end
|
@@ -38,34 +38,34 @@ class FixedPercentageSegmenterTest < MiniTest::Unit::TestCase
|
|
38
38
|
|
39
39
|
def test_raises_if_greather_than_100_percent
|
40
40
|
assert_raises(Verdict::SegmentationError) do
|
41
|
-
s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
41
|
+
s = Verdict::Segmenters::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
42
42
|
s.group :too_much, 101
|
43
43
|
s.verify!
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
def test_consistent_assignment_for_subjects
|
48
|
-
s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
48
|
+
s = Verdict::Segmenters::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
49
49
|
s.group :first_half, :half
|
50
50
|
s.group :second_half, :rest
|
51
51
|
s.verify!
|
52
52
|
|
53
|
-
3.times do
|
53
|
+
3.times do
|
54
54
|
assert s.groups['first_half'] === s.assign(1, nil, nil)
|
55
55
|
assert s.groups['second_half'] === s.assign(2, nil, nil)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
59
|
def test_fair_segmenting
|
60
|
-
s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
60
|
+
s = Verdict::Segmenters::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
61
61
|
s.group :first_third, 33
|
62
62
|
s.group :second_third, 33
|
63
63
|
s.group :final_third, :rest
|
64
64
|
s.verify!
|
65
65
|
|
66
66
|
assignments = { :first_third => 0, :second_third => 0, :final_third => 0 }
|
67
|
-
200.times do |n|
|
68
|
-
assignment = s.assign(n, nil, nil)
|
67
|
+
200.times do |n|
|
68
|
+
assignment = s.assign(n, nil, nil)
|
69
69
|
assignments[assignment.to_sym] += 1
|
70
70
|
end
|
71
71
|
|
@@ -76,7 +76,7 @@ class FixedPercentageSegmenterTest < MiniTest::Unit::TestCase
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def test_group_json_export
|
79
|
-
s = Verdict::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
79
|
+
s = Verdict::Segmenters::FixedPercentageSegmenter.new(Verdict::Experiment.new('test'))
|
80
80
|
s.group :first_third, 33
|
81
81
|
s.group :rest, :rest
|
82
82
|
s.verify!
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class RolloutPercentageSegmenterTest <
|
3
|
+
class RolloutPercentageSegmenterTest < Minitest::Test
|
4
4
|
|
5
5
|
def setup
|
6
6
|
@experiment = Verdict::Experiment.new('test') do
|
@@ -11,7 +11,7 @@ class RolloutPercentageSegmenterTest < MiniTest::Unit::TestCase
|
|
11
11
|
def test_assignment
|
12
12
|
included_subject = stub(id: 1)
|
13
13
|
excluded_subject = stub(id: 2)
|
14
|
-
|
14
|
+
|
15
15
|
included_assignment = @experiment.assign(included_subject)
|
16
16
|
assert included_assignment.qualified?
|
17
17
|
assert included_assignment.permanent?
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class StaticSegmenterTest <
|
3
|
+
class StaticSegmenterTest < Minitest::Test
|
4
4
|
|
5
5
|
def setup
|
6
|
-
@segmenter = Verdict::StaticSegmenter.new(Verdict::Experiment.new('test'))
|
6
|
+
@segmenter = Verdict::Segmenters::StaticSegmenter.new(Verdict::Experiment.new('test'))
|
7
7
|
@segmenter.group :beta, ['id1', 'id2']
|
8
8
|
end
|
9
9
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class RedisSubjectStorageTest <
|
3
|
+
class RedisSubjectStorageTest < Minitest::Test
|
4
4
|
|
5
5
|
def setup
|
6
6
|
@redis = ::Redis.new(host: REDIS_HOST, port: REDIS_PORT)
|
@@ -80,7 +80,7 @@ class RedisSubjectStorageTest < MiniTest::Unit::TestCase
|
|
80
80
|
|
81
81
|
def test_started_at
|
82
82
|
key = @storage.send(:generate_experiment_start_timestamp_key, @experiment)
|
83
|
-
|
83
|
+
|
84
84
|
assert !@redis.exists(key)
|
85
85
|
a = @experiment.send(:ensure_experiment_has_started)
|
86
86
|
assert @redis.exists(key)
|
data/test/test_helper.rb
CHANGED
data/verdict.gemspec
CHANGED
@@ -16,10 +16,10 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
|
-
|
20
|
-
gem.add_development_dependency
|
21
|
-
gem.add_development_dependency
|
22
|
-
gem.add_development_dependency
|
23
|
-
gem.add_development_dependency
|
24
|
-
gem.add_development_dependency
|
19
|
+
|
20
|
+
gem.add_development_dependency("minitest", '~> 5.2')
|
21
|
+
gem.add_development_dependency("rake")
|
22
|
+
gem.add_development_dependency("mocha")
|
23
|
+
gem.add_development_dependency("timecop")
|
24
|
+
gem.add_development_dependency("redis")
|
25
25
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: verdict
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5.2'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '5.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -99,14 +99,18 @@ files:
|
|
99
99
|
- lib/verdict/conversion.rb
|
100
100
|
- lib/verdict/event_logger.rb
|
101
101
|
- lib/verdict/experiment.rb
|
102
|
-
- lib/verdict/fixed_percentage_segmenter.rb
|
103
102
|
- lib/verdict/group.rb
|
104
103
|
- lib/verdict/metadata.rb
|
105
104
|
- lib/verdict/railtie.rb
|
106
|
-
- lib/verdict/
|
107
|
-
- lib/verdict/
|
108
|
-
- lib/verdict/
|
105
|
+
- lib/verdict/segmenters.rb
|
106
|
+
- lib/verdict/segmenters/base_segmenter.rb
|
107
|
+
- lib/verdict/segmenters/fixed_percentage_segmenter.rb
|
108
|
+
- lib/verdict/segmenters/rollout_segmenter.rb
|
109
|
+
- lib/verdict/segmenters/static_segmenter.rb
|
109
110
|
- lib/verdict/storage.rb
|
111
|
+
- lib/verdict/storage/memory_storage.rb
|
112
|
+
- lib/verdict/storage/mock_storage.rb
|
113
|
+
- lib/verdict/storage/redis_storage.rb
|
110
114
|
- lib/verdict/tasks.rake
|
111
115
|
- lib/verdict/version.rb
|
112
116
|
- test/assignment_test.rb
|
@@ -114,13 +118,13 @@ files:
|
|
114
118
|
- test/event_logger_test.rb
|
115
119
|
- test/experiment_test.rb
|
116
120
|
- test/experiments_repository_test.rb
|
117
|
-
- test/fixed_percentage_segmenter_test.rb
|
118
121
|
- test/group_test.rb
|
119
|
-
- test/memory_subject_storage_test.rb
|
120
122
|
- test/metadata_test.rb
|
121
|
-
- test/
|
122
|
-
- test/rollout_segmenter_test.rb
|
123
|
-
- test/static_segmenter_test.rb
|
123
|
+
- test/segmenters/fixed_percentage_segmenter_test.rb
|
124
|
+
- test/segmenters/rollout_segmenter_test.rb
|
125
|
+
- test/segmenters/static_segmenter_test.rb
|
126
|
+
- test/storage/memory_subject_storage_test.rb
|
127
|
+
- test/storage/redis_subject_storage_test.rb
|
124
128
|
- test/test_helper.rb
|
125
129
|
- verdict.gemspec
|
126
130
|
homepage: http://github.com/Shopify/verdict
|
@@ -142,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
146
|
version: '0'
|
143
147
|
requirements: []
|
144
148
|
rubyforge_project:
|
145
|
-
rubygems_version: 2.0.
|
149
|
+
rubygems_version: 2.0.3
|
146
150
|
signing_key:
|
147
151
|
specification_version: 4
|
148
152
|
summary: A library to centrally define experiments for your application, and collect
|
@@ -153,11 +157,12 @@ test_files:
|
|
153
157
|
- test/event_logger_test.rb
|
154
158
|
- test/experiment_test.rb
|
155
159
|
- test/experiments_repository_test.rb
|
156
|
-
- test/fixed_percentage_segmenter_test.rb
|
157
160
|
- test/group_test.rb
|
158
|
-
- test/memory_subject_storage_test.rb
|
159
161
|
- test/metadata_test.rb
|
160
|
-
- test/
|
161
|
-
- test/rollout_segmenter_test.rb
|
162
|
-
- test/static_segmenter_test.rb
|
162
|
+
- test/segmenters/fixed_percentage_segmenter_test.rb
|
163
|
+
- test/segmenters/rollout_segmenter_test.rb
|
164
|
+
- test/segmenters/static_segmenter_test.rb
|
165
|
+
- test/storage/memory_subject_storage_test.rb
|
166
|
+
- test/storage/redis_subject_storage_test.rb
|
163
167
|
- test/test_helper.rb
|
168
|
+
has_rdoc:
|
@@ -1,52 +0,0 @@
|
|
1
|
-
class Verdict::FixedPercentageSegmenter < Verdict::Segmenter
|
2
|
-
|
3
|
-
class Group < Verdict::Group
|
4
|
-
|
5
|
-
attr_reader :percentile_range
|
6
|
-
|
7
|
-
def initialize(experiment, handle, percentile_range)
|
8
|
-
super(experiment, handle)
|
9
|
-
@percentile_range = percentile_range
|
10
|
-
end
|
11
|
-
|
12
|
-
def percentage_size
|
13
|
-
percentile_range.end - percentile_range.begin
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_s
|
17
|
-
"#{handle} (#{percentage_size}%)"
|
18
|
-
end
|
19
|
-
|
20
|
-
def as_json(options = {})
|
21
|
-
super(options).merge(percentage: percentage_size)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def initialize(experiment)
|
26
|
-
super
|
27
|
-
@total_percentage_segmented = 0
|
28
|
-
end
|
29
|
-
|
30
|
-
def verify!
|
31
|
-
raise Verdict::SegmentationError, "Should segment exactly 100% of the cases, but segments add up to #{@total_percentage_segmented}%." if @total_percentage_segmented != 100
|
32
|
-
end
|
33
|
-
|
34
|
-
def register_group(handle, size)
|
35
|
-
percentage = size.kind_of?(Hash) && size[:percentage] ? size[:percentage] : size
|
36
|
-
n = case percentage
|
37
|
-
when :rest; 100 - @total_percentage_segmented
|
38
|
-
when :half; 50
|
39
|
-
when Integer; percentage
|
40
|
-
else Integer(percentage)
|
41
|
-
end
|
42
|
-
|
43
|
-
group = Group.new(experiment, handle, @total_percentage_segmented ... (@total_percentage_segmented + n))
|
44
|
-
@total_percentage_segmented += n
|
45
|
-
return group
|
46
|
-
end
|
47
|
-
|
48
|
-
def assign(identifier, subject, context)
|
49
|
-
percentile = Digest::MD5.hexdigest("#{@experiment.handle}#{identifier}").to_i(16) % 100
|
50
|
-
groups.values.find { |group| group.percentile_range.include?(percentile) }
|
51
|
-
end
|
52
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
class Verdict::RolloutSegmenter < Verdict::Segmenter
|
2
|
-
|
3
|
-
class Group < Verdict::Group
|
4
|
-
|
5
|
-
attr_reader :percentile_range
|
6
|
-
|
7
|
-
def initialize(experiment, handle, percentile_range)
|
8
|
-
super(experiment, handle)
|
9
|
-
@percentile_range = percentile_range
|
10
|
-
end
|
11
|
-
|
12
|
-
def percentage_size
|
13
|
-
percentile_range.end - percentile_range.begin
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_s
|
17
|
-
"#{handle} (#{percentage_size}%)"
|
18
|
-
end
|
19
|
-
|
20
|
-
def as_json(options = {})
|
21
|
-
super(options).merge(percentage: percentage_size)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def initialize(experiment)
|
26
|
-
super
|
27
|
-
@total_percentage_segmented = 0
|
28
|
-
end
|
29
|
-
|
30
|
-
def verify!
|
31
|
-
raise Verdict::SegmentationError, "Should segment less than 100% of the cases, but segments add up to #{@total_percentage_segmented}%." if @total_percentage_segmented >= 100
|
32
|
-
end
|
33
|
-
|
34
|
-
def register_group(handle, size)
|
35
|
-
percentage = size.kind_of?(Hash) && size[:percentage] ? size[:percentage] : size
|
36
|
-
n = case percentage
|
37
|
-
when :rest; 100 - @total_percentage_segmented
|
38
|
-
when :half; 50
|
39
|
-
when Integer; percentage
|
40
|
-
else Integer(percentage)
|
41
|
-
end
|
42
|
-
|
43
|
-
group = Group.new(experiment, handle, @total_percentage_segmented ... (@total_percentage_segmented + n))
|
44
|
-
@total_percentage_segmented += n
|
45
|
-
return group
|
46
|
-
end
|
47
|
-
|
48
|
-
def assign(identifier, subject, context)
|
49
|
-
percentile = Digest::MD5.hexdigest("#{@experiment.handle}#{identifier}").to_i(16) % 100
|
50
|
-
groups.values.find { |group| group.percentile_range.include?(percentile) }
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
data/lib/verdict/segmenter.rb
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
require 'digest/md5'
|
2
|
-
|
3
|
-
# Base class of all segmenters.
|
4
|
-
#
|
5
|
-
# The segmenter is responsible for assigning subjects to groups. You can
|
6
|
-
# implement any assignment strategy you like by subclassing this class and
|
7
|
-
# using it in your experiment.
|
8
|
-
#
|
9
|
-
# - You should implement the register_group method for the experiment definition DSL
|
10
|
-
# to make the system aware of the groups that the segmenter could return.
|
11
|
-
# - The verify! method is called after all the groups have been defined, so it can
|
12
|
-
# detect internal inconsistencies in the group definitions.
|
13
|
-
# - The assign method is where your assignment magic lives.
|
14
|
-
class Verdict::Segmenter
|
15
|
-
|
16
|
-
# The experiment to which this segmenter is associated
|
17
|
-
attr_reader :experiment
|
18
|
-
|
19
|
-
# A hash of the groups that are defined in this experiment, indexed by their
|
20
|
-
# handle. The assign method should return one of the groups in this hash
|
21
|
-
attr_reader :groups
|
22
|
-
|
23
|
-
def initialize(experiment)
|
24
|
-
@experiment = experiment
|
25
|
-
@groups = {}
|
26
|
-
end
|
27
|
-
|
28
|
-
# DSL method to register a group. It calls the register_group method of the
|
29
|
-
# segmenter implementation
|
30
|
-
def group(handle, *args, &block)
|
31
|
-
group = register_group(handle, *args)
|
32
|
-
@groups[group.handle] = group
|
33
|
-
group.instance_eval(&block) if block_given?
|
34
|
-
end
|
35
|
-
|
36
|
-
# The group method is called from the experiment definition DSL.
|
37
|
-
# It should register a new group to the segmenter, with the given handle.
|
38
|
-
#
|
39
|
-
# - The handle parameter is a symbol that uniquely identifies the group within
|
40
|
-
# this experiment.
|
41
|
-
# - The return value of this method should be a Verdict::Group instance.
|
42
|
-
def register_group(handle, *args)
|
43
|
-
raise NotImplementedError
|
44
|
-
end
|
45
|
-
|
46
|
-
# The verify! method is called after all the groups have been defined in the
|
47
|
-
# experiment definition DSL. You can run any consistency checks in this method,
|
48
|
-
# and if anything is off, you can raise a Verdict::SegmentationError to
|
49
|
-
# signify the problem.
|
50
|
-
def verify!
|
51
|
-
# noop by default
|
52
|
-
end
|
53
|
-
|
54
|
-
# The assign method is called to assign a subject to one of the groups that have been defined
|
55
|
-
# in the segmenter implementation.
|
56
|
-
#
|
57
|
-
# - The identifier parameter is a string that uniquely identifies the subject.
|
58
|
-
# - The subject paramater is the subject instance that was passed to the framework,
|
59
|
-
# when the application code calls Experiment#assign or Experiment#switch.
|
60
|
-
# - The context parameter is an object that was passed to the framework, you can use this
|
61
|
-
# object any way you like in your segmenting logic.
|
62
|
-
#
|
63
|
-
# This method should return the Verdict::Group instance to which the subject should be assigned.
|
64
|
-
# This instance should be one of the group instance that was registered in the definition DSL.
|
65
|
-
def assign(identifier, subject, context)
|
66
|
-
raise NotImplementedError
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
# This method is called whenever a subjects converts to a goal, i.e., when Experiment#convert
|
71
|
-
# is called. You can use this to implement a feedback loop in your segmenter.
|
72
|
-
#
|
73
|
-
# - The identifier parameter is a string that uniquely identifies the subject.
|
74
|
-
# - The subject paramater is the subject instance that was passed to the framework,
|
75
|
-
# when the application code calls Experiment#assign or Experiment#switch.
|
76
|
-
# - The conversion parameter is a Verdict::Conversion instance that describes what
|
77
|
-
# goal the subject converted to.
|
78
|
-
#
|
79
|
-
# The return value of this method is not used.
|
80
|
-
def conversion_feedback(identifier, subject, conversion)
|
81
|
-
# noop by default
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
require 'verdict/static_segmenter'
|
86
|
-
require 'verdict/fixed_percentage_segmenter'
|
87
|
-
require 'verdict/rollout_segmenter'
|