from otree.api import * import random doc = """ Beliefs elicitation: Q1–Q13, one randomly selected for payment. """ class C(BaseConstants): NAME_IN_URL = 'BeliefsElicitation' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 QUESTIONS_COUNT = 13 # Participation bonus is handled elsewhere in settings; keep 0 here GUARANTEED_PAYMENT = cu(0) PRIZE = cu(5) # Bounds for Q1–Q9 MIN_PUZZLES = 0 MAX_PUZZLES = 60 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): # Q1–Q9: precise performance (integers) p1 = models.IntegerField(min=C.MIN_PUZZLES, max=C.MAX_PUZZLES) p2 = models.IntegerField(min=C.MIN_PUZZLES, max=C.MAX_PUZZLES) p3 = models.IntegerField(min=C.MIN_PUZZLES, max=C.MAX_PUZZLES) p4 = models.IntegerField(min=C.MIN_PUZZLES, max=C.MAX_PUZZLES) p5 = models.IntegerField(min=C.MIN_PUZZLES, max=C.MAX_PUZZLES) p6 = models.IntegerField(min=C.MIN_PUZZLES, max=C.MAX_PUZZLES) p7 = models.IntegerField(min=C.MIN_PUZZLES, max=C.MAX_PUZZLES) p8 = models.IntegerField(min=C.MIN_PUZZLES, max=C.MAX_PUZZLES) p9 = models.IntegerField(min=C.MIN_PUZZLES, max=C.MAX_PUZZLES) # Q10–Q12: probabilities (0..100) p10 = models.IntegerField( min=0, max=30, label="Combien d’exercices de logique cette personne a-t-elle résolus correctement au total lors de la Partie 1 ?", ) p11 = models.IntegerField( min=0, max=30, label="Combien d’exercices de logique cette personne a-t-elle résolus correctement au total lors des 5 premières minutes de la Partie 2 ?", ) p12 = models.IntegerField( min=0, max=30, label="Combien d'exercices de logique cette personne a-t-elle résolus correctement au total lors des 5 dernières minutes de la Partie 2 ?", ) p13 = models.IntegerField( min=0, max=8, label="

Ces 9 personnes ont été classées selon leur nombre total d'exercices résolus. " "Deux classements distincts ont été construits:" "

" "

" "

Selon vous, la personne tirée au sort a changé de combien de rangs entre ces deux classements ?

", ) # Which target was drawn for this participant (export-friendly) target_index = models.IntegerField(initial=-1) # Store realized outcomes for Q10–Q12 (export-friendly) target_outcome10 = models.IntegerField() target_outcome11 = models.IntegerField() target_outcome12 = models.IntegerField() target_outcome13 = models.IntegerField() # Payment bookkeeping paid_question_index = models.IntegerField() # 1..12 paid_prediction_value = models.FloatField() paid_actual_value = models.FloatField() # Q1–Q9: true performance; Q10–Q12: outcome 0/1 belief_bonus = models.CurrencyField() guaranteed_payment = models.CurrencyField() part3_total_payment = models.CurrencyField() def get_answer_by_question_index(player: Player, qidx: int) -> float: if 1 <= qidx <= 13: return float(getattr(player, f"p{qidx}")) raise ValueError("qidx out of range") def pay_exact(pred: float, actual: float) -> Currency: return C.PRIZE if int(pred) == int(actual) else cu(0) def get_or_set_target_for_participant(player: Player) -> dict: """ Draw one target per participant (stored in participant.vars) and return its dict. Expects session.config['targets'] to be a list of dicts with keys: first5, middle5, last5, rank_middle, rank_last """ targets = player.session.config.get("targets") if not targets or not isinstance(targets, list): raise RuntimeError( "Missing session.config['targets']. Expected a non-empty list of target dicts." ) pv = player.participant.vars if "target_index" not in pv: pv["target_index"] = random.randrange(len(targets)) idx = int(pv["target_index"]) if idx < 0 or idx >= len(targets): raise RuntimeError( "participant.vars['target_index'] out of range for session.config['targets']." ) t = targets[idx] required = ["first5", "middle5", "last5", "rank_middle", "rank_last"] missing = [k for k in required if k not in t] if missing: raise RuntimeError(f"Target dict at index {idx} missing keys: {missing}") # store for export visibility player.target_index = idx return t class Instructions(Page): @staticmethod def vars_for_template(player: Player): return dict( bonus_participation=player.session.config.get('part_participation_bonus', {}).get('part3', 0), ) class Beliefs1(Page): # Template should be at BeliefsElicitation/Beliefs1.html form_model = "player" form_fields = ["p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9"] @staticmethod def error_message(player: Player, values): # Bounds check (custom message) lo, hi = C.MIN_PUZZLES, C.MAX_PUZZLES for i in range(1, 10): v = values.get(f"p{i}") if v is None: return f"Merci de répondre à la question {i}." if v < lo or v > hi: return ( f"Question {i}: merci d'indiquer un nombre entre {lo} et {hi}." ) # Existing monotonicity check b = [values[f"p{i}"] for i in range(1, 10)] for i in range(8): if b[i] < b[i + 1]: return ( "Vos réponses aux questions 1 à 9 doivent être classées de la plus élevée à la plus faible " "(Q1 ≥ Q2 ≥ ... ≥ Q9). Merci de corriger." ) class Beliefs2(Page): form_model = "player" form_fields = ["p10", "p11", "p12", "p13"] @staticmethod def error_message(player: Player, values): # Bounds check (custom message) for p in ["p10", "p11", "p12", "p13"]: v = values.get(p) num = {"p10": 10, "p11": 11, "p12": 12, "p13": 13}[p] if v is None: return "Merci de répondre à toutes les questions." if {num} == 13: if v < 0 or v > 8: return f"Question {num}: merci d'indiquer un nombre entre 0 et 8." else: if v < 0 or v > 30: return f"Question {num}: merci d'indiquer un nombre entre 0 et 30." @staticmethod def before_next_page(player: Player, timeout_happened): # Truth for Q1–Q9 opp_true = player.session.config.get("opponent_true_values") if not opp_true or len(opp_true) != 9: raise RuntimeError( "Expected session.config['opponent_true_values'] length 9 for Q1–Q9." ) # Participant-specific target for Q10–Q12 t = get_or_set_target_for_participant(player) first5 = int(t["first5"]) middle5 = int(t["middle5"]) last5 = int(t["last5"]) rank_middle = int(t["rank_middle"]) rank_last = int(t["rank_last"]) outcome13_change_rank = abs(rank_middle - rank_last) # Save outcomes for export player.target_outcome10 = first5 player.target_outcome11 = middle5 player.target_outcome12 = last5 player.target_outcome13 = outcome13_change_rank true_values = list(opp_true) + [ first5, middle5, last5, outcome13_change_rank, ] qidx = random.randint(1, C.QUESTIONS_COUNT) pred = get_answer_by_question_index(player, qidx) actual = float(true_values[qidx - 1]) bonus = pay_exact(pred, actual) total = C.GUARANTEED_PAYMENT + bonus player.paid_question_index = qidx player.paid_prediction_value = float(pred) player.paid_actual_value = float(actual) player.belief_bonus = bonus player.guaranteed_payment = C.GUARANTEED_PAYMENT player.part3_total_payment = total player.participant.vars["part3_payoff"] = float(total) player.payoff += total page_sequence = [Instructions, Beliefs1, Beliefs2]