Reporting.on_options()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 4
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 4
dl 4
loc 4
rs 10
c 0
b 0
f 0
cc 1
nop 2
1
"""
2
Combined Equipment Saving Report API
3
4
This module provides REST API endpoints for generating combined equipment saving reports.
5
It analyzes energy savings achieved through combined equipment optimization and efficiency
6
improvements, providing insights into saving opportunities and ROI calculations.
7
8
Key Features:
9
- Combined equipment energy saving analysis
10
- Base period vs reporting period comparison
11
- Saving calculation and verification
12
- ROI analysis and metrics
13
- Excel export functionality
14
- Saving optimization insights
15
16
Report Components:
17
- Combined equipment saving summary
18
- Base period comparison data
19
- Saving calculation details
20
- ROI analysis and metrics
21
- Saving optimization recommendations
22
- Cost-benefit analysis
23
24
The module uses Falcon framework for REST API and includes:
25
- Database queries for saving data
26
- Saving calculation algorithms
27
- ROI analysis tools
28
- Excel export via excelexporters
29
- Multi-language support
30
- User authentication and authorization
31
"""
32
33
import re
34
from datetime import datetime, timedelta, timezone
35
from decimal import Decimal
36
import falcon
37
import mysql.connector
38
import simplejson as json
39
import config
40
import excelexporters.combinedequipmentsaving
41
from core import utilities
42
from core.useractivity import access_control, api_key_control
43
44
45 View Code Duplication
class Reporting:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
46
    def __init__(self):
47
        """"Initializes Reporting"""
48
        pass
49
50
    @staticmethod
51
    def on_options(req, resp):
52
        _ = req
53
        resp.status = falcon.HTTP_200
54
55
    ####################################################################################################################
56
    # PROCEDURES
57
    # Step 1: valid parameters
58
    # Step 2: query the combined equipment
59
    # Step 3: query energy categories
60
    # Step 4: query associated points
61
    # Step 5: query associated equipments
62
    # Step 6: query base period energy saving
63
    # Step 7: query reporting period energy saving
64
    # Step 8: query tariff data
65
    # Step 9: query associated points data
66
    # Step 10: query associated equipments energy saving
67
    # Step 11: construct the report
68
    ####################################################################################################################
69
    @staticmethod
70
    def on_get(req, resp):
71
        if 'API-KEY' not in req.headers or \
72
                not isinstance(req.headers['API-KEY'], str) or \
73
                len(str.strip(req.headers['API-KEY'])) == 0:
74
            access_control(req)
75
        else:
76
            api_key_control(req)
77
        print(req.params)
78
        combined_equipment_id = req.params.get('combinedequipmentid')
79
        combined_equipment_uuid = req.params.get('combinedequipmentuuid')
80
        period_type = req.params.get('periodtype')
81
        base_period_start_datetime_local = req.params.get('baseperiodstartdatetime')
82
        base_period_end_datetime_local = req.params.get('baseperiodenddatetime')
83
        reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime')
84
        reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime')
85
        language = req.params.get('language')
86
        quick_mode = req.params.get('quickmode')
87
88
        ################################################################################################################
89
        # Step 1: valid parameters
90
        ################################################################################################################
91
        if combined_equipment_id is None and combined_equipment_uuid is None:
92
            raise falcon.HTTPError(status=falcon.HTTP_400,
93
                                   title='API.BAD_REQUEST',
94
                                   description='API.INVALID_COMBINED_EQUIPMENT_ID')
95
96
        if combined_equipment_id is not None:
97
            combined_equipment_id = str.strip(combined_equipment_id)
98
            if not combined_equipment_id.isdigit() or int(combined_equipment_id) <= 0:
99
                raise falcon.HTTPError(status=falcon.HTTP_400,
100
                                       title='API.BAD_REQUEST',
101
                                       description='API.INVALID_COMBINED_EQUIPMENT_ID')
102
103
        if combined_equipment_uuid is not None:
104
            regex = re.compile(r'^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', re.I)
105
            match = regex.match(str.strip(combined_equipment_uuid))
106
            if not bool(match):
107
                raise falcon.HTTPError(status=falcon.HTTP_400,
108
                                       title='API.BAD_REQUEST',
109
                                       description='API.INVALID_COMBINED_EQUIPMENT_UUID')
110
111
        if period_type is None:
112
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
113
                                   description='API.INVALID_PERIOD_TYPE')
114
        else:
115
            period_type = str.strip(period_type)
116
            if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']:
117
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
118
                                       description='API.INVALID_PERIOD_TYPE')
119
120
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
121
        if config.utc_offset[0] == '-':
122
            timezone_offset = -timezone_offset
123
124
        base_start_datetime_utc = None
125
        if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0:
126
            base_period_start_datetime_local = str.strip(base_period_start_datetime_local)
127
            try:
128
                base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S')
129
            except ValueError:
130
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
131
                                       description="API.INVALID_BASE_PERIOD_START_DATETIME")
132
            base_start_datetime_utc = \
133
                base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
134
            # nomalize the start datetime
135
            if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30:
136
                base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
137
            else:
138
                base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
139
140
        base_end_datetime_utc = None
141
        if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0:
142
            base_period_end_datetime_local = str.strip(base_period_end_datetime_local)
143
            try:
144
                base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S')
145
            except ValueError:
146
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
147
                                       description="API.INVALID_BASE_PERIOD_END_DATETIME")
