from otree.api import Currency as c, currency_range from otree.models import subsession from ._builtin import Page, WaitPage from .models import Constants from datetime import datetime, timedelta def order_is_84(page: Page): return page.participant.vars.get('order', '84') == '84' #def weeks_now(page: Page): # """根据 48/84 与当前段(1..18 / 19..36)返回 4 或 8。""" # # ✅ 如果我们明确已经进入 8w 阶段 # if page.participant.vars.get('force_4w', False): # return 4 # in_first_segment = page.round_number <= 18 # if order_is_84(page): # return 8 if in_first_segment else 4 # else: # return 4 if in_first_segment else 8 def weeks_now(page: Page): seq = page.participant.vars.get('discount_seq') order84 = (seq in (3, 4)) # seq=1/2 -> 48, seq=3/4 -> 84 in_first_segment = page.round_number <= 18 if order84: return 8 if in_first_segment else 4 else: return 4 if in_first_segment else 8 def is_before_4w(page: Page): """在第18轮之后、19轮正式开始前,用于显示 8w 的 Instructions 与 Quiz""" # 仅在 round 18 时显示一次(4w 刚结束) return page.round_number == 18 def round_index_1_to_6(page: Page): """将一段内 18 轮映射为 1..6(三次)。""" r = page.round_number if page.round_number <= 18 else (page.round_number - 18) if r < 7: return r elif r < 13: return r - 6 else: return r - 12 def block_last_round(page: Page): """是否是每 6 轮的最后一轮。""" return page.round_number % 6 == 0 # ---------- Debug dict ---------- def dbg(page: Page): pv = page.participant.vars return dict( dbg_round=page.round_number, dbg_page=pv.get('page'), dbg_node=pv.get('node'), dbg_weeks_now=weeks_now(page), dbg_force_4w=pv.get('force_4w'), dbg_ins4w_shown=pv.get('ins4w_shown'), dbg_quiz4w_shown=pv.get('quiz4w_shown'), dbg_mid4w_shown=pv.get('mid4w_shown'), dbg_path=pv.get('path'), dbg_order=pv.get('order'), dbg_discount_seq=pv.get('discount_seq'), ) class DebugPage(Page): def vars_for_template(self): pv = self.participant.vars return dict( discount_seq=pv.get('discount_seq'), order=pv.get('order'), path=pv.get('path'), page=pv.get('page'), node=pv.get('node'), ) class Instructions_8w(DebugPage): template_name = 'Time_Adaptive_CE_84/Instructions_8w.html' def is_displayed(self): return self.round_number == 1 and weeks_now(self) == 8 def vars_for_template(self): w = 8 date_str = (datetime.today() + timedelta(weeks=w)).strftime('%A, %B %d, %Y') ctx = super().vars_for_template() ctx.update(dict(date_56=date_str, weeks=w)) return ctx class Instructions(DebugPage): template_name = 'Time_Adaptive_CE_84/Instructions.html' def is_displayed(self): #shown = self.participant.vars.get('ins4w_shown', False) #return self.round_number == 19 and not shown and weeks_now(self) == 8 return self.round_number == 18 def vars_for_template(self): w = 4 date_str = (datetime.today() + timedelta(weeks=w)).strftime('%A, %B %d, %Y') ctx = super().vars_for_template() ctx.update(dict(date_28=date_str, weeks=w)) return ctx def before_next_page(self): self.participant.vars['ins4w_shown'] = True # ---------- Quiz ---------- class Time_CE_Quiz_8w(DebugPage): template_name = 'Time_Adaptive_CE_84/Time_CE_Quiz_8w.html' def is_displayed(self): return self.round_number == 1 and weeks_now(self) == 8 form_model = 'player' form_fields = ['q1a_8w', 'q1b_8w', 'q2_8w'] ans1a_8w = c(7.2) ans1b_8w = 3 ans2_8w = 2 def vars_for_template(self): ctx = super().vars_for_template() ctx.update(dict(weeks=8)) return ctx def error_message(self, values): # count number of incorrect attempts. if self.player.incorrect_attempts == None: self.player.incorrect_attempts = 0 if values['q1a_8w'] != self.ans1a_8w or values['q1b_8w'] != self.ans1b_8w or values['q2_8w'] != self.ans2_8w: self.player.incorrect_attempts += 1 # define error message that is displayed. if values['q1a_8w'] != self.ans1a_8w: return 'The amount you entered in question 1a is incorrect!' if values['q1b_8w'] != self.ans1b_8w: return 'Your response to question 1b is incorrect!' if values['q2_8w'] != self.ans2_8w: return 'Your response to question 2 is incorrect!' class Time_CE_Quiz(DebugPage): template_name = 'Time_Adaptive_CE_84/Time_CE_Quiz.html' def is_displayed(self): shown = self.participant.vars.get('quiz4w_shown', False) return self.round_number == 19 and not shown and weeks_now(self) == 4 form_model = 'player' form_fields = ['q1a', 'q1b', 'q2'] ans1a = c(4.1) ans1b = 2 ans2 = 3 def vars_for_template(self): ctx = super().vars_for_template() ctx.update(dict(weeks=4)) return ctx def error_message(self, values): if self.player.incorrect_attempts is None: self.player.incorrect_attempts = 0 if values['q1a'] != self.ans1a or values['q1b'] != self.ans1b or values['q2'] != self.ans2: self.player.incorrect_attempts += 1 if values['q1a'] != self.ans1a: return 'The amount you entered in question 1a is incorrect!' if values['q1b'] != self.ans1b: return 'Your response to question 1b is incorrect!' if values['q2'] != self.ans2: return 'Your response to question 2 is incorrect!' def before_next_page(self): self.participant.vars['quiz8w_shown'] = True # ---------- Discount_Lot1 ---------- class Discount_CE_1_8w(DebugPage): template_name = 'Time_Adaptive_CE_84/Discount_CE_1_8w.html' def is_displayed(self): return weeks_now(self) == 8 and (self.participant.vars['page'] == 1) form_model = 'player' form_fields = ['choice'] def vars_for_template(self): ctx = super().vars_for_template() if self.round_number in (1, 7, 13, 19, 25, 31): self.participant.vars['node'] = 1 round_index = round_index_1_to_6(self) choice_index = 6 if block_last_round(self) else self.participant.vars['node'] self.participant.vars['early'] = float(self.participant.vars['ce1_early'][0]) self.participant.vars['late'] = self.participant.vars['ce1_late_' + str(round_index)]['high_' + str(choice_index)] self.player.segment_index = 1 if self.round_number <= 18 else 2 self.player.current_week = 8 ctx.update({ 'ce_early': c(self.participant.vars['ce1_early'][0]), 'ce_late': c(self.participant.vars['ce1_late_' + str(round_index)]['high_' + str(choice_index)]), 'ce_node': self.participant.vars['node'], 'discount_seq': self.participant.vars['discount_seq'], 'weeks': 8, }) return ctx def before_next_page(self): self.player.set_payment_choice() self.player.set_node() self.player.set_ce() self.player.set_equivalent() self.player.set_option() class Discount_CE_1(Discount_CE_1_8w): template_name = 'Time_Adaptive_CE_84/Discount_CE_1.html' def is_displayed(self): return weeks_now(self) == 4 and (self.participant.vars['page'] == 1) def vars_for_template(self): ctx = super().vars_for_template() self.player.current_week = 4 ctx['weeks'] = 4 return ctx # ---------- Discount_Lot2 ---------- class Discount_CE_2_8w(DebugPage): template_name = 'Time_Adaptive_CE_84/Discount_CE_2_8w.html' def is_displayed(self): return weeks_now(self) == 8 and (self.participant.vars['page'] == 2) form_model = 'player' form_fields = ['choice'] def vars_for_template(self): ctx = super().vars_for_template() if self.round_number in (1, 7, 13, 19, 25, 31): self.participant.vars['node'] = 1 round_index = round_index_1_to_6(self) if block_last_round(self): choice_index = self.participant.vars['replica'] round_index = 3 else: choice_index = self.player.participant.vars['node'] if round_index == 3: self.participant.vars['replica'] = choice_index self.participant.vars['early'] = float(self.participant.vars['ce2_early'][0]) self.participant.vars['late'] = self.participant.vars['ce2_late_' + str(round_index)]['high_' + str(choice_index)] self.player.segment_index = 1 if self.round_number <= 18 else 2 self.player.current_week = 8 ctx.update({ 'ce_early': c(self.participant.vars['ce2_early'][0]), 'ce_late': c(self.participant.vars['ce2_late_' + str(round_index)]['high_' + str(choice_index)]), 'ce_node': self.participant.vars['node'], 'discount_seq': self.participant.vars['discount_seq'], 'weeks': 8, }) return ctx def before_next_page(self): self.player.set_payment_choice() self.player.set_node() self.player.set_ce() self.player.set_equivalent() self.player.set_option() class Discount_CE_2(Discount_CE_2_8w): template_name = 'Time_Adaptive_CE_84/Discount_CE_2.html' def is_displayed(self): return weeks_now(self) == 4 and (self.participant.vars['page'] == 2) def vars_for_template(self): ctx = super().vars_for_template() self.player.current_week = 4 ctx['weeks'] = 4 return ctx # ---------- Discount_Lot3 ---------- class Discount_CE_3_8w(DebugPage): template_name = 'Time_Adaptive_CE_84/Discount_CE_3_8w.html' def is_displayed(self): return weeks_now(self) == 8 and (self.participant.vars['page'] == 3) form_model = 'player' form_fields = ['choice'] def vars_for_template(self): ctx = super().vars_for_template() if self.round_number in (1, 7, 13, 19, 25, 31): self.participant.vars['node'] = 1 round_index = round_index_1_to_6(self) if block_last_round(self): choice_index = self.participant.vars['replica'] round_index = 2 else: choice_index = self.player.participant.vars['node'] if round_index == 2: self.participant.vars['replica'] = choice_index self.participant.vars['early'] = float(self.participant.vars['ce3_early'][0]) self.participant.vars['late'] = self.participant.vars['ce3_late_' + str(round_index)]['high_' + str(choice_index)] self.player.segment_index = 1 if self.round_number <= 18 else 2 self.player.current_week = 8 ctx.update({ 'ce_early': c(self.participant.vars['ce3_early'][0]), 'ce_late': c(self.participant.vars['ce3_late_' + str(round_index)]['high_' + str(choice_index)]), 'ce_node': self.participant.vars['node'], 'discount_seq': self.participant.vars['discount_seq'], 'weeks': 8, }) return ctx def before_next_page(self): self.player.set_payment_choice() self.player.set_node() self.player.set_ce() self.player.set_equivalent() self.player.set_option() class Discount_CE_3(Discount_CE_3_8w): template_name = 'Time_Adaptive_CE_84/Discount_CE_3.html' def is_displayed(self): return weeks_now(self) == 4 and (self.participant.vars['page'] == 3) def vars_for_template(self): ctx = super().vars_for_template() self.player.current_week = 4 ctx['weeks'] = 4 return ctx # ---------- Middle screen ---------- class Middle_Screen_8w(DebugPage): template_name = 'Time_Adaptive_CE_84/Middle_Screen_8w.html' def is_displayed(self): return weeks_now(self) == 8 and (self.round_number in (1, 7, 13)) def vars_for_template(self): ctx = super().vars_for_template() ctx.update( dict(round_number=self.round_number, weeks=8) ) return ctx def before_next_page(self): self.player.set_page() self.player.set_equivalent() # ✅ NEW: 生成 equi self.player.set_choice_options() # ✅ NEW: 生成 ce1_early/ce1_late_* 等 class Middle_Screen(DebugPage): template_name = 'Time_Adaptive_CE_84/Middle_Screen.html' def is_displayed(self): # 8w 的三个 block 起点:19, 25, 31 return weeks_now(self) == 4 and (self.round_number in (19, 25, 31)) def vars_for_template(self): ctx = super().vars_for_template() ctx.update( dict(round_number=self.round_number, weeks=4) ) return ctx def before_next_page(self): self.player.set_page() self.player.set_equivalent() # ✅ NEW self.player.set_choice_options() # ✅ NEW class End_Screen_8w(DebugPage): template_name = 'Time_Adaptive_CE_84/End_Screen_8w.html' def is_displayed(self): return (self.round_number == 18) and (weeks_now(self) == 8) def vars_for_template(self): ctx = super().vars_for_template() ctx.update( dict(round_number=self.round_number, ce_node=self.participant.vars.get('node'), weeks=8, discount_seq=self.participant.vars.get('discount_seq'), ) ) return ctx def before_next_page(self): self.participant.vars['force_8w'] = True # ✅ 防止 8w choices 在 round 19 抢跑 self.participant.vars['page'] = 0 # ✅ 可选:重置“只显示一次”的标记,避免你测试多次后被挡住 self.participant.vars.pop('ins4w_shown', None) self.participant.vars.pop('quiz4w_shown', None) self.participant.vars.pop('mid4w_shown', None) class End_Screen(DebugPage): template_name = 'Time_Adaptive_CE_84/End_Screen.html' # 8w 段结束:第 36 轮 def is_displayed(self): return (self.round_number == 36) and (weeks_now(self) == 4) def vars_for_template(self): ctx = super().vars_for_template() ctx.update( dict(round_number=self.round_number, ce_node=self.participant.vars.get('node'), weeks=4, discount_seq=self.participant.vars.get('discount_seq'), ) ) return ctx def app_after_this_page(self, upcoming_apps): # 保险:只有到了最后一轮才考虑跳转 if self.player.round_number != Constants.num_rounds: return seq = self.participant.vars.get('discount_seq') # seq=1:做完 CE 直接去 Belief_EFD(跳过 Lot2) if seq == 3 and 'Belief_EFD' in upcoming_apps: return 'Belief_EFD' # seq=2:CE -> Lot2 if seq == 4 and 'Time_Adaptive_Lot_2_84' in upcoming_apps: return 'Time_Adaptive_Lot_2_84' page_sequence = [ # 8w 开场(仍在第 18 轮显示说明与测验,然后第 19 轮正式进入 8w) Instructions_8w, Time_CE_Quiz_8w, Middle_Screen_8w, # 8w 的选择页 Discount_CE_1_8w, Discount_CE_2_8w, Discount_CE_3_8w, # —— 8w 结束页(第 36 轮) —— 放到最后 End_Screen_8w, # 4w 开场 Instructions, Time_CE_Quiz, Middle_Screen, # 4w 的选择页 Discount_CE_1, Discount_CE_2, Discount_CE_3, # —— 4w 结束页(第 18 轮) —— 必须在切换到 8w 的指引页之前 End_Screen, ]