conductor 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.0
1
+ 0.7.1
data/lib/conductor.rb CHANGED
@@ -11,17 +11,19 @@ require 'conductor/helpers/dashboard_helper'
11
11
 
12
12
  class Conductor
13
13
  MAX_WEIGHTING_FACTOR = 1.25
14
- MINIMUM_LAUNCH_DAYS = 7
14
+ EQUALIZATION_PERIOD_DEFAULT = 7
15
15
  DBG = false
16
16
 
17
17
  cattr_writer :cache
18
- cattr_writer :days_till_weighting
19
18
 
20
19
  def self.cache
21
20
  @@cache || Rails.cache
22
21
  end
23
22
 
24
23
  class << self
24
+ # Specifies a unique identity for the current visitor. If no identity is specified
25
+ # then a random value is selected. Conductor makes sure that the same visitor
26
+ # will always see the same alternative selections to reduce confusion.
25
27
  def identity=(value)
26
28
  @conductor_identity = value
27
29
  end
@@ -30,10 +32,26 @@ class Conductor
30
32
  return (@conductor_identity || ActiveSupport::SecureRandom.hex(16))
31
33
  end
32
34
 
33
- def minimum_launch_days
34
- return (@@days_till_weighting || MINIMUM_LAUNCH_DAYS)
35
+ # The equalization period is the initial amount of time, in days, that conductor
36
+ # should apply the max_weighting_factor towards a new alternative to ensure
37
+ # that it receives a far shot of performing.
38
+ #
39
+ # If an equalization period was not used then any new alternative would
40
+ # immediately be weighed very low since it has no conversions and would
41
+ # never have a chance of performing
42
+ def equalization_period=(value)
43
+ raise "Conductor.equalization_period must be a positive number > 0" unless value.is_a?(Numeric) && value > 0
44
+ @equalization_period = value
35
45
  end
36
46
 
47
+ def equalization_period
48
+ return (@equalization_period || EQUALIZATION_PERIOD_DEFAULT)
49
+ end
50
+
51
+ # The attribute for weighting specifies if the conversion_value OR number of conversions
52
+ # should be used to calculate the weight. The default is conversion_value.
53
+ #
54
+ # TODO: Allow of avg_conversion_value where acv = conversion_value / conversions
37
55
  def attribute_for_weighting=(value)
38
56
  raise "Conductor.attribute_for_weighting must be either :views, :conversions or :conversion_value (default)" unless [:views, :conversions, :conversion_value].include?(value)
39
57
  @attribute_for_weighting = value
@@ -53,7 +53,7 @@ class Conductor
53
53
  group_rows.group_by(&:alternative).each do |alternative_name, alternatives|
54
54
  days_ago = compute_days_ago(alternatives)
55
55
 
56
- if days_ago >= Conductor.minimum_launch_days
56
+ if days_ago >= Conductor.equalization_period
57
57
  data << {:name => alternative_name, :weight => (weighted_moving_avg[alternative_name] / total)}
58
58
  else
59
59
  recently_launched << {:name => alternative_name, :days_ago => days_ago}
@@ -88,8 +88,8 @@ class Conductor
88
88
  # slowly lowers its power until the launch period is over
89
89
  max_weight = 1 if data.empty?
90
90
  recently_launched.each do |alternative|
91
- handicap = (alternative[:days_ago].to_f / Conductor.minimum_launch_days)
92
- launch_window = (Conductor.minimum_launch_days - alternative[:days_ago]) if Conductor.minimum_launch_days > alternative[:days_ago]
91
+ handicap = (alternative[:days_ago].to_f / Conductor.equalization_period)
92
+ launch_window = (Conductor.equalization_period - alternative[:days_ago]) if Conductor.equalization_period > alternative[:days_ago]
93
93
  Conductor.log("Handicap for #{alternative[:name]} is #{handicap} (#{alternative[:days_ago]} days ago)")
94
94
  data << {:name => alternative[:name], :weight => max_weight * MAX_WEIGHTING_FACTOR * (1 - handicap), :launch_window => launch_window}
95
95
  end
@@ -22,13 +22,20 @@ class TestConductor < Test::Unit::TestCase
22
22
  x = Conductor.cache.read('testing')
23
23
  assert_equal x, 'value'
24
24
  end
25
-
26
- should "allow for the minimum_launch_days to be configurable" do
27
- Conductor.days_till_weighting = 3
28
- assert_equal(3, Conductor.minimum_launch_days)
25
+
26
+ should "allow for the equalization_period to be configurable" do
27
+ Conductor.equalization_period = 3
28
+ assert_equal(3, Conductor.equalization_period)
29
+ end
30
+
31
+ should "raise an error if a non-numeric value, negative or 0 value is specified for the equalization_period" do
32
+ assert_raise(RuntimeError, LoadError) { Conductor.equalization_period = 'junk'}
33
+ assert_raise(RuntimeError, LoadError) { Conductor.equalization_period = -1.0}
34
+ assert_raise(RuntimeError, LoadError) { Conductor.equalization_period = 0}
35
+ assert_nothing_raised(RuntimeError, LoadError) { Conductor.equalization_period = 3}
29
36
  end