148
            base_end_datetime_utc = \
149
                base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
150
151
        if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \
152
                base_start_datetime_utc >= base_end_datetime_utc:
153
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
154
                                   description='API.INVALID_BASE_PERIOD_END_DATETIME')
155
156
        if reporting_period_start_datetime_local is None:
157
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
158
                                   description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
159
        else:
160
            reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local)
161
            try:
162
                reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local,
163
                                                                 '%Y-%m-%dT%H:%M:%S')
164
            except ValueError:
165
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
166
                                       description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
167
            reporting_start_datetime_utc = \
168
                reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
169
            # nomalize the start datetime
170
            if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30:
171
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
172
            else:
173
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
174
175
        if reporting_period_end_datetime_local is None:
176
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
177
                                   description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
178
        else:
179
            reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local)
180
            try:
181
                reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local,
182
                                                               '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
183
                                             timedelta(minutes=timezone_offset)
184
            except ValueError:
185
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
186
                                       description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
187
188
        if reporting_start_datetime_utc >= reporting_end_datetime_utc:
189
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
190
                                   description='API.INVALID_REPORTING_PERIOD_END_DATETIME')
191
192
        # if turn quick mode on, do not return parameters data and excel file
193
        is_quick_mode = False
194
        if quick_mode is not None and \
195
                len(str.strip(quick_mode)) > 0 and \
196
                str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'):
197
            is_quick_mode = True
198
199
        trans = utilities.get_translation(language)
200
        trans.install()
201
        _ = trans.gettext
202
203
        ################################################################################################################
204
        # Step 2: query the combined equipment
205
        ################################################################################################################
206
        cnx_system = mysql.connector.connect(**config.myems_system_db)
207
        cursor_system = cnx_system.cursor()
208
209
        cnx_energy = mysql.connector.connect(**config.myems_energy_db)
210
        cursor_energy = cnx_energy.cursor()
211
212
        cnx_energy_baseline = mysql.connector.connect(**config.myems_energy_baseline_db)
213
        cursor_energy_baseline = cnx_energy_baseline.cursor()
214
215
        cnx_historical = mysql.connector.connect(**config.myems_historical_db)
216
        cursor_historical = cnx_historical.cursor()
217
218
        if combined_equipment_id is not None:
219
            cursor_system.execute(" SELECT id, name, cost_center_id "
220
                                  " FROM tbl_combined_equipments "
221
                                  " WHERE id = %s ", (combined_equipment_id,))
222
            row_combined_equipment = cursor_system.fetchone()
223
        elif combined_equipment_uuid is not None:
224
            cursor_system.execute(" SELECT id, name, cost_center_id "
225
                                  " FROM tbl_combined_equipments "
226
                                  " WHERE uuid = %s ", (combined_equipment_uuid,))
227
            row_combined_equipment = cursor_system.fetchone()
228
229
        if row_combined_equipment is None:
0 ignored issues
show
introduced by
The variable row_combined_equipment does not seem to be defined for all execution paths.
Loading history...
230
            if cursor_system:
231
                cursor_system.close()
232
            if cnx_system:
233
                cnx_system.close()
234
235
            if cursor_energy:
236
                cursor_energy.close()
237
            if cnx_energy:
238
                cnx_energy.close()
239
240
            if cursor_energy_baseline:
241
                cursor_energy_baseline.close()
242
            if cnx_energy_baseline:
243
                cnx_energy_baseline.close()
244
245
            if cursor_historical:
246
                cursor_historical.close()
247
            if cnx_historical:
248
                cnx_historical.close()
249
            raise falcon.HTTPError(status=falcon.HTTP_404,
250
                                   title='API.NOT_FOUND',
251
                                   description='API.COMBINED_EQUIPMENT_NOT_FOUND')
252
253
        combined_equipment = dict()
254
        combined_equipment['id'] = row_combined_equipment[0]
255
        combined_equipment['name'] = row_combined_equipment[1]
256
        combined_equipment['cost_center_id'] = row_combined_equipment[2]
257
258
        ################################################################################################################
259
        # Step 3: query energy categories
260
        ################################################################################################################
261
        energy_category_set = set()
262
        # query energy categories in base period
263
        cursor_energy.execute(" SELECT DISTINCT(energy_category_id) "
264
                              " FROM tbl_combined_equipment_input_category_hourly "
265
                              " WHERE combined_equipment_id = %s "
266
                              "     AND start_datetime_utc >= %s "
267
                              "     AND start_datetime_utc < %s ",
268
                              (combined_equipment['id'], base_start_datetime_utc, base_end_datetime_utc))
269
        rows_energy_categories = cursor_energy.fetchall()
270
        if rows_energy_categories is not None and len(rows_energy_categories) > 0:
271
            for row_energy_category in rows_energy_categories:
272
                energy_category_set.add(row_energy_category[0])
273
274
        # query energy categories in reporting period
