Ecosuite Docs
  • Ecosuite User Guide
  • Modules
    • Events
      • Creating a SolarNetwork reset event
      • Creating a Service Request
    • Energy
      • Download Datums
      • Asset Hierarchy
    • Economics
      • Economics Module FAQ
    • Data
      • Project
        • New Project Guide
        • Operating Reports
        • Status
          • Project and System Status and Project Flag filtering
        • Expected Energy Calculation Setup
        • Importing from Coda
      • Pro Forma
        • Importing Financial Models
      • Audit
      • Raw Data
    • Forge
  • Admin
    • User Management 101
    • Settings
      • Tariffs
  • Ecosuite Terminology
    • Forecasting
  • Ecosuite AI
    • Accessing raw data (e.g. for ML training)
  • Power Dashboard
  • 🏒Organizations
    • Google Integration
      • Getting Started
        • Self Hosted
        • Ecosuite Infrastructure
Powered by GitBook
On this page
  • About
  • Getting Started
  1. Modules
  2. Data
  3. Project

Expected Energy Calculation Setup

How to use SolarNetwork expected energy calculations for more fine-tuned results

PreviousProject and System Status and Project Flag filteringNextImporting from Coda

Last updated 1 month ago

This is a feature currently in preview.

This is advanced! Please make you sure are familiar with before starting this.

About

When we calculate the expected energy for a given datum we source from SolarNetwork, it runs through an algorithm for each solar array you have configured. This is usually good enough, but sometimes it's best to have some more fine-tuned access.

In Ecosuite, we have a setting you can use for this workflow: the system Calculate Expected in SolarNetwork field.

Getting Started

What we want to do is be be able to calculate the expected energy generation before it reaches Ecosuite. This way, you have a lot more control about how each aspect of your SolarNetwork system comes together.

Preparing SolarNetwork

