Calculation Models

Prev Next

Overview

Calculation Models in Bazefield are used to perform complex interval calculations within the Bazefield Calculation Service. They allow users to define reusable logic using templates and functions, enabling scalable and maintainable KPI generation across assets.


How Calculation Models Work

  • Common Functions: The same C# expression syntax of standard Interval Calculations is available within Calculation Models

  • Script-Based Logic: Built using a Handlebars-based script template and one or more function lists.

  • Interval Execution: Operate like Interval Calculated Points, running at scheduled intervals.

  • Reusable Components: Functions can be shared across models and dynamically compiled per asset.


Key Components

ℹ️ Script Template

Defines the variables used in the model using placeholders like:

string objectKey = "{{domainObject.objectKey}}";

These are resolved at compile time based on asset metadata.

ℹ️ Function Lists

Reusable code blocks that can be imported into the script. For example:

if (TagCount([@WindSpeed]) > 0)
    windSpeedValue = TagTimeAvg([@WindSpeed]);

ℹ️ Metadata Access

Models can reference any asset and site attributes using Handlebars syntax like:

  • {{domainObject.ratedPower}} (retrieve the ratedPower of the current object context)

  • {{parent.objectKey}} (retrieve the key of the current object’s parent object)


Configuration Workflow

  1. Create Model: Define Acronym, Name, Model Type, applicable Object Type, and Script Template.

  2. Add Lookup Functions: Import reusable logic blocks defined outside of the Calculation Model.

  3. Reference in Points: Use ScriptType = Interval_Calculation and Expression = $$CalculationModel$$. Set CalcModelRef to the acronym of the calculation model.

  4. Compile: Load the Point(s) into the Calculation Service to automatically resolve placeholders and assemble the final script.


Example Use Case

Turbine Theoretical Power Curve Lookup Calculation Model

  • Uses Nacelle wind speed reading to estimate expected energy based on user-uploaded reference power curve.

  • If Turbine Nacelle sensor reading is unavailable, falls back to alternative wind speed readings: neighbor turbine(s), Met Mast, or daily/weekly/annual average.

  • Compiled per asset using metadata like object keys, reference turbines, and site Annual Average Wind Speed (AAWS).

Script Template

Defines the templatized logic for the Calculation Model

//Theoretical Power based on best available WindSpeed and PC Lookup

string objectKey = "{{domainObject.objectKey}}";
string parentObjectKey = "{{parent.objectKey}}";

double? theoreticalPower = null;
double? windSpeedValue = null; 
double? annualAverageWindSpeed =null; 
string PMMWindSpeed = "";

