Zu Hauptinhalten wechseln

FV Decipher Unterstützung

Alle Themen, Ressourcen für FV Decipher benötigt.

 
decipher

Validate Tag: Custom Data Quality Checks

1:  Overview

Validation occurs immediately after a respondent has submitted their answers for a particular question. The <validate> tag enables you to perform custom data checks and provide error messages when the data doesn't meet the requirements of the question.

For example, you may want to restrict the answer's value relative to a question they answered earlier, provide a custom error message when the data entered doesn't meet your expectations, confirm that the answers provided are unique and that they make sense, etc...

2:  Validation Tags

There are several different validation tags available that enable you to run Python code to verify the data submitted:

All question validation can take place within a <validate> tag. This is the most popular choice for validating respondent data.

 

Validate Tag Description
<validate> Runs once for the entire question
<validateCell> Runs once for each non-disabled cell
<validateRow> Runs once for each non-disabled row
<validateCol> Runs once for each non-disabled column

In the examples below, we will make use of the error function to present error messages to the respondent. If the error function is called, the respondent will see the message provided and will not progressed to the next page until the error is resolved. The syntax for this function is below:

# SYNTAX: error(MESSAGE, ROW, COLUMN)
# EXAMPLES:
error("General error with no highlighted elements.")
error("Error on this row", Q1.r1)
error("Error on this row", row=Q1.r1)
error("Error on this row, '%s'" % Q1.r1.text, Q1.r1)
error("Error on this col", col=Q1.c1)
error("Error at this cell", Q1.r1, Q1.c1)

Be sure to visit the Best Practices & Considerations section below for some helpful tips on how to program good, translateable and reusable survey logic.

2.1:  <validate>

The <validate> tag will run once after the question has been submitted. You can use Python code to iterate through all of the cells available within each row and column to verify the data entered meets the requirements of the question.

There are so many different ways that you may need to validate your data that it would be impossible to cover them all in this document, but consider the following examples as a guide to help you write your own validation logic.

The following examples will start off simple and end up decently complex. Follow along to learn the syntax and methods for validating the different types of questions avialable.

Example 1

Below is a simple <radio> question with rows and columns.

<radio label="Q1" optional="0">
  <title>Please choose one.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
</radio>

If we needed to make sure that a unique value was selected for each row:

<radio label="Q1" optional="0">
  <title>Please choose one.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
  <validate>
if Q1.r1.val == Q1.r2.val:
    error("Please select a unique answer for each row.")
  </validate>
</radio>

If you run the code above, you'll notice that none of the rows were highlighted when the error was shown. We can modify the code a bit to show an error at both rows if the unique values are not selected:

<validate>
if Q1.r1.val == Q1.r2.val:
    error("Please select a unique answer for each row.", row=Q1.r1)
    error("Please select a unique answer for each row.", row=Q1.r2)
</validate>
</radio>

Here's another way to achieve the same task:

<validate>
if Q1.r1.val == Q1.r2.val:
    for eachRow in Q1.rows:
        error("Please select a unique answer for each row.", eachRow)
</validate>
</radio>

All three of the examples above work as expected, but only the last two highlight the rows upon receiving an error. This example is relatively simple, so let's move onto a more complex example using the same question.

Example 2

Given the same <radio> question, let's confirm that "c1" is not selected with "c3".

<radio label="Q1" optional="0">
  <title>Please choose one.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
  <validate>
if Q1.c1.any and Q1.c3.any:
    error("You cannot select '%s' with '%s'." % (Q1.c1.text, Q1.c3.text), col=Q1.c1)
    error("You cannot select '%s' with '%s'." % (Q1.c1.text, Q1.c3.text), col=Q1.c3)
  </validate>
</radio>

In this example, we provide a more custom error message displaying the column text with our error message. Both columns, c1 and c3, will be highlighted upon receiving an error. Since the error message is the same, it will not display twice. The end result is illustrated below:

validate_img1.png

Example 3

Let's take a look at validating a <checkbox> question.

<checkbox label="Q1" atleast="1" optional="0">
  <title>Please select all that apply.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
</checkbox>

If we needed to confirm that "c1" and "c3" were not selected together for any row:

