Calculated fields

A calculation data type holds a value Trialflare works out for you rather than one anybody enters. You write a formula, and every time a response is submitted or edited, the formula runs over that response's answers and the result is stored alongside them — scored, exported and shown like any other field.

Add one the same way as any other data type: Data types > Add data type, choose Calculation, and write the formula.

Writing a formula

A formula is a line of arithmetic. Wherever you want an answer from another data type, write its name in double braces:

{{Weight}} / (({{Height}} / 100) ** 2)

Use the Insert a value picker underneath the box rather than typing names by hand — a name has to match exactly, spaces and all. It offers your data types and the participant values below.

If your formula needs more than one line, do the working in as many lines as you like and put the answer in result:

height_m = {{Height}} / 100
bmi = {{Weight}} / (height_m ** 2)
result = round(bmi, 1)

You can check a formula before saving with Describe and check my formula, which explains in plain English what it will do.

Result type

Underneath the formula, tell Trialflare what it works out: Number, Text or Date. Most calculations are numbers, which is the default.

This matters for exports. A column's type has to be the same in every export of the trial, so Trialflare takes it from this setting rather than from whatever the formula happened to produce. A calculation that returns text — a category, a flag like "In window" — set to Number will compute and display correctly everywhere and come out empty in exports. If your formula produces anything other than a number, set this to match.

What a placeholder gives you

The data type What {{...}} becomes
Number, slider, rating, weight, height, temperature The number
Short text, paragraph The text
Date, date & time The date, ready for date arithmetic
Time The time as text, like "09:30:00"
Multiple choice A list of the chosen options
Likert matrix The total across its answered statements
BMI The calculated BMI number
Calculation Another calculation's result

When an answer is missing

A formula only runs once every placeholder in it has an answer. If any one of them is blank, the calculation is left blank too — it does not run with the missing value treated as zero. That is deliberate: a silently-wrong number is worse than an empty field.

So if you want a formula to cope with a field that is sometimes skipped, don't reference it directly; reference something that is always answered, or make the field required.

When a formula is broken

A missing answer is ordinary, and nothing happens. A formula that is genuinely wrong — it divides by zero, or expects a number and gets text — is different: it will blank its field on every response until it is corrected.

Trialflare emails the trial's admins the first time that happens, naming the calculation, the stage, the formula and what went wrong. You are emailed once per problem, not once per response, and again only if the problem changes. Correcting the formula clears it.

Values beyond this form's answers

As well as the answers on the form, a formula can reference a few things Trialflare already knows about the participant:

Placeholder What it is
{{Participant.ConsentDate}} When the participant completed their eConsent
{{Participant.RegisteredDate}} When the participant was added to the trial
{{Participant.Id}} Their participant identifier
{{Response.SubmittedDate}} When this response was submitted
{{Anchor.Randomisation}} The participant's date for an anchor — swap in your own anchor's name

These are spelled exactly as above. A misspelling is rejected when you save the formula rather than quietly leaving the field blank forever.

If the participant has no value for one — they have not consented, or that anchor date has not been set — the calculation stays blank, the same as a missing answer.

Working with dates

Date and date & time answers arrive as real dates, so you can compare them and do arithmetic on them.

Function What it does
now() The moment this response was submitted
today() The same, with the time stripped off
days_since(date) Days from date until now. Negative if date is in the future
days_between(start, end) Days from start to end. Negative if end is earlier
years_between(start, end) Whole years from start to end. end is optional and defaults to now
age(date_of_birth, on) Age in whole years. on is optional and defaults to now
parse_date(text) Turns a date written as text, like "2026-09-01", into a date
timedelta(days=4) A length of time you can add to or subtract from a date

now() is the response's own submission time, not the current time. That matters: if somebody edits a response months later, every calculation on it is worked out again, and pinning the clock to the submission means they get the same answers they got the first time.

Subtracting one date from another gives you the length of time between them, and when that is the formula's answer it is stored as a number of days, including the fraction:

{{Discharge date}} - {{Admission date}}

If you want to do more arithmetic on that gap — round it, compare it, add it up — use days_between() instead, which gives you a plain number to work with:

result = round(days_between({{Admission date}}, {{Discharge date}}), 1)

Available functions

Working with You can use
Numbers abs round min max sum pow divmod int float sqrt exp log log10 log1p floor ceil trunc fabs fmod hypot copysign factorial isnan isinf
Averages and spread mean median std var stdev variance fsum
Angles sin cos tan asin acos atan atan2 sinh cosh tanh degrees radians pi e
Text len str chr ord and the usual text methods, like {{Notes}}.strip() or {{Notes}}.lower()
Lists len list set sorted reversed sum min max any all enumerate zip range map filter dict
Dates The date functions in the table above, plus datetime, timezone and ZoneInfo for timezone conversion

Formulas can use if/else, for loops and list comprehensions. They cannot define their own functions, use while or try, import anything, or reach outside the calculation.

Limits

A formula is for working out a value from a handful of answers, so it is held to that: it must finish within two seconds, and it cannot build an unreasonable amount of data. A formula that hits one of these stops and reports why instead of producing a number. In practice you will not meet either.

Examples

Copy any of these into a formula and swap the names in braces for your own data types' names.

Body-mass index

Trialflare has a dedicated BMI data type that collects height and weight and does this for you, and you should normally use it. But if you already collect height and weight separately:

result = round({{Weight}} / (({{Height}} / 100) ** 2), 1)

The one people ask for most. Collect date of birth on a screening or baseline form, and work their age out against the date they consented rather than against today, so it doesn't creep upwards over the life of the study:

result = age({{Date of birth}}, {{Participant.ConsentDate}})

For age at this visit instead, leave the second value off — it defaults to when the response was submitted:

result = age({{Date of birth}})

How long they have been in the study

result = round(days_since({{Participant.ConsentDate}}))

Whether a visit landed in its window

Say the 30-day follow-up is acceptable between 28 and 35 days after randomisation:

days = days_since({{Anchor.Randomisation}})
result = "In window" if 28 <= days <= 35 else "Out of window"

Point a query at the result and out-of-window visits raise themselves.

Age band

years = age({{Date of birth}}, {{Participant.ConsentDate}})
if years < 18:
    result = "Under 18"
elif years < 40:
    result = "18-39"
elif years < 65:
    result = "40-64"
else:
    result = "65+"

A questionnaire total

If you already use a Likert matrix, its placeholder is the total, so you rarely need this. For separate rating fields:

result = {{Q1}} + {{Q2}} + {{Q3}} + {{Q4}} + {{Q5}}

Or an average, to one decimal place:

result = round(mean([{{Q1}}, {{Q2}}, {{Q3}}, {{Q4}}, {{Q5}}]), 1)

A subscale that reverses some items

Items 2 and 4 are worded the other way round on a 1-5 scale, so they score 6 - answer:

items = [{{Q1}}, 6 - {{Q2}}, {{Q3}}, 6 - {{Q4}}, {{Q5}}]
result = sum(items)

Change since baseline

Both values have to be on the same form for this — a formula sees one response, not the participant's history:

result = round(({{Weight now}} - {{Weight at baseline}}) / {{Weight at baseline}} * 100, 1)

Waist-to-hip ratio

result = round({{Waist circumference}} / {{Hip circumference}}, 2)

Mean arterial pressure

result = round(({{Systolic BP}} + 2 * {{Diastolic BP}}) / 3)

How many options they picked

A multiple-choice placeholder is a list, so:

result = len({{Symptoms}})

And to check for a particular one:

result = "Yes" if "Fever" in {{Symptoms}} else "No"

Length of stay

result = round(days_between({{Admission date}}, {{Discharge date}}), 1)
result = round(days_between({{Participant.ConsentDate}}, {{Response.SubmittedDate}}))

Using a calculation elsewhere

Once a calculation has a value it behaves like any other answer:

  • Scoring — a calculation's result can be scored, as long as it is a number. See screening questionnaires.
  • Queries — you can raise a query automatically when a result falls outside a range.
  • Exports — the result is a column like any other.
  • Other calculations — one calculation can reference another by name, and Trialflare works out the order for itself, so it does not matter where the two sit on the form.