Skip to content

Commit

Permalink
Merge pull request #3 from omkarmoghe/randomize-observation-order
Browse files Browse the repository at this point in the history
Randomize observation order
  • Loading branch information
omkarmoghe authored Jun 17, 2024
2 parents af96916 + b1d42dc commit fcf2c6c
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 9 deletions.
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.2.4
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## [0.1.6] - Unreleased
- [#1](https://github.com/omkarmoghe/lab_coat/issues/1)

## [0.1.5] - 2024-05-20
- Adds `select_observation` to allow users to control which observation value is returned by the experiment. This helps with controlled rollout.

Expand Down
29 changes: 20 additions & 9 deletions lib/lab_coat/experiment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module LabCoat
# A base experiment class meant to be subclassed to define various experiments.
class Experiment
OBSERVATIONS = %w[control candidate].freeze

attr_reader :name, :context

def initialize(name)
Expand Down Expand Up @@ -76,24 +78,33 @@ def select_observation(result)
# It's not recommended to override this method.
# @param context [Hash] Any data needed at runtime.
# @return [Object] An `Observation` value.
def run!(**context) # rubocop:disable Metrics/MethodLength
def run!(**context) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
# Set the context for this run.
@context = context

# Run the control and exit early if the experiment is not enabled.
control_obs = Observation.new("control", self) { control }
raised(control_obs) if control_obs.raised?
return control_obs.value unless enabled?
unless enabled?
control_obs = Observation.new("control", self) { control }
raised(control_obs) if control_obs.raised?
return control_obs.value
end

# Run the candidate.
candidate_obs = Observation.new("candidate", self) { candidate }
raised(candidate_obs) if candidate_obs.raised?
# Otherwise run the control and candidate in random order.
observations = OBSERVATIONS.shuffle.map do |name|
Observation.new(name, self) { public_send(name) }.tap do |observation|
raised(observation) if observation.raised?
end
end

# Compare and publish the results.
result = Result.new(self, control_obs, candidate_obs)
result = if observations.first.name == "control"
Result.new(self, observations.first, observations.last)
else
Result.new(self, observations.last, observations.first)
end
publish!(result)

# Always return the control.
# Return the selected observations, control by default.
select_observation(result).value.tap do
# Reset the context for this run. Done here so that `select_observation` has access to the runtime context.
@context = {}
Expand Down

0 comments on commit fcf2c6c

Please sign in to comment.