Ranking information in an entity's array

This is probably easier than I’m making it in my head…

I have an entity with an attribute that is an array of numbers (hourly energy prices with the Nordpool integration). I can figure out how to store each hour into a separate entity using a for loop.
But what I want to do is rank them. So let’s say 6am is the cheapest rate, I want [0] to be “6”. Any tips on how to go about this? I would end up with 24 entities per day, from cheapest electricity to most expensive.

Then what I want to achieve is that I can tell an automation to run only if “rate < 5” for example, or not to run if rate > X.

This is a step more than simply telling an automation to run during the cheapest hour, because that’s quite simple, but won’t achieve my goals.

Anyone done this before or have any tips how to procees?

Thanks!

Show us the data you’re working with: is it genuinely an array of numbers, or is it a dictionary of times and numbers?

Here’s the full entity. The part I’m interested in is either the list in today: or in raw:today. I can work with both.

state_class: measurement
average: 0.16746666666666665
off_peak_1: 0.1242375
off_peak_2: 0.194975
peak: 0.18711666666666665
min: 0.0997
max: 0.2352
mean: 0.1738
unit: kWh
currency: EUR
country: Netherlands
region: NL
low_price: true
price_percent_to_average: 0.9482484076433122
today:
  - 0.1332
  - 0.1246
  - 0.1168
  - 0.109
  - 0.0997
  - 0.1077
  - 0.1249
  - 0.178
  - 0.2097
  - 0.2016
  - 0.1693
  - 0.1367
  - 0.1573
  - 0.1588
  - 0.1735
  - 0.1815
  - 0.1814
  - 0.2226
  - 0.2352
  - 0.2178
  - 0.2017
  - 0.219
  - 0.1741
  - 0.1851
tomorrow: []
tomorrow_valid: false
raw_today:
  - start: '2023-02-03T00:00:00+01:00'
    end: '2023-02-03T01:00:00+01:00'
    value: 0.1332
  - start: '2023-02-03T01:00:00+01:00'
    end: '2023-02-03T02:00:00+01:00'
    value: 0.1246
  - start: '2023-02-03T02:00:00+01:00'
    end: '2023-02-03T03:00:00+01:00'
    value: 0.1168
  - start: '2023-02-03T03:00:00+01:00'
    end: '2023-02-03T04:00:00+01:00'
    value: 0.109
  - start: '2023-02-03T04:00:00+01:00'
    end: '2023-02-03T05:00:00+01:00'
    value: 0.0997
  - start: '2023-02-03T05:00:00+01:00'
    end: '2023-02-03T06:00:00+01:00'
    value: 0.1077
  - start: '2023-02-03T06:00:00+01:00'
    end: '2023-02-03T07:00:00+01:00'
    value: 0.1249
  - start: '2023-02-03T07:00:00+01:00'
    end: '2023-02-03T08:00:00+01:00'
    value: 0.178
  - start: '2023-02-03T08:00:00+01:00'
    end: '2023-02-03T09:00:00+01:00'
    value: 0.2097
  - start: '2023-02-03T09:00:00+01:00'
    end: '2023-02-03T10:00:00+01:00'
    value: 0.2016
  - start: '2023-02-03T10:00:00+01:00'
    end: '2023-02-03T11:00:00+01:00'
    value: 0.1693
  - start: '2023-02-03T11:00:00+01:00'
    end: '2023-02-03T12:00:00+01:00'
    value: 0.1367
  - start: '2023-02-03T12:00:00+01:00'
    end: '2023-02-03T13:00:00+01:00'
    value: 0.1573
  - start: '2023-02-03T13:00:00+01:00'
    end: '2023-02-03T14:00:00+01:00'
    value: 0.1588
  - start: '2023-02-03T14:00:00+01:00'
    end: '2023-02-03T15:00:00+01:00'
    value: 0.1735
  - start: '2023-02-03T15:00:00+01:00'
    end: '2023-02-03T16:00:00+01:00'
    value: 0.1815
  - start: '2023-02-03T16:00:00+01:00'
    end: '2023-02-03T17:00:00+01:00'
    value: 0.1814
  - start: '2023-02-03T17:00:00+01:00'
    end: '2023-02-03T18:00:00+01:00'
    value: 0.2226
  - start: '2023-02-03T18:00:00+01:00'
    end: '2023-02-03T19:00:00+01:00'
    value: 0.2352
  - start: '2023-02-03T19:00:00+01:00'
    end: '2023-02-03T20:00:00+01:00'
    value: 0.2178
  - start: '2023-02-03T20:00:00+01:00'
    end: '2023-02-03T21:00:00+01:00'
    value: 0.2017
  - start: '2023-02-03T21:00:00+01:00'
    end: '2023-02-03T22:00:00+01:00'
    value: 0.219
  - start: '2023-02-03T22:00:00+01:00'
    end: '2023-02-03T23:00:00+01:00'
    value: 0.1741
  - start: '2023-02-03T23:00:00+01:00'
    end: '2023-02-04T00:00:00+01:00'
    value: 0.1851
raw_tomorrow: []
current_price: 0.1588
additional_costs_current_hour: 0
unit_of_measurement: EUR/kWh
device_class: monetary
icon: mdi:flash
friendly_name: nordpool_kwh_nl_eur_4_10_021

