I have encountered an issue with cascading program rules that would appreciate if you have a workaround for it. When one program rule assigns a value to a data element, and a second program rule has a condition that depends on that assigned value, the second rule does not run in the same evaluation cycle. It only fires after the user makes another (unrelated) interaction with the form. In other words, assigned values do not reliably “cascade” through chained program rules within a single change event.
I built and have attached the metadata (pr_cascade.json) for the smallest possible scenario to demonstrate this issue. One program, one repeatable stage (“Visit”), and one section with four data elements:
Data element
Value type
Purpose
A
Date
User input
B
Date
Assigned by rule from A
C
Date
Assigned by rule from B
D (“dummy interaction”)
Yes (true only)
Used only to trigger re-evaluation
Two program rules, both on the Visit stage:
Rule 1 — Assign B one day after A (priority 1)
Condition:d2:hasValue(#{A})
Action:ASSIGN#{B} = d2:addDays(#{A}, 1)
Rule 2 — Assign C one day after B (priority 2)
Condition:d2:hasValue(#{B})
Action:ASSIGN#{C} = d2:addDays(#{B}, 1)
Then, to reproduce:
Open a new Visit event.
Enter a date in A.
B is populated immediately (A + 1 day), but C stays empty, even though B now has a value.
If the chain is unavoidable (e.g. B is genuinely needed by other rules), is there a recommended workaround other than the “dummy interaction” trick which is not a realistic option?
P.S. 1 This behavior is consistent across both web and android versions of Capture and as far as I could test, all stable versions.
P.S. 2 I’ve opened a Jira ticket for this issue too with more details.
Great explanation You’re right about having to do a workaround. The one I know of involves using calculated program rule variables. It works as follows: Whenever you assign a value to a field, you also assign the value to a corresponding calculated program rule variable. Then use this variable for subsequent computations that require the field value. Note that this variable replaces the variable that reads the value directly from the field (#{B} and #{C} in your example).
Hope this made sense, and that it doesn’t look too impractical.
Super creative workaround, thanks for sharing! Just gave it a try and with a little tweak on priorities, I can confirm that it works. I have attached the sketch of the workaround at pr_cascade_workaround.json (11.4 KB) and here are the rules for anyone else dealing with the issue:
Rule 1 — Assign B and X one day after A
Priority: 1
Condition: d2:hasValue(#{A})
Action 1: ASSIGN #{B} = d2:addDays(#{A}, 1)
Action 2: ASSIGN #{X} = d2:addDays(#{A}, 1) ← “Assign value” to a calculated program rule variable named X.
Rule 2 — Assign C one day after X (instead of B)
Priority: 2
Condition: d2:hasValue(#{X})
Action: ASSIGN #{C} = d2:addDays(#{X}, 1)
Because program rule variable X is set by Rule 1 within the cycle and Rule 2 reads X (not the assigned data element B), C is populated immediately when the user enters A and no dummy interaction is needed anymore. B still receives its value for storage/display but it’s just no longer on the rule cascade. Just note that order of rules matters and the rule that assigns X must have a lower priority number (runs first) than the rule that consumes it.
I tried to extend usage of calculated PRV to another scenario in which program rule cascading with data elements requires dummy interaction. The idea is to use X as a calculated PRV as the condition for assigning default value to a data element and then releasing the data element so that it’s editable. The single program rule is like:
Condition:!d2:hasValue(#{X}) (X is the calculated PRV)
Actions:
ASSIGN"TEST" to data element A
ASSIGN1 to program rule variable X so that the condition becomes invalid.
I expected that on a new event, the PR fires (because X is empty) and then A is set to "TEST" and X is set to 1. Subsequently, since X now has a value, the condition !d2:hasValue(#{X}) becomes invalid and the PR should stops firing, so its ASSIGN no longer holds data element A and it’s released and becomes editable.
This is how it behaves when the sequence is implemented via a helper DE (with a dummy interaction though) but with calculated PRV, the release doesn’t happen and A stays locked on "TEST" and cannot be edited.
The value of a calculated program rule variable does not get carried over from one rule engine execution to the next, so on the second run the value of X starts over as undefined. Consequently the field gets assigned every rule engine execution, and therefore stays locked.
The rigid locking behavior of program rule assignment makes it difficult to use them for providing default values in a natural way. Perhaps the lack of support for this means that it’s generally not needed in practice?
I can imagine one form design that would allow providing default values using assign actions, while hiding the quirk that the assignment initially locks the field: At the top of the form you put a mandatory field. When this field is empty, all subsequent fields in the form is either assigned with its current value, if defined, or otherwise a default value. Once the user enters a value in the top field, you stop the assignments, and the rest of the form gets unlocked for editing.
Thanks for your explanation on the dynamics of calculated program rule variables.
In clinic and health settings, one of our primary concerns is the speed and efficiency of form completion. This is why items such as the list of medications prescribed during the previous visit (which in many cases are repeated during follow-up visits with only minor modifications) require default but editable values. Many topics have been created in the community by developers addressing this issue but they have all ultimately resulted in non-natural workarounds, such as using dummy interactions or DEs (e.g., How to have attribute or data element show the default value). So, I believe support for default but editable values is not an uncommon need in practice, especially in healthcare settings.