core.utilities.get_translation()   A
last analyzed

Complexity

Conditions 5

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 9
rs 9.3333
c 0
b 0
f 0
cc 5
nop 1
1
import collections
2
import statistics
3
from datetime import datetime, timedelta
4
from decimal import Decimal
5
import mysql.connector
6
import config
7
import gettext
8
9
10
########################################################################################################################
11
# Aggregate hourly data by period
12
#
13
# This function aggregates hourly energy data into different time periods (hourly, daily, weekly, monthly, yearly).
14
# It processes raw hourly data and groups it according to the specified period type for reporting and analysis.
15
#
16
# Args:
17
#     rows_hourly: List of tuples containing (start_datetime_utc, actual_value) for hourly data
18
#                  Should belong to one energy_category_id
19
#     start_datetime_utc: Start datetime in UTC for the aggregation period
20
#     end_datetime_utc: End datetime in UTC for the aggregation period
21
#     period_type: Period type for aggregation - 'hourly', 'daily', 'weekly', 'monthly', or 'yearly'
22
#
23
# Returns:
24
#     List of tuples containing (datetime_utc, aggregated_value) for the specified period type
25
#
26
# Note: This procedure doesn't work with multiple energy categories
27
########################################################################################################################
28
def aggregate_hourly_data_by_period(rows_hourly, start_datetime_utc, end_datetime_utc, period_type):
29
    # Validate input parameters
30
    if start_datetime_utc is None or \
31
            end_datetime_utc is None or \
32
            start_datetime_utc >= end_datetime_utc or \
33
            period_type not in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'):
34
        return list()
35
36
    # Remove timezone info for consistent processing
37
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
38
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
39
40
    # Process hourly aggregation
41
    if period_type == "hourly":
42
        result_rows_hourly = list()
43
        # TODO: add config.working_day_start_time_local
44
        # TODO: add config.minutes_to_count
45
        current_datetime_utc = start_datetime_utc.replace(minute=0, second=0, microsecond=0, tzinfo=None)
46
        while current_datetime_utc <= end_datetime_utc:
47
            subtotal = None
48
            # Sum values within the current hour period
49
            for row in rows_hourly:
50
                if current_datetime_utc <= row[0] < current_datetime_utc + \
51
                        timedelta(minutes=config.minutes_to_count):
52
                    if row[1] is not None:
53
                        if subtotal is None:
54
                            subtotal = row[1]
55
                        else:
56
                            subtotal += row[1]
57
            result_rows_hourly.append((current_datetime_utc, subtotal))
58
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
59
60
        return result_rows_hourly
61
62
    # Process daily aggregation
63
    elif period_type == "daily":
64
        result_rows_daily = list()
65
        # TODO: add config.working_day_start_time_local
66
        # TODO: add config.minutes_to_count
67
        # Calculate the start datetime in UTC of the first day in local timezone
68
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
69
        current_datetime_utc = start_datetime_local.replace(hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
70
        while current_datetime_utc <= end_datetime_utc:
71
            subtotal = None
72
            for row in rows_hourly:
73
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=1):
74
                    if row[1] is not None:
75
                        if subtotal is None:
76
                            subtotal = row[1]
77
                        else:
78
                            subtotal += row[1]
79
            result_rows_daily.append((current_datetime_utc, subtotal))
80
            current_datetime_utc += timedelta(days=1)
81
82
        return result_rows_daily
83
84
    elif period_type == 'weekly':
85
        result_rows_weekly = list()
86
        # todo: add config.working_day_start_time_local
87
        # todo: add config.minutes_to_count
88
        # calculate the start datetime in utc of the monday in the first week in local
89
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
90
        weekday = start_datetime_local.weekday()
91
        current_datetime_utc = \
92
            start_datetime_local.replace(hour=0) - timedelta(days=weekday, hours=int(config.utc_offset[1:3]))
93
        while current_datetime_utc <= end_datetime_utc:
94
95
            next_datetime_utc = current_datetime_utc + timedelta(days=7)
96
            subtotal = None
97
            for row in rows_hourly:
98
                if current_datetime_utc <= row[0] < next_datetime_utc:
99
                    if row[1] is not None:
100
                        if subtotal is None:
101
                            subtotal = row[1]
102
                        else:
103
                            subtotal += row[1]
104
            result_rows_weekly.append((current_datetime_utc, subtotal))
105
            current_datetime_utc = next_datetime_utc
106
107
        return result_rows_weekly
108
109
    elif period_type == "monthly":
110
        result_rows_monthly = list()
111
        # todo: add config.working_day_start_time_local
112
        # todo: add config.minutes_to_count
113
        # calculate the start datetime the first day in the first month in local
114
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
115
        current_datetime_local = start_datetime_local.replace(day=1, hour=0, minute=0,
116
                                                              second=0, microsecond=0)
117
        end_datetime_local = end_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
118
        while current_datetime_local <= end_datetime_local:
119
            # calculate the next datetime in local
120
            if current_datetime_local.month < 12:
