import itertools import json as json import math import random import random as random import numpy as np import pandas as pd from otree.api import * author = 'Your name here' doc = """ Main part of the experiment """ def load_math_options(): with open( f'Treatment/static/Addition_Increasing_Difficulty/task_list_16_rounds.json', 'r' ) as read_file: task_list = json.load(read_file) return task_list def load_pwd_tasks(type='LinearDifficulty'): with open(f'Treatment/static/Code_{type}/counting_tasks.json', 'r') as read_file: task_list = json.load(read_file) return task_list def make_math_ability_field(): return models.IntegerField( blank=True, label='', ) def make_pwd_ability_field(): return models.IntegerField( blank=True, label='', ) def make_wtp_field(): return models.BooleanField() ## Production Function def get_production_func(df, task, timeout=40): """Obtain production function on second level Computes for every second the expected points each subject obtains. Parameters ------------ df : pd.DataFrame Information in standard (subject-round)-format. task : str `math` : math task. `mem` : memory. Returns ------------ df_res : pd.DataFrame Expected value in with seconds as index. """ df_res = pd.Series() if task == 'math': max_num = 10 elif task == 'pwd': max_num = 10 for sec in range(0, timeout + 1): tab = (df <= sec).mean() for num in reversed(range(1, max_num)): weight = np.array(tab[f'{task}_t_{num}'] - tab[f'{task}_t_{num+1}']) if (num + 1) == max_num: # Treat the last round extra exp_val = (num + 1) * np.array(tab[f'{task}_t_{num+1}']) exp_val += num * weight df_res.loc[sec] = exp_val # df_res.index = tab.index return df_res def get_optimal_allocation(df, p_vars): """ Find the optimal allocation of time between 'mem' and 'math' activities. Parameters: df (pd.DataFrame): DataFrame containing scores for 'mem' and 'math' activities Returns: tuple: A tuple of two elements: rslt (pd.DataFrame): DataFrame with columns 'switch_point', 'math_score', 'mem_score', and 'total_score' detail_dict (dict): A dictionary with individual level results for each subject The function iterates over all seconds, calculates scores for 'mem' and 'math' activities, and finds the optimal allocation that results in the highest total score. The results are returned as a DataFrame with columns 'switch_point', 'math_score', 'mem_score', and 'total_score' and as a dictionary with individual level results. """ allotted_time = Constants.timeout xs = np.ones((3, (allotted_time + 1))) * np.nan for sec in range(0, allotted_time + 1): pwd = df.loc[allotted_time - sec]['pwd'] math = df.loc[sec]['math'] xs[0, sec] = math xs[1, sec] = pwd xs[2, sec] = math + pwd # Optimal allocation alloc = np.argmax(xs[2, :]) p_vars['switch_point'] = int(alloc) p_vars['opt_math_score'] = round(float(xs[0, alloc]), 3) p_vars['opt_pwd_score'] = round(float(xs[1, alloc]), 3) p_vars['opt_total_score'] = round(float(xs[2, alloc]), 3) class Constants(BaseConstants): name_in_url = '9H32Ddw4gdqBa' players_per_group = None num_rounds = 5 num_comp = int(num_rounds / 2) num_participants = 10 # ordering = ['BA','AB'] likert_scale_agree = [ (1, 'I completely disagree'), (2, 'I somewhat disagree'), (3, 'Neither agree nor disagree'), (4, 'I somewhat agree'), (5, 'I completely agree'), ] # Load tasks from JSON addition_task = load_math_options() # linear_task = load_pwd_tasks(type='LinearDifficulty') convex_task = load_pwd_tasks(type='DecreasingDifficulty') # Payoff Constants payoff_incorrect = cu(0) pwd_rate_high = 12 pwd_piecerate_high = cu(pwd_rate_high) math_rate_high = 12 math_piecerate_high = cu(math_rate_high) pwd_rate_low = 10 pwd_piecerate_low = cu(pwd_rate_low) math_rate_low = 10 math_piecerate_low = cu(math_rate_low) task = 'MainPart' production_profile = ['Convex'] # ['Linear','Convex'] treatments = ['Control', 'Reminder', 'WTP'] incentives = ['High', 'Low'] timeout = 40 num_pwd_tasks = 10 num_math_tasks = 10 randomization_weights = [45/100,45/100,10/100] class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): # Task task = models.StringField() # Production function type production_profile = models.StringField() # Treatment treatment = models.StringField() ### WTP implementation random_num = models.IntegerField(blank=True) rel_decision = models.IntegerField(blank=True) WTP_cost = models.FloatField(blank=True) WTP_pos = models.BooleanField(blank=True) incentive_level = models.StringField() # Check correct answers math_correct = models.IntegerField(initial=0) # Check correct answers pwd_correct = models.IntegerField(initial=0) # Optimal Score optimal_score = models.FloatField() # Optimal Switch Point optimal_switch_point = models.IntegerField() # Most important timer math_time = models.FloatField() pwd_time = models.FloatField() ###### sequence task ############################ math_1 = make_math_ability_field() math_2 = make_math_ability_field() math_3 = make_math_ability_field() math_4 = make_math_ability_field() math_5 = make_math_ability_field() math_6 = make_math_ability_field() math_7 = make_math_ability_field() math_8 = make_math_ability_field() math_9 = make_math_ability_field() math_10 = make_math_ability_field() ### Cumulated Time needed for one sequence ################ math_1_time = models.FloatField(blank=True) math_2_time = models.FloatField(blank=True) math_3_time = models.FloatField(blank=True) math_4_time = models.FloatField(blank=True) math_5_time = models.FloatField(blank=True) math_6_time = models.FloatField(blank=True) math_7_time = models.FloatField(blank=True) math_8_time = models.FloatField(blank=True) math_9_time = models.FloatField(blank=True) math_10_time = models.FloatField(blank=True) ### Number tries needed to find the correct input math_1_tries = models.IntegerField(blank=True) math_2_tries = models.IntegerField(blank=True) math_3_tries = models.IntegerField(blank=True) math_4_tries = models.IntegerField(blank=True) math_5_tries = models.IntegerField(blank=True) math_6_tries = models.IntegerField(blank=True) math_7_tries = models.IntegerField(blank=True) math_8_tries = models.IntegerField(blank=True) math_9_tries = models.IntegerField(blank=True) math_10_tries = models.IntegerField(blank=True) # Timer timer_task1 = models.FloatField() new_timer_task1 = models.FloatField() timer_task2 = models.FloatField() new_timer_task2 = models.FloatField() total_time = models.FloatField() # Prompt Counter prompt_counter = models.IntegerField() # Switch Counter switch_counter = models.IntegerField() # Switch Timer switch_time_1 = models.FloatField(blank=True) switch_time_2 = models.FloatField(blank=True) switch_time_3 = models.FloatField(blank=True) switch_time_4 = models.FloatField(blank=True) switch_time_5 = models.FloatField(blank=True) switch_time_6 = models.FloatField(blank=True) switch_time_7 = models.FloatField(blank=True) switch_time_8 = models.FloatField(blank=True) switch_time_9 = models.FloatField(blank=True) switch_time_10 = models.FloatField(blank=True) # Switch Timer switch_1_math_perf = models.IntegerField(blank=True) switch_2_math_perf = models.IntegerField(blank=True) switch_3_math_perf = models.IntegerField(blank=True) switch_4_math_perf = models.IntegerField(blank=True) switch_5_math_perf = models.IntegerField(blank=True) switch_6_math_perf = models.IntegerField(blank=True) switch_7_math_perf = models.IntegerField(blank=True) switch_8_math_perf = models.IntegerField(blank=True) switch_9_math_perf = models.IntegerField(blank=True) switch_10_math_perf = models.IntegerField(blank=True) switch_1_pwd_perf = models.IntegerField(blank=True) switch_2_pwd_perf = models.IntegerField(blank=True) switch_3_pwd_perf = models.IntegerField(blank=True) switch_4_pwd_perf = models.IntegerField(blank=True) switch_5_pwd_perf = models.IntegerField(blank=True) switch_6_pwd_perf = models.IntegerField(blank=True) switch_7_pwd_perf = models.IntegerField(blank=True) switch_8_pwd_perf = models.IntegerField(blank=True) switch_9_pwd_perf = models.IntegerField(blank=True) switch_10_pwd_perf = models.IntegerField(blank=True) # Time Spent RandomizationPage_time_spent = models.FloatField(blank=True) InfoPage_time_spent = models.FloatField(blank=True) Instructions_Reminder_time_spent = models.FloatField(blank=True) Allocation_time_spent = models.FloatField(blank=True) Task_time_spent = models.FloatField(blank=True) Exit_Q1_time_spent = models.FloatField(blank=True) Exit_Q2_time_spent = models.FloatField(blank=True) # Warnings RandomizationPage_warnings = models.IntegerField() InfoPage_warnings = models.IntegerField() Instructions_Reminder_warnings = models.IntegerField() Allocation_warnings = models.IntegerField() StartRound_warnings = models.IntegerField() Task_warnings = models.IntegerField() Exit_Q1_warnings = models.IntegerField() Exit_Q2_warnings = models.IntegerField() task_option_this_round = models.IntegerField() # Actual Allocation Dummy for Testing actual_allocation = models.BooleanField() ## Control questions control_q1 = models.PositiveIntegerField(choices=[i * 10 for i in range(11)], initial=None) control_q2 = models.PositiveIntegerField(choices=[i * 10 for i in range(11)], initial=None) control_q3 = models.PositiveIntegerField(choices=[i * 10 for i in range(11)], initial=None) q_subjective_sophistication_2 = models.PositiveIntegerField( choices=Constants.likert_scale_agree, label='I chose a good allocation of my time between the two tasks.', widget=widgets.RadioSelectHorizontal(), ) q_subjective_optimality_2 = models.IntegerField( choices=range(1, Constants.timeout + 1), widget=widgets.RadioSelect ) # Payoffs and Piecerates # Piece Rate pwd_piecerate = models.CurrencyField() math_piecerate = models.CurrencyField() payoff_pwd = models.CurrencyField() payoff_math = models.CurrencyField() payoff_gross = models.CurrencyField() ### Cumulated Time needed for one math task ################ pwd_1_time = models.FloatField(blank=True) pwd_2_time = models.FloatField(blank=True) pwd_3_time = models.FloatField(blank=True) pwd_4_time = models.FloatField(blank=True) pwd_5_time = models.FloatField(blank=True) pwd_6_time = models.FloatField(blank=True) pwd_7_time = models.FloatField(blank=True) pwd_8_time = models.FloatField(blank=True) pwd_9_time = models.FloatField(blank=True) pwd_10_time = models.FloatField(blank=True) pwd_1 = make_pwd_ability_field() pwd_2 = make_pwd_ability_field() pwd_3 = make_pwd_ability_field() pwd_4 = make_pwd_ability_field() pwd_5 = make_pwd_ability_field() pwd_6 = make_pwd_ability_field() pwd_7 = make_pwd_ability_field() pwd_8 = make_pwd_ability_field() pwd_9 = make_pwd_ability_field() pwd_10 = make_pwd_ability_field() # Number of tries pwd_1_tries = models.IntegerField(blank=True) pwd_2_tries = models.IntegerField(blank=True) pwd_3_tries = models.IntegerField(blank=True) pwd_4_tries = models.IntegerField(blank=True) pwd_5_tries = models.IntegerField(blank=True) pwd_6_tries = models.IntegerField(blank=True) pwd_7_tries = models.IntegerField(blank=True) pwd_8_tries = models.IntegerField(blank=True) pwd_9_tries = models.IntegerField(blank=True) pwd_10_tries = models.IntegerField(blank=True) # Number of toggles pwd_1_toggles = models.IntegerField(blank=True) pwd_2_toggles = models.IntegerField(blank=True) pwd_3_toggles = models.IntegerField(blank=True) pwd_4_toggles = models.IntegerField(blank=True) pwd_5_toggles = models.IntegerField(blank=True) pwd_6_toggles = models.IntegerField(blank=True) pwd_7_toggles = models.IntegerField(blank=True) pwd_8_toggles = models.IntegerField(blank=True) pwd_9_toggles = models.IntegerField(blank=True) pwd_10_toggles = models.IntegerField(blank=True) # FUNCTIONS def creating_session(subsession: Subsession): ##### This code is for obtaining a balanced session ############ task_profiles = Constants.production_profile.copy() # shuffle the list random.shuffle(task_profiles) # make a cycle out of the shuffled list profiles = itertools.cycle(task_profiles) for p in subsession.get_players(): # Establishes task ordering & Incentives for Math Task if subsession.round_number == 1: try: p.participant.vars['production_profile'] except KeyError: # p.participant.vars['task_treatment'] = random.choice(Constants.task_treatment) p.participant.vars['production_profile'] = next(profiles) p.task = Constants.task p.task_option_this_round = subsession.round_number - 1 p.production_profile = p.participant.vars['production_profile'] def control_q1_error_message(player: Player, value): """Customized error message for control question 1.""" cond = value != 30 if cond: return 'Your input is incorrect. Please try again!' def control_q2_error_message(player: Player, value): """Customized error message for control question 2.""" cond = value != 60 if cond: return 'Your input is incorrect. Please try again!' def control_q3_error_message(player: Player, value): """Customized error message for control question 3.""" cond = value != 90 if cond: return 'Your input is incorrect. Please try again!' def q_subjective_optimality_2_error_message(player: Player, value): """Customized error message for subjective optimality.""" if value == None: return 'Your input is incorrect. Please try again!' # Questions def set_payoff(player: Player): if player.production_profile == 'Linear': pwd_solutions = Constants.linear_task[player.task_option_this_round][0] elif player.production_profile == 'Convex': pwd_solutions = Constants.convex_task[player.task_option_this_round][0] pwd_1 = player.field_maybe_none('pwd_1') pwd_2 = player.field_maybe_none('pwd_2') pwd_3 = player.field_maybe_none('pwd_3') pwd_4 = player.field_maybe_none('pwd_4') pwd_5 = player.field_maybe_none('pwd_5') pwd_6 = player.field_maybe_none('pwd_6') pwd_7 = player.field_maybe_none('pwd_7') pwd_8 = player.field_maybe_none('pwd_8') pwd_9 = player.field_maybe_none('pwd_9') pwd_10 = player.field_maybe_none('pwd_10') pwd_answers = [ pwd_1, pwd_2, pwd_3, pwd_4, pwd_5, pwd_6, pwd_7, pwd_8, pwd_9, pwd_10, ] player.pwd_correct = sum([1 for x, y in zip(pwd_answers, pwd_solutions) if x == y]) player.payoff_pwd = player.pwd_correct * player.pwd_piecerate math_solutions = Constants.addition_task[player.task_option_this_round][1] math_1 = player.field_maybe_none('math_1') math_2 = player.field_maybe_none('math_2') math_3 = player.field_maybe_none('math_3') math_4 = player.field_maybe_none('math_4') math_5 = player.field_maybe_none('math_5') math_6 = player.field_maybe_none('math_6') math_7 = player.field_maybe_none('math_7') math_8 = player.field_maybe_none('math_8') math_9 = player.field_maybe_none('math_9') math_10 = player.field_maybe_none('math_10') math_answers = [ math_1, math_2, math_3, math_4, math_5, math_6, math_7, math_8, math_9, math_10, ] player.math_correct = sum([1 for x, y in zip(math_answers, math_solutions) if x == y]) player.payoff_math = player.math_correct * player.math_piecerate player.payoff_gross = player.payoff_math + player.payoff_pwd if player.treatment == 'WTP': # Subtract cost player.payoff = player.payoff_gross - cu(player.WTP_cost) # Cost components player.participant.vars[f'treat_wtp_cost_round_{player.round_number}'] = cu(player.WTP_cost) else: ### Compute total payoff player.payoff = player.payoff_gross # Put it into the ether # Pwd player.participant.vars[f'treat_pwd_round_{player.round_number}'] = player.pwd_correct player.participant.vars[f'treat_pwd_payoff_round_{player.round_number}'] = player.payoff_pwd # Math player.participant.vars[f'treat_math_round_{player.round_number}'] = player.math_correct player.participant.vars[f'treat_math_payoff_round_{player.round_number}'] = player.payoff_math def comp_opt_alloc(player: Player): p_vars = player.participant.vars # Simplifies code later math_cols = [ 'math_t_1', 'math_t_2', 'math_t_3', 'math_t_4', 'math_t_5', 'math_t_6', 'math_t_7', 'math_t_8', 'math_t_9', 'math_t_10', ] pwd_cols = [ 'pwd_t_1', 'pwd_t_2', 'pwd_t_3', 'pwd_t_4', 'pwd_t_5', 'pwd_t_6', 'pwd_t_7', 'pwd_t_8', 'pwd_t_9', 'pwd_t_10', ] # Create df of only pwd data pwddict = {col: p_vars[col] for col in pwd_cols} df_pwd = pd.DataFrame(pwddict) # Create df of only math data mathdict = {col: p_vars[col] for col in math_cols} df_math = pd.DataFrame(mathdict) # Remove the first two rounds (practice) df_pwd = df_pwd.loc[2:, :] df_math = df_math.loc[2:, :] # Create respective production function rslt_pwd = get_production_func(df_pwd, 'pwd') rslt_math = get_production_func(df_math, 'math') # Find optimal allocation df_both = pd.DataFrame({'pwd': rslt_pwd, 'math': rslt_math}) get_optimal_allocation(df_both, p_vars) def write_math_time(player: Player): p_vars = player.participant.vars # p_vars['last_round_math_time'] = self.math_time p_vars['treat_num_pwd_tasks'] = Constants.num_pwd_tasks p_vars['treat_num_math_tasks'] = Constants.num_math_tasks p_vars['treat_num_rounds'] = Constants.num_rounds # PAGES class RandomizationPage(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def before_next_page(player: Player, timeout_happened): p = player p.participant.vars['incentive_level'] = random.choice(Constants.incentives) p.participant.vars['treatment'] = random.choices( Constants.treatments, weights=Constants.randomization_weights, k=1 )[0] if p.participant.vars['treatment'] == 'WTP': # Choose a random number # np.random.randint includes the lower bound but excludes the upper bound # with max_amount = 64, the upper bound is 63, the lower bound is -63. # In our sequence these are the highest and lowest possible stated WTPs. p.random_num = np.random.randint( ((-p.participant.vars['WTP_max_amount']) + 1), p.participant.vars['WTP_max_amount'] ) if p.random_num >= 0: # Standard Case: Positive Cost # High Incentive Level if p.participant.vars['incentive_level'] == 'High': # Cost are too large if p.random_num > p.participant.vars['WTP_high_lowest']: p.participant.vars['WTP_rel_decision'] = 0 p.participant.vars['WTP_cost'] = 0 else: # WTP >= Cost p.participant.vars['WTP_rel_decision'] = 1 p.participant.vars['WTP_cost'] = p.random_num p.WTP_pos = p.participant.vars['WTP_high_lowest'] >= 0 # Low Incentives elif p.participant.vars['incentive_level'] == 'Low': # Cost are too large if p.random_num > p.participant.vars['WTP_low_lowest']: p.participant.vars['WTP_rel_decision'] = 0 p.participant.vars['WTP_cost'] = 0 else: # WTP >= Cost p.participant.vars['WTP_rel_decision'] = 1 p.participant.vars['WTP_cost'] = p.random_num p.WTP_pos = p.participant.vars['WTP_low_lowest'] >= 0 else: # Negative Cost => Cost to avoid the reminder # High Incentive Level if p.participant.vars['incentive_level'] == 'High': # Cost are too large if p.random_num > p.participant.vars['WTP_high_lowest']: p.participant.vars['WTP_rel_decision'] = 0 p.participant.vars['WTP_cost'] = -p.random_num else: # WTP >= Cost p.participant.vars['WTP_rel_decision'] = 1 p.participant.vars['WTP_cost'] = 0 p.WTP_pos = p.participant.vars['WTP_high_lowest'] >= 0 # Low Incentives elif p.participant.vars['incentive_level'] == 'Low': # Cost are too large if p.random_num > p.participant.vars['WTP_low_lowest']: p.participant.vars['WTP_rel_decision'] = 0 p.participant.vars['WTP_cost'] = -p.random_num else: # WTP >= Cost p.participant.vars['WTP_rel_decision'] = 1 p.participant.vars['WTP_cost'] = 0 p.WTP_pos = p.participant.vars['WTP_low_lowest'] >= 0 else: p.participant.vars['WTP_rel_decision'] = None p.participant.vars['WTP_cost'] = None p.WTP_pos = None p.rel_decision = p.participant.vars['WTP_rel_decision'] p.WTP_cost = p.participant.vars['WTP_cost'] p.treatment = p.participant.vars['treatment'] p.incentive_level = p.participant.vars['incentive_level'] if p.incentive_level == 'High': p.pwd_piecerate = Constants.pwd_piecerate_high p.math_piecerate = Constants.math_piecerate_high if p.incentive_level == 'Low': p.pwd_piecerate = Constants.pwd_piecerate_low p.math_piecerate = Constants.math_piecerate_low @staticmethod def js_vars(player: Player): return dict( page_name='RandomizationPage', ) form_model = 'player' form_fields = ['RandomizationPage_time_spent', 'RandomizationPage_warnings'] class InfoPage(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def before_next_page(player: Player, timeout_happened): treatment = player.field_maybe_none('treatment') rel_decision = player.field_maybe_none('rel_decision') if (treatment == 'Reminder') | ( (treatment == 'WTP') & (rel_decision == 1) ): try: comp_opt_alloc(player) player.actual_allocation = True except KeyError: player.participant.vars['switch_point'] = 15 player.participant.vars['opt_total_score'] = 20 player.actual_allocation = False player.optimal_switch_point = player.participant.vars['switch_point'] player.optimal_score = player.participant.vars['opt_total_score'] form_model = 'player' form_fields = ['InfoPage_time_spent', 'InfoPage_warnings'] @staticmethod def js_vars(player: Player): return dict( page_name='InfoPage', timeout=Constants.timeout, ) @staticmethod def vars_for_template(player: Player): treatment = player.field_maybe_none('treatment') WTP_pos = player.field_maybe_none('WTP_pos') random_num = player.field_maybe_none('random_num') if treatment == 'WTP': try: if WTP_pos == True: if random_num > 0: # If I want the reminder and the cost is positive rand_cost = random_num else: # If I want the reminder and the cost is negative rand_cost = 0 return dict(rand_cost=rand_cost) elif WTP_pos == False: if random_num < 0: # If I do not want the reminder and the cost is negative rand_cost = -random_num else: # If I do not want the reminder and the cost is positive rand_cost = 0 return dict(rand_cost=rand_cost) except: pass class Instructions_Reminder(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 form_model = 'player' form_fields = ['Instructions_Reminder_time_spent', 'Instructions_Reminder_warnings'] @staticmethod def js_vars(player: Player): return dict( page_name='Instructions_Reminder', ) @staticmethod def vars_for_template(player: Player): WTP_cost = player.field_maybe_none('WTP_cost') if (WTP_cost == None) or (WTP_cost == 0): is_cost = False else: is_cost = True return dict(is_cost=is_cost) @staticmethod def before_next_page(player: Player, timeout_happened): treatment = player.field_maybe_none('treatment') rel_decision = player.field_maybe_none('rel_decision') cond = (treatment == 'Reminder') | ( (treatment == 'WTP') & (rel_decision == 1) ) if cond: try: comp_opt_alloc(player) player.actual_allocation = True except KeyError: player.participant.vars['switch_point'] = 15 player.participant.vars['opt_total_score'] = 20 player.actual_allocation = False player.optimal_switch_point = player.participant.vars['switch_point'] player.optimal_score = player.participant.vars['opt_total_score'] class Allocation(Page): @staticmethod def is_displayed(player: Player): treatment = player.field_maybe_none('treatment') rel_decision = player.field_maybe_none('rel_decision') cond = (treatment == 'Reminder') | ( (treatment == 'WTP') & (rel_decision == 1) ) return player.round_number == 1 & cond form_model = 'player' form_fields = ['Allocation_time_spent', 'Allocation_warnings'] @staticmethod def js_vars(player: Player): return dict( page_name='Allocation', ) @staticmethod def vars_for_template(player: Player): # conversion = self.session.config['real_world_currency_per_point'] opt_pwd_time = Constants.timeout - player.participant.vars['switch_point'] return dict(switch_point=player.participant.vars['switch_point'], opt_pwd_time=opt_pwd_time) class StartRound(Page): # oTree Timer timer_text = 'Time left until the round starts:' timeout_seconds = 5 @staticmethod def vars_for_template(player: Player): return dict(round_num=player.round_number) @staticmethod def js_vars(player: Player): return dict( page_name='StartRound', ) @staticmethod def before_next_page(player: Player, timeout_happened): p = player if player.round_number > 1: p.treatment = p.participant.vars['treatment'] p.incentive_level = p.participant.vars['incentive_level'] if p.incentive_level == 'High': p.pwd_piecerate = Constants.pwd_piecerate_high p.math_piecerate = Constants.math_piecerate_high if p.incentive_level == 'Low': p.pwd_piecerate = Constants.pwd_piecerate_low p.math_piecerate = Constants.math_piecerate_low try: p.rel_decision = p.participant.vars['WTP_rel_decision'] p.WTP_cost = p.participant.vars['WTP_cost'] except KeyError: pass form_model = 'player' form_fields = ['StartRound_warnings'] class MathMemory(Page): # oTree Timer timer_text = 'Time left in this round:' timeout_seconds = Constants.timeout @staticmethod def before_next_page(player: Player, timeout_happened): set_payoff(player) if player.round_number == Constants.num_rounds: write_math_time(player) @staticmethod def vars_for_template(player: Player): p = player label = Constants.addition_task[player.round_number - 1][0] if p.production_profile == 'Linear': pwd_label = Constants.linear_task[player.round_number - 1][0] elif p.production_profile == 'Convex': pwd_label = Constants.convex_task[player.round_number - 1][0] filler = ' = ' return dict( math_1_label=label[0] + filler, math_2_label=label[1] + filler, math_3_label=label[2] + filler, math_4_label=label[3] + filler, math_5_label=label[4] + filler, math_6_label=label[5] + filler, math_7_label=label[6] + filler, math_8_label=label[7] + filler, math_9_label=label[8] + filler, math_10_label=label[9] + filler, pwd_1_label=pwd_label[0], pwd_2_label=pwd_label[1], pwd_3_label=pwd_label[2], pwd_4_label=pwd_label[3], pwd_5_label=pwd_label[4], pwd_6_label=pwd_label[5], pwd_7_label=pwd_label[6], pwd_8_label=pwd_label[7], pwd_9_label=pwd_label[8], pwd_10_label=pwd_label[9], ) form_model = 'player' form_fields = [ 'timer_task1', 'new_timer_task1', 'timer_task2', 'new_timer_task2', 'pwd_time', 'total_time', 'prompt_counter', 'switch_counter', 'Task_time_spent', 'Task_warnings', 'math_1', 'math_2', 'math_3', 'math_4', 'math_5', 'math_6', 'math_7', 'math_8', 'math_9', 'math_10', 'math_time', 'math_1_time', 'math_2_time', 'math_3_time', 'math_4_time', 'math_5_time', 'math_6_time', 'math_7_time', 'math_8_time', 'math_9_time', 'math_10_time', 'math_1_tries', 'math_2_tries', 'math_3_tries', 'math_4_tries', 'math_5_tries', 'math_6_tries', 'math_7_tries', 'math_8_tries', 'math_9_tries', 'math_10_tries', 'switch_time_1', 'switch_time_2', 'switch_time_3', 'switch_time_4', 'switch_time_5', 'switch_time_6', 'switch_time_7', 'switch_time_8', 'switch_time_9', 'switch_time_10', 'switch_1_math_perf', 'switch_2_math_perf', 'switch_3_math_perf', 'switch_4_math_perf', 'switch_5_math_perf', 'switch_6_math_perf', 'switch_7_math_perf', 'switch_8_math_perf', 'switch_9_math_perf', 'switch_10_math_perf', 'switch_1_pwd_perf', 'switch_2_pwd_perf', 'switch_3_pwd_perf', 'switch_4_pwd_perf', 'switch_5_pwd_perf', 'switch_6_pwd_perf', 'switch_7_pwd_perf', 'switch_8_pwd_perf', 'switch_9_pwd_perf', 'switch_10_pwd_perf', 'pwd_1', 'pwd_2', 'pwd_3', 'pwd_4', 'pwd_5', 'pwd_6', 'pwd_7', 'pwd_8', 'pwd_9', 'pwd_10', 'pwd_time', 'pwd_1_time', 'pwd_2_time', 'pwd_3_time', 'pwd_4_time', 'pwd_5_time', 'pwd_6_time', 'pwd_7_time', 'pwd_8_time', 'pwd_9_time', 'pwd_10_time', 'pwd_1_tries', 'pwd_2_tries', 'pwd_3_tries', 'pwd_4_tries', 'pwd_5_tries', 'pwd_6_tries', 'pwd_7_tries', 'pwd_8_tries', 'pwd_9_tries', 'pwd_10_tries', 'pwd_1_toggles', 'pwd_2_toggles', 'pwd_3_toggles', 'pwd_4_toggles', 'pwd_5_toggles', 'pwd_6_toggles', 'pwd_7_toggles', 'pwd_8_toggles', 'pwd_9_toggles', 'pwd_10_toggles', ] class Control(MathMemory): @staticmethod def is_displayed(player: Player): treatment = player.field_maybe_none('treatment') rel_decision = player.field_maybe_none('rel_decision') return (treatment == 'Control') | ( (treatment == 'WTP') & (rel_decision == 0) ) def js_vars(player: Player): p = player if p.production_profile == 'Linear': pwd_sol = Constants.linear_task[p.round_number - 1][0] elif p.production_profile == 'Convex': pwd_sol = Constants.convex_task[p.round_number - 1][0] math_sol = Constants.addition_task[p.round_number - 1][1] return dict( pwd_solutions_array=pwd_sol, math_solutions_array=math_sol, page_name='Task', num_pwd_tasks=Constants.num_pwd_tasks, num_math_tasks=Constants.num_math_tasks, ) class Reminder(MathMemory): @staticmethod def is_displayed(player: Player): treatment = player.field_maybe_none('treatment') rel_decision = player.field_maybe_none('rel_decision') return (treatment == 'Reminder') | ( (treatment == 'WTP') & (rel_decision == 1) ) def js_vars(player: Player): p = player if p.production_profile == 'Linear': pwd_sol = Constants.linear_task[p.round_number - 1][0] elif p.production_profile == 'Convex': pwd_sol = Constants.convex_task[p.round_number - 1][0] math_sol = Constants.addition_task[p.round_number - 1][1] opt_pwd_time = Constants.timeout - p.participant.vars['switch_point'] return dict( pwd_solutions_array=pwd_sol, math_solutions_array=math_sol, page_name='Task', num_pwd_tasks=Constants.num_pwd_tasks, num_math_tasks=Constants.num_math_tasks, switch_point=int(p.participant.vars['switch_point']), opt_pwd_time=opt_pwd_time, ) class Exit_Q1(Page): @staticmethod def is_displayed(player: Player): return player.round_number == Constants.num_rounds @staticmethod def js_vars(player: Player): return dict( page_name='Exit_Q1', ) form_model = 'player' form_fields = ['Exit_Q1_warnings', 'Exit_Q1_time_spent', 'q_subjective_sophistication_2'] class Exit_Q2(Page): @staticmethod def is_displayed(player: Player): return player.round_number == Constants.num_rounds @staticmethod def js_vars(player: Player): return dict( page_name='Exit_Q2', ) form_model = 'player' form_fields = ['Exit_Q2_warnings', 'Exit_Q2_time_spent', 'q_subjective_optimality_2'] @staticmethod def error_message(player: Player, values): """Customized error message for control question 2. The error message is not actually displayed. This function simply validates the form for us. For the actual error message code look at the html code of the page. """ if values['q_subjective_optimality_2'] == None: return "Please indicate your answer by clicking on the slider." page_sequence = [ RandomizationPage, InfoPage, Instructions_Reminder, Allocation, StartRound, Control, Reminder, Exit_Q1, Exit_Q2, ]