Skip to content

Conversation

@Metroseksuaali
Copy link

@Metroseksuaali Metroseksuaali commented Jan 27, 2026

Summary

Fix multiple bugs in the services module and const.py.

Bug fixes:

  1. services.py:88 - weekly() service was calling yearly() method instead of weekly()
  2. services.py:23 - Missing raise keyword in area validator - invalid areas were silently accepted
  3. services.py:43 - Wrong strftime format "Y" instead of "%Y" causing regex validation error
  4. const.py:93 - Poland area code had trailing space "PL " instead of "PL"

All bugs were verified by testing in Home Assistant before fixing.

Related to #467

- Fix weekly service calling yearly() instead of weekly()
- Fix area validator not raising vol.Invalid (missing raise keyword)
- Fix year schema default using wrong strftime format ("Y" -> "%Y")
- Fix typo in AREA_TO_COUNTRY: "PL " -> "PL" (trailing space)
- Fix typo in error message: "in not in on of" -> "is not in one of"
@Hellowlol
Copy link
Collaborator

Solid changes. Thanks!

@Metroseksuaali
Copy link
Author

Hi, @Hellowlol

What timelane you have to merge this PR?
I'm using my version at the moment on my HA because i need to use weekly averages.
Just so i know when i can switch back to main branch

@dana-se
Copy link

dana-se commented Jan 29, 2026

Hi, @Metroseksuaali
When I tried your code and got this error:
Error executing script. Invalid data for call_service at pos 1: SE4 is not in one of the supported areas DK1,DK2,FI,EE,LT,LV,NO1,NO2,NO3,NO4,NO5,SE1,SE2,SE3,SE4,SYS,FR,NL,BE,AT,GER for dictionary value @ data['area']

@Metroseksuaali
Copy link
Author

Hi @dana-se, thanks for reporting this!

The error you're seeing is actually a pre-existing bug in the check_setting validator on line 21 of services.py, which our PR did not modify. Here's what's happening:

The validator iterates over the input string character by character:

c = any([i for i in value if i in list(_REGIONS.keys())])

When you pass area: SE4, Python iterates the characters "S", "E", "4" none of which match any key in _REGIONS, so validation fails even though SE4 is a valid area.

Before this PR, the bug was invisible because vol.Invalid() was called without raise, meaning the error was created but never actually raised validation silently passed regardless of input. Our fix correctly added raise, which exposed this underlying issue.

I'll push a fix for the validator logic shortly.

The check_setting validator had two pre-existing bugs exposed by the
raise fix in the previous commit:

1. cv.ensure_list was passed as an argument to check_setting but never
   called, so the area value remained a string instead of becoming a list
2. The validator iterated characters of the string (e.g. "S","E","4")
   instead of checking the whole area code, causing all areas to fail
   validation

Replace check_setting with _validate_areas which properly converts the
input to a list via cv.ensure_list and validates each area code as a
whole string against _REGIONS.
@Metroseksuaali
Copy link
Author

@dana-se Yeah my tests goes trough now. I don't know why those errors were not on my instanse before but i tested everything again and it should work as intented now

@dana-se
Copy link

dana-se commented Jan 29, 2026

@Metroseksuaali , Yes, this new code worked.
But can you explain how you get the weekly averages, because i don't get them right.
Thanks
Edit: I got daily values with action: nordpool.weekly

@Metroseksuaali
Copy link
Author

@dana-se I save the history for past weeks and calculate it. I tried to do it using the entity but did not manage to fix it completely. I think it has something to do with _parse_json. But that again is not anything that I have changed and I'm not sure yet how to fix it. I thought the weekly and daily bugs were caused because of a copy-paste error. Need to try to understand this code more to get it working.

DAILY, WEEKLY, and MONTHLY all share the same data_type value
"AggregatePrices", so _parse_json could not distinguish between them.
Added aggregation parameter throughout the fetch chain to select the
correct JSON data source for each aggregation level. Also fixed fetch()
to parse non-hourly responses instead of returning raw JSON.
@Metroseksuaali
Copy link
Author

Lol i think it did not need much. So

service: nordpool.weekly
data:
  currency: EUR
  area: FI

service: nordpool.monthly data: currency: EUR area: FI

service: nordpool.daily data: currency: EUR area: FI

service: nordpool.yearly data: currency: EUR area: FI

service: nordpool.hourly
data:
  currency: EUR
  date: "2026-01-30"
  area: FI

These all now return correct data as far as i know.

@dana-se
Copy link

dana-se commented Jan 31, 2026

@Metroseksuaali For me your code returned correct data except service: nordpool.daily data: currency: EUR area: FI
Got: Failed to perform the action nordpool.daily. Unknown error