30
-
31
- should "raise an error if an improper attribute is specified for @@attribute_for_weighting" do
37
+
38
+ should "raise an error if an improper attribute is specified for @attribute_for_weighting" do
32
39
  assert_raise(RuntimeError, LoadError) { Conductor.attribute_for_weighting = :random}
33
40
  end
34
41
 
@@ -133,7 +140,7 @@ class TestConductor < Test::Unit::TestCase
133
140
  assert Conductor::Experiment::Daily.all.detect {|x| x.views > 0}
134
141
  assert Conductor::Experiment::Daily.all.detect {|x| x.conversion_value > 0}
135
142
  end
136
-
143
+
137
144
  should "correctly populate weighting table when selecting a value" do
138
145
  selected = Conductor::Experiment.pick('a_group', ["a", "b", "c"])
139
146
  assert_equal 3, Conductor::Experiment::Weight.count
@@ -141,48 +148,49 @@ class TestConductor < Test::Unit::TestCase
141
148
 
142
149
  should "pull weights from the cache" do
143
150
  Conductor::Experiment.pick('a_group', ["a", "b", "c"])
144
-
151
+
145
152
  (1..100).each do |x|
146
153
  Conductor.identity = ActiveSupport::SecureRandom.hex(16)
147
154
  Conductor::Experiment.pick('a_group', ["a", "b", "c"])
148
155
  end
149
-
150
- # => if this works the history table should have only been updated one time not 101 so there should
156
+
157
+ # => if this works the history table should have only been updated one time not 101 so there should
151
158
  # => be three records (one for a, b and c)
152
159
  assert_equal 3, Conductor::Experiment::History.count
153
160
  end
154
-
161
+
155
162
  should "pull weights from the cache and then recreate weights when the alternative list changes" do
156
163
  Conductor::Experiment.pick('a_group', ["a", "b", "c"])
157
-
164
+
158
165
  (1..100).each do |x|
159
166
  Conductor.identity = ActiveSupport::SecureRandom.hex(16)
160
167
  Conductor::Experiment.pick('a_group', ["a", "b", "c"])
161
168
  end
162
169
 
163
- Conductor.identity = ActiveSupport::SecureRandom.hex(16)
170
+ Conductor.identity = ActiveSupport::SecureRandom.hex(16)
164
171
  Conductor::Experiment.pick('a_group', ["a", "c"])
165
-
166
- # => if this works the history table should have only been updated one time not 101 so there should
172
+
173
+ # => if this works the history table should have only been updated one time not 101 so there should
167
174
  # => be FIVE records (one for a, b and c and then one for a and c)
168
175
  assert_equal 5, Conductor::Experiment::History.count
169
176
  end
170
177
  end
171
178
 
172
179
  context "conductor" do
173
- should "populate the weighting table with equal weights if all new options are launched" do
180
+ setup do
181
+ wipe
174
182
  seed_raw_data(100, 7)
175
-
176
- # rollup
177
183
  Conductor::RollUp.process
184
+ end
178
185
 
186
+ should "populate the weighting table with equal weights if all new options are launched" do
179
187
  # hit after rollup to populare weight table
180
188
  Conductor.identity = ActiveSupport::SecureRandom.hex(16)
181
- Conductor.days_till_weighting = 7
189
+ Conductor.equalization_period = 7
182
190
  selected = Conductor::Experiment.pick('a_group', ["a", "b", "c"])
183
191
 
184
192
  # each weight will be equal to 0.18
185
- assert_equal 7, Conductor.minimum_launch_days
193
+ assert_equal 7, Conductor.equalization_period
186
194
  assert_equal 0.54, Conductor::Experiment::Weight.all.sum_it(:weight).to_f
187
195
  end
188
196
  end
@@ -234,7 +242,7 @@ class TestConductor < Test::Unit::TestCase
234
242
  assert_not_nil Conductor::Experiment::History.find(:all, :conditions => 'launch_window > 0')
235
243
  end
236
244
  end
237
-
245
+
238
246
  context "conductor" do
239
247
  setup do
240
248
  seed_raw_data(500, 30)
@@ -242,7 +250,7 @@ class TestConductor < Test::Unit::TestCase
242
250
  # rollup
243
251
  Conductor::RollUp.process
244
252
  end
245
-
253
+
246
254
  should "allow for the number of conversions to be used for weighting instead of conversion_value" do
247
255
  Conductor.identity = ActiveSupport::SecureRandom.hex(16)
248
256
  Conductor::Experiment.pick('a_group', ["a", "b", "c"])
@@ -252,12 +260,12 @@ class TestConductor < Test::Unit::TestCase
252
260
  Conductor.attribute_for_weighting = :conversions
253
261
  Conductor::Experiment.pick('a_group', ["a", "b", "c"])
254
262
  weights_c = Conductor::Experiment::Weight.all.map(&:weight).sort
255
-
263
+
256
264
  # since one is using conversion_value and the other is using conversions, they two weight arrays should be different
257
265
  assert_equal :conversions, Conductor.attribute_for_weighting
258
266
  assert_not_equal weights_cv, weights_c
259
267
  end
260
- end
268
+ end
261
269
 
262
270
 
263
271
  private
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 7
8
- - 0
9
- version: 0.7.0
8
+ - 1
9
+ version: 0.7.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Noctivity