openenergyid 0.1.31__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. openenergyid/__init__.py +8 -0
  2. openenergyid/abstractsim/__init__.py +5 -0
  3. openenergyid/abstractsim/abstract.py +102 -0
  4. openenergyid/baseload/__init__.py +15 -0
  5. openenergyid/baseload/analysis.py +190 -0
  6. openenergyid/baseload/exceptions.py +9 -0
  7. openenergyid/baseload/models.py +32 -0
  8. openenergyid/capacity/__init__.py +6 -0
  9. openenergyid/capacity/main.py +103 -0
  10. openenergyid/capacity/models.py +32 -0
  11. openenergyid/const.py +29 -0
  12. openenergyid/dyntar/__init__.py +20 -0
  13. openenergyid/dyntar/const.py +31 -0
  14. openenergyid/dyntar/main.py +313 -0
  15. openenergyid/dyntar/models.py +101 -0
  16. openenergyid/elia/__init__.py +4 -0
  17. openenergyid/elia/api.py +91 -0
  18. openenergyid/elia/const.py +18 -0
  19. openenergyid/energysharing/__init__.py +12 -0
  20. openenergyid/energysharing/const.py +8 -0
  21. openenergyid/energysharing/data_formatting.py +77 -0
  22. openenergyid/energysharing/main.py +122 -0
  23. openenergyid/energysharing/models.py +80 -0
  24. openenergyid/enums.py +16 -0
  25. openenergyid/models.py +174 -0
  26. openenergyid/mvlr/__init__.py +19 -0
  27. openenergyid/mvlr/helpers.py +30 -0
  28. openenergyid/mvlr/main.py +34 -0
  29. openenergyid/mvlr/models.py +227 -0
  30. openenergyid/mvlr/mvlr.py +450 -0
  31. openenergyid/pvsim/__init__.py +8 -0
  32. openenergyid/pvsim/abstract.py +60 -0
  33. openenergyid/pvsim/elia/__init__.py +3 -0
  34. openenergyid/pvsim/elia/main.py +89 -0
  35. openenergyid/pvsim/main.py +49 -0
  36. openenergyid/pvsim/pvlib/__init__.py +11 -0
  37. openenergyid/pvsim/pvlib/main.py +115 -0
  38. openenergyid/pvsim/pvlib/models.py +235 -0
  39. openenergyid/pvsim/pvlib/quickscan.py +99 -0
  40. openenergyid/pvsim/pvlib/weather.py +91 -0
  41. openenergyid/sim/__init__.py +5 -0
  42. openenergyid/sim/main.py +67 -0
  43. openenergyid/simeval/__init__.py +6 -0
  44. openenergyid/simeval/main.py +148 -0
  45. openenergyid/simeval/models.py +162 -0
  46. openenergyid-0.1.31.dist-info/METADATA +32 -0
  47. openenergyid-0.1.31.dist-info/RECORD +50 -0
  48. openenergyid-0.1.31.dist-info/WHEEL +5 -0
  49. openenergyid-0.1.31.dist-info/licenses/LICENSE +21 -0
  50. openenergyid-0.1.31.dist-info/top_level.txt +1 -0
