import random from otree.api import * doc = """ Part 1: Logic quiz for the real experiment. Ranking is determined by comparing the participant's score to 3 pilot benchmark scores from the same treatment. """ class C(BaseConstants): NAME_IN_URL = 'iq_quiz' PLAYERS_PER_GROUP = None NUM_ROUNDS = 10 QUESTION_TIME_LIMIT = 20 PART1_PRIZE = cu(1000) EASY_ITEMS = [ dict(item_id=1, question='easy_q1.png', ans='easy_a1.png', correct='A'), dict(item_id=2, question='easy_q2.png', ans='easy_a2.png', correct='B'), dict(item_id=3, question='easy_q3.png', ans='easy_a3.png', correct='D'), dict(item_id=4, question='easy_q4.png', ans='easy_a4.png', correct='C'), dict(item_id=5, question='easy_q5.png', ans='easy_a5.png', correct='C'), dict(item_id=6, question='easy_q6.png', ans='easy_a6.png', correct='A'), dict(item_id=7, question='easy_q7.png', ans='easy_a7.png', correct='A'), dict(item_id=8, question='easy_q8.png', ans='easy_a8.png', correct='D'), dict(item_id=9, question='easy_q9.png', ans='easy_a9.png', correct='A'), dict(item_id=10, question='easy_q10.png', ans='easy_a10.png', correct='D'), ] HARD_ITEMS = [ dict(item_id=1, question='hard_q1.png', ans='hard_a1.png', correct='A'), dict(item_id=2, question='hard_q2.png', ans='hard_a2.png', correct='B'), dict(item_id=3, question='hard_q3.png', ans='hard_a3.png', correct='D'), dict(item_id=4, question='hard_q4.png', ans='hard_a4.png', correct='C'), dict(item_id=5, question='hard_q5.png', ans='hard_a5.png', correct='A'), dict(item_id=6, question='hard_q6.png', ans='hard_a6.png', correct='A'), dict(item_id=7, question='hard_q7.png', ans='hard_a7.png', correct='B'), dict(item_id=8, question='hard_q8.png', ans='hard_a8.png', correct='D'), dict(item_id=9, question='hard_q9.png', ans='hard_a9.png', correct='C'), dict(item_id=10, question='hard_q10.png', ans='hard_a10.png', correct='C'), ] PILOT_EASY_SCORES = [ 9, 9, 7, 4, 7, 7, 10, 6, 10, 6, 9, 8, 2, 6, 7, 10, 10, 10, 6, 5, 9 ] PILOT_HARD_SCORES = [ 4, 6, 8, 6, 2, 5, 5, 3, 5, 2, 5, 5, 5, 5, 3, 7, 3, 6, 6, 4, 7, 4 ] class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): treatment = models.StringField() item_id = models.IntegerField() image_path = models.StringField() image_choice = models.StringField() correct_answer = models.StringField() answer = models.StringField( choices=['A', 'B', 'C', 'D'], blank=True, widget=widgets.RadioSelect, ) selected_option = models.StringField(blank=True) is_correct = models.BooleanField(initial=False) timed_out = models.BooleanField(initial=False) initial_top_belief = models.IntegerField(min=0, max=100, blank=True) # risk_preference = models.StringField( # choices=[ # ['A', 'Option A'], # ['B', 'Option B'], # ['C', 'Indifferent between the two options'] # ], # widget=widgets.RadioSelect, # blank=True, # ) # # risk_CE = models.IntegerField(min=0, max=10, blank=True) # # ambiguity_preference = models.StringField( # choices=[ # ['A', 'Option A'], # ['B', 'Option B'], # ['C', 'Indifferent between the two options'] # ], # widget=widgets.RadioSelect, # blank=True, # ) # # ambiguity_prob = models.IntegerField(min=0, max=100, blank=True) def bsr_payment(prob_percent, event_happened): q = prob_percent / 100 if event_happened: win_prob = 1 - (1 - q) ** 2 else: win_prob = 1 - q ** 2 return 1000 if random.random() < win_prob else 0 def assign_rank_from_pilot(subject_score, pilot_scores): comparison_scores = random.sample(pilot_scores, 3) entries = [ dict(type='subject', score=subject_score), dict(type='pilot', score=comparison_scores[0]), dict(type='pilot', score=comparison_scores[1]), dict(type='pilot', score=comparison_scores[2]), ] score_groups = {} for entry in entries: score_groups.setdefault(entry['score'], []).append(entry) sorted_scores = sorted(score_groups.keys(), reverse=True) ordered_entries = [] for score in sorted_scores: tied_entries = score_groups[score] random.shuffle(tied_entries) ordered_entries.extend(tied_entries) subject_position = None for idx, entry in enumerate(ordered_entries): if entry['type'] == 'subject': subject_position = idx break rank_type = 'top' if subject_position < 2 else 'bottom' return comparison_scores, rank_type def creating_session(subsession): import itertools players = subsession.get_players() if subsession.round_number == 1: treatments = itertools.cycle(['Easy', 'Hard']) for player in players: player.treatment = next(treatments) player.participant.treatment = player.treatment if player.treatment == 'Easy': player.participant.quiz_items = [item.copy() for item in C.EASY_ITEMS] else: player.participant.quiz_items = [item.copy() for item in C.HARD_ITEMS] else: for player in players: player.treatment = player.in_round(1).treatment for player in players: item = player.participant.quiz_items[subsession.round_number - 1] player.item_id = item['item_id'] player.image_path = item['question'] player.image_choice = item['ans'] player.correct_answer = item['correct'] class Welcome(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class QuizIntro(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class QuizPractice(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Quiz(Page): form_model = 'player' form_fields = ['answer'] timeout_seconds = C.QUESTION_TIME_LIMIT @staticmethod def vars_for_template(player: Player): return dict( round_num=player.round_number, total_rounds=C.NUM_ROUNDS, image_path=player.image_path, image_choice=player.image_choice, timer_seconds=C.QUESTION_TIME_LIMIT, ) @staticmethod def error_message(player: Player, values): if values.get('answer') in [None, '']: return 'Please select A, B, C, or D before continuing.' @staticmethod def before_next_page(player: Player, timeout_happened): answer = player.field_maybe_none('answer') if answer is None: player.selected_option = 'NA' player.is_correct = False if timeout_happened: player.timed_out = True else: player.selected_option = answer player.is_correct = (answer == player.correct_answer) setattr(player.participant, f'q{player.round_number}_answer', player.selected_option) class QuizResults(Page): form_model = 'player' form_fields = ['initial_top_belief'] @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS @staticmethod def error_message(player: Player, values): if values.get('initial_top_belief') is None: return 'Please report your belief before continuing.' @staticmethod def before_next_page(player: Player, timeout_happened): player.participant.initial_top_belief = player.initial_top_belief event_happened = (player.participant.rank_type == 'top') belief_points = bsr_payment(player.initial_top_belief, event_happened) player.participant.initial_belief_event = event_happened player.participant.initial_belief_payment_points = belief_points @staticmethod def vars_for_template(player: Player): rounds = player.in_all_rounds() total_correct = sum(p.is_correct for p in rounds) player.participant.quiz_score = total_correct chosen_round = random.choice(rounds) quiz_correct = chosen_round.is_correct quiz_points = 1000 if quiz_correct else 0 player.participant.quiz_payment_question = chosen_round.round_number player.participant.quiz_payment_correct = quiz_correct player.participant.quiz_payment_points = quiz_points if player.treatment == 'Easy': comparison_scores, rank_type = assign_rank_from_pilot( total_correct, C.PILOT_EASY_SCORES ) else: comparison_scores, rank_type = assign_rank_from_pilot( total_correct, C.PILOT_HARD_SCORES ) player.participant.pilot_comparison_scores = comparison_scores player.participant.rank_type = rank_type return dict( total_correct=total_correct, total_questions=C.NUM_ROUNDS, payment_question=chosen_round.round_number, payment_correct=quiz_correct, payment_points=quiz_points, ) page_sequence = [ Welcome, QuizIntro, QuizPractice, Quiz, QuizResults, ]