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
]