275
        cursor_energy.execute(" SELECT DISTINCT(energy_category_id) "
276
                              " FROM tbl_combined_equipment_input_category_hourly "
277
                              " WHERE combined_equipment_id = %s "
278
                              "     AND start_datetime_utc >= %s "
279
                              "     AND start_datetime_utc < %s ",
280
                              (combined_equipment['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
281
        rows_energy_categories = cursor_energy.fetchall()
282
        if rows_energy_categories is not None and len(rows_energy_categories) > 0:
283
            for row_energy_category in rows_energy_categories:
284
                energy_category_set.add(row_energy_category[0])
285
286
        # query all energy categories in base period and reporting period
287
        cursor_system.execute(" SELECT id, name, unit_of_measure, kgce, kgco2e "
288
                              " FROM tbl_energy_categories "
289
                              " ORDER BY id ", )
290
        rows_energy_categories = cursor_system.fetchall()
291
        if rows_energy_categories is None or len(rows_energy_categories) == 0:
292
            if cursor_system:
293
                cursor_system.close()
294
            if cnx_system:
295
                cnx_system.close()
296
297
            if cursor_energy:
298
                cursor_energy.close()
299
            if cnx_energy:
300
                cnx_energy.close()
301
302
            if cursor_energy_baseline:
303
                cursor_energy_baseline.close()
304
            if cnx_energy_baseline:
305
                cnx_energy_baseline.close()
306
307
            if cursor_historical:
308
                cursor_historical.close()
309
            if cnx_historical:
310
                cnx_historical.close()
311
            raise falcon.HTTPError(status=falcon.HTTP_404,
312
                                   title='API.NOT_FOUND',
313
                                   description='API.ENERGY_CATEGORY_NOT_FOUND')
314
        energy_category_dict = dict()
315
        for row_energy_category in rows_energy_categories:
316
            if row_energy_category[0] in energy_category_set:
317
                energy_category_dict[row_energy_category[0]] = {"name": row_energy_category[1],
318
                                                                "unit_of_measure": row_energy_category[2],
319
                                                                "kgce": row_energy_category[3],
320
                                                                "kgco2e": row_energy_category[4]}
321
322
        ################################################################################################################
323
        # Step 4: query associated points
324
        ################################################################################################################
325
        point_list = list()
326
        cursor_system.execute(" SELECT p.id, ep.name, p.units, p.object_type  "
327
                              " FROM tbl_combined_equipments e, tbl_combined_equipments_parameters ep, tbl_points p "
328
                              " WHERE e.id = %s AND e.id = ep.combined_equipment_id AND ep.parameter_type = 'point' "
329
                              "       AND ep.point_id = p.id "
330
                              " ORDER BY p.id ", (combined_equipment['id'],))
331
        rows_points = cursor_system.fetchall()
332
        if rows_points is not None and len(rows_points) > 0:
333
            for row in rows_points:
334
                point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]})
335
336
        ################################################################################################################
337
        # Step 5: query associated equipments
338
        ################################################################################################################
339
        associated_equipment_list = list()
340
        cursor_system.execute(" SELECT e.id, e.name "
341
                              " FROM tbl_equipments e,tbl_combined_equipments_equipments ee"
342
                              " WHERE ee.combined_equipment_id = %s AND e.id = ee.equipment_id"
343
                              " ORDER BY id ", (combined_equipment['id'],))
344
        rows_associated_equipments = cursor_system.fetchall()
345
        if rows_associated_equipments is not None and len(rows_associated_equipments) > 0:
346
            for row in rows_associated_equipments:
347
                associated_equipment_list.append({"id": row[0], "name": row[1]})
348
349
        ################################################################################################################
350
        # Step 6: query base period energy saving
351
        ################################################################################################################
352
        base = dict()
353
        if energy_category_set is not None and len(energy_category_set) > 0:
354
            for energy_category_id in energy_category_set:
355
                kgce = energy_category_dict[energy_category_id]['kgce']
356
                kgco2e = energy_category_dict[energy_category_id]['kgco2e']
357
358
                base[energy_category_id] = dict()
359
                base[energy_category_id]['timestamps'] = list()
360
                base[energy_category_id]['values_baseline'] = list()
361
                base[energy_category_id]['values_actual'] = list()
362
                base[energy_category_id]['values_saving'] = list()
363
                base[energy_category_id]['subtotal_baseline'] = Decimal(0.0)
364
                base[energy_category_id]['subtotal_actual'] = Decimal(0.0)
365
                base[energy_category_id]['subtotal_saving'] = Decimal(0.0)
366
                base[energy_category_id]['subtotal_in_kgce_baseline'] = Decimal(0.0)
367
                base[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0)
368
                base[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0)
369
                base[energy_category_id]['subtotal_in_kgco2e_baseline'] = Decimal(0.0)
370
                base[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0)
371
                base[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0)
372
                # query base period's energy baseline
373
                cursor_energy_baseline.execute(" SELECT start_datetime_utc, actual_value "
374
                                               " FROM tbl_combined_equipment_input_category_hourly "
375
                                               " WHERE combined_equipment_id = %s "
376
                                               "     AND energy_category_id = %s "
377
                                               "     AND start_datetime_utc >= %s "
378
                                               "     AND start_datetime_utc < %s "
379
                                               " ORDER BY start_datetime_utc ",
380
                                               (combined_equipment['id'],
381
                                                energy_category_id,
382
                                                base_start_datetime_utc,
383
                                                base_end_datetime_utc))
384
                rows_combined_equipment_hourly = cursor_energy_baseline.fetchall()
385
386
                rows_combined_equipment_periodically = \
387
                    utilities.aggregate_hourly_data_by_period(rows_combined_equipment_hourly,
388
                                                              base_start_datetime_utc,
389
                                                              base_end_datetime_utc,
390
                                                              period_type)
391
                for row_combined_equipment_periodically in rows_combined_equipment_periodically:
392
                    current_datetime_local = row_combined_equipment_periodically[0].replace(tzinfo=timezone.utc) + \
