Expert Guide

Advanced configuration for AI optimizers and complex systems.

Fentrica Expression Language (FEXL)

Fentrica Expression Language (FEXL) is a powerful context-based expression parser and evaluator designed to manage dynamic rules and logic within the Fentrica platform. It enables users to define flexible expressions that interact with real-time datasets and automate control processes based on dynamic data.

Expression Types

An expression in FEXL can be:

  • Literal: A fixed value (e.g., a number, string, or boolean).
  • Variable: A named value that can be accessed or modified.
  • Access Operator: Accessing object properties.
  • Function Call: Calling predefined or custom functions.
  • Evaluation Operator: Performing logical or arithmetic evaluations.
  • Arithmetic Operation: Performing calculations such as addition, subtraction, multiplication, or division. FEXL supports standard arithmetic operations and allows you to calculate values dynamically.

Using Datasets in Schedule Definitions

FEXL supports datasets, allowing them to be used as variables in expressions. Datasets represent structured data, such as energy prices, power production, or weather forecasts. Once a dataset is defined, it becomes accessible in expressions by its variable name.

For example, if a dataset energyPrices is defined, it can be referenced directly in expressions using energyPrices.

Variables

In addition to schedule datasets, default variables are also assigned to the context.

hour - representing the date-time for the hour in which the dynamic schedule rule is running.

dayStart - representing the start of the day in which the dynamic schedule rule is running.

dayEnd - representing the end of the day in which the dynamic schedule rule is running.

Functions

FEXL supports a variety of built-in functions, including:

Get Dataset Item value

Functions to get specific dataset item value.

First

first(array|dataset[])

Returns the first element of an array. If input is dataset, it will return the first sample's value.

first([1, 2, 3]) // returns 1
first(dataset) // returns dataset[0].value

Last

last(array|dataset[])

Returns the last element of an array. If input is dataset, it will return the last sample's value.

last([1, 2, 3]) // returns 3
last(dataset) // returns dataset[dataset.length-1].value

Has

has(dataset[], isoDateTime, granularity?: 'day'|'hour' = 'hour')

Checks if there is a sample in dataset with specified timestamp and returns true or false. The optional granularity parameter defaults to 'hour', and the timestampProperty argument specifies the property holding the timestamp.

has(dataset, '2011-10-05T14:48:00.000Z')
has(dataset, '2011-10-05T14:48:00.000Z', 'day')

Find

find(dataset[], isoDateTime, granularity?: 'day'|'hour' = 'hour')

Finds the first match from a dataset using a timestamp and returns the matching entry. The optional granularity parameter defaults to 'hour', and the timestampProperty argument specifies the property holding the timestamp.

find(dataset, '2011-10-05T14:48:00.000Z') // returns dataset element sample matching 2011-10-05T14:48:00.000Z date and hour .value
find(dataset, '2011-10-05T14:48:00.000Z', 'day') // returns dataset sample matching 2011-10-05T14:48:00.000Z date .value

Dataset filtering

Dataset filter functions return a dataset with matching samples.

Filter

filter(dataset[], isoDateTime, granularity?: 'day'|'hour' = 'hour')

Returns an filtered dataset matching the specified timestamp. The optional granularity parameter defaults to 'hour', and the timestampProperty argument specifies the property holding the timestamp.

filter(dataset, '2011-10-05T14:48:00.000Z') // returns filtered dataset matching 2011-10-05T14:48:00.000Z same day and hour.
filter(dataset, '2011-10-05T14:48:00.000Z', 'day') // returns filtered matching 2011-10-05T14:48:00.000Z same day.

Between

between(dataset[], startIsoDateTime, endIsoDateTime)

Returns an array of all matching entries from the dataset.

between(dataset, '2011-10-05T12:00:00.000Z', '2011-10-05T14:00:00.000Z') // returns all dataset samples matching between (inclusive) 2011-10-05T12:00:00.000Z and 2011-10-05T14:00:00.000Z .values

Exclude

exclude(dataset[], isoDateTime[], granularity?: 'day'|'hour' = 'hour')

Returns an array of all entries from the dataset except entries matching the specified timestamps. The optional granularity parameter defaults to 'hour', and the timestampProperty argument specifies the array holding the timestamps.

