#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.)