393
                                             timedelta(minutes=timezone_offset)
394
                    if period_type == 'hourly':
395
                        current_datetime = current_datetime_local.isoformat()[0:19]
396
                    elif period_type == 'daily':
397
                        current_datetime = current_datetime_local.isoformat()[0:10]
398
                    elif period_type == 'weekly':
399
                        current_datetime = current_datetime_local.isoformat()[0:10]
400
                    elif period_type == 'monthly':
401
                        current_datetime = current_datetime_local.isoformat()[0:7]
402
                    elif period_type == 'yearly':
403
                        current_datetime = current_datetime_local.isoformat()[0:4]
404
405
                    baseline_value = Decimal(0.0) if row_combined_equipment_periodically[1] is None \
406
                        else row_combined_equipment_periodically[1]
407
                    base[energy_category_id]['timestamps'].append(current_datetime)
0 ignored issues
show
introduced by
The variable current_datetime does not seem to be defined for all execution paths.
Loading history...
408
                    base[energy_category_id]['values_baseline'].append(baseline_value)
409
                    base[energy_category_id]['subtotal_baseline'] += baseline_value
410
                    base[energy_category_id]['subtotal_in_kgce_baseline'] += baseline_value * kgce
411
                    base[energy_category_id]['subtotal_in_kgco2e_baseline'] += baseline_value * kgco2e
412
413
                # query base period's energy actual
414
                cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
415
                                      " FROM tbl_combined_equipment_input_category_hourly "
416
                                      " WHERE combined_equipment_id = %s "
417
                                      "     AND energy_category_id = %s "
418
                                      "     AND start_datetime_utc >= %s "
419
                                      "     AND start_datetime_utc < %s "
420
                                      " ORDER BY start_datetime_utc ",
421
                                      (combined_equipment['id'],
422
                                       energy_category_id,
423
                                       base_start_datetime_utc,
424
                                       base_end_datetime_utc))
425
                rows_combined_equipment_hourly = cursor_energy.fetchall()
426
427
                rows_combined_equipment_periodically = \
428
                    utilities.aggregate_hourly_data_by_period(rows_combined_equipment_hourly,
429
                                                              base_start_datetime_utc,
430
                                                              base_end_datetime_utc,
431
                                                              period_type)
432
                for row_combined_equipment_periodically in rows_combined_equipment_periodically:
433
                    current_datetime_local = row_combined_equipment_periodically[0].replace(tzinfo=timezone.utc) + \
434
                                             timedelta(minutes=timezone_offset)
435
                    if period_type == 'hourly':
436
                        current_datetime = current_datetime_local.isoformat()[0:19]
437
                    elif period_type == 'daily':
438
                        current_datetime = current_datetime_local.isoformat()[0:10]
439
                    elif period_type == 'weekly':
440
                        current_datetime = current_datetime_local.isoformat()[0:10]
441
                    elif period_type == 'monthly':
442
                        current_datetime = current_datetime_local.isoformat()[0:7]
443
                    elif period_type == 'yearly':
444
                        current_datetime = current_datetime_local.isoformat()[0:4]
445
446
                    actual_value = Decimal(0.0) if row_combined_equipment_periodically[1] is None \
447
                        else row_combined_equipment_periodically[1]
448
                    base[energy_category_id]['values_actual'].append(actual_value)
449
                    base[energy_category_id]['subtotal_actual'] += actual_value
