from otree.api import * from . import ret_functions import random doc = """ Introduction and Consent """ class C(BaseConstants): NAME_IN_URL = 'Instructions' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 WORKER_ROLE = 'Worker' CUSTOMER_ROLE = 'Customer' Fixed_wage_high = 200 Fixed_wage_low = 160 Service_charge = 40 Customer_Task_Satisfaction = 6 Conversion_GBP = 1 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): # --- FIX IS HERE --- # Corrected structure for models.BooleanField with custom choices: # 1. 'choices' argument is passed to models.BooleanField # 2. 'widget' argument is passed the RadioSelect class name without parentheses/arguments label = models.StringField(default=str(" ")) # --- Comprehension tracking (Option 2: first wrong flag only) --- q1_first_wrong = models.BooleanField(initial=False) q2a_first_wrong = models.BooleanField(initial=False) q2b_first_wrong = models.BooleanField(initial=False) q3a_first_wrong = models.BooleanField(initial=False) q3b_first_wrong = models.BooleanField(initial=False) q4a_first_wrong = models.BooleanField(initial=False) q4b_first_wrong = models.BooleanField(initial=False) q5_first_wrong = models.BooleanField(initial=False) q6a_first_wrong = models.BooleanField(initial=False) q6b_first_wrong = models.BooleanField(initial=False) q7_first_wrong = models.BooleanField(initial=False) q8a_first_wrong = models.BooleanField(initial=False) q8b_first_wrong = models.BooleanField(initial=False) q9a_first_wrong = models.BooleanField(initial=False) q9b_first_wrong = models.BooleanField(initial=False) q10a_first_wrong = models.BooleanField(initial=False) q10b_first_wrong = models.BooleanField(initial=False) q11a_first_wrong = models.BooleanField(initial=False) q11b_first_wrong = models.BooleanField(initial=False) bots_q1 = models.StringField( choices=[ ['blue', 'Blue line'], ['red', 'Red line'], ['same', 'Neither (they are the same size)'] ], label="Which is longer, the blue line or the red line?", widget=widgets.RadioSelect ) # --- MINIMAL CHANGE: Added widget=widgets.RadioSelect to all StringFields --- # This ensures they render as radio buttons, consistent with your original HTML goal. q1 = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="Your pay will depend on the sum of tokens you earn over all rounds.", widget=widgets.RadioSelect # πŸ‘ˆ ADDED for proper rendering ) q2a = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="You will interact in the same Customer for 4 rounds.", widget=widgets.RadioSelect # πŸ‘ˆ ADDED for proper rendering ) q2b = models.StringField( choices=[['True', 'True'], ['False', 'False']], label= f"You will interact in the same Worker for 4 rounds.", widget=widgets.RadioSelect ) q3a = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="Each correctly completed decode represents the effort you put into providing the service and the quality of your service", widget=widgets.RadioSelect # πŸ‘ˆ ADDED for proper rendering ) q3b = models.StringField( choices = [['True', 'True'], ['False', 'False']], label = "Each correctly completed decode represents the effort the Worker puts into providing the service and the quality of their service. ", widget = widgets.RadioSelect ) q4a = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="The Customer’s payoff depends on their level of satisfaction with your service, " "which increases with the number of decodes you correctly complete.", widget=widgets.RadioSelect ) q4b = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="Your payoff depends on your level of satisfaction with the Worker’s service, " "which increases with the number of decodes the Worker correctly completes.", widget=widgets.RadioSelect ) q5 = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="Your fixed wage is higher than the standard wage paid at similar restaurants. ", widget=widgets.RadioSelect ) q6a = models.StringField( choices=[['True', 'True'], ['False', 'False']], widget=widgets.RadioSelect ) q6b = models.StringField( choices=[['True', 'True'], ['False', 'False']], widget=widgets.RadioSelect ) q7 = models.StringField( choices=[ ('a', 'Only the restaurant'), ('b', 'Only the Customer'), ('c', 'Both the restaurant and the Customer') ], label="Your compensation will be paid by:", widget=widgets.RadioSelect ) q8a = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="The customer can decide how much to pay you for providing the service.", widget=widgets.RadioSelect # πŸ‘ˆ ADDED for proper rendering ) q8b = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="You can decide how much to pay the Worker for providing the service.", widget=widgets.RadioSelect # πŸ‘ˆ ADDED for proper rendering ) q9a = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="When does the Customer tip you for providing the service?", widget=widgets.RadioSelect # πŸ‘ˆ ADDED for proper rendering ) q9b = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="When do you tip the Worker for providing the service?", widget=widgets.RadioSelect ) q10a = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="In each round, the Customer will be informed of the number of decodes you completed.", widget=widgets.RadioSelect ) q10b = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="In each round, you will be informed of the number of decodes the Worker completed.", widget=widgets.RadioSelect ) q11a = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="In each round, you will be informed of:", widget=widgets.RadioSelect # πŸ‘ˆ ADDED for proper rendering ) q11b = models.StringField( choices=[['True', 'True'], ['False', 'False']], label="In each round, the Worker will be informed of:", widget=widgets.RadioSelect # πŸ‘ˆ ADDED for proper rendering ) def role(self): return self.participant.vars.get('role') # ************************************************************* DISPLAYED ONLY TO WORKERS************************************************************** class Screen5_WorkerCompensation(Page): def is_displayed(self): return self.participant.vars.get('role') == 'Worker' def vars_for_template(self): compensation_type = self.session.config['compensation_type'] tipping_norms = self.session.config.get('tipping_norms', None) return dict( compensation_type=compensation_type, tipping_norms=tipping_norms, fixed_high = C.Fixed_wage_high, fixed_low = C.Fixed_wage_low, service_charge = C.Service_charge,) class Screen6_WorkerSatisfaction(Page): def is_displayed(self): return self.participant.vars.get('role') == 'Worker' def vars_for_template(player): return dict( compensation_type=player.session.config['compensation_type']) class Screen7_WorkerFeedback(Page): def is_displayed(self): return self.participant.vars.get('role') == 'Worker' def vars_for_template(player): return dict( compensation_type=player.session.config['compensation_type']) #*********************DISPLAYED ONLY TO CUSTOMERS************************************************************ class Screen8_Customer_WorkerTask(Page): def is_displayed(self): return self.participant.vars.get('role') == 'Customer' def vars_for_template(player): # Generate one sample decoding task task = ret_functions.Decoding() return dict( question=task.question, task_dict=task.task_dict, correct_answer=task.correct_answer, total_correct=0, task_time=120 ) class Screen9_Customer_Payoff(Page): def is_displayed(self): return self.participant.vars.get('role') == 'Customer' def vars_for_template(player): return dict( compensation_type=player.session.config['compensation_type'], payoff_factor = C.Customer_Task_Satisfaction) class Screen10_Customer_Compensation(Page): def is_displayed(self): return self.participant.vars.get('role') == 'Customer' def vars_for_template(player): return dict( compensation_type=player.session.config['compensation_type']) class Screen11_Customer_Feedback(Page): def is_displayed(self): return self.participant.vars.get('role') == 'Customer' def vars_for_template(player): return dict( compensation_type=player.session.config['compensation_type']) class Screen_BotsTrap1(Page): form_model = 'player' form_fields = ['bots_q1'] def before_next_page(player, timeout_happened): player.participant.vars['bot_failed_trap1'] = (player.bots_q1 != 'blue') # ============================================================ # SCREEN 12 – COMPREHENSION CHECK QUESTIONS # ============================================================ class Screen12_ComprehensionQ1(Page): form_model = 'player' form_fields = ['q1'] def error_message(player, values): if values['q1'] == 'False': if not player.q1_first_wrong: player.q1_first_wrong = True return ("" "❌ Your selection is incorrect. Your pay is determined by the sum of tokens you earn over ALL ROUNDS." ) class Screen12_ComprehensionQ2a(Page): form_model = 'player' form_fields = ['q2a'] def is_displayed(player): return player.participant.vars.get('role') == C.WORKER_ROLE def error_message(player, values): if values['q2a'] == 'False': if not player.q2a_first_wrong: player.q2a_first_wrong = True return ("" f"❌ Your selection is incorrect. You will be randomly re-paired with a new Customer in each round. " "The Customer you are paired with in a given round will be a different person from the previous round.") class Screen12_ComprehensionQ2b(Page): form_model = 'player' form_fields = ['q2b'] def is_displayed(player): return player.participant.vars.get('role') == C.CUSTOMER_ROLE def error_message(player, values): if values['q2b'] == 'False': if not player.q2b_first_wrong: player.q2b_first_wrong = True return ("" f"❌ Your selection is incorrect. You will be randomly re-paired with a new Worker in each round. " "The Worker you are paired with in a given round will be a different person from the previous round.") class Screen12_ComprehensionQ3a(Page): form_model = 'player' form_fields = ['q3a'] def is_displayed(player): return player.participant.vars.get('role') == C.WORKER_ROLE def error_message(player, values): if values['q3a'] == 'False': if not player.q3a_first_wrong: player.q3a_first_wrong = True return ("" "❌ Your selection is incorrect. The number of correctly completed decodes represents the effort you put into providing the service and the quality of your service. " "The more decodes you correctly complete during the 2-minute round, the higher the service quality.") class Screen12_ComprehensionQ3b(Page): form_model = 'player' form_fields = ['q3b'] def is_displayed(player): return player.participant.vars.get('role') == C.CUSTOMER_ROLE def error_message(player, values): if values['q3b'] == 'False': if not player.q3b_first_wrong: player.q3b_first_wrong = True return ("" "❌ Your selection is incorrect. The number of correctly completed decodes represents the effort the Worker " "puts into providing the service and the quality of their service. The more decodes a Worker correctly " "completes during the 2-minute round, the higher the service quality. ") class Screen12_ComprehensionQ4a(Page): form_model = 'player' form_fields = ['q4a'] def is_displayed(player): return player.participant.vars.get('role') == C.WORKER_ROLE def error_message(player, values): if values['q4a'] == 'False': if not player.q4a_first_wrong: player.q4a_first_wrong = True return ("" "❌ Your selection is incorrect. The Customer’s payoff depends on their level of satisfaction with your service, " "which increases with the number of decodes you correctly complete. The more decodes you complete, the higher the" " service quality, the greater the customer satisfaction, and the higher customer payoff.") class Screen12_ComprehensionQ4b(Page): form_model = 'player' form_fields = ['q4b'] def is_displayed(player): return player.participant.vars.get('role') == C.CUSTOMER_ROLE def error_message(player, values): if values['q4b'] == 'False': if not player.q4b_first_wrong: player.q4b_first_wrong = True return ("" "❌ Your selection is incorrect. Your payoff depends on your level of satisfaction with the Worker’s service," " which increases with the number of decodes the Worker correctly completes. The more decodes the Worker" " completes, the higher the service quality, the greater your satisfaction, and the higher your payoff. ") class Screen12_ComprehensionQ5(Page): form_model = 'player' form_fields = ['q5'] def vars_for_template(self): compensation_type = self.session.config['compensation_type'] return dict( compensation_type=compensation_type, ) def is_displayed(player): return player.participant.vars.get('role') == C.WORKER_ROLE def error_message(player, values): comp = player.session.config['compensation_type'] ans = values['q5'] if comp == 'Fixed_wage' and ans == 'False': if not player.q5_first_wrong: player.q5_first_wrong = True return ("" "❌ Your selection is incorrect. Your fixed wage is HIGHER than the standard wage paid at similar restaurants.") elif comp != 'Fixed_wage' and ans == 'False': if not player.q5_first_wrong: player.q5_first_wrong = True return ("" "❌ Your selection is incorrect. Your fixed wage is the STANDARD wage paid at similar restaurants. ") class Screen12_ComprehensionQ6a(Page): form_model = 'player' form_fields = ['q6a'] def vars_for_template(self): compensation_type = self.session.config['compensation_type'] return dict( compensation_type=compensation_type, ) def is_displayed(player): return player.participant.vars.get('role') == C.WORKER_ROLE def error_message(player, values): comp = player.session.config['compensation_type'] ans = values['q6a'] if comp == 'Fixed_wage' and ans == 'False': if not player.q6a_first_wrong: player.q6a_first_wrong = True return ("" "❌ Your selection is incorrect. The Customer does NOT pay you additional compensation for providing the service.") elif comp != 'Fixed_wage' and ans == 'False': if not player.q6a_first_wrong: player.q6a_first_wrong = True return ("" "❌ Your selection is incorrect. The Customer pays you ADDITIONAL compensation for providing the service. ") class Screen12_ComprehensionQ6b(Page): form_model = 'player' form_fields = ['q6b'] def vars_for_template(self): compensation_type = self.session.config['compensation_type'] return dict( compensation_type=compensation_type, ) def is_displayed(player): return player.participant.vars.get('role') == C.CUSTOMER_ROLE def error_message(player, values): comp = player.session.config['compensation_type'] ans = values['q6b'] if comp == 'Fixed_wage' and ans == 'False': if not player.q6b_first_wrong: player.q6b_first_wrong = True return ("" "❌ Your selection is incorrect. You do NOT pay the Worker additional compensation for providing the service." ) elif comp != 'Fixed_wage' and ans == 'False': if not player.q6b_first_wrong: player.q6b_first_wrong = True return ("" "❌ Your selection is incorrect. You pay the Worker ADDITIONAL compensation for providing the service." ) class Screen12_ComprehensionQ7(Page): form_model = 'player' form_fields = ['q7'] def is_displayed(player): return player.participant.vars.get('role') == C.WORKER_ROLE def error_message(player, values): comp = player.session.config['compensation_type'] ans = values['q7'] if comp == 'Fixed_wage' and ans != 'a': if not player.q7_first_wrong: player.q7_first_wrong = True return ("" "❌ Your selection is incorrect. Your compensation is paid by the RESTAURANT.") elif comp != 'Fixed_wage' and ans != 'c': if not player.q7_first_wrong: player.q7_first_wrong = True return ("" "❌ Your selection is incorrect. Your compensation from serving the Customer is paid by BOTH the RESTAURANT and the CUSTOMER.") class Screen12_ComprehensionQ8a(Page): form_model = 'player' form_fields = ['q8a'] def vars_for_template(self): compensation_type = self.session.config['compensation_type'] return dict( compensation_type=compensation_type, ) def is_displayed(player): # Get the role from participant vars is_worker = player.participant.vars.get('role') == C.WORKER_ROLE # Get the compensation type from session config compensation_type = player.session.config.get('compensation_type') is_valid_comp = compensation_type in ['Service_charge', 'Pre_tip', 'Post_tip'] # Return True only if both conditions are met return is_worker and is_valid_comp def error_message(player, values): comp = player.session.config['compensation_type'] ans = values['q8a'] if comp == 'Service_charge' and ans == 'False': if not player.q8a_first_wrong: player.q8a_first_wrong = True return ("" "❌ Your selection is incorrect. The Customer pays a FIXED service charge amount to you in each round. The Customer CANNOT decide how much to pay you for providing the service.") elif comp in ['Pre_tip', 'Post_tip'] and ans == 'False': if not player.q8a_first_wrong: player.q8a_first_wrong = True return ("" "❌ Your selection is incorrect. The Customer chooses a tip amount to pay you in each round. The Customer CAN decide how much to tip you for providing the service. ") class Screen12_ComprehensionQ8b(Page): form_model = 'player' form_fields = ['q8b'] def vars_for_template(self): compensation_type = self.session.config['compensation_type'] return dict( compensation_type=compensation_type, ) def is_displayed(player): # Get the role from participant vars is_customer = player.participant.vars.get('role') == C.CUSTOMER_ROLE # Get the compensation type from session config compensation_type = player.session.config.get('compensation_type') is_valid_comp = compensation_type in ['Service_charge', 'Pre_tip', 'Post_tip'] # Return True only if both conditions are met return is_customer and is_valid_comp def error_message(player, values): comp = player.session.config['compensation_type'] ans = values['q8b'] if comp == 'Service_charge' and ans == 'False': if not player.q8b_first_wrong: player.q8b_first_wrong = True return ("" "❌ Your selection is incorrect. You pay a FIXED service charge amount to the Worker in each round. You CANNOT decide how much to pay the Worker for providing the service.") elif comp in ['Pre_tip', 'Post_tip'] and ans == 'False': if not player.q8b_first_wrong: player.q8b_first_wrong = True return ("" "❌ Your selection is incorrect. You choose a tip amount to pay the Worker in each round. You CAN decide how much to tip the Worker for providing the service. ") class Screen12_ComprehensionQ9a(Page): form_model = 'player' form_fields = ['q9a'] def vars_for_template(self): compensation_type = self.session.config['compensation_type'] return dict( compensation_type=compensation_type, ) def is_displayed(player): is_worker = player.participant.vars.get('role') == C.WORKER_ROLE comp = player.session.config['compensation_type'] is_valid_comp = comp in ['Pre_tip', 'Post_tip'] return is_worker and is_valid_comp def error_message(player, values): comp = player.session.config['compensation_type'] ans = values['q9a'] if comp == 'Pre_tip' and ans == 'False': if not player.q9a_first_wrong: player.q9a_first_wrong = True return ("" "❌ Your selection is incorrect. The Customer decides whether to tip you BEFORE you provide the service.") elif comp == 'Post_tip' and ans == 'False': if not player.q9a_first_wrong: player.q9a_first_wrong = True return ("" "❌ Your selection is incorrect. The Customer decides whether to tip you AFTER you provide the service.") class Screen12_ComprehensionQ9b(Page): form_model = 'player' form_fields = ['q9b'] def vars_for_template(self): compensation_type = self.session.config['compensation_type'] return dict( compensation_type=compensation_type, ) def is_displayed(player): is_customer = player.participant.vars.get('role') == C.CUSTOMER_ROLE comp = player.session.config['compensation_type'] is_valid_comp = comp in ['Pre_tip', 'Post_tip'] return is_customer and is_valid_comp def error_message(player, values): comp = player.session.config['compensation_type'] ans = values['q9b'] if comp == 'Pre_tip' and ans == 'False': if not player.q9b_first_wrong: player.q9b_first_wrong = True return ("" "❌ Your selection is incorrect. You decide whether to tip the Worker BEFORE the Worker provides the service.") elif comp == 'Post_tip' and ans == 'False': if not player.q9b_first_wrong: player.q9b_first_wrong = True return ("" "❌ Your selection is incorrect. You decide whether to tip the Worker AFTER the Worker provides the service.") class Screen12_ComprehensionQ10a(Page): form_model = 'player' form_fields = ['q10a'] def is_displayed(player): is_worker = player.participant.vars.get('role') == C.WORKER_ROLE return is_worker def error_message(player, values): if values['q10a'] != 'True': if not player.q10a_first_wrong: player.q10a_first_wrong = True return ("" "❌ Your selection is incorrect. The Customer will only see the number of decodes you completed.") class Screen12_ComprehensionQ10b(Page): form_model = 'player' form_fields = ['q10b'] def is_displayed(player): is_customer = player.participant.vars.get('role') == C.CUSTOMER_ROLE return is_customer def error_message(player, values): if values['q10b'] != 'True': if not player.q10b_first_wrong: player.q10b_first_wrong = True return ("" "❌ Your selection is incorrect. You will only see the number of decodes the Worker completed.") class Screen12_ComprehensionQ11a(Page): form_model = 'player' form_fields = ['q11a'] def is_displayed(player): is_worker = player.participant.vars.get('role') == C.WORKER_ROLE comp = player.session.config['compensation_type'] is_valid_comp = comp in ['Pre_tip', 'Post_tip'] return is_worker and is_valid_comp def error_message(player, values): if values['q11a'] != 'True': if not player.q11a_first_wrong: player.q11a_first_wrong = True return ("" "❌ Your selection is incorrect. You will only receive feedback about how much the Customer tips you in each round. " ) class Screen12_ComprehensionQ11b(Page): form_model = 'player' form_fields = ['q11b'] def is_displayed(player): is_customer = player.participant.vars.get('role') == C.CUSTOMER_ROLE comp = player.session.config['compensation_type'] is_valid_comp = comp in ['Pre_tip', 'Post_tip'] return is_customer and is_valid_comp def error_message(player, values): if values['q11b'] != 'True': if not player.q11b_first_wrong: player.q11b_first_wrong = True return ("" "❌ Your selection is incorrect. The Worker will only receive feedback about how much you tip them in each round. ") class Screen13_ComprehensionEnd(WaitPage): """Waits for ALL participants in the entire session to finish the comprehension questions.""" wait_for_all_groups = True # βœ… ensures synchronization across ALL treatments title_text = "Waiting for All Participants" body_text = ( "βœ… You have completed the comprehension check. " "Please wait while all participants finish reading and answering their questions. " "Once everyone has finished, the study will automatically continue." ) def after_all_players_arrive(subsession): # Optional: record that everyone has passed comprehension check for p in subsession.get_players(): p.participant.vars['passed_comprehension'] = True page_sequence = [Screen5_WorkerCompensation, Screen6_WorkerSatisfaction, Screen7_WorkerFeedback, Screen8_Customer_WorkerTask, Screen9_Customer_Payoff, Screen10_Customer_Compensation, Screen11_Customer_Feedback, Screen_BotsTrap1, Screen12_ComprehensionQ1, Screen12_ComprehensionQ2a, Screen12_ComprehensionQ2b, Screen12_ComprehensionQ3a, Screen12_ComprehensionQ3b, Screen12_ComprehensionQ4a, Screen12_ComprehensionQ4b, Screen12_ComprehensionQ5, Screen12_ComprehensionQ6a,Screen12_ComprehensionQ6b, Screen12_ComprehensionQ8a, Screen12_ComprehensionQ8b, Screen12_ComprehensionQ9a, Screen12_ComprehensionQ9b, Screen12_ComprehensionQ10a, Screen12_ComprehensionQ10b, Screen12_ComprehensionQ11a, Screen12_ComprehensionQ11b ]