exclude(dataset, ['2011-10-05T14:48:00.000Z', '2011-10-05T14:49:00.000Z']) // returns dataset samples excluding 2011-10-05T14:48:00.000Z and 2011-10-05T14:49:00.000Z

limit

limit(dataset[], items: number)

Returns an array of the specified number of items from the dataset. If number is negative, it will return the last items number of items in reverse order.

limit(dataset, 2) // returns dataset first 2 samples
limit(dataset, -2) // returns dataset last 2 samples in reverse order

Values Manipulation

Size & Count

size(array|dataset[])count(array|dataset[])

Returns the size of an array or dataset.

size([1, 2, 3]) // returns 3
count([1, 2, 3]) // returns 3

Min

min(array|dataset[])

Returns the minimum value in an array or dataset.

min([1, 2, 3]) // returns 1

Max

max(array|dataset[])

Returns the maximum value in an array or dataset.

max([1, 2, 3]) // returns 3

Sum

sum(array|dataset[])

Returns the sum of all values in an array or dataset.

sum([1, 2, 3]) // returns 6

Avg

avg(array|dataset[]) Returns the average value of an array or dataset.

avg([1, 2, 3]) // returns 2

Abs

abs(number|datasetItem)

Returns the absolute value of a number or dataset item.

abs(-5) // returns 5

Round

round(number|datasetItem)

Returns the rounded value of a number or dataset item.

round(3.14159) // returns 3

Context store

Set

set(context, 'key', value)

Stores a numeric value in a context object for use across the entire pipeline. The context parameter refers to the storage object (currently, the only available storage is context). For Schedule Dynamic Rules, the pipeline runs for each day, and the context is reset at the beginning of each new day.

Set return value is always true.

Usage:

set(context, 'key', 1) // stores the value 1 in the context under the key 'key'. Returns true.

Get

get(context, 'key', defaultValue?)

Retrieves a numeric value stored in the context. If the key does not exist, it returns 0.

Usage:

get(context, 'key') // retrieves the value stored under 'key'. If the key 'key' does not exist, it returns 0.
get(context, 'key', 10) // retrieves the value stored under 'key'. If the key 'key' does not exist, it returns 10.

Date & Time

Before

before(isoDateTime, whatIsoDateTime, granularity?: 'day'|'hour'|'minute'|'second'|'15min' = 'hour')

Returns true if the first ISO date time is before the second ISO date time. When using '15min' granularity, both timestamps are rounded down to the nearest 15-minute interval before comparison, enabling MTU-aligned comparisons for electricity metering systems.

before('2011-10-05T14:48:00.000Z', '2011-10-05T14:49:00.000Z') // returns true
before('2011-10-05T14:48:00.000Z', '2011-10-05T14:47:00.000Z', 'day') // returns false
before('2025-01-15T10:07:00.000Z', '2025-01-15T10:18:00.000Z', '15min') // returns true (10:00 < 10:15)

After

after(isoDateTime, whatIsoDateTime, granularity?: 'day'|'hour'|'minute'|'second'|'15min' = 'hour')

Returns true if the first ISO date time is after the second ISO date time. When using '15min' granularity, both timestamps are rounded down to the nearest 15-minute interval before comparison, enabling MTU-aligned comparisons for electricity metering systems.

after('2011-10-05T14:48:00.000Z', '2011-10-05T14:47:00.000Z') // returns true
after('2011-10-05T14:48:00.000Z', '2011-10-05T14:47:00.000Z', 'day') // returns false
after('2025-01-15T10:18:00.000Z', '2025-01-15T10:07:00.000Z', '15min') // returns true (10:15 > 10:00)

Same

same(isoDateTime, whatIsoDateTime, granularity?: 'day'|'hour'|'minute'|'second'|'15min' = 'hour')

Returns true if the first ISO date time is the same as the second ISO date time. When using '15min' granularity, both timestamps are rounded down to the nearest 15-minute interval before comparison, enabling MTU-aligned comparisons for electricity metering systems.

