from otree.api import * # from otree.models import player doc = """ Read quiz questions from a CSV (simple version). See also the 'complex' version of this app. """ import numpy as np def read_csv(): import csv f = open(__name__ + '/stimuli.csv', encoding='utf-8-sig') rows = list(csv.DictReader(f)) return rows class C(BaseConstants): NAME_IN_URL = 'questions_from_csv_simple' PLAYERS_PER_GROUP = None COUNTRY = read_csv() NUM_ROUNDS = 6 FLOOR = 0 CEILING = 15 ENDOWMENT = 20 # money for participation GUESS_MAX = 100 REVIEW_INSTRUCTIONS = 'forecastingwithcsv/reviewinstructions.html' REVIEW_ASK_ADVICEWTP = 'forecastingwithcsv/review_ask_adviceWTP.html' REVIEW_ASK_DELEGATIONWTP = 'forecastingwithcsv/review_ask_delegationWTP.html' REVIEW_VALUESTOGUESS = 'forecastingwithcsv/review_valuestoguess.html' class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): # Treatments for group in subsession.get_groups(): if subsession.round_number == 1: # p.control = random.choice([True, False]) # print('set control to', p.control) import itertools #controls = itertools.cycle([True, False, True, False]) #only delegation #controls = itertools.cycle([False, False, False, False]) # change for all advice: controls = itertools.cycle([True, True, True, True]) datas = itertools.cycle([1, 2, 3]) for p in group.get_players(): p.control = next(controls) p.data = next(datas) # p.overconfidence = random.choice([True, False]) import itertools overconfidences = itertools.cycle([True, False, False, True]) # only nudge / no overconfidence # overconfidences = itertools.cycle([False, False, False, False]) # only overconfidence #overconfidences = itertools.cycle([True, True, True, True]) for p in group.get_players(): p.overconfidence = next(overconfidences) else: for p in group.get_players(): p.control = p.in_round(1).control p.overconfidence = p.in_round(1).overconfidence p.data = p.in_round(1).data # Randomization of Datasets current_country = C.COUNTRY[subsession.round_number - 1] # starts counting at 0 for p in subsession.get_players(): if p.data == 1: current_country = C.COUNTRY[subsession.round_number - 1] if p.data == 2: current_country = C.COUNTRY[subsession.round_number + 4] # 2 if p.data == 3: current_country = C.COUNTRY[subsession.round_number + 9] # 5 p.countryid = current_country['countryid'] p.countryname = current_country['countryname'] p.broadband = current_country['broadband'] p.lifeexpecttotal = current_country['lifeexpecttotal'] p.co2em = current_country['co2em'] p.gdpcap = current_country['gdpcap'] p.gdpcappredict = current_country['gdpcappredict'] p.happiness = current_country['happiness'] p.dataset = current_country['dataset'] class Group(BaseGroup): pass # ******************************************************************************************************************** # # *** PLAYER VARIABLES *** # # ******************************************************************************************************************** # class Player(BasePlayer): # Treatment control = models.BooleanField(choices=[[True, "Advice"], [False, "Delegation"]]) overconfidence = models.BooleanField(choices=[[True, "No Feedback"], [False, "Feedback"]]) data = models.IntegerField() countryid = models.StringField() countryname = models.StringField() broadband = models.StringField() lifeexpecttotal = models.StringField() co2em = models.StringField() gdpcap = models.StringField() happiness = models.StringField() gdpcappredict = models.StringField() dataset = models.StringField() # Forecasting Task initialguess = models.IntegerField( label="Please indicate your estimate of the normalised GDP per capita for this country:", min=0, max=C.GUESS_MAX) confidence = models.IntegerField() minconfidence = models.IntegerField() maxconfidence = models.IntegerField() # second confidence question finalconfidence = models.IntegerField() finalminconfidence = models.IntegerField() finalmaxconfidence = models.IntegerField() finalguess = models.IntegerField(label="Please indicate the estimate you want to submit as your final estimate:", min=0, max=C.GUESS_MAX) displayfinalguess = models.IntegerField(initial=1) nosupport = models.IntegerField(initial=0) showfeedback = models.IntegerField(initial=0) # Advice Treatment algoadvice = models.BooleanField(initial=False) # Delegation Treatment algodelegation = models.BooleanField(initial=False) # Reward reward = models.CurrencyField(initial=0.01) roundpayment = models.IntegerField() selected_round = models.IntegerField() totalpayment = models.CurrencyField() roundpayoff = models.IntegerField() flatpayment = models.CurrencyField(initial=2) # Error prediction predictionerror = models.IntegerField() finalpredictionerror = models.IntegerField() averageestimation = models.IntegerField(initial=100) algoerror = models.IntegerField() predictbelow5est1 = models.IntegerField() predictbelow5est2 = models.IntegerField() predictbelow5est3 = models.IntegerField() # Comparison of estimations performanceafter3est = models.IntegerField() performancepercent = models.IntegerField() errorintervall = models.IntegerField() algointervall = models.IntegerField() # Willingness-to-Pay / BDM WTP = models.IntegerField(label='', min=0, max=15) WTPcurrency = models.CurrencyField( choices=['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1.0', '1.1', '1.2', '1.3', '1.4', '1.5'], label='') cost = models.IntegerField() dice = models.IntegerField() dicecurrency = models.CurrencyField() scenario_auc = models.IntegerField() receivesupport = models.IntegerField(initial=0) totalsupport = models.IntegerField(initial=0) totalnosupport = models.IntegerField(initial=1) totalreward = models.CurrencyField(initial=0) wantsupport = models.IntegerField(initial=0) totalwantsupport = models.IntegerField(initial=0) def WTP_error_message(self, value): if value >= C.CEILING: return "Your submitted willingness to pay is higher than the randomly chosen price." if value <= C.FLOOR: return "Your submitted willingness to pay is lower than the randomly chosen price." # Control questions truewtp = models.BooleanField( label='It is optimal for me to state my true willingness to pay to receive the algorithm\'s support.', choices=[[True, 'Correct'], [False, 'False']], widget=widgets.RadioSelectHorizontal) # Ich sollte immer meine wahre Zahlungsbereitschaft angeben. truewtpcount = models.IntegerField() changeadvice = models.BooleanField(choices=[[True, 'Correct'], [False, 'False']], label='If I take the algorithm\'s estimate as advice, I must follow the algorithm\'s advice and cannot enter another value as final estimate.', widget=widgets.RadioSelectHorizontal) changeadvicecount = models.IntegerField(initial=1) changedelegation = models.BooleanField(choices=[[True, 'Correct'], [False, 'False']], label='If I delegate the estimation to the algorithm, I do not have to follow the algorithm and can enter another value as final estimate.', widget=widgets.RadioSelectHorizontal) changedelegationcount = models.IntegerField(initial=1) optimalwtp = models.CurrencyField( choices=['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1.0', '1.1', '1.2', '1.3', '1.4', '1.5'], label='The price for the algorithmic support can be between \xA30.00 and \xA31.50. Suppose your willingness to pay for the algorithm is \xA30.60. Please enter the value which you would optimally state as the maximum price you would pay for the algorithmic support:') optimalwtpcount = models.IntegerField() pricesupport = models.CurrencyField( choices=['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1.0', '1.1', '1.2', '1.3', '1.4', '1.5'], label='Suppose your stated willingness to pay for the algorithm is £0.60 and the randomly determined price for the algorithmic support is \xA30.40. Please enter the amount which you actually pay for the algorithmic support:') pricesupportcount = models.IntegerField() wtpcontrol = models.CurrencyField(initial=1.0) pricecontrol = models.CurrencyField(initial=0.6) wtpcount = models.IntegerField() falseanswerswtp = models.IntegerField() # Example WTP wtpzero = models.CurrencyField(initial=0) wtpmaxcu = models.CurrencyField(initial=1.5) examplewtp = models.CurrencyField(initial=0.7) examplepricelow = models.CurrencyField(initial=0.4) examplepricehigh = models.CurrencyField(initial=1.2) # Payment overview profit5 = models.CurrencyField(initial=5) profit4 = models.CurrencyField(initial=4) profit3 = models.CurrencyField(initial=3) profit2 = models.CurrencyField(initial=2) profit1 = models.CurrencyField(initial=1) profit0 = models.CurrencyField(initial=0) # Questionnaire after 3 estimations competence3est = models.IntegerField( label='How good did you find the quality of the algorithm\'s estimates in this study?', choices=[['1', 'very bad'], ['2', ''], ['3', ''], ['4', ''], ['5', ''], ['6', ''], ['7', 'very good']], widget=widgets.RadioSelect, initial=100) # "Wie gut fanden Sie die Qualität der Schätzungen des Algorithmus?." performance3est = models.StringField( label= 'How would you rate your estimation performance (in the first estimation) relative to that of the algorithm?', choices=[['1', 'very much worse'], ['2', ''], ['3', ''], ['4', 'equal'], ['5', ''], ['6', ''], ['7', 'very much better']], widget=widgets.RadioSelect) helpful3est = models.IntegerField( label='It was not necessary for me to use the algorithm in this study.', choices=[['1', 'does not describe me at all'], ['2', ''], ['3', ''], ['4', ''], ['5', ''], ['6', ''], ['7', 'describes me perfectly']], widget=widgets.RadioSelect) # Auch wenn der Algorithmus gute Schätzungen macht, ist es für mich nicht hilfreich ihn zu verwenden. useful3est = models.IntegerField( label='I felt uncomfortable using the algorithm in this study.', choices=[['1', 'does not describe me at all'], ['2', ''], ['3', ''], ['4', ''], ['5', ''], ['6', ''], ['7', 'describes me perfectly']], widget=widgets.RadioSelect) # "Auch wenn der Algorithmus nützlich ist, fühle ich mich unwohl ihn zu verwenden." trustalgo3est = models.IntegerField( label='How much did you trust the algorithm\'s estimations in this study?', choices=[['1', 'not trusted at all'], ['2', ''], ['3', ''], ['4', ''], ['5', ''], ['6', ''], ['7', 'totally trusted']], widget=widgets.RadioSelect) # "Wie sehr haben Sie den Einschätzungen des Algorithmus innerhalb dieses Experiments vertraut?" # Time variable page_pass_time = models.FloatField() # Nudge 1 prederrorest1 = models.IntegerField() prederrorest2 = models.IntegerField() prederrorest3 = models.IntegerField() algoerrorest1 = models.IntegerField() algoerrorest2 = models.IntegerField() algoerrorest3 = models.IntegerField() # Nudge 2 profitalgo_est1 = models.CurrencyField(inital=1) profitalgo_est2 = models.CurrencyField(inital=2) profitalgo_est3 = models.CurrencyField(inital=3) profitsolo_est1 = models.CurrencyField(inital=1) profitsolo_est2 = models.CurrencyField(inital=2) profitsolo_est3 = models.CurrencyField(inital=1) diff_est1 = models.IntegerField(inital=1) diff_est2 = models.IntegerField(inital=2) diff_est3 = models.IntegerField(inital=2) profit_est1 = models.CurrencyField() profit_est2 = models.CurrencyField() profit_est3 = models.CurrencyField() # def get_groups(self): # pass # ******************************************************************************************************************** # # *** PAGES *** # # ******************************************************************************************************************** # class ask_initialguess(Page): @staticmethod def vars_for_template(player: Player): return dict(player=player.in_round(player.round_number)) form_model = 'player' form_fields = ['initialguess'] @staticmethod def before_next_page(player, timeout_happened): if player.initialguess >= 5: player.minconfidence = player.initialguess - 5 player.maxconfidence = player.initialguess + 5 if player.initialguess <= 4: player.minconfidence = 0 player.maxconfidence = player.initialguess + 5 class check_confidence(Page): form_model = 'player' form_fields = ['confidence'] @staticmethod def before_next_page(player, timeout_happened): if player.overconfidence == False and player.round_number >= 2: player.showfeedback = 1 else: player.showfeedback = 0 class give_infoalgoWTP_advice(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 and player.control == True class give_infoalgoWTP_delegate(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 and player.control == False class question_truewtp(Page): form_model = 'player' form_fields = ['truewtp'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def error_message(player, values): print('values is', values) if values['truewtp'] != True: return 'Your answer is wrong. Please try again.' class question_truewtp_result(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 class question_changeadvice(Page): form_model = 'player' form_fields = ['changeadvice'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 and player.control == True @staticmethod def error_message(player, values): print('values is', values) if values['changeadvice'] != False: return 'Your answer is wrong. Please try again.' class question_changeadvice_result(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 and player.control == True class question_changedelegation(Page): form_model = 'player' form_fields = ['changedelegation'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 and player.control == False @staticmethod def error_message(player, values): print('values is', values) if values['changedelegation'] != False: return 'Your answer is wrong. Please try again.' class question_changedelegation_result(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 and player.control == False class question_optimalwtp(Page): form_model = 'player' form_fields = ['optimalwtp'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def error_message(player, values): print('values is', values) if values['optimalwtp'] != cu(0.60): return 'Your answer is wrong. Please try again.' class question_optimalwtp_result(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 class question_pricesupport(Page): form_model = 'player' form_fields = ['pricesupport'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def error_message(player, values): print('values is', values) if values['pricesupport'] != cu(0.40): return 'Your answer is wrong. Please try again.' class question_pricesupport_result(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 class use_algoadvice(Page): @staticmethod def is_displayed(player): return player.control == True form_model = 'player' form_fields = ['algoadvice'] # def before_next_page(player, timeout_happened): # if player.algoadvice == True: # player.wantsupport == 1 class use_algodelegation(Page): @staticmethod def is_displayed(player): return player.control == False form_model = 'player' form_fields = ['algodelegation'] class ask_adviceWTP(Page): @staticmethod def is_displayed(player): return player.control == True and player.algoadvice == True form_model = 'player' form_fields = ['WTPcurrency'] def before_next_page(player, timeout_happened): player.WTP = int(player.WTPcurrency * 10) player.dice = np.random.randint(C.FLOOR, C.CEILING) player.dicecurrency = cu(player.dice / 10) player.wantsupport = 1 if player.WTP >= player.dice: player.scenario_auc = 1 player.receivesupport = 1 else: player.scenario_auc = 2 player.receivesupport = 0 class ask_delegationWTP(Page): @staticmethod def is_displayed(player): return player.control == False and player.algodelegation == True form_model = 'player' form_fields = ['WTPcurrency'] @staticmethod def before_next_page(player, timeout_happened): player.WTP = int(player.WTPcurrency * 10) player.dice = np.random.randint(C.FLOOR, C.CEILING) player.dicecurrency = cu(player.dice / 10) player.wantsupport = 1 if player.WTP >= player.dice: player.scenario_auc = 1 player.receivesupport = 1 else: player.scenario_auc = 2 player.receivesupport = 0 class give_result_adviceWTP(Page): @staticmethod def is_displayed(player): return player.control == True and player.algoadvice == True # def is_displayed(self): # return self.round_number == Constants.num_rounds class give_result_delegationWTP(Page): @staticmethod def is_displayed(player): return player.control == False and player.algodelegation == True def before_next_page(player, timeout_happened): if player.control == False and player.receivesupport == 1: player.finalguess = int(player.gdpcappredict) player.displayfinalguess = 0 class give_predictionalgo(Page): @staticmethod def is_displayed(player): return player.algoadvice == True pass class ask_finalguess(Page): @staticmethod def is_displayed(player): return player.displayfinalguess == 1 form_model = 'player' form_fields = ['finalguess'] def before_next_page(player, timeout_happened): if player.receivesupport == 0: player.nosupport = 1 elif player.algoadvice == False or player.algodelegation == False: player.nosupport = 1 # elif player.algodelegation == False: # player.nosupport= 1 else: player.nosupport = 1 class check_finalconfidence(Page): form_model = 'player' form_fields = ['finalconfidence'] @staticmethod def before_next_page(player, timeout_happened): player.predictionerror = abs(int(player.gdpcap) - player.initialguess) player.algoerror = abs(int(player.gdpcap) - int(player.gdpcappredict)) player.finalpredictionerror = abs(int(player.gdpcap) - player.finalguess) # note: if the player.predictionerror equals 100 no value is set for player.errorintervall # the max value for player.errorintervall is 100 and is triggered when: 95 <= player.predictionerror <= 99 for i in range(5, 105, 5): if player.predictionerror <= i: player.errorintervall = i break for i in range(5, 105, 5): if player.algoerror <= i: player.algointervall = i break if player.nosupport == 1: if int(player.gdpcap) - 5 <= player.finalguess <= int(player.gdpcap) + 5: player.reward = 5 elif int(player.gdpcap) - 10 <= player.finalguess <= int(player.gdpcap) + 10: player.reward = 4 elif int(player.gdpcap) - 15 <= player.finalguess <= int(player.gdpcap) + 15: player.reward = 3 elif int(player.gdpcap) - 20 <= player.finalguess <= int(player.gdpcap) + 20: player.reward = 2 elif int(player.gdpcap) - 25 <= player.finalguess <= int(player.gdpcap) + 25: player.reward = 1 else: player.reward = 0 if player.receivesupport == 1: if int(player.gdpcap) - 5 <= player.finalguess <= int(player.gdpcap) + 5: player.reward = 5 - player.dicecurrency elif int(player.gdpcap) - 10 <= player.finalguess <= int(player.gdpcap) + 10: player.reward = 4 - player.dicecurrency elif int(player.gdpcap) - 15 <= player.finalguess <= int(player.gdpcap) + 15: player.reward = 3 - player.dicecurrency elif int(player.gdpcap) - 20 <= player.finalguess <= int(player.gdpcap) + 20 and player.dice <= 20: player.reward = 2 - player.dicecurrency # elif int(player.gdpcap) - 20 <= player.finalguess <= int(player.gdpcap) + 20 and player.dice >= 21: # player.reward = 0 elif int(player.gdpcap) - 25 <= player.finalguess <= int(player.gdpcap) + 25 and player.dice <= 10: player.reward = 1 - player.dicecurrency elif int(player.gdpcap) - 25 <= player.finalguess <= int(player.gdpcap) + 25 and player.dice >= 11: player.reward = 0 else: player.reward = 0 # @staticmethod # def before_next_page(player: Player, timeout_happened): import random participant = player.participant participant.vars['control'] = player.control # if it's the last round if player.round_number == C.NUM_ROUNDS: random_round = random.randint(1, C.NUM_ROUNDS) participant.selected_round = random_round player_in_selected_round = player.in_round(random_round) participant.roundpayment = player_in_selected_round.reward participant.payoff = player_in_selected_round.reward participant.totalpayment = (player.flatpayment + participant.payoff) # player.totalpayment = (C.ENDOWMENT + player.roundpayment) / 10 # player.payoff = (C.ENDOWMENT + player_in_selected_round.reward) / 10 # amount of in total received & wanted support player.totalsupport = sum([p.receivesupport for p in player.in_all_rounds()]) player.totalwantsupport = sum([p.wantsupport for p in player.in_all_rounds()]) # Use variables across apps # participant = player.participant # participant.selected_round = player.selected_round player.participant.vars['selected_round'] = participant.selected_round # participant.roundpayment = player.roundpayment player.participant.vars['roundpayment'] = participant.roundpayment # participant.totalpayment = player.totalpayment player.participant.vars['totalpayment'] = participant.totalpayment # actual received support participant.totalsupport = player.totalsupport player.participant.vars['totalsupport'] = participant.totalsupport # willingness to receive support participant.totalwantsupport = player.totalwantsupport player.participant.vars['totalwantsupport'] = participant.totalwantsupport class check_predictionerror(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 3 class give_feedbackwithcharts_ownest(Page): @staticmethod def is_displayed(player): return player.round_number > 1 and player.nosupport == 1 and player.receivesupport == 0 # return player.showfeedback == 1 and player.receivesupport == 0 @staticmethod def js_vars(player): return dict( round_number=player.round_number, gdpcap=int(player.gdpcap), gdpcappredict=int(player.gdpcappredict), finalguess=player.finalguess, initialguess=player.initialguess, finalconfidence=int(player.finalconfidence), confidence=int(player.confidence) ) class give_feedbackwithcharts_usealgo(Page): @staticmethod def is_displayed(player): return player.round_number > 1 and player.receivesupport == 1 # return player.showfeedback == 1 and player.receivesupport == 1 @staticmethod def js_vars(player): return dict( round_number=player.round_number, gdpcap=int(player.gdpcap), gdpcappredict=int(player.gdpcappredict), finalguess=player.finalguess, initialguess=player.initialguess, finalconfidence=int(player.finalconfidence), confidence=int(player.confidence) ) def player_in_round_number(param): pass class questionnaire_after3est(Page): @staticmethod def is_displayed(player): return player.round_number == 3 def before_next_page(player: Player, timeout_happened): #import time #player.page_pass_time = time.time() + 10 # change time to 45 sec if player.in_round(1).predictionerror < 6: player.predictbelow5est1 = 1 else: player.predictbelow5est1 = 0 if player.in_round(2).predictionerror < 6: player.predictbelow5est2 = 1 else: player.predictbelow5est2 = 0 if player.in_round(3).predictionerror < 6: player.predictbelow5est3 = 1 else: player.predictbelow5est3 = 0 if player.round_number == 3: player.performanceafter3est = player.predictbelow5est1 + player.predictbelow5est2 + player.predictbelow5est3 if player.performanceafter3est == 3: player.performancepercent = 100 elif player.performanceafter3est == 2: player.performancepercent = 67 elif player.performanceafter3est == 1: player.performancepercent = 33 else: player.performancepercent = 0 player.prederrorest1 = player.in_round(1).errorintervall player.prederrorest2 = player.in_round(2).errorintervall player.prederrorest3 = player.in_round(3).errorintervall player.algoerrorest1 = player.in_round(1).algointervall player.algoerrorest2 = player.in_round(2).algointervall player.algoerrorest3 = player.in_round(3).algointervall player.profit_est1 = player.in_round(1).reward player.profit_est2 = player.in_round(2).reward player.profit_est3 = player.in_round(3).reward form_model = 'player' form_fields = ['competence3est', 'performance3est', 'helpful3est', 'useful3est', 'trustalgo3est'] class nudge1_intra(Page): @staticmethod def is_displayed(player): return player.round_number == 3 and player.overconfidence == False class nudge2_inter(Page): @staticmethod def is_displayed(player): return player.round_number == 3 # change to round_number to 3 @staticmethod def error_message(player: Player, values): import time if time.time() < player.page_pass_time: return "You have to stay on this page at least 45 seconds. You cannot pass this page yet." class Results(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS page_sequence = [ask_initialguess, check_confidence, give_infoalgoWTP_advice, give_infoalgoWTP_delegate, question_truewtp, question_truewtp_result, question_changeadvice, question_changeadvice_result, question_changedelegation, question_changedelegation_result, question_optimalwtp, question_optimalwtp_result, question_pricesupport, question_pricesupport_result, use_algoadvice, use_algodelegation, ask_adviceWTP, ask_delegationWTP, give_result_adviceWTP, give_result_delegationWTP, ask_finalguess, check_finalconfidence,give_feedbackwithcharts_ownest, give_feedbackwithcharts_usealgo, questionnaire_after3est, nudge1_intra, Results] # with nudge & control questions # questionnaire_after3est, nudge1_intra, nudge2_inter,