from otree.api import * import random import itertools import json from collections import Counter import ast # Necessary to parse the string list from checkboxes doc = """ Your app description """ class C(BaseConstants): NAME_IN_URL = 'contingent_valuation' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 BID_PAIRS = list(range(1, 7)) # 1 to 6 BID_ORDERS = ['upper_first', 'lower_first'] class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): bid_pair = models.IntegerField() bid_order = models.StringField() monthly_increase_1 = models.IntegerField() monthly_increase_2 = models.IntegerField() percentage_increase_1 = models.IntegerField() percentage_increase_2 = models.IntegerField() res_knowledge = models.StringField( choices=[ [1, "Noch nie davon gehört"], [2, "Nur selten/ am Rande davon gehört"], [3, "Davon gehört aber kenne mich nicht aus"], [4, "Kenne mich grundlegend damit aus"], [5, "Kenne mich sehr gut damit aus"] ], label="Haben Sie sich vor dieser Befragung schon einmal mit dem Thema Energiesicherheit beschäftigt?", widget=widgets.RadioSelect ) res_interest = models.StringField( choices=[ [1, "Ja"], [2, "Vielleicht"], [3, "Nein"] ], label="Haben Sie grundsätzlich Interesse am Thema Energiesicherheit? " "(Mit dieser Frage wollen wir überprüfen, wie genau Sie die Fragen lesen. " "Bitte wählen Sie als Antwort „Vielleicht“ aus, unabhängig von Ihrem tatsächlichen Interesse.) ", widget=widgets.RadioSelect ) failed_attention_check = models.BooleanField(initial=False) support = models.IntegerField( choices=[ [1, "Lehne ich stark ab"], [2, "Lehne ich eher ab"], [3, "Weder noch"], [4, "Befürworte ich eher"], [5, "Befürworte ich stark"], ], label="Bitte geben Sie an, ob Sie Maßnahmen zur Stärkung der Resilienz des deutschen Energiesystems eher befürworten oder ablehnen.", widget=widgets.RadioSelect ) R1 = models.IntegerField( choices=[ [1, "Ja"], [0, "Nein"] ], label="", widget=widgets.RadioSelect ) R2 = models.IntegerField( choices=[ [1, "Ja"], [0, "Nein"] ], label="", widget=widgets.RadioSelect ) S = models.IntegerField( choices=[ [1, "Ja"], [0, "Nein"] ], label="Wären Sie grundsätzlich bereit, für ein resilienteres Energiesystem irgendeine Strompreiserhöhung in Kauf zu nehmen?", widget=widgets.RadioSelect ) why = models.LongStringField( label="Sie haben angegeben, dass Sie grundsätzlich bereit sind, zusätzliche Kosten für ein resilienteres Energiesystem zu zahlen. " "Warum sind Sie dazu bereit?", blank=True, ) why_dk = models.BooleanField( label="Keine Angabe", blank=True ) why_not = models.StringField( label="Aus welchen Gründen sind Sie nicht bereit, zusätzliche Kosten für ein resilienteres Energiesystem zu tragen? " "Bitte wählen Sie alle zutreffenden Gründe aus.", blank=True ) why_not_other = models.StringField( blank=True, label="Bitte geben Sie andere Gründe an." ) gas_crisis = models.IntegerField( choices=[ [1, "Überhaupt nicht beeinflusst"], [2, "Eher nicht beeinflusst"], [3, "Weder noch"], [4, "Eher beeinflusst"], [5, "Stark beeinflusst"] ], label="Das Risiko von Energieabhängigkeit hat sich während der Gaskrise der Jahre 2022/ 2023 sowie während des Krieges im Nahen Osten gezeigt. " "Wie stark haben diese Erfahrungen Ihre Antwort beeinflusst?", widget=widgets.RadioSelect ) def creating_session(subsession: Subsession): players = subsession.get_players() # All combinations of main group × bid pair × bid order combinations = list(itertools.product(C.BID_PAIRS, C.BID_ORDERS)) # Repeat pattern enough times to cover all players repeats_needed = -(-len(players) // len(combinations)) # ceil division assignment_pool = combinations * repeats_needed # Shuffle while keeping full coverage random.shuffle(assignment_pool) # Assign for player, (bid_pair, bid_order) in zip(players, assignment_pool): player.bid_pair = bid_pair player.bid_order = bid_order # --- Debug summary --- counts_bid_pair = Counter([p.bid_pair for p in players]) counts_bid_order = Counter([p.bid_order for p in players]) # --- Debug summary (nested) --- print("\n=== DEBUG: Randomization Tree ===") for bp in sorted(set(C.BID_PAIRS)): bp_players = [p for p in players if p.bid_pair == bp] if bp_players: print(f" {bp}: {len(bp_players)} players") for bo in sorted(set(C.BID_ORDERS)): bo_players = [p for p in bp_players if p.bid_order == bo] if bo_players: print(f" {bo}: {len(bo_players)} players") print("==================================\n") # PAGES class Res_Intro(Page): form_model = "player" class Res_Knowledge(Page): form_model = "player" form_fields = ["res_knowledge"] class Res_Interest(Page): form_model = "player" form_fields = ["res_interest"] def before_next_page(player, timeout_happened): # The correct answer is "2" (Vielleicht) if player.res_interest != "2": player.failed_attention_check = True class RedirectFailed(Page): @staticmethod def is_displayed(player): # Only show this page if they failed return player.failed_attention_check class Support(Page): form_model = "player" form_fields = ["support"] class CV1(Page): form_model = "player" form_fields = ["R1"] def vars_for_template(player): # Example bid pairs and their base (lower) bid values BID_VALUES = { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, } base_bid = BID_VALUES.get(player.bid_pair, 0) # Show bid depending on order if player.bid_order == "lower_first": bid_to_show = base_bid else: # UpperFirst bid_to_show = base_bid + 1 # Retrieve stored yearly consumption yearly_consumption = player.participant.vars.get('energy_cons', 0) yearly_consumption_dk = player.participant.vars.get('energy_cons_dk', 0) energy_price = player.participant.vars.get('energy_price', 0) energy_price_dk = player.participant.vars.get('energy_price_dk', 0) if yearly_consumption_dk == False: # Calculate yearly and monthly costs yearly_cost = (yearly_consumption * bid_to_show) / 100 # Cent to Euro monthly_cost = yearly_cost / 12 else: # Calculate consumption based on devices device_washing_machine = player.participant.vars.get('device_washing_machine', 0) device_dryer = player.participant.vars.get('device_dryer', 0) device_oven = player.participant.vars.get('device_oven', 0) device_tv = player.participant.vars.get('device_tv', 0) device_ev = player.participant.vars.get('device_ev', 0) device_heat_pump = player.participant.vars.get('device_heat_pump', 0) device_computer = player.participant.vars.get('device_computer', 0) device_dishwasher = player.participant.vars.get('device_dishwasher', 0) device_microwave = player.participant.vars.get('device_microwave', 0) device_water_heater = player.participant.vars.get('device_water_heater', 0) household_size = player.participant.vars.get('household_size', 0) living_size = player.participant.vars.get('living_size', 0) yearly_consumption = household_size * 200 + living_size * 9 + device_washing_machine * 200 + \ device_dryer * 200 + device_oven * 200 + device_tv * 200 + device_ev * 200 + \ device_heat_pump * 200 + device_computer * 200 + device_dishwasher * 200 + \ device_microwave * 200 + device_water_heater * 200 yearly_cost = (yearly_consumption * bid_to_show) / 100 # Cent to Euro monthly_cost = yearly_cost / 12 if energy_price_dk == False: percentage = (energy_price + bid_to_show)/energy_price - 1 else: percentage = (37 + bid_to_show)/37 - 1 player.monthly_increase_1 = max(round(monthly_cost), 1) player.percentage_increase_1 = round(percentage * 100) info_text = f"
Wären Sie zukünftig bereit eine Strompreiserhöhung von {bid_to_show} Cent/kWh in Kauf zu nehmen, wenn dadurch ein resilienteres Energiesystem gesichert würde? " \ f"Basierend auf Ihren vorherigen Angaben entspricht dies einer Preiserhöhung von {round(percentage * 100)}% und würde für Ihren Haushalt zusätzliche monatliche Kosten von ungefähr {max(round(monthly_cost), 1)}€ bedeuten.
" \ f"" \ f"(Falls Sie im vorherigen Abschnitt keine Angaben zu Ihrem Verbrauch gemacht haben, wurde ihr Energieverbrauch basierend auf Ihren genutzten Elektrogeräten geschätzt und mit dem durchschnittlichen Strompreis deutscher Haushalte kombiniert.)" \ f"
" return dict( info_text=info_text, bid=bid_to_show, ) class CV2(Page): # Another page for bid_order == upper_first & answer == no # Define as needed form_model = "player" form_fields = ["R2"] def vars_for_template(player: Player): BID_VALUES = { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, } base_bid = BID_VALUES.get(player.bid_pair, 0) if player.bid_order == "upper_first": bid_to_show = base_bid else: bid_to_show = base_bid + 1 # Retrieve stored yearly consumption yearly_consumption = player.participant.vars.get('energy_cons', 0) yearly_consumption_dk = player.participant.vars.get('energy_cons_dk', 0) energy_price = player.participant.vars.get('energy_price', 0) energy_price_dk = player.participant.vars.get('energy_price_dk', 0) if yearly_consumption_dk == False: # Calculate yearly and monthly costs yearly_cost = (yearly_consumption * bid_to_show) / 100 # Cent to Euro monthly_cost = yearly_cost / 12 else: # Calculate consumption based on devices device_washing_machine = player.participant.vars.get('device_washing_machine', 0) device_dryer = player.participant.vars.get('device_dryer', 0) device_oven = player.participant.vars.get('device_oven', 0) device_tv = player.participant.vars.get('device_tv', 0) device_ev = player.participant.vars.get('device_ev', 0) device_heat_pump = player.participant.vars.get('device_heat_pump', 0) device_computer = player.participant.vars.get('device_computer', 0) device_dishwasher = player.participant.vars.get('device_dishwasher', 0) device_microwave = player.participant.vars.get('device_microwave', 0) device_water_heater = player.participant.vars.get('device_water_heater', 0) household_size = player.participant.vars.get('household_size', 0) living_size = player.participant.vars.get('living_size', 0) yearly_consumption = household_size * 200 + living_size * 9 + device_washing_machine * 200 + \ device_dryer * 200 + device_oven * 200 + device_tv * 200 + device_ev * 200 + \ device_heat_pump * 200 + device_computer * 200 + device_dishwasher * 200 + \ device_microwave * 200 + device_water_heater * 200 yearly_cost = (yearly_consumption * bid_to_show) / 100 # Cent to Euro monthly_cost = yearly_cost / 12 if energy_price_dk == False: percentage = (energy_price + bid_to_show)/energy_price - 1 else: percentage = (37 + bid_to_show)/37 - 1 player.monthly_increase_2 = max(round(monthly_cost), 1) player.percentage_increase_2 = round(percentage * 100) info_text = f"
" \ f"Wären Sie bereit eine Strompreiserhöhung von {bid_to_show} Cent/kWh in Kauf zu nehmen? " \ f"Basierend auf Ihren vorherigen Angaben entspricht dies einer Preiserhöhung von {round(percentage * 100)}% und würde für Ihren Haushalt zusätzliche monatliche Kosten von ungefähr {max(round(monthly_cost), 1)}€ bedeuten." \ f"
" return dict( info_text=info_text, bid=bid_to_show, ) @staticmethod def is_displayed(player: Player): if player.bid_order == "upper_first": if player.R1 == 1: return False else: return True else: if player.R1 == 1: return True else: return False class At_all(Page): # Page shown if bid_order == lower_first & answer == no form_model = "player" form_fields = ["S"] @staticmethod def is_displayed(player: Player): if player.bid_order == "upper_first": if player.R1 == 1: return False else: if player.R2 == 1: return False else: return True else: if player.R1 == 1: return False else: return True class Why(Page): # Page shown if bid_order == upper_first & answer == yes form_model = "player" form_fields = ["why", "why_dk"] def error_message(self, values): if not values['why'] and not values['why_dk']: return "Wenn Sie keine Gründe angeben möchten, wählen Sie bitte \"Keine Angabe\" aus." @staticmethod def is_displayed(player: Player): if player.bid_order == "upper_first": if player.R1 == 1: return True else: if player.R2 == 1: return True else: if player.S == 1: return True else: return False else: if player.R1 == 1: return True else: if player.S == 1: return True else: return False class Why_not(Page): # Page shown if bid_order == lower_first & answer == no form_model = "player" form_fields = ["why_not", "why_not_other"] def error_message(self, values): # In this version, why_not is already a string like "opt1, opt2" if not values['why_not']: return "Bitte wählen Sie mindestens eine Option aus." if "anderes" in values['why_not'] and not values['why_not_other']: return "Bitte geben Sie andere Gründe an." @staticmethod def is_displayed(player: Player): if player.bid_order == "upper_first": if player.R1 == 1: return False else: if player.R2 == 1: return False else: if player.S == 1: return False else: return True else: if player.R1 == 1: return False else: if player.S == 1: return False else: return True class Gas_Crisis(Page): form_model = "player" form_fields = ["gas_crisis"] class Conjoint_Intro(Page): form_model = "player" page_sequence = [Res_Intro, Res_Knowledge, Res_Interest, RedirectFailed, Support, CV1, CV2, At_all, Why, Why_not, Gas_Crisis] #page_sequence = [Res_Intro]