energy-converter 0.1__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.2
2
+ Name: energy-converter
3
+ Version: 0.1
4
+ Summary: Converter for translating from different time intervals and different units of measure
5
+ Home-page: https://github.com/Artem.Pleshakov/energy-converter
6
+ Author: Artem Pleshakov
7
+ Author-email: Artem.Pleshakov@imby.energy
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Requires-Dist: pandas
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: classifier
16
+ Dynamic: home-page
17
+ Dynamic: requires-dist
18
+ Dynamic: requires-python
19
+ Dynamic: summary
@@ -0,0 +1 @@
1
+ Converter for translating from different time intervals and different units of measure
@@ -0,0 +1 @@
1
+ from .converter import convert
@@ -0,0 +1,354 @@
1
+ import pandas as pd
2
+ import calendar
3
+
4
+ def convert(date_col, df,
5
+ initial_interval, target_interval,
6
+ initial_unit, target_unit):
7
+ # Проверка столбца даты
8
+ if date_col not in df.columns:
9
+ print(f"Столбец '{date_col}' не найден.")
10
+ return None
11
+
12
+ df[date_col] = pd.to_datetime(df[date_col], errors='coerce')
13
+ time_mapping = {
14
+ '1min': 1,
15
+ '5min': 5,
16
+ '15min': 15,
17
+ '60min': 60,
18
+ '1day': 1440,
19
+ '1month': 'M',
20
+ '1year': 'Y'
21
+ }
22
+
23
+ if initial_interval not in time_mapping or target_interval not in time_mapping:
24
+ print("Неверные интервалы:", initial_interval, target_interval)
25
+ return None
26
+
27
+ init_val = time_mapping[initial_interval]
28
+ tgt_val = time_mapping[target_interval]
29
+
30
+ # -----------------------------------------------------------------
31
+ # 2. Проверяем, numeric ли оба интервала, или календарный
32
+ # -----------------------------------------------------------------
33
+ numeric_intervals = {1,5,15,60,1440}
34
+ # если оба init_val и tgt_val - числа => ваша логика
35
+ # иначе => календарная логика
36
+ def is_numeric(v):
37
+ return isinstance(v,int) or isinstance(v,float)
38
+
39
+ both_numeric = is_numeric(init_val) and is_numeric(tgt_val)
40
+
41
+ # -----------------------------------------------------------------
42
+ # 3. Если оба ЧИСЛОВЫЕ - Ваша логика
43
+ # -----------------------------------------------------------------
44
+ if both_numeric:
45
+ # Ниже — ваш исходный код, БЕЗ ИЗМЕНЕНИЙ.
46
+ initial_interval_minutes = init_val
47
+ target_interval_minutes = tgt_val
48
+
49
+ # ----- Случай 1: kWh -> kWh -----
50
+ if initial_unit == 'kwh' and target_unit == 'kwh':
51
+ if initial_interval_minutes > target_interval_minutes:
52
+ # "Разбиваем" большую гранулярность на более мелкую
53
+ expanded_rows = []
54
+ # Во сколько раз нужно разбить
55
+ num_intervals = initial_interval_minutes // target_interval_minutes
56
+
57
+ for _, row in df.iterrows():
58
+ for i in range(num_intervals):
59
+ new_row = row.copy()
60
+ new_row[date_col] = row[date_col] + pd.Timedelta(minutes=target_interval_minutes * i)
61
+ for col in df.columns:
62
+ if col != date_col:
63
+ # Делим количество kWh на число более мелких интервалов
64
+ new_row[col] = row[col] / num_intervals
65
+ expanded_rows.append(new_row)
66
+
67
+ df_expanded = pd.DataFrame(expanded_rows)
68
+ return df_expanded
69
+
70
+ else:
71
+ # При укрупнении интервала (или равенстве) - суммируем
72
+ df_resampled = (
73
+ df
74
+ .set_index(date_col)
75
+ .resample(f'{target_interval_minutes}T')
76
+ .sum()
77
+ .reset_index()
78
+ )
79
+ return df_resampled
80
+
81
+ # ----- Случай 2: kW -> kW -----
82
+ # При уменьшении интервала (был больше, стал меньше) - просто дублируем (мощность не делим)
83
+ # При увеличении интервала - берём среднее (т.к. мощность при укрупнении обычно усредняют)
84
+ elif initial_unit == 'kw' and target_unit == 'kw':
85
+ if initial_interval_minutes > target_interval_minutes:
86
+ # "Разбиваем" (исходный интервал крупнее целевого) - дублируем
87
+ expanded_rows = []
88
+ num_intervals = initial_interval_minutes // target_interval_minutes
89
+
90
+ for _, row in df.iterrows():
91
+ for i in range(num_intervals):
92
+ new_row = row.copy()
93
+ new_row[date_col] = row[date_col] + pd.Timedelta(minutes=target_interval_minutes * i)
94
+ # Значения в kW просто копируем, не делим
95
+ expanded_rows.append(new_row)
96
+
97
+ df_expanded = pd.DataFrame(expanded_rows)
98
+ return df_expanded
99
+ else:
100
+ # Укрупняем интервал или оставляем такой же - берём среднее мощности
101
+ df_resampled = (
102
+ df
103
+ .set_index(date_col)
104
+ .resample(f'{target_interval_minutes}T')
105
+ .mean()
106
+ .reset_index()
107
+ )
108
+ return df_resampled
109
+
110
+ # ----- Случай 3: kW -> kWh -----
111
+ elif initial_unit == 'kw' and target_unit == 'kwh':
112
+ expanded_rows = []
113
+ num_intervals = initial_interval_minutes // target_interval_minutes # 24
114
+ num_intervals2 = 60 / initial_interval_minutes
115
+ num_intervals3 = 60 / target_interval_minutes # 1
116
+ num_intervals4 = initial_interval_minutes / 60
117
+ num_intervals5 = target_interval_minutes / 60
118
+
119
+ if initial_interval_minutes > target_interval_minutes:
120
+ for _, row in df.iterrows():
121
+ for i in range(num_intervals):
122
+ new_row = row.copy()
123
+ new_row[date_col] = row[date_col] + pd.Timedelta(minutes=target_interval_minutes * i)
124
+ for col in df.columns:
125
+ if col != date_col:
126
+ if initial_interval_minutes <= 60:
127
+ new_row[col] = row[col] / (num_intervals * num_intervals2)
128
+ elif initial_interval_minutes > 60:
129
+ new_row[col] = row[col] * num_intervals5
130
+ expanded_rows.append(new_row)
131
+
132
+ df_expanded = pd.DataFrame(expanded_rows)
133
+ return df_expanded
134
+ elif initial_interval_minutes == target_interval_minutes:
135
+ # Просто делим значения в столбцах на количество интервалов, если интервалы одинаковые
136
+ df_copy = df.copy() # Создаём копию DataFrame для изменений
137
+ for col in df.columns:
138
+ if col != date_col:
139
+ if initial_interval_minutes <= 60:
140
+ df_copy[col] = df_copy[col] / num_intervals2
141
+ elif initial_interval_minutes > 60:
142
+ df_copy[col] = df_copy[col] * num_intervals4
143
+
144
+ return df_copy
145
+ elif initial_interval_minutes < target_interval_minutes:
146
+ df_copy = df.copy() # Создаём копию DataFrame для изменений
147
+ for col in df.columns:
148
+ if col != date_col:
149
+ if initial_interval_minutes <= 60:
150
+ df_copy[col] = df_copy[col] / num_intervals2
151
+ elif initial_interval_minutes > 60:
152
+ df_copy[col] = df_copy[col] * num_intervals4
153
+ df_resampled = (
154
+ df_copy
155
+ .set_index(date_col) # Устанавливаем столбец с датой как индекс
156
+ .resample(f'{target_interval_minutes}T') # Ресемплинг по целевому интервалу
157
+ .sum() # Суммируем значения по целевому интервалу
158
+ .reset_index() # Сбрасываем индекс, чтобы дата снова была столбцом
159
+ )
160
+ return df_resampled
161
+
162
+ # ----- Случай 4: kWh -> kW-----
163
+ elif initial_unit == 'kwh' and target_unit == 'kw':
164
+ expanded_rows = []
165
+ num_intervals = initial_interval_minutes // target_interval_minutes # 24
166
+ num_intervals2 = 60 / initial_interval_minutes
167
+ num_intervals3 = 60 / target_interval_minutes # 1
168
+ num_intervals4 = initial_interval_minutes / 60
169
+ num_intervals5 = target_interval_minutes / 60
170
+ if initial_interval_minutes > target_interval_minutes:
171
+ df_copy = df.copy() # Создаём копию DataFrame для изменений
172
+ for col in df.columns:
173
+ if col != date_col:
174
+ if initial_interval_minutes <= 60:
175
+ df_copy[col] = df_copy[col] * num_intervals2
176
+ elif initial_interval_minutes > 60:
177
+ df_copy[col] = df_copy[col] / num_intervals4
178
+ for _, row in df_copy.iterrows():
179
+ for i in range(num_intervals):
180
+ new_row = row.copy()
181
+ new_row[date_col] = row[date_col] + pd.Timedelta(minutes=target_interval_minutes * i)
182
+ # Значения в kW просто копируем, не делим
183
+ expanded_rows.append(new_row)
184
+ df_expanded = pd.DataFrame(expanded_rows)
185
+ return df_expanded
186
+ elif initial_interval_minutes == target_interval_minutes:
187
+ df_copy = df.copy()
188
+ for col in df.columns:
189
+ if col != date_col:
190
+ if initial_interval_minutes <= 60:
191
+ df_copy[col] = df_copy[col] * num_intervals2
192
+ elif initial_interval_minutes > 60:
193
+ df_copy[col] = df_copy[col] / num_intervals4
194
+ return df_copy
195
+ elif initial_interval_minutes < target_interval_minutes:
196
+ df_copy = df.copy()
197
+ for col in df.columns:
198
+ if col != date_col:
199
+ df_copy[col] = df_copy[col] * num_intervals2
200
+ df_resampled = (
201
+ df_copy
202
+ .set_index(date_col)
203
+ .resample(f'{target_interval_minutes}T')
204
+ .mean()
205
+ .reset_index()
206
+ )
207
+ return df_resampled
208
+ else:
209
+ print("Эта комбинация (initial_unit -> target_unit) не реализована (числовая ветка).")
210
+ return None
211
+
212
+ # -----------------------------------------------------------------
213
+ # 4. Если один или оба интервала = '1month'/'1year'
214
+ # => календарная логика, учитывая реальную длину месяцев/лет
215
+ # -----------------------------------------------------------------
216
+
217
+ # Вспомогательная функция: "укрупнить" (downsample) => .resample('M'/'Y')
218
+ # Выбираем sum() для kWh, mean() для kW (по аналогии с вашей логикой).
219
+ def calendar_aggregate(df_in, freq_alias, init_unit, tgt_unit):
220
+ # freq_alias = 'M' или 'Y'
221
+ if init_unit=='kwh' and tgt_unit=='kwh':
222
+ df_out = df_in.resample(freq_alias).sum()
223
+ elif init_unit=='kw' and tgt_unit=='kw':
224
+ df_out = df_in.resample(freq_alias).mean()
225
+ # Это под вопросом для месяца и года.
226
+ elif init_unit=='kw' and tgt_unit=='kwh':
227
+ df_out = df_in.resample(freq_alias).sum()
228
+ elif init_unit=='kwh' and tgt_unit=='kw':
229
+
230
+ df_out = df_in.resample(freq_alias).mean()
231
+ else:
232
+ print("Неизвестная комбинация при календарном укрупнении.")
233
+ return None
234
+ return df_out
235
+
236
+ # Функция "дробления" (upsample) из месяца/года в более мелкие интервалы.
237
+ # Равномерное распределение (kWh), дублирование (kW).
238
+ def calendar_expand(df_in, date_col, init_unit, tgt_unit, freq_alias):
239
+ """
240
+ Если исходный интервал = '1month' или '1year',
241
+ а целевой '1day', '60min', '15min' или даже '1month'/'1year' (второе реже нужно),
242
+ "дробим" каждую строку.
243
+ """
244
+ out_rows = []
245
+ numeric_cols = [c for c in df_in.columns if c != date_col]
246
+
247
+ # sort по дате — чтобы было удобнее
248
+ df_in = df_in.sort_values(by=date_col).reset_index(drop=True)
249
+
250
+ for _, row in df_in.iterrows():
251
+ dt_start = row[date_col]
252
+ if init_val == 'M':
253
+ # Реально начало и конец месяца
254
+ days_in_m = calendar.monthrange(dt_start.year, dt_start.month)[1]
255
+ dt_period_start = dt_start.replace(day=1, hour=0, minute=0, second=0)
256
+ dt_period_end = dt_start.replace(day=days_in_m, hour=23, minute=59, second=59)
257
+ else:
258
+ # init_val=='Y'
259
+ is_leap = calendar.isleap(dt_start.year)
260
+ days_in_y = 366 if is_leap else 365
261
+ dt_period_start = dt_start.replace(month=1, day=1, hour=0, minute=0, second=0)
262
+ dt_period_end = dt_start.replace(month=12, day=31, hour=23, minute=59, second=59)
263
+
264
+ # freq_alias – это что-то вроде '1D', '60T', '15T', ...
265
+ rng = pd.date_range(start=dt_period_start, end=dt_period_end, freq=freq_alias)
266
+ n_sub = len(rng)
267
+ if n_sub == 0:
268
+ # если вдруг freq_alias слишком крупный
269
+ # тогда хотя бы одну строку оставим (без изменений)
270
+ out_rows.append(row)
271
+ continue
272
+
273
+ for i in range(n_sub):
274
+ new_row = row.copy()
275
+ new_row[date_col] = rng[i]
276
+
277
+ # Ваш принцип: kWh -> делим, kW -> дублируем
278
+ if init_unit=='kwh' and tgt_unit=='kwh':
279
+ # делим
280
+ for c in numeric_cols:
281
+ new_row[c] = new_row[c] / n_sub
282
+ elif init_unit=='kw' and tgt_unit=='kw':
283
+ # дублируем
284
+ pass
285
+ elif init_unit=='kw' and tgt_unit=='kwh':
286
+ # kW->kWh при "дроблении" — в вашей логике
287
+ # обычно kW / num_intervals?
288
+ # Но если каждый sub-интервал = 1 день (или 15 минут),
289
+ # формально kWh = kW × (продолжительность sub-интервала)
290
+ # В упрощении сделаем "равномерное распределение".
291
+ for c in numeric_cols:
292
+ new_row[c] = new_row[c] / n_sub
293
+ elif init_unit=='kwh' and tgt_unit=='kw':
294
+ # kWh->kW при дроблении => "дублировать/делить"?
295
+ # У вас в логике, если "разбиваем" kWh, вы умножаете/делите...
296
+ # Упростим — средняя мощность на каждый sub-интервал = (kWh / n_sub)?
297
+ for c in numeric_cols:
298
+ new_row[c] = new_row[c] / n_sub
299
+ else:
300
+ print("Неизвестная комбинация при calendar_expand")
301
+ return None
302
+
303
+ out_rows.append(new_row)
304
+
305
+ df_expanded = pd.DataFrame(out_rows).sort_values(by=date_col).reset_index(drop=True)
306
+ return df_expanded
307
+
308
+ # -----------------------------------------------------
309
+ # Собственно календарная ветка:
310
+ # Выясняем, что "чаще" и что "реже"
311
+ # (порядок: '1min'=1 < '5min' < ... < '1day' < '1month'(M) < '1year'(Y))
312
+ # -----------------------------------------------------
313
+ freq_map = {
314
+ 1: ('1T',1), # 1 min
315
+ 5: ('5T',2),
316
+ 15: ('15T',3),
317
+ 60: ('60T',4),
318
+ 1440: ('1D',5),
319
+ 'M': ('M',6),
320
+ 'Y': ('Y',7)
321
+ }
322
+
323
+ def get_order(val):
324
+ return freq_map[val][1] # второе поле
325
+ def get_freq(val):
326
+ return freq_map[val][0] # первое поле
327
+
328
+ init_order = get_order(init_val)
329
+ tgt_order = get_order(tgt_val)
330
+
331
+ # Ставим df в индекс по дате (для resample)
332
+ df = df.copy()
333
+ df.set_index(date_col, inplace=True)
334
+ df.sort_index(inplace=True)
335
+
336
+ if init_order < tgt_order:
337
+ # Исходная частота "чаще" => целевая реже => УКРУПНЕНИЕ = resample
338
+ # (например, 15min->month, day->year, ...)
339
+
340
+ df_res = calendar_aggregate(df, get_freq(tgt_val), initial_unit, target_unit)
341
+ if df_res is None:
342
+ return None
343
+ df_res = df_res.reset_index()
344
+ return df_res
345
+
346
+ else:
347
+ # init_order > tgt_order => ДРОБИМ (например, month->day, year->month, etc.)
348
+ df.reset_index(inplace=True)
349
+ freq_str = get_freq(tgt_val) # напр. '15T','1D','M'...
350
+ df_expanded = calendar_expand(df, date_col, initial_unit, target_unit, freq_str)
351
+ return df_expanded
352
+
353
+
354
+
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.2
2
+ Name: energy-converter
3
+ Version: 0.1
4
+ Summary: Converter for translating from different time intervals and different units of measure
5
+ Home-page: https://github.com/Artem.Pleshakov/energy-converter
6
+ Author: Artem Pleshakov
7
+ Author-email: Artem.Pleshakov@imby.energy
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Requires-Dist: pandas
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: classifier
16
+ Dynamic: home-page
17
+ Dynamic: requires-dist
18
+ Dynamic: requires-python
19
+ Dynamic: summary
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ converter/__init__.py
5
+ converter/converter.py
6
+ energy_converter.egg-info/PKG-INFO
7
+ energy_converter.egg-info/SOURCES.txt
8
+ energy_converter.egg-info/dependency_links.txt
9
+ energy_converter.egg-info/requires.txt
10
+ energy_converter.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="energy-converter",
5
+ version="0.1",
6
+ packages=find_packages(),
7
+ install_requires=[
8
+ "pandas",
9
+ ],
10
+ description="Converter for translating from different time intervals and different units of measure",
11
+ author="Artem Pleshakov",
12
+ author_email="Artem.Pleshakov@imby.energy",
13
+ url="https://github.com/Artem.Pleshakov/energy-converter",
14
+ classifiers=[
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ],
19
+ python_requires=">=3.6",
20
+ )