{{#if parent.AAWS}}
annualAverageWindSpeed = Double.Parse(ObjectMetadata.GetAttribute(parentObjectKey, "AAWS"));
{{/if}}

{{#if domainObject.referencePMM}}
string PMMObjectKey = ObjectMetadata.GetAttribute(objectKey, "referencePMM");
PMMWindSpeed = PMMObjectKey + "-WindSpeed";
{{/if}}

{{#each importedFunctions.lookupFunctions.Functions}}
if (windSpeedValue == null)
{
   {{this.codeBlock}}
}
{{/each}}

if (windSpeedValue != null)
{
   var refCurve = RefCurves.GetRefCurve(objectKey, "Contractual", "Default");
   if (refCurve == null) refCurve = RefCurves.GetRefCurve(objectKey, null, "EnergyModel");
   if (refCurve == null) refCurve = RefCurves.GetRefCurve(parentObjectKey, null, "EnergyModel");
   theoreticalPower = RefCurves.LinearLookup(refCurve, windSpeedValue.Value);
}

return theoreticalPower;

Lookup Functions

Imported functions that will be used in the compiled Calculation Model

During runtime, the Lookup Functions will be evaluated in the order they are defined on the Calculation Model. They are generally used to “lookup” a value for a script variable in order of preference—such as for WindSpeed when you have multiple sensors like nacelle anemometer, neighboring turbine(s), and primary met mast.

AssetWindSpeed

Option 1: Retrieve the object’s WindSpeed reading. (Most desirable.)

if (TagCount([@WindSpeed]) > 0) windSpeedValue = TagTimeAvg([@WindSpeed]);

NeighborsAvgWindSpeed

Option 2: Retrieve average WindSpeed reading from neighboring turbines.

var refObjectIds = JsvStringArrayToArray(ObjectMetadata.GetAttribute(objectKey, "referenceTurbines")); 
int count = 1; 
foreach(string refObjectId in refObjectIds) {
   var refObjectKey = ObjectMetadata.GetAttribute(refObjectId, "objectKey"); 
   if (TagCount(refObjectKey + "-WindSpeed") > 0) { 
      if (windSpeedValue != null) { 
         count = count+1; 
         windSpeedValue = windSpeedValue + TagTimeAvg(refObjectKey + "-WindSpeed"); 
      } 
      if (windSpeedValue == null) windSpeedValue = TagTimeAvg(refObjectKey + "-WindSpeed"); 
   } 
} 
if (windSpeedValue != null) windSpeedValue = windSpeedValue/count;

PMMWindSpeed

Option 3: Retrieve the Primary Met Mast (PMM) WindSpeed reading.

if(PMMWindSpeed.Length > 0) if(TagCount(PMMWindSpeed) > 0) windSpeedValue = TagTimeAvg(PMMWindSpeed);

AssetWindSpeed1d

Option 4: Calculate the Turbine’s 1 day average WindSpeed value.

if(TagAggregate(objectKey + "-WindSpeed", "COUNT", IntervalEnd().AddHours(-24), IntervalEnd()) > 0) windSpeedValue = TagAggregate(objectKey + "-WindSpeed", "AVERAGE", IntervalEnd().AddHours(-24), IntervalEnd());

AssetWindSpeed7d

Option 5: Calculate the Turbine’s 7 day average WindSpeed value.

if(TagAggregate(objectKey + "-WindSpeed", "COUNT", IntervalEnd().AddHours(-168), IntervalEnd()) > 0) windSpeedValue = TagAggregate(objectKey + "-WindSpeed", "AVERAGE", IntervalEnd().AddHours(-168), IntervalEnd());

AssetWindSpeedAAWS

Option 6: Retrieve the site Average Annual WindSpeed attribute. (Least desirable.)

windSpeedValue = annualAverageWindSpeed;

Compiled Calculation Model

All Handlebars expressions and helpers are resolved. This means that the objectKey for the asset and site is resolved and that all the lookupFunctions are inserted.

//Theoretical Power based on best available WindSpeed and PC Lookup

string objectKey = "WP1-T001";
string parentObjectKey = "WP1";

double? theoreticalPower = null;
double? windSpeedValue = null; 
double? annualAverageWindSpeed =null; 
string PMMWindSpeed = "";

annualAverageWindSpeed = Double.Parse(ObjectMetadata.GetAttribute(parentObjectKey, "AAWS"));

string PMMObjectKey = ObjectMetadata.GetAttribute(objectKey, "referencePMM");
PMMWindSpeed = PMMObjectKey + "-WindSpeed";

if (windSpeedValue == null)
{
   if (TagCount([@WindSpeed]) > 0) windSpeedValue = TagTimeAvg([@WindSpeed]);
}

if (windSpeedValue == null)
{
   if (TagCount([@WindSpeed]) > 0) windSpeedValue = TagTimeAvg([@WindSpeed]);
   var refObjectIds = JsvStringArrayToArray(ObjectMetadata.GetAttribute(objectKey, "referenceTurbines")); 
   int count = 1; 
   foreach(string refObjectId in refObjectIds) { 
      var refObjectKey = ObjectMetadata.GetAttribute(refObjectId, "objectKey"); 
      if (TagCount(refObjectKey + "-WindSpeed") > 0) { 
         if (windSpeedValue != null) { 
            count = count+1; 
            windSpeedValue = windSpeedValue + TagTimeAvg(refObjectKey + "-WindSpeed"); 
         } 
         if (windSpeedValue == null) windSpeedValue = TagTimeAvg(refObjectKey + "-WindSpeed"); 
      } 
   } 
   if (windSpeedValue != null) windSpeedValue = windSpeedValue/count;
}

if (windSpeedValue == null)
{
   if(PMMWindSpeed.Length > 0) if(TagCount(PMMWindSpeed) > 0) windSpeedValue = TagTimeAvg(PMMWindSpeed);
}

if (windSpeedValue == null)
{
   if(TagAggregate(objectKey + "-WindSpeed", "COUNT", IntervalEnd().AddHours(-24), IntervalEnd()) > 0) windSpeedValue = TagAggregate(objectKey + "-WindSpeed", "AVERAGE", IntervalEnd().AddHours(-24), IntervalEnd());
}

if (windSpeedValue == null)
{
   if(TagAggregate(objectKey + "-WindSpeed", "COUNT", IntervalEnd().AddHours(-168), IntervalEnd()) > 0) windSpeedValue = TagAggregate(objectKey + "-WindSpeed", "AVERAGE", IntervalEnd().AddHours(-168), IntervalEnd());
}

if (windSpeedValue == null)
{
   windSpeedValue = annualAverageWindSpeed;
}

if (windSpeedValue != null)
{
   var refCurve = RefCurves.GetRefCurve(objectKey, "Contractual", "Default");
   if (refCurve == null) refCurve = RefCurves.GetRefCurve(objectKey, null, "EnergyModel");
   if (refCurve == null) refCurve = RefCurves.GetRefCurve(parentObjectKey, null, "EnergyModel");
   theoreticalPower = RefCurves.LinearLookup(refCurve, windSpeedValue.Value);
}

return theoreticalPower;

Why Calculation Models Matter

✔️ Advanced Logic: Go beyond one-line expressions with full scripting.

✔️ Reusability: Centralize and reuse logic across assets and projects.

✔️ Scalability: Dynamically compile per asset using metadata.

✔️ Maintainability: Update logic in one place and apply it fleet-wide.