same('2011-10-05T14:48:00.000Z', '2011-10-05T14:48:00.000Z') // returns true
same('2011-10-05T14:48:00.000Z', '2011-10-05T15:48:00.000Z', 'day') // returns true
same('2025-01-15T10:07:00.000Z', '2025-01-15T10:12:00.000Z', '15min') // returns true (both round to 10:00)

Special Functions

isEffectiveDischargeHour

isEffectiveDischargeHour(startSOC, dataset[], dischargeAmountEachHour, hour)

isEffectiveDischargeHour is a utility function designed to determine whether discharging the battery during a specific hour is effective. It evaluates the current State of Charge (SOC) and uses a sorted dataset to identify the most valuable hours for discharge based on the largest values in the dataset (e.g., energy prices, demand, etc.).

This sorting ensures that the battery is discharged during hours with the highest strategic value, maximizing profitability or aligning with operational goals.

The dataSet is sorted in descending order to prioritize hours with the largest values (e.g., highest prices or production levels). This ensures that the most beneficial hours are targeted for discharge.

Evaluates if the battery has enough SOC to sustain discharge for the current hour and subsequent hours.

Compare Current Hour with Priority Hours Determines whether the current hour falls within the top n hours, where n is the maximum number of hours the battery can sustain discharge (SOC / dischargeAmountEachHour).

isEffectiveDischargeHour(40, dataset, 10, hour);

filterEffectiveChargeHours

filterEffectiveChargeHours(currentSOC, maxSOC, dataset[], chargeAmountEachHour)

Returns most valuable hour values (eg. lowest prices) from dataset for charging the battery.

filterEffectiveChargeHours(40, 100, dataset, 10);

isEffectiveChargeHour

isEffectiveChargeHour(currentSOC, maxSOC, dataset[], chargeAmountEachHour, hour)

isEffectiveChargeHour is a utility function designed to determine whether charging the battery during a specific hour is effective. It evaluates the current State of Charge (SOC), maximum allowed SOC, and uses a sorted dataset to identify the most valuable hours for charging based on the smallest values in the dataset (e.g., lowest energy prices).

The dataSet is sorted in ascending order to prioritize hours with the smallest values (e.g., lowest prices). This ensures that the most cost-effective hours are targeted for charging.

Evaluates if the battery has enough capacity (maxSOC - currentSOC) to sustain charging for the current hour and subsequent hours.

Compare Current Hour with Priority Hours Determines whether the current hour falls within the bottom n hours, where n is the maximum number of hours the battery can sustain charging ((maxSOC - currentSOC) / chargeAmountEachHour).

isEffectiveChargeHour(40, 90, dataset, 10, hour);

filterEffectiveDischargeHours

filterEffectiveDischargeHours(currentSOC, maxSOC, dataset[], dischargeAmountEachHour)

Returns most valuable hour values (eg. highest prices or production levels) from dataset for discharging the battery.

filterEffectiveDischargeHours(40, 100, dataset, 10);

Battery Optimization Functions for 15-Minute Intervals

The following functions provide battery optimization at 15-minute granularity, enabling more precise control for systems using 15-minute MTU (Meter Time Unit) intervals. These functions work similarly to their hourly counterparts but operate on quarter-hour intervals.

filterEffectiveDischarge15Min

filterEffectiveDischarge15Min(startSOC, dataset[], dischargeAmountEach15Min, returnValuesOnly?)

Filters dataset to find optimal 15-minute intervals for battery discharge based on highest strategic values (e.g., highest prices or demand). The dataset is sorted in descending order to identify the most valuable discharge intervals.

Parameters:

  • startSOC (number): Starting State of Charge
  • dataset (array): Array of timestamped data with strategic values
  • dischargeAmountEach15Min (number): Amount discharged per 15-minute interval
  • returnValuesOnly (boolean, optional): Return only values vs full objects (default: true)

Returns: Array of optimal discharge intervals with highest strategic value

// Find top discharge intervals based on 50 kWh starting SOC, 5 kWh per 15 min
filterEffectiveDischarge15Min(50, priceDataset, 5);
// Returns: [prices from optimal 15-min discharge intervals]

isEffectiveDischarge15Min

isEffectiveDischarge15Min(startSOC, dataset[], dischargeAmountEach15Min, timestamp)

