# Auto-Populate and Calculations {#auto-populate-and-calculations}


Calculation expressions **derive a value from other survey answers**. They are
re-evaluated whenever any referenced answer changes, so the derived value stays
current even if the respondent goes back and edits an earlier answer.

There are two ways to use a calculation:

| Mode | Question type | Behaviour |
|---|---|---|
| **Calculated Value** | `calculate` | A read-only computed result. Displayed as text; respondents cannot edit it. |
| **Auto-Populate** | any other type (text, integer, decimal, select_one, date, ...) | A pre-filled value that the respondent **can** override. Re-computed whenever dependencies change. |

Both modes use the exact same expression syntax and function set.

> **Not the same as Default Values.** A default value is evaluated **once**, at
> form start, and never changes. A calculation re-runs on every dependency
> change. See [Default Values](/user-manual/default-values.md) and the comparison table below.

---


## Where to configure it {#where-to-configure-it}


1. Open your survey in the **Survey Designer**.
2. Select the question in the canvas.
3. In the right-hand **Properties** panel, scroll to the **Advanced** section.
4. Click **Calculated Value** (for a `calculate` question) or **Auto-Populate**
   (for any other type).

The **Calculation Editor** provides:

- **Available Variables** — questions before the current one. Click to insert
  a `${variable}` reference.
- **Functions** — the built-in function catalog. Click to insert the function
  name and an opening parenthesis.
- **Formula** textarea — type or paste your expression. It validates as you
  type (unknown variables and unbalanced parentheses are flagged).
- **Quick Insert** — operator buttons for `+ - * / ( ) = > < >= <= and or`.

The editor only offers **earlier** questions as variables — a calculation
cannot reference a question that appears later in the form.

---


## Referencing other questions {#referencing-other-questions}


Use `${question_name}` to reference another question's answer:

```text
${quantity} * ${unit_price}
```

multiplies the quantity by the unit price. The reference uses the question's
**variable name** — never its label.


## Arithmetic operators {#arithmetic-operators}


| Operator | Meaning | Example |
|---|---|---|
| `+` | addition | `${q1} + ${q2}` |
| `-` | subtraction | `${total} - ${discount}` |
| `*` | multiplication | `${qty} * ${price}` |
| `div` | division | `${total} div ${count}` |
| `/` | division | `${total} / ${count}` |
| `mod` | remainder | `${items} mod 2` |

`div` and `/` both divide; `mod` gives the remainder.


## Comparison and logical operators {#comparison-and-logical-operators}


Calculations can produce boolean values too:

| Operator | Meaning | Example |
|---|---|---|
| `=`, `==` | equals | `${pregnant} = 'yes'` |
| `!=` | not equals | `${pregnant} != 'yes'` |
| `>`, `>=`, `<`, `<=` | comparisons | `${age} >= 18` |
| `and` | logical AND | `${age} >= 15 and ${age} <= 49` |
| `or` | logical OR | `${district} = 'A' or ${district} = 'B'` |
| `not(...)` | logical NOT | `not(${pregnant} = 'yes')` |

Use parentheses to group: `(${a} = 1 or ${b} = 1) and ${c} = 1`.

---


## The calculation function set {#the-calculation-function-set}


MQUAD implements a frozen set of calculation functions identically across the
web designer/runtime, the Flutter mobile app, and the form-service backend.
Each function's exact semantics (argument order, units, null behaviour) are
defined in the [Date and Time Reference](/user-manual/date-time-reference.md) and the
[Expression Reference](/user-manual/expression-reference.md). Below is the summary of every
public calculation function, grouped by purpose.


### Date calculations {#date-calculations}


| Function | Purpose | Example |
|---|---|---|
| `date-diff(unit, start, end)` | Completed calendar units between two dates (signed) | `date-diff('months', ${enrollment}, ${visit})` |
| `date-add(date, amount, unit)` | Add calendar units with month-end clamping | `date-add(${visit_date}, 14, 'days')` |
| `date-subtract(date, amount, unit)` | Subtract calendar units with month-end clamping | `date-subtract(${visit_date}, 14, 'days')` |
| `day(date)` | Day of month, 1–31 | `day(${visit_date})` |
| `month(date)` | Month, 1–12 | `month(${visit_date})` |
| `year(date)` | Four-digit year | `year(${visit_date})` |
| `day-of-week(date)` | ISO day of week, 1=Mon…7=Sun | `day-of-week(${visit_date})` |

Units for `date-diff` / `date-add` / `date-subtract` are string literals:
`'days'`, `'weeks'`, `'months'`, `'years'` (singular forms also accepted).


### Time calculations {#time-calculations}


| Function | Purpose | Example |
|---|---|---|
| `time-diff(unit, start, end)` | Difference between two plain times or two datetimes | `time-diff('hours', ${start_time}, ${end_time})` |