450
                    base[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce
451
                    base[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e
452
453
                # calculate base period's energy savings
454
                for i in range(len(base[energy_category_id]['values_baseline'])):
455
                    base[energy_category_id]['values_saving'].append(
456
                        base[energy_category_id]['values_baseline'][i] -
457
                        base[energy_category_id]['values_actual'][i])
458
459
                base[energy_category_id]['subtotal_saving'] = \
460
                    base[energy_category_id]['subtotal_baseline'] - \
461
                    base[energy_category_id]['subtotal_actual']
462
                base[energy_category_id]['subtotal_in_kgce_saving'] = \
463
                    base[energy_category_id]['subtotal_in_kgce_baseline'] - \
464
                    base[energy_category_id]['subtotal_in_kgce_actual']
465
                base[energy_category_id]['subtotal_in_kgco2e_saving'] = \
466
                    base[energy_category_id]['subtotal_in_kgco2e_baseline'] - \
467
                    base[energy_category_id]['subtotal_in_kgco2e_actual']
468
        ################################################################################################################
469
        # Step 7: query reporting period energy saving
470
        ################################################################################################################
471
        reporting = dict()
472
        if energy_category_set is not None and len(energy_category_set) > 0:
473
            for energy_category_id in energy_category_set:
474
                kgce = energy_category_dict[energy_category_id]['kgce']
475
                kgco2e = energy_category_dict[energy_category_id]['kgco2e']
476
477
                reporting[energy_category_id] = dict()
478
                reporting[energy_category_id]['timestamps'] = list()
479
                reporting[energy_category_id]['values_baseline'] = list()
480
                reporting[energy_category_id]['values_actual'] = list()
481
                reporting[energy_category_id]['values_saving'] = list()
482
                reporting[energy_category_id]['subtotal_baseline'] = Decimal(0.0)
483
                reporting[energy_category_id]['subtotal_actual'] = Decimal(0.0)
484
                reporting[energy_category_id]['subtotal_saving'] = Decimal(0.0)
485
                reporting[energy_category_id]['subtotal_in_kgce_baseline'] = Decimal(0.0)
486
                reporting[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0)
487
                reporting[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0)
488
                reporting[energy_category_id]['subtotal_in_kgco2e_baseline'] = Decimal(0.0)
489
                reporting[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0)
490
                reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0)
491
                # query reporting period's energy baseline
492
                cursor_energy_baseline.execute(" SELECT start_datetime_utc, actual_value "
493
                                               " FROM tbl_combined_equipment_input_category_hourly "
494
                                               " WHERE combined_equipment_id = %s "
495
                                               "     AND energy_category_id = %s "
496
                                               "     AND start_datetime_utc >= %s "
497
                                               "     AND start_datetime_utc < %s "
498
                                               " ORDER BY start_datetime_utc ",
499
                                               (combined_equipment['id'],
500
                                                energy_category_id,
501
                                                reporting_start_datetime_utc,
502
                                                reporting_end_datetime_utc))
503
                rows_combined_equipment_hourly = cursor_energy_baseline.fetchall()
504
505
                rows_combined_equipment_periodically = \
506
                    utilities.aggregate_hourly_data_by_period(rows_combined_equipment_hourly,
507
                                                              reporting_start_datetime_utc,
508
                                                              reporting_end_datetime_utc,
509
                                                              period_type)
510
                for row_combined_equipment_periodically in rows_combined_equipment_periodically:
511
                    current_datetime_local = row_combined_equipment_periodically[0].replace(tzinfo=timezone.utc) + \
512
                                             timedelta(minutes=timezone_offset)
513
                    if period_type == 'hourly':
514
                        current_datetime = current_datetime_local.isoformat()[0:19]
515
                    elif period_type == 'daily':
516
                        current_datetime = current_datetime_local.isoformat()[0:10]
517
                    elif period_type == 'weekly':
518
                        current_datetime = current_datetime_local.isoformat()[0:10]
519
                    elif period_type == 'monthly':
520
                        current_datetime = current_datetime_local.isoformat()[0:7]
521
                    elif period_type == 'yearly':
522
                        current_datetime = current_datetime_local.isoformat()[0:4]
523
524
                    baseline_value = Decimal(0.0) if row_combined_equipment_periodically[1] is None \
525
                        else row_combined_equipment_periodically[1]
526
                    reporting[energy_category_id]['timestamps'].append(current_datetime)
527
                    reporting[energy_category_id]['values_baseline'].append(baseline_value)
528
                    reporting[energy_category_id]['subtotal_baseline'] += baseline_value
529
                    reporting[energy_category_id]['subtotal_in_kgce_baseline'] += baseline_value * kgce
530
                    reporting[energy_category_id]['subtotal_in_kgco2e_baseline'] += baseline_value * kgco2e
531
532
                # query reporting period's energy actual
533
                cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
534
                                      " FROM tbl_combined_equipment_input_category_hourly "
535
                                      " WHERE combined_equipment_id = %s "
536
                                      "     AND energy_category_id = %s "
537
                                      "     AND start_datetime_utc >= %s "
538
                                      "     AND start_datetime_utc < %s "
539
                                      " ORDER BY start_datetime_utc ",
540
                                      (combined_equipment['id'],
541
                                       energy_category_id,
542
                                       reporting_start_datetime_utc,
543
                                       reporting_end_datetime_utc))
544
                rows_combined_equipment_hourly = cursor_energy.fetchall()
545
546
                rows_combined_equipment_periodically = \
547
                    utilities.aggregate_hourly_data_by_period(rows_combined_equipment_hourly,
548
                                                              reporting_start_datetime_utc,
549
                                                              reporting_end_datetime_utc,
550
                                                              period_type)
551
                for row_combined_equipment_periodically in rows_combined_equipment_periodically:
552
                    current_datetime_local = row_combined_equipment_periodically[0].replace(tzinfo=timezone.utc) + \
553
                                             timedelta(minutes=timezone_offset)
554
                    if period_type == 'hourly':
555
                        current_datetime = current_datetime_local.isoformat()[0:19]
556
                    elif period_type == 'daily':
557
                        current_datetime = current_datetime_local.isoformat()[0:10]
558
                    elif period_type == 'weekly':
559
                        current_datetime = current_datetime_local.isoformat()[0:10]
560
                    elif period_type == 'monthly':
561
                        current_datetime = current_datetime_local.isoformat()[0:7]
562
                    elif period_type == 'yearly':
563
                        current_datetime = current_datetime_local.isoformat()[0:4]
564
565
                    actual_value = Decimal(0.0) if row_combined_equipment_periodically[1] is None \
566
                        else row_combined_equipment_periodically[1]
567
                    reporting[energy_category_id]['values_actual'].append(actual_value)
568
                    reporting[energy_category_id]['subtotal_actual'] += actual_value