`Logger: homeassistant.helpers.script.websocket_api_script
Source: helpers/script.py:524
First occurred: 14:40:42 (2 occurrences)
Last logged: 14:51:45

websocket_api script: Error executing script. Unexpected error for call_service at pos 1: 'int' object has no attribute 'replace'
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 524, in _async_step
await getattr(self, handler)()
File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1009, in _async_step_call_service
response_data = await self._async_run_long_action(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<9 lines>...
)
^
File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 624, in _async_run_long_action
return await long_task
^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/core.py", line 2835, in async_call
response_data = await coro
^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/core.py", line 2878, in _execute_service
return await target(service_call)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/config/custom_components/nordpool/services.py", line 108, in daily
value = await AioPrices(sc["currency"], client).daily(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
areas=sc["area"], end_date=sc["year"]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/config/custom_components/nordpool/aio_price.py", line 331, in daily
return await self.fetch(self.DAILY, end_date, areas, aggregation="daily")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/config/custom_components/nordpool/aio_price.py", line 301, in fetch
return await self._async_parse_json(raw_data, areas, data_type=data_type, aggregation=aggregation)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/config/custom_components/nordpool/aio_price.py", line 317, in _async_parse_json
return await loop.run_in_executor(
^^^^^^^^^^^^^^^^^^^^^^^^^^^
None, self._parse_json, data, areas, data_type, aggregation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/usr/local/lib/python3.13/concurrent/futures/thread.py", line 59, in run
result = self.fn(*self.args, **self.kwargs)
File "/config/custom_components/nordpool/aio_price.py", line 204, in _parse_json
"value": self._conv_to_float(area_price),
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
File "/config/custom_components/nordpool/aio_price.py", line 357, in _conv_to_float
return float(s.replace(",", ".").replace(" ", ""))
^^^^^^^^^
AttributeError: 'int' object has no attribute 'replace'`

Aggregate price API sometimes returns prices as int instead of float,
causing AttributeError on .replace() call. Handle both int and float
numeric types.
@Metroseksuaali
Copy link
Author

Metroseksuaali commented Jan 31, 2026

@dana-se I did not know api will return int values.

Now it works

end: "2025-12-31T23:00:00+00:00"
updated: "2026-01-31T12:14:00.434844+00:00"
currency: EUR
areas:
  FI:
    values:
      - start: "2026-01-31T23:00:00+00:00"
        end: "2026-01-31T23:00:00+00:00"
        value: 98.86
      - start: "2026-01-30T23:00:00+00:00"
        end: "2026-01-30T23:00:00+00:00"
        value: 156
      - start: "2026-01-29T23:00:00+00:00"
        end: "2026-01-29T23:00:00+00:00"
        value: 247.26
      - start: "2026-01-28T23:00:00+00:00"
        end: "2026-01-28T23:00:00+00:00"
        value: 208.47
      - start: "2026-01-27T23:00:00+00:00"
        end: "2026-01-27T23:00:00+00:00"
        value: 173.97
      - start: "2026-01-26T23:00:00+00:00"
        end: "2026-01-26T23:00:00+00:00"
        value: 216.98
      - start: "2026-01-25T23:00:00+00:00"
        end: "2026-01-25T23:00:00+00:00"
        value: 160.76
      - start: "2026-01-24T23:00:00+00:00"
        end: "2026-01-24T23:00:00+00:00"
        value: 107.01
      - start: "2026-01-23T23:00:00+00:00"
        end: "2026-01-23T23:00:00+00:00"
        value: 124.96
      - start: "2026-01-22T23:00:00+00:00"
        end: "2026-01-22T23:00:00+00:00"
        value: 158.33
      - start: "2026-01-21T23:00:00+00:00"
        end: "2026-01-21T23:00:00+00:00"
        value: 132.35
      - start: "2026-01-20T23:00:00+00:00"
        end: "2026-01-20T23:00:00+00:00"
        value: 111.77
      - start: "2026-01-19T23:00:00+00:00"
        end: "2026-01-19T23:00:00+00:00"
        value: 163.76
      - start: "2026-01-18T23:00:00+00:00"
        end: "2026-01-18T23:00:00+00:00"
        value: 134.72
      - start: "2026-01-17T23:00:00+00:00"
        end: "2026-01-17T23:00:00+00:00"
        value: 91.87
      - start: "2026-01-16T23:00:00+00:00"
        end: "2026-01-16T23:00:00+00:00"
        value: 38.98
      - start: "2026-01-15T23:00:00+00:00"
        end: "2026-01-15T23:00:00+00:00"
        value: 44.97
      - start: "2026-01-14T23:00:00+00:00"
        end: "2026-01-14T23:00:00+00:00"
        value: 81.4
      - start: "2026-01-13T23:00:00+00:00"
        end: "2026-01-13T23:00:00+00:00"
        value: 41.35
      - start: "2026-01-12T23:00:00+00:00"
        end: "2026-01-12T23:00:00+00:00"
        value: 99.7
      - start: "2026-01-11T23:00:00+00:00"
        end: "2026-01-11T23:00:00+00:00"
        value: 127.09
      - start: "2026-01-10T23:00:00+00:00"
        end: "2026-01-10T23:00:00+00:00"
        value: 89.87
      - start: "2026-01-09T23:00:00+00:00"
        end: "2026-01-09T23:00:00+00:00"
        value: 92.89
      - start: "2026-01-08T23:00:00+00:00"
        end: "2026-01-08T23:00:00+00:00"
        value: 124.82
      - start: "2026-01-07T23:00:00+00:00"
        end: "2026-01-07T23:00:00+00:00"
        value: 80.3
      - start: "2026-01-06T23:00:00+00:00"
        end: "2026-01-06T23:00:00+00:00"
        value: 80.71
      - start: "2026-01-05T23:00:00+00:00"
        end: "2026-01-05T23:00:00+00:00"
        value: 127.77
      - start: "2026-01-04T23:00:00+00:00"
        end: "2026-01-04T23:00:00+00:00"
        value: 146.53
      - start: "2026-01-03T23:00:00+00:00"
        end: "2026-01-03T23:00:00+00:00"
        value: 96.45
      - start: "2026-01-02T23:00:00+00:00"
        end: "2026-01-02T23:00:00+00:00"
        value: 72.63
      - start: "2026-01-01T23:00:00+00:00"
        end: "2026-01-01T23:00:00+00:00"
        value: 38.21
      - start: "2025-12-31T23:00:00+00:00"
        end: "2025-12-31T23:00:00+00:00"
        value: 63.51

