from otree.api import * import timer_utils # Import shared timer logic doc = """ Your app description """ VALID_CODES = [ "X3K9", "J7R2", "P4N8", "B6ZQ", "D2XT", "L8A5", "R5V3", "T1G9", "M2Q7", "H4KD", "W9EZ", "S3NU", "K5PT", "A1XR", "Y7LM", "N4CB", "C6UJ", "F8MD", "Z2LK", "G7YP", "V3TA", "E1RN", "Q6HW", "U9FD", "O5XS", "I2BN", "L4MJ", "J6DQ", "T7WK", "B3XZ", "R9LY", "M6QN", "H8VZ", "Y3CA", "K1UD", "Z5EP", "F2HR", "P9WM", "G6KS", "N8XL", "Q1BV", "A6TW", "S9JG", "E4ZM", "C8DX", "O3RY", "I5GU", "D7KP", "V6WQ", "U2AJ", "FORK", ] class C(BaseConstants): NAME_IN_URL = 'writing' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): instr_time_away_seconds = models.FloatField(initial=0) instr_tab_hide_count = models.IntegerField(initial=0) instr_copy = models.LongStringField(initial="", blank=True) writing_text = models.LongStringField(label="Type your response here:") char_count = models.IntegerField(initial=0) time_away_seconds = models.FloatField(initial=0) # per-page metric tab_hide_count = models.IntegerField(initial=0) copy = models.LongStringField(blank=True) upset = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) hostile = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) alert = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) ashamed = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) inspired = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) nervous = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) determined = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) attentive = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) afraid = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) active = models.IntegerField(choices=[1, 2, 3, 4, 5,6,7]) PANAS_pos = models.FloatField() PANAS_neg = models.FloatField() Subjective1 = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) Subjective2 = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) Subjective3 = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) Subjective4 = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) Subjective5 = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) Subjective6 = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) attention1 = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) Subjective_avg = models.FloatField() # PAGES class Intro(Page): form_model = 'player' # Track the specific variables for this instruction page form_fields = ['instr_time_away_seconds', 'instr_tab_hide_count', 'instr_copy'] # Keep your global timer logic active if needed timer_text = "Time remaining:" def get_timeout_seconds(player): rem = timer_utils.get_remaining_time(player.participant) if rem is not None and rem < timer_utils.WARNING_SECONDS: return rem def is_displayed(player): return not timer_utils.is_time_up(player.participant) def vars_for_template(player): # Pass the treatment variable to the template so it shows the correct prompt treatment = player.participant.vars.get('rich_poor_treatment', 'None') return dict( poor = "Yes" if treatment == 'Poor' else "No" ) class Main(Page): form_model = 'player' form_fields = ['writing_text', 'char_count', 'time_away_seconds', 'tab_hide_count', 'copy'] timer_text = "Page will automatically submit when time is up. Time remaining:" def get_timeout_seconds(player): # 1. Check the global timer global_rem = timer_utils.get_remaining_time(player.participant) # 2. Set the 5-minute limit (300 seconds) for this page page_limit = 240 # 3. Return whichever timer is shorter if global_rem is not None: return min(global_rem, page_limit) return page_limit def is_displayed(player): return not timer_utils.is_time_up(player.participant) def vars_for_template(player): treatment = player.participant.vars.get('rich_poor_treatment', 'None') return dict( poor="Yes" if treatment == 'Poor' else "No" ) class Fail(Page): def is_displayed(player): return player.char_count < 400 @staticmethod def vars_for_template(player): participant = player.participant return dict( link2=participant.session.config['bigpaylink']) class Subjective(Page): form_model = 'player' form_fields = [ 'Subjective1', 'Subjective2', 'Subjective3', 'Subjective4', 'Subjective5', 'Subjective6', 'attention1'] timer_text = "Time remaining:" def get_timeout_seconds(player): rem = timer_utils.get_remaining_time(player.participant) if rem < timer_utils.WARNING_SECONDS: return rem def is_displayed(player): return not timer_utils.is_time_up(player.participant) def before_next_page(player: Player, timeout_happened): player.Subjective_avg = sum( [player.Subjective1, player.Subjective2, player.Subjective3, player.Subjective4, player.Subjective5, player.Subjective6]) / 6 class PANAS(Page): form_model = 'player' form_fields = [ 'upset', 'hostile', 'alert', 'ashamed', 'inspired', 'nervous', 'determined', 'attentive', 'afraid', 'active'] timer_text = "Time remaining:" def get_timeout_seconds(player): rem = timer_utils.get_remaining_time(player.participant) if rem < timer_utils.WARNING_SECONDS: return rem def is_displayed(player): return not timer_utils.is_time_up(player.participant) def before_next_page(player: Player, timeout_happened): # Calculate PANAS scores neg_items = ['upset', 'hostile', 'afraid', 'ashamed', 'nervous'] pos_items = ['inspired', 'determined', 'attentive', 'alert', 'active'] player.PANAS_pos = sum(getattr(player, item) for item in pos_items)/5 player.PANAS_neg = sum(getattr(player, item) for item in neg_items)/5 page_sequence = [Intro, Main, Fail, Subjective, PANAS]