@@ -0,0 +1,313 @@
1
+ """Main module of the DynTar package."""
2
+
3
+ from typing import cast
4
+
5
+ import pandas as pd
6
+
7
+ from openenergyid.const import (
8
+ ELECTRICITY_DELIVERED,
9
+ ELECTRICITY_EXPORTED,
10
+ PRICE_ELECTRICITY_DELIVERED,
11
+ PRICE_ELECTRICITY_EXPORTED,
12
+ RLP,
13
+ SPP,
14
+ )
15
+
16
+ from .const import (
17
+ COST_ELECTRICITY_DELIVERED_SMR2,
18
+ COST_ELECTRICITY_DELIVERED_SMR3,
19
+ COST_ELECTRICITY_EXPORTED_SMR2,
20
+ COST_ELECTRICITY_EXPORTED_SMR3,
21
+ ELECTRICITY_DELIVERED_SMR2,
22
+ ELECTRICITY_DELIVERED_SMR3,
23
+ ELECTRICITY_EXPORTED_SMR2,
24
+ ELECTRICITY_EXPORTED_SMR3,
25
+ HEATMAP_DELIVERED,
26
+ HEATMAP_DELIVERED_DESCRIPTION,
27
+ HEATMAP_EXPORTED,
28
+ HEATMAP_EXPORTED_DESCRIPTION,
29
+ HEATMAP_TOTAL,
30
+ HEATMAP_TOTAL_DESCRIPTION,
31
+ RLP_WEIGHTED_PRICE_DELIVERED,
32
+ SPP_WEIGHTED_PRICE_EXPORTED,
33
+ Register,
34
+ )
35
+
36
+
37
+ def weigh_by_monthly_profile(df: pd.DataFrame, series_name, profile_name) -> pd.Series:
38
+ """Weigh a time series by a monthly profile."""
39
+ grouped = df.groupby(pd.Grouper(freq="MS"))
40
+ return grouped[series_name].transform("sum") * grouped[profile_name].transform(
41
+ lambda x: x / x.sum()
42
+ )
43
+
44
+
45
+ def extend_dataframe_with_smr2(
46
+ df: pd.DataFrame,
47
+ inplace: bool = False,
48
+ registers: list[Register] | None = None,
49
+ ) -> pd.DataFrame | None:
50
+ """Extend a DataFrame with the SMR2 columns."""
51
+ if not inplace:
52
+ result_df = df.copy()
53
+ else:
54
+ result_df = df
55
+
56
+ if registers is None:
57
+ registers = [Register.DELIVERY, Register.EXPORT]
58
+
59
+ if Register.DELIVERY in registers:
60
+ result_df[ELECTRICITY_DELIVERED_SMR2] = weigh_by_monthly_profile(
61
+ df, ELECTRICITY_DELIVERED, RLP
62
+ )
63
+ if Register.EXPORT in registers:
64
+ result_df[ELECTRICITY_EXPORTED_SMR2] = weigh_by_monthly_profile(
65
+ df, ELECTRICITY_EXPORTED, SPP
66
+ )
67
+
68
+ result_df.rename(
69
+ columns={
70
+ ELECTRICITY_DELIVERED: ELECTRICITY_DELIVERED_SMR3,
71
+ ELECTRICITY_EXPORTED: ELECTRICITY_EXPORTED_SMR3,
72
+ },
73
+ inplace=True,
74
+ errors="ignore",
75
+ )
76
+
77
+ if not inplace:
78
+ return result_df
79
+ return None
80
+
81
+
82
+ def extend_dataframe_with_costs(
83
+ df: pd.DataFrame, inplace: bool = False, registers: list[Register] | None = None
84
+ ) -> pd.DataFrame | None:
85
+ """Extend a DataFrame with the cost columns."""
86
+ if not inplace:
87
+ result_df = df.copy()
88
+ else:
89
+ result_df = df
90
+
91
+ if registers is None:
92
+ registers = [Register.DELIVERY, Register.EXPORT]
93
+
94
+ if Register.DELIVERY in registers:
95
+ result_df[COST_ELECTRICITY_DELIVERED_SMR2] = (
96
+ df[ELECTRICITY_DELIVERED_SMR2] * df[PRICE_ELECTRICITY_DELIVERED]
97
+ )
98
+ result_df[COST_ELECTRICITY_DELIVERED_SMR3] = (
99
+ df[ELECTRICITY_DELIVERED_SMR3] * df[PRICE_ELECTRICITY_DELIVERED]
100
+ )
101
+
102
+ if Register.EXPORT in registers:
103
+ result_df[COST_ELECTRICITY_EXPORTED_SMR2] = (
104
+ df[ELECTRICITY_EXPORTED_SMR2] * df[PRICE_ELECTRICITY_EXPORTED] * -1
105
+ )
106
+ result_df[COST_ELECTRICITY_EXPORTED_SMR3] = (
107
+ df[ELECTRICITY_EXPORTED_SMR3] * df[PRICE_ELECTRICITY_EXPORTED] * -1
108
+ )
109
+
110
+ if not inplace:
111
+ return result_df
112
+ return None
113
+
114
+
115
+ def extend_dataframe_with_weighted_prices(
116
+ df: pd.DataFrame, inplace: bool = False, registers: list[Register] | None = None
117
+ ) -> pd.DataFrame | None:
118
+ """Extend a DataFrame with the weighted price columns."""
119
+ if not inplace:
120
+ df = df.copy()
121
+
122
+ if registers is None:
123
+ registers = [Register.DELIVERY, Register.EXPORT]
124
+
125
+ if Register.DELIVERY in registers:
126
+ rlp_weighted_price_delivered = (df[PRICE_ELECTRICITY_DELIVERED] * df[RLP]).resample(
127
+ "MS"
128
+ ).sum() / df[RLP].resample("MS").sum()
129
+ df[RLP_WEIGHTED_PRICE_DELIVERED] = rlp_weighted_price_delivered.reindex_like(
130
+ df[RLP], method="ffill"
131
+ )
132
+
133
+ if Register.EXPORT in registers:
134
+ spp_weighted_price_exported = (df[PRICE_ELECTRICITY_EXPORTED] * df[SPP]).resample(
135
+ "MS"
136
+ ).sum() / df[SPP].resample("MS").sum()
137
+ df[SPP_WEIGHTED_PRICE_EXPORTED] = spp_weighted_price_exported.reindex_like(
138
+ df[SPP], method="ffill"
139
+ )
140
+
141
+ if not inplace:
142
+ return df
143
+ return None
144
+
145
+
146
+ def extend_dataframe_with_heatmap(
147
+ df: pd.DataFrame, inplace: bool = False, registers: list[Register] | None = None
148
+ ) -> pd.DataFrame | None:
149
+ """Extend a DataFrame with the heatmap columns."""
150
+ if not inplace:
151
+ df = df.copy()
152
+
153
+ if registers is None:
154
+ registers = [Register.DELIVERY, Register.EXPORT]
155
+
156
+ if Register.DELIVERY in registers:
157
+ energy_delta_delivered = df[ELECTRICITY_DELIVERED_SMR2] - df[ELECTRICITY_DELIVERED_SMR3]
158
+ price_delta_delivered = df[RLP_WEIGHTED_PRICE_DELIVERED] - df[PRICE_ELECTRICITY_DELIVERED]
159
+ heatmap_score_delivered = energy_delta_delivered * price_delta_delivered
160
+ heatmap_score_delivered.fillna(0, inplace=True)
161
+ # Invert score so that positive values indicate a positive impact
162
+ heatmap_score_delivered = -heatmap_score_delivered
163
+ df[HEATMAP_DELIVERED] = heatmap_score_delivered
164
+
165
+ if Register.EXPORT in registers:
166
+ energy_delta_exported = df[ELECTRICITY_EXPORTED_SMR2] - df[ELECTRICITY_EXPORTED_SMR3]
167
+ price_delta_exported = df[SPP_WEIGHTED_PRICE_EXPORTED] - df[PRICE_ELECTRICITY_EXPORTED]
168
+ heatmap_score_exported = energy_delta_exported * price_delta_exported
169
+ heatmap_score_exported.fillna(0, inplace=True)
170
+ df[HEATMAP_EXPORTED] = heatmap_score_exported
171
+
172
+ if Register.DELIVERY in registers and Register.EXPORT in registers:
173
+ heatmap_score_delivered = cast(pd.Series, df[HEATMAP_DELIVERED])
174
+ heatmap_score_exported = cast(pd.Series, df[HEATMAP_EXPORTED])
175
+ heatmap_score_combined = heatmap_score_delivered + heatmap_score_exported
176
+ elif Register.DELIVERY in registers:
177
+ heatmap_score_combined = heatmap_score_delivered
178
+ else:
179
+ heatmap_score_combined = heatmap_score_exported
180
+ df[HEATMAP_TOTAL] = heatmap_score_combined
181
+
182
+ if not inplace:
183
+ return df
184
+ return None
185
+
186
+
187
+ def map_delivery_description(
188
+ price_delivered, price_rlp, electricity_delivered_smr3, electricity_delivered_smr2
189
+ ):
190
+ """Map the delivery description."""
191
+ if price_delivered > price_rlp and electricity_delivered_smr3 > electricity_delivered_smr2:
192
+ return 1
193
+ if price_delivered > price_rlp and electricity_delivered_smr3 < electricity_delivered_smr2:
194
+ return 2
195
+ if price_delivered < price_rlp and electricity_delivered_smr3 > electricity_delivered_smr2:
196
+ return 3
197
+ if price_delivered < price_rlp and electricity_delivered_smr3 < electricity_delivered_smr2:
198
+ return 4
199
+ return 0
200
+
201
+
202
+ def map_export_description(
203
+ price_exported, price_spp, electricity_exported_smr3, electricity_exported_smr2
204
+ ):
205
+ """Map the export description."""
206
+ if price_exported > price_spp and electricity_exported_smr3 > electricity_exported_smr2:
207
+ return 5
208
+ if price_exported > price_spp and electricity_exported_smr3 < electricity_exported_smr2:
209
+ return 6
210
+ if price_exported < price_spp and electricity_exported_smr3 > electricity_exported_smr2:
211
+ return 7
212
+ if price_exported < price_spp and electricity_exported_smr3 < electricity_exported_smr2:
213
+ return 8
214
+ return 0
215
+
216
+
217
+ def map_total_description(
218
+ abs_heatmap_delivered, abs_heatmap_exported, delivered_description, exported_description
219
+ ):
220
+ """Map the total description."""
221
+ if abs_heatmap_delivered > abs_heatmap_exported:
222
+ return delivered_description
223
+ return exported_description
224
+
225
+
226
+ def extend_dataframe_with_heatmap_description(
227
+ df: pd.DataFrame, inplace: bool = False, registers: list[Register] | None = None
228
+ ) -> pd.DataFrame | None:
229
+ """Extend a DataFrame with the heatmap description columns."""
230
+ if not inplace:
231
+ df = df.copy()
232
+
233
+ if registers is None:
234
+ registers = [Register.DELIVERY, Register.EXPORT]
235
+
236
+ if Register.DELIVERY in registers:
237
+ df[HEATMAP_DELIVERED_DESCRIPTION] = list(
238
+ map(
239
+ map_delivery_description,
240
+ df[PRICE_ELECTRICITY_DELIVERED],
241
+ df[RLP_WEIGHTED_PRICE_DELIVERED],
242
+ df[ELECTRICITY_DELIVERED_SMR3],
243
+ df[ELECTRICITY_DELIVERED_SMR2],
244
+ )
245
+ )
246
+
247
+ if Register.EXPORT in registers:
248
+ df[HEATMAP_EXPORTED_DESCRIPTION] = list(
249
+ map(
250
+ map_export_description,
251
+ df[PRICE_ELECTRICITY_EXPORTED],
252
+ df[SPP_WEIGHTED_PRICE_EXPORTED],
253
+ df[ELECTRICITY_EXPORTED_SMR3],
254
+ df[ELECTRICITY_EXPORTED_SMR2],
255
+ )
256
+ )
257
+
258
+ if Register.DELIVERY in registers and Register.EXPORT in registers:
259
+ df[HEATMAP_TOTAL_DESCRIPTION] = list(
260
+ map(
261
+ map_total_description,
262
+ df[HEATMAP_DELIVERED].abs(),
263
+ df[HEATMAP_EXPORTED].abs(),
264
+ df[HEATMAP_DELIVERED_DESCRIPTION],
265
+ df[HEATMAP_EXPORTED_DESCRIPTION],
266
+ )
267
+ )
268
+ elif Register.DELIVERY in registers:
269
+ df[HEATMAP_TOTAL_DESCRIPTION] = df[HEATMAP_DELIVERED_DESCRIPTION]
270
+ else:
271
+ df[HEATMAP_TOTAL_DESCRIPTION] = df[HEATMAP_EXPORTED_DESCRIPTION]
272
+
273
+ if not inplace:
274
+ return df
275
+
276
+
277
+ def calculate_dyntar_columns(
278
+ df: pd.DataFrame,
279
+ inplace: bool = False,
280
+ registers: list[Register] | None = None,
281
+ ) -> pd.DataFrame | None:
282
+ """Calculate all columns required for the dynamic tariff analysis."""
283
+ if not inplace:
284
+ df = df.copy()
285
+
286
+ if registers is None:
287
+ registers = [Register.DELIVERY, Register.EXPORT]
288
+
289
+ extend_dataframe_with_smr2(df, inplace=True, registers=registers)
290
+ extend_dataframe_with_costs(df, inplace=True, registers=registers)
291
+ extend_dataframe_with_weighted_prices(df, inplace=True, registers=registers)
292
+ extend_dataframe_with_heatmap(df, inplace=True, registers=registers)
293
+ extend_dataframe_with_heatmap_description(df, inplace=True, registers=registers)
294
+
295
+ if not inplace:
296
+ return df
297
+ return None
298
+
299
+
300
+ def summarize_result(df: pd.DataFrame) -> pd.Series:
301
+ """Summarize the dynamic tariff analysis result."""
302
+ summary = df.filter(like="cost").sum()
303
+
304
+ abs_smr2 = summary.filter(like="smr2").abs().sum()
305
+
306
+ summary["cost_electricity_total_smr2"] = summary.filter(like="smr2").sum()
307
+ summary["cost_electricity_total_smr3"] = summary.filter(like="smr3").sum()
308
+
309
+ summary["ratio"] = (
310
+ summary["cost_electricity_total_smr3"] - summary["cost_electricity_total_smr2"]
311
+ ) / abs_smr2
312
+
313
+ return summary
@@ -0,0 +1,101 @@
1
+ """Models for dynamic tariff analysis."""
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, Field, confloat, conlist
6
+
7
+ from openenergyid.models import TimeDataFrame
8
+
9
+ from .const import Register
10
+
11
+ RequiredColumns = Literal[
12
+ "electricity_delivered",
13
+ "electricity_exported",
14
+ "price_electricity_delivered",
15
+ "price_electricity_exported",
16
+ "RLP",
17
+ "SPP",
18
+ ]
19
+
20
+ OutputColumns = Literal[
21
+ "electricity_delivered_smr3",
22
+ "electricity_exported_smr3",
23
+ "price_electricity_delivered",
24
+ "price_electricity_exported",
25
+ "RLP",
26
+ "SPP",
27
+ "electricity_delivered_smr2",
28
+ "electricity_exported_smr2",
29
+ "cost_electricity_delivered_smr2",
30
+ "cost_electricity_exported_smr2",
31
+ "cost_electricity_delivered_smr3",
32
+ "cost_electricity_exported_smr3",
33
+ "rlp_weighted_price_delivered",
34
+ "spp_weighted_price_exported",
35
+ "heatmap_delivered",
36
+ "heatmap_exported",
37
+ "heatmap_total",
38
+ "heatmap_delivered_description",
39
+ "heatmap_exported_description",
40
+ "heatmap_total_description",
41
+ ]
42
+
43
+
44
+ class DynamicTariffAnalysisInput(TimeDataFrame):
45
+ """Input frame for dynamic tariff analysis."""
46
+
47
+ columns: list[RequiredColumns] = Field(
48
+ min_length=3,
49
+ max_length=len(RequiredColumns.__args__),
50
+ examples=[RequiredColumns.__args__],
51
+ )
52
+ data: list[
53
+ conlist(
54
+ item_type=confloat(allow_inf_nan=True),
55
+ min_length=3,
56
+ max_length=len(RequiredColumns.__args__),
57
+ ) # type: ignore
58
+ ] = Field(examples=[[0.0] * len(RequiredColumns.__args__)])
59
+
60
+ @property
61
+ def registers(self) -> list[Register]:
62
+ """Check which registers are present in the input data."""
63
+ registers = []
64
+ columns = list(self.columns)
65
+ # if "electricity_delivered", "price_electricity_delivered" and "RLP" are present
66
+ if all(
67
+ column in columns
68
+ for column in [
69
+ "electricity_delivered",
70
+ "price_electricity_delivered",
71
+ "RLP",
72
+ ]
73
+ ):
74
+ registers.append(Register.DELIVERY)
75
+ # if "electricity_exported", "price_electricity_exported" and "SPP" are present
76
+ if all(
77
+ column in columns
78
+ for column in ["electricity_exported", "price_electricity_exported", "SPP"]
79
+ ):
80
+ registers.append(Register.EXPORT)
81
+ return registers
82
+
83
+
84
+ class DynamicTariffAnalysisOutputSummary(BaseModel):
85
+ """Summary of the dynamic tariff analysis output."""
86
+
87
+ cost_electricity_delivered_smr2: float | None = None
88
+ cost_electricity_delivered_smr3: float | None = None
89
+ cost_electricity_exported_smr2: float | None = None
90
+ cost_electricity_exported_smr3: float | None = None
91
+ cost_electricity_total_smr2: float | None = None
92
+ cost_electricity_total_smr3: float | None = None
93
+ ratio: float | None = None
94
+
95
+
96
+ class DynamicTariffAnalysisOutput(TimeDataFrame):
97
+ """Output frame for dynamic tariff analysis."""
98
+
99
+ columns: list[str]
100
+ data: list[list[float | None]]
101
+ summary: DynamicTariffAnalysisOutputSummary | None = None
@@ -0,0 +1,4 @@
1
+ from .api import get_dataset, parse_response
2
+ from .const import Region
3
+
4
+ __all__ = ["get_dataset", "parse_response", "Region"]
@@ -0,0 +1,91 @@
1
+ """
2
+ This module contains the functions to interact with the Elia API.
3
+ """
4
+
5
+ import datetime as dt
6
+
7
+ import aiohttp
8
+ import pandas as pd
9
+
10
+ from .const import Region
11
+
12
+ DATE_FORMAT = "%Y-%m-%d"
13
+
14
+
15
+ async def get_dataset(
16
+ dataset: str,
17
+ start: dt.date,
18
+ end: dt.date,
19
+ region: Region,
20
+ select: set[str],
21
+ session: aiohttp.ClientSession,
22
+ timezone: str = "Europe/Brussels",
23
+ ) -> dict:
24
+ """
25
+ Fetches a dataset from the Elia open data API within a specified date range and region.
26
+
27
+ Args:
28
+ dataset (str): The name of the dataset to fetch.
29
+ start (dt.date): The start date for the data range.
30
+ end (dt.date): The end date for the data range.
31
+ region (Region): The region for which to fetch the data.
32
+ select (set[str]): A set of fields to select in the dataset.
33
+ session (aiohttp.ClientSession): The aiohttp session to use for making the request.
34
+ timezone (str, optional): The timezone to use for the data. Defaults to "Europe/Brussels".
35
+
36
+ Returns:
37
+ dict: The dataset fetched from the API.
38
+
39
+ Raises:
40
+ aiohttp.ClientError: If there is an error making the request.
41
+ """
42
+ url = f"https://opendata.elia.be/api/explore/v2.1/catalog/datasets/{dataset}/exports/json"
43
+
44
+ if "datetime" not in select:
45
+ select.add("datetime")
46
+ select_str = ",".join(select)
47
+
48
+ params = {
49
+ "where": (
50
+ f"datetime IN [date'{start.strftime(DATE_FORMAT)}'..date'{end.strftime(DATE_FORMAT)}'] "
51
+ f"AND region='{region.value}'"
52
+ ),
53
+ "timezone": timezone,
54
+ "select": select_str,
55
+ }
56
+
57
+ async with session.get(url, params=params) as response:
58
+ data = await response.json()
59
+
60
+ return data
61
+
62
+
63
+ def parse_response(
64
+ data: dict, index: str, columns: list[str], timezone: str = "Europe/Brussels"
65
+ ) -> pd.DataFrame:
66
+ """
67
+ Parses a response dictionary into a pandas DataFrame.
68
+
69
+ Args:
70
+ data (dict): The input data where each key is a column name
71
+ and each value is a list of column values.
72
+ index (str): The key in the data dictionary to be used as the index for the DataFrame.
73
+ columns (list[str]): The list of column names for the DataFrame.
74
+ timezone (str, optional): The timezone to convert the DataFrame index to.
75
+ Defaults to "Europe/Brussels".
76
+
77
+ Returns:
78
+ pd.DataFrame: A pandas DataFrame with the specified columns and index,
79
+ converted to the specified timezone.
80
+ """
81
+ df = pd.DataFrame(
82
+ data,
83
+ index=pd.to_datetime([x[index] for x in data], utc=True),
84
+ columns=columns,
85
+ )
86
+ df.index = pd.DatetimeIndex(df.index)
87
+ df = df.tz_convert(timezone)
88
+ df.sort_index(inplace=True)
89
+ df.dropna(inplace=True)
90
+
91
+ return df
@@ -0,0 +1,18 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Region(Enum):
5
+ Belgium = "Belgium"
6
+ Brussels = "Brussels"
7
+ Flanders = "Flanders"
8
+ Wallonia = "Wallonia"
9
+ Antwerp = "Antwerp"
10
+ East_Flanders = "East-Flanders"
11
+ Flemish_Brabant = "Flemish-Brabant"
12
+ Limburg = "Limburg"
13
+ West_Flanders = "West-Flanders"
14
+ Hainaut = "Hainaut"
15
+ Liège = "Liège"
16
+ Luxembourg = "Luxembourg"
17
+ Namur = "Namur"
18
+ Walloon_Brabant = "Walloon-Brabant"
@@ -0,0 +1,12 @@
1
+ """Energy Sharing package."""
2
+
3
+ from .main import calculate
4
+ from .models import CalculationMethod, EnergySharingInput, EnergySharingOutput, KeyInput
5
+
6
+ __all__ = [
7
+ "calculate",
8
+ "CalculationMethod",
9
+ "EnergySharingInput",
10
+ "EnergySharingOutput",
11
+ "KeyInput",
12
+ ]
@@ -0,0 +1,8 @@
1
+ """Constants for the energysharing module."""
2
+
3
+ GROSS_INJECTION = "Gross Injection"
4
+ NET_INJECTION = "Net Injection"
5
+ GROSS_OFFTAKE = "Gross Offtake"
6
+ NET_OFFTAKE = "Net Offtake"
7
+ KEY = "Key"
8
+ SHARED_ENERGY = "Shared Energy"
@@ -0,0 +1,77 @@
1
+ """Functions to create multi-indexed DataFrames for input and output data for energy sharing."""
2
+
3
+ import pandas as pd
4
+
5
+ from .const import (
6
+ GROSS_INJECTION,
7
+ GROSS_OFFTAKE,
8
+ KEY,
9
+ NET_INJECTION,
10
+ NET_OFFTAKE,
11
+ SHARED_ENERGY,
12
+ )
13
+
14
+
15
+ def create_multi_index_input_frame(
16
+ gross_injection: pd.DataFrame,
17
+ gross_offtake: pd.DataFrame,
18
+ key: pd.DataFrame,
19
+ ) -> pd.DataFrame:
20
+ """Create a multi-indexed DataFrame with the input data for energy sharing."""
21
+ gross_injection = gross_injection.copy()
22
+ gross_offtake = gross_offtake.copy()
23
+ key = key.copy()
24
+
25
+ gross_injection.columns = pd.MultiIndex.from_product(
26
+ [[GROSS_INJECTION], gross_injection.columns]
27
+ )
28
+ gross_offtake.columns = pd.MultiIndex.from_product([[GROSS_OFFTAKE], gross_offtake.columns])
29
+ key.columns = pd.MultiIndex.from_product([[KEY], key.columns])
30
+
31
+ df = pd.concat([gross_injection, gross_offtake, key], axis=1)
32
+
33
+ return df
34
+
35
+
36
+ def create_multi_index_output_frame(
37
+ net_injection: pd.DataFrame,
38
+ net_offtake: pd.DataFrame,
39
+ shared_energy: pd.DataFrame,
40
+ ) -> pd.DataFrame:
41
+ """Create a multi-indexed DataFrame with the output data for energy sharing."""
42
+ net_injection = net_injection.copy()
43
+ net_offtake = net_offtake.copy()
44
+ shared_energy = shared_energy.copy()
45
+
46
+ net_injection.columns = pd.MultiIndex.from_product([[NET_INJECTION], net_injection.columns])
47
+ net_offtake.columns = pd.MultiIndex.from_product([[NET_OFFTAKE], net_offtake.columns])
48
+ shared_energy.columns = pd.MultiIndex.from_product([[SHARED_ENERGY], shared_energy.columns])
49
+
50
+ df = pd.concat([net_injection, net_offtake, shared_energy], axis=1)
51
+
52
+ return df
53
+
54
+
55
+ def result_to_input_for_reiteration(result: pd.DataFrame, key: pd.DataFrame) -> pd.DataFrame:
56
+ """Create a multi-indexed DataFrame with the input data for energy sharing after the first iteration."""
57
+ # We iterate again. The net injection of the previous result is taken as gross injection input
58
+ # And the net offtake is taken as the gross offtake input
59
+ # When a user's net offtake is 0, the key is set to 0; and the keys are re-normalized
60
+
61
+ gross_injection = result[NET_INJECTION].copy()
62
+ gross_offtake = result[NET_OFFTAKE].copy()
63
+
64
+ # Take the original key, but replace the value with 0.0 if result[NET_OFFTAKE] is 0.0
65
+
66
+ key = key.copy()
67
+ key = key.where(~result[NET_OFFTAKE].eq(0), 0)
68
+
69
+ # Re-normalize the keys
70
+
71
+ key = key.div(key.sum(axis=1), axis=0)
72
+
73
+ df = create_multi_index_input_frame(
74
+ gross_injection=gross_injection, gross_offtake=gross_offtake, key=key
75
+ )
76
+
77
+ return df