import json
import random
from otree.api import *
import math
doc = """
Your app description
"""
class Constants(BaseConstants):
name_in_url = 'HT_Project'
players_per_group = None
num_rounds = 10
class Subsession(BaseSubsession):
pass
class Group(BaseGroup):
pass
class Player(BasePlayer):
num_correct = models.IntegerField(initial=0)
num_incorrect = models.IntegerField(initial=0)
attempted = models.IntegerField(initial=0)
rank_i = models.IntegerField()
rank = models.IntegerField()
group_rank = models.IntegerField()
name = models.StringField()
is_top = models.BooleanField(initial=0)
is_promoted = models.BooleanField(initial=0)
payment = models.StringField()
consent = models.BooleanField()
def make_field(label):
choices = [[1, "True"], [2, "False"]]
return models.IntegerField(
choices=choices,
label=label,
widget=widgets.RadioSelect,
)
is_first_attempt = models.BooleanField(default=True)
q1 = make_field('1. There are two levels of workers: Level A and Level B workers.')
q2 = make_field('2. You will be randomly assigned to the Level A or Level B worker position.')
q3 = make_field('3. You will earn 2 Lira for each math problem you solve correctly in the placement period.')
q4 = make_field('1. Based on my performance in the Placement Period, I have been assigned the position of a Level B worker')
q5 = make_field('2. My task is to solve math problems by subtracting two-digit numbers from two-digit numbers.')
q6 = make_field('3. I am allowed to use a calculator.')
q7 = make_field('1. Based on my performance in the Placement Period, I have been assigned the position of a Level A worker.')
q8 = make_field('2. My task is to solve math problems by subtracting two-digit numbers from three-digit numbers and provide my answer by adjusting a slider to my answer.')
q9 = make_field('1. Based on my superior performance in periods 1-3, I have been promoted to the position of a Level A worker.')
q10 = make_field('2. My task is to solve math problems subtracting two-digit numbers from three-digit numbers and provide my answer by adjusting a slider to my answer.')
q11 = make_field('3. The other two workers in my group were originally assigned Level A worker positions from the Placement Period.')
q12 = make_field('4. I will see where my performance ranks among the other Level A workers in my group, but the other Level A workers in my group will not see where my performance ranks among them.')
q13 = make_field('1. I will be a part of multiple groups of three workers (including myself).')
q14 = make_field('2. Each group will consist of myself, the other worker originally assigned to the Level A worker position from the Placement Period, and one additional newly promoted worker from the Level B position.')
q15 = make_field('3. I will see where my performance ranks among the other Level A workers in each of my groups, but the other Level A workers in my groups will not see where my performance ranks among them. ')
l1 = models.IntegerField(
choices=(
[1, "1 - (Poorly)"],
[2, "2"],
[3, "3"],
[4, "4 - (Average)"],
[5, "5"],
[6, "6"],
[7, "7 - (Excellent)"],
),
widget=widgets.RadioSelectHorizontal,
label="How well do you think that you will do on the math tasks in today’s session?"
)
l2 = models.IntegerField(
choices=(
[1, "1 - (Worse than others)"],
[2, "2
"],
[3, "3
"],
[4, "4 - (About the same as others)"],
[5, "5
"],
[6, "6
"],
[7, "7 - (Better than others)"],
),
widget=widgets.RadioSelectHorizontal,
label="How well do you think you will do on the math tasks compared to the other participants in the session?"
)
def make_field2(label):
choices = (
[1, "1 - (Strongly disagree)"],
[2, "2
"],
[3, "3
"],
[4, "4 - (Neither agree nor disagree)"],
[5, "5
"],
[6, "6
"],
[7, "7 - (Strongly agree)"],
)
return models.IntegerField(
choices=choices,
label=label,
widget=widgets.RadioSelectHorizontal,
)
l3 = make_field2('I am confident that I can perform effectively on many different tasks.')
l4 = make_field2('Compared to other people, I can do most tasks very well.')
l5 = make_field2('When facing difficult tasks, I am certain that I will accomplish them.')
def make_field3(label):
choices = (
[1, "1 - (Not at all)"],
[2, "2"],
[3, "3"],
[4, "4 - (Neutral)"],
[5, "5"],
[6, "6"],
[7, "7 - (Very)"],
)
return models.IntegerField(
choices=choices,
label=label,
widget=widgets.RadioSelectHorizontal,
)
l6 = make_field3("Stressed")
l7 = make_field3("Nervous")
l8 = make_field3("Calm")
l9 = make_field3("Worried")
l10 = make_field3("Tense")
l11 = make_field3("Relaxed")
def make_field4(label):
choices = (
[1, "1 - (Not at all interesting)"],
[2, "2
"],
[3, "3
"],
[4, "4 - (Moderately interesting)"],
[5, "5
"],
[6, "6
"],
[7, "7 - (Very interesting)"],
)
return models.IntegerField(
choices=choices,
label=label,
widget=widgets.RadioSelectHorizontal,
)
l12 = make_field4("How interesting did you find the initial basic math task (2x2 subtraction without slider)?")
l13 = make_field4("How interesting did you find the advanced math task (2x3 subtraction with slider)?")
l14 = models.IntegerField(
choices=(
[1, "1 - (Never)"],
[2, "2"],
[3, "3"],
[4, "4 - (Sometimes)"],
[5, "5"],
[6, "6"],
[7, "7 - (Very Often)"],
),
widget=widgets.RadioSelectHorizontal,
label="How often did you think about how your performance compared to performance of the other two Level A "
"workers in your group?"
)
l15 = models.IntegerField(
choices=(
[1, "1 - (Not at all)"],
[2, "2"],
[3, "3"],
[4, "4 - (Neutral)"],
[5, "5"],
[6, "6"],
[7, "7 - (Quite a lot)"],
),
widget=widgets.RadioSelectHorizontal,
label="I felt my performance was being evaluated or judged by the other two Level A workers in my group."
)
l16 = models.IntegerField(
choices=(
[1, "1 - (Not at all)"],
[2, "2"],
[3, "3"],
[4, "4 - (Neutral)"],
[5, "5"],
[6, "6"],
[7, "7 - (Quite a lot)"],
),
widget=widgets.RadioSelectHorizontal,
label="I felt distracted while working on the advanced math task."
)
l17 = models.IntegerField(
choices=(
[1, "1 - (No attention)
"],
[2, "2
"],
[3, "3
"],
[4, "4 - (Some attention)"],
[5, "5
"],
[6, "6
"],
[7, "7 - (Complete attention)"],
),
widget=widgets.RadioSelectHorizontal,
label="To what extent was your attention focused on the advanced math task?"
)
l18 = models.IntegerField(
choices=(
[1, "1 - (Not at all nervous or concerned)"],
[2, "2
"],
[3, "3
"],
[4, "4 - (Somewhat nervous or concerned)"],
[5, "5
"],
[6, "6
"],
[7, "7 - (Very nervous or concerned)
"],
),
widget=widgets.RadioSelectHorizontal,
label="Were you nervous or concerned about how well you were performing relative to other two Level A "
"workers in your group?"
)
l19 = models.IntegerField(
choices=(
[1, "1 - (No at all)"],
[2, "2"],
[3, "3"],
[4, "4 - (Somewhat)"],
[5, "5"],
[6, "6"],
[7, "7 - (Very much)"],
),
widget=widgets.RadioSelectHorizontal,
label="Did thinking about how your performance compared to the other two Level A workers in your group "
"interfere with your ability to concentrate on the problems?"
)
l20 = models.IntegerField(
choices=(
[1, "1 - (Worse than the other two participants)"],
[2, "2
"],
[3, "3
"],
[4, "4 - (About the same as other two participants)"],
[5, "5
"],
[6, "6
"],
[7, "7 - (Better than the other two participants)"],
),
widget=widgets.RadioSelectHorizontal,
label="How well do you think you will do on the advanced math task compared to the other two "
"participants in your group?"
)
d1 = models.IntegerField(
choices=(
[1, "Male"],
[2, "Female"],
[3, "Other"],
[4, "Prefer not to answer"],
),
widget=widgets.RadioSelect,
label="Gender Identification:"
)
d2 = models.IntegerField(
choices=(
[1, "Freshman"],
[2, "Sophomore"],
[3, "Junior"],
[4, "Senior"],
[5, "Graduate"],
),
widget=widgets.RadioSelect,
label="Current Grade Status:"
)
d3 = models.IntegerField(label="Age (in years):")
d4 = models.StringField(label="What is your academic major? (answer 'none' or 'undecided' if applicable")
d5 = models.FloatField(label="What is your GSU GPA?", max=5)
d6 = models.IntegerField(label="How many college-level math classes have you taken? (answer with a number")
d7 = models.StringField(label="What is your nationality?")
# FUNCTIONS
# def initial_rank(g: Group):
# players = g.get_players()
# print(players)
# players.sort(key=lambda p: -p.num_correct)
# # this code checks if there is a tie and then assigns the same rank
# for i in range(len(players)):
# if i > 0 and players[i].num_correct == players[i - 1].num_correct:
# rank_i = players[i - 1].rank_i
# else:
# rank_i = i + 1
# players[i].rank_i = rank_i
# print("inital rank", rank_i)
# # top = [p.id_in_group for p in players if p.rank <= 2]
# for player in players:
# if player.rank_i <= 2:
# player.participant.vars['is_top'] = 1
# else:
# player.participant.vars['is_top'] = 0
# print(player.id_in_group, player.participant.vars['name'], player.participant.vars['is_top'])
def initial_rank(g: Group):
import random
players = g.get_players()
print(players)
players.sort(key=lambda p: (-p.num_correct, -p.num_correct/p.attempted if p.attempted != 0 else 0, random.random()))
for i in range(len(players)):
if i > 0 and players[i].num_correct == players[i - 1].num_correct:
rank_i = players[i - 1].rank_i
else:
rank_i = i + 1
players[i].rank_i = rank_i
print("inital rank", rank_i)
for player in players[:2]:
player.participant.vars['is_top'] = 1
for player in players[2:]:
player.participant.vars['is_top'] = 0
print([(p.id_in_group, p.participant.vars['name'], p.participant.vars['is_top']) for p in players])
def round_rank(g: Group):
players = g.get_players()
for player in players:
player.name = player.participant.vars['name']
# create a list of players who are not top players but are promoted players
bottom_players = [p for p in players if p.participant.vars['is_top'] == 0 and p.participant.vars['is_promoted'] == 1]
# create a list to store the ranks for each bottom player
rank2 = []
# for each bottom player, create a list of all top players and the bottom player
for player in bottom_players:
rank2_p = [p for p in players if p.participant.vars['is_top'] == 1]
rank2_p.append(player)
print("before sort", rank2_p)
# sort the list of players by their correct answers
rank2_p.sort(key=lambda p: -p.num_correct)
print("after sort", rank2_p)
# assign ranks to each player in the list
player_rank = []
for i in range(len(rank2_p)):
rank=0
if i > 0 and rank2_p[i].num_correct == rank2_p[i - 1].num_correct:
player_rank.append({"player": rank2_p[i].name, "rank": player_rank[i - 1].get("rank")})
else:
player_rank.append({"player": rank2_p[i].name, "rank": (i + 1)})
# add the list of the bottom player and their ranks to the rank2 list
rank2.append({"pr": player_rank, "p": rank2_p})
# assign names to the players
# return the rank2 list
print(rank2)
return rank2
# def promote_players(g: Group):
# non_top_players = filter(lambda p: p.participant.vars['is_top'] == 0, g.get_players())
# top_players = [p for p in g.get_players() if p.participant.vars['is_top'] == 1]
# non_top_players = sorted(non_top_players, key=lambda p: -p.num_correct)
# half = len(non_top_players)//2
# for i in range(half):
# non_top_players[i].participant.vars['is_promoted'] = 1
# for i in range(half, len(non_top_players)):
# non_top_players[i].participant.vars['is_promoted'] = 0
# for i in range(len(top_players)):
# top_players[i].participant.vars['is_promoted'] = 0
def promote_players(g: Group):
non_top_players = [p for p in g.get_players() if p.participant.vars['is_top'] == 0]
top_players = [p for p in g.get_players() if p.participant.vars['is_top'] == 1]
# Calculate the sum of num_correct values over the past three rounds for each player
for player in non_top_players:
past_rounds = [player.in_round(round_num) for round_num in range(g.round_number - 2, g.round_number + 1)]
player.participant.vars['num_correct_sum'] = sum([p.num_correct for p in past_rounds])
print(player.participant.vars['num_correct_sum'])
non_top_players = sorted(non_top_players, key=lambda p: -p.participant.vars['num_correct_sum'])
half = math.ceil(len(non_top_players)/2)
for i in range(half):
non_top_players[i].participant.vars['is_promoted'] = 1
for i in range(half, len(non_top_players)):
non_top_players[i].participant.vars['is_promoted'] = 0
for i in range(len(top_players)):
top_players[i].participant.vars['is_promoted'] = 0
# PAGES
class Consent(Page):
def is_displayed(player):
return player.round_number == 1
form_model = 'player'
form_fields = ["consent"]
class GenInfo(Page):
def is_displayed(player):
return player.round_number == 1
class Background(Page):
def is_displayed(player):
return player.round_number == 1
@staticmethod
def vars_for_template(player: Player):
participants = len(player.group.get_players()) - 2
return {'participants': participants}
class PlacementInfo(Page):
def is_displayed(player):
return player.round_number == 1
class Promotion(Page):
def is_displayed(player):
return player.round_number == 5 and player.participant.vars['is_promoted'] == 1
class Promotion2(Page):
def is_displayed(player):
return player.round_number == 5 and player.participant.vars['is_promoted'] == 1
@staticmethod
def vars_for_template(player: Player):
name = player.participant.vars['name']
return dict(
name=name)
class Promotion3(Page):
def is_displayed(player):
return player.round_number == 5 and player.participant.vars['is_promoted'] == 1
form_model = 'player'
form_fields = ["l20"]
class PromotionHL(Page):
def is_displayed(player):
return player.round_number == 5 and player.participant.vars['is_top'] == 1
@staticmethod
def vars_for_template(player: Player):
name = player.participant.vars['name']
return dict(
name=name)
class Quiz(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
form_model = 'player'
form_fields = ["q1", "q2", "q3"]
@staticmethod
def error_message(player, values):
solutions = dict(
q1=1,
q2=2,
q3=1,
)
error_messages = dict()
for field_name in solutions:
if values[field_name] != solutions[field_name]:
error_messages[field_name] = 'Your answer is incorrect. Please try again.'
return error_messages
class PreQuestionnaire(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
form_model = 'player'
form_fields = ["l1", "l2", "l3", "l4", "l5"]
class Name(Page):
def is_displayed(player):
return player.round_number == 1
form_model = 'player'
form_fields = ['name']
def before_next_page(player: Player, timeout_happened):
player.participant.vars['name'] = player.name
print(player.name)
class Task1(Page):
timeout_seconds = 180
def is_displayed(player):
if player.round_number > 1 and player.participant.vars['is_top'] == 1:
return False
elif player.round_number > 4 and player.participant.vars['is_promoted'] == 1:
return False
else:
return True
def live_method(player, data):
print(data)
if data == 0:
player.num_correct += 1
else:
player.num_incorrect += 1
player.attempted = player.num_correct + player.num_incorrect
print('number_correct:', player.num_correct)
print('number_incorrect:', player.num_incorrect)
print('number_attempted:', player.attempted)
# @staticmethod
# def before_next_page(player, timeout_happened):
# player.participant.vars['payoff'] += player.num_correct * 2
# print('payoff:', player.participant.vars['payoff'])
@staticmethod
def before_next_page(player, timeout_happened):
player.participant.vars['payoff'] = player.participant.vars.get('payoff', 0)
player.participant.vars['payoff'] += player.num_correct * 2
print('payoff:', player.participant.vars['payoff'])
class Slider(Page):
timeout_seconds = 180
def is_displayed(player):
return player.round_number > 1 and player.participant.vars['is_top'] == 1 or (player.round_number > 4
and player.participant.vars['is_promoted'] == 1)
def live_method(player, data):
print(data)
if data == 0:
player.num_correct += 1
else:
player.num_incorrect += 1
player.attempted = player.num_correct + player.num_incorrect
print('number_correct:', player.num_correct)
print('number_incorrect:', player.num_incorrect)
print('number_attempted:', player.attempted)
def js_vars(player: Player):
lower1 = 101
lower2 = 11
upper1 = 500
upper2 = 99
return dict(
lower1=lower1,
upper1=upper1,
lower2=lower2,
upper2=upper2,
)
@staticmethod
def before_next_page(player, timeout_happened):
player.participant.vars['payoff'] += player.num_correct * 12
print('payoff:', player.participant.vars['payoff'])
class ResultsWaitPage1(WaitPage):
def is_displayed(player):
return player.round_number == 1
after_all_players_arrive = 'initial_rank'
class ResultsWaitPage2(WaitPage):
def is_displayed(player):
return player.round_number == 4
after_all_players_arrive = 'promote_players'
class ResultsWaitPage3(WaitPage):
def is_displayed(player):
return player.round_number > 4
after_all_players_arrive = 'round_rank'
class RPI(Page):
def is_displayed(player):
# return player.round_number == 2 and player.participant.vars['is_top'] == 0
return player.round_number > 4 and (player.participant.vars['is_promoted'] == 1 or
player.participant.vars['is_top'] == 1)
@staticmethod
def vars_for_template(player: Player):
rank2 = round_rank(player.group)
print(rank2)
groups = ['Group 1', 'Group 2', 'Group 3']
player_ranks = []
for rank2_player in rank2:
if player in rank2_player.get("p"):
for pr in rank2_player.get("pr"):
if pr.get("player") == player.name:
player_ranks.append(pr.get("rank"))
print("RANK", pr.get("rank"))
combined = dict(zip(groups, player_ranks))
print("RANKS", player_ranks)
if player.participant.vars['is_top']:
top = 1
else:
top = 0
return {'combined': combined,
'top': top,
'player_ranks': player_ranks}
class PrePromotionResults(Page):
def is_displayed(player: Player):
return player.round_number <= 4
class NoRPI(Page):
def is_displayed(player):
return player.round_number > 4 and (player.participant.vars['is_promoted'] == 0 and
player.participant.vars['is_top'] == 0)
class PlacementResultsTop(Page):
def is_displayed(player):
return player.round_number == 2 and player.participant.vars['is_top'] == 1
class PlacementResultsBottom(Page):
def is_displayed(player):
return player.round_number == 2 and player.participant.vars['is_top'] == 0
class Quiz2LL(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == 2 and player.participant.vars['is_top'] == 0
form_model = 'player'
form_fields = ["q4", "q5", "q6"]
@staticmethod
def error_message(player, values):
solutions = dict(
q4=1,
q5=1,
q6=2,
)
error_messages = dict()
for field_name in solutions:
if values[field_name] != solutions[field_name]:
error_messages[field_name] = 'Your answer is incorrect. Please try again.'
return error_messages
class Quiz2HL(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == 2 and player.participant.vars['is_top'] == 1
form_model = 'player'
form_fields = ["q7", "q8", "q6"]
@staticmethod
def error_message(player, values):
solutions = dict(
q7=1,
q8=1,
q6=2,
)
error_messages = dict()
for field_name in solutions:
if values[field_name] != solutions[field_name]:
error_messages[field_name] = 'Your answer is incorrect. Please try again.'
return error_messages
class Quiz3TP(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == 5 and player.participant.vars['is_top'] == 1
form_model = 'player'
form_fields = ["q13", "q14", "q15"]
@staticmethod
def error_message(player, values):
solutions = dict(
q13=1,
q14=1,
q15=1,
)
error_messages = dict()
for field_name in solutions:
if values[field_name] != solutions[field_name]:
error_messages[field_name] = 'Your answer is incorrect. Please try again.'
return error_messages
class Quiz3NP(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == 5 and player.participant.vars['is_promoted'] == 1
form_model = 'player'
form_fields = ["q9", "q10", "q11", "q12"]
@staticmethod
def error_message(player, values):
solutions = dict(
q9=1,
q10=1,
q11=1,
q12=1,
)
error_messages = dict()
for field_name in solutions:
if values[field_name] != solutions[field_name]:
error_messages[field_name] = 'Your answer is incorrect. Please try again.'
return error_messages
class IntroWaitPage(WaitPage):
def is_displayed(player):
return player.round_number == 2
class Introductions(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == 2
@staticmethod
def vars_for_template(player):
if player.participant.vars['is_top'] == 1:
top = 1
else:
top = 0
return dict(
top=top,
)
class StartTask(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == 2
class PostQuestionnaire(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == Constants.num_rounds and (player.participant.vars['is_promoted'] == 1 or
player.participant.vars['is_top'] == 1)
form_model = 'player'
form_fields = ["l6", "l7", "l8", "l9", "l10", "l11"]
class PostQuestionnaireLL(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == Constants.num_rounds and (player.participant.vars['is_promoted'] == 1)
form_model = 'player'
form_fields = ["l12", "l13", "l14", "l15", "l16", "l17", "l18", "l19"]
class PostQuestionnaireHL(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == Constants.num_rounds and player.participant.vars['is_top'] == 1
form_model = 'player'
form_fields = ["l12", "l13", "l16", "l17"]
class Demographics(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == Constants.num_rounds
form_model = 'player'
form_fields = ["d1", "d2", "d3", "d4", "d5", "d6", "d7"]
# class ThankYou(Page):
# @staticmethod
# def is_displayed(player: Player):
# return player.round_number == Constants.num_rounds
#
# @staticmethod
# def vars_for_template(player):
# lira = player.participant.vars['payoff']
# x = lira/30
# y = round(x*4)/4
# conversion = '{:.2f}'.format(round(y, 2))
# total = y + 5
# payout = '{:.2f}'.format(total)
# player.payment = payout
# return dict(
# lira=lira,
# payout=payout,
# conversion=conversion,
# )
class ThankYou(Page):
@staticmethod
def is_displayed(player: Player):
return player.round_number == Constants.num_rounds
@staticmethod
def vars_for_template(player):
lira = player.participant.vars['payoff']
x = lira / 30
y = round(x * 4) / 4 # round to nearest 0.25
conversion = '{:.2f}'.format(y)
total = y + 5
payout = '{:.2f}'.format(total)
player.payment = payout
return dict(
lira=lira,
payout=payout,
conversion=conversion,
)
page_sequence = [Consent,
GenInfo,
Background,
PlacementInfo,
Quiz,
PreQuestionnaire,
Name,
PlacementResultsTop,
PlacementResultsBottom,
Quiz2LL,
Quiz2HL,
IntroWaitPage,
Introductions,
StartTask,
Promotion,
Promotion2,
Promotion3,
PromotionHL,
Quiz3NP,
Quiz3TP,
Task1,
Slider,
ResultsWaitPage1,
ResultsWaitPage2,
ResultsWaitPage3,
PrePromotionResults,
RPI,
NoRPI,
PostQuestionnaire,
PostQuestionnaireLL,
PostQuestionnaireHL,
Demographics,
ThankYou]