---
title: "Expression Tags"
description: "Create calculated tags using mathematical expressions, tag references, and built-in functions"
source_url: https://ai-ops.com/docs/tags/expressions
---

# Expression Tags

Expression tags compute their value from a formula evaluated by the **Expression Evaluator** service. Expressions can reference tags, devices, AI models, and model bindings, use arithmetic and logical operators, and call built-in math and statistical functions.

Expressions are evaluated in one of two ways:

- **Reactive** — when any referenced tag's value changes, the expression re-evaluates immediately (within milliseconds). This is the default when the expression references at least one tag value.
- **Timer-based** — if the expression has no reactive tag dependencies (e.g. it only references device or model status), it evaluates on a configurable interval set by the **Evaluation Rate** field.

Common use cases include:

- Unit conversions (e.g. Celsius to Fahrenheit)
- Scaling raw signals (e.g. 4–20 mA to 0–100%)
- Averaging multiple sensors
- Smoothing noisy readings with time-aware filters
- Computing rate of change for trend detection
- Status-based fallback logic (switch to backup sensor if primary fails)
- Alarm thresholds with deadband filtering
- Health monitoring (react to device or model failures)

## References

Type `@` in the expression editor to search and insert a reference. The autocomplete menu shows tags, devices, AI models, and model bindings — each with the fields you can reference.

### Tag References

Reference a tag's live value, status, or error code:

```text
@[Temperature Sensor:value]       → current numeric value
@[Temperature Sensor:status]      → 1 if running, 0 if stopped/failed
@[Temperature Sensor:error_code]  → numeric error code (0 = no error)
```

Tag value references are **reactive** — the expression re-evaluates immediately when the referenced tag's value changes.

### Device References

Reference a device's connection status or error code:

```text
@[OPC-UA Server 1:status]      → 1 if running, 0 if stopped/failed
@[OPC-UA Server 1:error_code]  → numeric error code (0 = no error)
```

Device references are useful for building failover logic — for example, switching to a backup device's tags when the primary device goes down.

### Model References

Reference an AI model's status or error code:

```text
@[Chiller Model:status]      → 1 if running, 0 if stopped/failed
@[Chiller Model:error_code]  → numeric error code (0 = no error)
```

### Binding References

Reference the raw prediction value from a model output binding:

```text
@[Chiller Model / Supply Temp:value]       → the binding's output value
@[Chiller Model / Supply Temp:status]      → 1 if running, 0 if stopped/failed
@[Chiller Model / Supply Temp:error_code]  → numeric error code (0 = no error)
```

Binding value references give you access to a model's prediction output without needing an in-memory tag to historize it first.

> [!TIP] Autocomplete helps avoid typos
> Always use the `@` autocomplete menu to insert references rather than typing them manually. This ensures the name and field are spelled correctly and creates a tracked dependency.

## Arithmetic Operations

Standard math operators are supported:

| Operation | Symbol | Example | Result |
|-----------|--------|---------|--------|
| Addition | `+` | `5 + 3` | 8 |
| Subtraction | `-` | `10 - 4` | 6 |
| Multiplication | `*` | `6 * 7` | 42 |
| Division | `/` | `20 / 4` | 5.0 |
| Power | `**` | `2 ** 3` | 8 |
| Modulo | `%` | `10 % 3` | 1 |

## Comparison Operations

Comparisons return `True` or `False` and are commonly used inside conditional expressions:

| Operation | Symbol | Example | Result |
|-----------|--------|---------|--------|
| Equal | `==` | `5 == 5` | True |
| Not Equal | `!=` | `5 != 3` | True |
| Greater Than | `>` | `10 > 5` | True |
| Less Than | `<` | `3 < 7` | True |
| Greater or Equal | `>=` | `5 >= 5` | True |
| Less or Equal | `<=` | `3 <= 5` | True |

## Logical Operations

Combine conditions with `and`, `or`, and `not`:

```text
True and True    → True
True or False    → True
not True         → False
```

## Math Functions

Built-in math functions for common calculations:

| Function | Description | Example | Result |
|----------|-------------|---------|--------|
| `sin(x)` | Sine (radians) | `sin(1.5708)` | 1.0 |
| `cos(x)` | Cosine (radians) | `cos(3.14159)` | -1.0 |
| `tan(x)` | Tangent (radians) | `tan(0.7854)` | 1.0 |
| `sqrt(x)` | Square root | `sqrt(16)` | 4.0 |
| `exp(x)` | e raised to power x | `exp(1)` | 2.718 |
| `log(x)` | Natural logarithm | `log(2.71828)` | 1.0 |
| `log10(x)` | Base-10 logarithm | `log10(100)` | 2.0 |
| `fabs(x)` | Absolute value | `fabs(-5.5)` | 5.5 |
| `ceil(x)` | Round up to integer | `ceil(4.3)` | 5 |
| `floor(x)` | Round down to integer | `floor(4.7)` | 4 |
| `trunc(x)` | Truncate to integer | `trunc(4.7)` | 4 |
| `pow(x, y)` | x raised to power y | `pow(2, 3)` | 8 |
| `degrees(x)` | Radians to degrees | `degrees(3.14159)` | 180.0 |
| `radians(x)` | Degrees to radians | `radians(180)` | 3.14159 |

## Statistical Functions

Functions that operate on lists of values:

