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 from datetime import datetime, timedelta import datetime class Constants(BaseConstants): # ------------------------------------------------------------------------------------------------------------ # # --- oTree Settings (Don't Modify) --- # # ------------------------------------------------------------------------------------------------------------ # name_in_url = 'Welcome' players_per_group = None num_rounds = 1 import random class Subsession(BaseSubsession): def creating_session(self): players = self.get_players() # optional testing switches from session config force_tg = self.session.config.get('force_treatment_group') # 'treatment' / 'control' / None force_seq = self.session.config.get('force_discount_seq') # 1..4 / None force_bonus_part = self.session.config.get('force_bonus_part') # 1..3 / None force_bonus_eligible = self.session.config.get('force_bonus_eligible') # True / False / None # optional testing switches for Risk payment draw force_risk_round = self.session.config.get('force_risk_round_to_pay') # 1..12 / None force_risk_row = self.session.config.get('force_risk_row_to_pay') # 1..16 / None force_risk_draw = self.session.config.get('force_risk_random_draw') # only assign once need_treat = [p for p in players if 'treatment_group' not in p.participant.vars] need_seq = [p for p in players if 'discount_seq' not in p.participant.vars] need_bonus_part = [p for p in players if 'bonus_part' not in p.participant.vars] need_bonus_eligible = [p for p in players if 'bonus_eligible' not in p.participant.vars] # 1) 先分 treatment/control(主要测试内容):尽量均衡 + 随机 if need_treat: random.shuffle(need_treat) n = len(need_treat) half = n // 2 for i, p in enumerate(need_treat): if force_tg in ('treatment', 'control'): p.participant.vars['treatment_group'] = force_tg else: if i < half: p.participant.vars['treatment_group'] = 'treatment' elif i < 2 * half: p.participant.vars['treatment_group'] = 'control' else: p.participant.vars['treatment_group'] = random.choice(['treatment', 'control']) # 2) 再分 seq(order):严格均衡 1-4 循环 + 随机顺序 => 与 treatment 独立 # seq:严格均衡 1-4 循环 # seq=1: Lot1_48-->CE_48 # seq=2: CE_48-->Lot2_48 # seq=3: Lot1_84-->CE_84 # seq=4: CE_84-->Lot2_84 if need_seq: random.shuffle(need_seq) for i, p in enumerate(need_seq): if isinstance(force_seq, int) and force_seq in (1, 2, 3, 4): p.participant.vars['discount_seq'] = force_seq else: p.participant.vars['discount_seq'] = (i % 4) + 1 # 3) Which part is selected as the bonus source: 1, 2, or 3 # 1 = Risk, 2 = Time, 3 = Ambiguity if need_bonus_part: random.shuffle(need_bonus_part) for i, p in enumerate(need_bonus_part): if isinstance(force_bonus_part, int) and force_bonus_part in (1, 2, 3): p.participant.vars['bonus_part'] = force_bonus_part else: p.participant.vars['bonus_part'] = (i % 3) + 1 # 4) Whether participant is in the 5% bonus-paid group if need_bonus_eligible: random.shuffle(need_bonus_eligible) n_bonus = round(len(need_bonus_eligible) * 0.05) for i, p in enumerate(need_bonus_eligible): if isinstance(force_bonus_eligible, bool): p.participant.vars['bonus_eligible'] = force_bonus_eligible else: p.participant.vars['bonus_eligible'] = (i < n_bonus) # 5) Risk payment draw: set once for everyone for p in players: pv = p.participant.vars if 'risk_round_to_pay' not in pv: if isinstance(force_risk_round, int) and 1 <= force_risk_round <= 12: pv['risk_round_to_pay'] = force_risk_round else: pv['risk_round_to_pay'] = random.randint(1, 12) if 'risk_row_to_pay' not in pv: if isinstance(force_risk_row, int) and 1 <= force_risk_row <= 16: pv['risk_row_to_pay'] = force_risk_row else: pv['risk_row_to_pay'] = random.randint(1, 16) if 'risk_choice_field' not in pv: pv['risk_choice_field'] = f"choice_{pv['risk_row_to_pay']}" if 'risk_random_draw' not in pv: if isinstance(force_risk_draw, (int, float)) and 0 <= force_risk_draw <= 1: pv['risk_random_draw'] = float(force_risk_draw) else: pv['risk_random_draw'] = random.random() # 6) Time payment draw force_time_task = self.session.config.get('force_time_task_to_pay') # 'lot_4w'/'lot_8w'/'ce_4w'/'ce_8w'/None force_time_round = self.session.config.get('force_time_round_to_pay') # 1..18 / None force_time_draw = self.session.config.get('force_time_random_draw') # 0 or 1 / None for p in players: pv = p.participant.vars if 'time_task_to_pay' not in pv: if force_time_task in ('lot_4w', 'lot_8w', 'ce_4w', 'ce_8w'): pv['time_task_to_pay'] = force_time_task else: pv['time_task_to_pay'] = random.choice(['lot_4w', 'lot_8w', 'ce_4w', 'ce_8w']) if 'time_round_to_pay' not in pv: if isinstance(force_time_round, int) and 1 <= force_time_round <= 18: pv['time_round_to_pay'] = force_time_round else: pv['time_round_to_pay'] = random.randint(1, 18) if 'time_random_draw' not in pv: if force_time_draw in (0, 1): pv['time_random_draw'] = force_time_draw else: pv['time_random_draw'] = random.randint(0, 1) # 7) Ambiguity payment draw force_amb_task = self.session.config.get('force_amb_task_to_pay') # 'task1'/'task2'/'task3'/None force_amb_box = self.session.config.get('force_amb_box_if_indiff') # 'U'/'K'/None force_amb_draw = self.session.config.get('force_amb_random_draw') # float in [0,1]/None for p in players: pv = p.participant.vars if 'amb_task_to_pay' not in pv: if force_amb_task in ('task1', 'task2', 'task3'): pv['amb_task_to_pay'] = force_amb_task else: pv['amb_task_to_pay'] = random.choice(['task1', 'task2', 'task3']) # only used if selected task ended with Indifferent if 'amb_box_if_indiff' not in pv: if force_amb_box in ('U', 'K'): pv['amb_box_if_indiff'] = force_amb_box else: pv['amb_box_if_indiff'] = random.choice(['U', 'K']) if 'amb_random_draw' not in pv: if isinstance(force_amb_draw, (int, float)) and 0 <= force_amb_draw <= 1: pv['amb_random_draw'] = float(force_amb_draw) else: pv['amb_random_draw'] = random.random() class Group(BaseGroup): pass class Player(BasePlayer): part_id = models.StringField() # Create Yes/No answers for Consent form Consent = models.StringField() question_order = models.IntegerField() # ✅ 新增:記錄 treatment/control 組 treatment_group = models.StringField() bonus_part = models.IntegerField() bonus_eligible = models.BooleanField() def set_consent(self): self.Consent = 'Yes' self.question_order = self.participant.vars['discount_seq'] self.treatment_group = self.participant.vars['treatment_group'] self.bonus_part = self.participant.vars['bonus_part'] self.bonus_eligible = self.participant.vars['bonus_eligible'] self.participant.vars['t_order'] = self.question_order self.part_id = self.participant.label