<checkbox label="Q1" atleast="1" optional="0">
    <title>Please select all that apply.</title>
    <row label="r1">Row 1</row>
    <row label="r2">Row 2</row>
    <col label="c1">Col 1</col>
    <col label="c2">Col 2</col>
    <col label="c3">Col 3</col>
    <validate>
for eachRow in Q1.rows:
    if eachRow.c1 and eachRow.c3:
        error("You may not 'Col 1' and 'Col 3' together in this row.", eachRow)
    </validate>
</checkbox>

In this example, we iterate through each row using a for loop to confirm that both "c1" and "c3" aren't chosen together.

Example 4

Using the same question, let's validate that only 1 column was selected or that all columns were selected.

<checkbox label="Q1" atleast="1" optional="0">
  <title>Please select all that apply.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
  <validate>
for eachRow in Q1.rows:
    if eachRow.count gt 1:
        if not (eachRow.c1 and eachRow.c2 and eachRow.c3):
            error("Please select just one or all items for this row.", eachRow)
  </validate>
</checkbox>

Since the question is not optional and atleast="1" is set, the respondent will receive the default error message if they fail to provide an answer for any row.

Example 5

Given the following <number> question:

<number label="Q1" size="3" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">Row 1</row>
</number>

Let's use a validate to confirm that the number entered is an even number.

<number label="Q1" size="3" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">Row 1</row>
  <validate>
if Q1.r1.ival % 2 != 0:
    error("Please enter an even number.")
  </validate>
</number>

Using the .ival syntax for number questions is very important. If a value was not provided (e.g. left blank) then Q1.r1.ival would return 0 instead of None. Had we used Q1.r1.val instead, this code would have produced a fatal error. This is because you cannot perform integer operations on a None value.

When dealing with <number> questions, use .ival instead of .val. Unless, of course, you're checking to see if the no answer was provided (e.g. Q1.r1.val == None).

Example 6

Using the following <number> question:

<number label="Q1" size="3" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <row label="r3">Row 3</row>
</number>

Let's confirm that the sum equals the number provided at some question, "Q0".

<number label="Q1" size="3" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <row label="r3">Row 3</row>
  <validate>
Q1_values = [x.ival for x in Q1.rows]
if sum(Q1_values) != Q0.ival:
    error("The total sum must equal %d exactly." % Q0.ival)
  </validate>
</number>

In this example, we used Python list comprehension to accumulate a list of values provided for Q1. Using the sum() function, we compared the total with the value provided at Q0 and showed an error if the total value was not equal to Q0's value.

Here's a different approach to the same problem with a slightly different error message:

  <validate>
sum_total = 0
for eachRow in Q1.rows:
    sum_total += eachRow.ival

if sum_total != Q0.ival:
    error("The total sum must equal %d exactly. (You entered %d)" % (Q0.ival, sum_total))
  </validate>
</number>

Example 7

Given the following <number> and <text> questions:

<number label="Q1" size="3" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
</number>

<text label="Q2" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
</text>

Let's validate that a value was provided for each question at every cell.

<number label="Q1" size="3" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
  <validate>
for eachRow in Q1.rows:
    for eachCol in Q1.cols:
        if Q1[eachRow][eachCol].val == None:
            error("Please provide a number for this cell.", eachRow, eachCol)
  </validate>
</number>

<text label="Q2" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
  <validate>
for eachRow in this.rows:
    for eachCol in this.cols:
        if not this[eachRow][eachCol].val:
            error("Please provide some text for this cell.", eachRow, eachCol)
  </validate>
</text>

Both validates check for the same thing (e.g. an empty cell), but the approaches are slightly different.

Notice the difference at Q2. The keyword this refers to the question containing the <validate> block. (e.g. this.r1.val is the same as Q2.r1.val).

Example 8

The method isdigit() checks whether the string consists of digits only.

Given the following <radio> question:

<radio label="Q1">
  <title>Please enter a whole number.</title>
  <row label="r1" open="1" openSize="25">Please Specify</row>
</radio>

Let's use a validate to confirm that the number entered is an integer.

 <radio label="Q1">
  <title>Please enter a whole number.</title>
  <validate>
if not(Q1.r1.open.isdigit()): 
    error("Please enter a whole number.")
  </validate>
  <row label="r1" open="1" openSize="25">Please Specify</row>
