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)

Rule Descriptions

  • 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.
  • 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.

Include Not(IsBlank(ITEM)) to confirm the data is not missing or null.

When evaluating number fields, you must 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

  • #define statements should identify the variable using the item’s exact Name, or as close to exact as possible, for identification and copying efficiency.
  • If the item’s Name starts with an underscore (_), you must rename the variable to remove the underscore in the #define statement. Otherwise, a syntax error will occur.
  • Use /* ... */ 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.

Extra whitespaces and lines

Time Zones

The following best practices apply for time zones in rules:

  • When using DateValue, always include the @Site.timezone__v parameter. 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.

Set Item Value and Set Derived Value Rules

Set Derived Value rules replaced Set Item Values in the 22R3 release with the Cross-form Derivations feature.

Because of this, you can’t have Set Item Value rules and Set Derived Value rules in the same study, so rules from a study using one rule type can’t be copied into a study containing the other rule type, and vice versa.

Both Set Item Value and Set Derived Value rules require that the @Form floating identifier (also known as “wildcard identifier”) be used. Since these rule types use the @Form floating identifier, you should qualify the rule action to the same level.

Learn more about Set Derived Value rules and Set Item Value rules.

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 DateValue({datetime}, @Site.timezone__v).

DateTime vs Date & Time

When adding a date item and time item together to create a datetime (DateItem + TimeItem), use StartOfDay(Date, @Site.timezone__v), then add your time value to it. When used with a date, StartOfDay returns 00:00.

The example below checks that the lab collection datetime is 8 hours +/- 30 minutes after the exposure oral start date and start time. It references the following study design:

  • LBDTC: Datetime-type Item on a lab panel Form
  • EXSTDAT: Date-type Item
  • EXSTTIM: Time-type Item on the same Form as EXSTDAT

Note that, since this example uses the @Form floating identifier (also known as “wildcard identifier”), in most cases the rule action should be qualified to the same level.

#define LBDTC @Form.LBHEADER.LBDTC
#define EXSTTIM $eg_MAINC1.ev_VISIT1.EXOR.ig_EXOR.EXSTTIM
#define EXSTDAT $eg_MAINC1.ev_VISIT1.EXOR.ig_EXOR.EXSTDAT

Not(IsBlank(LBDTC))
&&
Not(IsBlank(EXSTTIM))
&&
Not(IsBlank(EXSTDAT))
&&
Not(InWindow(
    LBDTC,
    StartOfDay(EXSTDAT, @Site.timezone__v) + EXSTTIM,
    Minutes(450),
    Minutes(510),
    false,
    false
))

Rule Details Form: Lab

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 Now() and Today() and 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 floating identifier 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

Rule Details Form: QS

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?

Use @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)

Static Timepoint

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 TimeA and 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(, @Site.timezone__v).

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
)

Rule Details Form: Adverse Events

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.

Age Rules

The following rule examples calculate and query age, accounting for leap year:

Age Derivation (Set Item Value)

This example rule expression derives the age of the subject based on data entered in the Demographics form. Since this expression uses the @Form floating identifier (also known as “wildcard identifier”), the referenced form must be selected in the Rule Details panel. In most cases, the rule action should be qualified to the same level as the identifier. For example, choose the Set Item Value action and select this form > ig_DM > Age

#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

Building the Study Schedule using Rules

Study designers can dynamically add Forms, Events, and Event Groups to the study Schedule by using rules.

In the example below, the rule will evaluate the sequence number of the Event Group to add the forms configured in the rule action at specific event sequences. These example expressions use the remainder operator. 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 the Study Design Specification generated by Studio.

Note that since these examples use a floating identifier (also known as “wildcard identifier”), in most cases the same identifier should be used in the rule action and both the rule expression and rule action should be qualified to the same level.

For all the examples below, select ‘this event group’ in the rule action to match the @EventGroup floating identifier in the #define statement of the rule expression. This will ensure that the system adds FORM1 only to Event Groups where the rule expression evaluates to true.

In Example 1, the rule expression evaluates the sequence number of the repeating event group to determine whether it is odd. The study designer can then configure the rule action to add the ECOG form to an event within the specified event group when the expression evaluates to true.

Example 1: Adding a form to all odd events in a cycle

#define EVDAT @EventGroup.ev_D1.event_date__v
#define EVSEQ @EventGroup.sequence__v

Not(IsBlank(EVDAT))
&&
(
     EVSEQ = 1
     ||
     EVSEQ % 2 = 1
)

Rule Action: Add Form: 'this event group' > ev_D1 > FORM1

