TheFes / cheapest-energy-hours

Jinja macro to find the cheapest energy prices
GNU General Public License v3.0
85 stars 9 forks source link

No first item, sequence was empty #140

Closed dgoodlad closed 5 months ago

dgoodlad commented 5 months ago

I'm trying to use the data from the Amber Electric integration, but the macro throws an error that I can't seem to trace: UndefinedError: No first item, sequence was empty..

I'm calling it in the developer template editor like this:

{%- set sensor = 'sensor.electricity_general_forecast' -%}
{% from "cheapest_energy_hours.jinja" import cheapest_energy_hours %}

{{cheapest_energy_hours(debug=false,
  sensor=sensor, 
  attr_all='forecasts', 
  time_key='start_time', 
  value_key='per_kwh',
  hours=1.5, start='13:00', end='23:00', 
  look_ahead=true, include_tomorrow=true)}}

The forecasts attribute of the sensor has data structured like this (sourced by printing state_attr(sensor, 'forecasts')):

[
  {
    "duration": 30.0,
    "date": "2024-06-10",
    "nem_date": "2024-06-10T12:30:00+10:00",
    "per_kwh": 0.17,
    "spot_per_kwh": 0.02,
    "start_time": "2024-06-10T02:00:01+00:00",
    "end_time": "2024-06-10T02:30:00+00:00",
    "renewables": 47,
    "spike_status": "none",
    "descriptor": "very_low"
  },
  {
    "duration": 30.0,
    "date": "2024-06-10",
    "nem_date": "2024-06-10T13:00:00+10:00",
    "per_kwh": 0.16,
    "spot_per_kwh": 0.01,
    "start_time": "2024-06-10T02:30:01+00:00",
    "end_time": "2024-06-10T03:00:00+00:00",
    "renewables": 47,
    "spike_status": "none",
    "descriptor": "very_low"
  },
  {
    "duration": 30.0,
    "date": "2024-06-10",
    "nem_date": "2024-06-10T13:30:00+10:00",
    "per_kwh": 0.18,
    "spot_per_kwh": 0.02,
    "start_time": "2024-06-10T03:00:01+00:00",
    "end_time": "2024-06-10T03:30:00+00:00",
    "renewables": 48,
    "spike_status": "none",
    "descriptor": "very_low"
  }
]

I've had a quick read of the macro, and it looks like it should be able to parse that correctly. I tested it using the code from https://github.com/TheFes/cheapest-energy-hours/blob/main/cheapest_energy_hours.jinja#L287-L295

{% set time_key = 'start_time' %}
{% set value_key = 'per_kwh' %}
{% set price_factor = 1 %}
{% set all = state_attr(sensor, 'forecasts') %}
{%- set all_check = all is list and all | count > 0 and all[0] is mapping -%}
{% set data = all %}

                {%- if data[0][time_key] is not datetime -%}
                    {%- set rebuild = namespace(data=[]) -%}
                    {%- for item in data -%}
                        {%- set time = item[time_key] -%}
                        {%- set time = as_local(as_datetime(time)) if time is string else time -%}
                        {%- set rebuild.data = rebuild.data + [{time_key: time, value_key: item[value_key] * price_factor | float(1)}] -%}
                    {%- endfor -%}
                    {%- set data = rebuild.data | sort(attribute='time') -%}
                {%- endif -%}

{{data}}

and get

[{'start_time': datetime.datetime(2024, 6, 10, 12, 0, 1, tzinfo=zoneinfo.ZoneInfo(key='Australia/Sydney')), 'per_kwh': 0.17},
{'start_time': datetime.datetime(2024, 6, 10, 12, 30, 1, tzinfo=zoneinfo.ZoneInfo(key='Australia/Sydney')), 'per_kwh': 0.16},
{'start_time': datetime.datetime(2024, 6, 10, 13, 0, 1, tzinfo=zoneinfo.ZoneInfo(key='Australia/Sydney')), 'per_kwh': 0.18},
...
]

This is about the limit of my ability to debug Jinja templating, so would love some help or suggestions as to where to 👀 next.

Thanks!

dgoodlad commented 5 months ago

I think I've worked it out: the extreme_now calculation assumes that the list of prices includes the current price, which is not the case for the data from Amber.

Is it worth making this configurable?

dgoodlad commented 5 months ago

I got it working by creating a template sensor which includes both the current and forecast values:

    - name: Energy Prices
      unique_id: energy_prices_template
      state: "{{ states('sensor.home_electricity_general_price') }}"
      attributes:
        forecasts: >
          {%- set forecasts_sensor = 'sensor.home_electricity_general_forecast' -%}
          {%- set price_sensor = 'sensor.home_electricity_general_price' -%}
          {%- set current = {"start_time": state_attr(price_sensor, 'start_time'), "per_kwh": state_attr(price_sensor, 'per_kwh')} -%}

          {{ [current] + state_attr(forecasts_sensor, 'forecasts') }}

@TheFes I think the lowest-effort "fix" here would be to document that the current price must be in the sensor data. I'm going to leave this issue open while the underlying problem remains, even though I've fixed it for myself 😄

TheFes commented 5 months ago

I think the problem is that you were using extreme_now but the selected time period was not including the current time. Let met look into this a bit further.

TheFes commented 5 months ago

No, that doesn't seem to be the problem. Wait, now I understand your comment, I will fix this :)

TheFes commented 5 months ago

Could you confirm it's fixed now?

dgoodlad commented 5 months ago

Confirmed! I flipped my template's sensor value back to the native forecast sensor from the Amber integration and it worked without crashing. Thank-you!

dgoodlad commented 5 months ago

With this fixed, I've opened a PR to document the configuration for Amber in https://github.com/TheFes/cheapest-energy-hours/pull/143