Units: `'seconds'`, `'minutes'`, `'hours'`, `'days'`.


### Age {#age}


| Function | Purpose | Example |
|---|---|---|
| `age(dob, ref)` | Completed years at the reference date | `age(${dob}, ${interview_date})` |
| `age-months(dob, ref)` | Completed months | `age-months(${dob}, ${visit_date})` |
| `age-days(dob, ref)` | Total calendar days | `age-days(${dob}, ${visit_date})` |

All age functions return `0` (never negative) when the reference date is on or
before the date of birth.


### Pregnancy / gestational age {#pregnancy--gestational-age}


| Function | Purpose | Example |
|---|---|---|
| `gestational-weeks(lmp, ref)` | Whole weeks since LMP | `gestational-weeks(${lmp}, ${visit_date})` |
| `gestational-days(lmp, ref)` | Remaining days (0–6) | `gestational-days(${lmp}, ${visit_date})` |
| `gestational-total-days(lmp, ref)` | Total days since LMP | `gestational-total-days(${lmp}, ${visit_date})` |

The reference point is the **LMP (last menstrual period) → reference date** —
not a due date. Values are clamped to `0` if the reference is before LMP.


### Health {#health}


| Function | Purpose | Example |
|---|---|---|
| `bmi(weightKg, heightCm)` | Body mass index | `bmi(${weight_kg}, ${height_cm})` |

**Height is always in centimetres** under the frozen contract:
`bmi(70, 175)` = `70 / (175/100)²` ≈ **22.86**. Do **not** pass metres
(`bmi(70, 1.75)` is wrong — it returns a huge value).


### Existing date/time functions (unchanged, legacy) {#existing-datetime-functions-unchanged-legacy}


| Function | Purpose | Example |
|---|---|---|
| `today()` | Current calendar date (device-local) | `today()` |
| `now()` | Current datetime, ISO 8601 | `now()` |
| `date(value)` / `date(y, m, d)` | Parse or construct a date | `date('2026-08-14')` |
| `days-between(d1, d2)` | Days between two dates | `days-between(${visit_1}, ${visit_2})` |
| `duration(end, start, [unit])` | Time difference, default hours | `duration(${end}, ${start}, 'minutes')` |
| `today-offset(days)` | Date `days` from today | `today-offset(-10)` |
| `format-date(value, fmt)` | Format a date with a pattern | `format-date(${visit_date}, '%d/%m/%Y')` |

> **`days-between` argument order — cross-platform caution.** The legacy
> `days-between(d1, d2)` computes `d2 − d1` on the web runtime, but `d1 − d2`
> on the mobile app and form-service backend. **Use `date-diff('days', start,
> end)` instead** — it is frozen, consistent everywhere, and signed
> (`date-diff('days', ${end}, ${start})` is the negation of
> `date-diff('days', ${start}, ${end})`).

> **`duration` argument order — end first!** `duration(end, start, [unit])`
> computes `end − start` on all platforms. It is *not* `duration(start, end)`.
> `duration(${end_time}, ${start_time})` gives a positive duration.


### Numeric functions {#numeric-functions}


| Function | Purpose | Example |
|---|---|---|
| `sum(a, b, ...)` | Sum of values | `sum(${q1}, ${q2}, ${q3})` |
| `min(a, b, ...)` | Minimum | `min(${q1}, ${q2})` |
| `max(a, b, ...)` | Maximum | `max(${q1}, ${q2})` |
| `round(v, n)` | Round to n decimals | `round(${result}, 2)` |
| `abs(v)` | Absolute value | `abs(${change})` |
| `int(v)` | Truncate to integer | `int(${value})` |
| `number(v)` | Convert to number | `number(${text_value})` |
| `pow(base, exp)` | Power | `pow(${radius}, 2)` |


### String functions {#string-functions}


| Function | Purpose | Example |
|---|---|---|
| `concat(a, b, ...)` | Join text | `concat(${first_name}, ' ', ${last_name})` |
| `join(sep, a, b, ...)` | Join with separator | `join('-', ${state}, ${district})` |
| `string(v)` | Convert to string | `string(${age})` |
| `string-length(v)` | Length of string | `string-length(${name})` |
| `substring(s, start, end?)` | Extract substring (web) | `substring(${code}, 0, 3)` |
| `contains(s, sub)` | Contains substring | `contains(${notes}, 'urgent')` |
| `starts-with(s, prefix)` | Starts with prefix | `starts-with(${id}, 'MH-')` |
| `ends-with(s, suffix)` | Ends with suffix | `ends-with(${email}, '@example.com')` |
| `replace(s, search, repl)` | Replace all occurrences | `replace(${text}, '-', '')` |
| `regex(v, pattern)` | Test regex match | `regex(${phone}, '^[0-9]{10}$')` |