569
                    reporting[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce
570
                    reporting[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e
571
572
                # calculate reporting period's energy savings
573
                for i in range(len(reporting[energy_category_id]['values_baseline'])):
574
                    reporting[energy_category_id]['values_saving'].append(
575
                        reporting[energy_category_id]['values_baseline'][i] -
576
                        reporting[energy_category_id]['values_actual'][i])
577
578
                reporting[energy_category_id]['subtotal_saving'] = \
579
                    reporting[energy_category_id]['subtotal_baseline'] - \
580
                    reporting[energy_category_id]['subtotal_actual']
581
                reporting[energy_category_id]['subtotal_in_kgce_saving'] = \
582
                    reporting[energy_category_id]['subtotal_in_kgce_baseline'] - \
583
                    reporting[energy_category_id]['subtotal_in_kgce_actual']
584
                reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = \
585
                    reporting[energy_category_id]['subtotal_in_kgco2e_baseline'] - \
586
                    reporting[energy_category_id]['subtotal_in_kgco2e_actual']
587
        ################################################################################################################
588
        # Step 8: query tariff data
589
        ################################################################################################################
590
        parameters_data = dict()
591
        parameters_data['names'] = list()
592
        parameters_data['timestamps'] = list()
593
        parameters_data['values'] = list()
594
        if not is_quick_mode:
595
            if config.is_tariff_appended and energy_category_set is not None and len(energy_category_set) > 0:
596
                for energy_category_id in energy_category_set:
597
                    energy_category_tariff_dict = \
598
                        utilities.get_energy_category_tariffs(combined_equipment['cost_center_id'],
599
                                                              energy_category_id,
600
                                                              reporting_start_datetime_utc,
601
                                                              reporting_end_datetime_utc)
602
                    tariff_timestamp_list = list()
603
                    tariff_value_list = list()
604
                    for k, v in energy_category_tariff_dict.items():
605
                        # convert k from utc to local
606
                        k = k + timedelta(minutes=timezone_offset)
607
                        tariff_timestamp_list.append(k.isoformat()[0:19])
608
                        tariff_value_list.append(v)
609
610
                    parameters_data['names'].append(
611
                        _('Tariff') + '-' + energy_category_dict[energy_category_id]['name'])
612
                    parameters_data['timestamps'].append(tariff_timestamp_list)
613
                    parameters_data['values'].append(tariff_value_list)
614
615
        ################################################################################################################
616
        # Step 9: query associated points data
617
        ################################################################################################################
618
        if not is_quick_mode:
619
            for point in point_list:
620
                point_values = []
621
                point_timestamps = []
622
                if point['object_type'] == 'ENERGY_VALUE':
623
                    query = (" SELECT utc_date_time, actual_value "
624
                             " FROM tbl_energy_value "
625
                             " WHERE point_id = %s "
626
                             "       AND utc_date_time BETWEEN %s AND %s "
627
                             " ORDER BY utc_date_time ")
628
                    cursor_historical.execute(query, (point['id'],
629
                                                      reporting_start_datetime_utc,
630
                                                      reporting_end_datetime_utc))
631
                    rows = cursor_historical.fetchall()
632
633
                    if rows is not None and len(rows) > 0:
634
                        for row in rows:
635
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
636
                                                     timedelta(minutes=timezone_offset)
637
                            current_datetime = current_datetime_local.isoformat()[0:19]
638
                            point_timestamps.append(current_datetime)
639
                            point_values.append(row[1])
640
                elif point['object_type'] == 'ANALOG_VALUE':
641
                    query = (" SELECT utc_date_time, actual_value "
642
                             " FROM tbl_analog_value "
643
                             " WHERE point_id = %s "
644
                             "       AND utc_date_time BETWEEN %s AND %s "
645
                             " ORDER BY utc_date_time ")
646
                    cursor_historical.execute(query, (point['id'],
647
                                                      reporting_start_datetime_utc,
648
                                                      reporting_end_datetime_utc))
649
                    rows = cursor_historical.fetchall()
650
651
                    if rows is not None and len(rows) > 0:
652
                        for row in rows:
653
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
654
                                                     timedelta(minutes=timezone_offset)
655
                            current_datetime = current_datetime_local.isoformat()[0:19]
656
                            point_timestamps.append(current_datetime)
657
                            point_values.append(row[1])
658
                elif point['object_type'] == 'DIGITAL_VALUE':
659
                    query = (" SELECT utc_date_time, actual_value "
660
                             " FROM tbl_digital_value "
661
                             " WHERE point_id = %s "
662
                             "       AND utc_date_time BETWEEN %s AND %s "
663
                             " ORDER BY utc_date_time ")
664
                    cursor_historical.execute(query, (point['id'],
665
                                                      reporting_start_datetime_utc,
666
                                                      reporting_end_datetime_utc))
667
                    rows = cursor_historical.fetchall()
668
669
                    if rows is not None and len(rows) > 0:
670
                        for row in rows:
671
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
672
                                                     timedelta(minutes=timezone_offset)
673
                            current_datetime = current_datetime_local.isoformat()[0:19]
674
                            point_timestamps.append(current_datetime)
675
                            point_values.append(row[1])
676
677
                parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')')
678
                parameters_data['timestamps'].append(point_timestamps)
679
                parameters_data['values'].append(point_values)
680
681
        ################################################################################################################
682
        # Step 10: query associated equipments energy saving
683
        ################################################################################################################
684
        associated_equipment_data = dict()
685
686
        if energy_category_set is not None and len(energy_category_set) > 0:
687
            for energy_category_id in energy_category_set:
688
                associated_equipment_data[energy_category_id] = dict()
689
                associated_equipment_data[energy_category_id]['associated_equipment_names'] = list()
690
                associated_equipment_data[energy_category_id]['subtotal_saving'] = list()
691
692
                for associated_equipment in associated_equipment_list:
693
                    subtotal_baseline = Decimal(0.0)
694
                    subtotal_actual = Decimal(0.0)
695
                    associated_equipment_data[energy_category_id]['associated_equipment_names'].append(
696
                        associated_equipment['name'])
697
698
                    cursor_energy_baseline.execute(" SELECT start_datetime_utc, actual_value "
699
                                                   " FROM tbl_equipment_input_category_hourly "
700
                                                   " WHERE equipment_id = %s "
701
                                                   "     AND energy_category_id = %s "
702
                                                   "     AND start_datetime_utc >= %s "
703
                                                   "     AND start_datetime_utc < %s "
704
                                                   " ORDER BY start_datetime_utc ",
705
                                                   (associated_equipment['id'],
706
                                                    energy_category_id,
707
                                                    reporting_start_datetime_utc,
708
                                                    reporting_end_datetime_utc))
709
                    rows_associated_equipment_hourly = cursor_energy_baseline.fetchall()
710
711
                    rows_associated_equipment_periodically = \
712
                        utilities.aggregate_hourly_data_by_period(rows_associated_equipment_hourly,
713
                                                                  reporting_start_datetime_utc,
714
                                                                  reporting_end_datetime_utc,
715
                                                                  period_type)
716
717
                    for row_associated_equipment_periodically in rows_associated_equipment_periodically:
718
                        baseline_value = Decimal(0.0) if row_associated_equipment_periodically[1] is None \
719
                            else row_associated_equipment_periodically[1]
720
                        subtotal_baseline += baseline_value
721
722
                    # query reporting period's energy actual
723
                    cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
724
                                          " FROM tbl_equipment_input_category_hourly "
725
                                          " WHERE equipment_id = %s "
726
                                          "     AND energy_category_id = %s "
727
                                          "     AND start_datetime_utc >= %s "
728
                                          "     AND start_datetime_utc < %s "
729
                                          " ORDER BY start_datetime_utc ",
730
                                          (associated_equipment['id'],
731
                                           energy_category_id,
732
                                           reporting_start_datetime_utc,
733
                                           reporting_end_datetime_utc))
