from otree.api import * import random class C(BaseConstants): NAME_IN_URL = 'DCE' PLAYERS_PER_GROUP = None NUM_ROUNDS = 6 CAR = 1 EBIKE = 2 BIKE = 3 WALK = 4 DIST_OPTIONS = ["1 km", "3 km", "5 km", "12 km", "18 km"] WEATHER_OPTIONS = ["Sonnig", "Bewölkt", "Leichter Regen"] DETOUR_OPTIONS = ["+ 1 km", "+ 3 km", "+ 5 km"] BIKELANE_OPTIONS = [ "Separat abgegrenzter Fahrradweg", "Fahrradstreifen direkt neben Hauptstrasse", "Fahren im gemischten Verkehr", ] LUGGAGE_OPTIONS = ["Kein Gepäck", "Rucksack"] PARK_COST_OPTIONS = ["Gratis", "6 €", "12 €"] PARK_AVAL_OPTIONS = ["Ausreichend Parkplätze", "Begrenzte Parkplätze", "Wenige Parkplätze"] BIKE_COST_OPTIONS = ["Gratis", "6 € pro E-Bike", "12 € pro E-Bike", "24 € pro E-Bike"] BIKE_CONVENIENCE_OPTIONS = ["Keine Registrierung/App erforderlich", "Registrierung an der Rezeption
(Formular ausfüllen, Ausweis vorzeigen)", "Registrierung über eine App erforderlich
(App herunterladen und registrieren)"] BIKE_RETURN_OPTIONS = ["Rückgabe nur bei der Unterkunft", "Rückgabe auch beim Badesse möglich"] MIA_CHOICES = [ (1, 'Wetter'), (2, 'Art des Fahrradweges'), (3, 'Entfernung zum Badesee'), (4, 'Notwendige Registrierung für Leihrad'), (5, 'Leihgebühren für E-Bike'), (6, 'Flexibilität bei der Rückgabe des Leihrads'), (7, 'Parkgebühren'), (8, 'Anzahl an Parkplätzen'), (9, 'Möglichkeit einer alternativen Strecke'), ] class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): # Generated by code; allow blank so model never throws required-field errors distance = models.StringField(blank=True) weather = models.StringField(blank=True) detour = models.StringField(blank=True) bikelane = models.StringField(blank=True) luggage = models.StringField(blank=True) park_cost = models.StringField(blank=True) park_aval = models.StringField(blank=True) bike_cost = models.StringField(blank=True) bike_conv = models.StringField(blank=True) bike_return = models.StringField(blank=True) # Participant choice DC_choice = models.IntegerField( choices=[ [C.CAR, 'PKW'], [C.BIKE, 'Fahrrad'], [C.EBIKE, 'E-Bike'], [C.WALK, 'Zu Fuß'], ] ) DCE_MIA1 = models.IntegerField(label="", blank=True, choices=C.MIA_CHOICES) DCE_MIA2 = models.IntegerField(label="", blank=True, choices=C.MIA_CHOICES) DCE_MIA3 = models.IntegerField(label="", blank=True, choices=C.MIA_CHOICES) DCE_OA = models.LongStringField( label="Hatten Sie ausreichend Informationen, um Ihre Entscheidung zu treffen, oder fehlten Ihnen bestimmte Informationen?", blank=True, max_length=2000, # Adjusting for an approximate word-to-character ratio. ) DCE_REALITY = models.IntegerField( min=0, max=10, blank=False ) DCE_REALITY2 = models.IntegerField( min=0, max=10, blank=False ) DCE_hed_1 = models.IntegerField( min=0, max=10, blank=True ) DCE_hed_2 = models.IntegerField( min=0, max=10, blank=True ) DCE_hed_3 = models.IntegerField( min=0, max=10, blank=True ) DCE_hed_4 = models.IntegerField( min=0, max=10, blank=True ) DCE_eud_1 = models.IntegerField( min=0, max=10, blank=True ) DCE_eud_2 = models.IntegerField( min=0, max=10, blank=True ) DCE_eud_3 = models.IntegerField( min=0, max=10, blank=True ) DCE_eud_4 = models.IntegerField( min=0, max=10, blank=True ) DCE_eud_5 = models.IntegerField( min=0, max=10, blank=True ) class DCE(Page): form_model = 'player' form_fields = ['DC_choice'] def is_displayed(player): return player.participant.vars.get('survey_participate') == 1 @staticmethod def vars_for_template(player): app = player.subsession.session.config.get('DCE') key = f"dce_attrs_round_{player.round_number}" # 1) If not assigned yet for this participant+round: assign now if key not in player.participant.vars: player.participant.vars[key] = dict( distance=random.choice(C.DIST_OPTIONS), weather=random.choice(C.WEATHER_OPTIONS), detour=random.choice(C.DETOUR_OPTIONS), bikelane=random.choice(C.BIKELANE_OPTIONS), luggage=random.choice(C.LUGGAGE_OPTIONS), park_cost=random.choice(C.PARK_COST_OPTIONS), park_aval=random.choice(C.PARK_AVAL_OPTIONS), bike_cost=random.choice(C.BIKE_COST_OPTIONS), bike_conv=random.choice(C.BIKE_CONVENIENCE_OPTIONS), bike_return=random.choice(C.BIKE_RETURN_OPTIONS) ) attrs = player.participant.vars[key] # 2) Copy into model fields (stored in DB) player.distance = attrs['distance'] player.weather = attrs['weather'] player.detour = attrs['detour'] player.bikelane = attrs['bikelane'] player.luggage = attrs['luggage'] player.park_cost = attrs['park_cost'] player.park_aval = attrs['park_aval'] player.bike_cost = attrs['bike_cost'] player.bike_conv = attrs['bike_conv'] player.bike_return = attrs['bike_return'] # 3) Provide to template return attrs @staticmethod def before_next_page(player, timeout_happened): """ Persist per-round attributes + choice in participant.vars so another app can use them later. """ all_rounds_key = "dce_round_data" if all_rounds_key not in player.participant.vars: player.participant.vars[all_rounds_key] = {} r = player.round_number player.participant.vars[all_rounds_key][r] = dict( round=r, distance=player.distance, weather=player.weather, detour=player.detour, bikelane=player.bikelane, luggage=player.luggage, park_cost=player.park_cost, park_aval=player.park_aval, bike_cost=player.bike_cost, bike_conv=player.bike_conv, bike_return=player.bike_return, choice=player.DC_choice, ) class Q_post_DCE(Page): form_model = 'player' form_fields = ['DCE_MIA1' ,'DCE_MIA2' ,'DCE_MIA3' , 'DCE_REALITY', 'DCE_REALITY2', 'DCE_OA'] def is_displayed(player): return player.round_number == C.NUM_ROUNDS and player.participant.vars.get('survey_participate') == 1 class DCE_bike(Page): form_model = 'player' form_fields = ['DCE_hed_1', 'DCE_hed_2', 'DCE_hed_3', 'DCE_hed_4', 'DCE_eud_1', 'DCE_eud_2', 'DCE_eud_3', 'DCE_eud_4', 'DCE_eud_5'] @staticmethod def is_displayed(player): if not ( player.round_number == C.NUM_ROUNDS and player.participant.vars.get('survey_participate') == 1 ): return False rounds = player.participant.vars.get("dce_round_data", {}) bike_like = [r for r, d in rounds.items() if d.get("choice") in [C.BIKE, C.EBIKE]] if not bike_like: return False bike_r = min(bike_like) # store picks so vars_for_template doesn't recompute player.participant.vars["dce_followup_rounds"] = dict(bike_round=bike_r) return True @staticmethod def vars_for_template(player): rounds = player.participant.vars.get("dce_round_data", {}) picks = player.participant.vars.get("dce_followup_rounds", {}) bike_r = picks.get("bike_round") return dict( bike_r=bike_r, bike_case=rounds.get(bike_r), ) class DCE_car(Page): form_model = 'player' form_fields = ['DCE_hed_1', 'DCE_hed_2', 'DCE_hed_3', 'DCE_hed_4', 'DCE_eud_1', 'DCE_eud_2', 'DCE_eud_3', 'DCE_eud_4', 'DCE_eud_5'] @staticmethod def is_displayed(player): if not ( player.round_number == C.NUM_ROUNDS and player.participant.vars.get('survey_participate') == 1 ): return False rounds = player.participant.vars.get("dce_round_data", {}) car_like = [r for r, d in rounds.items() if d.get("choice") == C.CAR] if not car_like: return False car_r = min(car_like) # store picks so vars_for_template doesn't recompute player.participant.vars["dce_followup_rounds"] = dict(car_round=car_r) return True @staticmethod def vars_for_template(player): rounds = player.participant.vars.get("dce_round_data", {}) picks = player.participant.vars.get("dce_followup_rounds", {}) car_r = picks.get("car_round") return dict( car_r=car_r, car_case=rounds.get(car_r), ) page_sequence = [DCE, DCE_bike, DCE_car, Q_post_DCE]