import random from decimal import Decimal, ROUND_HALF_UP import math from otree.api import * #from openai import OpenAI from otree.models import session, subsession doc = """ Your app description """ class C(BaseConstants): NAME_IN_URL = 'Uncertainty_Group_BC_FI_new' PLAYERS_PER_GROUP = 12 NUM_ROUNDS = 11 class Subsession(BaseSubsession): pass class Group(BaseGroup): clear = models.BooleanField() average_price = models.IntegerField() optimization = models.BooleanField() class Player(BasePlayer): # pictures = models.BooleanField() necessary for explanation picture? # if comprehension test is needed, see steph # constants omega_current = models.IntegerField(default = 40) omega_pre = models.IntegerField(default = 40) omega_small = models.IntegerField(default = 20) omega_large = models.IntegerField(default = 60) omega_average = models.IntegerField(default = 40) cost_deduction = models.IntegerField(default = 10) price_aim_parameter_pre = models.IntegerField(default = 15) price_aim_parameter_post = models.IntegerField(default = 25) price_aim_parameter_current = models.IntegerField(default = 15) price_aim_parameter_small = models.IntegerField(default=5) maximum_price = models.IntegerField() shock = models.IntegerField(default=6) shock_announcement = models.BooleanField() display_shock = models.BooleanField() explanation = models.LongStringField(label="Begründung:",max_length=2000) optimization_instruction = models.BooleanField() optimization = models.BooleanField() clear = models.BooleanField() clear_instruction = models.BooleanField() subgroup_id = models.IntegerField() sitzplatznummer = models.IntegerField() # player variables price = models.IntegerField(label= "",min=10) output = models.IntegerField() unit_costs = models.IntegerField() total_costs = models.IntegerField() revenue = models.IntegerField() earnings = models.IntegerField(min=0) per_piece_earnings = models.IntegerField() price_aim = models.IntegerField() #observer values is_observer = models.BooleanField(initial=False) observer_choice_id = models.IntegerField(blank=True) observer_guess = models.IntegerField(blank=True) pre_omega = models.IntegerField() pre_average_price = models.IntegerField() pre_price_aim = models.IntegerField() pre_price_aim_parameter = models.IntegerField() # variables for payoffs payoff_round = models.IntegerField(initial=0) # if I don't round, then it needs to be a float payoff_acc = models.IntegerField(initial=0) being_chosen = models.BooleanField(initial=False) bonus_being_chosen = models.CurrencyField(default = 0.5) times_chosen = models.IntegerField(initial=0) bonus_being_chosen_cum = models.CurrencyField(initial=0) exchange_rate = models.IntegerField(default=100) show_up_fee = models.CurrencyField(default=5) payoff_mon = models.CurrencyField(initial=0) payoff_mon_final = models.CurrencyField(initial=0) payoff_mon_final_otree = models.CurrencyField(initial=0) # Questionnaire Question_1 = models.IntegerField( choices=[[1, ("Die Produktion erfolgt automatisch in Höhe der Nachfrage.")], [2, ("Die Produktion wird zufällig bestimmt.")], [3, ("Die Produktion hängt vom Durchschnittspreis ab.")]], label="Frage 1: Wie wird die Höhe Ihrer Produktion bestimmt?", widget=widgets.RadioSelect) Question_2 = models.IntegerField( choices=[[1, "Je höher der Preis p, desto höher die Nachfrage y."], [2, "Je höher der Preis p, desto niedriger die Nachfrage y."], [3, "Je niedriger der Preis p, desto niedriger die Nachfrage y."]], label="Frage 2: Welche Aussage über Ihre Nachfrage y und Ihren Preis p ist richtig?", widget=widgets.RadioSelect) Question_3 = models.IntegerField( choices=[[1, ("Je höher der exogene Faktor ω, desto höher die Nachfrage y.")], [2, ("Je höher der exogene Faktor ω, desto niedriger die Nachfrage y.")], [3, ("Je niedriger der exogene Faktor ω, desto höher die Nachfrage y.")]], label="Frage 3: Welche Aussage über Ihre Nachfrage und den exogenen Faktor ω ist korrekt?", widget=widgets.RadioSelect) Question_4 = models.IntegerField( choices=[[1, "Der Preis des eigenen Produkts."], [2, "Der Durchschnittspreis aller Produzenten über alle Runden hinweg."], [3, "Der Durchschnittspreis aller Produzenten in der jeweiligen Runde."]], label="Frage 4: Welche der folgenden Variablen hat einen Einfluss auf die Kosten pro Stück Ihres Produkts?", widget=widgets.RadioSelect) Question_5 = models.IntegerField( choices=[[1, "... werden durch die Preissetzung aller Produzenten bestimmt und sind daher zu Beginn jeder Runde unbekannt."], [2, "... sind zu Beginn jeder Runde bekannt."], [3, "... resultieren aus dem von mir festgelegten Preis."]], label="Frage 5: Die Produktionskosten ...", widget=widgets.RadioSelect) Question_6 = models.IntegerField( choices=[[1, "An einen anderen zufällig ausgewählten Produzenten."], [2, "An einen zufällig ausgewählten Beobachter."], [3, "An alle Beobachter."]], label="Frage 6: An wen wird Ihr Preis und gegebenenfalls Ihre Begründung weitergeleitet?", widget=widgets.RadioSelect) Question_7 = models.IntegerField( choices=[[1, "Die Bonuszahlung wird zufällig bestimmt."], [2, "Die Bonuszahlung hängt von der Länge der Begründung ab."], [3, "Sie erhalten eine Bonuszahlung, wenn der Beobachter den Preis auswählt, den Sie mit Ihrer Begründung zusammen abgeschickt haben."]], label="Frage 7: Wie wird die Bonuszahlung für Ihre Begründung bestimmt?", widget=widgets.RadioSelect) Q1_first_attempt = models.IntegerField(blank=True) Q2_first_attempt = models.IntegerField(blank=True) Q3_first_attempt = models.IntegerField(blank=True) Q4_first_attempt = models.IntegerField(blank=True) Q5_first_attempt = models.IntegerField(blank=True) Q6_first_attempt = models.IntegerField(blank=True) Q7_first_attempt = models.IntegerField(blank=True) Question_1_Obs = models.IntegerField( choices=[[1, "Ich wähle frei eine Zahl zwischen 10 und 40."], [2, "Ich wähle keinen Preis aus."], [3, "Ich wähle einen Preis aus den beiden Preisen aus, die mir zugeschickt wurden."]], label="Frage 1: Wie wählen Sie in jeder Runde einen Preis aus?", widget=widgets.RadioSelect) Question_2_Obs = models.IntegerField( choices=[[1, ("Mein Gewinn wird durch den Preis, den ich ausgewählt habe, bestimmt.")], [2, ("Mein Gewinn wird durch den Durchschnitt der beiden Preise, die mir zugeschickt wurden, bestimmt.")], [3, ( "Mein Gewinn entspricht dem Durchschnittspreis.")]], label="Frage 2: Wie berechnet sich Ihr Gewinn?", widget = widgets.RadioSelect) Question_3_Obs = models.IntegerField( choices=[[1, "Je höher der Preis p, desto höher die Nachfrage y."], [2, "Je höher der Preis p, desto niedriger die Nachfrage y."], [3, "Je niedriger der Preis p, desto niedriger die Nachfrage y."]], label="Frage 3: Welche Aussage über Ihre Nachfrage y und Ihren Preis p ist richtig?", widget=widgets.RadioSelect) Question_4_Obs = models.IntegerField( choices=[[1, ("Je höher der exogene Faktor ω, desto höher die Nachfrage y.")], [2, ("Je höher der exogene Faktor ω, desto niedriger die Nachfrage y.")], [3, ("Je niedriger der exogene Faktor ω, desto höher die Nachfrage y.")]], label="Frage 4: Welche Aussage über Ihre Nachfrage und den exogenen Faktor ω ist korrekt?", widget=widgets.RadioSelect) Question_5_Obs = models.IntegerField( choices=[[1, "Der Preis des eigenen Produkts."], [2, "Der Durchschnittspreis aller Produzenten über alle Runden hinweg."], [3, "Der Durchschnittspreis aller Produzenten in der jeweiligen Runde."]], label="Frage 5: Welche der folgenden Variablen hat einen Einfluss auf die Kosten pro Stück Ihres Produkts?", widget=widgets.RadioSelect) Question_6_Obs = models.IntegerField( choices=[[1, "... werden durch die Preissetzung aller Produzenten bestimmt und sind daher zu Beginn jeder Runde unbekannt."], [2, "... sind zu Beginn jeder Runde bekannt."], [3, "... resultieren aus dem von mir festgelegten Preis."]], label="Frage 6: Die Produktionskosten ...", widget=widgets.RadioSelect) Q1_Obs_first_attempt = models.IntegerField(blank=True) Q2_Obs_first_attempt = models.IntegerField(blank=True) Q3_Obs_first_attempt = models.IntegerField(blank=True) Q4_Obs_first_attempt = models.IntegerField(blank=True) Q5_Obs_first_attempt = models.IntegerField(blank=True) Q6_Obs_first_attempt = models.IntegerField(blank=True) #BC questions Question_1_BC = models.IntegerField( choices=[[1, ("Je näher mein Preis p am Zielpreis z liegt, desto höher mein Gewinn.")], [2, ("Je näher mein Preis p am Zielpreis z liegt, desto niedriger mein Gewinn.")], [3, ("Es gibt nur einen Gewinn, wenn der Preis p exakt dem Zielpreis z entspricht.")]], label=("Frage 1: Welche Aussage über Ihren Gewinn ist korrekt?"), widget=widgets.RadioSelect) Question_2_BC = models.IntegerField( choices=[[1, ("Je höher der Durchschnittspreis P, desto höher der Zielpreis z.")], [2, ("Je höher der Durchschnittspreis P, desto niedriger der Zielpreis z.")], [3, ("Der Zielpreis z ist unabhängig vom Durchschnittspreis P.")]], label=("Frage 2: Welche Aussage über das Verhältnis zwischen dem Zielpreis z und dem Durchschnittspreis P ist korrekt?"), widget=widgets.RadioSelect) Question_3_BC = models.IntegerField( choices=[ [1, ("Der Zielpreis z liegt 15 Taler unter der Hälfte des Durchschnittspreises P.")], [2, ("Der Zielpreis z liegt 15 Taler über der Hälfte des Durchschnittspreises P.")], [3, ("Der Zielpreis z liegt 15 Taler über dem Durchschnittspreis P.")]], label=("Frage 3: Wie berechnet sich Ihr Zielpreis z?"), widget=widgets.RadioSelect, allow_html = True ) Question_4_BC = models.IntegerField( choices=[[1, "… wird durch die Preissetzung aller Produzenten bestimmt und ist daher zu Beginn jeder Runde unbekannt."], [2, "… ist zu Beginn jeder Runde bekannt."], [3, "… entspricht immer dem Zielpreis."]], label="Frage 4: Der Durchschnittspreis P ...", widget=widgets.RadioSelect) Question_5_BC = models.IntegerField( choices=[[1, "An einen anderen zufällig ausgewählten Produzenten."], [2, "An einen zufällig ausgewählten Beobachter."], [3, "An alle Beobachter."]], label="Frage 5: An wen wird Ihr Preis und gegebenenfalls Ihre Begründung weitergeleitet?", widget=widgets.RadioSelect) Question_6_BC = models.IntegerField( choices=[[1, "Die Bonuszahlung wird zufällig bestimmt."], [2, "Die Bonuszahlung hängt von der Länge der Begründung ab."], [3, "Sie erhalten eine Bonuszahlung, wenn der Beobachter den Preis auswählt, den Sie mit Ihrer Begründung zusammen abgeschickt haben."]], label="Frage 6: Wie wird die Bonuszahlung für Ihre Begründung bestimmt?", widget=widgets.RadioSelect) Q1_BC_first_attempt = models.IntegerField(blank=True) Q2_BC_first_attempt = models.IntegerField(blank=True) Q3_BC_first_attempt = models.IntegerField(blank=True) Q4_BC_first_attempt = models.IntegerField(blank=True) Q5_BC_first_attempt = models.IntegerField(blank=True) Q6_BC_first_attempt = models.IntegerField(blank=True) Question_1_BC_Obs = models.IntegerField( choices=[[1, "Ich wähle frei eine Zahl zwischen 10 und 40."], [2, "Ich wähle keinen Preis aus."], [3, "Ich wähle einen Preis aus den beiden Preisen aus, die mir zugeschickt wurden."]], label="Frage 1: Wie wählen Sie in jeder Runde einen Preis aus?", widget=widgets.RadioSelect) Question_2_BC_Obs = models.IntegerField( choices=[[1, ("Je näher der von mir ausgewählte Preis p am Zielpreis z liegt, desto höher mein Gewinn.")], [2, ("Je näher der von mir ausgewählte Preis p am Zielpreis z liegt, desto niedriger mein Gewinn.")], [3, ("Je näher die beiden Preise, die mir zugeschickt wurden, am Zielpreis z liegen, desto höher mein Gewinn.")]], label=("Frage 2: Wie berechnet sich Ihr Gewinn?"), widget=widgets.RadioSelect) Question_3_BC_Obs = models.IntegerField( choices=[[1, ("Je höher der Durchschnittspreis P, desto höher der Zielpreis z.")], [2, ("Je höher der Durchschnittspreis P, desto niedriger der Zielpreis z.")], [3, ("Der Zielpreis z ist unabhängig vom Durchschnittspreis P.")]], label=( "Frage 3: Welche Aussage über das Verhältnis zwischen dem Zielpreis z und dem Durchschnittspreis P ist korrekt?"), widget=widgets.RadioSelect) Question_4_BC_Obs = models.IntegerField( choices=[ [1, ("Der Zielpreis z liegt 15 Taler unter der Hälfte des Durchschnittspreises P.")], [2, ("Der Zielpreis z liegt 15 Taler über der Hälfte des Durchschnittspreises P.")], [3, ("Der Zielpreis z liegt 15 Taler über dem Durchschnittspreis P.")]], label=("Frage 4: Wie berechnet sich der Zielpreis z?"), widget=widgets.RadioSelect, allow_html=True ) Question_5_BC_Obs = models.IntegerField( choices=[ [1, "… wird durch die Preissetzung aller Produzenten bestimmt und ist daher zu Beginn jeder Runde unbekannt."], [2, "… ist zu Beginn jeder Runde bekannt."], [3, "… entspricht immer dem Zielpreis."]], label="Frage 5: Der Durchschnittspreis P ...", widget=widgets.RadioSelect) Q1_BC_Obs_first_attempt = models.IntegerField(blank=True) Q2_BC_Obs_first_attempt = models.IntegerField(blank=True) Q3_BC_Obs_first_attempt = models.IntegerField(blank=True) Q4_BC_Obs_first_attempt = models.IntegerField(blank=True) Q5_BC_Obs_first_attempt = models.IntegerField(blank=True) attempts = models.IntegerField(blank=True) bonus = models.IntegerField(blank=True) bonus_mon = models.CurrencyField(blank=True) bonus_eligible = models.BooleanField(blank=True) payoff_bonus = models.IntegerField(blank=True) #Demographics Gender = models.IntegerField( choices=[[1, "Weiblich"], [2, "Männlich"], [3, "Divers"], [4, "keine Angabe"]], label="Welchem Geschlecht fühlen Sie sich zugehörig?", widget=widgets.RadioSelect) Age = models.IntegerField(label="Wie alt sind Sie?", min=10, max=99) Faculty = models.IntegerField( choices=[[1, "Humanwissenschaften"], [2, "Geistes- und Kulturwissenschaften"], [3, "Gesellschaftswissenschaften"], [4, "Architektur - Stadtplanung - Landschaftsplanung"], [5, "Wirtschaftswissenschaften"], [6, "Mathematik und Naturwissenschaften"], [7, "Ökologische Agrarwissenschaften"], [8, "Bauingenieur- und Umweltingenieurwesen"], [9, "Maschinenbau"], [10, "Elektrotechnik/Informatik"], [11, "Kunsthochschule Kassel"], [12, "Sonstige"]], label="In welchem Fachbereich studieren Sie?", widget=widgets.RadioSelect) tech_problems = models.LongStringField(label="Haben Sie sonstige Anmerkungen oder sind Ihnen technische Fehler ausgefallen?",max_length=2000, blank=True) #Question on the explanation difficulty_explanation = models.IntegerField( choices=[[1, "Sehr einfach"], [2, "Eher einfach"], [3, "Weder einfach noch schwierig"], [4, "Eher schwierig"], [5, "Sehr schwierig"]], label="Wie schwierig fanden Sie es, während des Experiments eine Begründung für Ihren Preisvorschlag zu formulieren?", widget=widgets.RadioSelectHorizontal ) #decision data keystroke_data = models.LongStringField(blank=True) # Stores JSON keystroke data keystroke_price_data = models.LongStringField(blank=True) # same for price def price_max(player): return player.omega_current def price_min(player): return player.cost_deduction def final_price_max(player): return player.omega_current def final_price_min(player): return player.cost_deduction def price_proposal_max(player): return player.omega_current def price_proposal_min(player): return player.cost_deduction # assign treatments def creating_session(subsession): if subsession.round_number == 1: for group in subsession.get_groups(): all_players = subsession.get_players() num_observers = len(all_players) // 3 observers = all_players[:num_observers] producers = all_players[num_observers:] for player in observers: player.is_observer = True for player in producers: player.is_observer = False import itertools clear_cycle = itertools.cycle([ True, False, False, True, True]) for group in subsession.get_groups(): group.clear = next(clear_cycle) print(f"Group {group.id_in_subsession}: clear = {group.clear}") import itertools optimization_cycle = itertools.cycle([False, True, False, True, False]) for group in subsession.get_groups(): group.optimization = next(optimization_cycle) print(f"Group {group.id_in_subsession}: optimization = {group.optimization}") else: for group in subsession.get_groups(): group.clear = group.in_round(1).clear group.optimization = group.in_round(1).optimization for player in subsession.get_players(): player.is_observer = player.in_round(1).is_observer # currently this is only for a positive shock of omega --> group statt player for group in subsession.get_groups(): for p in group.get_players(): if 6<= group.round_number <=10: p.omega_current = p.omega_large p.price_aim_parameter_current = p.price_aim_parameter_post for group in subsession.get_groups(): if group.round_number==Player.shock: group.display_shock=True else: group.display_shock=False for group in subsession.get_groups(): for p in group.get_players(): p.optimization_instruction = group.optimization #print(f"Player {p.id_in_group}: optimization_instruction = {p.optimization_instruction}") for group in subsession.get_groups(): for p in group.get_players(): p.clear_instruction = group.clear #print(f"Player {p.id_in_group}: clear_instruction = {p.clear_instruction}") #now the random rematch between rounds for group in subsession.get_groups(): players = group.get_players() # split by role producers = [p for p in players if not p.is_observer] observers = [p for p in players if p.is_observer] # sanity check: we want exactly 2 producers per observer # e.g. 8 producers, 4 observers -> 4 triples assert len(producers) == 2 * len(observers), "Need 2 producers per observer!" random.shuffle(producers) random.shuffle(observers) subgroup_id = 0 for i, obs in enumerate(observers): prod1 = producers[2 * i] prod2 = producers[2 * i + 1] # assign same subgroup_id to 2 producers + 1 observer for p in (prod1, prod2, obs): p.subgroup_id = subgroup_id subgroup_id += 1 def explanation(player): return player.explanation # calculating the earnings of the producers def compute_observer_results(player: Player): """Compute earnings and monetary payoffs for a single observer in this round.""" group = player.group round_number = group.round_number # Round 1: observers don't earn anything yet if round_number == 1: player.price = 0 player.output = 0 player.revenue = 0 player.unit_costs = 0 player.total_costs = 0 player.per_piece_earnings = 0 player.earnings = 0 else: # --- Determine the guess / chosen price and underlying parameters --- # Case A: rounds with price-only info if round_number in [4, 5, 9, 10] and player.observer_guess is not None: prev_group = group.in_round(round_number - 1) pre_average_price = prev_group.average_price prev_players = prev_group.get_players() prev_producer = next(q for q in prev_players if not q.is_observer) pre_omega = prev_producer.omega_current pre_price_aim = prev_producer.price_aim pre_price_aim_parameter = prev_producer.price_aim_parameter_current player.pre_omega = pre_omega player.pre_average_price = pre_average_price player.pre_price_aim = pre_price_aim player.pre_price_aim_parameter = pre_price_aim_parameter guess = player.observer_guess # Case B: rounds with explanations & explicit choice of producer elif round_number in [2, 3, 6, 7, 8, 11] and player.observer_choice_id is not None: prev_group = group.in_round(round_number - 1) prev_players = prev_group.get_players() chosen_producer = next( q for q in prev_players if (not q.is_observer) and (q.id_in_subsession == player.observer_choice_id) ) guess = chosen_producer.price player.observer_guess = guess # store the implied guess pre_average_price = prev_group.average_price prev_producer = next(q for q in prev_players if not q.is_observer) pre_omega = prev_producer.omega_current pre_price_aim = prev_producer.price_aim pre_price_aim_parameter = prev_producer.price_aim_parameter_current player.pre_omega = pre_omega player.pre_average_price = pre_average_price player.pre_price_aim = pre_price_aim player.pre_price_aim_parameter = pre_price_aim_parameter # mark bonus for the chosen producer chosen_producer.being_chosen = True # Case C: no valid guess / choice else: player.price = 0 player.output = 0 player.revenue = 0 player.unit_costs = 0 player.total_costs = 0 player.per_piece_earnings = 0 player.earnings = 0 guess = None # just for clarity # --- Earnings calculation if we have a meaningful guess --- if guess is not None: player.output = player.pre_omega - guess if player.output < 0: player.output = 0 player.revenue = player.output * guess player.unit_costs = player.pre_average_price - player.cost_deduction player.total_costs = player.unit_costs * player.output player.per_piece_earnings = guess - player.unit_costs player.earnings = (guess - player.unit_costs) * player.output if player.earnings < 0: player.earnings = 0 # --- PAYOFF PART (for this observer only) --- player.payoff_round = player.earnings # cumulative payoff (sum over all rounds) all_rounds = player.in_all_rounds() player.payoff_acc = sum(r.payoff_round for r in all_rounds) # comprehension bonus (based on round 1) if player.in_round(1).attempts > 1: player.bonus_eligible = False player.bonus = 0 elif player.in_round(1).attempts == 1: player.bonus_eligible = True player.bonus = 200 player.bonus_mon = cu(player.bonus).to_real_world_currency(player.session) / player.exchange_rate player.payoff_mon = math.ceil( cu(player.payoff_acc).to_real_world_currency(player.session) / player.exchange_rate ) if player.payoff_mon < 0.1: player.payoff_mon = 5 # observers will normally have times_chosen == 0, but we can still define it player.times_chosen = sum(1 for r in all_rounds if r.being_chosen) player.bonus_being_chosen_cum = player.times_chosen * player.bonus_being_chosen player.payoff_mon_final = ( player.payoff_mon + player.bonus_mon + player.show_up_fee + player.bonus_being_chosen_cum ) player.payoff_mon_final_otree = ( player.payoff_mon + player.bonus_mon + player.bonus_being_chosen_cum ) if round_number == C.NUM_ROUNDS: player.payoff = player.payoff_mon_final_otree def compute_producer_and_payoffs(group: Group): """Compute producer outcomes and monetary payoffs for this round.""" players = group.get_players() producers = [p for p in players if not p.is_observer] # --- PRODUCER PART --- if group.round_number <= 10: # minimum price enforcement for p in producers: if p.price is not None and p.price < 10: p.price = 10 # average price over producers if producers: avg = sum(p.price for p in producers) / len(producers) group.average_price = int( Decimal(avg).to_integral_value(rounding=ROUND_HALF_UP) ) else: group.average_price = 0 for p in producers: p.output = p.omega_current - p.price if p.output < 0: p.output = 0 p.revenue = p.output * p.price p.unit_costs = group.average_price - p.cost_deduction p.total_costs = p.unit_costs * p.output p.per_piece_earnings = p.price - p.unit_costs p.earnings = (p.price - p.unit_costs) * p.output if p.earnings < 0: p.earnings = 0 p.price_aim = round( (p.omega_current + group.average_price - p.cost_deduction) / 2 ) else: # round 11: no new decisions group.average_price = 0 for p in producers: p.output = 0 p.revenue = 0 p.unit_costs = 0 p.total_costs = 0 p.per_piece_earnings = 0 p.earnings = 0 # --- PAYOFF PART FOR PRODUCERS ONLY --- for p in producers: p.payoff_round = p.earnings all_rounds = p.in_all_rounds() p.payoff_acc = sum(r.payoff_round for r in all_rounds) # comprehension bonus based on round 1 attempts if p.in_round(1).attempts > 1: p.bonus_eligible = False p.bonus = 0 elif p.in_round(1).attempts == 1: p.bonus_eligible = True p.bonus = 200 p.bonus_mon = cu(p.bonus).to_real_world_currency(p.session) / p.exchange_rate p.payoff_mon = math.ceil( cu(p.payoff_acc).to_real_world_currency(p.session) / p.exchange_rate ) if p.payoff_mon < 0.1: p.payoff_mon = 5 p.times_chosen = sum(1 for r in all_rounds if r.being_chosen) p.bonus_being_chosen_cum = p.times_chosen * p.bonus_being_chosen p.payoff_mon_final = math.ceil( p.payoff_mon + p.bonus_mon + p.show_up_fee + p.bonus_being_chosen_cum ) p.payoff_mon_final_otree = math.ceil( p.payoff_mon + p.bonus_mon + p.bonus_being_chosen_cum ) if group.round_number == C.NUM_ROUNDS: p.payoff = p.payoff_mon_final_otree # PAGES class Place(Page): form_model = 'player' form_fields = ['sitzplatznummer'] @staticmethod def before_next_page(player: Player, timeout_happened): player.participant.label = str(player.sitzplatznummer) @staticmethod def is_displayed(player: Player): return (player.round_number == 1) class Start(Page): @staticmethod def is_displayed(player: Player): return (player.round_number == 1) class Introduction(Page): #timeout_seconds = 240 @staticmethod def is_displayed(player: Player): return (player.round_number == 1) class Instructions(Page): #timeout_seconds = 720 @staticmethod def is_displayed(player: Player): return (player.round_number == 1) form_model = 'player' form_fields = ['Question_1', 'Question_2', 'Question_3', 'Question_4', 'attempts', 'Q1_first_attempt', 'Q2_first_attempt', 'Q3_first_attempt', 'Q4_first_attempt'] class Pre_Comprehension(Page): #timeout_seconds = 240 @staticmethod def is_displayed(player: Player): return (player.round_number == 1) class Comprehension_OT(Page): #timeout_seconds = 720 @staticmethod def is_displayed(player: Player): return (player.round_number == 1) and player.group.optimization == True and (not player.is_observer) form_model = 'player' form_fields = ['Question_1', 'Question_2', 'Question_3', 'Question_4', 'Question_5', 'Question_6', 'Question_7', 'attempts', 'Q1_first_attempt', 'Q2_first_attempt', 'Q3_first_attempt', 'Q4_first_attempt', 'Q5_first_attempt', 'Q6_first_attempt', 'Q7_first_attempt'] class Comprehension_OT_Observer(Page): #timeout_seconds = 720 @staticmethod def is_displayed(player: Player): return (player.round_number == 1) and player.group.optimization == True and player.is_observer form_model = 'player' form_fields = ['Question_1_Obs', 'Question_2_Obs', 'Question_3_Obs', 'Question_4_Obs', 'Question_5_Obs', 'Question_6_Obs', 'attempts', 'Q1_first_attempt_Obs', 'Q2_first_attempt_Obs', 'Q3_first_attempt_Obs', 'Q4_first_attempt_Obs', 'Q5_first_attempt_Obs', 'Q6_first_attempt_Obs'] class Comprehension_BC(Page): #timeout_seconds = 720 @staticmethod def is_displayed(player: Player): return (player.round_number == 1) and player.group.optimization == False and (not player.is_observer) form_model = 'player' form_fields = ['Question_1_BC', 'Question_2_BC', 'Question_3_BC', 'Question_4_BC', 'Question_5_BC','Question_6_BC', 'attempts', 'Q1_BC_first_attempt', 'Q2_BC_first_attempt', 'Q3_BC_first_attempt', 'Q4_BC_first_attempt', 'Q5_BC_first_attempt','Q6_BC_first_attempt' ] class Comprehension_BC_Observer(Page): #timeout_seconds = 720 @staticmethod def is_displayed(player: Player): return (player.round_number == 1) and player.group.optimization == False and player.is_observer form_model = 'player' form_fields = ['Question_1_BC_Obs', 'Question_2_BC_Obs', 'Question_3_BC_Obs', 'Question_4_BC_Obs', 'Question_5_BC_Obs', 'attempts', 'Q1_BC_Obs_first_attempt', 'Q2_BC_Obs_first_attempt', 'Q3_BC_Obs_first_attempt', 'Q4_BC_Obs_first_attempt', 'Q5_BC_Obs_first_attempt'] # yet no dropout marker right now. if I include it, then include it for the payoff as well class StartWait(WaitPage): @staticmethod def is_displayed(player: Player): return (player.round_number == 1) # after_all_players_arrive = 'assigned_roles' title_text = "Bitte warten Sie einen Moment." body_text = "Bitte warten Sie, bis die anderen Teilnehmenden bereit sind, um mit dem Experiment zu beginnen. " \ "Das Experiment wird in Kürze beginnen." class Price_and_Explanation(Page): @staticmethod def app_after_this_page(player, timeout_happened): if timeout_happened: return None # Stay on the same page if time runs out @staticmethod def is_displayed(player: Player): return (not player.is_observer) and player.round_number in [1,2,5,6,7,10] form_model = 'player' form_fields = ['price','explanation', 'keystroke_data', 'keystroke_price_data'] class PriceProposal(Page): @staticmethod def is_displayed(player: Player): return (not player.is_observer) and player.round_number in [3, 4, 8, 9] form_model = 'player' form_fields = ['price', 'keystroke_price_data'] class ObserverWait(WaitPage): @staticmethod def is_displayed(player: Player): return player.is_observer and player.round_number == 1 body_text = "Bitte warten Sie, bis die Produzenten ihre Entscheidung getroffen haben." class ObserverWithExplanation(Page): @staticmethod def is_displayed(player: Player): return player.is_observer and player.round_number in [2, 3, 6, 7, 8, 11] @staticmethod def vars_for_template(player: Player): prev_round = player.round_number - 1 prev_me = player.in_round(prev_round) prev_group = prev_me.group prev_producers = [ q for q in prev_group.get_players() if (q.subgroup_id == prev_me.subgroup_id) and (not q.is_observer) ] options = [ dict( producer_id =q.id_in_subsession, price=q.price, explanation=q.explanation, ) for q in prev_producers ] return dict( options=options, prev_round=prev_round, ) form_model = 'player' form_fields = ['observer_choice_id'] @staticmethod def before_next_page(player: Player, timeout_happened): compute_observer_results(player) class ObserverWithoutExplanation(Page): @staticmethod def is_displayed(player: Player): return player.is_observer and player.round_number in [4, 5, 9, 10] @staticmethod def vars_for_template(player: Player): prev_round = player.round_number - 1 prev_me = player.in_round(prev_round) prev_group = prev_me.group prev_producers = [ q for q in prev_group.get_players() if (q.subgroup_id == prev_me.subgroup_id) and (not q.is_observer) ] prices = [q.price for q in prev_producers] options = [ dict( producer_id=q.id_in_subsession, price=q.price, ) for q in prev_producers ] return dict( options=options, prev_round=prev_round, ) form_model = 'player' form_fields = ['observer_guess'] @staticmethod def before_next_page(player: Player, timeout_happened): compute_observer_results(player) class ResultsWaitProducers(WaitPage): after_all_players_arrive = 'compute_producer_and_payoffs' @staticmethod def is_displayed(player: Player): # only producers wait here return not player.is_observer title_text = "Bitte warten Sie einen Moment." body_text = "Bitte warten Sie kurz bis alle Produzenten eine Entscheidung getroffen haben. " \ "Es geht in Kürze weiter." class ResultsWaitObservers(WaitPage): @staticmethod def is_displayed(player: Player): # only observers wait here return player.is_observer and player.round_number > 1 title_text = "Bitte warten Sie einen Moment." body_text = "Bitte warten Sie kurz, bis die Produzenten ihre Entscheidung getroffen haben. " \ "Es geht in Kürze weiter." class ResultsFeedbackProducers(Page): @staticmethod def is_displayed(player: Player): return (player.round_number <= 10) and (not player.is_observer) class ResultsFeedbackObservers(Page): @staticmethod def is_displayed(player: Player): return (player.round_number >= 2) and player.is_observer @staticmethod def vars_for_template(player: Player): prev_round = player.round_number - 1 return dict( prev_round=prev_round, ) class ShockAnnouncementProducer(Page): @staticmethod def is_displayed(player: Player): if not player.is_observer: return player.round_number == player.shock class ShockAnnouncementObserver(Page): @staticmethod def is_displayed(player: Player): if player.is_observer: return player.round_number == player.shock + 1 class ProducerWait(Page): @staticmethod def is_displayed(player: Player): return (not player.is_observer) and player.round_number == 11 class FinalResults(Page): @staticmethod def is_displayed(player: Player): return (player.round_number == 11) class Demographics(Page): @staticmethod def is_displayed(player: Player): return (player.round_number == 11) form_model = 'player' form_fields = ['Gender', 'Age', 'Faculty', 'tech_problems'] class FinalPage(Page): @staticmethod def is_displayed(player: Player): return (player.round_number == 11) page_sequence = [Place, Start, Pre_Comprehension, Comprehension_OT, Comprehension_OT_Observer, Comprehension_BC, Comprehension_BC_Observer, StartWait, ShockAnnouncementProducer, ShockAnnouncementObserver, Price_and_Explanation, PriceProposal, ObserverWait, ObserverWithExplanation, ObserverWithoutExplanation, ResultsWaitProducers, ResultsFeedbackProducers, ResultsFeedbackObservers, ResultsWaitObservers, FinalResults, Demographics, FinalPage]