#1 by pjp
Hi, I have a problem with randomizing page sequence. Please see the full debugging MRE from the attached ZIP-folder. In short, I have a control group and 2 treatment groups. We want to obtain values pre-treatment and then values post-treatment conditional on the treatment group and its corresponding treatment channel {1, 2}. The three possible scenarios for page sequences excluding randomization are: Control: {A, B1, B2, C, D, E1, E2, F} Treatment 1: {A, B1, B2, C, X1, E1, F} Treatment 2: {A, B1, B2, C, X2, E2, F} Page X# is the treatment. Questionnaire pages {B1, B2} (pre) map directly to {E1, E2} (post). For control, the order of {B1, B2} should map directly to {E1, E2}. So if control has order {B2, B1}, it should see post-treatment questionnaire in order {E2, E1}. The treatments should also see {B1, B2} or {B2, B1}, but then the {E1 or E2} is dictated by the treatment {X1, X2}. Pages A, C, D, and F are fixed per their position. The page A should start the sequence always, then the pre-treatment questions B# shall be in a randomized order, and the post-treatment questions E# should match the randomized order of B#. Then in the middle, there are C, D for backgrounds of control, and treatment has only C in the middle of the sequence for background checks before continuing to treatment and its corresponding post-treatment question. Then F finishes off each and every page sequence. One must note that all these actions must be done per the same round_number, so that the output file is as clean as possible. So the page display/randomization must be done via some other means than utilizing round numbers. Use the following commands to initialize the virtual environment: cd/path/buggy project source ../new_otree_env/bin/activate otree devserver Thank you in advance!
#3 by DmitriiG
Hi Preface: I am not at all an expert, but I will try to explain the logic behind a potential solution. If it is of paramount importance to keep the number of rounds to 1 (otherwise, you can use https://groups.google.com/g/otree/c/xp1K2DrvePk, but it seems from the code that you have already tried that) and your pages are simple surveys, you can try to "randomize" the pages via templates/getforms. The idea is to randomise control/treatment1/treatment2 and additionally define a random "order" variable for each participant, which you will then pass on to the template on each page. So, roughly, you could do the following (just a rough sketch, not tested): def creating_session(subsession: Subsession): import itertools if subsession.round_number == 1: treatment_cycle= itertools.cycle(['C','T1','T2']) for p in subsession.get_players(): p.treatment = next(treatment_cycle) p.order=random.choice([True,False] ... class A(Page): template_name = 'bug/A.html' ... class BA(Page): template_name = 'bug/BA.html' @staticmethod def get_form_fields(player): if player.order: return [@FORMS FOR B1@] else: return [@FORMS FOR B2@] @staticmethod def vars_for_template(player: Player): return dict( random_Order = player.order ) ############# Then, in your template for BA, you put both B1 and B2 pages, but using conditionals in the template, only show 1 or 2, something like this: {{ if random_Order }} @@@@template for B1@@@@ {{ else }} @@@@template for B2@@@@ {{ endif }} ############ Then, in your template for BB, you put both B1 and B2 pages again, but now do: {{ if random_Order }} @@@@template for B2@@@@ {{ else }} @@@@template for B1@@@@ {{ endif }} The above logic would be sufficient to randomise for b1/b2 for both control and treatments. The C page is for everyone. The next two pages you display for control only (as in your code), but add the same kind of logic - i.e., add both to a big template and change it on the page itself rather than through __init (but don't forget the code for the display and different forms), like this: Page EA template {{ if random_Order }} @@@@template for E1@@@@ {{ else }} @@@@template for E2@@@@ {{ endif }} Page EB template {{ if random_Order }} @@@@template for E2@@@@ {{ else }} @@@@template for E1@@@@ {{ endif }} Lastly, you have two pages that you show to treatment. Then your code for the page in init would look something like this: class X1(Page): template_name = 'bug/XA.html' @staticmethod def is_displayed(player: Player): return player.treatment != 'C' def get_form_fields(player): if player.treatment=='T1': return [@FORMS FOR X1@] else: return [@FORMS FOR X2@] @staticmethod def vars_for_template(player: Player): return dict( treatment = player.treatment ) Page XA {{ if treatment=='T1'}} @@@@template for X1@@@@ {{ else }} @@@@template for X2@@@@ {{ endif }} Lastly, you add page XB, where the templates E1/E2 are shown depending on the treatment. I hope this works :)
#4 by pjp
Thank you for your response. I am quite confident that your suggestion (transferring the is_displayed method's functionality to the HTML and JS side) would work. However, there is a significant concern that prevented me from adopting this approach earlier, even though I briefly considered it a couple days ago. Moving essentially a backend functionality to the frontend poses a substantial risk. The software could become unstable with future updates to browsers (Chrome, Firefox, or Edge), I also have first-hand experience on such issues with oTree experiments. Moreover, developing on top of and maintaining such critical functions should ideally be done on the Python __init__.py side as much as possible. If anyone else comes across this question, please take into account that such functionality is better handled on the Python side. I hope I don't come across as too blunt. I just aim to be clear and concise.
#5 by DmitriiG (edited )
Well, you can achieve the same outcome without templates by simply having extra (duplicate) pages in your sequence: A @displayed for everyone B1 @displayed for player.order==True B2 @displayed for everyone BB1 @displayed for player.order==False C @displayed for everyone X1 @displayed for player.treatment=='T1' X2 @displayed for player.treatment=='T2' E1 @displayed for [(player.treatment=='C' AND player.order=True) OR (player.treatment=='T1')] E2 @displayed for [(player.treatment=='C' OR player.treatment=='T2')] EE1 @displayed for [(player.treatment=='C' AND player.order=False) OR (player.treatment=='T2')] f @for everyone page sequence=[a,b1,b2,bb1,c,x1,x2,e1,e2,ee1,f] Where BB1 is simply the same page as B1 but with different display condition, etc If i am not mistaken, this achieves the same randomisation as my previous comment but doesn't shift things to frontend :)