This documentation uses the path /FOO/BAR/BAZ/** for paths inside SolarNetwork.

If you're following along, make sure to change this to whatever your own path is!

Initializing a Datum Filter Chain

Make sure that you have a datum filter chain setup. If you don't already have one, create one now. You can name it whatever you want.

Our datum filter chain will end up looking something like this:

  1. Irradiance POA

  2. Expected Generation

The service names can be whatever you want, but make sure that they are consistent as we setup our datum filters.

Populating Irradiance Property

First, we want to create an expression datum filter component in SolarNetwork which will populate the expected energy generation. Make sure it's attached to the right PYR device that your data will be coming from.

Set the Property to irradiancePOA and the Property Type to Instantaneous.

latest('/FOO/BAR/BAZ/PYR/1')?.irradiance != null ? latest('/FOO/BAR/BAZ/PYR/1')?.irradiance : null

This basic expression simply takes whatever is currently at irradiance and copies it to irradiancePOA. This is good practice for the future if you want to add GHI transposition on top of whatever else you're doing.

For example, you could in the future change this datum filter to instead do something like this:

has('irradiance') ? httpGet('http://example.solarnetwork:8000/ghi-to-poa', union(nodeMetadata('/pm/pv-characteristics-poa'), {
	date: local(timestamp.atZone(nodeMetadata('/pm/pv-characteristics-poa/zone'))),
	irradiance: irradiance
}), {
	'Authorization': httpBasic('foo', 'bar')
})?.data?.poa_global : null

What we're doing here is instead using a cloud microservice to calculate GHI transposition efficiently without needing to do any processing later on. Now, anything that needs POA irradiance from a GHI setup will get it directly from SolarNetwork.

If you're sure you won't need this flexibility, you can always add this datum filter later.

Adding System Characteristics

The metadata pm property as described in the SolarNetwork API is arbitrary, meaning you can pretty much put whatever you want in there. As described above, however, there are some standard paths used by datum filters you should be aware of.

A Basic Algorithm

Next, let's consider a basic algorithm we could use to arrive at an expected generation.

  1. Check Input Validity:

    • Verify that current irradiance (sunlight) data and panel area are available

    • If either is missing, return null (no calculation possible)

  2. Calculate Base Power Output:

    • Multiply the current irradiance (sunlight hitting panels) by:

      • Total panel area

      • Panel efficiency rating

      • Performance ratio (accounts for various system losses)

      • Panel array tilt factor (optimizes for installation angle)

  3. Apply Age Degradation:

    • Calculate how many years the system has been operating since commission date

    • Apply yearly degradation rate using the formula: (1 - degradation rate)^years in operation

    • Multiply the base power by this degradation factor

  4. Apply Power Clipping:

    • Take the minimum value between:

      • The calculated power from steps 2-3

      • The nameplate AC maximum power of the system

  5. Return Final Value:

    • Round down the result to ensure conservative estimates

    • Return this as the expected energy generation

E=min(⌊IPOA​×Ap​×ηp​×Rperf​×Ftilt​×min[(1βˆ’d)y,1]βŒ‹,Pmax​)E=min(⌊IPOA​×Ap​×ηp​×Rperf​×Ftilt​×min[(1βˆ’d)y,1]βŒ‹,Pmax​)E=min(⌊IPOA​×Ap​×ηp​×Rperf​×Ftilt​×min[(1βˆ’d)y,1]βŒ‹,Pmax​)

Where:

  • EE E = Expected energy generation (power output)

  • IPOAIPOAIPOA = Irradiance on Plane of Array (sunlight hitting panels)

  • ApApAp = Panel area

  • Ξ·p​ηp​ηp​ = Panel efficiency

  • RperfRperfRperf= Performance ratio (system losses factor, see more above for more information)

  • FtiltFtiltFtilt = Panel array tilt factor

  • ddd = Degradation rate (annual, see above for more information)

  • yyy = Years between commission date and current date

  • PmaxPmaxPmax = Nameplate AC maximum power capacity

  • βŒŠβŒ‹βŒŠβŒ‹βŒŠβŒ‹ = Floor function (round down)

  • minminmin = Minimum function (select smaller value)

We'll assume this algorithm going forward, but you can easily change it if you think you have a better one!

Populating Expected Generation Property

Next, we want to take our new field that's populated and add it to our GEN source. Create a new expression datum filter component for the generation source ID.

Set the Property to expectedWattHours and the Property Type to Instantaneous. Note that while most steps here are open to user modification, this one is required!

With POAI Calculator Metadata

latest('/FOO/BAR/BAZ/PYR/1')?.irradiancePOA != null && getInfoNumber('pv-characteristics', 'panelArea') != null
? min(roundDown(latest('/FOO/BAR/BAZ/PYR/1')?.irradiancePOA
	* getInfoNumber('pv-characteristics', 'panelArea')
	* getInfoNumber('pv-characteristics', 'panelEfficiency')
	* getInfoNumber('pv-characteristics', 'performanceRatio')
	* getInfoNumber('pv-characteristics', 'panelArrayTiltFactor')
	* min(pow(
		1 - getInfoNumber('pv-characteristics', 'degradationRate'),
		yearsBetween(
			date(getInfoString('pv-characteristics', 'pvArrayCommissionDate')),
			today(getInfoString('pv-characteristics', 'zone'))
	    )
	  ), 1)
	, 0), getInfoNumber('pv-characteristics', 'nameplateAcMaxPower'))
: null
  • If you aren't using the irradiancePOA field described earlier, substitue any instances for irradiance.

Without POAI Calculator Metadata

We'll use some sensible system values for each variable in here. Yours will probably be different!

latest('/FOO/BAR/BAZ/PYR/1')?.irradiancePOA != null && getInfoNumber('pv-characteristics', 'panelArea') != null
? min(roundDown(latest('/FOO/BAR/BAZ/PYR/1')?.irradiancePOA
	* 
	* 0.192
	* 0.192
	* 1
	* min(pow(
		1 - 0.0063,
		yearsBetween(
			date("2021-12-22"),
			today("America/New_York")
	    )
	  ), 1)
	, 0), 333300)
: null
  • If you aren't using the irradiancePOA field described earlier, substitue any instances for irradiance.

Note that as indicated earlier, not using metadata will result in magic numbers in the script which may be hard to explain to colleagues! It's highly recommended to use metadata,even if you're not using the expected POAI Calculator metadata!

Checking Your Work

After you setup these datum filters, check the latest datum for your source to make sure that the required expectedWattHours field is being populated.

{
  "success": true,
  "data": {
    "totalResults": 1,
    "startingOffset": 0,
    "returnedResultCount": 1,
    "results": [
      {
        "created": "2025-05-14 23:54:37.003Z",
        "nodeId": 483,
        "sourceId": "/FOO/BAR/BAZ/GEN/1",
        "localDate": "2025-05-14",
        "localTime": "19:54",
        "watts": 0,
        "current": 0,
        "voltage": 278,
        "current_a": 0,
        "current_b": 0,
        "current_c": 0,
        "frequency": 60,
        "voltage_a": 277.4,
        "voltage_b": 276.9,
        "voltage_c": 279.8,
        "voltage_ab": 479.8,
        "voltage_bc": 482.1,
        "voltage_ca": 482.7,
        "lineVoltage": 481.5,
        "powerFactor": 1,
        "apparentPower": 0,
        "reactivePower": 0,
        "expectedWattHours": 439,
        "wattHours": 1758767000,
        "wattHoursReverse": 1066400,
        "phase": "Total"
      }
    ]
  }
}

Woohoo! If you're still having issues, some things to check include:

  • Consistent service names between the datum filter chain and the datum filters.

  • Metadata being correctly set (if you're using them).

Tying Together in Ecosuite

Finally, all you have to do is go into your Ecosuite system and click the checkbox Calculate Expected in SolarNetwork. That's it!

The Big Picture

What we've done is leveraged SolarNetwork to add a new field to each GEN node datum called expectedWattHours. Each time Ecosuite calls the SolarNetwork API, it will see that we now have this field and use it instead of trying to calculate on the fly.

Here's a rough diagram of the process:

The killer aspect of this is that we can further use whatever we want in the future to arrive at expected energy calculation at very fine detail. Maybe in the future we want to use GHI transposition from POA data -- this is now a rather trivial process that Ecosuite doesn't even need to know about.

In Less Words, Please

  • Make the SolarNetwork GEN node return an expectedWattHours field using datum filters.

  • Notify Ecosuite to start using them by selecting Calculate Expected in SolarNetwork.

A good practice to adopt is to add for configurable variables about your system.

One already standard way that this is done that we can leverage is to use the potentially already configured metadata in our calculations.

Potential Spel expression errors. Check the for any potential issues. If you're more unit test inclined, see how for inspiration.

Here's the used in the GHI to POA Calculator Datum Filter.

SolarNode expressions
SolarNetwork Metadata
POAI Calculator
SolarNode logs
SolarNetwork tests Spel expressions
SolarNetwork implementation of GHI transposition
pvlib