> **`substring` / `substr` — cross-platform caution.** On the web runtime,
> `substring(s, start, end?)` uses JavaScript semantics (0-based `start`, and
> `end` is an *end index*, not a length). The mobile runtime instead exposes
> `substr(s, start, length?)` (0-based `start`, third argument is a *length*),
> and the form-service backend implements `substring` with XPath-style 1-based
> indexing. Prefer `contains()`, `starts-with()`, and `string-length()` for
> cross-platform logic, or test `substring` on every platform you deploy to.


### Selection functions {#selection-functions}


| Function | Purpose | Example |
|---|---|---|
| `selected(value, choice)` | Is the choice selected? | `selected(${symptoms}, 'fever')` |
| `count-selected(value)` | Number of selected options | `count-selected(${symptoms})` |
| `selected-at(value, index)` | Choice at 0-based index | `selected-at(${symptoms}, 0)` |
| `count(value)` | Count items | `count(${multi})` |


### Conditional functions {#conditional-functions}


| Function | Purpose | Example |
|---|---|---|
| `if(cond, then, else)` | Conditional value | `if(${age} >= 18, 'adult', 'child')` |
| `coalesce(a, b, ...)` | First non-empty value | `coalesce(${phone_2}, ${phone_1}, 'none')` |
| `true()` / `false()` | Boolean constants | `if(${pregnant} = 'yes', true(), false())` |

> **Use `if()`, not `?:`.** The `if(condition, then, else)` function is the
> supported conditional in calculations on every platform. The `? :` ternary
> syntax exists only in the Flutter expression engine and will not parse on the
> web runtime — do not use it in calculations.


### Utility functions {#utility-functions}


| Function | Purpose | Example |
|---|---|---|
| `uuid()` | Random UUID v4 | `uuid()` |
| `random()` | Random number 0–1 | `random()` |
| `once(value)` | First computed value only (page lifetime) | `once(now())` |

---


## Common calculation patterns {#common-calculation-patterns}


**Total score:**
```text
${q1} + ${q2} + ${q3} + ${q4}
```

**Weighted score:**
```text
(${q1} * 2) + (${q2} * 3) + (${q3} * 1)
```

**Age from DOB (recommended):**
```text
age(${dob}, ${interview_date})
```

`interview_date` in this example is an ordinary **date question you create in
the survey** (for example "Interview date"). MQUAD does not create or infer an
interview date automatically — you must supply it as a question and reference
it explicitly.

**Age with today's date:**
```text
age(${dob}, today())
```

`today()` is evaluated when the calculation runs, so this age updates daily.
The difference from the previous example: `interview_date` is fixed once the
respondent answers it; `today()` always reflects the current date.

**Conditional discount:**
```text
if(${member_status} = 'Premium', ${total} * 0.2, 0)
```

**Composite ID:**
```text
concat(${site_code}, '-', ${participant_num})
```

**Estimated due date** (a generic date calculation — not medical advice):
```text
date-add(${lmp}, 280, 'days')
```

---


## Calculation vs Default Value {#calculation-vs-default-value}


| | Auto-Populate (calculation) | Default Value |
|---|---|---|
| **When evaluated** | Every time dependencies change | Once, at form start |
| **Reactive** | Yes — updates when referenced values change | No — static after form start |
| **User override** | Yes — if the field is editable | Yes — if the field is editable |
| **Reference syntax** | `${question_name}` | `#question_name` |
| **Can call functions** | Yes | No — default grammar has no function calls |
| **Best for** | Dynamic computed values | Initial static values |


## Calculations in XLSForm {#calculations-in-xlsform}


Use the `calculation` column:

| type | name | label | calculation |
|---|---|---|---|
| `calculate` | `total_score` | Total Score | `${q1} + ${q2} + ${q3}` |
| `integer` | `estimated_total` | Estimated Total | `${q1} * ${q2}` |

When importing, all calculations are preserved and visible in the survey
designer. See [XLSForm Mapping](/user-manual/xlsform-mapping.md) for details.


## Tips {#tips}


- Use **Calculated Value** (`type = calculate`) for read-only results.
- Use **Auto-Populate** on normal question types for editable pre-filled values
  that should stay current.
- For static initial values that never need to update, use
  [Default Values](/user-manual/default-values.md) instead.
- A calculation that references an unanswered question yields `null`/blank —
  see [Blank and Invalid Values](/user-manual/blank-and-invalid-values.md).
- Calculations run identically on web forms, the mobile field app, and the
  backend for the frozen function set above. Platform-specific differences are
  flagged inline (`days-between`, `substring`, ternary).
