from otree.api import * import os doc = """ Experiment-day pre-survey: enter experiment code + brief intro and pre-experiment emotional state. """ class C(BaseConstants): NAME_IN_URL = 'thesis_exp_pre_survey' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): # Experiment code typed at the start of the lab session exp_code = models.StringField( label="Please enter your lab code (e.g., A01, B01, C01) from the email:" ) confirm_code = models.BooleanField( choices=[[True, "Yes – continue"], [False, "No – go back and change"]], widget=widgets.RadioSelect ) treatment = models.StringField(blank=True) audio_filename = models.StringField(blank=True) audio_file_exists = models.BooleanField(initial=False) # Pre-experiment consent confirmation (for the day itself) confirm_ready = models.BooleanField( label="I confirm that I have read the instructions and agree to proceed with the experiment today.", widget=widgets.CheckboxInput, ) # Pre-experiment emotional state emo_calmness = models.IntegerField( label="How are you feeling right now?", choices=[ [1, "Very calm"], [2, "Slightly calm"], [3, "Neutral"], [4, "Slightly anxious"], [5, "Very anxious"], ], widget=widgets.RadioSelect, ) emo_energy = models.IntegerField( label="How energized do you feel right now?", choices=[ [1, "Very low energy"], [2, "Slightly low energy"], [3, "Neutral"], [4, "Slightly high energy"], [5, "Very high energy"], ], widget=widgets.RadioSelect, ) emo_mood = models.IntegerField( label="How would you describe your current mood?", choices=[ [1, "Very negative"], [2, "Slightly negative"], [3, "Neutral"], [4, "Slightly positive"], [5, "Very positive"], ], widget=widgets.RadioSelect, ) # PAGE 1: enter code class CodeEntry(Page): template_name = 'thesis_exp_pre_survey/CodeEntry.html' form_model = 'player' form_fields = ['exp_code'] @staticmethod def error_message(player, values): code = (values.get('exp_code') or "").strip() if len(code) != 3: return "Your lab code must be 3 characters long (e.g., A01)." # NEW: Validate A/B/C format + number range code_up = code.upper() if code_up[0] not in ['A', 'B', 'C']: return "Code must start with A, B, or C (e.g., A01)." if not code_up[1:].isdigit(): return "Code must be like A01, B02, C15." num = int(code_up[1:]) if num < 1 or num > 99: return "Code number must be between 01 and 99." # (optional) prevent re-use within THIS lab session used = player.session.vars.get('used_codes', set()) if code_up in used: return "This code was already used in this session. Please contact the experimenter." return None # PAGE 2: code confirm class CodeConfirm(Page): template_name = 'thesis_exp_pre_survey/CodeConfirm.html' form_model = 'player' form_fields = ['confirm_code'] @staticmethod def vars_for_template(player: Player): return dict(code=player.exp_code.strip().upper()) @staticmethod def error_message(player, values): if values.get('confirm_code') is False: return ( "If you entered an incorrect participant code, " "please approach the experimenter to proceed." ) @staticmethod def before_next_page(player, timeout_happened): # Only finalize if YES if not player.confirm_code: return code = player.exp_code.strip().upper() player.participant.vars['lab_code'] = code player.participant.vars['exp_code'] = code treatment_map = {'A': 'no_music', 'B': 'slow_music', 'C': 'personalized_music'} player.participant.vars['treatment'] = treatment_map.get(code[:1], 'no_music') used = player.session.vars.get('used_codes', set()) used.add(code) player.session.vars['used_codes'] = used # PAGE 3: Audio check class AudioCheck(Page): template_name = 'thesis_exp_pre_survey/AudioCheck.html' @staticmethod def is_displayed(player: Player): # must come after code confirmation if player.confirm_code is not True: return False treatment = player.participant.vars.get('treatment', 'no_music') lab_code = player.participant.vars.get('lab_code', '').strip().upper() if treatment == 'slow_music': filename = 'mozart.mp3' elif treatment == 'personalized_music': filename = f'{lab_code}.mp3' else: # no_music → nothing to check player.participant.vars['audio_file_exists'] = True return False audio_path = os.path.join('_static', 'audio', filename) file_exists = os.path.exists(audio_path) # store in participant.vars (runtime) player.participant.vars['audio_filename'] = filename player.participant.vars['audio_file_exists'] = file_exists # store in Player (CSV export) player.audio_filename = filename player.audio_file_exists = file_exists player.treatment = treatment # show this page ONLY if missing return not file_exists @staticmethod def vars_for_template(player: Player): return {} # PAGE 4: day-of consent confirmation (separate page) class Confirmation(Page): template_name = 'thesis_exp_pre_survey/Confirmation.html' form_model = 'player' form_fields = ['confirm_ready'] @staticmethod def error_message(player, values): if not values.get('confirm_ready'): return "Please confirm that you agree to proceed with the experiment before continuing." # PAGE 5: emotional survey (separate page) class ExpPreSurvey(Page): template_name = 'thesis_exp_pre_survey/ExpPreSurvey.html' form_model = 'player' form_fields = ['emo_calmness', 'emo_energy', 'emo_mood'] page_sequence = [CodeEntry, CodeConfirm, AudioCheck, Confirmation, ExpPreSurvey]