from otree.api import * import matplotlib.pyplot as plt from pathlib import Path import numpy as np import random ################################################################################################### class C(BaseConstants): NAME_IN_URL = 'jnd_afriat_experiment' PLAYERS_PER_GROUP = None NUM_ROUNDS_PART_A = 60 NUM_ROUNDS_PART_B = 25 NUM_ROUNDS_PART_C = 10 NUM_ROUNDS = NUM_ROUNDS_PART_A + NUM_ROUNDS_PART_B + NUM_ROUNDS_PART_C PAYMENT_PART_A = cu(0.5) EX = 2 TOKENS_MAX = 100 PARTICIPATION_FEE = 5 TIMEOUT = 10 # GENERATES GRAPHS FOR PART A OF THE EXPERIMENT AND SAVES THEM IN ./_static/graphs/numerosities_1 & numerosities_2 # BLOCK NUMEROSITIES DETERMINE THE NUMBER OF DOTS IN EACH TRIAL. CHANGE low AND high BELOW. #max_ratio = 0.4 #basenum = 100 #ratio = np.ones(NUM_ROUNDS_PART_A) #for i in range(1, NUM_ROUNDS_PART_A): # ratio[i] = ratio[i - 1] + max_ratio / (NUM_ROUNDS_PART_A - 1) #np.random.shuffle(ratio) #NUM1 = np.zeros(NUM_ROUNDS_PART_A, dtype=int) #NUM2 = np.zeros(NUM_ROUNDS_PART_A, dtype=int) #for i in range(0, NUM_ROUNDS_PART_A): # x = random.choice([0, 1]) # if x == 0: # NUM1[i] = basenum # NUM2[i] = np.floor(ratio[i] * NUM1[i]) # else: # NUM2[i] = basenum # NUM1[i] = np.floor(ratio[i] * NUM2[i]) NUMEROSITES = [[100, 120, 114, 116, 100, 100, 100, 100, 122, 137, 123, 100, 100, 100, 100, 138, 106, 100, 119, 100, 134, 139, 100, 100, 100, 100, 100, 131, 131, 100, 100, 113, 100, 115, 127, 103, 100, 123, 100, 117, 100, 100, 105, 139, 100, 121, 100, 107, 100, 100, 100, 112, 127, 100, 111, 100, 135, 100, 100, 129], [133, 100, 100, 100, 114, 130, 108, 132, 100, 100, 100, 110, 102, 104, 102, 100, 100, 100, 100, 121, 100, 100, 125, 128, 136, 100, 129, 100, 100, 104, 110, 100, 118, 100, 100, 100, 124, 100, 125, 100, 118, 116, 100, 100, 137, 100, 135, 100, 106, 108, 109, 100, 100, 101, 100, 126, 100, 133, 112, 100]] #for round in range(1, NUM_ROUNDS_PART_A + 1): # for num in [1, 2]: # points = np.random.rand(2, NUMEROSITES[num - 1][round - 1]) # plt.scatter(points[0], points[1]) # plt.xticks([]) # plt.yticks([]) # graph_path = Path( # f'./_static/graphs/numerosities_{num}/numerosities_{num}_{round}.png') # plt.savefig(graph_path) # plt.clf() #block_path = Path( # f'./_static/coefficients/numerosities.csv') #np.savetxt(block_path, NUMEROSITES, delimiter=",") # GENERATES END POINTS FOR BUDGET LINES IN PART B. ONE POINT IS ALWAYS IN [0,C.TOKENS_MAX] AND THE OTHER ONE IS # BETWEEN [TOKENS_MAX/2, TOKENS_MAX]. INTERCEPTS = np.zeros((2, NUM_ROUNDS_PART_B)) for round in range(1, NUM_ROUNDS_PART_B + 1): int_1 = np.random.randint(low=1, high=TOKENS_MAX) if int_1 < TOKENS_MAX / 2: int_2 = np.random.randint(low=TOKENS_MAX / 2, high=TOKENS_MAX) else: int_2 = np.random.randint(low=1, high=TOKENS_MAX) inter = [int_1, int_2] x = np.random.choice([0, 1]) INTERCEPTS[0][round - 1] = inter[x] INTERCEPTS[1][round - 1] = inter[x - 1] intercepts_path = Path( f'./_static/coefficients/intercepts.csv') np.savetxt(intercepts_path, INTERCEPTS, delimiter=",") # CONSTANTS FOR QUESTIONNAIRE crt_list_base = { 1: ['crt_bat', 'crt_widget', 'crt_lake'], 2: ['crt_barrels', 'crt_rank', 'crt_pigs'], } crt_questions = { 'crt_bat': """ A bat and a ball cost one pound and ten pence in total. The bat costs a pound more than the ball. How much does the ball cost in pence? """, 'crt_widget': """ If it takes 5 machines 5 minutes to make 5 widgets, how many minutes would it take 100 machines to make 100 widgets? """, 'crt_lake': """ In a lake, there is a patch of lily pads. Every day, the patch doubles in size. If it takes 48 days for the patch to cover the entire lake, how many days would it take for the patch to cover half of the lake? """, 'crt_barrels': """ If John can drink one barrel of water in 6 days, and Mary can drink one barrel of water in 12 days, how many days would it take them to drink one barrel of water together? """, 'crt_rank': """ Jerry received both the 15th highest and the 15th lowest mark in the class. How many students are in the class? """, 'crt_pigs': """ A man buys a pig for 60 pounds, sells it for 70 pounds, buys it back for 80 pounds, and sells it finally for 90 pounds. How many pounds has he made? """ } crt_correct_answers = { 'crt_bat': 5, 'crt_widget': 5, 'crt_lake': 47, 'crt_barrels': 4, 'crt_rank': 29, 'crt_pigs': 20 } crt_intuitive_answers = { 'crt_bat': 10, 'crt_widget': 100, 'crt_lake': 24, 'crt_barrels': 9, 'crt_rank': 30, 'crt_pigs': 10 } crt_names = [name for sublist in list(crt_list_base.values()) for name in sublist] ################################################################################################### class Subsession(BaseSubsession): pass ################################################################################################### class Group(BaseGroup): pass ################################################################################################### class Player(BasePlayer): # VARIABLES FOR PART A OF THE EXPERIMENT numerosity_1 = models.IntegerField() numerosity_2 = models.IntegerField() decision_num = models.IntegerField( choices=[[1, 'Left panel'], [0, 'The panels have equal number of dots'], [2, 'Right panel']], label='', widget=widgets.RadioSelectHorizontal ) # numerosity_confidence = models.IntegerField( # choices=[[1, 'Not at all'], [2, 'Slightly'], [3, 'Somewhat'], # [4, 'Fairly'], [5, 'Completely']], # label='', # widget=widgets.RadioSelectHorizontal # ) # VARIABLES FOR PART B OF THE EXPERIMENT intercept_BLUE = models.IntegerField() intercept_RED = models.IntegerField() decision_choice_BLUE = models.FloatField() decision_choice_RED = models.FloatField() # VARIABLES FOR PART C OF THE EXPERIMENT option_A = models.IntegerField() option_B = models.IntegerField() decision_binary = models.StringField( choices=[['A', 'Option A'], ['I', 'I am indifferent'], ['B', 'Option B']], label='', widget=widgets.RadioSelectHorizontal ) # PART B AND C payment_round = models.IntegerField() payment_option = models.StringField() payment_account = models.StringField() age = models.IntegerField( label='What is your age?', min=18, max=100 ) gender = models.StringField( choices=[ ['female', 'She, her, hers'], ['male', 'He, him, his'], ['neutral', 'They, their, theirs'], ['other', 'Other'], ['pass', 'Prefer not to say'] ], label='Which pronouns do you use?' ) nationality = models.StringField( label='What is your nationality? (Please leave blank if you prefer not to say)', blank=True ) riskNonIncentive = models.IntegerField( choices=[ [0, '0 = Completely unwilling to take risks.'], [1, '1'], [2, '2'], [3, '3'], [4, '4'], [5, '5'], [6, '6'], [7, '7'], [8, '8'], [9, '9'], [10, '10 = Very willing to take risks.'], ], label="""In general, are you a person who is willing to take risks or do you try to avoid risks? Please use the scale below, where 0 is completely unwilling to take risks and 10 is very willing to take risks.""" ) for name in ['crt_bat', 'crt_widget', 'crt_lake', 'crt_barrels', 'crt_rank', 'crt_pigs']: locals()[name] = models.IntegerField(label=C.crt_questions[name]) del name studyProgram = models.StringField( choices=['Foundation', 'Bachelors', 'Masters', 'Other'], label="""Are you registered in a foundation, bachelors (undergraduate), masters (postgraduate taught) or other program?""" ) studyYear = models.IntegerField( label='In what year of your studies are you currently enrolled? (Enter 0 if you are not currently enrolled)', min=0, max=7 ) studyMajor = models.StringField( label="""What is the subject (often referred to as degree title or major) of your studies?""" ) studyGPA = models.IntegerField( choices=[ [5, '70 or above'], [4, '60-70'], [3, '50-60'], [2, '40-50'], [1, '40 or lower'], [0, 'No grade to base average on'], [-1, 'Do not know, or prefer not to say'] ], label="""For the modules that you have already completed for your current course, which of the following best describes your average grade (mark out of 100)?""" ) mathsHighestLevel = models.IntegerField( choices=[ [1, 'GCSE or equivalent (i.e. secondary education course with exam typically taken at age 16).'], [2, 'A-level or equivalent (i.e. secondary education course with exam typically taken at age 18).'], [3, 'University undergraduate level module or equivalent.'], [4, 'University postgraduate level module or equivalent.'], [0, 'None of the above.'] ], label="""Which of the following best describes the highest level of mathematics course/module of study that you have completed?""" ) mathsHighestGrade = models.IntegerField( choices=[ [5, 'Very good'], [4, 'Good'], [3, 'Satisfactory'], [2, 'Sufficient'], [1, 'Below sufficient'], [0, 'No grade'], [-1, 'Do not know, or prefer not to say'] ], label="""How would you describe your final grade in this mathematics course/module?""" ) priorExperiments = models.IntegerField( label="""How many experiments have you participated in before?""", min=0, max=100 ) friendsPresent = models.IntegerField( label="""How many of your friends were present in today's experiment?""", min=0, max=35 ) instructionsClear = models.BooleanField( label="""Were the instructions clear?""", choices=[[True, 'Yes'], [False, 'No']], widget=widgets.RadioSelect ) instructionsImprove = models.LongStringField( label="""If not, please explain how they could be improved.""", blank=True ) otherComments = models.LongStringField( label="""Please use this space to tell us anything you would like.""", blank=True ) ################################################################################################### # PAGES ################################################################################################### class Introduction(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 ################################################################################################### class Currency(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 ################################################################################################### class Overview(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player): return { 'no_rounds': C.NUM_ROUNDS_PART_B + C.NUM_ROUNDS_PART_C } ################################################################################################### class IntroPartA(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player): return { 'gif': f'gifs/numerosity.gif', 'pay': round(C.PAYMENT_PART_A, 1) } ################################################################################################### class WaitingPagePartA(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 ################################################################################################### class ChoicePartA(Page): form_model = 'player' form_fields = ['decision_num'] timeout_seconds = C.TIMEOUT @staticmethod def is_displayed(player: Player): return player.round_number <= C.NUM_ROUNDS_PART_A @staticmethod def vars_for_template(player): return { 'panel_1': f'graphs/numerosities_1/numerosities_1_{player.round_number}.png', 'panel_2': f'graphs/numerosities_2/numerosities_2_{player.round_number}.png' } @staticmethod def before_next_page(player, timeout_happened): player.numerosity_1 = int(C.NUMEROSITES[0][player.round_number - 1]) player.numerosity_2 = int(C.NUMEROSITES[1][player.round_number - 1]) if player.numerosity_1 > player.numerosity_2: true_answer = 1 elif player.numerosity_1 == player.numerosity_2: true_answer = 0 else: true_answer = 2 if player.decision_num == true_answer: player.payoff = C.PAYMENT_PART_A/C.EX else: player.payoff = 0 ################################################################################################### #class ConfidencePartA(Page): # @staticmethod # def is_displayed(player: Player): # return player.round_number <= C.NUM_ROUNDS_PART_A ################################################################################################### class ResultsPartA(Page): @staticmethod def is_displayed(player: Player): return player.round_number <= C.NUM_ROUNDS_PART_A ################################################################################################### class IntroPartB(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS_PART_A @staticmethod def vars_for_template(player): return { 'no_rounds': C.NUM_ROUNDS_PART_B + C.NUM_ROUNDS_PART_C } ################################################################################################### class IntroPartB1a(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS_PART_A @staticmethod def vars_for_template(player): return { 'token_half': int(C.TOKENS_MAX/2), 'no_rounds': C.NUM_ROUNDS_PART_B + C.NUM_ROUNDS_PART_C, } ################################################################################################### class IntroPartB1b(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS_PART_A @staticmethod def vars_for_template(player): return { 'token_half': int(C.TOKENS_MAX/2), 'no_rounds': C.NUM_ROUNDS_PART_B + C.NUM_ROUNDS_PART_C, 'gif': f'gifs/slider.gif' } ################################################################################################### class ChoicePartB1(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B >= player.round_number > C.NUM_ROUNDS_PART_A @staticmethod def live_method(player, data): player.decision_choice_RED = float(data['value']) @staticmethod def vars_for_template(player): rnd = player.round_number - C.NUM_ROUNDS_PART_A player.intercept_BLUE = int(C.INTERCEPTS[0][rnd - 1]) player.intercept_BLUE = int(C.INTERCEPTS[0][rnd - 1]) player.intercept_RED = int(C.INTERCEPTS[1][rnd - 1]) if round(player.intercept_RED/player.intercept_BLUE,2) == player.intercept_RED/player.intercept_BLUE: exact = True else: exact = False return { 'intercept_BLUE': player.intercept_BLUE, 'intercept_RED': player.intercept_RED, 'price': round(player.intercept_RED/player.intercept_BLUE,2), 'inverse_price': round(player.intercept_BLUE/player.intercept_RED,2), 'round': rnd, 'exact': exact } @staticmethod def js_vars(player): rnd = player.round_number - C.NUM_ROUNDS_PART_A player.intercept_RED = int(C.INTERCEPTS[1][rnd - 1]) return dict( intercept_2= player.intercept_RED, price= player.intercept_RED / player.intercept_BLUE, ) @staticmethod def before_next_page(player, timeout_happened): price = player.intercept_RED / player.intercept_BLUE player.decision_choice_BLUE = (player.intercept_RED - player.decision_choice_RED)/price player.payment_option = "N/A" player.payment_account = random.choice(["BLUE", "RED"]) if player.payment_account == "BLUE": player.payoff = player.decision_choice_BLUE/C.EX else: player.payoff = player.decision_choice_RED/C.EX ################################################################################################### class ResultsPartB(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B @staticmethod def before_next_page(player, timeout_happened): n = C.NUM_ROUNDS_PART_B r_min = C.NUM_ROUNDS_PART_A + 1 r_max = C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B matrix = np.zeros((n,n), dtype=float) for i in player.in_rounds(r_min, r_max): for j in player.in_rounds(r_min, r_max): if i != j: row = i.round_number - C.NUM_ROUNDS_PART_A - 1 col = j.round_number - C.NUM_ROUNDS_PART_A - 1 price = i.intercept_RED/i.intercept_BLUE matrix[row][col] = price * j.decision_choice_BLUE + j.decision_choice_RED - price * i.decision_choice_BLUE - i.decision_choice_RED cycles = [] for i in range(0, n): for j in range(i + 1, n): if (matrix[i][j] <= 0 and matrix[j][i] < 0) or (matrix[i][j] < 0 and matrix[j][i] <= 0): cycles.append([i + 1, j + 1]) if len(cycles) > C.NUM_ROUNDS_PART_C: cycles = random.sample(cycles, C.NUM_ROUNDS_PART_C) for item in range(1, C.NUM_ROUNDS_PART_C + 1): round = C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B + item player.in_round(round).option_A = -100 player.in_round(round).option_B = -100 if item <= len(cycles): x = random.choice([0, 1]) player.in_round(round).option_A = C.NUM_ROUNDS_PART_A + cycles[item - 1][x] player.in_round(round).option_B = C.NUM_ROUNDS_PART_A + cycles[item - 1][1 - x] active_rounds = 0 for item in player.in_rounds(C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B + 1, C.NUM_ROUNDS): if item.option_A >= 0: active_rounds += 1 player.in_round(C.NUM_ROUNDS).payment_round = random.randint(C.NUM_ROUNDS_PART_A + 1, C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B + active_rounds) ################################################################################################### class IntroPartC(Page): @staticmethod def is_displayed(player: Player): return player.round_number == (C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B + 1) @staticmethod def vars_for_template(player): active_rounds = 0 for item in player.in_rounds(C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B + 1, C.NUM_ROUNDS): if item.option_A >= 0: active_rounds += 1 return { 'no_rounds': active_rounds, } ################################################################################################### class ChoicePartC(Page): form_model = 'player' form_fields = ['decision_binary'] @staticmethod def is_displayed(player: Player): return player.round_number > C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B and player.option_A >= 0 def vars_for_template(player): rounds = 0 for item in player.in_rounds(C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B+1, C.NUM_ROUNDS): if item.option_A > 0: rounds += 1 return { 'option_A1': (round(player.in_round(player.option_A).decision_choice_BLUE,2)), 'option_A2': (round(player.in_round(player.option_A).decision_choice_RED,2)), 'option_B1': (round(player.in_round(player.option_B).decision_choice_BLUE,2)), 'option_B2': (round(player.in_round(player.option_B).decision_choice_RED,2)), 'rounds': rounds, 'current_round': player.round_number - C.NUM_ROUNDS_PART_A - C.NUM_ROUNDS_PART_B } @staticmethod def before_next_page(player, timeout_happened): if player.decision_binary == "A": round = player.option_A player.payment_option = player.decision_binary elif player.decision_binary == "B": round = player.option_B player.payment_option = player.decision_binary else: player.payment_option = random.choice(["A", "B"]) if player.payment_option == "A": round = player.option_A else: round = player.option_B player.payment_account = random.choice(["BLUE", "RED"]) if player.payment_account == "BLUE": player.payoff = (player.in_round(round).decision_choice_BLUE)/C.EX else: player.payoff = (player.in_round(round).decision_choice_RED)/C.EX ################################################################################################### class ResultsPartC(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS and player.option_A >= 0 ################################################################################################### class Payment(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS @staticmethod def vars_for_template(player): # SUMMARISE RESULTS FROM PART A numerosity_success = 0 for item in player.in_rounds(1,C.NUM_ROUNDS_PART_A): if item.numerosity_1 > item.numerosity_2: true_answer = 1 elif item.numerosity_1 < item.numerosity_2: true_answer = 2 else: true_answer = 0 if true_answer == item.decision_num: numerosity_success += 1 numerosity_combined_pay = (numerosity_success*C.PAYMENT_PART_A) # SUMMARIZE RESULTS FROM PART B active_rounds = 0 for item in player.in_rounds(C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B + 1, C.NUM_ROUNDS): if item.option_A >= 0: active_rounds += 1 if player.payment_round <= C.NUM_ROUNDS_PART_A + C.NUM_ROUNDS_PART_B: payment_option_A1 = player.in_round(player.payment_round).decision_choice_BLUE payment_option_A2 = player.in_round(player.payment_round).decision_choice_RED payment_option_B1 = 0 payment_option_B2 = 0 payment_part = 'B1' rnd = player.payment_round - C.NUM_ROUNDS_PART_A account = player.in_round(player.payment_round).payment_account option = player.in_round(player.payment_round).payment_option choice_text = "" decision_bin = 'NA' if account == 'BLUE': decision_payment = payment_option_A1 else: decision_payment = payment_option_A2 else: round_option_A = player.in_round(player.payment_round).option_A round_option_B = player.in_round(player.payment_round).option_B payment_option_A1 = player.in_round(round_option_A).decision_choice_BLUE payment_option_A2 = player.in_round(round_option_A).decision_choice_RED payment_option_B1 = player.in_round(round_option_B).decision_choice_BLUE payment_option_B2 = player.in_round(round_option_B).decision_choice_RED payment_part = 'B2' rnd = player.payment_round - C.NUM_ROUNDS_PART_A - C.NUM_ROUNDS_PART_B account = player.in_round(player.payment_round).payment_account option = player.in_round(player.payment_round).payment_option decision_bin = player.in_round(player.payment_round).decision_binary if player.in_round(player.payment_round).decision_binary == 'A': choice_text = "you preferred option A to B" elif player.in_round(player.payment_round).decision_binary == 'B': choice_text = "you preferred option B to A" else: choice_text = "you were indifferent between option A and B" if option == 'A': if account == 'BLUE': decision_payment = payment_option_A1 else: decision_payment = payment_option_A2 else: if account == 'BLUE': decision_payment = payment_option_B1 else: decision_payment = payment_option_B2 for item in range(C.NUM_ROUNDS_PART_A + 1, C.NUM_ROUNDS+1): if item != player.payment_round: player.in_round(item).payoff = 0 return { 'participation_fee': format(C.PARTICIPATION_FEE, '.2f'), 'numerosity_combined_pay': format(float(numerosity_combined_pay), '.0f'), 'numerosity_success': numerosity_success, 'numerosity_currency': cu(numerosity_combined_pay/C.EX), 'option_A1': round(payment_option_A1,2), 'option_A2': round(payment_option_A2,2), 'option_B1': round(payment_option_B1,2), 'option_B2': round(payment_option_B2,2), 'payment_part': payment_part, 'pay_round': rnd, 'active_rounds': active_rounds, 'decision_payment': round(decision_payment,2), 'decision_currency': player.in_round(player.payment_round).payoff, 'decision_binary': decision_bin, 'payment_account': account, 'payment_option': option, 'choice_text': choice_text, 'total_pay': player.participant.payoff_plus_participation_fee(), } ################################################################################################### class Demographics(Page): form_model = 'player' form_fields = ['age', 'gender', 'nationality', 'riskNonIncentive'] @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS ################################################################################################### class CRT1(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS def get_form_fields(self): random.shuffle(C.crt_list_base[1]) questions = C.crt_list_base[1] return questions ################################################################################################### class CRT2(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS def get_form_fields(self): random.shuffle(C.crt_list_base[2]) questions = C.crt_list_base[2] return questions ################################################################################################### class Study(Page): form_model = 'player' form_fields = ['studyProgram', 'studyYear', 'studyMajor', 'studyGPA', 'mathsHighestLevel', 'mathsHighestGrade'] @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS ################################################################################################### class Misc(Page): form_model = 'player' form_fields = ['priorExperiments', 'friendsPresent', 'instructionsClear', 'instructionsImprove', 'otherComments'] @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS ################################################################################################### class Final(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS def vars_for_template(self): participant = self.participant payoff = self.participant.payoff total_payoff = self.participant.payoff_plus_participation_fee() return { 'hover_message': 'Hover over with mouse pointer to reveal', 'redemption_code': participant.label or participant.code, 'payoff': payoff, 'total_payoff': total_payoff } ################################################################################################### page_sequence = [Introduction, Currency, Overview, IntroPartA, WaitingPagePartA, ChoicePartA, ResultsPartA, IntroPartB, IntroPartB1a, IntroPartB1b, ChoicePartB1, ResultsPartB, IntroPartC, ChoicePartC, ResultsPartC, Payment, Demographics, CRT1, CRT2, Study, Misc, Final]