121
                next_datetime_local = datetime(year=current_datetime_local.year,
122
                                               month=current_datetime_local.month + 1,
123
                                               day=1, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
124
            elif current_datetime_local.month == 12:
125
                next_datetime_local = datetime(year=current_datetime_local.year + 1,
126
                                               month=1,
127
                                               day=1, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
128
            current_datetime_utc = current_datetime_local - timedelta(hours=int(config.utc_offset[1:3]))
129
            next_datetime_utc = next_datetime_local - timedelta(hours=int(config.utc_offset[1:3]))
0 ignored issues
show
introduced by
The variable next_datetime_local does not seem to be defined for all execution paths.
Loading history...
130
            subtotal = None
131
            for row in rows_hourly:
132
                if current_datetime_utc <= row[0] < next_datetime_utc:
133
                    if row[1] is not None:
134
                        if subtotal is None:
135
                            subtotal = row[1]
136
                        else:
137
                            subtotal += row[1]
138
139
            result_rows_monthly.append((current_datetime_utc, subtotal))
140
            current_datetime_local = next_datetime_local
141
142
        return result_rows_monthly
143
144
    elif period_type == "yearly":
145
        result_rows_yearly = list()
146
        # todo: add config.working_day_start_time_local
147
        # todo: add config.minutes_to_count
148
        # calculate the start datetime in utc of the first day in the first year in local
149
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
150
        current_datetime_utc = start_datetime_local.replace(month=1, day=1, hour=0) - timedelta(
151
            hours=int(config.utc_offset[1:3]))
152
153
        while current_datetime_utc <= end_datetime_utc:
154
            # calculate the next datetime in utc
155
            # todo: timedelta of year
156
            next_datetime_utc = datetime(year=current_datetime_utc.year + 2,
157
                                         month=1,
158
                                         day=1,
159
                                         hour=current_datetime_utc.hour,
160
                                         minute=current_datetime_utc.minute,
161
                                         second=current_datetime_utc.second,
162
                                         microsecond=current_datetime_utc.microsecond,
163
                                         tzinfo=current_datetime_utc.tzinfo) - timedelta(days=1)
164
            subtotal = None
165
            for row in rows_hourly:
166
                if current_datetime_utc <= row[0] < next_datetime_utc:
167
                    if row[1] is not None:
168
                        if subtotal is None:
169
                            subtotal = row[1]
170
                        else:
171
                            subtotal += row[1]
172
173
            result_rows_yearly.append((current_datetime_utc, subtotal))
174
            current_datetime_utc = next_datetime_utc
175
        return result_rows_yearly
176
    else:
177
        return list()
178
179
180
########################################################################################################################
181
# Get tariffs by energy category
182
########################################################################################################################
183 View Code Duplication
def get_energy_category_tariffs(cost_center_id, energy_category_id, start_datetime_utc, end_datetime_utc):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
184
    # todo: validate parameters
185
    if cost_center_id is None:
186
        return dict()
187
188
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
189
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
190
191
    # get timezone offset in minutes, this value will be returned to client
192
    timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
193
    if config.utc_offset[0] == '-':
194
        timezone_offset = -timezone_offset
195
196
    tariff_dict = collections.OrderedDict()
197
198
    cnx = None
199
    cursor = None
200
    try:
201
        cnx = mysql.connector.connect(**config.myems_system_db)
202
        cursor = cnx.cursor()
203
        query_tariffs = (" SELECT t.id, t.valid_from_datetime_utc, t.valid_through_datetime_utc "
204
                         " FROM tbl_tariffs t, tbl_cost_centers_tariffs cct "
205
                         " WHERE t.energy_category_id = %s AND "
206
                         "       t.id = cct.tariff_id AND "
207
                         "       cct.cost_center_id = %s AND "
208
                         "       t.valid_through_datetime_utc >= %s AND "
209
                         "       t.valid_from_datetime_utc <= %s "
210
                         " ORDER BY t.valid_from_datetime_utc ")
211
        cursor.execute(query_tariffs, (energy_category_id, cost_center_id, start_datetime_utc, end_datetime_utc,))
212
        rows_tariffs = cursor.fetchall()
213
    except Exception as e:
214
        print(str(e))
215
        if cnx:
216
            cnx.close()
217
        if cursor:
218
            cursor.close()
219
        return dict()
220
221
    if rows_tariffs is None or len(rows_tariffs) == 0:
222
        if cursor:
223
            cursor.close()
224
        if cnx:
225
            cnx.close()
226
        return dict()
227
228
    for row in rows_tariffs:
229
        tariff_dict[row[0]] = {'valid_from_datetime_utc': row[1],
230
                               'valid_through_datetime_utc': row[2],
231
                               'rates': list()}
232
233
    try:
234
        query_timeofuse_tariffs = (" SELECT tariff_id, start_time_of_day, end_time_of_day, price "
235
                                   " FROM tbl_tariffs_timeofuses "
236
                                   " WHERE tariff_id IN ( " + ', '.join(map(str, tariff_dict.keys())) + ")"
237
                                   " ORDER BY tariff_id, start_time_of_day ")
238
        cursor.execute(query_timeofuse_tariffs, )
239
        rows_timeofuse_tariffs = cursor.fetchall()
240
    except Exception as e:
241
        print(str(e))
242
        if cnx:
243
            cnx.close()
244
        if cursor:
245
            cursor.close()
246
        return dict()
247
248
    if cursor:
249
        cursor.close()
250
    if cnx:
251
        cnx.close()
252
253
    if rows_timeofuse_tariffs is None or len(rows_timeofuse_tariffs) == 0:
254
        return dict()
255
256
    for row in rows_timeofuse_tariffs:
257
        tariff_dict[row[0]]['rates'].append({'start_time_of_day': row[1],
258
                                             'end_time_of_day': row[2],
259
                                             'price': row[3]})
260
261
    result = dict()
262
    for tariff_id, tariff_value in tariff_dict.items():
263
        current_datetime_utc = tariff_value['valid_from_datetime_utc']
264
        while current_datetime_utc < tariff_value['valid_through_datetime_utc']:
265
            for rate in tariff_value['rates']:
266
                current_datetime_local = current_datetime_utc + timedelta(minutes=timezone_offset)
267
                seconds_since_midnight = (current_datetime_local -
268
                                          current_datetime_local.replace(hour=0,
269
                                                                         second=0,
270
                                                                         microsecond=0,
271
                                                                         tzinfo=None)).total_seconds()
272
                if rate['start_time_of_day'].total_seconds() <= \
273
                        seconds_since_midnight < rate['end_time_of_day'].total_seconds():
274
                    result[current_datetime_utc] = rate['price']
275
                    break
276
277
            # start from the next time slot
278
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
279
280
    return {k: v for k, v in result.items() if start_datetime_utc <= k <= end_datetime_utc}
281
282
283
########################################################################################################################
284
# Get peak types of tariff by energy category
285
# peak types: toppeak, onpeak, midpeak, offpeak, deep
286
########################################################################################################################
287 View Code Duplication
def get_energy_category_peak_types(cost_center_id, energy_category_id, start_datetime_utc, end_datetime_utc):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
288
    # todo: validate parameters
289
    if cost_center_id is None:
290
        return dict()
291
292
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
293
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
294
295
    # get timezone offset in minutes, this value will be returned to client
296
    timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
297
    if config.utc_offset[0] == '-':
298
        timezone_offset = -timezone_offset
299
300
    tariff_dict = collections.OrderedDict()
301
302
    cnx = None
303
    cursor = None
304
    try:
305
        cnx = mysql.connector.connect(**config.myems_system_db)
306
        cursor = cnx.cursor()
307
        query_tariffs = (" SELECT t.id, t.valid_from_datetime_utc, t.valid_through_datetime_utc "
308
                         " FROM tbl_tariffs t, tbl_cost_centers_tariffs cct "
309
                         " WHERE t.energy_category_id = %s AND "
310
                         "       t.id = cct.tariff_id AND "
311
                         "       cct.cost_center_id = %s AND "
312
                         "       t.valid_through_datetime_utc >= %s AND "
313
                         "       t.valid_from_datetime_utc <= %s "
314
                         " ORDER BY t.valid_from_datetime_utc ")
315
        cursor.execute(query_tariffs, (energy_category_id, cost_center_id, start_datetime_utc, end_datetime_utc,))
316
        rows_tariffs = cursor.fetchall()
317
    except Exception as e:
318
        print(str(e))
319
        if cnx:
320
            cnx.close()
321
        if cursor:
322
            cursor.close()
323
        return dict()
324
325
    if rows_tariffs is None or len(rows_tariffs) == 0:
326
        if cursor:
327
            cursor.close()
328
        if cnx:
329
            cnx.close()
330
        return dict()
331
332
    for row in rows_tariffs:
333
        tariff_dict[row[0]] = {'valid_from_datetime_utc': row[1],
334
                               'valid_through_datetime_utc': row[2],
335
                               'rates': list()}
336
337
    try:
338
        query_timeofuse_tariffs = (" SELECT tariff_id, start_time_of_day, end_time_of_day, peak_type "
339
                                   " FROM tbl_tariffs_timeofuses "
340
                                   " WHERE tariff_id IN ( " + ', '.join(map(str, tariff_dict.keys())) + ")"
341
                                   " ORDER BY tariff_id, start_time_of_day ")
342
        cursor.execute(query_timeofuse_tariffs, )
343
        rows_timeofuse_tariffs = cursor.fetchall()
344
    except Exception as e:
345
        print(str(e))
346
        if cnx:
347
            cnx.close()
348
        if cursor:
349
            cursor.close()
350
        return dict()
351
352
    if cursor:
353
        cursor.close()
354
    if cnx:
355
        cnx.close()
356
357
    if rows_timeofuse_tariffs is None or len(rows_timeofuse_tariffs) == 0:
358
        return dict()
359
360
    for row in rows_timeofuse_tariffs:
361
        tariff_dict[row[0]]['rates'].append({'start_time_of_day': row[1],
362
                                             'end_time_of_day': row[2],
363
                                             'peak_type': row[3]})
364
365
    result = dict()
366
    for tariff_id, tariff_value in tariff_dict.items():
367
        current_datetime_utc = tariff_value['valid_from_datetime_utc']
368
        while current_datetime_utc < tariff_value['valid_through_datetime_utc']:
369
            for rate in tariff_value['rates']:
370
                current_datetime_local = current_datetime_utc + timedelta(minutes=timezone_offset)
371
                seconds_since_midnight = (current_datetime_local -
372
                                          current_datetime_local.replace(hour=0,
373
                                                                         second=0,
374
                                                                         microsecond=0,
375
                                                                         tzinfo=None)).total_seconds()
376
                if rate['start_time_of_day'].total_seconds() <= \
377
                        seconds_since_midnight < rate['end_time_of_day'].total_seconds():
378
                    result[current_datetime_utc] = rate['peak_type']
379
                    break
380
381
            # start from the next time slot
382
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
383
384
    return {k: v for k, v in result.items() if start_datetime_utc <= k <= end_datetime_utc}
385
386
387
########################################################################################################################
388
# Averaging calculator of hourly data by period
389
#   rows_hourly: list of (start_datetime_utc, actual_value), should belong to one energy_category_id
390
#   start_datetime_utc: start datetime in utc
391
#   end_datetime_utc: end datetime in utc
392
#   period_type: use one of the period types, 'hourly', 'daily', 'weekly', 'monthly' and 'yearly'
393
# Returns: periodically data of average and maximum
394
# Note: this procedure doesn't work with multiple energy categories
395
########################################################################################################################
396
def averaging_hourly_data_by_period(rows_hourly, start_datetime_utc, end_datetime_utc, period_type):
397
    # todo: validate parameters
398
    if start_datetime_utc is None or \
399
            end_datetime_utc is None or \
400
            start_datetime_utc >= end_datetime_utc or \
401
            period_type not in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'):
402
        return list(), None, None
403
404
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
405
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
406
407
    if period_type == "hourly":
408
        result_rows_hourly = list()
409
        # todo: add config.working_day_start_time_local
410
        # todo: add config.minutes_to_count
411
        total = Decimal(0.0)
412
        maximum = None
413
        counter = 0
414
        current_datetime_utc = start_datetime_utc.replace(minute=0, second=0, microsecond=0, tzinfo=None)
415 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
416
            sub_total = Decimal(0.0)
417
            sub_maximum = None
418
            sub_counter = 0
419
            for row in rows_hourly:
420
                if current_datetime_utc <= row[0] < current_datetime_utc + \
421
                        timedelta(minutes=config.minutes_to_count):
422
                    sub_total += row[1]
423
                    if sub_maximum is None:
424
                        sub_maximum = row[1]
425
                    elif sub_maximum < row[1]:
426
                        sub_maximum = row[1]
427
                    sub_counter += 1
428
429
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
430
            result_rows_hourly.append((current_datetime_utc, sub_average, sub_maximum))
431
432
            total += sub_total
433
            counter += sub_counter
434
            if sub_maximum is None:
435
                pass
436
            elif maximum is None:
437
                maximum = sub_maximum
438
            elif maximum < sub_maximum:
439
                maximum = sub_maximum
440
441
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
442
443
        average = total / counter if counter > 0 else None
444
        return result_rows_hourly, average, maximum
445
446
    elif period_type == "daily":
447
        result_rows_daily = list()
448
        # todo: add config.working_day_start_time_local
449
        # todo: add config.minutes_to_count
450
        total = Decimal(0.0)
451
        maximum = None
452
        counter = 0
453
        # calculate the start datetime in utc of the first day in local
454
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
455
        current_datetime_utc = start_datetime_local.replace(hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
456 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
457
            sub_total = Decimal(0.0)
458
            sub_maximum = None
459
            sub_counter = 0
460
            for row in rows_hourly:
461
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=1):
462
                    sub_total += row[1]
463
                    if sub_maximum is None:
464
                        sub_maximum = row[1]
465
                    elif sub_maximum < row[1]:
466
                        sub_maximum = row[1]
467
                    sub_counter += 1
468
469
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
470
            result_rows_daily.append((current_datetime_utc, sub_average, sub_maximum))
471
            total += sub_total
472
            counter += sub_counter
473
            if sub_maximum is None:
474
                pass
475
            elif maximum is None:
476
                maximum = sub_maximum
477
            elif maximum < sub_maximum:
478
                maximum = sub_maximum
479
            current_datetime_utc += timedelta(days=1)
480
481
        average = total / counter if counter > 0 else None
482
        return result_rows_daily, average, maximum
483
484
    elif period_type == 'weekly':
485
        result_rows_weekly = list()
486
        # todo: add config.working_day_start_time_local
487
        # todo: add config.minutes_to_count
488
        total = Decimal(0.0)
489
        maximum = None
490
        counter = 0
491
        # calculate the start datetime in utc of the monday in the first week in local
492
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
493
        weekday = start_datetime_local.weekday()
494
        current_datetime_utc = \
495
            start_datetime_local.replace(hour=0) - timedelta(days=weekday, hours=int(config.utc_offset[1:3]))
496 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
497
            sub_total = Decimal(0.0)
498
            sub_maximum = None
499
            sub_counter = 0
500
            for row in rows_hourly:
501
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=7):
502
                    sub_total += row[1]
503
                    if sub_maximum is None:
504
                        sub_maximum = row[1]
505
                    elif sub_maximum < row[1]:
506
                        sub_maximum = row[1]
507
                    sub_counter += 1
508
509
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
510
            result_rows_weekly.append((current_datetime_utc, sub_average, sub_maximum))
511
            total += sub_total
512
            counter += sub_counter
513
            if sub_maximum is None:
514
                pass
515
            elif maximum is None:
516
                maximum = sub_maximum
517
            elif maximum < sub_maximum:
518
                maximum = sub_maximum
519
            current_datetime_utc += timedelta(days=7)
520
521
        average = total / counter if counter > 0 else None
522
        return result_rows_weekly, average, maximum
523
524
    elif period_type == "monthly":
525
        result_rows_monthly = list()
526
        # todo: add config.working_day_start_time_local
527
        # todo: add config.minutes_to_count
528
        total = Decimal(0.0)
529
        maximum = None
530
        counter = 0
531
        # calculate the start datetime in utc of the first day in the first month in local
532
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
533
        current_datetime_utc = \
534
            start_datetime_local.replace(day=1, hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
535
536
        while current_datetime_utc <= end_datetime_utc:
537
            # calculate the next datetime in utc
538 View Code Duplication
            if current_datetime_utc.month == 1:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
539
                temp_day = 28
540
                ny = current_datetime_utc.year
541
                if (ny % 100 != 0 and ny % 4 == 0) or (ny % 100 == 0 and ny % 400 == 0):
542
                    temp_day = 29
543
544
                next_datetime_utc = datetime(year=current_datetime_utc.year,
545
                                             month=current_datetime_utc.month + 1,
546
                                             day=temp_day,
547
                                             hour=current_datetime_utc.hour,
548
                                             minute=current_datetime_utc.minute,
549
                                             second=0,
550
                                             microsecond=0,
551
                                             tzinfo=None)
552
            elif current_datetime_utc.month == 2:
553
                next_datetime_utc = datetime(year=current_datetime_utc.year,
554
                                             month=current_datetime_utc.month + 1,
555
                                             day=31,
556
                                             hour=current_datetime_utc.hour,
557
                                             minute=current_datetime_utc.minute,
558
                                             second=0,
559
                                             microsecond=0,
560
                                             tzinfo=None)
561
            elif current_datetime_utc.month in [3, 5, 8, 10]:
562
                next_datetime_utc = datetime(year=current_datetime_utc.year,
563
                                             month=current_datetime_utc.month + 1,
564
                                             day=30,
565
                                             hour=current_datetime_utc.hour,
566
                                             minute=current_datetime_utc.minute,
567
                                             second=0,
568
                                             microsecond=0,
569
                                             tzinfo=None)
570
            elif current_datetime_utc.month == 7:
571
                next_datetime_utc = datetime(year=current_datetime_utc.year,
572
                                             month=current_datetime_utc.month + 1,
573
                                             day=31,
574
                                             hour=current_datetime_utc.hour,
575
                                             minute=current_datetime_utc.minute,
576
                                             second=0,
577
                                             microsecond=0,
578
                                             tzinfo=None)
579
            elif current_datetime_utc.month in [4, 6, 9, 11]:
580
                next_datetime_utc = datetime(year=current_datetime_utc.year,
581
                                             month=current_datetime_utc.month + 1,
582
                                             day=31,
583
                                             hour=current_datetime_utc.hour,
584
                                             minute=current_datetime_utc.minute,
585
                                             second=0,
586
                                             microsecond=0,
587
                                             tzinfo=None)
588
            elif current_datetime_utc.month == 12:
589
                next_datetime_utc = datetime(year=current_datetime_utc.year + 1,
590
                                             month=1,
591
                                             day=31,
592
                                             hour=current_datetime_utc.hour,
593
                                             minute=current_datetime_utc.minute,
594
                                             second=0,
595
                                             microsecond=0,
596
                                             tzinfo=None)
597
598
            sub_total = Decimal(0.0)
599
            sub_maximum = None
600
            sub_counter = 0
601
            for row in rows_hourly:
602
                if current_datetime_utc <= row[0] < next_datetime_utc:
0 ignored issues
show
introduced by
The variable next_datetime_utc does not seem to be defined for all execution paths.
Loading history...
603
                    sub_total += row[1]
604
                    if sub_maximum is None:
605
                        sub_maximum = row[1]
606
                    elif sub_maximum < row[1]:
607
                        sub_maximum = row[1]
608
                    sub_counter += 1
609
610
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
611
            result_rows_monthly.append((current_datetime_utc, sub_average, sub_maximum))
612
            total += sub_total
613
            counter += sub_counter
614
            if sub_maximum is None:
615
                pass
616
            elif maximum is None:
617
                maximum = sub_maximum
618
            elif maximum < sub_maximum:
619
                maximum = sub_maximum
620
            current_datetime_utc = next_datetime_utc
621
622
        average = total / counter if counter > 0 else None
623
        return result_rows_monthly, average, maximum
624
625
    elif period_type == "yearly":
626
        result_rows_yearly = list()
627
        # todo: add config.working_day_start_time_local
628
        # todo: add config.minutes_to_count
629
        total = Decimal(0.0)
630
        maximum = None
631
        counter = 0
632
        # calculate the start datetime in utc of the first day in the first month in local
633
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
634
        current_datetime_utc = start_datetime_local.replace(month=1, day=1, hour=0) - timedelta(
635
            hours=int(config.utc_offset[1:3]))
636
637
        while current_datetime_utc <= end_datetime_utc:
638
            # calculate the next datetime in utc
639
            # todo: timedelta of year
640
            next_datetime_utc = datetime(year=current_datetime_utc.year + 2,
641
                                         month=1,
642
                                         day=1,
643
                                         hour=current_datetime_utc.hour,
644
                                         minute=current_datetime_utc.minute,
645
                                         second=current_datetime_utc.second,
646
                                         microsecond=current_datetime_utc.microsecond,
647
                                         tzinfo=current_datetime_utc.tzinfo) - timedelta(days=1)
648
            sub_total = Decimal(0.0)
649
            sub_maximum = None
650
            sub_counter = 0
651
            for row in rows_hourly:
652
                if current_datetime_utc <= row[0] < next_datetime_utc:
653
                    sub_total += row[1]
654
                    if sub_maximum is None:
655
                        sub_maximum = row[1]
656
                    elif sub_maximum < row[1]:
657
                        sub_maximum = row[1]
658
                    sub_counter += 1
659
660
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
661
            result_rows_yearly.append((current_datetime_utc, sub_average, sub_maximum))
662
            total += sub_total
663
            counter += sub_counter
664
            if sub_maximum is None:
665
                pass
666
            elif maximum is None:
667
                maximum = sub_maximum
668
            elif maximum < sub_maximum:
669
                maximum = sub_maximum
670
            current_datetime_utc = next_datetime_utc
671
672
        average = total / counter if counter > 0 else None
673
        return result_rows_yearly, average, maximum
674
    else:
675
        return list(), None, None
676
677
678
########################################################################################################################
679
# Statistics calculator of hourly data by period
680
#   rows_hourly: list of (start_datetime_utc, actual_value), should belong to one energy_category_id
681
#   start_datetime_utc: start datetime in utc
682
#   end_datetime_utc: end datetime in utc
683
#   period_type: use one of the period types, 'hourly', 'daily', 'weekly', 'monthly' and 'yearly'
684
# Returns: periodically data of values and statistics of mean, median, minimum, maximum, stdev and variance
685
# Note: this procedure doesn't work with multiple energy categories
686
########################################################################################################################
687
def statistics_hourly_data_by_period(rows_hourly, start_datetime_utc, end_datetime_utc, period_type):
688
    # todo: validate parameters
689
    if start_datetime_utc is None or \
690
            end_datetime_utc is None or \
691
            start_datetime_utc >= end_datetime_utc or \
692
            period_type not in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'):
693
        return list(), None, None, None, None, None, None
694
695
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
696
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
697
698
    if period_type == "hourly":
699
        result_rows_hourly = list()
700
        sample_data = list()
701
        # todo: add config.working_day_start_time_local
702
        # todo: add config.minutes_to_count
703
        counter = 0
704
        mean = None
705
        median = None
706
        minimum = None
707
        maximum = None
708
        stdev = None
709
        variance = None
710
        current_datetime_utc = start_datetime_utc.replace(minute=0, second=0, microsecond=0, tzinfo=None)
711 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
712
            sub_total = Decimal(0.0)
713
            for row in rows_hourly:
714
                if current_datetime_utc <= row[0] < current_datetime_utc + \
715
                        timedelta(minutes=config.minutes_to_count):
716
                    sub_total += row[1]
717
718
            result_rows_hourly.append((current_datetime_utc, sub_total))
719
            sample_data.append(sub_total)
720
721
            counter += 1
722
            if minimum is None:
723
                minimum = sub_total
724
            elif minimum > sub_total:
725
                minimum = sub_total
726
727
            if maximum is None:
728
                maximum = sub_total
729
            elif maximum < sub_total:
730
                maximum = sub_total
731
732
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
733
734
        if len(sample_data) > 1:
735
            mean = statistics.mean(sample_data)
736
            median = statistics.median(sample_data)
737
            stdev = statistics.stdev(sample_data)
738
            variance = statistics.variance(sample_data)
739
740
        return result_rows_hourly, mean, median, minimum, maximum, stdev, variance
741
742
    elif period_type == "daily":
743
        result_rows_daily = list()
744
        sample_data = list()
745
        # todo: add config.working_day_start_time_local
746
        # todo: add config.minutes_to_count
747
        counter = 0
748
        mean = None
749
        median = None
750
        minimum = None
751
        maximum = None
752
        stdev = None
753
        variance = None
754
        # calculate the start datetime in utc of the first day in local
755
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
756
        current_datetime_utc = start_datetime_local.replace(hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
757 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
758
            sub_total = Decimal(0.0)
759
            for row in rows_hourly:
760
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=1):
761
                    sub_total += row[1]
762
763
            result_rows_daily.append((current_datetime_utc, sub_total))
764
            sample_data.append(sub_total)
765
766
            counter += 1
767
            if minimum is None:
768
                minimum = sub_total
769
            elif minimum > sub_total:
770
                minimum = sub_total
771
772
            if maximum is None:
773
                maximum = sub_total
774
            elif maximum < sub_total:
775
                maximum = sub_total
776
            current_datetime_utc += timedelta(days=1)
777
778
        if len(sample_data) > 1:
779
            mean = statistics.mean(sample_data)
780
            median = statistics.median(sample_data)
781
            stdev = statistics.stdev(sample_data)
782
            variance = statistics.variance(sample_data)
783
784
        return result_rows_daily, mean, median, minimum, maximum, stdev, variance
785
786
    elif period_type == "weekly":
787
        result_rows_weekly = list()
788
        sample_data = list()
789
        # todo: add config.working_day_start_time_local
790
        # todo: add config.minutes_to_count
791
        counter = 0
792
        mean = None
793
        median = None
794
        minimum = None
795
        maximum = None
796
        stdev = None
797
        variance = None
798
        # calculate the start datetime in utc of the monday in the first week in local
799
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
800
        weekday = start_datetime_local.weekday()
801
        current_datetime_utc = \
802
            start_datetime_local.replace(hour=0) - timedelta(days=weekday, hours=int(config.utc_offset[1:3]))
803 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
804
            sub_total = Decimal(0.0)
805
            for row in rows_hourly:
806
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=7):
807
                    sub_total += row[1]
808
809
            result_rows_weekly.append((current_datetime_utc, sub_total))
810
            sample_data.append(sub_total)
811
812
            counter += 1
813
            if minimum is None:
814
                minimum = sub_total
815
            elif minimum > sub_total:
816
                minimum = sub_total
817
818
            if maximum is None:
819
                maximum = sub_total
820
            elif maximum < sub_total:
821
                maximum = sub_total
822
            current_datetime_utc += timedelta(days=7)
823
824
        if len(sample_data) > 1:
825
            mean = statistics.mean(sample_data)
826
            median = statistics.median(sample_data)
827
            stdev = statistics.stdev(sample_data)
828
            variance = statistics.variance(sample_data)
829
830
        return result_rows_weekly, mean, median, minimum, maximum, stdev, variance
831
832
    elif period_type == "monthly":
833
        result_rows_monthly = list()
834
        sample_data = list()
835
        # todo: add config.working_day_start_time_local
836
        # todo: add config.minutes_to_count
837
        counter = 0
838
        mean = None
839
        median = None
840
        minimum = None
841
        maximum = None
842
        stdev = None
843
        variance = None
844
        # calculate the start datetime in utc of the first day in the first month in local
845
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
846
        current_datetime_utc = \
847
            start_datetime_local.replace(day=1, hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
848
849
        while current_datetime_utc <= end_datetime_utc:
850
            # calculate the next datetime in utc
851 View Code Duplication
            if current_datetime_utc.month == 1:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
852
                temp_day = 28
853
                ny = current_datetime_utc.year
854
                if (ny % 100 != 0 and ny % 4 == 0) or (ny % 100 == 0 and ny % 400 == 0):
855
                    temp_day = 29
856
857
                next_datetime_utc = datetime(year=current_datetime_utc.year,
858
                                             month=current_datetime_utc.month + 1,
859
                                             day=temp_day,
860
                                             hour=current_datetime_utc.hour,
861
                                             minute=current_datetime_utc.minute,
862
                                             second=0,
863
                                             microsecond=0,
864
                                             tzinfo=None)
865
            elif current_datetime_utc.month == 2:
866
                next_datetime_utc = datetime(year=current_datetime_utc.year,
867
                                             month=current_datetime_utc.month + 1,
868
                                             day=31,
869
                                             hour=current_datetime_utc.hour,
870
                                             minute=current_datetime_utc.minute,
871
                                             second=0,
872
                                             microsecond=0,
873
                                             tzinfo=None)
874
            elif current_datetime_utc.month in [3, 5, 8, 10]:
875
                next_datetime_utc = datetime(year=current_datetime_utc.year,
876
                                             month=current_datetime_utc.month + 1,
877
                                             day=30,
878
                                             hour=current_datetime_utc.hour,
879
                                             minute=current_datetime_utc.minute,
880
                                             second=0,
881
                                             microsecond=0,
882
                                             tzinfo=None)
883
            elif current_datetime_utc.month == 7:
884
                next_datetime_utc = datetime(year=current_datetime_utc.year,
885
                                             month=current_datetime_utc.month + 1,
886
                                             day=31,
887
                                             hour=current_datetime_utc.hour,
888
                                             minute=current_datetime_utc.minute,
889
                                             second=0,
890
                                             microsecond=0,
891
                                             tzinfo=None)
892
            elif current_datetime_utc.month in [4, 6, 9, 11]:
893
                next_datetime_utc = datetime(year=current_datetime_utc.year,
894
                                             month=current_datetime_utc.month + 1,
895
                                             day=31,
896
                                             hour=current_datetime_utc.hour,
897
                                             minute=current_datetime_utc.minute,
898
                                             second=0,
899
                                             microsecond=0,
900
                                             tzinfo=None)
901
            elif current_datetime_utc.month == 12:
902
                next_datetime_utc = datetime(year=current_datetime_utc.year + 1,
903
                                             month=1,
904
                                             day=31,
905
                                             hour=current_datetime_utc.hour,
906
                                             minute=current_datetime_utc.minute,
907
                                             second=0,
908
                                             microsecond=0,
909
                                             tzinfo=None)
910
911
            sub_total = Decimal(0.0)
912
            for row in rows_hourly:
913
                if current_datetime_utc <= row[0] < next_datetime_utc:
0 ignored issues
show
introduced by
The variable next_datetime_utc does not seem to be defined for all execution paths.
Loading history...
914
                    sub_total += row[1]
915
916
            result_rows_monthly.append((current_datetime_utc, sub_total))
917
            sample_data.append(sub_total)
918
919
            counter += 1
920
            if minimum is None:
921
                minimum = sub_total
922
            elif minimum > sub_total:
923
                minimum = sub_total
924
925
            if maximum is None:
926
                maximum = sub_total
927
            elif maximum < sub_total:
928
                maximum = sub_total
929
            current_datetime_utc = next_datetime_utc
930
931
        if len(sample_data) > 1:
932
            mean = statistics.mean(sample_data)
933
            median = statistics.median(sample_data)
934
            stdev = statistics.stdev(sample_data)
935
            variance = statistics.variance(sample_data)
936
937
        return result_rows_monthly, mean, median, minimum, maximum, stdev, variance
938
939
    elif period_type == "yearly":
940
        result_rows_yearly = list()
941
        sample_data = list()
942
        # todo: add config.working_day_start_time_local
943
        # todo: add config.minutes_to_count
944
        mean = None
945
        median = None
946
        minimum = None
947
        maximum = None
948
        stdev = None
949
        variance = None
950
        # calculate the start datetime in utc of the first day in the first month in local
951
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
952
        current_datetime_utc = start_datetime_local.replace(month=1, day=1, hour=0) - timedelta(
953
            hours=int(config.utc_offset[1:3]))
954
955
        while current_datetime_utc <= end_datetime_utc:
956
            # calculate the next datetime in utc
957
            # todo: timedelta of year
958
            next_datetime_utc = datetime(year=current_datetime_utc.year + 2,
959
                                         month=1,
960
                                         day=1,
961
                                         hour=current_datetime_utc.hour,
962
                                         minute=current_datetime_utc.minute,
963
                                         second=current_datetime_utc.second,
964
                                         microsecond=current_datetime_utc.microsecond,
965
                                         tzinfo=current_datetime_utc.tzinfo) - timedelta(days=1)
966
            sub_total = Decimal(0.0)
967
            for row in rows_hourly:
968
                if current_datetime_utc <= row[0] < next_datetime_utc:
969
                    sub_total += row[1]
970
971
            result_rows_yearly.append((current_datetime_utc, sub_total))
972
            sample_data.append(sub_total)
973
974
            if minimum is None:
975
                minimum = sub_total
976
            elif minimum > sub_total:
977
                minimum = sub_total
978
            if maximum is None:
979
                maximum = sub_total
980
            elif maximum < sub_total:
981
                maximum = sub_total
982
983
            current_datetime_utc = next_datetime_utc
984
985
        if len(sample_data) > 1:
986
            mean = statistics.mean(sample_data)
987
            median = statistics.median(sample_data)
988
            stdev = statistics.stdev(sample_data)
989
            variance = statistics.variance(sample_data)
990
991
        return result_rows_yearly, mean, median, minimum, maximum, stdev, variance
992
993
    else:
994
        return list(), None, None, None, None, None, None
995
996
997
def get_translation(language):
998
    if language is None or not isinstance(language, str) or len(language) == 0:
999
        return gettext.translation('myems', './i18n/', languages=['en'])
1000
1001
    if language not in ['zh_CN', 'en', 'de', 'fr', 'es', 'ru', 'ar', 'vi', 'th', 'tr', 'ms', 'id', 'zh_TW', 'pt']:
1002
        return gettext.translation('myems', './i18n/', languages=['en'])
1003
    else:
1004
        language_list = [language]
1005
        return gettext.translation('myems', './i18n/', languages=language_list)
1006
1007
1008
def int16_to_hhmm(actual_value):
1009
    """Convert int16 to time in HH:mm"""
1010
    hh = int(actual_value / 256)
1011
    if hh < 10:
1012
        hh = '0' + str(hh)
1013
    elif hh < 24:
1014
        hh = str(hh)
1015
    else:
1016
        return None
1017
    mm = actual_value % 256
1018
    if mm < 10:
1019
        mm = '0' + str(mm)
1020
    elif mm < 60:
1021
        mm = str(mm)
1022
    else:
1023
        return None
1024
    return hh + ':' + mm
1025
1026
1027
def round2(actual_value, precision):
1028
    if actual_value is not None:
1029
        try:
1030
            result = round(actual_value, precision)
1031
        except (TypeError, NameError, SyntaxError):
1032
            return "-"
1033
        return result
1034
    else:
1035
        return "-"
1036