That was a bit harder than expected. Drop this in the template editor and have a play. First line is a guess at what you need to replace the hardcoded second line I was playing with.

{% set prices = state_attr('sensor.nordpool_kwh_nl_eur_4_10_021','today') %}
{% set prices = [0.1332,0.1246,0.1168,0.109,0.0997,0.1077,0.1249,0.178,0.2097,0.2016,0.1693,0.1367,0.1573,0.1588,0.1735,0.1815,0.1814,0.2226,0.2352,0.2178,0.2017,0.219,0.1741,0.1851] %}
{% set ns = namespace(str="{") %}
{% for price in prices %}
  {% set ns.str = ns.str ~ "\"" ~
                  price+[0,(loop.index0/1000000)][(prices.count(price) > 1)] ~ "\":" ~
                  loop.index0 ~ "," %}
{% endfor %}
{% set ns.str = ns.str[:-1] ~ "}" %}
{{ ns.str|from_json|dictsort|map(attribute=1)|list }}

There are a couple of simpler options, but they fall over if two time slots are the same price. I’ve worked around this here by adding a different tiny amount to any identical prices before doing the ranking, so earlier identical price slots are favoured. This assumes that the adjusted amounts are not going to form a new identical pair!

Sadly, our options for constructing dictionaries are limited in Jinja2, so I have faked it up by building a string of {price: hour} pairs and converting it to a JSON object before sorting by price and pulling out the hours.

1 Like

Ooh thanks, that’s a big step forward. It’s close but not really accurate yet, the sorting is a bit off.

But maybe I’m also not going the right route to what I want to achieve (often overthinking it).

I basically want a way when creating an automation to add a condition as an energy threshold. For example:

  • Trigger
  • Condition: If current price is < 6 on the price scale (or is NOT > 12 on the scale).
  • Action

Current price is easy (it’s the state), but how to check it in the list is the part I’m trying to solve.

I also saw that jinja has a sort[] option but can’t really figure it out.

In what way is the sorting a bit off?

1 Like

Oh damn… :man_facepalming: I was reading the list the wrong way round. Thanks! Ok Let me work out the conditional logic from this and see how far I get.

1 Like

I came here from the other thread, but basically you want to know if you’re inside a threshold that hour?

If yes:

This builds a list of items sorted on price, lowest to highest.

{% set prices = state_attr('sensor.nordpool_kwh_nl_eur_4_10_021','today') %}
{% set ns = namespace(items=[]) %}
{% for price in prices %}
  {% set ns.items = ns.items + [{'hour': loop.index0, 'price': price}] %}
{% endfor %}
{{ ns.items | sort(attribute='price') }}

If you want a threshold of 6

{% set threshold = 6 %}
{% set prices = state_attr('sensor.nordpool_kwh_nl_eur_4_10_021','today') %}
{% set ns = namespace(items=[]) %}
{% for price in prices %}
  {% set ns.items = ns.items + [{'hour': loop.index0, 'price': price}] %}
{% endfor %}
{{ (ns.items | sort(attribute='price'))[:threshold] }}

if you want to see if the current hour is in the threshold

{% set threshold = 6 %}
{% set prices = state_attr('sensor.nordpool_kwh_nl_eur_4_10_021','today') %}
{% set ns = namespace(items=[]) %}
{% for price in prices %}
  {% set ns.items = ns.items + [{'hour': loop.index0, 'price': price}] %}
{% endfor %}
{{ now().hour in (ns.items | sort(attribute='price'))[:threshold] | map(attribute='hour') | list }}
1 Like

That’s much more elegant than mine :slight_smile:.

Note that it should be loop.index0 rather than loop.index if you want the start hour for each slot.

2 Likes

Thanks, this is helping me a lot to get my head around how these templates work and how to manage these entities.

While playing around I also came up with a different method. This then doesn’t create a new list, but instead searches it every time it’s needed. Not yet sure how it would handle multiples of the same value, but that shouldn’t happen anyways, I’m using up to 5 decimals.

Useful for other contexts because I’ll still need the sorted list for telling me “14:00 - 15:00 is the cheapest hour” I think.

{% set prices = state_attr('sensor.nordpool_kwh_nl_eur_4_10_021','today')|sort() %}
{% set currentrank = prices.index(states('sensor.nordpool_kwh_nl_eur_4_10_021')|float()) %}
{% if currentrank <= 12 %}

{% endif %}

You’re removing the hour with what you have. What I provided links the hour to the value.

Yeah exactly, so I think I will probably end up using both, depending on the need. Very useful :slight_smile: thanks!

But there’s no reason to use both. You have a list of dictionaries that has both built into it.

{% set prices = state_attr('sensor.nordpool_kwh_nl_eur_4_10_021','today') %}
{% set ns = namespace(items=[]) %}
{% for price in prices %}
  {% set ns.items = ns.items + [{'hour': loop.index0, 'price': price}] %}
{% endfor %}
{{ ns.items | sort(attribute='price') }}

If you’re struggling using that, just ask questions.

I haven’t played with them both yet, will try it out first before I ask so at least if I need to my question will be clear :slight_smile: Now that I’ve figured out this part, I need to figure out the logic part for the automation first…