from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range ) doc = '' class Constants(BaseConstants): name_in_url = 'disclosure_survey' players_per_group = None num_rounds = 1 judge_base_reward = c(4.5) base_reward = c(2) estimator_bonus_less_than_neg_40 = c(0) estimator_bonus_within_neg_40_and_neg_10 = c(2) estimator_bonus_within_neg_10_and_10 = c(5) estimator_bonus_within_10_and_40 = c(2) estimator_bonus_greater_than_40 = c(0) my_constant = c(0) appeal_reward = c(2) appeal_reward_split = c(1) appeal_cost = c(0.5) default_input = 454 instructions_template = 'disclosure_survey/instructions.html' communicationformtemplate_template = 'disclosure_survey/communicationformtemplate.html' instructions2_template = 'disclosure_survey/instructions2.html' class Subsession(BaseSubsession): total_player = models.IntegerField(initial=0) num_advisor = models.IntegerField(initial=0) num_estimator = models.IntegerField(initial=0) num_judge = models.IntegerField(initial=0) def updateTotal(self): self.total_player = self.num_advisor + self.num_estimator + self.num_judge def getNextRole(self): es = self.num_estimator ad = self.num_advisor ju = self.num_judge if (ad < 30 + es) or (ad < 30 + ju): self.num_advisor += 1 self.updateTotal() return "advisor" elif es < 30 + ju: self.num_advisor += 1 self.updateTotal() return "advisor" else: self.num_judge += 1 self.updateTotal() return "judge" def get_next_disclosure(self): return self.total_player % 2 == 0 def get_matched_info(self): #returns tuple (id of player ready for match, role of match) #check if any pending advisors for player in self.get_players(): if player.survey_complete and not player.matched and player.is_advisor() and player.all_comprehension_correct: #next player will be estimator due to pending advisor self.num_estimator += 1 self.updateTotal() player.matched = True return (player.id_in_group, "estimator") #check if any pending estimator for player in self.get_players(): if player.survey_complete and not player.matched and player.is_estimator(): #next player will be judge due to pending estimator self.num_judge += 1 self.updateTotal() player.matched = True return (player.id_in_group, "judge") #no one ready for match, return advisor self.num_advisor += 1 self.updateTotal() return (-1, "advisor") def simulate_get_matched_info(self): #returns tuple (id of player ready for match, role of match) #check if any pending advisors for player in self.get_players(): if player.survey_complete and not player.matched and player.is_advisor() and player.all_comprehension_correct: return (player.id_in_group, "estimator") #check if any pending estimator for player in self.get_players(): if player.survey_complete and not player.matched and player.is_estimator(): #next player will be judge due to pending estimator return (player.id_in_group, "judge") #no one ready for match, return advisor return (-1, "advisor") class Group(BaseGroup): pass class Player(BasePlayer): player_role = models.StringField() consentAge = models.BooleanField(choices=[[True, 'Yes'], [False, 'No']], initial=True, label='I am 18 or older:') consentRead = models.BooleanField(choices=[[True, 'Yes'], [False, 'No']], initial=True, label='I have read and understand the above information:') consentWant = models.BooleanField(choices=[[True, 'Yes'], [False, 'No']], label='I accept and want to participate in the study:') huskyId = models.StringField(blank=True, label='Please enter your Husky ID number. (We will use this information for the sole purpose of awarding you the base payment any bonus payments that you earn via your Husky Card Account within a few weeks):') sonaId = models.StringField(label='Please enter your Sona ID number. (We will use this information for the sole purpose of awarding you course credit via the SONA system. Because we have to award credit for this study manually, your credit will appear in the SONA system 48 hours after completing the survey):') manip_adv_adviser_payment_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='In the dots estimation task, you get a bonus only if the estimator underestimates the true number of solid dots.') manip_adv_estimator_payment_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='In the dots estimation task, the estimator gets a bigger bonus the more accurate his/her estimate is.') manip_adv_payment_scheme_disclosed = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='Your payment scheme is disclosed to the estimator in the online communication form.') manip_adv_payment_scheme_not_disclosed = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='Your payment scheme is NOT disclosed to the estimator in the online communication form.') manip_est_estimator_payment_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='In the dots estimation task, you will get a bigger bonus the more accurate your estimate is.') manip_judge_adv_payment_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='In the dots estimation task, the adviser gets a bonus only if the estimator underestimates the true number of solid dots.') manip_judge_est_payment_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='In the dots estimation task, the estimator gets a bigger bonus the more accurate his/her estimate is.') manip_judge_disclosure_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='Did estimators know that the advisers would get a bonus if they overestimated?') manip_judge_adv_case_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='In the case, the adviser would get a bigger bonus the more the estimator underestimated the true number of solid dots.') manip_judge_est_case_question = models.BooleanField(blank=True, choices=[[True, 'True'], [False, 'False']], label='In the case, the estimator would get a bigger bonus the more accurate his/her estimate was.') manip_judge_disclosed_case_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='manip_judge_disclosed_case_question here') comment = models.LongStringField(blank=True, label='Do you have any comments for the researchers? (Optional)') estimator_appeal_question = models.LongStringField() judge_bonus_awarded_clarify = models.LongStringField(label='Why did you decide that the estimator should receive $2.00 and the advisor should receive nothing?') judge_bonus_not_awarded_clarify = models.LongStringField(label='Why did you decide that the estimator and the advisor should both receive $1.00?') blame_EST_I_blame_myself_for_my_guess = models.IntegerField(choices=[[7, 'Strongly agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat disagree'], [2, 'Disagree'], [1, 'Strongly disagree']], label='I blame myself for my guess.') blame_EST_I_blame_the_adviser_for_my_guess = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='I blame the adviser for my guess.') blame_EST_I_have_a_legitimate_grievance_against_the_adviser = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='I have a legitimate grievance against the adviser.') blame_EST_I_have_a_strong_case_if_I_choose_to_pursue_an_appeal = models.IntegerField(choices=[[1, 'Strongly Agree'], [2, 'Agree'], [3, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [5, 'Somewhat Disagree'], [6, 'Disagree'], [7, 'Strongly Disagree']], label='I have a strong case if I choose to pursue an appeal.') blame_EST_I_believe_that_others_would_rule_in_my_favor = models.IntegerField(choices=[[1, 'Strongly Agree'], [2, 'Agree'], [3, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [5, 'Somewhat Disagree'], [6, 'Disagree'], [7, 'Strongly Disagree']], label='I believe that others would rule in my favor on an appeal.') blame_EST_The_adviser_treated_me_fairly = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='The adviser treated me fairly.') blame_EST_I_was_mistreated_by_the_adviser = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='I was mistreated by the adviser.') blame_EST_I_deserve_to_receive_the_full_bonus = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='I deserve to receive the full bonus of $2.00.') blame_JUDGE_I_blame_the_estimator_for_their_guess = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='I blame the estimator for their guess.') blame_JUDGE_I_blame_the_adviser_for_the_estimators_guess = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label="I blame the advisor for the estimator's guess.") blame_JUDGE_estimator_has_legitimate_grievance_against_adviser = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='The estimator has a legitimate grievance against the adviser.') blame_JUDGE_estimator_has_a_strong_case_to_pursue_an_appeal = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='The estimator has a strong case if he or she choses to pursue an appeal.') blame_JUDGE_I_believe_others_would_rule_in_estimators_favor = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label="I believe that others would rule in the estimator's favor on an appeal.") blame_JUDGE_The_adviser_treated_the_estimator_fairly = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='The adviser treated the estimator fairly.') blame_JUDGE_The_estimator_was_mistreated_by_the_adviser = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='The estimator was mistreated by the adviser.') blame_JUDGE_The_estimator_deserves_to_receive_the_full_bonus = models.IntegerField(choices=[[7, 'Strongly Agree'], [6, 'Agree'], [5, 'Somewhat Agree'], [4, 'Neither agree nor disagree'], [3, 'Somewhat Disagree'], [2, 'Disagree'], [1, 'Strongly Disagree']], label='The estimator deserves to receive the full bonus of $2.00.') disclosure = models.BooleanField(initial=False) recommendation = models.IntegerField(max=900, min=0) estimate = models.IntegerField(max=900, min=1) grid_path = models.StringField(initial='none') big_grid_path = models.StringField(initial='none') appealed = models.BooleanField(choices=[[True, 'Yes'], [False, 'No']], label='Would you like to file an appeal?') appeal_granted = models.BooleanField(choices=[[True, 'The estimator should receive $2.00 and the adviser should receive nothing.'], [False, 'The estimator and the adviser should both receive $1.00.']], label='What is your judgment?', widget=widgets.RadioSelect) manip_final_adviser_payment_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']]) manip_final_estimator_payment_question = models.BooleanField(choices=[[True, 'True'], [False, 'False']]) manip_final_conflict_disclosed_or_not = models.BooleanField(choices=[[True, 'True'], [False, 'False']], label='The payment scheme was disclosed to the estimator in the online communication form.') final_compensation = models.CurrencyField(initial=2.5) gender = models.StringField(choices=[['Female', 'Female'], ['Male', 'Male'], ['Other', 'Other']], label='What is your gender?') age = models.IntegerField(label='What is your age?', max=100, min=0) race = models.StringField(choices=[['White', 'White'], ['Black, African-American', 'Black, African-American'], ['American Indian or Alaska Native', 'American Indian or Alaska Native'], ['Asian or Asian-American', 'Asian or Asian-American'], ['Pacific Islander', 'Pacific Islander'], ['Multiple races or some other race', 'Multiple races or some other race']], label='What is your race?') survey_complete = models.BooleanField(initial=False) matched = models.BooleanField(initial=False) match_id = models.IntegerField(initial=-1) number_off = models.IntegerField() appeal_question_clarify = models.LongStringField() grid_reward = models.CurrencyField(initial=0) grid_number = models.IntegerField(initial=-1) all_comprehension_correct = models.BooleanField(initial=True) full_name = models.StringField(blank=True, label='Please enter your first and last name:') def is_advisor(self): return self.player_role == "advisor" def is_estimator(self): return self.player_role == "estimator" def is_judge(self): return self.player_role == "judge" def get_grid_number(self): if self.player_role != "advisor": self.grid_number = self.matched_advisor().get_grid_number() return self.grid_number if self.grid_number != -1: return self.grid_number def randint(a, b): "Return random integer in range [a, b], including both end points." def random_bytes(n): "Return n random bytes" with open('/dev/urandom', 'rb') as file: return file.read(n) ans = 0 n = b - a + 1 if n <= 0: raise ValueError k = n.bit_length() numbytes = (k + 7) // 8 while True: r = int.from_bytes(random_bytes(numbytes), 'big') r >>= numbytes * 8 - k if r < n: ans = r break return a + ans self.grid_number = randint(1, 4) return self.grid_number def get_small_grid_path(self): gridNum = self.get_grid_number() if gridNum == 1: self.grid_path = 'grids/small_grid1.png' return 'grids/small_grid1.png' elif gridNum == 2: self.grid_path = 'grids/small_grid2.png' return 'grids/small_grid2.png' elif gridNum == 3: self.grid_path = 'grids/small_grid3.png' return 'grids/small_grid3.png' else: self.grid_path = 'grids/small_grid4.png' return 'grids/small_grid4.png' def get_correct_answer(self): gridNum = self.get_grid_number() if gridNum == 1: return 468 elif gridNum == 2: return 448 elif gridNum == 3: return 423 else: return 422 def matched_advisor(self): if self.is_advisor(): return self if self.is_judge(): estimator_match = self.matched_estimator().match_id return self.group.get_player_by_id(estimator_match) #for player in self.subsession.get_players(): #if player.id_in_group == estimator_match: #return player return self.group.get_player_by_id(self.match_id) def matched_estimator(self): if self.is_estimator(): return self if self.is_advisor(): for player in self.subsession.get_players(): if player.match_id == self.id_in_group: return player #for player in self.subsession.get_players(): #if player.id_in_group == self.match_id: #return player return self.group.get_player_by_id(self.match_id) #return self def matched_judge(self): if self.is_advisor(): return self.matched_estimator().matched_judge elif self.is_estimator(): return self.group.get_player_by_id(self.match_id) #for player in self.subsession.get_players(): #if player.id_in_group == self.match_id: #return player else: return self def get_big_grid_path(self): gridNum = self.get_grid_number() if gridNum == 1: self.big_grid_path = 'grids/grid1_468.png' return 'grids/grid1_468.png' elif gridNum == 2: self.big_grid_path = 'grids/grid2_448.png' return 'grids/grid2_448.png' elif gridNum == 3: self.big_grid_path = 'grids/grid3_423.png' return 'grids/grid3_423.png' else: self.big_grid_path = 'grids/grid4_422.png' return 'grids/grid4_422.png' def get_estimate(self): return self.estimate def get_grid_reward(self): #return Constants.judge_base_reward if self.is_advisor(): advDiff = self.matched_estimator().estimate - self.matched_estimator().get_correct_answer() if advDiff > 40: return c(5) elif advDiff > 10: return c(2) else: return c(0) elif self.is_estimator(): estDiff = abs(self.estimate - self.get_correct_answer()) if estDiff > 40: return c(0) elif estDiff > 10: return c(2) else: return c(5) else: return Constants.judge_base_reward def get_recommendation(self): if not self.is_advisor(): recom = self.matched_advisor().get_recommendation() if recom is None: return Constants.default_input return recom return self.recommendation def set_number_off(self): self.number_off = abs(self.estimate - self.get_correct_answer()) def set_final_compensation(self): advisor = self.matched_advisor() estimator = self.matched_estimator() if not estimator.appealed: estimator.final_compensation = estimator.get_grid_reward() + Constants.appeal_reward_split advisor.final_compensation = advisor.get_grid_reward() + Constants.appeal_reward_split else: if self.appeal_granted: estimator.final_compensation = estimator.get_grid_reward() + Constants.appeal_reward advisor.final_compensation = advisor.get_grid_reward() else: estimator.final_compensation = estimator.grid_reward + Constants.appeal_reward_split - Constants.appeal_cost advisor.final_compensation = advisor.get_grid_reward() + Constants.appeal_reward_split if self.is_judge(): self.grid_reward = Constants.judge_base_reward