</radio>

Using the .open syntax for <radio> and <checkbox> questions allows us to access the text of open-ended rows within those questions.

Using the .isdigit syntax validates that the open-ended entry consists of whole numbers only.

2.2:  <validateCell>

The <validateCell> will run once for every input available to the respondent. Validation checks will not occur at disabled inputs when using <validateCell>.

To access the value that was provided, use the data keyword. When using the error function, cells that have an error will automatically be highlighted.

Example 1

In this example, we verify that every value provided is greater than 20.

<number label="Q1" optional="0" size="2">
  <title>Please enter a value greater than 20 for each input.</title>
  <row label="r1">Row 1</row>
  <row label="r2">Row 2</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
  <validateCell>
if data lt 20:
    error("Value must be larger than 20.")
  </validateCell>
</number>

validate_img2.png

Example 2

In this example, we verify that a valid color was provided for every input.

<text label="Q1" optional="0" grouping="cols">
  <title>
    What are the first two colors you think of when you think about the items listed below.
  </title>
  <comment>Please only use the following colors: red, green, black, brown, blue, yellow, purple, orange, or white.</comment>
  <col label="c1">Tree</col>
  <col label="c2">Flower</col>
  <col label="c3">Dog</col>
  <row label="r1">Color 1</row>
  <row label="r2">Color 2</row>
  <validateCell>
colors_available = ['red', 'green', 'black', 'brown', 'blue', 'yellow', 'purple', 'orange', 'white']
if data.lower() not in colors_available:
    error("This color was not acceptable.")
  </validateCell>
</text>

validate_img3.png

Example 3

In this example, we confirm that every checkbox was checked.

<checkbox label="Q1">
  <title>
    Please check all boxes.
  </title>
  <row label="r1">1</row>
  <row label="r2">2</row>
  <col label="c1">1</col>
  <col label="c2">2</col>
  <validateCell>
if not data:
    error("Please read the instructions.")
  </validateCell>
</checkbox>

2.2:  <validateRow>

The <validateRow> element will run once for each row in the question. Use the row keyword to access each row.

Example 1

In this example, we confirm that only two selections were made per row.

<checkbox label="Q1" atleast="1">
  <title>
    Please select two for each item.
  </title>
  <row label="r1">Item 1</row>
  <row label="r2">Item 2</row>
  <row label="r3">Item 3</row>
  <col label="c1">Col 1</col>
  <col label="c2">Col 2</col>
  <col label="c3">Col 3</col>
  <validateRow>
if len([x for x in row.data if x]) != 2:
    error("Please select 2 for '%s'" % row.text)
  </validateRow>
</checkbox>

Example 2

In this example, the rows are checked to confirm that a value no greater than 100 is supplied. Notice that the validate works correctly even with a disabled row.

<exec>Q1.r4.disabled = True</exec>
<number label="Q1" optional="0" size="3" ss:postText="%">
  <title>
    What percentage of your time is spent in the following areas.
  </title>
  <row label="r1">Life</row>
  <row label="r2">Work</row>
  <row label="r3">Fun</row>
  <row label="r4">Doing the impossible</row>
  <validateRow>
if row.data[0] gt 100:
    error("100% is all the time we have. Please adjust the time spent in '{}' ".format(row.text))
  </validateRow>
</number>

2.4:  <validateCol>

The <validateCol> element will run once for each col in the question. Use the <data>col</data> keyword to access each column.

Example 1

In this example, we confirm that the total sum equals 100% exactly.

<number label="Q1" optional="0" size="3" ss:postText="%" grouping="cols">
  <title>
    What percentage of your time is spent in the following areas.
  </title>
  <row label="r1">Life</row>
  <row label="r2">Work</row>
  <row label="r3">Fun</row>
  <row label="r4">Doing the impossible</row>
  <validateCol>
if sum(col.data) != 100:
    error("Please enter 100% exactly.")
  </validateCol>
</number>

3:  Error Messages

When displaying an error message, make sure that the error message you are showing will display properly for the translated version of your survey. For example:

  <validate>
error("Please provide an answer")
  </validate>

