import json import random from otree.api import * import math doc = """ Your app description """ class Constants(BaseConstants): name_in_url = 'HT_Project' players_per_group = None num_rounds = 10 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): num_correct = models.IntegerField(initial=0) num_incorrect = models.IntegerField(initial=0) attempted = models.IntegerField(initial=0) rank_i = models.IntegerField() rank = models.IntegerField() group_rank = models.IntegerField() name = models.StringField() is_top = models.BooleanField(initial=0) is_promoted = models.BooleanField(initial=0) payment = models.StringField() consent = models.BooleanField() def make_field(label): choices = [[1, "True"], [2, "False"]] return models.IntegerField( choices=choices, label=label, widget=widgets.RadioSelect, ) is_first_attempt = models.BooleanField(default=True) q1 = make_field('1. There are two levels of workers: Level A and Level B workers.') q2 = make_field('2. You will be randomly assigned to the Level A or Level B worker position.') q3 = make_field('3. You will earn 2 Lira for each math problem you solve correctly in the placement period.') q4 = make_field('1. Based on my performance in the Placement Period, I have been assigned the position of a Level B worker') q5 = make_field('2. My task is to solve math problems by subtracting two-digit numbers from two-digit numbers.') q6 = make_field('3. I am allowed to use a calculator.') q7 = make_field('1. Based on my performance in the Placement Period, I have been assigned the position of a Level A worker.') q8 = make_field('2. My task is to solve math problems by subtracting two-digit numbers from three-digit numbers and provide my answer by adjusting a slider to my answer.') q9 = make_field('1. Based on my superior performance in periods 1-3, I have been promoted to the position of a Level A worker.') q10 = make_field('2. My task is to solve math problems subtracting two-digit numbers from three-digit numbers and provide my answer by adjusting a slider to my answer.') q11 = make_field('3. The other two workers in my group were originally assigned Level A worker positions from the Placement Period.') q12 = make_field('4. I will see where my performance ranks among the other Level A workers in my group, but the other Level A workers in my group will not see where my performance ranks among them.') q13 = make_field('1. I will be a part of multiple groups of three workers (including myself).') q14 = make_field('2. Each group will consist of myself, the other worker originally assigned to the Level A worker position from the Placement Period, and one additional newly promoted worker from the Level B position.') q15 = make_field('3. I will see where my performance ranks among the other Level A workers in each of my groups, but the other Level A workers in my groups will not see where my performance ranks among them. ') l1 = models.IntegerField( choices=( [1, "1 - (Poorly)"], [2, "2"], [3, "3"], [4, "4 - (Average)"], [5, "5"], [6, "6"], [7, "7 - (Excellent)"], ), widget=widgets.RadioSelectHorizontal, label="How well do you think that you will do on the math tasks in today’s session?" ) l2 = models.IntegerField( choices=( [1, "1 - (Worse than others)"], [2, "2

"], [3, "3

"], [4, "4 - (About the same as others)"], [5, "5

"], [6, "6

"], [7, "7 - (Better than others)"], ), widget=widgets.RadioSelectHorizontal, label="How well do you think you will do on the math tasks compared to the other participants in the session?" ) def make_field2(label): choices = ( [1, "1 - (Strongly disagree)"], [2, "2

"], [3, "3

"], [4, "4 - (Neither agree nor disagree)"], [5, "5

"], [6, "6

"], [7, "7 - (Strongly agree)"], ) return models.IntegerField( choices=choices, label=label, widget=widgets.RadioSelectHorizontal, ) l3 = make_field2('I am confident that I can perform effectively on many different tasks.') l4 = make_field2('Compared to other people, I can do most tasks very well.') l5 = make_field2('When facing difficult tasks, I am certain that I will accomplish them.') def make_field3(label): choices = ( [1, "1 - (Not at all)"], [2, "2"], [3, "3"], [4, "4 - (Neutral)"], [5, "5"], [6, "6"], [7, "7 - (Very)"], ) return models.IntegerField( choices=choices, label=label, widget=widgets.RadioSelectHorizontal, ) l6 = make_field3("Stressed") l7 = make_field3("Nervous") l8 = make_field3("Calm") l9 = make_field3("Worried") l10 = make_field3("Tense") l11 = make_field3("Relaxed") def make_field4(label): choices = ( [1, "1 - (Not at all interesting)"], [2, "2

"], [3, "3

"], [4, "4 - (Moderately interesting)"], [5, "5

"], [6, "6

"], [7, "7 - (Very interesting)"], ) return models.IntegerField( choices=choices, label=label, widget=widgets.RadioSelectHorizontal, ) l12 = make_field4("How interesting did you find the initial basic math task (2x2 subtraction without slider)?") l13 = make_field4("How interesting did you find the advanced math task (2x3 subtraction with slider)?") l14 = models.IntegerField( choices=( [1, "1 - (Never)"], [2, "2"], [3, "3"], [4, "4 - (Sometimes)"], [5, "5"], [6, "6"], [7, "7 - (Very Often)"], ), widget=widgets.RadioSelectHorizontal, label="How often did you think about how your performance compared to performance of the other two Level A " "workers in your group?" ) l15 = models.IntegerField( choices=( [1, "1 - (Not at all)"], [2, "2"], [3, "3"], [4, "4 - (Neutral)"], [5, "5"], [6, "6"], [7, "7 - (Quite a lot)"], ), widget=widgets.RadioSelectHorizontal, label="I felt my performance was being evaluated or judged by the other two Level A workers in my group." ) l16 = models.IntegerField( choices=( [1, "1 - (Not at all)"], [2, "2"], [3, "3"], [4, "4 - (Neutral)"], [5, "5"], [6, "6"], [7, "7 - (Quite a lot)"], ), widget=widgets.RadioSelectHorizontal, label="I felt distracted while working on the advanced math task." ) l17 = models.IntegerField( choices=( [1, "1 - (No attention)

"], [2, "2

"], [3, "3

"], [4, "4 - (Some attention)"], [5, "5

"], [6, "6

"], [7, "7 - (Complete attention)"], ), widget=widgets.RadioSelectHorizontal, label="To what extent was your attention focused on the advanced math task?" ) l18 = models.IntegerField( choices=( [1, "1 - (Not at all nervous or concerned)"], [2, "2


"], [3, "3


"], [4, "4 - (Somewhat nervous or concerned)"], [5, "5


"], [6, "6


"], [7, "7 - (Very nervous or concerned)

"], ), widget=widgets.RadioSelectHorizontal, label="Were you nervous or concerned about how well you were performing relative to other two Level A " "workers in your group?" ) l19 = models.IntegerField( choices=( [1, "1 - (No at all)"], [2, "2"], [3, "3"], [4, "4 - (Somewhat)"], [5, "5"], [6, "6"], [7, "7 - (Very much)"], ), widget=widgets.RadioSelectHorizontal, label="Did thinking about how your performance compared to the other two Level A workers in your group " "interfere with your ability to concentrate on the problems?" ) l20 = models.IntegerField( choices=( [1, "1 - (Worse than the other two participants)"], [2, "2

"], [3, "3

"], [4, "4 - (About the same as other two participants)"], [5, "5

"], [6, "6

"], [7, "7 - (Better than the other two participants)"], ), widget=widgets.RadioSelectHorizontal, label="How well do you think you will do on the advanced math task compared to the other two " "participants in your group?" ) d1 = models.IntegerField( choices=( [1, "Male"], [2, "Female"], [3, "Other"], [4, "Prefer not to answer"], ), widget=widgets.RadioSelect, label="Gender Identification:" ) d2 = models.IntegerField( choices=( [1, "Freshman"], [2, "Sophomore"], [3, "Junior"], [4, "Senior"], [5, "Graduate"], ), widget=widgets.RadioSelect, label="Current Grade Status:" ) d3 = models.IntegerField(label="Age (in years):") d4 = models.StringField(label="What is your academic major? (answer 'none' or 'undecided' if applicable") d5 = models.FloatField(label="What is your GSU GPA?", max=5) d6 = models.IntegerField(label="How many college-level math classes have you taken? (answer with a number") d7 = models.StringField(label="What is your nationality?") # FUNCTIONS # def initial_rank(g: Group): # players = g.get_players() # print(players) # players.sort(key=lambda p: -p.num_correct) # # this code checks if there is a tie and then assigns the same rank # for i in range(len(players)): # if i > 0 and players[i].num_correct == players[i - 1].num_correct: # rank_i = players[i - 1].rank_i # else: # rank_i = i + 1 # players[i].rank_i = rank_i # print("inital rank", rank_i) # # top = [p.id_in_group for p in players if p.rank <= 2] # for player in players: # if player.rank_i <= 2: # player.participant.vars['is_top'] = 1 # else: # player.participant.vars['is_top'] = 0 # print(player.id_in_group, player.participant.vars['name'], player.participant.vars['is_top']) def initial_rank(g: Group): import random players = g.get_players() print(players) players.sort(key=lambda p: (-p.num_correct, -p.num_correct/p.attempted if p.attempted != 0 else 0, random.random())) for i in range(len(players)): if i > 0 and players[i].num_correct == players[i - 1].num_correct: rank_i = players[i - 1].rank_i else: rank_i = i + 1 players[i].rank_i = rank_i print("inital rank", rank_i) for player in players[:2]: player.participant.vars['is_top'] = 1 for player in players[2:]: player.participant.vars['is_top'] = 0 print([(p.id_in_group, p.participant.vars['name'], p.participant.vars['is_top']) for p in players]) def round_rank(g: Group): players = g.get_players() for player in players: player.name = player.participant.vars['name'] # create a list of players who are not top players but are promoted players bottom_players = [p for p in players if p.participant.vars['is_top'] == 0 and p.participant.vars['is_promoted'] == 1] # create a list to store the ranks for each bottom player rank2 = [] # for each bottom player, create a list of all top players and the bottom player for player in bottom_players: rank2_p = [p for p in players if p.participant.vars['is_top'] == 1] rank2_p.append(player) print("before sort", rank2_p) # sort the list of players by their correct answers rank2_p.sort(key=lambda p: -p.num_correct) print("after sort", rank2_p) # assign ranks to each player in the list player_rank = [] for i in range(len(rank2_p)): rank=0 if i > 0 and rank2_p[i].num_correct == rank2_p[i - 1].num_correct: player_rank.append({"player": rank2_p[i].name, "rank": player_rank[i - 1].get("rank")}) else: player_rank.append({"player": rank2_p[i].name, "rank": (i + 1)}) # add the list of the bottom player and their ranks to the rank2 list rank2.append({"pr": player_rank, "p": rank2_p}) # assign names to the players # return the rank2 list print(rank2) return rank2 # def promote_players(g: Group): # non_top_players = filter(lambda p: p.participant.vars['is_top'] == 0, g.get_players()) # top_players = [p for p in g.get_players() if p.participant.vars['is_top'] == 1] # non_top_players = sorted(non_top_players, key=lambda p: -p.num_correct) # half = len(non_top_players)//2 # for i in range(half): # non_top_players[i].participant.vars['is_promoted'] = 1 # for i in range(half, len(non_top_players)): # non_top_players[i].participant.vars['is_promoted'] = 0 # for i in range(len(top_players)): # top_players[i].participant.vars['is_promoted'] = 0 def promote_players(g: Group): non_top_players = [p for p in g.get_players() if p.participant.vars['is_top'] == 0] top_players = [p for p in g.get_players() if p.participant.vars['is_top'] == 1] # Calculate the sum of num_correct values over the past three rounds for each player for player in non_top_players: past_rounds = [player.in_round(round_num) for round_num in range(g.round_number - 2, g.round_number + 1)] player.participant.vars['num_correct_sum'] = sum([p.num_correct for p in past_rounds]) print(player.participant.vars['num_correct_sum']) non_top_players = sorted(non_top_players, key=lambda p: -p.participant.vars['num_correct_sum']) half = math.ceil(len(non_top_players)/2) for i in range(half): non_top_players[i].participant.vars['is_promoted'] = 1 for i in range(half, len(non_top_players)): non_top_players[i].participant.vars['is_promoted'] = 0 for i in range(len(top_players)): top_players[i].participant.vars['is_promoted'] = 0 # PAGES class Consent(Page): def is_displayed(player): return player.round_number == 1 form_model = 'player' form_fields = ["consent"] class GenInfo(Page): def is_displayed(player): return player.round_number == 1 class Background(Page): def is_displayed(player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): participants = len(player.group.get_players()) - 2 return {'participants': participants} class PlacementInfo(Page): def is_displayed(player): return player.round_number == 1 class Promotion(Page): def is_displayed(player): return player.round_number == 5 and player.participant.vars['is_promoted'] == 1 class Promotion2(Page): def is_displayed(player): return player.round_number == 5 and player.participant.vars['is_promoted'] == 1 @staticmethod def vars_for_template(player: Player): name = player.participant.vars['name'] return dict( name=name) class Promotion3(Page): def is_displayed(player): return player.round_number == 5 and player.participant.vars['is_promoted'] == 1 form_model = 'player' form_fields = ["l20"] class PromotionHL(Page): def is_displayed(player): return player.round_number == 5 and player.participant.vars['is_top'] == 1 @staticmethod def vars_for_template(player: Player): name = player.participant.vars['name'] return dict( name=name) class Quiz(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 form_model = 'player' form_fields = ["q1", "q2", "q3"] @staticmethod def error_message(player, values): solutions = dict( q1=1, q2=2, q3=1, ) error_messages = dict() for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[field_name] = 'Your answer is incorrect. Please try again.' return error_messages class PreQuestionnaire(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 form_model = 'player' form_fields = ["l1", "l2", "l3", "l4", "l5"] class Name(Page): def is_displayed(player): return player.round_number == 1 form_model = 'player' form_fields = ['name'] def before_next_page(player: Player, timeout_happened): player.participant.vars['name'] = player.name print(player.name) class Task1(Page): timeout_seconds = 180 def is_displayed(player): if player.round_number > 1 and player.participant.vars['is_top'] == 1: return False elif player.round_number > 4 and player.participant.vars['is_promoted'] == 1: return False else: return True def live_method(player, data): print(data) if data == 0: player.num_correct += 1 else: player.num_incorrect += 1 player.attempted = player.num_correct + player.num_incorrect print('number_correct:', player.num_correct) print('number_incorrect:', player.num_incorrect) print('number_attempted:', player.attempted) # @staticmethod # def before_next_page(player, timeout_happened): # player.participant.vars['payoff'] += player.num_correct * 2 # print('payoff:', player.participant.vars['payoff']) @staticmethod def before_next_page(player, timeout_happened): player.participant.vars['payoff'] = player.participant.vars.get('payoff', 0) player.participant.vars['payoff'] += player.num_correct * 2 print('payoff:', player.participant.vars['payoff']) class Slider(Page): timeout_seconds = 180 def is_displayed(player): return player.round_number > 1 and player.participant.vars['is_top'] == 1 or (player.round_number > 4 and player.participant.vars['is_promoted'] == 1) def live_method(player, data): print(data) if data == 0: player.num_correct += 1 else: player.num_incorrect += 1 player.attempted = player.num_correct + player.num_incorrect print('number_correct:', player.num_correct) print('number_incorrect:', player.num_incorrect) print('number_attempted:', player.attempted) def js_vars(player: Player): lower1 = 101 lower2 = 11 upper1 = 500 upper2 = 99 return dict( lower1=lower1, upper1=upper1, lower2=lower2, upper2=upper2, ) @staticmethod def before_next_page(player, timeout_happened): player.participant.vars['payoff'] += player.num_correct * 12 print('payoff:', player.participant.vars['payoff']) class ResultsWaitPage1(WaitPage): def is_displayed(player): return player.round_number == 1 after_all_players_arrive = 'initial_rank' class ResultsWaitPage2(WaitPage): def is_displayed(player): return player.round_number == 4 after_all_players_arrive = 'promote_players' class ResultsWaitPage3(WaitPage): def is_displayed(player): return player.round_number > 4 after_all_players_arrive = 'round_rank' class RPI(Page): def is_displayed(player): # return player.round_number == 2 and player.participant.vars['is_top'] == 0 return player.round_number > 4 and (player.participant.vars['is_promoted'] == 1 or player.participant.vars['is_top'] == 1) @staticmethod def vars_for_template(player: Player): rank2 = round_rank(player.group) print(rank2) groups = ['Group 1', 'Group 2', 'Group 3'] player_ranks = [] for rank2_player in rank2: if player in rank2_player.get("p"): for pr in rank2_player.get("pr"): if pr.get("player") == player.name: player_ranks.append(pr.get("rank")) print("RANK", pr.get("rank")) combined = dict(zip(groups, player_ranks)) print("RANKS", player_ranks) if player.participant.vars['is_top']: top = 1 else: top = 0 return {'combined': combined, 'top': top, 'player_ranks': player_ranks} class PrePromotionResults(Page): def is_displayed(player: Player): return player.round_number <= 4 class NoRPI(Page): def is_displayed(player): return player.round_number > 4 and (player.participant.vars['is_promoted'] == 0 and player.participant.vars['is_top'] == 0) class PlacementResultsTop(Page): def is_displayed(player): return player.round_number == 2 and player.participant.vars['is_top'] == 1 class PlacementResultsBottom(Page): def is_displayed(player): return player.round_number == 2 and player.participant.vars['is_top'] == 0 class Quiz2LL(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 2 and player.participant.vars['is_top'] == 0 form_model = 'player' form_fields = ["q4", "q5", "q6"] @staticmethod def error_message(player, values): solutions = dict( q4=1, q5=1, q6=2, ) error_messages = dict() for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[field_name] = 'Your answer is incorrect. Please try again.' return error_messages class Quiz2HL(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 2 and player.participant.vars['is_top'] == 1 form_model = 'player' form_fields = ["q7", "q8", "q6"] @staticmethod def error_message(player, values): solutions = dict( q7=1, q8=1, q6=2, ) error_messages = dict() for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[field_name] = 'Your answer is incorrect. Please try again.' return error_messages class Quiz3TP(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 5 and player.participant.vars['is_top'] == 1 form_model = 'player' form_fields = ["q13", "q14", "q15"] @staticmethod def error_message(player, values): solutions = dict( q13=1, q14=1, q15=1, ) error_messages = dict() for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[field_name] = 'Your answer is incorrect. Please try again.' return error_messages class Quiz3NP(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 5 and player.participant.vars['is_promoted'] == 1 form_model = 'player' form_fields = ["q9", "q10", "q11", "q12"] @staticmethod def error_message(player, values): solutions = dict( q9=1, q10=1, q11=1, q12=1, ) error_messages = dict() for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[field_name] = 'Your answer is incorrect. Please try again.' return error_messages class IntroWaitPage(WaitPage): def is_displayed(player): return player.round_number == 2 class Introductions(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 2 @staticmethod def vars_for_template(player): if player.participant.vars['is_top'] == 1: top = 1 else: top = 0 return dict( top=top, ) class StartTask(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 2 class PostQuestionnaire(Page): @staticmethod def is_displayed(player: Player): return player.round_number == Constants.num_rounds and (player.participant.vars['is_promoted'] == 1 or player.participant.vars['is_top'] == 1) form_model = 'player' form_fields = ["l6", "l7", "l8", "l9", "l10", "l11"] class PostQuestionnaireLL(Page): @staticmethod def is_displayed(player: Player): return player.round_number == Constants.num_rounds and (player.participant.vars['is_promoted'] == 1) form_model = 'player' form_fields = ["l12", "l13", "l14", "l15", "l16", "l17", "l18", "l19"] class PostQuestionnaireHL(Page): @staticmethod def is_displayed(player: Player): return player.round_number == Constants.num_rounds and player.participant.vars['is_top'] == 1 form_model = 'player' form_fields = ["l12", "l13", "l16", "l17"] class Demographics(Page): @staticmethod def is_displayed(player: Player): return player.round_number == Constants.num_rounds form_model = 'player' form_fields = ["d1", "d2", "d3", "d4", "d5", "d6", "d7"] # class ThankYou(Page): # @staticmethod # def is_displayed(player: Player): # return player.round_number == Constants.num_rounds # # @staticmethod # def vars_for_template(player): # lira = player.participant.vars['payoff'] # x = lira/30 # y = round(x*4)/4 # conversion = '{:.2f}'.format(round(y, 2)) # total = y + 5 # payout = '{:.2f}'.format(total) # player.payment = payout # return dict( # lira=lira, # payout=payout, # conversion=conversion, # ) class ThankYou(Page): @staticmethod def is_displayed(player: Player): return player.round_number == Constants.num_rounds @staticmethod def vars_for_template(player): lira = player.participant.vars['payoff'] x = lira / 30 y = round(x * 4) / 4 # round to nearest 0.25 conversion = '{:.2f}'.format(y) total = y + 5 payout = '{:.2f}'.format(total) player.payment = payout return dict( lira=lira, payout=payout, conversion=conversion, ) page_sequence = [Consent, GenInfo, Background, PlacementInfo, Quiz, PreQuestionnaire, Name, PlacementResultsTop, PlacementResultsBottom, Quiz2LL, Quiz2HL, IntroWaitPage, Introductions, StartTask, Promotion, Promotion2, Promotion3, PromotionHL, Quiz3NP, Quiz3TP, Task1, Slider, ResultsWaitPage1, ResultsWaitPage2, ResultsWaitPage3, PrePromotionResults, RPI, NoRPI, PostQuestionnaire, PostQuestionnaireLL, PostQuestionnaireHL, Demographics, ThankYou]