from otree.api import * import random class C(BaseConstants): NAME_IN_URL = 'game' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 class Subsession(BaseSubsession): def creating_session(self): # Shuffle and create groups after all players are in the session self.group_randomly() class IntroductionPage(Page): def before_next_page(self, **kwargs): # Initialize session variables self.participant.vars['check_question_answered_correctly'] = False class CheckQuestionPage(Page): form_model = 'player' form_fields = ['check_question'] def is_displayed(self): return not self.participant.vars.get('check_question_answered_correctly', False) def vars_for_template(self): explanation_for_incorrect = { 'a': "Participants can choose between Automated Control, Human Control, or be Indifferent.", 'b': "Participants have a 3-minute window, not 1 minute, to complete as many calculations as they choose.", 'd': "Participants can leave a response blank, which will be treated as incomplete." } check_question_value = self.field_maybe_none('check_question') # Store the explanation in a local variable instead of attaching it to self. incorrect_explanation = explanation_for_incorrect.get(check_question_value, "") return { 'question': "Which of the following statements is true about the 'Automated vs. Human Work Control' experiment based on the introduction you read?", 'option_a': "You will only be assigned tasks under Human Control (HC).", 'option_b': "You have to complete all 25 math calculations within 1 minute.", 'option_c': "Automated Control (AC) involves checking and grading a part of your tasks systematically.", 'option_d': "There's no option to leave a response blank or incomplete.", 'incorrect_explanation': incorrect_explanation, 'attempted': check_question_value is not None # Indicates if the participant has attempted to answer the question. } def before_next_page(self, **kwargs): if self.check_question == 'c': self.participant.vars['check_question_answered_correctly'] = True else: self.participant.vars['check_question_answered_correctly'] = False class Group(BaseGroup): principal_assigned = models.BooleanField(initial=False) threshold = models.IntegerField(min=0, max=100, label="Set your threshold:", blank=True) class Player(BasePlayer): # Existing fields original_choice = models.StringField( choices=['Automated Control', 'Human Control', 'Indifferent'], label="Original choice:" ) control_choice = models.StringField( choices=['Automated Control', 'Human Control', 'Indifferent'], label="Choose your control type:", widget=widgets.RadioSelect ) is_principal = models.BooleanField(initial=False) threshold = models.IntegerField(min=0, max=100, blank=True) incomplete_responses = models.IntegerField(initial=0) check_question = models.StringField( choices=[ ('a', 'Option A'), ('b', 'Option B'), ('c', 'Option C'), ('d', 'Option D') ], label="", widget=widgets.RadioSelect ) check_question_correct = models.BooleanField(initial=False) num1 = models.IntegerField() num2 = models.IntegerField() num3 = models.IntegerField() num4 = models.IntegerField() num5 = models.IntegerField() num6 = models.IntegerField() num7 = models.IntegerField() num8 = models.IntegerField() num9 = models.IntegerField() num10 = models.IntegerField() num11 = models.IntegerField() num12 = models.IntegerField() num13 = models.IntegerField() num14 = models.IntegerField() num15 = models.IntegerField() num16 = models.IntegerField() num17 = models.IntegerField() num18 = models.IntegerField() num19 = models.IntegerField() num20 = models.IntegerField() num21 = models.IntegerField() num22 = models.IntegerField() num23 = models.IntegerField() num24 = models.IntegerField() num25 = models.IntegerField() num26 = models.IntegerField() num27 = models.IntegerField() num28 = models.IntegerField() num29 = models.IntegerField() num30 = models.IntegerField() num31 = models.IntegerField() num32 = models.IntegerField() num33 = models.IntegerField() num34 = models.IntegerField() num35 = models.IntegerField() num36 = models.IntegerField() num37 = models.IntegerField() num38 = models.IntegerField() num39 = models.IntegerField() num40 = models.IntegerField() num41 = models.IntegerField() num42 = models.IntegerField() num43 = models.IntegerField() num44 = models.IntegerField() num45 = models.IntegerField() num46 = models.IntegerField() num47 = models.IntegerField() num48 = models.IntegerField() num49 = models.IntegerField() num50 = models.IntegerField() response1 = models.IntegerField(min=-999, max=99999, blank=True) response2 = models.IntegerField(min=-999, max=99999, blank=True) response3 = models.IntegerField(min=-999, max=99999, blank=True) response4 = models.IntegerField(min=-999, max=99999, blank=True) response5 = models.IntegerField(min=-999, max=99999, blank=True) response6 = models.IntegerField(min=-999, max=99999, blank=True) response7 = models.IntegerField(min=-999, max=99999, blank=True) response8 = models.IntegerField(min=-999, max=99999, blank=True) response9 = models.IntegerField(min=-999, max=99999, blank=True) response10 = models.IntegerField(min=-999, max=99999, blank=True) response11 = models.IntegerField(min=-999, max=99999, blank=True) response12 = models.IntegerField(min=-999, max=99999, blank=True) response13 = models.IntegerField(min=-999, max=99999, blank=True) response14 = models.IntegerField(min=-999, max=99999, blank=True) response15 = models.IntegerField(min=-999, max=99999, blank=True) response16 = models.IntegerField(min=-999, max=99999, blank=True) response17 = models.IntegerField(min=-999, max=99999, blank=True) response18 = models.IntegerField(min=-999, max=99999, blank=True) response19 = models.IntegerField(min=-999, max=99999, blank=True) response20 = models.IntegerField(min=-999, max=99999, blank=True) response21 = models.IntegerField(min=-999, max=99999, blank=True) response22 = models.IntegerField(min=-999, max=99999, blank=True) response23 = models.IntegerField(min=-999, max=99999, blank=True) response24 = models.IntegerField(min=-999, max=99999, blank=True) response25 = models.IntegerField(min=-999, max=99999, blank=True) correct_responses = models.IntegerField(initial=0) wrong_responses = models.IntegerField(initial=0) checked_responses_info = models.LongStringField() checked_responses = models.StringField() total_correct_responses = models.IntegerField(initial=0) total_incomplete_responses = models.IntegerField(initial=0) final_payoff = models.FloatField() # Survey fields q_trust_HC_1 = models.StringField( choices=[['1', 'Strongly disagree'], ['2', 'Disagree'], ['3', 'Neutral'], ['4', 'Agree'], ['5', 'Strongly agree']], label='Generally speaking, most people can be trusted', widget=widgets.RadioSelectHorizontal ) q_trust_HC_2 = models.StringField( choices=[['1', 'Strongly disagree'], ['2', 'Disagree'], ['3', 'Neutral'], ['4', 'Agree'], ['5', 'Strongly agree']], label='Most people would try to be fair with me', widget=widgets.RadioSelectHorizontal ) q_trust_HC_3 = models.StringField( choices=[['5', 'Strongly disagree'], ['4', 'Disagree'], ['3', 'Neutral'], ['2', 'Agree'], ['1', 'Strongly agree']], label='Most of the time people are mostly just looking out for themselves', widget=widgets.RadioSelectHorizontal ) q_trust_HC_4 = models.StringField( choices=[['5', 'Strongly disagree'], ['4', 'Disagree'], ['3', 'Neutral'], ['2', 'Agree'], ['1', 'Strongly agree']], label='Most people would try to take advantage of me if they got the chance', widget=widgets.RadioSelectHorizontal ) q_trust_AC_1 = models.StringField( choices=[['1', 'Strongly disagree'], ['2', 'Disagree'], ['3', 'Neutral'], ['4', 'Agree'], ['5', 'Strongly agree']], label='I am totally comfortable working with technology.', widget=widgets.RadioSelectHorizontal ) q_trust_AC_2 = models.StringField( choices=[['1', 'Strongly disagree'], ['2', 'Disagree'], ['3', 'Neutral'], ['4', 'Agree'], ['5', 'Strongly agree']], label='I believe that most technologies are effective at what they are designed to do.', widget=widgets.RadioSelectHorizontal ) q_trust_AC_3 = models.StringField( choices=[['1', 'Strongly disagree'], ['2', 'Disagree'], ['3', 'Neutral'], ['4', 'Agree'], ['5', 'Strongly agree']], label='I believe that AI Algorithm would act in my best interest.', widget=widgets.RadioSelectHorizontal ) q_trust_AC_4 = models.StringField( choices=[['1', 'Strongly disagree'], ['2', 'Disagree'], ['3', 'Neutral'], ['4', 'Agree'], ['5', 'Strongly agree']], label='My typical approach is to trust new technologies until they prove to me that I shouldn’t trust them', widget=widgets.RadioSelectHorizontal ) feedback = models.LongStringField( label='Please enter any feedback or concerns you have about the experiment (optional)', blank=True ) #Demographics q_demo_1 = models.StringField( label='What is your age?', widget=widgets.RadioSelect, choices=[['U18', 'Less than 18'],['18-24', '18-24'], ['25-34', '25-34'], ['35-44', '35-44'], ['45-54', '45-54'],['55 or older', '55 or older'], ['NA', 'Prefer not to say']] ) q_demo_2 = models.StringField( label='What is your gender?', choices=[['M', 'Male'], ['F', 'Female'], ['X', 'Other'], ['NA', 'Prefer not to say']], widget=widgets.RadioSelect ) q_demo_3 = models.StringField( choices=[['HS', 'High School or lower'], ['BSC', 'Bachelors Degree'], ['MSC', 'Masters Degree'], ['PHD', 'Ph.D. or higher'], ['FH', 'Trade School'], ['NA', 'Prefer not to say']], label='What is the highest degree or level of education you have completed?', widget=widgets.RadioSelect ) q_demo_4 = models.StringField( label='Do you have any past working experience', choices=[['0','No past working experience'], ['1', 'Less than 1 year'],['1-3', '1-3 years'], ['4-7', '4-7 years'], ['8', '7+ years'], ['NA', 'Prefer not to say']], widget=widgets.RadioSelect ) q_demo_5 = models.StringField( label='Have you ever experienced forms of control in the workplace?', choices=[['HC','Human Control (e.g. Supervisors, Managers, Other colleagues)'], ['AC','Automated Control (e.g. Actions-per-Minute, Video Surveillance)'], ['Both','Both Forms of Control'], ['Never','Never experienced Forms of Control in the workplace'], ['NS','Not Sure'], ['NE','No working experience'], ['NA','Prefer not to say']], widget=widgets.RadioSelect ) q_demo_6 = models.StringField( choices=[['1', 'Strongly disagree'], ['2', 'Disagree'], ['3', 'Neutral'], ['4', 'Agree'], ['5', 'Strongly agree']], label='Overall I agree that work control systems are trustworthy.', widget=widgets.RadioSelectHorizontal ) class ControlChoicePage(Page): form_model = 'player' form_fields = ['control_choice'] def is_displayed(self): return self.participant.vars.get('check_question_answered_correctly', False) def before_next_page(self, **kwargs): self.original_choice = self.control_choice if self.control_choice == 'Indifferent': self.control_choice = random.choice(['Automated Control', 'Human Control']) self.participant.vars['group'] = self.control_choice class MyWaitPage(WaitPage): wait_for_all_groups = True # Ensure all players have made their choice @staticmethod def after_all_players_arrive(subsession: Subsession): # Separate players into AC and HC based on their choice or assignment ac_players = [p for p in subsession.get_players() if p.control_choice == 'Automated Control'] hc_players = [ p for p in subsession.get_players() if p.control_choice == 'Human Control' or (p.control_choice == 'Indifferent' and p.participant.vars.get('group') == 'Human Control') ] # Initialize a new group matrix for HC players new_hc_matrix = [] # Define the sizes of the HC groups group_sizes = [5] * (len(hc_players) // 5) + ([len(hc_players) % 5] if len(hc_players) % 5 else []) current_index = 0 for size in group_sizes: # Get the slice of players for the new HC group group_players = hc_players[current_index:current_index + size] # Append the new group to the HC matrix new_hc_matrix.append(group_players) # Assign a principal randomly within the HC group principal = random.choice(group_players) principal.is_principal = True # Move the index current_index += size # Reintegrate AC players into the group matrix for ac_player in ac_players: new_hc_matrix.append([ac_player]) # Set the new group matrix, ensuring all players are included once subsession.set_group_matrix(new_hc_matrix) class PrincipalThresholdPage(Page): @staticmethod def is_displayed(player: Player): return player.is_principal form_model = 'player' form_fields = ['threshold'] def before_next_page(self, **kwargs): self.original_choice = self.control_choice if self.field_maybe_none('threshold') is None: self.group.threshold = 0 else: self.group.threshold = self.threshold class TaskPage(Page): form_model = 'player' form_fields = ['response1', 'response2', 'response3', 'response4', 'response5', 'response6', 'response7', 'response8', 'response9', 'response10', 'response11','response12', 'response13', 'response14', 'response15', 'response16','response17', 'response18', 'response19', 'response20', 'response21', 'response22', 'response23', 'response24', 'response25'] timeout_seconds = 180 def vars_for_template(self): self.num1 = random.randint(10, 99) self.num2 = random.randint(10, 99) self.num3 = random.randint(10, 99) self.num4 = random.randint(10, 99) self.num5 = random.randint(10, 99) self.num6 = random.randint(10, 99) self.num7 = random.randint(10, 99) self.num8 = random.randint(10, 99) self.num9 = random.randint(10, 99) self.num10 = random.randint(10, 99) self.num11 = random.randint(10, 99) self.num12 = random.randint(10, 99) self.num13 = random.randint(10, 99) self.num14 = random.randint(10, 99) self.num15 = random.randint(10, 99) self.num16 = random.randint(10, 99) self.num17 = random.randint(10, 99) self.num18 = random.randint(10, 99) self.num19 = random.randint(10, 99) self.num20 = random.randint(10, 99) self.num21 = random.randint(10, 99) self.num22 = random.randint(10, 99) self.num23 = random.randint(10, 99) self.num24 = random.randint(10, 99) self.num25 = random.randint(10, 99) self.num26 = random.randint(10, 99) self.num27 = random.randint(10, 99) self.num28 = random.randint(10, 99) self.num29 = random.randint(10, 99) self.num30 = random.randint(10, 99) self.num31 = random.randint(10, 99) self.num32 = random.randint(10, 99) self.num33 = random.randint(10, 99) self.num34 = random.randint(10, 99) self.num35 = random.randint(10, 99) self.num36 = random.randint(10, 99) self.num37 = random.randint(10, 99) self.num38 = random.randint(10, 99) self.num39 = random.randint(10, 99) self.num40 = random.randint(10, 99) self.num41 = random.randint(10, 99) self.num42 = random.randint(10, 99) self.num43 = random.randint(10, 99) self.num44 = random.randint(10, 99) self.num45 = random.randint(10, 99) self.num46 = random.randint(10, 99) self.num47 = random.randint(10, 99) self.num48 = random.randint(10, 99) self.num49 = random.randint(10, 99) self.num50 = random.randint(10, 99) return { 'num1': self.num1, 'num2': self.num2, 'num3': self.num3, 'num4': self.num4, 'num5': self.num5, 'num6': self.num6, 'num7': self.num7, 'num8': self.num8, 'num9': self.num9, 'num10': self.num10, 'num11': self.num11, 'num12': self.num12, 'num13': self.num13, 'num14': self.num14, 'num15': self.num15, 'num16': self.num16, 'num17': self.num17, 'num18': self.num18, 'num19': self.num19, 'num20': self.num20, 'num21': self.num21, 'num22': self.num22, 'num23': self.num23, 'num24': self.num24, 'num25': self.num25, 'num26': self.num26, 'num27': self.num27, 'num28': self.num28, 'num29': self.num29, 'num30': self.num30, 'num31': self.num31, 'num32': self.num32, 'num33': self.num33, 'num34': self.num34, 'num35': self.num35, 'num36': self.num36, 'num37': self.num37, 'num38': self.num38, 'num39': self.num39, 'num40': self.num40, 'num41': self.num41, 'num42': self.num42, 'num43': self.num43, 'num44': self.num44, 'num45': self.num45, 'num46': self.num46, 'num47': self.num47, 'num48': self.num48, 'num49': self.num49, 'num50': self.num50, } def before_next_page(self, **kwargs): responses_to_check = 25 checked_responses_info = [] if self.control_choice == 'Automated Control': responses_to_check = max(1, round(0.3 * 25)) if self.control_choice == 'Human Control': threshold = self.group.field_maybe_none('threshold') # If the threshold is None, set it to 0; otherwise, use the principal's threshold if threshold is None: self.group.threshold = 0 responses_to_check = round((self.group.threshold / 100) * 25) response_keys = ['response1', 'response2', 'response3', 'response4', 'response5', 'response6', 'response7', 'response8', 'response9', 'response10', 'response11','response12', 'response13', 'response14', 'response15', 'response16','response17', 'response18', 'response19', 'response20', 'response21', 'response22', 'response23', 'response24', 'response25'] random.shuffle(response_keys) response_keys_to_check = random.sample(response_keys, responses_to_check) self.checked_responses = ','.join(response_keys_to_check) operations = { 'response1': f"{self.num1} + {self.num2}", 'response2': f"{self.num3} + {self.num4}", 'response3': f"{self.num5} + {self.num6}", 'response4': f"{self.num7} + {self.num8}", 'response5': f"{self.num9} + {self.num10}", 'response6': f"{self.num11} + {self.num12}", 'response7': f"{self.num13} + {self.num14}", 'response8': f"{self.num15} + {self.num16}", 'response9': f"{self.num17} + {self.num18}", 'response10': f"{self.num19} + {self.num20}", 'response11': f"{self.num21} + {self.num22}", 'response12': f"{self.num23} + {self.num24}", 'response13': f"{self.num25} + {self.num26}", 'response14': f"{self.num27} + {self.num28}", 'response15': f"{self.num29} + {self.num30}", 'response16': f"{self.num31} + {self.num32}", 'response17': f"{self.num33} + {self.num34}", 'response18': f"{self.num35} + {self.num36}", 'response19': f"{self.num37} + {self.num38}", 'response20': f"{self.num39} + {self.num40}", 'response21': f"{self.num41} + {self.num42}", 'response22': f"{self.num43} + {self.num44}", 'response23': f"{self.num45} + {self.num46}", 'response24': f"{self.num47} + {self.num48}", 'response25': f"{self.num49} + {self.num50}", } correct_answers = { 'response1': self.num1 + self.num2, 'response2': self.num3 + self.num4, 'response3': self.num5 + self.num6, 'response4': self.num7 + self.num8, 'response5': self.num9 + self.num10, 'response6': self.num11 + self.num12, 'response7': self.num13 + self.num14, 'response8': self.num15 + self.num16, 'response9': self.num17 + self.num18, 'response10': self.num19 + self.num20, 'response11': self.num21 + self.num22, 'response12': self.num23 + self.num24, 'response13': self.num25 + self.num26, 'response14': self.num27 + self.num28, 'response15': self.num29 + self.num30, 'response16': self.num31 + self.num32, 'response17': self.num33 + self.num34, 'response18': self.num35 + self.num36, 'response19': self.num37 + self.num38, 'response20': self.num39 + self.num40, 'response21': self.num41 + self.num42, 'response22': self.num43 + self.num44, 'response23': self.num45 + self.num46, 'response24': self.num47 + self.num48, 'response25': self.num49 + self.num50, } if self.is_principal: self.final_payoff = 3 # Fixed payoff for principals else: # Initial fixed wage for agents in HC and players in AC payoff = 5 for key in response_keys_to_check: operation = operations[key] response_value = self.field_maybe_none(key) if response_value is not None: correct_value = correct_answers[key] if response_value == correct_value: self.correct_responses += 1 else: self.wrong_responses += 1 payoff -= 0.2 else: self.incomplete_responses += 1 payoff -= 0.2 # Ensure payoff doesn't go below 0 self.participant.payoff = max(payoff, 0) self.final_payoff = round(self.participant.payoff, 1) self.total_correct_responses = 0 self.total_incomplete_responses = 0 # Calculate totals for all responses for i in range(1, 26): response_key = f"response{i}" response_value = self.field_maybe_none(response_key) num1_key = f"num{2*i-1}" num2_key = f"num{2*i}" correct_value = getattr(self, num1_key) + getattr(self, num2_key) if response_value is not None: if response_value == correct_value: self.total_correct_responses += 1 else: self.total_incomplete_responses += 1 @staticmethod def is_displayed(player: Player): return player.control_choice == 'Automated Control' or (not player.is_principal and player.control_choice == 'Human Control') class Results(Page): @staticmethod def vars_for_template(player: Player): is_controlled = player.control_choice in ['Automated Control', 'Human Control'] is_principal = player.is_principal # Calculate the number of tasks checked total_tasks = 25 tasks_checked = 0 if player.control_choice == 'Automated Control': tasks_checked = max(1, round(0.3 * total_tasks)) elif player.control_choice == 'Human Control' and player.group.threshold is not None: tasks_checked = round((player.group.threshold / 100) * total_tasks) checked_percentage = (tasks_checked / total_tasks) * 100 # Prepare the data for the table checked_responses_str = player.field_maybe_none('checked_responses') if checked_responses_str: response_keys_to_check = checked_responses_str.split(',') else: response_keys_to_check = [] table_data = [] for response_key in response_keys_to_check: i = int(response_key.replace('response', '')) num1 = player.field_maybe_none('num' + str(2*i-1)) num2 = player.field_maybe_none('num' + str(2*i)) response_value = player.field_maybe_none(response_key) if num1 is not None and num2 is not None: operation = f"{num1} + {num2}" correct_answer = num1 + num2 player_answer = response_value if response_value is not None else "None" if response_value is None: status = "Incomplete" else: status = "Correct" if response_value == correct_answer else "Wrong" table_data.append({ 'response_number': i, 'operation': operation, 'player_answer': player_answer, 'correct_answer': correct_answer, 'status': status }) final_payoff = player.field_maybe_none('final_payoff') if final_payoff is None: final_payoff = player.participant.payoff context = { 'tasks_checked': tasks_checked, 'total_tasks': total_tasks, 'checked_percentage': checked_percentage, 'control_choice': player.control_choice, 'threshold': player.group.threshold if is_principal else None, 'correct_responses': player.correct_responses, 'wrong_responses': player.wrong_responses, 'is_controlled': is_controlled, 'is_principal': is_principal, 'incomplete_responses': player.incomplete_responses, 'checked_percentage': checked_percentage, 'table_data': table_data, # Add the table data to the context 'payoff': player.field_maybe_none('final_payoff'), } return context class Thanks(Page): form_model = 'player' form_fields = ['feedback'] @staticmethod def is_displayed(player: Player): return True # You can set conditions for when this page is displayed def vars_for_template(self): return { 'player_id': self.id_in_group } class SurveyHC(Page): form_model = 'player' form_fields = ['q_trust_HC_1','q_trust_HC_2', 'q_trust_HC_3', 'q_trust_HC_4', ] class SurveyAC(Page): form_model = 'player' form_fields = ['q_trust_AC_1','q_trust_AC_2', 'q_trust_AC_3', 'q_trust_AC_4', ] class Demographics(Page): form_model = 'player' form_fields = ['q_demo_1', 'q_demo_2', 'q_demo_3', 'q_demo_4', 'q_demo_5','q_demo_6'] page_sequence = [IntroductionPage, CheckQuestionPage, CheckQuestionPage, CheckQuestionPage, CheckQuestionPage, CheckQuestionPage, CheckQuestionPage, Demographics, ControlChoicePage, MyWaitPage, PrincipalThresholdPage, TaskPage, Results, SurveyHC, SurveyAC, Thanks]