from otree.api import * import timer_utils # Import the shared timer logic class C(BaseConstants): NAME_IN_URL = 'survey' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): kids = models.StringField( choices=[['0', '0'], ['1', '1'], ['2', '2'], ['3', '3'], ['4', '4'], ['5', '5 and more']], label='How many children are you financially responsible for?' ) age = models.IntegerField(label='What is your age?', min=13, max=125) perceived_trans_econ_status = models.IntegerField( label='Thinking about transgender people in United Kingdom, how financially well-off do you think they are, on average, compared with other people in United Kingdom?', choices=[[1, 'Much less financially well-off'], [2, 'Less financially well-off'], [3, 'Slightly less financially well-off'], [4, 'About the same'], [5, 'Slightly more financially well-off'], [6, 'More financially well-off'], [7, 'Much more financially well-off'] ]) perceived_trans_social_privilege = models.IntegerField( label='Thinking about transgender people in United Kingdom, how socially privileged do you think they are, on average, compared with other people?', choices= [[1, 'Much less privileged'], [2, 'Less privileged'], [3, 'Slightly less privileged'], [4, 'About the same'], [5, 'Slightly more privileged'], [6, 'More privileged'], [7, 'Much more privileged']] ) # Duplicate field 'atractionsex' was in your original code twice. # I kept the one with the fuller choices list. atractionsex = models.StringField( choices=[['males', 'I am only attracted to males'], ['Mostlymales', 'I am mostly attracted to males'], ['Equal', 'I am equally attracted to males and females'], ['mostlyfemales', 'I am mostly attracted to females'], ['females', 'I am only attracted to females'], ['notsure', 'I am not sure'], ['NA', 'No answer']], label='People are different in their sexual attraction to other people. Which best describes your feelings?', ) maritial = models.StringField( choices=[['Married', 'Married'], ['Single', 'Single'], ['Relationship', 'Dating exclusively'], ['Widowed', 'Widowed'], ['Divorced', 'Divorced'], ['NA', 'Decline to answer']], label='Please indicate your current relationship status.', ) education = models.StringField( choices=[ ['NoQual', 'No qualifications'], ['GCSE', 'GCSEs / O-levels (or equivalent)'], ['ALevel', 'A-levels / AS-levels (or equivalent)'], ['Apprentice', 'Apprenticeship'], ['Vocational', 'Other vocational/technical qualification (e.g., BTEC, NVQ, City & Guilds)'], ['Bachelor', "Undergraduate degree (e.g., BA, BSc)"], ['Master', "Postgraduate degree (e.g., MA, MSc, MBA)"], ['Professional', "Professional degree/qualification (e.g., PGCE, Medicine, Dentistry, Law)"], ['Phd', 'Doctorate (e.g., PhD, DPhil, EdD)'], ['NA', 'Prefer not to say'], ], label='What is the highest level of education or qualification you have completed?', ) Ethnicity = models.StringField( choices=[ ['White', 'White'], ['Mixed', 'Mixed or multiple ethnic groups'], ['Asian', 'Asian or Asian British'], ['Black', 'Black, African, Caribbean or Black British'], ['Other', 'Other ethnic group'], ['NA', 'Prefer not to say'], ], label='Which of the following best describes your ethnic group?', ) income = models.IntegerField( label='What is your total household income per year, including all earners in your household (after tax), in GBP (£)?', ) aim = models.StringField( label='What do you think this study was trying to discover?', ) knows_lgbt = models.BooleanField( choices=[[1, "Yes"], [0, "No"]], label="Do you personally know anyone who is LGBTQ?" ) lgbt_connection = models.StringField( blank=True, label="If yes, what is their relation to you? (e.g. family member, friend, colleague)" ) lgbt_subgroups = models.LongStringField(blank=True) employment = models.StringField( choices=[['Employed', 'Employed full-time or part-time'], ['Unemployed', 'Unemployed and looking for work'], ['Student', 'Student'], ['Retired', 'Retired'], ['Other', 'Other']], label='What is your current employment status?', ) Trans_opinion = models.StringField( choices=[['7', 'Very favourable'], ['6', 'Moderately favourable'], ['5', 'Slightly favourable'], ['4', 'Neither favourable nor unfavourable'], ['3', 'Slightly unfavourable'], ['2', 'Moderately unfavourable'], ['1','Very unfavourable'],['NA', 'No answer']], label='Overall what is your opinion about transgender people?', ) aim = models.StringField( label='What do you think this study was trying to discover?', ) comments = models.StringField( label='Do you have any other comments about this study?', ) clearinstructions1 = models.StringField( choices=[ ['strongly_agree', 'Strongly agree'], ['agree', 'Agree'], ['neutral', 'Neither agree nor disagree'], ['disagree', 'Disagree'], ['strongly_disagree', 'Strongly disagree'], ], label='The instructions of this study were clear.', widget=widgets.RadioSelectHorizontal, ) CRT1 = models.StringField( choices=[ ['1st', '1st'], # correct ['2nd', '2nd'], # intuitive but wrong ['3rd', '3rd'], ], label="If you’re running a race and you pass the person in second place, what place are you in?", widget=widgets.RadioSelectHorizontal, ) CRT2 = models.IntegerField( min=0, max=1000, label="A farmer had 15 sheep and all but 8 died. How many are left?" ) CRT3 = models.StringField( label="Emily’s father has three daughters. The first two are named April and May. What is the third daughter’s name?" ) CRT4 = models.IntegerField( min=0, max=1000, label="How many cubic feet of dirt are there in a hole that is 3’ deep x 3’ wide x 3’ long?" ) crt_score = models.FloatField(initial=0.0) policy_identify = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) policy_nhs_hormones = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) policy_under16_hormones = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) policy_risk = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) attention2 = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) policy_funding = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) policy_immigration = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) policy_trust = models.IntegerField(choices=[1, 2, 3, 4, 5, 6, 7]) # Store the calculated mean policy_mean = models.FloatField() left_right = models.IntegerField( label=("In politics people sometimes talk of 'left' and 'right. Where would you place yourself on a scale from 0 to 10, where 0 means the left and 10 means the right?"), choices=( [[i, str(i)] for i in range(0, 11)] + [[98, "Don't know"], [99, "Prefer not to say"]] ), widget=widgets.RadioSelectHorizontal, ) party_closest = models.StringField( label="Which political party do you feel closest to, if any?", choices=[ ['lab', 'Labour'], ['con', 'Conservative'], ['ld', 'Liberal Democrats'], ['reform', 'Reform UK'], ['green', 'Green Party'], ['snp', 'Scottish National Party (SNP)'], ['plaid', 'Plaid Cymru'], ['dup', 'Democratic Unionist Party (DUP)'], ['sf', 'Sinn Féin'], ['sdlp', 'Social Democratic and Labour Party (SDLP)'], ['alliance', 'Alliance Party'], ['uup', 'Ulster Unionist Party (UUP)'], ['other', 'Other party'], ['none', 'None / no party'], ['dk', "Don't know"], ['pnts', 'Prefer not to say'], ]) # --- PAGES --- class CRT(Page): form_model = 'player' form_fields = ['CRT1', 'CRT2', 'CRT3', 'CRT4'] timer_text = "Time remaining:" def before_next_page(player: Player, timeout_happened): score = 0 if player.CRT1 == '1st': # Following your comment marking '1st' as correct score += 1 if player.CRT2 == 8: score += 1 if player.CRT3 and player.CRT3.strip().lower() == 'emily': score += 1 if player.CRT4 == 0: score += 1 player.crt_score = score 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) class Thoughts(Page): form_model = 'player' form_fields = ['aim', 'comments','clearinstructions1'] 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) class Policy(Page): form_model = 'player' form_fields = [ 'policy_identify','policy_nhs_hormones', 'policy_under16_hormones', 'policy_risk', 'attention2', 'policy_funding', 'policy_immigration', 'policy_trust' ] @staticmethod def before_next_page(player: Player, timeout_happened): # 1. Calculate the reverse score for policy_risk (8 - score) # We check if it is not None just in case a participant managed to skip it if player.policy_risk is not None: reversed_risk = 8 - player.policy_risk else: reversed_risk = None # 2. Gather all answers into a list, making sure to use the newly reversed variable! answers = [ player.policy_identify, player.policy_nhs_hormones, player.policy_under16_hormones, reversed_risk # <--- Using the reversed score here ] # 3. Calculate the mean as normal if None not in answers: player.policy_mean = sum(answers) / len(answers) 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) class Rela(Page): form_model = 'player' form_fields = ['atractionsex', 'knows_lgbt', 'lgbt_connection', 'lgbt_subgroups', 'Trans_opinion', 'perceived_trans_social_privilege','perceived_trans_econ_status'] 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) class Demographics(Page): form_model = 'player' form_fields = ['left_right','party_closest','maritial', 'kids', 'income','employment', 'education', 'Ethnicity'] 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) @staticmethod def before_next_page(player: Player, timeout_happened): participant = player.participant participant.finished = True # --- END PAGES --- class TimeoutEnd(Page): """ This page ONLY displays if the user ran out of time. """ @staticmethod def is_displayed(player): # Show ONLY if time is up return timer_utils.is_time_up(player.participant) class End(Page): """ The standard success page. """ def is_displayed(player): # Show ONLY if time is NOT up return not timer_utils.is_time_up(player.participant) page_sequence = [ CRT, Thoughts, Policy, Rela, Demographics, TimeoutEnd, # Added to catch fall-throughs End ]