diff --git a/pyrecoy/.vs/ProjectSettings.json b/pyrecoy/.vs/ProjectSettings.json new file mode 100644 index 0000000..f8b4888 --- /dev/null +++ b/pyrecoy/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/pyrecoy/.vs/VSWorkspaceState.json b/pyrecoy/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..023fff2 --- /dev/null +++ b/pyrecoy/.vs/VSWorkspaceState.json @@ -0,0 +1,11 @@ +{ + "ExpandedNodes": [ + "", + "\\pyrecoy", + "\\pyrecoy\\pyrecoy", + "\\pyrecoy\\pyrecoy\\data", + "\\pyrecoy\\pyrecoy\\data\\tax_tariffs" + ], + "SelectedNode": "\\pyrecoy\\pyrecoy\\prices.py", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/pyrecoy/.vs/pyrecoy/FileContentIndex/0062bb29-2b94-4e4b-9323-e2eb61396713.vsidx b/pyrecoy/.vs/pyrecoy/FileContentIndex/0062bb29-2b94-4e4b-9323-e2eb61396713.vsidx new file mode 100644 index 0000000..70aef67 Binary files /dev/null and b/pyrecoy/.vs/pyrecoy/FileContentIndex/0062bb29-2b94-4e4b-9323-e2eb61396713.vsidx differ diff --git a/pyrecoy/.vs/pyrecoy/FileContentIndex/2b9a0a9e-14bd-43a1-b3ee-98764114df6d.vsidx b/pyrecoy/.vs/pyrecoy/FileContentIndex/2b9a0a9e-14bd-43a1-b3ee-98764114df6d.vsidx new file mode 100644 index 0000000..1d8edeb Binary files /dev/null and b/pyrecoy/.vs/pyrecoy/FileContentIndex/2b9a0a9e-14bd-43a1-b3ee-98764114df6d.vsidx differ diff --git a/pyrecoy/.vs/pyrecoy/FileContentIndex/5f785a9b-4ecf-4815-9457-ede32ca8bb05.vsidx b/pyrecoy/.vs/pyrecoy/FileContentIndex/5f785a9b-4ecf-4815-9457-ede32ca8bb05.vsidx new file mode 100644 index 0000000..70aef67 Binary files /dev/null and b/pyrecoy/.vs/pyrecoy/FileContentIndex/5f785a9b-4ecf-4815-9457-ede32ca8bb05.vsidx differ diff --git a/pyrecoy/.vs/pyrecoy/FileContentIndex/cf3214f8-06fa-462c-9ca5-e67ee6237457.vsidx b/pyrecoy/.vs/pyrecoy/FileContentIndex/cf3214f8-06fa-462c-9ca5-e67ee6237457.vsidx new file mode 100644 index 0000000..70aef67 Binary files /dev/null and b/pyrecoy/.vs/pyrecoy/FileContentIndex/cf3214f8-06fa-462c-9ca5-e67ee6237457.vsidx differ diff --git a/pyrecoy/.vs/pyrecoy/FileContentIndex/f607662a-174d-4632-bc3f-60cd06b08d1a.vsidx b/pyrecoy/.vs/pyrecoy/FileContentIndex/f607662a-174d-4632-bc3f-60cd06b08d1a.vsidx new file mode 100644 index 0000000..70aef67 Binary files /dev/null and b/pyrecoy/.vs/pyrecoy/FileContentIndex/f607662a-174d-4632-bc3f-60cd06b08d1a.vsidx differ diff --git a/pyrecoy/.vs/pyrecoy/v17/.wsuo b/pyrecoy/.vs/pyrecoy/v17/.wsuo new file mode 100644 index 0000000..cfe34f1 Binary files /dev/null and b/pyrecoy/.vs/pyrecoy/v17/.wsuo differ diff --git a/pyrecoy/.vs/pyrecoy/v17/DocumentLayout.json b/pyrecoy/.vs/pyrecoy/v17/DocumentLayout.json new file mode 100644 index 0000000..d4ecd3e --- /dev/null +++ b/pyrecoy/.vs/pyrecoy/v17/DocumentLayout.json @@ -0,0 +1,100 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\prices.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:pyrecoy\\pyrecoy\\prices.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_horticulture_eb.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_horticulture_eb.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_eb.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_eb.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\data\\tax_tariffs\\electricity_eb.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:pyrecoy\\pyrecoy\\data\\tax_tariffs\\electricity_eb.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 6, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:129:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "gas_eb.json", + "DocumentMoniker": "C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_eb.json", + "RelativeDocumentMoniker": "pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_eb.json", + "ToolTip": "C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_eb.json", + "RelativeToolTip": "pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_eb.json", + "ViewState": "AQIAAEMAAAAAAAAAAAAAAE8AAAADAAAA", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|", + "WhenOpened": "2024-04-22T13:54:50.515Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "gas_horticulture_eb.json", + "DocumentMoniker": "C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_horticulture_eb.json", + "RelativeDocumentMoniker": "pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_horticulture_eb.json", + "ToolTip": "C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_horticulture_eb.json", + "RelativeToolTip": "pyrecoy\\pyrecoy\\data\\tax_tariffs\\gas_horticulture_eb.json", + "ViewState": "AQIAAAAAAAAAAAAAAAAAAAAAAABhAAAA", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|", + "WhenOpened": "2024-04-22T13:54:38.231Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 3, + "Title": "electricity_eb.json", + "DocumentMoniker": "C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\data\\tax_tariffs\\electricity_eb.json", + "RelativeDocumentMoniker": "pyrecoy\\pyrecoy\\data\\tax_tariffs\\electricity_eb.json", + "ToolTip": "C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\data\\tax_tariffs\\electricity_eb.json", + "RelativeToolTip": "pyrecoy\\pyrecoy\\data\\tax_tariffs\\electricity_eb.json", + "ViewState": "AQIAACgAAAAAAAAAAAAIwEEAAAABAAAA", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|", + "WhenOpened": "2024-04-22T08:40:35.654Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "prices.py", + "DocumentMoniker": "C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\prices.py", + "RelativeDocumentMoniker": "pyrecoy\\pyrecoy\\prices.py", + "ToolTip": "C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\asset-case-studies\\pyrecoy\\pyrecoy\\pyrecoy\\prices.py", + "RelativeToolTip": "pyrecoy\\pyrecoy\\prices.py", + "ViewState": "AQIAALEBAAAAAAAAAAASwMoBAAAAAAAA", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2024-04-16T14:15:11.652Z", + "EditorCaption": "" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/pyrecoy/.vs/slnx.sqlite b/pyrecoy/.vs/slnx.sqlite new file mode 100644 index 0000000..72a6ce3 Binary files /dev/null and b/pyrecoy/.vs/slnx.sqlite differ diff --git a/pyrecoy/pyrecoy/.gitignore b/pyrecoy/pyrecoy/.gitignore new file mode 100644 index 0000000..71db39a --- /dev/null +++ b/pyrecoy/pyrecoy/.gitignore @@ -0,0 +1,6 @@ +*.egg-info +.vscode +__pycache__ +*.__pycache__ +*.ipynb_checkpoints +*.pytest_cache \ No newline at end of file diff --git a/pyrecoy/pyrecoy/README.md b/pyrecoy/pyrecoy/README.md new file mode 100644 index 0000000..6bd22b9 --- /dev/null +++ b/pyrecoy/pyrecoy/README.md @@ -0,0 +1,25 @@ +# The _pyrecoy_ Package + +Modelling framework and tools and modelling of flexible assets on energy markets. + +## Getting started: +### Prerequisites +* It is recommended to set-up your Python development environment according to [this Wiki page](https://gitlab.com/recoy-internal/pyrecoy-package/-/wikis/Recommended-Development-Environment) +* The _pyrecoy_ package is best used in a Jupyter Lab / Notebooks environment +* Environment variables: + * `ICE_USERNAME` and `ICE_PASSWORD` login credentials to https://www.ice.if5.com are required if you want to use TTF (gas) and ETS (CO2) prices in your model. + * `FORECAST_DATA_FOLDER`: Local path to the "Forecast Data" folder on the Sharepoint server. This is required if you want to use forecast/price data in your model, e.g. `C:/Users/username/Recoy/Recoy - Documents/03 - Libraries/12 - Data Management/Forecast Data/` +* Jupyter Lab extensions: + * `ipywidgets` and the [Plotly extension ](https://plotly.com/python/getting-started/#jupyterlab-support-python-35) may be needed to view all graphs as intended. + +### Installation + __For usage in specific project only__ +* Clone the repo to your project directory + +__For global installation in your Python environment__ +* Run `pip install git+ssh://git@gitlab.com/recoy-internal/pyrecoy-package.git` +* You should be able `import pyrecoy` package in any Python script using your environment + +## Usage: +... + diff --git a/pyrecoy/pyrecoy/Untitled.ipynb b/pyrecoy/pyrecoy/Untitled.ipynb new file mode 100644 index 0000000..e196edb --- /dev/null +++ b/pyrecoy/pyrecoy/Untitled.ipynb @@ -0,0 +1,35 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "56a3f3f4-59a6-441f-96c7-f44845539991", + "metadata": {}, + "outputs": [], + "source": [ + "from pyrecoy.colors import *" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/__init__.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/__init__.py new file mode 100644 index 0000000..bdad31f --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/__init__.py @@ -0,0 +1 @@ +from .database.Models.base import * \ No newline at end of file diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/assets.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/assets.py new file mode 100644 index 0000000..d8a420c --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/assets.py @@ -0,0 +1,789 @@ +import warnings +from functools import partial, lru_cache +from numbers import Number +from itertools import count + +import numpy as np +from numpy.polynomial import Polynomial +from scipy.optimize import minimize_scalar + +from .converters import * + + +class Asset: + """Generic class for producing/consuming assets. Specific asset classes can + inherit from this class. + + Parameters: + ----------- + max_power : int/float + Maximum asset power in MW electric + min_power : int/float + Minimium asset load in MW electric + + Usage: + ------ + Use the set_load and get_load methods to set and get asset status in MW. + + Convention is negative values for inputs (consumption) and positive + values for outputs (production). + """ + + _freq_to_multiplier = {"H": 1, "15T": (1 / 4), "1T": (1 / 60)} + _ids = count(0) + + def __init__(self, name, max_power, min_power): + if min_power > max_power: + raise ValueError("'min_power' can not be larger than 'max_power'.") + + self.name = name + self.id = next(self._ids) + self.max_power = max_power + self.min_power = min_power + self.modes = {"max": max_power, "min": min_power} + + def __repr__(self): + return f"{self.__class__.__name__}(self, max_power={self.max_power}, min_power={self.min_power})" + + def set_load(self, load): + """Set Asset load in MW. + + Convention is negative value for consumption and positive value + for production. Subclasses might use a different convention if + this seems more intiutive. + + Returns the load that is set in MW. + """ + if load < self.min_power or load > self.max_power: + warnings.warn( + f"Chosen Asset load for {self.name} is out of range. " + f"Should be between {self.min_power} and {self.max_power}. " + f"Function will return boundary load level for now." + ) + load = min(max(load, self.min_power), self.max_power) + return load + + def set_mode(self, mode): + """ """ + load = self.modes[mode] + return self.set_load(load) + + def MW_to_MWh(self, MW): + """Performs conversion from MW to MWh using the time_factor variable.""" + return MW * self.time_factor + + def MWh_to_MW(self, MWh): + """Performs conversion from MWh to MW using the time_factor variable.""" + return MWh / self.time_factor + + def set_freq(self, freq): + """ + Function that aligns time frequency between Model and Asset. + Can be '1T', '15T' or 'H' + The time_factor variable is used in subclasses to perform MW to MWh conversions. + """ + self.freq = freq + self.time_factor = Asset._freq_to_multiplier[freq] + + def set_financials( + self, capex, opex, devex, lifetime=None, depreciate=True, salvage_value=0 + ): + """Set financial data of the asset.""" + self.capex = capex + self.opex = opex + self.devex = devex + self.lifetime = lifetime + self.depreciate = depreciate + self.salvage_value = salvage_value + + +class Eboiler(Asset): + """Subclass for an E-boiler.""" + + def __init__(self, name, max_power, min_power=0, efficiency=0.99): + super().__init__(name, min_power=-max_power, max_power=-min_power) + self.efficiency = efficiency + self.max_thermal_output = max_power * 0.99 + + def __repr__(self): + return ( + f"{self.__class__.__name__}(name={self.name}, max_power={self.max_power}, " + f"min_power={self.min_power}, efficiency={self.efficiency})" + ) + + def set_load(self, load): + """Set load in MWe, returns (load, heat_output) in MWe and MWth + + Convention is negative numbers for consumption. + Inserting a positive value will return an exception. + """ + + if load > 0: + raise ValueError( + f"Eboiler.set_load() only accepts negative numbers by convention. " + f"{load} was inserted." + ) + + load = super().set_load(load) + heat_output = -load * self.efficiency + return (load, heat_output) + + def set_heat_output(self, heat_output): + """Set heat output in MWth, returns tuple (heat_output, eload) in MW""" + load = -heat_output / self.efficiency + load, heat_output = self.set_load(load) + return heat_output, load + + +class Heatpump(Asset): + """Subclass for a Heatpump. + + Use cop parameter to set fixed COP (float/int) or COP curve (func). + COP curve should take load in MWhe and return COP. + + Parameters: + ----------- + max_th_power : numeric + Maximum thermal output in MW (positive value) + cop_curve : numeric or list or function + 3 ways to set the COP of the Heatpump: + (1) Fixed COP based on [numeric] value. + (2) Polynomial with coefficients based on [list] input. + + Input coeficients in format [c0, c1, c2, ..., c(n)], + will generate Polynomial p(x) = c0 + c1*x + c2*x^2 ... cn*x^n, + where x = % thermal load (in % of thermal capacity) as decimal value. + + Example: + cop=[1, 2, 3, 4] will result in following COP curve: + p(x) = 1 + 2x + 3x**2 + 4x**3, + + (3) [function] in format func(*args, **kwargs) + Function should return a Polynomial that takes 'load_perc' as parameter. + + min_th_power : numeric + Minimum thermal output in MW (positive value) + + Notes: + ------ + Sign convention: + Thermal power outputs have positive values + Electric power inputs have negative values + """ + + def __init__( + self, + name, + max_th_power, + cop_curve, + min_th_power=0, + ): + if max_th_power < 0 or min_th_power < 0: + raise ValueError("Thermal power can not have negative values.") + + if min_th_power > max_th_power: + raise ValueError("'min_th_power' can not be larger than 'max_th_power'.") + + self.name = name + self.max_th_power = max_th_power + self.min_th_power = min_th_power + self.cop_curve = self._set_cop_curve(cop_curve) + + def __repr__(self): + return ( + f"{self.__class__.__name__}(name='{self.name}', max_thermal_power={self.max_th_power}, " + f"cop_curve={self.cop_curve}, min_th_power={self.min_th_power})" + ) + + # Is turning everything into a Polynomial the best solution here? + @staticmethod + @lru_cache(maxsize=None) + def _set_cop_curve(cop_curve): + """Generate COP curve function based on different inputtypes. + + Returns a function that takes *args **kwargs and returns a Polynomial. + """ + if isinstance(cop_curve, list): + + def func(*args, **kwargs): + return Polynomial(cop_curve) + + return func + + return cop_curve + + @lru_cache(maxsize=None) + def get_cop(self, heat_output, Tsink=None, Tsource=None): + """Get COP corresponding to certain load. + + Parameters: + ----------- + heat_output : numeric + Thermal load in MW + Tsink : numeric + Sink temperature in degrees celcius + Tsource : numeric + Source temperature in degrees celcius + + Notes: + ------ + Sign convention: + Positive values for thermal load + Negative values for electric load + """ + load_perc = heat_output / self.max_th_power + cop_curve = self.cop_curve + + if not callable(cop_curve): + return cop_curve + else: + return cop_curve(Tsink=Tsink, Tsource=Tsource)(load_perc) + + def th_to_el_power(self, heat_output, Tsink=None, Tsource=None): + if not self.min_th_power <= heat_output <= self.max_th_power: + warnings.warn( + f"Chosen heat output is out of range [{self.min_th_power} - {self.max_th_power}]. " + "Heat output is being limited to the closest boundary." + ) + heat_output = min(max(heat_output, self.min_th_power), self.max_th_power) + + cop = self.get_cop(heat_output=heat_output, Tsink=Tsink, Tsource=Tsource) + return -heat_output / cop + + def set_load(self, *args, **kwargs): + raise NotImplementedError( + "Directly setting the electric load of the heatpump is not possible (yet). " + "Functionality will be implemented if there is a specific usecase for it." + ) + + @lru_cache(maxsize=None) + def set_heat_output(self, heat_output, Tsink=None, Tsource=None): + """Set heat output in MWth, returns load of heatpump as tuple (MWe, MWth)""" + if not self.min_th_power <= heat_output <= self.max_th_power: + warnings.warn( + f"Chosen heat output is out of range [{self.min_th_power} - {self.max_th_power}]. " + "Heat output is being limited to the closest boundary." + ) + heat_output = min(max(heat_output, self.min_th_power), self.max_th_power) + + if Tsink is not None and Tsource is not None and Tsink <= Tsource: + raise ValueError(f"Tsource '{Tsource}' can not be higher than '{Tsink}'.") + + cop = self.get_cop(heat_output=heat_output, Tsink=Tsink, Tsource=Tsource) + e_load = -heat_output / cop + return e_load, heat_output + + def _cost_function(self, x, c1, c2, c3, Tsink=None, Tsource=None): + """Objective function for set_opt_load function. + + x = heatpump thermal load in MW + c1 = electricity_cost + c2 = alt_heat_price + c3 = demand + """ + return ( + x / self.get_cop(heat_output=x, Tsink=Tsink, Tsource=Tsource) * c1 + + (c3 - x) * c2 + ) + + @lru_cache(maxsize=None) + def set_opt_load( + self, + electricity_cost, + alt_heat_price, + demand, + Tsink=None, + Tsource=None, + tolerance=0.01, + ): + """Set optimal load of Heatpump with minimal total heat costs. + + Function uses np.minimize_scalar to minimize cost function. + + Parameters: + ----------- + electricity_cost: + Cost of input electricity in €/MWh(e) + alt_heat_price: + Price of heat from alternative source in €/MWh(th) + demand: + Heat demand in MW(th) + + Returns: + -------- + Optimal load of heatpump as tuple (MWe, MWth) + """ + c1 = electricity_cost + c2 = alt_heat_price + c3 = demand + + cop_curve = self.cop_curve + if isinstance(cop_curve, Number): + if c1 / cop_curve <= c2: + return self.max_th_power + else: + return self.min_th_power + + obj_func = partial( + self._cost_function, c1=c1, c2=c2, c3=c3, Tsink=Tsink, Tsource=Tsource + ) + + low_bound = 0 + up_bound = min(c3, self.max_th_power) + + opt_th_load = minimize_scalar( + obj_func, + bounds=(low_bound, up_bound), + method="bounded", + options={"xatol": tolerance}, + ).x + opt_e_load, opt_th_load = self.set_heat_output( + opt_th_load, Tsink=Tsink, Tsource=Tsource + ) + + return opt_e_load, opt_th_load + + +class Battery(Asset): + """Subclass for a Battery. + + Battery is modeled as follows: + - Rated power is power in MW that battery can + import from and export to the grid + - Efficiency loss is applied at charging, meaning that + SoC increase when charging is lower than the SoC decrease + when discharging + """ + + def __init__( + self, + name, + rated_power, + rated_capacity, + roundtrip_eff, + min_soc=0, + max_soc=1, + soc_at_start=None, + cycle_lifetime=None, + ): + super().__init__(name=name, max_power=rated_power, min_power=-rated_power) + self.capacity = rated_capacity + self.min_soc = min_soc + self.max_soc = max_soc + self.min_chargelevel = min_soc * self.capacity + self.max_chargelevel = max_soc * self.capacity + self.rt_eff = roundtrip_eff + self.one_way_eff = np.sqrt(roundtrip_eff) + self.cycle_count = 0 + self.cycle_lifetime = cycle_lifetime + + soc_at_start = min_soc if soc_at_start is None else soc_at_start + self.set_chargelevel(soc_at_start * self.capacity) + + def __repr__(self): + return ( + f"Battery(self, rated_power={self.max_power}, rated_capacity={self.capacity}, " + f"roundtrip_eff={self.rt_eff}, min_soc={self.min_soc}, max_soc={self.max_soc})" + ) + + def get_soc(self): + """Get the SoC in % (decimal value)""" + return self.chargelevel / self.capacity + + def set_chargelevel(self, chargelevel): + """Set the chargelevel in MWh. Will automatically change the SoC accordingly.""" + # if round(chargelevel,2) < round(self.min_chargelevel,2) or round(chargelevel,2) > round(self.max_chargelevel,2): + # raise ValueError( + # f"Tried to set Charge Level to {chargelevel}. " + # f"Charge Level must be a value between " + # f"{self.min_chargelevel} and {self.max_chargelevel} (in MWh)" + # ) + + self.chargelevel = chargelevel + + def set_load(self, load): + """Set load of the battery. + + Use negative values for charging and positive values for discharging. + Returns actual chargespeed, considering technical limitations of the battery. + + Note: We currently assume all efficiency losses occur during charging (no losses during discharge) + """ + if not hasattr(self, "freq"): + raise AttributeError( + "Time frequency of the model is not defined. " + "Assign asset to a CaseStudy or use Asset.freq(). " + "to set de time frequency and try again." + ) + + load = super().set_load(load) + + unbound_charging = self.MW_to_MWh(load) + + if load < 0: + unbound_charging *= self.rt_eff + + chargelevel = self.chargelevel + max_charging = chargelevel - self.max_chargelevel + max_discharging = chargelevel - self.min_chargelevel + + bound_charging = min(max(unbound_charging, max_charging), max_discharging) + newcl = chargelevel - bound_charging + self.set_chargelevel(newcl) + + if bound_charging < 0: + bound_charging /= self.rt_eff + + self.cycle_count += abs(bound_charging / (self.capacity * 2)) + + return self.MWh_to_MW(bound_charging) + + def charge(self, chargespeed): + """Charge the battery with given chargespeed. + + Redirects to Battery.set_load(). + Returns load (negative value for charging). + """ + chargespeed = self.max_power if chargespeed == "max" else chargespeed + + if chargespeed < 0: + raise ValueError( + f"Chargespeed should be always be a positive value by convention. " + f"Inserted {chargespeed}." + ) + + chargespeed = self.set_load(-chargespeed) + + return chargespeed + + def discharge(self, dischargespeed): + """Discharge the battery by given amount. + + Redirects to Battery.set_load(). + Returns load (positive value for discharging). + """ + dischargespeed = self.max_power if dischargespeed == "max" else dischargespeed + + if dischargespeed < 0: + raise ValueError( + f"Dischargespeed should be always be a positive value by convention. " + f"Inserted {dischargespeed}." + ) + + dischargespeed = self.set_load(dischargespeed) + + return dischargespeed + + def get_cost_per_cycle(self, cycle_lifetime): + return self.capex / self.cycle_lifetime + + +class EV(Battery): + def __init__( + self, + name, + rated_power, + rated_capacity, + roundtrip_eff, + min_soc=0, + max_soc=1, + soc_at_start=None, + id=None, + ): + super().__init__( + name, + rated_power, + rated_capacity, + roundtrip_eff, + min_soc, + max_soc, + soc_at_start, + ) + if id: + self.id = id + + +class HotWaterStorage(Battery): + """Subclass for a storage asset. + + Parameters: + ----------- + rated_capacity : int/float + Rated capacity in MWh + min_buffer_level_perc : float + Minimum buffer level in % + buffer_level_at_start : float + Buffer level at start in % + """ + + def __init__( + self, + name, + rated_power, + capacity_per_volume, + volume, + temperature, + min_storagelevel, + initial_storagelevel=None, + ): + rated_capacity = capacity_per_volume * volume + + if not initial_storagelevel: + initial_storagelevel = min_storagelevel + soc_at_start = initial_storagelevel / rated_capacity + max_storagelevel = rated_capacity * 0.95 + min_soc = min_storagelevel / rated_capacity + max_soc = max_storagelevel / rated_capacity + self.temperature = temperature + + super().__init__( + name=name, + rated_power=rated_power, + rated_capacity=rated_capacity, + roundtrip_eff=1, + min_soc=min_soc, + max_soc=max_soc, + soc_at_start=soc_at_start, + ) + + def __repr__(self): + return ( + f"{self.__class__.__name__}(name={self.name}, rated_power={self.max_power}, capacity={self.capacity}, " + f"temperature={self.temperature}, min_storagelevel={self.min_chargelevel})" + ) + + @property + def charging_power_limit(self): + max_charging_energy = self.max_chargelevel - self.chargelevel + return min(self.MWh_to_MW(max_charging_energy), -self.min_power) + + @property + def discharging_power_limit(self): + max_discharging_energy = self.chargelevel - self.min_chargelevel + return min(self.MWh_to_MW(max_discharging_energy), self.max_power) + + +class GasBoiler(Asset): + """Representation of a Gas-fired boiler. + + name : str + Unique name of the asset + max_th_output : numeric + Maximum thermal output in MW thermal + efficiency : float + Thermal efficiency of the gasboiler as decimal value. + min_th_output : numeric + Minimum thermal output in MW thermal + """ + + def __init__( + self, + name, + max_th_output, + min_th_output=0, + efficiency=0.9, + ): + super().__init__(name=name, max_power=max_th_output, min_power=min_th_output) + self.efficiency = efficiency + + def __repr__(self): + return ( + f"{self.__class__.__name__}(name={self.name}, max_power={self.max_power}, " + f"min_power={self.min_power}, efficiency={self.efficiency})" + ) + + def set_load(self, *args, **kwargs): + raise NotImplementedError( + "Gasboiler does not have electric load. " + "Use Gasboiler.set_heat_output() instead." + ) + + @lru_cache(maxsize=None) + def set_heat_output(self, output): + """Redirect to Gasboiler.set_load()""" + heat_output = super().set_load(output) + gas_input = -heat_output / self.efficiency + return heat_output, gas_input + + +class Electrolyser(Asset): + def __init__( + self, + name, + rated_power, + kwh_per_kg=60, + min_flex_load_in_perc=15, + ): + min_flex_power = min_flex_load_in_perc / 100 * rated_power + + super().__init__(name=name, max_power=-min_flex_power, min_power=-rated_power) + + self.rated_power = rated_power + self.min_flex_load = min_flex_load_in_perc + self.min_flex_power = self.min_flex_load / 100 * self.rated_power + self.kwh_per_kg = kwh_per_kg + self.kg_per_MWh = 1000 / self.kwh_per_kg + + def __repr__(self): + return ( + f"Electrolyser(name={self.name}, rated_power={self.rated_power}, " + f"kwh_per_kg={self.kwh_per_kg}, flex_range_in_perc=[{self.min_flex_load}, " + f"{self.max_flex_load}])" + ) + + def set_load(self, load): + """Set load of the Electrolyser in MW.""" + if not hasattr(self, "freq"): + raise AttributeError( + "Time frequency of the model is not defined. " + "Assign asset to a CaseStudy or use Asset.freq(). " + "to set de time frequency and try again." + ) + + load = -abs(load) + load = super().set_load(load) + + h2_output_kg = self.MW_to_MWh(-load) * self.kg_per_MWh + return load, h2_output_kg + + +class Battolyser(Asset): + def __init__( + self, + name, + rated_power, + rated_capacity, + rt_eff, + soc_at_start=None, + ): + super().__init__(name=name, max_power=rated_power, min_power=-rated_power) + + self.capacity = rated_capacity + self.min_soc = 0.05 + self.max_soc = 1.00 + self.min_chargelevel = self.min_soc * self.capacity + self.max_chargelevel = self.max_soc * self.capacity + self.rt_eff = rt_eff + self.cycle_count = 0 + + soc_at_start = self.min_soc if soc_at_start is None else soc_at_start + self.set_chargelevel(soc_at_start * self.capacity) + + def __repr__(self): + return ( + f"Battolyser(name={self.name}, rated_power={self.max_power}, " + f"rated_capacity={self.capacity}, rt_eff={self.rt_eff})" + ) + + def get_soc(self): + """Get the SoC in % (decimal value)""" + return self.chargelevel / self.capacity + + def set_chargelevel(self, chargelevel): + """Set the chargelevel in MWh. Will automatically change the SoC accordingly.""" + if chargelevel < self.min_chargelevel or chargelevel > self.max_chargelevel: + raise ValueError( + f"Tried to set Charge Level to {chargelevel}. " + f"Charge Level must be a value between " + f"{self.min_chargelevel} and {self.max_chargelevel} (in MWh)" + ) + self.chargelevel = chargelevel + + def set_load(self, load): + """Set load of the Battolyser in MW. + + Use negative values for charging and positive values for discharging. + Returns actual chargespeed, considering technical limitations of the battery. + + Note: We currently assume all efficiency losses occur during discharging + (no losses during charging) + """ + if not hasattr(self, "freq"): + raise AttributeError( + "Time frequency of the model is not defined. " + "Assign asset to a CaseStudy or use Asset.freq(). " + "to set de time frequency and try again." + ) + + load = super().set_load(load) + + unbound_charging = self.MW_to_MWh(load) + if load > 0: + unbound_charging /= self.rt_eff + + chargelevel = self.chargelevel + max_charging = chargelevel - self.max_chargelevel + max_discharging = chargelevel - self.min_chargelevel + bound_charging = min(max(unbound_charging, max_charging), max_discharging) + newcl = chargelevel - bound_charging + self.set_chargelevel(newcl) + + if bound_charging > 0: + bound_charging *= self.rt_eff + charging_power = self.MWh_to_MW(bound_charging) + h2_power = -self.MWh_to_MW(max(bound_charging - unbound_charging, 0)) + self.cycle_count += abs(bound_charging / (self.capacity * 2)) + return charging_power, h2_power + + def charge(self, chargespeed): + """Charge the battery with given chargespeed. + + Redirects to Battery.set_load(). + Returns load (negative value for charging). + """ + chargespeed = self.max_power if chargespeed == "max" else chargespeed + + if chargespeed < 0: + raise ValueError( + f"Chargespeed should be always be a positive value by convention. " + f"Inserted {chargespeed}." + ) + + chargespeed, h2_prod_in_MW = self.set_load(-chargespeed) + + return chargespeed, h2_prod_in_MW + + def discharge(self, dischargespeed): + """Discharge the battery by given amount. + + Redirects to Battery.set_load(). + Returns load (positive value for discharging). + """ + dischargespeed = self.max_power if dischargespeed == "max" else dischargespeed + + if dischargespeed < 0: + raise ValueError( + f"Dischargespeed should be always be a positive value by convention. " + f"Inserted {dischargespeed}." + ) + + dischargespeed = self.set_load(dischargespeed)[0] + return dischargespeed + + +##Added by Shahla, very similar to Hotwaterstorage +class HeatBuffer(Battery): + """Subclass for a storage asset. + + Parameters: + ----------- + rated_capacity : int/float + Rated capacity in MWh + min_buffer_level_perc : float + Minimum buffer level in % + buffer_level_at_start : float + Buffer level at start in % + """ + + def __init__( + self, name, rated_capacity, min_buffer_level_perc, buffer_level_at_start + ): + super().__init__( + name=name, + rated_power=100, + rated_capacity=rated_capacity, + roundtrip_eff=1, + min_soc=min_buffer_level_perc, + max_soc=1, + soc_at_start=buffer_level_at_start, + ) diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/basepath.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/basepath.py new file mode 100644 index 0000000..9afd49e --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/basepath.py @@ -0,0 +1,11 @@ +import os +from pathlib import Path + +# if os.environ.get("USERNAME") == "mekre": +# BASEPATH = Path("C:\\Users\\mekre\\") +# elif os.environ.get("USERNAME") == "Karel van Doesburg": +# BASEPATH = Path("C:\\RecoyShare\\") +# elif os.environ.get("USERNAME") == "Shahla Huseynova": +# BASEPATH = Path("C:\\Users\\Shahla Huseynova\\") +# elif os.environ.get("USERNAME") == "shahla.huseynova": +BASEPATH = Path("(C:\\Users\\shahla.huseynova\\Heliox Group B.V\\") diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/casestudy.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/casestudy.py new file mode 100644 index 0000000..9feb669 --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/casestudy.py @@ -0,0 +1,537 @@ +import warnings +from copy import deepcopy + +import numpy as np +import pandas as pd + +from .framework import TimeFramework +from .financial import ( + calc_business_case, + calc_co2_costs, + calc_electr_market_results, + calc_grid_costs, + calculate_eb_ode, +) +from .forecasts import Mipf, Qipf +from .prices import get_ets_prices, get_ttf_prices +from .converters import EURpertonCO2_to_EURperMWh + + +class CaseStudy: + """ + Representation of a casestudy + """ + + instances = {} + + def __init__(self, time_fw: TimeFramework, freq, name, data=None, forecast=None): + self.name = name + self.modelled_time_period_years = time_fw.modelled_time_period_years + self.start = time_fw.start + self.end = time_fw.end + self.freq = freq + self.dt_index = time_fw.dt_index(freq) + self.data = pd.DataFrame(index=self.dt_index) + self.assets = {} + self.cashflows = {} + self.irregular_cashflows = {} + self.capex = {} + self.total_capex = 0 + self.kpis = {} + + amount_of_days_in_year = 365 + + if self.start.year % 4 == 0: + amount_of_days_in_year = 366 + + self.year_case_duration = (self.end - self.start).total_seconds() / ( + 3600 * 24 * amount_of_days_in_year + ) + self.days_case_duration = self.year_case_duration * amount_of_days_in_year + self.hours_case_duration = self.days_case_duration * 24 + self.quarters_case_duration = self.days_case_duration * 24 * 4 + self.minutes_case_duration = self.days_case_duration * 24 * 60 + # self.year_case_duration = 1 + + if data is not None: + if len(data) != len(self.data): + raise ValueError( + "Length of data is not same as length of CaseStudy.data" + ) + data.index = self.dt_index + self.data = pd.concat([self.data, data], axis=1) + + if forecast is not None: + self.add_forecast(forecast, freq) + + CaseStudy.instances[self.name] = self + + @classmethod + def list_instances(cls): + """ + Returns a list with all CaseStudy instances. + Useful if you want to iterate over all instances + or use them as input to a function. + """ + return list(cls.instances.values()) + + def add_forecast(self, forecast, freq): + """ + Add forecast and price data to the data table of the CaseStudy instance. + """ + # TODO Add error handling for frequencies + if forecast == "mipf" and freq == "1T": + forecast_data = Mipf( + start=self.start, end=self.end, tidy=True, include_nextQ=False + ).data + elif forecast == "mipf" and freq == "15T": + forecast_data = Mipf( + start=self.start, end=self.end, tidy=False, include_nextQ=False + ).data + elif forecast == "qipf": + forecast_data = Qipf(start=self.start, end=self.end, freq=self.freq).data + else: + raise ValueError("Forecast does not exist. Use 'mipf' or 'qipf'.") + + self.data = pd.concat([self.data, forecast_data], axis=1) + + def add_gasprices(self): + """ + Add gas price data (TTF day-head) to the data table of the CaseStudy instance. + """ + self.data["Gas prices (€/MWh)"] = get_ttf_prices( + start=self.start, end=self.end, freq=self.freq + )["Gas prices (€/MWh)"] + + def add_co2prices(self, perMWh=False): + """ + Add CO2 prices (ETS) data to the data table of the CaseStudy instance. + """ + self.data["CO2 prices (€/ton)"] = get_ets_prices( + start=self.start, end=self.end, freq=self.freq + )["CO2 prices (€/MWh)"] + + if perMWh: + self.data["CO2 prices (€/MWh)"] = EURpertonCO2_to_EURperMWh( + self.data["CO2 prices (€/ton)"] + ).round(2) + + def add_asset(self, asset): + """Assign an Asset instance to CaseStudy instance. + + Method will create a unique copy of the Asset instance. + If Asset contains financial information, + cashflows are automatically updated. + """ + assetcopy = deepcopy(asset) + assetcopy.set_freq(self.freq) + self.assets[assetcopy.name] = assetcopy + + if hasattr(assetcopy, "opex"): + self.add_cashflow(f"{assetcopy.name} OPEX (€)", -assetcopy.opex) + + if hasattr(assetcopy, "capex"): + self.add_capex(f"{assetcopy.name} CAPEX (€)", -assetcopy.capex) + + if hasattr(assetcopy, "devex"): + self.add_capex(f"{assetcopy.name} DEVEX (€)", -assetcopy.devex) + + def get_assets(self): + """Returns all Asset instances assigned to CaseStudy instance.""" + return list(self.assets.values()) + + def add_cashflow(self, label, amount): + """Add a yearly cashflow to the CaseStudy + + Convention is negative values for costs and positive values for revenue. + """ + self.cashflows[label] = round(amount, 2) + + def add_capex(self, label, amount): + """Add a capex component to the CaseStudy + + Convention is to use positive values + """ + capex = round(amount, 2) * -1 + self.capex[label] = capex + self.total_capex += capex + + def add_irregular_cashflow(self, amount, year): + base = self.irregular_cashflows[year] if year in self.irregular_cashflows else 0 + self.irregular_cashflows[year] = base + amount + + def generate_electr_market_results(self, real_col, nom_col=None): + """Generates a dictionary with results of the simulation on energy market. + + Dictionary is saved in CaseStudy.energy_market_results. + Total market result is automatically added to cashflow dictionary. + """ + if nom_col is None: + nom_col = "Nom. vol." + self.data[nom_col] = 0 + + data = calc_electr_market_results(self.data, nom_col=nom_col, real_col=real_col) + self.data = data + + total_produced = data["Prod. vol."].sum() + total_consumed = -data["Cons. vol."].sum() + + self.total_electricity_cons = total_consumed * (-1) + + selling = data[real_col] > 0 + mean_selling_price = ( + data["Combined Result"].where(selling).sum() / total_produced + if total_produced != 0 + else 0 + ) + + mean_buying_price = ( + data["Combined Result"].where(~selling).sum() / total_consumed * (-1) + if round(total_consumed, 2) != 0 + else 0 + ) + + total_comb_result = data["Combined Result"].sum() + + self.electr_market_results = { + "Total net volume (MWh)": data[real_col].sum(), + "Total exported to grid (MWh)": total_produced, + "Total consumed from grid (MWh)": total_consumed, + "Total nominated volume (MWh)": data[nom_col].sum(), + "Absolute imbalance volume (MWh)": data["Imb. vol."].abs().sum(), + "Mean selling price (€/MWh)": mean_selling_price, + "Mean buying price (€/MWh)": mean_buying_price, + "Total day-ahead result (€)": data["Day-Ahead Result"].sum(), + "Total POS result (€)": data["POS Result"].sum(), + "Total NEG result (€)": data["NEG Result"].sum(), + "Total imbalance result (€)": data["Imbalance Result"].sum(), + "Total combined result (€)": total_comb_result, + } + + self.electr_market_result = total_comb_result + self.add_cashflow("Result on electricity market (€)", total_comb_result) + + def add_gas_costs(self, gasvolumes_col, gasprice_col="Gas prices (€/MWh)"): + """Calculate gas costs and add to cashflows + + Parameters: + ----------- + gasprices_col : str + Column containing gas prices in CaseStudy.data dataframe + gasvolumes_col : str + List of column names containing gas volumes in CaseStudy.data dataframe + """ + gasprices = self.data[gasprice_col] + gasvolumes = self.data[gasvolumes_col].abs() + gas_costs = gasprices * gasvolumes * -1 + self.data["Gas commodity costs (€)"] = gas_costs + + self.total_gas_cons = gasvolumes.sum() + self.total_gas_costs = round(gas_costs.sum(), 2) + self.add_cashflow("Gas consumption costs (€)", self.total_gas_costs) + + def add_co2_costs( + self, volume_cols, co2_price_col="CO2 prices (€/ton)", fuel="gas" + ): + """Calculate co2 costs and add to cashflows + + Parameters: + ----------- + Gasprices : str + Column containing gas prices in CaseStudy.data dataframe + Gasvolumes : list + List of column names containing gas volumes in CaseStudy.data dataframe + """ + if isinstance(volume_cols, str): + volume_cols = [volume_cols] + + co2_prices = self.data[co2_price_col] + volumes = [self.data[col] for col in volume_cols] + self.total_co2_costs = calc_co2_costs( + co2_prices=co2_prices, volumes=volumes, fuel=fuel + ) + self.add_cashflow("CO2 emission costs (€)", self.total_co2_costs) + + def add_eb_ode( + self, + commodity, + cons_col=None, + tax_bracket=None, + base_cons=None, + horti=False, + m3=False, + add_cons_MWh=0, + year=2020, + split=False, + ): + """Add EB & ODE to cashflows + + See financial.calc_eb_ode() for more detailed documentation. + + Parameters: + ----------- + commodity : str + {'gas', 'electricity'} + cons_col : str + Optional parameter to specificy column name of the + consumption values in MWh. + tax_bracket : numeric + Tax bracket that the client is in [1-4] + Use either 'tax_bracket' of 'base_cons', not both. + base_cons : numeric + Base consumption volume of the client + Use either 'tax_bracket' of 'base_cons', not both. + horti : bool + Set to True to use horticulture rates + m3 : bool + Set to True if you want to enter gas volumes in m3 + add_cons_MWh : + Enables manually adding extra consumption + """ + if cons_col: + cons = self.data[cons_col].abs().sum() + else: + cons = getattr(self, f"total_{commodity}_cons") + + cons = cons + add_cons_MWh + + eb, ode = calculate_eb_ode( + cons=cons, + electr=(commodity == "electricity"), + tax_bracket=tax_bracket, + base_cons=base_cons, + horti=horti, + m3=m3, + year=year, + ) + if split: + self.add_cashflow(f"EB {commodity.capitalize()} (€)", eb) + self.add_cashflow(f"ODE {commodity.capitalize()} (€)", ode) + else: + self.add_cashflow(f"{commodity.capitalize()} taxes (€)", eb + ode) + + def add_grid_costs( + self, + power_MW_col, + grid_operator, + year, + connection_type, + cons_MWh_col=None, + kw_contract_kW=None, + path=None, + add_peak_kW=0, + add_cons_MWh=0, + ): + """Add variable grid transport costs to cashflows + + See financial.calc_grid_costs() for more detailed documentation. + + Parameters: + ----------- + power_MW_col : str + Column in data table with power usage in MW + grid_operator : str + {'tennet', 'liander', 'enexis', 'stedin'} + year : int + Year, e.g. 2020 + connection_type : str + Connection type, e.g. 'HS' + cons_MWh_col : str + Column in data table containing grid consumption in MWh + kw_contract_kW : numeric + in kW. If provided, function will assume fixed value kW contract + path : str + Specify path with grid tariff files. Leave empty to use default path. + add_peak_kW : float + Enables manually adding peak consumption to the data + """ + cols = [power_MW_col] + if cons_MWh_col is not None: + cols.append(cons_MWh_col) + + peaks_kW = ( + (self.data[power_MW_col] * 1000 - add_peak_kW) + .resample("15T") + .mean() + .abs() + .resample("M") + .max() + .to_list() + ) + + cons_kWh = ( + self.data[cons_MWh_col].sum() * 1000 if cons_MWh_col is not None else 0 + ) + add_cons_MWh + + self.grid_costs = calc_grid_costs( + peakload_kW=peaks_kW, + grid_operator=grid_operator, + year=year, + connection_type=connection_type, + kw_contract_kW=kw_contract_kW, + totalcons_kWh=cons_kWh, + path=path, + modelled_time_period_years=self.modelled_time_period_years, + ) + + total_grid_costs = sum(self.grid_costs.values()) + + self.add_cashflow("Grid transport costs (€)", total_grid_costs) + + def calculate_ebitda(self, project_duration, residual_value=None): + """Calculate yearly EBITDA based on cashflows + + Calculation table and EBITDA value are saved in CaseStudy. + """ + for key, val in self.cashflows.items(): + if np.isnan(val): + warnings.warn( + f"Cashflow '{key}' for CaseStudy '{self.name}' contains NaN value. " + "Something might have gone wrong. Replacing NaN with 0 for now." + ) + self.cashflows[key] = 0 + + assets = self.get_assets() + + for asset in assets: + if not asset.depreciate: + pass + elif asset.lifetime is None: + raise ValueError(f"'lifetime' property of {asset.name} was not set.") + elif project_duration > asset.lifetime: + warnings.warn( + f"Project duration is larger than technical lifetime of asset '{asset.name}'. " + "Will continue by limiting project duration to the technical lifetime of the asset." + ) + project_duration = int(asset.lifetime) + + depreciations, residual_value = CaseStudy._calc_depr_and_residual_val( + assets, self.total_capex, residual_value, project_duration + ) + + depreciations = self.total_capex / project_duration + + self.ebitda = sum(self.cashflows.values()) + self.ebitda_calc = deepcopy(self.cashflows) + self.ebitda_calc["EBITDA (€)"] = self.ebitda + self.ebitda_calc["Depreciation (€)"] = depreciations * -1 + self.ebitda_calc["EBITDA + depr (€)"] = self.ebitda + depreciations * -1 + + def calculate_business_case( + self, + project_duration, + discount_rate, + residual_value=None, + baseline=None, + bl_res_value=None, + eia=False, + vamil=False, + fixed_income_tax=False, + ): + """Calculates business case (NPV, IRR) for the CaseStudy. + + Business case calculation is stored in CaseStudy.business_case + NPV is stored in CaseStudy.npv + IRR is stored in Casestudy.irr + + Parameters: + ----------- + project_duration : int + In years + discount_rate : float + In % (decimal value) + residual_value : numeric + Can be used to manually set residual value of assets (all assets combined). + + Defaults to None, in which case residual_value is calculated + based on linear depreciation over technical lifetime. + baseline : CaseStudy + Baseline to compare against + bl_res_value : numeric + Similar to 'residual_value' for baseline + eia : bool + Apply EIA ("Energie Investerings Aftrek") tax discounts. + Defaults to False. + vamil : bool + Apply VAMIL ("Willekeurige afschrijving milieu-investeringen") tax discounts. + Defaults to False. + """ + + assets = self.get_assets() + + for asset in assets: + if not asset.depreciate: + pass + elif asset.lifetime is None: + raise ValueError(f"'lifetime' property of {asset.name} was not set.") + elif project_duration > asset.lifetime: + warnings.warn( + f"Project duration is larger than technical lifetime of asset '{asset.name}'. " + "Will continue by limiting project duration to the technical lifetime of the asset." + ) + project_duration = int(asset.lifetime) + + capex = self.total_capex + yearly_ebitda = self.ebitda / self.modelled_time_period_years + + irregular_cashflows = ( + self._calc_irregular_cashflows(project_duration, baseline=baseline) + if self.irregular_cashflows + else 0 + ) + + depreciations, residual_value = CaseStudy._calc_depr_and_residual_val( + assets, capex, residual_value, project_duration + ) + + if baseline is not None: + bl_assets = baseline.assets.values() + bl_capex = baseline.total_capex + bl_depr, bl_res_val = CaseStudy._calc_depr_and_residual_val( + bl_assets, bl_capex, bl_res_value, project_duration + ) + capex -= bl_capex + depreciations -= bl_depr + residual_value -= bl_res_val + yearly_ebitda -= baseline.ebitda / self.modelled_time_period_years + + self.business_case = calc_business_case( + capex=capex, + discount_rate=discount_rate, + project_duration=project_duration, + depreciation=depreciations, + residual_value=residual_value, + regular_earnings=yearly_ebitda, + irregular_cashflows=irregular_cashflows, + eia=eia, + vamil=vamil, + fixed_income_tax=fixed_income_tax, + ) + + self.irr = self.business_case.loc["IRR (%)", "Year 0"] / 100 + self.npv = self.business_case.loc["NPV (€)", "Year 0"] + self.spp = self.business_case.loc["Simple Payback Period", "Year 0"] + + @staticmethod + def _calc_depr_and_residual_val(assets, capex, residual_value, project_duration): + if residual_value is None: + assets = [asset for asset in assets if asset.depreciate] + depreciations = sum( + (asset.capex - asset.salvage_value) / asset.lifetime for asset in assets + ) + residual_value = capex - depreciations * project_duration + else: + depreciations = (capex - residual_value) / project_duration + + return depreciations, residual_value + + def _calc_irregular_cashflows(self, project_duration, baseline=None): + irr_earnings = [0] * (project_duration) + + for year, cashflow in self.irregular_cashflows.items(): + if baseline: + cashflow -= baseline.irregular_cashflows.get(year, 0) + + irr_earnings[int(year) - 1] = cashflow + + return irr_earnings diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/colors.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/colors.py new file mode 100644 index 0000000..1660c9e --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/colors.py @@ -0,0 +1,43 @@ +recoy_colordict = { + "RecoyDarkBlue": "#0e293b", + "RecoyBlue": "#1f8376", + "RecoyRed": "#dd433b", + "RecoyYellow": "#f3d268", + "RecoyGreen": "#46a579", + "RecoyPurple": "#6d526b", + "RecoyOrange": "#f2a541", + "RecoyBlueGrey": "#145561", + "RecoyDarkGrey": "#2a2a2a", + "RecoyLilac": "#C3ACCE", + "RecoyBrown": "#825E52", + "RecoyLightGreen": "#7E9181", + "RecoyCitron": "#CFD186", + "RecoyPink": "#F5B3B3" +} + +recoy_greysdict = { + "RecoyLightGrey": "#e6e6e6", + "RecoyGrey": "#c0c0c0", + "RecoyDarkGrey": "#2a2a2a", +} + +recoydarkblue = recoy_colordict["RecoyDarkBlue"] +recoyyellow = recoy_colordict["RecoyYellow"] +recoygreen = recoy_colordict["RecoyGreen"] +recoyred = recoy_colordict["RecoyRed"] +recoyblue = recoy_colordict["RecoyBlue"] +recoyorange = recoy_colordict["RecoyOrange"] +recoypurple = recoy_colordict["RecoyPurple"] +recoybluegrey = recoy_colordict["RecoyBlueGrey"] +recoylightgrey = recoy_greysdict["RecoyLightGrey"] +recoygrey = recoy_greysdict["RecoyGrey"] +recoydarkgrey = recoy_greysdict["RecoyDarkGrey"] +recoylilac = recoy_colordict["RecoyLilac"] +recoybrown = recoy_colordict["RecoyBrown"] +recoylightgreen = recoy_colordict["RecoyLightGreen"] +recoycitron = recoy_colordict["RecoyCitron"] +recoypink = recoy_colordict["RecoyPink"] + +recoycolors = list(recoy_colordict.values()) + +transparent = "rgba(0, 0, 0, 0)" diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/converters.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/converters.py new file mode 100644 index 0000000..6b5b239 --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/converters.py @@ -0,0 +1,66 @@ +import pandas as pd + + +def MWh_to_m3(MWh): + return MWh / 9.769 * 1000 + + +def MWh_to_GJ(MWh): + return MWh * 3.6 + + +def EURperm3_to_EURperMWh(EURperm3): + return EURperm3 / 9.769 * 1000 + + +def EURperMWh_to_EURperGJ(EURperMWh): + return EURperMWh * 3.6 + + +def MWh_gas_to_tonnes_CO2(MWh): + return MWh * 1.84 / 9.769 + + +def EURpertonCO2_to_EURperMWh(EURpertonCO2): + return EURpertonCO2 * 1.884 / 9.769 + + +def EURperLHV_to_EURperHHV(MWh_LHV): + return MWh_LHV / 35.17 * 31.65 + + +def EURperHHV_to_EURperLHV(MWh_HHV): + return MWh_HHV / 31.65 * 35.17 + + +def GJ_gas_to_kg_NOX(GJ): + return GJ * 0.02 + + +def MWh_gas_to_kg_NOX(MWh): + return GJ_gas_to_kg_NOX(MWh_to_GJ(MWh)) + + +def fastround(n, decimals): + """Round a value to certain number of decimals, faster than Python implementation""" + multiplier = 10**decimals + return int(n * multiplier + 0.5) / multiplier + + +def add_season_column(data): + """Adds a column containing seasons to a DataFrame with datetime index""" + data["season"] = (data.index.month % 12 + 3) // 3 + + seasons = {1: "Winter", 2: "Spring", 3: "Summer", 4: "Fall"} + data["season"] = data["season"].map(seasons) + return data + + +def dt_column_to_local_time(column): + return column.dt.tz_localize("UTC").dt.tz_convert("Europe/Amsterdam") + + +def timestamp_to_utc(timestamp): + if isinstance(timestamp, str): + timestamp = pd.to_datetime(timestamp).tz_localize("Europe/Amsterdam") + return timestamp.tz_convert("UTC") diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/databases.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/databases.py new file mode 100644 index 0000000..9654153 --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/databases.py @@ -0,0 +1,69 @@ +from sqlalchemy import create_engine, MetaData, Table +import pandas as pd + +DATABASES = { + "ngsc_dev": { + "db_url": "ngsc-dev-msql.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "ngsc_dev", + "db_user": "ngsc_dev", + "db_password": "AKIAZQ2BV5F5K6LLBC47", + "db_port": "1433", + }, + "ngsc_test": { + "db_url": "ngsc-test-msql.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "ngsc_test", + "db_user": "ngsc_test", + "db_password": "AKIAZQ2BV5F5K6LLBC47", + "db_port": "1433", + }, + "ngsc_prod": { + "db_url": "rop-ngsc-prod.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "ngsc_test", + "db_user": "ngsc_test", + "db_password": "AKIAZQ2BV5F5K6LLBC47", + "db_port": "1433", + }, + "rop_prices_test": { + "db_url": "rop-prices-test.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "test", + "db_user": "rop", + "db_password": "OptimalTransition", + "db_port": "8472", + }, + "rop_assets_test": { + "db_url": "rop-assets-test.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "test", + "db_user": "rop", + "db_password": "OptimalTransition", + "db_port": "1433", + }, +} + + +def db_engine(db_name): + db_config = DATABASES[db_name] + + connection_string = ( + f"mssql+pyodbc://{db_config['db_user']}:{db_config['db_password']}" + f"@{db_config['db_url']}:{db_config['db_port']}/" + f"{db_config['db_name']}?driver=ODBC+Driver+17+for+SQL+Server" + ) + return create_engine(connection_string) + + +def read_entire_table(table_name, db_engine): + return pd.read_sql_table(table_name, db_engine) + + +def create_connection(engine, tables): + connection = engine.connect() + metadata = MetaData() + + if isinstance(tables, str): + return connection, Table(tables, metadata, autoload=True, autoload_with=engine) + else: + db_tables = { + table: Table(table, metadata, autoload=True, autoload_with=engine) + for table in tables + } + return connection, db_tables diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/decorators.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/decorators.py new file mode 100644 index 0000000..135cb2d --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/decorators.py @@ -0,0 +1,17 @@ +import time +from functools import wraps + + +def time_method(func): + """Prints the runtime of a method of a class.""" + + @wraps(func) + def wrapper_timer(self, *args, **kwargs): + start = time.perf_counter() + value = func(self, *args, **kwargs) + end = time.perf_counter() + run_time = end - start + print(f"Finished running {self.name} in {run_time:.2f} seconds.") + return value + + return wrapper_timer diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/financial.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/financial.py new file mode 100644 index 0000000..166a134 --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/financial.py @@ -0,0 +1,667 @@ +from pathlib import Path +from datetime import timedelta + +import numpy as np +import numpy_financial as npf +import pandas as pd + +import warnings + + +def npv(discount_rate, cashflows): + cashflows = np.array(cashflows) + print('discount rate',discount_rate) + print('cashflows', cashflows) + print('answer', cashflows / (1 + discount_rate) ** np.arange(1, len(cashflows) + 1)) + return (cashflows / (1 + discount_rate) ** np.arange(1, len(cashflows) + 1)).sum( + axis=0 + ) + + +def calc_electr_market_results(model, nom_col=None, real_col=None): + """Function to calculate the financial result on Day-Ahead and Imbalance market for the input model. + + Parameters: + ----------- + model : df + DataFrame containing at least 'DAM', 'POS' and 'NEG' columns. + nom_col : str + Name of the column containing the Day-Ahead nominations in MWh + Negative values = Buy, positive values = Sell + imb_col : str + Name of the column containing the Imbalance volumes in MWh + Negative values = Buy, positive values = Sell + + Returns: + -------- + Original df with added columns showing the financial results per timeunit. + """ + if nom_col is None: + nom_col = "Nom. vol." + model[nom_col] = 0 + + producing = model[real_col] > 0 + model["Prod. vol."] = model[real_col].where(producing, other=0) + model["Cons. vol."] = model[real_col].where(~producing, other=0) + model["Imb. vol."] = model[real_col] - model[nom_col] + + model["Day-Ahead Result"] = model[nom_col] * model["DAM"] + model["POS Result"] = 0 + model["NEG Result"] = 0 + posimb = model["Imb. vol."] > 0 + model["POS Result"] = model["POS"] * model["Imb. vol."].where(posimb, other=0) + model["NEG Result"] = model["NEG"] * model["Imb. vol."].where(~posimb, other=0) + model["Imbalance Result"] = model["POS Result"] + model["NEG Result"] + model["Combined Result"] = model["Day-Ahead Result"] + model["Imbalance Result"] + return model + + +def calc_co2_costs(co2_prices, volumes, fuel): + """Calculates gas market results + + Parameters: + ----------- + co2_prices : numeric or array + CO2 prices in €/ton + volumes : list + List of arrays containing volumes + fuel : list + List of arrays containing gas volumes + + Returns: + -------- + Returns a single negative value (=costs) in € + """ + if not isinstance(volumes, list): + volumes = [volumes] + + emission_factors = { + "gas": 1.884 / 9.769 + } # in ton/MWh (based on 1.884 kg CO2/Nm3, 9.769 kWh/Nm3) + + if fuel not in emission_factors.keys(): + raise NotImplementedError( + f"Emission factor for chosen fuel '{fuel}' is not implemented." + f"Implement it by adding emission factor to the 'emission_factors' table." + ) + + emission_factor = emission_factors[fuel] + return -round( + abs(sum((array * emission_factor * co2_prices).sum() for array in volumes)), 2 + ) + + +def calculate_eb_ode( + cons, + electr=True, + year=2020, + tax_bracket=None, + base_cons=None, + horti=False, + m3=False, +): + """Calculates energy tax and ODE for consumption of electricity or natural gas in given year. + + Function calculates total tax to be payed for electricity or natural gas consumption, + consisting of energy tax ('Energiebelasting') and sustainable energy surcharge ('Opslag Duurzame Energie'). + + Tax bracket that applies is based on consumption level, with a different tax + rate for each bracket. + + For Gas + 1: 0 - 170.000 m3 + 3: 170.000 - 1 mln. m3 + 4: 1 mln. - 10 mln. m3 + 5: > 10 mln. m3 + + For Electricity + 1: 0 - 10 MWh + 2: 10 - 50 MWh + 3: 50 - 10.000 MWh + 4: > 10.000 MWh + + Parameters: + ----------- + cons : numeric + Total consumption in given year for which to calculate taxes. + Electricity consumption in MWh + Gas consumption in MWh (or m3 and use m3=True) + electr : bool + Set to False for natural gas rates. Default is True. + year : int + Year for which tax rates should be used. Tax rates are updated + annually and can differ significantly. + tax_bracket : int + Tax bracket (1-4) to assume. + Parameter can not be used in conjunction ith 'base_cons'. + base_cons : numeric + Baseline consumption to assume, in same unit as 'cons'. + Specified value is used to decide what tax bracket to start in. + Taxes from baseline consumption are not included in calculation of the tax amount. + Parameter can not be used in conjunction with 'tax_bracket'. + horti : bool + The horticulture sector gets a discount on gas taxes. + m3 : bool + Set to True if you want to enter gas consumption in m3. + Default is to enter consumption in MWh. + + Returns: + -------- + Total tax amount as negative number (costs). + + Note: + ----- + This function is rather complicated, due to all its optionalities. + Should probably be simplified or split into different functions. + """ + if tax_bracket is not None and base_cons is not None: + raise ValueError( + "Parameters 'tax_bracket' and 'base_cons' can not be used at the same time." + ) + if tax_bracket is None and base_cons is None: + raise ValueError( + "Function requires input for either 'tax_bracket' or 'base_cons'." + ) + + cons = abs(cons) + commodity = "electricity" if electr else "gas" + + if commodity == "gas": + if not m3: + cons /= 9.769 / 1000 # Conversion factor for gas: 1 m3 = 9.769 kWh + base_cons = base_cons / (9.769 / 1000) if base_cons is not None else None + else: + cons *= 1000 # conversion MWh to kWh + base_cons = base_cons * 1000 if base_cons is not None else None + + tax_brackets = { + "gas": [0, 170_000, 1_000_000, 10_000_000], + "electricity": [0, 10_000, 50_000, 10_000_000], + } + tax_brackets = tax_brackets[commodity] + base_cons = tax_brackets[tax_bracket - 1] if tax_bracket else base_cons + + if commodity == "gas" and horti: + commodity += "_horticulture" + eb_rates, ode_rates = get_tax_tables(commodity) + + eb = 0 + ode = 0 + + for bracket in range(4): + if bracket < 3: + br_lower_limit = tax_brackets[bracket] + br_upper_limit = tax_brackets[bracket + 1] + if base_cons > br_upper_limit: + continue + bracket_size = br_upper_limit - max(br_lower_limit, base_cons) + cons_in_bracket = min(cons, bracket_size) + else: + cons_in_bracket = cons + + # print(eb_rates.columns[bracket], cons_in_bracket, round(eb_rates.loc[year, eb_rates.columns[bracket]], 6), round(eb_rates.loc[year, eb_rates.columns[bracket]] * cons_in_bracket,2)) + eb += eb_rates.loc[year, eb_rates.columns[bracket]] * cons_in_bracket + ode += ode_rates.loc[year, ode_rates.columns[bracket]] * cons_in_bracket + cons -= cons_in_bracket + + if cons == 0: + break + + return -round(eb, 2), -round(ode, 2) + + +def get_tax_tables(commodity): + """Get EB and ODE tax rate tables from json files. + + Returns two tax rate tables as DataFrame. + If table is not up-to-date, try use update_tax_tables() function. + """ + folder = Path(__file__).resolve().parent / "data" / "tax_tariffs" + eb_table = pd.read_json(folder / f"{commodity}_eb.json") + ode_table = pd.read_json(folder / f"{commodity}_ode.json") + + if commodity == "electricity": + ode_table.drop(columns=ode_table.columns[3], inplace=True) + else: + eb_table.drop(columns=eb_table.columns[0], inplace=True) + + if commodity != "gas_horticulture": + eb_table.drop(columns=eb_table.columns[3], inplace=True) + return eb_table, ode_table + + +def get_tax_rate(commodity, year, tax_bracket, perMWh=True): + """Get tax rate for specific year and tax bracket. + + Parameters: + ----------- + commodity : str + {'gas' or 'electricity'} + year : int + {2013 - current year} + tax_bracket : int + {1 - 4} + + For Gas: + 1: 0 - 170.000 m3 + 3: 170.000 - 1 mln. m3 + 4: 1 mln. - 10 mln. m3 + 5: > 10 mln. m3 + + For Electricity: + 1: 0 - 10 MWh + 2: 10 - 50 MWh + 3: 50 - 10.000 MWh + 4: > 10.000 MWh + perMWh : bool + Defaults to True. Will return rates (for gas) in €/MWh instead of €/m3. + + Returns: + -------- + Dictionary with EB, ODE and combined rates (in €/MWh for electricity and €/m3 for gas) + {'EB' : float + 'ODE' : float, + 'EB+ODE' : float} + """ + eb_table, ode_table = get_tax_tables(commodity) + + eb_rate = eb_table.loc[year, :].iloc[tax_bracket - 1].astype(float).round(5) * 1000 + ode_rate = ( + ode_table.loc[year, :].iloc[tax_bracket - 1].astype(float).round(5) * 1000 + ) + + if commodity == "gas" and perMWh == True: + eb_rate /= 9.769 + ode_rate /= 9.769 + + comb_rate = (eb_rate + ode_rate).round(5) + return {"EB": eb_rate, "ODE": ode_rate, "EB+ODE": comb_rate} + + +def update_tax_tables(): + """Function to get EB and ODE tax rate tables from belastingdienst.nl and save as json file.""" + url = ( + "https://www.belastingdienst.nl/wps/wcm/connect/bldcontentnl/belastingdienst/" + "zakelijk/overige_belastingen/belastingen_op_milieugrondslag/tarieven_milieubelastingen/" + "tabellen_tarieven_milieubelastingen?projectid=6750bae7-383b-4c97-bc7a-802790bd1110" + ) + + tables = pd.read_html(url) + + table_index = { + 3: "gas_eb", + 4: "gas_horticulture_eb", + 6: "electricity_eb", + 8: "gas_ode", + 9: "gas_horticulture_ode", + 10: "electricity_ode", + } + + for key, val in table_index.items(): + table = tables[key].astype(str) + table = table.applymap(lambda x: x.strip("*")) + table = table.applymap(lambda x: x.strip("€ ")) + table = table.applymap(lambda x: x.replace(",", ".")) + table = table.astype(float) + table["Jaar"] = table["Jaar"].astype(int) + table.set_index("Jaar", inplace=True) + path = Path(__file__).resolve().parent / "data" / "tax_tariffs" / f"{val}.json" + table.to_json(path) + + +def calc_grid_costs( + peakload_kW, + grid_operator, + year, + connection_type, + totalcons_kWh=0, + kw_contract_kW=None, + path=None, + modelled_time_period_years = 1 +): + """Calculate grid connection costs for one full year + + Parameters: + ----------- + peakload_kW : numeric or list + Peak load in kW. Can be single value (for entire year) or value per month (list). + grid_operator : str + {'tennet', 'liander', 'enexis', 'stedin'} + year : int + Year to get tariffs for, e.g. 2020 + connection_type : str + Type of grid connection, e.g. 'TS' or 'HS'. + Definitions are different for each grid operator. + totalcons_kWh : numeric + Total yearly consumption in kWh + kw_contract_kW : numeric + in kW. If provided, function will assume fixed value kW contract + path : str + Path to directory with grid tariff files. + Default is None; function will to look for default folder on SharePoint. + Returns: + -------- + Total variable grid connection costs in €/year (fixed costs 'vastrecht' nog included) + """ + + totalcons_kWh /= modelled_time_period_years + + tariffs = get_grid_tariffs_electricity(grid_operator, year, connection_type, path) + kw_max_kW = np.mean(peakload_kW) + max_peakload_kW = np.max(peakload_kW) + + if kw_contract_kW is None: + kw_contract_kW = False + + if bool(kw_contract_kW) & (kw_contract_kW < max_peakload_kW): + warnings.warn( + "Maximum peak consumption is higher than provided 'kw_contract' value." + "Will continue to assume max peak consumption as kW contract." + ) + kw_contract_kW = max_peakload_kW + + if not bool(kw_contract_kW): + kw_contract_kW = max_peakload_kW + + if (tariffs["kWh tarief"] != 0) and (totalcons_kWh is None): + raise ValueError( + "For this grid connection type a tariff for kWh has to be paid. " + "Therefore 'totalcons_kWh' can not be None." + ) + + return { + "Variable": -round(tariffs["kWh tarief"] * abs(totalcons_kWh) * modelled_time_period_years, 2), + "kW contract": -round(tariffs["kW contract per jaar"] * kw_contract_kW * modelled_time_period_years, 2), + "kW max": -round(tariffs["kW max per jaar"] * max_peakload_kW * modelled_time_period_years, 2), + } + + +def get_grid_tariffs_electricity(grid_operator, year, connection_type, path=None): + """Get grid tranposrt tariffs + + Parameters: + ----------- + grid_operator : str + {'tennet', 'liander', 'enexis', 'stedin'} + year : int + Year to get tariffs for, e.g. 2020 + connection_type : str + Type of grid connection, e.g. 'TS' or 'HS'. + Definitions are different for each grid operator. + path : str + Path to directory with grid tariff files. + Default is None; function will to look for default folder on SharePoint. + + Returns: + -------- + Dictionary containing grid tariffs in €/kW/year and €/kWh + """ + if path is None: + path = Path(__file__).resolve().parent / "data" / "grid_tariffs" + else: + path = Path(path) + + if not path.exists(): + raise SystemError( + f"Path '{path}' not found. Specify different path and try again." + ) + + filename = f"{grid_operator.lower()}_{year}.csv" + filepath = path / filename + + if not filepath.exists(): + raise NotImplementedError( + f"File '{filename}' does not exist. Files available: {[file.name for file in path.glob('*.csv')]}" + ) + + rates_table = pd.read_csv( + path / filename, sep=";", decimal=",", index_col="Aansluiting" + ) + + if connection_type not in rates_table.index: + raise ValueError( + f"The chosen connection type '{connection_type}' is not available " + f"for grid operator '{grid_operator}'. Please choose one of {list(rates_table.index)}." + ) + return rates_table.loc[connection_type, :].to_dict() + + +def income_tax(ebit, fixed_tax_rate): + """ + Calculates income tax based on EBIT. + 2021 tax rates + """ + if fixed_tax_rate: + return round(ebit * -0.25, 0) + if ebit > 245_000: + return round(245_000 * -0.15 + (ebit - 200_000) * -0.25, 2) + if ebit < 0: + return 0 + else: + return -round(ebit * 0.15, 2) + + +def calc_business_case( + capex, + discount_rate, + project_duration, + depreciation, + residual_value, + regular_earnings, + irregular_cashflows=0, + eia=False, + vamil=False, + fixed_income_tax=False +): + """Calculate NPV and IRR for business case. + + All input paremeters are either absolute or relative to a baseline. + + Parameters: + ----------- + capex : numeric + Total CAPEX or extra CAPEX compared to baseline + discount_rate : numeric + % as decimal value + project_duration : numeric + in years + depreciation : numeric of list + Yearly depreciation costs + residual_value : numeric + Residual value at end of project in €, total or compared to baseline. + regular_earnings : numeric + Regular earnings, usually EBITDA + irregular_cashflows : list + Pass list with value for each year. + eia : bool + Apply EIA ("Energie Investerings Aftrek") tax discounts. + Defaults to False. + vamil : bool + Apply VAMIL ("Willekeurige afschrijving milieu-investeringen") tax discounts. + Defaults to False. + + Returns: + -------- + DataFrame showing complete calculation resulting in NPV and IRR + """ + years = [f"Year {y}" for y in range(project_duration + 1)] + years_o = years[1:] + + bc_calc = pd.DataFrame(columns=years) + bc_calc.loc["CAPEX (€)", "Year 0"] = -capex + bc_calc.loc["Regular Earnings (€)", years_o] = regular_earnings + bc_calc.loc["Irregular Cashflows (€)", years_o] = irregular_cashflows + bc_calc.loc["EBITDA (€)", years_o] = ( + bc_calc.loc["Regular Earnings (€)", years_o] + + bc_calc.loc["Irregular Cashflows (€)", years_o] + ) + + depreciations = [depreciation] * project_duration + + if vamil: + ebitdas = bc_calc.loc["EBITDA (€)", years_o].to_list() + depreciations = _apply_vamil(depreciations, project_duration, ebitdas) + + bc_calc.loc["Depreciations (€) -/-", years_o] = np.array(depreciations) * -1 + bc_calc.loc["EBIT (€)", years_o] = ( + bc_calc.loc["EBITDA (€)", years_o] + + bc_calc.loc["Depreciations (€) -/-", years_o] + ) + + if eia: + bc_calc = _apply_eia(bc_calc, project_duration, capex, years_o) + + bc_calc.loc["Income tax (Vpb.) (€)", years_o] = bc_calc.loc["EBIT (€)", :].apply( + income_tax, args=[fixed_income_tax] + ) + + if eia: + bc_calc.loc["NOPLAT (€)", years_o] = ( + bc_calc.loc["EBIT before EIA (€)", :] + + bc_calc.loc["Income tax (Vpb.) (€)", years_o] + ) + else: + bc_calc.loc["NOPLAT (€)", years_o] = ( + bc_calc.loc["EBIT (€)", :] + bc_calc.loc["Income tax (Vpb.) (€)", years_o] + ) + + bc_calc.loc["Depreciations (€) +/+", years_o] = depreciations + bc_calc.loc["Free Cash Flow (€)", years] = ( + bc_calc.loc["CAPEX (€)", years].fillna(0) + + bc_calc.loc["NOPLAT (€)", years].fillna(0) + + bc_calc.loc["Depreciations (€) +/+", years].fillna(0) + ) + + spp = calc_simple_payback_time( + capex=capex, + free_cashflows=bc_calc.loc["Free Cash Flow (€)", years_o].values, + ) + bc_calc.loc["Simple Payback Period", "Year 0"] = spp + + try: + bc_calc.loc["IRR (%)", "Year 0"] = ( + npf.irr(bc_calc.loc["Free Cash Flow (€)", years].values) * 100 + ) + except: + bc_calc.loc["IRR (%)", "Year 0"] = np.nan + + bc_calc.loc["WACC (%)", "Year 0"] = discount_rate * 100 + + bc_calc.loc["NPV of explicit period (€)", "Year 0"] = npv( + discount_rate, bc_calc.loc["Free Cash Flow (€)"].values + ) + bc_calc.loc["Discounted residual value (€)", "Year 0"] = ( + residual_value / (1 + discount_rate) ** project_duration + ) + bc_calc.loc["NPV (€)", "Year 0"] = ( + bc_calc.loc["NPV of explicit period (€)", "Year 0"] + # + bc_calc.loc["Discounted residual value (€)", "Year 0"] + ) + + return bc_calc.round(2) + + +def calc_simple_payback_time(capex, free_cashflows): + if free_cashflows.sum() < capex: + return np.nan + + year = 0 + spp = 0 + while capex > 0: + cashflow = free_cashflows[year] + spp += min(capex, cashflow) / cashflow + capex -= cashflow + year += 1 + return round(spp, 1) + + +def _apply_vamil(depreciations, project_duration, ebitdas): + remaining_depr = sum(depreciations) + remaining_vamil = 0.75 * remaining_depr + for i in range(project_duration): + vamil_depr = min(ebitdas[i], remaining_vamil) if remaining_vamil > 0 else 0 + + if remaining_depr > 0: + lin_depr = remaining_depr / (project_duration - i) + depr = max(vamil_depr, lin_depr) + depreciations[i] = max(vamil_depr, lin_depr) + + remaining_vamil -= vamil_depr + remaining_depr -= depr + else: + depreciations[i] = 0 + + return depreciations + + +def _apply_eia(bc_calc, project_duration, capex, years_o): + remaining_eia = 0.45 * capex + eia_per_year = [0] * project_duration + bc_calc = bc_calc.rename(index={"EBIT (€)": "EBIT before EIA (€)"}) + ebits = bc_calc.loc["EBIT before EIA (€)", years_o].to_list() + eia_duration = min(10, project_duration) + for i in range(eia_duration): + if remaining_eia > 0: + eia_curr_year = max(min(remaining_eia, ebits[i]), 0) + eia_per_year[i] = eia_curr_year + remaining_eia -= eia_curr_year + else: + break + + bc_calc.loc["EIA (€)", years_o] = np.array(eia_per_year) * -1 + bc_calc.loc["EBIT (€)", :] = ( + bc_calc.loc["EBIT before EIA (€)", :] + bc_calc.loc["EIA (€)", :] + ) + return bc_calc + + +def calc_irf_value( + data, irf_volume, nomination_col=None, realisation_col=None, reco_col="reco" +): + """Calculate IRF value + + Takes a DataFrame [data] and returns the same DataFrame with a new column "IRF Value" + + Parameters + ---------- + data : DataFrame + DataFrame that contains data. Should include price data (DAM, POS and NEG). + irf_volume : int + Volume on IRF in MW. + nomination_col : str + Name of the column containing nomination data in MWh. + realisation_col : str + Name of the column containing realisation data in MWh. + reco_col : str + Name of the column contaning recommendations. + """ + if not nomination_col: + nomination_col = "zero_nom" + data[nomination_col] = 0 + + if not realisation_col: + realisation_col = "zero_nom" + data[realisation_col] = 0 + + conversion_factor = pd.to_timedelta(data.index.freq) / timedelta(hours=1) + + imb_pre_irf = data[realisation_col] - data[nomination_col] + result_pre_irf = ( + data[nomination_col] * data["DAM"] + + imb_pre_irf.where(imb_pre_irf > 0, other=0) * data["POS"] + + imb_pre_irf.where(imb_pre_irf < 0, other=0) * data["NEG"] + ) + + data["IRF Nom"] = ( + data[nomination_col] - data[reco_col] * irf_volume * conversion_factor + ) + data["IRF Imb"] = data[realisation_col] - data["IRF Nom"] + + result_post_irf = ( + data["IRF Nom"] * data["DAM"] + + data["IRF Imb"].where(data["IRF Imb"] > 0, other=0) * data["POS"] + + data["IRF Imb"].where(data["IRF Imb"] < 0, other=0) * data["NEG"] + ) + data["IRF Value"] = result_post_irf - result_pre_irf + + return data diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/forecasts.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/forecasts.py new file mode 100644 index 0000000..6bd3f71 --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/forecasts.py @@ -0,0 +1,343 @@ +import json +import os +import time +import pytz +from datetime import datetime, timedelta +from pathlib import Path + +import numpy as np +import pandas as pd +import requests +from pyrecoy.prices import * + + +class Forecast: + """Load dataset from SharePoint server as DataFrame in local datetime format. + + Parameters: + ---------- + filename : str + Name of the csv file, e.g. "marketprices_nl.csv" + start : datetime + Startdate of the dataset + end : datetime + Enddate of the dataset + freq : str + {'1T', '15T', 'H'} + Time frequency of the data + folder_path : str + Local path to forecast data on Recoy SharePoint, + e.g. "C:/Users/username/Recoy/Recoy - Documents/03 - Libraries/12 - Data Management/Forecast Data/" + + """ + + def __init__(self, filename, start=None, end=None, freq="15T", folder_path=None, from_database=False, add_days_to_start_end=False): + self.file = filename + self.from_database = from_database + + if isinstance(start, str): + start = datetime.strptime(start, "%Y-%m-%d").astimezone(pytz.timezone('Europe/Amsterdam')) + print(start) + if isinstance(end, str): + end = datetime.strptime(end, "%Y-%m-%d").astimezone(pytz.timezone('Europe/Amsterdam')) + print(end) + + self.data = self.get_dataset(start, end, freq, folder_path=folder_path, add_days_to_start_end=add_days_to_start_end) + + # print(self.data) + + if len(self.data) == 0: + raise Exception("No data available for those dates.") + + def get_dataset(self, start, end, freq, folder_path=None, add_days_to_start_end=False): + if folder_path is None and self.from_database: + if add_days_to_start_end: + start = start + timedelta(days=-1) + end = end + timedelta(days=1) + + start = start.astimezone(pytz.utc) + end = end.astimezone(pytz.utc) + + dam = get_day_ahead_prices_from_database(start, end, 'NLD') + dam = dam.resample('15T').ffill() + + imb = get_imbalance_prices_from_database(start, end, 'NLD') + data = pd.concat([imb, dam], axis='columns') + data = data[['DAM', 'POS', 'NEG']] + data = data.tz_convert('Europe/Amsterdam') + # data = data.loc[(data.index >= start) & (data.index < end)] + return data + + + else: + if folder_path is None: + folder_path = Path(os.environ["FORECAST_DATA_FOLDER"]) + else: + folder_path = Path(folder_path) + + data = pd.read_csv( + folder_path / self.file, + delimiter=";", + decimal=",", + parse_dates=False, + index_col="datetime", + ) + ix_start = pd.to_datetime(data.index[0], utc=True).tz_convert( + "Europe/Amsterdam" + ) + ix_end = pd.to_datetime(data.index[-1], utc=True).tz_convert("Europe/Amsterdam") + new_idx = pd.date_range(ix_start, ix_end, freq=freq, tz="Europe/Amsterdam") + + if len(new_idx) == len(data.index): + data.index = new_idx + else: + print(f"Warning: Entries missing from dataset '{self.file}'.") + data.index = pd.to_datetime(data.index, utc=True).tz_convert( + "Europe/Amsterdam" + ) + data = data.reindex(new_idx) + print("Issue solved: Dataset was reindexed automatically.") + + data.index.name = "datetime" + + if start and end: + return data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")].round(2) + elif start: + return data[start.strftime("%Y-%m-%d") :].round(2) + elif end: + return data[: end.strftime("%Y-%m-%d")].round(2) + else: + return data.round(2) + + + def reindex_to_freq(self, freq): + """Reindex dataset to a different timefrequency. + + Parameters: + ----------- + freq : string + options: '1T' + """ + ix_start = pd.to_datetime(self.data.index[0], utc=True).tz_convert( + "Europe/Amsterdam" + ) + ix_end = pd.to_datetime(self.data.index[-1], utc=True).tz_convert( + "Europe/Amsterdam" + ) + + idx = pd.date_range( + ix_start, ix_end + timedelta(minutes=14), freq=freq, tz="Europe/Amsterdam" + ) + self.data = self.data.reindex(index=idx, method="ffill") + + +class Mipf(Forecast): + """Load MIPF dataset from SharePoint server as DataFrame in local datetime format. + + Parameters: + ---------- + start : datetime + Startdate of the dataset + end : datetime + Enddate of the dataset + tidy : bool + Get a dataframe in tidy format (1 minute freq). + include_nextQ : bool + Include forecast for next Quarter hour. + Requires tidy=True to work. + folder_path : str + Local path to forecast data on Recoy SharePoint, + e.g. "C:/Users/username/Recoy/Recoy - Documents/03 - Libraries/12 - Data Management/Forecast Data/" + """ + + def __init__( + self, start=None, end=None, tidy=True, include_nextQ=False, folder_path=None + ): + self.file = "imbalanceRT_nl.csv" + super().__init__( + self.file, start=start, end=end, freq="15T", folder_path=folder_path + ) + + if tidy and self.from_database == False: + self.tidy(include_nextQ=include_nextQ) + + elif self.from_database == True: + self.addMipfData(include_nextQ=include_nextQ) + + def tidy(self, include_price_data=True, include_nextQ=False): + self.data = tidy_mipf(self.data, include_price_data, include_nextQ) + + + # def addMipfData(self, include_nextQ=False): + + + + + +def tidy_mipf(data, include_price_data=True, include_nextQ=False): + """Takes MIPF dataset (unstacked) and turns it into a tidy dataset (stacked). + + Parameters: + ---------- + include_price_data : bool + Set as True if columns 'DAM', 'POS' and 'NEG' data should be included in the output. + include_nextQ : bool + Set to True to include next Qh forecast + """ + mipf_pos = data[[f"POS_horizon{h}" for h in np.flip(np.arange(3, 18))]].copy() + mipf_neg = data[[f"NEG_horizon{h}" for h in np.flip(np.arange(3, 18))]].copy() + + cols = ["ForePos", "ForeNeg"] + dfs = [mipf_pos, mipf_neg] + + if include_nextQ: + pos_nextQ = data[[f"POS_horizon{h}" for h in np.flip(np.arange(18, 30))]].copy() + neg_nextQ = data[[f"NEG_horizon{h}" for h in np.flip(np.arange(18, 30))]].copy() + + for h in np.arange(30, 33): + pos_nextQ.insert(0, f"POS_horizon{h}", np.NaN) + neg_nextQ.insert(0, f"POS_horizon{h}", np.NaN) + + cols += ["ForePos_nextQ", "ForeNeg_nextQ"] + dfs += [pos_nextQ, neg_nextQ] + + tidy_df = pd.DataFrame() + + for df, col in zip(dfs, cols): + df.columns = range(15) + df.reset_index(drop=True, inplace=True) + df.reset_index(inplace=True) + df_melt = ( + df.melt(id_vars=["index"], var_name="min", value_name=col) + .sort_values(["index", "min"]) + .reset_index(drop=True) + ) + tidy_df[col] = df_melt[col] + + ix_start = data.index[0] + ix_end = data.index[-1] + timedelta(minutes=14) + + tidy_df.index = pd.date_range(ix_start, ix_end, freq="1T", tz="Europe/Amsterdam") + tidy_df.index.name = "datetime" + + if include_price_data: + for col in np.flip(["DAM", "POS", "NEG", "regulation state"]): + try: + price_col = data.loc[:, col].reindex( + index=tidy_df.index, method="ffill" + ) + if col == "regulation state": + price_col.name = "RS" + tidy_df = pd.concat([price_col, tidy_df], axis="columns") + except Exception as e: + print(e) + + return tidy_df + + +class Qipf(Forecast): + """Load QIPF dataset from SharePoint server as DataFrame in local datetime format. + + Parameters: + ---------- + start : datetime + Startdate of the dataset + end : datetime + Enddate of the dataset + folder_path : str + Local path to forecast data on Recoy SharePoint, + e.g. "C:/Users/username/Recoy/Recoy - Documents/03 - Libraries/12 - Data Management/Forecast Data/" + """ + + def __init__(self, start=None, end=None, freq="15T", folder_path=None): + self.file = "imbalance_nl.csv" + self.data = self.get_dataset(start, end, "15T", folder_path=folder_path) + + if freq != "15T": + self.reindex_to_freq(freq) + + +class Irf(Forecast): + """Load QIPF dataset from SharePoint server as DataFrame in local datetime format.""" + + def __init__( + self, country, horizon, start=None, end=None, freq="60T", folder_path=None + ): + if freq == "15T": + self.file = f"irf_{country}_{horizon}_15min.csv" + else: + self.file = f"irf_{country}_{horizon}.csv" + + self.data = self.get_dataset(start, end, freq, folder_path=folder_path) + + return data + + +class NsideApiRequest: + """ + Request forecast data from N-SIDE API + + If request fails, code will retry 5 times by default. + + Output on success: data as DataFrame, containing forecast data. Index is timezone-aware datetime (Dutch time). + Output on error: [] + """ + + def __init__( + self, + endpoint, + country, + start=None, + end=None, + auth_token=None, + ): + if not auth_token: + try: + auth_token = os.environ["NSIDE_API_KEY"] + except: + raise ValueError("N-SIDE token not provided.") + + self.data = self.get_data(auth_token, endpoint, country, start, end) + + def get_data(self, token, endpoint, country, start, end): + if start is not None: + start = pd.to_datetime(start).strftime("%Y-%m-%d") + if end is not None: + end = pd.to_datetime(end).strftime("%Y-%m-%d") + + url = f"https://energy-forecasting-api.eu.n-side.com/api/forecasts/{country}/{endpoint}" + if start and end: + url += f"?from={start}&to={end}" + print(url) + headers = {"Accept": "application/json", "Authorization": f"Token {token}"} + + retry = 5 + self.success = False + + i = 0 + while i <= retry: + resp = requests.get(url, headers=headers) + self.statuscode = resp.status_code + + if self.statuscode == requests.codes.ok: + self.content = resp.content + json_data = json.loads(self.content) + data = pd.DataFrame(json_data["records"]) + data = data.set_index("datetime") + data.index = pd.to_datetime(data.index, utc=True).tz_convert( + "Europe/Amsterdam" + ) + self.success = True + return data.sort_index() + else: + print( + f"Attempt failled, status code {str(self.statuscode)}. Trying again..." + ) + time.sleep(5) + i += 1 + + if not self.success: + print( + "Request failed. Please contact your Recoy contact person or try again later." + ) + return [] diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/framework.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/framework.py new file mode 100644 index 0000000..6a49ce0 --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/framework.py @@ -0,0 +1,60 @@ +import warnings +from datetime import datetime, timedelta + +import pandas as pd +import pytz + + +class TimeFramework: + """ + Representation of the modelled timeperiod. + Variables in this class are equal for all CaseStudies. + """ + + def __init__(self, start, end): + if type(start) is str: + start = pytz.timezone("Europe/Amsterdam").localize( + datetime.strptime(start, "%Y-%m-%d") + ) + + if type(end) is str: + end = pytz.timezone("Europe/Amsterdam").localize( + datetime.strptime(end, "%Y-%m-%d") + ) + end += timedelta(days=1) + end -= timedelta(minutes=1) + + self.start = start + self.end = end + + amount_of_days = 365 + + if start.year % 4 == 0: + amount_of_days = 366 + + self.days = (self.end - self.start + timedelta(days=1)) / timedelta(days=1) + + self.modelled_time_period_years = (end - start).total_seconds() / (3600 * 24 * amount_of_days) + + if self.days != 365: + warnings.warn( + f"The chosen timeperiod spans {self.days} days, " + "which is not a full year. Beware that certain " + "functions that use yearly rates might return " + "incorrect values." + ) + + def dt_index(self, freq): + # Workaround to make sure time range is always complete, + # Even with DST changes + # end = self.end + timedelta(days=1) # + timedelta(hours=1) + # end = self.end + # end - timedelta(end.hour) + return pd.date_range( + start=self.start, + end=self.end, + freq=freq, + tz="Europe/Amsterdam", + # inclusive="left", + name="datetime", + ) diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/intelligent_baseline.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/intelligent_baseline.py new file mode 100644 index 0000000..286f734 --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/intelligent_baseline.py @@ -0,0 +1,226 @@ +import numpy as np +import pandas as pd +import warnings +from tqdm.notebook import tqdm + +from .prices import get_tennet_data, get_balansdelta_nl +from .forecasts import Forecast + +# TODO: This whole thing needs serious refactoring /MK + + +def generate_intelligent_baseline(startdate, enddate): + bd = get_balansdelta_nl(start=startdate, end=enddate) + bd.drop( + columns=[ + "datum", + "volgnr", + "tijd", + "IGCCBijdrage_op", + "IGCCBijdrage_af", + "opregelen_reserve", + "afregelen_reserve", + ], + inplace=True, + ) + net_regelvolume = bd["opregelen"] - bd["Afregelen"] + bd.insert(2, "net_regelvolume", net_regelvolume) + vol_delta = bd["net_regelvolume"].diff(periods=1) + bd.insert(3, "vol_delta", vol_delta) + + pc = get_tennet_data( + exporttype="verrekenprijzen", start=startdate, end=enddate + ).reindex(index=bd.index, method="ffill")[["prikkelcomponent"]] + + if len(pc) == 0: + pc = pd.Series(0, index=bd.index) + + prices = Forecast("marketprices_nl.csv", start=startdate, end=enddate) + prices.reindex_to_freq("1T") + prices = prices.data + + inputdata = pd.concat([prices, bd, pc], axis=1) + + Qhs = len(inputdata) / 15 + + if Qhs % 1 > 0: + raise Exception( + "A dataset with incomplete quarter-hours was passed in, please insert new dataset!" + ) + + data = np.array([inputdata[col].to_numpy() for col in inputdata.columns]) + + lstoutput = [] + + for q in tqdm(range(int(Qhs))): + q_data = [col[q * 15 : (q + 1) * 15] for col in data] + q_output = apply_imbalance_logic_for_quarter_hour(q_data) + + if lstoutput: + for (ix, col) in enumerate(lstoutput): + lstoutput[ix] += q_output[ix] + else: + lstoutput = q_output + + ib = pd.DataFrame( + lstoutput, + index=[ + "DAM", + "POS", + "NEG", + "regulation state", + "ib_inv", + "ib_afn", + "ib_rt", + "nv_op", + "nv_af", + "opgeregeld", + "afgeregeld", + ], + ).T + + ib.index = inputdata.index + return ib + + +def apply_imbalance_logic_for_quarter_hour(q_data): + [nv_op, nv_af, opgeregeld, afgeregeld] = [False] * 4 + + lst_inv = [np.NaN] * 15 + lst_afn = [np.NaN] * 15 + lst_rt = [np.NaN] * 15 + + lst_nv_op = [np.NaN] * 15 + lst_nv_af = [np.NaN] * 15 + lst_afr = [np.NaN] * 15 + lst_opr = [np.NaN] * 15 + + mins = iter(range(15)) + + for m in mins: + [ + DAM, + POS, + NEG, + rt, + vol_op, + vol_af, + net_vol, + delta_vol, + nood_op, + nood_af, + prijs_hoog, + prijs_mid, + prijs_laag, + prikkelc, + ] = [col[0 : m + 1] for col in q_data] + + delta_vol[0] = 0 + + if nood_op.sum() > 0: + nv_op = True + + if nood_af.sum() > 0: + nv_af = True + + if pd.notna(prijs_hoog).any() > 0: + opgeregeld = True + + if pd.notna(prijs_laag).any() > 0: + afgeregeld = True + + if (opgeregeld == True) and (afgeregeld == False): + regeltoestand = 1 + + elif (opgeregeld == False) and (afgeregeld == True): + regeltoestand = -1 + + elif (opgeregeld == False) and (afgeregeld == False): + if nv_op == True: + regeltoestand = 1 + if nv_af == True: + regeltoestand = -1 + else: + regeltoestand = 0 + + else: + # Zowel opregeld als afregeld > kijk naar trend + # Continue niet-dalend: RT1 + # Continue dalend: RT -1 + # Geen continue trend: RT 2 + + if all(i >= 0 for i in delta_vol): + regeltoestand = 1 + elif all(i <= 0 for i in delta_vol): + regeltoestand = -1 + else: + regeltoestand = 2 + + # Bepaal de verwachte onbalansprijzen + dam = DAM[0] + pc = prikkelc[0] + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + + hoogste_prijs = np.nanmax(prijs_hoog) + mid_prijs = prijs_mid[-1] + laagste_prijs = np.nanmin(prijs_laag) + + if regeltoestand == 0: + prijs_inv = mid_prijs + prijs_afn = mid_prijs + + elif regeltoestand == -1: + if nv_af: + prijs_afn = np.nanmin((dam - 200, laagste_prijs)) + + else: + prijs_afn = laagste_prijs + + prijs_inv = prijs_afn + + elif regeltoestand == 1: + if nv_op: + prijs_inv = np.nanmax((dam + 200, hoogste_prijs)) + + else: + prijs_inv = hoogste_prijs + + prijs_afn = prijs_inv + + elif regeltoestand == 2: + if nv_op: + prijs_afn = np.nanmax((dam + 200, hoogste_prijs, mid_prijs)) + else: + prijs_afn = np.nanmax((mid_prijs, hoogste_prijs)) + + if nv_af: + prijs_inv = np.nanmin((dam - 200, laagste_prijs, mid_prijs)) + else: + prijs_inv = np.nanmin((mid_prijs, laagste_prijs)) + + prijs_inv -= pc + prijs_afn += pc + + lst_inv[m] = prijs_inv + lst_afn[m] = prijs_afn + lst_rt[m] = regeltoestand + lst_nv_op[m] = nv_op + lst_nv_af[m] = nv_af + lst_opr[m] = opgeregeld + lst_afr[m] = afgeregeld + + return [ + list(DAM), + list(POS), + list(NEG), + list(rt), + lst_inv, + lst_afn, + lst_rt, + lst_nv_op, + lst_nv_af, + lst_opr, + lst_afr, + ] diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/plotting.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/plotting.py new file mode 100644 index 0000000..74ceade --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/plotting.py @@ -0,0 +1,143 @@ +import plotly.graph_objects as go +from millify import millify +from plotly import figure_factory as ff + +from .colors import * +from .reports import SingleFigureComparison + + +def npv_bar_chart( + cases, color=recoydarkblue, title="NPV Comparison in k€", n_format="%{text:.3s}€" +): + series = SingleFigureComparison(cases, "npv", "NPV (€)").report + case_names = series.index + npvs = series.values + return single_figure_barchart(npvs, case_names, title, color, n_format) + + +def irr_bar_chart( + cases, color=recoydarkblue, title="IRR Comparison in %", n_format="%{text:.1f}%" +): + series = SingleFigureComparison(cases, "irr", "IRR (€)").report + case_names = series.index + irrs = series.values * 100 + return single_figure_barchart(irrs, case_names, title, color, n_format) + + +def ebitda_bar_chart( + cases, color=recoydarkblue, title="EBITDA comparison in k€", n_format="%{text:.3s}€" +): + series = SingleFigureComparison(cases, "ebitda", "EBITDA (€)").report + case_names = series.index + ebitdas = series.values + return single_figure_barchart(ebitdas, case_names, title, color, n_format) + + +def capex_bar_chart( + cases, color=recoydarkblue, title="CAPEX comparison in k€", n_format="%{text:.3s}€" +): + series = SingleFigureComparison(cases, "total_capex", "CAPEX (€)").report + case_names = series.index + capex = series.values * -1 + return single_figure_barchart(capex, case_names, title, color, n_format) + + +def single_figure_barchart(y_values, x_labels, title, color, n_format): + fig = go.Figure() + fig.add_trace( + go.Bar( + x=x_labels, + y=y_values, + text=y_values, + marker_color=color, + cliponaxis=False, + ) + ) + fig.update_layout(title=title) + ymin = min(y_values.min(), 0) * 1.1 + ymax = max(y_values.max(), 0) * 1.1 + fig.update_yaxes(range=[ymin, ymax]) + fig.update_traces(texttemplate=n_format, textposition="outside") + return fig + + +def heatmap( + data, + title=None, + labels=None, + colormap="reds", + mult_factor=1, + decimals=2, + min_value=None, + max_value=None, + width=600, + height=400, + hover_prefix=None, + reversescale=False, +): + data_lists = (data * mult_factor).round(decimals).values.tolist() + + xs = data.columns.tolist() + ys = data.index.to_list() + + annotations = ( + (data * mult_factor) + .applymap(lambda x: millify(x, precision=decimals)) + .values.tolist() + ) + if hover_prefix: + hover_labels = [ + [f"{hover_prefix} {ann}" for ann in sublist] for sublist in annotations + ] + else: + hover_labels = annotations + + # This is an ugly trick to fix a bug with + # the axis labels not showing correctly + xs_ = [f"{str(x)}_" for x in xs] + ys_ = [f"{str(y)}_" for y in ys] + + fig = ff.create_annotated_heatmap( + data_lists, + x=xs_, + y=ys_, + annotation_text=annotations, + colorscale=colormap, + showscale=True, + text=hover_labels, + hoverinfo="text", + reversescale=reversescale, + ) + + # Part 2 of the bug fix + fig.update_xaxes(tickvals=xs_, ticktext=xs) + fig.update_yaxes(tickvals=ys_, ticktext=ys) + + fig.layout.xaxis.type = "category" + fig.layout.yaxis.type = "category" + fig["layout"]["xaxis"].update(side="bottom") + + if min_value: + fig["data"][0]["zmin"] = min_value * mult_factor + if max_value: + fig["data"][0]["zmax"] = max_value * mult_factor + + if labels: + xlabel = labels[0] + ylabel = labels[1] + else: + xlabel = data.columns.name + ylabel = data.index.name + + fig.update_xaxes(title=xlabel) + fig.update_yaxes(title=ylabel) + + if title: + fig.update_layout( + title=title, + title_x=0.5, + title_y=0.85, + width=width, + height=height, + ) + return fig diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/prices-LAPTOP-2MK43FDK.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/prices-LAPTOP-2MK43FDK.py new file mode 100644 index 0000000..2f90008 --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/prices-LAPTOP-2MK43FDK.py @@ -0,0 +1,765 @@ +import os +from datetime import timedelta +from io import BytesIO +from pathlib import Path +from zipfile import ZipFile +import time +import warnings +import json +import pytz + +import numpy as np +import pandas as pd +import requests +from bs4 import BeautifulSoup +from entsoe.entsoe import EntsoePandasClient +from sqlalchemy import MetaData, Table, insert, and_, or_ +from pyrecoy import * + + +def get_fcr_prices(start, end, freq="H") -> pd.DataFrame: + """Get FCR settlement prices from Regelleistung website + + Returns: DataFrame with FCR prices with index with given time frequency in local time. + """ + start = start + timedelta(-1) + end = end + timedelta(1) + data = get_FCR_prices_from_database(start, end, 'NLD') + data = data.resample('15T').ffill() + data = data[['PricePerMWPerISP']] + data.columns = ['FCR NL (EUR/ISP)'] + data.index.name = 'datetime' + data = data.tz_convert('Europe/Amsterdam') + return data + + path = Path( + f"./data/fcr_prices_{freq}_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + df = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype(float) + startdate = pd.to_datetime(df.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(df.index[-1]).strftime("%Y-%m-%d %H:%M") + df.index = pd.date_range( + startdate, enddate, freq=freq, tz="Europe/Amsterdam", name="datetime" + ) + return df + + dfs = [] + retry = 5 + for date in pd.date_range(start=start, end=end + timedelta(days=1)): + r = 0 + # print(f'DEBUG: {date}') + while r < retry: + try: + url = ( + f"https://www.regelleistung.net/apps/cpp-publisher/api/v1/download/tenders/" + f"resultsoverview?date={date.strftime('%Y-%m-%d')}&exportFormat=xlsx&market=CAPACITY&productTypes=FCR" + ) + df = pd.read_excel(url, engine="openpyxl")[ + [ + "DATE_FROM", + "PRODUCTNAME", + "NL_SETTLEMENTCAPACITY_PRICE_[EUR/MW]", + "DE_SETTLEMENTCAPACITY_PRICE_[EUR/MW]", + ] + ] + # print(f'DEBUG: {date} read in') + dfs.append(df) + break + except Exception: + # print(r) + time.sleep(1) + r += 1 + warnings.warn( + f'No data received for {date.strftime("%Y-%m-%d")}. Retrying...({r}/{retry})' + ) + + if r == retry: + raise RuntimeError(f'No data received for {date.strftime("%Y-%m-%d")}') + + df = pd.concat(dfs, axis=0) + df["hour"] = df["PRODUCTNAME"].map(lambda x: int(x.split("_")[1])) + df["Timeblocks"] = ( + df["PRODUCTNAME"].map(lambda x: int(x.split("_")[2])) - df["hour"] + ) + df.index = df.apply( + lambda row: pd.to_datetime(row["DATE_FROM"]) + timedelta(hours=row["hour"]), + axis=1, + ).dt.tz_localize("Europe/Amsterdam") + df.drop(columns=["DATE_FROM", "PRODUCTNAME", "hour"], inplace=True) + df.rename( + columns={ + "NL_SETTLEMENTCAPACITY_PRICE_[EUR/MW]": f"FCR Price NL [EUR/MW/{freq}]", + "DE_SETTLEMENTCAPACITY_PRICE_[EUR/MW]": f"FCR Price DE [EUR/MW/{freq}]", + }, + inplace=True, + ) + + try: + df[f"FCR Price NL [EUR/MW/{freq}]"] = df[ + f"FCR Price NL [EUR/MW/{freq}]" + ].astype(float) + df[f"FCR Price DE [EUR/MW/{freq}]"] = df[ + f"FCR Price NL [EUR/MW/{freq}]" + ].astype(float) + except Exception as e: + warnings.warn( + f"Could not convert data to floats. Should check... Exception: {e}" + ) + + df = df[~df.index.duplicated(keep="first")] + new_ix = pd.date_range( + start=df.index[0], end=df.index[-1], freq=freq, tz="Europe/Amsterdam" + ) + df = df.reindex(new_ix, method="ffill") + mult = {"H": 1, "4H": 4, "D": 24} + df[f"FCR Price NL [EUR/MW/{freq}]"] /= df["Timeblocks"] / mult[freq] + df[f"FCR Price DE [EUR/MW/{freq}]"] /= df["Timeblocks"] / mult[freq] + df = df[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + df.to_csv(path, sep=";", decimal=",", index_label="datetime") + return df + + +def get_tennet_data(exporttype, start, end): + """Download data from TenneT API + + TenneT documentation: + https://www.tennet.org/bedrijfsvoering/exporteer_data_toelichting.aspx + + Parameters: + ----------- + exporttype : str + Exporttype as defined in TenneT documentation. + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + + Returns: + -------- + DataFrame with API output. + """ + datefrom = start.strftime("%d-%m-%Y") + dateto = end.strftime("%d-%m-%Y") + url = ( + f"http://www.tennet.org/bedrijfsvoering/ExporteerData.aspx?exporttype={exporttype}" + f"&format=csv&datefrom={datefrom}&dateto={dateto}&submit=1" + ) + + return pd.read_csv(url, decimal=",") + + +def get_imb_prices_nl(start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame: + exporttype = "verrekenprijzen" + data = get_tennet_data(exporttype, start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, exporttype) + date_ix = pd.date_range(first_entry, last_entry, freq="15T", tz="Europe/Amsterdam") + + if len(data) == len(date_ix): + data.index = date_ix + else: + data = _handle_missing_data_by_reindexing(data) + + data = data[["invoeden", "Afnemen", "regeltoestand"]] + data.columns = ["POS", "NEG", "RS"] + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + return data + + +def get_balansdelta_nl(start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame: + filename = f"balansdelta_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + path = Path("./data") / filename + if path.exists(): + data = pd.read_csv(path, sep=";", decimal=",", index_col="datetime") + startdate = pd.to_datetime(data.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(data.index[-1]).strftime("%Y-%m-%d %H:%M") + data.index = pd.date_range(startdate, enddate, freq="1T", tz="Europe/Amsterdam") + return data + + exporttype = "balansdelta2017" + data = get_tennet_data(exporttype, start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, exporttype) + date_ix = pd.date_range(first_entry, last_entry, freq="1T", tz="Europe/Amsterdam") + data.index = date_ix + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def _get_afrr_prices_from_entsoe(start, end, marketagreement_type, entsoe_api_key): + client = EntsoePandasClient(entsoe_api_key) + return client.query_contracted_reserve_prices( + country_code="NL", + start=start, + end=end + timedelta(days=1), + type_marketagreement_type=marketagreement_type, + ) + + +def get_afrr_capacity_fees_nl(start, end, entsoe_api_key=None): + path = Path( + f"./data/afrr_capacity_fees_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + df = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype(float) + startdate = pd.to_datetime(df.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(df.index[-1]).strftime("%Y-%m-%d %H:%M") + df.index = pd.date_range(startdate, enddate, freq="D", tz="Europe/Amsterdam") + return df + + if not entsoe_api_key: + try: + entsoe_api_key = os.environ["ENTSOE_API_KEY"] + except: + raise ValueError("Please enter ENTSOE API key") + + date_to_daily_bids = pd.to_datetime("2020-08-31").tz_localize("Europe/Amsterdam") + + if start < date_to_daily_bids: + _start = start - timedelta(days=7) + data = _get_afrr_prices_from_entsoe( + start=_start, + end=min(date_to_daily_bids, end), + marketagreement_type="A02", + entsoe_api_key=entsoe_api_key, + )[["Automatic frequency restoration reserve - Symmetric"]] + + if end > date_to_daily_bids: + _end = date_to_daily_bids - timedelta(days=1) + else: + _end = end + dt_index = pd.date_range(start, _end, freq="D", tz="Europe/Amsterdam") + data = data.reindex(dt_index, method="ffill") + + # ENTSOE: + # "Before week no. 1 of 2020 the values are published per period + # per MW (Currency/MW per procurement period); meaning that it + # is not divided by MTU/ISP in that period." + if start < pd.to_datetime("2019-12-23"): + data[: pd.to_datetime("2019-12-22")] /= 7 * 24 * 4 + + if end >= date_to_daily_bids: + _data = ( + _get_afrr_prices_from_entsoe( + start=max(date_to_daily_bids, start), + end=end, + marketagreement_type="A01", + entsoe_api_key=entsoe_api_key, + ) + .resample("D") + .first() + ) + cols = [ + "Automatic frequency restoration reserve - Down", + "Automatic frequency restoration reserve - Symmetric", + "Automatic frequency restoration reserve - Up", + ] + + for col in cols: + if col not in _data.columns: + _data[col] = np.NaN + + _data = _data[cols] + + try: + data = pd.concat([data, _data], axis=0) + except Exception: + data = _data + + data = data[start:end] + + new_col_names = { + "Automatic frequency restoration reserve - Down": "aFRR Down [€/MW/day]", + "Automatic frequency restoration reserve - Symmetric": "aFRR Symmetric [€/MW/day]", + "Automatic frequency restoration reserve - Up": "aFRR Up [€/MW/day]", + } + data.rename(columns=new_col_names, inplace=True) + hours_per_day = ( + pd.Series( + data=0, + index=pd.date_range( + start, + end + timedelta(days=1), + freq="15T", + tz="Europe/Amsterdam", + inclusive="left", + ), + ) + .resample("D") + .count() + ) + data = data.multiply(hours_per_day.values, axis=0).round(2) + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def _get_afrr_prices_nl_from_tennet(start, end): + """Get aFRR prices from TenneT API + + Parameters: + ----------- + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + + Returns: + -------- + DataFrame with imbalance prices. + """ + filename = f"afrr_prices_nl_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + path = Path("./data") / filename + if path.exists(): + data = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype( + float + ) + startdate = pd.to_datetime(data.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(data.index[-1]).strftime("%Y-%m-%d %H:%M") + data.index = pd.date_range( + startdate, enddate, freq="15T", tz="Europe/Amsterdam" + ) + return data + + data = get_tennet_data("verrekenprijzen", start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, "verrekenprijzen") + date_ix = pd.date_range(first_entry, last_entry, freq="15T", tz="Europe/Amsterdam") + + if len(data) == len(date_ix): + data.index = date_ix + else: + data = _handle_missing_data_by_reindexing(data) + + data = data[["opregelen", "Afregelen"]] + data.columns = ["price_up", "price_down"] + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def get_afrr_prices_nl(start, end): + bd = get_balansdelta_nl(start=start, end=end)[ + ["Hoogste_prijs_opregelen", "Laagste_prijs_afregelen"] + ] + bd.columns = ["rt_price_UP", "rt_price_DOWN"] + afrr_prices = _get_afrr_prices_nl_from_tennet(start, end).reindex( + bd.index, method="ffill" + ) + return pd.concat([afrr_prices, bd], axis=1) + + +def _get_index_first_and_last_entry(data, exporttype): + if exporttype == "balansdelta2017": + time_col_name = "tijd" + elif exporttype == "verrekenprijzen": + time_col_name = "periode_van" + return [ + pd.to_datetime( + " ".join((data["datum"].iloc[ix], data[time_col_name].iloc[ix])), + format="%d-%m-%Y %H:%M", + ) + for ix in [0, -1] + ] + + +def _handle_missing_data_by_reindexing(data): + print("Warning: Entries missing from TenneT data.") + data.index = data[["datum", "periode_van"]].apply(lambda x: " ".join(x), axis=1) + data.index = pd.to_datetime(data.index, format="%d-%m-%Y %H:%M").tz_localize( + "Europe/Amsterdam", ambiguous=True + ) + data = data[~data.index.duplicated(keep="first")] + date_ix = pd.date_range( + data.index[0], data.index[-1], freq="15T", tz="Europe/Amsterdam" + ) + data = data.reindex(date_ix) + print("Workaround implemented: Dataset was reindexed automatically.") + return data + + +def get_imb_prices_be(startdate, enddate): + start = pd.to_datetime(startdate).tz_localize("Europe/Brussels").tz_convert("UTC") + end = ( + pd.to_datetime(enddate).tz_localize("Europe/Brussels") + timedelta(days=1) + ).tz_convert("UTC") + rows = int((end - start) / timedelta(minutes=15)) + resp_df = pd.DataFrame() + + while rows > 0: + print(f"Getting next chunk, {rows} remaining.") + chunk = min(3000, rows) + end = start + timedelta(minutes=chunk * 15) + resp_df = pd.concat([resp_df, elia_api_call(start, end)], axis=0) + start = end + rows -= chunk + + resp_df.index = pd.date_range( + start=resp_df.index[0], end=resp_df.index[-1], tz="Europe/Brussels", freq="15T" + ) + + resp_df.index.name = "datetime" + resp_df = resp_df[ + ["positiveimbalanceprice", "negativeimbalanceprice", "qualitystatus"] + ].rename(columns={"positiveimbalanceprice": "POS", "negativeimbalanceprice": "NEG"}) + resp_df["Validated"] = False + resp_df.loc[resp_df["qualitystatus"] == "Validated", "Validated"] = True + resp_df.drop(columns=["qualitystatus"], inplace=True) + return resp_df + + +def elia_api_call(start, end): + dataset = "ods047" + sort_by = "datetime" + url = "https://opendata.elia.be/api/records/1.0/search/" + rows = int((end - start) / timedelta(minutes=15)) + end = end - timedelta(minutes=15) + endpoint = ( + f"?dataset={dataset}&q=datetime:[{start.strftime('%Y-%m-%dT%H:%M:%SZ')}" + f" TO {end.strftime('%Y-%m-%dT%H:%M:%SZ')}]&rows={rows}&sort={sort_by}" + ) + + for _ in range(5): + try: + resp = requests.get(url + endpoint) + if resp.ok: + break + else: + raise Exception() + except Exception: + print("retrying...") + time.sleep(1) + + if not resp.ok: + raise Exception(f"Error when calling API. Status code: {resp.status_code}") + + resp_json = json.loads(resp.content) + resp_json = [entry["fields"] for entry in resp_json["records"]] + + df = pd.DataFrame(resp_json).set_index("datetime") + df.index = pd.to_datetime(df.index, utc=True).tz_convert("Europe/Brussels") + df = df.sort_index() + return df + + +def get_da_prices_from_entsoe( + start, end, country_code, tz, freq="H", entsoe_api_key=None +): + """Get Day-Ahead prices from ENTSOE + + Parameters: + ----------- + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with day-ahead prices. + """ + if not entsoe_api_key: + try: + entsoe_api_key = "f6c67fd5-e423-47bc-8a3c-98125ccb645e" + except: + raise ValueError("Please enter ENTSOE API key") + + client = EntsoePandasClient(entsoe_api_key) + data = client.query_day_ahead_prices( + country_code, start=start, end=end + timedelta(days=1) + ) + data = data[~data.index.duplicated()] + data.index = pd.date_range(data.index[0], data.index[-1], freq="H", tz=tz) + + if freq != "H": + data = _reindex_to_freq(data, freq, tz) + + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + return data + + +def _reindex_to_freq(data, freq, tz): + new_ix = pd.date_range( + data.index[0], + data.index[-1] + timedelta(hours=1), + freq=freq, + tz=tz, + ) + return data.reindex(index=new_ix, method="ffill") + + +def get_da_prices_nl(start, end, freq="H", entsoe_api_key=None): + return get_da_prices_from_entsoe( + start, end, "NL", "Europe/Amsterdam", freq=freq, entsoe_api_key=entsoe_api_key + ) + + +def get_da_prices_be(start, end, freq="H", entsoe_api_key=None): + return get_da_prices_from_entsoe( + start, end, "BE", "Europe/Brussels", freq=freq, entsoe_api_key=entsoe_api_key + ) + + +def get_ets_prices(start, end, freq="D"): + """Get CO2 prices (ETS) from ICE + + Values are in €/ton CO2 + + Parameters: + ----------- + start : datetime + Start date + end : datetime + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with ETS settlement prices with datetime index (local time) + """ + + start_x = start + timedelta(days=-2) + end_x = end + timedelta(days=2) + + data = get_ets_prices_from_database(start_x, end_x, 'NLD') + data = data.resample('1T').ffill() + data = data.loc[(data.index >= start) & (data.index < end)] + return data + + + # here = pytz.timezone("Europe/Amsterdam") + # start_file = pd.Timestamp(str(start.year) + "-1-1", tz=here).to_pydatetime() + # end_file = pd.Timestamp(str(start.year) + "-12-31", tz=here).to_pydatetime() + + # path = Path( + # f"./data/ets_prices_{freq}_{start_file.strftime('%Y%m%d')}_{end_file.strftime('%Y%m%d')}.csv" + # ) + # if path.exists(): + # return _load_from_csv(path, freq=freq) + # else: + # raise Exception("Data not available for chosen dates.") + + +def get_ttf_prices(start, end, freq="D"): + """Get Day-Ahead natural gas prices (TTF Day-ahead) from ICE + + Values are in €/MWh + + Parameters: + ----------- + start : datetime + Start date + end : datetime + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with TTF day-ahead prices with datetime index (local time) + + Start and End are converted into start of year and end of year + """ + + start_x = start + timedelta(days=-2) + end_x = end + timedelta(days=2) + + data = get_ttf_prices_from_database(start_x, end_x, 'NLD') + data = data.resample('1T').ffill() + data = data.loc[(data.index >= start) & (data.index < end)] + return data + + # # while start_year <= end_year: + # here = pytz.timezone("Europe/Amsterdam") + # start_file = pd.Timestamp(str(start.year) + "-1-1", tz=here).to_pydatetime() + # end_file = pd.Timestamp(str(start.year) + "-12-31", tz=here).to_pydatetime() + + # path = Path( + # f"./data/ttf_prices_{freq}_{start_file.strftime('%Y%m%d')}_{end_file.strftime('%Y%m%d')}.csv" + # ) + # print(path) + + # if path.exists(): + # return _load_from_csv(path, freq=freq) + # else: + # raise Exception("Data not available for chosen dates.") + + +def _load_from_csv(filepath, freq): + data = pd.read_csv( + filepath, + delimiter=";", + decimal=",", + parse_dates=False, + index_col="datetime", + ) + ix_start = pd.to_datetime(data.index[0], utc=True).tz_convert("Europe/Amsterdam") + ix_end = pd.to_datetime(data.index[-1], utc=True).tz_convert("Europe/Amsterdam") + data.index = pd.date_range(ix_start, ix_end, freq=freq, tz="Europe/Amsterdam") + return data.squeeze() + + +##### RECOY DATABASE QUERIES ##### + +def get_day_ahead_prices_from_database(start_hour, end_hour, CountryIsoCode, tz='utc'): + table = 'DayAheadPrices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['HourStartTime'] >= start_hour, + table.columns['HourStartTime'] < end_hour + )) + + data = pd.DataFrame(data) + data['HourStartTime'] = pd.to_datetime(data['HourStartTime'], utc=True) + data.index = data['HourStartTime'] + data.index.name = 'datetime' + data = data[['Price', 'CountryIsoCode']] + data.columns = ['DAM', 'CountryIsoCode'] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + + +def get_imbalance_prices_from_database(start_quarter, end_quarter, CountryIsoCode, tz='utc'): + table = 'ImbalancePrices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['QuarterStartTime'] >= start_quarter, + table.columns['QuarterStartTime'] < end_quarter + )) + + data = pd.DataFrame(data) + data['QuarterStartTime'] = pd.to_datetime(data['QuarterStartTime'], utc=True) + data.index = data['QuarterStartTime'] + data.index.name = 'datetime' + data = data[['FeedToGridPrice', 'TakeFromGridPrice', 'CountryIsoCode']] + data.columns = ['POS', 'NEG', 'CountryIsoCode'] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + + +def get_FCR_prices_from_database(start_day, end_day, CountryIsoCode, tz='utc'): + table = 'ReservePrices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['Timestamp'] >= start_day, + table.columns['Timestamp'] <= end_day, + table.columns['ReserveType'] == 'FCR' + )) + + data = pd.DataFrame(data) + data['Timestamp'] = pd.to_datetime(data['Timestamp'], utc=True) + data.index = data['Timestamp'] + data.index.name = 'datetime' + data = data[['PricePerMWPerISP', 'CountryIsoCode']] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + + +def get_imbalance_forecasts_from_database_on_publication_time(start_publication_time, end_publication_time, ForecastSources, CountryIsoCodes): + table = 'ImbalancePriceForecasts' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'].in_(CountryIsoCodes), + table.columns['PublicationTime'] >= start_publication_time, + table.columns['PublicationTime'] < end_publication_time, + table.columns['ForecastSource'].in_(ForecastSources) + )) + + return pd.DataFrame(data) + + +def get_imbalance_forecasts_from_database_on_quarter_start_time(start_quarter, end_quarter, ForecastSources, CountryIsoCode): + table = 'ImbalancePriceForecasts' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['QuarterStartTime'] >= start_quarter, + table.columns['QuarterStartTime'] < end_quarter, + table.columns['PublicationTime'] < end_quarter, + table.columns['ForecastSource'].in_(ForecastSources) + )) + + return pd.DataFrame(data) + + +def get_ttf_prices_from_database(start, end, CountryIsoCode, tz='utc'): + if start.tzinfo != pytz.utc: + start = start.astimezone(pytz.utc) + if end.tzinfo != pytz.utc: + end = end.astimezone(pytz.utc) + + table = 'GasPrices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['Timestamp'] >= start, + table.columns['Timestamp'] < end + )) + + data = pd.DataFrame(data) + data['Timestamp'] = pd.to_datetime(data['Timestamp'], utc=True) + data.index = data['Timestamp'] + data.index.name = 'datetime' + data = data[['TTFPrice']] + data.columns = ['Gas prices (€/MWh)'] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + + +def get_ets_prices_from_database(start, end, CountryIsoCode, tz='utc'): + if start.tzinfo != pytz.utc: + start = start.astimezone(pytz.utc) + if end.tzinfo != pytz.utc: + end = end.astimezone(pytz.utc) + + table = 'Co2Prices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['Timestamp'] >= start, + table.columns['Timestamp'] < end + )) + + data = pd.DataFrame(data) + data['Timestamp'] = pd.to_datetime(data['Timestamp'], utc=True) + data.index = data['Timestamp'] + data.index.name = 'datetime' + data = data[['Price']] + data.columns = ['CO2 prices (€/MWh)'] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/prices.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/prices.py new file mode 100644 index 0000000..1570e8c --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/prices.py @@ -0,0 +1,792 @@ +import os +from datetime import timedelta +from io import BytesIO +from pathlib import Path +from zipfile import ZipFile +import time +import warnings +import json +import pytz + +import numpy as np +import pandas as pd +import requests +from bs4 import BeautifulSoup +from entsoe.entsoe import EntsoePandasClient +from sqlalchemy import MetaData, Table, insert, and_, or_ +from pyrecoy import * + + +def get_fcr_prices(start, end, freq="H") -> pd.DataFrame: + """Get FCR settlement prices from Regelleistung website + + Returns: DataFrame with FCR prices with index with given time frequency in local time. + """ + start = start + timedelta(-1) + end = end + timedelta(1) + data = get_FCR_prices_from_database(start, end, "NLD") + data = data.resample("15T").ffill() + data = data[["PricePerMWPerISP"]] + data.columns = ["FCR NL (EUR/ISP)"] + data.index.name = "datetime" + data = data.tz_convert("Europe/Amsterdam") + return data + + path = Path( + f"./data/fcr_prices_{freq}_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + df = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype(float) + startdate = pd.to_datetime(df.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(df.index[-1]).strftime("%Y-%m-%d %H:%M") + df.index = pd.date_range( + startdate, enddate, freq=freq, tz="Europe/Amsterdam", name="datetime" + ) + return df + + dfs = [] + retry = 5 + for date in pd.date_range(start=start, end=end + timedelta(days=1)): + r = 0 + # print(f'DEBUG: {date}') + while r < retry: + try: + url = ( + f"https://www.regelleistung.net/apps/cpp-publisher/api/v1/download/tenders/" + f"resultsoverview?date={date.strftime('%Y-%m-%d')}&exportFormat=xlsx&market=CAPACITY&productTypes=FCR" + ) + df = pd.read_excel(url, engine="openpyxl")[ + [ + "DATE_FROM", + "PRODUCTNAME", + "NL_SETTLEMENTCAPACITY_PRICE_[EUR/MW]", + "DE_SETTLEMENTCAPACITY_PRICE_[EUR/MW]", + ] + ] + # print(f'DEBUG: {date} read in') + dfs.append(df) + break + except Exception: + # print(r) + time.sleep(1) + r += 1 + warnings.warn( + f'No data received for {date.strftime("%Y-%m-%d")}. Retrying...({r}/{retry})' + ) + + if r == retry: + raise RuntimeError(f'No data received for {date.strftime("%Y-%m-%d")}') + + df = pd.concat(dfs, axis=0) + df["hour"] = df["PRODUCTNAME"].map(lambda x: int(x.split("_")[1])) + df["Timeblocks"] = ( + df["PRODUCTNAME"].map(lambda x: int(x.split("_")[2])) - df["hour"] + ) + df.index = df.apply( + lambda row: pd.to_datetime(row["DATE_FROM"]) + timedelta(hours=row["hour"]), + axis=1, + ).dt.tz_localize("Europe/Amsterdam") + df.drop(columns=["DATE_FROM", "PRODUCTNAME", "hour"], inplace=True) + df.rename( + columns={ + "NL_SETTLEMENTCAPACITY_PRICE_[EUR/MW]": f"FCR Price NL [EUR/MW/{freq}]", + "DE_SETTLEMENTCAPACITY_PRICE_[EUR/MW]": f"FCR Price DE [EUR/MW/{freq}]", + }, + inplace=True, + ) + + try: + df[f"FCR Price NL [EUR/MW/{freq}]"] = df[ + f"FCR Price NL [EUR/MW/{freq}]" + ].astype(float) + df[f"FCR Price DE [EUR/MW/{freq}]"] = df[ + f"FCR Price NL [EUR/MW/{freq}]" + ].astype(float) + except Exception as e: + warnings.warn( + f"Could not convert data to floats. Should check... Exception: {e}" + ) + + df = df[~df.index.duplicated(keep="first")] + new_ix = pd.date_range( + start=df.index[0], end=df.index[-1], freq=freq, tz="Europe/Amsterdam" + ) + df = df.reindex(new_ix, method="ffill") + mult = {"H": 1, "4H": 4, "D": 24} + df[f"FCR Price NL [EUR/MW/{freq}]"] /= df["Timeblocks"] / mult[freq] + df[f"FCR Price DE [EUR/MW/{freq}]"] /= df["Timeblocks"] / mult[freq] + df = df[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + df.to_csv(path, sep=";", decimal=",", index_label="datetime") + return df + + +def get_tennet_data(exporttype, start, end): + """Download data from TenneT API + + TenneT documentation: + https://www.tennet.org/bedrijfsvoering/exporteer_data_toelichting.aspx + + Parameters: + ----------- + exporttype : str + Exporttype as defined in TenneT documentation. + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + + Returns: + -------- + DataFrame with API output. + """ + datefrom = start.strftime("%d-%m-%Y") + dateto = end.strftime("%d-%m-%Y") + url = ( + f"http://www.tennet.org/bedrijfsvoering/ExporteerData.aspx?exporttype={exporttype}" + f"&format=csv&datefrom={datefrom}&dateto={dateto}&submit=1" + ) + + return pd.read_csv(url, decimal=",") + + +def get_imb_prices_nl(start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame: + exporttype = "verrekenprijzen" + data = get_tennet_data(exporttype, start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, exporttype) + date_ix = pd.date_range(first_entry, last_entry, freq="15T", tz="Europe/Amsterdam") + + if len(data) == len(date_ix): + data.index = date_ix + else: + data = _handle_missing_data_by_reindexing(data) + + data = data[["invoeden", "Afnemen", "regeltoestand"]] + data.columns = ["POS", "NEG", "RS"] + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + return data + + +def get_balansdelta_nl(start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame: + filename = f"balansdelta_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + path = Path("./data") / filename + if path.exists(): + data = pd.read_csv(path, sep=";", decimal=",", index_col="datetime") + startdate = pd.to_datetime(data.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(data.index[-1]).strftime("%Y-%m-%d %H:%M") + data.index = pd.date_range(startdate, enddate, freq="1T", tz="Europe/Amsterdam") + return data + + exporttype = "balansdelta2017" + data = get_tennet_data(exporttype, start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, exporttype) + date_ix = pd.date_range(first_entry, last_entry, freq="1T", tz="Europe/Amsterdam") + data.index = date_ix + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def _get_afrr_prices_from_entsoe(start, end, marketagreement_type, entsoe_api_key): + client = EntsoePandasClient(entsoe_api_key) + return client.query_contracted_reserve_prices( + country_code="NL", + start=start, + end=end + timedelta(days=1), + type_marketagreement_type=marketagreement_type, + ) + + +def get_afrr_capacity_fees_nl(start, end, entsoe_api_key=None): + path = Path( + f"./data/afrr_capacity_fees_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + df = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype(float) + startdate = pd.to_datetime(df.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(df.index[-1]).strftime("%Y-%m-%d %H:%M") + df.index = pd.date_range(startdate, enddate, freq="D", tz="Europe/Amsterdam") + return df + + if not entsoe_api_key: + try: + entsoe_api_key = os.environ["ENTSOE_API_KEY"] + except: + raise ValueError("Please enter ENTSOE API key") + + date_to_daily_bids = pd.to_datetime("2020-08-31").tz_localize("Europe/Amsterdam") + + if start < date_to_daily_bids: + _start = start - timedelta(days=7) + data = _get_afrr_prices_from_entsoe( + start=_start, + end=min(date_to_daily_bids, end), + marketagreement_type="A02", + entsoe_api_key=entsoe_api_key, + )[["Automatic frequency restoration reserve - Symmetric"]] + + if end > date_to_daily_bids: + _end = date_to_daily_bids - timedelta(days=1) + else: + _end = end + dt_index = pd.date_range(start, _end, freq="D", tz="Europe/Amsterdam") + data = data.reindex(dt_index, method="ffill") + + # ENTSOE: + # "Before week no. 1 of 2020 the values are published per period + # per MW (Currency/MW per procurement period); meaning that it + # is not divided by MTU/ISP in that period." + if start < pd.to_datetime("2019-12-23"): + data[: pd.to_datetime("2019-12-22")] /= 7 * 24 * 4 + + if end >= date_to_daily_bids: + _data = ( + _get_afrr_prices_from_entsoe( + start=max(date_to_daily_bids, start), + end=end, + marketagreement_type="A01", + entsoe_api_key=entsoe_api_key, + ) + .resample("D") + .first() + ) + cols = [ + "Automatic frequency restoration reserve - Down", + "Automatic frequency restoration reserve - Symmetric", + "Automatic frequency restoration reserve - Up", + ] + + for col in cols: + if col not in _data.columns: + _data[col] = np.NaN + + _data = _data[cols] + + try: + data = pd.concat([data, _data], axis=0) + except Exception: + data = _data + + data = data[start:end] + + new_col_names = { + "Automatic frequency restoration reserve - Down": "aFRR Down [€/MW/day]", + "Automatic frequency restoration reserve - Symmetric": "aFRR Symmetric [€/MW/day]", + "Automatic frequency restoration reserve - Up": "aFRR Up [€/MW/day]", + } + data.rename(columns=new_col_names, inplace=True) + hours_per_day = ( + pd.Series( + data=0, + index=pd.date_range( + start, + end + timedelta(days=1), + freq="15T", + tz="Europe/Amsterdam", + inclusive="left", + ), + ) + .resample("D") + .count() + ) + data = data.multiply(hours_per_day.values, axis=0).round(2) + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def _get_afrr_prices_nl_from_tennet(start, end): + """Get aFRR prices from TenneT API + + Parameters: + ----------- + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + + Returns: + -------- + DataFrame with imbalance prices. + """ + filename = f"afrr_prices_nl_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + path = Path("./data") / filename + if path.exists(): + data = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype( + float + ) + startdate = pd.to_datetime(data.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(data.index[-1]).strftime("%Y-%m-%d %H:%M") + data.index = pd.date_range( + startdate, enddate, freq="15T", tz="Europe/Amsterdam" + ) + return data + + data = get_tennet_data("verrekenprijzen", start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, "verrekenprijzen") + date_ix = pd.date_range(first_entry, last_entry, freq="15T", tz="Europe/Amsterdam") + + if len(data) == len(date_ix): + data.index = date_ix + else: + data = _handle_missing_data_by_reindexing(data) + + data = data[["opregelen", "Afregelen"]] + data.columns = ["price_up", "price_down"] + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def get_afrr_prices_nl(start, end): + bd = get_balansdelta_nl(start=start, end=end)[ + ["Hoogste_prijs_opregelen", "Laagste_prijs_afregelen"] + ] + bd.columns = ["rt_price_UP", "rt_price_DOWN"] + afrr_prices = _get_afrr_prices_nl_from_tennet(start, end).reindex( + bd.index, method="ffill" + ) + return pd.concat([afrr_prices, bd], axis=1) + + +def _get_index_first_and_last_entry(data, exporttype): + if exporttype == "balansdelta2017": + time_col_name = "tijd" + elif exporttype == "verrekenprijzen": + time_col_name = "periode_van" + return [ + pd.to_datetime( + " ".join((data["datum"].iloc[ix], data[time_col_name].iloc[ix])), + format="%d-%m-%Y %H:%M", + ) + for ix in [0, -1] + ] + + +def _handle_missing_data_by_reindexing(data): + print("Warning: Entries missing from TenneT data.") + data.index = data[["datum", "periode_van"]].apply(lambda x: " ".join(x), axis=1) + data.index = pd.to_datetime(data.index, format="%d-%m-%Y %H:%M").tz_localize( + "Europe/Amsterdam", ambiguous=True + ) + data = data[~data.index.duplicated(keep="first")] + date_ix = pd.date_range( + data.index[0], data.index[-1], freq="15T", tz="Europe/Amsterdam" + ) + data = data.reindex(date_ix) + print("Workaround implemented: Dataset was reindexed automatically.") + return data + + +def get_imb_prices_be(startdate, enddate): + start = pd.to_datetime(startdate).tz_localize("Europe/Brussels").tz_convert("UTC") + end = ( + pd.to_datetime(enddate).tz_localize("Europe/Brussels") + timedelta(days=1) + ).tz_convert("UTC") + rows = int((end - start) / timedelta(minutes=15)) + resp_df = pd.DataFrame() + + while rows > 0: + print(f"Getting next chunk, {rows} remaining.") + chunk = min(3000, rows) + end = start + timedelta(minutes=chunk * 15) + resp_df = pd.concat([resp_df, elia_api_call(start, end)], axis=0) + start = end + rows -= chunk + + resp_df.index = pd.date_range( + start=resp_df.index[0], end=resp_df.index[-1], tz="Europe/Brussels", freq="15T" + ) + + resp_df.index.name = "datetime" + resp_df = resp_df[ + ["positiveimbalanceprice", "negativeimbalanceprice", "qualitystatus"] + ].rename(columns={"positiveimbalanceprice": "POS", "negativeimbalanceprice": "NEG"}) + resp_df["Validated"] = False + resp_df.loc[resp_df["qualitystatus"] == "Validated", "Validated"] = True + resp_df.drop(columns=["qualitystatus"], inplace=True) + return resp_df + + +def elia_api_call(start, end): + dataset = "ods047" + sort_by = "datetime" + url = "https://opendata.elia.be/api/records/1.0/search/" + rows = int((end - start) / timedelta(minutes=15)) + end = end - timedelta(minutes=15) + endpoint = ( + f"?dataset={dataset}&q=datetime:[{start.strftime('%Y-%m-%dT%H:%M:%SZ')}" + f" TO {end.strftime('%Y-%m-%dT%H:%M:%SZ')}]&rows={rows}&sort={sort_by}" + ) + + for _ in range(5): + try: + resp = requests.get(url + endpoint) + if resp.ok: + break + else: + raise Exception() + except Exception: + print("retrying...") + time.sleep(1) + + if not resp.ok: + raise Exception(f"Error when calling API. Status code: {resp.status_code}") + + resp_json = json.loads(resp.content) + resp_json = [entry["fields"] for entry in resp_json["records"]] + + df = pd.DataFrame(resp_json).set_index("datetime") + df.index = pd.to_datetime(df.index, utc=True).tz_convert("Europe/Brussels") + df = df.sort_index() + return df + + +def get_da_prices_from_entsoe( + start, end, country_code, tz, freq="H", entsoe_api_key=None +): + """Get Day-Ahead prices from ENTSOE + + Parameters: + ----------- + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with day-ahead prices. + """ + if not entsoe_api_key: + try: + entsoe_api_key = "f6c67fd5-e423-47bc-8a3c-98125ccb645e" + except: + raise ValueError("Please enter ENTSOE API key") + + client = EntsoePandasClient(entsoe_api_key) + data = client.query_day_ahead_prices( + country_code, start=start, end=end + timedelta(days=1) + ) + data = data[~data.index.duplicated()] + data.index = pd.date_range(data.index[0], data.index[-1], freq="H", tz=tz) + + if freq != "H": + data = _reindex_to_freq(data, freq, tz) + + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + return data + + +def _reindex_to_freq(data, freq, tz): + new_ix = pd.date_range( + data.index[0], + data.index[-1] + timedelta(hours=1), + freq=freq, + tz=tz, + ) + return data.reindex(index=new_ix, method="ffill") + + +def get_da_prices_nl(start, end, freq="H", entsoe_api_key=None): + return get_da_prices_from_entsoe( + start, end, "NL", "Europe/Amsterdam", freq=freq, entsoe_api_key=entsoe_api_key + ) + + +def get_da_prices_be(start, end, freq="H", entsoe_api_key=None): + return get_da_prices_from_entsoe( + start, end, "BE", "Europe/Brussels", freq=freq, entsoe_api_key=entsoe_api_key + ) + + +def get_ets_prices(start, end, freq="D"): + """Get CO2 prices (ETS) from ICE + + Values are in €/ton CO2 + + Parameters: + ----------- + start : datetime + Start date + end : datetime + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with ETS settlement prices with datetime index (local time) + """ + + start_x = start + timedelta(days=-2) + end_x = end + timedelta(days=2) + + data = get_ets_prices_from_database(start_x, end_x, "NLD") + data = data.resample("1T").ffill() + data = data.loc[(data.index >= start) & (data.index < end)] + return data + + here = pytz.timezone("Europe/Amsterdam") + start_file = pd.Timestamp(str(start.year) + "-1-1", tz=here).to_pydatetime() + end_file = pd.Timestamp(str(start.year) + "-12-31", tz=here).to_pydatetime() + + path = Path( + f"./data/ets_prices_{freq}_{start_file.strftime('%Y%m%d')}_{end_file.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + return _load_from_csv(path, freq=freq) + else: + raise Exception("Data not available for chosen dates.") + + +def get_ttf_prices(start, end, freq="D"): + """Get Day-Ahead natural gas prices (TTF Day-ahead) from ICE + + Values are in €/MWh + + Parameters: + ----------- + start : datetime + Start date + end : datetime + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with TTF day-ahead prices with datetime index (local time) + + Start and End are converted into start of year and end of year + """ + + start_x = start + timedelta(days=-2) + end_x = end + timedelta(days=2) + + data = get_ttf_prices_from_database(start_x, end_x, "NLD") + data = data.resample("1T").ffill() + data = data.loc[(data.index >= start) & (data.index < end)] + return data + + # while start_year <= end_year: + here = pytz.timezone("Europe/Amsterdam") + start_file = pd.Timestamp(str(start.year) + "-1-1", tz=here).to_pydatetime() + end_file = pd.Timestamp(str(start.year) + "-12-31", tz=here).to_pydatetime() + + path = Path( + f"./data/ttf_prices_{freq}_{start_file.strftime('%Y%m%d')}_{end_file.strftime('%Y%m%d')}.csv" + ) + + if path.exists(): + return _load_from_csv(path, freq=freq) + else: + raise Exception("Data not available for chosen dates.") + + +def _load_from_csv(filepath, freq): + data = pd.read_csv( + filepath, + delimiter=";", + decimal=",", + parse_dates=False, + index_col="datetime", + ) + ix_start = pd.to_datetime(data.index[0], utc=True).tz_convert("Europe/Amsterdam") + ix_end = pd.to_datetime(data.index[-1], utc=True).tz_convert("Europe/Amsterdam") + data.index = pd.date_range(ix_start, ix_end, freq=freq, tz="Europe/Amsterdam") + return data.squeeze() + + +##### RECOY DATABASE QUERIES ##### + + +def convert_columns_to_localized_datetime_from_utc(df, columns, tz): + for column in columns: + df[column] = pd.to_datetime(df[column], utc=True) + df[column] = df[column].dt.tz_convert(tz) + return df + + +def get_price_data_from_database( + database_name, + time_index_column, + database_columns, + rename_columns, + start, + end, + CountryIsoCode, + tz="utc", + to_datetime_columns=[], +): + """_summary_ + + Args: + database_name (string): name of the database + time_index_column (string): column which is converted to a datetime column and used as the index + database_columns (list of strings): columns of the database table you want to query + rename_columns (list of strings): new names for the columns which are queried + start (string or datetime): start time of the data you want to select based on the time_index_column + end (string or datetime): end time of the data you want to select based on the time_index_column + CountryIsoCode (string): CountryIsoCode of the data + tz (str, optional): Timezone you want the datatime columns to be converted to + to_datetime_columns (list, optional): Additional columns which are transferred to datetime columns. Defaults to []. + + Returns: + _type_: _description_ + """ + table = database_name + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter( + and_( + table.columns["CountryIsoCode"] == CountryIsoCode, + table.columns[time_index_column] >= start, + table.columns[time_index_column] < end, + ) + ) + data = pd.DataFrame(data) + data[time_index_column] = pd.to_datetime(data[time_index_column], utc=True) + data.index = data[time_index_column] + data.index.name = "datetime" + data = data[database_columns + ["CountryIsoCode"]] + data.columns = rename_columns + ["CountryIsoCode"] + if tz.__eq__("utc") is False: + data = data.tz_convert(tz) + data = convert_columns_to_localized_datetime_from_utc(data, to_datetime_columns, tz) + return data + + +def get_day_ahead_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_price_data_from_database( + "DayAheadPrices", + "HourStartTime", + ["Price"], + ["DAM"], + start, + end, + CountryIsoCode, + tz=tz, + ) + + +def get_imbalance_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_price_data_from_database( + "ImbalancePrices", + "QuarterStartTime", + ["FeedToGridPrice", "TakeFromGridPrice"], + ["POS", "NEG"], + start, + end, + CountryIsoCode, + tz=tz, + ) + + +def get_imbalance_forecasts_from_database_on_publication_time( + start, end, CountryIsoCode, tz="utc" +): + return get_price_data_from_database( + "ImbalancePriceForecasts", + "PublicationTime", + ["PublicationTime", "QuarterStartTime", "FeedToGridPrice", "TakeFromGridPrice"], + ["PublicationTime", "QuarterStartTime", "ForePos", "ForeNeg"], + start, + end, + CountryIsoCode, + tz=tz, + to_datetime_columns=["QuarterStartTime"], + ) + + +def get_imbalance_forecasts_from_database_on_quarter_start_time( + start, end, CountryIsoCode, tz="utc" +): + return get_price_data_from_database( + "ImbalancePriceForecasts", + "QuarterStartTime", + ["PublicationTime", "QuarterStartTime", "FeedToGridPrice", "TakeFromGridPrice"], + ["PublicationTime", "QuarterStartTime", "ForePos", "ForeNeg"], + start, + end, + CountryIsoCode, + tz=tz, + to_datetime_columns=["QuarterStartTime"], + ) + + +def get_ttf_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_price_data_from_database( + "GasPrices", + "DeliveryDate", + ["Price"], + ["Gas prices (€/MWh)"], + start, + end, + CountryIsoCode, + tz=tz, + ) + + +def get_ets_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_price_data_from_database( + "Co2Prices", + "DeliveryDate", + ["Price"], + ["CO2 prices (€/MWh)"], + start, + end, + CountryIsoCode, + tz=tz, + ) + + +def get_reserve_prices_from_database( + start, end, reserve_type, CountryIsoCode, tz="utc" +): + data = get_price_data_from_database( + "ReservePrices", + "Timestamp", + ["PricePerMWPerISP", "ReserveType"], + ["PricePerMWPerISP", "ReserveType"], + start, + end, + CountryIsoCode, + tz=tz, + ) + data = data.loc[data["ReserveType"] == reserve_type] + return data + + +def get_FCR_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database(start, end, "FCR", CountryIsoCode, tz=tz) + + +def get_aFRR_up_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database( + start, end, "aFRR Up", CountryIsoCode, tz=tz + ) + + +def get_aFRR_up_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database( + start, end, "aFRR Down", CountryIsoCode, tz=tz + ) + + +def get_aFRR_up_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database( + start, end, "mFRR Up", CountryIsoCode, tz=tz + ) + + +def get_aFRR_up_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database( + start, end, "mFRR Up", CountryIsoCode, tz=tz + ) diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/reports.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/reports.py new file mode 100644 index 0000000..6108272 --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/reports.py @@ -0,0 +1,156 @@ +import numpy as np +import pandas as pd + +from .styling import businesscase_formatter, num_formatting, perc_formatting + + +class CaseReport: + """Dataframe report showing KPIs for specific CaseStudy. + + Parameters: + ----------- + case : CaseStudy + kind : str + The report type. {electr_market_results', cashflows', 'ebitda_calc'}. + baseline : CaseStudy + include_perc: bool + """ + + def __init__(self, case, kind): + self._check_if_attr_exists(case, kind) + case_data = getattr(case, kind) + self.report = self.create_report(case.name, case_data) + self.formatting = "number" + + def _check_if_attr_exists(self, case, kind): + if not hasattr(case, kind): + raise AttributeError( + f"Attribute '{kind}' is not available for '{case.name}' case. " + "You should first generate it using " + "the appropriate CaseStudy method." + ) + + def create_report(self, case_name, case_data): + if isinstance(case_data, dict): + case_data = pd.Series(case_data) + + return pd.DataFrame(case_data, columns=[case_name]) + + def show(self, presentation_format=True): + if not presentation_format: + return self.report + + if self.formatting == "percentage": + return self.report.applymap(perc_formatting) + else: + return self.report.applymap(num_formatting) + + +class ComparisonReport(CaseReport): + """Dataframe report showing a copmarison of KPIs between CaseStudy instances. + + Parameters: + ----------- + cases : list + List of CaseStudy instances + kind : str + Type of report + baseline : CaseStudy + CaseStudy instance to use as baseline + comparison : str + {'absolute', 'relative', 'percentage'} + Sets how the numbers in the comparison are in relation to the baseline. + """ + + def __init__(self, cases, kind, baseline=None, comparison="absolute"): + case_reports = [] + self.formatting = "number" + + for case in cases: + case_report = CaseReport(case=case, kind=kind).report + case_reports.append(case_report) + + self.report = pd.concat(case_reports, axis=1).fillna(0) + + if comparison == "relative": + self._comp_relative(baseline) + elif comparison == "percentage": + self._comp_percentage(baseline) + + # ugly fix to make sure EBITDA is at the bottom when df is printed + if kind == "ebitda_calc": + ix = self.report.index.to_list() + ix.remove("EBITDA (€)") + ix.remove("Depreciation (€)") + ix.remove("EBITDA + depr (€)") + ix.append("EBITDA (€)") + ix.append("Depreciation (€)") + ix.append("EBITDA + depr (€)") + self.report = self.report.reindex(ix) + + def _comp_relative(self, baseline): + baseline_report = self.report[baseline.name] + self.report = self.report.subtract(baseline_report, axis=0) + + if baseline.name in self.report.columns: + self.report.drop(columns=baseline.name, inplace=True) + if baseline.name in self.report.index: + self.report.drop(index=baseline.name, inplace=True) + + self.formatting = "number" + + def _comp_percentage(self, baseline): + baseline_report = self.report[baseline.name] + self.report = self.report.divide(baseline_report / 100, axis=0).replace( + [-np.inf, np.inf], 0 + ) + self.report.replace([-np.inf, np.inf], 0, inplace=True) + self.formatting = "percentage" + + +class BusinessCaseReport(CaseReport): + """Show business case for CaseStudy""" + + def __init__(self, case, presentation_format=False): + self._check_if_attr_exists(case, "business_case") + self.report = getattr(case, "business_case") + + def show(self, presentation_format=True): + if presentation_format: + return businesscase_formatter(self.report) + else: + return self.report + + +class SingleFigureComparison(ComparisonReport): + def __init__( + self, + cases, + kpi, + label, + baseline=None, + comparison="absolute", + ): + figure_dict = {} + for case in cases: + self._check_if_attr_exists(case, kpi) + figure_dict[case.name] = getattr(case, kpi) + + self.report = pd.Series(figure_dict, name=label) + + if comparison == "relative": + self._comp_relative(baseline) + elif comparison == "percentage": + self._comp_percentage(baseline) + + def show(self, nformat=None): + if nformat is not None: + return self.report.apply(nformat.format) + else: + return self.report + + def _comp_relative(self, baseline): + baseline_report = self.report[baseline.name] + self.report = self.report.subtract(baseline_report, axis=0) + self.report.drop(index=baseline.name, inplace=True) + self.formatting = "number" diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/rop_assets.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/rop_assets.py new file mode 100644 index 0000000..41f604a --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/rop_assets.py @@ -0,0 +1,34 @@ +def get_power_profiles(start, end, country, in_local_time=True): + start = timestamp_to_utc(start) + end = timestamp_to_utc(end) + engine = db_engine("rop_test") + connection, table = create_connection(engine, "ImbalancePrices") + start = start.floor("15T") + query = ( + select([table]) + .where( + table.columns.QuarterStartTime >= start.strftime("%Y-%m-%d %H:%M"), + table.columns.QuarterStartTime < end.strftime("%Y-%m-%d %H:%M"), + table.columns.CountryIsoCode == country, + ) + .order_by(table.columns.QuarterStartTime) + ) + result = connection.execute(query).fetchall() + if len(result) == 0: + raise Exception("Day-ahead prices data not yet available.") + + data = pd.DataFrame(result, columns=result[0].keys()) + + if in_local_time: + data["QuarterStartTime"] = dt_column_to_local_time(data["QuarterStartTime"]) + + data.drop(columns=["Id", "CountryIsoCode"], inplace=True) + data.rename( + columns={ + "QuarterStartTime": "datetime", + "TakeFromGridPrice": "NEG", + "FeedToGridPrice": "POS", + }, + inplace=True, + ) + return data.set_index("datetime")[["POS", "NEG"]] \ No newline at end of file diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/rop_prices.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/rop_prices.py new file mode 100644 index 0000000..9ab908a --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/rop_prices.py @@ -0,0 +1,92 @@ +from sqlalchemy import select +import pandas as pd +from .converters import dt_column_to_local_time, timestamp_to_utc +from .databases import db_engine, create_connection + + +def get_imbalance_prices(start, end, country, in_local_time=True): + start = timestamp_to_utc(start) + end = timestamp_to_utc(end) + engine = db_engine("rop_prices_test") + connection, table = create_connection(engine, "ImbalancePrices") + start = start.floor("15T") + query = ( + select([table]) + .where( + table.columns.QuarterStartTime >= start.strftime("%Y-%m-%d %H:%M"), + table.columns.QuarterStartTime < end.strftime("%Y-%m-%d %H:%M"), + table.columns.CountryIsoCode == country, + ) + .order_by(table.columns.QuarterStartTime) + ) + result = connection.execute(query).fetchall() + if len(result) == 0: + raise Exception("Day-ahead prices data not yet available.") + + data = pd.DataFrame(result, columns=result[0].keys()) + + if in_local_time: + data["QuarterStartTime"] = dt_column_to_local_time(data["QuarterStartTime"]) + + data.drop(columns=["Id", "CountryIsoCode"], inplace=True) + data.rename( + columns={ + "QuarterStartTime": "datetime", + "TakeFromGridPrice": "NEG", + "FeedToGridPrice": "POS", + }, + inplace=True, + ) + return data.set_index("datetime")[["POS", "NEG"]] + + +def get_dayahead_prices(start, end, country, in_local_time=True): + start = timestamp_to_utc(start) + end = timestamp_to_utc(end) + engine = db_engine("rop_prices_test") + connection, table = create_connection(engine, "DayAheadPrices") + start = start.floor("60T") + query = ( + select([table]) + .where( + table.columns.HourStartTime >= start.strftime("%Y-%m-%d %H:%M"), + table.columns.HourStartTime < end.strftime("%Y-%m-%d %H:%M"), + table.columns.CountryIsoCode == country, + ) + .order_by(table.columns.HourStartTime) + ) + result = connection.execute(query).fetchall() + if len(result) == 0: + raise Exception("Day-ahead prices data not yet available.") + + data = pd.DataFrame(result, columns=result[0].keys()) + + if in_local_time: + data["HourStartTime"] = dt_column_to_local_time(data["HourStartTime"]) + + data.drop(columns=["Id", "CountryIsoCode"], inplace=True) + data.rename(columns={"HourStartTime": "datetime", "Price": "DAM"}, inplace=True) + return data.set_index("datetime") + + +def get_market_price_data(start, end, country, in_local_time=True): + tz = "Europe/Amsterdam" if in_local_time else "UTC" + dt_ix = pd.date_range( + start=start.floor("H"), + end=end.ceil("H"), + freq="15T", + tz=tz, + inclusive="left", + ) + prices = pd.DataFrame(index=dt_ix, columns=["DAM", "POS", "NEG"]) + prices["DAM"] = get_dayahead_prices( + start, end, country=country, in_local_time=in_local_time + ).reindex(dt_ix, method="ffill") + prices["DAM"].fillna(method="ffill", inplace=True) + + imbprices = get_imbalance_prices( + start, end, country=country, in_local_time=in_local_time + ) + prices["POS"] = imbprices["POS"] + prices["NEG"] = imbprices["NEG"] + return prices diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/sensitivity.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/sensitivity.py new file mode 100644 index 0000000..3c5628f --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/sensitivity.py @@ -0,0 +1,225 @@ +import itertools +import gc +from copy import deepcopy +from tkinter import Label +import warnings + +import pandas as pd +import numpy as np +from tqdm.notebook import tqdm +from millify import millify +from plotly.graph_objs import Figure + +from .casestudy import CaseStudy +from .colors import recoygreen, recoyred + + +class SensitivityAnalysis: + """ + Runs an simulation routine with different input configurations, + so that sensitivity of variables can be analysed. + """ + + def __init__(self, c, s, routine, param, values, output_kpis): + self.configs = self._generate_configs(c, param, values) + output_dict = self._prepare_output_dict(s.cases, output_kpis) + self.kpis = self._run_sensitivities(s, routine, output_kpis, output_dict) + + def _generate_configs(self, c, param, values): + configs = {} + for value in values: + _c = deepcopy(c) + setattr(_c, param, value) + configs[value] = _c + return configs + + def _prepare_output_dict(self, cases, output_kpis): + output_dict = dict.fromkeys(self.configs.keys()) + for value in self.configs: + output_dict[value] = dict.fromkeys(output_kpis) + for kpi in output_kpis: + output_dict[value][kpi] = dict.fromkeys([case.name for case in cases]) + return output_dict + + def _run_sensitivities(self, s, routine, output_kpis, output_dict): + for name, c in tqdm(self.configs.items()): + _s = deepcopy(s) + _s = routine(c, _s) + for kpi in output_kpis: + for case in _s.cases: + output_dict[name][kpi][case.name] = getattr(case, kpi, np.nan) + del _s + gc.collect() + return output_dict + + def single_kpi_overview(self, kpi, case_names=None): + """Creates a DataFrame with chosen output kpi, + for each CaseStudy in each Configuration. + """ + if not case_names: + case_names = CaseStudy.instances.keys() + + kpi_values = { + name: {case: self.kpis[name][kpi][case] for case in case_names} + for name in self.kpis.keys() + } + + return pd.DataFrame(kpi_values).T + + def cashflows_comparison(self, case=None, baseline=None): + ebitda_calc_overview = {} + baseline_calc = {} + for input_value, kpi_data in self.kpis.items(): + for kpi, case_data in kpi_data.items(): + for case_name, data in case_data.items(): + if kpi == "cashflows": + if case_name == case: + ebitda_calc_overview[input_value] = data + if case_name == baseline: + baseline_calc[input_value] = data + + ebitda_calc_overview = pd.DataFrame(ebitda_calc_overview) + if not baseline: + return ebitda_calc_overview + + baseline_calc = pd.DataFrame(baseline_calc) + return ebitda_calc_overview.subtract(baseline_calc, fill_value=0) + + +class SensitivityMatrix: + def __init__(self, c, s, routine, x_param, y_param, x_vals, y_vals, output_kpis): + self.x_param = x_param + self.y_param = y_param + + self.configs = self._generate_configs(c, x_vals, y_vals) + output_dict = self._prepare_output_dict(s.cases, output_kpis) + self.kpis = self._run_sensitivities(s, routine, output_kpis, output_dict) + + def _generate_configs(self, c, x_vals, y_vals): + configs = {x_val: dict.fromkeys(y_vals) for x_val in x_vals} + + self.xy_combinations = list(itertools.product(x_vals, y_vals)) + for x_val, y_val in self.xy_combinations: + _c = deepcopy(c) + setattr(_c, self.x_param, x_val) + setattr(_c, self.y_param, y_val) + configs[x_val][y_val] = _c + return configs + + def _prepare_output_dict(self, cases, output_kpis): + output_dict = {} + for name in [case.name for case in cases]: + output_dict[name] = dict.fromkeys(output_kpis) + for kpi in output_kpis: + output_dict[name][kpi] = deepcopy(self.configs) + return output_dict + + def _run_sensitivities(self, s, routine, output_kpis, output_dict): + for x_val, y_val in tqdm(self.xy_combinations): + _c = self.configs[x_val][y_val] + _s = deepcopy(s) + _s = routine(_c, _s) + for kpi in output_kpis: + for case in _s.cases: + output = getattr(case, kpi, np.nan) + output_dict[case.name][kpi][x_val][y_val] = output + del _s + del _c + gc.collect() + return output_dict + + def show_matrix(self, case_name, kpi): + """ + Creates a DataFrame with chosen output kpi, + for each XY combination + """ + matrix = pd.DataFrame(self.kpis[case_name][kpi]) + matrix.columns.name = self.x_param + matrix.index.name = self.y_param + return matrix + + +class ScenarioAnalysis(SensitivityAnalysis): + def __init__(self, c, s, routine, params_dict, labels, output_kpis): + self.labels = labels + self.configs = self._generate_configs(c, params_dict, labels) + output_dict = self._prepare_output_dict(s.cases, output_kpis) + self.kpis = self._run_sensitivities(s, routine, output_kpis, output_dict) + + def _generate_configs(self, c, params_dict, labels): + configs = {} + for i, label in enumerate(labels): + _c = deepcopy(c) + for param, values in params_dict.items(): + setattr(_c, param, values[i]) + configs[label] = _c + return configs + + +class TornadoChart: + """ + TODO: Absolute comparison instead of relative + """ + + def __init__(self, c, s, routine, case, tornado_vars, output_kpis): + self.case = case + self.kpis = self._run_sensitivities( + c, s, routine, case, tornado_vars, output_kpis + ) + + def _run_sensitivities(self, c, s, routine, case, tornado_vars, output_kpis): + labels = ["Low", "Medium", "High"] + outputs = {kpi: pd.DataFrame(index=labels) for kpi in output_kpis} + + for param, values in tornado_vars.items(): + sens = SensitivityAnalysis(c, s, routine, param, values, output_kpis) + + for kpi in output_kpis: + output = sens.single_kpi_overview(kpi, case_names=[case.name])[ + case.name + ] + output.index = labels + outputs[kpi][" ".join((param, str(values)))] = output + + for kpi in output_kpis: + base_performance = deepcopy(outputs[kpi].loc["Medium", :]) + for scen in labels: + scen_performance = outputs[kpi].loc[scen, :] + relative_performance = (scen_performance / base_performance - 1) * 100 + outputs[kpi].loc[scen, :] = relative_performance + + outputs[kpi] = outputs[kpi].round(1) + outputs[kpi].sort_values(by="Low", axis=1, ascending=False, inplace=True) + return outputs + + def show_chart( + self, kpi, dimensions=(800, 680), title="Tornado Chart", sort_by="Low" + ): + outputs = self.kpis[kpi].sort_values(by=sort_by, axis=1, ascending=False) + traces = [] + colors = {"Low": recoyred, "High": recoygreen} + + for scenario in ["Low", "High"]: + trace = { + "type": "bar", + "x": outputs.loc[scenario, :].tolist(), + "y": outputs.columns, + "orientation": "h", + "name": scenario, + "marker": {"color": colors[scenario]}, + } + traces.append(trace) + + layout = { + "title": title, + "width": dimensions[0], + "height": dimensions[1], + "barmode": "relative", + "autosize": True, + "showlegend": True, + } + fig = Figure(data=traces, layout=layout) + fig.update_xaxes( + title_text=f"{kpi.upper()} % change compared to base scenario (Base {kpi.upper()} = {millify(getattr(self.case, kpi))})" + ) + return fig diff --git a/pyrecoy/pyrecoy/build/lib/pyrecoy/styling.py b/pyrecoy/pyrecoy/build/lib/pyrecoy/styling.py new file mode 100644 index 0000000..bc224cf --- /dev/null +++ b/pyrecoy/pyrecoy/build/lib/pyrecoy/styling.py @@ -0,0 +1,47 @@ +from copy import deepcopy +from numbers import Number +import numpy as np + + +def num_formatting(val): + if np.isnan(val) or round(val, 0) == 0: + return "-" + else: + return f"{val:,.0f}" + + +def perc_formatting(val): + if np.isnan(val) or round(val, 0) == 0: + return "-" + else: + return f"{val:.1f}%" + + +def bc_formatting(val): + if not isinstance(val, Number): + return val + if np.isnan(val): + return "" + elif round(val, 2) == 0: + return "-" + else: + return f"{val:,.0f}" + + +def businesscase_formatter(df): + df_c = deepcopy(df) + + spp = df_c.loc["Simple Payback Period", "Year 0"] + spp_str = "N/A" if np.isnan(spp) else str(spp) + " years" + df_c.loc["Simple Payback Period", "Year 0"] = spp_str + + irr = df_c.loc["IRR (%)", "Year 0"] + if np.isnan(irr): + df_c.loc["IRR (%)", "Year 0"] = "N/A" + + df_c = df_c.applymap(bc_formatting) + + if not np.isnan(irr): + df_c.loc["IRR (%)", "Year 0"] += "%" + df_c.loc["WACC (%)", "Year 0"] += "%" + return df_c diff --git a/pyrecoy/pyrecoy/dist/pyrecoy-0.1-py3.9.egg b/pyrecoy/pyrecoy/dist/pyrecoy-0.1-py3.9.egg new file mode 100644 index 0000000..6b32d2b Binary files /dev/null and b/pyrecoy/pyrecoy/dist/pyrecoy-0.1-py3.9.egg differ diff --git a/pyrecoy/pyrecoy/notepad.py b/pyrecoy/pyrecoy/notepad.py new file mode 100644 index 0000000..95ed92f --- /dev/null +++ b/pyrecoy/pyrecoy/notepad.py @@ -0,0 +1,11 @@ +from pyrecoy.prices import get_ttf_prices +import pandas as pd + +data = get_ttf_prices( + start=pd.to_datetime("2019-01-01"), + end=pd.to_datetime("2019-12-31"), + ice_username="XXX", + ice_password="YYY", +) + +print(data.head()) diff --git a/pyrecoy/pyrecoy/pyrecoy/__init__.py b/pyrecoy/pyrecoy/pyrecoy/__init__.py new file mode 100644 index 0000000..bdad31f --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/__init__.py @@ -0,0 +1 @@ +from .database.Models.base import * \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/assets.py b/pyrecoy/pyrecoy/pyrecoy/assets.py new file mode 100644 index 0000000..d8a420c --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/assets.py @@ -0,0 +1,789 @@ +import warnings +from functools import partial, lru_cache +from numbers import Number +from itertools import count + +import numpy as np +from numpy.polynomial import Polynomial +from scipy.optimize import minimize_scalar + +from .converters import * + + +class Asset: + """Generic class for producing/consuming assets. Specific asset classes can + inherit from this class. + + Parameters: + ----------- + max_power : int/float + Maximum asset power in MW electric + min_power : int/float + Minimium asset load in MW electric + + Usage: + ------ + Use the set_load and get_load methods to set and get asset status in MW. + + Convention is negative values for inputs (consumption) and positive + values for outputs (production). + """ + + _freq_to_multiplier = {"H": 1, "15T": (1 / 4), "1T": (1 / 60)} + _ids = count(0) + + def __init__(self, name, max_power, min_power): + if min_power > max_power: + raise ValueError("'min_power' can not be larger than 'max_power'.") + + self.name = name + self.id = next(self._ids) + self.max_power = max_power + self.min_power = min_power + self.modes = {"max": max_power, "min": min_power} + + def __repr__(self): + return f"{self.__class__.__name__}(self, max_power={self.max_power}, min_power={self.min_power})" + + def set_load(self, load): + """Set Asset load in MW. + + Convention is negative value for consumption and positive value + for production. Subclasses might use a different convention if + this seems more intiutive. + + Returns the load that is set in MW. + """ + if load < self.min_power or load > self.max_power: + warnings.warn( + f"Chosen Asset load for {self.name} is out of range. " + f"Should be between {self.min_power} and {self.max_power}. " + f"Function will return boundary load level for now." + ) + load = min(max(load, self.min_power), self.max_power) + return load + + def set_mode(self, mode): + """ """ + load = self.modes[mode] + return self.set_load(load) + + def MW_to_MWh(self, MW): + """Performs conversion from MW to MWh using the time_factor variable.""" + return MW * self.time_factor + + def MWh_to_MW(self, MWh): + """Performs conversion from MWh to MW using the time_factor variable.""" + return MWh / self.time_factor + + def set_freq(self, freq): + """ + Function that aligns time frequency between Model and Asset. + Can be '1T', '15T' or 'H' + The time_factor variable is used in subclasses to perform MW to MWh conversions. + """ + self.freq = freq + self.time_factor = Asset._freq_to_multiplier[freq] + + def set_financials( + self, capex, opex, devex, lifetime=None, depreciate=True, salvage_value=0 + ): + """Set financial data of the asset.""" + self.capex = capex + self.opex = opex + self.devex = devex + self.lifetime = lifetime + self.depreciate = depreciate + self.salvage_value = salvage_value + + +class Eboiler(Asset): + """Subclass for an E-boiler.""" + + def __init__(self, name, max_power, min_power=0, efficiency=0.99): + super().__init__(name, min_power=-max_power, max_power=-min_power) + self.efficiency = efficiency + self.max_thermal_output = max_power * 0.99 + + def __repr__(self): + return ( + f"{self.__class__.__name__}(name={self.name}, max_power={self.max_power}, " + f"min_power={self.min_power}, efficiency={self.efficiency})" + ) + + def set_load(self, load): + """Set load in MWe, returns (load, heat_output) in MWe and MWth + + Convention is negative numbers for consumption. + Inserting a positive value will return an exception. + """ + + if load > 0: + raise ValueError( + f"Eboiler.set_load() only accepts negative numbers by convention. " + f"{load} was inserted." + ) + + load = super().set_load(load) + heat_output = -load * self.efficiency + return (load, heat_output) + + def set_heat_output(self, heat_output): + """Set heat output in MWth, returns tuple (heat_output, eload) in MW""" + load = -heat_output / self.efficiency + load, heat_output = self.set_load(load) + return heat_output, load + + +class Heatpump(Asset): + """Subclass for a Heatpump. + + Use cop parameter to set fixed COP (float/int) or COP curve (func). + COP curve should take load in MWhe and return COP. + + Parameters: + ----------- + max_th_power : numeric + Maximum thermal output in MW (positive value) + cop_curve : numeric or list or function + 3 ways to set the COP of the Heatpump: + (1) Fixed COP based on [numeric] value. + (2) Polynomial with coefficients based on [list] input. + + Input coeficients in format [c0, c1, c2, ..., c(n)], + will generate Polynomial p(x) = c0 + c1*x + c2*x^2 ... cn*x^n, + where x = % thermal load (in % of thermal capacity) as decimal value. + + Example: + cop=[1, 2, 3, 4] will result in following COP curve: + p(x) = 1 + 2x + 3x**2 + 4x**3, + + (3) [function] in format func(*args, **kwargs) + Function should return a Polynomial that takes 'load_perc' as parameter. + + min_th_power : numeric + Minimum thermal output in MW (positive value) + + Notes: + ------ + Sign convention: + Thermal power outputs have positive values + Electric power inputs have negative values + """ + + def __init__( + self, + name, + max_th_power, + cop_curve, + min_th_power=0, + ): + if max_th_power < 0 or min_th_power < 0: + raise ValueError("Thermal power can not have negative values.") + + if min_th_power > max_th_power: + raise ValueError("'min_th_power' can not be larger than 'max_th_power'.") + + self.name = name + self.max_th_power = max_th_power + self.min_th_power = min_th_power + self.cop_curve = self._set_cop_curve(cop_curve) + + def __repr__(self): + return ( + f"{self.__class__.__name__}(name='{self.name}', max_thermal_power={self.max_th_power}, " + f"cop_curve={self.cop_curve}, min_th_power={self.min_th_power})" + ) + + # Is turning everything into a Polynomial the best solution here? + @staticmethod + @lru_cache(maxsize=None) + def _set_cop_curve(cop_curve): + """Generate COP curve function based on different inputtypes. + + Returns a function that takes *args **kwargs and returns a Polynomial. + """ + if isinstance(cop_curve, list): + + def func(*args, **kwargs): + return Polynomial(cop_curve) + + return func + + return cop_curve + + @lru_cache(maxsize=None) + def get_cop(self, heat_output, Tsink=None, Tsource=None): + """Get COP corresponding to certain load. + + Parameters: + ----------- + heat_output : numeric + Thermal load in MW + Tsink : numeric + Sink temperature in degrees celcius + Tsource : numeric + Source temperature in degrees celcius + + Notes: + ------ + Sign convention: + Positive values for thermal load + Negative values for electric load + """ + load_perc = heat_output / self.max_th_power + cop_curve = self.cop_curve + + if not callable(cop_curve): + return cop_curve + else: + return cop_curve(Tsink=Tsink, Tsource=Tsource)(load_perc) + + def th_to_el_power(self, heat_output, Tsink=None, Tsource=None): + if not self.min_th_power <= heat_output <= self.max_th_power: + warnings.warn( + f"Chosen heat output is out of range [{self.min_th_power} - {self.max_th_power}]. " + "Heat output is being limited to the closest boundary." + ) + heat_output = min(max(heat_output, self.min_th_power), self.max_th_power) + + cop = self.get_cop(heat_output=heat_output, Tsink=Tsink, Tsource=Tsource) + return -heat_output / cop + + def set_load(self, *args, **kwargs): + raise NotImplementedError( + "Directly setting the electric load of the heatpump is not possible (yet). " + "Functionality will be implemented if there is a specific usecase for it." + ) + + @lru_cache(maxsize=None) + def set_heat_output(self, heat_output, Tsink=None, Tsource=None): + """Set heat output in MWth, returns load of heatpump as tuple (MWe, MWth)""" + if not self.min_th_power <= heat_output <= self.max_th_power: + warnings.warn( + f"Chosen heat output is out of range [{self.min_th_power} - {self.max_th_power}]. " + "Heat output is being limited to the closest boundary." + ) + heat_output = min(max(heat_output, self.min_th_power), self.max_th_power) + + if Tsink is not None and Tsource is not None and Tsink <= Tsource: + raise ValueError(f"Tsource '{Tsource}' can not be higher than '{Tsink}'.") + + cop = self.get_cop(heat_output=heat_output, Tsink=Tsink, Tsource=Tsource) + e_load = -heat_output / cop + return e_load, heat_output + + def _cost_function(self, x, c1, c2, c3, Tsink=None, Tsource=None): + """Objective function for set_opt_load function. + + x = heatpump thermal load in MW + c1 = electricity_cost + c2 = alt_heat_price + c3 = demand + """ + return ( + x / self.get_cop(heat_output=x, Tsink=Tsink, Tsource=Tsource) * c1 + + (c3 - x) * c2 + ) + + @lru_cache(maxsize=None) + def set_opt_load( + self, + electricity_cost, + alt_heat_price, + demand, + Tsink=None, + Tsource=None, + tolerance=0.01, + ): + """Set optimal load of Heatpump with minimal total heat costs. + + Function uses np.minimize_scalar to minimize cost function. + + Parameters: + ----------- + electricity_cost: + Cost of input electricity in €/MWh(e) + alt_heat_price: + Price of heat from alternative source in €/MWh(th) + demand: + Heat demand in MW(th) + + Returns: + -------- + Optimal load of heatpump as tuple (MWe, MWth) + """ + c1 = electricity_cost + c2 = alt_heat_price + c3 = demand + + cop_curve = self.cop_curve + if isinstance(cop_curve, Number): + if c1 / cop_curve <= c2: + return self.max_th_power + else: + return self.min_th_power + + obj_func = partial( + self._cost_function, c1=c1, c2=c2, c3=c3, Tsink=Tsink, Tsource=Tsource + ) + + low_bound = 0 + up_bound = min(c3, self.max_th_power) + + opt_th_load = minimize_scalar( + obj_func, + bounds=(low_bound, up_bound), + method="bounded", + options={"xatol": tolerance}, + ).x + opt_e_load, opt_th_load = self.set_heat_output( + opt_th_load, Tsink=Tsink, Tsource=Tsource + ) + + return opt_e_load, opt_th_load + + +class Battery(Asset): + """Subclass for a Battery. + + Battery is modeled as follows: + - Rated power is power in MW that battery can + import from and export to the grid + - Efficiency loss is applied at charging, meaning that + SoC increase when charging is lower than the SoC decrease + when discharging + """ + + def __init__( + self, + name, + rated_power, + rated_capacity, + roundtrip_eff, + min_soc=0, + max_soc=1, + soc_at_start=None, + cycle_lifetime=None, + ): + super().__init__(name=name, max_power=rated_power, min_power=-rated_power) + self.capacity = rated_capacity + self.min_soc = min_soc + self.max_soc = max_soc + self.min_chargelevel = min_soc * self.capacity + self.max_chargelevel = max_soc * self.capacity + self.rt_eff = roundtrip_eff + self.one_way_eff = np.sqrt(roundtrip_eff) + self.cycle_count = 0 + self.cycle_lifetime = cycle_lifetime + + soc_at_start = min_soc if soc_at_start is None else soc_at_start + self.set_chargelevel(soc_at_start * self.capacity) + + def __repr__(self): + return ( + f"Battery(self, rated_power={self.max_power}, rated_capacity={self.capacity}, " + f"roundtrip_eff={self.rt_eff}, min_soc={self.min_soc}, max_soc={self.max_soc})" + ) + + def get_soc(self): + """Get the SoC in % (decimal value)""" + return self.chargelevel / self.capacity + + def set_chargelevel(self, chargelevel): + """Set the chargelevel in MWh. Will automatically change the SoC accordingly.""" + # if round(chargelevel,2) < round(self.min_chargelevel,2) or round(chargelevel,2) > round(self.max_chargelevel,2): + # raise ValueError( + # f"Tried to set Charge Level to {chargelevel}. " + # f"Charge Level must be a value between " + # f"{self.min_chargelevel} and {self.max_chargelevel} (in MWh)" + # ) + + self.chargelevel = chargelevel + + def set_load(self, load): + """Set load of the battery. + + Use negative values for charging and positive values for discharging. + Returns actual chargespeed, considering technical limitations of the battery. + + Note: We currently assume all efficiency losses occur during charging (no losses during discharge) + """ + if not hasattr(self, "freq"): + raise AttributeError( + "Time frequency of the model is not defined. " + "Assign asset to a CaseStudy or use Asset.freq(). " + "to set de time frequency and try again." + ) + + load = super().set_load(load) + + unbound_charging = self.MW_to_MWh(load) + + if load < 0: + unbound_charging *= self.rt_eff + + chargelevel = self.chargelevel + max_charging = chargelevel - self.max_chargelevel + max_discharging = chargelevel - self.min_chargelevel + + bound_charging = min(max(unbound_charging, max_charging), max_discharging) + newcl = chargelevel - bound_charging + self.set_chargelevel(newcl) + + if bound_charging < 0: + bound_charging /= self.rt_eff + + self.cycle_count += abs(bound_charging / (self.capacity * 2)) + + return self.MWh_to_MW(bound_charging) + + def charge(self, chargespeed): + """Charge the battery with given chargespeed. + + Redirects to Battery.set_load(). + Returns load (negative value for charging). + """ + chargespeed = self.max_power if chargespeed == "max" else chargespeed + + if chargespeed < 0: + raise ValueError( + f"Chargespeed should be always be a positive value by convention. " + f"Inserted {chargespeed}." + ) + + chargespeed = self.set_load(-chargespeed) + + return chargespeed + + def discharge(self, dischargespeed): + """Discharge the battery by given amount. + + Redirects to Battery.set_load(). + Returns load (positive value for discharging). + """ + dischargespeed = self.max_power if dischargespeed == "max" else dischargespeed + + if dischargespeed < 0: + raise ValueError( + f"Dischargespeed should be always be a positive value by convention. " + f"Inserted {dischargespeed}." + ) + + dischargespeed = self.set_load(dischargespeed) + + return dischargespeed + + def get_cost_per_cycle(self, cycle_lifetime): + return self.capex / self.cycle_lifetime + + +class EV(Battery): + def __init__( + self, + name, + rated_power, + rated_capacity, + roundtrip_eff, + min_soc=0, + max_soc=1, + soc_at_start=None, + id=None, + ): + super().__init__( + name, + rated_power, + rated_capacity, + roundtrip_eff, + min_soc, + max_soc, + soc_at_start, + ) + if id: + self.id = id + + +class HotWaterStorage(Battery): + """Subclass for a storage asset. + + Parameters: + ----------- + rated_capacity : int/float + Rated capacity in MWh + min_buffer_level_perc : float + Minimum buffer level in % + buffer_level_at_start : float + Buffer level at start in % + """ + + def __init__( + self, + name, + rated_power, + capacity_per_volume, + volume, + temperature, + min_storagelevel, + initial_storagelevel=None, + ): + rated_capacity = capacity_per_volume * volume + + if not initial_storagelevel: + initial_storagelevel = min_storagelevel + soc_at_start = initial_storagelevel / rated_capacity + max_storagelevel = rated_capacity * 0.95 + min_soc = min_storagelevel / rated_capacity + max_soc = max_storagelevel / rated_capacity + self.temperature = temperature + + super().__init__( + name=name, + rated_power=rated_power, + rated_capacity=rated_capacity, + roundtrip_eff=1, + min_soc=min_soc, + max_soc=max_soc, + soc_at_start=soc_at_start, + ) + + def __repr__(self): + return ( + f"{self.__class__.__name__}(name={self.name}, rated_power={self.max_power}, capacity={self.capacity}, " + f"temperature={self.temperature}, min_storagelevel={self.min_chargelevel})" + ) + + @property + def charging_power_limit(self): + max_charging_energy = self.max_chargelevel - self.chargelevel + return min(self.MWh_to_MW(max_charging_energy), -self.min_power) + + @property + def discharging_power_limit(self): + max_discharging_energy = self.chargelevel - self.min_chargelevel + return min(self.MWh_to_MW(max_discharging_energy), self.max_power) + + +class GasBoiler(Asset): + """Representation of a Gas-fired boiler. + + name : str + Unique name of the asset + max_th_output : numeric + Maximum thermal output in MW thermal + efficiency : float + Thermal efficiency of the gasboiler as decimal value. + min_th_output : numeric + Minimum thermal output in MW thermal + """ + + def __init__( + self, + name, + max_th_output, + min_th_output=0, + efficiency=0.9, + ): + super().__init__(name=name, max_power=max_th_output, min_power=min_th_output) + self.efficiency = efficiency + + def __repr__(self): + return ( + f"{self.__class__.__name__}(name={self.name}, max_power={self.max_power}, " + f"min_power={self.min_power}, efficiency={self.efficiency})" + ) + + def set_load(self, *args, **kwargs): + raise NotImplementedError( + "Gasboiler does not have electric load. " + "Use Gasboiler.set_heat_output() instead." + ) + + @lru_cache(maxsize=None) + def set_heat_output(self, output): + """Redirect to Gasboiler.set_load()""" + heat_output = super().set_load(output) + gas_input = -heat_output / self.efficiency + return heat_output, gas_input + + +class Electrolyser(Asset): + def __init__( + self, + name, + rated_power, + kwh_per_kg=60, + min_flex_load_in_perc=15, + ): + min_flex_power = min_flex_load_in_perc / 100 * rated_power + + super().__init__(name=name, max_power=-min_flex_power, min_power=-rated_power) + + self.rated_power = rated_power + self.min_flex_load = min_flex_load_in_perc + self.min_flex_power = self.min_flex_load / 100 * self.rated_power + self.kwh_per_kg = kwh_per_kg + self.kg_per_MWh = 1000 / self.kwh_per_kg + + def __repr__(self): + return ( + f"Electrolyser(name={self.name}, rated_power={self.rated_power}, " + f"kwh_per_kg={self.kwh_per_kg}, flex_range_in_perc=[{self.min_flex_load}, " + f"{self.max_flex_load}])" + ) + + def set_load(self, load): + """Set load of the Electrolyser in MW.""" + if not hasattr(self, "freq"): + raise AttributeError( + "Time frequency of the model is not defined. " + "Assign asset to a CaseStudy or use Asset.freq(). " + "to set de time frequency and try again." + ) + + load = -abs(load) + load = super().set_load(load) + + h2_output_kg = self.MW_to_MWh(-load) * self.kg_per_MWh + return load, h2_output_kg + + +class Battolyser(Asset): + def __init__( + self, + name, + rated_power, + rated_capacity, + rt_eff, + soc_at_start=None, + ): + super().__init__(name=name, max_power=rated_power, min_power=-rated_power) + + self.capacity = rated_capacity + self.min_soc = 0.05 + self.max_soc = 1.00 + self.min_chargelevel = self.min_soc * self.capacity + self.max_chargelevel = self.max_soc * self.capacity + self.rt_eff = rt_eff + self.cycle_count = 0 + + soc_at_start = self.min_soc if soc_at_start is None else soc_at_start + self.set_chargelevel(soc_at_start * self.capacity) + + def __repr__(self): + return ( + f"Battolyser(name={self.name}, rated_power={self.max_power}, " + f"rated_capacity={self.capacity}, rt_eff={self.rt_eff})" + ) + + def get_soc(self): + """Get the SoC in % (decimal value)""" + return self.chargelevel / self.capacity + + def set_chargelevel(self, chargelevel): + """Set the chargelevel in MWh. Will automatically change the SoC accordingly.""" + if chargelevel < self.min_chargelevel or chargelevel > self.max_chargelevel: + raise ValueError( + f"Tried to set Charge Level to {chargelevel}. " + f"Charge Level must be a value between " + f"{self.min_chargelevel} and {self.max_chargelevel} (in MWh)" + ) + self.chargelevel = chargelevel + + def set_load(self, load): + """Set load of the Battolyser in MW. + + Use negative values for charging and positive values for discharging. + Returns actual chargespeed, considering technical limitations of the battery. + + Note: We currently assume all efficiency losses occur during discharging + (no losses during charging) + """ + if not hasattr(self, "freq"): + raise AttributeError( + "Time frequency of the model is not defined. " + "Assign asset to a CaseStudy or use Asset.freq(). " + "to set de time frequency and try again." + ) + + load = super().set_load(load) + + unbound_charging = self.MW_to_MWh(load) + if load > 0: + unbound_charging /= self.rt_eff + + chargelevel = self.chargelevel + max_charging = chargelevel - self.max_chargelevel + max_discharging = chargelevel - self.min_chargelevel + bound_charging = min(max(unbound_charging, max_charging), max_discharging) + newcl = chargelevel - bound_charging + self.set_chargelevel(newcl) + + if bound_charging > 0: + bound_charging *= self.rt_eff + charging_power = self.MWh_to_MW(bound_charging) + h2_power = -self.MWh_to_MW(max(bound_charging - unbound_charging, 0)) + self.cycle_count += abs(bound_charging / (self.capacity * 2)) + return charging_power, h2_power + + def charge(self, chargespeed): + """Charge the battery with given chargespeed. + + Redirects to Battery.set_load(). + Returns load (negative value for charging). + """ + chargespeed = self.max_power if chargespeed == "max" else chargespeed + + if chargespeed < 0: + raise ValueError( + f"Chargespeed should be always be a positive value by convention. " + f"Inserted {chargespeed}." + ) + + chargespeed, h2_prod_in_MW = self.set_load(-chargespeed) + + return chargespeed, h2_prod_in_MW + + def discharge(self, dischargespeed): + """Discharge the battery by given amount. + + Redirects to Battery.set_load(). + Returns load (positive value for discharging). + """ + dischargespeed = self.max_power if dischargespeed == "max" else dischargespeed + + if dischargespeed < 0: + raise ValueError( + f"Dischargespeed should be always be a positive value by convention. " + f"Inserted {dischargespeed}." + ) + + dischargespeed = self.set_load(dischargespeed)[0] + return dischargespeed + + +##Added by Shahla, very similar to Hotwaterstorage +class HeatBuffer(Battery): + """Subclass for a storage asset. + + Parameters: + ----------- + rated_capacity : int/float + Rated capacity in MWh + min_buffer_level_perc : float + Minimum buffer level in % + buffer_level_at_start : float + Buffer level at start in % + """ + + def __init__( + self, name, rated_capacity, min_buffer_level_perc, buffer_level_at_start + ): + super().__init__( + name=name, + rated_power=100, + rated_capacity=rated_capacity, + roundtrip_eff=1, + min_soc=min_buffer_level_perc, + max_soc=1, + soc_at_start=buffer_level_at_start, + ) diff --git a/pyrecoy/pyrecoy/pyrecoy/basepath.py b/pyrecoy/pyrecoy/pyrecoy/basepath.py new file mode 100644 index 0000000..0ca1bfd --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/basepath.py @@ -0,0 +1,11 @@ +import os +from pathlib import Path + +if os.environ.get("USERNAME") == "mekre": + BASEPATH = Path("C:\\Users\\mekre\\") +elif os.environ.get("USERNAME") == "christiaan.buitelaar": + BASEPATH = Path("C:\\Users\\christiaan.buitelaar\\Documents\\GitHub\\asset-case-studies\\") +elif os.environ.get("USERNAME") == "karel.van.doesburg": + BASEPATH = Path("C:\\Users\\karel.van.doesburg\\Documents\\asset_studies_recoy\\") +elif os.environ.get("USERNAME") == "shahla.huseynova": + BASEPATH = Path("C:\\Users\\shahla.huseynova\\Documents\\asset_studies_recoy\\asset-case-studies\\") diff --git a/pyrecoy/pyrecoy/pyrecoy/casestudy.py b/pyrecoy/pyrecoy/pyrecoy/casestudy.py new file mode 100644 index 0000000..355a3d1 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/casestudy.py @@ -0,0 +1,548 @@ +import warnings +from copy import deepcopy + +import numpy as np +import pandas as pd + +from .framework import TimeFramework +from .financial import ( + calc_business_case, + calc_co2_costs, + calc_electr_market_results, + calc_grid_costs, + calculate_eb_ode, +) +from .forecasts import Mipf, Qipf +from .prices import get_ets_prices, get_ets_prices_excel, get_ttf_prices +from .converters import EURpertonCO2_to_EURperMWh + + +class CaseStudy: + """ + Representation of a casestudy + """ + + instances = {} + + def __init__(self, time_fw: TimeFramework, freq, name, data=None, forecast=None): + self.name = name + self.modelled_time_period_years = time_fw.modelled_time_period_years + self.start = time_fw.start + self.end = time_fw.end + self.freq = freq + self.dt_index = time_fw.dt_index(freq) + self.data = pd.DataFrame(index=self.dt_index) + self.assets = {} + self.cashflows = {} + self.irregular_cashflows = {} + self.capex = {} + self.total_capex = 0 + self.kpis = {} + + amount_of_days_in_year = 365 + + if self.start.year % 4 == 0: + amount_of_days_in_year = 366 + + self.year_case_duration = (self.end - self.start).total_seconds() / ( + 3600 * 24 * amount_of_days_in_year + ) + self.days_case_duration = self.year_case_duration * amount_of_days_in_year + self.hours_case_duration = self.days_case_duration * 24 + self.quarters_case_duration = self.days_case_duration * 24 * 4 + self.minutes_case_duration = self.days_case_duration * 24 * 60 + # self.year_case_duration = 1 + + if data is not None: + if len(data) != len(self.data): + raise ValueError( + "Length of data is not same as length of CaseStudy.data" + ) + data.index = self.dt_index + self.data = pd.concat([self.data, data], axis=1) + + if forecast is not None: + self.add_forecast(forecast, freq) + + CaseStudy.instances[self.name] = self + + @classmethod + def list_instances(cls): + """ + Returns a list with all CaseStudy instances. + Useful if you want to iterate over all instances + or use them as input to a function. + """ + return list(cls.instances.values()) + + def add_forecast(self, forecast, freq): + """ + Add forecast and price data to the data table of the CaseStudy instance. + """ + # TODO Add error handling for frequencies + if forecast == "mipf" and freq == "1T": + forecast_data = Mipf( + start=self.start, end=self.end, tidy=True, include_nextQ=False + ).data + elif forecast == "mipf" and freq == "15T": + forecast_data = Mipf( + start=self.start, end=self.end, tidy=False, include_nextQ=False + ).data + elif forecast == "qipf": + forecast_data = Qipf(start=self.start, end=self.end, freq=self.freq).data + else: + raise ValueError("Forecast does not exist. Use 'mipf' or 'qipf'.") + + self.data = pd.concat([self.data, forecast_data], axis=1) + + def add_gasprices(self): + """ + Add gas price data (TTF day-head) to the data table of the CaseStudy instance. + """ + self.data["Gas prices (€/MWh)"] = get_ttf_prices( + start=self.start, end=self.end, freq=self.freq + )["Gas prices (€/MWh)"] + + def add_co2prices(self, perMWh=False): + """ + Add CO2 prices (ETS) data to the data table of the CaseStudy instance. + """ + self.data["CO2 prices (€/ton)"] = get_ets_prices( + start=self.start, end=self.end, freq=self.freq + )["CO2 prices (€/MWh)"] + + if perMWh: + self.data["CO2 prices (€/MWh)"] = EURpertonCO2_to_EURperMWh( + self.data["CO2 prices (€/ton)"] + ).round(2) + + def add_co2prices_excel(self, perMWh=False): + """ + Add CO2 prices (ETS) data to the data table of the CaseStudy instance. + """ + self.data["CO2 prices (€/ton)"] = get_ets_prices_excel( + start=self.start, end=self.end, freq=self.freq + )["CO2 prices (€/ton)"] + + if perMWh: + self.data["CO2 prices (€/MWh)"] = EURpertonCO2_to_EURperMWh( + self.data["CO2 prices (€/ton)"] + ).round(2) + + def add_asset(self, asset): + """Assign an Asset instance to CaseStudy instance. + + Method will create a unique copy of the Asset instance. + If Asset contains financial information, + cashflows are automatically updated. + """ + assetcopy = deepcopy(asset) + assetcopy.set_freq(self.freq) + self.assets[assetcopy.name] = assetcopy + + if hasattr(assetcopy, "opex"): + self.add_cashflow(f"{assetcopy.name} OPEX (€)", -assetcopy.opex) + + if hasattr(assetcopy, "capex"): + self.add_capex(f"{assetcopy.name} CAPEX (€)", -assetcopy.capex) + + if hasattr(assetcopy, "devex"): + self.add_capex(f"{assetcopy.name} DEVEX (€)", -assetcopy.devex) + + def get_assets(self): + """Returns all Asset instances assigned to CaseStudy instance.""" + return list(self.assets.values()) + + def add_cashflow(self, label, amount): + """Add a yearly cashflow to the CaseStudy + + Convention is negative values for costs and positive values for revenue. + """ + self.cashflows[label] = round(amount, 2) + + def add_capex(self, label, amount): + """Add a capex component to the CaseStudy + + Convention is to use positive values + """ + capex = round(amount, 2) * -1 + self.capex[label] = capex + self.total_capex += capex + + def add_irregular_cashflow(self, amount, year): + base = self.irregular_cashflows[year] if year in self.irregular_cashflows else 0 + self.irregular_cashflows[year] = base + amount + + def generate_electr_market_results(self, real_col, nom_col=None): + """Generates a dictionary with results of the simulation on energy market. + + Dictionary is saved in CaseStudy.energy_market_results. + Total market result is automatically added to cashflow dictionary. + """ + if nom_col is None: + nom_col = "Nom. vol." + self.data[nom_col] = 0 + + data = calc_electr_market_results(self.data, nom_col=nom_col, real_col=real_col) + self.data = data + + total_produced = data["Prod. vol."].sum() + total_consumed = -data["Cons. vol."].sum() + + self.total_electricity_cons = total_consumed * (-1) + + selling = data[real_col] > 0 + mean_selling_price = ( + data["Combined Result"].where(selling).sum() / total_produced + if total_produced != 0 + else 0 + ) + + mean_buying_price = ( + data["Combined Result"].where(~selling).sum() / total_consumed * (-1) + if round(total_consumed, 2) != 0 + else 0 + ) + + total_comb_result = data["Combined Result"].sum() + + self.electr_market_results = { + "Total net volume (MWh)": data[real_col].sum(), + "Total exported to grid (MWh)": total_produced, + "Total consumed from grid (MWh)": total_consumed, + "Total nominated volume (MWh)": data[nom_col].sum(), + "Absolute imbalance volume (MWh)": data["Imb. vol."].abs().sum(), + "Mean selling price (€/MWh)": mean_selling_price, + "Mean buying price (€/MWh)": mean_buying_price, + "Total day-ahead result (€)": data["Day-Ahead Result"].sum(), + "Total POS result (€)": data["POS Result"].sum(), + "Total NEG result (€)": data["NEG Result"].sum(), + "Total imbalance result (€)": data["Imbalance Result"].sum(), + "Total combined result (€)": total_comb_result, + } + + self.electr_market_result = total_comb_result + self.add_cashflow("Result on electricity market (€)", total_comb_result) + + def add_gas_costs(self, gasvolumes_col, gasprice_col="Gas prices (€/MWh)"): + """Calculate gas costs and add to cashflows + + Parameters: + ----------- + gasprices_col : str + Column containing gas prices in CaseStudy.data dataframe + gasvolumes_col : str + List of column names containing gas volumes in CaseStudy.data dataframe + """ + gasprices = self.data[gasprice_col] + gasvolumes = self.data[gasvolumes_col].abs() + gas_costs = gasprices * gasvolumes * -1 + self.data["Gas commodity costs (€)"] = gas_costs + + self.total_gas_cons = gasvolumes.sum() + self.total_gas_costs = round(gas_costs.sum(), 2) + self.add_cashflow("Gas consumption costs (€)", self.total_gas_costs) + + def add_co2_costs( + self, volume_cols, co2_price_col="CO2 prices (€/ton)", fuel="gas" + ): + """Calculate co2 costs and add to cashflows + + Parameters: + ----------- + Gasprices : str + Column containing gas prices in CaseStudy.data dataframe + Gasvolumes : list + List of column names containing gas volumes in CaseStudy.data dataframe + """ + if isinstance(volume_cols, str): + volume_cols = [volume_cols] + + co2_prices = self.data[co2_price_col] + volumes = [self.data[col] for col in volume_cols] + self.total_co2_costs = calc_co2_costs( + co2_prices=co2_prices, volumes=volumes, fuel=fuel + ) + self.add_cashflow("CO2 emission costs (€)", self.total_co2_costs) + + def add_eb_ode( + self, + commodity, + cons_col=None, + tax_bracket=None, + base_cons=None, + horti=False, + m3=False, + add_cons_MWh=0, + year=2020, + split=False, + ): + """Add EB & ODE to cashflows + + See financial.calc_eb_ode() for more detailed documentation. + + Parameters: + ----------- + commodity : str + {'gas', 'electricity'} + cons_col : str + Optional parameter to specificy column name of the + consumption values in MWh. + tax_bracket : numeric + Tax bracket that the client is in [1-4] + Use either 'tax_bracket' of 'base_cons', not both. + base_cons : numeric + Base consumption volume of the client + Use either 'tax_bracket' of 'base_cons', not both. + horti : bool + Set to True to use horticulture rates + m3 : bool + Set to True if you want to enter gas volumes in m3 + add_cons_MWh : + Enables manually adding extra consumption + """ + if cons_col: + cons = self.data[cons_col].abs().sum() + else: + cons = getattr(self, f"total_{commodity}_cons") + + cons = cons + add_cons_MWh + + eb, ode = calculate_eb_ode( + cons=cons, + electr=(commodity == "electricity"), + tax_bracket=tax_bracket, + base_cons=base_cons, + horti=horti, + m3=m3, + year=year, + ) + if split: + self.add_cashflow(f"EB {commodity.capitalize()} (€)", eb) + self.add_cashflow(f"ODE {commodity.capitalize()} (€)", ode) + else: + self.add_cashflow(f"{commodity.capitalize()} taxes (€)", eb + ode) + + def add_grid_costs( + self, + power_MW_col, + grid_operator, + year, + connection_type, + cons_MWh_col=None, + kw_contract_kW=None, + path=None, + add_peak_kW=0, + add_cons_MWh=0, + ): + """Add variable grid transport costs to cashflows + + See financial.calc_grid_costs() for more detailed documentation. + + Parameters: + ----------- + power_MW_col : str + Column in data table with power usage in MW + grid_operator : str + {'tennet', 'liander', 'enexis', 'stedin'} + year : int + Year, e.g. 2020 + connection_type : str + Connection type, e.g. 'HS' + cons_MWh_col : str + Column in data table containing grid consumption in MWh + kw_contract_kW : numeric + in kW. If provided, function will assume fixed value kW contract + path : str + Specify path with grid tariff files. Leave empty to use default path. + add_peak_kW : float + Enables manually adding peak consumption to the data + """ + cols = [power_MW_col] + if cons_MWh_col is not None: + cols.append(cons_MWh_col) + + peaks_kW = ( + (self.data[power_MW_col] * 1000 - add_peak_kW) + .resample("15T") + .mean() + .abs() + .resample("M") + .max() + .to_list() + ) + + cons_kWh = ( + self.data[cons_MWh_col].sum() * 1000 if cons_MWh_col is not None else 0 + ) + add_cons_MWh + + self.grid_costs = calc_grid_costs( + peakload_kW=peaks_kW, + grid_operator=grid_operator, + year=year, + connection_type=connection_type, + kw_contract_kW=kw_contract_kW, + totalcons_kWh=cons_kWh, + path=path, + modelled_time_period_years=self.modelled_time_period_years, + ) + + total_grid_costs = sum(self.grid_costs.values()) + + self.add_cashflow("Grid transport costs (€)", total_grid_costs) + + def calculate_ebitda(self, project_duration, residual_value=None): + """Calculate yearly EBITDA based on cashflows + + Calculation table and EBITDA value are saved in CaseStudy. + """ + for key, val in self.cashflows.items(): + if np.isnan(val): + warnings.warn( + f"Cashflow '{key}' for CaseStudy '{self.name}' contains NaN value. " + "Something might have gone wrong. Replacing NaN with 0 for now." + ) + self.cashflows[key] = 0 + + assets = self.get_assets() + + for asset in assets: + if not asset.depreciate: + pass + elif asset.lifetime is None: + raise ValueError(f"'lifetime' property of {asset.name} was not set.") + elif project_duration > asset.lifetime: + warnings.warn( + f"Project duration is larger than technical lifetime of asset '{asset.name}'. " + "Will continue by limiting project duration to the technical lifetime of the asset." + ) + project_duration = int(asset.lifetime) + + depreciations, residual_value = CaseStudy._calc_depr_and_residual_val( + assets, self.total_capex, residual_value, project_duration + ) + + self.ebitda = sum(self.cashflows.values()) + self.ebitda_calc = deepcopy(self.cashflows) + self.ebitda_calc["EBITDA (€)"] = self.ebitda + self.ebitda_calc["Depreciation (€)"] = depreciations * -1 + self.ebitda_calc["EBITDA + depr (€)"] = self.ebitda + depreciations * -1 + + def calculate_business_case( + self, + project_duration, + discount_rate, + residual_value=None, + baseline=None, + bl_res_value=None, + eia=False, + vamil=False, + fixed_income_tax=False, + ): + """Calculates business case (NPV, IRR) for the CaseStudy. + + Business case calculation is stored in CaseStudy.business_case + NPV is stored in CaseStudy.npv + IRR is stored in Casestudy.irr + + Parameters: + ----------- + project_duration : int + In years + discount_rate : float + In % (decimal value) + residual_value : numeric + Can be used to manually set residual value of assets (all assets combined). + + Defaults to None, in which case residual_value is calculated + based on linear depreciation over technical lifetime. + baseline : CaseStudy + Baseline to compare against + bl_res_value : numeric + Similar to 'residual_value' for baseline + eia : bool + Apply EIA ("Energie Investerings Aftrek") tax discounts. + Defaults to False. + vamil : bool + Apply VAMIL ("Willekeurige afschrijving milieu-investeringen") tax discounts. + Defaults to False. + """ + + assets = self.get_assets() + + for asset in assets: + if not asset.depreciate: + pass + elif asset.lifetime is None: + raise ValueError(f"'lifetime' property of {asset.name} was not set.") + elif project_duration > asset.lifetime: + warnings.warn( + f"Project duration is larger than technical lifetime of asset '{asset.name}'. " + "Will continue by limiting project duration to the technical lifetime of the asset." + ) + project_duration = int(asset.lifetime) + + capex = self.total_capex + yearly_ebitda = self.ebitda / self.modelled_time_period_years + + irregular_cashflows = ( + self._calc_irregular_cashflows(project_duration, baseline=baseline) + if self.irregular_cashflows + else 0 + ) + + depreciations, residual_value = CaseStudy._calc_depr_and_residual_val( + assets, capex, residual_value, project_duration + ) + + if baseline is not None: + bl_assets = baseline.assets.values() + bl_capex = baseline.total_capex + bl_depr, bl_res_val = CaseStudy._calc_depr_and_residual_val( + bl_assets, bl_capex, bl_res_value, project_duration + ) + capex -= bl_capex + depreciations -= bl_depr + residual_value -= bl_res_val + yearly_ebitda -= baseline.ebitda / self.modelled_time_period_years + + self.business_case = calc_business_case( + capex=capex, + discount_rate=discount_rate, + project_duration=project_duration, + depreciation=depreciations, + residual_value=residual_value, + regular_earnings=yearly_ebitda, + irregular_cashflows=irregular_cashflows, + eia=eia, + vamil=vamil, + fixed_income_tax=fixed_income_tax, + ) + + self.irr = self.business_case.loc["IRR (%)", "Year 0"] / 100 + self.npv = self.business_case.loc["NPV (€)", "Year 0"] + self.spp = self.business_case.loc["Simple Payback Period", "Year 0"] + + @staticmethod + def _calc_depr_and_residual_val(assets, capex, residual_value, project_duration): + if residual_value is None: + assets = [asset for asset in assets if asset.depreciate] + depreciations = sum( + (asset.capex - asset.salvage_value) / asset.lifetime for asset in assets + ) + residual_value = capex - depreciations * project_duration + else: + depreciations = (capex - residual_value) / project_duration + + return depreciations, residual_value + + def _calc_irregular_cashflows(self, project_duration, baseline=None): + irr_earnings = [0] * (project_duration) + + for year, cashflow in self.irregular_cashflows.items(): + if baseline: + cashflow -= baseline.irregular_cashflows.get(year, 0) + + irr_earnings[int(year) - 1] = cashflow + + return irr_earnings diff --git a/pyrecoy/pyrecoy/pyrecoy/colors.py b/pyrecoy/pyrecoy/pyrecoy/colors.py new file mode 100644 index 0000000..1660c9e --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/colors.py @@ -0,0 +1,43 @@ +recoy_colordict = { + "RecoyDarkBlue": "#0e293b", + "RecoyBlue": "#1f8376", + "RecoyRed": "#dd433b", + "RecoyYellow": "#f3d268", + "RecoyGreen": "#46a579", + "RecoyPurple": "#6d526b", + "RecoyOrange": "#f2a541", + "RecoyBlueGrey": "#145561", + "RecoyDarkGrey": "#2a2a2a", + "RecoyLilac": "#C3ACCE", + "RecoyBrown": "#825E52", + "RecoyLightGreen": "#7E9181", + "RecoyCitron": "#CFD186", + "RecoyPink": "#F5B3B3" +} + +recoy_greysdict = { + "RecoyLightGrey": "#e6e6e6", + "RecoyGrey": "#c0c0c0", + "RecoyDarkGrey": "#2a2a2a", +} + +recoydarkblue = recoy_colordict["RecoyDarkBlue"] +recoyyellow = recoy_colordict["RecoyYellow"] +recoygreen = recoy_colordict["RecoyGreen"] +recoyred = recoy_colordict["RecoyRed"] +recoyblue = recoy_colordict["RecoyBlue"] +recoyorange = recoy_colordict["RecoyOrange"] +recoypurple = recoy_colordict["RecoyPurple"] +recoybluegrey = recoy_colordict["RecoyBlueGrey"] +recoylightgrey = recoy_greysdict["RecoyLightGrey"] +recoygrey = recoy_greysdict["RecoyGrey"] +recoydarkgrey = recoy_greysdict["RecoyDarkGrey"] +recoylilac = recoy_colordict["RecoyLilac"] +recoybrown = recoy_colordict["RecoyBrown"] +recoylightgreen = recoy_colordict["RecoyLightGreen"] +recoycitron = recoy_colordict["RecoyCitron"] +recoypink = recoy_colordict["RecoyPink"] + +recoycolors = list(recoy_colordict.values()) + +transparent = "rgba(0, 0, 0, 0)" diff --git a/pyrecoy/pyrecoy/pyrecoy/converters.py b/pyrecoy/pyrecoy/pyrecoy/converters.py new file mode 100644 index 0000000..6b5b239 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/converters.py @@ -0,0 +1,66 @@ +import pandas as pd + + +def MWh_to_m3(MWh): + return MWh / 9.769 * 1000 + + +def MWh_to_GJ(MWh): + return MWh * 3.6 + + +def EURperm3_to_EURperMWh(EURperm3): + return EURperm3 / 9.769 * 1000 + + +def EURperMWh_to_EURperGJ(EURperMWh): + return EURperMWh * 3.6 + + +def MWh_gas_to_tonnes_CO2(MWh): + return MWh * 1.84 / 9.769 + + +def EURpertonCO2_to_EURperMWh(EURpertonCO2): + return EURpertonCO2 * 1.884 / 9.769 + + +def EURperLHV_to_EURperHHV(MWh_LHV): + return MWh_LHV / 35.17 * 31.65 + + +def EURperHHV_to_EURperLHV(MWh_HHV): + return MWh_HHV / 31.65 * 35.17 + + +def GJ_gas_to_kg_NOX(GJ): + return GJ * 0.02 + + +def MWh_gas_to_kg_NOX(MWh): + return GJ_gas_to_kg_NOX(MWh_to_GJ(MWh)) + + +def fastround(n, decimals): + """Round a value to certain number of decimals, faster than Python implementation""" + multiplier = 10**decimals + return int(n * multiplier + 0.5) / multiplier + + +def add_season_column(data): + """Adds a column containing seasons to a DataFrame with datetime index""" + data["season"] = (data.index.month % 12 + 3) // 3 + + seasons = {1: "Winter", 2: "Spring", 3: "Summer", 4: "Fall"} + data["season"] = data["season"].map(seasons) + return data + + +def dt_column_to_local_time(column): + return column.dt.tz_localize("UTC").dt.tz_convert("Europe/Amsterdam") + + +def timestamp_to_utc(timestamp): + if isinstance(timestamp, str): + timestamp = pd.to_datetime(timestamp).tz_localize("Europe/Amsterdam") + return timestamp.tz_convert("UTC") diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2020.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2020.csv new file mode 100644 index 0000000..23eb1a4 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2020.csv @@ -0,0 +1,6 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441;0,0092;20,54;17,04 +MS-D;441;0,0092;12,24;17,04 +MS-T;441;0,0055;10,84;13,56 +HS/MS;2760;0;16,95;20,52 +TS;2760;0;12,01;16,56 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2021.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2021.csv new file mode 100644 index 0000000..dda155d --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2021.csv @@ -0,0 +1,6 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441,00;0,0101;22,74;18,84 +MS-D;441,00;0,0101;13,57;18,84 +MS-T;441,00;0,0062;12,26;15,36 +HS/MS;2760,00;0,00;18,71;22,68 +TS;2760,00;0,00;13,68;18,84 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2022.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2022.csv new file mode 100644 index 0000000..25e20d6 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2022.csv @@ -0,0 +1,6 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441,00;0,0111;24,23;20,4 +MS-D;441,00;0,0111;14,30;20,4 +MS-T;441,00;0,0069;13,03;16,8 +HS/MS;2760,00;0,00;20,12;24,72 +TS;2760,00;0,00;14,43;21 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2023.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2023.csv new file mode 100644 index 0000000..061f6ac --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2023.csv @@ -0,0 +1,6 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441,00;0,0111;31,55;26,52 +MS-D;441,00;0,0111;18,62;26,52 +MS-T;441,00;0,0069;16,99;21,96 +HS/MS;2760,00;0,00;26,19;32,16 +TS;2760,00;0,00;18,80;27,36 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2024.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2024.csv new file mode 100644 index 0000000..e5dde2d --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/enexis_2024.csv @@ -0,0 +1,6 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441;0,0206;44,84;37,68 +MS-D;441;0,0206;26,47;37,68 +MS-T;441;0,0128;24,19;31,2 +HS/MS;2760;0;37,2;45,84 +TS;2760;0;26,89;38,64 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2020.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2020.csv new file mode 100644 index 0000000..1bd3bc5 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2020.csv @@ -0,0 +1,7 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441;0,0097;21,12;19,2 +MS;441;0,0097;13,44;19,2 +TS/MS;2760;0;21,84;27,24 +HS/MS;2760;0;21,84;27,24 +TS;2760;0;20,88;26,4 +HS;2760;0;10,56;13,32 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2021.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2021.csv new file mode 100644 index 0000000..d137767 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2021.csv @@ -0,0 +1,7 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441,00;0,01;23,16;20,88 +MS;441,00;0,01;14,76;20,88 +TS/MS;2760,00;0,00;24,12;30,00 +HS/MS;2760,00;0,00;24,12;30,00 +TS;2760,00;0,00;23,04;29,16 +HS;2760,00;0,00;11,64;14,76 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2022.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2022.csv new file mode 100644 index 0000000..c42f9f6 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2022.csv @@ -0,0 +1,7 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441,00;0,0107;23,04;20,76 +MS;441,00;0,0107;14,52;20,76 +TS/MS;2760,00;0,00;23,28;32,40 +HS/MS;2760,00;0,00;23,28;32,40 +TS;2760,00;0,00;23,16;30,00 +HS;2760,00;0,00;12,00;14,88 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2023.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2023.csv new file mode 100644 index 0000000..7f83aee --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2023.csv @@ -0,0 +1,7 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441,00;0,0160;34,56;31,08 +MS;441,00;0,0160;21,84;31,08 +TS/MS;2760,00;0,00;35,04;48,72 +HS/MS;2760,00;0,00;35,04;48,72 +TS;2760,00;0,00;34,68;45,00 +HS;2760,00;0,00;18,00;22,44 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2024.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2024.csv new file mode 100644 index 0000000..4542fa5 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/liander_2024.csv @@ -0,0 +1,7 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;705,6;0,0256;55,296;49,728 +MS;705,6;0,0256;34,944;49,728 +TS/MS;4416;0;56,064;77,952 +HS/MS;4416;0;56,064;77,952 +TS;4416;0;55,488;72 +HS;4416;0;28,8;35,904 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2020.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2020.csv new file mode 100644 index 0000000..6838cde --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2020.csv @@ -0,0 +1,3 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +EHS;12478,96;0,00;13,29;16,20 +HS;2760;0,00;23,58;27,48 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2021.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2021.csv new file mode 100644 index 0000000..f3d35bb --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2021.csv @@ -0,0 +1,5 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441;0,01;23,00;18,80 +MS;441;0,01;12,36;18,80 +HS+TS/MS;2760;0,00;23,00;29,52 + TS ;2760;0,00;21,80;30,48 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2022.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2022.csv new file mode 100644 index 0000000..3111dda --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2022.csv @@ -0,0 +1,5 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;453;0,01;25,04;19,16 +MS;453;0,01;12,71;19,16 +HS+TS/MS;2760;0,00;24,06;32,1 +TS ;2760;0,00;23,25;31,73 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2023.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2023.csv new file mode 100644 index 0000000..bed966f --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2023.csv @@ -0,0 +1,5 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441;0,01;38,06;29,12 +MS;441;0,01;19,32;29,12 +HS+TS/MS;2760;0,00;36,57;48,78 +TS ;2760;0,00;35,33;48,22 \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2024.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2024.csv new file mode 100644 index 0000000..9afcc83 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/stedin_2024.csv @@ -0,0 +1,5 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +MS/LS;441;0,0176;43,1;34,23 +MS;441;0,0176;23;34,23 +HS+TS/MS;2760;0;42,45;56,62 +TS ;2760;0;33,46;46,52 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2020.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2020.csv new file mode 100644 index 0000000..6838cde --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2020.csv @@ -0,0 +1,3 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +EHS;12478,96;0,00;13,29;16,20 +HS;2760;0,00;23,58;27,48 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2021.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2021.csv new file mode 100644 index 0000000..f2981ec --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2021.csv @@ -0,0 +1,3 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +EHS;12478,96;0,00;14,80;18,00 +HS;2760;0,00;24,80;29,04 \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2022.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2022.csv new file mode 100644 index 0000000..0154df6 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2022.csv @@ -0,0 +1,3 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +EHS;12478,96;0,00;11,45;14,88 +HS;2760,00;0,00;23,70;29,16 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2023.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2023.csv new file mode 100644 index 0000000..232d699 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2023.csv @@ -0,0 +1,3 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +EHS;12478,96;0,00;27,98;36,36 +HS;2760,00;0,00;41,04;50,52 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2024.csv b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2024.csv new file mode 100644 index 0000000..0227fed --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/grid_tariffs/tennet_2024.csv @@ -0,0 +1,3 @@ +Aansluiting;Vastrecht per jaar;kWh tarief;kW contract per jaar;kW max per jaar +EHS;12478,96;0,00;60,65;82,92 +HS;2760,00;0,00;73,52;91,44 diff --git a/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/electricity_eb.json b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/electricity_eb.json new file mode 100644 index 0000000..17d9914 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/electricity_eb.json @@ -0,0 +1,72 @@ +{ + "0 t/m 10.000 kWh": { + "2013": 0.1165, + "2014": 0.1185, + "2015": 0.1196, + "2016": 0.1007, + "2017": 0.1013, + "2018": 0.10458, + "2019": 0.09863, + "2020": 0.0977, + "2021": 0.09428, + "2022": 0.03679, + "2023": 0.12599, + "2024": 0.1088 + }, + "10.001 t/m 50.000 kWh": { + "2013": 0.0424, + "2014": 0.0431, + "2015": 0.0469, + "2016": 0.04996, + "2017": 0.04901, + "2018": 0.05274, + "2019": 0.05337, + "2020": 0.05083, + "2021": 0.05164, + "2022": 0.04361, + "2023": 0.10046, + "2024": 0.09037 + }, + "50.001 t/m 10 miljoen kWh": { + "2013": 0.0113, + "2014": 0.0115, + "2015": 0.0125, + "2016": 0.01331, + "2017": 0.01305, + "2018": 0.01404, + "2019": 0.01421, + "2020": 0.01353, + "2021": 0.01375, + "2022": 0.01189, + "2023": 0.03942, + "2024": 0.03943 + }, + "meer dan 10 miljoen kWh particulier": { + "2013": 0.001, + "2014": 0.001, + "2015": 0.001, + "2016": 0.00107, + "2017": 0.00107, + "2018": 0.00116, + "2019": 0.00117, + "2020": 0.00111, + "2021": 0.00113, + "2022": 0.00114, + "2023": 0.00175, + "2024": 0.00254 + }, + "meer dan 10 miljoen kWh zakelijk": { + "2013": 0.0005, + "2014": 0.0005, + "2015": 0.0005, + "2016": 0.00053, + "2017": 0.00053, + "2018": 0.00057, + "2019": 0.00058, + "2020": 0.00055, + "2021": 0.00056, + "2022": 0.00057, + "2023": 0.00115, + "2024": 0.00188 + } +} \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/electricity_ode.json b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/electricity_ode.json new file mode 100644 index 0000000..d203659 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/electricity_ode.json @@ -0,0 +1,67 @@ +{ + "0 t/m 10.000 kWh": { + "2013": 0.0011, + "2014": 0.0023, + "2015": 0.0036, + "2016": 0.0056, + "2017": 0.0074, + "2018": 0.0132, + "2019": 0.0189, + "2020": 0.0273, + "2021": 0.03, + "2022": 0.0305, + "2023": 0.00 + }, + "10.001 t/m 50.000 kWh": { + "2013": 0.0014, + "2014": 0.0027, + "2015": 0.0046, + "2016": 0.007, + "2017": 0.0123, + "2018": 0.018, + "2019": 0.0278, + "2020": 0.0375, + "2021": 0.0411, + "2022": 0.0418, + "2023": 0.00 + }, + "50.001 t/m 10 mln kWh": { + "2013": 0.0004, + "2014": 0.0007, + "2015": 0.0012, + "2016": 0.0019, + "2017": 0.0033, + "2018": 0.0048, + "2019": 0.0074, + "2020": 0.0205, + "2021": 0.0225, + "2022": 0.0229, + "2023": 0.00 + }, + "meer dan 10 mln kWh niet zakelijk": { + "2013": 0.000017, + "2014": 0.000034, + "2015": 0.000055, + "2016": 0.000084, + "2017": 0.000131, + "2018": 0.000194, + "2019": 0.0003, + "2020": 0.0004, + "2021": 0.0004, + "2022": 0.0005, + "2023": 0.00 + }, + "meer dan 10 mln kWh zakelijk": { + "2013": 0.000017, + "2014": 0.000034, + "2015": 0.000055, + "2016": 0.000084, + "2017": 0.000131, + "2018": 0.000194, + "2019": 0.0003, + "2020": 0.0004, + "2021": 0.0004, + "2022": 0.0005, + "2023": 0.00 + } + } \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_eb.json b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_eb.json new file mode 100644 index 0000000..a2e4ff3 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_eb.json @@ -0,0 +1,86 @@ +{ + "0 t/m 5.000 m3 en blokverwarming": { + "2013": 0.1862, + "2014": 0.1894, + "2015": 0.1911, + "2016": 0.25168, + "2017": 0.25244, + "2018": 0.26001, + "2019": 0.29313, + "2020": 0.33307, + "2021": 0.34856, + "2022": 0.36322, + "2023": 0.48980, + "2024": 0.58301 + }, + "5.001 t/m 170.000 m3": { + "2013": 0.1862, + "2014": 0.1894, + "2015": 0.1911, + "2016": 0.25168, + "2017": 0.25244, + "2018": 0.26001, + "2019": 0.29313, + "2020": 0.33307, + "2021": 0.34856, + "2022": 0.36322, + "2023": 0.48980, + "2024": 0.58301 + }, + "170.001 t/m 1 miljoen m3": { + "2013": 0.0439, + "2014": 0.0446, + "2015": 0.0677, + "2016": 0.06954, + "2017": 0.06215, + "2018": 0.06464, + "2019": 0.06542, + "2020": 0.06444, + "2021": 0.06547, + "2022": 0.06632, + "2023": 0.09621, + "2024": 0.22378 + }, + "meer dan 1 miljoen t/m 10 miljoen m3": { + "2013": 0.016, + "2014": 0.0163, + "2015": 0.0247, + "2016": 0.02537, + "2017": 0.02265, + "2018": 0.02355, + "2019": 0.02383, + "2020": 0.02348, + "2021": 0.02386, + "2022": 0.02417, + "2023": 0.05109, + "2024": 0.12825 + }, + "meer dan 10 miljoen m3 particulier": { + "2013": 0.0115, + "2014": 0.0117, + "2015": 0.0118, + "2016": 0.01212, + "2017": 0.01216, + "2018": 0.01265, + "2019": 0.0128, + "2020": 0.01261, + "2021": 0.01281, + "2022": 0.01298, + "2023": 0.03919, + "2024": 0.04886 + }, + "meer dan 10 miljoen m3 zakelijk": { + "2013": 0.0115, + "2014": 0.0117, + "2015": 0.0118, + "2016": 0.01212, + "2017": 0.01216, + "2018": 0.01265, + "2019": 0.0128, + "2020": 0.01261, + "2021": 0.01281, + "2022": 0.01298, + "2023": 0.03919, + "2024": 0.04886 + } + } \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_horticulture_eb.json b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_horticulture_eb.json new file mode 100644 index 0000000..a1defc9 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_horticulture_eb.json @@ -0,0 +1 @@ +{"0 t\/m 5.000 m\u00b3":{"2013":0.02991,"2014":0.03042,"2015":0.03069,"2016":0.04042,"2017":0.04054,"2018":0.04175,"2019":0.04707,"2020":0.05348,"2021":0.05597, "2022":0.05833, "2023":0.07867, "2024":0.09365},"5.001 t\/m 170.000 m\u00b3":{"2013":0.02991,"2014":0.03042,"2015":0.03069,"2016":0.04042,"2017":0.04054,"2018":0.04175,"2019":0.04707,"2020":0.05348,"2021":0.05597, "2022":0.05833, "2023":0.07867, "2024":0.09365},"170.001 t\/m 1\u00a0miljoen m\u00b3":{"2013":0.0222,"2014":0.02258,"2015":0.02278,"2016":0.02339,"2017":0.02346,"2018":0.0244,"2019":0.02469,"2020":0.02432,"2021":0.02471, "2022":0.02503, "2023":0.03629, "2024":0.08444},"meer dan 1\u00a0miljoen t\/m 10\u00a0miljoen m\u00b3":{"2013":0.016,"2014":0.0163,"2015":0.0247,"2016":0.02537,"2017":0.02265,"2018":0.02355,"2019":0.02383,"2020":0.02348,"2021":0.02386, "2022":0.02417, "2023":0.05109, "2024":0.12855},"meer dan 10 miljoen m\u00b3":{"2013":0.0115,"2014":0.0117,"2015":0.0118,"2016":0.01212,"2017":0.01216,"2018":0.01265,"2019":0.0128,"2020":0.01261,"2021":0.01281, "2022":0.01298, "2023":0.03919, "2024":0.04886}} \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_horticulture_ode.json b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_horticulture_ode.json new file mode 100644 index 0000000..52606e7 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_horticulture_ode.json @@ -0,0 +1 @@ +{"0 t\/m 170.000 m\u00b3":{"2013":0.0004,"2014":0.0007,"2015":0.0012,"2016":0.0018,"2017":0.0026,"2018":0.0046,"2019":0.0084,"2020":0.0124,"2021":0.0137, "2022":0.0139, "2023":0.00},"170.001 t\/m 1 mln m\u00b3":{"2013":0.0004,"2014":0.0009,"2015":0.0014,"2016":0.0021,"2017":0.0025,"2018":0.004,"2019":0.0061,"2020":0.0081,"2021":0.0089, "2022":0.0090, "2023":0.00},"meer dan 1 mln t\/m 10 mln m\u00b3":{"2013":0.0003,"2014":0.0005,"2015":0.0008,"2016":0.0013,"2017":0.0027,"2018":0.0039,"2019":0.0059,"2020":0.0212,"2021":0.0232, "2022":0.0236, "2023":0.00},"meer dan 10 mln m\u00b3":{"2013":0.0002,"2014":0.0004,"2015":0.0006,"2016":0.0009,"2017":0.0013,"2018":0.0021,"2019":0.0031,"2020":0.0212,"2021":0.0232, "2022":0.0236, "2023":0.00}} \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_ode.json b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_ode.json new file mode 100644 index 0000000..1390ce4 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/data/tax_tariffs/gas_ode.json @@ -0,0 +1,54 @@ +{ + "0 t/m 170.000 m3 en blokverwarming": { + "2013": 0.0023, + "2014": 0.0046, + "2015": 0.0074, + "2016": 0.0113, + "2017": 0.0159, + "2018": 0.0285, + "2019": 0.0524, + "2020": 0.0775, + "2021": 0.0851, + "2022": 0.0865, + "2023": 0.00 + }, + "170.001 t/m 1 mln m3": { + "2013": 0.0009, + "2014": 0.0017, + "2015": 0.0028, + "2016": 0.0042, + "2017": 0.0074, + "2018": 0.0106, + "2019": 0.0161, + "2020": 0.0214, + "2021": 0.0235, + "2022": 0.0239, + "2023": 0.00 + }, + "meer dan 1 mln t/m 10 mln m3": { + "2013": 0.0003, + "2014": 0.0005, + "2015": 0.0008, + "2016": 0.0013, + "2017": 0.0027, + "2018": 0.0039, + "2019": 0.0059, + "2020": 0.0212, + "2021": 0.0232, + "2022": 0.0236, + "2023": 0.00 + }, + "meer dan 10 mln m3": { + "2013": 0.0002, + "2014": 0.0004, + "2015": 0.0006, + "2016": 0.0009, + "2017": 0.0013, + "2018": 0.0021, + "2019": 0.0031, + "2020": 0.0212, + "2021": 0.0232, + "2022": 0.0236, + "2023": 0.00 + } + } \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/database/Models/base.py b/pyrecoy/pyrecoy/pyrecoy/database/Models/base.py new file mode 100644 index 0000000..f746c28 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/database/Models/base.py @@ -0,0 +1,25 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import pytz +from sqlalchemy import DateTime, TypeDecorator + +class DateTimeTZ(TypeDecorator): + impl = DateTime + + def process_result_value(self, value, dialect): + if value is None: + return None + return value.replace(tzinfo=pytz.utc) + +DATABASE_URL = 'mssql+pyodbc://rop:OptimalTransition@rop-prices-test.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com:8472/test?driver=ODBC+Driver+17+for+SQL+Server' +# DATABASE_URL = 'mssql+pyodbc://rop:OptimalTransition@rop-prices-dev-20230123.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com:1433/test?driver=ODBC+Driver+17+for+SQL+Server' +# DATABASE_URL = Config().SQLALCHEMY_DATABASE_URI +BASE = declarative_base() +ENGINE_PRICES = create_engine( + DATABASE_URL, + pool_size=2000, + max_overflow=0, + connect_args={"options": "-c timezone=utc"}, +) +SESSION = sessionmaker(bind=ENGINE_PRICES) diff --git a/pyrecoy/pyrecoy/pyrecoy/databases.py b/pyrecoy/pyrecoy/pyrecoy/databases.py new file mode 100644 index 0000000..9654153 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/databases.py @@ -0,0 +1,69 @@ +from sqlalchemy import create_engine, MetaData, Table +import pandas as pd + +DATABASES = { + "ngsc_dev": { + "db_url": "ngsc-dev-msql.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "ngsc_dev", + "db_user": "ngsc_dev", + "db_password": "AKIAZQ2BV5F5K6LLBC47", + "db_port": "1433", + }, + "ngsc_test": { + "db_url": "ngsc-test-msql.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "ngsc_test", + "db_user": "ngsc_test", + "db_password": "AKIAZQ2BV5F5K6LLBC47", + "db_port": "1433", + }, + "ngsc_prod": { + "db_url": "rop-ngsc-prod.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "ngsc_test", + "db_user": "ngsc_test", + "db_password": "AKIAZQ2BV5F5K6LLBC47", + "db_port": "1433", + }, + "rop_prices_test": { + "db_url": "rop-prices-test.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "test", + "db_user": "rop", + "db_password": "OptimalTransition", + "db_port": "8472", + }, + "rop_assets_test": { + "db_url": "rop-assets-test.cyqrsg0mmelh.eu-central-1.rds.amazonaws.com", + "db_name": "test", + "db_user": "rop", + "db_password": "OptimalTransition", + "db_port": "1433", + }, +} + + +def db_engine(db_name): + db_config = DATABASES[db_name] + + connection_string = ( + f"mssql+pyodbc://{db_config['db_user']}:{db_config['db_password']}" + f"@{db_config['db_url']}:{db_config['db_port']}/" + f"{db_config['db_name']}?driver=ODBC+Driver+17+for+SQL+Server" + ) + return create_engine(connection_string) + + +def read_entire_table(table_name, db_engine): + return pd.read_sql_table(table_name, db_engine) + + +def create_connection(engine, tables): + connection = engine.connect() + metadata = MetaData() + + if isinstance(tables, str): + return connection, Table(tables, metadata, autoload=True, autoload_with=engine) + else: + db_tables = { + table: Table(table, metadata, autoload=True, autoload_with=engine) + for table in tables + } + return connection, db_tables diff --git a/pyrecoy/pyrecoy/pyrecoy/decorators.py b/pyrecoy/pyrecoy/pyrecoy/decorators.py new file mode 100644 index 0000000..135cb2d --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/decorators.py @@ -0,0 +1,17 @@ +import time +from functools import wraps + + +def time_method(func): + """Prints the runtime of a method of a class.""" + + @wraps(func) + def wrapper_timer(self, *args, **kwargs): + start = time.perf_counter() + value = func(self, *args, **kwargs) + end = time.perf_counter() + run_time = end - start + print(f"Finished running {self.name} in {run_time:.2f} seconds.") + return value + + return wrapper_timer diff --git a/pyrecoy/pyrecoy/pyrecoy/financial.py b/pyrecoy/pyrecoy/pyrecoy/financial.py new file mode 100644 index 0000000..33710e2 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/financial.py @@ -0,0 +1,678 @@ +from pathlib import Path +from datetime import timedelta + +import numpy as np +import numpy_financial as npf +import pandas as pd + +import warnings + + +def npv(discount_rate, cashflows): + cashflows = np.array(cashflows) + return (cashflows / (1 + discount_rate) ** np.arange(1, len(cashflows) + 1)).sum( + axis=0 + ) + + +def calc_electr_market_results(model, nom_col=None, real_col=None): + """Function to calculate the financial result on Day-Ahead and Imbalance market for the input model. + + Parameters: + ----------- + model : df + DataFrame containing at least 'DAM', 'POS' and 'NEG' columns. + nom_col : str + Name of the column containing the Day-Ahead nominations in MWh + Negative values = Buy, positive values = Sell + imb_col : str + Name of the column containing the Imbalance volumes in MWh + Negative values = Buy, positive values = Sell + + Returns: + -------- + Original df with added columns showing the financial results per timeunit. + """ + if nom_col is None: + nom_col = "Nom. vol." + model[nom_col] = 0 + + producing = model[real_col] > 0 + model["Prod. vol."] = model[real_col].where(producing, other=0) + model["Cons. vol."] = model[real_col].where(~producing, other=0) + model["Imb. vol."] = model[real_col] - model[nom_col] + + model["Day-Ahead Result"] = model[nom_col] * model["DAM"] + model["POS Result"] = 0 + model["NEG Result"] = 0 + posimb = model["Imb. vol."] > 0 + model["POS Result"] = model["POS"] * model["Imb. vol."].where(posimb, other=0) + model["NEG Result"] = model["NEG"] * model["Imb. vol."].where(~posimb, other=0) + model["Imbalance Result"] = model["POS Result"] + model["NEG Result"] + model["Combined Result"] = model["Day-Ahead Result"] + model["Imbalance Result"] + return model + + +def calc_co2_costs(co2_prices, volumes, fuel): + """Calculates gas market results + + Parameters: + ----------- + co2_prices : numeric or array + CO2 prices in €/ton + volumes : list + List of arrays containing volumes + fuel : list + List of arrays containing gas volumes + + Returns: + -------- + Returns a single negative value (=costs) in € + """ + if not isinstance(volumes, list): + volumes = [volumes] + + emission_factors = { + "gas": 1.884 / 9.769 + } # in ton/MWh (based on 1.884 kg CO2/Nm3, 9.769 kWh/Nm3) + + if fuel not in emission_factors.keys(): + raise NotImplementedError( + f"Emission factor for chosen fuel '{fuel}' is not implemented." + f"Implement it by adding emission factor to the 'emission_factors' table." + ) + + emission_factor = emission_factors[fuel] + return -round( + abs(sum((array * emission_factor * co2_prices).sum() for array in volumes)), 2 + ) + + +def calculate_eb_ode( + cons, + electr=True, + year=2020, + tax_bracket=None, + base_cons=None, + horti=False, + m3=False, +): + """Calculates energy tax and ODE for consumption of electricity or natural gas in given year. + + Function calculates total tax to be payed for electricity or natural gas consumption, + consisting of energy tax ('Energiebelasting') and sustainable energy surcharge ('Opslag Duurzame Energie'). + + Tax bracket that applies is based on consumption level, with a different tax + rate for each bracket. + + For Gas + 1: 0 - 170.000 m3 + 3: 170.000 - 1 mln. m3 + 4: 1 mln. - 10 mln. m3 + 5: > 10 mln. m3 + + For Electricity + 1: 0 - 10 MWh + 2: 10 - 50 MWh + 3: 50 - 10.000 MWh + 4: > 10.000 MWh + + Parameters: + ----------- + cons : numeric + Total consumption in given year for which to calculate taxes. + Electricity consumption in MWh + Gas consumption in MWh (or m3 and use m3=True) + electr : bool + Set to False for natural gas rates. Default is True. + year : int + Year for which tax rates should be used. Tax rates are updated + annually and can differ significantly. + tax_bracket : int + Tax bracket (1-4) to assume. + Parameter can not be used in conjunction ith 'base_cons'. + base_cons : numeric + Baseline consumption to assume, in same unit as 'cons'. + Specified value is used to decide what tax bracket to start in. + Taxes from baseline consumption are not included in calculation of the tax amount. + Parameter can not be used in conjunction with 'tax_bracket'. + horti : bool + The horticulture sector gets a discount on gas taxes. + m3 : bool + Set to True if you want to enter gas consumption in m3. + Default is to enter consumption in MWh. + + Returns: + -------- + Total tax amount as negative number (costs). + + Note: + ----- + This function is rather complicated, due to all its optionalities. + Should probably be simplified or split into different functions. + """ + if tax_bracket is not None and base_cons is not None: + raise ValueError( + "Parameters 'tax_bracket' and 'base_cons' can not be used at the same time." + ) + if tax_bracket is None and base_cons is None: + raise ValueError( + "Function requires input for either 'tax_bracket' or 'base_cons'." + ) + + cons = abs(cons) + commodity = "electricity" if electr else "gas" + + if commodity == "gas": + if not m3: + cons /= 9.769 / 1000 # Conversion factor for gas: 1 m3 = 9.769 kWh + base_cons = base_cons / (9.769 / 1000) if base_cons is not None else None + else: + cons *= 1000 # conversion MWh to kWh + base_cons = base_cons * 1000 if base_cons is not None else None + + tax_brackets = { + "gas": [0, 170_000, 1_000_000, 10_000_000], + "electricity": [0, 10_000, 50_000, 10_000_000], + } + tax_brackets = tax_brackets[commodity] + base_cons = tax_brackets[tax_bracket - 1] if tax_bracket else base_cons + + if commodity == "gas" and horti: + commodity += "_horticulture" + eb_rates, ode_rates = get_tax_tables(commodity) + + eb = 0 + ode = 0 + + for bracket in range(4): + if bracket < 3: + br_lower_limit = tax_brackets[bracket] + br_upper_limit = tax_brackets[bracket + 1] + if base_cons > br_upper_limit: + continue + bracket_size = br_upper_limit - max(br_lower_limit, base_cons) + cons_in_bracket = min(cons, bracket_size) + else: + cons_in_bracket = cons + + # print(eb_rates.columns[bracket], cons_in_bracket, round(eb_rates.loc[year, eb_rates.columns[bracket]], 6), round(eb_rates.loc[year, eb_rates.columns[bracket]] * cons_in_bracket,2)) + eb += eb_rates.loc[year, eb_rates.columns[bracket]] * cons_in_bracket + ode += ode_rates.loc[year, ode_rates.columns[bracket]] * cons_in_bracket + cons -= cons_in_bracket + + if cons == 0: + break + + return -round(eb, 2), -round(ode, 2) + + +def get_tax_tables(commodity): + """Get EB and ODE tax rate tables from json files. + + Returns two tax rate tables as DataFrame. + If table is not up-to-date, try use update_tax_tables() function. + """ + folder = Path(__file__).resolve().parent / "data" / "tax_tariffs" + eb_table = pd.read_json(folder / f"{commodity}_eb.json") + ode_table = pd.read_json(folder / f"{commodity}_ode.json") + + if commodity == "electricity": + ode_table.drop(columns=ode_table.columns[3], inplace=True) + else: + eb_table.drop(columns=eb_table.columns[0], inplace=True) + + if commodity != "gas_horticulture": + eb_table.drop(columns=eb_table.columns[3], inplace=True) + return eb_table, ode_table + + +def get_tax_rate(commodity, year, tax_bracket, perMWh=True): + """Get tax rate for specific year and tax bracket. + + Parameters: + ----------- + commodity : str + {'gas' or 'electricity'} + year : int + {2013 - current year} + tax_bracket : int + {1 - 4} + + For Gas: + 1: 0 - 170.000 m3 + 3: 170.000 - 1 mln. m3 + 4: 1 mln. - 10 mln. m3 + 5: > 10 mln. m3 + + For Electricity: + 1: 0 - 10 MWh + 2: 10 - 50 MWh + 3: 50 - 10.000 MWh + 4: > 10.000 MWh + perMWh : bool + Defaults to True. Will return rates (for gas) in €/MWh instead of €/m3. + + Returns: + -------- + Dictionary with EB, ODE and combined rates (in €/MWh for electricity and €/m3 for gas) + {'EB' : float + 'ODE' : float, + 'EB+ODE' : float} + """ + eb_table, ode_table = get_tax_tables(commodity) + + eb_rate = eb_table.loc[year, :].iloc[tax_bracket - 1].astype(float).round(5) * 1000 + ode_rate = ( + ode_table.loc[year, :].iloc[tax_bracket - 1].astype(float).round(5) * 1000 + ) + + if commodity == "gas" and perMWh == True: + eb_rate /= 9.769 + ode_rate /= 9.769 + + comb_rate = (eb_rate + ode_rate).round(5) + return {"EB": eb_rate, "ODE": ode_rate, "EB+ODE": comb_rate} + + +def update_tax_tables(): + """Function to get EB and ODE tax rate tables from belastingdienst.nl and save as json file.""" + url = ( + "https://www.belastingdienst.nl/wps/wcm/connect/bldcontentnl/belastingdienst/" + "zakelijk/overige_belastingen/belastingen_op_milieugrondslag/tarieven_milieubelastingen/" + "tabellen_tarieven_milieubelastingen?projectid=6750bae7-383b-4c97-bc7a-802790bd1110" + ) + + tables = pd.read_html(url) + + table_index = { + 3: "gas_eb", + 4: "gas_horticulture_eb", + 6: "electricity_eb", + 8: "gas_ode", + 9: "gas_horticulture_ode", + 10: "electricity_ode", + } + + for key, val in table_index.items(): + table = tables[key].astype(str) + table = table.applymap(lambda x: x.strip("*")) + table = table.applymap(lambda x: x.strip("€ ")) + table = table.applymap(lambda x: x.replace(",", ".")) + table = table.astype(float) + table["Jaar"] = table["Jaar"].astype(int) + table.set_index("Jaar", inplace=True) + path = Path(__file__).resolve().parent / "data" / "tax_tariffs" / f"{val}.json" + table.to_json(path) + + +def calc_grid_costs( + peakload_kW, + grid_operator, + year, + connection_type, + totalcons_kWh=0, + kw_contract_kW=None, + path=None, + modelled_time_period_years = 1 +): + """Calculate grid connection costs for one full year + + Parameters: + ----------- + peakload_kW : numeric or list + Peak load in kW. Can be single value (for entire year) or value per month (list). + grid_operator : str + {'tennet', 'liander', 'enexis', 'stedin'} + year : int + Year to get tariffs for, e.g. 2020 + connection_type : str + Type of grid connection, e.g. 'TS' or 'HS'. + Definitions are different for each grid operator. + totalcons_kWh : numeric + Total yearly consumption in kWh + kw_contract_kW : numeric + in kW. If provided, function will assume fixed value kW contract + path : str + Path to directory with grid tariff files. + Default is None; function will to look for default folder on SharePoint. + Returns: + -------- + Total variable grid connection costs in €/year (fixed costs 'vastrecht' nog included) + """ + + totalcons_kWh /= modelled_time_period_years + + tariffs = get_grid_tariffs_electricity(grid_operator, year, connection_type, path) + kw_max_kW = np.mean(peakload_kW) + max_peakload_kW = np.max(peakload_kW) + + if kw_contract_kW is None: + kw_contract_kW = False + + if bool(kw_contract_kW) & (kw_contract_kW < max_peakload_kW): + warnings.warn( + "Maximum peak consumption is higher than provided 'kw_contract' value." + "Will continue to assume max peak consumption as kW contract." + ) + kw_contract_kW = max_peakload_kW + + if not bool(kw_contract_kW): + kw_contract_kW = max_peakload_kW + + if (tariffs["kWh tarief"] != 0) and (totalcons_kWh is None): + raise ValueError( + "For this grid connection type a tariff for kWh has to be paid. " + "Therefore 'totalcons_kWh' can not be None." + ) + + # kw_contract = 1000 + + # print("tarieven") + # print(abs(totalcons_kWh)) + # print(modelled_time_period_years) + # print(-round(tariffs["kWh tarief"])) + + # print({ + # "Variable": -round(tariffs["kWh tarief"] * abs(totalcons_kWh) * modelled_time_period_years, 2), + # "kW contract": -round(tariffs["kW contract per jaar"] * kw_contract_kW * modelled_time_period_years, 2), + # "kW max": -round(tariffs["kW max per jaar"] * max_peakload_kW * modelled_time_period_years, 2), + # }) + + return { + "Variable": -round(tariffs["kWh tarief"] * abs(totalcons_kWh) * modelled_time_period_years, 2), + "kW contract": -round(tariffs["kW contract per jaar"] * kw_contract_kW * modelled_time_period_years, 2), + "kW max": -round(tariffs["kW max per jaar"] * max_peakload_kW * modelled_time_period_years, 2), + } + + +def get_grid_tariffs_electricity(grid_operator, year, connection_type, path=None): + """Get grid tranposrt tariffs + + Parameters: + ----------- + grid_operator : str + {'tennet', 'liander', 'enexis', 'stedin'} + year : int + Year to get tariffs for, e.g. 2020 + connection_type : str + Type of grid connection, e.g. 'TS' or 'HS'. + Definitions are different for each grid operator. + path : str + Path to directory with grid tariff files. + Default is None; function will to look for default folder on SharePoint. + + Returns: + -------- + Dictionary containing grid tariffs in €/kW/year and €/kWh + """ + if path is None: + path = Path(__file__).resolve().parent / "data" / "grid_tariffs" + else: + path = Path(path) + + if not path.exists(): + raise SystemError( + f"Path '{path}' not found. Specify different path and try again." + ) + + filename = f"{grid_operator.lower()}_{year}.csv" + filepath = path / filename + + if not filepath.exists(): + raise NotImplementedError( + f"File '{filename}' does not exist. Files available: {[file.name for file in path.glob('*.csv')]}" + ) + + rates_table = pd.read_csv( + path / filename, sep=";", decimal=",", index_col="Aansluiting" + ) + + if connection_type not in rates_table.index: + raise ValueError( + f"The chosen connection type '{connection_type}' is not available " + f"for grid operator '{grid_operator}'. Please choose one of {list(rates_table.index)}." + ) + return rates_table.loc[connection_type, :].to_dict() + + +def income_tax(ebit, fixed_tax_rate): + """ + Calculates income tax based on EBIT. + 2021 tax rates + """ + if fixed_tax_rate: + return round(ebit * -0.25, 0) + if ebit > 245_000: + return round(245_000 * -0.15 + (ebit - 200_000) * -0.25, 2) + if ebit < 0: + return 0 + else: + return -round(ebit * 0.15, 2) + + +def calc_business_case( + capex, + discount_rate, + project_duration, + depreciation, + residual_value, + regular_earnings, + irregular_cashflows=0, + eia=False, + vamil=False, + fixed_income_tax=False +): + """Calculate NPV and IRR for business case. + + All input paremeters are either absolute or relative to a baseline. + + Parameters: + ----------- + capex : numeric + Total CAPEX or extra CAPEX compared to baseline + discount_rate : numeric + % as decimal value + project_duration : numeric + in years + depreciation : numeric of list + Yearly depreciation costs + residual_value : numeric + Residual value at end of project in €, total or compared to baseline. + regular_earnings : numeric + Regular earnings, usually EBITDA + irregular_cashflows : list + Pass list with value for each year. + eia : bool + Apply EIA ("Energie Investerings Aftrek") tax discounts. + Defaults to False. + vamil : bool + Apply VAMIL ("Willekeurige afschrijving milieu-investeringen") tax discounts. + Defaults to False. + + Returns: + -------- + DataFrame showing complete calculation resulting in NPV and IRR + """ + years = [f"Year {y}" for y in range(project_duration + 1)] + years_o = years[1:] + + bc_calc = pd.DataFrame(columns=years) + bc_calc.loc["CAPEX (€)", "Year 0"] = -capex + bc_calc.loc["Regular Earnings (€)", years_o] = regular_earnings + bc_calc.loc["Irregular Cashflows (€)", years_o] = irregular_cashflows + bc_calc.loc["EBITDA (€)", years_o] = ( + bc_calc.loc["Regular Earnings (€)", years_o] + + bc_calc.loc["Irregular Cashflows (€)", years_o] + ) + + depreciations = [depreciation] * project_duration + + if vamil: + ebitdas = bc_calc.loc["EBITDA (€)", years_o].to_list() + depreciations = _apply_vamil(depreciations, project_duration, ebitdas) + + bc_calc.loc["Depreciations (€) -/-", years_o] = np.array(depreciations) * -1 + bc_calc.loc["EBIT (€)", years_o] = ( + bc_calc.loc["EBITDA (€)", years_o] + + bc_calc.loc["Depreciations (€) -/-", years_o] + ) + + if eia: + bc_calc = _apply_eia(bc_calc, project_duration, capex, years_o) + + bc_calc.loc["Income tax (Vpb.) (€)", years_o] = bc_calc.loc["EBIT (€)", :].apply( + income_tax, args=[fixed_income_tax] + ) + + if eia: + bc_calc.loc["NOPLAT (€)", years_o] = ( + bc_calc.loc["EBIT before EIA (€)", :] + + bc_calc.loc["Income tax (Vpb.) (€)", years_o] + ) + else: + bc_calc.loc["NOPLAT (€)", years_o] = ( + bc_calc.loc["EBIT (€)", :] + bc_calc.loc["Income tax (Vpb.) (€)", years_o] + ) + + bc_calc.loc["Depreciations (€) +/+", years_o] = depreciations + bc_calc.loc["Free Cash Flow (€)", years] = ( + bc_calc.loc["CAPEX (€)", years].fillna(0) + + bc_calc.loc["NOPLAT (€)", years].fillna(0) + + bc_calc.loc["Depreciations (€) +/+", years].fillna(0) + ) + + spp = calc_simple_payback_time( + capex=capex, + free_cashflows=bc_calc.loc["Free Cash Flow (€)", years_o].values, + ) + bc_calc.loc["Simple Payback Period", "Year 0"] = spp + + try: + bc_calc.loc["IRR (%)", "Year 0"] = ( + npf.irr(bc_calc.loc["Free Cash Flow (€)", years].values) * 100 + ) + except: + bc_calc.loc["IRR (%)", "Year 0"] = np.nan + + bc_calc.loc["WACC (%)", "Year 0"] = discount_rate * 100 + + bc_calc.loc["NPV of explicit period (€)", "Year 0"] = npv( + discount_rate, bc_calc.loc["Free Cash Flow (€)"].values + ) + bc_calc.loc["Discounted residual value (€)", "Year 0"] = ( + residual_value / (1 + discount_rate) ** project_duration + ) + bc_calc.loc["NPV (€)", "Year 0"] = ( + bc_calc.loc["NPV of explicit period (€)", "Year 0"] + # + bc_calc.loc["Discounted residual value (€)", "Year 0"] + ) + + return bc_calc.round(2) + + +def calc_simple_payback_time(capex, free_cashflows): + if free_cashflows.sum() < capex: + return np.nan + + year = 0 + spp = 0 + while capex > 0: + cashflow = free_cashflows[year] + spp += min(capex, cashflow) / cashflow + capex -= cashflow + year += 1 + return spp + return round(spp, 1) + + +def _apply_vamil(depreciations, project_duration, ebitdas): + remaining_depr = sum(depreciations) + remaining_vamil = 0.75 * remaining_depr + for i in range(project_duration): + vamil_depr = min(ebitdas[i], remaining_vamil) if remaining_vamil > 0 else 0 + + if remaining_depr > 0: + lin_depr = remaining_depr / (project_duration - i) + depr = max(vamil_depr, lin_depr) + depreciations[i] = max(vamil_depr, lin_depr) + + remaining_vamil -= vamil_depr + remaining_depr -= depr + else: + depreciations[i] = 0 + + return depreciations + + +def _apply_eia(bc_calc, project_duration, capex, years_o): + remaining_eia = 0.45 * capex + eia_per_year = [0] * project_duration + bc_calc = bc_calc.rename(index={"EBIT (€)": "EBIT before EIA (€)"}) + ebits = bc_calc.loc["EBIT before EIA (€)", years_o].to_list() + eia_duration = min(10, project_duration) + for i in range(eia_duration): + if remaining_eia > 0: + eia_curr_year = max(min(remaining_eia, ebits[i]), 0) + eia_per_year[i] = eia_curr_year + remaining_eia -= eia_curr_year + else: + break + + bc_calc.loc["EIA (€)", years_o] = np.array(eia_per_year) * -1 + bc_calc.loc["EBIT (€)", :] = ( + bc_calc.loc["EBIT before EIA (€)", :] + bc_calc.loc["EIA (€)", :] + ) + return bc_calc + + +def calc_irf_value( + data, irf_volume, nomination_col=None, realisation_col=None, reco_col="reco" +): + """Calculate IRF value + + Takes a DataFrame [data] and returns the same DataFrame with a new column "IRF Value" + + Parameters + ---------- + data : DataFrame + DataFrame that contains data. Should include price data (DAM, POS and NEG). + irf_volume : int + Volume on IRF in MW. + nomination_col : str + Name of the column containing nomination data in MWh. + realisation_col : str + Name of the column containing realisation data in MWh. + reco_col : str + Name of the column contaning recommendations. + """ + if not nomination_col: + nomination_col = "zero_nom" + data[nomination_col] = 0 + + if not realisation_col: + realisation_col = "zero_nom" + data[realisation_col] = 0 + + conversion_factor = pd.to_timedelta(data.index.freq) / timedelta(hours=1) + + imb_pre_irf = data[realisation_col] - data[nomination_col] + result_pre_irf = ( + data[nomination_col] * data["DAM"] + + imb_pre_irf.where(imb_pre_irf > 0, other=0) * data["POS"] + + imb_pre_irf.where(imb_pre_irf < 0, other=0) * data["NEG"] + ) + + data["IRF Nom"] = ( + data[nomination_col] - data[reco_col] * irf_volume * conversion_factor + ) + data["IRF Imb"] = data[realisation_col] - data["IRF Nom"] + + result_post_irf = ( + data["IRF Nom"] * data["DAM"] + + data["IRF Imb"].where(data["IRF Imb"] > 0, other=0) * data["POS"] + + data["IRF Imb"].where(data["IRF Imb"] < 0, other=0) * data["NEG"] + ) + data["IRF Value"] = result_post_irf - result_pre_irf + + return data diff --git a/pyrecoy/pyrecoy/pyrecoy/forecasts.py b/pyrecoy/pyrecoy/pyrecoy/forecasts.py new file mode 100644 index 0000000..ca2696e --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/forecasts.py @@ -0,0 +1,293 @@ +import json +import os +import time +import pytz +from datetime import datetime, timedelta +from pathlib import Path + +import numpy as np +import pandas as pd +import requests +from pyrecoy.prices import * + + +class Forecast: + """Load dataset from SharePoint server as DataFrame in local datetime format. + + Parameters: + ---------- + filename : str + Name of the csv file, e.g. "marketprices_nl.csv" + start : datetime + Startdate of the dataset + end : datetime + Enddate of the dataset + freq : str + {'1T', '15T', 'H'} + Time frequency of the data + folder_path : str + Local path to forecast data on Recoy SharePoint, + e.g. "C:/Users/username/Recoy/Recoy - Documents/03 - Libraries/12 - Data Management/Forecast Data/" + + """ + + def __init__(self, filename, start=None, end=None, freq="15T", folder_path=None, from_database=False, add_days_to_start_end=False): + self.file = filename + self.from_database = from_database + + if isinstance(start, str): + start = datetime.strptime(start, "%Y-%m-%d").astimezone(pytz.timezone('Europe/Amsterdam')) + print(start) + if isinstance(end, str): + end = datetime.strptime(end, "%Y-%m-%d").astimezone(pytz.timezone('Europe/Amsterdam')) + print(end) + + self.data = self.get_dataset(start, end, freq, folder_path=folder_path, add_days_to_start_end=add_days_to_start_end) + + # print(self.data) + + if len(self.data) == 0: + raise Exception("No data available for those dates.") + + def get_dataset(self, start, end, freq, folder_path=None, add_days_to_start_end=False): + if folder_path is None and self.from_database: + if add_days_to_start_end: + start = start + timedelta(days=-1) + end = end + timedelta(days=1) + + start = start.astimezone(pytz.utc) + end = end.astimezone(pytz.utc) + + dam = get_day_ahead_prices_from_database(start, end, 'NLD') + dam = dam.resample('15T').ffill() + + imb = get_imbalance_prices_from_database(start, end, 'NLD') + data = pd.concat([imb, dam], axis='columns') + data = data[['DAM', 'POS', 'NEG']] + data = data.tz_convert('Europe/Amsterdam') + # data = data.loc[(data.index >= start) & (data.index < end)] + return data + + + def reindex_to_freq(self, freq): + """Reindex dataset to a different timefrequency. + + Parameters: + ----------- + freq : string + options: '1T' + """ + ix_start = pd.to_datetime(self.data.index[0], utc=True).tz_convert( + "Europe/Amsterdam" + ) + ix_end = pd.to_datetime(self.data.index[-1], utc=True).tz_convert( + "Europe/Amsterdam" + ) + + idx = pd.date_range( + ix_start, ix_end + timedelta(minutes=14), freq=freq, tz="Europe/Amsterdam" + ) + self.data = self.data.reindex(index=idx, method="ffill") + + +class Mipf(Forecast): + """ + Load MIPF dataset from SharePoint server as DataFrame in local datetime format. + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + forecasts = get_imbalance_forecasts_from_database_on_quarter_start_time(kwargs['start'], kwargs['end'],'NLD') + forecasts['PublicationTime'] = pd.to_datetime(forecasts['PublicationTime'], utc=True) + forecasts['PublicationTime'] = forecasts['PublicationTime'].dt.ceil('min') + forecasts['PublicationTime'] = forecasts['PublicationTime'].dt.tz_convert('Europe/Amsterdam') + forecasts['QuarterStartTime'] = pd.to_datetime(forecasts['QuarterStartTime'], utc=True) + forecasts['QuarterStartTime'] = forecasts['QuarterStartTime'].dt.tz_convert('Europe/Amsterdam') + + forecasts = forecasts.loc[ + (forecasts['PublicationTime'] >= forecasts['QuarterStartTime']) + & (forecasts['PublicationTime'] < forecasts['QuarterStartTime'] + pd.Timedelta(minutes=15)) + ] + forecasts = forecasts.groupby('PublicationTime').last() + forecasts.index.name = '' + forecasts = forecasts[['ForeNeg','ForePos']] + + prices = self.data + idx = pd.date_range( + prices.index[0], prices.index[-1] + timedelta(minutes=14), freq='1T', tz="Europe/Amsterdam" + ) + prices = prices.reindex(idx, method='ffill') + res = pd.concat([prices, forecasts], axis='columns') + + self.data = res + + + + + +def tidy_mipf(data, include_price_data=True, include_nextQ=False): + """Takes MIPF dataset (unstacked) and turns it into a tidy dataset (stacked). + + Parameters: + ---------- + include_price_data : bool + Set as True if columns 'DAM', 'POS' and 'NEG' data should be included in the output. + include_nextQ : bool + Set to True to include next Qh forecast + """ + mipf_pos = data[[f"POS_horizon{h}" for h in np.flip(np.arange(3, 18))]].copy() + mipf_neg = data[[f"NEG_horizon{h}" for h in np.flip(np.arange(3, 18))]].copy() + + cols = ["ForePos", "ForeNeg"] + dfs = [mipf_pos, mipf_neg] + + if include_nextQ: + pos_nextQ = data[[f"POS_horizon{h}" for h in np.flip(np.arange(18, 30))]].copy() + neg_nextQ = data[[f"NEG_horizon{h}" for h in np.flip(np.arange(18, 30))]].copy() + + for h in np.arange(30, 33): + pos_nextQ.insert(0, f"POS_horizon{h}", np.NaN) + neg_nextQ.insert(0, f"POS_horizon{h}", np.NaN) + + cols += ["ForePos_nextQ", "ForeNeg_nextQ"] + dfs += [pos_nextQ, neg_nextQ] + + tidy_df = pd.DataFrame() + + for df, col in zip(dfs, cols): + df.columns = range(15) + df.reset_index(drop=True, inplace=True) + df.reset_index(inplace=True) + df_melt = ( + df.melt(id_vars=["index"], var_name="min", value_name=col) + .sort_values(["index", "min"]) + .reset_index(drop=True) + ) + tidy_df[col] = df_melt[col] + + ix_start = data.index[0] + ix_end = data.index[-1] + timedelta(minutes=14) + + tidy_df.index = pd.date_range(ix_start, ix_end, freq="1T", tz="Europe/Amsterdam") + tidy_df.index.name = "datetime" + + if include_price_data: + for col in np.flip(["DAM", "POS", "NEG", "regulation state"]): + try: + price_col = data.loc[:, col].reindex( + index=tidy_df.index, method="ffill" + ) + if col == "regulation state": + price_col.name = "RS" + tidy_df = pd.concat([price_col, tidy_df], axis="columns") + except Exception as e: + print(e) + + return tidy_df + + +class Qipf(Forecast): + """Load QIPF dataset from SharePoint server as DataFrame in local datetime format. + + Parameters: + ---------- + start : datetime + Startdate of the dataset + end : datetime + Enddate of the dataset + folder_path : str + Local path to forecast data on Recoy SharePoint, + e.g. "C:/Users/username/Recoy/Recoy - Documents/03 - Libraries/12 - Data Management/Forecast Data/" + """ + + def __init__(self, start=None, end=None, freq="15T", folder_path=None): + self.file = "imbalance_nl.csv" + self.data = self.get_dataset(start, end, "15T", folder_path=folder_path) + + if freq != "15T": + self.reindex_to_freq(freq) + + +class Irf(Forecast): + """Load QIPF dataset from SharePoint server as DataFrame in local datetime format.""" + + def __init__( + self, country, horizon, start=None, end=None, freq="60T", folder_path=None + ): + if freq == "15T": + self.file = f"irf_{country}_{horizon}_15min.csv" + else: + self.file = f"irf_{country}_{horizon}.csv" + + self.data = self.get_dataset(start, end, freq, folder_path=folder_path) + + return data + + +class NsideApiRequest: + """ + Request forecast data from N-SIDE API + + If request fails, code will retry 5 times by default. + + Output on success: data as DataFrame, containing forecast data. Index is timezone-aware datetime (Dutch time). + Output on error: [] + """ + + def __init__( + self, + endpoint, + country, + start=None, + end=None, + auth_token=None, + ): + if not auth_token: + try: + auth_token = os.environ["NSIDE_API_KEY"] + except: + raise ValueError("N-SIDE token not provided.") + + self.data = self.get_data(auth_token, endpoint, country, start, end) + + def get_data(self, token, endpoint, country, start, end): + if start is not None: + start = pd.to_datetime(start).strftime("%Y-%m-%d") + if end is not None: + end = pd.to_datetime(end).strftime("%Y-%m-%d") + + url = f"https://energy-forecasting-api.eu.n-side.com/api/forecasts/{country}/{endpoint}" + if start and end: + url += f"?from={start}&to={end}" + print(url) + headers = {"Accept": "application/json", "Authorization": f"Token {token}"} + + retry = 5 + self.success = False + + i = 0 + while i <= retry: + resp = requests.get(url, headers=headers) + self.statuscode = resp.status_code + + if self.statuscode == requests.codes.ok: + self.content = resp.content + json_data = json.loads(self.content) + data = pd.DataFrame(json_data["records"]) + data = data.set_index("datetime") + data.index = pd.to_datetime(data.index, utc=True).tz_convert( + "Europe/Amsterdam" + ) + self.success = True + return data.sort_index() + else: + print( + f"Attempt failled, status code {str(self.statuscode)}. Trying again..." + ) + time.sleep(5) + i += 1 + + if not self.success: + print( + "Request failed. Please contact your Recoy contact person or try again later." + ) + return [] diff --git a/pyrecoy/pyrecoy/pyrecoy/framework.py b/pyrecoy/pyrecoy/pyrecoy/framework.py new file mode 100644 index 0000000..e4d6785 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/framework.py @@ -0,0 +1,61 @@ +import warnings +from datetime import datetime, timedelta + +import pandas as pd +import pytz + + +class TimeFramework: + """ + Representation of the modelled timeperiod. + Variables in this class are equal for all CaseStudies. + """ + + def __init__(self, start, end): + print('testets') + if type(start) is str: + start = pytz.timezone("Europe/Amsterdam").localize( + datetime.strptime(start, "%Y-%m-%d") + ) + + if type(end) is str: + end = pytz.timezone("Europe/Amsterdam").localize( + datetime.strptime(end, "%Y-%m-%d") + ) + end += timedelta(days=1) + end -= timedelta(minutes=1) + + self.start = start + self.end = end + + amount_of_days = 365 + + if start.year % 4 == 0: + amount_of_days = 366 + + self.days = (self.end - self.start + timedelta(days=1)) / timedelta(days=1) + + self.modelled_time_period_years = (end - start).total_seconds() / (3600 * 24 * amount_of_days) + + if self.days != 365: + warnings.warn( + f"The chosen timeperiod spans {self.days} days, " + "which is not a full year. Beware that certain " + "functions that use yearly rates might return " + "incorrect values." + ) + + def dt_index(self, freq): + # Workaround to make sure time range is always complete, + # Even with DST changes + # end = self.end + timedelta(days=1) # + timedelta(hours=1) + # end = self.end + # end - timedelta(end.hour) + return pd.date_range( + start=self.start, + end=self.end, + freq=freq, + tz="Europe/Amsterdam", + # inclusive="left", + name="datetime", + ) diff --git a/pyrecoy/pyrecoy/pyrecoy/intelligent_baseline.py b/pyrecoy/pyrecoy/pyrecoy/intelligent_baseline.py new file mode 100644 index 0000000..286f734 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/intelligent_baseline.py @@ -0,0 +1,226 @@ +import numpy as np +import pandas as pd +import warnings +from tqdm.notebook import tqdm + +from .prices import get_tennet_data, get_balansdelta_nl +from .forecasts import Forecast + +# TODO: This whole thing needs serious refactoring /MK + + +def generate_intelligent_baseline(startdate, enddate): + bd = get_balansdelta_nl(start=startdate, end=enddate) + bd.drop( + columns=[ + "datum", + "volgnr", + "tijd", + "IGCCBijdrage_op", + "IGCCBijdrage_af", + "opregelen_reserve", + "afregelen_reserve", + ], + inplace=True, + ) + net_regelvolume = bd["opregelen"] - bd["Afregelen"] + bd.insert(2, "net_regelvolume", net_regelvolume) + vol_delta = bd["net_regelvolume"].diff(periods=1) + bd.insert(3, "vol_delta", vol_delta) + + pc = get_tennet_data( + exporttype="verrekenprijzen", start=startdate, end=enddate + ).reindex(index=bd.index, method="ffill")[["prikkelcomponent"]] + + if len(pc) == 0: + pc = pd.Series(0, index=bd.index) + + prices = Forecast("marketprices_nl.csv", start=startdate, end=enddate) + prices.reindex_to_freq("1T") + prices = prices.data + + inputdata = pd.concat([prices, bd, pc], axis=1) + + Qhs = len(inputdata) / 15 + + if Qhs % 1 > 0: + raise Exception( + "A dataset with incomplete quarter-hours was passed in, please insert new dataset!" + ) + + data = np.array([inputdata[col].to_numpy() for col in inputdata.columns]) + + lstoutput = [] + + for q in tqdm(range(int(Qhs))): + q_data = [col[q * 15 : (q + 1) * 15] for col in data] + q_output = apply_imbalance_logic_for_quarter_hour(q_data) + + if lstoutput: + for (ix, col) in enumerate(lstoutput): + lstoutput[ix] += q_output[ix] + else: + lstoutput = q_output + + ib = pd.DataFrame( + lstoutput, + index=[ + "DAM", + "POS", + "NEG", + "regulation state", + "ib_inv", + "ib_afn", + "ib_rt", + "nv_op", + "nv_af", + "opgeregeld", + "afgeregeld", + ], + ).T + + ib.index = inputdata.index + return ib + + +def apply_imbalance_logic_for_quarter_hour(q_data): + [nv_op, nv_af, opgeregeld, afgeregeld] = [False] * 4 + + lst_inv = [np.NaN] * 15 + lst_afn = [np.NaN] * 15 + lst_rt = [np.NaN] * 15 + + lst_nv_op = [np.NaN] * 15 + lst_nv_af = [np.NaN] * 15 + lst_afr = [np.NaN] * 15 + lst_opr = [np.NaN] * 15 + + mins = iter(range(15)) + + for m in mins: + [ + DAM, + POS, + NEG, + rt, + vol_op, + vol_af, + net_vol, + delta_vol, + nood_op, + nood_af, + prijs_hoog, + prijs_mid, + prijs_laag, + prikkelc, + ] = [col[0 : m + 1] for col in q_data] + + delta_vol[0] = 0 + + if nood_op.sum() > 0: + nv_op = True + + if nood_af.sum() > 0: + nv_af = True + + if pd.notna(prijs_hoog).any() > 0: + opgeregeld = True + + if pd.notna(prijs_laag).any() > 0: + afgeregeld = True + + if (opgeregeld == True) and (afgeregeld == False): + regeltoestand = 1 + + elif (opgeregeld == False) and (afgeregeld == True): + regeltoestand = -1 + + elif (opgeregeld == False) and (afgeregeld == False): + if nv_op == True: + regeltoestand = 1 + if nv_af == True: + regeltoestand = -1 + else: + regeltoestand = 0 + + else: + # Zowel opregeld als afregeld > kijk naar trend + # Continue niet-dalend: RT1 + # Continue dalend: RT -1 + # Geen continue trend: RT 2 + + if all(i >= 0 for i in delta_vol): + regeltoestand = 1 + elif all(i <= 0 for i in delta_vol): + regeltoestand = -1 + else: + regeltoestand = 2 + + # Bepaal de verwachte onbalansprijzen + dam = DAM[0] + pc = prikkelc[0] + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + + hoogste_prijs = np.nanmax(prijs_hoog) + mid_prijs = prijs_mid[-1] + laagste_prijs = np.nanmin(prijs_laag) + + if regeltoestand == 0: + prijs_inv = mid_prijs + prijs_afn = mid_prijs + + elif regeltoestand == -1: + if nv_af: + prijs_afn = np.nanmin((dam - 200, laagste_prijs)) + + else: + prijs_afn = laagste_prijs + + prijs_inv = prijs_afn + + elif regeltoestand == 1: + if nv_op: + prijs_inv = np.nanmax((dam + 200, hoogste_prijs)) + + else: + prijs_inv = hoogste_prijs + + prijs_afn = prijs_inv + + elif regeltoestand == 2: + if nv_op: + prijs_afn = np.nanmax((dam + 200, hoogste_prijs, mid_prijs)) + else: + prijs_afn = np.nanmax((mid_prijs, hoogste_prijs)) + + if nv_af: + prijs_inv = np.nanmin((dam - 200, laagste_prijs, mid_prijs)) + else: + prijs_inv = np.nanmin((mid_prijs, laagste_prijs)) + + prijs_inv -= pc + prijs_afn += pc + + lst_inv[m] = prijs_inv + lst_afn[m] = prijs_afn + lst_rt[m] = regeltoestand + lst_nv_op[m] = nv_op + lst_nv_af[m] = nv_af + lst_opr[m] = opgeregeld + lst_afr[m] = afgeregeld + + return [ + list(DAM), + list(POS), + list(NEG), + list(rt), + lst_inv, + lst_afn, + lst_rt, + lst_nv_op, + lst_nv_af, + lst_opr, + lst_afr, + ] diff --git a/pyrecoy/pyrecoy/pyrecoy/plotting.py b/pyrecoy/pyrecoy/pyrecoy/plotting.py new file mode 100644 index 0000000..74ceade --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/plotting.py @@ -0,0 +1,143 @@ +import plotly.graph_objects as go +from millify import millify +from plotly import figure_factory as ff + +from .colors import * +from .reports import SingleFigureComparison + + +def npv_bar_chart( + cases, color=recoydarkblue, title="NPV Comparison in k€", n_format="%{text:.3s}€" +): + series = SingleFigureComparison(cases, "npv", "NPV (€)").report + case_names = series.index + npvs = series.values + return single_figure_barchart(npvs, case_names, title, color, n_format) + + +def irr_bar_chart( + cases, color=recoydarkblue, title="IRR Comparison in %", n_format="%{text:.1f}%" +): + series = SingleFigureComparison(cases, "irr", "IRR (€)").report + case_names = series.index + irrs = series.values * 100 + return single_figure_barchart(irrs, case_names, title, color, n_format) + + +def ebitda_bar_chart( + cases, color=recoydarkblue, title="EBITDA comparison in k€", n_format="%{text:.3s}€" +): + series = SingleFigureComparison(cases, "ebitda", "EBITDA (€)").report + case_names = series.index + ebitdas = series.values + return single_figure_barchart(ebitdas, case_names, title, color, n_format) + + +def capex_bar_chart( + cases, color=recoydarkblue, title="CAPEX comparison in k€", n_format="%{text:.3s}€" +): + series = SingleFigureComparison(cases, "total_capex", "CAPEX (€)").report + case_names = series.index + capex = series.values * -1 + return single_figure_barchart(capex, case_names, title, color, n_format) + + +def single_figure_barchart(y_values, x_labels, title, color, n_format): + fig = go.Figure() + fig.add_trace( + go.Bar( + x=x_labels, + y=y_values, + text=y_values, + marker_color=color, + cliponaxis=False, + ) + ) + fig.update_layout(title=title) + ymin = min(y_values.min(), 0) * 1.1 + ymax = max(y_values.max(), 0) * 1.1 + fig.update_yaxes(range=[ymin, ymax]) + fig.update_traces(texttemplate=n_format, textposition="outside") + return fig + + +def heatmap( + data, + title=None, + labels=None, + colormap="reds", + mult_factor=1, + decimals=2, + min_value=None, + max_value=None, + width=600, + height=400, + hover_prefix=None, + reversescale=False, +): + data_lists = (data * mult_factor).round(decimals).values.tolist() + + xs = data.columns.tolist() + ys = data.index.to_list() + + annotations = ( + (data * mult_factor) + .applymap(lambda x: millify(x, precision=decimals)) + .values.tolist() + ) + if hover_prefix: + hover_labels = [ + [f"{hover_prefix} {ann}" for ann in sublist] for sublist in annotations + ] + else: + hover_labels = annotations + + # This is an ugly trick to fix a bug with + # the axis labels not showing correctly + xs_ = [f"{str(x)}_" for x in xs] + ys_ = [f"{str(y)}_" for y in ys] + + fig = ff.create_annotated_heatmap( + data_lists, + x=xs_, + y=ys_, + annotation_text=annotations, + colorscale=colormap, + showscale=True, + text=hover_labels, + hoverinfo="text", + reversescale=reversescale, + ) + + # Part 2 of the bug fix + fig.update_xaxes(tickvals=xs_, ticktext=xs) + fig.update_yaxes(tickvals=ys_, ticktext=ys) + + fig.layout.xaxis.type = "category" + fig.layout.yaxis.type = "category" + fig["layout"]["xaxis"].update(side="bottom") + + if min_value: + fig["data"][0]["zmin"] = min_value * mult_factor + if max_value: + fig["data"][0]["zmax"] = max_value * mult_factor + + if labels: + xlabel = labels[0] + ylabel = labels[1] + else: + xlabel = data.columns.name + ylabel = data.index.name + + fig.update_xaxes(title=xlabel) + fig.update_yaxes(title=ylabel) + + if title: + fig.update_layout( + title=title, + title_x=0.5, + title_y=0.85, + width=width, + height=height, + ) + return fig diff --git a/pyrecoy/pyrecoy/pyrecoy/prices-LAPTOP-2MK43FDK.py b/pyrecoy/pyrecoy/pyrecoy/prices-LAPTOP-2MK43FDK.py new file mode 100644 index 0000000..2f90008 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/prices-LAPTOP-2MK43FDK.py @@ -0,0 +1,765 @@ +import os +from datetime import timedelta +from io import BytesIO +from pathlib import Path +from zipfile import ZipFile +import time +import warnings +import json +import pytz + +import numpy as np +import pandas as pd +import requests +from bs4 import BeautifulSoup +from entsoe.entsoe import EntsoePandasClient +from sqlalchemy import MetaData, Table, insert, and_, or_ +from pyrecoy import * + + +def get_fcr_prices(start, end, freq="H") -> pd.DataFrame: + """Get FCR settlement prices from Regelleistung website + + Returns: DataFrame with FCR prices with index with given time frequency in local time. + """ + start = start + timedelta(-1) + end = end + timedelta(1) + data = get_FCR_prices_from_database(start, end, 'NLD') + data = data.resample('15T').ffill() + data = data[['PricePerMWPerISP']] + data.columns = ['FCR NL (EUR/ISP)'] + data.index.name = 'datetime' + data = data.tz_convert('Europe/Amsterdam') + return data + + path = Path( + f"./data/fcr_prices_{freq}_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + df = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype(float) + startdate = pd.to_datetime(df.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(df.index[-1]).strftime("%Y-%m-%d %H:%M") + df.index = pd.date_range( + startdate, enddate, freq=freq, tz="Europe/Amsterdam", name="datetime" + ) + return df + + dfs = [] + retry = 5 + for date in pd.date_range(start=start, end=end + timedelta(days=1)): + r = 0 + # print(f'DEBUG: {date}') + while r < retry: + try: + url = ( + f"https://www.regelleistung.net/apps/cpp-publisher/api/v1/download/tenders/" + f"resultsoverview?date={date.strftime('%Y-%m-%d')}&exportFormat=xlsx&market=CAPACITY&productTypes=FCR" + ) + df = pd.read_excel(url, engine="openpyxl")[ + [ + "DATE_FROM", + "PRODUCTNAME", + "NL_SETTLEMENTCAPACITY_PRICE_[EUR/MW]", + "DE_SETTLEMENTCAPACITY_PRICE_[EUR/MW]", + ] + ] + # print(f'DEBUG: {date} read in') + dfs.append(df) + break + except Exception: + # print(r) + time.sleep(1) + r += 1 + warnings.warn( + f'No data received for {date.strftime("%Y-%m-%d")}. Retrying...({r}/{retry})' + ) + + if r == retry: + raise RuntimeError(f'No data received for {date.strftime("%Y-%m-%d")}') + + df = pd.concat(dfs, axis=0) + df["hour"] = df["PRODUCTNAME"].map(lambda x: int(x.split("_")[1])) + df["Timeblocks"] = ( + df["PRODUCTNAME"].map(lambda x: int(x.split("_")[2])) - df["hour"] + ) + df.index = df.apply( + lambda row: pd.to_datetime(row["DATE_FROM"]) + timedelta(hours=row["hour"]), + axis=1, + ).dt.tz_localize("Europe/Amsterdam") + df.drop(columns=["DATE_FROM", "PRODUCTNAME", "hour"], inplace=True) + df.rename( + columns={ + "NL_SETTLEMENTCAPACITY_PRICE_[EUR/MW]": f"FCR Price NL [EUR/MW/{freq}]", + "DE_SETTLEMENTCAPACITY_PRICE_[EUR/MW]": f"FCR Price DE [EUR/MW/{freq}]", + }, + inplace=True, + ) + + try: + df[f"FCR Price NL [EUR/MW/{freq}]"] = df[ + f"FCR Price NL [EUR/MW/{freq}]" + ].astype(float) + df[f"FCR Price DE [EUR/MW/{freq}]"] = df[ + f"FCR Price NL [EUR/MW/{freq}]" + ].astype(float) + except Exception as e: + warnings.warn( + f"Could not convert data to floats. Should check... Exception: {e}" + ) + + df = df[~df.index.duplicated(keep="first")] + new_ix = pd.date_range( + start=df.index[0], end=df.index[-1], freq=freq, tz="Europe/Amsterdam" + ) + df = df.reindex(new_ix, method="ffill") + mult = {"H": 1, "4H": 4, "D": 24} + df[f"FCR Price NL [EUR/MW/{freq}]"] /= df["Timeblocks"] / mult[freq] + df[f"FCR Price DE [EUR/MW/{freq}]"] /= df["Timeblocks"] / mult[freq] + df = df[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + df.to_csv(path, sep=";", decimal=",", index_label="datetime") + return df + + +def get_tennet_data(exporttype, start, end): + """Download data from TenneT API + + TenneT documentation: + https://www.tennet.org/bedrijfsvoering/exporteer_data_toelichting.aspx + + Parameters: + ----------- + exporttype : str + Exporttype as defined in TenneT documentation. + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + + Returns: + -------- + DataFrame with API output. + """ + datefrom = start.strftime("%d-%m-%Y") + dateto = end.strftime("%d-%m-%Y") + url = ( + f"http://www.tennet.org/bedrijfsvoering/ExporteerData.aspx?exporttype={exporttype}" + f"&format=csv&datefrom={datefrom}&dateto={dateto}&submit=1" + ) + + return pd.read_csv(url, decimal=",") + + +def get_imb_prices_nl(start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame: + exporttype = "verrekenprijzen" + data = get_tennet_data(exporttype, start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, exporttype) + date_ix = pd.date_range(first_entry, last_entry, freq="15T", tz="Europe/Amsterdam") + + if len(data) == len(date_ix): + data.index = date_ix + else: + data = _handle_missing_data_by_reindexing(data) + + data = data[["invoeden", "Afnemen", "regeltoestand"]] + data.columns = ["POS", "NEG", "RS"] + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + return data + + +def get_balansdelta_nl(start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame: + filename = f"balansdelta_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + path = Path("./data") / filename + if path.exists(): + data = pd.read_csv(path, sep=";", decimal=",", index_col="datetime") + startdate = pd.to_datetime(data.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(data.index[-1]).strftime("%Y-%m-%d %H:%M") + data.index = pd.date_range(startdate, enddate, freq="1T", tz="Europe/Amsterdam") + return data + + exporttype = "balansdelta2017" + data = get_tennet_data(exporttype, start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, exporttype) + date_ix = pd.date_range(first_entry, last_entry, freq="1T", tz="Europe/Amsterdam") + data.index = date_ix + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def _get_afrr_prices_from_entsoe(start, end, marketagreement_type, entsoe_api_key): + client = EntsoePandasClient(entsoe_api_key) + return client.query_contracted_reserve_prices( + country_code="NL", + start=start, + end=end + timedelta(days=1), + type_marketagreement_type=marketagreement_type, + ) + + +def get_afrr_capacity_fees_nl(start, end, entsoe_api_key=None): + path = Path( + f"./data/afrr_capacity_fees_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + df = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype(float) + startdate = pd.to_datetime(df.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(df.index[-1]).strftime("%Y-%m-%d %H:%M") + df.index = pd.date_range(startdate, enddate, freq="D", tz="Europe/Amsterdam") + return df + + if not entsoe_api_key: + try: + entsoe_api_key = os.environ["ENTSOE_API_KEY"] + except: + raise ValueError("Please enter ENTSOE API key") + + date_to_daily_bids = pd.to_datetime("2020-08-31").tz_localize("Europe/Amsterdam") + + if start < date_to_daily_bids: + _start = start - timedelta(days=7) + data = _get_afrr_prices_from_entsoe( + start=_start, + end=min(date_to_daily_bids, end), + marketagreement_type="A02", + entsoe_api_key=entsoe_api_key, + )[["Automatic frequency restoration reserve - Symmetric"]] + + if end > date_to_daily_bids: + _end = date_to_daily_bids - timedelta(days=1) + else: + _end = end + dt_index = pd.date_range(start, _end, freq="D", tz="Europe/Amsterdam") + data = data.reindex(dt_index, method="ffill") + + # ENTSOE: + # "Before week no. 1 of 2020 the values are published per period + # per MW (Currency/MW per procurement period); meaning that it + # is not divided by MTU/ISP in that period." + if start < pd.to_datetime("2019-12-23"): + data[: pd.to_datetime("2019-12-22")] /= 7 * 24 * 4 + + if end >= date_to_daily_bids: + _data = ( + _get_afrr_prices_from_entsoe( + start=max(date_to_daily_bids, start), + end=end, + marketagreement_type="A01", + entsoe_api_key=entsoe_api_key, + ) + .resample("D") + .first() + ) + cols = [ + "Automatic frequency restoration reserve - Down", + "Automatic frequency restoration reserve - Symmetric", + "Automatic frequency restoration reserve - Up", + ] + + for col in cols: + if col not in _data.columns: + _data[col] = np.NaN + + _data = _data[cols] + + try: + data = pd.concat([data, _data], axis=0) + except Exception: + data = _data + + data = data[start:end] + + new_col_names = { + "Automatic frequency restoration reserve - Down": "aFRR Down [€/MW/day]", + "Automatic frequency restoration reserve - Symmetric": "aFRR Symmetric [€/MW/day]", + "Automatic frequency restoration reserve - Up": "aFRR Up [€/MW/day]", + } + data.rename(columns=new_col_names, inplace=True) + hours_per_day = ( + pd.Series( + data=0, + index=pd.date_range( + start, + end + timedelta(days=1), + freq="15T", + tz="Europe/Amsterdam", + inclusive="left", + ), + ) + .resample("D") + .count() + ) + data = data.multiply(hours_per_day.values, axis=0).round(2) + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def _get_afrr_prices_nl_from_tennet(start, end): + """Get aFRR prices from TenneT API + + Parameters: + ----------- + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + + Returns: + -------- + DataFrame with imbalance prices. + """ + filename = f"afrr_prices_nl_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + path = Path("./data") / filename + if path.exists(): + data = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype( + float + ) + startdate = pd.to_datetime(data.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(data.index[-1]).strftime("%Y-%m-%d %H:%M") + data.index = pd.date_range( + startdate, enddate, freq="15T", tz="Europe/Amsterdam" + ) + return data + + data = get_tennet_data("verrekenprijzen", start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, "verrekenprijzen") + date_ix = pd.date_range(first_entry, last_entry, freq="15T", tz="Europe/Amsterdam") + + if len(data) == len(date_ix): + data.index = date_ix + else: + data = _handle_missing_data_by_reindexing(data) + + data = data[["opregelen", "Afregelen"]] + data.columns = ["price_up", "price_down"] + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def get_afrr_prices_nl(start, end): + bd = get_balansdelta_nl(start=start, end=end)[ + ["Hoogste_prijs_opregelen", "Laagste_prijs_afregelen"] + ] + bd.columns = ["rt_price_UP", "rt_price_DOWN"] + afrr_prices = _get_afrr_prices_nl_from_tennet(start, end).reindex( + bd.index, method="ffill" + ) + return pd.concat([afrr_prices, bd], axis=1) + + +def _get_index_first_and_last_entry(data, exporttype): + if exporttype == "balansdelta2017": + time_col_name = "tijd" + elif exporttype == "verrekenprijzen": + time_col_name = "periode_van" + return [ + pd.to_datetime( + " ".join((data["datum"].iloc[ix], data[time_col_name].iloc[ix])), + format="%d-%m-%Y %H:%M", + ) + for ix in [0, -1] + ] + + +def _handle_missing_data_by_reindexing(data): + print("Warning: Entries missing from TenneT data.") + data.index = data[["datum", "periode_van"]].apply(lambda x: " ".join(x), axis=1) + data.index = pd.to_datetime(data.index, format="%d-%m-%Y %H:%M").tz_localize( + "Europe/Amsterdam", ambiguous=True + ) + data = data[~data.index.duplicated(keep="first")] + date_ix = pd.date_range( + data.index[0], data.index[-1], freq="15T", tz="Europe/Amsterdam" + ) + data = data.reindex(date_ix) + print("Workaround implemented: Dataset was reindexed automatically.") + return data + + +def get_imb_prices_be(startdate, enddate): + start = pd.to_datetime(startdate).tz_localize("Europe/Brussels").tz_convert("UTC") + end = ( + pd.to_datetime(enddate).tz_localize("Europe/Brussels") + timedelta(days=1) + ).tz_convert("UTC") + rows = int((end - start) / timedelta(minutes=15)) + resp_df = pd.DataFrame() + + while rows > 0: + print(f"Getting next chunk, {rows} remaining.") + chunk = min(3000, rows) + end = start + timedelta(minutes=chunk * 15) + resp_df = pd.concat([resp_df, elia_api_call(start, end)], axis=0) + start = end + rows -= chunk + + resp_df.index = pd.date_range( + start=resp_df.index[0], end=resp_df.index[-1], tz="Europe/Brussels", freq="15T" + ) + + resp_df.index.name = "datetime" + resp_df = resp_df[ + ["positiveimbalanceprice", "negativeimbalanceprice", "qualitystatus"] + ].rename(columns={"positiveimbalanceprice": "POS", "negativeimbalanceprice": "NEG"}) + resp_df["Validated"] = False + resp_df.loc[resp_df["qualitystatus"] == "Validated", "Validated"] = True + resp_df.drop(columns=["qualitystatus"], inplace=True) + return resp_df + + +def elia_api_call(start, end): + dataset = "ods047" + sort_by = "datetime" + url = "https://opendata.elia.be/api/records/1.0/search/" + rows = int((end - start) / timedelta(minutes=15)) + end = end - timedelta(minutes=15) + endpoint = ( + f"?dataset={dataset}&q=datetime:[{start.strftime('%Y-%m-%dT%H:%M:%SZ')}" + f" TO {end.strftime('%Y-%m-%dT%H:%M:%SZ')}]&rows={rows}&sort={sort_by}" + ) + + for _ in range(5): + try: + resp = requests.get(url + endpoint) + if resp.ok: + break + else: + raise Exception() + except Exception: + print("retrying...") + time.sleep(1) + + if not resp.ok: + raise Exception(f"Error when calling API. Status code: {resp.status_code}") + + resp_json = json.loads(resp.content) + resp_json = [entry["fields"] for entry in resp_json["records"]] + + df = pd.DataFrame(resp_json).set_index("datetime") + df.index = pd.to_datetime(df.index, utc=True).tz_convert("Europe/Brussels") + df = df.sort_index() + return df + + +def get_da_prices_from_entsoe( + start, end, country_code, tz, freq="H", entsoe_api_key=None +): + """Get Day-Ahead prices from ENTSOE + + Parameters: + ----------- + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with day-ahead prices. + """ + if not entsoe_api_key: + try: + entsoe_api_key = "f6c67fd5-e423-47bc-8a3c-98125ccb645e" + except: + raise ValueError("Please enter ENTSOE API key") + + client = EntsoePandasClient(entsoe_api_key) + data = client.query_day_ahead_prices( + country_code, start=start, end=end + timedelta(days=1) + ) + data = data[~data.index.duplicated()] + data.index = pd.date_range(data.index[0], data.index[-1], freq="H", tz=tz) + + if freq != "H": + data = _reindex_to_freq(data, freq, tz) + + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + return data + + +def _reindex_to_freq(data, freq, tz): + new_ix = pd.date_range( + data.index[0], + data.index[-1] + timedelta(hours=1), + freq=freq, + tz=tz, + ) + return data.reindex(index=new_ix, method="ffill") + + +def get_da_prices_nl(start, end, freq="H", entsoe_api_key=None): + return get_da_prices_from_entsoe( + start, end, "NL", "Europe/Amsterdam", freq=freq, entsoe_api_key=entsoe_api_key + ) + + +def get_da_prices_be(start, end, freq="H", entsoe_api_key=None): + return get_da_prices_from_entsoe( + start, end, "BE", "Europe/Brussels", freq=freq, entsoe_api_key=entsoe_api_key + ) + + +def get_ets_prices(start, end, freq="D"): + """Get CO2 prices (ETS) from ICE + + Values are in €/ton CO2 + + Parameters: + ----------- + start : datetime + Start date + end : datetime + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with ETS settlement prices with datetime index (local time) + """ + + start_x = start + timedelta(days=-2) + end_x = end + timedelta(days=2) + + data = get_ets_prices_from_database(start_x, end_x, 'NLD') + data = data.resample('1T').ffill() + data = data.loc[(data.index >= start) & (data.index < end)] + return data + + + # here = pytz.timezone("Europe/Amsterdam") + # start_file = pd.Timestamp(str(start.year) + "-1-1", tz=here).to_pydatetime() + # end_file = pd.Timestamp(str(start.year) + "-12-31", tz=here).to_pydatetime() + + # path = Path( + # f"./data/ets_prices_{freq}_{start_file.strftime('%Y%m%d')}_{end_file.strftime('%Y%m%d')}.csv" + # ) + # if path.exists(): + # return _load_from_csv(path, freq=freq) + # else: + # raise Exception("Data not available for chosen dates.") + + +def get_ttf_prices(start, end, freq="D"): + """Get Day-Ahead natural gas prices (TTF Day-ahead) from ICE + + Values are in €/MWh + + Parameters: + ----------- + start : datetime + Start date + end : datetime + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with TTF day-ahead prices with datetime index (local time) + + Start and End are converted into start of year and end of year + """ + + start_x = start + timedelta(days=-2) + end_x = end + timedelta(days=2) + + data = get_ttf_prices_from_database(start_x, end_x, 'NLD') + data = data.resample('1T').ffill() + data = data.loc[(data.index >= start) & (data.index < end)] + return data + + # # while start_year <= end_year: + # here = pytz.timezone("Europe/Amsterdam") + # start_file = pd.Timestamp(str(start.year) + "-1-1", tz=here).to_pydatetime() + # end_file = pd.Timestamp(str(start.year) + "-12-31", tz=here).to_pydatetime() + + # path = Path( + # f"./data/ttf_prices_{freq}_{start_file.strftime('%Y%m%d')}_{end_file.strftime('%Y%m%d')}.csv" + # ) + # print(path) + + # if path.exists(): + # return _load_from_csv(path, freq=freq) + # else: + # raise Exception("Data not available for chosen dates.") + + +def _load_from_csv(filepath, freq): + data = pd.read_csv( + filepath, + delimiter=";", + decimal=",", + parse_dates=False, + index_col="datetime", + ) + ix_start = pd.to_datetime(data.index[0], utc=True).tz_convert("Europe/Amsterdam") + ix_end = pd.to_datetime(data.index[-1], utc=True).tz_convert("Europe/Amsterdam") + data.index = pd.date_range(ix_start, ix_end, freq=freq, tz="Europe/Amsterdam") + return data.squeeze() + + +##### RECOY DATABASE QUERIES ##### + +def get_day_ahead_prices_from_database(start_hour, end_hour, CountryIsoCode, tz='utc'): + table = 'DayAheadPrices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['HourStartTime'] >= start_hour, + table.columns['HourStartTime'] < end_hour + )) + + data = pd.DataFrame(data) + data['HourStartTime'] = pd.to_datetime(data['HourStartTime'], utc=True) + data.index = data['HourStartTime'] + data.index.name = 'datetime' + data = data[['Price', 'CountryIsoCode']] + data.columns = ['DAM', 'CountryIsoCode'] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + + +def get_imbalance_prices_from_database(start_quarter, end_quarter, CountryIsoCode, tz='utc'): + table = 'ImbalancePrices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['QuarterStartTime'] >= start_quarter, + table.columns['QuarterStartTime'] < end_quarter + )) + + data = pd.DataFrame(data) + data['QuarterStartTime'] = pd.to_datetime(data['QuarterStartTime'], utc=True) + data.index = data['QuarterStartTime'] + data.index.name = 'datetime' + data = data[['FeedToGridPrice', 'TakeFromGridPrice', 'CountryIsoCode']] + data.columns = ['POS', 'NEG', 'CountryIsoCode'] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + + +def get_FCR_prices_from_database(start_day, end_day, CountryIsoCode, tz='utc'): + table = 'ReservePrices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['Timestamp'] >= start_day, + table.columns['Timestamp'] <= end_day, + table.columns['ReserveType'] == 'FCR' + )) + + data = pd.DataFrame(data) + data['Timestamp'] = pd.to_datetime(data['Timestamp'], utc=True) + data.index = data['Timestamp'] + data.index.name = 'datetime' + data = data[['PricePerMWPerISP', 'CountryIsoCode']] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + + +def get_imbalance_forecasts_from_database_on_publication_time(start_publication_time, end_publication_time, ForecastSources, CountryIsoCodes): + table = 'ImbalancePriceForecasts' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'].in_(CountryIsoCodes), + table.columns['PublicationTime'] >= start_publication_time, + table.columns['PublicationTime'] < end_publication_time, + table.columns['ForecastSource'].in_(ForecastSources) + )) + + return pd.DataFrame(data) + + +def get_imbalance_forecasts_from_database_on_quarter_start_time(start_quarter, end_quarter, ForecastSources, CountryIsoCode): + table = 'ImbalancePriceForecasts' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['QuarterStartTime'] >= start_quarter, + table.columns['QuarterStartTime'] < end_quarter, + table.columns['PublicationTime'] < end_quarter, + table.columns['ForecastSource'].in_(ForecastSources) + )) + + return pd.DataFrame(data) + + +def get_ttf_prices_from_database(start, end, CountryIsoCode, tz='utc'): + if start.tzinfo != pytz.utc: + start = start.astimezone(pytz.utc) + if end.tzinfo != pytz.utc: + end = end.astimezone(pytz.utc) + + table = 'GasPrices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['Timestamp'] >= start, + table.columns['Timestamp'] < end + )) + + data = pd.DataFrame(data) + data['Timestamp'] = pd.to_datetime(data['Timestamp'], utc=True) + data.index = data['Timestamp'] + data.index.name = 'datetime' + data = data[['TTFPrice']] + data.columns = ['Gas prices (€/MWh)'] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + + +def get_ets_prices_from_database(start, end, CountryIsoCode, tz='utc'): + if start.tzinfo != pytz.utc: + start = start.astimezone(pytz.utc) + if end.tzinfo != pytz.utc: + end = end.astimezone(pytz.utc) + + table = 'Co2Prices' + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + + data = session.query(table).filter(and_( + table.columns['CountryIsoCode'] == CountryIsoCode, + table.columns['Timestamp'] >= start, + table.columns['Timestamp'] < end + )) + + data = pd.DataFrame(data) + data['Timestamp'] = pd.to_datetime(data['Timestamp'], utc=True) + data.index = data['Timestamp'] + data.index.name = 'datetime' + data = data[['Price']] + data.columns = ['CO2 prices (€/MWh)'] + if tz.__eq__('utc') is False: + data = data.tz_convert(tz) + return data + diff --git a/pyrecoy/pyrecoy/pyrecoy/prices.py b/pyrecoy/pyrecoy/pyrecoy/prices.py new file mode 100644 index 0000000..600b42d --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/prices.py @@ -0,0 +1,752 @@ +import os +from datetime import timedelta +from io import BytesIO +from pathlib import Path +from zipfile import ZipFile +import time +import warnings +import json +import pytz + +import numpy as np +import pandas as pd +import requests +from bs4 import BeautifulSoup +from entsoe.entsoe import EntsoePandasClient +from sqlalchemy import MetaData, Table, insert, and_, or_ +from pyrecoy import * + + +def get_fcr_prices(start, end, freq="H") -> pd.DataFrame: + """Get FCR settlement prices from Regelleistung website + + Returns: DataFrame with FCR prices with index with given time frequency in local time. + """ + start = start + timedelta(-1) + end = end + timedelta(1) + data = get_FCR_prices_from_database(start, end, "NLD") + data = data.resample("15T").ffill() + data = data[["PricePerMWPerISP"]] + data.columns = ["FCR NL (EUR/ISP)"] + data.index.name = "datetime" + data = data.tz_convert("Europe/Amsterdam") + return data + + +def get_tennet_data(exporttype, start, end): + """Download data from TenneT API + + TenneT documentation: + https://www.tennet.org/bedrijfsvoering/exporteer_data_toelichting.aspx + + Parameters: + ----------- + exporttype : str + Exporttype as defined in TenneT documentation. + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + + Returns: + -------- + DataFrame with API output. + """ + datefrom = start.strftime("%d-%m-%Y") + dateto = end.strftime("%d-%m-%Y") + url = ( + f"http://www.tennet.org/bedrijfsvoering/ExporteerData.aspx?exporttype={exporttype}" + f"&format=csv&datefrom={datefrom}&dateto={dateto}&submit=1" + ) + + return pd.read_csv(url, decimal=",") + + +def get_imb_prices_nl(start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame: + exporttype = "verrekenprijzen" + data = get_tennet_data(exporttype, start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, exporttype) + date_ix = pd.date_range(first_entry, last_entry, freq="15T", tz="Europe/Amsterdam") + + if len(data) == len(date_ix): + data.index = date_ix + else: + data = _handle_missing_data_by_reindexing(data) + + data = data[["invoeden", "Afnemen", "regeltoestand"]] + data.columns = ["POS", "NEG", "RS"] + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + return data + + +def get_balansdelta_nl(start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame: + filename = f"balansdelta_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + path = Path("./data") / filename + if path.exists(): + data = pd.read_csv(path, sep=";", decimal=",", index_col="datetime") + startdate = pd.to_datetime(data.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(data.index[-1]).strftime("%Y-%m-%d %H:%M") + data.index = pd.date_range(startdate, enddate, freq="1T", tz="Europe/Amsterdam") + return data + + exporttype = "balansdelta2017" + data = get_tennet_data(exporttype, start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, exporttype) + date_ix = pd.date_range(first_entry, last_entry, freq="1T", tz="Europe/Amsterdam") + data.index = date_ix + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def _get_afrr_prices_from_entsoe(start, end, marketagreement_type, entsoe_api_key): + client = EntsoePandasClient(entsoe_api_key) + return client.query_contracted_reserve_prices( + country_code="NL", + start=start, + end=end + timedelta(days=1), + type_marketagreement_type=marketagreement_type, + ) + + +def get_afrr_capacity_fees_nl(start, end, entsoe_api_key=None): + path = Path( + f"./data/afrr_capacity_fees_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + df = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype(float) + startdate = pd.to_datetime(df.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(df.index[-1]).strftime("%Y-%m-%d %H:%M") + df.index = pd.date_range(startdate, enddate, freq="D", tz="Europe/Amsterdam") + return df + + if not entsoe_api_key: + try: + entsoe_api_key = os.environ["ENTSOE_API_KEY"] + except: + raise ValueError("Please enter ENTSOE API key") + + date_to_daily_bids = pd.to_datetime("2020-08-31").tz_localize("Europe/Amsterdam") + + if start < date_to_daily_bids: + _start = start - timedelta(days=7) + data = _get_afrr_prices_from_entsoe( + start=_start, + end=min(date_to_daily_bids, end), + marketagreement_type="A02", + entsoe_api_key=entsoe_api_key, + )[["Automatic frequency restoration reserve - Symmetric"]] + + if end > date_to_daily_bids: + _end = date_to_daily_bids - timedelta(days=1) + else: + _end = end + dt_index = pd.date_range(start, _end, freq="D", tz="Europe/Amsterdam") + data = data.reindex(dt_index, method="ffill") + + # ENTSOE: + # "Before week no. 1 of 2020 the values are published per period + # per MW (Currency/MW per procurement period); meaning that it + # is not divided by MTU/ISP in that period." + if start < pd.to_datetime("2019-12-23"): + data[: pd.to_datetime("2019-12-22")] /= 7 * 24 * 4 + + if end >= date_to_daily_bids: + _data = ( + _get_afrr_prices_from_entsoe( + start=max(date_to_daily_bids, start), + end=end, + marketagreement_type="A01", + entsoe_api_key=entsoe_api_key, + ) + .resample("D") + .first() + ) + cols = [ + "Automatic frequency restoration reserve - Down", + "Automatic frequency restoration reserve - Symmetric", + "Automatic frequency restoration reserve - Up", + ] + + for col in cols: + if col not in _data.columns: + _data[col] = np.NaN + + _data = _data[cols] + + try: + data = pd.concat([data, _data], axis=0) + except Exception: + data = _data + + data = data[start:end] + + new_col_names = { + "Automatic frequency restoration reserve - Down": "aFRR Down [€/MW/day]", + "Automatic frequency restoration reserve - Symmetric": "aFRR Symmetric [€/MW/day]", + "Automatic frequency restoration reserve - Up": "aFRR Up [€/MW/day]", + } + data.rename(columns=new_col_names, inplace=True) + hours_per_day = ( + pd.Series( + data=0, + index=pd.date_range( + start, + end + timedelta(days=1), + freq="15T", + tz="Europe/Amsterdam", + inclusive="left", + ), + ) + .resample("D") + .count() + ) + data = data.multiply(hours_per_day.values, axis=0).round(2) + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def _get_afrr_prices_nl_from_tennet(start, end): + """Get aFRR prices from TenneT API + + Parameters: + ----------- + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + + Returns: + -------- + DataFrame with imbalance prices. + """ + filename = f"afrr_prices_nl_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv" + path = Path("./data") / filename + if path.exists(): + data = pd.read_csv(path, sep=";", decimal=",", index_col="datetime").astype( + float + ) + startdate = pd.to_datetime(data.index[0]).strftime("%Y-%m-%d %H:%M") + enddate = pd.to_datetime(data.index[-1]).strftime("%Y-%m-%d %H:%M") + data.index = pd.date_range( + startdate, enddate, freq="15T", tz="Europe/Amsterdam" + ) + return data + + data = get_tennet_data("verrekenprijzen", start, end) + first_entry, last_entry = _get_index_first_and_last_entry(data, "verrekenprijzen") + date_ix = pd.date_range(first_entry, last_entry, freq="15T", tz="Europe/Amsterdam") + + if len(data) == len(date_ix): + data.index = date_ix + else: + data = _handle_missing_data_by_reindexing(data) + + data = data[["opregelen", "Afregelen"]] + data.columns = ["price_up", "price_down"] + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + + if not path.exists(): + data.to_csv(path, sep=";", decimal=",", index_label="datetime") + return data + + +def get_afrr_prices_nl(start, end): + bd = get_balansdelta_nl(start=start, end=end)[ + ["Hoogste_prijs_opregelen", "Laagste_prijs_afregelen"] + ] + bd.columns = ["rt_price_UP", "rt_price_DOWN"] + afrr_prices = _get_afrr_prices_nl_from_tennet(start, end).reindex( + bd.index, method="ffill" + ) + return pd.concat([afrr_prices, bd], axis=1) + + +def _get_index_first_and_last_entry(data, exporttype): + if exporttype == "balansdelta2017": + time_col_name = "tijd" + elif exporttype == "verrekenprijzen": + time_col_name = "periode_van" + return [ + pd.to_datetime( + " ".join((data["datum"].iloc[ix], data[time_col_name].iloc[ix])), + format="%d-%m-%Y %H:%M", + ) + for ix in [0, -1] + ] + + +def _handle_missing_data_by_reindexing(data): + print("Warning: Entries missing from TenneT data.") + data.index = data[["datum", "periode_van"]].apply(lambda x: " ".join(x), axis=1) + data.index = pd.to_datetime(data.index, format="%d-%m-%Y %H:%M").tz_localize( + "Europe/Amsterdam", ambiguous=True + ) + data = data[~data.index.duplicated(keep="first")] + date_ix = pd.date_range( + data.index[0], data.index[-1], freq="15T", tz="Europe/Amsterdam" + ) + data = data.reindex(date_ix) + print("Workaround implemented: Dataset was reindexed automatically.") + return data + + +def get_imb_prices_be(startdate, enddate): + start = pd.to_datetime(startdate).tz_localize("Europe/Brussels").tz_convert("UTC") + end = ( + pd.to_datetime(enddate).tz_localize("Europe/Brussels") + timedelta(days=1) + ).tz_convert("UTC") + rows = int((end - start) / timedelta(minutes=15)) + resp_df = pd.DataFrame() + + while rows > 0: + print(f"Getting next chunk, {rows} remaining.") + chunk = min(3000, rows) + end = start + timedelta(minutes=chunk * 15) + resp_df = pd.concat([resp_df, elia_api_call(start, end)], axis=0) + start = end + rows -= chunk + + resp_df.index = pd.date_range( + start=resp_df.index[0], end=resp_df.index[-1], tz="Europe/Brussels", freq="15T" + ) + + resp_df.index.name = "datetime" + resp_df = resp_df[ + ["positiveimbalanceprice", "negativeimbalanceprice", "qualitystatus"] + ].rename(columns={"positiveimbalanceprice": "POS", "negativeimbalanceprice": "NEG"}) + resp_df["Validated"] = False + resp_df.loc[resp_df["qualitystatus"] == "Validated", "Validated"] = True + resp_df.drop(columns=["qualitystatus"], inplace=True) + return resp_df + + +def elia_api_call(start, end): + dataset = "ods047" + sort_by = "datetime" + url = "https://opendata.elia.be/api/records/1.0/search/" + rows = int((end - start) / timedelta(minutes=15)) + end = end - timedelta(minutes=15) + endpoint = ( + f"?dataset={dataset}&q=datetime:[{start.strftime('%Y-%m-%dT%H:%M:%SZ')}" + f" TO {end.strftime('%Y-%m-%dT%H:%M:%SZ')}]&rows={rows}&sort={sort_by}" + ) + + for _ in range(5): + try: + resp = requests.get(url + endpoint) + if resp.ok: + break + else: + raise Exception() + except Exception: + print("retrying...") + time.sleep(1) + + if not resp.ok: + raise Exception(f"Error when calling API. Status code: {resp.status_code}") + + resp_json = json.loads(resp.content) + resp_json = [entry["fields"] for entry in resp_json["records"]] + + df = pd.DataFrame(resp_json).set_index("datetime") + df.index = pd.to_datetime(df.index, utc=True).tz_convert("Europe/Brussels") + df = df.sort_index() + return df + + +def get_da_prices_from_entsoe( + start, end, country_code, tz, freq="H", entsoe_api_key=None +): + """Get Day-Ahead prices from ENTSOE + + Parameters: + ----------- + start : pd.Timestamp + Start date + end : pd.Timestamp + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with day-ahead prices. + """ + if not entsoe_api_key: + try: + entsoe_api_key = "f6c67fd5-e423-47bc-8a3c-98125ccb645e" + except: + raise ValueError("Please enter ENTSOE API key") + + client = EntsoePandasClient(entsoe_api_key) + data = client.query_day_ahead_prices( + country_code, start=start, end=end + timedelta(days=1) + ) + data = data[~data.index.duplicated()] + data.index = pd.date_range(data.index[0], data.index[-1], freq="H", tz=tz) + + if freq != "H": + data = _reindex_to_freq(data, freq, tz) + + data = data[start.strftime("%Y-%m-%d") : end.strftime("%Y-%m-%d")] + return data + + +def _reindex_to_freq(data, freq, tz): + new_ix = pd.date_range( + data.index[0], + data.index[-1] + timedelta(hours=1), + freq=freq, + tz=tz, + ) + return data.reindex(index=new_ix, method="ffill") + + +def get_da_prices_nl(start, end, freq="H", entsoe_api_key=None): + return get_da_prices_from_entsoe( + start, end, "NL", "Europe/Amsterdam", freq=freq, entsoe_api_key=entsoe_api_key + ) + + +def get_da_prices_be(start, end, freq="H", entsoe_api_key=None): + return get_da_prices_from_entsoe( + start, end, "BE", "Europe/Brussels", freq=freq, entsoe_api_key=entsoe_api_key + ) + + +def get_ets_prices(start, end, freq="D"): + """Get CO2 prices (ETS) from ICE + + Values are in €/ton CO2 + + Parameters: + ----------- + start : datetime + Start date + end : datetime + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with ETS settlement prices with datetime index (local time) + """ + + start_x = start + timedelta(days=-2) + end_x = end + timedelta(days=2) + + data = get_ets_prices_from_database(start_x, end_x, "NLD") + data = data.resample("1T").ffill() + data = data.loc[(data.index >= start) & (data.index < end)] + return data + + here = pytz.timezone("Europe/Amsterdam") + start_file = pd.Timestamp(str(start.year) + "-1-1", tz=here).to_pydatetime() + end_file = pd.Timestamp(str(start.year) + "-12-31", tz=here).to_pydatetime() + + path = Path( + f"./data/ets_prices_{freq}_{start_file.strftime('%Y%m%d')}_{end_file.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + return _load_from_csv(path, freq=freq) + else: + raise Exception("Data not available for chosen dates.") + +def get_ets_prices_excel(start, end, freq="D"): + """Get CO2 prices (ETS) from ICE + + Values are in €/ton CO2 + + Parameters: + ----------- + start : datetime + Start date + end : datetime + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with ETS settlement prices with datetime index (local time) + """ + + start_x = start + timedelta(days=-2) + end_x = end + timedelta(days=2) + + data = pd.read_excel(r"C:\Users\shahla.huseynova\Heliox Group B.V\Recoy - Documents\01 _ Acquisition\FOLBB\CO2 prijzen EEX 2021-2023.xlsx", + sheet_name = 'SINGLE COL', + parse_dates = True, + index_col = 0 + ) + data.set_index(pd.to_datetime(data.index)) + data.index = pd.to_datetime(data.index,dayfirst=True) + data = data.groupby(data.index).last() + data.index = data.index.tz_localize('Europe/Amsterdam') + data = data.resample("1T").ffill() + data = data.loc[(data.index >= start) & (data.index < end)] + return data + + here = pytz.timezone("Europe/Amsterdam") + start_file = pd.Timestamp(str(start.year) + "-1-1", tz=here).to_pydatetime() + end_file = pd.Timestamp(str(start.year) + "-12-31", tz=here).to_pydatetime() + + path = Path( + f"./data/ets_prices_{freq}_{start_file.strftime('%Y%m%d')}_{end_file.strftime('%Y%m%d')}.csv" + ) + if path.exists(): + return _load_from_csv(path, freq=freq) + else: + raise Exception("Data not available for chosen dates.") + +def get_ttf_prices(start, end, freq="D"): + """Get Day-Ahead natural gas prices (TTF Day-ahead) from ICE + + Values are in €/MWh + + Parameters: + ----------- + start : datetime + Start date + end : datetime + End date + freq : str + Frequency, e.g. '15T' or 'H' + + Returns: + -------- + Series with TTF day-ahead prices with datetime index (local time) + + Start and End are converted into start of year and end of year + """ + + start_x = start + timedelta(days=-2) + end_x = end + timedelta(days=2) + + data = get_ttf_prices_from_database(start_x, end_x, "NLD") + data = data.resample("1T").ffill() + data = data.loc[(data.index >= start) & (data.index < end)] + return data + + # while start_year <= end_year: + here = pytz.timezone("Europe/Amsterdam") + start_file = pd.Timestamp(str(start.year) + "-1-1", tz=here).to_pydatetime() + end_file = pd.Timestamp(str(start.year) + "-12-31", tz=here).to_pydatetime() + + path = Path( + f"./data/ttf_prices_{freq}_{start_file.strftime('%Y%m%d')}_{end_file.strftime('%Y%m%d')}.csv" + ) + + if path.exists(): + return _load_from_csv(path, freq=freq) + else: + raise Exception("Data not available for chosen dates.") + + +def _load_from_csv(filepath, freq): + data = pd.read_csv( + filepath, + delimiter=";", + decimal=",", + parse_dates=False, + index_col="datetime", + ) + ix_start = pd.to_datetime(data.index[0], utc=True).tz_convert("Europe/Amsterdam") + ix_end = pd.to_datetime(data.index[-1], utc=True).tz_convert("Europe/Amsterdam") + data.index = pd.date_range(ix_start, ix_end, freq=freq, tz="Europe/Amsterdam") + return data.squeeze() + + +##### RECOY DATABASE QUERIES ##### + + +def convert_columns_to_localized_datetime_from_utc(df, columns, tz): + for column in columns: + df[column] = pd.to_datetime(df[column], utc=True) + df[column] = df[column].dt.tz_convert(tz) + return df + + +def get_price_data_from_database( + database_name, + time_index_column, + database_columns, + rename_columns, + start, + end, + CountryIsoCode, + tz="utc", + to_datetime_columns=[], +): + """_summary_ + + Args: + database_name (string): name of the database + time_index_column (string): column which is converted to a datetime column and used as the index + database_columns (list of strings): columns of the database table you want to query + rename_columns (list of strings): new names for the columns which are queried + start (string or datetime): start time of the data you want to select based on the time_index_column + end (string or datetime): end time of the data you want to select based on the time_index_column + CountryIsoCode (string): CountryIsoCode of the data + tz (str, optional): Timezone you want the datatime columns to be converted to + to_datetime_columns (list, optional): Additional columns which are transferred to datetime columns. Defaults to []. + + Returns: + _type_: _description_ + """ + table = database_name + md = MetaData(ENGINE_PRICES) + table = Table(table, md, autoload=True) + session = sessionmaker(bind=ENGINE_PRICES)() + end = end + timedelta(days=+1) + + data = session.query(table).filter( + and_( + table.columns["CountryIsoCode"] == CountryIsoCode, + table.columns[time_index_column] >= start, + table.columns[time_index_column] < end, + ) + ) + data = pd.DataFrame(data) + data[time_index_column] = pd.to_datetime(data[time_index_column], utc=True) + data.index = data[time_index_column] + data.index.name = "datetime" + data = data[database_columns + ["CountryIsoCode"]] + data.columns = rename_columns + ["CountryIsoCode"] + if tz.__eq__("utc") is False: + data = data.tz_convert(tz) + data = convert_columns_to_localized_datetime_from_utc(data, to_datetime_columns, tz) + return data + + +def get_day_ahead_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_price_data_from_database( + "DayAheadPrices", + "HourStartTime", + ["Price"], + ["DAM"], + start, + end, + CountryIsoCode, + tz=tz, + ) + + +def get_imbalance_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_price_data_from_database( + "ImbalancePrices", + "QuarterStartTime", + ["FeedToGridPrice", "TakeFromGridPrice"], + ["POS", "NEG"], + start, + end, + CountryIsoCode, + tz=tz, + ) + + +def get_imbalance_forecasts_from_database_on_publication_time( + start, end, CountryIsoCode, tz="utc" +): + return get_price_data_from_database( + "ImbalancePriceForecasts", + "PublicationTime", + ["PublicationTime", "QuarterStartTime", "FeedToGridPrice", "TakeFromGridPrice"], + ["PublicationTime", "QuarterStartTime", "ForePos", "ForeNeg"], + start, + end, + CountryIsoCode, + tz=tz, + to_datetime_columns=["QuarterStartTime"], + ) + + +def get_imbalance_forecasts_from_database_on_quarter_start_time( + start, end, CountryIsoCode, tz="utc" +): + return get_price_data_from_database( + "ImbalancePriceForecasts", + "QuarterStartTime", + ["PublicationTime", "QuarterStartTime", "FeedToGridPrice", "TakeFromGridPrice"], + ["PublicationTime", "QuarterStartTime", "ForePos", "ForeNeg"], + start, + end, + CountryIsoCode, + tz=tz, + to_datetime_columns=["QuarterStartTime"], + ) + + +def get_ttf_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_price_data_from_database( + "GasPrices", + "DeliveryDate", + ["Price"], + ["Gas prices (€/MWh)"], + start, + end, + CountryIsoCode, + tz=tz, + ) + + +def get_ets_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_price_data_from_database( + "Co2Prices", + "DeliveryDate", + ["Price"], + ["CO2 prices (€/MWh)"], + start, + end, + CountryIsoCode, + tz=tz, + ) + + +def get_reserve_prices_from_database( + start, end, reserve_type, CountryIsoCode, tz="utc" +): + data = get_price_data_from_database( + "ReservePrices", + "Timestamp", + ["PricePerMWPerISP", "ReserveType"], + ["PricePerMWPerISP", "ReserveType"], + start, + end, + CountryIsoCode, + tz=tz, + ) + data = data.loc[data["ReserveType"] == reserve_type] + return data + + +def get_FCR_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database(start, end, "FCR", CountryIsoCode, tz=tz) + + +def get_aFRR_up_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database( + start, end, "aFRR Up", CountryIsoCode, tz=tz + ) + + +def get_aFRR_up_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database( + start, end, "aFRR Down", CountryIsoCode, tz=tz + ) + + +def get_aFRR_up_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database( + start, end, "mFRR Up", CountryIsoCode, tz=tz + ) + + +def get_aFRR_up_prices_from_database(start, end, CountryIsoCode, tz="utc"): + return get_reserve_prices_from_database( + start, end, "mFRR Up", CountryIsoCode, tz=tz + ) diff --git a/pyrecoy/pyrecoy/pyrecoy/reports.py b/pyrecoy/pyrecoy/pyrecoy/reports.py new file mode 100644 index 0000000..6108272 --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/reports.py @@ -0,0 +1,156 @@ +import numpy as np +import pandas as pd + +from .styling import businesscase_formatter, num_formatting, perc_formatting + + +class CaseReport: + """Dataframe report showing KPIs for specific CaseStudy. + + Parameters: + ----------- + case : CaseStudy + kind : str + The report type. {electr_market_results', cashflows', 'ebitda_calc'}. + baseline : CaseStudy + include_perc: bool + """ + + def __init__(self, case, kind): + self._check_if_attr_exists(case, kind) + case_data = getattr(case, kind) + self.report = self.create_report(case.name, case_data) + self.formatting = "number" + + def _check_if_attr_exists(self, case, kind): + if not hasattr(case, kind): + raise AttributeError( + f"Attribute '{kind}' is not available for '{case.name}' case. " + "You should first generate it using " + "the appropriate CaseStudy method." + ) + + def create_report(self, case_name, case_data): + if isinstance(case_data, dict): + case_data = pd.Series(case_data) + + return pd.DataFrame(case_data, columns=[case_name]) + + def show(self, presentation_format=True): + if not presentation_format: + return self.report + + if self.formatting == "percentage": + return self.report.applymap(perc_formatting) + else: + return self.report.applymap(num_formatting) + + +class ComparisonReport(CaseReport): + """Dataframe report showing a copmarison of KPIs between CaseStudy instances. + + Parameters: + ----------- + cases : list + List of CaseStudy instances + kind : str + Type of report + baseline : CaseStudy + CaseStudy instance to use as baseline + comparison : str + {'absolute', 'relative', 'percentage'} + Sets how the numbers in the comparison are in relation to the baseline. + """ + + def __init__(self, cases, kind, baseline=None, comparison="absolute"): + case_reports = [] + self.formatting = "number" + + for case in cases: + case_report = CaseReport(case=case, kind=kind).report + case_reports.append(case_report) + + self.report = pd.concat(case_reports, axis=1).fillna(0) + + if comparison == "relative": + self._comp_relative(baseline) + elif comparison == "percentage": + self._comp_percentage(baseline) + + # ugly fix to make sure EBITDA is at the bottom when df is printed + if kind == "ebitda_calc": + ix = self.report.index.to_list() + ix.remove("EBITDA (€)") + ix.remove("Depreciation (€)") + ix.remove("EBITDA + depr (€)") + ix.append("EBITDA (€)") + ix.append("Depreciation (€)") + ix.append("EBITDA + depr (€)") + self.report = self.report.reindex(ix) + + def _comp_relative(self, baseline): + baseline_report = self.report[baseline.name] + self.report = self.report.subtract(baseline_report, axis=0) + + if baseline.name in self.report.columns: + self.report.drop(columns=baseline.name, inplace=True) + if baseline.name in self.report.index: + self.report.drop(index=baseline.name, inplace=True) + + self.formatting = "number" + + def _comp_percentage(self, baseline): + baseline_report = self.report[baseline.name] + self.report = self.report.divide(baseline_report / 100, axis=0).replace( + [-np.inf, np.inf], 0 + ) + self.report.replace([-np.inf, np.inf], 0, inplace=True) + self.formatting = "percentage" + + +class BusinessCaseReport(CaseReport): + """Show business case for CaseStudy""" + + def __init__(self, case, presentation_format=False): + self._check_if_attr_exists(case, "business_case") + self.report = getattr(case, "business_case") + + def show(self, presentation_format=True): + if presentation_format: + return businesscase_formatter(self.report) + else: + return self.report + + +class SingleFigureComparison(ComparisonReport): + def __init__( + self, + cases, + kpi, + label, + baseline=None, + comparison="absolute", + ): + figure_dict = {} + for case in cases: + self._check_if_attr_exists(case, kpi) + figure_dict[case.name] = getattr(case, kpi) + + self.report = pd.Series(figure_dict, name=label) + + if comparison == "relative": + self._comp_relative(baseline) + elif comparison == "percentage": + self._comp_percentage(baseline) + + def show(self, nformat=None): + if nformat is not None: + return self.report.apply(nformat.format) + else: + return self.report + + def _comp_relative(self, baseline): + baseline_report = self.report[baseline.name] + self.report = self.report.subtract(baseline_report, axis=0) + self.report.drop(index=baseline.name, inplace=True) + self.formatting = "number" diff --git a/pyrecoy/pyrecoy/pyrecoy/rop_assets.py b/pyrecoy/pyrecoy/pyrecoy/rop_assets.py new file mode 100644 index 0000000..41f604a --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/rop_assets.py @@ -0,0 +1,34 @@ +def get_power_profiles(start, end, country, in_local_time=True): + start = timestamp_to_utc(start) + end = timestamp_to_utc(end) + engine = db_engine("rop_test") + connection, table = create_connection(engine, "ImbalancePrices") + start = start.floor("15T") + query = ( + select([table]) + .where( + table.columns.QuarterStartTime >= start.strftime("%Y-%m-%d %H:%M"), + table.columns.QuarterStartTime < end.strftime("%Y-%m-%d %H:%M"), + table.columns.CountryIsoCode == country, + ) + .order_by(table.columns.QuarterStartTime) + ) + result = connection.execute(query).fetchall() + if len(result) == 0: + raise Exception("Day-ahead prices data not yet available.") + + data = pd.DataFrame(result, columns=result[0].keys()) + + if in_local_time: + data["QuarterStartTime"] = dt_column_to_local_time(data["QuarterStartTime"]) + + data.drop(columns=["Id", "CountryIsoCode"], inplace=True) + data.rename( + columns={ + "QuarterStartTime": "datetime", + "TakeFromGridPrice": "NEG", + "FeedToGridPrice": "POS", + }, + inplace=True, + ) + return data.set_index("datetime")[["POS", "NEG"]] \ No newline at end of file diff --git a/pyrecoy/pyrecoy/pyrecoy/rop_prices.py b/pyrecoy/pyrecoy/pyrecoy/rop_prices.py new file mode 100644 index 0000000..9ab908a --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/rop_prices.py @@ -0,0 +1,92 @@ +from sqlalchemy import select +import pandas as pd +from .converters import dt_column_to_local_time, timestamp_to_utc +from .databases import db_engine, create_connection + + +def get_imbalance_prices(start, end, country, in_local_time=True): + start = timestamp_to_utc(start) + end = timestamp_to_utc(end) + engine = db_engine("rop_prices_test") + connection, table = create_connection(engine, "ImbalancePrices") + start = start.floor("15T") + query = ( + select([table]) + .where( + table.columns.QuarterStartTime >= start.strftime("%Y-%m-%d %H:%M"), + table.columns.QuarterStartTime < end.strftime("%Y-%m-%d %H:%M"), + table.columns.CountryIsoCode == country, + ) + .order_by(table.columns.QuarterStartTime) + ) + result = connection.execute(query).fetchall() + if len(result) == 0: + raise Exception("Day-ahead prices data not yet available.") + + data = pd.DataFrame(result, columns=result[0].keys()) + + if in_local_time: + data["QuarterStartTime"] = dt_column_to_local_time(data["QuarterStartTime"]) + + data.drop(columns=["Id", "CountryIsoCode"], inplace=True) + data.rename( + columns={ + "QuarterStartTime": "datetime", + "TakeFromGridPrice": "NEG", + "FeedToGridPrice": "POS", + }, + inplace=True, + ) + return data.set_index("datetime")[["POS", "NEG"]] + + +def get_dayahead_prices(start, end, country, in_local_time=True): + start = timestamp_to_utc(start) + end = timestamp_to_utc(end) + engine = db_engine("rop_prices_test") + connection, table = create_connection(engine, "DayAheadPrices") + start = start.floor("60T") + query = ( + select([table]) + .where( + table.columns.HourStartTime >= start.strftime("%Y-%m-%d %H:%M"), + table.columns.HourStartTime < end.strftime("%Y-%m-%d %H:%M"), + table.columns.CountryIsoCode == country, + ) + .order_by(table.columns.HourStartTime) + ) + result = connection.execute(query).fetchall() + if len(result) == 0: + raise Exception("Day-ahead prices data not yet available.") + + data = pd.DataFrame(result, columns=result[0].keys()) + + if in_local_time: + data["HourStartTime"] = dt_column_to_local_time(data["HourStartTime"]) + + data.drop(columns=["Id", "CountryIsoCode"], inplace=True) + data.rename(columns={"HourStartTime": "datetime", "Price": "DAM"}, inplace=True) + return data.set_index("datetime") + + +def get_market_price_data(start, end, country, in_local_time=True): + tz = "Europe/Amsterdam" if in_local_time else "UTC" + dt_ix = pd.date_range( + start=start.floor("H"), + end=end.ceil("H"), + freq="15T", + tz=tz, + inclusive="left", + ) + prices = pd.DataFrame(index=dt_ix, columns=["DAM", "POS", "NEG"]) + prices["DAM"] = get_dayahead_prices( + start, end, country=country, in_local_time=in_local_time + ).reindex(dt_ix, method="ffill") + prices["DAM"].fillna(method="ffill", inplace=True) + + imbprices = get_imbalance_prices( + start, end, country=country, in_local_time=in_local_time + ) + prices["POS"] = imbprices["POS"] + prices["NEG"] = imbprices["NEG"] + return prices diff --git a/pyrecoy/pyrecoy/pyrecoy/sensitivity.py b/pyrecoy/pyrecoy/pyrecoy/sensitivity.py new file mode 100644 index 0000000..3c5628f --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/sensitivity.py @@ -0,0 +1,225 @@ +import itertools +import gc +from copy import deepcopy +from tkinter import Label +import warnings + +import pandas as pd +import numpy as np +from tqdm.notebook import tqdm +from millify import millify +from plotly.graph_objs import Figure + +from .casestudy import CaseStudy +from .colors import recoygreen, recoyred + + +class SensitivityAnalysis: + """ + Runs an simulation routine with different input configurations, + so that sensitivity of variables can be analysed. + """ + + def __init__(self, c, s, routine, param, values, output_kpis): + self.configs = self._generate_configs(c, param, values) + output_dict = self._prepare_output_dict(s.cases, output_kpis) + self.kpis = self._run_sensitivities(s, routine, output_kpis, output_dict) + + def _generate_configs(self, c, param, values): + configs = {} + for value in values: + _c = deepcopy(c) + setattr(_c, param, value) + configs[value] = _c + return configs + + def _prepare_output_dict(self, cases, output_kpis): + output_dict = dict.fromkeys(self.configs.keys()) + for value in self.configs: + output_dict[value] = dict.fromkeys(output_kpis) + for kpi in output_kpis: + output_dict[value][kpi] = dict.fromkeys([case.name for case in cases]) + return output_dict + + def _run_sensitivities(self, s, routine, output_kpis, output_dict): + for name, c in tqdm(self.configs.items()): + _s = deepcopy(s) + _s = routine(c, _s) + for kpi in output_kpis: + for case in _s.cases: + output_dict[name][kpi][case.name] = getattr(case, kpi, np.nan) + del _s + gc.collect() + return output_dict + + def single_kpi_overview(self, kpi, case_names=None): + """Creates a DataFrame with chosen output kpi, + for each CaseStudy in each Configuration. + """ + if not case_names: + case_names = CaseStudy.instances.keys() + + kpi_values = { + name: {case: self.kpis[name][kpi][case] for case in case_names} + for name in self.kpis.keys() + } + + return pd.DataFrame(kpi_values).T + + def cashflows_comparison(self, case=None, baseline=None): + ebitda_calc_overview = {} + baseline_calc = {} + for input_value, kpi_data in self.kpis.items(): + for kpi, case_data in kpi_data.items(): + for case_name, data in case_data.items(): + if kpi == "cashflows": + if case_name == case: + ebitda_calc_overview[input_value] = data + if case_name == baseline: + baseline_calc[input_value] = data + + ebitda_calc_overview = pd.DataFrame(ebitda_calc_overview) + if not baseline: + return ebitda_calc_overview + + baseline_calc = pd.DataFrame(baseline_calc) + return ebitda_calc_overview.subtract(baseline_calc, fill_value=0) + + +class SensitivityMatrix: + def __init__(self, c, s, routine, x_param, y_param, x_vals, y_vals, output_kpis): + self.x_param = x_param + self.y_param = y_param + + self.configs = self._generate_configs(c, x_vals, y_vals) + output_dict = self._prepare_output_dict(s.cases, output_kpis) + self.kpis = self._run_sensitivities(s, routine, output_kpis, output_dict) + + def _generate_configs(self, c, x_vals, y_vals): + configs = {x_val: dict.fromkeys(y_vals) for x_val in x_vals} + + self.xy_combinations = list(itertools.product(x_vals, y_vals)) + for x_val, y_val in self.xy_combinations: + _c = deepcopy(c) + setattr(_c, self.x_param, x_val) + setattr(_c, self.y_param, y_val) + configs[x_val][y_val] = _c + return configs + + def _prepare_output_dict(self, cases, output_kpis): + output_dict = {} + for name in [case.name for case in cases]: + output_dict[name] = dict.fromkeys(output_kpis) + for kpi in output_kpis: + output_dict[name][kpi] = deepcopy(self.configs) + return output_dict + + def _run_sensitivities(self, s, routine, output_kpis, output_dict): + for x_val, y_val in tqdm(self.xy_combinations): + _c = self.configs[x_val][y_val] + _s = deepcopy(s) + _s = routine(_c, _s) + for kpi in output_kpis: + for case in _s.cases: + output = getattr(case, kpi, np.nan) + output_dict[case.name][kpi][x_val][y_val] = output + del _s + del _c + gc.collect() + return output_dict + + def show_matrix(self, case_name, kpi): + """ + Creates a DataFrame with chosen output kpi, + for each XY combination + """ + matrix = pd.DataFrame(self.kpis[case_name][kpi]) + matrix.columns.name = self.x_param + matrix.index.name = self.y_param + return matrix + + +class ScenarioAnalysis(SensitivityAnalysis): + def __init__(self, c, s, routine, params_dict, labels, output_kpis): + self.labels = labels + self.configs = self._generate_configs(c, params_dict, labels) + output_dict = self._prepare_output_dict(s.cases, output_kpis) + self.kpis = self._run_sensitivities(s, routine, output_kpis, output_dict) + + def _generate_configs(self, c, params_dict, labels): + configs = {} + for i, label in enumerate(labels): + _c = deepcopy(c) + for param, values in params_dict.items(): + setattr(_c, param, values[i]) + configs[label] = _c + return configs + + +class TornadoChart: + """ + TODO: Absolute comparison instead of relative + """ + + def __init__(self, c, s, routine, case, tornado_vars, output_kpis): + self.case = case + self.kpis = self._run_sensitivities( + c, s, routine, case, tornado_vars, output_kpis + ) + + def _run_sensitivities(self, c, s, routine, case, tornado_vars, output_kpis): + labels = ["Low", "Medium", "High"] + outputs = {kpi: pd.DataFrame(index=labels) for kpi in output_kpis} + + for param, values in tornado_vars.items(): + sens = SensitivityAnalysis(c, s, routine, param, values, output_kpis) + + for kpi in output_kpis: + output = sens.single_kpi_overview(kpi, case_names=[case.name])[ + case.name + ] + output.index = labels + outputs[kpi][" ".join((param, str(values)))] = output + + for kpi in output_kpis: + base_performance = deepcopy(outputs[kpi].loc["Medium", :]) + for scen in labels: + scen_performance = outputs[kpi].loc[scen, :] + relative_performance = (scen_performance / base_performance - 1) * 100 + outputs[kpi].loc[scen, :] = relative_performance + + outputs[kpi] = outputs[kpi].round(1) + outputs[kpi].sort_values(by="Low", axis=1, ascending=False, inplace=True) + return outputs + + def show_chart( + self, kpi, dimensions=(800, 680), title="Tornado Chart", sort_by="Low" + ): + outputs = self.kpis[kpi].sort_values(by=sort_by, axis=1, ascending=False) + traces = [] + colors = {"Low": recoyred, "High": recoygreen} + + for scenario in ["Low", "High"]: + trace = { + "type": "bar", + "x": outputs.loc[scenario, :].tolist(), + "y": outputs.columns, + "orientation": "h", + "name": scenario, + "marker": {"color": colors[scenario]}, + } + traces.append(trace) + + layout = { + "title": title, + "width": dimensions[0], + "height": dimensions[1], + "barmode": "relative", + "autosize": True, + "showlegend": True, + } + fig = Figure(data=traces, layout=layout) + fig.update_xaxes( + title_text=f"{kpi.upper()} % change compared to base scenario (Base {kpi.upper()} = {millify(getattr(self.case, kpi))})" + ) + return fig diff --git a/pyrecoy/pyrecoy/pyrecoy/styling.py b/pyrecoy/pyrecoy/pyrecoy/styling.py new file mode 100644 index 0000000..bc224cf --- /dev/null +++ b/pyrecoy/pyrecoy/pyrecoy/styling.py @@ -0,0 +1,47 @@ +from copy import deepcopy +from numbers import Number +import numpy as np + + +def num_formatting(val): + if np.isnan(val) or round(val, 0) == 0: + return "-" + else: + return f"{val:,.0f}" + + +def perc_formatting(val): + if np.isnan(val) or round(val, 0) == 0: + return "-" + else: + return f"{val:.1f}%" + + +def bc_formatting(val): + if not isinstance(val, Number): + return val + if np.isnan(val): + return "" + elif round(val, 2) == 0: + return "-" + else: + return f"{val:,.0f}" + + +def businesscase_formatter(df): + df_c = deepcopy(df) + + spp = df_c.loc["Simple Payback Period", "Year 0"] + spp_str = "N/A" if np.isnan(spp) else str(spp) + " years" + df_c.loc["Simple Payback Period", "Year 0"] = spp_str + + irr = df_c.loc["IRR (%)", "Year 0"] + if np.isnan(irr): + df_c.loc["IRR (%)", "Year 0"] = "N/A" + + df_c = df_c.applymap(bc_formatting) + + if not np.isnan(irr): + df_c.loc["IRR (%)", "Year 0"] += "%" + df_c.loc["WACC (%)", "Year 0"] += "%" + return df_c diff --git a/pyrecoy/pyrecoy/setup.py b/pyrecoy/pyrecoy/setup.py new file mode 100644 index 0000000..72b3202 --- /dev/null +++ b/pyrecoy/pyrecoy/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup + +# to install run : pip install -e pyrecoy from directory + +setup( + name="pyrecoy", + version="0.1", + description="Private package containing utils for flexible power system modelling on energy markets.", + url="#", + author="mekremer", + author_email="kremer@recoy.com", + license="", + packages=["pyrecoy"], + install_requires=[ + "requests", + "pandas", + "numpy", + "entsoe-py", + "numpy-financial", + "scipy", + "plotly", + "tqdm", + "millify", + "bs4", + "xmltodict", + "openpyxl", + ], + zip_safe=False, +) diff --git a/pyrecoy/pyrecoy/templates/pyrecoy_template.ipynb b/pyrecoy/pyrecoy/templates/pyrecoy_template.ipynb new file mode 100644 index 0000000..7a43e3e --- /dev/null +++ b/pyrecoy/pyrecoy/templates/pyrecoy_template.ipynb @@ -0,0 +1,16965 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Demo of pyrecoy package\n", + "Recoy Modelling Package for Python" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import cufflinks\n", + "from tqdm.notebook import tqdm\n", + "import numpy as np\n", + "\n", + "from pyrecoy.assets import Heatpump, Eboiler, GasBoiler, Battery\n", + "from pyrecoy.colors import *\n", + "from pyrecoy.converters import *\n", + "from pyrecoy.financial import calculate_eb_ode, get_tax_tables\n", + "from pyrecoy.framework import TimeFramework, CaseStudy\n", + "from pyrecoy.plotting import ebitda_bar_chart, npv_bar_chart\n", + "from pyrecoy.reports import CaseReport, ComparisonReport, BusinessCaseReport, SingleFigureComparison\n", + "\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model set-up" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Timeframework" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "time_fw = TimeFramework(start='2019-01-01', end='2019-12-31')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "datetime.datetime(2019, 1, 1, 0, 0)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "time_fw.start" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "datetime.datetime(2019, 12, 31, 0, 0)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "time_fw.end" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "365.0" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "time_fw.days" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Casestudies" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "baseline = CaseStudy(time_fw=time_fw, freq='1T', name='Baseline')\n", + "hpcase = CaseStudy(time_fw=time_fw, freq='1T', name='Optimisation', forecast='mipf')\n", + "\n", + "cases = CaseStudy.get_instances().values()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
datetime
2019-01-01 00:00:00+01:00
2019-01-01 00:01:00+01:00
2019-01-01 00:02:00+01:00
2019-01-01 00:03:00+01:00
2019-01-01 00:04:00+01:00
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: []\n", + "Index: [2019-01-01 00:00:00+01:00, 2019-01-01 00:01:00+01:00, 2019-01-01 00:02:00+01:00, 2019-01-01 00:03:00+01:00, 2019-01-01 00:04:00+01:00]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "baseline.data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DAMPOSNEGRSForePosForeNeg
datetime
2019-01-01 00:00:00+01:0068.9234.8558.012.046.8352.70
2019-01-01 00:01:00+01:0068.9234.8558.012.032.1254.45
2019-01-01 00:02:00+01:0068.9234.8558.012.030.9748.64
2019-01-01 00:03:00+01:0068.9234.8558.012.051.1348.35
2019-01-01 00:04:00+01:0068.9234.8558.012.048.0452.01
\n", + "
" + ], + "text/plain": [ + " DAM POS NEG RS ForePos ForeNeg\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 68.92 34.85 58.01 2.0 46.83 52.70\n", + "2019-01-01 00:01:00+01:00 68.92 34.85 58.01 2.0 32.12 54.45\n", + "2019-01-01 00:02:00+01:00 68.92 34.85 58.01 2.0 30.97 48.64\n", + "2019-01-01 00:03:00+01:00 68.92 34.85 58.01 2.0 51.13 48.35\n", + "2019-01-01 00:04:00+01:00 68.92 34.85 58.01 2.0 48.04 52.01" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hpcase.data.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Gas & CO2 prices" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "baseline.add_gasprices()\n", + "baseline.add_co2prices()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Gas prices (€/MWh)CO2 prices (€/ton)
datetime
2019-01-01 00:00:00+01:0022.3824.98
2019-01-01 00:01:00+01:0022.3824.98
2019-01-01 00:02:00+01:0022.3824.98
2019-01-01 00:03:00+01:0022.3824.98
2019-01-01 00:04:00+01:0022.3824.98
\n", + "
" + ], + "text/plain": [ + " Gas prices (€/MWh) CO2 prices (€/ton)\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 22.38 24.98\n", + "2019-01-01 00:01:00+01:00 22.38 24.98\n", + "2019-01-01 00:02:00+01:00 22.38 24.98\n", + "2019-01-01 00:03:00+01:00 22.38 24.98\n", + "2019-01-01 00:04:00+01:00 22.38 24.98" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "baseline.data.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Heat demand" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "for case in cases:\n", + " case.data['Heat demand'] = 5" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Gas prices (€/MWh)CO2 prices (€/ton)Heat demand
datetime
2019-01-01 00:00:00+01:0022.3824.985
2019-01-01 00:01:00+01:0022.3824.985
2019-01-01 00:02:00+01:0022.3824.985
2019-01-01 00:03:00+01:0022.3824.985
2019-01-01 00:04:00+01:0022.3824.985
\n", + "
" + ], + "text/plain": [ + " Gas prices (€/MWh) CO2 prices (€/ton) Heat demand\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 22.38 24.98 5\n", + "2019-01-01 00:01:00+01:00 22.38 24.98 5\n", + "2019-01-01 00:02:00+01:00 22.38 24.98 5\n", + "2019-01-01 00:03:00+01:00 22.38 24.98 5\n", + "2019-01-01 00:04:00+01:00 22.38 24.98 5" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "baseline.data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DAMPOSNEGRSForePosForeNegHeat demand
datetime
2019-01-01 00:00:00+01:0068.9234.8558.012.046.8352.705
2019-01-01 00:01:00+01:0068.9234.8558.012.032.1254.455
2019-01-01 00:02:00+01:0068.9234.8558.012.030.9748.645
2019-01-01 00:03:00+01:0068.9234.8558.012.051.1348.355
2019-01-01 00:04:00+01:0068.9234.8558.012.048.0452.015
\n", + "
" + ], + "text/plain": [ + " DAM POS NEG RS ForePos ForeNeg \\\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 68.92 34.85 58.01 2.0 46.83 52.70 \n", + "2019-01-01 00:01:00+01:00 68.92 34.85 58.01 2.0 32.12 54.45 \n", + "2019-01-01 00:02:00+01:00 68.92 34.85 58.01 2.0 30.97 48.64 \n", + "2019-01-01 00:03:00+01:00 68.92 34.85 58.01 2.0 51.13 48.35 \n", + "2019-01-01 00:04:00+01:00 68.92 34.85 58.01 2.0 48.04 52.01 \n", + "\n", + " Heat demand \n", + "datetime \n", + "2019-01-01 00:00:00+01:00 5 \n", + "2019-01-01 00:01:00+01:00 5 \n", + "2019-01-01 00:02:00+01:00 5 \n", + "2019-01-01 00:03:00+01:00 5 \n", + "2019-01-01 00:04:00+01:00 5 " + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hpcase.data.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Assets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Heatpump" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "heatpump = Heatpump(\n", + " name='Heatpump',\n", + " max_th_power=5,\n", + " min_th_power=0,\n", + " cop_curve=3.5\n", + ")\n", + "\n", + "heatpump.set_financials(capex=1_000_000, opex=10_000, devex=20_000, lifetime=25)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Heatpump(name='Heatpump', max_thermal_power=5, cop_curve=3.5, min_th_power=0)" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "heatpump" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Eboiler" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "eboiler = Eboiler(name='Eboiler', min_power=-6, max_power=0)\n", + "eboiler.set_financials(capex=200_000, opex=2_000, devex=10_000, lifetime=25)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Gasboiler" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "gasboiler = GasBoiler(\n", + " name='Gasboiler',\n", + " max_th_output=5,\n", + " efficiency=0.9\n", + ")\n", + "\n", + "gasboiler.set_financials(capex=0, opex=20_000, devex=0, lifetime=25)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Battery" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "battery = Battery(\n", + " name='Battery', \n", + " rated_power=2, \n", + " rated_capacity=2, \n", + " roundtrip_eff=0.9, \n", + " min_soc=0.1,\n", + " max_soc=0.9, \n", + " soc_at_start=0.5\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.5" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.get_soc()" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "battery.set_freq('15T')" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\users\\mekre\\onedrive - recoy\\work\\code\\python\\_packages\\pyrecoy\\pyrecoy\\assets.py:57: UserWarning:\n", + "\n", + "Chosen Asset load for Battery is out of range. Should be between -2 and 2. Function will return boundary load level for now.\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "-2.0" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.charge(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.725" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.get_soc()" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.45" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.chargelevel" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-1.5555555555555558" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.charge(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.9" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.get_soc()" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.charge(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.0" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.discharge(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.65" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.get_soc()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.0" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.discharge(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.0" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.discharge(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.40000000000000013" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.discharge(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.1" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "battery.get_soc()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Assign assets to casestudies" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "#add type annotation\n", + "baseline.add_asset(gasboiler)\n", + "hpcase.add_asset(heatpump)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Optimization" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "# make getting asset from casestudy more readable\n", + "def simulation(case):\n", + " asset = case.get_assets()[0]\n", + " demand = case.data['Heat demand'].to_list()\n", + " minutes = range(len(case.data))\n", + " output_MW = [0] * len(case.data)\n", + " input_MW = [0] * len(case.data)\n", + " \n", + " for m in minutes:\n", + " output_MW[m], input_MW[m] = asset.set_heat_output(demand[m])\n", + " case.data['output_MW'] = np.array(output_MW)\n", + " case.data['input_MW'] = np.array(input_MW)\n", + " \n", + " for col in case.data.columns:\n", + " if col.endswith('MW'):\n", + " case.data[col + 'h'] = case.data[col] / 60" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "baseline.assign_algorithm(simulation)\n", + "hpcase.assign_algorithm(simulation)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wall time: 682 ms\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Gas prices (€/MWh)CO2 prices (€/ton)Heat demandoutput_MWinput_MWoutput_MWhinput_MWh
datetime
2019-01-01 00:00:00+01:0022.3824.9855-5.5555560.083333-0.092593
2019-01-01 00:01:00+01:0022.3824.9855-5.5555560.083333-0.092593
2019-01-01 00:02:00+01:0022.3824.9855-5.5555560.083333-0.092593
2019-01-01 00:03:00+01:0022.3824.9855-5.5555560.083333-0.092593
2019-01-01 00:04:00+01:0022.3824.9855-5.5555560.083333-0.092593
\n", + "
" + ], + "text/plain": [ + " Gas prices (€/MWh) CO2 prices (€/ton) \\\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 22.38 24.98 \n", + "2019-01-01 00:01:00+01:00 22.38 24.98 \n", + "2019-01-01 00:02:00+01:00 22.38 24.98 \n", + "2019-01-01 00:03:00+01:00 22.38 24.98 \n", + "2019-01-01 00:04:00+01:00 22.38 24.98 \n", + "\n", + " Heat demand output_MW input_MW output_MWh \\\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 5 5 -5.555556 0.083333 \n", + "2019-01-01 00:01:00+01:00 5 5 -5.555556 0.083333 \n", + "2019-01-01 00:02:00+01:00 5 5 -5.555556 0.083333 \n", + "2019-01-01 00:03:00+01:00 5 5 -5.555556 0.083333 \n", + "2019-01-01 00:04:00+01:00 5 5 -5.555556 0.083333 \n", + "\n", + " input_MWh \n", + "datetime \n", + "2019-01-01 00:00:00+01:00 -0.092593 \n", + "2019-01-01 00:01:00+01:00 -0.092593 \n", + "2019-01-01 00:02:00+01:00 -0.092593 \n", + "2019-01-01 00:03:00+01:00 -0.092593 \n", + "2019-01-01 00:04:00+01:00 -0.092593 " + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%time baseline.run()\n", + "baseline.data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "linkText": "Export to plot.ly", + "plotlyServerURL": "https://plot.ly", + "showLink": true + }, + "data": [ + { + "line": { + "color": "rgba(255, 153, 51, 1.0)", + "dash": "solid", + "shape": "linear", + "width": 1.3 + }, + "mode": "lines", + "name": "input_MW", + "text": "", + "type": "scatter", + "x": [ + "2019-01-01 00:00:00+01:00", + "2019-01-01 00:01:00+01:00", + "2019-01-01 00:02:00+01:00", + "2019-01-01 00:03:00+01:00", + "2019-01-01 00:04:00+01:00", + "2019-01-01 00:05:00+01:00", + "2019-01-01 00:06:00+01:00", + "2019-01-01 00:07:00+01:00", + "2019-01-01 00:08:00+01:00", + "2019-01-01 00:09:00+01:00", + "2019-01-01 00:10:00+01:00", + "2019-01-01 00:11:00+01:00", + "2019-01-01 00:12:00+01:00", + "2019-01-01 00:13:00+01:00", + "2019-01-01 00:14:00+01:00", + "2019-01-01 00:15:00+01:00", + "2019-01-01 00:16:00+01:00", + "2019-01-01 00:17:00+01:00", + "2019-01-01 00:18:00+01:00", + "2019-01-01 00:19:00+01:00", + "2019-01-01 00:20:00+01:00", + "2019-01-01 00:21:00+01:00", + "2019-01-01 00:22:00+01:00", + "2019-01-01 00:23:00+01:00", + "2019-01-01 00:24:00+01:00", + "2019-01-01 00:25:00+01:00", + "2019-01-01 00:26:00+01:00", + "2019-01-01 00:27:00+01:00", + "2019-01-01 00:28:00+01:00", + "2019-01-01 00:29:00+01:00", + "2019-01-01 00:30:00+01:00", + "2019-01-01 00:31:00+01:00", + "2019-01-01 00:32:00+01:00", + "2019-01-01 00:33:00+01:00", + "2019-01-01 00:34:00+01:00", + "2019-01-01 00:35:00+01:00", + "2019-01-01 00:36:00+01:00", + "2019-01-01 00:37:00+01:00", + "2019-01-01 00:38:00+01:00", + "2019-01-01 00:39:00+01:00", + "2019-01-01 00:40:00+01:00", + "2019-01-01 00:41:00+01:00", + "2019-01-01 00:42:00+01:00", + "2019-01-01 00:43:00+01:00", + "2019-01-01 00:44:00+01:00", + "2019-01-01 00:45:00+01:00", + "2019-01-01 00:46:00+01:00", + "2019-01-01 00:47:00+01:00", + "2019-01-01 00:48:00+01:00", + "2019-01-01 00:49:00+01:00", + "2019-01-01 00:50:00+01:00", + "2019-01-01 00:51:00+01:00", + "2019-01-01 00:52:00+01:00", + "2019-01-01 00:53:00+01:00", + "2019-01-01 00:54:00+01:00", + "2019-01-01 00:55:00+01:00", + "2019-01-01 00:56:00+01:00", + "2019-01-01 00:57:00+01:00", + "2019-01-01 00:58:00+01:00", + "2019-01-01 00:59:00+01:00", + "2019-01-01 01:00:00+01:00", + "2019-01-01 01:01:00+01:00", + "2019-01-01 01:02:00+01:00", + "2019-01-01 01:03:00+01:00", + "2019-01-01 01:04:00+01:00", + "2019-01-01 01:05:00+01:00", + "2019-01-01 01:06:00+01:00", + "2019-01-01 01:07:00+01:00", + "2019-01-01 01:08:00+01:00", + "2019-01-01 01:09:00+01:00", + "2019-01-01 01:10:00+01:00", + "2019-01-01 01:11:00+01:00", + "2019-01-01 01:12:00+01:00", + "2019-01-01 01:13:00+01:00", + "2019-01-01 01:14:00+01:00", + "2019-01-01 01:15:00+01:00", + "2019-01-01 01:16:00+01:00", + "2019-01-01 01:17:00+01:00", + "2019-01-01 01:18:00+01:00", + "2019-01-01 01:19:00+01:00", + "2019-01-01 01:20:00+01:00", + "2019-01-01 01:21:00+01:00", + "2019-01-01 01:22:00+01:00", + "2019-01-01 01:23:00+01:00", + "2019-01-01 01:24:00+01:00", + "2019-01-01 01:25:00+01:00", + "2019-01-01 01:26:00+01:00", + "2019-01-01 01:27:00+01:00", + "2019-01-01 01:28:00+01:00", + "2019-01-01 01:29:00+01:00", + "2019-01-01 01:30:00+01:00", + "2019-01-01 01:31:00+01:00", + "2019-01-01 01:32:00+01:00", + "2019-01-01 01:33:00+01:00", + "2019-01-01 01:34:00+01:00", + "2019-01-01 01:35:00+01:00", + "2019-01-01 01:36:00+01:00", + "2019-01-01 01:37:00+01:00", + "2019-01-01 01:38:00+01:00", + "2019-01-01 01:39:00+01:00", + "2019-01-01 01:40:00+01:00", + "2019-01-01 01:41:00+01:00", + "2019-01-01 01:42:00+01:00", + "2019-01-01 01:43:00+01:00", + "2019-01-01 01:44:00+01:00", + "2019-01-01 01:45:00+01:00", + "2019-01-01 01:46:00+01:00", + "2019-01-01 01:47:00+01:00", + "2019-01-01 01:48:00+01:00", + "2019-01-01 01:49:00+01:00", + "2019-01-01 01:50:00+01:00", + "2019-01-01 01:51:00+01:00", + "2019-01-01 01:52:00+01:00", + "2019-01-01 01:53:00+01:00", + "2019-01-01 01:54:00+01:00", + "2019-01-01 01:55:00+01:00", + "2019-01-01 01:56:00+01:00", + "2019-01-01 01:57:00+01:00", + "2019-01-01 01:58:00+01:00", + "2019-01-01 01:59:00+01:00", + "2019-01-01 02:00:00+01:00", + "2019-01-01 02:01:00+01:00", + "2019-01-01 02:02:00+01:00", + "2019-01-01 02:03:00+01:00", + "2019-01-01 02:04:00+01:00", + "2019-01-01 02:05:00+01:00", + "2019-01-01 02:06:00+01:00", + "2019-01-01 02:07:00+01:00", + "2019-01-01 02:08:00+01:00", + "2019-01-01 02:09:00+01:00", + "2019-01-01 02:10:00+01:00", + "2019-01-01 02:11:00+01:00", + "2019-01-01 02:12:00+01:00", + "2019-01-01 02:13:00+01:00", + "2019-01-01 02:14:00+01:00", + "2019-01-01 02:15:00+01:00", + "2019-01-01 02:16:00+01:00", + "2019-01-01 02:17:00+01:00", + "2019-01-01 02:18:00+01:00", + "2019-01-01 02:19:00+01:00", + "2019-01-01 02:20:00+01:00", + "2019-01-01 02:21:00+01:00", + "2019-01-01 02:22:00+01:00", + "2019-01-01 02:23:00+01:00", + "2019-01-01 02:24:00+01:00", + "2019-01-01 02:25:00+01:00", + "2019-01-01 02:26:00+01:00", + "2019-01-01 02:27:00+01:00", + "2019-01-01 02:28:00+01:00", + "2019-01-01 02:29:00+01:00", + "2019-01-01 02:30:00+01:00", + "2019-01-01 02:31:00+01:00", + "2019-01-01 02:32:00+01:00", + "2019-01-01 02:33:00+01:00", + "2019-01-01 02:34:00+01:00", + "2019-01-01 02:35:00+01:00", + "2019-01-01 02:36:00+01:00", + "2019-01-01 02:37:00+01:00", + "2019-01-01 02:38:00+01:00", + "2019-01-01 02:39:00+01:00", + "2019-01-01 02:40:00+01:00", + "2019-01-01 02:41:00+01:00", + "2019-01-01 02:42:00+01:00", + "2019-01-01 02:43:00+01:00", + "2019-01-01 02:44:00+01:00", + "2019-01-01 02:45:00+01:00", + "2019-01-01 02:46:00+01:00", + "2019-01-01 02:47:00+01:00", + "2019-01-01 02:48:00+01:00", + "2019-01-01 02:49:00+01:00", + "2019-01-01 02:50:00+01:00", + "2019-01-01 02:51:00+01:00", + "2019-01-01 02:52:00+01:00", + "2019-01-01 02:53:00+01:00", + "2019-01-01 02:54:00+01:00", + "2019-01-01 02:55:00+01:00", + "2019-01-01 02:56:00+01:00", + "2019-01-01 02:57:00+01:00", + "2019-01-01 02:58:00+01:00", + "2019-01-01 02:59:00+01:00", + "2019-01-01 03:00:00+01:00", + "2019-01-01 03:01:00+01:00", + "2019-01-01 03:02:00+01:00", + "2019-01-01 03:03:00+01:00", + "2019-01-01 03:04:00+01:00", + "2019-01-01 03:05:00+01:00", + "2019-01-01 03:06:00+01:00", + "2019-01-01 03:07:00+01:00", + "2019-01-01 03:08:00+01:00", + "2019-01-01 03:09:00+01:00", + "2019-01-01 03:10:00+01:00", + "2019-01-01 03:11:00+01:00", + "2019-01-01 03:12:00+01:00", + "2019-01-01 03:13:00+01:00", + "2019-01-01 03:14:00+01:00", + "2019-01-01 03:15:00+01:00", + "2019-01-01 03:16:00+01:00", + "2019-01-01 03:17:00+01:00", + "2019-01-01 03:18:00+01:00", + "2019-01-01 03:19:00+01:00", + "2019-01-01 03:20:00+01:00", + "2019-01-01 03:21:00+01:00", + "2019-01-01 03:22:00+01:00", + "2019-01-01 03:23:00+01:00", + "2019-01-01 03:24:00+01:00", + "2019-01-01 03:25:00+01:00", + "2019-01-01 03:26:00+01:00", + "2019-01-01 03:27:00+01:00", + "2019-01-01 03:28:00+01:00", + "2019-01-01 03:29:00+01:00", + "2019-01-01 03:30:00+01:00", + "2019-01-01 03:31:00+01:00", + "2019-01-01 03:32:00+01:00", + "2019-01-01 03:33:00+01:00", + "2019-01-01 03:34:00+01:00", + "2019-01-01 03:35:00+01:00", + "2019-01-01 03:36:00+01:00", + "2019-01-01 03:37:00+01:00", + "2019-01-01 03:38:00+01:00", + "2019-01-01 03:39:00+01:00", + "2019-01-01 03:40:00+01:00", + "2019-01-01 03:41:00+01:00", + "2019-01-01 03:42:00+01:00", + "2019-01-01 03:43:00+01:00", + "2019-01-01 03:44:00+01:00", + "2019-01-01 03:45:00+01:00", + "2019-01-01 03:46:00+01:00", + "2019-01-01 03:47:00+01:00", + "2019-01-01 03:48:00+01:00", + "2019-01-01 03:49:00+01:00", + "2019-01-01 03:50:00+01:00", + "2019-01-01 03:51:00+01:00", + "2019-01-01 03:52:00+01:00", + "2019-01-01 03:53:00+01:00", + "2019-01-01 03:54:00+01:00", + "2019-01-01 03:55:00+01:00", + "2019-01-01 03:56:00+01:00", + "2019-01-01 03:57:00+01:00", + "2019-01-01 03:58:00+01:00", + "2019-01-01 03:59:00+01:00", + "2019-01-01 04:00:00+01:00", + "2019-01-01 04:01:00+01:00", + "2019-01-01 04:02:00+01:00", + "2019-01-01 04:03:00+01:00", + "2019-01-01 04:04:00+01:00", + "2019-01-01 04:05:00+01:00", + "2019-01-01 04:06:00+01:00", + "2019-01-01 04:07:00+01:00", + "2019-01-01 04:08:00+01:00", + "2019-01-01 04:09:00+01:00", + "2019-01-01 04:10:00+01:00", + "2019-01-01 04:11:00+01:00", + "2019-01-01 04:12:00+01:00", + "2019-01-01 04:13:00+01:00", + "2019-01-01 04:14:00+01:00", + "2019-01-01 04:15:00+01:00", + "2019-01-01 04:16:00+01:00", + "2019-01-01 04:17:00+01:00", + "2019-01-01 04:18:00+01:00", + "2019-01-01 04:19:00+01:00", + "2019-01-01 04:20:00+01:00", + "2019-01-01 04:21:00+01:00", + "2019-01-01 04:22:00+01:00", + "2019-01-01 04:23:00+01:00", + "2019-01-01 04:24:00+01:00", + "2019-01-01 04:25:00+01:00", + "2019-01-01 04:26:00+01:00", + "2019-01-01 04:27:00+01:00", + "2019-01-01 04:28:00+01:00", + "2019-01-01 04:29:00+01:00", + "2019-01-01 04:30:00+01:00", + "2019-01-01 04:31:00+01:00", + "2019-01-01 04:32:00+01:00", + "2019-01-01 04:33:00+01:00", + "2019-01-01 04:34:00+01:00", + "2019-01-01 04:35:00+01:00", + "2019-01-01 04:36:00+01:00", + "2019-01-01 04:37:00+01:00", + "2019-01-01 04:38:00+01:00", + "2019-01-01 04:39:00+01:00", + "2019-01-01 04:40:00+01:00", + "2019-01-01 04:41:00+01:00", + "2019-01-01 04:42:00+01:00", + "2019-01-01 04:43:00+01:00", + "2019-01-01 04:44:00+01:00", + "2019-01-01 04:45:00+01:00", + "2019-01-01 04:46:00+01:00", + "2019-01-01 04:47:00+01:00", + "2019-01-01 04:48:00+01:00", + "2019-01-01 04:49:00+01:00", + "2019-01-01 04:50:00+01:00", + "2019-01-01 04:51:00+01:00", + "2019-01-01 04:52:00+01:00", + "2019-01-01 04:53:00+01:00", + "2019-01-01 04:54:00+01:00", + "2019-01-01 04:55:00+01:00", + "2019-01-01 04:56:00+01:00", + "2019-01-01 04:57:00+01:00", + "2019-01-01 04:58:00+01:00", + "2019-01-01 04:59:00+01:00", + "2019-01-01 05:00:00+01:00", + "2019-01-01 05:01:00+01:00", + "2019-01-01 05:02:00+01:00", + "2019-01-01 05:03:00+01:00", + "2019-01-01 05:04:00+01:00", + "2019-01-01 05:05:00+01:00", + "2019-01-01 05:06:00+01:00", + "2019-01-01 05:07:00+01:00", + "2019-01-01 05:08:00+01:00", + "2019-01-01 05:09:00+01:00", + "2019-01-01 05:10:00+01:00", + "2019-01-01 05:11:00+01:00", + "2019-01-01 05:12:00+01:00", + "2019-01-01 05:13:00+01:00", + "2019-01-01 05:14:00+01:00", + "2019-01-01 05:15:00+01:00", + "2019-01-01 05:16:00+01:00", + "2019-01-01 05:17:00+01:00", + "2019-01-01 05:18:00+01:00", + "2019-01-01 05:19:00+01:00", + "2019-01-01 05:20:00+01:00", + "2019-01-01 05:21:00+01:00", + "2019-01-01 05:22:00+01:00", + "2019-01-01 05:23:00+01:00", + "2019-01-01 05:24:00+01:00", + "2019-01-01 05:25:00+01:00", + "2019-01-01 05:26:00+01:00", + "2019-01-01 05:27:00+01:00", + "2019-01-01 05:28:00+01:00", + "2019-01-01 05:29:00+01:00", + "2019-01-01 05:30:00+01:00", + "2019-01-01 05:31:00+01:00", + "2019-01-01 05:32:00+01:00", + "2019-01-01 05:33:00+01:00", + "2019-01-01 05:34:00+01:00", + "2019-01-01 05:35:00+01:00", + "2019-01-01 05:36:00+01:00", + "2019-01-01 05:37:00+01:00", + "2019-01-01 05:38:00+01:00", + "2019-01-01 05:39:00+01:00", + "2019-01-01 05:40:00+01:00", + "2019-01-01 05:41:00+01:00", + "2019-01-01 05:42:00+01:00", + "2019-01-01 05:43:00+01:00", + "2019-01-01 05:44:00+01:00", + "2019-01-01 05:45:00+01:00", + "2019-01-01 05:46:00+01:00", + "2019-01-01 05:47:00+01:00", + "2019-01-01 05:48:00+01:00", + "2019-01-01 05:49:00+01:00", + "2019-01-01 05:50:00+01:00", + "2019-01-01 05:51:00+01:00", + "2019-01-01 05:52:00+01:00", + "2019-01-01 05:53:00+01:00", + "2019-01-01 05:54:00+01:00", + "2019-01-01 05:55:00+01:00", + "2019-01-01 05:56:00+01:00", + "2019-01-01 05:57:00+01:00", + "2019-01-01 05:58:00+01:00", + "2019-01-01 05:59:00+01:00", + "2019-01-01 06:00:00+01:00", + "2019-01-01 06:01:00+01:00", + "2019-01-01 06:02:00+01:00", + "2019-01-01 06:03:00+01:00", + "2019-01-01 06:04:00+01:00", + "2019-01-01 06:05:00+01:00", + "2019-01-01 06:06:00+01:00", + "2019-01-01 06:07:00+01:00", + "2019-01-01 06:08:00+01:00", + "2019-01-01 06:09:00+01:00", + "2019-01-01 06:10:00+01:00", + "2019-01-01 06:11:00+01:00", + "2019-01-01 06:12:00+01:00", + "2019-01-01 06:13:00+01:00", + "2019-01-01 06:14:00+01:00", + "2019-01-01 06:15:00+01:00", + "2019-01-01 06:16:00+01:00", + "2019-01-01 06:17:00+01:00", + "2019-01-01 06:18:00+01:00", + "2019-01-01 06:19:00+01:00", + "2019-01-01 06:20:00+01:00", + "2019-01-01 06:21:00+01:00", + "2019-01-01 06:22:00+01:00", + "2019-01-01 06:23:00+01:00", + "2019-01-01 06:24:00+01:00", + "2019-01-01 06:25:00+01:00", + "2019-01-01 06:26:00+01:00", + "2019-01-01 06:27:00+01:00", + "2019-01-01 06:28:00+01:00", + "2019-01-01 06:29:00+01:00", + "2019-01-01 06:30:00+01:00", + "2019-01-01 06:31:00+01:00", + "2019-01-01 06:32:00+01:00", + "2019-01-01 06:33:00+01:00", + "2019-01-01 06:34:00+01:00", + "2019-01-01 06:35:00+01:00", + "2019-01-01 06:36:00+01:00", + "2019-01-01 06:37:00+01:00", + "2019-01-01 06:38:00+01:00", + "2019-01-01 06:39:00+01:00", + "2019-01-01 06:40:00+01:00", + "2019-01-01 06:41:00+01:00", + "2019-01-01 06:42:00+01:00", + "2019-01-01 06:43:00+01:00", + "2019-01-01 06:44:00+01:00", + "2019-01-01 06:45:00+01:00", + "2019-01-01 06:46:00+01:00", + "2019-01-01 06:47:00+01:00", + "2019-01-01 06:48:00+01:00", + "2019-01-01 06:49:00+01:00", + "2019-01-01 06:50:00+01:00", + "2019-01-01 06:51:00+01:00", + "2019-01-01 06:52:00+01:00", + "2019-01-01 06:53:00+01:00", + "2019-01-01 06:54:00+01:00", + "2019-01-01 06:55:00+01:00", + "2019-01-01 06:56:00+01:00", + "2019-01-01 06:57:00+01:00", + "2019-01-01 06:58:00+01:00", + "2019-01-01 06:59:00+01:00", + "2019-01-01 07:00:00+01:00", + "2019-01-01 07:01:00+01:00", + "2019-01-01 07:02:00+01:00", + "2019-01-01 07:03:00+01:00", + "2019-01-01 07:04:00+01:00", + "2019-01-01 07:05:00+01:00", + "2019-01-01 07:06:00+01:00", + "2019-01-01 07:07:00+01:00", + "2019-01-01 07:08:00+01:00", + "2019-01-01 07:09:00+01:00", + "2019-01-01 07:10:00+01:00", + "2019-01-01 07:11:00+01:00", + "2019-01-01 07:12:00+01:00", + "2019-01-01 07:13:00+01:00", + "2019-01-01 07:14:00+01:00", + "2019-01-01 07:15:00+01:00", + "2019-01-01 07:16:00+01:00", + "2019-01-01 07:17:00+01:00", + "2019-01-01 07:18:00+01:00", + "2019-01-01 07:19:00+01:00", + "2019-01-01 07:20:00+01:00", + "2019-01-01 07:21:00+01:00", + "2019-01-01 07:22:00+01:00", + "2019-01-01 07:23:00+01:00", + "2019-01-01 07:24:00+01:00", + "2019-01-01 07:25:00+01:00", + "2019-01-01 07:26:00+01:00", + "2019-01-01 07:27:00+01:00", + "2019-01-01 07:28:00+01:00", + "2019-01-01 07:29:00+01:00", + "2019-01-01 07:30:00+01:00", + "2019-01-01 07:31:00+01:00", + "2019-01-01 07:32:00+01:00", + "2019-01-01 07:33:00+01:00", + "2019-01-01 07:34:00+01:00", + "2019-01-01 07:35:00+01:00", + "2019-01-01 07:36:00+01:00", + "2019-01-01 07:37:00+01:00", + "2019-01-01 07:38:00+01:00", + "2019-01-01 07:39:00+01:00", + "2019-01-01 07:40:00+01:00", + "2019-01-01 07:41:00+01:00", + "2019-01-01 07:42:00+01:00", + "2019-01-01 07:43:00+01:00", + "2019-01-01 07:44:00+01:00", + "2019-01-01 07:45:00+01:00", + "2019-01-01 07:46:00+01:00", + "2019-01-01 07:47:00+01:00", + "2019-01-01 07:48:00+01:00", + "2019-01-01 07:49:00+01:00", + "2019-01-01 07:50:00+01:00", + "2019-01-01 07:51:00+01:00", + "2019-01-01 07:52:00+01:00", + "2019-01-01 07:53:00+01:00", + "2019-01-01 07:54:00+01:00", + "2019-01-01 07:55:00+01:00", + "2019-01-01 07:56:00+01:00", + "2019-01-01 07:57:00+01:00", + "2019-01-01 07:58:00+01:00", + "2019-01-01 07:59:00+01:00", + "2019-01-01 08:00:00+01:00", + "2019-01-01 08:01:00+01:00", + "2019-01-01 08:02:00+01:00", + "2019-01-01 08:03:00+01:00", + "2019-01-01 08:04:00+01:00", + "2019-01-01 08:05:00+01:00", + "2019-01-01 08:06:00+01:00", + "2019-01-01 08:07:00+01:00", + "2019-01-01 08:08:00+01:00", + "2019-01-01 08:09:00+01:00", + "2019-01-01 08:10:00+01:00", + "2019-01-01 08:11:00+01:00", + "2019-01-01 08:12:00+01:00", + "2019-01-01 08:13:00+01:00", + "2019-01-01 08:14:00+01:00", + "2019-01-01 08:15:00+01:00", + "2019-01-01 08:16:00+01:00", + "2019-01-01 08:17:00+01:00", + "2019-01-01 08:18:00+01:00", + "2019-01-01 08:19:00+01:00", + "2019-01-01 08:20:00+01:00", + "2019-01-01 08:21:00+01:00", + "2019-01-01 08:22:00+01:00", + "2019-01-01 08:23:00+01:00", + "2019-01-01 08:24:00+01:00", + "2019-01-01 08:25:00+01:00", + "2019-01-01 08:26:00+01:00", + "2019-01-01 08:27:00+01:00", + "2019-01-01 08:28:00+01:00", + "2019-01-01 08:29:00+01:00", + "2019-01-01 08:30:00+01:00", + "2019-01-01 08:31:00+01:00", + "2019-01-01 08:32:00+01:00", + "2019-01-01 08:33:00+01:00", + "2019-01-01 08:34:00+01:00", + "2019-01-01 08:35:00+01:00", + "2019-01-01 08:36:00+01:00", + "2019-01-01 08:37:00+01:00", + "2019-01-01 08:38:00+01:00", + "2019-01-01 08:39:00+01:00", + "2019-01-01 08:40:00+01:00", + "2019-01-01 08:41:00+01:00", + "2019-01-01 08:42:00+01:00", + "2019-01-01 08:43:00+01:00", + "2019-01-01 08:44:00+01:00", + "2019-01-01 08:45:00+01:00", + "2019-01-01 08:46:00+01:00", + "2019-01-01 08:47:00+01:00", + "2019-01-01 08:48:00+01:00", + "2019-01-01 08:49:00+01:00", + "2019-01-01 08:50:00+01:00", + "2019-01-01 08:51:00+01:00", + "2019-01-01 08:52:00+01:00", + "2019-01-01 08:53:00+01:00", + "2019-01-01 08:54:00+01:00", + "2019-01-01 08:55:00+01:00", + "2019-01-01 08:56:00+01:00", + "2019-01-01 08:57:00+01:00", + "2019-01-01 08:58:00+01:00", + "2019-01-01 08:59:00+01:00", + "2019-01-01 09:00:00+01:00", + "2019-01-01 09:01:00+01:00", + "2019-01-01 09:02:00+01:00", + "2019-01-01 09:03:00+01:00", + "2019-01-01 09:04:00+01:00", + "2019-01-01 09:05:00+01:00", + "2019-01-01 09:06:00+01:00", + "2019-01-01 09:07:00+01:00", + "2019-01-01 09:08:00+01:00", + "2019-01-01 09:09:00+01:00", + "2019-01-01 09:10:00+01:00", + "2019-01-01 09:11:00+01:00", + "2019-01-01 09:12:00+01:00", + "2019-01-01 09:13:00+01:00", + "2019-01-01 09:14:00+01:00", + "2019-01-01 09:15:00+01:00", + "2019-01-01 09:16:00+01:00", + "2019-01-01 09:17:00+01:00", + "2019-01-01 09:18:00+01:00", + "2019-01-01 09:19:00+01:00", + "2019-01-01 09:20:00+01:00", + "2019-01-01 09:21:00+01:00", + "2019-01-01 09:22:00+01:00", + "2019-01-01 09:23:00+01:00", + "2019-01-01 09:24:00+01:00", + "2019-01-01 09:25:00+01:00", + "2019-01-01 09:26:00+01:00", + "2019-01-01 09:27:00+01:00", + "2019-01-01 09:28:00+01:00", + "2019-01-01 09:29:00+01:00", + "2019-01-01 09:30:00+01:00", + "2019-01-01 09:31:00+01:00", + "2019-01-01 09:32:00+01:00", + "2019-01-01 09:33:00+01:00", + "2019-01-01 09:34:00+01:00", + "2019-01-01 09:35:00+01:00", + "2019-01-01 09:36:00+01:00", + "2019-01-01 09:37:00+01:00", + "2019-01-01 09:38:00+01:00", + "2019-01-01 09:39:00+01:00", + "2019-01-01 09:40:00+01:00", + "2019-01-01 09:41:00+01:00", + "2019-01-01 09:42:00+01:00", + "2019-01-01 09:43:00+01:00", + "2019-01-01 09:44:00+01:00", + "2019-01-01 09:45:00+01:00", + "2019-01-01 09:46:00+01:00", + "2019-01-01 09:47:00+01:00", + "2019-01-01 09:48:00+01:00", + "2019-01-01 09:49:00+01:00", + "2019-01-01 09:50:00+01:00", + "2019-01-01 09:51:00+01:00", + "2019-01-01 09:52:00+01:00", + "2019-01-01 09:53:00+01:00", + "2019-01-01 09:54:00+01:00", + "2019-01-01 09:55:00+01:00", + "2019-01-01 09:56:00+01:00", + "2019-01-01 09:57:00+01:00", + "2019-01-01 09:58:00+01:00", + "2019-01-01 09:59:00+01:00", + "2019-01-01 10:00:00+01:00", + "2019-01-01 10:01:00+01:00", + "2019-01-01 10:02:00+01:00", + "2019-01-01 10:03:00+01:00", + "2019-01-01 10:04:00+01:00", + "2019-01-01 10:05:00+01:00", + "2019-01-01 10:06:00+01:00", + "2019-01-01 10:07:00+01:00", + "2019-01-01 10:08:00+01:00", + "2019-01-01 10:09:00+01:00", + "2019-01-01 10:10:00+01:00", + "2019-01-01 10:11:00+01:00", + "2019-01-01 10:12:00+01:00", + "2019-01-01 10:13:00+01:00", + "2019-01-01 10:14:00+01:00", + "2019-01-01 10:15:00+01:00", + "2019-01-01 10:16:00+01:00", + "2019-01-01 10:17:00+01:00", + "2019-01-01 10:18:00+01:00", + "2019-01-01 10:19:00+01:00", + "2019-01-01 10:20:00+01:00", + "2019-01-01 10:21:00+01:00", + "2019-01-01 10:22:00+01:00", + "2019-01-01 10:23:00+01:00", + "2019-01-01 10:24:00+01:00", + "2019-01-01 10:25:00+01:00", + "2019-01-01 10:26:00+01:00", + "2019-01-01 10:27:00+01:00", + "2019-01-01 10:28:00+01:00", + "2019-01-01 10:29:00+01:00", + "2019-01-01 10:30:00+01:00", + "2019-01-01 10:31:00+01:00", + "2019-01-01 10:32:00+01:00", + "2019-01-01 10:33:00+01:00", + "2019-01-01 10:34:00+01:00", + "2019-01-01 10:35:00+01:00", + "2019-01-01 10:36:00+01:00", + "2019-01-01 10:37:00+01:00", + "2019-01-01 10:38:00+01:00", + "2019-01-01 10:39:00+01:00", + "2019-01-01 10:40:00+01:00", + "2019-01-01 10:41:00+01:00", + "2019-01-01 10:42:00+01:00", + "2019-01-01 10:43:00+01:00", + "2019-01-01 10:44:00+01:00", + "2019-01-01 10:45:00+01:00", + "2019-01-01 10:46:00+01:00", + "2019-01-01 10:47:00+01:00", + "2019-01-01 10:48:00+01:00", + "2019-01-01 10:49:00+01:00", + "2019-01-01 10:50:00+01:00", + "2019-01-01 10:51:00+01:00", + "2019-01-01 10:52:00+01:00", + "2019-01-01 10:53:00+01:00", + "2019-01-01 10:54:00+01:00", + "2019-01-01 10:55:00+01:00", + "2019-01-01 10:56:00+01:00", + "2019-01-01 10:57:00+01:00", + "2019-01-01 10:58:00+01:00", + "2019-01-01 10:59:00+01:00", + "2019-01-01 11:00:00+01:00", + "2019-01-01 11:01:00+01:00", + "2019-01-01 11:02:00+01:00", + "2019-01-01 11:03:00+01:00", + "2019-01-01 11:04:00+01:00", + "2019-01-01 11:05:00+01:00", + "2019-01-01 11:06:00+01:00", + "2019-01-01 11:07:00+01:00", + "2019-01-01 11:08:00+01:00", + "2019-01-01 11:09:00+01:00", + "2019-01-01 11:10:00+01:00", + "2019-01-01 11:11:00+01:00", + "2019-01-01 11:12:00+01:00", + "2019-01-01 11:13:00+01:00", + "2019-01-01 11:14:00+01:00", + "2019-01-01 11:15:00+01:00", + "2019-01-01 11:16:00+01:00", + "2019-01-01 11:17:00+01:00", + "2019-01-01 11:18:00+01:00", + "2019-01-01 11:19:00+01:00", + "2019-01-01 11:20:00+01:00", + "2019-01-01 11:21:00+01:00", + "2019-01-01 11:22:00+01:00", + "2019-01-01 11:23:00+01:00", + "2019-01-01 11:24:00+01:00", + "2019-01-01 11:25:00+01:00", + "2019-01-01 11:26:00+01:00", + "2019-01-01 11:27:00+01:00", + "2019-01-01 11:28:00+01:00", + "2019-01-01 11:29:00+01:00", + "2019-01-01 11:30:00+01:00", + "2019-01-01 11:31:00+01:00", + "2019-01-01 11:32:00+01:00", + "2019-01-01 11:33:00+01:00", + "2019-01-01 11:34:00+01:00", + "2019-01-01 11:35:00+01:00", + "2019-01-01 11:36:00+01:00", + "2019-01-01 11:37:00+01:00", + "2019-01-01 11:38:00+01:00", + "2019-01-01 11:39:00+01:00", + "2019-01-01 11:40:00+01:00", + "2019-01-01 11:41:00+01:00", + "2019-01-01 11:42:00+01:00", + "2019-01-01 11:43:00+01:00", + "2019-01-01 11:44:00+01:00", + "2019-01-01 11:45:00+01:00", + "2019-01-01 11:46:00+01:00", + "2019-01-01 11:47:00+01:00", + "2019-01-01 11:48:00+01:00", + "2019-01-01 11:49:00+01:00", + "2019-01-01 11:50:00+01:00", + "2019-01-01 11:51:00+01:00", + "2019-01-01 11:52:00+01:00", + "2019-01-01 11:53:00+01:00", + "2019-01-01 11:54:00+01:00", + "2019-01-01 11:55:00+01:00", + "2019-01-01 11:56:00+01:00", + "2019-01-01 11:57:00+01:00", + "2019-01-01 11:58:00+01:00", + "2019-01-01 11:59:00+01:00", + "2019-01-01 12:00:00+01:00", + "2019-01-01 12:01:00+01:00", + "2019-01-01 12:02:00+01:00", + "2019-01-01 12:03:00+01:00", + "2019-01-01 12:04:00+01:00", + "2019-01-01 12:05:00+01:00", + "2019-01-01 12:06:00+01:00", + "2019-01-01 12:07:00+01:00", + "2019-01-01 12:08:00+01:00", + "2019-01-01 12:09:00+01:00", + "2019-01-01 12:10:00+01:00", + "2019-01-01 12:11:00+01:00", + "2019-01-01 12:12:00+01:00", + "2019-01-01 12:13:00+01:00", + "2019-01-01 12:14:00+01:00", + "2019-01-01 12:15:00+01:00", + "2019-01-01 12:16:00+01:00", + "2019-01-01 12:17:00+01:00", + "2019-01-01 12:18:00+01:00", + "2019-01-01 12:19:00+01:00", + "2019-01-01 12:20:00+01:00", + "2019-01-01 12:21:00+01:00", + "2019-01-01 12:22:00+01:00", + "2019-01-01 12:23:00+01:00", + "2019-01-01 12:24:00+01:00", + "2019-01-01 12:25:00+01:00", + "2019-01-01 12:26:00+01:00", + "2019-01-01 12:27:00+01:00", + "2019-01-01 12:28:00+01:00", + "2019-01-01 12:29:00+01:00", + "2019-01-01 12:30:00+01:00", + "2019-01-01 12:31:00+01:00", + "2019-01-01 12:32:00+01:00", + "2019-01-01 12:33:00+01:00", + "2019-01-01 12:34:00+01:00", + "2019-01-01 12:35:00+01:00", + "2019-01-01 12:36:00+01:00", + "2019-01-01 12:37:00+01:00", + "2019-01-01 12:38:00+01:00", + "2019-01-01 12:39:00+01:00", + "2019-01-01 12:40:00+01:00", + "2019-01-01 12:41:00+01:00", + "2019-01-01 12:42:00+01:00", + "2019-01-01 12:43:00+01:00", + "2019-01-01 12:44:00+01:00", + "2019-01-01 12:45:00+01:00", + "2019-01-01 12:46:00+01:00", + "2019-01-01 12:47:00+01:00", + "2019-01-01 12:48:00+01:00", + "2019-01-01 12:49:00+01:00", + "2019-01-01 12:50:00+01:00", + "2019-01-01 12:51:00+01:00", + "2019-01-01 12:52:00+01:00", + "2019-01-01 12:53:00+01:00", + "2019-01-01 12:54:00+01:00", + "2019-01-01 12:55:00+01:00", + "2019-01-01 12:56:00+01:00", + "2019-01-01 12:57:00+01:00", + "2019-01-01 12:58:00+01:00", + "2019-01-01 12:59:00+01:00", + "2019-01-01 13:00:00+01:00", + "2019-01-01 13:01:00+01:00", + "2019-01-01 13:02:00+01:00", + "2019-01-01 13:03:00+01:00", + "2019-01-01 13:04:00+01:00", + "2019-01-01 13:05:00+01:00", + "2019-01-01 13:06:00+01:00", + "2019-01-01 13:07:00+01:00", + "2019-01-01 13:08:00+01:00", + "2019-01-01 13:09:00+01:00", + "2019-01-01 13:10:00+01:00", + "2019-01-01 13:11:00+01:00", + "2019-01-01 13:12:00+01:00", + "2019-01-01 13:13:00+01:00", + "2019-01-01 13:14:00+01:00", + "2019-01-01 13:15:00+01:00", + "2019-01-01 13:16:00+01:00", + "2019-01-01 13:17:00+01:00", + "2019-01-01 13:18:00+01:00", + "2019-01-01 13:19:00+01:00", + "2019-01-01 13:20:00+01:00", + "2019-01-01 13:21:00+01:00", + "2019-01-01 13:22:00+01:00", + "2019-01-01 13:23:00+01:00", + "2019-01-01 13:24:00+01:00", + "2019-01-01 13:25:00+01:00", + "2019-01-01 13:26:00+01:00", + "2019-01-01 13:27:00+01:00", + "2019-01-01 13:28:00+01:00", + "2019-01-01 13:29:00+01:00", + "2019-01-01 13:30:00+01:00", + "2019-01-01 13:31:00+01:00", + "2019-01-01 13:32:00+01:00", + "2019-01-01 13:33:00+01:00", + "2019-01-01 13:34:00+01:00", + "2019-01-01 13:35:00+01:00", + "2019-01-01 13:36:00+01:00", + "2019-01-01 13:37:00+01:00", + "2019-01-01 13:38:00+01:00", + "2019-01-01 13:39:00+01:00", + "2019-01-01 13:40:00+01:00", + "2019-01-01 13:41:00+01:00", + "2019-01-01 13:42:00+01:00", + "2019-01-01 13:43:00+01:00", + "2019-01-01 13:44:00+01:00", + "2019-01-01 13:45:00+01:00", + "2019-01-01 13:46:00+01:00", + "2019-01-01 13:47:00+01:00", + "2019-01-01 13:48:00+01:00", + "2019-01-01 13:49:00+01:00", + "2019-01-01 13:50:00+01:00", + "2019-01-01 13:51:00+01:00", + "2019-01-01 13:52:00+01:00", + "2019-01-01 13:53:00+01:00", + "2019-01-01 13:54:00+01:00", + "2019-01-01 13:55:00+01:00", + "2019-01-01 13:56:00+01:00", + "2019-01-01 13:57:00+01:00", + "2019-01-01 13:58:00+01:00", + "2019-01-01 13:59:00+01:00", + "2019-01-01 14:00:00+01:00", + "2019-01-01 14:01:00+01:00", + "2019-01-01 14:02:00+01:00", + "2019-01-01 14:03:00+01:00", + "2019-01-01 14:04:00+01:00", + "2019-01-01 14:05:00+01:00", + "2019-01-01 14:06:00+01:00", + "2019-01-01 14:07:00+01:00", + "2019-01-01 14:08:00+01:00", + "2019-01-01 14:09:00+01:00", + "2019-01-01 14:10:00+01:00", + "2019-01-01 14:11:00+01:00", + "2019-01-01 14:12:00+01:00", + "2019-01-01 14:13:00+01:00", + "2019-01-01 14:14:00+01:00", + "2019-01-01 14:15:00+01:00", + "2019-01-01 14:16:00+01:00", + "2019-01-01 14:17:00+01:00", + "2019-01-01 14:18:00+01:00", + "2019-01-01 14:19:00+01:00", + "2019-01-01 14:20:00+01:00", + "2019-01-01 14:21:00+01:00", + "2019-01-01 14:22:00+01:00", + "2019-01-01 14:23:00+01:00", + "2019-01-01 14:24:00+01:00", + "2019-01-01 14:25:00+01:00", + "2019-01-01 14:26:00+01:00", + "2019-01-01 14:27:00+01:00", + "2019-01-01 14:28:00+01:00", + "2019-01-01 14:29:00+01:00", + "2019-01-01 14:30:00+01:00", + "2019-01-01 14:31:00+01:00", + "2019-01-01 14:32:00+01:00", + "2019-01-01 14:33:00+01:00", + "2019-01-01 14:34:00+01:00", + "2019-01-01 14:35:00+01:00", + "2019-01-01 14:36:00+01:00", + "2019-01-01 14:37:00+01:00", + "2019-01-01 14:38:00+01:00", + "2019-01-01 14:39:00+01:00", + "2019-01-01 14:40:00+01:00", + "2019-01-01 14:41:00+01:00", + "2019-01-01 14:42:00+01:00", + "2019-01-01 14:43:00+01:00", + "2019-01-01 14:44:00+01:00", + "2019-01-01 14:45:00+01:00", + "2019-01-01 14:46:00+01:00", + "2019-01-01 14:47:00+01:00", + "2019-01-01 14:48:00+01:00", + "2019-01-01 14:49:00+01:00", + "2019-01-01 14:50:00+01:00", + "2019-01-01 14:51:00+01:00", + "2019-01-01 14:52:00+01:00", + "2019-01-01 14:53:00+01:00", + "2019-01-01 14:54:00+01:00", + "2019-01-01 14:55:00+01:00", + "2019-01-01 14:56:00+01:00", + "2019-01-01 14:57:00+01:00", + "2019-01-01 14:58:00+01:00", + "2019-01-01 14:59:00+01:00", + "2019-01-01 15:00:00+01:00", + "2019-01-01 15:01:00+01:00", + "2019-01-01 15:02:00+01:00", + "2019-01-01 15:03:00+01:00", + "2019-01-01 15:04:00+01:00", + "2019-01-01 15:05:00+01:00", + "2019-01-01 15:06:00+01:00", + "2019-01-01 15:07:00+01:00", + "2019-01-01 15:08:00+01:00", + "2019-01-01 15:09:00+01:00", + "2019-01-01 15:10:00+01:00", + "2019-01-01 15:11:00+01:00", + "2019-01-01 15:12:00+01:00", + "2019-01-01 15:13:00+01:00", + "2019-01-01 15:14:00+01:00", + "2019-01-01 15:15:00+01:00", + "2019-01-01 15:16:00+01:00", + "2019-01-01 15:17:00+01:00", + "2019-01-01 15:18:00+01:00", + "2019-01-01 15:19:00+01:00", + "2019-01-01 15:20:00+01:00", + "2019-01-01 15:21:00+01:00", + "2019-01-01 15:22:00+01:00", + "2019-01-01 15:23:00+01:00", + "2019-01-01 15:24:00+01:00", + "2019-01-01 15:25:00+01:00", + "2019-01-01 15:26:00+01:00", + "2019-01-01 15:27:00+01:00", + "2019-01-01 15:28:00+01:00", + "2019-01-01 15:29:00+01:00", + "2019-01-01 15:30:00+01:00", + "2019-01-01 15:31:00+01:00", + "2019-01-01 15:32:00+01:00", + "2019-01-01 15:33:00+01:00", + "2019-01-01 15:34:00+01:00", + "2019-01-01 15:35:00+01:00", + "2019-01-01 15:36:00+01:00", + "2019-01-01 15:37:00+01:00", + "2019-01-01 15:38:00+01:00", + "2019-01-01 15:39:00+01:00", + "2019-01-01 15:40:00+01:00", + "2019-01-01 15:41:00+01:00", + "2019-01-01 15:42:00+01:00", + "2019-01-01 15:43:00+01:00", + "2019-01-01 15:44:00+01:00", + "2019-01-01 15:45:00+01:00", + "2019-01-01 15:46:00+01:00", + "2019-01-01 15:47:00+01:00", + "2019-01-01 15:48:00+01:00", + "2019-01-01 15:49:00+01:00", + "2019-01-01 15:50:00+01:00", + "2019-01-01 15:51:00+01:00", + "2019-01-01 15:52:00+01:00", + "2019-01-01 15:53:00+01:00", + "2019-01-01 15:54:00+01:00", + "2019-01-01 15:55:00+01:00", + "2019-01-01 15:56:00+01:00", + "2019-01-01 15:57:00+01:00", + "2019-01-01 15:58:00+01:00", + "2019-01-01 15:59:00+01:00", + "2019-01-01 16:00:00+01:00", + "2019-01-01 16:01:00+01:00", + "2019-01-01 16:02:00+01:00", + "2019-01-01 16:03:00+01:00", + "2019-01-01 16:04:00+01:00", + "2019-01-01 16:05:00+01:00", + "2019-01-01 16:06:00+01:00", + "2019-01-01 16:07:00+01:00", + "2019-01-01 16:08:00+01:00", + "2019-01-01 16:09:00+01:00", + "2019-01-01 16:10:00+01:00", + "2019-01-01 16:11:00+01:00", + "2019-01-01 16:12:00+01:00", + "2019-01-01 16:13:00+01:00", + "2019-01-01 16:14:00+01:00", + "2019-01-01 16:15:00+01:00", + "2019-01-01 16:16:00+01:00", + "2019-01-01 16:17:00+01:00", + "2019-01-01 16:18:00+01:00", + "2019-01-01 16:19:00+01:00", + "2019-01-01 16:20:00+01:00", + "2019-01-01 16:21:00+01:00", + "2019-01-01 16:22:00+01:00", + "2019-01-01 16:23:00+01:00", + "2019-01-01 16:24:00+01:00", + "2019-01-01 16:25:00+01:00", + "2019-01-01 16:26:00+01:00", + "2019-01-01 16:27:00+01:00", + "2019-01-01 16:28:00+01:00", + "2019-01-01 16:29:00+01:00", + "2019-01-01 16:30:00+01:00", + "2019-01-01 16:31:00+01:00", + "2019-01-01 16:32:00+01:00", + "2019-01-01 16:33:00+01:00", + "2019-01-01 16:34:00+01:00", + "2019-01-01 16:35:00+01:00", + "2019-01-01 16:36:00+01:00", + "2019-01-01 16:37:00+01:00", + "2019-01-01 16:38:00+01:00", + "2019-01-01 16:39:00+01:00", + "2019-01-01 16:40:00+01:00", + "2019-01-01 16:41:00+01:00", + "2019-01-01 16:42:00+01:00", + "2019-01-01 16:43:00+01:00", + "2019-01-01 16:44:00+01:00", + "2019-01-01 16:45:00+01:00", + "2019-01-01 16:46:00+01:00", + "2019-01-01 16:47:00+01:00", + "2019-01-01 16:48:00+01:00", + "2019-01-01 16:49:00+01:00", + "2019-01-01 16:50:00+01:00", + "2019-01-01 16:51:00+01:00", + "2019-01-01 16:52:00+01:00", + "2019-01-01 16:53:00+01:00", + "2019-01-01 16:54:00+01:00", + "2019-01-01 16:55:00+01:00", + "2019-01-01 16:56:00+01:00", + "2019-01-01 16:57:00+01:00", + "2019-01-01 16:58:00+01:00", + "2019-01-01 16:59:00+01:00", + "2019-01-01 17:00:00+01:00", + "2019-01-01 17:01:00+01:00", + "2019-01-01 17:02:00+01:00", + "2019-01-01 17:03:00+01:00", + "2019-01-01 17:04:00+01:00", + "2019-01-01 17:05:00+01:00", + "2019-01-01 17:06:00+01:00", + "2019-01-01 17:07:00+01:00", + "2019-01-01 17:08:00+01:00", + "2019-01-01 17:09:00+01:00", + "2019-01-01 17:10:00+01:00", + "2019-01-01 17:11:00+01:00", + "2019-01-01 17:12:00+01:00", + "2019-01-01 17:13:00+01:00", + "2019-01-01 17:14:00+01:00", + "2019-01-01 17:15:00+01:00", + "2019-01-01 17:16:00+01:00", + "2019-01-01 17:17:00+01:00", + "2019-01-01 17:18:00+01:00", + "2019-01-01 17:19:00+01:00", + "2019-01-01 17:20:00+01:00", + "2019-01-01 17:21:00+01:00", + "2019-01-01 17:22:00+01:00", + "2019-01-01 17:23:00+01:00", + "2019-01-01 17:24:00+01:00", + "2019-01-01 17:25:00+01:00", + "2019-01-01 17:26:00+01:00", + "2019-01-01 17:27:00+01:00", + "2019-01-01 17:28:00+01:00", + "2019-01-01 17:29:00+01:00", + "2019-01-01 17:30:00+01:00", + "2019-01-01 17:31:00+01:00", + "2019-01-01 17:32:00+01:00", + "2019-01-01 17:33:00+01:00", + "2019-01-01 17:34:00+01:00", + "2019-01-01 17:35:00+01:00", + "2019-01-01 17:36:00+01:00", + "2019-01-01 17:37:00+01:00", + "2019-01-01 17:38:00+01:00", + "2019-01-01 17:39:00+01:00", + "2019-01-01 17:40:00+01:00", + "2019-01-01 17:41:00+01:00", + "2019-01-01 17:42:00+01:00", + "2019-01-01 17:43:00+01:00", + "2019-01-01 17:44:00+01:00", + "2019-01-01 17:45:00+01:00", + "2019-01-01 17:46:00+01:00", + "2019-01-01 17:47:00+01:00", + "2019-01-01 17:48:00+01:00", + "2019-01-01 17:49:00+01:00", + "2019-01-01 17:50:00+01:00", + "2019-01-01 17:51:00+01:00", + "2019-01-01 17:52:00+01:00", + "2019-01-01 17:53:00+01:00", + "2019-01-01 17:54:00+01:00", + "2019-01-01 17:55:00+01:00", + "2019-01-01 17:56:00+01:00", + "2019-01-01 17:57:00+01:00", + "2019-01-01 17:58:00+01:00", + "2019-01-01 17:59:00+01:00", + "2019-01-01 18:00:00+01:00", + "2019-01-01 18:01:00+01:00", + "2019-01-01 18:02:00+01:00", + "2019-01-01 18:03:00+01:00", + "2019-01-01 18:04:00+01:00", + "2019-01-01 18:05:00+01:00", + "2019-01-01 18:06:00+01:00", + "2019-01-01 18:07:00+01:00", + "2019-01-01 18:08:00+01:00", + "2019-01-01 18:09:00+01:00", + "2019-01-01 18:10:00+01:00", + "2019-01-01 18:11:00+01:00", + "2019-01-01 18:12:00+01:00", + "2019-01-01 18:13:00+01:00", + "2019-01-01 18:14:00+01:00", + "2019-01-01 18:15:00+01:00", + "2019-01-01 18:16:00+01:00", + "2019-01-01 18:17:00+01:00", + "2019-01-01 18:18:00+01:00", + "2019-01-01 18:19:00+01:00", + "2019-01-01 18:20:00+01:00", + "2019-01-01 18:21:00+01:00", + "2019-01-01 18:22:00+01:00", + "2019-01-01 18:23:00+01:00", + "2019-01-01 18:24:00+01:00", + "2019-01-01 18:25:00+01:00", + "2019-01-01 18:26:00+01:00", + "2019-01-01 18:27:00+01:00", + "2019-01-01 18:28:00+01:00", + "2019-01-01 18:29:00+01:00", + "2019-01-01 18:30:00+01:00", + "2019-01-01 18:31:00+01:00", + "2019-01-01 18:32:00+01:00", + "2019-01-01 18:33:00+01:00", + "2019-01-01 18:34:00+01:00", + "2019-01-01 18:35:00+01:00", + "2019-01-01 18:36:00+01:00", + "2019-01-01 18:37:00+01:00", + "2019-01-01 18:38:00+01:00", + "2019-01-01 18:39:00+01:00", + "2019-01-01 18:40:00+01:00", + "2019-01-01 18:41:00+01:00", + "2019-01-01 18:42:00+01:00", + "2019-01-01 18:43:00+01:00", + "2019-01-01 18:44:00+01:00", + "2019-01-01 18:45:00+01:00", + "2019-01-01 18:46:00+01:00", + "2019-01-01 18:47:00+01:00", + "2019-01-01 18:48:00+01:00", + "2019-01-01 18:49:00+01:00", + "2019-01-01 18:50:00+01:00", + "2019-01-01 18:51:00+01:00", + "2019-01-01 18:52:00+01:00", + "2019-01-01 18:53:00+01:00", + "2019-01-01 18:54:00+01:00", + "2019-01-01 18:55:00+01:00", + "2019-01-01 18:56:00+01:00", + "2019-01-01 18:57:00+01:00", + "2019-01-01 18:58:00+01:00", + "2019-01-01 18:59:00+01:00", + "2019-01-01 19:00:00+01:00", + "2019-01-01 19:01:00+01:00", + "2019-01-01 19:02:00+01:00", + "2019-01-01 19:03:00+01:00", + "2019-01-01 19:04:00+01:00", + "2019-01-01 19:05:00+01:00", + "2019-01-01 19:06:00+01:00", + "2019-01-01 19:07:00+01:00", + "2019-01-01 19:08:00+01:00", + "2019-01-01 19:09:00+01:00", + "2019-01-01 19:10:00+01:00", + "2019-01-01 19:11:00+01:00", + "2019-01-01 19:12:00+01:00", + "2019-01-01 19:13:00+01:00", + "2019-01-01 19:14:00+01:00", + "2019-01-01 19:15:00+01:00", + "2019-01-01 19:16:00+01:00", + "2019-01-01 19:17:00+01:00", + "2019-01-01 19:18:00+01:00", + "2019-01-01 19:19:00+01:00", + "2019-01-01 19:20:00+01:00", + "2019-01-01 19:21:00+01:00", + "2019-01-01 19:22:00+01:00", + "2019-01-01 19:23:00+01:00", + "2019-01-01 19:24:00+01:00", + "2019-01-01 19:25:00+01:00", + "2019-01-01 19:26:00+01:00", + "2019-01-01 19:27:00+01:00", + "2019-01-01 19:28:00+01:00", + "2019-01-01 19:29:00+01:00", + "2019-01-01 19:30:00+01:00", + "2019-01-01 19:31:00+01:00", + "2019-01-01 19:32:00+01:00", + "2019-01-01 19:33:00+01:00", + "2019-01-01 19:34:00+01:00", + "2019-01-01 19:35:00+01:00", + "2019-01-01 19:36:00+01:00", + "2019-01-01 19:37:00+01:00", + "2019-01-01 19:38:00+01:00", + "2019-01-01 19:39:00+01:00", + "2019-01-01 19:40:00+01:00", + "2019-01-01 19:41:00+01:00", + "2019-01-01 19:42:00+01:00", + "2019-01-01 19:43:00+01:00", + "2019-01-01 19:44:00+01:00", + "2019-01-01 19:45:00+01:00", + "2019-01-01 19:46:00+01:00", + "2019-01-01 19:47:00+01:00", + "2019-01-01 19:48:00+01:00", + "2019-01-01 19:49:00+01:00", + "2019-01-01 19:50:00+01:00", + "2019-01-01 19:51:00+01:00", + "2019-01-01 19:52:00+01:00", + "2019-01-01 19:53:00+01:00", + "2019-01-01 19:54:00+01:00", + "2019-01-01 19:55:00+01:00", + "2019-01-01 19:56:00+01:00", + "2019-01-01 19:57:00+01:00", + "2019-01-01 19:58:00+01:00", + "2019-01-01 19:59:00+01:00", + "2019-01-01 20:00:00+01:00", + "2019-01-01 20:01:00+01:00", + "2019-01-01 20:02:00+01:00", + "2019-01-01 20:03:00+01:00", + "2019-01-01 20:04:00+01:00", + "2019-01-01 20:05:00+01:00", + "2019-01-01 20:06:00+01:00", + "2019-01-01 20:07:00+01:00", + "2019-01-01 20:08:00+01:00", + "2019-01-01 20:09:00+01:00", + "2019-01-01 20:10:00+01:00", + "2019-01-01 20:11:00+01:00", + "2019-01-01 20:12:00+01:00", + "2019-01-01 20:13:00+01:00", + "2019-01-01 20:14:00+01:00", + "2019-01-01 20:15:00+01:00", + "2019-01-01 20:16:00+01:00", + "2019-01-01 20:17:00+01:00", + "2019-01-01 20:18:00+01:00", + "2019-01-01 20:19:00+01:00", + "2019-01-01 20:20:00+01:00", + "2019-01-01 20:21:00+01:00", + "2019-01-01 20:22:00+01:00", + "2019-01-01 20:23:00+01:00", + "2019-01-01 20:24:00+01:00", + "2019-01-01 20:25:00+01:00", + "2019-01-01 20:26:00+01:00", + "2019-01-01 20:27:00+01:00", + "2019-01-01 20:28:00+01:00", + "2019-01-01 20:29:00+01:00", + "2019-01-01 20:30:00+01:00", + "2019-01-01 20:31:00+01:00", + "2019-01-01 20:32:00+01:00", + "2019-01-01 20:33:00+01:00", + "2019-01-01 20:34:00+01:00", + "2019-01-01 20:35:00+01:00", + "2019-01-01 20:36:00+01:00", + "2019-01-01 20:37:00+01:00", + "2019-01-01 20:38:00+01:00", + "2019-01-01 20:39:00+01:00", + "2019-01-01 20:40:00+01:00", + "2019-01-01 20:41:00+01:00", + "2019-01-01 20:42:00+01:00", + "2019-01-01 20:43:00+01:00", + "2019-01-01 20:44:00+01:00", + "2019-01-01 20:45:00+01:00", + "2019-01-01 20:46:00+01:00", + "2019-01-01 20:47:00+01:00", + "2019-01-01 20:48:00+01:00", + "2019-01-01 20:49:00+01:00", + "2019-01-01 20:50:00+01:00", + "2019-01-01 20:51:00+01:00", + "2019-01-01 20:52:00+01:00", + "2019-01-01 20:53:00+01:00", + "2019-01-01 20:54:00+01:00", + "2019-01-01 20:55:00+01:00", + "2019-01-01 20:56:00+01:00", + "2019-01-01 20:57:00+01:00", + "2019-01-01 20:58:00+01:00", + "2019-01-01 20:59:00+01:00", + "2019-01-01 21:00:00+01:00", + "2019-01-01 21:01:00+01:00", + "2019-01-01 21:02:00+01:00", + "2019-01-01 21:03:00+01:00", + "2019-01-01 21:04:00+01:00", + "2019-01-01 21:05:00+01:00", + "2019-01-01 21:06:00+01:00", + "2019-01-01 21:07:00+01:00", + "2019-01-01 21:08:00+01:00", + "2019-01-01 21:09:00+01:00", + "2019-01-01 21:10:00+01:00", + "2019-01-01 21:11:00+01:00", + "2019-01-01 21:12:00+01:00", + "2019-01-01 21:13:00+01:00", + "2019-01-01 21:14:00+01:00", + "2019-01-01 21:15:00+01:00", + "2019-01-01 21:16:00+01:00", + "2019-01-01 21:17:00+01:00", + "2019-01-01 21:18:00+01:00", + "2019-01-01 21:19:00+01:00", + "2019-01-01 21:20:00+01:00", + "2019-01-01 21:21:00+01:00", + "2019-01-01 21:22:00+01:00", + "2019-01-01 21:23:00+01:00", + "2019-01-01 21:24:00+01:00", + "2019-01-01 21:25:00+01:00", + "2019-01-01 21:26:00+01:00", + "2019-01-01 21:27:00+01:00", + "2019-01-01 21:28:00+01:00", + "2019-01-01 21:29:00+01:00", + "2019-01-01 21:30:00+01:00", + "2019-01-01 21:31:00+01:00", + "2019-01-01 21:32:00+01:00", + "2019-01-01 21:33:00+01:00", + "2019-01-01 21:34:00+01:00", + "2019-01-01 21:35:00+01:00", + "2019-01-01 21:36:00+01:00", + "2019-01-01 21:37:00+01:00", + "2019-01-01 21:38:00+01:00", + "2019-01-01 21:39:00+01:00", + "2019-01-01 21:40:00+01:00", + "2019-01-01 21:41:00+01:00", + "2019-01-01 21:42:00+01:00", + "2019-01-01 21:43:00+01:00", + "2019-01-01 21:44:00+01:00", + "2019-01-01 21:45:00+01:00", + "2019-01-01 21:46:00+01:00", + "2019-01-01 21:47:00+01:00", + "2019-01-01 21:48:00+01:00", + "2019-01-01 21:49:00+01:00", + "2019-01-01 21:50:00+01:00", + "2019-01-01 21:51:00+01:00", + "2019-01-01 21:52:00+01:00", + "2019-01-01 21:53:00+01:00", + "2019-01-01 21:54:00+01:00", + "2019-01-01 21:55:00+01:00", + "2019-01-01 21:56:00+01:00", + "2019-01-01 21:57:00+01:00", + "2019-01-01 21:58:00+01:00", + "2019-01-01 21:59:00+01:00", + "2019-01-01 22:00:00+01:00", + "2019-01-01 22:01:00+01:00", + "2019-01-01 22:02:00+01:00", + "2019-01-01 22:03:00+01:00", + "2019-01-01 22:04:00+01:00", + "2019-01-01 22:05:00+01:00", + "2019-01-01 22:06:00+01:00", + "2019-01-01 22:07:00+01:00", + "2019-01-01 22:08:00+01:00", + "2019-01-01 22:09:00+01:00", + "2019-01-01 22:10:00+01:00", + "2019-01-01 22:11:00+01:00", + "2019-01-01 22:12:00+01:00", + "2019-01-01 22:13:00+01:00", + "2019-01-01 22:14:00+01:00", + "2019-01-01 22:15:00+01:00", + "2019-01-01 22:16:00+01:00", + "2019-01-01 22:17:00+01:00", + "2019-01-01 22:18:00+01:00", + "2019-01-01 22:19:00+01:00", + "2019-01-01 22:20:00+01:00", + "2019-01-01 22:21:00+01:00", + "2019-01-01 22:22:00+01:00", + "2019-01-01 22:23:00+01:00", + "2019-01-01 22:24:00+01:00", + "2019-01-01 22:25:00+01:00", + "2019-01-01 22:26:00+01:00", + "2019-01-01 22:27:00+01:00", + "2019-01-01 22:28:00+01:00", + "2019-01-01 22:29:00+01:00", + "2019-01-01 22:30:00+01:00", + "2019-01-01 22:31:00+01:00", + "2019-01-01 22:32:00+01:00", + "2019-01-01 22:33:00+01:00", + "2019-01-01 22:34:00+01:00", + "2019-01-01 22:35:00+01:00", + "2019-01-01 22:36:00+01:00", + "2019-01-01 22:37:00+01:00", + "2019-01-01 22:38:00+01:00", + "2019-01-01 22:39:00+01:00", + "2019-01-01 22:40:00+01:00", + "2019-01-01 22:41:00+01:00", + "2019-01-01 22:42:00+01:00", + "2019-01-01 22:43:00+01:00", + "2019-01-01 22:44:00+01:00", + "2019-01-01 22:45:00+01:00", + "2019-01-01 22:46:00+01:00", + "2019-01-01 22:47:00+01:00", + "2019-01-01 22:48:00+01:00", + "2019-01-01 22:49:00+01:00", + "2019-01-01 22:50:00+01:00", + "2019-01-01 22:51:00+01:00", + "2019-01-01 22:52:00+01:00", + "2019-01-01 22:53:00+01:00", + "2019-01-01 22:54:00+01:00", + "2019-01-01 22:55:00+01:00", + "2019-01-01 22:56:00+01:00", + "2019-01-01 22:57:00+01:00", + "2019-01-01 22:58:00+01:00", + "2019-01-01 22:59:00+01:00", + "2019-01-01 23:00:00+01:00", + "2019-01-01 23:01:00+01:00", + "2019-01-01 23:02:00+01:00", + "2019-01-01 23:03:00+01:00", + "2019-01-01 23:04:00+01:00", + "2019-01-01 23:05:00+01:00", + "2019-01-01 23:06:00+01:00", + "2019-01-01 23:07:00+01:00", + "2019-01-01 23:08:00+01:00", + "2019-01-01 23:09:00+01:00", + "2019-01-01 23:10:00+01:00", + "2019-01-01 23:11:00+01:00", + "2019-01-01 23:12:00+01:00", + "2019-01-01 23:13:00+01:00", + "2019-01-01 23:14:00+01:00", + "2019-01-01 23:15:00+01:00", + "2019-01-01 23:16:00+01:00", + "2019-01-01 23:17:00+01:00", + "2019-01-01 23:18:00+01:00", + "2019-01-01 23:19:00+01:00", + "2019-01-01 23:20:00+01:00", + "2019-01-01 23:21:00+01:00", + "2019-01-01 23:22:00+01:00", + "2019-01-01 23:23:00+01:00", + "2019-01-01 23:24:00+01:00", + "2019-01-01 23:25:00+01:00", + "2019-01-01 23:26:00+01:00", + "2019-01-01 23:27:00+01:00", + "2019-01-01 23:28:00+01:00", + "2019-01-01 23:29:00+01:00", + "2019-01-01 23:30:00+01:00", + "2019-01-01 23:31:00+01:00", + "2019-01-01 23:32:00+01:00", + "2019-01-01 23:33:00+01:00", + "2019-01-01 23:34:00+01:00", + "2019-01-01 23:35:00+01:00", + "2019-01-01 23:36:00+01:00", + "2019-01-01 23:37:00+01:00", + "2019-01-01 23:38:00+01:00", + "2019-01-01 23:39:00+01:00", + "2019-01-01 23:40:00+01:00", + "2019-01-01 23:41:00+01:00", + "2019-01-01 23:42:00+01:00", + "2019-01-01 23:43:00+01:00", + "2019-01-01 23:44:00+01:00", + "2019-01-01 23:45:00+01:00", + "2019-01-01 23:46:00+01:00", + "2019-01-01 23:47:00+01:00", + "2019-01-01 23:48:00+01:00", + "2019-01-01 23:49:00+01:00", + "2019-01-01 23:50:00+01:00", + "2019-01-01 23:51:00+01:00", + "2019-01-01 23:52:00+01:00", + "2019-01-01 23:53:00+01:00", + "2019-01-01 23:54:00+01:00", + "2019-01-01 23:55:00+01:00", + "2019-01-01 23:56:00+01:00", + "2019-01-01 23:57:00+01:00", + "2019-01-01 23:58:00+01:00", + "2019-01-01 23:59:00+01:00" + ], + "xaxis": "x", + "y": [ + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555, + -5.555555555555555 + ], + "yaxis": "y" + }, + { + "line": { + "color": "rgba(55, 128, 191, 1.0)", + "dash": "solid", + "shape": "linear", + "width": 1.3 + }, + "mode": "lines", + "name": "output_MW", + "text": "", + "type": "scatter", + "x": [ + "2019-01-01 00:00:00+01:00", + "2019-01-01 00:01:00+01:00", + "2019-01-01 00:02:00+01:00", + "2019-01-01 00:03:00+01:00", + "2019-01-01 00:04:00+01:00", + "2019-01-01 00:05:00+01:00", + "2019-01-01 00:06:00+01:00", + "2019-01-01 00:07:00+01:00", + "2019-01-01 00:08:00+01:00", + "2019-01-01 00:09:00+01:00", + "2019-01-01 00:10:00+01:00", + "2019-01-01 00:11:00+01:00", + "2019-01-01 00:12:00+01:00", + "2019-01-01 00:13:00+01:00", + "2019-01-01 00:14:00+01:00", + "2019-01-01 00:15:00+01:00", + "2019-01-01 00:16:00+01:00", + "2019-01-01 00:17:00+01:00", + "2019-01-01 00:18:00+01:00", + "2019-01-01 00:19:00+01:00", + "2019-01-01 00:20:00+01:00", + "2019-01-01 00:21:00+01:00", + "2019-01-01 00:22:00+01:00", + "2019-01-01 00:23:00+01:00", + "2019-01-01 00:24:00+01:00", + "2019-01-01 00:25:00+01:00", + "2019-01-01 00:26:00+01:00", + "2019-01-01 00:27:00+01:00", + "2019-01-01 00:28:00+01:00", + "2019-01-01 00:29:00+01:00", + "2019-01-01 00:30:00+01:00", + "2019-01-01 00:31:00+01:00", + "2019-01-01 00:32:00+01:00", + "2019-01-01 00:33:00+01:00", + "2019-01-01 00:34:00+01:00", + "2019-01-01 00:35:00+01:00", + "2019-01-01 00:36:00+01:00", + "2019-01-01 00:37:00+01:00", + "2019-01-01 00:38:00+01:00", + "2019-01-01 00:39:00+01:00", + "2019-01-01 00:40:00+01:00", + "2019-01-01 00:41:00+01:00", + "2019-01-01 00:42:00+01:00", + "2019-01-01 00:43:00+01:00", + "2019-01-01 00:44:00+01:00", + "2019-01-01 00:45:00+01:00", + "2019-01-01 00:46:00+01:00", + "2019-01-01 00:47:00+01:00", + "2019-01-01 00:48:00+01:00", + "2019-01-01 00:49:00+01:00", + "2019-01-01 00:50:00+01:00", + "2019-01-01 00:51:00+01:00", + "2019-01-01 00:52:00+01:00", + "2019-01-01 00:53:00+01:00", + "2019-01-01 00:54:00+01:00", + "2019-01-01 00:55:00+01:00", + "2019-01-01 00:56:00+01:00", + "2019-01-01 00:57:00+01:00", + "2019-01-01 00:58:00+01:00", + "2019-01-01 00:59:00+01:00", + "2019-01-01 01:00:00+01:00", + "2019-01-01 01:01:00+01:00", + "2019-01-01 01:02:00+01:00", + "2019-01-01 01:03:00+01:00", + "2019-01-01 01:04:00+01:00", + "2019-01-01 01:05:00+01:00", + "2019-01-01 01:06:00+01:00", + "2019-01-01 01:07:00+01:00", + "2019-01-01 01:08:00+01:00", + "2019-01-01 01:09:00+01:00", + "2019-01-01 01:10:00+01:00", + "2019-01-01 01:11:00+01:00", + "2019-01-01 01:12:00+01:00", + "2019-01-01 01:13:00+01:00", + "2019-01-01 01:14:00+01:00", + "2019-01-01 01:15:00+01:00", + "2019-01-01 01:16:00+01:00", + "2019-01-01 01:17:00+01:00", + "2019-01-01 01:18:00+01:00", + "2019-01-01 01:19:00+01:00", + "2019-01-01 01:20:00+01:00", + "2019-01-01 01:21:00+01:00", + "2019-01-01 01:22:00+01:00", + "2019-01-01 01:23:00+01:00", + "2019-01-01 01:24:00+01:00", + "2019-01-01 01:25:00+01:00", + "2019-01-01 01:26:00+01:00", + "2019-01-01 01:27:00+01:00", + "2019-01-01 01:28:00+01:00", + "2019-01-01 01:29:00+01:00", + "2019-01-01 01:30:00+01:00", + "2019-01-01 01:31:00+01:00", + "2019-01-01 01:32:00+01:00", + "2019-01-01 01:33:00+01:00", + "2019-01-01 01:34:00+01:00", + "2019-01-01 01:35:00+01:00", + "2019-01-01 01:36:00+01:00", + "2019-01-01 01:37:00+01:00", + "2019-01-01 01:38:00+01:00", + "2019-01-01 01:39:00+01:00", + "2019-01-01 01:40:00+01:00", + "2019-01-01 01:41:00+01:00", + "2019-01-01 01:42:00+01:00", + "2019-01-01 01:43:00+01:00", + "2019-01-01 01:44:00+01:00", + "2019-01-01 01:45:00+01:00", + "2019-01-01 01:46:00+01:00", + "2019-01-01 01:47:00+01:00", + "2019-01-01 01:48:00+01:00", + "2019-01-01 01:49:00+01:00", + "2019-01-01 01:50:00+01:00", + "2019-01-01 01:51:00+01:00", + "2019-01-01 01:52:00+01:00", + "2019-01-01 01:53:00+01:00", + "2019-01-01 01:54:00+01:00", + "2019-01-01 01:55:00+01:00", + "2019-01-01 01:56:00+01:00", + "2019-01-01 01:57:00+01:00", + "2019-01-01 01:58:00+01:00", + "2019-01-01 01:59:00+01:00", + "2019-01-01 02:00:00+01:00", + "2019-01-01 02:01:00+01:00", + "2019-01-01 02:02:00+01:00", + "2019-01-01 02:03:00+01:00", + "2019-01-01 02:04:00+01:00", + "2019-01-01 02:05:00+01:00", + "2019-01-01 02:06:00+01:00", + "2019-01-01 02:07:00+01:00", + "2019-01-01 02:08:00+01:00", + "2019-01-01 02:09:00+01:00", + "2019-01-01 02:10:00+01:00", + "2019-01-01 02:11:00+01:00", + "2019-01-01 02:12:00+01:00", + "2019-01-01 02:13:00+01:00", + "2019-01-01 02:14:00+01:00", + "2019-01-01 02:15:00+01:00", + "2019-01-01 02:16:00+01:00", + "2019-01-01 02:17:00+01:00", + "2019-01-01 02:18:00+01:00", + "2019-01-01 02:19:00+01:00", + "2019-01-01 02:20:00+01:00", + "2019-01-01 02:21:00+01:00", + "2019-01-01 02:22:00+01:00", + "2019-01-01 02:23:00+01:00", + "2019-01-01 02:24:00+01:00", + "2019-01-01 02:25:00+01:00", + "2019-01-01 02:26:00+01:00", + "2019-01-01 02:27:00+01:00", + "2019-01-01 02:28:00+01:00", + "2019-01-01 02:29:00+01:00", + "2019-01-01 02:30:00+01:00", + "2019-01-01 02:31:00+01:00", + "2019-01-01 02:32:00+01:00", + "2019-01-01 02:33:00+01:00", + "2019-01-01 02:34:00+01:00", + "2019-01-01 02:35:00+01:00", + "2019-01-01 02:36:00+01:00", + "2019-01-01 02:37:00+01:00", + "2019-01-01 02:38:00+01:00", + "2019-01-01 02:39:00+01:00", + "2019-01-01 02:40:00+01:00", + "2019-01-01 02:41:00+01:00", + "2019-01-01 02:42:00+01:00", + "2019-01-01 02:43:00+01:00", + "2019-01-01 02:44:00+01:00", + "2019-01-01 02:45:00+01:00", + "2019-01-01 02:46:00+01:00", + "2019-01-01 02:47:00+01:00", + "2019-01-01 02:48:00+01:00", + "2019-01-01 02:49:00+01:00", + "2019-01-01 02:50:00+01:00", + "2019-01-01 02:51:00+01:00", + "2019-01-01 02:52:00+01:00", + "2019-01-01 02:53:00+01:00", + "2019-01-01 02:54:00+01:00", + "2019-01-01 02:55:00+01:00", + "2019-01-01 02:56:00+01:00", + "2019-01-01 02:57:00+01:00", + "2019-01-01 02:58:00+01:00", + "2019-01-01 02:59:00+01:00", + "2019-01-01 03:00:00+01:00", + "2019-01-01 03:01:00+01:00", + "2019-01-01 03:02:00+01:00", + "2019-01-01 03:03:00+01:00", + "2019-01-01 03:04:00+01:00", + "2019-01-01 03:05:00+01:00", + "2019-01-01 03:06:00+01:00", + "2019-01-01 03:07:00+01:00", + "2019-01-01 03:08:00+01:00", + "2019-01-01 03:09:00+01:00", + "2019-01-01 03:10:00+01:00", + "2019-01-01 03:11:00+01:00", + "2019-01-01 03:12:00+01:00", + "2019-01-01 03:13:00+01:00", + "2019-01-01 03:14:00+01:00", + "2019-01-01 03:15:00+01:00", + "2019-01-01 03:16:00+01:00", + "2019-01-01 03:17:00+01:00", + "2019-01-01 03:18:00+01:00", + "2019-01-01 03:19:00+01:00", + "2019-01-01 03:20:00+01:00", + "2019-01-01 03:21:00+01:00", + "2019-01-01 03:22:00+01:00", + "2019-01-01 03:23:00+01:00", + "2019-01-01 03:24:00+01:00", + "2019-01-01 03:25:00+01:00", + "2019-01-01 03:26:00+01:00", + "2019-01-01 03:27:00+01:00", + "2019-01-01 03:28:00+01:00", + "2019-01-01 03:29:00+01:00", + "2019-01-01 03:30:00+01:00", + "2019-01-01 03:31:00+01:00", + "2019-01-01 03:32:00+01:00", + "2019-01-01 03:33:00+01:00", + "2019-01-01 03:34:00+01:00", + "2019-01-01 03:35:00+01:00", + "2019-01-01 03:36:00+01:00", + "2019-01-01 03:37:00+01:00", + "2019-01-01 03:38:00+01:00", + "2019-01-01 03:39:00+01:00", + "2019-01-01 03:40:00+01:00", + "2019-01-01 03:41:00+01:00", + "2019-01-01 03:42:00+01:00", + "2019-01-01 03:43:00+01:00", + "2019-01-01 03:44:00+01:00", + "2019-01-01 03:45:00+01:00", + "2019-01-01 03:46:00+01:00", + "2019-01-01 03:47:00+01:00", + "2019-01-01 03:48:00+01:00", + "2019-01-01 03:49:00+01:00", + "2019-01-01 03:50:00+01:00", + "2019-01-01 03:51:00+01:00", + "2019-01-01 03:52:00+01:00", + "2019-01-01 03:53:00+01:00", + "2019-01-01 03:54:00+01:00", + "2019-01-01 03:55:00+01:00", + "2019-01-01 03:56:00+01:00", + "2019-01-01 03:57:00+01:00", + "2019-01-01 03:58:00+01:00", + "2019-01-01 03:59:00+01:00", + "2019-01-01 04:00:00+01:00", + "2019-01-01 04:01:00+01:00", + "2019-01-01 04:02:00+01:00", + "2019-01-01 04:03:00+01:00", + "2019-01-01 04:04:00+01:00", + "2019-01-01 04:05:00+01:00", + "2019-01-01 04:06:00+01:00", + "2019-01-01 04:07:00+01:00", + "2019-01-01 04:08:00+01:00", + "2019-01-01 04:09:00+01:00", + "2019-01-01 04:10:00+01:00", + "2019-01-01 04:11:00+01:00", + "2019-01-01 04:12:00+01:00", + "2019-01-01 04:13:00+01:00", + "2019-01-01 04:14:00+01:00", + "2019-01-01 04:15:00+01:00", + "2019-01-01 04:16:00+01:00", + "2019-01-01 04:17:00+01:00", + "2019-01-01 04:18:00+01:00", + "2019-01-01 04:19:00+01:00", + "2019-01-01 04:20:00+01:00", + "2019-01-01 04:21:00+01:00", + "2019-01-01 04:22:00+01:00", + "2019-01-01 04:23:00+01:00", + "2019-01-01 04:24:00+01:00", + "2019-01-01 04:25:00+01:00", + "2019-01-01 04:26:00+01:00", + "2019-01-01 04:27:00+01:00", + "2019-01-01 04:28:00+01:00", + "2019-01-01 04:29:00+01:00", + "2019-01-01 04:30:00+01:00", + "2019-01-01 04:31:00+01:00", + "2019-01-01 04:32:00+01:00", + "2019-01-01 04:33:00+01:00", + "2019-01-01 04:34:00+01:00", + "2019-01-01 04:35:00+01:00", + "2019-01-01 04:36:00+01:00", + "2019-01-01 04:37:00+01:00", + "2019-01-01 04:38:00+01:00", + "2019-01-01 04:39:00+01:00", + "2019-01-01 04:40:00+01:00", + "2019-01-01 04:41:00+01:00", + "2019-01-01 04:42:00+01:00", + "2019-01-01 04:43:00+01:00", + "2019-01-01 04:44:00+01:00", + "2019-01-01 04:45:00+01:00", + "2019-01-01 04:46:00+01:00", + "2019-01-01 04:47:00+01:00", + "2019-01-01 04:48:00+01:00", + "2019-01-01 04:49:00+01:00", + "2019-01-01 04:50:00+01:00", + "2019-01-01 04:51:00+01:00", + "2019-01-01 04:52:00+01:00", + "2019-01-01 04:53:00+01:00", + "2019-01-01 04:54:00+01:00", + "2019-01-01 04:55:00+01:00", + "2019-01-01 04:56:00+01:00", + "2019-01-01 04:57:00+01:00", + "2019-01-01 04:58:00+01:00", + "2019-01-01 04:59:00+01:00", + "2019-01-01 05:00:00+01:00", + "2019-01-01 05:01:00+01:00", + "2019-01-01 05:02:00+01:00", + "2019-01-01 05:03:00+01:00", + "2019-01-01 05:04:00+01:00", + "2019-01-01 05:05:00+01:00", + "2019-01-01 05:06:00+01:00", + "2019-01-01 05:07:00+01:00", + "2019-01-01 05:08:00+01:00", + "2019-01-01 05:09:00+01:00", + "2019-01-01 05:10:00+01:00", + "2019-01-01 05:11:00+01:00", + "2019-01-01 05:12:00+01:00", + "2019-01-01 05:13:00+01:00", + "2019-01-01 05:14:00+01:00", + "2019-01-01 05:15:00+01:00", + "2019-01-01 05:16:00+01:00", + "2019-01-01 05:17:00+01:00", + "2019-01-01 05:18:00+01:00", + "2019-01-01 05:19:00+01:00", + "2019-01-01 05:20:00+01:00", + "2019-01-01 05:21:00+01:00", + "2019-01-01 05:22:00+01:00", + "2019-01-01 05:23:00+01:00", + "2019-01-01 05:24:00+01:00", + "2019-01-01 05:25:00+01:00", + "2019-01-01 05:26:00+01:00", + "2019-01-01 05:27:00+01:00", + "2019-01-01 05:28:00+01:00", + "2019-01-01 05:29:00+01:00", + "2019-01-01 05:30:00+01:00", + "2019-01-01 05:31:00+01:00", + "2019-01-01 05:32:00+01:00", + "2019-01-01 05:33:00+01:00", + "2019-01-01 05:34:00+01:00", + "2019-01-01 05:35:00+01:00", + "2019-01-01 05:36:00+01:00", + "2019-01-01 05:37:00+01:00", + "2019-01-01 05:38:00+01:00", + "2019-01-01 05:39:00+01:00", + "2019-01-01 05:40:00+01:00", + "2019-01-01 05:41:00+01:00", + "2019-01-01 05:42:00+01:00", + "2019-01-01 05:43:00+01:00", + "2019-01-01 05:44:00+01:00", + "2019-01-01 05:45:00+01:00", + "2019-01-01 05:46:00+01:00", + "2019-01-01 05:47:00+01:00", + "2019-01-01 05:48:00+01:00", + "2019-01-01 05:49:00+01:00", + "2019-01-01 05:50:00+01:00", + "2019-01-01 05:51:00+01:00", + "2019-01-01 05:52:00+01:00", + "2019-01-01 05:53:00+01:00", + "2019-01-01 05:54:00+01:00", + "2019-01-01 05:55:00+01:00", + "2019-01-01 05:56:00+01:00", + "2019-01-01 05:57:00+01:00", + "2019-01-01 05:58:00+01:00", + "2019-01-01 05:59:00+01:00", + "2019-01-01 06:00:00+01:00", + "2019-01-01 06:01:00+01:00", + "2019-01-01 06:02:00+01:00", + "2019-01-01 06:03:00+01:00", + "2019-01-01 06:04:00+01:00", + "2019-01-01 06:05:00+01:00", + "2019-01-01 06:06:00+01:00", + "2019-01-01 06:07:00+01:00", + "2019-01-01 06:08:00+01:00", + "2019-01-01 06:09:00+01:00", + "2019-01-01 06:10:00+01:00", + "2019-01-01 06:11:00+01:00", + "2019-01-01 06:12:00+01:00", + "2019-01-01 06:13:00+01:00", + "2019-01-01 06:14:00+01:00", + "2019-01-01 06:15:00+01:00", + "2019-01-01 06:16:00+01:00", + "2019-01-01 06:17:00+01:00", + "2019-01-01 06:18:00+01:00", + "2019-01-01 06:19:00+01:00", + "2019-01-01 06:20:00+01:00", + "2019-01-01 06:21:00+01:00", + "2019-01-01 06:22:00+01:00", + "2019-01-01 06:23:00+01:00", + "2019-01-01 06:24:00+01:00", + "2019-01-01 06:25:00+01:00", + "2019-01-01 06:26:00+01:00", + "2019-01-01 06:27:00+01:00", + "2019-01-01 06:28:00+01:00", + "2019-01-01 06:29:00+01:00", + "2019-01-01 06:30:00+01:00", + "2019-01-01 06:31:00+01:00", + "2019-01-01 06:32:00+01:00", + "2019-01-01 06:33:00+01:00", + "2019-01-01 06:34:00+01:00", + "2019-01-01 06:35:00+01:00", + "2019-01-01 06:36:00+01:00", + "2019-01-01 06:37:00+01:00", + "2019-01-01 06:38:00+01:00", + "2019-01-01 06:39:00+01:00", + "2019-01-01 06:40:00+01:00", + "2019-01-01 06:41:00+01:00", + "2019-01-01 06:42:00+01:00", + "2019-01-01 06:43:00+01:00", + "2019-01-01 06:44:00+01:00", + "2019-01-01 06:45:00+01:00", + "2019-01-01 06:46:00+01:00", + "2019-01-01 06:47:00+01:00", + "2019-01-01 06:48:00+01:00", + "2019-01-01 06:49:00+01:00", + "2019-01-01 06:50:00+01:00", + "2019-01-01 06:51:00+01:00", + "2019-01-01 06:52:00+01:00", + "2019-01-01 06:53:00+01:00", + "2019-01-01 06:54:00+01:00", + "2019-01-01 06:55:00+01:00", + "2019-01-01 06:56:00+01:00", + "2019-01-01 06:57:00+01:00", + "2019-01-01 06:58:00+01:00", + "2019-01-01 06:59:00+01:00", + "2019-01-01 07:00:00+01:00", + "2019-01-01 07:01:00+01:00", + "2019-01-01 07:02:00+01:00", + "2019-01-01 07:03:00+01:00", + "2019-01-01 07:04:00+01:00", + "2019-01-01 07:05:00+01:00", + "2019-01-01 07:06:00+01:00", + "2019-01-01 07:07:00+01:00", + "2019-01-01 07:08:00+01:00", + "2019-01-01 07:09:00+01:00", + "2019-01-01 07:10:00+01:00", + "2019-01-01 07:11:00+01:00", + "2019-01-01 07:12:00+01:00", + "2019-01-01 07:13:00+01:00", + "2019-01-01 07:14:00+01:00", + "2019-01-01 07:15:00+01:00", + "2019-01-01 07:16:00+01:00", + "2019-01-01 07:17:00+01:00", + "2019-01-01 07:18:00+01:00", + "2019-01-01 07:19:00+01:00", + "2019-01-01 07:20:00+01:00", + "2019-01-01 07:21:00+01:00", + "2019-01-01 07:22:00+01:00", + "2019-01-01 07:23:00+01:00", + "2019-01-01 07:24:00+01:00", + "2019-01-01 07:25:00+01:00", + "2019-01-01 07:26:00+01:00", + "2019-01-01 07:27:00+01:00", + "2019-01-01 07:28:00+01:00", + "2019-01-01 07:29:00+01:00", + "2019-01-01 07:30:00+01:00", + "2019-01-01 07:31:00+01:00", + "2019-01-01 07:32:00+01:00", + "2019-01-01 07:33:00+01:00", + "2019-01-01 07:34:00+01:00", + "2019-01-01 07:35:00+01:00", + "2019-01-01 07:36:00+01:00", + "2019-01-01 07:37:00+01:00", + "2019-01-01 07:38:00+01:00", + "2019-01-01 07:39:00+01:00", + "2019-01-01 07:40:00+01:00", + "2019-01-01 07:41:00+01:00", + "2019-01-01 07:42:00+01:00", + "2019-01-01 07:43:00+01:00", + "2019-01-01 07:44:00+01:00", + "2019-01-01 07:45:00+01:00", + "2019-01-01 07:46:00+01:00", + "2019-01-01 07:47:00+01:00", + "2019-01-01 07:48:00+01:00", + "2019-01-01 07:49:00+01:00", + "2019-01-01 07:50:00+01:00", + "2019-01-01 07:51:00+01:00", + "2019-01-01 07:52:00+01:00", + "2019-01-01 07:53:00+01:00", + "2019-01-01 07:54:00+01:00", + "2019-01-01 07:55:00+01:00", + "2019-01-01 07:56:00+01:00", + "2019-01-01 07:57:00+01:00", + "2019-01-01 07:58:00+01:00", + "2019-01-01 07:59:00+01:00", + "2019-01-01 08:00:00+01:00", + "2019-01-01 08:01:00+01:00", + "2019-01-01 08:02:00+01:00", + "2019-01-01 08:03:00+01:00", + "2019-01-01 08:04:00+01:00", + "2019-01-01 08:05:00+01:00", + "2019-01-01 08:06:00+01:00", + "2019-01-01 08:07:00+01:00", + "2019-01-01 08:08:00+01:00", + "2019-01-01 08:09:00+01:00", + "2019-01-01 08:10:00+01:00", + "2019-01-01 08:11:00+01:00", + "2019-01-01 08:12:00+01:00", + "2019-01-01 08:13:00+01:00", + "2019-01-01 08:14:00+01:00", + "2019-01-01 08:15:00+01:00", + "2019-01-01 08:16:00+01:00", + "2019-01-01 08:17:00+01:00", + "2019-01-01 08:18:00+01:00", + "2019-01-01 08:19:00+01:00", + "2019-01-01 08:20:00+01:00", + "2019-01-01 08:21:00+01:00", + "2019-01-01 08:22:00+01:00", + "2019-01-01 08:23:00+01:00", + "2019-01-01 08:24:00+01:00", + "2019-01-01 08:25:00+01:00", + "2019-01-01 08:26:00+01:00", + "2019-01-01 08:27:00+01:00", + "2019-01-01 08:28:00+01:00", + "2019-01-01 08:29:00+01:00", + "2019-01-01 08:30:00+01:00", + "2019-01-01 08:31:00+01:00", + "2019-01-01 08:32:00+01:00", + "2019-01-01 08:33:00+01:00", + "2019-01-01 08:34:00+01:00", + "2019-01-01 08:35:00+01:00", + "2019-01-01 08:36:00+01:00", + "2019-01-01 08:37:00+01:00", + "2019-01-01 08:38:00+01:00", + "2019-01-01 08:39:00+01:00", + "2019-01-01 08:40:00+01:00", + "2019-01-01 08:41:00+01:00", + "2019-01-01 08:42:00+01:00", + "2019-01-01 08:43:00+01:00", + "2019-01-01 08:44:00+01:00", + "2019-01-01 08:45:00+01:00", + "2019-01-01 08:46:00+01:00", + "2019-01-01 08:47:00+01:00", + "2019-01-01 08:48:00+01:00", + "2019-01-01 08:49:00+01:00", + "2019-01-01 08:50:00+01:00", + "2019-01-01 08:51:00+01:00", + "2019-01-01 08:52:00+01:00", + "2019-01-01 08:53:00+01:00", + "2019-01-01 08:54:00+01:00", + "2019-01-01 08:55:00+01:00", + "2019-01-01 08:56:00+01:00", + "2019-01-01 08:57:00+01:00", + "2019-01-01 08:58:00+01:00", + "2019-01-01 08:59:00+01:00", + "2019-01-01 09:00:00+01:00", + "2019-01-01 09:01:00+01:00", + "2019-01-01 09:02:00+01:00", + "2019-01-01 09:03:00+01:00", + "2019-01-01 09:04:00+01:00", + "2019-01-01 09:05:00+01:00", + "2019-01-01 09:06:00+01:00", + "2019-01-01 09:07:00+01:00", + "2019-01-01 09:08:00+01:00", + "2019-01-01 09:09:00+01:00", + "2019-01-01 09:10:00+01:00", + "2019-01-01 09:11:00+01:00", + "2019-01-01 09:12:00+01:00", + "2019-01-01 09:13:00+01:00", + "2019-01-01 09:14:00+01:00", + "2019-01-01 09:15:00+01:00", + "2019-01-01 09:16:00+01:00", + "2019-01-01 09:17:00+01:00", + "2019-01-01 09:18:00+01:00", + "2019-01-01 09:19:00+01:00", + "2019-01-01 09:20:00+01:00", + "2019-01-01 09:21:00+01:00", + "2019-01-01 09:22:00+01:00", + "2019-01-01 09:23:00+01:00", + "2019-01-01 09:24:00+01:00", + "2019-01-01 09:25:00+01:00", + "2019-01-01 09:26:00+01:00", + "2019-01-01 09:27:00+01:00", + "2019-01-01 09:28:00+01:00", + "2019-01-01 09:29:00+01:00", + "2019-01-01 09:30:00+01:00", + "2019-01-01 09:31:00+01:00", + "2019-01-01 09:32:00+01:00", + "2019-01-01 09:33:00+01:00", + "2019-01-01 09:34:00+01:00", + "2019-01-01 09:35:00+01:00", + "2019-01-01 09:36:00+01:00", + "2019-01-01 09:37:00+01:00", + "2019-01-01 09:38:00+01:00", + "2019-01-01 09:39:00+01:00", + "2019-01-01 09:40:00+01:00", + "2019-01-01 09:41:00+01:00", + "2019-01-01 09:42:00+01:00", + "2019-01-01 09:43:00+01:00", + "2019-01-01 09:44:00+01:00", + "2019-01-01 09:45:00+01:00", + "2019-01-01 09:46:00+01:00", + "2019-01-01 09:47:00+01:00", + "2019-01-01 09:48:00+01:00", + "2019-01-01 09:49:00+01:00", + "2019-01-01 09:50:00+01:00", + "2019-01-01 09:51:00+01:00", + "2019-01-01 09:52:00+01:00", + "2019-01-01 09:53:00+01:00", + "2019-01-01 09:54:00+01:00", + "2019-01-01 09:55:00+01:00", + "2019-01-01 09:56:00+01:00", + "2019-01-01 09:57:00+01:00", + "2019-01-01 09:58:00+01:00", + "2019-01-01 09:59:00+01:00", + "2019-01-01 10:00:00+01:00", + "2019-01-01 10:01:00+01:00", + "2019-01-01 10:02:00+01:00", + "2019-01-01 10:03:00+01:00", + "2019-01-01 10:04:00+01:00", + "2019-01-01 10:05:00+01:00", + "2019-01-01 10:06:00+01:00", + "2019-01-01 10:07:00+01:00", + "2019-01-01 10:08:00+01:00", + "2019-01-01 10:09:00+01:00", + "2019-01-01 10:10:00+01:00", + "2019-01-01 10:11:00+01:00", + "2019-01-01 10:12:00+01:00", + "2019-01-01 10:13:00+01:00", + "2019-01-01 10:14:00+01:00", + "2019-01-01 10:15:00+01:00", + "2019-01-01 10:16:00+01:00", + "2019-01-01 10:17:00+01:00", + "2019-01-01 10:18:00+01:00", + "2019-01-01 10:19:00+01:00", + "2019-01-01 10:20:00+01:00", + "2019-01-01 10:21:00+01:00", + "2019-01-01 10:22:00+01:00", + "2019-01-01 10:23:00+01:00", + "2019-01-01 10:24:00+01:00", + "2019-01-01 10:25:00+01:00", + "2019-01-01 10:26:00+01:00", + "2019-01-01 10:27:00+01:00", + "2019-01-01 10:28:00+01:00", + "2019-01-01 10:29:00+01:00", + "2019-01-01 10:30:00+01:00", + "2019-01-01 10:31:00+01:00", + "2019-01-01 10:32:00+01:00", + "2019-01-01 10:33:00+01:00", + "2019-01-01 10:34:00+01:00", + "2019-01-01 10:35:00+01:00", + "2019-01-01 10:36:00+01:00", + "2019-01-01 10:37:00+01:00", + "2019-01-01 10:38:00+01:00", + "2019-01-01 10:39:00+01:00", + "2019-01-01 10:40:00+01:00", + "2019-01-01 10:41:00+01:00", + "2019-01-01 10:42:00+01:00", + "2019-01-01 10:43:00+01:00", + "2019-01-01 10:44:00+01:00", + "2019-01-01 10:45:00+01:00", + "2019-01-01 10:46:00+01:00", + "2019-01-01 10:47:00+01:00", + "2019-01-01 10:48:00+01:00", + "2019-01-01 10:49:00+01:00", + "2019-01-01 10:50:00+01:00", + "2019-01-01 10:51:00+01:00", + "2019-01-01 10:52:00+01:00", + "2019-01-01 10:53:00+01:00", + "2019-01-01 10:54:00+01:00", + "2019-01-01 10:55:00+01:00", + "2019-01-01 10:56:00+01:00", + "2019-01-01 10:57:00+01:00", + "2019-01-01 10:58:00+01:00", + "2019-01-01 10:59:00+01:00", + "2019-01-01 11:00:00+01:00", + "2019-01-01 11:01:00+01:00", + "2019-01-01 11:02:00+01:00", + "2019-01-01 11:03:00+01:00", + "2019-01-01 11:04:00+01:00", + "2019-01-01 11:05:00+01:00", + "2019-01-01 11:06:00+01:00", + "2019-01-01 11:07:00+01:00", + "2019-01-01 11:08:00+01:00", + "2019-01-01 11:09:00+01:00", + "2019-01-01 11:10:00+01:00", + "2019-01-01 11:11:00+01:00", + "2019-01-01 11:12:00+01:00", + "2019-01-01 11:13:00+01:00", + "2019-01-01 11:14:00+01:00", + "2019-01-01 11:15:00+01:00", + "2019-01-01 11:16:00+01:00", + "2019-01-01 11:17:00+01:00", + "2019-01-01 11:18:00+01:00", + "2019-01-01 11:19:00+01:00", + "2019-01-01 11:20:00+01:00", + "2019-01-01 11:21:00+01:00", + "2019-01-01 11:22:00+01:00", + "2019-01-01 11:23:00+01:00", + "2019-01-01 11:24:00+01:00", + "2019-01-01 11:25:00+01:00", + "2019-01-01 11:26:00+01:00", + "2019-01-01 11:27:00+01:00", + "2019-01-01 11:28:00+01:00", + "2019-01-01 11:29:00+01:00", + "2019-01-01 11:30:00+01:00", + "2019-01-01 11:31:00+01:00", + "2019-01-01 11:32:00+01:00", + "2019-01-01 11:33:00+01:00", + "2019-01-01 11:34:00+01:00", + "2019-01-01 11:35:00+01:00", + "2019-01-01 11:36:00+01:00", + "2019-01-01 11:37:00+01:00", + "2019-01-01 11:38:00+01:00", + "2019-01-01 11:39:00+01:00", + "2019-01-01 11:40:00+01:00", + "2019-01-01 11:41:00+01:00", + "2019-01-01 11:42:00+01:00", + "2019-01-01 11:43:00+01:00", + "2019-01-01 11:44:00+01:00", + "2019-01-01 11:45:00+01:00", + "2019-01-01 11:46:00+01:00", + "2019-01-01 11:47:00+01:00", + "2019-01-01 11:48:00+01:00", + "2019-01-01 11:49:00+01:00", + "2019-01-01 11:50:00+01:00", + "2019-01-01 11:51:00+01:00", + "2019-01-01 11:52:00+01:00", + "2019-01-01 11:53:00+01:00", + "2019-01-01 11:54:00+01:00", + "2019-01-01 11:55:00+01:00", + "2019-01-01 11:56:00+01:00", + "2019-01-01 11:57:00+01:00", + "2019-01-01 11:58:00+01:00", + "2019-01-01 11:59:00+01:00", + "2019-01-01 12:00:00+01:00", + "2019-01-01 12:01:00+01:00", + "2019-01-01 12:02:00+01:00", + "2019-01-01 12:03:00+01:00", + "2019-01-01 12:04:00+01:00", + "2019-01-01 12:05:00+01:00", + "2019-01-01 12:06:00+01:00", + "2019-01-01 12:07:00+01:00", + "2019-01-01 12:08:00+01:00", + "2019-01-01 12:09:00+01:00", + "2019-01-01 12:10:00+01:00", + "2019-01-01 12:11:00+01:00", + "2019-01-01 12:12:00+01:00", + "2019-01-01 12:13:00+01:00", + "2019-01-01 12:14:00+01:00", + "2019-01-01 12:15:00+01:00", + "2019-01-01 12:16:00+01:00", + "2019-01-01 12:17:00+01:00", + "2019-01-01 12:18:00+01:00", + "2019-01-01 12:19:00+01:00", + "2019-01-01 12:20:00+01:00", + "2019-01-01 12:21:00+01:00", + "2019-01-01 12:22:00+01:00", + "2019-01-01 12:23:00+01:00", + "2019-01-01 12:24:00+01:00", + "2019-01-01 12:25:00+01:00", + "2019-01-01 12:26:00+01:00", + "2019-01-01 12:27:00+01:00", + "2019-01-01 12:28:00+01:00", + "2019-01-01 12:29:00+01:00", + "2019-01-01 12:30:00+01:00", + "2019-01-01 12:31:00+01:00", + "2019-01-01 12:32:00+01:00", + "2019-01-01 12:33:00+01:00", + "2019-01-01 12:34:00+01:00", + "2019-01-01 12:35:00+01:00", + "2019-01-01 12:36:00+01:00", + "2019-01-01 12:37:00+01:00", + "2019-01-01 12:38:00+01:00", + "2019-01-01 12:39:00+01:00", + "2019-01-01 12:40:00+01:00", + "2019-01-01 12:41:00+01:00", + "2019-01-01 12:42:00+01:00", + "2019-01-01 12:43:00+01:00", + "2019-01-01 12:44:00+01:00", + "2019-01-01 12:45:00+01:00", + "2019-01-01 12:46:00+01:00", + "2019-01-01 12:47:00+01:00", + "2019-01-01 12:48:00+01:00", + "2019-01-01 12:49:00+01:00", + "2019-01-01 12:50:00+01:00", + "2019-01-01 12:51:00+01:00", + "2019-01-01 12:52:00+01:00", + "2019-01-01 12:53:00+01:00", + "2019-01-01 12:54:00+01:00", + "2019-01-01 12:55:00+01:00", + "2019-01-01 12:56:00+01:00", + "2019-01-01 12:57:00+01:00", + "2019-01-01 12:58:00+01:00", + "2019-01-01 12:59:00+01:00", + "2019-01-01 13:00:00+01:00", + "2019-01-01 13:01:00+01:00", + "2019-01-01 13:02:00+01:00", + "2019-01-01 13:03:00+01:00", + "2019-01-01 13:04:00+01:00", + "2019-01-01 13:05:00+01:00", + "2019-01-01 13:06:00+01:00", + "2019-01-01 13:07:00+01:00", + "2019-01-01 13:08:00+01:00", + "2019-01-01 13:09:00+01:00", + "2019-01-01 13:10:00+01:00", + "2019-01-01 13:11:00+01:00", + "2019-01-01 13:12:00+01:00", + "2019-01-01 13:13:00+01:00", + "2019-01-01 13:14:00+01:00", + "2019-01-01 13:15:00+01:00", + "2019-01-01 13:16:00+01:00", + "2019-01-01 13:17:00+01:00", + "2019-01-01 13:18:00+01:00", + "2019-01-01 13:19:00+01:00", + "2019-01-01 13:20:00+01:00", + "2019-01-01 13:21:00+01:00", + "2019-01-01 13:22:00+01:00", + "2019-01-01 13:23:00+01:00", + "2019-01-01 13:24:00+01:00", + "2019-01-01 13:25:00+01:00", + "2019-01-01 13:26:00+01:00", + "2019-01-01 13:27:00+01:00", + "2019-01-01 13:28:00+01:00", + "2019-01-01 13:29:00+01:00", + "2019-01-01 13:30:00+01:00", + "2019-01-01 13:31:00+01:00", + "2019-01-01 13:32:00+01:00", + "2019-01-01 13:33:00+01:00", + "2019-01-01 13:34:00+01:00", + "2019-01-01 13:35:00+01:00", + "2019-01-01 13:36:00+01:00", + "2019-01-01 13:37:00+01:00", + "2019-01-01 13:38:00+01:00", + "2019-01-01 13:39:00+01:00", + "2019-01-01 13:40:00+01:00", + "2019-01-01 13:41:00+01:00", + "2019-01-01 13:42:00+01:00", + "2019-01-01 13:43:00+01:00", + "2019-01-01 13:44:00+01:00", + "2019-01-01 13:45:00+01:00", + "2019-01-01 13:46:00+01:00", + "2019-01-01 13:47:00+01:00", + "2019-01-01 13:48:00+01:00", + "2019-01-01 13:49:00+01:00", + "2019-01-01 13:50:00+01:00", + "2019-01-01 13:51:00+01:00", + "2019-01-01 13:52:00+01:00", + "2019-01-01 13:53:00+01:00", + "2019-01-01 13:54:00+01:00", + "2019-01-01 13:55:00+01:00", + "2019-01-01 13:56:00+01:00", + "2019-01-01 13:57:00+01:00", + "2019-01-01 13:58:00+01:00", + "2019-01-01 13:59:00+01:00", + "2019-01-01 14:00:00+01:00", + "2019-01-01 14:01:00+01:00", + "2019-01-01 14:02:00+01:00", + "2019-01-01 14:03:00+01:00", + "2019-01-01 14:04:00+01:00", + "2019-01-01 14:05:00+01:00", + "2019-01-01 14:06:00+01:00", + "2019-01-01 14:07:00+01:00", + "2019-01-01 14:08:00+01:00", + "2019-01-01 14:09:00+01:00", + "2019-01-01 14:10:00+01:00", + "2019-01-01 14:11:00+01:00", + "2019-01-01 14:12:00+01:00", + "2019-01-01 14:13:00+01:00", + "2019-01-01 14:14:00+01:00", + "2019-01-01 14:15:00+01:00", + "2019-01-01 14:16:00+01:00", + "2019-01-01 14:17:00+01:00", + "2019-01-01 14:18:00+01:00", + "2019-01-01 14:19:00+01:00", + "2019-01-01 14:20:00+01:00", + "2019-01-01 14:21:00+01:00", + "2019-01-01 14:22:00+01:00", + "2019-01-01 14:23:00+01:00", + "2019-01-01 14:24:00+01:00", + "2019-01-01 14:25:00+01:00", + "2019-01-01 14:26:00+01:00", + "2019-01-01 14:27:00+01:00", + "2019-01-01 14:28:00+01:00", + "2019-01-01 14:29:00+01:00", + "2019-01-01 14:30:00+01:00", + "2019-01-01 14:31:00+01:00", + "2019-01-01 14:32:00+01:00", + "2019-01-01 14:33:00+01:00", + "2019-01-01 14:34:00+01:00", + "2019-01-01 14:35:00+01:00", + "2019-01-01 14:36:00+01:00", + "2019-01-01 14:37:00+01:00", + "2019-01-01 14:38:00+01:00", + "2019-01-01 14:39:00+01:00", + "2019-01-01 14:40:00+01:00", + "2019-01-01 14:41:00+01:00", + "2019-01-01 14:42:00+01:00", + "2019-01-01 14:43:00+01:00", + "2019-01-01 14:44:00+01:00", + "2019-01-01 14:45:00+01:00", + "2019-01-01 14:46:00+01:00", + "2019-01-01 14:47:00+01:00", + "2019-01-01 14:48:00+01:00", + "2019-01-01 14:49:00+01:00", + "2019-01-01 14:50:00+01:00", + "2019-01-01 14:51:00+01:00", + "2019-01-01 14:52:00+01:00", + "2019-01-01 14:53:00+01:00", + "2019-01-01 14:54:00+01:00", + "2019-01-01 14:55:00+01:00", + "2019-01-01 14:56:00+01:00", + "2019-01-01 14:57:00+01:00", + "2019-01-01 14:58:00+01:00", + "2019-01-01 14:59:00+01:00", + "2019-01-01 15:00:00+01:00", + "2019-01-01 15:01:00+01:00", + "2019-01-01 15:02:00+01:00", + "2019-01-01 15:03:00+01:00", + "2019-01-01 15:04:00+01:00", + "2019-01-01 15:05:00+01:00", + "2019-01-01 15:06:00+01:00", + "2019-01-01 15:07:00+01:00", + "2019-01-01 15:08:00+01:00", + "2019-01-01 15:09:00+01:00", + "2019-01-01 15:10:00+01:00", + "2019-01-01 15:11:00+01:00", + "2019-01-01 15:12:00+01:00", + "2019-01-01 15:13:00+01:00", + "2019-01-01 15:14:00+01:00", + "2019-01-01 15:15:00+01:00", + "2019-01-01 15:16:00+01:00", + "2019-01-01 15:17:00+01:00", + "2019-01-01 15:18:00+01:00", + "2019-01-01 15:19:00+01:00", + "2019-01-01 15:20:00+01:00", + "2019-01-01 15:21:00+01:00", + "2019-01-01 15:22:00+01:00", + "2019-01-01 15:23:00+01:00", + "2019-01-01 15:24:00+01:00", + "2019-01-01 15:25:00+01:00", + "2019-01-01 15:26:00+01:00", + "2019-01-01 15:27:00+01:00", + "2019-01-01 15:28:00+01:00", + "2019-01-01 15:29:00+01:00", + "2019-01-01 15:30:00+01:00", + "2019-01-01 15:31:00+01:00", + "2019-01-01 15:32:00+01:00", + "2019-01-01 15:33:00+01:00", + "2019-01-01 15:34:00+01:00", + "2019-01-01 15:35:00+01:00", + "2019-01-01 15:36:00+01:00", + "2019-01-01 15:37:00+01:00", + "2019-01-01 15:38:00+01:00", + "2019-01-01 15:39:00+01:00", + "2019-01-01 15:40:00+01:00", + "2019-01-01 15:41:00+01:00", + "2019-01-01 15:42:00+01:00", + "2019-01-01 15:43:00+01:00", + "2019-01-01 15:44:00+01:00", + "2019-01-01 15:45:00+01:00", + "2019-01-01 15:46:00+01:00", + "2019-01-01 15:47:00+01:00", + "2019-01-01 15:48:00+01:00", + "2019-01-01 15:49:00+01:00", + "2019-01-01 15:50:00+01:00", + "2019-01-01 15:51:00+01:00", + "2019-01-01 15:52:00+01:00", + "2019-01-01 15:53:00+01:00", + "2019-01-01 15:54:00+01:00", + "2019-01-01 15:55:00+01:00", + "2019-01-01 15:56:00+01:00", + "2019-01-01 15:57:00+01:00", + "2019-01-01 15:58:00+01:00", + "2019-01-01 15:59:00+01:00", + "2019-01-01 16:00:00+01:00", + "2019-01-01 16:01:00+01:00", + "2019-01-01 16:02:00+01:00", + "2019-01-01 16:03:00+01:00", + "2019-01-01 16:04:00+01:00", + "2019-01-01 16:05:00+01:00", + "2019-01-01 16:06:00+01:00", + "2019-01-01 16:07:00+01:00", + "2019-01-01 16:08:00+01:00", + "2019-01-01 16:09:00+01:00", + "2019-01-01 16:10:00+01:00", + "2019-01-01 16:11:00+01:00", + "2019-01-01 16:12:00+01:00", + "2019-01-01 16:13:00+01:00", + "2019-01-01 16:14:00+01:00", + "2019-01-01 16:15:00+01:00", + "2019-01-01 16:16:00+01:00", + "2019-01-01 16:17:00+01:00", + "2019-01-01 16:18:00+01:00", + "2019-01-01 16:19:00+01:00", + "2019-01-01 16:20:00+01:00", + "2019-01-01 16:21:00+01:00", + "2019-01-01 16:22:00+01:00", + "2019-01-01 16:23:00+01:00", + "2019-01-01 16:24:00+01:00", + "2019-01-01 16:25:00+01:00", + "2019-01-01 16:26:00+01:00", + "2019-01-01 16:27:00+01:00", + "2019-01-01 16:28:00+01:00", + "2019-01-01 16:29:00+01:00", + "2019-01-01 16:30:00+01:00", + "2019-01-01 16:31:00+01:00", + "2019-01-01 16:32:00+01:00", + "2019-01-01 16:33:00+01:00", + "2019-01-01 16:34:00+01:00", + "2019-01-01 16:35:00+01:00", + "2019-01-01 16:36:00+01:00", + "2019-01-01 16:37:00+01:00", + "2019-01-01 16:38:00+01:00", + "2019-01-01 16:39:00+01:00", + "2019-01-01 16:40:00+01:00", + "2019-01-01 16:41:00+01:00", + "2019-01-01 16:42:00+01:00", + "2019-01-01 16:43:00+01:00", + "2019-01-01 16:44:00+01:00", + "2019-01-01 16:45:00+01:00", + "2019-01-01 16:46:00+01:00", + "2019-01-01 16:47:00+01:00", + "2019-01-01 16:48:00+01:00", + "2019-01-01 16:49:00+01:00", + "2019-01-01 16:50:00+01:00", + "2019-01-01 16:51:00+01:00", + "2019-01-01 16:52:00+01:00", + "2019-01-01 16:53:00+01:00", + "2019-01-01 16:54:00+01:00", + "2019-01-01 16:55:00+01:00", + "2019-01-01 16:56:00+01:00", + "2019-01-01 16:57:00+01:00", + "2019-01-01 16:58:00+01:00", + "2019-01-01 16:59:00+01:00", + "2019-01-01 17:00:00+01:00", + "2019-01-01 17:01:00+01:00", + "2019-01-01 17:02:00+01:00", + "2019-01-01 17:03:00+01:00", + "2019-01-01 17:04:00+01:00", + "2019-01-01 17:05:00+01:00", + "2019-01-01 17:06:00+01:00", + "2019-01-01 17:07:00+01:00", + "2019-01-01 17:08:00+01:00", + "2019-01-01 17:09:00+01:00", + "2019-01-01 17:10:00+01:00", + "2019-01-01 17:11:00+01:00", + "2019-01-01 17:12:00+01:00", + "2019-01-01 17:13:00+01:00", + "2019-01-01 17:14:00+01:00", + "2019-01-01 17:15:00+01:00", + "2019-01-01 17:16:00+01:00", + "2019-01-01 17:17:00+01:00", + "2019-01-01 17:18:00+01:00", + "2019-01-01 17:19:00+01:00", + "2019-01-01 17:20:00+01:00", + "2019-01-01 17:21:00+01:00", + "2019-01-01 17:22:00+01:00", + "2019-01-01 17:23:00+01:00", + "2019-01-01 17:24:00+01:00", + "2019-01-01 17:25:00+01:00", + "2019-01-01 17:26:00+01:00", + "2019-01-01 17:27:00+01:00", + "2019-01-01 17:28:00+01:00", + "2019-01-01 17:29:00+01:00", + "2019-01-01 17:30:00+01:00", + "2019-01-01 17:31:00+01:00", + "2019-01-01 17:32:00+01:00", + "2019-01-01 17:33:00+01:00", + "2019-01-01 17:34:00+01:00", + "2019-01-01 17:35:00+01:00", + "2019-01-01 17:36:00+01:00", + "2019-01-01 17:37:00+01:00", + "2019-01-01 17:38:00+01:00", + "2019-01-01 17:39:00+01:00", + "2019-01-01 17:40:00+01:00", + "2019-01-01 17:41:00+01:00", + "2019-01-01 17:42:00+01:00", + "2019-01-01 17:43:00+01:00", + "2019-01-01 17:44:00+01:00", + "2019-01-01 17:45:00+01:00", + "2019-01-01 17:46:00+01:00", + "2019-01-01 17:47:00+01:00", + "2019-01-01 17:48:00+01:00", + "2019-01-01 17:49:00+01:00", + "2019-01-01 17:50:00+01:00", + "2019-01-01 17:51:00+01:00", + "2019-01-01 17:52:00+01:00", + "2019-01-01 17:53:00+01:00", + "2019-01-01 17:54:00+01:00", + "2019-01-01 17:55:00+01:00", + "2019-01-01 17:56:00+01:00", + "2019-01-01 17:57:00+01:00", + "2019-01-01 17:58:00+01:00", + "2019-01-01 17:59:00+01:00", + "2019-01-01 18:00:00+01:00", + "2019-01-01 18:01:00+01:00", + "2019-01-01 18:02:00+01:00", + "2019-01-01 18:03:00+01:00", + "2019-01-01 18:04:00+01:00", + "2019-01-01 18:05:00+01:00", + "2019-01-01 18:06:00+01:00", + "2019-01-01 18:07:00+01:00", + "2019-01-01 18:08:00+01:00", + "2019-01-01 18:09:00+01:00", + "2019-01-01 18:10:00+01:00", + "2019-01-01 18:11:00+01:00", + "2019-01-01 18:12:00+01:00", + "2019-01-01 18:13:00+01:00", + "2019-01-01 18:14:00+01:00", + "2019-01-01 18:15:00+01:00", + "2019-01-01 18:16:00+01:00", + "2019-01-01 18:17:00+01:00", + "2019-01-01 18:18:00+01:00", + "2019-01-01 18:19:00+01:00", + "2019-01-01 18:20:00+01:00", + "2019-01-01 18:21:00+01:00", + "2019-01-01 18:22:00+01:00", + "2019-01-01 18:23:00+01:00", + "2019-01-01 18:24:00+01:00", + "2019-01-01 18:25:00+01:00", + "2019-01-01 18:26:00+01:00", + "2019-01-01 18:27:00+01:00", + "2019-01-01 18:28:00+01:00", + "2019-01-01 18:29:00+01:00", + "2019-01-01 18:30:00+01:00", + "2019-01-01 18:31:00+01:00", + "2019-01-01 18:32:00+01:00", + "2019-01-01 18:33:00+01:00", + "2019-01-01 18:34:00+01:00", + "2019-01-01 18:35:00+01:00", + "2019-01-01 18:36:00+01:00", + "2019-01-01 18:37:00+01:00", + "2019-01-01 18:38:00+01:00", + "2019-01-01 18:39:00+01:00", + "2019-01-01 18:40:00+01:00", + "2019-01-01 18:41:00+01:00", + "2019-01-01 18:42:00+01:00", + "2019-01-01 18:43:00+01:00", + "2019-01-01 18:44:00+01:00", + "2019-01-01 18:45:00+01:00", + "2019-01-01 18:46:00+01:00", + "2019-01-01 18:47:00+01:00", + "2019-01-01 18:48:00+01:00", + "2019-01-01 18:49:00+01:00", + "2019-01-01 18:50:00+01:00", + "2019-01-01 18:51:00+01:00", + "2019-01-01 18:52:00+01:00", + "2019-01-01 18:53:00+01:00", + "2019-01-01 18:54:00+01:00", + "2019-01-01 18:55:00+01:00", + "2019-01-01 18:56:00+01:00", + "2019-01-01 18:57:00+01:00", + "2019-01-01 18:58:00+01:00", + "2019-01-01 18:59:00+01:00", + "2019-01-01 19:00:00+01:00", + "2019-01-01 19:01:00+01:00", + "2019-01-01 19:02:00+01:00", + "2019-01-01 19:03:00+01:00", + "2019-01-01 19:04:00+01:00", + "2019-01-01 19:05:00+01:00", + "2019-01-01 19:06:00+01:00", + "2019-01-01 19:07:00+01:00", + "2019-01-01 19:08:00+01:00", + "2019-01-01 19:09:00+01:00", + "2019-01-01 19:10:00+01:00", + "2019-01-01 19:11:00+01:00", + "2019-01-01 19:12:00+01:00", + "2019-01-01 19:13:00+01:00", + "2019-01-01 19:14:00+01:00", + "2019-01-01 19:15:00+01:00", + "2019-01-01 19:16:00+01:00", + "2019-01-01 19:17:00+01:00", + "2019-01-01 19:18:00+01:00", + "2019-01-01 19:19:00+01:00", + "2019-01-01 19:20:00+01:00", + "2019-01-01 19:21:00+01:00", + "2019-01-01 19:22:00+01:00", + "2019-01-01 19:23:00+01:00", + "2019-01-01 19:24:00+01:00", + "2019-01-01 19:25:00+01:00", + "2019-01-01 19:26:00+01:00", + "2019-01-01 19:27:00+01:00", + "2019-01-01 19:28:00+01:00", + "2019-01-01 19:29:00+01:00", + "2019-01-01 19:30:00+01:00", + "2019-01-01 19:31:00+01:00", + "2019-01-01 19:32:00+01:00", + "2019-01-01 19:33:00+01:00", + "2019-01-01 19:34:00+01:00", + "2019-01-01 19:35:00+01:00", + "2019-01-01 19:36:00+01:00", + "2019-01-01 19:37:00+01:00", + "2019-01-01 19:38:00+01:00", + "2019-01-01 19:39:00+01:00", + "2019-01-01 19:40:00+01:00", + "2019-01-01 19:41:00+01:00", + "2019-01-01 19:42:00+01:00", + "2019-01-01 19:43:00+01:00", + "2019-01-01 19:44:00+01:00", + "2019-01-01 19:45:00+01:00", + "2019-01-01 19:46:00+01:00", + "2019-01-01 19:47:00+01:00", + "2019-01-01 19:48:00+01:00", + "2019-01-01 19:49:00+01:00", + "2019-01-01 19:50:00+01:00", + "2019-01-01 19:51:00+01:00", + "2019-01-01 19:52:00+01:00", + "2019-01-01 19:53:00+01:00", + "2019-01-01 19:54:00+01:00", + "2019-01-01 19:55:00+01:00", + "2019-01-01 19:56:00+01:00", + "2019-01-01 19:57:00+01:00", + "2019-01-01 19:58:00+01:00", + "2019-01-01 19:59:00+01:00", + "2019-01-01 20:00:00+01:00", + "2019-01-01 20:01:00+01:00", + "2019-01-01 20:02:00+01:00", + "2019-01-01 20:03:00+01:00", + "2019-01-01 20:04:00+01:00", + "2019-01-01 20:05:00+01:00", + "2019-01-01 20:06:00+01:00", + "2019-01-01 20:07:00+01:00", + "2019-01-01 20:08:00+01:00", + "2019-01-01 20:09:00+01:00", + "2019-01-01 20:10:00+01:00", + "2019-01-01 20:11:00+01:00", + "2019-01-01 20:12:00+01:00", + "2019-01-01 20:13:00+01:00", + "2019-01-01 20:14:00+01:00", + "2019-01-01 20:15:00+01:00", + "2019-01-01 20:16:00+01:00", + "2019-01-01 20:17:00+01:00", + "2019-01-01 20:18:00+01:00", + "2019-01-01 20:19:00+01:00", + "2019-01-01 20:20:00+01:00", + "2019-01-01 20:21:00+01:00", + "2019-01-01 20:22:00+01:00", + "2019-01-01 20:23:00+01:00", + "2019-01-01 20:24:00+01:00", + "2019-01-01 20:25:00+01:00", + "2019-01-01 20:26:00+01:00", + "2019-01-01 20:27:00+01:00", + "2019-01-01 20:28:00+01:00", + "2019-01-01 20:29:00+01:00", + "2019-01-01 20:30:00+01:00", + "2019-01-01 20:31:00+01:00", + "2019-01-01 20:32:00+01:00", + "2019-01-01 20:33:00+01:00", + "2019-01-01 20:34:00+01:00", + "2019-01-01 20:35:00+01:00", + "2019-01-01 20:36:00+01:00", + "2019-01-01 20:37:00+01:00", + "2019-01-01 20:38:00+01:00", + "2019-01-01 20:39:00+01:00", + "2019-01-01 20:40:00+01:00", + "2019-01-01 20:41:00+01:00", + "2019-01-01 20:42:00+01:00", + "2019-01-01 20:43:00+01:00", + "2019-01-01 20:44:00+01:00", + "2019-01-01 20:45:00+01:00", + "2019-01-01 20:46:00+01:00", + "2019-01-01 20:47:00+01:00", + "2019-01-01 20:48:00+01:00", + "2019-01-01 20:49:00+01:00", + "2019-01-01 20:50:00+01:00", + "2019-01-01 20:51:00+01:00", + "2019-01-01 20:52:00+01:00", + "2019-01-01 20:53:00+01:00", + "2019-01-01 20:54:00+01:00", + "2019-01-01 20:55:00+01:00", + "2019-01-01 20:56:00+01:00", + "2019-01-01 20:57:00+01:00", + "2019-01-01 20:58:00+01:00", + "2019-01-01 20:59:00+01:00", + "2019-01-01 21:00:00+01:00", + "2019-01-01 21:01:00+01:00", + "2019-01-01 21:02:00+01:00", + "2019-01-01 21:03:00+01:00", + "2019-01-01 21:04:00+01:00", + "2019-01-01 21:05:00+01:00", + "2019-01-01 21:06:00+01:00", + "2019-01-01 21:07:00+01:00", + "2019-01-01 21:08:00+01:00", + "2019-01-01 21:09:00+01:00", + "2019-01-01 21:10:00+01:00", + "2019-01-01 21:11:00+01:00", + "2019-01-01 21:12:00+01:00", + "2019-01-01 21:13:00+01:00", + "2019-01-01 21:14:00+01:00", + "2019-01-01 21:15:00+01:00", + "2019-01-01 21:16:00+01:00", + "2019-01-01 21:17:00+01:00", + "2019-01-01 21:18:00+01:00", + "2019-01-01 21:19:00+01:00", + "2019-01-01 21:20:00+01:00", + "2019-01-01 21:21:00+01:00", + "2019-01-01 21:22:00+01:00", + "2019-01-01 21:23:00+01:00", + "2019-01-01 21:24:00+01:00", + "2019-01-01 21:25:00+01:00", + "2019-01-01 21:26:00+01:00", + "2019-01-01 21:27:00+01:00", + "2019-01-01 21:28:00+01:00", + "2019-01-01 21:29:00+01:00", + "2019-01-01 21:30:00+01:00", + "2019-01-01 21:31:00+01:00", + "2019-01-01 21:32:00+01:00", + "2019-01-01 21:33:00+01:00", + "2019-01-01 21:34:00+01:00", + "2019-01-01 21:35:00+01:00", + "2019-01-01 21:36:00+01:00", + "2019-01-01 21:37:00+01:00", + "2019-01-01 21:38:00+01:00", + "2019-01-01 21:39:00+01:00", + "2019-01-01 21:40:00+01:00", + "2019-01-01 21:41:00+01:00", + "2019-01-01 21:42:00+01:00", + "2019-01-01 21:43:00+01:00", + "2019-01-01 21:44:00+01:00", + "2019-01-01 21:45:00+01:00", + "2019-01-01 21:46:00+01:00", + "2019-01-01 21:47:00+01:00", + "2019-01-01 21:48:00+01:00", + "2019-01-01 21:49:00+01:00", + "2019-01-01 21:50:00+01:00", + "2019-01-01 21:51:00+01:00", + "2019-01-01 21:52:00+01:00", + "2019-01-01 21:53:00+01:00", + "2019-01-01 21:54:00+01:00", + "2019-01-01 21:55:00+01:00", + "2019-01-01 21:56:00+01:00", + "2019-01-01 21:57:00+01:00", + "2019-01-01 21:58:00+01:00", + "2019-01-01 21:59:00+01:00", + "2019-01-01 22:00:00+01:00", + "2019-01-01 22:01:00+01:00", + "2019-01-01 22:02:00+01:00", + "2019-01-01 22:03:00+01:00", + "2019-01-01 22:04:00+01:00", + "2019-01-01 22:05:00+01:00", + "2019-01-01 22:06:00+01:00", + "2019-01-01 22:07:00+01:00", + "2019-01-01 22:08:00+01:00", + "2019-01-01 22:09:00+01:00", + "2019-01-01 22:10:00+01:00", + "2019-01-01 22:11:00+01:00", + "2019-01-01 22:12:00+01:00", + "2019-01-01 22:13:00+01:00", + "2019-01-01 22:14:00+01:00", + "2019-01-01 22:15:00+01:00", + "2019-01-01 22:16:00+01:00", + "2019-01-01 22:17:00+01:00", + "2019-01-01 22:18:00+01:00", + "2019-01-01 22:19:00+01:00", + "2019-01-01 22:20:00+01:00", + "2019-01-01 22:21:00+01:00", + "2019-01-01 22:22:00+01:00", + "2019-01-01 22:23:00+01:00", + "2019-01-01 22:24:00+01:00", + "2019-01-01 22:25:00+01:00", + "2019-01-01 22:26:00+01:00", + "2019-01-01 22:27:00+01:00", + "2019-01-01 22:28:00+01:00", + "2019-01-01 22:29:00+01:00", + "2019-01-01 22:30:00+01:00", + "2019-01-01 22:31:00+01:00", + "2019-01-01 22:32:00+01:00", + "2019-01-01 22:33:00+01:00", + "2019-01-01 22:34:00+01:00", + "2019-01-01 22:35:00+01:00", + "2019-01-01 22:36:00+01:00", + "2019-01-01 22:37:00+01:00", + "2019-01-01 22:38:00+01:00", + "2019-01-01 22:39:00+01:00", + "2019-01-01 22:40:00+01:00", + "2019-01-01 22:41:00+01:00", + "2019-01-01 22:42:00+01:00", + "2019-01-01 22:43:00+01:00", + "2019-01-01 22:44:00+01:00", + "2019-01-01 22:45:00+01:00", + "2019-01-01 22:46:00+01:00", + "2019-01-01 22:47:00+01:00", + "2019-01-01 22:48:00+01:00", + "2019-01-01 22:49:00+01:00", + "2019-01-01 22:50:00+01:00", + "2019-01-01 22:51:00+01:00", + "2019-01-01 22:52:00+01:00", + "2019-01-01 22:53:00+01:00", + "2019-01-01 22:54:00+01:00", + "2019-01-01 22:55:00+01:00", + "2019-01-01 22:56:00+01:00", + "2019-01-01 22:57:00+01:00", + "2019-01-01 22:58:00+01:00", + "2019-01-01 22:59:00+01:00", + "2019-01-01 23:00:00+01:00", + "2019-01-01 23:01:00+01:00", + "2019-01-01 23:02:00+01:00", + "2019-01-01 23:03:00+01:00", + "2019-01-01 23:04:00+01:00", + "2019-01-01 23:05:00+01:00", + "2019-01-01 23:06:00+01:00", + "2019-01-01 23:07:00+01:00", + "2019-01-01 23:08:00+01:00", + "2019-01-01 23:09:00+01:00", + "2019-01-01 23:10:00+01:00", + "2019-01-01 23:11:00+01:00", + "2019-01-01 23:12:00+01:00", + "2019-01-01 23:13:00+01:00", + "2019-01-01 23:14:00+01:00", + "2019-01-01 23:15:00+01:00", + "2019-01-01 23:16:00+01:00", + "2019-01-01 23:17:00+01:00", + "2019-01-01 23:18:00+01:00", + "2019-01-01 23:19:00+01:00", + "2019-01-01 23:20:00+01:00", + "2019-01-01 23:21:00+01:00", + "2019-01-01 23:22:00+01:00", + "2019-01-01 23:23:00+01:00", + "2019-01-01 23:24:00+01:00", + "2019-01-01 23:25:00+01:00", + "2019-01-01 23:26:00+01:00", + "2019-01-01 23:27:00+01:00", + "2019-01-01 23:28:00+01:00", + "2019-01-01 23:29:00+01:00", + "2019-01-01 23:30:00+01:00", + "2019-01-01 23:31:00+01:00", + "2019-01-01 23:32:00+01:00", + "2019-01-01 23:33:00+01:00", + "2019-01-01 23:34:00+01:00", + "2019-01-01 23:35:00+01:00", + "2019-01-01 23:36:00+01:00", + "2019-01-01 23:37:00+01:00", + "2019-01-01 23:38:00+01:00", + "2019-01-01 23:39:00+01:00", + "2019-01-01 23:40:00+01:00", + "2019-01-01 23:41:00+01:00", + "2019-01-01 23:42:00+01:00", + "2019-01-01 23:43:00+01:00", + "2019-01-01 23:44:00+01:00", + "2019-01-01 23:45:00+01:00", + "2019-01-01 23:46:00+01:00", + "2019-01-01 23:47:00+01:00", + "2019-01-01 23:48:00+01:00", + "2019-01-01 23:49:00+01:00", + "2019-01-01 23:50:00+01:00", + "2019-01-01 23:51:00+01:00", + "2019-01-01 23:52:00+01:00", + "2019-01-01 23:53:00+01:00", + "2019-01-01 23:54:00+01:00", + "2019-01-01 23:55:00+01:00", + "2019-01-01 23:56:00+01:00", + "2019-01-01 23:57:00+01:00", + "2019-01-01 23:58:00+01:00", + "2019-01-01 23:59:00+01:00" + ], + "xaxis": "x2", + "y": [ + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5 + ], + "yaxis": "y2" + } + ], + "layout": { + "annotations": [ + { + "font": { + "size": 16 + }, + "showarrow": false, + "text": "Input (MW)", + "x": 0.225, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + }, + { + "font": { + "size": 16 + }, + "showarrow": false, + "text": "Output (MW)", + "x": 0.775, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + } + ], + "autosize": true, + "legend": { + "bgcolor": "#F5F6F9", + "font": { + "color": "#4D5663" + } + }, + "paper_bgcolor": "#F5F6F9", + "plot_bgcolor": "#F5F6F9", + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "Gasboiler" + }, + "xaxis": { + "anchor": "y", + "autorange": true, + "domain": [ + 0, + 0.45 + ], + "gridcolor": "#E1E5ED", + "range": [ + "2019-01-01", + "2019-01-01 23:59" + ], + "showgrid": true, + "tickfont": { + "color": "#4D5663" + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "" + }, + "type": "date", + "zerolinecolor": "#E1E5ED" + }, + "xaxis2": { + "anchor": "y2", + "autorange": true, + "domain": [ + 0.55, + 1 + ], + "gridcolor": "#E1E5ED", + "range": [ + "2019-01-01", + "2019-01-01 23:59" + ], + "showgrid": true, + "tickfont": { + "color": "#4D5663" + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "" + }, + "type": "date", + "zerolinecolor": "#E1E5ED" + }, + "yaxis": { + "anchor": "x", + "autorange": true, + "domain": [ + 0, + 1 + ], + "gridcolor": "#E1E5ED", + "range": [ + -6.555555555555555, + -4.555555555555555 + ], + "showgrid": true, + "tickfont": { + "color": "#4D5663" + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "" + }, + "type": "linear", + "zerolinecolor": "#E1E5ED" + }, + "yaxis2": { + "anchor": "x2", + "autorange": true, + "domain": [ + 0, + 1 + ], + "gridcolor": "#E1E5ED", + "range": [ + 4, + 6 + ], + "showgrid": true, + "tickfont": { + "color": "#4D5663" + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "" + }, + "type": "linear", + "zerolinecolor": "#E1E5ED" + } + } + }, + "image/png": "iVBORw0KGgoAAAANSUhEUgAABBgAAAHCCAYAAABFfHqHAAAgAElEQVR4Xu3dedxUdd3/8Y+oXKAsgguLC5pabkjuGqGiYSiJoaAIKCBEJgoUd/7CvHMt7iwTEVIJFBIRFUNRlMDERMJyC3G3VBIBdzZlUeH3+J4601zDXNc1850z73OdM6/r/uNOrvM935nnd+DM9brOObPNp59t3GJ8IYAAAggggAACCCCAAAIIIIAAAiUIbENgKEGPoQgggAACCCCAAAIIIIAAAgggEAgQGHghIIAAAggggAACCCCAAAIIIIBAyQIEhpIJ2QECCCCAAAIIIIAAAggggAACCBAYeA0ggAACCCCAAAIIIIAAAggggEDJAgSGkgnZAQIIIIAAAggggAACCCCAAAIIEBh4DSCAAAIIIIAAAggggAACCCCAQMkCBIaSCdkBAggggAACCCCAAAIIIIAAAggQGHgNIIAAAggggAACCCCAAAIIIIBAyQIEhpIJ2QECCCCAAAIIIIAAAggggAACCBAYeA0ggAACCCCAAAIIIIAAAggggEDJAgSGkgnZAQIIIIAAAggggAACCCCAAAIIEBh4DSCAAAIIIIAAAggggAACCCCAQMkCBIaSCdkBAggggAACCCCAAAIIIIAAAggQGHgNIIAAAggggAACCCCAAAIIIIBAyQIEhpIJ2QECCCCAAAIIIIAAAggggAACCBAYeA0ggAACCCCAAAIIIIAAAggggEDJAgSGkgnZAQIIIIAAAggggAACCCCAAAIIEBh4DSCAAAIIIIAAAggggAACCCCAQMkCBIaSCdkBAggggAACCCCAAAIIIIAAAggQGHgNIIAAAggggAACCCCAAAIIIIBAyQIEhpIJ2QECCCCAAAIIIIAAAggggAACCBAYeA0ggAACCCCAAAIIIIAAAggggEDJAgSGkgnZAQIIIIAAAggggAACCCCAAAIIEBh4DSCAAAIIIIAAAggggAACCCCAQMkCBIaSCdkBAggggAACCCCAAAIIIIAAAggQGHgNIIAAAggggAACCCCAAAIIIIBAyQIEhpIJ2QECCCCAAAIIIIAAAggggAACCBAYeA0ggAACCCCAAAIIIIAAAggggEDJAgSGkgnZAQIIIIAAAggggAACCCCAAAIIEBh4DSCAAAIIIIAAAggggAACCCCAQMkCBIaSCdkBAggggAAC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", + "text/html": [ + "
\n", + " \n", + " \n", + "
\n", + " \n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "baseline.data.loc['2019-01-01', ['input_MW', 'output_MW']].iplot(subplots=True, title='Gasboiler', subplot_titles=['Input (MW)', 'Output (MW)'])" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wall time: 786 ms\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DAMPOSNEGRSForePosForeNegHeat demandoutput_MWinput_MWoutput_MWhinput_MWh
datetime
2019-01-01 00:00:00+01:0068.9234.8558.012.046.8352.7055-1.4285710.083333-0.02381
2019-01-01 00:01:00+01:0068.9234.8558.012.032.1254.4555-1.4285710.083333-0.02381
2019-01-01 00:02:00+01:0068.9234.8558.012.030.9748.6455-1.4285710.083333-0.02381
2019-01-01 00:03:00+01:0068.9234.8558.012.051.1348.3555-1.4285710.083333-0.02381
2019-01-01 00:04:00+01:0068.9234.8558.012.048.0452.0155-1.4285710.083333-0.02381
\n", + "
" + ], + "text/plain": [ + " DAM POS NEG RS ForePos ForeNeg \\\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 68.92 34.85 58.01 2.0 46.83 52.70 \n", + "2019-01-01 00:01:00+01:00 68.92 34.85 58.01 2.0 32.12 54.45 \n", + "2019-01-01 00:02:00+01:00 68.92 34.85 58.01 2.0 30.97 48.64 \n", + "2019-01-01 00:03:00+01:00 68.92 34.85 58.01 2.0 51.13 48.35 \n", + "2019-01-01 00:04:00+01:00 68.92 34.85 58.01 2.0 48.04 52.01 \n", + "\n", + " Heat demand output_MW input_MW output_MWh \\\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 5 5 -1.428571 0.083333 \n", + "2019-01-01 00:01:00+01:00 5 5 -1.428571 0.083333 \n", + "2019-01-01 00:02:00+01:00 5 5 -1.428571 0.083333 \n", + "2019-01-01 00:03:00+01:00 5 5 -1.428571 0.083333 \n", + "2019-01-01 00:04:00+01:00 5 5 -1.428571 0.083333 \n", + "\n", + " input_MWh \n", + "datetime \n", + "2019-01-01 00:00:00+01:00 -0.02381 \n", + "2019-01-01 00:01:00+01:00 -0.02381 \n", + "2019-01-01 00:02:00+01:00 -0.02381 \n", + "2019-01-01 00:03:00+01:00 -0.02381 \n", + "2019-01-01 00:04:00+01:00 -0.02381 " + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%time hpcase.run()\n", + "hpcase.data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "linkText": "Export to plot.ly", + "plotlyServerURL": "https://plot.ly", + "showLink": true + }, + "data": [ + { + "line": { + "color": "rgba(255, 153, 51, 1.0)", + "dash": "solid", + "shape": "linear", + "width": 1.3 + }, + "mode": "lines", + "name": "input_MW", + "text": "", + "type": "scatter", + "x": [ + "2019-01-01 00:00:00+01:00", + "2019-01-01 00:01:00+01:00", + "2019-01-01 00:02:00+01:00", + "2019-01-01 00:03:00+01:00", + "2019-01-01 00:04:00+01:00", + "2019-01-01 00:05:00+01:00", + "2019-01-01 00:06:00+01:00", + "2019-01-01 00:07:00+01:00", + "2019-01-01 00:08:00+01:00", + "2019-01-01 00:09:00+01:00", + "2019-01-01 00:10:00+01:00", + "2019-01-01 00:11:00+01:00", + "2019-01-01 00:12:00+01:00", + "2019-01-01 00:13:00+01:00", + "2019-01-01 00:14:00+01:00", + "2019-01-01 00:15:00+01:00", + "2019-01-01 00:16:00+01:00", + "2019-01-01 00:17:00+01:00", + "2019-01-01 00:18:00+01:00", + "2019-01-01 00:19:00+01:00", + "2019-01-01 00:20:00+01:00", + "2019-01-01 00:21:00+01:00", + "2019-01-01 00:22:00+01:00", + "2019-01-01 00:23:00+01:00", + "2019-01-01 00:24:00+01:00", + "2019-01-01 00:25:00+01:00", + "2019-01-01 00:26:00+01:00", + "2019-01-01 00:27:00+01:00", + "2019-01-01 00:28:00+01:00", + "2019-01-01 00:29:00+01:00", + "2019-01-01 00:30:00+01:00", + "2019-01-01 00:31:00+01:00", + "2019-01-01 00:32:00+01:00", + "2019-01-01 00:33:00+01:00", + "2019-01-01 00:34:00+01:00", + "2019-01-01 00:35:00+01:00", + "2019-01-01 00:36:00+01:00", + "2019-01-01 00:37:00+01:00", + "2019-01-01 00:38:00+01:00", + "2019-01-01 00:39:00+01:00", + "2019-01-01 00:40:00+01:00", + "2019-01-01 00:41:00+01:00", + "2019-01-01 00:42:00+01:00", + "2019-01-01 00:43:00+01:00", + "2019-01-01 00:44:00+01:00", + "2019-01-01 00:45:00+01:00", + "2019-01-01 00:46:00+01:00", + "2019-01-01 00:47:00+01:00", + "2019-01-01 00:48:00+01:00", + "2019-01-01 00:49:00+01:00", + "2019-01-01 00:50:00+01:00", + "2019-01-01 00:51:00+01:00", + "2019-01-01 00:52:00+01:00", + "2019-01-01 00:53:00+01:00", + "2019-01-01 00:54:00+01:00", + "2019-01-01 00:55:00+01:00", + "2019-01-01 00:56:00+01:00", + "2019-01-01 00:57:00+01:00", + "2019-01-01 00:58:00+01:00", + "2019-01-01 00:59:00+01:00", + "2019-01-01 01:00:00+01:00", + "2019-01-01 01:01:00+01:00", + "2019-01-01 01:02:00+01:00", + "2019-01-01 01:03:00+01:00", + "2019-01-01 01:04:00+01:00", + "2019-01-01 01:05:00+01:00", + "2019-01-01 01:06:00+01:00", + "2019-01-01 01:07:00+01:00", + "2019-01-01 01:08:00+01:00", + "2019-01-01 01:09:00+01:00", + "2019-01-01 01:10:00+01:00", + "2019-01-01 01:11:00+01:00", + "2019-01-01 01:12:00+01:00", + "2019-01-01 01:13:00+01:00", + "2019-01-01 01:14:00+01:00", + "2019-01-01 01:15:00+01:00", + "2019-01-01 01:16:00+01:00", + "2019-01-01 01:17:00+01:00", + "2019-01-01 01:18:00+01:00", + "2019-01-01 01:19:00+01:00", + "2019-01-01 01:20:00+01:00", + "2019-01-01 01:21:00+01:00", + "2019-01-01 01:22:00+01:00", + "2019-01-01 01:23:00+01:00", + "2019-01-01 01:24:00+01:00", + "2019-01-01 01:25:00+01:00", + "2019-01-01 01:26:00+01:00", + "2019-01-01 01:27:00+01:00", + "2019-01-01 01:28:00+01:00", + "2019-01-01 01:29:00+01:00", + "2019-01-01 01:30:00+01:00", + "2019-01-01 01:31:00+01:00", + "2019-01-01 01:32:00+01:00", + "2019-01-01 01:33:00+01:00", + "2019-01-01 01:34:00+01:00", + "2019-01-01 01:35:00+01:00", + "2019-01-01 01:36:00+01:00", + "2019-01-01 01:37:00+01:00", + "2019-01-01 01:38:00+01:00", + "2019-01-01 01:39:00+01:00", + "2019-01-01 01:40:00+01:00", + "2019-01-01 01:41:00+01:00", + "2019-01-01 01:42:00+01:00", + "2019-01-01 01:43:00+01:00", + "2019-01-01 01:44:00+01:00", + "2019-01-01 01:45:00+01:00", + "2019-01-01 01:46:00+01:00", + "2019-01-01 01:47:00+01:00", + "2019-01-01 01:48:00+01:00", + "2019-01-01 01:49:00+01:00", + "2019-01-01 01:50:00+01:00", + "2019-01-01 01:51:00+01:00", + "2019-01-01 01:52:00+01:00", + "2019-01-01 01:53:00+01:00", + "2019-01-01 01:54:00+01:00", + "2019-01-01 01:55:00+01:00", + "2019-01-01 01:56:00+01:00", + "2019-01-01 01:57:00+01:00", + "2019-01-01 01:58:00+01:00", + "2019-01-01 01:59:00+01:00", + "2019-01-01 02:00:00+01:00", + "2019-01-01 02:01:00+01:00", + "2019-01-01 02:02:00+01:00", + "2019-01-01 02:03:00+01:00", + "2019-01-01 02:04:00+01:00", + "2019-01-01 02:05:00+01:00", + "2019-01-01 02:06:00+01:00", + "2019-01-01 02:07:00+01:00", + "2019-01-01 02:08:00+01:00", + "2019-01-01 02:09:00+01:00", + "2019-01-01 02:10:00+01:00", + "2019-01-01 02:11:00+01:00", + "2019-01-01 02:12:00+01:00", + "2019-01-01 02:13:00+01:00", + "2019-01-01 02:14:00+01:00", + "2019-01-01 02:15:00+01:00", + "2019-01-01 02:16:00+01:00", + "2019-01-01 02:17:00+01:00", + "2019-01-01 02:18:00+01:00", + "2019-01-01 02:19:00+01:00", + "2019-01-01 02:20:00+01:00", + "2019-01-01 02:21:00+01:00", + "2019-01-01 02:22:00+01:00", + "2019-01-01 02:23:00+01:00", + "2019-01-01 02:24:00+01:00", + "2019-01-01 02:25:00+01:00", + "2019-01-01 02:26:00+01:00", + "2019-01-01 02:27:00+01:00", + "2019-01-01 02:28:00+01:00", + "2019-01-01 02:29:00+01:00", + "2019-01-01 02:30:00+01:00", + "2019-01-01 02:31:00+01:00", + "2019-01-01 02:32:00+01:00", + "2019-01-01 02:33:00+01:00", + "2019-01-01 02:34:00+01:00", + "2019-01-01 02:35:00+01:00", + "2019-01-01 02:36:00+01:00", + "2019-01-01 02:37:00+01:00", + "2019-01-01 02:38:00+01:00", + "2019-01-01 02:39:00+01:00", + "2019-01-01 02:40:00+01:00", + "2019-01-01 02:41:00+01:00", + "2019-01-01 02:42:00+01:00", + "2019-01-01 02:43:00+01:00", + "2019-01-01 02:44:00+01:00", + "2019-01-01 02:45:00+01:00", + "2019-01-01 02:46:00+01:00", + "2019-01-01 02:47:00+01:00", + "2019-01-01 02:48:00+01:00", + "2019-01-01 02:49:00+01:00", + "2019-01-01 02:50:00+01:00", + "2019-01-01 02:51:00+01:00", + "2019-01-01 02:52:00+01:00", + "2019-01-01 02:53:00+01:00", + "2019-01-01 02:54:00+01:00", + "2019-01-01 02:55:00+01:00", + "2019-01-01 02:56:00+01:00", + "2019-01-01 02:57:00+01:00", + "2019-01-01 02:58:00+01:00", + "2019-01-01 02:59:00+01:00", + "2019-01-01 03:00:00+01:00", + "2019-01-01 03:01:00+01:00", + "2019-01-01 03:02:00+01:00", + "2019-01-01 03:03:00+01:00", + "2019-01-01 03:04:00+01:00", + "2019-01-01 03:05:00+01:00", + "2019-01-01 03:06:00+01:00", + "2019-01-01 03:07:00+01:00", + "2019-01-01 03:08:00+01:00", + "2019-01-01 03:09:00+01:00", + "2019-01-01 03:10:00+01:00", + "2019-01-01 03:11:00+01:00", + "2019-01-01 03:12:00+01:00", + "2019-01-01 03:13:00+01:00", + "2019-01-01 03:14:00+01:00", + "2019-01-01 03:15:00+01:00", + "2019-01-01 03:16:00+01:00", + "2019-01-01 03:17:00+01:00", + "2019-01-01 03:18:00+01:00", + "2019-01-01 03:19:00+01:00", + "2019-01-01 03:20:00+01:00", + "2019-01-01 03:21:00+01:00", + "2019-01-01 03:22:00+01:00", + "2019-01-01 03:23:00+01:00", + "2019-01-01 03:24:00+01:00", + "2019-01-01 03:25:00+01:00", + "2019-01-01 03:26:00+01:00", + "2019-01-01 03:27:00+01:00", + "2019-01-01 03:28:00+01:00", + "2019-01-01 03:29:00+01:00", + "2019-01-01 03:30:00+01:00", + "2019-01-01 03:31:00+01:00", + "2019-01-01 03:32:00+01:00", + "2019-01-01 03:33:00+01:00", + "2019-01-01 03:34:00+01:00", + "2019-01-01 03:35:00+01:00", + "2019-01-01 03:36:00+01:00", + "2019-01-01 03:37:00+01:00", + "2019-01-01 03:38:00+01:00", + "2019-01-01 03:39:00+01:00", + "2019-01-01 03:40:00+01:00", + "2019-01-01 03:41:00+01:00", + "2019-01-01 03:42:00+01:00", + "2019-01-01 03:43:00+01:00", + "2019-01-01 03:44:00+01:00", + "2019-01-01 03:45:00+01:00", + "2019-01-01 03:46:00+01:00", + "2019-01-01 03:47:00+01:00", + "2019-01-01 03:48:00+01:00", + "2019-01-01 03:49:00+01:00", + "2019-01-01 03:50:00+01:00", + "2019-01-01 03:51:00+01:00", + "2019-01-01 03:52:00+01:00", + "2019-01-01 03:53:00+01:00", + "2019-01-01 03:54:00+01:00", + "2019-01-01 03:55:00+01:00", + "2019-01-01 03:56:00+01:00", + "2019-01-01 03:57:00+01:00", + "2019-01-01 03:58:00+01:00", + "2019-01-01 03:59:00+01:00", + "2019-01-01 04:00:00+01:00", + "2019-01-01 04:01:00+01:00", + "2019-01-01 04:02:00+01:00", + "2019-01-01 04:03:00+01:00", + "2019-01-01 04:04:00+01:00", + "2019-01-01 04:05:00+01:00", + "2019-01-01 04:06:00+01:00", + "2019-01-01 04:07:00+01:00", + "2019-01-01 04:08:00+01:00", + "2019-01-01 04:09:00+01:00", + "2019-01-01 04:10:00+01:00", + "2019-01-01 04:11:00+01:00", + "2019-01-01 04:12:00+01:00", + "2019-01-01 04:13:00+01:00", + "2019-01-01 04:14:00+01:00", + "2019-01-01 04:15:00+01:00", + "2019-01-01 04:16:00+01:00", + "2019-01-01 04:17:00+01:00", + "2019-01-01 04:18:00+01:00", + "2019-01-01 04:19:00+01:00", + "2019-01-01 04:20:00+01:00", + "2019-01-01 04:21:00+01:00", + "2019-01-01 04:22:00+01:00", + "2019-01-01 04:23:00+01:00", + "2019-01-01 04:24:00+01:00", + "2019-01-01 04:25:00+01:00", + "2019-01-01 04:26:00+01:00", + "2019-01-01 04:27:00+01:00", + "2019-01-01 04:28:00+01:00", + "2019-01-01 04:29:00+01:00", + "2019-01-01 04:30:00+01:00", + "2019-01-01 04:31:00+01:00", + "2019-01-01 04:32:00+01:00", + "2019-01-01 04:33:00+01:00", + "2019-01-01 04:34:00+01:00", + "2019-01-01 04:35:00+01:00", + "2019-01-01 04:36:00+01:00", + "2019-01-01 04:37:00+01:00", + "2019-01-01 04:38:00+01:00", + "2019-01-01 04:39:00+01:00", + "2019-01-01 04:40:00+01:00", + "2019-01-01 04:41:00+01:00", + "2019-01-01 04:42:00+01:00", + "2019-01-01 04:43:00+01:00", + "2019-01-01 04:44:00+01:00", + "2019-01-01 04:45:00+01:00", + "2019-01-01 04:46:00+01:00", + "2019-01-01 04:47:00+01:00", + "2019-01-01 04:48:00+01:00", + "2019-01-01 04:49:00+01:00", + "2019-01-01 04:50:00+01:00", + "2019-01-01 04:51:00+01:00", + "2019-01-01 04:52:00+01:00", + "2019-01-01 04:53:00+01:00", + "2019-01-01 04:54:00+01:00", + "2019-01-01 04:55:00+01:00", + "2019-01-01 04:56:00+01:00", + "2019-01-01 04:57:00+01:00", + "2019-01-01 04:58:00+01:00", + "2019-01-01 04:59:00+01:00", + "2019-01-01 05:00:00+01:00", + "2019-01-01 05:01:00+01:00", + "2019-01-01 05:02:00+01:00", + "2019-01-01 05:03:00+01:00", + "2019-01-01 05:04:00+01:00", + "2019-01-01 05:05:00+01:00", + "2019-01-01 05:06:00+01:00", + "2019-01-01 05:07:00+01:00", + "2019-01-01 05:08:00+01:00", + "2019-01-01 05:09:00+01:00", + "2019-01-01 05:10:00+01:00", + "2019-01-01 05:11:00+01:00", + "2019-01-01 05:12:00+01:00", + "2019-01-01 05:13:00+01:00", + "2019-01-01 05:14:00+01:00", + "2019-01-01 05:15:00+01:00", + "2019-01-01 05:16:00+01:00", + "2019-01-01 05:17:00+01:00", + "2019-01-01 05:18:00+01:00", + "2019-01-01 05:19:00+01:00", + "2019-01-01 05:20:00+01:00", + "2019-01-01 05:21:00+01:00", + "2019-01-01 05:22:00+01:00", + "2019-01-01 05:23:00+01:00", + "2019-01-01 05:24:00+01:00", + "2019-01-01 05:25:00+01:00", + "2019-01-01 05:26:00+01:00", + "2019-01-01 05:27:00+01:00", + "2019-01-01 05:28:00+01:00", + "2019-01-01 05:29:00+01:00", + "2019-01-01 05:30:00+01:00", + "2019-01-01 05:31:00+01:00", + "2019-01-01 05:32:00+01:00", + "2019-01-01 05:33:00+01:00", + "2019-01-01 05:34:00+01:00", + "2019-01-01 05:35:00+01:00", + "2019-01-01 05:36:00+01:00", + "2019-01-01 05:37:00+01:00", + "2019-01-01 05:38:00+01:00", + "2019-01-01 05:39:00+01:00", + "2019-01-01 05:40:00+01:00", + "2019-01-01 05:41:00+01:00", + "2019-01-01 05:42:00+01:00", + "2019-01-01 05:43:00+01:00", + "2019-01-01 05:44:00+01:00", + "2019-01-01 05:45:00+01:00", + "2019-01-01 05:46:00+01:00", + "2019-01-01 05:47:00+01:00", + "2019-01-01 05:48:00+01:00", + "2019-01-01 05:49:00+01:00", + "2019-01-01 05:50:00+01:00", + "2019-01-01 05:51:00+01:00", + "2019-01-01 05:52:00+01:00", + "2019-01-01 05:53:00+01:00", + "2019-01-01 05:54:00+01:00", + "2019-01-01 05:55:00+01:00", + "2019-01-01 05:56:00+01:00", + "2019-01-01 05:57:00+01:00", + "2019-01-01 05:58:00+01:00", + "2019-01-01 05:59:00+01:00", + "2019-01-01 06:00:00+01:00", + "2019-01-01 06:01:00+01:00", + "2019-01-01 06:02:00+01:00", + "2019-01-01 06:03:00+01:00", + "2019-01-01 06:04:00+01:00", + "2019-01-01 06:05:00+01:00", + "2019-01-01 06:06:00+01:00", + "2019-01-01 06:07:00+01:00", + "2019-01-01 06:08:00+01:00", + "2019-01-01 06:09:00+01:00", + "2019-01-01 06:10:00+01:00", + "2019-01-01 06:11:00+01:00", + "2019-01-01 06:12:00+01:00", + "2019-01-01 06:13:00+01:00", + "2019-01-01 06:14:00+01:00", + "2019-01-01 06:15:00+01:00", + "2019-01-01 06:16:00+01:00", + "2019-01-01 06:17:00+01:00", + "2019-01-01 06:18:00+01:00", + "2019-01-01 06:19:00+01:00", + "2019-01-01 06:20:00+01:00", + "2019-01-01 06:21:00+01:00", + "2019-01-01 06:22:00+01:00", + "2019-01-01 06:23:00+01:00", + "2019-01-01 06:24:00+01:00", + "2019-01-01 06:25:00+01:00", + "2019-01-01 06:26:00+01:00", + "2019-01-01 06:27:00+01:00", + "2019-01-01 06:28:00+01:00", + "2019-01-01 06:29:00+01:00", + "2019-01-01 06:30:00+01:00", + "2019-01-01 06:31:00+01:00", + "2019-01-01 06:32:00+01:00", + "2019-01-01 06:33:00+01:00", + "2019-01-01 06:34:00+01:00", + "2019-01-01 06:35:00+01:00", + "2019-01-01 06:36:00+01:00", + "2019-01-01 06:37:00+01:00", + "2019-01-01 06:38:00+01:00", + "2019-01-01 06:39:00+01:00", + "2019-01-01 06:40:00+01:00", + "2019-01-01 06:41:00+01:00", + "2019-01-01 06:42:00+01:00", + "2019-01-01 06:43:00+01:00", + "2019-01-01 06:44:00+01:00", + "2019-01-01 06:45:00+01:00", + "2019-01-01 06:46:00+01:00", + "2019-01-01 06:47:00+01:00", + "2019-01-01 06:48:00+01:00", + "2019-01-01 06:49:00+01:00", + "2019-01-01 06:50:00+01:00", + "2019-01-01 06:51:00+01:00", + "2019-01-01 06:52:00+01:00", + "2019-01-01 06:53:00+01:00", + "2019-01-01 06:54:00+01:00", + "2019-01-01 06:55:00+01:00", + "2019-01-01 06:56:00+01:00", + "2019-01-01 06:57:00+01:00", + "2019-01-01 06:58:00+01:00", + "2019-01-01 06:59:00+01:00", + "2019-01-01 07:00:00+01:00", + "2019-01-01 07:01:00+01:00", + "2019-01-01 07:02:00+01:00", + "2019-01-01 07:03:00+01:00", + "2019-01-01 07:04:00+01:00", + "2019-01-01 07:05:00+01:00", + "2019-01-01 07:06:00+01:00", + "2019-01-01 07:07:00+01:00", + "2019-01-01 07:08:00+01:00", + "2019-01-01 07:09:00+01:00", + "2019-01-01 07:10:00+01:00", + "2019-01-01 07:11:00+01:00", + "2019-01-01 07:12:00+01:00", + "2019-01-01 07:13:00+01:00", + "2019-01-01 07:14:00+01:00", + "2019-01-01 07:15:00+01:00", + "2019-01-01 07:16:00+01:00", + "2019-01-01 07:17:00+01:00", + "2019-01-01 07:18:00+01:00", + "2019-01-01 07:19:00+01:00", + "2019-01-01 07:20:00+01:00", + "2019-01-01 07:21:00+01:00", + "2019-01-01 07:22:00+01:00", + "2019-01-01 07:23:00+01:00", + "2019-01-01 07:24:00+01:00", + "2019-01-01 07:25:00+01:00", + "2019-01-01 07:26:00+01:00", + "2019-01-01 07:27:00+01:00", + "2019-01-01 07:28:00+01:00", + "2019-01-01 07:29:00+01:00", + "2019-01-01 07:30:00+01:00", + "2019-01-01 07:31:00+01:00", + "2019-01-01 07:32:00+01:00", + "2019-01-01 07:33:00+01:00", + "2019-01-01 07:34:00+01:00", + "2019-01-01 07:35:00+01:00", + "2019-01-01 07:36:00+01:00", + "2019-01-01 07:37:00+01:00", + "2019-01-01 07:38:00+01:00", + "2019-01-01 07:39:00+01:00", + "2019-01-01 07:40:00+01:00", + "2019-01-01 07:41:00+01:00", + "2019-01-01 07:42:00+01:00", + "2019-01-01 07:43:00+01:00", + "2019-01-01 07:44:00+01:00", + "2019-01-01 07:45:00+01:00", + "2019-01-01 07:46:00+01:00", + "2019-01-01 07:47:00+01:00", + "2019-01-01 07:48:00+01:00", + "2019-01-01 07:49:00+01:00", + "2019-01-01 07:50:00+01:00", + "2019-01-01 07:51:00+01:00", + "2019-01-01 07:52:00+01:00", + "2019-01-01 07:53:00+01:00", + "2019-01-01 07:54:00+01:00", + "2019-01-01 07:55:00+01:00", + "2019-01-01 07:56:00+01:00", + "2019-01-01 07:57:00+01:00", + "2019-01-01 07:58:00+01:00", + "2019-01-01 07:59:00+01:00", + "2019-01-01 08:00:00+01:00", + "2019-01-01 08:01:00+01:00", + "2019-01-01 08:02:00+01:00", + "2019-01-01 08:03:00+01:00", + "2019-01-01 08:04:00+01:00", + "2019-01-01 08:05:00+01:00", + "2019-01-01 08:06:00+01:00", + "2019-01-01 08:07:00+01:00", + "2019-01-01 08:08:00+01:00", + "2019-01-01 08:09:00+01:00", + "2019-01-01 08:10:00+01:00", + "2019-01-01 08:11:00+01:00", + "2019-01-01 08:12:00+01:00", + "2019-01-01 08:13:00+01:00", + "2019-01-01 08:14:00+01:00", + "2019-01-01 08:15:00+01:00", + "2019-01-01 08:16:00+01:00", + "2019-01-01 08:17:00+01:00", + "2019-01-01 08:18:00+01:00", + "2019-01-01 08:19:00+01:00", + "2019-01-01 08:20:00+01:00", + "2019-01-01 08:21:00+01:00", + "2019-01-01 08:22:00+01:00", + "2019-01-01 08:23:00+01:00", + "2019-01-01 08:24:00+01:00", + "2019-01-01 08:25:00+01:00", + "2019-01-01 08:26:00+01:00", + "2019-01-01 08:27:00+01:00", + "2019-01-01 08:28:00+01:00", + "2019-01-01 08:29:00+01:00", + "2019-01-01 08:30:00+01:00", + "2019-01-01 08:31:00+01:00", + "2019-01-01 08:32:00+01:00", + "2019-01-01 08:33:00+01:00", + "2019-01-01 08:34:00+01:00", + "2019-01-01 08:35:00+01:00", + "2019-01-01 08:36:00+01:00", + "2019-01-01 08:37:00+01:00", + "2019-01-01 08:38:00+01:00", + "2019-01-01 08:39:00+01:00", + "2019-01-01 08:40:00+01:00", + "2019-01-01 08:41:00+01:00", + "2019-01-01 08:42:00+01:00", + "2019-01-01 08:43:00+01:00", + "2019-01-01 08:44:00+01:00", + "2019-01-01 08:45:00+01:00", + "2019-01-01 08:46:00+01:00", + "2019-01-01 08:47:00+01:00", + "2019-01-01 08:48:00+01:00", + "2019-01-01 08:49:00+01:00", + "2019-01-01 08:50:00+01:00", + "2019-01-01 08:51:00+01:00", + "2019-01-01 08:52:00+01:00", + "2019-01-01 08:53:00+01:00", + "2019-01-01 08:54:00+01:00", + "2019-01-01 08:55:00+01:00", + "2019-01-01 08:56:00+01:00", + "2019-01-01 08:57:00+01:00", + "2019-01-01 08:58:00+01:00", + "2019-01-01 08:59:00+01:00", + "2019-01-01 09:00:00+01:00", + "2019-01-01 09:01:00+01:00", + "2019-01-01 09:02:00+01:00", + "2019-01-01 09:03:00+01:00", + "2019-01-01 09:04:00+01:00", + "2019-01-01 09:05:00+01:00", + "2019-01-01 09:06:00+01:00", + "2019-01-01 09:07:00+01:00", + "2019-01-01 09:08:00+01:00", + "2019-01-01 09:09:00+01:00", + "2019-01-01 09:10:00+01:00", + "2019-01-01 09:11:00+01:00", + "2019-01-01 09:12:00+01:00", + "2019-01-01 09:13:00+01:00", + "2019-01-01 09:14:00+01:00", + "2019-01-01 09:15:00+01:00", + "2019-01-01 09:16:00+01:00", + "2019-01-01 09:17:00+01:00", + "2019-01-01 09:18:00+01:00", + "2019-01-01 09:19:00+01:00", + "2019-01-01 09:20:00+01:00", + "2019-01-01 09:21:00+01:00", + "2019-01-01 09:22:00+01:00", + "2019-01-01 09:23:00+01:00", + "2019-01-01 09:24:00+01:00", + "2019-01-01 09:25:00+01:00", + "2019-01-01 09:26:00+01:00", + "2019-01-01 09:27:00+01:00", + "2019-01-01 09:28:00+01:00", + "2019-01-01 09:29:00+01:00", + "2019-01-01 09:30:00+01:00", + "2019-01-01 09:31:00+01:00", + "2019-01-01 09:32:00+01:00", + "2019-01-01 09:33:00+01:00", + "2019-01-01 09:34:00+01:00", + "2019-01-01 09:35:00+01:00", + "2019-01-01 09:36:00+01:00", + "2019-01-01 09:37:00+01:00", + "2019-01-01 09:38:00+01:00", + "2019-01-01 09:39:00+01:00", + "2019-01-01 09:40:00+01:00", + "2019-01-01 09:41:00+01:00", + "2019-01-01 09:42:00+01:00", + "2019-01-01 09:43:00+01:00", + "2019-01-01 09:44:00+01:00", + "2019-01-01 09:45:00+01:00", + "2019-01-01 09:46:00+01:00", + "2019-01-01 09:47:00+01:00", + "2019-01-01 09:48:00+01:00", + "2019-01-01 09:49:00+01:00", + "2019-01-01 09:50:00+01:00", + "2019-01-01 09:51:00+01:00", + "2019-01-01 09:52:00+01:00", + "2019-01-01 09:53:00+01:00", + "2019-01-01 09:54:00+01:00", + "2019-01-01 09:55:00+01:00", + "2019-01-01 09:56:00+01:00", + "2019-01-01 09:57:00+01:00", + "2019-01-01 09:58:00+01:00", + "2019-01-01 09:59:00+01:00", + "2019-01-01 10:00:00+01:00", + "2019-01-01 10:01:00+01:00", + "2019-01-01 10:02:00+01:00", + "2019-01-01 10:03:00+01:00", + "2019-01-01 10:04:00+01:00", + "2019-01-01 10:05:00+01:00", + "2019-01-01 10:06:00+01:00", + "2019-01-01 10:07:00+01:00", + "2019-01-01 10:08:00+01:00", + "2019-01-01 10:09:00+01:00", + "2019-01-01 10:10:00+01:00", + "2019-01-01 10:11:00+01:00", + "2019-01-01 10:12:00+01:00", + "2019-01-01 10:13:00+01:00", + "2019-01-01 10:14:00+01:00", + "2019-01-01 10:15:00+01:00", + "2019-01-01 10:16:00+01:00", + "2019-01-01 10:17:00+01:00", + "2019-01-01 10:18:00+01:00", + "2019-01-01 10:19:00+01:00", + "2019-01-01 10:20:00+01:00", + "2019-01-01 10:21:00+01:00", + "2019-01-01 10:22:00+01:00", + "2019-01-01 10:23:00+01:00", + "2019-01-01 10:24:00+01:00", + "2019-01-01 10:25:00+01:00", + "2019-01-01 10:26:00+01:00", + "2019-01-01 10:27:00+01:00", + "2019-01-01 10:28:00+01:00", + "2019-01-01 10:29:00+01:00", + "2019-01-01 10:30:00+01:00", + "2019-01-01 10:31:00+01:00", + "2019-01-01 10:32:00+01:00", + "2019-01-01 10:33:00+01:00", + "2019-01-01 10:34:00+01:00", + "2019-01-01 10:35:00+01:00", + "2019-01-01 10:36:00+01:00", + "2019-01-01 10:37:00+01:00", + "2019-01-01 10:38:00+01:00", + "2019-01-01 10:39:00+01:00", + "2019-01-01 10:40:00+01:00", + "2019-01-01 10:41:00+01:00", + "2019-01-01 10:42:00+01:00", + "2019-01-01 10:43:00+01:00", + "2019-01-01 10:44:00+01:00", + "2019-01-01 10:45:00+01:00", + "2019-01-01 10:46:00+01:00", + "2019-01-01 10:47:00+01:00", + "2019-01-01 10:48:00+01:00", + "2019-01-01 10:49:00+01:00", + "2019-01-01 10:50:00+01:00", + "2019-01-01 10:51:00+01:00", + "2019-01-01 10:52:00+01:00", + "2019-01-01 10:53:00+01:00", + "2019-01-01 10:54:00+01:00", + "2019-01-01 10:55:00+01:00", + "2019-01-01 10:56:00+01:00", + "2019-01-01 10:57:00+01:00", + "2019-01-01 10:58:00+01:00", + "2019-01-01 10:59:00+01:00", + "2019-01-01 11:00:00+01:00", + "2019-01-01 11:01:00+01:00", + "2019-01-01 11:02:00+01:00", + "2019-01-01 11:03:00+01:00", + "2019-01-01 11:04:00+01:00", + "2019-01-01 11:05:00+01:00", + "2019-01-01 11:06:00+01:00", + "2019-01-01 11:07:00+01:00", + "2019-01-01 11:08:00+01:00", + "2019-01-01 11:09:00+01:00", + "2019-01-01 11:10:00+01:00", + "2019-01-01 11:11:00+01:00", + "2019-01-01 11:12:00+01:00", + "2019-01-01 11:13:00+01:00", + "2019-01-01 11:14:00+01:00", + "2019-01-01 11:15:00+01:00", + "2019-01-01 11:16:00+01:00", + "2019-01-01 11:17:00+01:00", + "2019-01-01 11:18:00+01:00", + "2019-01-01 11:19:00+01:00", + "2019-01-01 11:20:00+01:00", + "2019-01-01 11:21:00+01:00", + "2019-01-01 11:22:00+01:00", + "2019-01-01 11:23:00+01:00", + "2019-01-01 11:24:00+01:00", + "2019-01-01 11:25:00+01:00", + "2019-01-01 11:26:00+01:00", + "2019-01-01 11:27:00+01:00", + "2019-01-01 11:28:00+01:00", + "2019-01-01 11:29:00+01:00", + "2019-01-01 11:30:00+01:00", + "2019-01-01 11:31:00+01:00", + "2019-01-01 11:32:00+01:00", + "2019-01-01 11:33:00+01:00", + "2019-01-01 11:34:00+01:00", + "2019-01-01 11:35:00+01:00", + "2019-01-01 11:36:00+01:00", + "2019-01-01 11:37:00+01:00", + "2019-01-01 11:38:00+01:00", + "2019-01-01 11:39:00+01:00", + "2019-01-01 11:40:00+01:00", + "2019-01-01 11:41:00+01:00", + "2019-01-01 11:42:00+01:00", + "2019-01-01 11:43:00+01:00", + "2019-01-01 11:44:00+01:00", + "2019-01-01 11:45:00+01:00", + "2019-01-01 11:46:00+01:00", + "2019-01-01 11:47:00+01:00", + "2019-01-01 11:48:00+01:00", + "2019-01-01 11:49:00+01:00", + "2019-01-01 11:50:00+01:00", + "2019-01-01 11:51:00+01:00", + "2019-01-01 11:52:00+01:00", + "2019-01-01 11:53:00+01:00", + "2019-01-01 11:54:00+01:00", + "2019-01-01 11:55:00+01:00", + "2019-01-01 11:56:00+01:00", + "2019-01-01 11:57:00+01:00", + "2019-01-01 11:58:00+01:00", + "2019-01-01 11:59:00+01:00", + "2019-01-01 12:00:00+01:00", + "2019-01-01 12:01:00+01:00", + "2019-01-01 12:02:00+01:00", + "2019-01-01 12:03:00+01:00", + "2019-01-01 12:04:00+01:00", + "2019-01-01 12:05:00+01:00", + "2019-01-01 12:06:00+01:00", + "2019-01-01 12:07:00+01:00", + "2019-01-01 12:08:00+01:00", + "2019-01-01 12:09:00+01:00", + "2019-01-01 12:10:00+01:00", + "2019-01-01 12:11:00+01:00", + "2019-01-01 12:12:00+01:00", + "2019-01-01 12:13:00+01:00", + "2019-01-01 12:14:00+01:00", + "2019-01-01 12:15:00+01:00", + "2019-01-01 12:16:00+01:00", + "2019-01-01 12:17:00+01:00", + "2019-01-01 12:18:00+01:00", + "2019-01-01 12:19:00+01:00", + "2019-01-01 12:20:00+01:00", + "2019-01-01 12:21:00+01:00", + "2019-01-01 12:22:00+01:00", + "2019-01-01 12:23:00+01:00", + "2019-01-01 12:24:00+01:00", + "2019-01-01 12:25:00+01:00", + "2019-01-01 12:26:00+01:00", + "2019-01-01 12:27:00+01:00", + "2019-01-01 12:28:00+01:00", + "2019-01-01 12:29:00+01:00", + "2019-01-01 12:30:00+01:00", + "2019-01-01 12:31:00+01:00", + "2019-01-01 12:32:00+01:00", + "2019-01-01 12:33:00+01:00", + "2019-01-01 12:34:00+01:00", + "2019-01-01 12:35:00+01:00", + "2019-01-01 12:36:00+01:00", + "2019-01-01 12:37:00+01:00", + "2019-01-01 12:38:00+01:00", + "2019-01-01 12:39:00+01:00", + "2019-01-01 12:40:00+01:00", + "2019-01-01 12:41:00+01:00", + "2019-01-01 12:42:00+01:00", + "2019-01-01 12:43:00+01:00", + "2019-01-01 12:44:00+01:00", + "2019-01-01 12:45:00+01:00", + "2019-01-01 12:46:00+01:00", + "2019-01-01 12:47:00+01:00", + "2019-01-01 12:48:00+01:00", + "2019-01-01 12:49:00+01:00", + "2019-01-01 12:50:00+01:00", + "2019-01-01 12:51:00+01:00", + "2019-01-01 12:52:00+01:00", + "2019-01-01 12:53:00+01:00", + "2019-01-01 12:54:00+01:00", + "2019-01-01 12:55:00+01:00", + "2019-01-01 12:56:00+01:00", + "2019-01-01 12:57:00+01:00", + "2019-01-01 12:58:00+01:00", + "2019-01-01 12:59:00+01:00", + "2019-01-01 13:00:00+01:00", + "2019-01-01 13:01:00+01:00", + "2019-01-01 13:02:00+01:00", + "2019-01-01 13:03:00+01:00", + "2019-01-01 13:04:00+01:00", + "2019-01-01 13:05:00+01:00", + "2019-01-01 13:06:00+01:00", + "2019-01-01 13:07:00+01:00", + "2019-01-01 13:08:00+01:00", + "2019-01-01 13:09:00+01:00", + "2019-01-01 13:10:00+01:00", + "2019-01-01 13:11:00+01:00", + "2019-01-01 13:12:00+01:00", + "2019-01-01 13:13:00+01:00", + "2019-01-01 13:14:00+01:00", + "2019-01-01 13:15:00+01:00", + "2019-01-01 13:16:00+01:00", + "2019-01-01 13:17:00+01:00", + "2019-01-01 13:18:00+01:00", + "2019-01-01 13:19:00+01:00", + "2019-01-01 13:20:00+01:00", + "2019-01-01 13:21:00+01:00", + "2019-01-01 13:22:00+01:00", + "2019-01-01 13:23:00+01:00", + "2019-01-01 13:24:00+01:00", + "2019-01-01 13:25:00+01:00", + "2019-01-01 13:26:00+01:00", + "2019-01-01 13:27:00+01:00", + "2019-01-01 13:28:00+01:00", + "2019-01-01 13:29:00+01:00", + "2019-01-01 13:30:00+01:00", + "2019-01-01 13:31:00+01:00", + "2019-01-01 13:32:00+01:00", + "2019-01-01 13:33:00+01:00", + "2019-01-01 13:34:00+01:00", + "2019-01-01 13:35:00+01:00", + "2019-01-01 13:36:00+01:00", + "2019-01-01 13:37:00+01:00", + "2019-01-01 13:38:00+01:00", + "2019-01-01 13:39:00+01:00", + "2019-01-01 13:40:00+01:00", + "2019-01-01 13:41:00+01:00", + "2019-01-01 13:42:00+01:00", + "2019-01-01 13:43:00+01:00", + "2019-01-01 13:44:00+01:00", + "2019-01-01 13:45:00+01:00", + "2019-01-01 13:46:00+01:00", + "2019-01-01 13:47:00+01:00", + "2019-01-01 13:48:00+01:00", + "2019-01-01 13:49:00+01:00", + "2019-01-01 13:50:00+01:00", + "2019-01-01 13:51:00+01:00", + "2019-01-01 13:52:00+01:00", + "2019-01-01 13:53:00+01:00", + "2019-01-01 13:54:00+01:00", + "2019-01-01 13:55:00+01:00", + "2019-01-01 13:56:00+01:00", + "2019-01-01 13:57:00+01:00", + "2019-01-01 13:58:00+01:00", + "2019-01-01 13:59:00+01:00", + "2019-01-01 14:00:00+01:00", + "2019-01-01 14:01:00+01:00", + "2019-01-01 14:02:00+01:00", + "2019-01-01 14:03:00+01:00", + "2019-01-01 14:04:00+01:00", + "2019-01-01 14:05:00+01:00", + "2019-01-01 14:06:00+01:00", + "2019-01-01 14:07:00+01:00", + "2019-01-01 14:08:00+01:00", + "2019-01-01 14:09:00+01:00", + "2019-01-01 14:10:00+01:00", + "2019-01-01 14:11:00+01:00", + "2019-01-01 14:12:00+01:00", + "2019-01-01 14:13:00+01:00", + "2019-01-01 14:14:00+01:00", + "2019-01-01 14:15:00+01:00", + "2019-01-01 14:16:00+01:00", + "2019-01-01 14:17:00+01:00", + "2019-01-01 14:18:00+01:00", + "2019-01-01 14:19:00+01:00", + "2019-01-01 14:20:00+01:00", + "2019-01-01 14:21:00+01:00", + "2019-01-01 14:22:00+01:00", + "2019-01-01 14:23:00+01:00", + "2019-01-01 14:24:00+01:00", + "2019-01-01 14:25:00+01:00", + "2019-01-01 14:26:00+01:00", + "2019-01-01 14:27:00+01:00", + "2019-01-01 14:28:00+01:00", + "2019-01-01 14:29:00+01:00", + "2019-01-01 14:30:00+01:00", + "2019-01-01 14:31:00+01:00", + "2019-01-01 14:32:00+01:00", + "2019-01-01 14:33:00+01:00", + "2019-01-01 14:34:00+01:00", + "2019-01-01 14:35:00+01:00", + "2019-01-01 14:36:00+01:00", + "2019-01-01 14:37:00+01:00", + "2019-01-01 14:38:00+01:00", + "2019-01-01 14:39:00+01:00", + "2019-01-01 14:40:00+01:00", + "2019-01-01 14:41:00+01:00", + "2019-01-01 14:42:00+01:00", + "2019-01-01 14:43:00+01:00", + "2019-01-01 14:44:00+01:00", + "2019-01-01 14:45:00+01:00", + "2019-01-01 14:46:00+01:00", + "2019-01-01 14:47:00+01:00", + "2019-01-01 14:48:00+01:00", + "2019-01-01 14:49:00+01:00", + "2019-01-01 14:50:00+01:00", + "2019-01-01 14:51:00+01:00", + "2019-01-01 14:52:00+01:00", + "2019-01-01 14:53:00+01:00", + "2019-01-01 14:54:00+01:00", + "2019-01-01 14:55:00+01:00", + "2019-01-01 14:56:00+01:00", + "2019-01-01 14:57:00+01:00", + "2019-01-01 14:58:00+01:00", + "2019-01-01 14:59:00+01:00", + "2019-01-01 15:00:00+01:00", + "2019-01-01 15:01:00+01:00", + "2019-01-01 15:02:00+01:00", + "2019-01-01 15:03:00+01:00", + "2019-01-01 15:04:00+01:00", + "2019-01-01 15:05:00+01:00", + "2019-01-01 15:06:00+01:00", + "2019-01-01 15:07:00+01:00", + "2019-01-01 15:08:00+01:00", + "2019-01-01 15:09:00+01:00", + "2019-01-01 15:10:00+01:00", + "2019-01-01 15:11:00+01:00", + "2019-01-01 15:12:00+01:00", + "2019-01-01 15:13:00+01:00", + "2019-01-01 15:14:00+01:00", + "2019-01-01 15:15:00+01:00", + "2019-01-01 15:16:00+01:00", + "2019-01-01 15:17:00+01:00", + "2019-01-01 15:18:00+01:00", + "2019-01-01 15:19:00+01:00", + "2019-01-01 15:20:00+01:00", + "2019-01-01 15:21:00+01:00", + "2019-01-01 15:22:00+01:00", + "2019-01-01 15:23:00+01:00", + "2019-01-01 15:24:00+01:00", + "2019-01-01 15:25:00+01:00", + "2019-01-01 15:26:00+01:00", + "2019-01-01 15:27:00+01:00", + "2019-01-01 15:28:00+01:00", + "2019-01-01 15:29:00+01:00", + "2019-01-01 15:30:00+01:00", + "2019-01-01 15:31:00+01:00", + "2019-01-01 15:32:00+01:00", + "2019-01-01 15:33:00+01:00", + "2019-01-01 15:34:00+01:00", + "2019-01-01 15:35:00+01:00", + "2019-01-01 15:36:00+01:00", + "2019-01-01 15:37:00+01:00", + "2019-01-01 15:38:00+01:00", + "2019-01-01 15:39:00+01:00", + "2019-01-01 15:40:00+01:00", + "2019-01-01 15:41:00+01:00", + "2019-01-01 15:42:00+01:00", + "2019-01-01 15:43:00+01:00", + "2019-01-01 15:44:00+01:00", + "2019-01-01 15:45:00+01:00", + "2019-01-01 15:46:00+01:00", + "2019-01-01 15:47:00+01:00", + "2019-01-01 15:48:00+01:00", + "2019-01-01 15:49:00+01:00", + "2019-01-01 15:50:00+01:00", + "2019-01-01 15:51:00+01:00", + "2019-01-01 15:52:00+01:00", + "2019-01-01 15:53:00+01:00", + "2019-01-01 15:54:00+01:00", + "2019-01-01 15:55:00+01:00", + "2019-01-01 15:56:00+01:00", + "2019-01-01 15:57:00+01:00", + "2019-01-01 15:58:00+01:00", + "2019-01-01 15:59:00+01:00", + "2019-01-01 16:00:00+01:00", + "2019-01-01 16:01:00+01:00", + "2019-01-01 16:02:00+01:00", + "2019-01-01 16:03:00+01:00", + "2019-01-01 16:04:00+01:00", + "2019-01-01 16:05:00+01:00", + "2019-01-01 16:06:00+01:00", + "2019-01-01 16:07:00+01:00", + "2019-01-01 16:08:00+01:00", + "2019-01-01 16:09:00+01:00", + "2019-01-01 16:10:00+01:00", + "2019-01-01 16:11:00+01:00", + "2019-01-01 16:12:00+01:00", + "2019-01-01 16:13:00+01:00", + "2019-01-01 16:14:00+01:00", + "2019-01-01 16:15:00+01:00", + "2019-01-01 16:16:00+01:00", + "2019-01-01 16:17:00+01:00", + "2019-01-01 16:18:00+01:00", + "2019-01-01 16:19:00+01:00", + "2019-01-01 16:20:00+01:00", + "2019-01-01 16:21:00+01:00", + "2019-01-01 16:22:00+01:00", + "2019-01-01 16:23:00+01:00", + "2019-01-01 16:24:00+01:00", + "2019-01-01 16:25:00+01:00", + "2019-01-01 16:26:00+01:00", + "2019-01-01 16:27:00+01:00", + "2019-01-01 16:28:00+01:00", + "2019-01-01 16:29:00+01:00", + "2019-01-01 16:30:00+01:00", + "2019-01-01 16:31:00+01:00", + "2019-01-01 16:32:00+01:00", + "2019-01-01 16:33:00+01:00", + "2019-01-01 16:34:00+01:00", + "2019-01-01 16:35:00+01:00", + "2019-01-01 16:36:00+01:00", + "2019-01-01 16:37:00+01:00", + "2019-01-01 16:38:00+01:00", + "2019-01-01 16:39:00+01:00", + "2019-01-01 16:40:00+01:00", + "2019-01-01 16:41:00+01:00", + "2019-01-01 16:42:00+01:00", + "2019-01-01 16:43:00+01:00", + "2019-01-01 16:44:00+01:00", + "2019-01-01 16:45:00+01:00", + "2019-01-01 16:46:00+01:00", + "2019-01-01 16:47:00+01:00", + "2019-01-01 16:48:00+01:00", + "2019-01-01 16:49:00+01:00", + "2019-01-01 16:50:00+01:00", + "2019-01-01 16:51:00+01:00", + "2019-01-01 16:52:00+01:00", + "2019-01-01 16:53:00+01:00", + "2019-01-01 16:54:00+01:00", + "2019-01-01 16:55:00+01:00", + "2019-01-01 16:56:00+01:00", + "2019-01-01 16:57:00+01:00", + "2019-01-01 16:58:00+01:00", + "2019-01-01 16:59:00+01:00", + "2019-01-01 17:00:00+01:00", + "2019-01-01 17:01:00+01:00", + "2019-01-01 17:02:00+01:00", + "2019-01-01 17:03:00+01:00", + "2019-01-01 17:04:00+01:00", + "2019-01-01 17:05:00+01:00", + "2019-01-01 17:06:00+01:00", + "2019-01-01 17:07:00+01:00", + "2019-01-01 17:08:00+01:00", + "2019-01-01 17:09:00+01:00", + "2019-01-01 17:10:00+01:00", + "2019-01-01 17:11:00+01:00", + "2019-01-01 17:12:00+01:00", + "2019-01-01 17:13:00+01:00", + "2019-01-01 17:14:00+01:00", + "2019-01-01 17:15:00+01:00", + "2019-01-01 17:16:00+01:00", + "2019-01-01 17:17:00+01:00", + "2019-01-01 17:18:00+01:00", + "2019-01-01 17:19:00+01:00", + "2019-01-01 17:20:00+01:00", + "2019-01-01 17:21:00+01:00", + "2019-01-01 17:22:00+01:00", + "2019-01-01 17:23:00+01:00", + "2019-01-01 17:24:00+01:00", + "2019-01-01 17:25:00+01:00", + "2019-01-01 17:26:00+01:00", + "2019-01-01 17:27:00+01:00", + "2019-01-01 17:28:00+01:00", + "2019-01-01 17:29:00+01:00", + "2019-01-01 17:30:00+01:00", + "2019-01-01 17:31:00+01:00", + "2019-01-01 17:32:00+01:00", + "2019-01-01 17:33:00+01:00", + "2019-01-01 17:34:00+01:00", + "2019-01-01 17:35:00+01:00", + "2019-01-01 17:36:00+01:00", + "2019-01-01 17:37:00+01:00", + "2019-01-01 17:38:00+01:00", + "2019-01-01 17:39:00+01:00", + "2019-01-01 17:40:00+01:00", + "2019-01-01 17:41:00+01:00", + "2019-01-01 17:42:00+01:00", + "2019-01-01 17:43:00+01:00", + "2019-01-01 17:44:00+01:00", + "2019-01-01 17:45:00+01:00", + "2019-01-01 17:46:00+01:00", + "2019-01-01 17:47:00+01:00", + "2019-01-01 17:48:00+01:00", + "2019-01-01 17:49:00+01:00", + "2019-01-01 17:50:00+01:00", + "2019-01-01 17:51:00+01:00", + "2019-01-01 17:52:00+01:00", + "2019-01-01 17:53:00+01:00", + "2019-01-01 17:54:00+01:00", + "2019-01-01 17:55:00+01:00", + "2019-01-01 17:56:00+01:00", + "2019-01-01 17:57:00+01:00", + "2019-01-01 17:58:00+01:00", + "2019-01-01 17:59:00+01:00", + "2019-01-01 18:00:00+01:00", + "2019-01-01 18:01:00+01:00", + "2019-01-01 18:02:00+01:00", + "2019-01-01 18:03:00+01:00", + "2019-01-01 18:04:00+01:00", + "2019-01-01 18:05:00+01:00", + "2019-01-01 18:06:00+01:00", + "2019-01-01 18:07:00+01:00", + "2019-01-01 18:08:00+01:00", + "2019-01-01 18:09:00+01:00", + "2019-01-01 18:10:00+01:00", + "2019-01-01 18:11:00+01:00", + "2019-01-01 18:12:00+01:00", + "2019-01-01 18:13:00+01:00", + "2019-01-01 18:14:00+01:00", + "2019-01-01 18:15:00+01:00", + "2019-01-01 18:16:00+01:00", + "2019-01-01 18:17:00+01:00", + "2019-01-01 18:18:00+01:00", + "2019-01-01 18:19:00+01:00", + "2019-01-01 18:20:00+01:00", + "2019-01-01 18:21:00+01:00", + "2019-01-01 18:22:00+01:00", + "2019-01-01 18:23:00+01:00", + "2019-01-01 18:24:00+01:00", + "2019-01-01 18:25:00+01:00", + "2019-01-01 18:26:00+01:00", + "2019-01-01 18:27:00+01:00", + "2019-01-01 18:28:00+01:00", + "2019-01-01 18:29:00+01:00", + "2019-01-01 18:30:00+01:00", + "2019-01-01 18:31:00+01:00", + "2019-01-01 18:32:00+01:00", + "2019-01-01 18:33:00+01:00", + "2019-01-01 18:34:00+01:00", + "2019-01-01 18:35:00+01:00", + "2019-01-01 18:36:00+01:00", + "2019-01-01 18:37:00+01:00", + "2019-01-01 18:38:00+01:00", + "2019-01-01 18:39:00+01:00", + "2019-01-01 18:40:00+01:00", + "2019-01-01 18:41:00+01:00", + "2019-01-01 18:42:00+01:00", + "2019-01-01 18:43:00+01:00", + "2019-01-01 18:44:00+01:00", + "2019-01-01 18:45:00+01:00", + "2019-01-01 18:46:00+01:00", + "2019-01-01 18:47:00+01:00", + "2019-01-01 18:48:00+01:00", + "2019-01-01 18:49:00+01:00", + "2019-01-01 18:50:00+01:00", + "2019-01-01 18:51:00+01:00", + "2019-01-01 18:52:00+01:00", + "2019-01-01 18:53:00+01:00", + "2019-01-01 18:54:00+01:00", + "2019-01-01 18:55:00+01:00", + "2019-01-01 18:56:00+01:00", + "2019-01-01 18:57:00+01:00", + "2019-01-01 18:58:00+01:00", + "2019-01-01 18:59:00+01:00", + "2019-01-01 19:00:00+01:00", + "2019-01-01 19:01:00+01:00", + "2019-01-01 19:02:00+01:00", + "2019-01-01 19:03:00+01:00", + "2019-01-01 19:04:00+01:00", + "2019-01-01 19:05:00+01:00", + "2019-01-01 19:06:00+01:00", + "2019-01-01 19:07:00+01:00", + "2019-01-01 19:08:00+01:00", + "2019-01-01 19:09:00+01:00", + "2019-01-01 19:10:00+01:00", + "2019-01-01 19:11:00+01:00", + "2019-01-01 19:12:00+01:00", + "2019-01-01 19:13:00+01:00", + "2019-01-01 19:14:00+01:00", + "2019-01-01 19:15:00+01:00", + "2019-01-01 19:16:00+01:00", + "2019-01-01 19:17:00+01:00", + "2019-01-01 19:18:00+01:00", + "2019-01-01 19:19:00+01:00", + "2019-01-01 19:20:00+01:00", + "2019-01-01 19:21:00+01:00", + "2019-01-01 19:22:00+01:00", + "2019-01-01 19:23:00+01:00", + "2019-01-01 19:24:00+01:00", + "2019-01-01 19:25:00+01:00", + "2019-01-01 19:26:00+01:00", + "2019-01-01 19:27:00+01:00", + "2019-01-01 19:28:00+01:00", + "2019-01-01 19:29:00+01:00", + "2019-01-01 19:30:00+01:00", + "2019-01-01 19:31:00+01:00", + "2019-01-01 19:32:00+01:00", + "2019-01-01 19:33:00+01:00", + "2019-01-01 19:34:00+01:00", + "2019-01-01 19:35:00+01:00", + "2019-01-01 19:36:00+01:00", + "2019-01-01 19:37:00+01:00", + "2019-01-01 19:38:00+01:00", + "2019-01-01 19:39:00+01:00", + "2019-01-01 19:40:00+01:00", + "2019-01-01 19:41:00+01:00", + "2019-01-01 19:42:00+01:00", + "2019-01-01 19:43:00+01:00", + "2019-01-01 19:44:00+01:00", + "2019-01-01 19:45:00+01:00", + "2019-01-01 19:46:00+01:00", + "2019-01-01 19:47:00+01:00", + "2019-01-01 19:48:00+01:00", + "2019-01-01 19:49:00+01:00", + "2019-01-01 19:50:00+01:00", + "2019-01-01 19:51:00+01:00", + "2019-01-01 19:52:00+01:00", + "2019-01-01 19:53:00+01:00", + "2019-01-01 19:54:00+01:00", + "2019-01-01 19:55:00+01:00", + "2019-01-01 19:56:00+01:00", + "2019-01-01 19:57:00+01:00", + "2019-01-01 19:58:00+01:00", + "2019-01-01 19:59:00+01:00", + "2019-01-01 20:00:00+01:00", + "2019-01-01 20:01:00+01:00", + "2019-01-01 20:02:00+01:00", + "2019-01-01 20:03:00+01:00", + "2019-01-01 20:04:00+01:00", + "2019-01-01 20:05:00+01:00", + "2019-01-01 20:06:00+01:00", + "2019-01-01 20:07:00+01:00", + "2019-01-01 20:08:00+01:00", + "2019-01-01 20:09:00+01:00", + "2019-01-01 20:10:00+01:00", + "2019-01-01 20:11:00+01:00", + "2019-01-01 20:12:00+01:00", + "2019-01-01 20:13:00+01:00", + "2019-01-01 20:14:00+01:00", + "2019-01-01 20:15:00+01:00", + "2019-01-01 20:16:00+01:00", + "2019-01-01 20:17:00+01:00", + "2019-01-01 20:18:00+01:00", + "2019-01-01 20:19:00+01:00", + "2019-01-01 20:20:00+01:00", + "2019-01-01 20:21:00+01:00", + "2019-01-01 20:22:00+01:00", + "2019-01-01 20:23:00+01:00", + "2019-01-01 20:24:00+01:00", + "2019-01-01 20:25:00+01:00", + "2019-01-01 20:26:00+01:00", + "2019-01-01 20:27:00+01:00", + "2019-01-01 20:28:00+01:00", + "2019-01-01 20:29:00+01:00", + "2019-01-01 20:30:00+01:00", + "2019-01-01 20:31:00+01:00", + "2019-01-01 20:32:00+01:00", + "2019-01-01 20:33:00+01:00", + "2019-01-01 20:34:00+01:00", + "2019-01-01 20:35:00+01:00", + "2019-01-01 20:36:00+01:00", + "2019-01-01 20:37:00+01:00", + "2019-01-01 20:38:00+01:00", + "2019-01-01 20:39:00+01:00", + "2019-01-01 20:40:00+01:00", + "2019-01-01 20:41:00+01:00", + "2019-01-01 20:42:00+01:00", + "2019-01-01 20:43:00+01:00", + "2019-01-01 20:44:00+01:00", + "2019-01-01 20:45:00+01:00", + "2019-01-01 20:46:00+01:00", + "2019-01-01 20:47:00+01:00", + "2019-01-01 20:48:00+01:00", + "2019-01-01 20:49:00+01:00", + "2019-01-01 20:50:00+01:00", + "2019-01-01 20:51:00+01:00", + "2019-01-01 20:52:00+01:00", + "2019-01-01 20:53:00+01:00", + "2019-01-01 20:54:00+01:00", + "2019-01-01 20:55:00+01:00", + "2019-01-01 20:56:00+01:00", + "2019-01-01 20:57:00+01:00", + "2019-01-01 20:58:00+01:00", + "2019-01-01 20:59:00+01:00", + "2019-01-01 21:00:00+01:00", + "2019-01-01 21:01:00+01:00", + "2019-01-01 21:02:00+01:00", + "2019-01-01 21:03:00+01:00", + "2019-01-01 21:04:00+01:00", + "2019-01-01 21:05:00+01:00", + "2019-01-01 21:06:00+01:00", + "2019-01-01 21:07:00+01:00", + "2019-01-01 21:08:00+01:00", + "2019-01-01 21:09:00+01:00", + "2019-01-01 21:10:00+01:00", + "2019-01-01 21:11:00+01:00", + "2019-01-01 21:12:00+01:00", + "2019-01-01 21:13:00+01:00", + "2019-01-01 21:14:00+01:00", + "2019-01-01 21:15:00+01:00", + "2019-01-01 21:16:00+01:00", + "2019-01-01 21:17:00+01:00", + "2019-01-01 21:18:00+01:00", + "2019-01-01 21:19:00+01:00", + "2019-01-01 21:20:00+01:00", + "2019-01-01 21:21:00+01:00", + "2019-01-01 21:22:00+01:00", + "2019-01-01 21:23:00+01:00", + "2019-01-01 21:24:00+01:00", + "2019-01-01 21:25:00+01:00", + "2019-01-01 21:26:00+01:00", + "2019-01-01 21:27:00+01:00", + "2019-01-01 21:28:00+01:00", + "2019-01-01 21:29:00+01:00", + "2019-01-01 21:30:00+01:00", + "2019-01-01 21:31:00+01:00", + "2019-01-01 21:32:00+01:00", + "2019-01-01 21:33:00+01:00", + "2019-01-01 21:34:00+01:00", + "2019-01-01 21:35:00+01:00", + "2019-01-01 21:36:00+01:00", + "2019-01-01 21:37:00+01:00", + "2019-01-01 21:38:00+01:00", + "2019-01-01 21:39:00+01:00", + "2019-01-01 21:40:00+01:00", + "2019-01-01 21:41:00+01:00", + "2019-01-01 21:42:00+01:00", + "2019-01-01 21:43:00+01:00", + "2019-01-01 21:44:00+01:00", + "2019-01-01 21:45:00+01:00", + "2019-01-01 21:46:00+01:00", + "2019-01-01 21:47:00+01:00", + "2019-01-01 21:48:00+01:00", + "2019-01-01 21:49:00+01:00", + "2019-01-01 21:50:00+01:00", + "2019-01-01 21:51:00+01:00", + "2019-01-01 21:52:00+01:00", + "2019-01-01 21:53:00+01:00", + "2019-01-01 21:54:00+01:00", + "2019-01-01 21:55:00+01:00", + "2019-01-01 21:56:00+01:00", + "2019-01-01 21:57:00+01:00", + "2019-01-01 21:58:00+01:00", + "2019-01-01 21:59:00+01:00", + "2019-01-01 22:00:00+01:00", + "2019-01-01 22:01:00+01:00", + "2019-01-01 22:02:00+01:00", + "2019-01-01 22:03:00+01:00", + "2019-01-01 22:04:00+01:00", + "2019-01-01 22:05:00+01:00", + "2019-01-01 22:06:00+01:00", + "2019-01-01 22:07:00+01:00", + "2019-01-01 22:08:00+01:00", + "2019-01-01 22:09:00+01:00", + "2019-01-01 22:10:00+01:00", + "2019-01-01 22:11:00+01:00", + "2019-01-01 22:12:00+01:00", + "2019-01-01 22:13:00+01:00", + "2019-01-01 22:14:00+01:00", + "2019-01-01 22:15:00+01:00", + "2019-01-01 22:16:00+01:00", + "2019-01-01 22:17:00+01:00", + "2019-01-01 22:18:00+01:00", + "2019-01-01 22:19:00+01:00", + "2019-01-01 22:20:00+01:00", + "2019-01-01 22:21:00+01:00", + "2019-01-01 22:22:00+01:00", + "2019-01-01 22:23:00+01:00", + "2019-01-01 22:24:00+01:00", + "2019-01-01 22:25:00+01:00", + "2019-01-01 22:26:00+01:00", + "2019-01-01 22:27:00+01:00", + "2019-01-01 22:28:00+01:00", + "2019-01-01 22:29:00+01:00", + "2019-01-01 22:30:00+01:00", + "2019-01-01 22:31:00+01:00", + "2019-01-01 22:32:00+01:00", + "2019-01-01 22:33:00+01:00", + "2019-01-01 22:34:00+01:00", + "2019-01-01 22:35:00+01:00", + "2019-01-01 22:36:00+01:00", + "2019-01-01 22:37:00+01:00", + "2019-01-01 22:38:00+01:00", + "2019-01-01 22:39:00+01:00", + "2019-01-01 22:40:00+01:00", + "2019-01-01 22:41:00+01:00", + "2019-01-01 22:42:00+01:00", + "2019-01-01 22:43:00+01:00", + "2019-01-01 22:44:00+01:00", + "2019-01-01 22:45:00+01:00", + "2019-01-01 22:46:00+01:00", + "2019-01-01 22:47:00+01:00", + "2019-01-01 22:48:00+01:00", + "2019-01-01 22:49:00+01:00", + "2019-01-01 22:50:00+01:00", + "2019-01-01 22:51:00+01:00", + "2019-01-01 22:52:00+01:00", + "2019-01-01 22:53:00+01:00", + "2019-01-01 22:54:00+01:00", + "2019-01-01 22:55:00+01:00", + "2019-01-01 22:56:00+01:00", + "2019-01-01 22:57:00+01:00", + "2019-01-01 22:58:00+01:00", + "2019-01-01 22:59:00+01:00", + "2019-01-01 23:00:00+01:00", + "2019-01-01 23:01:00+01:00", + "2019-01-01 23:02:00+01:00", + "2019-01-01 23:03:00+01:00", + "2019-01-01 23:04:00+01:00", + "2019-01-01 23:05:00+01:00", + "2019-01-01 23:06:00+01:00", + "2019-01-01 23:07:00+01:00", + "2019-01-01 23:08:00+01:00", + "2019-01-01 23:09:00+01:00", + "2019-01-01 23:10:00+01:00", + "2019-01-01 23:11:00+01:00", + "2019-01-01 23:12:00+01:00", + "2019-01-01 23:13:00+01:00", + "2019-01-01 23:14:00+01:00", + "2019-01-01 23:15:00+01:00", + "2019-01-01 23:16:00+01:00", + "2019-01-01 23:17:00+01:00", + "2019-01-01 23:18:00+01:00", + "2019-01-01 23:19:00+01:00", + "2019-01-01 23:20:00+01:00", + "2019-01-01 23:21:00+01:00", + "2019-01-01 23:22:00+01:00", + "2019-01-01 23:23:00+01:00", + "2019-01-01 23:24:00+01:00", + "2019-01-01 23:25:00+01:00", + "2019-01-01 23:26:00+01:00", + "2019-01-01 23:27:00+01:00", + "2019-01-01 23:28:00+01:00", + "2019-01-01 23:29:00+01:00", + "2019-01-01 23:30:00+01:00", + "2019-01-01 23:31:00+01:00", + "2019-01-01 23:32:00+01:00", + "2019-01-01 23:33:00+01:00", + "2019-01-01 23:34:00+01:00", + "2019-01-01 23:35:00+01:00", + "2019-01-01 23:36:00+01:00", + "2019-01-01 23:37:00+01:00", + "2019-01-01 23:38:00+01:00", + "2019-01-01 23:39:00+01:00", + "2019-01-01 23:40:00+01:00", + "2019-01-01 23:41:00+01:00", + "2019-01-01 23:42:00+01:00", + "2019-01-01 23:43:00+01:00", + "2019-01-01 23:44:00+01:00", + "2019-01-01 23:45:00+01:00", + "2019-01-01 23:46:00+01:00", + "2019-01-01 23:47:00+01:00", + "2019-01-01 23:48:00+01:00", + "2019-01-01 23:49:00+01:00", + "2019-01-01 23:50:00+01:00", + "2019-01-01 23:51:00+01:00", + "2019-01-01 23:52:00+01:00", + "2019-01-01 23:53:00+01:00", + "2019-01-01 23:54:00+01:00", + "2019-01-01 23:55:00+01:00", + "2019-01-01 23:56:00+01:00", + "2019-01-01 23:57:00+01:00", + "2019-01-01 23:58:00+01:00", + "2019-01-01 23:59:00+01:00" + ], + "xaxis": "x", + "y": [ + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286, + -1.4285714285714286 + ], + "yaxis": "y" + }, + { + "line": { + "color": "rgba(55, 128, 191, 1.0)", + "dash": "solid", + "shape": "linear", + "width": 1.3 + }, + "mode": "lines", + "name": "output_MW", + "text": "", + "type": "scatter", + "x": [ + "2019-01-01 00:00:00+01:00", + "2019-01-01 00:01:00+01:00", + "2019-01-01 00:02:00+01:00", + "2019-01-01 00:03:00+01:00", + "2019-01-01 00:04:00+01:00", + "2019-01-01 00:05:00+01:00", + "2019-01-01 00:06:00+01:00", + "2019-01-01 00:07:00+01:00", + "2019-01-01 00:08:00+01:00", + "2019-01-01 00:09:00+01:00", + "2019-01-01 00:10:00+01:00", + "2019-01-01 00:11:00+01:00", + "2019-01-01 00:12:00+01:00", + "2019-01-01 00:13:00+01:00", + "2019-01-01 00:14:00+01:00", + "2019-01-01 00:15:00+01:00", + "2019-01-01 00:16:00+01:00", + "2019-01-01 00:17:00+01:00", + "2019-01-01 00:18:00+01:00", + "2019-01-01 00:19:00+01:00", + "2019-01-01 00:20:00+01:00", + "2019-01-01 00:21:00+01:00", + "2019-01-01 00:22:00+01:00", + "2019-01-01 00:23:00+01:00", + "2019-01-01 00:24:00+01:00", + "2019-01-01 00:25:00+01:00", + "2019-01-01 00:26:00+01:00", + "2019-01-01 00:27:00+01:00", + "2019-01-01 00:28:00+01:00", + "2019-01-01 00:29:00+01:00", + "2019-01-01 00:30:00+01:00", + "2019-01-01 00:31:00+01:00", + "2019-01-01 00:32:00+01:00", + "2019-01-01 00:33:00+01:00", + "2019-01-01 00:34:00+01:00", + "2019-01-01 00:35:00+01:00", + "2019-01-01 00:36:00+01:00", + "2019-01-01 00:37:00+01:00", + "2019-01-01 00:38:00+01:00", + "2019-01-01 00:39:00+01:00", + "2019-01-01 00:40:00+01:00", + "2019-01-01 00:41:00+01:00", + "2019-01-01 00:42:00+01:00", + "2019-01-01 00:43:00+01:00", + "2019-01-01 00:44:00+01:00", + "2019-01-01 00:45:00+01:00", + "2019-01-01 00:46:00+01:00", + "2019-01-01 00:47:00+01:00", + "2019-01-01 00:48:00+01:00", + "2019-01-01 00:49:00+01:00", + "2019-01-01 00:50:00+01:00", + "2019-01-01 00:51:00+01:00", + "2019-01-01 00:52:00+01:00", + "2019-01-01 00:53:00+01:00", + "2019-01-01 00:54:00+01:00", + "2019-01-01 00:55:00+01:00", + "2019-01-01 00:56:00+01:00", + "2019-01-01 00:57:00+01:00", + "2019-01-01 00:58:00+01:00", + "2019-01-01 00:59:00+01:00", + "2019-01-01 01:00:00+01:00", + "2019-01-01 01:01:00+01:00", + "2019-01-01 01:02:00+01:00", + "2019-01-01 01:03:00+01:00", + "2019-01-01 01:04:00+01:00", + "2019-01-01 01:05:00+01:00", + "2019-01-01 01:06:00+01:00", + "2019-01-01 01:07:00+01:00", + "2019-01-01 01:08:00+01:00", + "2019-01-01 01:09:00+01:00", + "2019-01-01 01:10:00+01:00", + "2019-01-01 01:11:00+01:00", + "2019-01-01 01:12:00+01:00", + "2019-01-01 01:13:00+01:00", + "2019-01-01 01:14:00+01:00", + "2019-01-01 01:15:00+01:00", + "2019-01-01 01:16:00+01:00", + "2019-01-01 01:17:00+01:00", + "2019-01-01 01:18:00+01:00", + "2019-01-01 01:19:00+01:00", + "2019-01-01 01:20:00+01:00", + "2019-01-01 01:21:00+01:00", + "2019-01-01 01:22:00+01:00", + "2019-01-01 01:23:00+01:00", + "2019-01-01 01:24:00+01:00", + "2019-01-01 01:25:00+01:00", + "2019-01-01 01:26:00+01:00", + "2019-01-01 01:27:00+01:00", + "2019-01-01 01:28:00+01:00", + "2019-01-01 01:29:00+01:00", + "2019-01-01 01:30:00+01:00", + "2019-01-01 01:31:00+01:00", + "2019-01-01 01:32:00+01:00", + "2019-01-01 01:33:00+01:00", + "2019-01-01 01:34:00+01:00", + "2019-01-01 01:35:00+01:00", + "2019-01-01 01:36:00+01:00", + "2019-01-01 01:37:00+01:00", + "2019-01-01 01:38:00+01:00", + "2019-01-01 01:39:00+01:00", + "2019-01-01 01:40:00+01:00", + "2019-01-01 01:41:00+01:00", + "2019-01-01 01:42:00+01:00", + "2019-01-01 01:43:00+01:00", + "2019-01-01 01:44:00+01:00", + "2019-01-01 01:45:00+01:00", + "2019-01-01 01:46:00+01:00", + "2019-01-01 01:47:00+01:00", + "2019-01-01 01:48:00+01:00", + "2019-01-01 01:49:00+01:00", + "2019-01-01 01:50:00+01:00", + "2019-01-01 01:51:00+01:00", + "2019-01-01 01:52:00+01:00", + "2019-01-01 01:53:00+01:00", + "2019-01-01 01:54:00+01:00", + "2019-01-01 01:55:00+01:00", + "2019-01-01 01:56:00+01:00", + "2019-01-01 01:57:00+01:00", + "2019-01-01 01:58:00+01:00", + "2019-01-01 01:59:00+01:00", + "2019-01-01 02:00:00+01:00", + "2019-01-01 02:01:00+01:00", + "2019-01-01 02:02:00+01:00", + "2019-01-01 02:03:00+01:00", + "2019-01-01 02:04:00+01:00", + "2019-01-01 02:05:00+01:00", + "2019-01-01 02:06:00+01:00", + "2019-01-01 02:07:00+01:00", + "2019-01-01 02:08:00+01:00", + "2019-01-01 02:09:00+01:00", + "2019-01-01 02:10:00+01:00", + "2019-01-01 02:11:00+01:00", + "2019-01-01 02:12:00+01:00", + "2019-01-01 02:13:00+01:00", + "2019-01-01 02:14:00+01:00", + "2019-01-01 02:15:00+01:00", + "2019-01-01 02:16:00+01:00", + "2019-01-01 02:17:00+01:00", + "2019-01-01 02:18:00+01:00", + "2019-01-01 02:19:00+01:00", + "2019-01-01 02:20:00+01:00", + "2019-01-01 02:21:00+01:00", + "2019-01-01 02:22:00+01:00", + "2019-01-01 02:23:00+01:00", + "2019-01-01 02:24:00+01:00", + "2019-01-01 02:25:00+01:00", + "2019-01-01 02:26:00+01:00", + "2019-01-01 02:27:00+01:00", + "2019-01-01 02:28:00+01:00", + "2019-01-01 02:29:00+01:00", + "2019-01-01 02:30:00+01:00", + "2019-01-01 02:31:00+01:00", + "2019-01-01 02:32:00+01:00", + "2019-01-01 02:33:00+01:00", + "2019-01-01 02:34:00+01:00", + "2019-01-01 02:35:00+01:00", + "2019-01-01 02:36:00+01:00", + "2019-01-01 02:37:00+01:00", + "2019-01-01 02:38:00+01:00", + "2019-01-01 02:39:00+01:00", + "2019-01-01 02:40:00+01:00", + "2019-01-01 02:41:00+01:00", + "2019-01-01 02:42:00+01:00", + "2019-01-01 02:43:00+01:00", + "2019-01-01 02:44:00+01:00", + "2019-01-01 02:45:00+01:00", + "2019-01-01 02:46:00+01:00", + "2019-01-01 02:47:00+01:00", + "2019-01-01 02:48:00+01:00", + "2019-01-01 02:49:00+01:00", + "2019-01-01 02:50:00+01:00", + "2019-01-01 02:51:00+01:00", + "2019-01-01 02:52:00+01:00", + "2019-01-01 02:53:00+01:00", + "2019-01-01 02:54:00+01:00", + "2019-01-01 02:55:00+01:00", + "2019-01-01 02:56:00+01:00", + "2019-01-01 02:57:00+01:00", + "2019-01-01 02:58:00+01:00", + "2019-01-01 02:59:00+01:00", + "2019-01-01 03:00:00+01:00", + "2019-01-01 03:01:00+01:00", + "2019-01-01 03:02:00+01:00", + "2019-01-01 03:03:00+01:00", + "2019-01-01 03:04:00+01:00", + "2019-01-01 03:05:00+01:00", + "2019-01-01 03:06:00+01:00", + "2019-01-01 03:07:00+01:00", + "2019-01-01 03:08:00+01:00", + "2019-01-01 03:09:00+01:00", + "2019-01-01 03:10:00+01:00", + "2019-01-01 03:11:00+01:00", + "2019-01-01 03:12:00+01:00", + "2019-01-01 03:13:00+01:00", + "2019-01-01 03:14:00+01:00", + "2019-01-01 03:15:00+01:00", + "2019-01-01 03:16:00+01:00", + "2019-01-01 03:17:00+01:00", + "2019-01-01 03:18:00+01:00", + "2019-01-01 03:19:00+01:00", + "2019-01-01 03:20:00+01:00", + "2019-01-01 03:21:00+01:00", + "2019-01-01 03:22:00+01:00", + "2019-01-01 03:23:00+01:00", + "2019-01-01 03:24:00+01:00", + "2019-01-01 03:25:00+01:00", + "2019-01-01 03:26:00+01:00", + "2019-01-01 03:27:00+01:00", + "2019-01-01 03:28:00+01:00", + "2019-01-01 03:29:00+01:00", + "2019-01-01 03:30:00+01:00", + "2019-01-01 03:31:00+01:00", + "2019-01-01 03:32:00+01:00", + "2019-01-01 03:33:00+01:00", + "2019-01-01 03:34:00+01:00", + "2019-01-01 03:35:00+01:00", + "2019-01-01 03:36:00+01:00", + "2019-01-01 03:37:00+01:00", + "2019-01-01 03:38:00+01:00", + "2019-01-01 03:39:00+01:00", + "2019-01-01 03:40:00+01:00", + "2019-01-01 03:41:00+01:00", + "2019-01-01 03:42:00+01:00", + "2019-01-01 03:43:00+01:00", + "2019-01-01 03:44:00+01:00", + "2019-01-01 03:45:00+01:00", + "2019-01-01 03:46:00+01:00", + "2019-01-01 03:47:00+01:00", + "2019-01-01 03:48:00+01:00", + "2019-01-01 03:49:00+01:00", + "2019-01-01 03:50:00+01:00", + "2019-01-01 03:51:00+01:00", + "2019-01-01 03:52:00+01:00", + "2019-01-01 03:53:00+01:00", + "2019-01-01 03:54:00+01:00", + "2019-01-01 03:55:00+01:00", + "2019-01-01 03:56:00+01:00", + "2019-01-01 03:57:00+01:00", + "2019-01-01 03:58:00+01:00", + "2019-01-01 03:59:00+01:00", + "2019-01-01 04:00:00+01:00", + "2019-01-01 04:01:00+01:00", + "2019-01-01 04:02:00+01:00", + "2019-01-01 04:03:00+01:00", + "2019-01-01 04:04:00+01:00", + "2019-01-01 04:05:00+01:00", + "2019-01-01 04:06:00+01:00", + "2019-01-01 04:07:00+01:00", + "2019-01-01 04:08:00+01:00", + "2019-01-01 04:09:00+01:00", + "2019-01-01 04:10:00+01:00", + "2019-01-01 04:11:00+01:00", + "2019-01-01 04:12:00+01:00", + "2019-01-01 04:13:00+01:00", + "2019-01-01 04:14:00+01:00", + "2019-01-01 04:15:00+01:00", + "2019-01-01 04:16:00+01:00", + "2019-01-01 04:17:00+01:00", + "2019-01-01 04:18:00+01:00", + "2019-01-01 04:19:00+01:00", + "2019-01-01 04:20:00+01:00", + "2019-01-01 04:21:00+01:00", + "2019-01-01 04:22:00+01:00", + "2019-01-01 04:23:00+01:00", + "2019-01-01 04:24:00+01:00", + "2019-01-01 04:25:00+01:00", + "2019-01-01 04:26:00+01:00", + "2019-01-01 04:27:00+01:00", + "2019-01-01 04:28:00+01:00", + "2019-01-01 04:29:00+01:00", + "2019-01-01 04:30:00+01:00", + "2019-01-01 04:31:00+01:00", + "2019-01-01 04:32:00+01:00", + "2019-01-01 04:33:00+01:00", + "2019-01-01 04:34:00+01:00", + "2019-01-01 04:35:00+01:00", + "2019-01-01 04:36:00+01:00", + "2019-01-01 04:37:00+01:00", + "2019-01-01 04:38:00+01:00", + "2019-01-01 04:39:00+01:00", + "2019-01-01 04:40:00+01:00", + "2019-01-01 04:41:00+01:00", + "2019-01-01 04:42:00+01:00", + "2019-01-01 04:43:00+01:00", + "2019-01-01 04:44:00+01:00", + "2019-01-01 04:45:00+01:00", + "2019-01-01 04:46:00+01:00", + "2019-01-01 04:47:00+01:00", + "2019-01-01 04:48:00+01:00", + "2019-01-01 04:49:00+01:00", + "2019-01-01 04:50:00+01:00", + "2019-01-01 04:51:00+01:00", + "2019-01-01 04:52:00+01:00", + "2019-01-01 04:53:00+01:00", + "2019-01-01 04:54:00+01:00", + "2019-01-01 04:55:00+01:00", + "2019-01-01 04:56:00+01:00", + "2019-01-01 04:57:00+01:00", + "2019-01-01 04:58:00+01:00", + "2019-01-01 04:59:00+01:00", + "2019-01-01 05:00:00+01:00", + "2019-01-01 05:01:00+01:00", + "2019-01-01 05:02:00+01:00", + "2019-01-01 05:03:00+01:00", + "2019-01-01 05:04:00+01:00", + "2019-01-01 05:05:00+01:00", + "2019-01-01 05:06:00+01:00", + "2019-01-01 05:07:00+01:00", + "2019-01-01 05:08:00+01:00", + "2019-01-01 05:09:00+01:00", + "2019-01-01 05:10:00+01:00", + "2019-01-01 05:11:00+01:00", + "2019-01-01 05:12:00+01:00", + "2019-01-01 05:13:00+01:00", + "2019-01-01 05:14:00+01:00", + "2019-01-01 05:15:00+01:00", + "2019-01-01 05:16:00+01:00", + "2019-01-01 05:17:00+01:00", + "2019-01-01 05:18:00+01:00", + "2019-01-01 05:19:00+01:00", + "2019-01-01 05:20:00+01:00", + "2019-01-01 05:21:00+01:00", + "2019-01-01 05:22:00+01:00", + "2019-01-01 05:23:00+01:00", + "2019-01-01 05:24:00+01:00", + "2019-01-01 05:25:00+01:00", + "2019-01-01 05:26:00+01:00", + "2019-01-01 05:27:00+01:00", + "2019-01-01 05:28:00+01:00", + "2019-01-01 05:29:00+01:00", + "2019-01-01 05:30:00+01:00", + "2019-01-01 05:31:00+01:00", + "2019-01-01 05:32:00+01:00", + "2019-01-01 05:33:00+01:00", + "2019-01-01 05:34:00+01:00", + "2019-01-01 05:35:00+01:00", + "2019-01-01 05:36:00+01:00", + "2019-01-01 05:37:00+01:00", + "2019-01-01 05:38:00+01:00", + "2019-01-01 05:39:00+01:00", + "2019-01-01 05:40:00+01:00", + "2019-01-01 05:41:00+01:00", + "2019-01-01 05:42:00+01:00", + "2019-01-01 05:43:00+01:00", + "2019-01-01 05:44:00+01:00", + "2019-01-01 05:45:00+01:00", + "2019-01-01 05:46:00+01:00", + "2019-01-01 05:47:00+01:00", + "2019-01-01 05:48:00+01:00", + "2019-01-01 05:49:00+01:00", + "2019-01-01 05:50:00+01:00", + "2019-01-01 05:51:00+01:00", + "2019-01-01 05:52:00+01:00", + "2019-01-01 05:53:00+01:00", + "2019-01-01 05:54:00+01:00", + "2019-01-01 05:55:00+01:00", + "2019-01-01 05:56:00+01:00", + "2019-01-01 05:57:00+01:00", + "2019-01-01 05:58:00+01:00", + "2019-01-01 05:59:00+01:00", + "2019-01-01 06:00:00+01:00", + "2019-01-01 06:01:00+01:00", + "2019-01-01 06:02:00+01:00", + "2019-01-01 06:03:00+01:00", + "2019-01-01 06:04:00+01:00", + "2019-01-01 06:05:00+01:00", + "2019-01-01 06:06:00+01:00", + "2019-01-01 06:07:00+01:00", + "2019-01-01 06:08:00+01:00", + "2019-01-01 06:09:00+01:00", + "2019-01-01 06:10:00+01:00", + "2019-01-01 06:11:00+01:00", + "2019-01-01 06:12:00+01:00", + "2019-01-01 06:13:00+01:00", + "2019-01-01 06:14:00+01:00", + "2019-01-01 06:15:00+01:00", + "2019-01-01 06:16:00+01:00", + "2019-01-01 06:17:00+01:00", + "2019-01-01 06:18:00+01:00", + "2019-01-01 06:19:00+01:00", + "2019-01-01 06:20:00+01:00", + "2019-01-01 06:21:00+01:00", + "2019-01-01 06:22:00+01:00", + "2019-01-01 06:23:00+01:00", + "2019-01-01 06:24:00+01:00", + "2019-01-01 06:25:00+01:00", + "2019-01-01 06:26:00+01:00", + "2019-01-01 06:27:00+01:00", + "2019-01-01 06:28:00+01:00", + "2019-01-01 06:29:00+01:00", + "2019-01-01 06:30:00+01:00", + "2019-01-01 06:31:00+01:00", + "2019-01-01 06:32:00+01:00", + "2019-01-01 06:33:00+01:00", + "2019-01-01 06:34:00+01:00", + "2019-01-01 06:35:00+01:00", + "2019-01-01 06:36:00+01:00", + "2019-01-01 06:37:00+01:00", + "2019-01-01 06:38:00+01:00", + "2019-01-01 06:39:00+01:00", + "2019-01-01 06:40:00+01:00", + "2019-01-01 06:41:00+01:00", + "2019-01-01 06:42:00+01:00", + "2019-01-01 06:43:00+01:00", + "2019-01-01 06:44:00+01:00", + "2019-01-01 06:45:00+01:00", + "2019-01-01 06:46:00+01:00", + "2019-01-01 06:47:00+01:00", + "2019-01-01 06:48:00+01:00", + "2019-01-01 06:49:00+01:00", + "2019-01-01 06:50:00+01:00", + "2019-01-01 06:51:00+01:00", + "2019-01-01 06:52:00+01:00", + "2019-01-01 06:53:00+01:00", + "2019-01-01 06:54:00+01:00", + "2019-01-01 06:55:00+01:00", + "2019-01-01 06:56:00+01:00", + "2019-01-01 06:57:00+01:00", + "2019-01-01 06:58:00+01:00", + "2019-01-01 06:59:00+01:00", + "2019-01-01 07:00:00+01:00", + "2019-01-01 07:01:00+01:00", + "2019-01-01 07:02:00+01:00", + "2019-01-01 07:03:00+01:00", + "2019-01-01 07:04:00+01:00", + "2019-01-01 07:05:00+01:00", + "2019-01-01 07:06:00+01:00", + "2019-01-01 07:07:00+01:00", + "2019-01-01 07:08:00+01:00", + "2019-01-01 07:09:00+01:00", + "2019-01-01 07:10:00+01:00", + "2019-01-01 07:11:00+01:00", + "2019-01-01 07:12:00+01:00", + "2019-01-01 07:13:00+01:00", + "2019-01-01 07:14:00+01:00", + "2019-01-01 07:15:00+01:00", + "2019-01-01 07:16:00+01:00", + "2019-01-01 07:17:00+01:00", + "2019-01-01 07:18:00+01:00", + "2019-01-01 07:19:00+01:00", + "2019-01-01 07:20:00+01:00", + "2019-01-01 07:21:00+01:00", + "2019-01-01 07:22:00+01:00", + "2019-01-01 07:23:00+01:00", + "2019-01-01 07:24:00+01:00", + "2019-01-01 07:25:00+01:00", + "2019-01-01 07:26:00+01:00", + "2019-01-01 07:27:00+01:00", + "2019-01-01 07:28:00+01:00", + "2019-01-01 07:29:00+01:00", + "2019-01-01 07:30:00+01:00", + "2019-01-01 07:31:00+01:00", + "2019-01-01 07:32:00+01:00", + "2019-01-01 07:33:00+01:00", + "2019-01-01 07:34:00+01:00", + "2019-01-01 07:35:00+01:00", + "2019-01-01 07:36:00+01:00", + "2019-01-01 07:37:00+01:00", + "2019-01-01 07:38:00+01:00", + "2019-01-01 07:39:00+01:00", + "2019-01-01 07:40:00+01:00", + "2019-01-01 07:41:00+01:00", + "2019-01-01 07:42:00+01:00", + "2019-01-01 07:43:00+01:00", + "2019-01-01 07:44:00+01:00", + "2019-01-01 07:45:00+01:00", + "2019-01-01 07:46:00+01:00", + "2019-01-01 07:47:00+01:00", + "2019-01-01 07:48:00+01:00", + "2019-01-01 07:49:00+01:00", + "2019-01-01 07:50:00+01:00", + "2019-01-01 07:51:00+01:00", + "2019-01-01 07:52:00+01:00", + "2019-01-01 07:53:00+01:00", + "2019-01-01 07:54:00+01:00", + "2019-01-01 07:55:00+01:00", + "2019-01-01 07:56:00+01:00", + "2019-01-01 07:57:00+01:00", + "2019-01-01 07:58:00+01:00", + "2019-01-01 07:59:00+01:00", + "2019-01-01 08:00:00+01:00", + "2019-01-01 08:01:00+01:00", + "2019-01-01 08:02:00+01:00", + "2019-01-01 08:03:00+01:00", + "2019-01-01 08:04:00+01:00", + "2019-01-01 08:05:00+01:00", + "2019-01-01 08:06:00+01:00", + "2019-01-01 08:07:00+01:00", + "2019-01-01 08:08:00+01:00", + "2019-01-01 08:09:00+01:00", + "2019-01-01 08:10:00+01:00", + "2019-01-01 08:11:00+01:00", + "2019-01-01 08:12:00+01:00", + "2019-01-01 08:13:00+01:00", + "2019-01-01 08:14:00+01:00", + "2019-01-01 08:15:00+01:00", + "2019-01-01 08:16:00+01:00", + "2019-01-01 08:17:00+01:00", + "2019-01-01 08:18:00+01:00", + "2019-01-01 08:19:00+01:00", + "2019-01-01 08:20:00+01:00", + "2019-01-01 08:21:00+01:00", + "2019-01-01 08:22:00+01:00", + "2019-01-01 08:23:00+01:00", + "2019-01-01 08:24:00+01:00", + "2019-01-01 08:25:00+01:00", + "2019-01-01 08:26:00+01:00", + "2019-01-01 08:27:00+01:00", + "2019-01-01 08:28:00+01:00", + "2019-01-01 08:29:00+01:00", + "2019-01-01 08:30:00+01:00", + "2019-01-01 08:31:00+01:00", + "2019-01-01 08:32:00+01:00", + "2019-01-01 08:33:00+01:00", + "2019-01-01 08:34:00+01:00", + "2019-01-01 08:35:00+01:00", + "2019-01-01 08:36:00+01:00", + "2019-01-01 08:37:00+01:00", + "2019-01-01 08:38:00+01:00", + "2019-01-01 08:39:00+01:00", + "2019-01-01 08:40:00+01:00", + "2019-01-01 08:41:00+01:00", + "2019-01-01 08:42:00+01:00", + "2019-01-01 08:43:00+01:00", + "2019-01-01 08:44:00+01:00", + "2019-01-01 08:45:00+01:00", + "2019-01-01 08:46:00+01:00", + "2019-01-01 08:47:00+01:00", + "2019-01-01 08:48:00+01:00", + "2019-01-01 08:49:00+01:00", + "2019-01-01 08:50:00+01:00", + "2019-01-01 08:51:00+01:00", + "2019-01-01 08:52:00+01:00", + "2019-01-01 08:53:00+01:00", + "2019-01-01 08:54:00+01:00", + "2019-01-01 08:55:00+01:00", + "2019-01-01 08:56:00+01:00", + "2019-01-01 08:57:00+01:00", + "2019-01-01 08:58:00+01:00", + "2019-01-01 08:59:00+01:00", + "2019-01-01 09:00:00+01:00", + "2019-01-01 09:01:00+01:00", + "2019-01-01 09:02:00+01:00", + "2019-01-01 09:03:00+01:00", + "2019-01-01 09:04:00+01:00", + "2019-01-01 09:05:00+01:00", + "2019-01-01 09:06:00+01:00", + "2019-01-01 09:07:00+01:00", + "2019-01-01 09:08:00+01:00", + "2019-01-01 09:09:00+01:00", + "2019-01-01 09:10:00+01:00", + "2019-01-01 09:11:00+01:00", + "2019-01-01 09:12:00+01:00", + "2019-01-01 09:13:00+01:00", + "2019-01-01 09:14:00+01:00", + "2019-01-01 09:15:00+01:00", + "2019-01-01 09:16:00+01:00", + "2019-01-01 09:17:00+01:00", + "2019-01-01 09:18:00+01:00", + "2019-01-01 09:19:00+01:00", + "2019-01-01 09:20:00+01:00", + "2019-01-01 09:21:00+01:00", + "2019-01-01 09:22:00+01:00", + "2019-01-01 09:23:00+01:00", + "2019-01-01 09:24:00+01:00", + "2019-01-01 09:25:00+01:00", + "2019-01-01 09:26:00+01:00", + "2019-01-01 09:27:00+01:00", + "2019-01-01 09:28:00+01:00", + "2019-01-01 09:29:00+01:00", + "2019-01-01 09:30:00+01:00", + "2019-01-01 09:31:00+01:00", + "2019-01-01 09:32:00+01:00", + "2019-01-01 09:33:00+01:00", + "2019-01-01 09:34:00+01:00", + "2019-01-01 09:35:00+01:00", + "2019-01-01 09:36:00+01:00", + "2019-01-01 09:37:00+01:00", + "2019-01-01 09:38:00+01:00", + "2019-01-01 09:39:00+01:00", + "2019-01-01 09:40:00+01:00", + "2019-01-01 09:41:00+01:00", + "2019-01-01 09:42:00+01:00", + "2019-01-01 09:43:00+01:00", + "2019-01-01 09:44:00+01:00", + "2019-01-01 09:45:00+01:00", + "2019-01-01 09:46:00+01:00", + "2019-01-01 09:47:00+01:00", + "2019-01-01 09:48:00+01:00", + "2019-01-01 09:49:00+01:00", + "2019-01-01 09:50:00+01:00", + "2019-01-01 09:51:00+01:00", + "2019-01-01 09:52:00+01:00", + "2019-01-01 09:53:00+01:00", + "2019-01-01 09:54:00+01:00", + "2019-01-01 09:55:00+01:00", + "2019-01-01 09:56:00+01:00", + "2019-01-01 09:57:00+01:00", + "2019-01-01 09:58:00+01:00", + "2019-01-01 09:59:00+01:00", + "2019-01-01 10:00:00+01:00", + "2019-01-01 10:01:00+01:00", + "2019-01-01 10:02:00+01:00", + "2019-01-01 10:03:00+01:00", + "2019-01-01 10:04:00+01:00", + "2019-01-01 10:05:00+01:00", + "2019-01-01 10:06:00+01:00", + "2019-01-01 10:07:00+01:00", + "2019-01-01 10:08:00+01:00", + "2019-01-01 10:09:00+01:00", + "2019-01-01 10:10:00+01:00", + "2019-01-01 10:11:00+01:00", + "2019-01-01 10:12:00+01:00", + "2019-01-01 10:13:00+01:00", + "2019-01-01 10:14:00+01:00", + "2019-01-01 10:15:00+01:00", + "2019-01-01 10:16:00+01:00", + "2019-01-01 10:17:00+01:00", + "2019-01-01 10:18:00+01:00", + "2019-01-01 10:19:00+01:00", + "2019-01-01 10:20:00+01:00", + "2019-01-01 10:21:00+01:00", + "2019-01-01 10:22:00+01:00", + "2019-01-01 10:23:00+01:00", + "2019-01-01 10:24:00+01:00", + "2019-01-01 10:25:00+01:00", + "2019-01-01 10:26:00+01:00", + "2019-01-01 10:27:00+01:00", + "2019-01-01 10:28:00+01:00", + "2019-01-01 10:29:00+01:00", + "2019-01-01 10:30:00+01:00", + "2019-01-01 10:31:00+01:00", + "2019-01-01 10:32:00+01:00", + "2019-01-01 10:33:00+01:00", + "2019-01-01 10:34:00+01:00", + "2019-01-01 10:35:00+01:00", + "2019-01-01 10:36:00+01:00", + "2019-01-01 10:37:00+01:00", + "2019-01-01 10:38:00+01:00", + "2019-01-01 10:39:00+01:00", + "2019-01-01 10:40:00+01:00", + "2019-01-01 10:41:00+01:00", + "2019-01-01 10:42:00+01:00", + "2019-01-01 10:43:00+01:00", + "2019-01-01 10:44:00+01:00", + "2019-01-01 10:45:00+01:00", + "2019-01-01 10:46:00+01:00", + "2019-01-01 10:47:00+01:00", + "2019-01-01 10:48:00+01:00", + "2019-01-01 10:49:00+01:00", + "2019-01-01 10:50:00+01:00", + "2019-01-01 10:51:00+01:00", + "2019-01-01 10:52:00+01:00", + "2019-01-01 10:53:00+01:00", + "2019-01-01 10:54:00+01:00", + "2019-01-01 10:55:00+01:00", + "2019-01-01 10:56:00+01:00", + "2019-01-01 10:57:00+01:00", + "2019-01-01 10:58:00+01:00", + "2019-01-01 10:59:00+01:00", + "2019-01-01 11:00:00+01:00", + "2019-01-01 11:01:00+01:00", + "2019-01-01 11:02:00+01:00", + "2019-01-01 11:03:00+01:00", + "2019-01-01 11:04:00+01:00", + "2019-01-01 11:05:00+01:00", + "2019-01-01 11:06:00+01:00", + "2019-01-01 11:07:00+01:00", + "2019-01-01 11:08:00+01:00", + "2019-01-01 11:09:00+01:00", + "2019-01-01 11:10:00+01:00", + "2019-01-01 11:11:00+01:00", + "2019-01-01 11:12:00+01:00", + "2019-01-01 11:13:00+01:00", + "2019-01-01 11:14:00+01:00", + "2019-01-01 11:15:00+01:00", + "2019-01-01 11:16:00+01:00", + "2019-01-01 11:17:00+01:00", + "2019-01-01 11:18:00+01:00", + "2019-01-01 11:19:00+01:00", + "2019-01-01 11:20:00+01:00", + "2019-01-01 11:21:00+01:00", + "2019-01-01 11:22:00+01:00", + "2019-01-01 11:23:00+01:00", + "2019-01-01 11:24:00+01:00", + "2019-01-01 11:25:00+01:00", + "2019-01-01 11:26:00+01:00", + "2019-01-01 11:27:00+01:00", + "2019-01-01 11:28:00+01:00", + "2019-01-01 11:29:00+01:00", + "2019-01-01 11:30:00+01:00", + "2019-01-01 11:31:00+01:00", + "2019-01-01 11:32:00+01:00", + "2019-01-01 11:33:00+01:00", + "2019-01-01 11:34:00+01:00", + "2019-01-01 11:35:00+01:00", + "2019-01-01 11:36:00+01:00", + "2019-01-01 11:37:00+01:00", + "2019-01-01 11:38:00+01:00", + "2019-01-01 11:39:00+01:00", + "2019-01-01 11:40:00+01:00", + "2019-01-01 11:41:00+01:00", + "2019-01-01 11:42:00+01:00", + "2019-01-01 11:43:00+01:00", + "2019-01-01 11:44:00+01:00", + "2019-01-01 11:45:00+01:00", + "2019-01-01 11:46:00+01:00", + "2019-01-01 11:47:00+01:00", + "2019-01-01 11:48:00+01:00", + "2019-01-01 11:49:00+01:00", + "2019-01-01 11:50:00+01:00", + "2019-01-01 11:51:00+01:00", + "2019-01-01 11:52:00+01:00", + "2019-01-01 11:53:00+01:00", + "2019-01-01 11:54:00+01:00", + "2019-01-01 11:55:00+01:00", + "2019-01-01 11:56:00+01:00", + "2019-01-01 11:57:00+01:00", + "2019-01-01 11:58:00+01:00", + "2019-01-01 11:59:00+01:00", + "2019-01-01 12:00:00+01:00", + "2019-01-01 12:01:00+01:00", + "2019-01-01 12:02:00+01:00", + "2019-01-01 12:03:00+01:00", + "2019-01-01 12:04:00+01:00", + "2019-01-01 12:05:00+01:00", + "2019-01-01 12:06:00+01:00", + "2019-01-01 12:07:00+01:00", + "2019-01-01 12:08:00+01:00", + "2019-01-01 12:09:00+01:00", + "2019-01-01 12:10:00+01:00", + "2019-01-01 12:11:00+01:00", + "2019-01-01 12:12:00+01:00", + "2019-01-01 12:13:00+01:00", + "2019-01-01 12:14:00+01:00", + "2019-01-01 12:15:00+01:00", + "2019-01-01 12:16:00+01:00", + "2019-01-01 12:17:00+01:00", + "2019-01-01 12:18:00+01:00", + "2019-01-01 12:19:00+01:00", + "2019-01-01 12:20:00+01:00", + "2019-01-01 12:21:00+01:00", + "2019-01-01 12:22:00+01:00", + "2019-01-01 12:23:00+01:00", + "2019-01-01 12:24:00+01:00", + "2019-01-01 12:25:00+01:00", + "2019-01-01 12:26:00+01:00", + "2019-01-01 12:27:00+01:00", + "2019-01-01 12:28:00+01:00", + "2019-01-01 12:29:00+01:00", + "2019-01-01 12:30:00+01:00", + "2019-01-01 12:31:00+01:00", + "2019-01-01 12:32:00+01:00", + "2019-01-01 12:33:00+01:00", + "2019-01-01 12:34:00+01:00", + "2019-01-01 12:35:00+01:00", + "2019-01-01 12:36:00+01:00", + "2019-01-01 12:37:00+01:00", + "2019-01-01 12:38:00+01:00", + "2019-01-01 12:39:00+01:00", + "2019-01-01 12:40:00+01:00", + "2019-01-01 12:41:00+01:00", + "2019-01-01 12:42:00+01:00", + "2019-01-01 12:43:00+01:00", + "2019-01-01 12:44:00+01:00", + "2019-01-01 12:45:00+01:00", + "2019-01-01 12:46:00+01:00", + "2019-01-01 12:47:00+01:00", + "2019-01-01 12:48:00+01:00", + "2019-01-01 12:49:00+01:00", + "2019-01-01 12:50:00+01:00", + "2019-01-01 12:51:00+01:00", + "2019-01-01 12:52:00+01:00", + "2019-01-01 12:53:00+01:00", + "2019-01-01 12:54:00+01:00", + "2019-01-01 12:55:00+01:00", + "2019-01-01 12:56:00+01:00", + "2019-01-01 12:57:00+01:00", + "2019-01-01 12:58:00+01:00", + "2019-01-01 12:59:00+01:00", + "2019-01-01 13:00:00+01:00", + "2019-01-01 13:01:00+01:00", + "2019-01-01 13:02:00+01:00", + "2019-01-01 13:03:00+01:00", + "2019-01-01 13:04:00+01:00", + "2019-01-01 13:05:00+01:00", + "2019-01-01 13:06:00+01:00", + "2019-01-01 13:07:00+01:00", + "2019-01-01 13:08:00+01:00", + "2019-01-01 13:09:00+01:00", + "2019-01-01 13:10:00+01:00", + "2019-01-01 13:11:00+01:00", + "2019-01-01 13:12:00+01:00", + "2019-01-01 13:13:00+01:00", + "2019-01-01 13:14:00+01:00", + "2019-01-01 13:15:00+01:00", + "2019-01-01 13:16:00+01:00", + "2019-01-01 13:17:00+01:00", + "2019-01-01 13:18:00+01:00", + "2019-01-01 13:19:00+01:00", + "2019-01-01 13:20:00+01:00", + "2019-01-01 13:21:00+01:00", + "2019-01-01 13:22:00+01:00", + "2019-01-01 13:23:00+01:00", + "2019-01-01 13:24:00+01:00", + "2019-01-01 13:25:00+01:00", + "2019-01-01 13:26:00+01:00", + "2019-01-01 13:27:00+01:00", + "2019-01-01 13:28:00+01:00", + "2019-01-01 13:29:00+01:00", + "2019-01-01 13:30:00+01:00", + "2019-01-01 13:31:00+01:00", + "2019-01-01 13:32:00+01:00", + "2019-01-01 13:33:00+01:00", + "2019-01-01 13:34:00+01:00", + "2019-01-01 13:35:00+01:00", + "2019-01-01 13:36:00+01:00", + "2019-01-01 13:37:00+01:00", + "2019-01-01 13:38:00+01:00", + "2019-01-01 13:39:00+01:00", + "2019-01-01 13:40:00+01:00", + "2019-01-01 13:41:00+01:00", + "2019-01-01 13:42:00+01:00", + "2019-01-01 13:43:00+01:00", + "2019-01-01 13:44:00+01:00", + "2019-01-01 13:45:00+01:00", + "2019-01-01 13:46:00+01:00", + "2019-01-01 13:47:00+01:00", + "2019-01-01 13:48:00+01:00", + "2019-01-01 13:49:00+01:00", + "2019-01-01 13:50:00+01:00", + "2019-01-01 13:51:00+01:00", + "2019-01-01 13:52:00+01:00", + "2019-01-01 13:53:00+01:00", + "2019-01-01 13:54:00+01:00", + "2019-01-01 13:55:00+01:00", + "2019-01-01 13:56:00+01:00", + "2019-01-01 13:57:00+01:00", + "2019-01-01 13:58:00+01:00", + "2019-01-01 13:59:00+01:00", + "2019-01-01 14:00:00+01:00", + "2019-01-01 14:01:00+01:00", + "2019-01-01 14:02:00+01:00", + "2019-01-01 14:03:00+01:00", + "2019-01-01 14:04:00+01:00", + "2019-01-01 14:05:00+01:00", + "2019-01-01 14:06:00+01:00", + "2019-01-01 14:07:00+01:00", + "2019-01-01 14:08:00+01:00", + "2019-01-01 14:09:00+01:00", + "2019-01-01 14:10:00+01:00", + "2019-01-01 14:11:00+01:00", + "2019-01-01 14:12:00+01:00", + "2019-01-01 14:13:00+01:00", + "2019-01-01 14:14:00+01:00", + "2019-01-01 14:15:00+01:00", + "2019-01-01 14:16:00+01:00", + "2019-01-01 14:17:00+01:00", + "2019-01-01 14:18:00+01:00", + "2019-01-01 14:19:00+01:00", + "2019-01-01 14:20:00+01:00", + "2019-01-01 14:21:00+01:00", + "2019-01-01 14:22:00+01:00", + "2019-01-01 14:23:00+01:00", + "2019-01-01 14:24:00+01:00", + "2019-01-01 14:25:00+01:00", + "2019-01-01 14:26:00+01:00", + "2019-01-01 14:27:00+01:00", + "2019-01-01 14:28:00+01:00", + "2019-01-01 14:29:00+01:00", + "2019-01-01 14:30:00+01:00", + "2019-01-01 14:31:00+01:00", + "2019-01-01 14:32:00+01:00", + "2019-01-01 14:33:00+01:00", + "2019-01-01 14:34:00+01:00", + "2019-01-01 14:35:00+01:00", + "2019-01-01 14:36:00+01:00", + "2019-01-01 14:37:00+01:00", + "2019-01-01 14:38:00+01:00", + "2019-01-01 14:39:00+01:00", + "2019-01-01 14:40:00+01:00", + "2019-01-01 14:41:00+01:00", + "2019-01-01 14:42:00+01:00", + "2019-01-01 14:43:00+01:00", + "2019-01-01 14:44:00+01:00", + "2019-01-01 14:45:00+01:00", + "2019-01-01 14:46:00+01:00", + "2019-01-01 14:47:00+01:00", + "2019-01-01 14:48:00+01:00", + "2019-01-01 14:49:00+01:00", + "2019-01-01 14:50:00+01:00", + "2019-01-01 14:51:00+01:00", + "2019-01-01 14:52:00+01:00", + "2019-01-01 14:53:00+01:00", + "2019-01-01 14:54:00+01:00", + "2019-01-01 14:55:00+01:00", + "2019-01-01 14:56:00+01:00", + "2019-01-01 14:57:00+01:00", + "2019-01-01 14:58:00+01:00", + "2019-01-01 14:59:00+01:00", + "2019-01-01 15:00:00+01:00", + "2019-01-01 15:01:00+01:00", + "2019-01-01 15:02:00+01:00", + "2019-01-01 15:03:00+01:00", + "2019-01-01 15:04:00+01:00", + "2019-01-01 15:05:00+01:00", + "2019-01-01 15:06:00+01:00", + "2019-01-01 15:07:00+01:00", + "2019-01-01 15:08:00+01:00", + "2019-01-01 15:09:00+01:00", + "2019-01-01 15:10:00+01:00", + "2019-01-01 15:11:00+01:00", + "2019-01-01 15:12:00+01:00", + "2019-01-01 15:13:00+01:00", + "2019-01-01 15:14:00+01:00", + "2019-01-01 15:15:00+01:00", + "2019-01-01 15:16:00+01:00", + "2019-01-01 15:17:00+01:00", + "2019-01-01 15:18:00+01:00", + "2019-01-01 15:19:00+01:00", + "2019-01-01 15:20:00+01:00", + "2019-01-01 15:21:00+01:00", + "2019-01-01 15:22:00+01:00", + "2019-01-01 15:23:00+01:00", + "2019-01-01 15:24:00+01:00", + "2019-01-01 15:25:00+01:00", + "2019-01-01 15:26:00+01:00", + "2019-01-01 15:27:00+01:00", + "2019-01-01 15:28:00+01:00", + "2019-01-01 15:29:00+01:00", + "2019-01-01 15:30:00+01:00", + "2019-01-01 15:31:00+01:00", + "2019-01-01 15:32:00+01:00", + "2019-01-01 15:33:00+01:00", + "2019-01-01 15:34:00+01:00", + "2019-01-01 15:35:00+01:00", + "2019-01-01 15:36:00+01:00", + "2019-01-01 15:37:00+01:00", + "2019-01-01 15:38:00+01:00", + "2019-01-01 15:39:00+01:00", + "2019-01-01 15:40:00+01:00", + "2019-01-01 15:41:00+01:00", + "2019-01-01 15:42:00+01:00", + "2019-01-01 15:43:00+01:00", + "2019-01-01 15:44:00+01:00", + "2019-01-01 15:45:00+01:00", + "2019-01-01 15:46:00+01:00", + "2019-01-01 15:47:00+01:00", + "2019-01-01 15:48:00+01:00", + "2019-01-01 15:49:00+01:00", + "2019-01-01 15:50:00+01:00", + "2019-01-01 15:51:00+01:00", + "2019-01-01 15:52:00+01:00", + "2019-01-01 15:53:00+01:00", + "2019-01-01 15:54:00+01:00", + "2019-01-01 15:55:00+01:00", + "2019-01-01 15:56:00+01:00", + "2019-01-01 15:57:00+01:00", + "2019-01-01 15:58:00+01:00", + "2019-01-01 15:59:00+01:00", + "2019-01-01 16:00:00+01:00", + "2019-01-01 16:01:00+01:00", + "2019-01-01 16:02:00+01:00", + "2019-01-01 16:03:00+01:00", + "2019-01-01 16:04:00+01:00", + "2019-01-01 16:05:00+01:00", + "2019-01-01 16:06:00+01:00", + "2019-01-01 16:07:00+01:00", + "2019-01-01 16:08:00+01:00", + "2019-01-01 16:09:00+01:00", + "2019-01-01 16:10:00+01:00", + "2019-01-01 16:11:00+01:00", + "2019-01-01 16:12:00+01:00", + "2019-01-01 16:13:00+01:00", + "2019-01-01 16:14:00+01:00", + "2019-01-01 16:15:00+01:00", + "2019-01-01 16:16:00+01:00", + "2019-01-01 16:17:00+01:00", + "2019-01-01 16:18:00+01:00", + "2019-01-01 16:19:00+01:00", + "2019-01-01 16:20:00+01:00", + "2019-01-01 16:21:00+01:00", + "2019-01-01 16:22:00+01:00", + "2019-01-01 16:23:00+01:00", + "2019-01-01 16:24:00+01:00", + "2019-01-01 16:25:00+01:00", + "2019-01-01 16:26:00+01:00", + "2019-01-01 16:27:00+01:00", + "2019-01-01 16:28:00+01:00", + "2019-01-01 16:29:00+01:00", + "2019-01-01 16:30:00+01:00", + "2019-01-01 16:31:00+01:00", + "2019-01-01 16:32:00+01:00", + "2019-01-01 16:33:00+01:00", + "2019-01-01 16:34:00+01:00", + "2019-01-01 16:35:00+01:00", + "2019-01-01 16:36:00+01:00", + "2019-01-01 16:37:00+01:00", + "2019-01-01 16:38:00+01:00", + "2019-01-01 16:39:00+01:00", + "2019-01-01 16:40:00+01:00", + "2019-01-01 16:41:00+01:00", + "2019-01-01 16:42:00+01:00", + "2019-01-01 16:43:00+01:00", + "2019-01-01 16:44:00+01:00", + "2019-01-01 16:45:00+01:00", + "2019-01-01 16:46:00+01:00", + "2019-01-01 16:47:00+01:00", + "2019-01-01 16:48:00+01:00", + "2019-01-01 16:49:00+01:00", + "2019-01-01 16:50:00+01:00", + "2019-01-01 16:51:00+01:00", + "2019-01-01 16:52:00+01:00", + "2019-01-01 16:53:00+01:00", + "2019-01-01 16:54:00+01:00", + "2019-01-01 16:55:00+01:00", + "2019-01-01 16:56:00+01:00", + "2019-01-01 16:57:00+01:00", + "2019-01-01 16:58:00+01:00", + "2019-01-01 16:59:00+01:00", + "2019-01-01 17:00:00+01:00", + "2019-01-01 17:01:00+01:00", + "2019-01-01 17:02:00+01:00", + "2019-01-01 17:03:00+01:00", + "2019-01-01 17:04:00+01:00", + "2019-01-01 17:05:00+01:00", + "2019-01-01 17:06:00+01:00", + "2019-01-01 17:07:00+01:00", + "2019-01-01 17:08:00+01:00", + "2019-01-01 17:09:00+01:00", + "2019-01-01 17:10:00+01:00", + "2019-01-01 17:11:00+01:00", + "2019-01-01 17:12:00+01:00", + "2019-01-01 17:13:00+01:00", + "2019-01-01 17:14:00+01:00", + "2019-01-01 17:15:00+01:00", + "2019-01-01 17:16:00+01:00", + "2019-01-01 17:17:00+01:00", + "2019-01-01 17:18:00+01:00", + "2019-01-01 17:19:00+01:00", + "2019-01-01 17:20:00+01:00", + "2019-01-01 17:21:00+01:00", + "2019-01-01 17:22:00+01:00", + "2019-01-01 17:23:00+01:00", + "2019-01-01 17:24:00+01:00", + "2019-01-01 17:25:00+01:00", + "2019-01-01 17:26:00+01:00", + "2019-01-01 17:27:00+01:00", + "2019-01-01 17:28:00+01:00", + "2019-01-01 17:29:00+01:00", + "2019-01-01 17:30:00+01:00", + "2019-01-01 17:31:00+01:00", + "2019-01-01 17:32:00+01:00", + "2019-01-01 17:33:00+01:00", + "2019-01-01 17:34:00+01:00", + "2019-01-01 17:35:00+01:00", + "2019-01-01 17:36:00+01:00", + "2019-01-01 17:37:00+01:00", + "2019-01-01 17:38:00+01:00", + "2019-01-01 17:39:00+01:00", + "2019-01-01 17:40:00+01:00", + "2019-01-01 17:41:00+01:00", + "2019-01-01 17:42:00+01:00", + "2019-01-01 17:43:00+01:00", + "2019-01-01 17:44:00+01:00", + "2019-01-01 17:45:00+01:00", + "2019-01-01 17:46:00+01:00", + "2019-01-01 17:47:00+01:00", + "2019-01-01 17:48:00+01:00", + "2019-01-01 17:49:00+01:00", + "2019-01-01 17:50:00+01:00", + "2019-01-01 17:51:00+01:00", + "2019-01-01 17:52:00+01:00", + "2019-01-01 17:53:00+01:00", + "2019-01-01 17:54:00+01:00", + "2019-01-01 17:55:00+01:00", + "2019-01-01 17:56:00+01:00", + "2019-01-01 17:57:00+01:00", + "2019-01-01 17:58:00+01:00", + "2019-01-01 17:59:00+01:00", + "2019-01-01 18:00:00+01:00", + "2019-01-01 18:01:00+01:00", + "2019-01-01 18:02:00+01:00", + "2019-01-01 18:03:00+01:00", + "2019-01-01 18:04:00+01:00", + "2019-01-01 18:05:00+01:00", + "2019-01-01 18:06:00+01:00", + "2019-01-01 18:07:00+01:00", + "2019-01-01 18:08:00+01:00", + "2019-01-01 18:09:00+01:00", + "2019-01-01 18:10:00+01:00", + "2019-01-01 18:11:00+01:00", + "2019-01-01 18:12:00+01:00", + "2019-01-01 18:13:00+01:00", + "2019-01-01 18:14:00+01:00", + "2019-01-01 18:15:00+01:00", + "2019-01-01 18:16:00+01:00", + "2019-01-01 18:17:00+01:00", + "2019-01-01 18:18:00+01:00", + "2019-01-01 18:19:00+01:00", + "2019-01-01 18:20:00+01:00", + "2019-01-01 18:21:00+01:00", + "2019-01-01 18:22:00+01:00", + "2019-01-01 18:23:00+01:00", + "2019-01-01 18:24:00+01:00", + "2019-01-01 18:25:00+01:00", + "2019-01-01 18:26:00+01:00", + "2019-01-01 18:27:00+01:00", + "2019-01-01 18:28:00+01:00", + "2019-01-01 18:29:00+01:00", + "2019-01-01 18:30:00+01:00", + "2019-01-01 18:31:00+01:00", + "2019-01-01 18:32:00+01:00", + "2019-01-01 18:33:00+01:00", + "2019-01-01 18:34:00+01:00", + "2019-01-01 18:35:00+01:00", + "2019-01-01 18:36:00+01:00", + "2019-01-01 18:37:00+01:00", + "2019-01-01 18:38:00+01:00", + "2019-01-01 18:39:00+01:00", + "2019-01-01 18:40:00+01:00", + "2019-01-01 18:41:00+01:00", + "2019-01-01 18:42:00+01:00", + "2019-01-01 18:43:00+01:00", + "2019-01-01 18:44:00+01:00", + "2019-01-01 18:45:00+01:00", + "2019-01-01 18:46:00+01:00", + "2019-01-01 18:47:00+01:00", + "2019-01-01 18:48:00+01:00", + "2019-01-01 18:49:00+01:00", + "2019-01-01 18:50:00+01:00", + "2019-01-01 18:51:00+01:00", + "2019-01-01 18:52:00+01:00", + "2019-01-01 18:53:00+01:00", + "2019-01-01 18:54:00+01:00", + "2019-01-01 18:55:00+01:00", + "2019-01-01 18:56:00+01:00", + "2019-01-01 18:57:00+01:00", + "2019-01-01 18:58:00+01:00", + "2019-01-01 18:59:00+01:00", + "2019-01-01 19:00:00+01:00", + "2019-01-01 19:01:00+01:00", + "2019-01-01 19:02:00+01:00", + "2019-01-01 19:03:00+01:00", + "2019-01-01 19:04:00+01:00", + "2019-01-01 19:05:00+01:00", + "2019-01-01 19:06:00+01:00", + "2019-01-01 19:07:00+01:00", + "2019-01-01 19:08:00+01:00", + "2019-01-01 19:09:00+01:00", + "2019-01-01 19:10:00+01:00", + "2019-01-01 19:11:00+01:00", + "2019-01-01 19:12:00+01:00", + "2019-01-01 19:13:00+01:00", + "2019-01-01 19:14:00+01:00", + "2019-01-01 19:15:00+01:00", + "2019-01-01 19:16:00+01:00", + "2019-01-01 19:17:00+01:00", + "2019-01-01 19:18:00+01:00", + "2019-01-01 19:19:00+01:00", + "2019-01-01 19:20:00+01:00", + "2019-01-01 19:21:00+01:00", + "2019-01-01 19:22:00+01:00", + "2019-01-01 19:23:00+01:00", + "2019-01-01 19:24:00+01:00", + "2019-01-01 19:25:00+01:00", + "2019-01-01 19:26:00+01:00", + "2019-01-01 19:27:00+01:00", + "2019-01-01 19:28:00+01:00", + "2019-01-01 19:29:00+01:00", + "2019-01-01 19:30:00+01:00", + "2019-01-01 19:31:00+01:00", + "2019-01-01 19:32:00+01:00", + "2019-01-01 19:33:00+01:00", + "2019-01-01 19:34:00+01:00", + "2019-01-01 19:35:00+01:00", + "2019-01-01 19:36:00+01:00", + "2019-01-01 19:37:00+01:00", + "2019-01-01 19:38:00+01:00", + "2019-01-01 19:39:00+01:00", + "2019-01-01 19:40:00+01:00", + "2019-01-01 19:41:00+01:00", + "2019-01-01 19:42:00+01:00", + "2019-01-01 19:43:00+01:00", + "2019-01-01 19:44:00+01:00", + "2019-01-01 19:45:00+01:00", + "2019-01-01 19:46:00+01:00", + "2019-01-01 19:47:00+01:00", + "2019-01-01 19:48:00+01:00", + "2019-01-01 19:49:00+01:00", + "2019-01-01 19:50:00+01:00", + "2019-01-01 19:51:00+01:00", + "2019-01-01 19:52:00+01:00", + "2019-01-01 19:53:00+01:00", + "2019-01-01 19:54:00+01:00", + "2019-01-01 19:55:00+01:00", + "2019-01-01 19:56:00+01:00", + "2019-01-01 19:57:00+01:00", + "2019-01-01 19:58:00+01:00", + "2019-01-01 19:59:00+01:00", + "2019-01-01 20:00:00+01:00", + "2019-01-01 20:01:00+01:00", + "2019-01-01 20:02:00+01:00", + "2019-01-01 20:03:00+01:00", + "2019-01-01 20:04:00+01:00", + "2019-01-01 20:05:00+01:00", + "2019-01-01 20:06:00+01:00", + "2019-01-01 20:07:00+01:00", + "2019-01-01 20:08:00+01:00", + "2019-01-01 20:09:00+01:00", + "2019-01-01 20:10:00+01:00", + "2019-01-01 20:11:00+01:00", + "2019-01-01 20:12:00+01:00", + "2019-01-01 20:13:00+01:00", + "2019-01-01 20:14:00+01:00", + "2019-01-01 20:15:00+01:00", + "2019-01-01 20:16:00+01:00", + "2019-01-01 20:17:00+01:00", + "2019-01-01 20:18:00+01:00", + "2019-01-01 20:19:00+01:00", + "2019-01-01 20:20:00+01:00", + "2019-01-01 20:21:00+01:00", + "2019-01-01 20:22:00+01:00", + "2019-01-01 20:23:00+01:00", + "2019-01-01 20:24:00+01:00", + "2019-01-01 20:25:00+01:00", + "2019-01-01 20:26:00+01:00", + "2019-01-01 20:27:00+01:00", + "2019-01-01 20:28:00+01:00", + "2019-01-01 20:29:00+01:00", + "2019-01-01 20:30:00+01:00", + "2019-01-01 20:31:00+01:00", + "2019-01-01 20:32:00+01:00", + "2019-01-01 20:33:00+01:00", + "2019-01-01 20:34:00+01:00", + "2019-01-01 20:35:00+01:00", + "2019-01-01 20:36:00+01:00", + "2019-01-01 20:37:00+01:00", + "2019-01-01 20:38:00+01:00", + "2019-01-01 20:39:00+01:00", + "2019-01-01 20:40:00+01:00", + "2019-01-01 20:41:00+01:00", + "2019-01-01 20:42:00+01:00", + "2019-01-01 20:43:00+01:00", + "2019-01-01 20:44:00+01:00", + "2019-01-01 20:45:00+01:00", + "2019-01-01 20:46:00+01:00", + "2019-01-01 20:47:00+01:00", + "2019-01-01 20:48:00+01:00", + "2019-01-01 20:49:00+01:00", + "2019-01-01 20:50:00+01:00", + "2019-01-01 20:51:00+01:00", + "2019-01-01 20:52:00+01:00", + "2019-01-01 20:53:00+01:00", + "2019-01-01 20:54:00+01:00", + "2019-01-01 20:55:00+01:00", + "2019-01-01 20:56:00+01:00", + "2019-01-01 20:57:00+01:00", + "2019-01-01 20:58:00+01:00", + "2019-01-01 20:59:00+01:00", + "2019-01-01 21:00:00+01:00", + "2019-01-01 21:01:00+01:00", + "2019-01-01 21:02:00+01:00", + "2019-01-01 21:03:00+01:00", + "2019-01-01 21:04:00+01:00", + "2019-01-01 21:05:00+01:00", + "2019-01-01 21:06:00+01:00", + "2019-01-01 21:07:00+01:00", + "2019-01-01 21:08:00+01:00", + "2019-01-01 21:09:00+01:00", + "2019-01-01 21:10:00+01:00", + "2019-01-01 21:11:00+01:00", + "2019-01-01 21:12:00+01:00", + "2019-01-01 21:13:00+01:00", + "2019-01-01 21:14:00+01:00", + "2019-01-01 21:15:00+01:00", + "2019-01-01 21:16:00+01:00", + "2019-01-01 21:17:00+01:00", + "2019-01-01 21:18:00+01:00", + "2019-01-01 21:19:00+01:00", + "2019-01-01 21:20:00+01:00", + "2019-01-01 21:21:00+01:00", + "2019-01-01 21:22:00+01:00", + "2019-01-01 21:23:00+01:00", + "2019-01-01 21:24:00+01:00", + "2019-01-01 21:25:00+01:00", + "2019-01-01 21:26:00+01:00", + "2019-01-01 21:27:00+01:00", + "2019-01-01 21:28:00+01:00", + "2019-01-01 21:29:00+01:00", + "2019-01-01 21:30:00+01:00", + "2019-01-01 21:31:00+01:00", + "2019-01-01 21:32:00+01:00", + "2019-01-01 21:33:00+01:00", + "2019-01-01 21:34:00+01:00", + "2019-01-01 21:35:00+01:00", + "2019-01-01 21:36:00+01:00", + "2019-01-01 21:37:00+01:00", + "2019-01-01 21:38:00+01:00", + "2019-01-01 21:39:00+01:00", + "2019-01-01 21:40:00+01:00", + "2019-01-01 21:41:00+01:00", + "2019-01-01 21:42:00+01:00", + "2019-01-01 21:43:00+01:00", + "2019-01-01 21:44:00+01:00", + "2019-01-01 21:45:00+01:00", + "2019-01-01 21:46:00+01:00", + "2019-01-01 21:47:00+01:00", + "2019-01-01 21:48:00+01:00", + "2019-01-01 21:49:00+01:00", + "2019-01-01 21:50:00+01:00", + "2019-01-01 21:51:00+01:00", + "2019-01-01 21:52:00+01:00", + "2019-01-01 21:53:00+01:00", + "2019-01-01 21:54:00+01:00", + "2019-01-01 21:55:00+01:00", + "2019-01-01 21:56:00+01:00", + "2019-01-01 21:57:00+01:00", + "2019-01-01 21:58:00+01:00", + "2019-01-01 21:59:00+01:00", + "2019-01-01 22:00:00+01:00", + "2019-01-01 22:01:00+01:00", + "2019-01-01 22:02:00+01:00", + "2019-01-01 22:03:00+01:00", + "2019-01-01 22:04:00+01:00", + "2019-01-01 22:05:00+01:00", + "2019-01-01 22:06:00+01:00", + "2019-01-01 22:07:00+01:00", + "2019-01-01 22:08:00+01:00", + "2019-01-01 22:09:00+01:00", + "2019-01-01 22:10:00+01:00", + "2019-01-01 22:11:00+01:00", + "2019-01-01 22:12:00+01:00", + "2019-01-01 22:13:00+01:00", + "2019-01-01 22:14:00+01:00", + "2019-01-01 22:15:00+01:00", + "2019-01-01 22:16:00+01:00", + "2019-01-01 22:17:00+01:00", + "2019-01-01 22:18:00+01:00", + "2019-01-01 22:19:00+01:00", + "2019-01-01 22:20:00+01:00", + "2019-01-01 22:21:00+01:00", + "2019-01-01 22:22:00+01:00", + "2019-01-01 22:23:00+01:00", + "2019-01-01 22:24:00+01:00", + "2019-01-01 22:25:00+01:00", + "2019-01-01 22:26:00+01:00", + "2019-01-01 22:27:00+01:00", + "2019-01-01 22:28:00+01:00", + "2019-01-01 22:29:00+01:00", + "2019-01-01 22:30:00+01:00", + "2019-01-01 22:31:00+01:00", + "2019-01-01 22:32:00+01:00", + "2019-01-01 22:33:00+01:00", + "2019-01-01 22:34:00+01:00", + "2019-01-01 22:35:00+01:00", + "2019-01-01 22:36:00+01:00", + "2019-01-01 22:37:00+01:00", + "2019-01-01 22:38:00+01:00", + "2019-01-01 22:39:00+01:00", + "2019-01-01 22:40:00+01:00", + "2019-01-01 22:41:00+01:00", + "2019-01-01 22:42:00+01:00", + "2019-01-01 22:43:00+01:00", + "2019-01-01 22:44:00+01:00", + "2019-01-01 22:45:00+01:00", + "2019-01-01 22:46:00+01:00", + "2019-01-01 22:47:00+01:00", + "2019-01-01 22:48:00+01:00", + "2019-01-01 22:49:00+01:00", + "2019-01-01 22:50:00+01:00", + "2019-01-01 22:51:00+01:00", + "2019-01-01 22:52:00+01:00", + "2019-01-01 22:53:00+01:00", + "2019-01-01 22:54:00+01:00", + "2019-01-01 22:55:00+01:00", + "2019-01-01 22:56:00+01:00", + "2019-01-01 22:57:00+01:00", + "2019-01-01 22:58:00+01:00", + "2019-01-01 22:59:00+01:00", + "2019-01-01 23:00:00+01:00", + "2019-01-01 23:01:00+01:00", + "2019-01-01 23:02:00+01:00", + "2019-01-01 23:03:00+01:00", + "2019-01-01 23:04:00+01:00", + "2019-01-01 23:05:00+01:00", + "2019-01-01 23:06:00+01:00", + "2019-01-01 23:07:00+01:00", + "2019-01-01 23:08:00+01:00", + "2019-01-01 23:09:00+01:00", + "2019-01-01 23:10:00+01:00", + "2019-01-01 23:11:00+01:00", + "2019-01-01 23:12:00+01:00", + "2019-01-01 23:13:00+01:00", + "2019-01-01 23:14:00+01:00", + "2019-01-01 23:15:00+01:00", + "2019-01-01 23:16:00+01:00", + "2019-01-01 23:17:00+01:00", + "2019-01-01 23:18:00+01:00", + "2019-01-01 23:19:00+01:00", + "2019-01-01 23:20:00+01:00", + "2019-01-01 23:21:00+01:00", + "2019-01-01 23:22:00+01:00", + "2019-01-01 23:23:00+01:00", + "2019-01-01 23:24:00+01:00", + "2019-01-01 23:25:00+01:00", + "2019-01-01 23:26:00+01:00", + "2019-01-01 23:27:00+01:00", + "2019-01-01 23:28:00+01:00", + "2019-01-01 23:29:00+01:00", + "2019-01-01 23:30:00+01:00", + "2019-01-01 23:31:00+01:00", + "2019-01-01 23:32:00+01:00", + "2019-01-01 23:33:00+01:00", + "2019-01-01 23:34:00+01:00", + "2019-01-01 23:35:00+01:00", + "2019-01-01 23:36:00+01:00", + "2019-01-01 23:37:00+01:00", + "2019-01-01 23:38:00+01:00", + "2019-01-01 23:39:00+01:00", + "2019-01-01 23:40:00+01:00", + "2019-01-01 23:41:00+01:00", + "2019-01-01 23:42:00+01:00", + "2019-01-01 23:43:00+01:00", + "2019-01-01 23:44:00+01:00", + "2019-01-01 23:45:00+01:00", + "2019-01-01 23:46:00+01:00", + "2019-01-01 23:47:00+01:00", + "2019-01-01 23:48:00+01:00", + "2019-01-01 23:49:00+01:00", + "2019-01-01 23:50:00+01:00", + "2019-01-01 23:51:00+01:00", + "2019-01-01 23:52:00+01:00", + "2019-01-01 23:53:00+01:00", + "2019-01-01 23:54:00+01:00", + "2019-01-01 23:55:00+01:00", + "2019-01-01 23:56:00+01:00", + "2019-01-01 23:57:00+01:00", + "2019-01-01 23:58:00+01:00", + "2019-01-01 23:59:00+01:00" + ], + "xaxis": "x2", + "y": [ + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5 + ], + "yaxis": "y2" + } + ], + "layout": { + "annotations": [ + { + "font": { + "size": 16 + }, + "showarrow": false, + "text": "Input (MW)", + "x": 0.225, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + }, + { + "font": { + "size": 16 + }, + "showarrow": false, + "text": "Output (MW)", + "x": 0.775, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + } + ], + "autosize": true, + "legend": { + "bgcolor": "#F5F6F9", + "font": { + "color": "#4D5663" + } + }, + "paper_bgcolor": "#F5F6F9", + "plot_bgcolor": "#F5F6F9", + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "Heatpump" + }, + "xaxis": { + "anchor": "y", + "autorange": true, + "domain": [ + 0, + 0.45 + ], + "gridcolor": "#E1E5ED", + "range": [ + "2019-01-01", + "2019-01-01 23:59" + ], + "showgrid": true, + "tickfont": { + "color": "#4D5663" + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "" + }, + "type": "date", + "zerolinecolor": "#E1E5ED" + }, + "xaxis2": { + "anchor": "y2", + "autorange": true, + "domain": [ + 0.55, + 1 + ], + "gridcolor": "#E1E5ED", + "range": [ + "2019-01-01", + "2019-01-01 23:59" + ], + "showgrid": true, + "tickfont": { + "color": "#4D5663" + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "" + }, + "type": "date", + "zerolinecolor": "#E1E5ED" + }, + "yaxis": { + "anchor": "x", + "autorange": true, + "domain": [ + 0, + 1 + ], + "gridcolor": "#E1E5ED", + "range": [ + -2.428571428571429, + -0.4285714285714286 + ], + "showgrid": true, + "tickfont": { + "color": "#4D5663" + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "" + }, + "type": "linear", + "zerolinecolor": "#E1E5ED" + }, + "yaxis2": { + "anchor": "x2", + "autorange": true, + "domain": [ + 0, + 1 + ], + "gridcolor": "#E1E5ED", + "range": [ + 4, + 6 + ], + "showgrid": true, + "tickfont": { + "color": "#4D5663" + }, + "title": { + "font": { + "color": "#4D5663" + }, + "text": "" + }, + "type": "linear", + "zerolinecolor": "#E1E5ED" + } + } + }, + "image/png": "iVBORw0KGgoAAAANSUhEUgAABBgAAAHCCAYAAABFfHqHAAAgAElEQVR4Xu3debyUdd3/8Y8gHJDFJZPFhVwqNyS31AgVDUJRCgVZFRAiC0WK+7YwS1HvKMtEklsjSCgE3MJdAhOTTPu53binLS4EaKVwQHbO+T2+l13TdebMnDPznWve17nmvM79x50y3+sz8/yOzDmvc801u3y4aWut8YUAAggggAACCCCAAAIIIIAAAgiUILALgaEEPZYigAACCCCAAAIIIIAAAggggEAgQGDgiYAAAggggAACCCCAAAIIIIAAAiULEBhKJuQACCCAAAIIIIAAAggggAACCCBAYOA5gAACCCCAAAIIIIAAAggggAACJQsQGEom5AAIIIAAAggggAACCCCAAAIIIEBg4DmAAAIIIIAAAggggAACCCCAAAIlCxAYSibkAAgggAACCCCAAAIIIIAAAgggQGDgOYAAAggggAACCCCAAAIIIIAAAiULEBhKJuQACCCAAAIIIIAAAggggAACCCBAYOA5gAACCCCAAAIIIIAAAggggAACJQsQGEom5AAIIIAAAggggAACCCCAAAIIIEBg4DmAAAIIIIAAAggggAACCCCAAAIlCxAYSibkAAgggAACCCCAAAIIIIAAAgggQGDgOYAAAggggAACCCCAAAIIIIAAAiULEBhKJuQACCCAAAIIIIAAAggggAACCCBAYOA5gAACCCCAAAIIIIAAAggggAACJQsQGEom5AAIIIAAAggggAACCCCAAAIIIEBg4DmAAAIIIIAAAggggAACCCCAAAIlCxAYSibkAAgggAACCCCAAAIIIIAAAgggQGDgOYAAAggggAACCCCAAAIIIIAAAiULEBhKJuQACCCAAAIIIIAAAggggAACCCBAYOA5gAACCCCAAAIIIIAAAggggAACJQsQGEom5AAIIIAAAggggAACCCCAAAIIIEBg4DmAAAIIIIAAAggggAACCCCAAAIlCxAYSibkAAgggAACCCCAAAIIIIAAAgggQGDgOYAAAggggAACCCCAAAIIIIAAAiULEBhKJuQACCCAAAIIIIAAAggggAACCCBAYOA5gAACCCCAAAIIIIAAAggggAACJQsQGEom5AAIIIAAAggggAACCCCAAAIIIEBg4DmAAAIIIIAAAggggAACCCCAAAIlCxAYSibkAAgggAAC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c10G92dz599ur73+l+DijUce/mkbcu4A+9XCu8xdDDLOMxi+1L9vcE2Jxx7/g23fscMO+sQBdsHwQfapQw4q6cnFYgQQQAABBBBAAAEEEEAAgeYjUJGBoalunwsMuS7ymNT9DS8umesij0ndJ+YigAACCCCAAAIIIIAAAgikU4DAINw3AoMQm1EIIIAAAggggAACCCCAAAJSAQKDkJvAIMRmFAIIIIAAAggggAACCCCAgFSAwCDkJjAIsRmFAAIIIIAAAggggAACCCAgFSAwSLkZhgACCCCAAAIIIIAAAggggEBlChAYKnNfeVQIIIAAAggggAACCCCAAAIISAUIDFJuhiGAAAIIIIAAAggggAACCCBQmQIEhsrcVx4VAggggAACCCCAAAIIIIAAAlIBAoOUm2EIIIAAAggggAACCCCAAAIIVKYAgaEy95VHhQACCCCAAAIIIIAAAggggIBUgMAg5WYYAggggAACCCCAAAIIIIAAApUpQGCozH3lUSGAAAIIIIAAAggggAACCCAgFSAwSLkZhgACCCCAAAIIIIAAAggggEBlChAYKnNfeVQIIIAAAggggAACCCCAAAIISAUIDFJuhiGAAAIIIIAAAggggAACCCBQmQIEhsrcVx4VAggggAACCCCAAAIIIIAAAlIBAoOUm2EIIIAAAggggAACCCCAAAIIVKYAgaEy95VHhQACCCCAAAIIIIAAAggggIBUgMAg5WYYAggggAACCCCAAAIIIIAAApUpQGCozH3lUSGAAAIIIIAAAggggAACCCAgFSAwSLkZhgACCCCAAAIIIIAAAggggEBlChAYKnNfeVQIIIAAAggggAACCCCAAAIISAUIDFJuhiGAAAIIIIAAAggggAACCCBQmQIEhsrcVx4VAggggAACCCCAAAIIIIAAAlIBAoOUm2EIIIAAAggggAACCCCAAAIIVKYAgaEy95VHhQACCCCAAAIIIIAAAggggIBUgMAg5WYYAggggAACCCCAAAIIIIAAApUpQGCozH3lUSGAAAIIIIAAAggggAACCCAgFSAwSLkZhgACCCCAAAIIIIAAAggggEBlChAYKnNfeVQIIIAAAggggAACCCCAAAIISAUIDFJuhiGAAAIIIIAAAggggAACCCBQmQIEhsrcVx4VAggggAACCCCAAAIIIIAAAlIBAoOUm2EI5Ba4b+kT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NpZbmOMjgAACCCDQ9AVSHxiiF3pq6OMm49wK3usZpybHQgABBBCIWyD8DX/0TIG4Z2Qfj9fGcgtzfAQQQAABBJq+QOoDQ9Mn5h4igAACCCCAAAIIIIAAAgggUPkCBIbK32MeIQIIIIAAAggggAACCCCAAAJlFyAwlJ2YAQgggAACCCCAAAIIIIAAAghUvgCBofL3mEeIAAIIIIAAAggggAACCCCAQNkFCAxlJ2YAAggggAACCCCAAAIIIIAAApUvQGCo/D3mESKAAAIIIIAAAggggAACCCBQdgECQ9mJGYAAAggggAACCCCAAAIIIIBA5QsQGCp/j3mECCCAAAIIIIAAAggggAACCJRdgMBQdmIGIIAAAggggAACCCCAAAIIIFD5AgSGyt9jHiECCCCAAAIIIIAAAggggAACZRcgMJSdmAEIIIAAAggggAACCCCAAAIIVL4AgaHy95hHiAACCCCAAAIIIIAAAggggEDZBQgMZSdmAAIIIIAAAggggAACCCCAAAKVL0BgqPw95hEigAACCCCAAAIIIIAAAgggUHYBAkPZiRmAAAIIIIAAAggggAACCCCAQOULEBgqf495hAgggAACCCCAAAIIIIAAAgiUXYDAUHZiBiCAAAIIIIAAAggggAACCCBQ+QIEhsrfYx4hAggggAACCCCAAAIIIIAAAmUXIDCUnZgBCCCAAAIIIIAAAggggAACCFS+AIGh8veYR4gAAggggAACCCCAAAIIIIBA2QUIDGUnZgACCCCAAAIIIIAAAggggAAClS9AYKj8PeYRIoAAAggggAACCCCAAAIIIFB2AQJD2YkZgAACCCCAAAIIIIAAAggggEDlCxAYKn+PeYQIIIAAAggggAACCCCAAAIIlF2AwFB2YgYggAACCCCAAAIIIIAAAgggUPkCBIbK32MeIQIIIIAAAggggAACCCCAAAJlFyAwlJ2YAQgggAACCCCAAAIIIIAAAghUvgCBofL3mEeIAAIIIIAAAggggAACCCCAQNkFCAxlJ2YAAggggAACCCCAAAIIIIAAApUvQGCo/D3mESKAAAIIIIAAAggggAACCCBQdgECQ9mJGYAAAggggAACCCCAAAIIIIBA5QsQGCp/j3mECCCAAAIIIIAAAggggAACCJRdgMBQdmIGIIAAAggggAACCCCAAAIIIFD5AgSGyt9jHiECCCCAAAIIIIAAAggggAACZRcgMJSdmAEIIIAAAggggAACCCCAAAIIVL4AgaHy95hHiAACCCCAAAIIIIAAAggggEDZBQgMZSdmAAIIIIAAAggggAACCCCAAAKVL0BgqPw95hEigAACCCCAAAIIIIAAAgggUHYBAkPZiRmAAAIIIIAAAggggAACCCCAQOULEBgqf495hAgggAACCCCAAAIIIIAAAgiUXYDAUHZiBiCAAAIIIIAAAggggAACCCBQ+QIEhsrfYx4hAggggAACCCCAAAIIIIAAAmUXIDCUnZgBCCCAAAIIIIAAAggggAACCFS+AIGh8veYR4gAAggggAACCCCAAAIIIIBA2QUIDGUnZgACCCCAAAIIIIAAAggggAAClS9AYKj8PeYRIoAAAggggAACCCCAAAIIIFB2AQJD2YkZgAACCCCAAAIIIIAAAggggEDlCxAYKn+PeYQIIIAAAggggAACCCCAAAIIlF2AwFB2YgYggAACCCCAAAIIIIAAAgggUPkC/x8MWqM98ApywQAAAABJRU5ErkJggg==", + "text/html": [ + "
\n", + " \n", + " \n", + "
\n", + " \n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "hpcase.data.loc['2019-01-01', ['input_MW', 'output_MW']].iplot(subplots=True, title='Heatpump', subplot_titles=['Input (MW)', 'Output (MW)'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Financials" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Collect cashflows" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Electricity market results*" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [], + "source": [ + "hpcase.generate_electr_market_results(nom_col=None, real_col='input_MWh')" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'CaseReport' object has no attribute 'formatting'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mmarketreport\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mCaseReport\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcase\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mhpcase\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mkind\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m'electr_market_results'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mmarketreport\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mshow\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32mc:\\users\\mekre\\onedrive - recoy\\work\\code\\python\\_packages\\pyrecoy\\pyrecoy\\reports.py\u001b[0m in \u001b[0;36mshow\u001b[1;34m(self, presentation_format)\u001b[0m\n\u001b[0;32m 40\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mreport\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 41\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 42\u001b[1;33m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mformatting\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m\"percentage\"\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 43\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mreport\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mapplymap\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mperc_formatting\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 44\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mAttributeError\u001b[0m: 'CaseReport' object has no attribute 'formatting'" + ] + } + ], + "source": [ + "marketreport = CaseReport(case=hpcase, kind='electr_market_results')\n", + "marketreport.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DAMPOSNEGRSForePosForeNegHeat demandoutput_MWinput_MWoutput_MWhinput_MWhNom. vol.Prod. vol.Cons. vol.Imb. vol.Day-Ahead ResultPOS ResultNEG ResultImbalance ResultCombined Result
datetime
2019-01-01 00:00:00+01:0068.9234.8558.012.046.8352.7055-1.4285710.083333-0.0238100.0-0.02381-0.023810.00.0-1.38119-1.38119-1.38119
2019-01-01 00:01:00+01:0068.9234.8558.012.032.1254.4555-1.4285710.083333-0.0238100.0-0.02381-0.023810.00.0-1.38119-1.38119-1.38119
2019-01-01 00:02:00+01:0068.9234.8558.012.030.9748.6455-1.4285710.083333-0.0238100.0-0.02381-0.023810.00.0-1.38119-1.38119-1.38119
2019-01-01 00:03:00+01:0068.9234.8558.012.051.1348.3555-1.4285710.083333-0.0238100.0-0.02381-0.023810.00.0-1.38119-1.38119-1.38119
2019-01-01 00:04:00+01:0068.9234.8558.012.048.0452.0155-1.4285710.083333-0.0238100.0-0.02381-0.023810.00.0-1.38119-1.38119-1.38119
\n", + "
" + ], + "text/plain": [ + " DAM POS NEG RS ForePos ForeNeg \\\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 68.92 34.85 58.01 2.0 46.83 52.70 \n", + "2019-01-01 00:01:00+01:00 68.92 34.85 58.01 2.0 32.12 54.45 \n", + "2019-01-01 00:02:00+01:00 68.92 34.85 58.01 2.0 30.97 48.64 \n", + "2019-01-01 00:03:00+01:00 68.92 34.85 58.01 2.0 51.13 48.35 \n", + "2019-01-01 00:04:00+01:00 68.92 34.85 58.01 2.0 48.04 52.01 \n", + "\n", + " Heat demand output_MW input_MW output_MWh \\\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 5 5 -1.428571 0.083333 \n", + "2019-01-01 00:01:00+01:00 5 5 -1.428571 0.083333 \n", + "2019-01-01 00:02:00+01:00 5 5 -1.428571 0.083333 \n", + "2019-01-01 00:03:00+01:00 5 5 -1.428571 0.083333 \n", + "2019-01-01 00:04:00+01:00 5 5 -1.428571 0.083333 \n", + "\n", + " input_MWh Nom. vol. Prod. vol. Cons. vol. \\\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 -0.02381 0 0.0 -0.02381 \n", + "2019-01-01 00:01:00+01:00 -0.02381 0 0.0 -0.02381 \n", + "2019-01-01 00:02:00+01:00 -0.02381 0 0.0 -0.02381 \n", + "2019-01-01 00:03:00+01:00 -0.02381 0 0.0 -0.02381 \n", + "2019-01-01 00:04:00+01:00 -0.02381 0 0.0 -0.02381 \n", + "\n", + " Imb. vol. Day-Ahead Result POS Result \\\n", + "datetime \n", + "2019-01-01 00:00:00+01:00 -0.02381 0.0 0.0 \n", + "2019-01-01 00:01:00+01:00 -0.02381 0.0 0.0 \n", + "2019-01-01 00:02:00+01:00 -0.02381 0.0 0.0 \n", + "2019-01-01 00:03:00+01:00 -0.02381 0.0 0.0 \n", + "2019-01-01 00:04:00+01:00 -0.02381 0.0 0.0 \n", + "\n", + " NEG Result Imbalance Result Combined Result \n", + "datetime \n", + "2019-01-01 00:00:00+01:00 -1.38119 -1.38119 -1.38119 \n", + "2019-01-01 00:01:00+01:00 -1.38119 -1.38119 -1.38119 \n", + "2019-01-01 00:02:00+01:00 -1.38119 -1.38119 -1.38119 \n", + "2019-01-01 00:03:00+01:00 -1.38119 -1.38119 -1.38119 \n", + "2019-01-01 00:04:00+01:00 -1.38119 -1.38119 -1.38119 " + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hpcase.data.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Gas costs* " + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [], + "source": [ + "baseline.add_gas_costs(gasvolumes_cols=['input_MWh'])\n", + "baseline.add_co2_costs(volume_cols=['input_MWh'], fuel='gas')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Grid costs and EB & ODE*" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "CaseStudy 'Optimisation' does not have attribute 'total_electricity_cons', so EB & ODE for electricity could not be calculated. Use 'calculate_eb_ode' function from pyrecoy.financial if you want to do the calculation manually.", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mbaseline\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0madd_eb_ode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcommodity\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m'gas'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtax_bracket\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m4\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mhpcase\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0madd_eb_ode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcommodity\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m'electricity'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtax_bracket\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m4\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m hpcase.add_grid_costs(\n\u001b[0;32m 5\u001b[0m \u001b[0mpower_MW_col\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m'input_MW'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mc:\\users\\mekre\\onedrive - recoy\\work\\code\\python\\_packages\\pyrecoy\\pyrecoy\\framework.py\u001b[0m in \u001b[0;36madd_eb_ode\u001b[1;34m(self, commodity, tax_bracket, base_cons, horti, m3)\u001b[0m\n\u001b[0;32m 281\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34mf\"total_{commodity}_cons\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 282\u001b[0m raise AttributeError(\n\u001b[1;32m--> 283\u001b[1;33m \u001b[1;34mf\"CaseStudy '{self.name}' does not have attribute 'total_{commodity}_cons', \"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 284\u001b[0m \u001b[1;34mf\"so EB & ODE for {commodity} could not be calculated. Use 'calculate_eb_ode' function \"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 285\u001b[0m \u001b[1;34m\"from pyrecoy.financial if you want to do the calculation manually.\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mAttributeError\u001b[0m: CaseStudy 'Optimisation' does not have attribute 'total_electricity_cons', so EB & ODE for electricity could not be calculated. Use 'calculate_eb_ode' function from pyrecoy.financial if you want to do the calculation manually." + ] + } + ], + "source": [ + "baseline.add_eb_ode(commodity='gas', tax_bracket=4)\n", + "\n", + "hpcase.add_eb_ode(commodity='electricity', tax_bracket=4)\n", + "hpcase.add_grid_costs(\n", + " power_MW_col='input_MW',\n", + " grid_operator='tennet',\n", + " year=2020, \n", + " connection_type='HS'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Other cashflows*" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [], + "source": [ + "hpcase.add_cashflow('SDE++ subsidy', 20_000)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Heatpump OPEX (€)': -10000,\n", + " 'Result on electricity market (€)': -532533.94,\n", + " 'SDE++ subsidy': 20000}" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hpcase.cashflows" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### EBITDA" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [], + "source": [ + "baseline.calculate_ebitda()\n", + "hpcase.calculate_ebitda()" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "cliponaxis": false, + "marker": { + "color": "#0e293b" + }, + "text": [ + -1080682.3, + -522533.93999999994 + ], + "textposition": "outside", + "texttemplate": "%{text:.3s}€", + "type": "bar", + "x": [ + "Baseline", + "Optimisation" + ], + "y": [ + -1080682.3, + -522533.93999999994 + ] + } + ], + "layout": { + "height": 400, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "EBITDA comparison in k€" + }, + "width": 800, + "xaxis": { + "autorange": true, + "range": [ + -0.5, + 1.5 + ], + "type": "category" + }, + "yaxis": { + "range": [ + -1188750.5300000003, + 0 + ], + "type": "linear" + } + } + }, + "image/png": "iVBORw0KGgoAAAANSUhEUgAABBgAAAGQCAYAAAAEOtN7AAAgAElEQVR4Xu3dC7zmU70/8EUug4acjnJcMomQXHMrZ8otRBLOUEbkzjCHRpwxJ06HzoxcJpKZEFFIzXEpETUkk864DqZOoyTCHELuY6jM/7V+/X9Pz96z98zevmbs9fzez+vVq5n9POv3rPX+LvP7PZ+9futZZM6cOXOSBwECBAgQIECAAAECBAgQIEAgILCIgCGgpykBAgQIECBAgAABAgQIECBQCQgYTAQCBAgQIECAAAECBAgQIEAgLCBgCBM6AAECBAgQIECAAAECBAgQICBgMAcIECBAgAABAgQIECBAgACBsICAIUzoAAQIECBAgAABAgQIECBAgICAwRwgQIAAAQIECBAgQIAAAQIEwgIChjChAxAgQIAAAQIECBAgQIAAAQICBnOAAAECBAgQIECAAAECBAgQCAsIGMKEDkCAAAECBAgQIECAAAECBAgIGMwBAgQIECBAgAABAgQIECBAICwgYAgTOgABAgQIECBAgAABAgQIECAgYDAHCBAgQIAAAQIECBAgQIAAgbCAgCFM6AAECBAgQIAAAQIECBAgQICAgMEcIECAAAECBAgQIECAAAECBMICAoYwoQMQIECAAAECBAgQIECAAAECAgZzgAABAgQIECBAgAABAgQIEAgLCBjChA5AgAABAgQIECBAgAABAgQICBjMAQIECBAgQIAAAQIECBAgQCAsIGAIEzoAAQIECBAgQIAAAQIECBAgIGAwBwgQIECAAAECBAgQIECAAIGwgIAhTOgABAgQIECAAAECBAgQIECAgIDBHCBAgAABAgQIECBAgAABAgTCAgKGMKEDECBAgAABAgQIECBAgAABAgIGc4AAAQIECBAgQIAAAQIECBAICwgYwoQOQIAAAQIECBAgQIAAAQIECAgYzAECBAgQIECAAAECBAgQIEAgLCBgCBM6AAECBAgQIECAAAECBAgQICBgMAcIECBAgAABAgQIECBAgACBsICAIUzoAAQIECBAgAABAgQIECBAgICAwRwgQIAAAQIECBAgQIAAAQIEwgIChjChAxAgQIAAAQIECBAgQIAAAQICBnOAAAECBAgQIECAAAECBAgQCAsIGMKEDkCAAAECBAgQIECAAAECBAgIGMwBAgQIECBAgAABAgQIECBAICwgYAgTOgABAgQIECBAgAABAgQIECAgYDAHCBAgQIAAAQIECBAgQIAAgbCAgCFM6ABvpsCfnnk+nTrh8rTPHh9N71/73W9mV7q893U3Tk2//u3DaeSBe6QlFl8s1K8HH56ZxpzyjTR29EFp9dVWCh1LYwIECBAgQIAAAQIECCwogY4MGGbPfjWdePqF6drJU+dyW3+d96QJpxydll9ucLryuinphFMv6PKaVf5phfTBTdZNn9lj+/SeIX//MJdfO/XuX6WTPn9Amn7/g+mzR50yz5rsvN0Wvb72H/9hubTphmunT39y27TR+9dMiy66SI/HmvXy7DRm3PlppRVXSMccOiy95S1vWVDzoNjjPvb4U+lzJ34tHX3wv6QPbfr+ATOOCy//Ufr1bx5K/3ns/mnppQaF+tWfgOG11+akW++Ynr7+rR+kX97/++p9N17vvemze+6Q/nmz9cyhUCU0JkCAAAECBAgQIEBgXgIdHTBs8L410k7bbt5l/Isusmga/Nalqw/1OTS4Zeq9afSRe6cll1w8zXltTvrdH2amb0/6cfrdwzPT1/7rqPTud/1T1b49YHjLWxZNL856uXXc6268Ld1xz4zWcfITi73lLemtyyyV7rzv/nTaOZenU084LC237DJVm0dm/jFNvuWudMkVP0nDd98uHbH/bmnQkkvMVad7fvlAGnnCV9M7/3H5dM64o6v/92ieQF8Dhr/+9a9p4rd+kCZdc3M6aO+d01Yf2jC98uqf082/uCflCOuze+0oYGje9DFiAgQIECBAgAABAgtNoKMDhi02XjftvtPQXjHbQ4NBg/7+Af+Jp55JI0Z/Je23547pE9t/aK6Aof213cOH7s/dce+MNP7rk1qrJto7c+e996djT56YRh6wx1z9zB8Wzzh3Ulp8sbek//vj02mrD22Udtqma1iy0GaJN3pTBfoaMORAatQXz0ljxxycttj4fW9qn705AQIECBAgQIAAAQLNExAw/P/bHtqDgXxf/2Gjv5IO2nuntP1HNl1gAcOcOXPSeZf8MN027X/TV754ZGuFQ37DvMrhmP+cmP7z859Nv3/k8fTjm29PY48/eL7L7V/9819Svv//4kk3pN/87pG07OBl0se23iwdO+JTaalBS6b8nr+6/6F01gVXpNun/Tq9ZdFF04e32CAdtu8n0tprvKv1X0AOX2Y88HB633uHpPMv/WF66JHHU761Y99/2T7tvN0H06Qf3pz++4c/S0/96bm07nuHpKMO3iN9aJP3p0UW+dvtHuPPm5TetfI70nKDl0kTLro6/ebBR9OQVVdMh+yzS/r4dlu0fpOe+5NXf1x65eR0+z0z0vMvvNTr8YassmJaa41V0xlf/2667e5fp5OPOzBtveWGacToM9Oow4alTTdYu3rvX/3moXTOhVelqXf/b/Ub/Pe+Z9W06/Zbpk/vtm1aconFq9c8OvPJdNY3/jvd+PO7019fe626jeDQz+ySNt9ondYY8gf70yZeno4b8en0vR/8NF19w60p37ay2UbrVLdk5HH39ugeXj3z3AvV7S5HHrB7mnrnr9K3/vvHvdp1P2ZPAcNd9/0mffGMi9K/H7VPK0w4+4Ir0x+ffiad8Ln9wvs+NO+fQiMmQIAAAQIECBAgQCAqIGDoFjDkD5BX/ejn6eZfTEtf/vdD0z8sv+wCCxjyge+e/pt0zH9OSBPGfS6ts+ZqXT7g3zL1nipUePqZ59O/nnB2+o/P7Zc2fP8avda8XiKf95445rA908bvX7P6EDvziafT0M3/dv99/tB9wpcvSAfuvXPa9p83Tn/5y1/TFdfdkq689pZ05slHprxHRX7U+1N8ZIsN0ucP3yu9bdm3pp/+4p50+te/Wz2fV4bss/tH02KLvSVdce0t6dobp6YJ445Oq670jlbAcMUPf5a232rTtN+wHdKyb106TZ326/Tlr11WhQx777Zt64P81df/PC291JLVfhT58b1rbq5Ckq9/eVSX4+Xw4Y9PPVu1XfPdK1dhy1/++tcuAUMOZo49+etp2Me3Slt/aMPqeDN+90h6efYraZstN6re84GHHktHn/i1tOPWm6U9dvpwNYYcNEy8+PvpC0d/Jn30w5tU7fIH++yeQ4kcwHxgvfdWgcW3Jt2Q7pr+2+oWmnf849t6rEdPAUMOQma/8kraa9dtKvv8yLX69hU/ThPHfa4KQnp6dA8Ycv//7UvnplGHDqtCnd//4f/SocedUdW5+6PeC6T7yproPxzaEyBAgAABAgQIECBAoLtARwcMPW3ymD9YjzpkWJcP0e0o+YPY/nt9rPrfMkv/fXO+3m6nyG3n9dy8bpGoP8TmD4d5WXv9G/h6c8ftt9qsui0iBwdfPuc71Qfqow7ao/XBvHsx84qF/IH4lDGH9BhE1MddZ80h6ZB9Pt46Tj7+aRO/m16a9XLrt995TBd859ouH/Lzh/QTTr0wzXr5lfTlfz+k2ssiP+oVH/kDb700P69g+OWMB+damZGPe/n3b5rnh/P6FpXRI/dumeTj/SiHGKd8Lq357lVaQ88rA9pXMOTA5r/OuiSdM/botOI7/mGu/+LrW09eePGlLr/pzyspLrvqxnTjz+9q9Tl/sM+1+dyhe3a5PaWn/nV/o94ChrwvQrv9S7Nmp9H/dV7aesuNer2dpz1gmDMnpePHnV/tp5BXptQrRuqNTed3W5B/AgkQIECAAAECBAgQILCgBDo6YOhpk8clFl+8FRx03+QxIz/51LPVUvj/ufOX6fQTR7S+SWJhBgz5XvovnfXtdOZJR6b8rRb58fPbp6evffOq9NWT/7XX35r/4Me/qJby5w/X9YaS7RMn3+ZwxJgz08nHHVDdEtD+yCsbTptwefUBPm8mmcf7izt/Wb0231qRH/WH2BXf8fZWSJN/Xn/IH7bLVq0PyTkQeO2vr1UrKeoPwfm1+bfvR51wdho35uDWaonuk7t7aJCfz8ebPfuV9G9HfLrLRoXdX5tDlC+d+e30+z88ng7eZ+fqN/ztG2g+9/xLlcGen9i6tb9G/f65b8eeNLFaNZJXk+QP9sfkv48+qMvqkp7619eA4bD9PpHyqpD60ZdgoA4YcriUb1fZ5aMfSp/c8Z+7uPblOAvqHxHHJUCAAAECBAgQIECAQBbo6IBhfr/N7S00yPsYnPyVi9Oiiy6avnDUZ9Liiy82z1UKkRUMeYXDF065oPqWiDWGrFytVsibO178vet7nKGnnXh4r5s9zqsf+WDz2iww92PM2PPTuacek1ZfbaUex9vfgCG/Z71apB5MvSqgXrGRv1bx9nt+nS6/+sb0wEMzq+X+9eOis0Z3WcHQ0/F6+rCf6/eLO36ZLrz8uvTLGb+vwoSDh388vX35ZVthSPueDb31rTevSMDQ/X37EgzUQUfuZ16lcvh+u6bD9/1EK2ipTXu6RaLd0D95BAgQIECAAAECBAgQWJACAoYeNnnM4Pk35o//8el00ucPSPm2iQWxgiGHCRMu+n6679e/S+O/eER1y0HeQyDf5nDs4Z9K66z5900Xc5/y11r+7qHHet3sMffxyutuecNWMEztZtPfgKGnFQftKxjWW3v1dMF3rktX/WhKGvOvw9OG665ZrS7pbQVDXwOG+j+YfNvDHx77Y7Up5MuvvJpOP+Hw6qn+rGAYc8o3qhUMOXSpH29GwDDi+DPTfnvuUK1oyX3KwdcOW/1tA9L6YZPHBflPpWMTIECAAAECBAgQIDA/AQFDDwHDwljBkD/8/vQX09J/nH5Rlw+LOST44eRfzLV3QS5kvcfCqScc2uPtBTmoGPnvX01nnTTyDdmDIRowzG8Phrzx44mnX5hy0PCZf9m+NVdzyHLYv41PX/z8Z1/XCobukz6HGmPGfSOdMubgtNoq76xWiPR1D4aBEjDU/cj9n/itH6Qf3XRbOuvkkdWql/rhayrn98+d5wkQIECAAAECBAgQWJACHR0w9LQHw6KLLFqtFFh00UWqVQm3TL03jT5y77Tkkn/7+sK8B0PehPAnU+5K4784ovUBN7KC4bRzLk+nnnBYtS9C/taG//3tw9W3JEyeclc6cv/dqq9+zN/wUG/CuPnG70uf/uS2c9W9Dj5W+ad3dNkosH5h/S0Seb+G/DWKa71n1fTiSy+nO+65P330I5ukwcsslepbIepvkcht87dAXHrV5GrjxQ3e9/dvkYgGDJde+ZP0ie237PItEqdPuDyNPHD3ag+B1157rfqwf9///i79xzH7VV+D+X9P/KnaayJ/dWf+Fol648u8oiQ/ut9y0X01wT2/eiDddOu0tPvHhlbeedVF/taHBx6eWa1gyD/Lt2GM/MJXW98ikb8lIn9Dxpnf+O8uYc9AukWiPeio95l4/Mk/VWOqv+mkrv+ka25OB+29c8obSub9J/J8mzVrdvrYNpsvyH9LHJsAAQIECBAgQIAAgYYLdHTA0NO3SOSvYZxwytFp+eUGt76KsX0O5A+bH95ig+qrFPMtCvUGhZGA4bNHndJlmuVl7h/cZN00fPftqt9A1++RfwP9n1+5OH315JGtr2fsPj+vu+m2dNF3r+/1Wxhmv/Jq9TWP3/3+TSlv6rjs4GXSrjtsmUYesHtrc8sZD/whff1bP6jClfzI481fw7j2Gn+/JaOn8fb3Fok///kvab11Vk/nX3JN+s2Dj6Yhq65YuX58uy1a+wfkb6D48oTvpJ/87M7019deS5tttE4aecBu6Yc/+Z8qFOlvwJC/4WHCRVenH//szpS/1jKPf/uPbJIO/cwn0krvfHuLM+9XcO63f1C9Ln9gz5teHvqZXdLmG63TqsdADRiqIOzpZ9OxJ309bbLhWl32Y8ghw8+m3psuuOy69Mv7f1+N9/1rvbsKHPI3VXgQIECAAAECBAgQIEBgQQl0ZMCwoLAct+8Cva046PsRvJIAAQIECBAgQIAAAQIEShIQMJRUrYL6KmAoqFi6SoAAAQIECBAgQIAAgTdAQMDwBiA6xNwCAgazggABAgQIECBAgAABAs0SEDA0q94LbbQChoVG7Y0IECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAwlF0/vSdAgAABAgQIECBAgAABAgNCQMAwIMqgEwQIECBAgAABAgQIECBAoGwBAUPZ9dN7AgQIECBAgAABAgQIECAwIAQEDAOiDDpBgAABAgQIECBAgAABAgTKFhAw9KF+s2e/mk48/cJ07eSp1asvOmt02nSDtfvQ0ksIECBAgAABAgQIECBAgEAzBAQMfajzlddNqV61+05D0zPPvZDGnX1pOn7k8LT8coP70NpLCBAgQIAAAQIECBAgQIBA5wsIGOZT47x64bSJl6fhu2+XVl9tperV48+blIassmIVOHgQIECAAAECBAgQIECAAAECKQkY5jMLelqx0L6iwSQiQIAAAQIECBAgQIAAAQIEBAzznQM5YPjmd69PI/bdNQ0atET1+jvunZGm3DY9jTpk2HzbewEBAgQIECBAgAABAgQIEGiCgBUM86lyJ61geG3OnHTDLXemi753VRPmtjESIEBgwAp8ds/d0g4f3iQtusgiA7aPJXfM+a7k6uk7AQKdJOB810nV7NtYBAzzcZrfHgwzn365b9ID5FV33Ts97XPkqAHSG90gQIBAMwUu+dr49IEN1mvm4BfSqJ3vFhK0tyFAgMA8BEo836309qXUNCAgYOgDXt5z4aFHH69uicgrGsaMOz8de/inqk0fBQx9APQSAgQIEOgiUOIFV2klFDCUVjH9JUCgEwVKPN8JGGIzUcDQB7+8iuHE0y9M106eWr36orNGp003WLv6s4ChD4BeQoAAAQIChoU8BwQMCxnc2xEgQKAHAQFD86aFgCFYcwFDEFBzAgQINFCgxAuu0sokYCitYvpLgEAnCpR4vrOCITYTBQwxPysYgn6aEyBAoIkCJV5wlVYnAUNpFdNfAgQ6UaDE852AITYTBQwxPwFD0E9zAgQINFGgxAuu0uokYCitYvpLgEAnCpR4vhMwxGaigCHmJ2AI+mlOgACBJgqUeMFVWp0EDKVVTH8JEOhEgRLPdwKG2EwUMMT8BAxBP80JECDQRIESL7hKq5OAobSK6S8BAp0oUOL5TsAQm4kChpifgCHopzkBAgSaKFDiBVdpdRIwlFYx/SVAoBMFSjzfCRhiM1HAEPMTMAT9NCdAgEATBUq84CqtTgKG0iqmvwQIdKJAiec7AUNsJgoYYn4ChqCf5gQIEGiiQIkXXKXVScBQWsX0lwCBThQo8XwnYIjNRAFDzE/AEPTTnAABAk0UKPGCq7Q6CRhKq5j+EiDQiQIlnu8EDLGZKGCI+QkYgn6aEyBAoIkCJV5wlVYnAUNpFdNfAgQ6UaDE852AITYTBQwxPwFD0E9zAgQINFGgxAuu0uokYCitYvpLgEAnCpR4vhMwxGaigCHmJ2AI+mlOgACBJgqUeMFVWp0EDKVVTH8JEOhEgRLPdwKG2EwUMMT8BAxBP80JECDQRIESL7hKq5OAobSK6S8BAp0oUOL5TsAQm4kChpifgCHopzkBAgSaKFDiBVdpdRIwlFYx/SVAoBMFSjzfCRhiM1HAEPMTMAT9NCdAgEATBUq84CqtTgKG0iqmvwQIdKJAiec7AUNsJgoYYn4ChqCf5gQIEGiiQIkXXKXVScBQWsX0lwCBThQo8XwnYIjNRAFDzE/AEPTTnAABAk0UKPGCq7Q6CRhKq5j+EiDQiQIlnu8EDLGZKGCI+QkYgn6aEyBAoIkCJV5wlVYnAUNpFdNfAgQ6UaDE852AITYTBQwxPwFD0E9zAgQINFGgxAuu0uokYCitYvpLgEAnCpR4vhMwxGaigCHmJ2AI+mlOgACBJgqUeMFVWp0EDKVVTH8JEOhEgRLPdwKG2EwUMMT8BAxBP80JECDQRIESL7hKq5OAobSK6S8BAp0oUOL5TsAQm4kChpifgCHopzkBAgSaKFDiBVdpdRIwlFYx/SVAoBMFSjzfCRhiM1HAEPMTMAT9NCdAgEATBUq84CqtTgKG0iqmvwQIdKJAiec7AUNsJgoYYn4ChqCf5gQIEGiiQIkXXKXVScBQWsX0lwCBThQo8XwnYIjNRAFDzE/AEPTTnAABAk0UKPGCq7Q6CRhKq5j+EiDQiQIlnu8EDLGZKGCI+QkYgn6aEyBAoIkCJV5wlVYnAUNpFdNfAgQ6UaDE852AITYTBQwxPwFD0E9zAgQINFGgxAuu0uokYCitYvpLgEAnCpR4vhMwxGaigCHmJ2AI+mlOgACBJgqUeMFVWp0EDKVVTH8JEOhEgRLPdwKG2EzsuIBh9uxX04mnX5iunTy1krnorNFp0w3WnkvpwYdnpkOPOyPNfOLpuV535XVT0sSLr07nnnpMWn21lbq0HX/epHTHtBlpwilHp+WXGyxgiM0/rQkQINBIgRIvuEorlIChtIrpLwECnShQ4vlOwBCbiR0XMORwID9232loeua5F9K4sy9Nx48cXoUB9SOHEBO+9f20/147Vj/PYcNpEy9PY48/uPp7PsYNN9+edthqs+o49aM+3gsvzmq9dubTL8cqsJBbu+BayODejgABAj0IlHjBVVohne9Kq5j+EiDQiQIlnu8EDLGZ2FEBQw4OclAwfPftWisP8oqDIaus2CUo6E7WvV0OGJ7607PpxVmz04h9d02DBi1RNal//sBDj7VCCwFDbAJqTYAAgSYKlHjBVVqdBAylVUx/CRDoRIESz3cChthM7KiAoacVC+0rGnqj6t6ubvPQo4+noZuvV91iUYcQO26zWZp0zc0Chti805oAAQKNFijxgqu0ggkYSquY/hIg0IkCJZ7vBAyxmVhcwNB9j4V6+Dtvt0Uadcie6bKrb+yy6uCOe2ekKbdNT6MOGdarVF7lUAcJ9UqF/P+rrrxCq22+jWLylLvSsF226nLbxQuz/hyrwEJsPWdOSlNun5b2PmLUQnxXb0WAAAEC3QUuO2d8GrrZRmmRRdgsCAHnuwWh6pgECBDov0CJ57vBSy/e/4Fq0RIoLmCYV+1ezwqGnlY41D/bessNW2HCT2+9pwoc1hiycteA4eW/FDOdqguu2+4WMBRTMR0lQKBTBaoLrs03FjAsoAI73y0gWIclQIBAPwVKPN8NXmqxfo7Sy9sFOipg6O8eDDlIyLdBdF/d0B465NUNm6y/VrrzvvurTSHzo33jSHsw+A+KAAECBPorUOKS0f6O8c1+vVsk3uwKeH8CBAikVOL5zi0SsZnbUQFDpmgPDfKKhjHjzk/HHv6ptPzbBrf+nL96srdwoT5G/v/8DRL111l+bNt8C8awub6ZQsAQm4BaEyBAoIkCJV5wlVYnAUNpFdNfAgQ6UaDE852AITYTOy5g6L5Hw0Vnja42aeweNowYfWa679e/66J38nEHVqFC+wqG+nh574X6OFYwxCad1gQIEGi6QIkXXKXVTMBQWsX0lwCBThQo8XwnYIjNxI4LGGIc/W9tBUP/zbQgQIBA0wVKvOAqrWYChtIqpr8ECHSiQInnOwFDbCYKGGJ+ScAQBNScAAECDRQo8YKrtDIJGEqrmP4SINCJAiWe7wQMsZkoYIj5CRiCfpoTIECgiQIlXnCVVicBQ2kV018CBDpRoMTznYAhNhMFDDE/AUPQT3MCBAg0UaDEC67S6iRgKDl3DsoAACAASURBVK1i+kuAQCcKlHi+EzDEZqKAIeYnYAj6aU6AAIEmCpR4wVVanQQMpVVMfwkQ6ESBEs93AobYTBQwxPwEDEE/zQkQINBEgRIvuEqrk4ChtIrpLwECnShQ4vlOwBCbiQKGmJ+AIeinOQECBJooUOIFV2l1EjCUVjH9JUCgEwVKPN8JGGIzUcAQ8xMwBP00J0CAQBMFSrzgKq1OAobSKqa/BAh0okCJ5zsBQ2wmChhifgKGoJ/mBAgQaKJAiRdcpdVJwFBaxfSXAIFOFCjxfCdgiM1EAUPMT8AQ9NOcAAECTRQo8YKrtDoJGEqrmP4SINCJAiWe7wQMsZkoYIj5CRiCfpoTIECgiQIlXnCVVicBQ2kV018CBDpRoMTznYAhNhMFDDE/AUPQT3MCBAg0UaDEC67S6iRgKK1i+kuAQCcKlHi+EzDEZqKAIeYnYAj6aU6AAIEmCpR4wVVanQQMpVVMfwkQ6ESBEs93AobYTBQwxPwEDEE/zQkQINBEgRIvuEqrk4ChtIrpLwECnShQ4vlOwBCbiQKGmJ+AIeinOQECBJooUOIFV2l1EjCUVjH9JUCgEwVKPN8JGGIzUcAQ8xMwBP00J0CAQBMFSrzgKq1OAobSKqa/BAh0okCJ5zsBQ2wmChhifgKGoJ/mBAgQaKJAiRdcpdVJwFBaxfSXAIFOFCjxfCdgiM1EAUPMT8AQ9NOcAAECTRQo8YKrtDoJGEqrmP4SINCJAiWe7wQMsZkoYIj5CRiCfpoTIECgiQIlXnCVVicBQ2kV018CBDpRoMTznYAhNhMFDDE/AUPQT3MCBAg0UaDEC67S6iRgKK1i+kuAQCcKlHi+EzDEZqKAIeYnYAj6aU6AAIEmCpR4wVVanQQMpVVMfwkQ6ESBEs93AobYTBQwxPwEDEE/zQkQINBEgRIvuEqrk4ChtIrpLwECnShQ4vlOwBCbiQKGmJ+AIeinOQECBJooUOIFV2l1EjCUVjH9JUCgEwVKPN8JGGIzUcAQ8xMwBP00J0CAQBMFSrzgKq1OAobSKqa/BAh0okCJ5zsBQ2wmChhifgKGoJ/mBAgQaKJAiRdcpdVJwFBaxfSXAIFOFCjxfCdgiM1EAUPMT8AQ9NOcAAECTRQo8YKrtDoJGEqrmP4SINCJAiWe7wQMsZkoYIj5CRiCfpoTIECgiQIlXnCVVicBQ2kV018CBDpRoMTznYAhNhMFDDE/AUPQT3MCBAg0UaDEC67S6iRgKK1i+kuAQCcKlHi+EzDEZmLHBQyzZ7+aTjz9wnTt5KmVzEVnjU6bbrD2PJXGnzepen7UIcOq/7/yuilp4sVXp3NPPSatvtpKXdrm194xbUaacMrRafnlBgsYYvNPawIECDRSoMQLrtIKJWAorWL6S4BAJwqUeL4TMMRmYscFDDkcyI/ddxqannnuhTTu7EvT8SOHV2FAT4877p2Rrr/p9vTOFZZPh+yzSytguOHm29MOW21WHad+1Md74cVZaezxBwsYYnNPawIECDRWoMQLrtKKJWAorWL6S4BAJwqUeL4TMMRmYkcFDHn1wmkTL0/Dd9+utfIgrzgYssqKXYKC7oHBsF22StOm/7ZLwPDUn55NL86anUbsu2saNGiJVvCQf/7AQ4+1QouZT78cq8BCbu2CayGDezsCBAj0IFDiBVdphXS+K61i+kuAQCcKlHi+EzDEZmJHBQw9rVhoX9HQneq8S65J2w39QPXjyVPu6hIw5J899Ojjaejm61W3WNThxY7bbJYmXXOzgCE277QmQIBAowVKvOAqrWAChtIqpr8ECHSiQInnOwFDbCYWFzB032OhHv7O222RRh2yZ7rs6hu7rDrIt0BMuW16a3+F+vX554889mS1suHBh2f2GDCsuvIKrbb1a/Jqh/bbLp6f9edYBRZi6zlzUppy+7Q0/IhRC/FdvRUBAgQIdBe49JzxaehmG6VFFmGzIASc7xaEqmMSIECg/wIlnu+WXXrx/g9Ui5ZAcQHDvGrX1xUM+XXf/O71rSCit4Bh6y03bIUJP731npQDhzWGrNwlYHjx5b8UM53yBdctt92d9hYwFFMzHSVAoDMFLjtnfPrw5hsLGBZQeZ3vFhCswxIgQKCfAiWe79661GL9HKWXtwt0VMDQ1z0Y8m0TJ5x6wVwzYf113lN9O0QOE/Ijr27Iezhssv5a6c777k/777Vj9fP2FQz2YPAfFAECBAj0V6DEJaP9HeOb/Xq3SLzZFfD+BAgQSKnE851bJGIzt6MChkyRw4O8d0L+ysm8UmHMuPPTsYd/Ki3/tsGtP3f/6sneVjDUt08cetwZ6WPb5lsw/nZMAUNs0mlNgACBpguUeMFVWs0EDKVVTH8JEOhEgRLPdwKG2EzsuICh+x4NF501utqksT1s6E/AUB8v771QH0fAEJt0WhMgQKDpAiVecJVWMwFDaRXTXwIEOlGgxPOdgCE2EzsuYIhx9L+1WyT6b6YFAQIEmi5Q4gVXaTUTMJRWMf0lQKATBUo83wkYYjNRwBDzSwKGIKDmBAgQaKBAiRdcpZVJwFBaxfSXAIFOFCjxfCdgiM1EAUPMT8AQ9NOcAAECTRQo8YKrtDoJGEqrmP4SINCJAiWe7wQMsZkoYIj5CRiCfpoTIECgiQIlXnCVVicBQ2kV018CBDpRoMTznYAhNhMFDDE/AUPQT3MCBAg0UaDEC67S6iRgKK1i+kuAQCcKlHi+EzDEZqKAIeYnYAj6aU6AAIEmCpR4wVVanQQMpVVMfwkQ6ESBEs93AobYTBQwxPwEDEE/zQkQINBEgRIvuEqrk4ChtIrpLwECnShQ4vlOwBCbiQKGmJ+AIeinOQECBJooUOIFV2l1EjCUVjH9JUCgEwVKPN8JGGIzUcAQ8xMwBP00J0CAQBMFSrzgKq1OAobSKqa/BAh0okCJ5zsBQ2wmChhifgKGoJ/mBAgQaKJAiRdcpdVJwFBaxfSXAIFOFCjxfCdgiM1EAUPMT8AQ9NOcAAECTRQo8YKrtDoJGEqrmP4SINCJAiWe7wQMsZkoYIj5CRiCfpoTIECgiQIlXnCVVicBQ2kV018CBDpRoMTznYAhNhMFDDE/AUPQT3MCBAg0UaDEC67S6iRgKK1i+kuAQCcKlHi+EzDEZqKAIeYnYAj6aU6AAIEmCpR4wVVanQQMpVVMfwkQ6ESBEs93AobYTBQwxPwEDEE/zQkQINBEgRIvuEqrk4ChtIrpLwECnShQ4vlOwBCbiQKGmJ+AIeinOQECBJooUOIFV2l1EjCUVjH9JUCgEwVKPN8JGGIzUcAQ8xMwBP00J0CAQBMFSrzgKq1OAobSKqa/BAh0okCJ5zsBQ2wmChhifgKGoJ/mBAgQaKJAiRdcpdVJwFBaxfSXAIFOFCjxfCdgiM1EAUPMT8AQ9NOcAAECTRQo8YKrtDrlgOHpZ54trdv6S4AAgY4SePvyb0sf2GC9osYkYIiVS8AQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqG3NTBf7w6ONpzJfOTk88+aeKYO01352+NGZEWm7Zt6bnnn8xfWHshDTjt7+vnjvmiM+kHbf5UPXn+37123TMieNbbO3PfeOSq9JmG70/rb/umj2y5ue/e9WPq+feucI/pLFfGJnetcqKTS2BcRPol4CAoV9cc71YwBDzEzAE/TQnQIBAEwUEDE2sujE3VSAHBbdP+2U6aJ/d5iL4zhXXpy0337D68J/DhlPPvjgdut8eVShw3eRb007bbZmWXHKJLs/l184rYMjP5UdP79fUGhg3gf4ICBj6ozX3azsuYJg9+9V04ukXpmsnT61Ge9FZo9OmG6zdo9KDD89Mhx53Rpr5xNNp/XXekyaccnRafrnB6crrpqSJF1+dzj31mLT6ait1aTv+vEnpjmkzWq+d+fTLsQos5NZ33Ts97XPkqIX8rt6OAAECBNoFBAzmA4HmCOSAYeYTT7ZWJsxr5L0FB6+88mo69+Ir0id32roKI3p7XQ4pJlz4vTTigD2rFRIeBAj0X0DA0H+z9hYdFzDkcCA/dt9paHrmuRfSuLMvTcePHF4FB+2PHC6MOeUbaezog+YKEfIxbrj59rTDVptVx6kf9fFeeHFWGnv8wdUxBQyxCag1AQIEmiggYGhi1Y25qQLX3/SLdMY5324Nf6/dtu9xdUH3EKHdK4cU1/5kShp1+D7Viob2gCE/d+rZF6WRB386nX3+d1q3YuT22wzdtNWmqf7GTaC/AgKG/op1fX1HBQx59cJpEy9Pw3ffrhUa5BUHQ1ZZsUtQkAnyz4duvl6PqxtywPDUn55NL86anUbsu2saNGiJSq3++QMPPdYKLQQMsQmoNQECBJooIGBoYtWNmUBKOUQYP/GStNH6a8+1oiEHEflR78FQv/amKXd02bchv6YOGPLKiGn3zWiFCPO6HYM/AQJ9ExAw9M2pt1d1VMDQ04qF9hUNNUIOIiZ86/tp3fcOSaO+eE714wP33jmNOmRYK0jIf3jo0cdbIUQdXuy4zWZp0jU3Cxhi805rAgQINFpAwNDo8ht8hwvUGyz2trliTyHA/IKBejPIgz+zW7WxY36PBx96LA1+69KtcKH7ZpKZuX1jyA5nNzwCb5iAgCFGWVzA0H2PhXr4O2+3RRp1yJ7psqtv7LLq4I57Z6Qpt01vhQf59TmIGDH6zLTpRmtXP6+POWyXraoVDXUoserKK7Ta5lsqJk+5K+XXtN928fysP8cqsBBbz5mT0pTbp6XhR9iDYSGyeysCBAjMJXDpOePT0M02SossAocAgaYJTJv+m/Q/d05PI/bfoxp6/vvVP7olHX/UvmnQkn9bNdvT44c//nn1449v/89pwjevSO9a+Z2tl+Wf5cezz7+YvvL1y9PnDvtUeps9GJo2tYz3DRJYdunF36AjNfMwxQUM8ypTX1cw9PS69iCiDhi23nLDVpjw01vvSTlwWGPIyl0Chhdf/ksxMycHDLfcdnfaW8BQTM10lACBzhS47Jzx6cObbyxg6MzyGhWBXgVyADDqxK+mIw/cI2283lrp7un3V+HCmKP26xIu5NflsKEOHLq3O+fCK9IHN31/et97353GnnVx+uTHPlwdLz/yc/lxxAF/CzA8CBDon8Bbl1qsfw28uotARwUMfd2DoafX5YDhkceerPZqaL+tIu/VsMn6a6U777s/7b/XjhVe+woGezD4L4oAAQIE+ivgFon+ink9gXIF6lsm6hGccdKo6jaH9j0W2keXN4Ec9omPpi+MnZBm/Pb3rafab3do3+Sx++0TuUH7e6695rvTl8aM8K0S5U4hPV/IAm6RiIF3VMCQKXI4kPdOyLc+5JUKY8adn449/FNp+bcNbv05f/Vk++t6u0Uihw31V1l+bNt8C8bfjilgiE06rQkQINB0AQFD02eA8RMgQIDAQBUQMMQq03EBQ/c9Gi46a3S1r0J72JADhvzIqxMuuOza6s8nH3dg65sm2lcwdA8fBAyxCac1AQIECKQkYDALCBAgQIDAwBQQMMTq0nEBQ4yj/63dItF/My0IECDQdAEBQ9NngPETIECAwEAVEDDEKiNgiPklAUMQUHMCBAg0UEDA0MCiGzIBAgQIFCEgYIiVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVScAQ8xMwBP00J0CAQBMFBAxNrLoxEyBAgEAJAgKGWJUEDDE/AUPQT3MCBAg0UUDA0MSqGzMBAgQIlCAgYIhVqeMChtmzX00nnn5hunby1ErmorNGp003WHsupWeeeyGNGH1muu/Xv6ueO/m4A9PuOw2t/nzldVPSxIuvTueeekxafbWVurQdf96kdMe0GWnCKUen5ZcbLGCIzT+tCRAg0EgBAUMjy27QBAgQIFCAgIAhVqSOCxhyOJAfOSzIIcK4sy9Nx48cXoUB7Y8cFAxZZcXW68aMOz8de/inqkAhH+OGm29PO2y1WSt0yG3r473w4qw09viDBQyxuac1AQIEGisgYGhs6Q2cAAECBAa4gIAhVqCOChjy6oXTJl6ehu++XWvlQXuQUFP19rqhm69XrXbIAcNTf3o2vThrdhqx765p0KAlqqb1zx946LFWaDHz6ZdjFVjIre+6d3ra58hRC/ldvR0BAgQItAsIGMwHAgQIECAwMAUEDLG6dFTA0NOKhfYVDe1Ud9w7I0265uZ00ucPSNPvf7D15xwm1G0eevTxVIcOdSix4zabVa+tV0UIGGITUGsCBAg0UUDA0MSqGzMBAgQIlCAgYIhVqbiAofseC/Xwd95uizTqkD3TZVff2GXVQQ4Sptw2PY06ZNhcUvm5zx51Ssptc9DQvlIhv3jVlVdotX3w4Zlp8pS70rBdtupy28Xzs/4cq8BCbD1nTkpTbp+Whh9hBcNCZPdWBAgQmEvg0nPGp6GbbZQWWQQOAQIECBAgMJAEll168YHUneL6UlzAMC/h/qxgyLdO5EcOHnJ4MOaUb6Sxow9q7cGQn9t6yw1bYcJPb72nChzWGLJyl4DhxZf/UkzRc8Bwy213p70FDMXUTEcJEOhMgcvOGZ8+vPnGAobOLK9RESBAgEDBAm9darGCe//md72jAoa+7sHQUxCRVzM88tiT1aaO7bdV5CBik/XXSnfed3/af68dq4q1bxzpFok3fxLrAQECBEoTcItEaRXTXwIECBBoioBbJGKV7qiAIVPkcCDvnZBXJuQgof52iOXfNrjHP9dfQ9m+GWR7wJBXNxx63BnpY9vmWzD+dkwBQ2zSaU2AAIGmCwgYmj4DjJ8AAQIEBqqAgCFWmY4LGLrv0XDRWaOrb4ZoDxtyqFAHBzOfeLoSPHDvnVv7NLQHDPXx8t4L9XEEDLFJpzUBAgSaLiBgaPoMMH4CBAgQGKgCAoZYZTouYIhx9L+1WyT6b6YFAQIEmi4gYGj6DDB+AgQIEBioAgKGWGUEDDG/JGAIAmpOgACBBgoIGBpYdEMmQIAAgSIEBAyxMgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKgkYYn4ChqCf5gQIEGiigIChiVU3ZgIECBAoQUDAEKuSgCHmJ2AI+mlOgACBJgoIGJpYdWMmQIAAgRIEBAyxKjU6YJg9+9V04ukXpi02XjftvtPQluSV101JEy++Op176jFp9dVW6iI8/rxJ6Y5pM9KEU45Oyy83WMAQm39aEyBAoJECAoZGlt2gCRAgQKAAAQFDrEiNDRjqcOG5519KO2y12VwBww033z7Xz5957oU07uxL0wsvzkpjjz9YwBCbe1oTIECgsQIChsaW3sAJECBAYIALCBhiBWpswHDHvTPSI489mVZdeYXq/7uvYHjqT8+mF2fNTiP23TUNGrREpZxXNuSfP/DQY+n4kcMFDLG5pzUBAgQaKyBgaGzpDZwAAQIEBriAgCFWoMYGDDVbDg3yo3vAkH/20KOPp6Gbr5c23WDtlFc8nDbx8rTjNpulSdfcLGCIzTutCRAg0GgBAUOjy2/wBAgQIDCABQQMseJ0bMBQ3wJx7eSpXYR23m6LdNLnD+iyKqG3gCGvbphy2/Q06pBh6cGHZ6bJU+5Kw3bZqrpNol7B8NxLf45VYCG2njMnpZ/fMS0NP2LUQnxXb0WAAAEC3QUuPWd8+udNN0qLLMKGAAECBAgQGEgCyy2z+EDqTnF96diAoa+VmNcKhq233LAVJvz01nuq2ynWGLJyl4Dhpdl/6etbvemvywHDz6benfYWMLzptdABAgSaLXDZOePTR7bYWMDQ7Glg9AQIECAwAAWWGbTYAOxVOV0SMMzjFol820T+1ohN1l8r3Xnf/Wn/vXasKtu+gmHm0y+XU+2U0l33Tk/7HGkFQ1FF01kCBDpOwC0SHVdSAyJAgACBDhFwi0SskAKG+QQM+daIQ487I31s2y2qWyXqb5KwyWNs4mlNgACBJgsIGJpcfWMnQIAAgYEsIGCIVUfAMJ+Aod7LIe+9kDd7FDDEJpzWBAgQIJCSgMEsIECAAAECA1NAwBCrS+MDhhhfSm6RiApqT4AAgeYJCBiaV3MjJkCAAIEyBAQMsToJGGJ+Aoagn+YECBBoooCAoYlVN2YCBAgQKEFAwBCrkoAh5idgCPppToAAgSYKCBiaWHVjJkCAAIESBAQMsSoJGGJ+Aoagn+YECBBoooCAoYlVN2YCBAgQKEFAwBCrkoAh5idgCPppToAAgSYKCBiaWHVjJkCAAIESBAQMsSoJGGJ+Aoagn+YECBBoooCAoYlVN2YCBAgQKEFAwBCrkoAh5idgCPppToAAgSYKCBiaWHVjJkCAAIESBAQMsSoJGGJ+Aoagn+YECBBoooCAoYlVN2YCBAgQKEFAwBCrkoAh5idgCPppToAAgSYKCBiaWHVjJkCAAIESBAQMsSoJGGJ+Aoagn+YECBBoooCAoYlVN2YCBAgQKEFAwBCrkoAh5ldkwHDJFVcHR605AQIECEQE9tnjk+kDG6wXOYS2BAgQIECAwAIQEDDEUAUMMb/iAobgcDUnQIAAAQIECBAgQIBAxwoIGGKlFTDE/AQMQT/NCRAgQIAAAQIECBAgMFAEBAyxSggYYn4ChqCf5gQIECBAgAABAgQIEBgoAgKGWCUEDDE/AUPQT3MCBAgQIECAAAECBAgMFAEBQ6wSAoaYn4Ah6Kc5AQIECBAgQIAAAQIEBoqAgCFWCQFDzE/AEPTTnAABAgQIECBAgAABAgNFQMAQq4SAIeYnYAj6SuB7lwAADvdJREFUaU6AAAECBAgQIECAAIGBIiBgiFVCwBDzEzAE/TRvnsB9v/ptOv/bV6UvjRmRllv2rT0C5Nccc+L46rlthm6aRh2+T1pyySWqv19/0y/SGed8u/rz2mu+u3Wcus0ZJ41K66+7Zpfj5jaXfO/aNPYLI9O7Vlmxeu6VV15N4ydekm6acsdcx2peVYyYAAECBAgQIEAgCwgYYvNAwBDzEzAE/TRvlkAOAU49+6K07trvSSMO2LPHgOG5519MEy78Xuv5HA7kx47bfCj94dHH07kXX5GOG7lf1TY/9+jMJ9JB++yW8rEvv+qGtPqQlau/148cJOQ2j//x6XTYZ/+lChjqcGGj9deujutBgAABAgQIECBAQMAQnwMChqDhzKdfDh5BcwLNEMgf6r896dr0iR0/kn5w/c/SsE98tMeAIQcFt0/7ZSskaA8VHn7k/+Z67urrfpoO3W+PdP8DD6ebb70zLb30oC7HzsfLP8+PT+60dRUw5GPW7eqVEc2oglESIECAAAECBAjMS8AKhtj8EDDE/KxgCPpp3jyB7isUugu0r1jIz7W/ftCSS1S3Nez80aFprTVWa/053xJRBxOrrPTO6pD1yoRvXHJVWv99a6apd06vAoZnn3uhdftF/d577bZ9l1UPzauKERMgQIAAAQIECGQBAUNsHggYYn4ChqCf5s0T6EvAsNI7V2jto1Df4lCvPmjfO6F9v4U6YNh+qw+2VifMfuXVNOkHP6lWNFx8+TWtFQzdQ4zmVcGICRAgQIAAAQIEehIQMMTmhYAh5idgCPpp3rkCeeXAd6/6cXrnCv/QZXPFvgQMWaVegdD++vzn0752cTr2yP2qWx3ye+RHvQdDvrXiM8N2rvZcqFcrzHziybT1lpt0+Vm9gWSt39PGkJ1bGSMjQIAAAQIECBDoTUDAEJsbAoaYn4Ah6Kd58wTmFzDMaw+G/7nzvgqsDh/qfR3yCoX2/RnqFQrPPPN82nLzDauQow4d7MHQvDlnxAQIECBAgACBvgoIGPoq1fPrBAwxPwFD0E/z5gn0FDC0fxtEfv7Usy+uNm7MYUD7c+1/znK9bQCZj/GFsRPSSiv+Y/UVl/nRHjD4FonmzTsjJkCAAAECBAj0RUDA0Bel3l8jYIj5CRiCfpo3T2B+AUMWyasY6tsYthm6aRUS1N/2UN96kV/XfvtF95UP+XV5w8e82qH7Pg65bfteDvnv3d+neZUxYgIECBAgQIAAAQFDbA4IGGJ+Aoagn+YECBAgQIAAAQIECBAYKAIChlglBAwxPwFD0E9zAgQIECBAgAABAgQIDBQBAUOsEgKGmJ+AIeinOQECBAgQIECAAAECBAaKgIAhVgkBQ8xPwBD005wAAQIECBAgQIAAAQIDRUDAEKuEgCHmpzUBAgQIECBAgAABAgQIECCQUhIwmAYECBAgQIAAAQIECBAgQIBAWEDAECZ0AAIECBAgQIAAAQIECBAgQEDAYA4QIECAAAECBAgQIECAAAECYQEBQ5jQAQgQ6EngjntnpM8edUqXp04+7sC0+05D33Cw8edNSkM3Xy9tusHab/ixHZAAAQIEmidw5XVT0gmnXtAa+IF775xGHTKseRBGTIAAgX4KCBj6CeblBAj0TSAHDFNum966IHvmuRfSmHHnp2MP/1RafbWV+naQPr6qPWDIF4UPPfq4C8E+2nkZAQIECHQVyOeUx//4dDrp8wekQYOWqJ7MP8uP1xMyPPjwzHTaxMvT2OMPTssvN7jP3G/0edP5sc/0XkiAQEBAwBDA05QAgd4FugcM9QXaglhpYAWDmUiAAAECb4RAb2FAfz7s52P89vePpR222vSN6NLrPkY+D+eH1X2vm1BDAgReh4CA4XWgaUKAwPwFelrBMO7sS9PxI4dXv8HJF2CHHndGmvnE09XB6tsnZs9+NZ14+oXp2slTq5+vv8570oRTjq7atN920f7z7isYcrt8K0b+bc2sl2eni793fet9LjprdOtiq7fjzX90XkGAAAECnSjQUzhej7M+16wxZOX0ze9en1566eV0+fdvqp6ub6Ho6dy24brvSZdeOblawTfziafS5Cl3pRdnzU4XXHZt1Tafl/KKv/rv9fkwhxr1eXOpJZfs9dyY+1W3rc+NDzz0WJfbFPN7PPLYk9X71bcq9tSuPj9ffcOt1SqO+lzsFpFOnO3GRGDBCAgYFoyroxJovED3PRhWeufb07mnHtPj7RHtF1H5oihfBHXfqyFftNUXaHnJavvfJ3zr+609GHKo0B4wTLrm5i4BRf57XvaaL/J6O169JLbxRQRAgACBhgnM6zaC+vyy9ZYbphGjz0yjDhtWBdZ1ML7FxutW5658/ms/j7Wfr/K5J4frY8ccXLWtA4n2v9e3U2T6OmDo7dzYvTzdz4GrrrxCK1Tv/lz77YT5ual3/6p1fmzvY39WbzRsuhguAQI9CAgYTAsCBBaIQPffAtUXYMN22arLRVW9gqH+rUvuTL5wy4965UL+c/cNt/LP6jb5N0n1rRfdL6DqsCH/f75Iyq8dse+u6bqbbuuygVf78fpzj+wCwXNQAgQIEHhTBPq6gqF9RV7uaHu7+QUM7eF2e8Cezz3tf28PGHo7N+aft69EyH+vV0Dk82FPAcNO22xe7QkxfPftWqF/+/s+8+wLXQL4fMzzLrkmbTf0A2/4HkpvSpG9KQECC1RAwLBAeR2cQHMFerpIq38ztP9eO3bZ8LH7BVYdBrQHDT+99Z4Ks6dvoZjXLRLzChh6O15zq2bkBAgQaLZAX/ZgWP5tg1srC+pAekEHDPX75PNl93Nj95UI9blNwNDsuWz0BN4sAQHDmyXvfQl0uEBvKxjyEtJ8P2r7jtr5teO/PqnLioWap/6tSf77mFO+kcaOPmiu36C8noAhL1Pt7XgdXhrDI0CAAIF5CMzvWyTqD/l5RV4OvXu6RaL9W5S63yLxelYwdF9ZV58b814JQ1ZZscd+tK/oy8Ptzy0S7X3Mba1g8J8MAQJ9FRAw9FXK6wgQ6JdA9z0YcuP2TaLab3n41K7bpGWWWSrllQ3dN6Zqb9P9mPVzrydgyPss9Ha8fg3UiwkQIECg4wS635ZX33aQB1qvust/7mkTxDqAuO/Xv6tuV+i+yePrCRh6Oze2v1fe62i/PXdMSy81qAoc2jec7O8mjwKGjpvSBkRgoQkIGBYatTciQIAAAQIECBAoXaCn2/pKH5P+EyBA4I0SEDC8UZKOQ4AAAQIECBAg0PECAoaOL7EBEiAQEBAwBPA0JUCAAAECBAgQaJaAgKFZ9TZaAgT6JyBg6J+XVxMgQIAAAQIECBAgQIAAAQI9CAgYTAsCBAgQIECAAAECBAgQIEAgLCBgCBM6AAECBAgQIECAAAECBAgQICBgMAcIECBAgAABAgQIECBAgACBsICAIUzoAAQIECBAgAABAgQIECBAgICAwRwgQIAAAQIECBAgQIAAAQIEwgIChjChAxAgQIAAAQIECBAgQIAAAQICBnOAAAECBAgQIECAAAECBAgQCAsIGMKEDkCAAAECBAgQIECAAAECBAgIGMwBAgQIECBAgAABAgQIECBAICwgYAgTOgABAgQIECBAgAABAgQIECAgYDAHCBAgQIAAAQIECBAgQIAAgbCAgCFM6AAECBAgQIAAAQIECBAgQICAgMEcIECAAAECBAgQIECAAAECBMICAoYwoQMQIECAAAECBAgQIECAAAECAgZzgAABAgQIECBAgAABAgQIEAgLCBjChA5AgAABAgQIECBAgAABAgQICBjMAQIECBAgQIAAAQIECBAgQCAsIGAIEzoAAQIECBAgQIAAAQIECBAgIGAwBwgQIECAAAECBAgQIECAAIGwgIAhTOgABAgQIECAAAECBAgQIECAgIDBHCBAgAABAgQIECBAgAABAgTCAgKGMKEDECBAgAABAgQIECBAgAABAgIGc4AAAQIECBAgQIAAAQIECBAICwgYwoQOQIAAAQIECBAgQIAAAQIECAgYzAECBAgQIECAAAECBAgQIEAgLCBgCBM6AAECBAgQIECAAAECBAgQICBgMAcIECBAgAABAgQIECBAgACBsICAIUzoAAQIECBAgAABAgQIECBAgICAwRwgQIAAAQIECBAgQIAAAQIEwgIChjChAxAgQIAAAQIECBAgQIAAAQICBnOAAAECBAgQIECAAAECBAgQCAsIGMKEDkCAAAECBAgQIECAAAECBAgIGMwBAgQIECBAgAABAgQIECBAICwgYAgTOgABAgQIECBAgAABAgQIECAgYDAHCBAgQIAAAQIECBAgQIAAgbCAgCFM6AAECBAgQIAAAQIECBAgQICAgMEcIECAAAECBAgQIECAAAECBMICAoYwoQMQIECAAAECBAgQIECAAAECAgZzgAABAgQIECBAgAABAgQIEAgLCBjChA5AgAABAgQIECBAgAABAgQICBjMAQIECBAgQIAAAQIECBAgQCAsIGAIEzoAAQIECBAgQIAAAQIECBAgIGAwBwgQIECAAAECBAgQIECAAIGwgIAhTOgABAgQIECAAAECBAgQIECAgIDBHCBAgAABAgQIECBAgAABAgTCAgKGMKEDECBAgAABAgQIECBAgAABAgIGc4AAAQIECBAgQIAAAQIECBAICwgYwoQOQIAAAQIECBAgQIAAAQIECAgYzAECBAgQIECAAAECBAgQIEAgLCBgCBM6AAECBAgQIECAAAECBAgQICBgMAcIECBAgAABAgQIECBAgACBsICAIUzoAAQIECBAgAABAgQIECBAgICAwRwgQIAAAQIECBAgQIAAAQIEwgIChjChAxAgQIAAAQIECBAgQIAAAQICBnOAAAECBAgQIECAAAECBAgQCAsIGMKEDkCAAAECBAgQIECAAAECBAgIGMwBAgQIECBAgAABAgQIECBAICwgYAgTOgABAgQIECBAgAABAgQIECAgYDAHCBAgQIAAAQIECBAgQIAAgbCAgCFM6AAECBAgQIAAAQIECBAgQICAgMEcIECAAAECBAgQIECAAAECBMICAoYwoQMQIECAAAECBAgQIECAAAECAgZzgAABAgQIECBAgAABAgQIEAgL/D8W51VtvGBxRgAAAABJRU5ErkJggg==", + "text/html": [ + "
\n", + " \n", + " \n", + "
\n", + " \n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ebitda_bar_chart(cases, color=recoydarkblue).update_layout(width=800, height=400)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BaselineOptimisation
Gasboiler OPEX (€)-20,000-
Gas consumption costs (€)-658,886-
CO2 emission costs (€)-233,364-
Gas taxes (€)-168,433-
Heatpump OPEX (€)--10,000
Result on electricity market (€)--532,534
SDE++ subsidy-20,000
EBITDA (€)-1,080,682-522,534
\n", + "
" + ], + "text/plain": [ + " Baseline Optimisation\n", + "Gasboiler OPEX (€) -20,000 -\n", + "Gas consumption costs (€) -658,886 -\n", + "CO2 emission costs (€) -233,364 -\n", + "Gas taxes (€) -168,433 -\n", + "Heatpump OPEX (€) - -10,000\n", + "Result on electricity market (€) - -532,534\n", + "SDE++ subsidy - 20,000\n", + "EBITDA (€) -1,080,682 -522,534" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "casereport = ComparisonReport(cases=cases, kind='ebitda_calc')\n", + "casereport.show(presentation_format=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Business case" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [], + "source": [ + "hpcase.calculate_business_case(project_duration=15, discount_rate=0.1, baseline=baseline)" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Year 0Year 1Year 2Year 3Year 4Year 5Year 6Year 7Year 8Year 9Year 10Year 11Year 12Year 13Year 14Year 15
CAPEX (€)-1,020,000
Regular Earnings (€)558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148
Irregular Earnings (€)---------------
EBITDA (€)558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148558,148
Depreciations (€) -/--40,000-40,000-40,000-40,000-40,000-40,000-40,000-40,000-40,000-40,000-40,000-40,000-40,000-40,000-40,000
EBIT (€)518,148518,148518,148518,148518,148518,148518,148518,148518,148518,148518,148518,148518,148518,148518,148
Income tax (Vpb.) (€)-106,209-106,209-106,209-106,209-106,209-106,209-106,209-106,209-106,209-106,209-106,209-106,209-106,209-106,209-106,209
NOPLAT (€)411,939411,939411,939411,939411,939411,939411,939411,939411,939411,939411,939411,939411,939411,939411,939
Depreciations (€) +/+40,00040,00040,00040,00040,00040,00040,00040,00040,00040,00040,00040,00040,00040,00040,000
Free Cash Flow (€)-1,020,000451,939451,939451,939451,939451,939451,939451,939451,939451,939451,939451,939451,939451,939451,939451,939
IRR (%)44%
WACC (%)10%
NPV of explicit period (€)2,197,712
Discounted residual value (€)95,757
NPV (€)2,293,469
\n", + "
" + ], + "text/plain": [ + " Year 0 Year 1 Year 2 Year 3 \\\n", + "CAPEX (€) -1,020,000 \n", + "Regular Earnings (€) 558,148 558,148 558,148 \n", + "Irregular Earnings (€) - - - \n", + "EBITDA (€) 558,148 558,148 558,148 \n", + "Depreciations (€) -/- -40,000 -40,000 -40,000 \n", + "EBIT (€) 518,148 518,148 518,148 \n", + "Income tax (Vpb.) (€) -106,209 -106,209 -106,209 \n", + "NOPLAT (€) 411,939 411,939 411,939 \n", + "Depreciations (€) +/+ 40,000 40,000 40,000 \n", + "Free Cash Flow (€) -1,020,000 451,939 451,939 451,939 \n", + "IRR (%) 44% \n", + "WACC (%) 10% \n", + "NPV of explicit period (€) 2,197,712 \n", + "Discounted residual value (€) 95,757 \n", + "NPV (€) 2,293,469 \n", + "\n", + " Year 4 Year 5 Year 6 Year 7 \\\n", + "CAPEX (€) \n", + "Regular Earnings (€) 558,148 558,148 558,148 558,148 \n", + "Irregular Earnings (€) - - - - \n", + "EBITDA (€) 558,148 558,148 558,148 558,148 \n", + "Depreciations (€) -/- -40,000 -40,000 -40,000 -40,000 \n", + "EBIT (€) 518,148 518,148 518,148 518,148 \n", + "Income tax (Vpb.) (€) -106,209 -106,209 -106,209 -106,209 \n", + "NOPLAT (€) 411,939 411,939 411,939 411,939 \n", + "Depreciations (€) +/+ 40,000 40,000 40,000 40,000 \n", + "Free Cash Flow (€) 451,939 451,939 451,939 451,939 \n", + "IRR (%) \n", + "WACC (%) \n", + "NPV of explicit period (€) \n", + "Discounted residual value (€) \n", + "NPV (€) \n", + "\n", + " Year 8 Year 9 Year 10 Year 11 \\\n", + "CAPEX (€) \n", + "Regular Earnings (€) 558,148 558,148 558,148 558,148 \n", + "Irregular Earnings (€) - - - - \n", + "EBITDA (€) 558,148 558,148 558,148 558,148 \n", + "Depreciations (€) -/- -40,000 -40,000 -40,000 -40,000 \n", + "EBIT (€) 518,148 518,148 518,148 518,148 \n", + "Income tax (Vpb.) (€) -106,209 -106,209 -106,209 -106,209 \n", + "NOPLAT (€) 411,939 411,939 411,939 411,939 \n", + "Depreciations (€) +/+ 40,000 40,000 40,000 40,000 \n", + "Free Cash Flow (€) 451,939 451,939 451,939 451,939 \n", + "IRR (%) \n", + "WACC (%) \n", + "NPV of explicit period (€) \n", + "Discounted residual value (€) \n", + "NPV (€) \n", + "\n", + " Year 12 Year 13 Year 14 Year 15 \n", + "CAPEX (€) \n", + "Regular Earnings (€) 558,148 558,148 558,148 558,148 \n", + "Irregular Earnings (€) - - - - \n", + "EBITDA (€) 558,148 558,148 558,148 558,148 \n", + "Depreciations (€) -/- -40,000 -40,000 -40,000 -40,000 \n", + "EBIT (€) 518,148 518,148 518,148 518,148 \n", + "Income tax (Vpb.) (€) -106,209 -106,209 -106,209 -106,209 \n", + "NOPLAT (€) 411,939 411,939 411,939 411,939 \n", + "Depreciations (€) +/+ 40,000 40,000 40,000 40,000 \n", + "Free Cash Flow (€) 451,939 451,939 451,939 451,939 \n", + "IRR (%) \n", + "WACC (%) \n", + "NPV of explicit period (€) \n", + "Discounted residual value (€) \n", + "NPV (€) " + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "BusinessCaseReport(hpcase).show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:py38]", + "language": "python", + "name": "conda-env-py38-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/pyrecoy/pyrecoy/tests/__init__.py b/pyrecoy/pyrecoy/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyrecoy/pyrecoy/tests/test_assets.py b/pyrecoy/pyrecoy/tests/test_assets.py new file mode 100644 index 0000000..4a7c3f6 --- /dev/null +++ b/pyrecoy/pyrecoy/tests/test_assets.py @@ -0,0 +1,324 @@ +import pytest +from pyrecoy import assets +from numpy.polynomial import Polynomial + + +class TestAsset: + @pytest.fixture() + def asset(self): + return assets.Asset( + name="TestAsset", max_power=2, min_power=-2, default_load=1, idle_load=0.2 + ) + + def test_init(self, asset): + assert asset.max_power == 2 + assert asset.min_power == -2 + assert asset.default_load == 1 + assert asset.idle_load == 0.2 + + @pytest.mark.skip(reason="Not implemented.") + def test_set_load(self): + assert NotImplementedError() + + @pytest.mark.skip(reason="Not implemented.") + def test_set_freq(self): + assert NotImplementedError() + + @pytest.mark.skip(reason="Not implemented.") + def test_MW_to_MWh(self): + assert NotImplementedError() + + @pytest.mark.skip(reason="Not implemented.") + def test_MWh_to_MW(self): + assert NotImplementedError() + + @pytest.mark.skip(reason="Not implemented.") + def test_set_financials(self): + assert NotImplementedError() + + +class TestEboiler: + @pytest.fixture() + def eboiler(self): + return None + + @pytest.mark.skip(reason="Not implemented.") + def test_init(self): + assert NotImplementedError() + + @pytest.mark.skip(reason="Not implemented.") + def test_set_load(self): + assert NotImplementedError() + + @pytest.mark.skip(reason="Not implemented.") + def test_set_heat_output(self): + assert NotImplementedError() + + +class TestHeatpump: + @pytest.fixture + def hp_fixed(self): + return assets.Heatpump( + name="Heatpump w/ Fixed COP", max_th_power=4, cop_curve=2 + ) + + @pytest.fixture + def hp_poly(self): + return assets.Heatpump( + name="Heatpump w/ Polynomial COP", max_th_power=4, cop_curve=[4, -2] + ) + + @pytest.fixture + def hp_func(self): + def func(Tsource, Tsink): + Tsink += 273 + Tsource += 273 + + c1 = 0.267 * Tsink / (Tsink - Tsource) + c2 = 0.333 * Tsink / (Tsink - Tsource) + + return Polynomial([c2, c1]) + + return assets.Heatpump( + name="Heatpump w/ Func COP", max_th_power=4, cop_curve=func + ) + + def test_init(self, hp_fixed, hp_poly, hp_func): + assert hp_fixed.max_th_power == 4 + assert hp_fixed.min_th_power == 0 + assert callable(hp_fixed.cop_curve) + + assert callable(hp_poly.cop_curve) + + assert callable(hp_func.cop_curve) + + def test_get_cop(self, hp_fixed, hp_poly, hp_func): + assert hp_fixed.get_cop(load=4) == 2 + assert hp_fixed.get_cop(load=4, Tsink=100, Tsource=10) == 2 + assert hp_fixed.get_cop(load=0) == 2 + assert hp_fixed.get_cop(load=2) == 2 + assert hp_fixed.get_cop(load=1.25) == 2 + + assert hp_poly.get_cop(load=0) == 4 + assert hp_poly.get_cop(load=2) == 3 + assert hp_poly.get_cop(load=4) == 2 + + with pytest.raises(ValueError): + hp_fixed.get_cop(load=5) + + with pytest.raises(ValueError): + hp_fixed.get_cop(load=-1) + + assert round(hp_func.get_cop(load=3, Tsink=110, Tsource=20), 2) == 2.27 + + def test_th_to_el_power(self, hp_fixed, hp_poly, hp_func): + assert hp_fixed._th_to_el_power(p_th=4) == -2 + assert hp_fixed._th_to_el_power(p_th=2) == -1 + assert hp_fixed._th_to_el_power(p_th=0) == 0 + + assert hp_poly._th_to_el_power(p_th=2) == -2 / 3 + + assert round( + hp_func._th_to_el_power(p_th=3, Tsink=110, Tsource=20), 2 + ) == round(-3 / 2.27, 2) + + def test_set_load(self, hp_fixed): + with pytest.raises(NotImplementedError): + hp_fixed.set_load(3) + + def test_set_heat_output(self, hp_fixed, hp_poly, hp_func): + with pytest.raises(ValueError): + hp_fixed.set_heat_output(th_output=-1) + + with pytest.raises(ValueError): + hp_fixed.set_heat_output(th_output=5) + + with pytest.raises(ValueError): + hp_fixed.set_heat_output(th_output=5, Tsink=20, Tsource=100) + + assert hp_fixed.set_heat_output(th_output=2) == 2 + assert hp_fixed.set_heat_output(th_output=2, Tsink=100, Tsource=10) == 2 + assert hp_fixed.set_heat_output(th_output=2, return_eload=True) == (2, -1) + assert hp_fixed.get_heat_output() == 2 + + assert hp_poly.set_heat_output(th_output=2, return_eload=True) == (2, -2 / 3) + assert hp_poly.get_heat_output() == 2 + + assert hp_func.set_heat_output(th_output=2, Tsink=110, Tsource=20) == 2 + + def test_cost_function(self, hp_fixed): + assert hp_fixed._cost_function(x=2, c1=40, c2=20, c3=4) == 40 + 40 + assert hp_fixed._cost_function(x=2, c1=40, c2=20, c3=5) == 40 + 60 + + def test_set_opt_load(self, hp_fixed, hp_poly, hp_func): + assert round(hp_fixed.set_opt_load(50, 20, 4), 2) == 0 + assert round(hp_fixed.set_opt_load(30, 20, 4), 2) == -2 + assert round(hp_fixed.set_opt_load(30, 20, 5), 2) == -2 + e_load, th_load = hp_fixed.set_opt_load(30, 20, 5, return_th_load=True) + assert round(e_load, 2) == -2 + assert round(th_load, 2) == 4 + + assert round(hp_poly.set_opt_load(20, 20, 4), 2) == -2 + assert round(hp_poly.set_opt_load(30, 20, 4), 2) == -1.27 + assert round(hp_poly.set_opt_load(40, 20, 4), 2) == -0.83 + assert round(hp_poly.set_opt_load(50, 20, 4), 2) == -0.53 + assert round(hp_poly.set_opt_load(60, 20, 4), 2) == -0.31 + assert round(hp_poly.set_opt_load(70, 20, 4), 2) == -0.14 + assert round(hp_poly.set_opt_load(80, 20, 4), 2) == 0 + assert round(hp_poly.set_opt_load(100, 20, 4), 2) == 0 + + assert round(hp_func.set_opt_load(0, 20, 4, Tsink=110, Tsource=20), 2) == -1.57 + assert round(hp_func.set_opt_load(30, 20, 4, Tsink=110, Tsource=20), 2) == -1.57 + assert round(hp_func.set_opt_load(100, 20, 4, Tsink=110, Tsource=20), 2) == 0 + + +class TestBattery: + @pytest.fixture + def battery(self): + return assets.Battery( + name="Battery", + rated_power=2, + rated_capacity=2, + roundtrip_eff=0.9, + min_soc=0.2, + max_soc=0.8, + ) + + def test_init(self, battery): + assert battery.max_power == 2 + assert battery.min_power == -2 + assert battery.min_soc == 0.2 + assert battery.min_chargelevel == 0.4 + assert battery.max_soc == 0.8 + assert battery.max_chargelevel == 1.6 + assert battery.soc == battery.min_soc + assert battery.rt_eff == 0.9 + + def test_set_soc(self, battery): + battery.set_soc(0.5) + assert battery.get_soc() == 0.5 + assert battery.get_chargelevel() == 1 + + with pytest.raises(ValueError): + battery.set_soc(0.9) + + with pytest.raises(ValueError): + battery.set_soc(1.1) + + with pytest.raises(ValueError): + battery.set_soc(0.1) + + with pytest.raises(ValueError): + battery.set_soc(-0.1) + + def test_set_chargelevel(self, battery): + battery.set_chargelevel(1) + assert battery.get_soc() == 0.5 + assert battery.get_chargelevel() == 1 + + battery.set_chargelevel(1.6) + assert battery.get_chargelevel() == 1.6 + assert battery.get_chargelevel() == battery.max_chargelevel + assert battery.get_soc() == battery.max_soc + + with pytest.raises(ValueError): + battery.set_chargelevel(2) + + with pytest.raises(ValueError): + battery.set_chargelevel(1.9) + + with pytest.raises(ValueError): + battery.set_chargelevel(0) + + with pytest.raises(ValueError): + battery.set_chargelevel(-1) + + def test_set_load(self, battery): + battery.set_freq("15T") + battery.set_soc(0.5) + assert round(battery.set_load(2), 4) == 2 + assert round(battery.get_load(), 4) == 2 + assert round(battery.get_soc(), 4) == 0.25 + assert round(battery.get_chargelevel(), 4) == 0.5 + + battery.set_soc(0.5) + assert round(battery.set_load(-2), 4) == -2 + assert round(battery.get_load(), 4) == -2 + assert round(battery.get_soc(), 4) == round(0.5 + 0.25 * 0.9, 4) + assert round(battery.get_chargelevel(), 4) == round(1 + 0.5 * 0.9, 4) + + battery.set_soc(0.25) + assert round(battery.set_load(2), 4) == 0.4 + assert round(battery.get_load(), 4) == 0.4 + assert round(battery.get_soc(), 4) == 0.2 + assert round(battery.get_chargelevel(), 4) == 0.4 + + battery.set_soc(0.75) + assert round(battery.set_load(-2), 4) == round(-0.4 / 0.9, 4) + assert round(battery.get_load(), 4) == round(-0.4 / 0.9, 4) + assert round(battery.get_soc(), 4) == 0.8 + assert round(battery.get_chargelevel(), 4) == 1.6 + + battery.set_soc(0.5) + + with pytest.warns(UserWarning): + assert round(battery.set_load(3), 4) == 2 + + battery.set_soc(0.5) + with pytest.warns(UserWarning): + assert round(battery.set_load(-3), 4) == -2 + + battery.set_soc(0.5) + assert round(battery.set_load("max"), 4) == 2 + + battery.set_soc(0.5) + assert battery.set_load("min") == -2 + + def test_charge(self, battery): + battery.set_freq("15T") + battery.set_soc(0.5) + assert battery.charge(2) == -2 + assert battery.get_load() == -2 + assert battery.get_soc() == round(0.5 + 0.25 * 0.9, 4) + + with pytest.raises(ValueError): + battery.charge(-1) + + def test_discharge(self, battery): + battery.set_freq("15T") + battery.set_soc(0.5) + assert battery.discharge(2) == 2 + assert battery.get_load() == 2 + assert battery.get_soc() == 0.25 + + with pytest.raises(ValueError): + battery.discharge(-1) + + +class TestGasBoiler: + @pytest.fixture() + def gasboiler(self): + return assets.GasBoiler(name="Gasboiler", max_th_output=10, efficiency=0.90) + + def test_init(self, gasboiler): + assert gasboiler.max_power == 10 + assert gasboiler.min_power == 0 + assert gasboiler.efficiency == 0.9 + + def test_get_load(self, gasboiler): + with pytest.raises(NotImplementedError): + gasboiler.get_load() + + def test_set_load(self, gasboiler): + with pytest.raises(NotImplementedError): + gasboiler.set_load(5) + + def test_set_heat_output(self, gasboiler): + assert gasboiler.set_heat_output(5) == 5 + assert gasboiler.get_heat_output() == 5 + assert gasboiler.set_heat_output("max") == 10 + assert gasboiler.set_heat_output("min") == 0 + + def test_get_gas_cons(self, gasboiler): + gasboiler.set_heat_output(5) + assert gasboiler.get_gas_cons() == -5 / gasboiler.efficiency diff --git a/pyrecoy/pyrecoy/tests/test_financial.py b/pyrecoy/pyrecoy/tests/test_financial.py new file mode 100644 index 0000000..a6f5f8a --- /dev/null +++ b/pyrecoy/pyrecoy/tests/test_financial.py @@ -0,0 +1,61 @@ +import pytest +from pyrecoy.financial import * + + +@pytest.mark.skip(reason="Not implemented.") +def test_calc_energy_market_results(): + assert NotImplementedError() + + +@pytest.mark.parametrize( + "inputs, exp_output", + [ + ({"cons": 1, "year": 2020, "base_cons": 0}, -125), + ({"cons": 10, "year": 2020, "base_cons": 0}, -1250), + ({"cons": 100, "year": 2020, "base_cons": 0}, -6484.7), + ({"cons": 1000, "year": 2020, "base_cons": 0}, -37_111.7), + ({"cons": 100_000, "year": 2020, "base_cons": 0}, -428_881.7), + ({"cons": 100, "year": 2020, "base_cons": 100_000}, -95), + ({"cons": 100_000, "year": 2020, "tax_bracket": 1}, -428_881.7), + ({"cons": 100_000, "year": 2020, "tax_bracket": 2}, -427_641.2), + ({"cons": 100_000, "year": 2020, "tax_bracket": 3}, -424_146), + ({"cons": 100_000, "year": 2020, "tax_bracket": 4}, -95_000), + ( + {"cons": 100_000, "electr": False, "year": 2020, "base_cons": 100}, + -547_302.20, + ), + ( + { + "cons": 100_000, + "electr": False, + "year": 2020, + "base_cons": 0, + "m3": True, + }, + -41_057, + ), + ( + { + "cons": 100_000, + "electr": False, + "year": 2020, + "base_cons": 0, + "horti": True, + "m3": True, + }, + -6_588, + ), + ], +) +def test_calculate_eb_ode(inputs, exp_output): + assert calculate_eb_ode(**inputs) == exp_output + + +@pytest.mark.skip(reason="Not implemented.") +def test_income_tax(): + assert NotImplementedError() + + +@pytest.mark.skip(reason="Not implemented.") +def test_calc_business_case(): + assert NotImplementedError()