@dana-se
Copy link

dana-se commented Jan 31, 2026

@Metroseksuaali Yes, now even service: nordpool.daily data: currency: EUR area: FI works.
Perfect, and thanks for your work.

But now the Example for an automation that get the last months average price, don't work.

alias: Example automation action call with storing with parsing and storing result
triggers: null
actions:
  - action: nordpool.yearly
    data:
      currency: NOK
      area: NO2
      year: "2024"
    response_variable: np_result
  - action: input_text.set_value
    target:
      entity_id: input_text.test
    data:
      value: "{{np_result.prices[0].averagePerArea.NO2 | float}}"
mode: single

@Metroseksuaali
Copy link
Author

@dana-se The response structure has changed because non-hourly services now go through _parse_json instead of returning raw API JSON. The new response format is:

start: "2025-12-31T23:00:00+00:00"
end: "2021-12-30T23:00:00+00:00"
updated: "2024-03-26T13:32:39.733019+00:00"
currency: NOK
areas:
  NO2:
    values:
      - start: "2025-12-31T23:00:00+00:00"
        end: "2026-01-30T23:00:00+00:00"
        value: 1230.05
      - start: "2024-12-31T23:00:00+00:00"
        end: "2025-12-30T23:00:00+00:00"
        value: 767.23

So the automation template needs to be updated from:

value: "{{np_result.prices[0].averagePerArea.NO2 | float}}"

To:

value: "{{np_result.areas.NO2.values[0].value | float}}"

I also noticed the README.md (line 221-241) contains the old example with the raw API path. This should be updated to match the new response structure. I can update the README as part of this PR.

Actions no longer return raw API JSON. Updated documentation with
service table, response format, template path examples, and
automation examples for yearly and monthly average prices.
@dana-se
Copy link

dana-se commented Feb 1, 2026

@Metroseksuaali Thanks again for your god job.
When I try your new automation code: Get monthly average price from Nordpool I get this errors;

2026-02-01 09:07:49.279 ERROR (MainThread) [homeassistant.helpers.template] Template variable error: builtin_function_or_method object has no element 0 when rendering '{{np_result.areas.FI.values[0].value | float}}'
2026-02-01 09:07:49.280 ERROR (MainThread) [homeassistant.components.automation.get_monthly_average_price_from_nordpool] Get monthly average price from Nordpool: Error executing script. Error for call_service at pos 2: Error rendering data template: UndefinedError: builtin_function_or_method object has no element 0
2026-02-01 09:07:49.285 ERROR (MainThread) [homeassistant.components.automation.get_monthly_average_price_from_nordpool] Error while executing automation automation.get_monthly_average_price_from_nordpool: Error rendering data template: UndefinedError: builtin_function_or_method object has no element 0

Use bracket notation ["values"] instead of .values to avoid conflict
with Python's built-in dict.values() method in Jinja2 templates.
@Metroseksuaali
Copy link
Author

@dana-se The error you got with the monthly automation is caused by Jinja2 interpreting .values as Python's built-in dict.values() method instead of the dictionary key "values".

The fix is to use bracket notation instead of dot notation:

# This does NOT work:
value: "{{ np_result.areas.FI.values[0].value | float }}"

# This works:
value: "{{ np_result.areas.FI["values"][0].value | float }}"

I've updated the README examples with the correct notation. Sorry for the confusion!

@dana-se
Copy link

dana-se commented Feb 1, 2026

@Metroseksuaali Now the automations works.

So thanks again for the quick response and solutions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants