from collections import Counter
from otree.api import *
import itertools
from itertools import cycle
import random
import json
doc = """
Your app description
"""
class C(BaseConstants):
NAME_IN_URL = 'players'
PLAYERS_PER_GROUP = 3
NUM_ROUNDS = 1
base_tasks = 3
extra_tasks = 3
payment_per_task = 40
treatments = ['fraud']
class Subsession(BaseSubsession):
pass
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='')
num_tasks = models.IntegerField()
correct_answers = models.IntegerField(initial=0)
task_1 = models.IntegerField(blank=False)
task_2 = models.IntegerField(blank=False)
task_3 = models.IntegerField(blank=False)
task_4 = models.IntegerField(blank=False)
task_5 = models.IntegerField(blank=False)
task_6 = models.IntegerField(blank=False)
points = models.IntegerField(initial=0)
declared_balance = models.IntegerField(blank=True, max=240, label="")
earned = models.CurrencyField(initial=0)
clicked_finish = models.IntegerField(initial=0)
submitted_bonus = models.IntegerField(initial=0)
skip_until_finish = models.BooleanField(initial=False)
misreport_attempts = models.IntegerField(initial=0)
### Demographic Questions (AllSurvey Page)
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 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
)
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",
"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.")
# FUNCTIONS
def creating_session(subsession: Subsession):
players = subsession.get_players()
group_matrix = []
treatment_cycle = cycle(C.treatments)
# Group with 3 players
for i in range(0, len(players), 3):
group = players[i:i + 3]
group[0].treatment = 'control'
group[1].treatment = 'control'
group[2].treatment = next(treatment_cycle) # rotate treatments for the 3rd player
for p in group:
if p.treatment == 'control':
p.num_tasks = C.base_tasks
elif p.treatment == 'fraud':
p.num_tasks = C.base_tasks + C.extra_tasks
group_matrix.append(group)
# Set the group matrix
subsession.set_group_matrix(group_matrix)
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_players(subsession, N=8):
for p in subsession.get_players():
p.is_chosen = False
# only fraud players
fraud_players = [
p for p in subsession.get_players()
if p.treatment == 'fraud'
]
if not fraud_players:
return
chosen = random.sample(fraud_players, k=min(N, len(fraud_players)))
for p in chosen:
p.is_chosen = True
# PAGES
class P1Intro(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
class P2Instr(Page):
form_model = "player"
@staticmethod
def vars_for_template(player):
maybe_draw_players(player.subsession, N=8) # for example
return {}
class P3Task(Page):
form_model = "player"
@staticmethod
def get_form_fields(player):
if player.treatment == 'control':
return ['task_1', 'task_2', 'task_3']
else: # fraud
return ['task_1', 'task_2', 'task_3', 'task_4', 'task_5', 'task_6']
@staticmethod
def error_message(player, values):
# Check if any submitted field is empty
for field in values:
if values[field] is None:
return "Please answer all questions."
@staticmethod
def vars_for_template(player):
base_tasks = [
{'letter': 't', 'string': 'afdygtuyftdwubtiskjdfttr'},
{'letter': 'f', 'string': 'fdajfuklfdbfjklafjfdkl'},
{'letter': 's', 'string': 'asdhfksjlsakdfjsalkjs'},
]
extra_tasks = [
{'letter': 'k', 'string': 'kjfkdljfkldjfklajfkdljf'},
{'letter': 'j', 'string': 'jafjkjfjkjfjkjfjkjfjkfj'},
{'letter': 'd', 'string': 'dfkdjfkdlfjdklfjdklfjd'},
]
if player.treatment == 'fraud':
tasks = base_tasks + extra_tasks
else:
tasks = base_tasks
return dict(tasks=tasks, num_tasks=len(tasks))
@staticmethod
def before_next_page(player, timeout_happened):
correct_answers = 0
# Define correct answers in the same order as tasks
# Correct answers
answers_control = [5, 6, 5]
answers_fraud = [5, 6, 5, 5, 11, 6]
if player.treatment == 'control':
answers = answers_control
submitted = [player.task_1, player.task_2, player.task_3]
else:
answers = answers_fraud
submitted = [player.task_1, player.task_2, player.task_3,
player.task_4, player.task_5, player.task_6]
for sub, correct in zip(submitted, answers):
if sub == correct:
correct_answers += 1
player.correct_answers = correct_answers
player.points = correct_answers * C.payment_per_task
class P4Info(Page):
form_model = "player"
@staticmethod
def vars_for_template(player):
return dict(points = player.points)
class P5Instr(Page):
form_model = "player"
class P6Fund(Page):
form_model = "player"
form_fields = ["declared_balance", "submitted_bonus", "clicked_finish"]
@staticmethod
def js_vars(player):
correct_balance = player.points
return {"correct_balance": correct_balance}
@staticmethod
def vars_for_template(player):
return {"points": player.points}
@staticmethod
def live_method(player, data):
if data.get('misreport_attempt'):
player.misreport_attempts += 1
@staticmethod
def before_next_page(player, timeout_happened):
if player.clicked_finish:
player.skip_until_finish = True
elif player.submitted_bonus:
player.skip_until_finish = True
else:
player.skip_until_finish = False
print("clicked_finish:", player.clicked_finish)
print("submitted_bonus:", player.submitted_bonus)
class P7Survey(Page):
form_model = "player"
form_fields = ["age", "sex", "ethnicity", "ethnicity_other", "education",
"job", "income", "spectrum", "political", "political_other", "comment"]
class P8Finish(Page):
form_model = "player"
form_fields = ["submitted_bonus"]
@staticmethod
def vars_for_template(player):
player.earned = player.points
if player.earned < 80:
player.earned = 80
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 {
"earned": real_world_earned_formatted
}
page_sequence = [P1Intro, P2Instr, P3Task, P4Info, P5Instr, P6Fund, P7Survey, P8Finish]