test_tibber_reporter.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. from __future__ import annotations
  2. import pytest
  3. from flexmeasures.data.models.reporting.pandas_reporter import PandasReporter
  4. from flexmeasures.data.models.time_series import Sensor, DataSource, TimedBelief
  5. from flexmeasures.data.models.generic_assets import GenericAssetType, GenericAsset
  6. import pandas as pd
  7. from datetime import datetime, timedelta
  8. from pytz import utc
  9. index = pd.date_range(
  10. datetime(2023, 4, 13), datetime(2023, 4, 13, 23), freq="h", tz=utc
  11. )
  12. entsoe_prices = [
  13. 97.23,
  14. 85.09,
  15. 79.49,
  16. 72.86,
  17. 71.12,
  18. 82.50,
  19. 102.06,
  20. 115.04,
  21. 122.15,
  22. 105.39,
  23. 83.40,
  24. 34.90,
  25. -4.50,
  26. -50.00,
  27. -50.07,
  28. -50.00,
  29. -0.90,
  30. 108.10,
  31. 128.10,
  32. 151.00,
  33. 155.20,
  34. 152.00,
  35. 134.04,
  36. 120.10,
  37. ] # EUR / MWh
  38. tibber_app_price = [
  39. 29.2,
  40. 27.7,
  41. 27.0,
  42. 26.2,
  43. 26.0,
  44. 27.4,
  45. 29.8,
  46. 31.3,
  47. 32.2,
  48. 30.2,
  49. 27.5,
  50. 21.6, # originally 21.7 due to a rounding error from 216.4569 EUR/MWh (probably via 216.5) to 217 EUR/MWh
  51. 16.9,
  52. 11.4,
  53. 11.4,
  54. 11.4,
  55. 17.3,
  56. 30.5,
  57. 32.9,
  58. 35.7,
  59. 36.2,
  60. 35.8,
  61. 33.6,
  62. 32.0,
  63. ] # cents/kWh
  64. pandas_reporter_config = dict(
  65. required_input=[{"name": v} for v in ["energy_tax", "VAT", "tariff", "da_prices"]],
  66. required_output=[{"name": "da_prices"}],
  67. transformations=[
  68. dict(
  69. df_input="VAT",
  70. method="droplevel",
  71. args=[[1, 2, 3]],
  72. ),
  73. dict(method="add", args=[1]), # this is to get 1 + VAT
  74. dict(
  75. df_input="energy_tax",
  76. method="droplevel",
  77. args=[[1, 2, 3]],
  78. ),
  79. dict(
  80. df_input="tariff",
  81. method="droplevel",
  82. args=[[1, 2, 3]],
  83. ),
  84. dict(
  85. df_input="da_prices",
  86. method="droplevel",
  87. args=[[1, 2, 3]],
  88. ),
  89. dict(method="add", args=["@tariff"]), # da_prices = da_prices + tibber_tariff
  90. dict(method="add", args=["@energy_tax"]), # da_prices = da_prices + energy_tax
  91. dict(method="multiply", args=["@VAT"]), # da_prices = da_price * VAT, VAT
  92. dict(method="round"),
  93. ],
  94. )
  95. def beliefs_from_timeseries(index, values, sensor, source):
  96. beliefs = []
  97. for dt, value in zip(index, values):
  98. beliefs.append(
  99. TimedBelief(
  100. event_start=dt,
  101. belief_horizon=timedelta(hours=24),
  102. event_value=value,
  103. sensor=sensor,
  104. source=source,
  105. )
  106. )
  107. return beliefs
  108. @pytest.fixture()
  109. def tibber_test_data(fresh_db, app):
  110. db = fresh_db
  111. tax = GenericAssetType(name="Tax")
  112. price = GenericAssetType(name="Price")
  113. report = GenericAssetType(name="Report")
  114. db.session.add_all([tax, price])
  115. # Taxes
  116. electricity_price = GenericAsset(name="Electricity Price", generic_asset_type=price)
  117. VAT_asset = GenericAsset(name="VAT", generic_asset_type=tax)
  118. electricity_tax = GenericAsset(name="Energy Tax", generic_asset_type=tax)
  119. tibber_report = GenericAsset(name="TibberReport", generic_asset_type=report)
  120. db.session.add_all([electricity_price, VAT_asset, electricity_tax, tibber_report])
  121. # Taxes
  122. VAT = Sensor(
  123. "VAT",
  124. generic_asset=VAT_asset,
  125. event_resolution=timedelta(days=365),
  126. unit="",
  127. )
  128. EnergyTax = Sensor(
  129. "EnergyTax",
  130. generic_asset=electricity_tax,
  131. event_resolution=timedelta(days=365),
  132. unit="EUR/MWh",
  133. )
  134. # Tibber Tariff
  135. tibber_tariff = Sensor(
  136. "Tibber Tariff",
  137. generic_asset=electricity_price,
  138. event_resolution=timedelta(days=365),
  139. unit="EUR/MWh",
  140. )
  141. db.session.add_all([VAT, EnergyTax, tibber_tariff])
  142. """
  143. Saving TimeBeliefs to the DB
  144. """
  145. # Add EnergyTax, VAT and Tibber Tariff beliefs to the DB
  146. for sensor, source_name, value in [
  147. (VAT, "Tax Authority", 0.21),
  148. (EnergyTax, "Tax Authority", 125.99), # EUR / MWh
  149. (tibber_tariff, "Tibber", 18.0), # EUR /MWh
  150. ]:
  151. belief = TimedBelief(
  152. sensor=sensor,
  153. source=DataSource(source_name),
  154. event_value=value,
  155. event_start=datetime(2023, 1, 1, tzinfo=utc),
  156. belief_time=datetime(2023, 1, 1, tzinfo=utc),
  157. )
  158. db.session.add(belief)
  159. # DA Prices
  160. entsoe = DataSource("ENTSOE")
  161. da_prices = Sensor(
  162. "DA prices",
  163. generic_asset=electricity_price,
  164. event_resolution=timedelta(hours=1),
  165. )
  166. db.session.add(da_prices)
  167. da_prices_beliefs = beliefs_from_timeseries(index, entsoe_prices, da_prices, entsoe)
  168. db.session.add_all(da_prices_beliefs)
  169. tibber_report_sensor = Sensor(
  170. "TibberReportSensor",
  171. generic_asset=tibber_report,
  172. event_resolution=timedelta(hours=1),
  173. unit="EUR/MWh",
  174. )
  175. db.session.add(tibber_report_sensor)
  176. db.session.commit()
  177. return tibber_report_sensor, EnergyTax, VAT, tibber_tariff, da_prices
  178. def test_tibber_reporter(tibber_test_data):
  179. """
  180. This test checks if the calculation of the energy prices gets close enough to the ones
  181. displayed in Tibber's App.
  182. """
  183. tibber_report_sensor, EnergyTax, VAT, tibber_tariff, da_prices = tibber_test_data
  184. tibber_reporter = PandasReporter(config=pandas_reporter_config)
  185. result = tibber_reporter.compute(
  186. input=[
  187. {"name": "energy_tax", "sensor": EnergyTax},
  188. {"name": "VAT", "sensor": VAT},
  189. {"name": "tariff", "sensor": tibber_tariff},
  190. {"name": "da_prices", "sensor": da_prices},
  191. ],
  192. output=[dict(sensor=tibber_report_sensor, name="da_prices")],
  193. start=datetime(2023, 4, 13, tzinfo=utc),
  194. end=datetime(2023, 4, 14, tzinfo=utc),
  195. )[0]["data"]
  196. # check that we got a result for 24 hours
  197. assert len(result) == 24
  198. tibber_app_price_df = (
  199. pd.DataFrame(tibber_app_price, index=index, columns=["event_value"])
  200. * 10 # convert cents/kWh to EUR/MWh
  201. )
  202. result = result.droplevel(["source", "belief_time", "cumulative_probability"])
  203. error = abs(result - tibber_app_price_df)
  204. # check that (EPEX + EnergyTax + Tibber Tariff)*(1 + VAT) = Tibber App Price
  205. assert error.sum(min_count=1).event_value == 0