Docs/Infohub (Flow)/T04: Your First Calculation
Infohub (Flow)How-to

T04: Your First Calculation

Build a KPI, break it deliberately by omitting the zero guard, then fix it and learn why recalculation matters.

Updated 15 Oct 2026Infohub Docs

What you will build: a specific water consumption KPI, an unguarded division that fails, and the fix.

Time: about 30 minutes.

You need: T02 and T03 completed.


Step 1 — Add the energy chain#

One more chain, so you have something to divide. Under Production, in the Filler 1 metric:

Name Retrieval type Interval Aggregation Source / Tag Unit
Energy Meter Retrieved Tag — 110-JQ-001.PV kWh
Energy Hourly Aggregated Hour(s) Counter Energy Meter kWh
Energy Daily Aggregated Day(s) Sum Energy Hourly kWh

Another totaliser, so Counter then Sum — the pattern from T02.

Step 2 — Build the KPI#

Create a metric Efficiency under Production, with:

Field Value
Name Energy per 1000 Bottles
Retrieval type Calculated
Interval Day(s)
Unit kWh/1000btl
Format 0.00
Dependencies Energy Daily, Bottles Daily

Now the expression. Write it without a guard, deliberately:

Measure_<energyDailyId>.Value / Measure_<bottlesDailyId>.Value * 1000

Adding the dependencies is what creates the Measure_<id> symbols — you do not type the dependency list into the expression.

Let it run a day. It works, and produces a plausible number.

Step 3 — Break it#

Now make the denominator zero, the way a real plant does.

The simplest way to see it: wait for a period with no production, or temporarily point Bottle Counter at a tag that does not move. In a real plant it happens by itself — a maintenance day, a public holiday, a shutdown.

When bottles produced for the day is zero, the calculation divides by zero. It does not produce a wrong number — it fails, and the measure has no value for that day.

And the failure travels. Anything aggregating from this measure now has a hole. A monthly figure built from thirty days where two failed is computed from twenty-eight, and it is quietly too low.

Important

There is no red banner. Nothing announces this. The dashboard shows a gap that looks like a data outage, and teams have chased "the historian dropped out" for weeks when the real cause was one unguarded ratio meeting one zero.

Step 4 — Fix it#

Change the expression to:

Measure_<bottlesDailyId>.Value == 0 ? 0
    : Measure_<energyDailyId>.Value / Measure_<bottlesDailyId>.Value * 1000

Read it as: "if bottles is zero, the answer is zero; otherwise divide."

That is the zero guard, and every division in every Infohub calculation should have one. It costs one line and it means the calculation cannot fail on that division.

Step 5 — Recalculate#

Here is the part people miss.

Fixing the expression changes how future periods are calculated. The days that already failed still have no value. Infohub does not go back and retry them because the configuration changed.

Recalculate the affected range. Then check that the previously-failed days now have values.

Important

Every fix needs a recalculation. This applies to almost every correction you will ever make — a wrong aggregation type, a missing dependency, a bad expression. Fix the configuration, then recalculate the range that was wrong. A model where half the history uses the old logic and half the new is worse than either, because nobody can tell which figures to trust.

Step 6 — Zero or null?#

You returned 0. Consider whether that is honest.

On a day with no production, is energy per 1000 bottles genuinely zero? Arguably not — it is undefined. And if this measure gets averaged into a monthly figure, those zeros will drag the average down and understate the plant's real consumption rate.

The alternative:

Measure_<bottlesDailyId>.Value == 0 ? (double?)null
    : Measure_<energyDailyId>.Value / Measure_<bottlesDailyId>.Value * 1000

Now the day has no value, the gap is visible, and the monthly average is computed only from days that actually ran.

The rule of thumb: return 0 where zero is the operational truth; return null where the value is genuinely unknown and will be averaged. For a ratio that gets averaged upward, null is usually more truthful.

Change it to null and recalculate. Compare the monthly average before and after — the difference is the lesson.

Step 7 — Guard against nulls too#

.Value is a nullable double, and a null divisor is a different problem from a zero one. == 0 does not catch it.

The safest single form covers both:

(Measure_<bottlesDailyId>.Value ?? 0) == 0 ? (double?)null
    : Measure_<energyDailyId>.Value / Measure_<bottlesDailyId>.Value * 1000

Use this shape when you are not certain the divisor always exists.

Step 8 — Add a target comparison#

One more calculation, using the manual target from T03. In Efficiency:

Field Value
Name Production vs Target Daily
Retrieval type Calculated
Interval Day(s)
Unit %
Format 0.0 '%'
Dependencies Bottles Daily, Production Target Daily
(Measure_<targetDailyId>.Value ?? 0) == 0 ? (double?)null
    : Measure_<bottlesDailyId>.Value / Measure_<targetDailyId>.Value * 100

Note the format: 0.0 '%'. The measure holds 94.2 meaning 94.2%, so the % is a quoted literal. Using 0.0% instead would display 9420.0%, because % in a .NET format string multiplies by 100.

What you have learned#

Calculations are C#. Ternaries, Math, LINQ, null-coalescing — all available. The expression must evaluate to a double?.

Dependencies create the symbols. Declare them; do not type them into the expression.

Every division needs a guard. Zero divisors are routine, not exceptional: shutdowns, unfilled manual entries, sensor faults, new measures with no history.

Failures propagate silently. One unguarded division leaves holes several layers up.

Choose zero or null deliberately. Zero into an average understates. Null keeps the gap visible.

Fix, then recalculate. Configuration changes are not retrospective.

The unit and format are yours to set. Infohub derives neither. % in a format string multiplies by 100.

Try this before moving on#

Write the guard as a reusable function instead. Create a custom function:

public static double? SafeDivide(double? numerator, double? denominator)
{
    if (!denominator.HasValue || denominator.Value == 0) return null;
    return (numerator ?? 0) / denominator.Value;
}

Then the expression becomes:

SafeDivide(Measure_<energyDailyId>.Value, Measure_<bottlesDailyId>.Value) * 1000

Readable, impossible to forget, and if you later change your mind about null versus zero you change one function rather than two hundred expressions. T11 covers functions properly.


Next: T05: Charts and Dashboards — making all of this visible.

esc
↑↓ navigate · ↵ open · ⌘K anywhere