734
                    rows_associated_equipment_hourly = cursor_energy.fetchall()
735
736
                    rows_associated_equipment_periodically = \
737
                        utilities.aggregate_hourly_data_by_period(rows_associated_equipment_hourly,
738
                                                                  reporting_start_datetime_utc,
739
                                                                  reporting_end_datetime_utc,
740
                                                                  period_type)
741
742
                    for row_associated_equipment_periodically in rows_associated_equipment_periodically:
743
                        actual_value = Decimal(0.0) if row_associated_equipment_periodically[1] is None \
744
                            else row_associated_equipment_periodically[1]
745
                        subtotal_actual += actual_value
746
747
                    associated_equipment_data[energy_category_id]['subtotal_saving'].append(
748
                        subtotal_baseline - subtotal_actual)
749
750
        ################################################################################################################
751
        # Step 11: construct the report
752
        ################################################################################################################
753
        if cursor_system:
754
            cursor_system.close()
755
        if cnx_system:
756
            cnx_system.close()
757
758
        if cursor_energy:
759
            cursor_energy.close()
760
        if cnx_energy:
761
            cnx_energy.close()
762
763
        if cursor_energy_baseline:
764
            cursor_energy_baseline.close()
765
        if cnx_energy_baseline:
766
            cnx_energy_baseline.close()
767
768
        if cursor_historical:
769
            cursor_historical.close()
770
        if cnx_historical:
771
            cnx_historical.close()
772
773
        result = dict()
774
775
        result['combined_equipment'] = dict()
776
        result['combined_equipment']['name'] = combined_equipment['name']
777
778
        result['base_period'] = dict()
779
        result['base_period']['names'] = list()
780
        result['base_period']['units'] = list()
781
        result['base_period']['timestamps'] = list()
782
        result['base_period']['values_saving'] = list()
783
        result['base_period']['subtotals_saving'] = list()
784
        result['base_period']['subtotals_in_kgce_saving'] = list()
785
        result['base_period']['subtotals_in_kgco2e_saving'] = list()
786
        result['base_period']['total_in_kgce_saving'] = Decimal(0.0)
787
        result['base_period']['total_in_kgco2e_saving'] = Decimal(0.0)
788
        if energy_category_set is not None and len(energy_category_set) > 0:
789
            for energy_category_id in energy_category_set:
790
                result['base_period']['names'].append(energy_category_dict[energy_category_id]['name'])
791
                result['base_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure'])
792
                result['base_period']['timestamps'].append(base[energy_category_id]['timestamps'])
793
                result['base_period']['values_saving'].append(base[energy_category_id]['values_saving'])
794
                result['base_period']['subtotals_saving'].append(base[energy_category_id]['subtotal_saving'])
795
                result['base_period']['subtotals_in_kgce_saving'].append(
796
                    base[energy_category_id]['subtotal_in_kgce_saving'])
797
                result['base_period']['subtotals_in_kgco2e_saving'].append(
798
                    base[energy_category_id]['subtotal_in_kgco2e_saving'])
799
                result['base_period']['total_in_kgce_saving'] += base[energy_category_id]['subtotal_in_kgce_saving']
800
                result['base_period']['total_in_kgco2e_saving'] += base[energy_category_id]['subtotal_in_kgco2e_saving']
801
802
        result['reporting_period'] = dict()
803
        result['reporting_period']['names'] = list()
804
        result['reporting_period']['energy_category_ids'] = list()
805
        result['reporting_period']['units'] = list()
