Thanks again! And I somewhat understand what your are doing/your logic. With small modification it has an output however it also looks ‘backward’ eg this one produces an outcome in the past while the lowest price is actually ahead (at 6am the next day). This is probably because it only looks at today and not the day after ?
Works:
{% set ns = namespace(lowest = 9999999, time = "") %}
{% for item in state_attr('sensor.zonneplan_current_electricity_tariff', 'forcast') -%}
{% if as_datetime(item.datetime).date() == now().date() %}
{% if item.price < ns.lowest %}
{% set ns.lowest = item.price %}
{% set ns.time = item.datetime %}
{% endif %}
{% endif %}
{%- endfor %}
{{ ns.lowest/100000 ~ " at " ~ns.time }}
So you mean for example at 12 the price is 10, and at 13 it’s 5, the sum is 15.
Now is this sum of two hours the lowest of all consecutive hours of the day.
Well it’s a completely different animal for sure.
My simple thought is if you want two hours then we would need two arrays.
One that holds for instance the sum of prices at 11 and 12 o’clock then 13 and 14, and one that holds 12 and 13 then 14 and 15.
This makes the two arrays offset of each other.
Now if we get the lowest price from each of these arrays and compare them then we should get the two lowest consecutive hours.
I’m currently making dinner so not available at the time, perhaps not for the weekend.
But I’ll think about if there is a simpler solution.
Regarding the previous task, I would go for the method Trion posted.
Much more involved, yes. I used to do this with the Octopus Agile electricity tariff, with its half- hourly pricing. I used AppDaemon for this. See the discussion starting here:
As you read down, you’ll get to a Python script I wrote to do this, initially without using AppDaemon. Eventually, I moved it across to AD for various reasons — I had to install it for a different project anyway.
I’m already thankful for what you and @Troon did up till now and make sure you enjoy your diner!
To illustrate see below graph. In some cases you have tasks (eg running the washing machine) that take (for example) 2 hours. In that case you want to begin at a time you mimimize cost over those 2 hours and in those case starting at the lowest point is not always the best moment. The shorter the task, the less of problem it is but with very long tasks (eg charging your EV or heating your hottub) you want to be able to choose the right window with the right starting time.
If this day is common then it seems the three cheapest hours are consecutive from one hour before to one hour after the cheapest price.
Which seems to make sense in my opinion.
{% set fclist = state_attr('sensor.zonneplan_current_electricity_tariff','forcast') %}
{% set plist = fclist|map(attribute='price')|list %}
{% set ns = namespace(fc2=[]) %}
{%- for fc in fclist[:-1] -%}
{%- set ns.fc2 = ns.fc2 + [{'datetime':fc['datetime'],'price':(plist[loop.index0] + plist[loop.index0+1])/2}] -%}
{%- endfor -%}
{% set pmin = ns.fc2|map(attribute='price')|list|min %}
{{ (ns.fc2|selectattr('price','eq',pmin)|first)['datetime'] }}
It reads through the forecast up to the penultimate item (that’s what fclist[:-1] means) and builds a list of dictionaries with the datetime copied from the original sensor, and the price calculated as the mean of that slot and the next one.
We then do the same process of finding the minimum price in the new list and find the matching datetime.
You can expand this to any size of time period — just add subsequent items and adjust the total divisor in the mean calculation, and adjust the end point of the list so that your “looking forward” doesn’t go past the end.
OK — this is nothing to do with timezones, but is the problem of you blindly copying the code we suggest without understanding what it does. I’d missed the fact that your sensor also includes past data — my code finds the datetime of the cheapest price in the data regardless of when it is.
Try this for the single-slot forecast, which starts out by restricting the data to future entries:
{% set dtnow = now().isoformat()[0:26]~"Z" %}
{% set fclist = state_attr('sensor.zonneplan_current_electricity_tariff','forcast')
|selectattr('datetime','>=',dtnow)|list %}
{% set pmin = fclist|map(attribute='price')|list|min %}
{{ (fclist|selectattr('price','eq',pmin)|first)['datetime'] }}
The first line builds a datetime string that matches the format in your data; the second part of the second line uses that as a “filter” on the sensor data. Strongly recommend you play around in Developer Tools / Templates to see what’s going on.
You can work out how to translate that into the hourly calculator I posted above.
Thanks again. Will study this (and the one above). And you right, I only understand max 70% of your template but quite a steep learning curve as these are quite advanced compared to the ones I’m able to make.
@Troon I have been playing around with your template and starting to understand the logic (that is at least something) .
Few questions:
I understand what the [Z] does at the end of the datetime but not sure about the [0:26] when testing. Could also not find it by Googling. Could you explain?
Checking: To increase the consecutive hours at lowest price you mean adding another loop.index0-1 (for instance) the 5th line and then for each addition making sure I divide by the correct total divisor (to get the avg of those values & compare them with others). I’ve played around with it and this seems the logic but not 100% sure. Is my assumption Correct?
Half way the day, new data is added (another 24hours) to the attributes . This can create a situation where the lowest price is more than a day away (after this addition). This is still valuable info, but is it also possible to limit the list it creates and/or looks in to find the lowest price? With the objective to find -for example- the lowest price after ‘now’ but not further away then 12 hours?
Your sensor contains datetimes that look like this:
datetime: '2023-01-15T07:00:00.000000Z'
so I manipulate the output of now().isoformat() to match that format: first 26 characters and stick a Z on the end. Once that’s done, it’s possible to compare correctly-ordered date/time strings “alphabetically” as a later time string will always evaluate as “greater” than an earlier time string. See the first general principle of ISO8601.
Nearly, but not loop.index0-1 as that would be looking backwards. The idea is to look forward n slots (hours) and take the average. My example above was for two hours; for three, you’d do:
{% set fclist = state_attr('sensor.zonneplan_current_electricity_tariff','forcast') %}
{% set plist = fclist|map(attribute='price')|list %}
{% set ns = namespace(fc3=[]) %}
{%- for fc in fclist[:-2] -%}
{%- set ns.fc3 = ns.fc3 + [{'datetime':fc['datetime'],
'price':(plist[loop.index0] +
plist[loop.index0+1] +
plist[loop.index0+2]) / 3}] -%}
{%- endfor -%}
{% set pmin = ns.fc3|map(attribute='price')|list|min %}
{{ (ns.fc3|selectattr('price','eq',pmin)|first)['datetime'] }}
The set ns.fc3 line now gets the mean of the following three hours starting at each time. The previous (fourth) line fclist[:-2] restricts the starting point of the search to only go up to the third-last item of the list, because you can’t look two steps forward from later items.
Yes. Have a play in the template editor: see if you can filter fclist to exclude too-far-out elements from the start of the template. Here’s how to generate a correctly-formatted timestamp that is 12 hours away:
{% set dtend = (now()+timedelta(hours=12)).isoformat()[0:26]~"Z" %}
Just add a |selectattr('datetime','<=',dtend) onto your fclist definition and you should be good to go.
Ah! That was the part I did not get at first. I was assuming your approach, but was reaching the end of the list so that was I was going down. I just needed to adjust the starting point to compensate which makes total sense.
Your suggestions on limiting the fclist search also works!
On the datatime: I played around with that (the ones below tested in the in the template test part of HA) and I get different outputs (obviously), but I’m a bit surprised that the total template does not create another output if I modify the date part. All works independent of isoformat so I should not be concerned but was just trying to understand why that is.
{{ now().isoformat() }} gives 2023-01-18T10:09:00.101813+01:00
{{ now().isoformat()[0:26] }} gives 2023-01-18T10:09:00.102033
{{ now().isoformat()[0:26]~"Z" }} gives 2023-01-18T10:09:00.102180Z (which is the format the sensor we are looking into uses)
isoformat() (docs: I hadn’t realised this wasn’t directly in the HA docs but only referred to under “other datetime functions”) converts that datetime object to a string in a consistent format that we can pull about to match what we need.
The way I’ve approached this is not the only way it could be done: there are many different ways to work with date/time comparisons.