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",
+ " | datetime | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2019-01-01 00:00:00+01:00 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:01:00+01:00 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:02:00+01:00 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:03:00+01:00 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:04:00+01:00 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " DAM | \n",
+ " POS | \n",
+ " NEG | \n",
+ " RS | \n",
+ " ForePos | \n",
+ " ForeNeg | \n",
+ "
\n",
+ " \n",
+ " | datetime | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2019-01-01 00:00:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 46.83 | \n",
+ " 52.70 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:01:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 32.12 | \n",
+ " 54.45 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:02:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 30.97 | \n",
+ " 48.64 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:03:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 51.13 | \n",
+ " 48.35 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:04:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 48.04 | \n",
+ " 52.01 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " Gas prices (€/MWh) | \n",
+ " CO2 prices (€/ton) | \n",
+ "
\n",
+ " \n",
+ " | datetime | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2019-01-01 00:00:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:01:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:02:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:03:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:04:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " Gas prices (€/MWh) | \n",
+ " CO2 prices (€/ton) | \n",
+ " Heat demand | \n",
+ "
\n",
+ " \n",
+ " | datetime | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2019-01-01 00:00:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:01:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:02:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:03:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:04:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " DAM | \n",
+ " POS | \n",
+ " NEG | \n",
+ " RS | \n",
+ " ForePos | \n",
+ " ForeNeg | \n",
+ " Heat demand | \n",
+ "
\n",
+ " \n",
+ " | datetime | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2019-01-01 00:00:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 46.83 | \n",
+ " 52.70 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:01:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 32.12 | \n",
+ " 54.45 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:02:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 30.97 | \n",
+ " 48.64 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:03:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 51.13 | \n",
+ " 48.35 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:04:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 48.04 | \n",
+ " 52.01 | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " Gas prices (€/MWh) | \n",
+ " CO2 prices (€/ton) | \n",
+ " Heat demand | \n",
+ " output_MW | \n",
+ " input_MW | \n",
+ " output_MWh | \n",
+ " input_MWh | \n",
+ "
\n",
+ " \n",
+ " | datetime | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2019-01-01 00:00:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -5.555556 | \n",
+ " 0.083333 | \n",
+ " -0.092593 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:01:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -5.555556 | \n",
+ " 0.083333 | \n",
+ " -0.092593 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:02:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -5.555556 | \n",
+ " 0.083333 | \n",
+ " -0.092593 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:03:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -5.555556 | \n",
+ " 0.083333 | \n",
+ " -0.092593 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:04:00+01:00 | \n",
+ " 22.38 | \n",
+ " 24.98 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -5.555556 | \n",
+ " 0.083333 | \n",
+ " -0.092593 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " DAM | \n",
+ " POS | \n",
+ " NEG | \n",
+ " RS | \n",
+ " ForePos | \n",
+ " ForeNeg | \n",
+ " Heat demand | \n",
+ " output_MW | \n",
+ " input_MW | \n",
+ " output_MWh | \n",
+ " input_MWh | \n",
+ "
\n",
+ " \n",
+ " | datetime | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2019-01-01 00:00:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 46.83 | \n",
+ " 52.70 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:01:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 32.12 | \n",
+ " 54.45 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:02:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 30.97 | \n",
+ " 48.64 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:03:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 51.13 | \n",
+ " 48.35 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:04:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 48.04 | \n",
+ " 52.01 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " DAM | \n",
+ " POS | \n",
+ " NEG | \n",
+ " RS | \n",
+ " ForePos | \n",
+ " ForeNeg | \n",
+ " Heat demand | \n",
+ " output_MW | \n",
+ " input_MW | \n",
+ " output_MWh | \n",
+ " input_MWh | \n",
+ " Nom. vol. | \n",
+ " Prod. vol. | \n",
+ " Cons. vol. | \n",
+ " Imb. vol. | \n",
+ " Day-Ahead Result | \n",
+ " POS Result | \n",
+ " NEG Result | \n",
+ " Imbalance Result | \n",
+ " Combined Result | \n",
+ "
\n",
+ " \n",
+ " | datetime | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2019-01-01 00:00:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 46.83 | \n",
+ " 52.70 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " -0.02381 | \n",
+ " -0.02381 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:01:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 32.12 | \n",
+ " 54.45 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " -0.02381 | \n",
+ " -0.02381 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:02:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 30.97 | \n",
+ " 48.64 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " -0.02381 | \n",
+ " -0.02381 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:03:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 51.13 | \n",
+ " 48.35 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " -0.02381 | \n",
+ " -0.02381 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ "
\n",
+ " \n",
+ " | 2019-01-01 00:04:00+01:00 | \n",
+ " 68.92 | \n",
+ " 34.85 | \n",
+ " 58.01 | \n",
+ " 2.0 | \n",
+ " 48.04 | \n",
+ " 52.01 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " -1.428571 | \n",
+ " 0.083333 | \n",
+ " -0.02381 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " -0.02381 | \n",
+ " -0.02381 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ " -1.38119 | \n",
+ "
\n",
+ " \n",
+ "
\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/Tq9ZdFF04e32CAdtu8n0tprvKv1X0AOX2Y88HB633uH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AAECBAgQaJaAgKFZ9TZaAgT6JyBg6J+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",
+ " Baseline | \n",
+ " Optimisation | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Gasboiler OPEX (€) | \n",
+ " -20,000 | \n",
+ " - | \n",
+ "
\n",
+ " \n",
+ " | Gas consumption costs (€) | \n",
+ " -658,886 | \n",
+ " - | \n",
+ "
\n",
+ " \n",
+ " | CO2 emission costs (€) | \n",
+ " -233,364 | \n",
+ " - | \n",
+ "
\n",
+ " \n",
+ " | Gas taxes (€) | \n",
+ " -168,433 | \n",
+ " - | \n",
+ "
\n",
+ " \n",
+ " | Heatpump OPEX (€) | \n",
+ " - | \n",
+ " -10,000 | \n",
+ "
\n",
+ " \n",
+ " | Result on electricity market (€) | \n",
+ " - | \n",
+ " -532,534 | \n",
+ "
\n",
+ " \n",
+ " | SDE++ subsidy | \n",
+ " - | \n",
+ " 20,000 | \n",
+ "
\n",
+ " \n",
+ " | EBITDA (€) | \n",
+ " -1,080,682 | \n",
+ " -522,534 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " Year 0 | \n",
+ " Year 1 | \n",
+ " Year 2 | \n",
+ " Year 3 | \n",
+ " Year 4 | \n",
+ " Year 5 | \n",
+ " Year 6 | \n",
+ " Year 7 | \n",
+ " Year 8 | \n",
+ " Year 9 | \n",
+ " Year 10 | \n",
+ " Year 11 | \n",
+ " Year 12 | \n",
+ " Year 13 | \n",
+ " Year 14 | \n",
+ " Year 15 | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | CAPEX (€) | \n",
+ " -1,020,000 | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " | Regular Earnings (€) | \n",
+ " | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ "
\n",
+ " \n",
+ " | Irregular Earnings (€) | \n",
+ " | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ " - | \n",
+ "
\n",
+ " \n",
+ " | EBITDA (€) | \n",
+ " | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ " 558,148 | \n",
+ "
\n",
+ " \n",
+ " | Depreciations (€) -/- | \n",
+ " | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ " -40,000 | \n",
+ "
\n",
+ " \n",
+ " | EBIT (€) | \n",
+ " | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ " 518,148 | \n",
+ "
\n",
+ " \n",
+ " | Income tax (Vpb.) (€) | \n",
+ " | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ " -106,209 | \n",
+ "
\n",
+ " \n",
+ " | NOPLAT (€) | \n",
+ " | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ " 411,939 | \n",
+ "
\n",
+ " \n",
+ " | Depreciations (€) +/+ | \n",
+ " | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ " 40,000 | \n",
+ "
\n",
+ " \n",
+ " | Free Cash Flow (€) | \n",
+ " -1,020,000 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ " 451,939 | \n",
+ "
\n",
+ " \n",
+ " | IRR (%) | \n",
+ " 44% | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " | WACC (%) | \n",
+ " 10% | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " | NPV of explicit period (€) | \n",
+ " 2,197,712 | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " | Discounted residual value (€) | \n",
+ " 95,757 | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " | NPV (€) | \n",
+ " 2,293,469 | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ "
\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()