This error message will always be displayed in English because the string used will not be accounted for in the translation system. To resolve this issue, we can use a <res> tag.

<res label="no_answer">Please provide an answer</res>
  ...
  ...
  <validate>
error(res.no_answer)
  </validate>

Resource tags are picked up by the translation system and will be visible to the translation team. Another solution is to use the dictionary which already has numerous translations available:

  <validate>
error(this.lget("textNoAnswerSelected"))
  </validate>

This uses our language system to pull out the same text, already on file, and present it nicely for all countries that we have a translation for.

Learn more: System Language Resources

When referencing a message that contains variables (e.g. "Sorry, but the value must be at least $(min)."), you must pass in the value to reference. For example:

<number label="Q1" optional="0" size="3" verify="range(1, 120)">
  <title>How old are you?</title>
</number>
<suspend/>

<number label="Q2" optional="0" size="3" verify="range(1, 120)">
  <title>How many of those years would you guess that you were sleeping?</title>
  <validate>
if Q2.ival gt Q1.ival:
    error(this.lget("valueAtMost", max=Q1.ival))
  </validate>
</number>

<checkbox label="Q3" optional="0">
  <title>Please choose 2 items that you value the most.</title>
  <row label="r1">Happiness</row>
  <row label="r2">Health</row>
  <row label="r3">Wealth</row>
  <row label="r4">Money</row>
  <row label="r5">Love</row>
  <row label="r6">Freedom</row>
  <row label="r7">Sleeping</row>
  <validate>
if this.count != 2:
    error(this.lget("check-error-exactly-plur-column", count=2, actual=this.count))
  </validate>
</checkbox>

Click here to view our language page with all of the translations and countries available. For example, see what French translations we have available on file here.

4:  Best Practices & Considerations

There are some serious considerations that you should make before writing out your <validate> functions.

4.1:  Disabled Cells

Question to ask yourself: "Are all of the cells that I'm checking visible to the respondent?"

If you have a question where some rows or columns were disabled previously in some <exec> block or due to a rowCond, iterating through every row will not account for these disabled rows unless you explicitly check if the element is disabled. For example, given the following XML code:

<exec>Q1.r2.disabled = True</exec>
<text label="Q1" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">1</row>
  <row label="r2">2</row>
  <row label="r3">3</row>
  <validate>
for eachRow in Q1.rows:
    if not eachRow.val:
        error("Please answer all elements.")
  </validate>
</text>

Because Q1.r2 is disabled in the <exec> block, the <validate> will always produce an error because the the respondent is not able to provide an isdigit for Q1.r2. One solution is to use a <validateRow> tag mentioned above, but the easier solution would be to check if the row is disabled:

<exec>Q1.r2.disabled = True</exec>
<text label="Q1" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">1</row>
  <row label="r2">2</row>
  <row label="r3">3</row>
  <validate>
for eachRow in Q1.rows:
    if not eachRow.disabled:
        if not eachRow.val:
            error("Please answer all elements.")
  </validate>
</text>

This solution properly checks to verify that the row was shown to the respondent before checking to see if a value was supplied.

If you're unsure about your <validate> logic, have someone on your team review your code!

4.2:  Empty Values

Question to ask yourself: "Will my code break if the respondent does not provide a value for one of the cells?"

The value for an empty question or cell returns None. You must always account for this when creating <validate> logic as forgetting to do so may result in a fatal error. Consider the following example that ensures all values entered are greater than 10:

<number label="Q1" size="3" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">1</row>
  <row label="r2">2</row>
  <row label="r3">3</row>
  <validate>
for eachRow in Q1.rows:
    if eachRow.val gt 10:
        error("Your value cannot exceed 10.", eachRow)
  </validate>
</number>

If a respondent leaves any row, r1 - r3, blank, then this code will produce an error that is visible to the respondent. In this situation, we should use .ival instead of .val (e.g. eachRow.ival gt 10:). Another solution would be to verify an answer was provided before comparing the value against an integer:

<number label="Q1" size="3" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">1</row>
  <row label="r2">2</row>
  <row label="r3">3</row>
  <validate>
for eachRow in Q1.rows:
    if eachRow.val:
        if eachRow.val gt 10:
            error("Your value cannot exceed 10.", eachRow)
    else:
        error("Please enter a value for %s" % eachRow.text, eachRow)
  </validate>
