Skip to content

Commit

Permalink
Merge pull request #4 from avataar/update-prices-2024
Browse files Browse the repository at this point in the history
Update prices as of July 2024
  • Loading branch information
avataar authored Jun 30, 2024
2 parents 7b6e449 + 234b3e8 commit ff0df1c
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 76 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ Custom integration for [Home Assistant](https://www.home-assistant.io) that prov

All three major regional providers, Electrohold, EVN, and ENERGO-PRO are supported.

The prices are defined statically as they change only about once a year. The official source for the current prices is section 6 from [Resolution C-14/30.06.2023 of the Bulgarian Energy and Water Regulatory Commission](https://www.dker.bg/uploads/reshenia/2023/res_c_14_23.pdf). All prices are the final amount that you'd pay, including VAT.
The prices are defined statically as they change only about once a year. The official source for the current prices is section 6 from:

- Since 1 July 2024: [Resolution C-17/30.06.2024 of the Bulgarian Energy and Water Regulatory Commission](https://www.dker.bg/uploads/reshenia/2024/res-c-17-2024.pdf).
- Since 1 July 2023: [Resolution C-14/30.06.2023 of the Bulgarian Energy and Water Regulatory Commission](https://www.dker.bg/uploads/reshenia/2023/res_c_14_23.pdf).

All prices are the final amount that you'd pay, including VAT.

The night tariff starts at 22:00 UTC+2 and ends at 06:00 UTC+2. Note that even though Bulgaria switches to UTC+3 in the summer, meter clocks are not adjusted. In other words, the night tariff starts at 22:00/ends at 06:00 in the winter and at 23:00/07:00 in the summer.

Expand Down
35 changes: 33 additions & 2 deletions custom_components/bg_electricity_regulated_pricing/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@

VAT_RATE = 0.2

PROVIDER_PRICES = {
PROVIDER_PRICES_BEFORE_JULY_2024 = {
# Section 6.1, https://www.dker.bg/uploads/reshenia/2023/res_c_14_23.pdf
"electrohold": {
"day": .14875,
"night": .05997,
"fees": .01623 + .00754 + .04232
},
# Section 6.1, https://www.dker.bg/uploads/reshenia/2023/res_c_14_23.pdf
# Section 6.2, https://www.dker.bg/uploads/reshenia/2023/res_c_14_23.pdf
"evn": {
"day": .14667,
"night": .05531,
Expand All @@ -38,3 +38,34 @@
"fees": .01623 + .00959 + .04825
}
}

PROVIDER_PRICES = {
# Section 6.1, https://www.dker.bg/uploads/reshenia/2024/res-c-17-2024.pdf
"electrohold": {
"day": .16210,
"night": .07104,
"fees": .01354 + .00770 + .03803
},
# Section 6.2, https://www.dker.bg/uploads/reshenia/2024/res-c-17-2024.pdf
"evn": {
"day": .15926,
"night": .06833,
"fees": .01354 + .00819 + .03704
},
# Section 6.3, https://www.dker.bg/uploads/reshenia/2024/res-c-17-2024.pdf
"energo_pro": {
"day": .16341,
"night": .06636,
"fees": .01354 + .00977 + .03689
}
}

PROVIDER_PRICES_BY_DATE = [
{
"until": 1719777600, # midnight 2024-07-01 UTC+2
"prices": PROVIDER_PRICES_BEFORE_JULY_2024
},
{
"prices": PROVIDER_PRICES
}
]
39 changes: 21 additions & 18 deletions custom_components/bg_electricity_regulated_pricing/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
SensorStateClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import utcnow
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo


from .const import CONF_TARIFF_TYPE, CONF_PROVIDER, CONF_CUSTOM_DAY_PRICE, \
CONF_CUSTOM_NIGHT_PRICE, PROVIDER_PRICES, CONF_CLOCK_OFFSET, \
BGN_PER_KILOWATT_HOUR, VAT_RATE, DOMAIN
CONF_CUSTOM_NIGHT_PRICE, CONF_CLOCK_OFFSET, \
BGN_PER_KILOWATT_HOUR, VAT_RATE, DOMAIN, PROVIDER_PRICES_BY_DATE


async def async_setup_entry(
Expand All @@ -28,16 +27,24 @@ async def async_setup_entry(
clock_offset = config_entry.options[CONF_CLOCK_OFFSET]
provider = config_entry.options[CONF_PROVIDER]
if provider == "custom":
price_day = config_entry.options[CONF_CUSTOM_DAY_PRICE]
price_night = config_entry.options[CONF_CUSTOM_NIGHT_PRICE]
def price_provider_fun(x):
if x == "day":
return config_entry.options[CONF_CUSTOM_DAY_PRICE]
else:
return config_entry.options[CONF_CUSTOM_NIGHT_PRICE]
else:
price_day = (PROVIDER_PRICES[provider]["day"]
+ PROVIDER_PRICES[provider]["fees"]) * (1 + VAT_RATE)
price_night = (PROVIDER_PRICES[provider]["night"]
+ PROVIDER_PRICES[provider]["fees"]) * (1 + VAT_RATE)
def price_provider_fun(x):
price = 0
fees = 0
for pp in PROVIDER_PRICES_BY_DATE:
price = pp["prices"][provider][x]
fees = pp["prices"][provider]["fees"]
if "until" in pp and now_utc().timestamp() < pp["until"]:
break
return (price + fees) * (1 + VAT_RATE)

price_provider = BgElectricityRegulatedPricingProvider(tariff_type, clock_offset,
price_day, price_night)
price_provider_fun)

desc_price = SensorEntityDescription(
key="price",
Expand Down Expand Up @@ -119,11 +126,10 @@ def update(self):
class BgElectricityRegulatedPricingProvider:
"""Pricing provider aware of current tariff and price."""

def __init__(self, tariff_type, clock_offset, price_day, price_night):
def __init__(self, tariff_type, clock_offset, price_provider):
self._tariff_type = tariff_type
self._clock_offset = clock_offset
self._price_day = price_day
self._price_night = price_night
self._price_provider = price_provider

def tariff(self):
# Current hour and minutes in minutes since midnight, UTC+2.
Expand All @@ -140,7 +146,4 @@ def tariff(self):
return "day"

def price(self):
if self.tariff() == "day":
return self._price_day
else:
return self._price_night
return self._price_provider(self.tariff())
175 changes: 120 additions & 55 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from custom_components.bg_electricity_regulated_pricing.const import DOMAIN

EXPECTED_PRICES = {
EXPECTED_PRICES_BEFORE_JULY_2024 = {
"electrohold": {
"day": 0.257808,
"night": 0.151272
Expand All @@ -30,66 +30,122 @@
}
}

EXPECTED_PRICES = {
"electrohold": {
"day": 0.265644,
"night": 0.156372
},
"evn": {
"day": 0.261636,
"night": 0.15252
},
"energo_pro": {
"day": 0.268332,
"night": 0.151872
},
"custom": {
"day": 0.25,
"night": 0.15
}
}

EXPECTED_PRICES_BY_DATE = [
{
"until": 1719777600, # midnight 2024-07-01 UTC+2
"prices": EXPECTED_PRICES_BEFORE_JULY_2024
},
{
"prices": EXPECTED_PRICES
}
]


@pytest.mark.parametrize("provider", ("electrohold", "evn", "energo_pro", "custom"))
async def test_setup_and_remove_config_entry(hass: HomeAssistant,
provider: str) -> None:
"""Test setting up and removing a config entry."""
with mock_time(21, 59):
await do_setup_test(hass, provider, "dual", "day")
await do_setup_test(hass, provider, "single", "day")

with mock_time(22, 0):
await do_setup_test(hass, provider, "dual", "night")
await do_setup_test(hass, provider, "single", "day")

with mock_time(5, 59):
await do_setup_test(hass, provider, "dual", "night")
await do_setup_test(hass, provider, "single", "day")

with mock_time(6, 0):
await do_setup_test(hass, provider, "dual", "day")
await do_setup_test(hass, provider, "single", "day")

# Meter clock is 30 minutes ahead
with mock_time(21, 30):
await do_setup_test(hass, provider, "dual", "night", 30)
await do_setup_test(hass, provider, "single", "day", 30)

with mock_time(5, 30):
await do_setup_test(hass, provider, "dual", "day", 30)
await do_setup_test(hass, provider, "single", "day", 30)

# Meter clock is 30 minutes behind
with mock_time(22, 29):
await do_setup_test(hass, provider, "dual", "day", -30)
await do_setup_test(hass, provider, "single", "day", -30)

with mock_time(6, 29):
await do_setup_test(hass, provider, "dual", "night", -30)
await do_setup_test(hass, provider, "single", "day", -30)

# Meter clock is 10 hours ahead
with mock_time(12, 0):
await do_setup_test(hass, provider, "dual", "night", 600)
await do_setup_test(hass, provider, "single", "day", 600)

with mock_time(20, 0):
await do_setup_test(hass, provider, "dual", "day", 600)
await do_setup_test(hass, provider, "single", "day", 600)

# Meter clock is 10 hours behind
with mock_time(8, 0):
await do_setup_test(hass, provider, "dual", "night", -600)
await do_setup_test(hass, provider, "single", "day", -600)

with mock_time(16, 0):
await do_setup_test(hass, provider, "dual", "day", -600)
await do_setup_test(hass, provider, "single", "day", -600)
for mp in [[mock_time, EXPECTED_PRICES], [mock_time_before_july_2024, EXPECTED_PRICES_BEFORE_JULY_2024]]:
mock = mp[0]
expected_prices = mp[1]
with mock(21, 59):
await do_setup_test(hass, provider, "dual", "day",
expected_prices)
await do_setup_test(hass, provider, "single", "day",
expected_prices)

with mock(22, 0):
await do_setup_test(hass, provider, "dual", "night",
expected_prices)
await do_setup_test(hass, provider, "single", "day",
expected_prices)

with mock(5, 59):
await do_setup_test(hass, provider, "dual", "night",
expected_prices)
await do_setup_test(hass, provider, "single", "day",
expected_prices)

with mock(6, 0):
await do_setup_test(hass, provider, "dual", "day",
expected_prices)
await do_setup_test(hass, provider, "single", "day",
expected_prices)

# Meter clock is 30 minutes ahead
with mock(21, 30):
await do_setup_test(hass, provider, "dual", "night",
expected_prices, 30)
await do_setup_test(hass, provider, "single", "day",
expected_prices, 30)

with mock(5, 30):
await do_setup_test(hass, provider, "dual", "day",
expected_prices, 30)
await do_setup_test(hass, provider, "single", "day",
expected_prices, 30)

# Meter clock is 30 minutes behind
with mock(22, 29):
await do_setup_test(hass, provider, "dual", "day",
expected_prices, -30)
await do_setup_test(hass, provider, "single", "day",
expected_prices, -30)

with mock(6, 29):
await do_setup_test(hass, provider, "dual", "night",
expected_prices, -30)
await do_setup_test(hass, provider, "single", "day",
expected_prices, -30)

# Meter clock is 10 hours ahead
with mock(12, 0):
await do_setup_test(hass, provider, "dual", "night",
expected_prices, 600)
await do_setup_test(hass, provider, "single", "day",
expected_prices, 600)

with mock(20, 0):
await do_setup_test(hass, provider, "dual", "day",
expected_prices, 600)
await do_setup_test(hass, provider, "single", "day",
expected_prices, 600)

# Meter clock is 10 hours behind
with mock(8, 0):
await do_setup_test(hass, provider, "dual", "night",
expected_prices, -600)
await do_setup_test(hass, provider, "single", "day",
expected_prices, -600)

with mock(16, 0):
await do_setup_test(hass, provider, "dual", "day",
expected_prices, -600)
await do_setup_test(hass, provider, "single", "day",
expected_prices, -600)


async def do_setup_test(hass: HomeAssistant, provider: str, tariff_type: str,
expected_tariff: str, clock_offset=0):
expected_tariff: str, expected_prices, clock_offset=0):
registry = er.async_get(hass)
custom_day_price = 0.25
custom_night_price = 0.15
Expand Down Expand Up @@ -120,7 +176,7 @@ async def do_setup_test(hass: HomeAssistant, provider: str, tariff_type: str,

# Check the platform for price is setup correctly
state = hass.states.get(price_entity_id)
assert state.state == str(EXPECTED_PRICES[provider][expected_tariff])
assert state.state == str(expected_prices[provider][expected_tariff])
assert state.attributes == {
'friendly_name': 'My Provider Price',
'icon': 'mdi:currency-eur',
Expand Down Expand Up @@ -150,10 +206,19 @@ async def do_setup_test(hass: HomeAssistant, provider: str, tariff_type: str,
assert registry.async_get(tariff_entity_id) is None


def mock_time(hour: int, minute: int):
def mock_time_before_july_2024(hour: int, minute: int):
hour -= 2
if hour < 0:
hour += 24
return mock.patch(
"custom_components.bg_electricity_regulated_pricing.sensor.now_utc",
return_value=datetime(2023, 12, 23, hour, minute, 0, 0, pytz.UTC))


def mock_time(hour: int, minute: int):
hour -= 2
if hour < 0:
hour += 24
return mock.patch(
"custom_components.bg_electricity_regulated_pricing.sensor.now_utc",
return_value=datetime(2024, 7, 1, hour, minute, 0, 0, pytz.UTC))

0 comments on commit ff0df1c

Please sign in to comment.