from otree.api import (
Currency,
cu,
currency_range,
models,
widgets,
BaseConstants,
BaseSubsession,
BaseGroup,
BasePlayer,
ExtraModel,
WaitPage,
Page,
read_csv,
)
import units
import shared_out
doc = ''
class C(BaseConstants):
# built-in constants
NAME_IN_URL = 'hiring_funnel'
PLAYERS_PER_GROUP = None
NUM_ROUNDS = 12
# user-defined constants
NUM_CANDIDATES = 10
MU = 50
SIGMA = 15
SIGMA_RESUME = 15
SIGMA_ZOOM = 15
SIGMA_INPERSON = 15
DEFAULT_REWARD = 4.0
BONUS_RATE = 0.05
MAX_REWARD = 10
GATE1_SELECT = 5
GATE2_SELECT = 3
CANDIDATE_LABELS = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J')
class Subsession(BaseSubsession):
pass
class Group(BaseGroup):
pass
class Player(BasePlayer):
Q111_instructions_willing = models.IntegerField(
label='Sometimes survey takers rush through the questions without reading any instructions. Doing that ruins our results. Are you willing to read the instructions on the next pages?',
choices=[
(1, 'No'),
(2, 'Yes'),
],
widget=widgets.RadioSelect
)
Q139_condition_choice = models.IntegerField(
label='Which condition you do want to test?',
choices=[
(1, 'Condition 1 (Single-stage screening)'),
(2, 'Condition 2 (Multi-stage screening)'),
],
widget=widgets.RadioSelect
)
gate1_selected = models.LongStringField()
gate2_selected = models.LongStringField()
final_hire = models.StringField()
hired_true_quality = models.IntegerField()
reward_string = models.StringField()
# built-in hook function(s) (called automatically by oTree)
#
def creating_session(subsession: Subsession):
import json
import random
for player in subsession.get_players():
participant = player.participant
if subsession.round_number == 1:
if 'condition' in subsession.session.config:
participant.condition = subsession.session.config['condition']
else:
participant.condition = random.choice([1, 2])
practice_data = generate_candidate_data()
participant.practice_data_json = json.dumps(practice_data)
main_data = []
for _ in range(C.NUM_ROUNDS):
main_data.append(generate_candidate_data())
participant.main_data_json = json.dumps(main_data)
participant.history_json = json.dumps([])
#
# the below function(s) are user-defined, not called by oTree
#
def generate_candidate_data():
import random
result = {}
for i in range(C.NUM_CANDIDATES):
true_q = round(C.MU + random.gauss(0, 1) * C.SIGMA)
true_q = max(0, true_q)
resume = round(true_q + random.gauss(0, 1) * C.SIGMA_RESUME)
resume = max(0, resume)
zoom = round(true_q + random.gauss(0, 1) * C.SIGMA_ZOOM)
zoom = max(0, zoom)
inperson = round(true_q + random.gauss(0, 1) * C.SIGMA_INPERSON)
inperson = max(0, inperson)
result[C.CANDIDATE_LABELS[i]] = {
'true_q': true_q,
'resume': resume,
'zoom': zoom,
'inperson': inperson
}
return result
def calculate_reward(true_quality):
reward = min(C.MAX_REWARD, C.DEFAULT_REWARD + C.BONUS_RATE * true_quality)
return f"${reward:.2f}"
def get_history(participant):
import json
if participant.history_json:
return json.loads(participant.history_json)
return []
def add_to_history(participant, round_num, true_q, reward_str):
import json
history = get_history(participant)
history.append({
'round': round_num,
'true_q': true_q,
'reward': reward_str
})
participant.history_json = json.dumps(history)
def select_final_payment(participant):
import random
import json
history = get_history(participant)
main_rounds = [h for h in history if h['round'] > 0]
if main_rounds:
selected = random.choice(main_rounds)
participant.selected_round = selected['round']
participant.selected_reward_string = selected['reward']
reward_float = float(selected['reward'].replace('$', ''))
participant.payoff = cu(reward_float)
main_data = json.loads(participant.main_data_json)
round_data = main_data[selected['round'] - 1]
for label, data in round_data.items():
if data['true_q'] == selected['true_q']:
participant.selected_candidate = label
participant.selected_true_quality = selected['true_q']
break
#
class Consent(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
class Instructions(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
@staticmethod
def vars_for_template(player: Player):
return dict(
condition=player.participant.condition,
is_funnel=player.participant.condition == 2
)
class Comprehension(Page):
form_model = 'player'
form_fields = ['Q111_instructions_willing', 'Q139_condition_choice']
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
class PracticeIntro(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
class PracticeGate1(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class PracticeGate1After(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class PracticeGate2(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class PracticeGate2After(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class PracticeGate3(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
@staticmethod
def before_next_page(player: Player, timeout_happened):
pass
class PracticePerformance(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class MainRoundGate1(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class MainRoundGate1After(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class MainRoundGate2(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class MainRoundGate2After(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class MainRoundGate3(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
@staticmethod
def before_next_page(player: Player, timeout_happened):
pass
class MainRoundPerformance(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
class PostTaskWeights(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def error_message(player: Player, values):
pass
class Demographics(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
class FinalPayoff(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
pass
@staticmethod
def vars_for_template(player: Player):
pass
@staticmethod
def before_next_page(player: Player, timeout_happened):
pass
page_sequence = [Consent, Instructions, Comprehension, PracticeIntro, PracticeGate1, PracticeGate1After, PracticeGate2, PracticeGate2After, PracticeGate3, PracticePerformance, MainRoundGate1, MainRoundGate1After, MainRoundGate2, MainRoundGate2After, MainRoundGate3, MainRoundPerformance, PostTaskWeights, Demographics, FinalPayoff]