openenergyid 0.1.18__py2.py3-none-any.whl → 0.1.20__py2.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.

Potentially problematic release.


This version of openenergyid might be problematic. Click here for more details.

openenergyid/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Open Energy ID Python SDK."""
2
2
 
3
- __version__ = "0.1.18"
3
+ __version__ = "0.1.20"
4
4
 
5
5
  from .enums import Granularity
6
6
  from .models import TimeDataFrame, TimeSeries
@@ -1,9 +1,10 @@
1
1
  """Dynamic Tariff Analysis module."""
2
2
 
3
- from .main import calculate_dyntar_columns
3
+ from .main import calculate_dyntar_columns, summarize_result
4
4
  from .models import (
5
5
  DynamicTariffAnalysisInput,
6
6
  DynamicTariffAnalysisOutput,
7
+ DynamicTariffAnalysisOutputSummary,
7
8
  OutputColumns,
8
9
  RequiredColumns,
9
10
  )
@@ -12,6 +13,8 @@ __all__ = [
12
13
  "calculate_dyntar_columns",
13
14
  "DynamicTariffAnalysisInput",
14
15
  "DynamicTariffAnalysisOutput",
16
+ "DynamicTariffAnalysisOutputSummary",
15
17
  "OutputColumns",
16
18
  "RequiredColumns",
19
+ "summarize_result",
17
20
  ]
@@ -1,5 +1,7 @@
1
1
  """Constants for the dyntar analysis."""
2
2
 
3
+ from enum import Enum
4
+
3
5
  ELECTRICITY_DELIVERED_SMR3 = "electricity_delivered_smr3"
4
6
  ELECTRICITY_EXPORTED_SMR3 = "electricity_exported_smr3"
5
7
  ELECTRICITY_DELIVERED_SMR2 = "electricity_delivered_smr2"
@@ -20,3 +22,10 @@ HEATMAP_TOTAL = "heatmap_total"
20
22
  HEATMAP_DELIVERED_DESCRIPTION = "heatmap_delivered_description"
21
23
  HEATMAP_EXPORTED_DESCRIPTION = "heatmap_exported_description"
22
24
  HEATMAP_TOTAL_DESCRIPTION = "heatmap_total_description"
25
+
26
+
27
+ class Register(Enum):
28
+ """Register for dynamic tariff analysis."""
29
+
30
+ DELIVERY = "delivery"
31
+ EXPORT = "export"
@@ -1,6 +1,6 @@
1
1
  """Main module of the DynTar package."""
2
2
 
3
- import numpy as np
3
+ from typing import cast
4
4
  import pandas as pd
5
5
 
6
6
  from openenergyid.const import (
@@ -29,33 +29,40 @@ from .const import (
29
29
  HEATMAP_DELIVERED_DESCRIPTION,
30
30
  HEATMAP_EXPORTED_DESCRIPTION,
31
31
  HEATMAP_TOTAL_DESCRIPTION,
32
+ Register,
32
33
  )
33
34
 
34
35
 
35
- def weigh_by_monthly_profile(series: pd.Series, profile: pd.Series) -> pd.Series:
36
+ def weigh_by_monthly_profile(df: pd.DataFrame, series_name, profile_name) -> pd.Series:
36
37
  """Weigh a time series by a monthly profile."""
37
- df = pd.DataFrame({"series": series, "profile": profile})
38
- results = []
39
- for _, frame in df.groupby(pd.Grouper(freq="MS")):
40
- frame = frame.copy()
41
- frame["weighted"] = frame["series"].sum() * (frame["profile"] / frame["profile"].sum())
42
- results.append(frame)
43
- return pd.concat(results)["weighted"]
38
+ grouped = df.groupby(pd.Grouper(freq="MS"))
39
+ return grouped[series_name].transform("sum") * grouped[profile_name].transform(
40
+ lambda x: x / x.sum()
41
+ )
44
42
 
45
43
 
46
- def extend_dataframe_with_smr2(df: pd.DataFrame, inplace: bool = False) -> pd.DataFrame | None:
44
+ def extend_dataframe_with_smr2(
45
+ df: pd.DataFrame,
46
+ inplace: bool = False,
47
+ registers: list[Register] | None = None,
48
+ ) -> pd.DataFrame | None:
47
49
  """Extend a DataFrame with the SMR2 columns."""
48
50
  if not inplace:
49
51
  result_df = df.copy()
50
52
  else:
51
53
  result_df = df
52
54
 
53
- result_df[ELECTRICITY_DELIVERED_SMR2] = weigh_by_monthly_profile(
54
- df[ELECTRICITY_DELIVERED], df[RLP]
55
- )
56
- result_df[ELECTRICITY_EXPORTED_SMR2] = weigh_by_monthly_profile(
57
- df[ELECTRICITY_EXPORTED], df[SPP]
58
- )
55
+ if registers is None:
56
+ registers = [Register.DELIVERY, Register.EXPORT]
57
+
58
+ if Register.DELIVERY in registers:
59
+ result_df[ELECTRICITY_DELIVERED_SMR2] = weigh_by_monthly_profile(
60
+ df, ELECTRICITY_DELIVERED, RLP
61
+ )
62
+ if Register.EXPORT in registers:
63
+ result_df[ELECTRICITY_EXPORTED_SMR2] = weigh_by_monthly_profile(
64
+ df, ELECTRICITY_EXPORTED, SPP
65
+ )
59
66
 
60
67
  result_df.rename(
61
68
  columns={
@@ -63,6 +70,7 @@ def extend_dataframe_with_smr2(df: pd.DataFrame, inplace: bool = False) -> pd.Da
63
70
  ELECTRICITY_EXPORTED: ELECTRICITY_EXPORTED_SMR3,
64
71
  },
65
72
  inplace=True,
73
+ errors="ignore",
66
74
  )
67
75
 
68
76
  if not inplace:
@@ -70,26 +78,33 @@ def extend_dataframe_with_smr2(df: pd.DataFrame, inplace: bool = False) -> pd.Da
70
78
  return None
71
79
 
72
80
 
73
- def extend_dataframe_with_costs(df: pd.DataFrame, inplace: bool = False) -> pd.DataFrame | None:
81
+ def extend_dataframe_with_costs(
82
+ df: pd.DataFrame, inplace: bool = False, registers: list[Register] | None = None
83
+ ) -> pd.DataFrame | None:
74
84
  """Extend a DataFrame with the cost columns."""
75
85
  if not inplace:
76
86
  result_df = df.copy()
77
87
  else:
78
88
  result_df = df
79
89
 
80
- result_df[COST_ELECTRICITY_DELIVERED_SMR2] = (
81
- df[ELECTRICITY_DELIVERED_SMR2] * df[PRICE_ELECTRICITY_DELIVERED]
82
- )
83
- result_df[COST_ELECTRICITY_EXPORTED_SMR2] = (
84
- df[ELECTRICITY_EXPORTED_SMR2] * df[PRICE_ELECTRICITY_EXPORTED]
85
- )
86
-
87
- result_df[COST_ELECTRICITY_DELIVERED_SMR3] = (
88
- df[ELECTRICITY_DELIVERED_SMR3] * df[PRICE_ELECTRICITY_DELIVERED]
89
- )
90
- result_df[COST_ELECTRICITY_EXPORTED_SMR3] = (
91
- df[ELECTRICITY_EXPORTED_SMR3] * df[PRICE_ELECTRICITY_EXPORTED]
92
- )
90
+ if registers is None:
91
+ registers = [Register.DELIVERY, Register.EXPORT]
92
+
93
+ if Register.DELIVERY in registers:
94
+ result_df[COST_ELECTRICITY_DELIVERED_SMR2] = (
95
+ df[ELECTRICITY_DELIVERED_SMR2] * df[PRICE_ELECTRICITY_DELIVERED]
96
+ )
97
+ result_df[COST_ELECTRICITY_DELIVERED_SMR3] = (
98
+ df[ELECTRICITY_DELIVERED_SMR3] * df[PRICE_ELECTRICITY_DELIVERED]
99
+ )
100
+
101
+ if Register.EXPORT in registers:
102
+ result_df[COST_ELECTRICITY_EXPORTED_SMR2] = (
103
+ df[ELECTRICITY_EXPORTED_SMR2] * df[PRICE_ELECTRICITY_EXPORTED] * -1
104
+ )
105
+ result_df[COST_ELECTRICITY_EXPORTED_SMR3] = (
106
+ df[ELECTRICITY_EXPORTED_SMR3] * df[PRICE_ELECTRICITY_EXPORTED] * -1
107
+ )
93
108
 
94
109
  if not inplace:
95
110
  return result_df
@@ -97,63 +112,70 @@ def extend_dataframe_with_costs(df: pd.DataFrame, inplace: bool = False) -> pd.D
97
112
 
98
113
 
99
114
  def extend_dataframe_with_weighted_prices(
100
- df: pd.DataFrame, inplace: bool = False
115
+ df: pd.DataFrame, inplace: bool = False, registers: list[Register] | None = None
101
116
  ) -> pd.DataFrame | None:
102
117
  """Extend a DataFrame with the weighted price columns."""
103
118
  if not inplace:
104
119
  df = df.copy()
105
120
 
106
- rlp_weighted_price_delivered = (df[PRICE_ELECTRICITY_DELIVERED] * df[RLP]).resample(
107
- "MS"
108
- ).sum() / df[RLP].resample("MS").sum()
109
- df[RLP_WEIGHTED_PRICE_DELIVERED] = rlp_weighted_price_delivered.reindex_like(
110
- df[RLP], method="ffill"
111
- )
112
- spp_weighted_price_exported = (df[PRICE_ELECTRICITY_EXPORTED] * df[SPP]).resample(
113
- "MS"
114
- ).sum() / df[SPP].resample("MS").sum()
115
- df[SPP_WEIGHTED_PRICE_EXPORTED] = spp_weighted_price_exported.reindex_like(
116
- df[SPP], method="ffill"
117
- )
121
+ if registers is None:
122
+ registers = [Register.DELIVERY, Register.EXPORT]
123
+
124
+ if Register.DELIVERY in registers:
125
+ rlp_weighted_price_delivered = (df[PRICE_ELECTRICITY_DELIVERED] * df[RLP]).resample(
126
+ "MS"
127
+ ).sum() / df[RLP].resample("MS").sum()
128
+ df[RLP_WEIGHTED_PRICE_DELIVERED] = rlp_weighted_price_delivered.reindex_like(
129
+ df[RLP], method="ffill"
130
+ )
131
+
132
+ if Register.EXPORT in registers:
133
+ spp_weighted_price_exported = (df[PRICE_ELECTRICITY_EXPORTED] * df[SPP]).resample(
134
+ "MS"
135
+ ).sum() / df[SPP].resample("MS").sum()
136
+ df[SPP_WEIGHTED_PRICE_EXPORTED] = spp_weighted_price_exported.reindex_like(
137
+ df[SPP], method="ffill"
138
+ )
118
139
 
119
140
  if not inplace:
120
141
  return df
121
142
  return None
122
143
 
123
144
 
124
- def extend_dataframe_with_heatmap(df: pd.DataFrame, inplace: bool = False) -> pd.DataFrame | None:
145
+ def extend_dataframe_with_heatmap(
146
+ df: pd.DataFrame, inplace: bool = False, registers: list[Register] | None = None
147
+ ) -> pd.DataFrame | None:
125
148
  """Extend a DataFrame with the heatmap columns."""
126
149
  if not inplace:
127
150
  df = df.copy()
128
151
 
129
- normalized_energy_delta_delivered = (
130
- df[ELECTRICITY_DELIVERED_SMR2] - df[ELECTRICITY_DELIVERED_SMR3]
131
- ) / df[ELECTRICITY_DELIVERED_SMR2]
132
- normalized_price_delta_delivered = (
133
- df[RLP_WEIGHTED_PRICE_DELIVERED] - df[PRICE_ELECTRICITY_DELIVERED]
134
- ) / df[RLP_WEIGHTED_PRICE_DELIVERED]
135
- heatmap_score_delivered = normalized_energy_delta_delivered * normalized_price_delta_delivered
136
-
137
- normalized_energy_delta_exported = (
138
- df[ELECTRICITY_EXPORTED_SMR2] - df[ELECTRICITY_EXPORTED_SMR3]
139
- ) / df[ELECTRICITY_EXPORTED_SMR2]
140
- normalized_energy_delta_exported = normalized_energy_delta_exported.replace(
141
- [np.inf, -np.inf], np.nan
142
- )
143
- normalized_price_delta_exported = (
144
- df[SPP_WEIGHTED_PRICE_EXPORTED] - df[PRICE_ELECTRICITY_EXPORTED]
145
- ) / df[SPP_WEIGHTED_PRICE_EXPORTED]
146
- heatmap_score_exported = normalized_energy_delta_exported * normalized_price_delta_exported
147
-
148
- heatmap_score_delivered.fillna(0, inplace=True)
149
- heatmap_score_exported.fillna(0, inplace=True)
150
-
151
- # Invert scores so that positive values indicate a positive impact
152
- heatmap_score_delivered = -heatmap_score_delivered
153
- heatmap_score_combined = heatmap_score_delivered + heatmap_score_exported
154
-
155
- df[HEATMAP_DELIVERED] = heatmap_score_delivered
156
- df[HEATMAP_EXPORTED] = heatmap_score_exported
152
+ if registers is None:
153
+ registers = [Register.DELIVERY, Register.EXPORT]
154
+
155
+ if Register.DELIVERY in registers:
156
+ energy_delta_delivered = df[ELECTRICITY_DELIVERED_SMR2] - df[ELECTRICITY_DELIVERED_SMR3]
157
+ price_delta_delivered = df[RLP_WEIGHTED_PRICE_DELIVERED] - df[PRICE_ELECTRICITY_DELIVERED]
158
+ heatmap_score_delivered = energy_delta_delivered * price_delta_delivered
159
+ heatmap_score_delivered.fillna(0, inplace=True)
160
+ # Invert score so that positive values indicate a positive impact
161
+ heatmap_score_delivered = -heatmap_score_delivered
162
+ df[HEATMAP_DELIVERED] = heatmap_score_delivered
163
+
164
+ if Register.EXPORT in registers:
165
+ energy_delta_exported = df[ELECTRICITY_EXPORTED_SMR2] - df[ELECTRICITY_EXPORTED_SMR3]
166
+ price_delta_exported = df[SPP_WEIGHTED_PRICE_EXPORTED] - df[PRICE_ELECTRICITY_EXPORTED]
167
+ heatmap_score_exported = energy_delta_exported * price_delta_exported
168
+ heatmap_score_exported.fillna(0, inplace=True)
169
+ df[HEATMAP_EXPORTED] = heatmap_score_exported
170
+
171
+ if Register.DELIVERY in registers and Register.EXPORT in registers:
172
+ heatmap_score_delivered = cast(pd.Series, df[HEATMAP_DELIVERED])
173
+ heatmap_score_exported = cast(pd.Series, df[HEATMAP_EXPORTED])
174
+ heatmap_score_combined = heatmap_score_delivered + heatmap_score_exported
175
+ elif Register.DELIVERY in registers:
176
+ heatmap_score_combined = heatmap_score_delivered
177
+ else:
178
+ heatmap_score_combined = heatmap_score_exported
157
179
  df[HEATMAP_TOTAL] = heatmap_score_combined
158
180
 
159
181
  if not inplace:
@@ -161,127 +183,130 @@ def extend_dataframe_with_heatmap(df: pd.DataFrame, inplace: bool = False) -> pd
161
183
  return None
162
184
 
163
185
 
186
+ def map_delivery_description(
187
+ price_delivered, price_rlp, electricity_delivered_smr3, electricity_delivered_smr2
188
+ ):
189
+ """Map the delivery description."""
190
+ if price_delivered > price_rlp and electricity_delivered_smr3 > electricity_delivered_smr2:
191
+ return 1
192
+ if price_delivered > price_rlp and electricity_delivered_smr3 < electricity_delivered_smr2:
193
+ return 2
194
+ if price_delivered < price_rlp and electricity_delivered_smr3 > electricity_delivered_smr2:
195
+ return 3
196
+ if price_delivered < price_rlp and electricity_delivered_smr3 < electricity_delivered_smr2:
197
+ return 4
198
+ return 0
199
+
200
+
201
+ def map_export_description(
202
+ price_exported, price_spp, electricity_exported_smr3, electricity_exported_smr2
203
+ ):
204
+ """Map the export description."""
205
+ if price_exported > price_spp and electricity_exported_smr3 > electricity_exported_smr2:
206
+ return 5
207
+ if price_exported > price_spp and electricity_exported_smr3 < electricity_exported_smr2:
208
+ return 6
209
+ if price_exported < price_spp and electricity_exported_smr3 > electricity_exported_smr2:
210
+ return 7
211
+ if price_exported < price_spp and electricity_exported_smr3 < electricity_exported_smr2:
212
+ return 8
213
+ return 0
214
+
215
+
216
+ def map_total_description(
217
+ abs_heatmap_delivered, abs_heatmap_exported, delivered_description, exported_description
218
+ ):
219
+ """Map the total description."""
220
+ if abs_heatmap_delivered > abs_heatmap_exported:
221
+ return delivered_description
222
+ return exported_description
223
+
224
+
164
225
  def extend_dataframe_with_heatmap_description(
165
- df: pd.DataFrame, inplace: bool = False
226
+ df: pd.DataFrame, inplace: bool = False, registers: list[Register] | None = None
166
227
  ) -> pd.DataFrame | None:
167
228
  """Extend a DataFrame with the heatmap description columns."""
168
229
  if not inplace:
169
230
  df = df.copy()
170
231
 
171
- # Delivered
172
-
173
- # Where Heatmap is 0, we put a desription of 0 (No impact)
174
- df[HEATMAP_DELIVERED_DESCRIPTION] = df[HEATMAP_DELIVERED].apply(
175
- lambda x: 0 if x == 0 else float("NaN")
176
- )
177
- # When the energy delta is positive, and the price delta is positive, we put a description of 1 (high consumption, high price)
178
- df[HEATMAP_DELIVERED_DESCRIPTION] = df.apply(
179
- lambda x: 1
180
- if x[PRICE_ELECTRICITY_DELIVERED] > x[RLP_WEIGHTED_PRICE_DELIVERED]
181
- and x[ELECTRICITY_DELIVERED_SMR3] > x[ELECTRICITY_DELIVERED_SMR2]
182
- else x[HEATMAP_DELIVERED_DESCRIPTION],
183
- axis=1,
184
- )
185
- # When the energy delta is negative, and the price delta is positive, we put a description of 2 (low consumption, high price)
186
- df[HEATMAP_DELIVERED_DESCRIPTION] = df.apply(
187
- lambda x: 2
188
- if x[PRICE_ELECTRICITY_DELIVERED] > x[RLP_WEIGHTED_PRICE_DELIVERED]
189
- and x[ELECTRICITY_DELIVERED_SMR3] < x[ELECTRICITY_DELIVERED_SMR2]
190
- else x[HEATMAP_DELIVERED_DESCRIPTION],
191
- axis=1,
192
- )
193
- # When the energy delta is positive, and the price delta is negative, we put a description of 3 (high consumption, low price)
194
- df[HEATMAP_DELIVERED_DESCRIPTION] = df.apply(
195
- lambda x: 3
196
- if x[PRICE_ELECTRICITY_DELIVERED] < x[RLP_WEIGHTED_PRICE_DELIVERED]
197
- and x[ELECTRICITY_DELIVERED_SMR3] > x[ELECTRICITY_DELIVERED_SMR2]
198
- else x[HEATMAP_DELIVERED_DESCRIPTION],
199
- axis=1,
200
- )
201
- # When the energy delta is negative, and the price delta is negative, we put a description of 4 (low consumption, low price)
202
- df[HEATMAP_DELIVERED_DESCRIPTION] = df.apply(
203
- lambda x: 4
204
- if x[PRICE_ELECTRICITY_DELIVERED] < x[RLP_WEIGHTED_PRICE_DELIVERED]
205
- and x[ELECTRICITY_DELIVERED_SMR3] < x[ELECTRICITY_DELIVERED_SMR2]
206
- else x[HEATMAP_DELIVERED_DESCRIPTION],
207
- axis=1,
208
- )
209
- # All other cases are put as 0
210
- df[HEATMAP_DELIVERED_DESCRIPTION] = df[HEATMAP_DELIVERED_DESCRIPTION].replace(np.nan, 0)
211
-
212
- # Exported
213
-
214
- # Where Heatmap is 0, we put a desription of 0 (No impact)
215
- df[HEATMAP_EXPORTED_DESCRIPTION] = df[HEATMAP_EXPORTED].apply(
216
- lambda x: 0 if x == 0 else float("NaN")
217
- )
218
- # When the energy delta is positive, and the price delta is positive, we put a description of 5 (high injection, high price)
219
- df[HEATMAP_EXPORTED_DESCRIPTION] = df.apply(
220
- lambda x: 5
221
- if x[PRICE_ELECTRICITY_EXPORTED] > x[SPP_WEIGHTED_PRICE_EXPORTED]
222
- and x[ELECTRICITY_EXPORTED_SMR3] > x[ELECTRICITY_EXPORTED_SMR2]
223
- else x[HEATMAP_EXPORTED_DESCRIPTION],
224
- axis=1,
225
- )
226
- # When the energy delta is negative, and the price delta is positive, we put a description of 6 (low injection, high price)
227
- df[HEATMAP_EXPORTED_DESCRIPTION] = df.apply(
228
- lambda x: 6
229
- if x[PRICE_ELECTRICITY_EXPORTED] > x[SPP_WEIGHTED_PRICE_EXPORTED]
230
- and x[ELECTRICITY_EXPORTED_SMR3] < x[ELECTRICITY_EXPORTED_SMR2]
231
- else x[HEATMAP_EXPORTED_DESCRIPTION],
232
- axis=1,
233
- )
234
- # When the energy delta is positive, and the price delta is negative, we put a description of 7 (high injection, low price)
235
- df[HEATMAP_EXPORTED_DESCRIPTION] = df.apply(
236
- lambda x: 7
237
- if x[PRICE_ELECTRICITY_EXPORTED] < x[SPP_WEIGHTED_PRICE_EXPORTED]
238
- and x[ELECTRICITY_EXPORTED_SMR3] > x[ELECTRICITY_EXPORTED_SMR2]
239
- else x[HEATMAP_EXPORTED_DESCRIPTION],
240
- axis=1,
241
- )
242
- # When the energy delta is negative, and the price delta is negative, we put a description of 8 (low injection, low price)
243
- df[HEATMAP_EXPORTED_DESCRIPTION] = df.apply(
244
- lambda x: 8
245
- if x[PRICE_ELECTRICITY_EXPORTED] < x[SPP_WEIGHTED_PRICE_EXPORTED]
246
- and x[ELECTRICITY_EXPORTED_SMR3] < x[ELECTRICITY_EXPORTED_SMR2]
247
- else x[HEATMAP_EXPORTED_DESCRIPTION],
248
- axis=1,
249
- )
250
- # All other cases are put as 0
251
- df[HEATMAP_EXPORTED_DESCRIPTION] = df[HEATMAP_EXPORTED_DESCRIPTION].replace(np.nan, 0)
252
-
253
- # Total
254
-
255
- # We see which of the individual heatmaps has the highest absolute value
256
- # We put the description of the highest absolute value
257
- df[HEATMAP_TOTAL_DESCRIPTION] = df.apply(
258
- lambda x: x[HEATMAP_DELIVERED_DESCRIPTION]
259
- if abs(x[HEATMAP_DELIVERED]) > abs(x[HEATMAP_EXPORTED])
260
- else x[HEATMAP_EXPORTED_DESCRIPTION],
261
- axis=1,
262
- )
263
- # Where Heatmap is 0, we put a desription of 0 (No impact)
264
- df[HEATMAP_TOTAL_DESCRIPTION] = df.apply(
265
- lambda x: 0 if x[HEATMAP_TOTAL] == 0 else x[HEATMAP_TOTAL_DESCRIPTION], axis=1
266
- )
267
- # All other cases are put as 0
268
- df[HEATMAP_TOTAL_DESCRIPTION] = df[HEATMAP_TOTAL_DESCRIPTION].replace(np.nan, 0)
232
+ if registers is None:
233
+ registers = [Register.DELIVERY, Register.EXPORT]
234
+
235
+ if Register.DELIVERY in registers:
236
+ df[HEATMAP_DELIVERED_DESCRIPTION] = list(
237
+ map(
238
+ map_delivery_description,
239
+ df[PRICE_ELECTRICITY_DELIVERED],
240
+ df[RLP_WEIGHTED_PRICE_DELIVERED],
241
+ df[ELECTRICITY_DELIVERED_SMR3],
242
+ df[ELECTRICITY_DELIVERED_SMR2],
243
+ )
244
+ )
245
+
246
+ if Register.EXPORT in registers:
247
+ df[HEATMAP_EXPORTED_DESCRIPTION] = list(
248
+ map(
249
+ map_export_description,
250
+ df[PRICE_ELECTRICITY_EXPORTED],
251
+ df[SPP_WEIGHTED_PRICE_EXPORTED],
252
+ df[ELECTRICITY_EXPORTED_SMR3],
253
+ df[ELECTRICITY_EXPORTED_SMR2],
254
+ )
255
+ )
256
+
257
+ if Register.DELIVERY in registers and Register.EXPORT in registers:
258
+ df[HEATMAP_TOTAL_DESCRIPTION] = list(
259
+ map(
260
+ map_total_description,
261
+ df[HEATMAP_DELIVERED].abs(),
262
+ df[HEATMAP_EXPORTED].abs(),
263
+ df[HEATMAP_DELIVERED_DESCRIPTION],
264
+ df[HEATMAP_EXPORTED_DESCRIPTION],
265
+ )
266
+ )
267
+ elif Register.DELIVERY in registers:
268
+ df[HEATMAP_TOTAL_DESCRIPTION] = df[HEATMAP_DELIVERED_DESCRIPTION]
269
+ else:
270
+ df[HEATMAP_TOTAL_DESCRIPTION] = df[HEATMAP_EXPORTED_DESCRIPTION]
269
271
 
270
272
  if not inplace:
271
273
  return df
272
274
 
273
275
 
274
- def calculate_dyntar_columns(df: pd.DataFrame, inplace: bool = False) -> pd.DataFrame | None:
276
+ def calculate_dyntar_columns(
277
+ df: pd.DataFrame,
278
+ inplace: bool = False,
279
+ registers: list[Register] | None = None,
280
+ ) -> pd.DataFrame | None:
275
281
  """Calculate all columns required for the dynamic tariff analysis."""
276
282
  if not inplace:
277
283
  df = df.copy()
278
284
 
279
- extend_dataframe_with_smr2(df, inplace=True)
280
- extend_dataframe_with_costs(df, inplace=True)
281
- extend_dataframe_with_weighted_prices(df, inplace=True)
282
- extend_dataframe_with_heatmap(df, inplace=True)
283
- extend_dataframe_with_heatmap_description(df, inplace=True)
285
+ if registers is None:
286
+ registers = [Register.DELIVERY, Register.EXPORT]
287
+
288
+ extend_dataframe_with_smr2(df, inplace=True, registers=registers)
289
+ extend_dataframe_with_costs(df, inplace=True, registers=registers)
290
+ extend_dataframe_with_weighted_prices(df, inplace=True, registers=registers)
291
+ extend_dataframe_with_heatmap(df, inplace=True, registers=registers)
292
+ extend_dataframe_with_heatmap_description(df, inplace=True, registers=registers)
284
293
 
285
294
  if not inplace:
286
295
  return df
287
296
  return None
297
+
298
+
299
+ def summarize_result(df: pd.DataFrame) -> pd.Series:
300
+ """Summarize the dynamic tariff analysis result."""
301
+ summary = df.filter(like="cost").sum()
302
+
303
+ abs_smr2 = summary.filter(like="smr2").abs().sum()
304
+
305
+ summary["cost_electricity_total_smr2"] = summary.filter(like="smr2").sum()
306
+ summary["cost_electricity_total_smr3"] = summary.filter(like="smr3").sum()
307
+
308
+ summary["ratio"] = (
309
+ summary["cost_electricity_total_smr3"] - summary["cost_electricity_total_smr2"]
310
+ ) / abs_smr2
311
+
312
+ return summary
@@ -1,9 +1,10 @@
1
1
  """Models for dynamic tariff analysis."""
2
2
 
3
3
  from typing import Literal
4
- from pydantic import Field, conlist, confloat
4
+ from pydantic import Field, conlist, confloat, BaseModel
5
5
 
6
6
  from openenergyid.models import TimeDataFrame
7
+ from .const import Register
7
8
 
8
9
 
9
10
  RequiredColumns = Literal[
@@ -43,18 +44,53 @@ class DynamicTariffAnalysisInput(TimeDataFrame):
43
44
  """Input frame for dynamic tariff analysis."""
44
45
 
45
46
  columns: list[RequiredColumns] = Field(
46
- min_length=len(RequiredColumns.__args__),
47
+ min_length=3,
47
48
  max_length=len(RequiredColumns.__args__),
48
49
  examples=[RequiredColumns.__args__],
49
50
  )
50
51
  data: list[
51
52
  conlist(
52
53
  item_type=confloat(allow_inf_nan=True),
53
- min_length=len(RequiredColumns.__args__),
54
+ min_length=3,
54
55
  max_length=len(RequiredColumns.__args__),
55
56
  ) # type: ignore
56
57
  ] = Field(examples=[[0.0] * len(RequiredColumns.__args__)])
57
58
 
59
+ @property
60
+ def registers(self) -> list[Register]:
61
+ """Check which registers are present in the input data."""
62
+ registers = []
63
+ columns = list(self.columns)
64
+ # if "electricity_delivered", "price_electricity_delivered" and "RLP" are present
65
+ if all(
66
+ column in columns
67
+ for column in [
68
+ "electricity_delivered",
69
+ "price_electricity_delivered",
70
+ "RLP",
71
+ ]
72
+ ):
73
+ registers.append(Register.DELIVERY)
74
+ # if "electricity_exported", "price_electricity_exported" and "SPP" are present
75
+ if all(
76
+ column in columns
77
+ for column in ["electricity_exported", "price_electricity_exported", "SPP"]
78
+ ):
79
+ registers.append(Register.EXPORT)
80
+ return registers
81
+
82
+
83
+ class DynamicTariffAnalysisOutputSummary(BaseModel):
84
+ """Summary of the dynamic tariff analysis output."""
85
+
86
+ cost_electricity_delivered_smr2: float | None = None
87
+ cost_electricity_delivered_smr3: float | None = None
88
+ cost_electricity_exported_smr2: float | None = None
89
+ cost_electricity_exported_smr3: float | None = None
90
+ cost_electricity_total_smr2: float | None = None
91
+ cost_electricity_total_smr3: float | None = None
92
+ ratio: float | None = None
93
+
58
94
 
59
95
  class DynamicTariffAnalysisOutput(TimeDataFrame):
60
96
  """Output frame for dynamic tariff analysis."""
@@ -71,3 +107,4 @@ class DynamicTariffAnalysisOutput(TimeDataFrame):
71
107
  max_length=len(OutputColumns.__args__),
72
108
  ) # type: ignore
73
109
  ] = Field(examples=[[0.0] * len(OutputColumns.__args__)])
110
+ summary: DynamicTariffAnalysisOutputSummary | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openenergyid
3
- Version: 0.1.18
3
+ Version: 0.1.20
4
4
  Summary: Open Source Python library for energy analytics and simulations
5
5
  Project-URL: Homepage, https://energyid.eu
6
6
  Project-URL: Repository, https://github.com/EnergieID/OpenEnergyID
@@ -1,14 +1,14 @@
1
- openenergyid/__init__.py,sha256=ntL_zbXBUmsAcqrx7QRz8_g3CNWyjWhlg9osouG9a-k,193
1
+ openenergyid/__init__.py,sha256=XVOYN9RAdcNJV1DaB0k2pD_zyPnk2aF6D5Tk4HFt4TU,193
2
2
  openenergyid/const.py,sha256=D-xUnUyVuLmphClkePgxpFP6z0RDhw_6m7rX0BHBgrw,823
3
3
  openenergyid/enums.py,sha256=jdw4CB1gkisx0re_SesrTEyh_T-UxYp6uieE7iYlHdA,357
4
4
  openenergyid/models.py,sha256=CO6VdthCOQ9hNXqVSan_4IOBpiQvOix-ea3U6TR7Vgc,4343
5
5
  openenergyid/capacity/__init__.py,sha256=1En96HlPV8kd1hOJO9RjRbXNInp5ZSkmjsjp0jfZlcQ,221
6
6
  openenergyid/capacity/main.py,sha256=G6_EtXs1k_W-fxS33pFrCNKajuH81skdI32zp5RX9bI,3674
7
7
  openenergyid/capacity/models.py,sha256=qi0IFyF_QOVleSzN8g0U2Fzqcc9ZDfNKt8oteFLY6Q0,832
8
- openenergyid/dyntar/__init__.py,sha256=iQXQXrEQOiVNeeF6LRmUf3oOhKlGjMNF7o4T04IWTGA,371
9
- openenergyid/dyntar/const.py,sha256=17qL0-S0SImsqrDEDrGS2GLyJYcJRw6GmmcTiML7tR0,956
10
- openenergyid/dyntar/main.py,sha256=z_F520eCsLcSYTo9e0iJBGpzoMo1G_TEECVCNi5rA_M,10732
11
- openenergyid/dyntar/models.py,sha256=FZq7HI1F-3nVeHwPkuB38-8u32JzdvsZaCzrhirFD2g,2094
8
+ openenergyid/dyntar/__init__.py,sha256=lUrk7ktS7yAqiafRHFoBE0RvFSI9mzDoO37diwLHuBg,495
9
+ openenergyid/dyntar/const.py,sha256=eJJV9VfpHlS9vWV47DWQkS3ICIXWhDmG4cU-ofbZJ3Q,1100
10
+ openenergyid/dyntar/main.py,sha256=i8EkayRicnMhG66cyrxGwUumFx3UGe7KDSImfFqmK04,10638
11
+ openenergyid/dyntar/models.py,sha256=lI4IjdAFallhsCqbw-EbBPbmk0g2MACgZnmMtTX7Pq0,3452
12
12
  openenergyid/energysharing/__init__.py,sha256=A4JfrUYf-hBCzhUm0qL1GGlNMvpO8OwXJo80dJxFIvw,274
13
13
  openenergyid/energysharing/const.py,sha256=X2zEPtTlsmZ66w6RmLS_h8NmdzObAEi5N6-0yrLN5V4,219
14
14
  openenergyid/energysharing/data_formatting.py,sha256=Kwuhyn6ao_8Brdm9frlA6VzYOqimNYZsRbYwNXnE7yc,2583
@@ -19,7 +19,7 @@ openenergyid/mvlr/helpers.py,sha256=Uzbfrj3IpH26wA206KOl0hNucKE-n9guJNC_EROBVKA,
19
19
  openenergyid/mvlr/main.py,sha256=cn7jZ98cHn2eh-0zG9q8Pad0Ft_FuI-u3a-eeHeF8jA,1304
20
20
  openenergyid/mvlr/models.py,sha256=XvkViOLlYqi0ffgF3AD4Jvk3yL05gsoKdKgBAsGJ7L4,8581
21
21
  openenergyid/mvlr/mvlr.py,sha256=F7WvWnZQtqUmK1vsguemsn9n8pDDk3tQ1weOlv-bo0c,18626
22
- openenergyid-0.1.18.dist-info/METADATA,sha256=d5UuJ8gqzwzSFx7aokbArsVr7RqetZoL8fJ3TwRuGlc,2477
23
- openenergyid-0.1.18.dist-info/WHEEL,sha256=fl6v0VwpzfGBVsGtkAkhILUlJxROXbA3HvRL6Fe3140,105
24
- openenergyid-0.1.18.dist-info/licenses/LICENSE,sha256=NgRdcNHwyXVCXZ8sJwoTp0DCowThJ9LWWl4xhbV1IUY,1074
25
- openenergyid-0.1.18.dist-info/RECORD,,
22
+ openenergyid-0.1.20.dist-info/METADATA,sha256=UJ-vbPX22VRhTyEtFnf0XzxrQa-U2LbdocBPcUFucuo,2477
23
+ openenergyid-0.1.20.dist-info/WHEEL,sha256=fl6v0VwpzfGBVsGtkAkhILUlJxROXbA3HvRL6Fe3140,105
24
+ openenergyid-0.1.20.dist-info/licenses/LICENSE,sha256=NgRdcNHwyXVCXZ8sJwoTp0DCowThJ9LWWl4xhbV1IUY,1074
25
+ openenergyid-0.1.20.dist-info/RECORD,,