Some results are measured, others are derived: total hardness from calcium and magnesium, a concentration from a reading and a dilution factor, a dry mass result from a wet one. Whenever a technician reaches for a calculator or a spreadsheet next to SENAITE, the arithmetic belongs into a Calculation. Then the formula is stored once, the inputs are on the sample, and the derived result is checked and verified like any other.
The formula
Calculations live under Setup, Calculations. Each has a name and a Calculation Formula. Inputs are referenced by keyword in square brackets:
[Ca] * 2.497 + [Mg] * 4.118
This is total hardness as CaCO3 in mg/L, from the results of
the services with the keywords Ca and Mg. Link the
calculation to a service “Total hardness” on its Methods tab,
and that service no longer takes manual entry: its result
appears as soon as both inputs are there.
The formula is a Python expression. Besides + - * / % and
brackets you can use min, max, round, pow, sum, the
math module (math.log10([x])) and conditional expressions
such as [A] - [B] if [A] > [B] else 0. Further functions can be
imported under Additional Python Libraries; floor and
ceil from math are there by default. For more involved math
a lab can import functions from its own Python module installed
on the server.
Dependencies add themselves
When a sample gets “Total hardness”, SENAITE adds Ca and Mg
as well, because the calculation needs them. The same happens
through an analysis profile:
a profile with only the calculated service brings its inputs
along.
Dependencies also protect the setup. A service cannot be deactivated while an active service still calculates from it, and a service whose calculation or input services are inactive cannot be activated.
Interim fields
Not every input is the result of another analysis. A dilution
factor, a sample weight or a volume comes from the analyst. These
are Calculation Interim Fields, defined on the calculation
and referenced the same way, for example [x_df].
Each interim field has a keyword, a title, an optional default
value, a unit and a result type (numeric, or a selection with
choices such as 1:1|10:10|100:100). Further switches:
- Allow empty: the field may stay empty.
- Report: the value is printed on the report as a note under the result.
- Apply wide: on a worksheet, one value can be set for all analyses of that service at once, which saves typing the same dilution factor thirty times.
Interim fields appear as extra columns next to the result when
results are entered. Their values belong to the analysis and are
submitted and verified together with its result. Their keywords
must be unique across the system and must not collide with a
service keyword; many labs prefix them with x_.
Detection limits inside a formula
For each input analysis a formula can also read its limits and
flags, by appending a suffix to the keyword: [Ca.LDL],
[Ca.UDL], [Ca.LOQ], and the flags [Ca.BELOWLDL],
[Ca.ABOVEUDL], [Ca.BELOWLOQ], which are 1 or 0. That allows
rules such as “count a non-detect as zero”:
(0 if [Ca.BELOWLDL] else [Ca]) * 2.497
+ (0 if [Mg.BELOWLDL] else [Mg]) * 4.118
Whether a non-detect should count as zero, as half the limit or make the sum a non-detect itself is a decision for the lab’s method, not for the software. Write it down and put it into the formula. Our post on detection limits explains where those limits come from.
Test before you link
Below the formula, Test Parameters list every keyword the
formula uses: the input services and the interim fields. Enter
realistic values and save; Test Result shows what the
formula returns. With Ca = 40 and Mg = 12 the hardness
formula above gives 149.296. Check one or two known values from
your current spreadsheet before the calculation goes live.
![Calculation Total Hardness: formula [Ca] + [Mg], test parameters Ca 11 and Mg 5, test result 16.](/_astro/training-calculation-formula.C1R2LcIO_QHmLx.webp)
What happens when something is missing or wrong
| Situation | Calculated result |
|---|---|
| An input analysis has no result yet | empty; nothing is calculated |
| An input result is removed again | the calculated result is cleared |
| Division by zero | 0/0 |
| A keyword that cannot be resolved, a non-numeric input | NA |
NA and 0/0 are visible on the analysis, so they are caught
at verification. An empty result simply waits for its inputs.
Snapshots: what changes when you edit a calculation
From SENAITE 2.7 on, an analysis keeps a copy of the formula, imports and interim fields from the moment the calculation was linked to it. Editing the calculation affects analyses created afterwards. Results that already exist are not recalculated with a formula they were never computed with, which is what an auditor expects.
Pitfalls that catch new labs
Renaming a keyword. Formulas reference services by keyword
as text. SENAITE refuses a new keyword that a formula already
uses, but it does not rewrite formulas when an existing keyword
changes. Rename Ca and the hardness formula can no longer
resolve it. Settle keywords before go-live.
Rounding. Calculations use the stored results, not the
rounded values on screen. A formula fed with 1.234 uses
1.234, even if the service shows 1.2. If your method demands
rounded intermediate values, round in the formula with
round([Ca], 1).
A calculated service with its own uncertainty and limits. The calculated service is a normal service: precision, uncertainty and specifications come from its own settings, not from its inputs. SENAITE does not propagate uncertainties through the formula. If you need the combined uncertainty, calculate it.
One calculation per variant. If the same arithmetic runs with different factors, use an interim field with a default value instead of copying the calculation. Copies drift apart.
Every repeated calculation outside SENAITE is a transcription step that nobody verifies. Moving it into a Calculation takes a few minutes and removes that step for every sample that follows.