Checks if a specific timestamp falls within an optimal discharge 15-minute interval. This function evaluates whether discharging during the given timestamp aligns with the highest-value intervals identified by the optimization algorithm.

Parameters:

  • startSOC (number): Starting State of Charge
  • dataset (array): Array of timestamped data with strategic values
  • dischargeAmountEach15Min (number): Amount discharged per 15-minute interval
  • timestamp (string): ISO timestamp to check

Returns: Boolean - true if timestamp is in optimal discharge interval

// Check if 10:07 is an optimal discharge time
isEffectiveDischarge15Min(50, priceDataset, 5, '2025-01-15T10:07:00');
// Returns: true/false

filterEffectiveCharge15Min

filterEffectiveCharge15Min(startSOC, maxSOC, dataset[], chargeAmountEach15Min, returnValuesOnly?)

Filters dataset to find optimal 15-minute intervals for battery charging based on lowest strategic values (e.g., cheapest electricity prices). The dataset is sorted in ascending order to identify the most cost-effective charging intervals.

Parameters:

  • startSOC (number): Starting State of Charge
  • maxSOC (number): Maximum State of Charge
  • dataset (array): Array of timestamped data with strategic values
  • chargeAmountEach15Min (number): Amount charged per 15-minute interval
  • returnValuesOnly (boolean, optional): Return only values vs full objects (default: true)

Returns: Array of optimal charge intervals with lowest strategic value

// Find cheapest charge intervals: 30 kWh current, 80 kWh max, 5 kWh per 15 min
filterEffectiveCharge15Min(30, 80, priceDataset, 5);
// Returns: [prices from cheapest 15-min intervals to reach 80 kWh]

isEffectiveCharge15Min

isEffectiveCharge15Min(startSOC, maxSOC, dataset[], chargeAmountEach15Min, timestamp)

Checks if a specific timestamp falls within an optimal charging 15-minute interval. This function evaluates whether charging during the given timestamp aligns with the lowest-value intervals identified by the optimization algorithm.

Parameters:

  • startSOC (number): Starting State of Charge
  • maxSOC (number): Maximum State of Charge
  • dataset (array): Array of timestamped data with strategic values
  • chargeAmountEach15Min (number): Amount charged per 15-minute interval
  • timestamp (string): ISO timestamp to check

Returns: Boolean - true if timestamp is in optimal charge interval

// Check if 14:23 is an optimal charging time
isEffectiveCharge15Min(30, 80, priceDataset, 5, '2025-01-15T14:23:00');
// Returns: true/false

Technical Implementation Details

15-Minute Rounding Algorithm:

All 15-minute functions use consistent timestamp rounding logic to align with quarter-hour boundaries:

Rounding Examples:

  • 10:07:0010:00:00
  • 10:15:0010:15:00
  • 10:23:4510:15:00
  • 10:47:1210:45:00

Key Differences from Hourly Functions:

AspectHourly Functions15-Minute Functions
Time granularity1 hour15 minutes (¼ hour)
Calculation multiplier×1×4 (4 intervals per hour)
Parameter naming...EachHour...Each15Min
Timestamp comparisonisSame(date, 'hour')Custom 15-min rounding

Operators

Unary Operators

OperationSymbol
Negate!

Binary Operators

OperationSymbol
Add, Concat+
Subtract-
Multiply*
Divide/
Divide and floor//
Modulus%
Power of^
Logical AND&&
Logical OR||

Comparison Operators

ComparisonSymbol
Equal==
Not equal!=
Greater than>
Greater than or equal>=
Less than<
Less than or equal<=
Element in array or stringin

Example: Using FEXL in Schedule Dynamic Rule

Assume you have defined two datasets:

  • energyPrices: contains day-ahead energy prices for each hour.
  • powerProduction: contains estimated power production values for each hour based on weather forecasts.

You want to create a dynamic schedule that checks if the energy price during the generation hour is positive and enough power is available (above 100W). The following FEXL expression achieves this:

has(energyPrices, hour) && has(powerProduction, hour) &&
 find(energyPrices, hour) > 0 &&
 find(powerProduction, hour) > 100

This expression validates each hour in the schedule, allowing an entry to be created when both conditions are satisfied: the energy price is not negative, and power production exceeds 100 units.