806
        result['reporting_period']['timestamps'] = list()
807
        result['reporting_period']['values_saving'] = list()
808
        result['reporting_period']['rates_saving'] = list()
809
        result['reporting_period']['subtotals_saving'] = list()
810
        result['reporting_period']['subtotals_in_kgce_saving'] = list()
811
        result['reporting_period']['subtotals_in_kgco2e_saving'] = list()
812
        result['reporting_period']['increment_rates_saving'] = list()
813
        result['reporting_period']['total_in_kgce_saving'] = Decimal(0.0)
814
        result['reporting_period']['total_in_kgco2e_saving'] = Decimal(0.0)
815
        result['reporting_period']['increment_rate_in_kgce_saving'] = Decimal(0.0)
816
        result['reporting_period']['increment_rate_in_kgco2e_saving'] = Decimal(0.0)
817
818
        if energy_category_set is not None and len(energy_category_set) > 0:
819
            for energy_category_id in energy_category_set:
820
                result['reporting_period']['names'].append(energy_category_dict[energy_category_id]['name'])
821
                result['reporting_period']['energy_category_ids'].append(energy_category_id)
822
                result['reporting_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure'])
823
                result['reporting_period']['timestamps'].append(reporting[energy_category_id]['timestamps'])
824
                result['reporting_period']['values_saving'].append(reporting[energy_category_id]['values_saving'])
825
                result['reporting_period']['subtotals_saving'].append(reporting[energy_category_id]['subtotal_saving'])
826
                result['reporting_period']['subtotals_in_kgce_saving'].append(
827
                    reporting[energy_category_id]['subtotal_in_kgce_saving'])
828
                result['reporting_period']['subtotals_in_kgco2e_saving'].append(
829
                    reporting[energy_category_id]['subtotal_in_kgco2e_saving'])
830
                result['reporting_period']['increment_rates_saving'].append(
831
                    (reporting[energy_category_id]['subtotal_saving'] - base[energy_category_id]['subtotal_saving']) /
832
                    base[energy_category_id]['subtotal_saving']
833
                    if base[energy_category_id]['subtotal_saving'] != Decimal(0.0) else None)
834
                result['reporting_period']['total_in_kgce_saving'] += \
835
                    reporting[energy_category_id]['subtotal_in_kgce_saving']
836
                result['reporting_period']['total_in_kgco2e_saving'] += \
837
                    reporting[energy_category_id]['subtotal_in_kgco2e_saving']
838
839
                rate = list()
840
                for index, value in enumerate(reporting[energy_category_id]['values_saving']):
841
                    if index < len(base[energy_category_id]['values_saving']) \
842
                            and base[energy_category_id]['values_saving'][index] != 0 and value != 0:
843
                        rate.append((value - base[energy_category_id]['values_saving'][index])
844
                                    / base[energy_category_id]['values_saving'][index])
845
                    else:
846
                        rate.append(None)
847
                result['reporting_period']['rates_saving'].append(rate)
848
849
        result['reporting_period']['increment_rate_in_kgce_saving'] = \
850
            (result['reporting_period']['total_in_kgce_saving'] - result['base_period']['total_in_kgce_saving']) / \
851
            result['base_period']['total_in_kgce_saving'] \
852
            if result['base_period']['total_in_kgce_saving'] != Decimal(0.0) else None
853
854
        result['reporting_period']['increment_rate_in_kgco2e_saving'] = \
855
            (result['reporting_period']['total_in_kgco2e_saving'] - result['base_period']['total_in_kgco2e_saving']) / \
856
            result['base_period']['total_in_kgco2e_saving'] \
857
            if result['base_period']['total_in_kgco2e_saving'] != Decimal(0.0) else None
858
859
        result['parameters'] = {
860
            "names": parameters_data['names'],
861
            "timestamps": parameters_data['timestamps'],
862
            "values": parameters_data['values']
863
        }
864
865
        result['associated_equipment'] = dict()
866
        result['associated_equipment']['energy_category_names'] = list()
867
        result['associated_equipment']['units'] = list()
868
        result['associated_equipment']['associated_equipment_names_array'] = list()
869
        result['associated_equipment']['subtotals_saving_array'] = list()
870
        if energy_category_set is not None and len(energy_category_set) > 0:
871
            for energy_category_id in energy_category_set:
872
                result['associated_equipment']['energy_category_names'].append(
873
                    energy_category_dict[energy_category_id]['name'])
874
                result['associated_equipment']['units'].append(
875
                    energy_category_dict[energy_category_id]['unit_of_measure'])
876
                result['associated_equipment']['associated_equipment_names_array'].append(
877
                    associated_equipment_data[energy_category_id]['associated_equipment_names'])
878
                result['associated_equipment']['subtotals_saving_array'].append(
879
                    associated_equipment_data[energy_category_id]['subtotal_saving'])
880
881
        # export result to Excel file and then encode the file to base64 string
882
        result['excel_bytes_base64'] = None
883
        if not is_quick_mode:
884
            result['excel_bytes_base64'] = \
885
                excelexporters.combinedequipmentsaving.export(result,
886
                                                              combined_equipment['name'],
887
                                                              base_period_start_datetime_local,
888
                                                              base_period_end_datetime_local,
889
                                                              reporting_period_start_datetime_local,
890
                                                              reporting_period_end_datetime_local,
891
                                                              period_type,
892
                                                              language)
893
894
        resp.text = json.dumps(result)
895