from otree.api import Currency as c, currency_range from otree.api import safe_json from ._builtin import Page, WaitPage from .models import Constants import random import itertools import numpy as np class RandomizationPage(Page): def is_displayed(self): return self.round_number == 1 def before_next_page(self): p = self.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 def js_vars(self): return dict( page_name='RandomizationPage', ) form_model = 'player' form_fields = ['RandomizationPage_time_spent','RandomizationPage_warnings'] class InfoPage(Page): def is_displayed(self): return self.round_number == 1 def before_next_page(self): if (self.player.treatment == 'Reminder') | ((self.player.treatment == 'WTP') & (self.player.rel_decision == 1)): try: self.player.comp_opt_alloc() self.player.actual_allocation = True except KeyError: self.participant.vars['switch_point'] = 15 self.participant.vars['opt_total_score'] = 20 self.player.actual_allocation = False self.player.optimal_switch_point = self.player.participant.vars['switch_point'] self.player.optimal_score = self.player.participant.vars['opt_total_score'] form_model = 'player' form_fields = ['InfoPage_time_spent','InfoPage_warnings'] def js_vars(self): return dict( page_name='InfoPage', timeout=Constants.timeout, ) def vars_for_template(self): if self.player.treatment == 'WTP': try: if self.player.WTP_pos == True: if self.player.random_num>0: # If I want the reminder and the cost is positive rand_cost = self.player.random_num else: # If I want the reminder and the cost is negative rand_cost = 0 return dict(rand_cost=rand_cost) elif self.player.WTP_pos == False: if self.player.random_num<0: # If I do not want the reminder and the cost is negative rand_cost = (-self.player.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): def is_displayed(self): return self.round_number == 1 form_model = 'player' form_fields = ['Instructions_Reminder_time_spent','Instructions_Reminder_warnings'] def js_vars(self): return dict( page_name='Instructions_Reminder', ) def vars_for_template(self): if (self.player.WTP_cost == None) or (self.player.WTP_cost == 0): is_cost = False else: is_cost = True return dict(is_cost=is_cost) def before_next_page(self): cond = ((self.player.treatment == 'Reminder') | ((self.player.treatment == 'WTP') & (self.player.rel_decision == 1))) if cond: try: self.player.comp_opt_alloc() self.player.actual_allocation = True except KeyError: self.participant.vars['switch_point'] = 15 self.participant.vars['opt_total_score'] = 20 self.player.actual_allocation = False self.player.optimal_switch_point = self.player.participant.vars['switch_point'] self.player.optimal_score = self.player.participant.vars['opt_total_score'] class Allocation(Page): def is_displayed(self): cond = ((self.player.treatment == 'Reminder') | ((self.player.treatment == 'WTP') & (self.player.rel_decision == 1))) return self.round_number == 1 & cond form_model = 'player' form_fields = ['Allocation_time_spent','Allocation_warnings'] def js_vars(self): return dict( page_name='Allocation', ) def vars_for_template(self): # conversion = self.session.config['real_world_currency_per_point'] opt_pwd_time = Constants.timeout-self.participant.vars['switch_point'] return dict(switch_point = self.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 def vars_for_template(self): return dict(round_num=self.round_number) def js_vars(self): return dict( page_name='StartRound', ) def before_next_page(self): p = self.player if self.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 def before_next_page(self): self.player.set_payoff() if self.round_number == Constants.num_rounds: self.player.write_math_time() def vars_for_template(self): p = self.player label = Constants.addition_task[self.round_number-1][0] if p.production_profile == 'Linear': pwd_label = Constants.linear_task[self.round_number-1][0] elif p.production_profile == 'Convex': pwd_label = Constants.convex_task[self.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): def is_displayed(self): return ((self.player.treatment == 'Control') | ((self.player.treatment == 'WTP') & (self.player.rel_decision == 0))) def js_vars(self): p = self.player if p.production_profile == 'Linear': pwd_sol = Constants.linear_task[self.round_number-1][0] elif p.production_profile == 'Convex': pwd_sol = Constants.convex_task[self.round_number-1][0] math_sol = Constants.addition_task[self.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): def is_displayed(self): return ((self.player.treatment == 'Reminder') | ((self.player.treatment == 'WTP') & (self.player.rel_decision == 1))) def js_vars(self): p = self.player if p.production_profile == 'Linear': pwd_sol = Constants.linear_task[self.round_number-1][0] elif p.production_profile == 'Convex': pwd_sol = Constants.convex_task[self.round_number-1][0] math_sol = Constants.addition_task[self.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(self.participant.vars['switch_point']), opt_pwd_time=opt_pwd_time ) class Exit_Q1(Page): def is_displayed(self): return self.round_number == Constants.num_rounds def js_vars(self): 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): def is_displayed(self): return self.round_number == Constants.num_rounds def js_vars(self): return dict( page_name='Exit_Q2', ) form_model = 'player' form_fields = ['Exit_Q2_warnings', 'Exit_Q2_time_spent', 'q_subjective_optimality_2'] def error_message(self,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, ]