from otree.api import (
models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer,
Currency as c, currency_range
)
from otree.models import subsession
from Multiple_Price_List.config import *
import random
from random import randrange
# ---------------------------------------------------------------------------------------------------------------- #
# *** CLASS CONSTANTS *** #
# ---------------------------------------------------------------------------------------------------------------- #
class Constants(BaseConstants):
# ------------------------------------------------------------------------------------------------------------ #
# --- oTree Settings (Don't Modify) --- #
# ------------------------------------------------------------------------------------------------------------ #
name_in_url = 'Time_Adaptive_CE_84'
players_per_group = None
num_rounds = 36
class Subsession(BaseSubsession):
def creating_session(self):
for p in self.get_players():
pv = p.participant.vars
# ------------------------------------------------------------
# 1) discount_seq 兜底(只有单测 CE 时才会用到)
# 正常完整流程:Risk/Welcome 已经写好,就不会走这个兜底
# ------------------------------------------------------------
seq = pv.get('discount_seq')
if seq is None:
seq = 3
pv['discount_seq'] = seq
# ------------------------------------------------------------
# 2) ✅ round 1:强制把 path/order 修正成与 seq 一致 + 清残留
# 这一步是防止:seq=1/2 但 order 残留成 84 或 page 残留成 2
# ------------------------------------------------------------
if p.round_number == 1:
pv['path'] = 'Lot1→CE' if seq in (1, 3) else 'CE→Lot2'
pv['order'] = '48' if seq in (1, 2) else '84'
# 清理跨 app/多次测试残留的强制切段标记
pv.pop('force_8w', None)
pv.pop('force_4w', None)
# 清理“一次性页面”标记(方便你反复测试,也避免跳页)
pv.pop('ins8w_shown', None); pv.pop('quiz8w_shown', None); pv.pop('mid8w_shown', None)
pv.pop('ins4w_shown', None); pv.pop('quiz4w_shown', None); pv.pop('mid4w_shown', None)
# ✅ 关键:重置 randseq/page,避免沿用上一个 app 的 page=2/3 直接抢跑到 choice 页
pv.pop('randseq', None)
pv['page'] = 0 # 先置 0,等下面生成 randseq 再设定
# ------------------------------------------------------------
# 3) 支付相关兜底
# ------------------------------------------------------------
pv.setdefault('task_to_pay', 0)
pv.setdefault('round_to_pay', random.randint(1, Constants.num_rounds))
# ------------------------------------------------------------
# 4) ✅ 单测兜底:如果没从 Risk 来,就补齐 cert_equi_list2/9/12
# (你 CE 的 set_equivalent/set_choice_options 需要它们)
# ------------------------------------------------------------
pv.setdefault('cert_equi_list2', [c(4.1)])
pv.setdefault('cert_equi_list9', [c(4.1)])
pv.setdefault('cert_equi_list12', [c(4.1)])
# ------------------------------------------------------------
# 5) block 开始 node=1
# ------------------------------------------------------------
if p.round_number in (1, 7, 13, 19, 25, 31):
pv['node'] = 1
# ------------------------------------------------------------
# 6) round 1 生成 randseq,并设置 page
# ------------------------------------------------------------
if p.round_number == 1:
randseq = pv.get('randseq')
if not randseq:
randseq = [3, 2, 1]
random.shuffle(randseq)
pv['randseq'] = randseq
pv['page'] = randseq[0]
class Group(BaseGroup):
pass
class Player(BasePlayer):
### variables for quiz ###
q1a = models.CurrencyField(
label=' a) Please enter the amount you will earn. '
'(Please use "." to separate Dollar and Cent values)',
min=0
)
q1b = models.IntegerField(
choices=[[1, 'Today'], [2, 'Tomorrow'], [3, 'In 4 weeks'], ],
label=' b) When will you receive your payments from this task?',
)
q2 = models.IntegerField(
choices=[[1, 'Never'], [2, '3 times - every time a new round starts'], [3, 'For each time (18 times)'], ],
label='2. How often does the amount of the Alternative - 4 weeks change during this task?',
)
q1a_8w = models.CurrencyField(
label=' a) Please enter the amount you will earn. '
'(Please use "." to separate Dollar and Cent values)',
min=0
)
q1b_8w = models.IntegerField(
choices=[[1, 'Today'], [2, 'Tomorrow'], [3, 'In 8 weeks'], ],
label=' b) When will you receive your payments from this task?',
)
q2_8w = models.IntegerField(
choices=[[1, 'Never'], [2, '3 times - every time a new round starts'], [3, 'For each time (18 times)'], ],
label='2. How often does the amount of the Alternative - tomorrow change during this task?',
)
incorrect_attempts = models.IntegerField(initial=0)
ce = models.StringField() # indicates from which lottery the CE comes from
equi = models.CurrencyField() # indicates the CE of the lottery from the risk task
early_option = models.CurrencyField()
late_option = models.CurrencyField()
current_week = models.IntegerField()
segment_index = models.IntegerField()
def set_option(self):
self.early_option = self.participant.vars['early']
self.late_option = self.participant.vars['late']
choice = models.StringField() #indecates the choice.
def set_node(self):
# ✅ CHANGED: 36轮下,每个block最后一轮都重置 node(6,12,18,24,30,36)
if self.round_number in (6, 12, 18, 24, 30, 36):
self.participant.vars['node'] = 1
else:
last_round_node = self.participant.vars['node']
if self.choice == 'early':
self.participant.vars['node'] = last_round_node * 2
else:
self.participant.vars['node'] = last_round_node * 2 - 1
def set_page(self):
# ✅ CHANGED: 36轮=两个18轮段(每段3个block),重复 randseq[0..2]
if self.round_number in (1, 19):
self.participant.vars['page'] = self.participant.vars['randseq'][0]
elif self.round_number in (7, 25):
self.participant.vars['page'] = self.participant.vars['randseq'][1]
elif self.round_number in (13, 31):
self.participant.vars['page'] = self.participant.vars['randseq'][2]
def set_choice_options(self):
if self.participant.vars['page'] == 1:
##################################################################
############## CE 1 #############################################
##################################################################
# define choices for early choices
self.participant.vars['ce1_early'] = self.participant.vars['cert_equi_list2']
float(self.participant.vars['ce1_early'][0])
# define multiplication factor
multfact = 1.05
# define choices for choice 5
late_5 = {}
for i in range(1, 17):
value = self.participant.vars['ce1_early'][0] * multfact ** (i - 1)
late_5['high_%s' % i] = value
# define choices for choice 4
late_4 = {}
for i in range(1, 9):
num1 = 2 * i
num2 = num1 - 1
value = (late_5['high_' + str(num1)] + late_5['high_' + str(num2)]) / 2
late_4['high_%s' % i] = value
# define choices for choice 3
late_3 = {}
for i in range(1, 5):
num1 = 2 * i
num2 = num1 - 1
value = (late_4['high_' + str(num1)] + late_4['high_' + str(num2)]) / 2
late_3['high_%s' % i] = value
# define choices for choice 2
late_2 = {}
for i in range(1, 3):
num1 = 2 * i
num2 = num1 - 1
value = (late_3['high_' + str(num1)] + late_3['high_' + str(num2)]) / 2
late_2['high_%s' % i] = value
# define choices for choice 1
late_1 = {
'high_1': (late_2['high_2'] + late_2['high_1']) / 2,
}
# create options for 6th choice for which later payment is lower than early payment
late_6 = {
'high_6': self.participant.vars['ce1_early'][0] - 0.50,
}
# rounding
new_1 = {}
for i in late_1:
new_1[i] = round(late_1[i] + 0.01, 1) # we have to add this very small number since otherwise the rounding inpython is strange
self.participant.vars['ce1_late_1'] = new_1
new_2 = {}
for i in late_2:
new_2[i] = round(late_2[i] + 0.01, 1)
self.participant.vars['ce1_late_2'] = new_2
new_3 = {}
for i in late_3:
new_3[i] = round(late_3[i] + 0.01, 1)
self.participant.vars['ce1_late_3'] = new_3
new_4 = {}
for i in late_4:
new_4[i] = round(late_4[i] + 0.01, 1)
self.participant.vars['ce1_late_4'] = new_4
new_5 = {}
for i in late_5:
new_5[i] = round(late_5[i] + 0.01, 1)
self.participant.vars['ce1_late_5'] = new_5
new_6 = {}
for i in late_6:
new_6[i] = round(late_6[i] + 0.01, 1)
self.participant.vars['ce1_late_6'] = new_6
elif self.participant.vars['page'] == 2:
##################################################################
############## CE 2 ########################################
##################################################################
# define choices for early choices
self.participant.vars['ce2_early'] = self.participant.vars['cert_equi_list9']
float(self.participant.vars['ce2_early'][0])
# define multiplication factor
multfact = 1.04
# define choices for choice 5
late_5 = {}
for i in range(1, 17):
value = self.participant.vars['ce2_early'][0] * multfact ** (i - 1)
late_5['high_%s' % i] = value
# define choices for choice 4
late_4 = {}
for i in range(1, 9):
num1 = 2 * i
num2 = num1 - 1
value = (late_5['high_' + str(num1)] + late_5['high_' + str(num2)]) / 2
late_4['high_%s' % i] = value
# define choices for choice 3
late_3 = {}
for i in range(1, 5):
num1 = 2 * i
num2 = num1 - 1
value = (late_4['high_' + str(num1)] + late_4['high_' + str(num2)]) / 2
late_3['high_%s' % i] = value
# define choices for choice 2
late_2 = {}
for i in range(1, 3):
num1 = 2 * i
num2 = num1 - 1
value = (late_3['high_' + str(num1)] + late_3['high_' + str(num2)]) / 2
late_2['high_%s' % i] = value
# define choices for choice 1
late_1 = {
'high_1': (late_2['high_2'] + late_2['high_1']) / 2,
}
# the 6th choice will be a replica of another but this will be defined in pages.
# rounding
new_1 = {}
for i in late_1:
new_1[i] = round(late_1[i] + 0.01, 1)
self.participant.vars['ce2_late_1'] = new_1
new_2 = {}
for i in late_2:
new_2[i] = round(late_2[i] + 0.01, 1)
self.participant.vars['ce2_late_2'] = new_2
new_3 = {}
for i in late_3:
new_3[i] = round(late_3[i] + 0.01, 1)
self.participant.vars['ce2_late_3'] = new_3
new_4 = {}
for i in late_4:
new_4[i] = round(late_4[i] + 0.01, 1)
self.participant.vars['ce2_late_4'] = new_4
new_5 = {}
for i in late_5:
new_5[i] = round(late_5[i] + 0.01, 1)
self.participant.vars['ce2_late_5'] = new_5
else:
##################################################################
############## CE 3 ########################################
##################################################################
# define choices for early choices
self.participant.vars['ce3_early'] = self.participant.vars['cert_equi_list12']
float(self.participant.vars['ce3_early'][0])
# define multiplication factor
multfact = 1.06
# define choices for choice 5
late_5 = {}
for i in range(1, 17):
value = self.participant.vars['ce3_early'][0] * multfact ** (i - 1)
late_5['high_%s' % i] = value
# define choices for choice 4
late_4 = {}
for i in range(1, 9):
num1 = 2 * i
num2 = num1 - 1
value = (late_5['high_' + str(num1)] + late_5['high_' + str(num2)]) / 2
late_4['high_%s' % i] = value
# define choices for choice 3
late_3 = {}
for i in range(1, 5):
num1 = 2 * i
num2 = num1 - 1
value = (late_4['high_' + str(num1)] + late_4['high_' + str(num2)]) / 2
late_3['high_%s' % i] = value
# define choices for choice 2
late_2 = {}
for i in range(1, 3):
num1 = 2 * i
num2 = num1 - 1
value = (late_3['high_' + str(num1)] + late_3['high_' + str(num2)]) / 2
late_2['high_%s' % i] = value
# define choices for choice 1
late_1 = {
'high_1': (late_2['high_2'] + late_2['high_1']) / 2,
}
# the 6th choice will be a replica of another but this will be defined in pages.
# rounding
new_1 = {}
for i in late_1:
new_1[i] = round(late_1[i] + 0.01, 1)
self.participant.vars['ce3_late_1'] = new_1
new_2 = {}
for i in late_2:
new_2[i] = round(late_2[i] + 0.01, 1)
self.participant.vars['ce3_late_2'] = new_2
new_3 = {}
for i in late_3:
new_3[i] = round(late_3[i] + 0.01, 1)
self.participant.vars['ce3_late_3'] = new_3
new_4 = {}
for i in late_4:
new_4[i] = round(late_4[i] + 0.01, 1)
self.participant.vars['ce3_late_4'] = new_4
new_5 = {}
for i in late_5:
new_5[i] = round(late_5[i] + 0.01, 1)
self.participant.vars['ce3_late_5'] = new_5
def set_equivalent(self):
if self.participant.vars['page'] == 1:
self.equi = self.participant.vars['cert_equi_list2'][0]
elif self.participant.vars['page'] == 2:
self.equi = self.participant.vars['cert_equi_list9'][0]
else:
self.equi = self.participant.vars['cert_equi_list12'][0]
def set_ce(self):
if self.participant.vars['page'] == 1:
self.ce = 'list2'
elif self.participant.vars['page'] == 2:
self.ce = 'list9'
else:
self.ce = 'list12'
round_to_pay = models.StringField()
payment = models.CurrencyField()
def set_payment_choice(self):
pv = self.participant.vars
self.round_to_pay = 'no'
self.payment = None
selected_task = pv.get('time_task_to_pay')
selected_task_round = pv.get('time_round_to_pay')
# This app: 8w first, then 4w
# rounds 1–18 = ce_8w
# rounds 19–36 = ce_4w
if selected_task == 'ce_8w':
target_round = selected_task_round
delay_code = 8
elif selected_task == 'ce_4w':
target_round = 18 + selected_task_round
delay_code = 4
else:
return
if self.round_number != target_round:
return
self.round_to_pay = 'yes'
# CE task: deterministic payment
if self.choice == 'early':
self.payment = pv['early']
pv['payoff_time'] = self.payment
pv['payoff_time_type'] = 0
pv['payoff_time_date'] = 0
else:
self.payment = pv['late']
pv['payoff_time'] = self.payment
pv['payoff_time_type'] = 0
pv['payoff_time_date'] = 1
# store for results page
pv['time_selected_task'] = selected_task
pv['time_selected_round'] = selected_task_round
pv['time_selected_choice'] = self.choice
pv['time_selected_type'] = 'ce'
pv['time_selected_delay'] = delay_code
pv['time_selected_early'] = pv['early']
pv['time_selected_late'] = pv['late']
pv['time_lottery_realization'] = 'not_applicable'
pv['time_random_draw_used'] = None
pv['time_final_bonus'] = self.payment