Docs/Infohub (Flow)/Calculation Expression Reference
Infohub (Flow)Reference

Calculation Expression Reference

Infohub calculations are compiled C#. This is everything an expression has in scope.

Updated 15 Oct 2026Infohub Docs

An Infohub calculation expression is not a formula in a bespoke mini-language. It is a fragment of C# that Infohub compiles, with a handful of objects placed in scope for you. That has a liberating consequence: anything legal in a C# expression — ternaries, Math, LINQ, string handling, null coalescing — is legal in an Infohub calculation.

An interval calculation must evaluate to a double?. Returning null means "no value for this interval", which is different from returning 0.

Referring to other measures#

Every measure your calculation depends on appears as a variable named Measure_ followed by that measure's ID:

Measure_1043.Value

You do not type the dependency list by hand in Flow Config — adding a measure to the calculation's dependencies creates the symbol. From the CLI, dependencies are passed explicitly alongside the expression.

Important

The number is a measure ID, so expressions are not portable. Measure_1043 means measure 1043 in this database. The same expression copied to another instance refers to whatever measure happens to hold that ID there. This is why templates and model attributes exist — see Model Attributes.

What a measure gives you#

Each Measure_<id> is a MeasureProxy:

Member Type What it is
.Value double? The value for the period being calculated
.Quality int Source quality code for that value
.Duration long? Duration in milliseconds associated with the value
.Events collection Events overlapping this period
.Attributes collection Event attribute values in scope
.Limits collection Limits configured on this measure
.ModelAttributes collection Model attributes resolved for this measure
.Slices collection The period's slices
.NullAttribute proxy Fallback used when an attribute is absent

Reaching other periods#

This is the part that makes Infohub calculations powerful, and it is easy to miss.

A single offset period — index the measure with a relative period number. 0 is the period being calculated, -1 the one before it:

Measure_1043[-1].Value            previous period
Measure_1043[-12].Value           twelve periods ago

Growth against the previous period, guarded:

Measure_1043[-1].Value == 0 ? 0
    : (Measure_1043.Value - Measure_1043[-1].Value) / Measure_1043[-1].Value * 100

A range of periods — index with a start and an end to get a collection:

Measure_1043[-7, 0]

That collection exposes parallel arrays, which LINQ then operates on:

Member Type
.Values double?[]
.Qualities int?[]
.Durations long?[]
.Limits collection
.SliceCollections collection per period

A rolling seven-period average:

Measure_1043[-7, 0].Values.Average()

And because it is real LINQ, so is this:

Measure_1043[-30, 0].Values.Where(v => v.HasValue && v > 0).Average()
Tip

Rolling windows beat extra measures. A rolling seven-day average is one calculated measure with a window expression. Building it as seven measures plus a sum is a common early mistake that triples the model size and breaks the first time the window changes.

The period being calculated#

Period describes the interval the calculation is producing. It is how a single expression can behave differently on different days.

Member Type Notes
Period.Start, Period.End DateTime UTC
Period.StartLocal, Period.EndLocal DateTime Converted to the period's time zone
Period.TimeZoneInfo TimeZoneInfo The zone in play
Period.Duration TimeSpan Length of the period
Period.IsTimePeriod bool True for interval periods
Period.IsEventPeriod bool True when calculating against an event
Period.Hour, .DayOfWeek, .DayOfMonth, .DayOfYear int? Calendar position
Period.Week, .Month, .Quarter, .Year int? Calendar position
Period.Shift string Shift name, where the period is a shift
Period.Interval int? Interval number
Period.Index long? Sequential period index

Period can also be offset, exactly like a measure — Period[-1] is the previous period, and Period[-7, 0] is a range.

Two things this makes easy. Per-day-of-month normalisation, using the real length of the month:

Measure_1043.Value / Period.Duration.TotalDays

Weekend or shift-specific behaviour:

(Period.DayOfWeek == 6 || Period.DayOfWeek == 7) ? 0 : Measure_1043.Value
Important

Period.Start and Period.End are UTC. Use StartLocal and EndLocal when comparing against wall-clock times an operator would recognise. Comparing Period.Start.Hour against a shift start is a classic off-by-the-timezone-offset bug.

Context and model attributes#

Context carries what the calculation is being evaluated for:

Member What it is
Context.Type The kind of thing being calculated
Context.Name Name of the object in context
Context.Event, .Attribute, .AttributeValue Event context, where applicable
Context.Attributes Attribute values, by key
Context.ModelAttributes Model attributes, by key

Model attributes are the mechanism that makes one expression serve many instances. Where a model attribute holds a design capacity or a conversion factor, the expression reads it rather than hard-coding a number:

Convert.ToDouble(Context.ModelAttributes["Design Capacity"].Value)

Model attribute values are strings, so convert them. Guard the conversion if the attribute may be missing or blank.

Measure collections#

Collection gives an expression access to a measure collection — a set of measures defined by a filter rather than listed one by one. That is what allows "the sum of this metric's children" to keep working when someone adds another child.

Collections are defined by their own filter expression, evaluated against Me, MyMetric, MyEvent, MyParent, MyFolder, MyTemplate, and MyModelAttributes.

Custom functions#

Function libraries you define are compiled alongside the expression and their namespaces are imported, so their methods are callable unqualified:

SafeDivide(Measure_1043.Value, Measure_1044.Value)

See Custom Functions. Because a library edit invalidates the cached calculation assembly, changing a function takes effect for every calculation that uses it.

Available namespaces#

System, System.Linq, and System.Collections.Generic are imported, so Math, Convert, TimeSpan, DateTime, and the LINQ operators are all available:

Math.Round(Measure_1043.Value ?? 0, 2)
Math.Max(Measure_1043.Value ?? 0, 0)
Convert.ToDouble(Context.ModelAttributes["Factor"].Value)

Working with nulls#

.Value is double?, and null means the value is genuinely absent. Decide deliberately which you want:

Measure_1043.Value ?? 0                       treat missing as zero
Measure_1043.Value.HasValue ? … : (double?)null   propagate missing

Propagating null is usually more honest: a gap stays visible instead of being silently reported as zero. Substituting zero is right when zero is genuinely the operational meaning — no production on a shutdown day.

Guard every division#

Measure_1044.Value == 0 ? 0 : Measure_1043.Value / Measure_1044.Value

This is not a stylistic preference. See The Zero Guard.

Timestamp calculations differ#

A calculation on a Tag interval measure runs per raw value rather than per interval, and its symbols differ: Timestamp is in scope for the current point, and .Points and .Duration are available on each measure.

Two behaviours to know. Values are emitted only when the value or quality changes from the previous point, so a timestamp calculation produces a sparse, step-shaped series rather than one point per input. And because each point's duration extends to the next point, the last point in a window takes its duration from the window end.

Practical patterns#

Percentage of target, guarded and bounded:

Measure_2001.Value == 0 ? 0
    : Math.Min(Measure_2000.Value / Measure_2001.Value * 100 ?? 0, 100)

Availability from an event-derived downtime measure:

Period.Duration.TotalMinutes == 0 ? 0
    : (Period.Duration.TotalMinutes - (Measure_3010.Value ?? 0)) / Period.Duration.TotalMinutes * 100

Month-to-date from a rolling window on a daily measure:

Measure_1043[-Period.DayOfMonth ?? 0, 0].Values.Where(v => v.HasValue).Sum()

Year-on-year comparison for a monthly measure:

Measure_1043[-12].Value
esc
↑↓ navigate · ↵ open · ⌘K anywhere