| Function | Description | Example | Result |
|----------|-------------|---------|--------|
| `mean(list)` | Average of values | `mean([1, 2, 3, 4, 5])` | 3.0 |
| `median(list)` | Middle value | `median([1, 3, 5, 7, 9])` | 5 |
| `min(list)` | Minimum value | `min([5, 2, 8, 1, 9])` | 1 |
| `max(list)` | Maximum value | `max([5, 2, 8, 1, 9])` | 9 |
| `sum(list)` | Sum of all values | `sum([1, 2, 3, 4, 5])` | 15 |

> [!NOTE] Lists use square brackets
> Statistical functions require a list as input. Create lists using square brackets: `[value1, value2, value3]`. You can mix tag references and constants inside a list.

## Filter Functions

Filter functions smooth, transform, or gate input signals. They maintain internal state across evaluations, so each call remembers its previous output.

### filter — Exponential Moving Average

`filter(value, tau)` applies an exponential moving average. The `tau` parameter is the **time constant in seconds** — it controls how quickly the output tracks the input, regardless of how often the expression evaluates.

| Parameter | Description |
|-----------|-------------|
| `value` | The current input value |
| `tau` | Time constant in seconds. Larger values = more smoothing |

At each evaluation the filter computes: `alpha = 1 - e^(-dt / tau)`, then `output = alpha x input + (1 - alpha) x previous`. On the first evaluation the output equals the input.

| Expression | Description | Use Case |
|------------|-------------|----------|
| `filter(@[Temperature:value], 5)` | 5-second time constant | Fast-changing signals, light smoothing |
| `filter(@[Pressure:value], 30)` | 30-second time constant | Moderate noise reduction |
| `filter(@[Level:value], 120)` | 2-minute time constant | Very noisy sensors, stable reading needed |

> [!TIP] Time-aware smoothing
> The `tau` parameter is specified in real seconds, so the filter behaves identically whether the expression evaluates once per second or once per minute. A `tau` of 30 always means a 30-second time constant.

### moving_avg — Moving Average

`moving_avg(value, window)` computes the average of all samples received within the last `window` seconds.

| Parameter | Description |
|-----------|-------------|
| `value` | The current input value |
| `window` | Window size in seconds |

| Expression | Description |
|------------|-------------|
| `moving_avg(@[Flow:value], 60)` | Average flow over the last 60 seconds |
| `moving_avg(@[Power:value], 300)` | 5-minute rolling average of power consumption |

### rate — Rate of Change

`rate(value)` returns the rate of change of the input in **units per second**. It only updates when the input actually changes, so repeated evaluations with a stale value don't distort the result.

| Parameter | Description |
|-----------|-------------|
| `value` | The current input value |

| Expression | Description |
|------------|-------------|
| `rate(@[Temperature:value])` | Degrees per second of temperature change |
| `rate(@[Level:value]) * 60` | Level change per minute |

### deadband — Deadband Filter

`deadband(value, threshold)` only passes through changes larger than `threshold`. The output holds its last accepted value until the input moves far enough away.

| Parameter | Description |
|-----------|-------------|
| `value` | The current input value |
| `threshold` | Minimum change to pass through (must be > 0) |

| Expression | Description |
|------------|-------------|
| `deadband(@[Setpoint:value], 0.5)` | Ignore changes smaller than 0.5 |
| `deadband(filter(@[Noisy:value], 10), 1.0)` | Smooth first, then deadband |

## Conditional Expressions

Use Python's ternary syntax for if-else logic:

```text
value_if_true if condition else value_if_false
```

| Expression | Description |
|------------|-------------|
| `1 if @[Switch:value] == 1 else 0` | Binary output based on switch state |
| `@[Temp:value] * 1.8 + 32 if @[Units:value] == 1 else @[Temp:value]` | Convert to Fahrenheit if units flag is set |
| `"High" if @[Level:value] > 80 else "Low"` | Text output based on threshold |
| `100 if @[Valve:value] == 1 else 50 if @[Valve:value] == 0.5 else 0` | Multiple conditions (nested) |

## Practical Examples

### Temperature Conversion

Celsius to Fahrenheit:

```text
@[Temperature C:value] * 1.8 + 32
```

### Scaling and Offset

Convert a 4–20 mA signal to 0–100%:

```text
(@[Current:value] - 4) * 100 / 16
```

### Average of Multiple Sensors

```text
(@[Sensor1:value] + @[Sensor2:value] + @[Sensor3:value]) / 3
```

### Status-Based Fallback

Use a backup sensor if the primary tag fails:

```text
@[Primary:value] if @[Primary:status] == 1 else @[Backup:value]
```

### Device Failover

Switch to a backup device's sensor when the primary device connection goes down:

```text
@[Primary Sensor:value] if @[Primary OPC-UA:status] == 1 else @[Backup Sensor:value]
```

### Model Health Check

Output 1 when the AI model is running, 0 when it's stopped or failed:

```text
1 if @[Chiller Model:status] == 1 else 0
```

### Alarm with Deadband

High alarm with a 5-unit deadband — turns on above 95, off below 90, holds previous state in between:

```text
1 if @[Process:value] > 95 else (0 if @[Process:value] < 90 else @[Alarm:value])
```

### Smoothed Differential

Smoothed pressure differential with a 30-second time constant:

```text
filter(@[Pressure In:value] - @[Pressure Out:value], 30)
```

### Rate of Temperature Change

Degrees per minute:

```text
rate(@[Supply Temp:value]) * 60
```

### Smoothed Deadband

Reduce noise first, then gate small changes:

```text
deadband(filter(@[Vibration:value], 10), 0.5)
```
