<s> """<STR_LIT>""" <EOL> from __future__ import absolute_import , division , print_function , with_statement , unicode_literals <EOL> from tornado import escape , util <EOL> from . actions import * <EOL> from . middleware import * <EOL> from . tests import * <EOL> import re <EOL> ACTIVE_EXPERIMENTS_REDIS_KEY = "<STR_LIT>" <EOL> ARCHIVED_EXPERIMENTS_REDIS_KEY = "<STR_LIT>" <EOL> EXPERIMENT_REDIS_KEY_TEMPLATE = "<STR_LIT>" <EOL> ALLOWED_NAMES = re . compile ( "<STR_LIT>" ) <EOL> CHI_SQUARE_DISTRIBUTION = [ <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> <NUM_LIT> , <EOL> ] <EOL> def chi_squared ( * choices ) : <EOL> """<STR_LIT>""" <EOL> term = lambda expected , observed : float ( ( expected - observed ) ** <NUM_LIT:2> ) / max ( expected , <NUM_LIT:1> ) <EOL> mean_success_rate = float ( sum ( [ c . rewards for c in choices ] ) ) / max ( sum ( [ c . plays for c in choices ] ) , <NUM_LIT:1> ) <EOL> mean_failure_rate = <NUM_LIT:1> - mean_success_rate <EOL> return sum ( [ <EOL> term ( mean_success_rate * c . plays , c . rewards ) <EOL> + term ( mean_failure_rate * c . plays , c . plays - c . rewards <EOL> ) for c in choices ] ) <EOL> def is_confident ( csq , num_choices ) : <EOL> """<STR_LIT>""" <EOL> return csq >= CHI_SQUARE_DISTRIBUTION [ num_choices - <NUM_LIT:2> ] <EOL> def parse_json ( raw ) : <EOL> """<STR_LIT>""" <EOL> return escape . recursive_unicode ( escape . json_decode ( raw ) ) if raw != None else None <EOL> def sync_from_spec ( redis , schema ) : <EOL> """<STR_LIT>""" <EOL> def get_experiments_dict ( active = True ) : <EOL> """<STR_LIT>""" <EOL> return dict ( ( experiment . name , experiment ) for experiment in get_experiments ( redis , active = active ) ) <EOL> active_experiments = get_experiments_dict ( ) <EOL> archived_experiments = get_experiments_dict ( active = False ) <EOL> new_experiment_names = set ( schema . keys ( ) ) <EOL> active_experiment_names = set ( active_experiments . keys ( ) ) <EOL> unarchivable_experiment_names = ( new_experiment_names - active_experiment_names ) & set ( archived_experiments . keys ( ) ) <EOL> for unarchivable_experiment_name in unarchivable_experiment_names : <EOL> print ( "<STR_LIT>" % unarchivable_experiment_name ) <EOL> pipe = redis . pipeline ( transaction = True ) <EOL> pipe . sadd ( ACTIVE_EXPERIMENTS_REDIS_KEY , unarchivable_experiment_name ) <EOL> pipe . srem ( ARCHIVED_EXPERIMENTS_REDIS_KEY , unarchivable_experiment_name ) <EOL> pipe . execute ( ) <EOL> if unarchivable_experiment_names : <EOL> active_experiments = get_experiments_dict ( ) <EOL> active_experiment_names = set ( active_experiments . keys ( ) ) <EOL> for new_experiment_name in new_experiment_names - active_experiment_names : <EOL> print ( "<STR_LIT>" % new_experiment_name ) <EOL> experiment = add_experiment ( redis , new_experiment_name ) <EOL> for choice in schema [ new_experiment_name ] : <EOL> print ( "<STR_LIT>" % choice ) <EOL> experiment . add_choice ( choice ) <EOL> for archivable_experiment_name in active_experiment_names - new_experiment_names : <EOL> print ( "<STR_LIT>" % archivable_experiment_name ) <EOL> active_experiments [ archivable_experiment_name ] . archive ( ) <EOL> for experiment_name in new_experiment_names & active_experiment_names : <EOL> experiment = active_experiments [ experiment_name ] <EOL> new_choice_names = set ( schema [ experiment_name ] ) <EOL> old_choice_names = set ( experiment . choice_names ) <EOL> for new_choice_name in new_choice_names - old_choice_names : <EOL> print ( "<STR_LIT>" % ( new_choice_name , experiment_name ) ) <EOL> experiment . add_choice ( new_choice_name ) <EOL> for removable_choice_name in old_choice_names - new_choice_names : <EOL> print ( "<STR_LIT>" % ( removable_choice_name , experiment_name ) ) <EOL> experiment . remove_choice ( removable_choice_name ) <EOL> class ExperimentException ( Exception ) : <EOL> """<STR_LIT>""" <EOL> def __init__ ( self , experiment_name , message ) : <EOL> super ( ExperimentException , self ) . __init__ ( "<STR_LIT>" % ( experiment_name , message ) ) <EOL> self . experiment_name = experiment_name <EOL> class Experiment ( object ) : <EOL> """<STR_LIT>""" <EOL> def __init__ ( self , redis , name ) : <EOL> self . redis = redis <EOL> self . name = name <EOL> self . refresh ( ) <EOL> def refresh ( self ) : <EOL> """<STR_LIT>""" <EOL> pipe = self . redis . pipeline ( ) <EOL> pipe . hget ( EXPERIMENT_REDIS_KEY_TEMPLATE % self . name , "<STR_LIT>" ) <EOL> pipe . hget ( EXPERIMENT_REDIS_KEY_TEMPLATE % self . name , "<STR_LIT>" ) <EOL> pipe . hget ( EXPERIMENT_REDIS_KEY_TEMPLATE % self . name , "<STR_LIT>" ) <EOL> results = pipe . execute ( ) <EOL> if results [ <NUM_LIT:0> ] == None : <EOL> raise ExperimentException ( self . name , "<STR_LIT>" ) <EOL> self . metadata = parse_json ( results [ <NUM_LIT:0> ] ) <EOL> self . choice_names = parse_json ( results [ <NUM_LIT:1> ] ) if results [ <NUM_LIT:1> ] != None else [ ] <EOL> self . default_choice = escape . to_unicode ( results [ <NUM_LIT:2> ] ) <EOL> self . _choices = None <EOL> @ property <EOL> def choices ( self ) : <EOL> """<STR_LIT>""" <EOL> if self . _choices == None : <EOL> self . _choices = [ ExperimentChoice ( self , choice_name ) for choice_name in self . choice_names ] <EOL> return self . _choices <EOL> def confidence ( self ) : <EOL> """<STR_LIT>""" <EOL> choices = self . choices <EOL> if len ( choices ) >= <NUM_LIT:2> : <EOL> csq = chi_squared ( * choices ) <EOL> confident = is_confident ( csq , len ( choices ) ) if len ( choices ) <= <NUM_LIT:10> else None <EOL> else : <EOL> csq = None <EOL> confident = False <EOL> return ( csq , confident ) <EOL> def archive ( self ) : <EOL> """<STR_LIT>""" <EOL> pipe = self . redis . pipeline ( transaction = True ) <EOL> pipe . srem ( ACTIVE_EXPERIMENTS_REDIS_KEY , self . name ) <EOL> pipe . sadd ( ARCHIVED_EXPERIMENTS_REDIS_KEY , self . name ) <EOL> pipe . execute ( ) <EOL> def add_choice ( self , choice_name ) : <EOL> """<STR_LIT>""" <EOL> if not ALLOWED_NAMES . match ( choice_name ) : <EOL> raise ExperimentException ( self . name , "<STR_LIT>" % choice_name ) <EOL> if choice_name in self . choice_names : <EOL> raise ExperimentException ( self . name , "<STR_LIT>" % choice_name ) <EOL> self . choice_names . append ( choice_name ) <EOL> self . redis . hset ( EXPERIMENT_REDIS_KEY_TEMPLATE % self . name , "<STR_LIT>" , escape . json_encode ( self . choice_names ) ) <EOL> self . refresh ( ) <EOL> def remove_choice ( self , choice_name ) : <EOL> """<STR_LIT>""" <EOL> self . choice_names . remove ( choice_name ) <EOL> self . redis . hset ( EXPERIMENT_REDIS_KEY_TEMPLATE % self . name , "<STR_LIT>" , escape . json_encode ( self . choice_names ) ) <EOL> self . refresh ( ) <EOL> def add_play ( self , choice , count = <NUM_LIT:1> ) : <EOL> """<STR_LIT>""" <EOL> self . redis . hincrby ( EXPERIMENT_REDIS_KEY_TEMPLATE % self . name , "<STR_LIT>" % choice , count ) <EOL> self . _choices = None <EOL> def add_reward ( self , choice , count = <NUM_LIT:1> ) : <EOL> """<STR_LIT>""" <EOL> self . redis . hincrby ( EXPERIMENT_REDIS_KEY_TEMPLATE % self . name , "<STR_LIT>" % choice , count ) <EOL> self . _choices = None <EOL> def compute_default_choice ( self ) : <EOL> """<STR_LIT>""" <EOL> choices = self . choices <EOL> if len ( choices ) == <NUM_LIT:0> : <EOL> return None <EOL> high_choice = max ( choices , key = lambda choice : choice . performance ) <EOL> self . redis . hset ( EXPERIMENT_REDIS_KEY_TEMPLATE % self . name , "<STR_LIT>" , high_choice . name ) <EOL> self . refresh ( ) <EOL> return high_choice <EOL> class ExperimentChoice ( object ) : <EOL> """<STR_LIT>""" <EOL> def __init__ ( self , experiment , name ) : <EOL> self . experiment = experiment <EOL> self . name = name <EOL> self . refresh ( ) <EOL> def refresh ( self ) : <EOL> """<STR_LIT>""" <EOL> redis_key = EXPERIMENT_REDIS_KEY_TEMPLATE % self . experiment . name <EOL> self . plays = int ( self . experiment . redis . hget ( redis_key , "<STR_LIT>" % self . name ) or <NUM_LIT:0> ) <EOL> self . rewards = int ( self . experiment . redis . hget ( redis_key , "<STR_LIT>" % self . name ) or <NUM_LIT:0> ) <EOL> self . performance = float ( self . rewards ) / max ( self . plays , <NUM_LIT:1> ) <EOL> def add_experiment ( redis , name ) : <EOL> """<STR_LIT>""" <EOL> if not ALLOWED_NAMES . match ( name ) : <EOL> raise ExperimentException ( name , "<STR_LIT>" ) <EOL> if redis . exists ( EXPERIMENT_REDIS_KEY_TEMPLATE % name ) : <EOL> raise ExperimentException ( name , "<STR_LIT>" ) <EOL> json = dict ( creation_date = util . unicode_type ( datetime . datetime . now ( ) ) ) <EOL> pipe = redis . pipeline ( transaction = True ) <EOL> pipe . sadd ( ACTIVE_EXPERIMENTS_REDIS_KEY , name ) <EOL> pipe . hset ( EXPERIMENT_REDIS_KEY_TEMPLATE % name , "<STR_LIT>" , escape . json_encode ( json ) ) <EOL> pipe . execute ( ) <EOL> return Experiment ( redis , name ) <EOL> def get_experiments ( redis , active = True ) : <EOL> """<STR_LIT>""" <EOL> key = ACTIVE_EXPERIMENTS_REDIS_KEY if active else ARCHIVED_EXPERIMENTS_REDIS_KEY <EOL> return [ Experiment ( redis , escape . to_unicode ( name ) ) for name in redis . smembers ( key ) ] </s>
