from cProfile import label from collections import Counter from random import choices from otree.api import * import itertools import random import json from otree.database import IntegerField from sqlalchemy import Integer doc = """ Your app description """ class C(BaseConstants): NAME_IN_URL = 'spectators' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 PAY = 100 BONUS = 100 TREATMENTS = ['control', 'immig', 'redis', 'combined'] class Subsession(BaseSubsession): spectators_drawn = models.BooleanField(initial=False) class Group(BaseGroup): pass class Player(BasePlayer): p_id = models.StringField(label="
Please enter your unique Prolific ID: ") is_chosen = models.BooleanField(initial=False) consent = models.BooleanField(initial=False) treatment = models.StringField(initial='') earned = models.CurrencyField(initial=0) audit = models.IntegerField( label="What is your audit decision?", widget=widgets.RadioSelect) penalty = models.IntegerField(min=10, max=100) reason = models.LongStringField(label="Could you briefly explain why you made exactly these " "audit and penalty decisions?") appr = models.IntegerField() fair = models.IntegerField() strategic = models.IntegerField() ###### Demographic Questions ###### age = models.IntegerField(label="Please enter your age: ", min=18, max=80) sex = models.StringField( label="
What is your sex, as recorded on legal/official documents?", choices=["Male", "Female", "Prefer not to say"], widget=widgets.RadioSelect, ) ethnicity = models.StringField( label="
What ethnic group do you belong to?", choices=["White", "Asian", "Black", "Mixed", "Other"], widget=widgets.RadioSelect ) ethnicity_other = models.StringField(blank=True, label="If Mixed or Other, please specify:") education = models.StringField( label="
What is the highest level of education you have completed?", choices=["No formal qualifications", "Primary education", "Secondary education", "Vocational qualification", "Undergraduate degree", "Postgraduate degree", "Doctorate degree"], widget=widgets.RadioSelect ) job = models.StringField( label="
What is your current employment status?", choices=["Employed / Self-employed", "Out of work / Unemployed", "Student / Part-time employment", "Retired"], widget=widgets.RadioSelect ) income = models.StringField( label="
What was your total household income before taxes in 2024 (in GBP)?", choices=["Under 10,000", "10,000 to 19,999", "20,000 to 29,999", "30,000 to 39,999", "40,000 to 49,999", "50,000 to 59,999", "60,000 to 69,999", "70,000 to 79,999", "80,000 to 99,999", "100,000 or more"], widget=widgets.RadioSelect ) children = models.StringField( label="
How many children do you have?", choices=["I do not have children", "1", "2", "3", "4", "5 or more"] ) spectrum = models.StringField( label="
On economic policy matters, where do you see yourself on the liberal/conservative spectrum?", choices=[ ('vlib', 'Very Liberal'), ('lib', 'Liberal'), ('mod', 'Moderate'), ('cons', 'Conservative'), ('vcons', 'Very Conservative'), ], widget=widgets.RadioSelect ) political = models.StringField( label="
Which political party do you most identify with?", choices=["Conservative Party", "Labour Party", "Liberal Democrats", "Green Party", "Reform UK", "None / Not affiliated", "Other"], widget=widgets.RadioSelect ) political_other = models.StringField(blank=True, label="If Other, please specify:") comment = models.LongStringField(blank=True, label="
Do you have any comments or suggestions regarding the study? " "If not, you can leave this field blank.") ###### Baseline Questions ###### b1 = models.StringField( label="
How would you rate the overall quality of public services in the United Kingdom?", choices=[ (1, 'Very low'), (2, 'Somewhat low'), (3, 'Neutral'), (4, 'Somewhat high'), (5, 'Very high'), ], widget=widgets.RadioSelectHorizontal) b2 = models.StringField( label="
How easy do you think it is for people in the UK to improve their economic " "situation through hard work?", choices=[ (1, 'Very difficult'), (2, 'Somewhat difficult'), (3, 'Neutral'), (4, 'Somewhat easy'), (5, 'Very easy'), ], widget=widgets.RadioSelectHorizontal) b3 = models.IntegerField( label="
What percentage of the UK population do you think has completed university-level education?", min=0, max=100, choices=[ (0, '0%'), (10, '10%'), (20, '20%'), (30, '30%'), (40, '40%'), (50, '50%'), (60, '60%'), (70, '70%'), (80, '80%'), (90, '90%'), (100, '100%'), ], widget=widgets.RadioSelectHorizontal ) b4 = models.StringField( label="
How important do you think education is for economic success in the UK today?", choices=[ (1, 'Not important'), (2, 'Slightly important'), (3, 'Moderately important'), (4, 'Somewhat important'), (5, 'Very important'), ], widget=widgets.RadioSelectHorizontal) b5 = models.IntegerField( label="
What percentage of the UK population do you think is currently employed?", min=0, max=100, choices=[ (0, '0%'), (10, '10%'), (20, '20%'), (30, '30%'), (40, '40%'), (50, '50%'), (60, '60%'), (70, '70%'), (80, '80%'), (90, '90%'), (100, '100%'), ], widget=widgets.RadioSelectHorizontal ) ###### Control ###### c1 = models.StringField( label="
In what type of area do you think most people in the United Kingdom live?", choices=[ (5, 'Large cities'), (4, 'Medium-sized towns'), (3, 'Small towns'), (2, 'Rural areas'), (1, 'Equally across these areas'), ], widget=widgets.RadioSelect) c2 = models.IntegerField( label="
What percentage of the population do you think lives in London and its surrounding metropolitan area?", min=0, max=100, choices=[ (0, '0%'), (10, '10%'), (20, '20%'), (30, '30%'), (40, '40%'), (50, '50%'), (60, '60%'), (70, '70%'), (80, '80%'), (90, '90%'), (100, '100%'), ], widget=widgets.RadioSelectHorizontal ) c3 = models.IntegerField( label="
How common do you think it is for people in the UK to move to a different city or " "region during their lifetime?", choices=[ (1, 'Very uncommon'), (2, 'Somewhat uncommon'), (3, 'Neutral'), (4, 'Somewhat common'), (5, 'Very common'), ], widget=widgets.RadioSelectHorizontal) c4 = models.IntegerField( label="
What percentage of the population do you think works from home at least occasionally?", min=0, max=100, choices=[ (0, '0%'), (10, '10%'), (20, '20%'), (30, '30%'), (40, '40%'), (50, '50%'), (60, '60%'), (70, '70%'), (80, '80%'), (90, '90%'), (100, '100%'), ], widget=widgets.RadioSelectHorizontal) c5 = models.StringField( label="
How common do you think it is for households in the UK to have internet access at home?", choices=[ (1, 'Very uncommon'), (2, 'Somewhat uncommon'), (3, 'Neutral'), (4, 'Somewhat common'), (5, 'Very common'), ], widget=widgets.RadioSelectHorizontal) ###### Redistribution ###### r1 = models.StringField( label="
What do you think about the following statement: ''The government should allocate resources to " "support those in need.''", choices=[ (1, 'Fully Disagree'), (2, 'Somewhat Disagree'), (3, 'Neutral'), (4, 'Somewhat Agree'), (5, 'Fully Agree'), ], widget=widgets.RadioSelectHorizontal ) r2 = models.IntegerField( label="
To what extent do you think social welfare programs in the UK reach people who genuinely need support?", choices=[ (1, 'Not at all'), (2, 'To a small extent'), (3, 'Neutral'), (4, 'To a large extent'), (5, 'To a very large extent'), ], widget=widgets.RadioSelectHorizontal ) r3 = models.IntegerField( label="
Roughly what percentage of total government spending do you think goes to social welfare benefits?", min=0, max=100, choices=[ (0, '0%'), (10, '10%'), (20, '20%'), (30, '30%'), (40, '40%'), (50, '50%'), (60, '60%'), (70, '70%'), (80, '80%'), (90, '90%'), (100, '100%'), ], widget=widgets.RadioSelectHorizontal ) r4 = models.StringField( label="
Overall, how fair do you think the UK tax system is?", choices=[ (1, 'Very unfair'), (2, 'Somewhat unfair'), (3, 'Neutral'), (4, 'Somewhat fair'), (5, 'Very fair'), ], widget=widgets.RadioSelectHorizontal) r5 = models.IntegerField( label="
What percentage of adults in the UK do you think receive social welfare benefits?", min=0, max=100, choices=[ (0, '0%'), (10, '10%'), (20, '20%'), (30, '30%'), (40, '40%'), (50, '50%'), (60, '60%'), (70, '70%'), (80, '80%'), (90, '90%'), (100, '100%'), ], widget=widgets.RadioSelectHorizontal ) ###### Immigration ###### i1 = models.StringField( label="
What do you think about the following statement: ''The current number of immigrants coming to the " "UK is about right.''", choices=[ (1, 'Fully Disagree'), (2, 'Somewhat Disagree'), (3, 'Neutral'), (4, 'Somewhat Agree'), (5, "Fully Agree"), ], widget=widgets.RadioSelectHorizontal ) i2 = models.StringField( label="
In your opinion, what are the three most common countries of origin among immigrants in the UK?" ) i3 = models.IntegerField( label="
What percentage of the total population do you think are first-generation immigrants (born abroad)?", min=0, max=100, choices=[ (0, '0%'), (10, '10%'), (20, '20%'), (30, '30%'), (40, '40%'), (50, '50%'), (60, '60%'), (70, '70%'), (80, '80%'), (90, '90%'), (100, '100%'), ], widget=widgets.RadioSelectHorizontal ) i4 = models.IntegerField( label="
What percentage of immigrants do you think are currently employed?", min=0, max=100, choices=[ (0, '0%'), (10, '10%'), (20, '20%'), (30, '30%'), (40, '40%'), (50, '50%'), (60, '60%'), (70, '70%'), (80, '80%'), (90, '90%'), (100, '100%'), ], widget=widgets.RadioSelectHorizontal ) i5 = models.StringField( label="
In your view, what is the overall impact of immigration on the United Kingdom?", choices=[ (1, 'Very negative'), (2, 'Somewhat negative'), (3, 'Neutral'), (4, 'Somewhat positive'), (5, 'Very positive'), ], widget=widgets.RadioSelectHorizontal ) attention_check = models.StringField( label="
To prove that you are reading questions attentively, please select ''Fully Agree'' here.", choices=[ (1, 'Fully Disagree'), (2, 'Somewhat Disagree'), (3, 'Neutral'), (4, 'Somewhat Agree'), (5, 'Fully Agree'), ], widget=widgets.RadioSelectHorizontal ) # FUNCTIONS def creating_session(subsession: Subsession): for p in subsession.get_players(): p.consent = False # default p.treatment = "" # placeholder p.is_chosen = False def age_error_message(player, value): if value < 18: return "You need to be at least 18 years old." elif value > 80: return "Please enter your age." def maybe_draw_spectators(subsession, N=1): for p in subsession.get_players(): p.is_chosen = False eligible = [p for p in subsession.get_players() if p.consent and p.treatment] for treatment in C.TREATMENTS: group_players = [p for p in eligible if p.treatment == treatment] if not group_players: continue chosen = random.sample(group_players, k=min(N, len(group_players))) for p in chosen: p.is_chosen = True def audit_choices(player): choices = [(1, "Audit 30% (Cost £0.10)"), (2, "Audit 70% (Cost £0.40)")] random.shuffle(choices) return choices # PAGES class S1Consent(Page): form_model = "player" form_fields = ["p_id"] @staticmethod def before_next_page(player, timeout_happened): # Only assign treatment if they consented player.consent = True # Get already-consented players subsession = player.subsession consented = [p for p in subsession.get_players() if p.consent and p.treatment] treatments = C.TREATMENTS # Simple round-robin based on number of consenters so far player.treatment = treatments[len(consented) % len(treatments)] class S2Intro(Page): form_model = "player" @staticmethod def vars_for_template(player): subsession = player.subsession maybe_draw_spectators(subsession) return {} class S3Survey(Page): form_model = "player" @staticmethod def get_form_fields(player): baseline = ["b1", "b2", "b3", "b4", "b5"] if player.treatment == "control": randomized = ["c1", "c2", "c3", "c4", "c5", "attention_check"] random.shuffle(randomized) return baseline + randomized elif player.treatment == "redis": randomized = ["r1", "r2", "r3", "r4", "r5", "attention_check"] random.shuffle(randomized) return baseline + randomized elif player.treatment == "immig": randomized = ["i1", "i2", "i3", "i4", "i5", "attention_check"] random.shuffle(randomized) return baseline + randomized elif player.treatment == "combined": randomized = ["r1", "r2", "r3", "r4", "r5", "i1", "i2", "i3", "i4", "i5", "attention_check"] random.shuffle(randomized) return baseline + randomized class S4Info(Page): form_model = "player" class S5Players(Page): form_model = "player" class S6Audit(Page): form_model = "player" class S7Audit(Page): form_model = "player" form_fields = ["audit"] class S9Penalty(Page): form_model = "player" form_fields = ["penalty"] class S10Views(Page): form_model = "player" form_fields = ["reason"] class S11Demo(Page): form_model = "player" form_fields = ["age", "sex", "ethnicity", "ethnicity_other", "education", "job", "income", "spectrum", "political", "political_other", "comment"] class S12Finish(Page): form_model = "player" @staticmethod def vars_for_template(player): player.earned = C.PAY + C.BONUS if player.audit in [1]: player.earned -= 10 elif player.audit in [2]: player.earned -= 40 conversion_rate = player.session.config['real_world_currency_per_point'] real_world_earned = player.earned * conversion_rate real_world_earned_formatted = '{:.2f}'.format(round(real_world_earned, 2)) return { "pence": player.earned, "earned": real_world_earned_formatted, } page_sequence = [S1Consent, S2Intro, S3Survey, S4Info, S5Players, S6Audit, S7Audit, S9Penalty, S10Views, S11Demo, S12Finish]