#1
by
ccrabbe
Hi all -
I am in a situation where I am trying to use app_after_this_page to kick out subjects who time out on a decision page in my experiment.
My plan was to set a participant.vars flag in before_next page if timeout_happened=True, then check this flag in app_after_this_page, and if true to return the name of a dropout catcher app later in my app_sequence.
However, it seems like app_after_this_page isn't executed when timeout_happened==True for some reason? Here's the print trace of my execution with 4 subjects, with the last one timing out:
Guess.before_next_page called for player: 178
Guess.app_after_this_page called for player: 178
player.participant.vars= {'randomly_paid_task': 'g2_r8', 'role': 'col', 'previous_partner': 179, 'chosen_for_bonus': False, 'wait_page_arrival_1': 1719528280.0934074}
Guess.before_next_page called for player: 179
Guess.app_after_this_page called for player: 179
player.participant.vars= {'randomly_paid_task': 'g1_q2_r2', 'role': 'row', 'previous_partner': 178, 'chosen_for_bonus': False, 'wait_page_arrival_1': 1719528280.2678099}
Guess.before_next_page called for player: 180
Guess.app_after_this_page called for player: 180
player.participant.vars= {'randomly_paid_task': 'g2_r2', 'role': 'row', 'previous_partner': 181, 'chosen_for_bonus': True, 'wait_page_arrival_1': 1719528281.133287}
Guess.before_next_page called for player: 181
player 181 Guess timeout
participant.vars= {'randomly_paid_task': 'g2b_r1', 'role': 'col', 'previous_partner': 180, 'chosen_for_bonus': False, 'wait_page_arrival_1': 1719528281.217235, 'dropped_out.r1': True}
The "Guess timeout" is called inside before_next_page only if timeout_happened is true. It doesn't appear to execute app_after_this_page if timeout_happened is true.
If this is the case, does anybody have a good idea how I can skip to a later app upon soemeone timing out?
Thanks,
--Chris
#2
by
BonnEconLab
Can’t you simply introduce an intermediate pseudo page that follows your Guess page? That is, on Guess, use before_next_page to set your participant.vars dropout flag, and then on PseudoPage, check that flag in app_after_this_page?
#3
by
ccrabbe
Thanks for the workaround idea. I still can't think of the benefit of having app_after_this_page not called if timeout_happneed, but I'm sure there's a good reason. Since I am attempting to detect dropouts on many pages, instead of doubling the length of my page_sequence it feels simpler/cleaner to have one pseudopage at the end of my page_sequence which is displayed only to people with the dropped_out flag. It's auto-submitted by javascript, and its app_after_this_page is what kicks subjects to the end of the app_sequence. Thanks again, --Chris
#4
by
BonnEconLab
(edited )
I just tried to reproduce your issue in a minimal working example. However, I was unable to reproduce the behavior that you described:
> My plan was to set a participant.vars flag in before_next page if timeout_happened=True, then check this flag in app_after_this_page, and if true to return the name of a dropout catcher app later in my app_sequence.
>
> However, it seems like app_after_this_page isn't executed when timeout_happened==True for some reason?
In other words, your approach works flawlessly in my MWE:
class MyPage(Page):
timeout_seconds = 10
@staticmethod
def vars_for_template(player):
player.participant.dropped_out = False
@staticmethod
def before_next_page(player, timeout_happened):
if timeout_happened:
player.participant.dropped_out = True
@staticmethod
def app_after_this_page(player, upcoming_apps):
if player.participant.dropped_out == True:
return "zzz_dropout_app_end"
In the settings.py file, I set:
SESSION_CONFIGS = [
dict(
display_name = "Flag dropouts and move them to other app (https://www.otreehub.com/forum/1146/)",
name = "zzz_dropout_app",
app_sequence = ["zzz_dropout_app", "zzz_dropout_app_end"],
num_demo_participants = 4,
),
]
and
PARTICIPANT_FIELDS = [
"dropped_out",
]
(I used oTree 5.10.4 for my testing.)