Search code examples
logiccontrolsdocassemble

Best practice for review screens + off-ramps?


Following-up on this question, I'm using the now attempting to implement a review screen, which will re-evaluate the off-ramp.

The code works this way:

  1. user makes selection [mandatory code triggers question] ... one of the options gives the value "not_supported"
  2. if user chooses unsupported feature, is sent to a "not supported" screen [event] - they must then click "back" to continue the interview and re-enter the information at the screen at item 1
  3. once user makes a supported selection, they eventually end up at a review screen

What's happening now is that if the user, on the review screen, chooses the "not supported" feature from the screen at item 1 above, they still go back to the review screen, but with the "not supported" selection as a variable (not the desired behavior).

One idea is to simply stop showing that selection if it has been chosen previous (i.e. if the user has previously been told that option is not currently supported) per this question. That's only a partial solution, though, because if the user hasn't chosen the unsupported option at some point, that option can still be selected from the review screen. I'm running into a small problem here, though in that I'm setting a "flag" variable in my code (see below) to remove the option using 'show if': not not_supported_shown_flag, which actually works great, except that hitting "review" from a review screen unsets it -- QUESTION 1: is there a way to prevent that from happening (i.e. to "review-proof" certain variables?).

For the larger issue, I thought there was probably a solution by including the code with a initial specifier (or another logic control specifier). Eg:

mandatory: True
depends on: var
code: |
    if var == "not_supported":
        not_supported_shown_flag = True # the "offramp taken flag" variable, meant to remove the non-supported option from future visits to that screen by the user
        not_supported_event_screen # the "off ramp"

The problems I'm running into now is that, following the top answer on this question are that:

  1. the "back" button takes the user back to the review screen without triggering not_supported_event_screen
  2. all sorts of chaos then ensues, including that (a) the user is still able to advance past the review screen by clicking "resume" and then at any point subsequent if they hit back they are taken to the not_supported_event_screen, and (b) when the user clicks "revisit" again, they are taken to not_supported_event_screen, where the "Exit" and "Exit and clear answers" buttons don't work as expected.

QUESTION 2: wondering if there's a best-practice / accepted method for solving for this sort of situation? Initial and reconsider seem unnecessarily process-heavy because they'd call the code block (if I understand correctly) every time the screen changes (not that I expect process-intensity to make a noticeable difference here). mandatory: True appears necessary, or the code block doesn't run on the first pass (only when recalculating).

Ideas I have had include:

  1. Breaking-up the code that sets the not_supported_shown_flag and the not_supported_event_screen into two separate code blocks, to use two separate logic control modifiers
  2. Using code on the actual question / specific answers to the question (I'm not sure if that's possible, because the question has been created in the format shown here).
  3. Somehow forcing certain questions / review pages to "refresh" and thus trigger code blocks (the lack of which I suspect is part of the issue).

I'm just really not sure what the solve is - it seems like going "back" doesn't constitute a refresh sufficient to trigger a "refresh," and review pages work to some degree similarly as "events."


Solution

  • It would be necessary to see your YAML to answer these questions.

    Which question is shown when the screen loads is a function of the interview logic. When the screen loads, Docassemble evaluates the initial or mandatory blocks in order from the top of the YAML to the bottom, and it skips any mandatory blocks that have been fully executed already. If any undefined variable is encountered, Docassemble looks for a block in the YAML that will define that variable, and it will try to execute that block.

    If your review screen is not accessed through an "action," then you can divert the user away from the review screen it by having a mandatory or initial block that is evaluated every time the screen loads, which sends the user down a different path if var == "not_supported". It does not take much processing power for Python to make a comparison like this, so there is no problem having this in the code that gets executed every time the screen loads. My typical approach to the interview logic is to have a single long mandatory code block that contains many variable references and if/else statements containing references to particular variables, or calls to .gather(). This block is like the "to do" list that docassemble follows every time the screen loads. This ensures that if the user makes any changes to variables that impact the logic, then the next time the screen loads, that logic will respond appropriately to the changed circumstances. This approach simplifies the specification of the interview logic. In my own interviews, I don't need to set "flag" variables like not_supported_shown_flag.

    The depends on modifier is typically used on questions that are non-mandatory. In your example, depends on means that if var changes, the not_supported_shown_flag variable is invalidated. Whether the invalidation of not_supported_shown_flag causes the interview logic to do anything differently depends on whether the interview logic uses not_supported_shown_flag in the code that runs when the screen loads. Note that if your mandatory cod block has already been executed, the depends on feature does not cause the block to re-enter the interview logic flow.

    There is a function forget_result_of() that can be used to "reset" a mandatory block that has already been executed to completion. This function can be used to bring an already-executed mandatory block back into the interview logic that runs when the screen loads. I don't think I have ever had to use forget_result_of(), though. I have always found simpler ways to specify the interview logic and adjust for changes that the user makes.

    There is a similar feature to depends on called on change that can be used to cause particular code to run when a variable changes, so if you wanted the variable not_supported_shown_flag to be defined a certain way whenever the value of var changes, you could use on change to accomplish that. (Note that on change is only intended for running code and adjusting interview answers, not for showing particular screens.)

    If your review screen is triggered by an "action," you might need to make adjustments to account for how actions work. Before your interview logic is evaluated, the process_action() function is called, and this function causes the action to execute. Actions can be stacked; i.e., an action can trigger another action, which can trigger another action. Once the third action in the stack is completed, process_action() will continue to work on the second action, and when that is completed, process_action() will continue to work on the first action. For example, the first action might be the showing of the review screen, and the second action might be the editing of a certain variable. When the user edits the variable, docassemble goes back to the first action, namely the review screen. Only when the user moves on from the review screen does the interview logic get evaluated.

    It is not necessary that process_action() is called prior to your interview logic. You could call process_action() at a particular point in your interview logic, so that if the screen loads, and var is now equal to "not_supported", the user is diverted to a kick-out screen. So if you don't want the user to be sent back to the review screen after changing var to "not_supported", you could put the logic of the kick-out in an initial code block, and call process_action() from that code block.