from otree.api import * c = cu doc = 'to measure ambiguity attitude between human and AI\n' class C(BaseConstants): NAME_IN_URL = 'ambiguity_task_1' PLAYERS_PER_GROUP = None NUM_ROUNDS = 16 NUM_CHOICES = 20 FROM_AI = 'algorithmic' FROM_HUMAN = 'human' AMBIGUITY_PERCENTAGES_SINGLE = ( 0, 0.01, 0.02, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.85, 1) NUM_BLOCKS = 4 AMBIGUITY_PERCENTAGES_COMPOSITE = ( 0, 0.2, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.93, 0.95, 0.97, 0.98, 0.99, 1) ANALYST_TYPES = ('AI', 'H') EVENT_TYPES = ('s', 'c') SINGLE_PROB_LOWER = (0, 40, 80.01) SINGLE_PROB_HIGHER = (39.99, 80, 100) SINGLE_PROB_TEXT = ('more than 0% and strictly less than 40%', 'more than 40% and less than 80%', 'strictly more than 80% and less than 100%') COMPOSITE_PROB_LOWER = (0, 80.01, 0, 40) COMPOSITE_PROB_HIGHER = (39.99, 100, 80, 100) COMPOSITE_PROB_TEXT = ('more than 0% and strictly less than 40% or strictly more than 80% and less than 100%', 'more than 0% and less than 80%', 'more than 40% and less than 100%') BREAK_QUESTION_TITLES = ('Survey: gender', 'Survey: age', 'Survey: major', 'Survey: education') NUM_EVENTS_PER_BLOCK = 4 BLOCK_1 = ('AI', 'c') BLOCK_2 = ('AI', 's') BLOCK_3 = ('H', 'c') BLOCK_4 = ('H', 's') EVENTS_PER_TYPE = 3 EVENTS_NUM_PER_TYPE = (1, 2, 3) OPTION_A_BONUS = 10 OPTION_B_BONUS = 10 INSTRUCTION_TEMPLATE = 'ambiguity_task_1/instruction.html' class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): session = subsession.session from math import ceil from random import shuffle, sample, choice, randint players = subsession.get_players() round_number = subsession.round_number if round_number == 1: ps_choice_first = [p for p in players if p.participant.first_task == 'choice'] ps_ambiguity_first = [p for p in players if p.participant.first_task == 'ambiguity'] group_matrix = [ps_choice_first, ps_ambiguity_first] for s in subsession.in_rounds(1, C.NUM_ROUNDS): s.set_group_matrix(group_matrix) from itertools import cycle conditions = [ (C.BLOCK_1, C.BLOCK_2, C.BLOCK_3, C.BLOCK_4), (C.BLOCK_1, C.BLOCK_2, C.BLOCK_4, C.BLOCK_3), (C.BLOCK_2, C.BLOCK_1, C.BLOCK_3, C.BLOCK_4), (C.BLOCK_2, C.BLOCK_1, C.BLOCK_4, C.BLOCK_3), (C.BLOCK_4, C.BLOCK_3, C.BLOCK_2, C.BLOCK_1), (C.BLOCK_3, C.BLOCK_4, C.BLOCK_2, C.BLOCK_1), (C.BLOCK_4, C.BLOCK_3, C.BLOCK_1, C.BLOCK_2), (C.BLOCK_3, C.BLOCK_4, C.BLOCK_1, C.BLOCK_2), ] conditions = cycle(conditions) shuffle(players) for p in players: participant = p.participant participant.vars['condition'] = next(conditions) participant.vars['events_per_block'] = [] participant.ambiguity_round_to_pay = randint(1, C.NUM_ROUNDS) participant.ambiguity_decision_to_pay = randint(1, C.NUM_CHOICES) participant.ambiguity_draw = randint(0, 99) ps_ambiguity_first = [p for p in players if p.participant.first_task == 'ambiguity'] for p in ps_ambiguity_first: block_num = ceil(round_number / C.NUM_EVENTS_PER_BLOCK) event_num = round_number - (block_num - 1) * C.NUM_EVENTS_PER_BLOCK condition = p.participant.vars['condition'][block_num - 1] p.block_num = block_num p.event_num = event_num p.analyst_type = condition[0] p.event_type = condition[1] if round_number % C.NUM_EVENTS_PER_BLOCK == 1: events = sample(C.EVENTS_NUM_PER_TYPE, k=len(C.EVENTS_NUM_PER_TYPE)) events.append(choice(C.EVENTS_NUM_PER_TYPE)) p.participant.vars['events_per_block'].append(events) p.event_type_num = p.participant.vars['events_per_block'][block_num - 1][event_num - 1] class Group(BaseGroup): pass class Player(BasePlayer): q1 = models.StringField(choices=[['€10', '€10'], ['€0', '€0'], ['I do not know', 'I do not know']], widget=widgets.RadioSelect) q2 = models.StringField(choices=[['€10', '€10'], ['€0', '€0'], ['I do not know', 'I do not know']], widget=widgets.RadioSelect) num_errors = models.IntegerField(initial=0) block_num = models.IntegerField(max=C.NUM_BLOCKS, min=1) event_num = models.IntegerField(max=C.NUM_EVENTS_PER_BLOCK, min=1) analyst_type = models.StringField(choices=C.ANALYST_TYPES) event_type = models.StringField(choices=C.EVENT_TYPES) event_type_num = models.IntegerField(max=C.EVENTS_PER_TYPE, min=1) ambiguity_switch = models.IntegerField(max=C.NUM_CHOICES + 1, min=1) break_1 = models.IntegerField(choices=[[0, 'Female'], [1, 'Male'], [2, 'Non-binary']], label='Please indicate your gender:', widget=widgets.RadioSelect) break_2 = models.IntegerField(label='How old are you, in years? (Please enter a number)', min=18, max=80) break_3 = models.StringField( label='What is your major at the university? (Please list all of them if you have more than one major)') break_4 = models.IntegerField( choices=[[0, 'Some high school'], [1, 'High school diploma or equivalent'], [2, 'Some college'], [3, 'Trade/vocational training'], [4, "Associate's degree"], [5, "Bachelor's degree"], [6, "Master's degree"], [7, 'Doctorate degree'], [8, 'Professional degree'], [9, 'Prefer not to say']], label='What is the highest degree or level of school you have completed? If currently enrolled, highest degree received.', widget=widgets.RadioSelect) def set_ambiguity_payoff(player: Player): participant = player.participant if participant.task_to_pay == 'ambiguity': player_to_pay = player.in_round(participant.ambiguity_round_to_pay) analyst_type = player_to_pay.analyst_type event_type = player_to_pay.event_type event_type_num = player_to_pay.event_type_num switch = player_to_pay.ambiguity_switch participant.vars['ambiguity_player_to_pay'] = dict( analyst_type=analyst_type, event_type=event_type, event_type_num=event_type_num, switch=switch, ) if participant.ambiguity_decision_to_pay >= switch: prob = C.AMBIGUITY_PERCENTAGES_SINGLE if event_type == 's' else C.AMBIGUITY_PERCENTAGES_COMPOSITE participant.payoff = C.OPTION_B_BONUS * ( prob[participant.ambiguity_decision_to_pay - 1] * 100 > participant.ambiguity_draw) else: records = participant.vars['records'] if analyst_type == 'H': accuracy = sum([1 if r[-3] == 'TRUE' else 0 for r in records]) / len(records) else: accuracy = sum([1 if r[-1] == 'TRUE' else 0 for r in records]) / len(records) accuracy = accuracy * 100 if event_type == 's': intervals = list(zip(C.SINGLE_PROB_LOWER, C.SINGLE_PROB_HIGHER)) interval = intervals[event_type_num - 1] print(interval) participant.payoff = C.OPTION_A_BONUS * (interval[0] <= accuracy <= interval[1]) else: intervals = list(zip(C.COMPOSITE_PROB_LOWER, C.COMPOSITE_PROB_HIGHER)) if event_type_num != 1: interval = intervals[event_type_num] print(interval) participant.payoff = C.OPTION_A_BONUS * (interval[0] <= accuracy <= interval[1]) else: intervals = intervals[:2] print(intervals) participant.payoff = C.OPTION_A_BONUS * ( intervals[0][0] <= accuracy <= intervals[0][1] or intervals[1][0] <= accuracy <= intervals[1][1] ) class WaitingTask(WaitPage): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Proceed(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Instructions(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Practice_0(Page): form_model = 'player' form_fields = ['ambiguity_switch'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): participant = player.participant practice_percentages_1 = C.AMBIGUITY_PERCENTAGES_SINGLE return dict( practice_percentages_1=[f'{p:.0%}' for p in practice_percentages_1], graphs=[f'stock/record_id_{n}.png' for n in participant.graphs] ) @staticmethod def js_vars(player: Player): return dict( num_choices=C.NUM_CHOICES ) class Topractice(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Practice_1(Page): form_model = 'player' form_fields = ['ambiguity_switch'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): participant = player.participant practice_percentages_1 = C.AMBIGUITY_PERCENTAGES_SINGLE return dict( practice_percentages_1=[f'{p:.0%}' for p in practice_percentages_1], graphs=[f'stock/record_id_{n}.png' for n in participant.graphs] ) @staticmethod def js_vars(player: Player): return dict( num_choices=C.NUM_CHOICES ) class Practice_2(Page): form_model = 'player' form_fields = ['ambiguity_switch'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): participant = player.participant practice_percentages_2 = C.AMBIGUITY_PERCENTAGES_COMPOSITE return dict( practice_percentages_2=[f'{p:.0%}' for p in practice_percentages_2], graphs=[f'stock/record_id_{n}.png' for n in participant.graphs] ) @staticmethod def js_vars(player: Player): return dict( num_choices=C.NUM_CHOICES ) class Practice_3(Page): form_model = 'player' form_fields = ['ambiguity_switch'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): participant = player.participant practice_percentages_1 = C.AMBIGUITY_PERCENTAGES_SINGLE return dict( practice_percentages_1=[f'{p:.0%}' for p in practice_percentages_1], graphs=[f'stock/record_id_{n}.png' for n in participant.graphs] ) @staticmethod def js_vars(player: Player): return dict( num_choices=C.NUM_CHOICES ) class Practice_4(Page): form_model = 'player' form_fields = ['ambiguity_switch'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): participant = player.participant practice_percentages_2 = C.AMBIGUITY_PERCENTAGES_COMPOSITE return dict( practice_percentages_2=[f'{p:.0%}' for p in practice_percentages_2], graphs=[f'stock/record_id_{n}.png' for n in participant.graphs] ) @staticmethod def js_vars(player: Player): return dict( num_choices=C.NUM_CHOICES ) class Payment(Page): form_model = 'player' form_fields = ['q1', 'q2'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def error_message(player: Player, values): if values["q1"] != "€10" or values["q2"] != "€0": player.num_errors += 1 return "There is at least one mistake in your responses. Please try again." class CorrectAnswer(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Decision(Page): form_model = 'player' form_fields = ['ambiguity_switch'] @staticmethod def vars_for_template(player: Player): participant = player.participant ambiguity_percentages = C.AMBIGUITY_PERCENTAGES_SINGLE if player.event_type == 's' else C.AMBIGUITY_PERCENTAGES_COMPOSITE algorithm_statement = C.FROM_AI if player.analyst_type == 'AI' else C.FROM_HUMAN prob_range = C.SINGLE_PROB_TEXT if player.event_type == 's' else C.COMPOSITE_PROB_TEXT title = player.round_number return dict( title=title, ambiguity_percentages=[f'{p:.0%}' for p in ambiguity_percentages], algorithm_statement=algorithm_statement, prob_range=prob_range[player.event_type_num - 1], event=f'event/{player.event_type}_{player.event_type_num}.png', graphs=[f'stock/record_id_{n}.png' for n in participant.graphs] ) @staticmethod def js_vars(player: Player): return dict( num_choices=C.NUM_CHOICES ) @staticmethod def before_next_page(player: Player, timeout_happened): if player.round_number == C.NUM_ROUNDS: set_ambiguity_payoff(player) class Question(Page): form_model = 'player' @staticmethod def get_form_fields(player: Player): return [f'break_{player.block_num}'] @staticmethod def is_displayed(player: Player): return player.round_number % C.NUM_EVENTS_PER_BLOCK == 0 @staticmethod def vars_for_template(player: Player): return dict( title=C.BREAK_QUESTION_TITLES[player.block_num - 1] ) @staticmethod def app_after_this_page(player: Player, upcoming_apps): if player.round_number == C.NUM_ROUNDS: return 'survey' class Break(Page): form_model = 'player' timeout_seconds = 1 # 90 @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_EVENTS_PER_BLOCK * len(C.EVENT_TYPES) page_sequence = [Instructions, Practice_0, Topractice, Practice_1, Practice_2, Practice_3, Practice_4, Payment, CorrectAnswer, Decision, Question, Break]