Tips & Best Practices for Rules
This page lists various tips and best practices to help you create rules for your study. For instructions on rule creation, refer to Creating Rules.
Rule Details (Properties)
- Use short and complete sentences.
- Explicitly indicate the intention of the rule including all parameters.
- When adding forms and events, enter a description for how the rule operates
- Example: Did the subject meet all eligibility criteria? If Yes, then add the Treatment Event Group (egTREAT)
- For query rules, indicate exactly how values need to be handled, ‘…greater than or equal to’ vs ‘…greater than’
- Example: End Date is before Start Date. vs. End Date is on or before Start Date.
- When adding forms and events, enter a description for how the rule operates
- Keep in mind that testers will test against the description to determine if the rule passes
- Use item labels within descriptions because they generate the study specifications, for example, use “Start Date” instead of “AESTDAT”.
Rule Scope & Dynamic Action
Always ensure that you select Within Event Group for Rule Scope when necessary. This is a common reason for troubleshooting why a rule doesn’t fire as expected.
Send Email Rules
Always use tokens when creating Send Email rules, as Vault does not store rule execution data for email rules.
Evaluating Missing Data (Blank Handling)
Best Practice: The Required property on an Item creates a system-generated rule (univariate) that fires whenever that Item is left blank. Use this as the primary check for missing data.
For user-defined rules, include logic within your expression to confirm the data is not missing. If you forget to consider the scenario of missing data, then you may encounter server errors, duplicate queries, or other unexpected behavior.
Not(IsBlank(ITEM)) to confirm the data is not missing or null. With this, for number fields, also select As null for the rule’s Blank Handling property. For dates, Vault automatically treats blanks as null values.
When evaluating number fields, you can use the Blank Handling property in the Rule Editor to choose how Vault handles blank values. You can choose between these options:
- As zero: Vault substitutes a zero for the blank value, allowing you to complete the formula calculation.
- As null: Vault treats the blank value as null, causing the entire expression to return a null/blank value.
Expression & Logic
#definestatements should identify the variable using the item’s exact Name, or as close to exact as possible, for identification and copying efficiency.
/* ... */to add comments at the top of the rule if needed for clarity. You must add these at the top of your rule expression, or you won’t be able to save your rule.
- Consider that another study designer might be needed during study conduct. Well-named items in the expression help with rule clarity and reduce efforts during revisions.
- Use white space and line breaks to help with rule clarity.
Here the study designer chose to put extra lines to break up separate sections of logic. They also used a separate line for each logical piece of the expression, aligning the operators with extra whitespaces.
The following best practices apply for time zones in rules:
- When using
DateValue, always include the
@Site.timezone__vparameter. This ensures that Vault returns the date using the site’s timezone. Alternatively, you can specify a time zone of your choosing. See this list of time zone entry formats.
- When adding a date and time together to return a datetime, the operator
+uses the user’s time zone. To perform this operation with the site’s timezone (recommended for CDMS), use the
StartOfDate(date, @Site.timezone__v) + time.
Date Comparison Rules
Best Practice: To compare date Items to other date Items, we recommend that you use the Date Comparison Configurator whenever possible.
DateTime vs Date
The two examples below show how to compare the value from a DateTime-type Item to an Event Date. This syntax applies to any date/datetime comparisons.
DateTime Item doesn’t allow for unknowns:
Not(IsBlank(DTC)) && Not(IsBlank(EVDAT)) && DateValue(DTC, @Site.timezone__v) != EVDAT
DateTime Item allowing for unknowns:
Not(IsBlank(EVDAT)) && Not(IsBlank(VSDTC)) && If(Right(VSDTC , 4) = "UNKZ", DateValue(MinDateTime(VSDTC), 'UTC'), DateValue(MinDateTime(VSDTC), @Site.timezone__v)) != EVDAT
When you compare a date and a datetime with
=, Vault converts the datetime to a date using the vault’s timezone. If you want to use the site’s timezone instead, use
Future Date Rules
Best Practice: We recommend that you simply select the Future Date property in the Edit Checks section of the Properties panel for an Event or Item to check for future dates. When this checkbox is selected, Vault opens a query whenever a site user enters a future date (based on the site user’s timezone).
If you require a custom rule to check for future dates, then use
DateValue(Now(), @Site.timezone__v) in the expression. Functions like
DateValue() will return the dates converted to UTC format. Coordinated Universal Time (UTC) is the standardized time based on the 0° longitude meridian.
For any country where dates and times are beyond UTC, e.g. Italy, South Africa, Australia, this can cause the rule to fire unexpectedly when the
@Site.timezone__v is not included. By using
@Site.timezone__v as a best practice, it will return the converted value back to the site’s timezone and ensure the rule fires as expected.
Comparing Event Dates with Date Items
Study designers may attempt to use
@Event.event_date__v as a wildcard to cover more Events and write fewer rules. However, unlike with @Form, Events are not reciprocal. Rules with
@Event.event_date__v are not re-evaluated when a particular Event Date is modified.
As best practice, use fully-qualified Event identifiers when comparing Event Dates (
$EventGroup.Event.event_date__v or @EventGroup.Event.event_date__v), and write one rule for each Event instance.
For example, a rule like the one below would not re-evaluate if a user corrected the Event Date after completing the defined Form within the Event.
#define EVDAT @Event.event_date__v #define DAT @Form.ig_VS.VSDAT EVDAT != DAT
The example below shows a fully-qualified Event Date identifier compared to a Date Item, which doesn’t allow partial (unknown) dates:
#define QSDAT @Form.QS.QSDAT #define EVDAT $FOLLUP.V7.event_date__v Not(IsBlank(QSDAT)) && Not(IsBlank(EVDAT)) && QSDAT > EVDAT
The example below compares a fully-qualified Event Date identifier to a datetime-type Item that allows for unknown times. This expression is for a Query-action rule that opens a query when the Date and Time of Assessment (EGDTC) item is not the same as the Event Date for that Form.
#define EVDAT $FOLLUP.FOLLUP.event_date__v #define EGDTC $FOLLUP.FOLLUP.ECG.ig_ECG.EGDTC Not(IsBlank(EVDAT)) && Not(IsBlank(EGDTC)) && If( Right(EGDTC, 4) = "UNKZ", DateValue(MinDateTime(EGDTC), 'UTC'), DateValue(MinDateTime(EGDTC), @Site.timezone__v) ) != EVDAT
What about other use cases for @Event?
@Event to reference other Forms within the same Event. For example, if Vital Signs have to be taken within a certain time of the Drug Administration (EX) form in the same event, you can use
@Event.FORM.IG.ITEM to reference the other form. Writing the expression in this way makes the rule reusable across multiple events.
#define VSDTC @Form.VSTPT2.VSDTC #define EXSTDTC @Event.EX.EX.EXSTDTC Not(IsBlank(VSDTC)) && Not(IsBlank(EXSTDTC)) && Not(InWindow(VSDTC, EXSTDTC, Minutes(5), Minutes(10), false, false)) Rule Details Form: VS
Time vs Time (Time Comparison Rules)
Example: Fire query when the time entered is after 12:00pm
TIM1 > Time(12,0,0)
Comparing Two Times
Time - Time returns the number of minutes between two times.
These rules are written as a statement (
TimeA - TimeB > #), where the calculation returns the delta between
TimeB in minutes as a number.
Example: Dose End Time is on or after Dose Start Time
EXENTIM - EXSTTIM >= 0
Example: Infusion End Time is more than 12 hours after Infusion Start Time (Note: 12 Hours X 60 minutes = 720 minutes)
EXENTIM - EXSTTIM > 720
This example can also be written by comparing the item to an expression calculating the number of minutes.
EXENTIM - EXSTTIM > 12*60
Direct Comparisons Not Supported: Vault doesn’t support direct, logical comparisons between times, such as
Time + Time,
Time = Time, or
Time (any logical operator) Time. Vault also doesn’t support
Time + Number or
Time - Number.
Rules with DateValue
When writing rules with the
DateValue() function, always use `DateValue(
Event Date Rules & Did Not Occur
It’s important to consider if a protocol allows a subject to miss an Event and continue on to the next Event. Likewise, it’s important to consider rules that examine Event Dates where sites may mark an Event as Did Not Occur.
You may already be familiar with using the
Not(IsBlank()) functions in the rule’s expression to confirm that a date is entered. You can extend this to check if a site marked the_ Event_ as Did Not Occur by checking the entry for the event’s Change Reason (
$EventGroup.Event.change_reason__v). This is where Vault records the reason the site selected for the event not occurring.
The example below explicitly examines the Did Not Occur reason:
#define EVDAT $TX.DAY9.event_date__v #define EVDNO $TX.DAY9.change_reason__v Not(IsBlank(EVDAT)) || EVDNO = "Subject missed event"
In this rule expression (used with an Add Event rule action), we checked the change reason and allowed the roll out of the next Event if the Change Reason was “Subject missed event”. If a site were to select “Subject early terminated” instead, this rule would not add the next event.
Be sure to confirm the exact text for the Change Reasons available in your study’s vault. These must be an exact match for the rule to evaluate correctly. You can view the available reasons, both standard and custom, from Tools > System Tools > Change Reasons.
Rules on Boolean Items (Checkboxes)
Boolean items (checkboxes) that are not set to true or false are considered blank (“Undetermined”). This can occur when the site never checks the checkbox vs. checking and then unchecking the checkbox. Proper logic within the rules expression will help ensure that the appropriate scenarios are evaluated as expected. The best practice is to explicitly set Booleans as
= true or
= false in your rule expression.
Include the appropriate define statements when evaluating a set of Booleans are all left blank. Take consideration of when a form/item is marked with Intentionally Left Blank (ILB) and how you’d need the rule to operate.
In the example below, the rule checks if a Reason was entered and one or more of the checkboxes was selected.
#define ND1 @Form.ig_V1.V1ND #define ND2 @Form.ig_V2.V2ND #define ND3 @Form.ig_V3.V3ND #define ND4 @Form.ig_V4.V4ND #define REAS @Form.ig_V5.VREAS Not(IsBlank(REAS)) && (ND1 = true || ND2 = true || ND3 = true || ND4 = true)
When evaluating if a set of items or checkboxes is left blank, Include Intentionally Left Blank (ILB) in the define statements to evaluate, as needed, if forms or items are marked ILB. This will ensure the rule doesn’t fire when it shouldn’t.
#define AENONE @Form.AE.AENONE #define AEMED @Form.AE.AEMED #define AEACNOTH @Form.AE.AEACNOTH #define AETRANS @Form.AE.AETRANS #define FORMILB @Form.intentionally_left_blank__v FORMILB = false && ( AENONE = false && AEMED = false && AEACNOTH = false && AETRANS = false )
This is often applicable to rules related to Adverse Events or Questionnaire and Medical Device forms to evaluate if none of the “check all that apply” checkboxes have been selected. Some common rules where this logic is applicable are:
- No selections have been made for Race. Check all that apply
- AE Action Taken: None is selected and also Medication, [etc]. were selected. Please correct.
The following rule examples calculate and query age, accounting for leap year:
Age Derivation (Set Item Value)
#define BRTHDAT @Form.ig_DM.BRTHDAT #define RFICDAT @Form.ig_DM.RFICDAT If(BRTHDAT > RFICDAT - Years((Year(RFICDAT) - Year(BRTHDAT))), (Year(RFICDAT) - Year(BRTHDAT)) - 1, (Year(RFICDAT) - Year(BRTHDAT)) ) Rule Details Form: Demographics
Age Query (Query if Under 18)
#define BRTHDAT $eg_SCREEN.ev_SCREEN.DM.ig_DM.BRTHDAT #define EVDAT $eg_SCREEN.ev_SCREEN.event_date__v Not(IsBlank(EVDAT)) && Not(IsBlank(BRTHDAT)) && ((EVDAT - MaxDate(BRTHDAT)) / 365.25) < 17.998
Check if a Text Item Contains Specific Characters
In this example, the rule checks a text-type Item, and Vault opens a query if any part of the entered value contains a number, instead of alpha characters.
#define ITEM @Form.IG.ITEM If(Find("0", ITEM) != 0, true, If(Find("1", ITEM) != 0, true, If(Find("2", ITEM) != 0, true, If(Find("3", ITEM) != 0, true, If(Find("4", ITEM) != 0, true, If(Find("5", ITEM) != 0, true, If(Find("6", ITEM) != 0, true, If(Find("7", ITEM) != 0, true, If(Find("8", ITEM) != 0, true, If(Find("9", ITEM) != 0, true, false))))))))))
Adding Forms using Rules
With the Repeating Event Group feature and the use of rules, study designers can easily create the Schedule of Assessments (SOA) with much less effort. In the example below, the rule will add specific forms at specific event sequences. Be sure to confirm the correct sequence numbers, as they relate to the labels configured in the repeating event group. They can also be referenced in the Schedule Tree tab of theStudio generated Study Design Specification.
In the example below, the rule adds the ECOG form to all the Day 1 events within the Treatment Cycle (repeating event group). In this design, the Cycle # - Day 1 events are the sequence numbers 1, 3, 5, 7, 9 and 11.
#define EVDAT $eg_TREAT.ev_D1.event_date__v #define EVSEQ @EventGroup.sequence__v Not(IsBlank(EVDAT)) && ( EVSEQ = 1 || EVSEQ = 3 || EVSEQ = 5 || EVSEQ = 7 || EVSEQ = 9 || EVSEQ = 11 )
- Clearly reference the issue and options for resolution. Be factual and refrain from messages that are leading or could bring bias to the data.
- Avoid using special characters:
- For example, spell out greater than or equal to, instead of using “> or =”
- Use characters from your natural keyboard. Don’t use Alt+ keys or symbols thats might impact downstream systems or applications.
- Use single quotes around responses (example: Was the subject enrolled? Is ‘No’ and …).
- Refrain from using double quotes.
Best Practice: Typical unit testing includes, at a minimum, testing to make each rule open (create) and resolve a query once. Testing one rule thoroughly, similar to a full verification, and then creating similar rules by copying that rule can help you raise rule quality and reduce configuration rework and revalidation efforts.
Whether you are Unit Testing or more formally verifying your rules, consider the following best practices:
- Include in-range values and out-of-range values. This includes numerical values, dates, and datetime windows
- Include testing what makes the rule fire and what makes it resolve, for all cases.
- Confirm every scenario from a codelist.
- For cross-form queries, include tests that show the rule creates and resolves queries as expected upon completion of both the first and second forms (forward and backward).
- If a rule references an Item within a repeating Item Group, verify that the rule opens and resolves the query on the Item in the correct sequence (instance) of the Item Group.
- Test within, equal to, and beyond datetime windows.
- Test the “next day” scenario. Pick a time within the window but on the next calendar day.
- Test unknown (UNK) times, days, and months per the item’s masking properties.
- Set up sites with applicable timezones for testing rules when data is entered from various site locations
- Add a few testing sites in UAT that represent a sample of expected time zones from the countries included in the study.
- Set the time zone of both the site and UAT test user assigned to that site.
- Test time window rules as applicable for the study conduct. For example, if a longer IV infusion would result in the end time or expected ECG or PK time windows near midnight.
- Consider a risk-based approach to testing, where peer reviews on code can raise quality and reduce findings in UAT.