In Example 2, the rule expression evaluates the sequence number of the repeating event group to determine whether it is even. The study designer can then configure the rule action to add the desired form to an event within the specified event group when the expression evaluates to true.

Example 2: Adding a form to all even events in a cycle

#define EVDAT @EventGroup.ev_D1.event_date__v
#define EVSEQ @EventGroup.sequence__v

Not(IsBlank(EVDAT))

&&

EVSEQ % 2 = 0

Rule Action: Add Form: 'this event group' > ev_D1 > FORM1

You can also write rules to evaluate the sequence number of the event group in the cycle. In Example 3, the rule expression evaluates the sequence number of the repeating event group to determine whether it is every third cycle, for a repeating event that starts at Cycle 2.

Example 3: Adding a form to every nth event in a cycle

#define EVDAT @EventGroup.ev_D1.event_date__v
#define EVSEQ @EventGroup.sequence__v

Not(IsBlank(EVDAT))

&&
(EVSEQ + 1) % 3 = 0

Rule Action: Add Form: 'this event group' > ev_D1 > FORM1

Query Messages

  • 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, as they might not appear correctly in export files:
    • 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 that might impact downstream systems or applications.
  • Use single quotes around responses (example: Was the subject enrolled? Is ‘No’ and …).
  • Refrain from using double quotes.
  • Query messages have a 500 character limit.

Rules with Floating Identifiers (@Form, @Event, @EventGroup)

As of 20R1 (April 2020), for any new rules that use a floating identifier such as @Form (also known as “wildcard identifier”), Vault automatically evaluates the rule each time the identified form is submitted.

Reciprocal Rules

When a rule expression references data from multiple Forms, even when both @ and fully qualified identifiers are used, Vault will evaluate the rule upon the submission of the Form that is selected in the Rule Details panel and will also re-evaluate the rule each time data changes on any Form that is referenced in the rule expression. This process is called Reciprocal Rules. For example, if you use @Form to point to a Vitals form that you use in multiple Events, and also use a fully qualified identifier to point to a PE form, Vault will run the rule and evaluate the data in the Study object that is pointed to each time a Vitals form or the specific PE form is submitted.

If the Form that you selected in the Rule Details panel is repeating and fully qualified, Vault will evaluate the rule on each Form instance.

Fully Qualified vs Floating Identifiers Format

The format you use when entering the identifier into the Rule Expression field indicates to the system whether you are using a fully qualified identifier or a floating identifier (also known as wildcard identifier).

In most cases, fully qualified identifiers begin with a dollar sign ($), which indicates to the system that you are fully qualifying the reference to the study object.

Fully qualified identifier format example:

$EVENTGROUP.EVENT.FORM.ITEMGROUP.ITEM

Floating identifiers always begin with an at sign (@), which indicates to the system that you want it to look at the study object that is pointed to based on the context.

Floating identifier format example:

@EventGroup.EVENT.FORM.ITEMGROUP.ITEM, @Event.FORM.ITEMGROUP.ITEM, or @Form.ITEMGROUP.ITEM

In the Rule Action, the “@” is denoted as “this Event”, “this Event Group”, or “this Form.”

Using Floating Identifiers: In most cases, if you use a floating identifier (also known as ‘wildcard identifier’) in your rule expression, you should use the same identifier in your rule action and both the rule expression and rule action should be qualified to the same level. This ensures that the rule action and rule expression are scoped to the same object instances in the Casebook. Otherwise, the rule action may impact unintended forms.

When a rule contains a repeating Event Group, Event, or Form that is fully qualified, Vault looks at every instance that matches that identifier. For this reason, it’s recommended that you not use fully qualified identifiers on repeating objects unless you want the rule to execute against all of them.

If you need to create a new Rule using the non-reciprocal behavior (pre-20R1), work with your Veeva Services representative to do so.

Avoid Errors with @Form in 23R2: As of 23R2, rules using @Form or ‘this form’ identifiers when the associated form in the Rule Details panel doesn’t match the identifier must be fixed or inactivated to avoid validation errors.

Best Practices for Optimizing Rule Permutations

When writing rules using aggregates on repeating forms, use an aggregate identifier [*].

Example #define statement using an aggregate identifier:

#define AEOUT   $LOGS.LOGS.AE[*].igAE.AEOUT

When writing rules that reference repeating forms and event groups, use the @Form or @EventGroup floating identifier floating identifier (also known as “wildcard identifier”) to prevent excess permutations.

Testing Recommendations

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 TST that represent a sample of expected time zones from the countries included in the study.
    • Set the time zone of both the site and TST 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 user acceptance testing.