Common Recipes

Copy-and-adapt calculation, validation, and skip logic recipes for real surveys

Practical, copy-and-adapt examples for real surveys. Every expression uses realistic variable names — replace them with your survey's question names.

For the exact behaviour of each function, see the Expression Reference and Date and Time Reference.


Age

Question: dob (date) · Calculated question: age

age(${dob}, ${interview_date})

where interview_date is an ordinary date question you create in the survey ("Interview date"). MQUAD does not invent an interview date — the calculation needs two explicitly supplied dates.

Alternatively, use today's date:

age(${dob}, today())

The difference: ${interview_date} is fixed once answered; today() is re-evaluated each time the calculation runs, so the age creeps up to the next birthday on the day itself.

Child age in months

age-months(${dob}, ${visit_date})

For a child under two, months are more meaningful than years.

Pregnancy week

Questions: lmp (date) · visit_date (date)

gestational-weeks(${lmp}, ${visit_date})

Pregnancy week + remaining days

Two calculated fields, usually shown together as "16 weeks + 4 days":

gestational-weeks(${lmp}, ${visit_date})
gestational-days(${lmp}, ${visit_date})

gestational-days returns the 0–6 remainder, and gestational-total-days(${lmp}, ${visit_date}) returns the raw total.

BMI

Questions: weight_kg (decimal) · height_cm (decimal)

bmi(${weight_kg}, ${height_cm})

Height is in centimetres. bmi(70, 175) ≈ 22.86. Do not pass metres.

Days between visits

date-diff('days', ${visit_1}, ${visit_2})

This is signed: reversing the arguments negates the result. Prefer it over the legacy days-between, whose argument order differs by platform.

Months since enrollment

date-diff('months', ${enrollment_date}, ${visit_date})

Completed months — a visit exactly 30 days after enrollment is still 0 months.

Follow-up date

date-add(${visit_date}, 14, 'days')

Adds 14 calendar days. Month-end clamping applies to 'months'/'years' units: date-add('2026-01-31', 1, 'months') → 2026-02-28.

Estimated due date

A generic date calculation (presented as a calculation, not medical advice):

date-add(${lmp}, 280, 'days')

Interview duration

Using the legacy duration (end first!):

duration(${end_time}, ${start_time}, 'minutes')

or with the newer time-diff:

time-diff('minutes', ${start_time}, ${end_time})

time-diff treats both plain times as clock values with no midnight rollover (23:00 → 01:00 is -22 hours). For durations that cross midnight, use datetime questions instead.

Show "Other, specify"

Skip logic on the "Other, specify" question, shown when Other is selected in a select-multiple:

selected(${symptoms}, 'other')

For a select-one the simpler equality works:

${symptoms} = 'other'

Minimum number of selections

Validation on a select-multiple question:

count-selected(${symptoms}) >= 2

Age-based section

Skip logic on the first adult question (age is a calculated question):

${age} >= 18

Pregnancy section

Skip logic on the first pregnancy question:

${pregnant} = 'yes'

Child eligibility

Show child-specific questions when the respondent is pregnant and under a certain age (combines a calculated value and an answer):

${pregnant} = 'yes' and ${age} < 18

Copy household identifier

Default value on a text question:

#household_num

Generate compound identifier

Calculation on a text question (functions available):

concat(${state}, '-', ${district}, '-', ${household_num})

produces e.g. MH-PUNE-0012. (In a default value the same composition uses #state-#district-#household_num — no functions there.)

Conditional calculated value

if(${age} >= 18, 'adult', 'child')

Date validation

End date cannot be earlier than start date — validation on end_date:

${end_date} >= ${start_date}

Numeric range validation

Weight must be between realistic bounds — validation on weight_kg:

. >= 2 and . <= 200

The dot . is the current question's value.

Required only under a condition

MQUAD's required flag is unconditional. To make a field required only when a condition holds, use if() as a validation:

if(${has_children} = 'yes', . != '', .)

Advanced examples

Multiple conditions

Show a question when the respondent is female, aged 15–49, and married:

${sex} = 'female' and ${age} >= 15 and ${age} <= 49 and ${marital_status} = 'married'

Read: "female AND age 15 to 49 AND married".

Multiple-choice logic

Show treatment-details questions when any relevant treatment was selected:

selected(${treatments}, 'antibiotics') or selected(${treatments}, 'hospital') or count-selected(${treatments}) > 0

Since any selection means at least one, the simpler form works too:

count-selected(${treatments}) >= 1

Chained calculated values

A dependency chain — DOB → Age → Age group → section visibility:

  1. dob (date question, answered by the respondent)
  2. age (calculated): age(${dob}, ${interview_date})
  3. age_group (calculated): if(${age} < 5, 'under5', if(${age} < 18, 'child', 'adult'))
  4. Section skip logic: ${age_group} = 'adult'

When dob changes, MQUAD's dependency graph re-evaluates age, then age_group, then the section visibility — in that order.

BMI chain

  1. weight_kg and height_cm (answered)
  2. bmi (calculated): bmi(${weight_kg}, ${height_cm})
  3. bmi_category (calculated):
if(${bmi} < 18.5, 'underweight', if(${bmi} < 25, 'normal', if(${bmi} < 30, 'overweight', 'obese')))

if() nests: the second if runs only when the first condition is false.

Pregnancy chain

  1. lmp (answered)
  2. gest_age_weeks (calculated): gestational-weeks(${lmp}, ${visit_date})
  3. trimester (calculated):
if(${gest_age_weeks} < 13, 'first', if(${gest_age_weeks} < 28, 'second', 'third'))
  1. Follow-up questions with skip logic like ${trimester} = 'third'

There is no dedicated trimester function — conditional expressions derive it, as shown.

Conditional calculated value with numbers

if(${member_status} = 'premium', ${total} * 0.2, 0)

20% discount for premium members, else 0.