</number>

4.3:  Multi-Language Studies

Question to ask yourself: "Will everything that I'm showing to the respondent properly display in a different language after translation? Am I using any strings in my logic that may not make sense in a multi-language study?"

Each and every example above does not utilize best practices. Notice that in every <validate> example above, an error message is displayed like this:

  ...
  <validate>
if not blah_blah:
    error("Please provide a value for each input.")
  </validate>
</radio>

This is bad practice! For every message in our entire survey displayed to the respondent, we should make sure that it can be caught and accounted for by our Language System. To accommodate the language system, we should use <res> tags. (read: resource tags). Here's that same example utlizing best practices:

  ...
  <validate>
if not blah_blah:
    error(res.no_value_provided)
  </validate>
</radio>
<res label="no_value_provided">Please provide a value for each input.>/res>

All <res> tags are translateable and very friendly to use. We can reuse them all over our survey and even pipe text into them. Consider the following example:

<res label="value_cannot_exceed">Your value cannot exceed %d.</res>
<res label="value_missing_for">Please enter a value for %s.</res>
<number label="Q1" size="3" optional="0">
  <title>Please be as specific as possible.</title>
  <row label="r1">1</row>
  <row label="r2">2</row>
  <row label="r3">3</row>
  <validate>
for eachRow in Q1.rows:
    if eachRow.val:
        if eachRow.val gt 10:
            error(res.value_cannot_exceed % 10, eachRow)
    else:
        error(res.value_missing_for % eachRow.text, eachRow)
  </validate>
</number>

The use of <res> is extremely important for multi-language studies. Forgetting them can result in having to recontact the translation team to translate the missing text in your survey. You never know if the survey will be fielded again at a later time for a different country!

Use <res> tags for all strings used in Python code.

4.4:  DRY (Don't Repeat Yourself)

Question to ask yourself: "Will writing a function be more efficient and/or save time?"

If you need to add validation logic that is repeated in numerous areas of your project, it may be best to create a function that can be reused at each question rather than rewriting the logic out. Consider the following example:

<res label="value_cannot_exceed">The value cannot be greater than %d.</res>
<number label="Q1" optional="0" size="3" verify="range(1, 365)">
  <title>How many days this year have you had fun?</title>
</number>
<suspend/>

<number label="Q2" optional="0" size="3" cond="Q1.ival gt 0">
  <title>How many days have you been fishing?</title>
  <validate>
if this.ival gt Q1.ival:
    error(res.value_cannot_exceed % Q1.ival)
  </validate>
</number>

<number label="Q3" optional="0" size="3" cond="Q1.ival gt 0">
  <title>How many days have you been to the arcade?</title>
  <validate>
if this.ival gt Q1.ival:
    error(res.value_cannot_exceed % Q1.ival)
  </validate>
</number>

Writing a function to reuse across the survey saves time, allows you to modify the logic in one place (as oppose to for each question), and is a lot easier to read. For example:

<res label="value_cannot_exceed">The value cannot be greater than %d.</res>
<exec when="init">
def no_greater_than(main_question, source_question):
    if main_question.ival gt source_question.ival:
        error(res.value_cannot_exceed % source_question.ival)
</exec>

<number label="Q1" optional="0" size="3" verify="range(1, 365)">
  <title>How many days this year have you had fun?</title>
</number>
<suspend/>

<number label="Q2" optional="0" size="3" cond="Q1.ival gt 0">
  <title>How many days have you been fishing?</title>
  <validate>
no_greater_than(this, Q1)
  </validate>
</number>

<number label="Q3" optional="0" size="3" cond="Q1.ival gt 0">
  <title>How many days have you been to the arcade?</title>
  <validate>
no_greater_than(this, Q1)
  </validate>
</number>

4.5:  Opt-Out Options (e.g. <noanswer>)

If a <noanswer> option is available and selected, the <validate> tag will not be executed and the respondent will be allowed to continue.

5:  What's Next?

Check out this document on the Streamline Conditions with Vector Logic syntax that was used in some of the examples above.

See the System Language Resource document for some of the built-in error messages available.

  • War dieser Artikel hilfreich?