reports.tenantsaving.Reporting.on_get()   F
last analyzed

Complexity

Conditions 151

Size

Total Lines 741
Code Lines 564

Duplication

Lines 741
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 564
dl 741
loc 741
rs 0
c 0
b 0
f 0
cc 151
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like reports.tenantsaving.Reporting.on_get() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
Tenant Saving Report API
3
4
This module provides REST API endpoints for generating tenant saving reports.
5
It analyzes energy savings achieved through tenant optimization and efficiency
6
improvements, providing insights into saving opportunities and ROI calculations.
7
8
Key Features:
9
- Tenant 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
- Tenant 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
40
import config
41
import excelexporters.tenantsaving
42
from core import utilities
43
from core.useractivity import access_control, api_key_control
44
45
46 View Code Duplication
class Reporting:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
47
    def __init__(self):
48
        """"Initializes Reporting"""
49
        pass
50
51
    @staticmethod
52
    def on_options(req, resp):
53
        _ = req
54
        resp.status = falcon.HTTP_200
55
56
    ####################################################################################################################
57
    # PROCEDURES
58
    # Step 1: valid parameters
59
    # Step 2: query the tenant
60
    # Step 3: query energy categories
61
    # Step 4: query associated sensors
62
    # Step 5: query associated points
63
    # Step 6: query base period energy saving
64
    # Step 7: query reporting period energy saving
65
    # Step 8: query tariff data
66
    # Step 9: query associated sensors and points data
67
    # Step 10: 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
        tenant_id = req.params.get('tenantid')
79
        tenant_uuid = req.params.get('tenantuuid')
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 tenant_id is None and tenant_uuid is None:
92
            raise falcon.HTTPError(status=falcon.HTTP_400,
93
                                   title='API.BAD_REQUEST',
94
                                   description='API.INVALID_TENANT_ID')
95
96
        if tenant_id is not None:
97
            tenant_id = str.strip(tenant_id)
98
            if not tenant_id.isdigit() or int(tenant_id) <= 0:
99
                raise falcon.HTTPError(status=falcon.HTTP_400,
100
                                       title='API.BAD_REQUEST',
101
                                       description='API.INVALID_TENANT_ID')
102
103
        if tenant_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(tenant_uuid))
106
            if not bool(match):
107
                raise falcon.HTTPError(status=falcon.HTTP_400,
108
                                       title='API.BAD_REQUEST',
109
                                       description='API.INVALID_TENANT_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 tenant
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 tenant_id is not None:
219
            cursor_system.execute(" SELECT id, name, area, cost_center_id "
220
                                  " FROM tbl_tenants "
221
                                  " WHERE id = %s ", (tenant_id,))
222
            row_tenant = cursor_system.fetchone()
223
        elif tenant_uuid is not None:
224
            cursor_system.execute(" SELECT id, name, area, cost_center_id "
225
                                  " FROM tbl_tenants "
226
                                  " WHERE uuid = %s ", (tenant_uuid,))
227
            row_tenant = cursor_system.fetchone()
228
229
        if row_tenant is None:
0 ignored issues
show
introduced by
The variable row_tenant 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, title='API.NOT_FOUND', description='API.TENANT_NOT_FOUND')
250
251
        tenant = dict()
252
        tenant['id'] = row_tenant[0]
253
        tenant['name'] = row_tenant[1]
254
        tenant['area'] = row_tenant[2]
255
        tenant['cost_center_id'] = row_tenant[3]
256
257
        ################################################################################################################
258
        # Step 3: query energy categories
259
        ################################################################################################################
260
        energy_category_set = set()
261
        # query energy categories in base period
262
        cursor_energy.execute(" SELECT DISTINCT(energy_category_id) "
263
                              " FROM tbl_tenant_input_category_hourly "
264
                              " WHERE tenant_id = %s "
265
                              "     AND start_datetime_utc >= %s "
266
                              "     AND start_datetime_utc < %s ",
267
                              (tenant['id'], base_start_datetime_utc, base_end_datetime_utc))
268
        rows_energy_categories = cursor_energy.fetchall()
269
        if rows_energy_categories is not None and len(rows_energy_categories) > 0:
270
            for row_energy_category in rows_energy_categories:
271
                energy_category_set.add(row_energy_category[0])
272
273
        # query energy categories in reporting period
274
        cursor_energy.execute(" SELECT DISTINCT(energy_category_id) "
275
                              " FROM tbl_tenant_input_category_hourly "
276
                              " WHERE tenant_id = %s "
277
                              "     AND start_datetime_utc >= %s "
278
                              "     AND start_datetime_utc < %s ",
279
                              (tenant['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
280
        rows_energy_categories = cursor_energy.fetchall()
281
        if rows_energy_categories is not None and len(rows_energy_categories) > 0:
282
            for row_energy_category in rows_energy_categories:
283
                energy_category_set.add(row_energy_category[0])
284
285
        # query all energy categories in base period and reporting period
286
        cursor_system.execute(" SELECT id, name, unit_of_measure, kgce, kgco2e "
287
                              " FROM tbl_energy_categories "
288
                              " ORDER BY id ", )
289
        rows_energy_categories = cursor_system.fetchall()
290
        if rows_energy_categories is None or len(rows_energy_categories) == 0:
291
            if cursor_system:
292
                cursor_system.close()
293
            if cnx_system:
294
                cnx_system.close()
295
296
            if cursor_energy:
297
                cursor_energy.close()
298
            if cnx_energy:
299
                cnx_energy.close()
300
301
            if cursor_energy_baseline:
302
                cursor_energy_baseline.close()
303
            if cnx_energy_baseline:
304
                cnx_energy_baseline.close()
305
306
            if cursor_historical:
307
                cursor_historical.close()
308
            if cnx_historical:
309
                cnx_historical.close()
310
            raise falcon.HTTPError(status=falcon.HTTP_404,
311
                                   title='API.NOT_FOUND',
312
                                   description='API.ENERGY_CATEGORY_NOT_FOUND')
313
        energy_category_dict = dict()
314
        for row_energy_category in rows_energy_categories:
315
            if row_energy_category[0] in energy_category_set:
316
                energy_category_dict[row_energy_category[0]] = {"name": row_energy_category[1],
317
                                                                "unit_of_measure": row_energy_category[2],
318
                                                                "kgce": row_energy_category[3],
319
                                                                "kgco2e": row_energy_category[4]}
320
321
        ################################################################################################################
322
        # Step 4: query associated sensors
323
        ################################################################################################################
324
        point_list = list()
325
        cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type  "
326
                              " FROM tbl_tenants t, tbl_sensors s, tbl_tenants_sensors ts, "
327
                              "      tbl_points p, tbl_sensors_points sp "
328
                              " WHERE t.id = %s AND t.id = ts.tenant_id AND ts.sensor_id = s.id "
329
                              "       AND s.id = sp.sensor_id AND sp.point_id = p.id "
330
                              " ORDER BY p.id ", (tenant['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 points
338
        ################################################################################################################
339
        cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type  "
340
                              " FROM tbl_tenants t, tbl_tenants_points tp, tbl_points p "
341
                              " WHERE t.id = %s AND t.id = tp.tenant_id AND tp.point_id = p.id "
342
                              " ORDER BY p.id ", (tenant['id'],))
343
        rows_points = cursor_system.fetchall()
344
        if rows_points is not None and len(rows_points) > 0:
345
            for row in rows_points:
346
                point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]})
347
348
        ################################################################################################################
349
        # Step 6: query base period energy saving
350
        ################################################################################################################
351
        base = dict()
352
        if energy_category_set is not None and len(energy_category_set) > 0:
353
            for energy_category_id in energy_category_set:
354
                kgce = energy_category_dict[energy_category_id]['kgce']
355
                kgco2e = energy_category_dict[energy_category_id]['kgco2e']
356
357
                base[energy_category_id] = dict()
358
                base[energy_category_id]['timestamps'] = list()
359
                base[energy_category_id]['values_baseline'] = list()
360
                base[energy_category_id]['values_actual'] = list()
361
                base[energy_category_id]['values_saving'] = list()
362
                base[energy_category_id]['subtotal_baseline'] = Decimal(0.0)
363
                base[energy_category_id]['subtotal_actual'] = Decimal(0.0)
364
                base[energy_category_id]['subtotal_saving'] = Decimal(0.0)
365
                base[energy_category_id]['subtotal_in_kgce_baseline'] = Decimal(0.0)
366
                base[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0)
367
                base[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0)
368
                base[energy_category_id]['subtotal_in_kgco2e_baseline'] = Decimal(0.0)
369
                base[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0)
370
                base[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0)
371
                # query base period's energy baseline
372
                cursor_energy_baseline.execute(" SELECT start_datetime_utc, actual_value "
373
                                               " FROM tbl_tenant_input_category_hourly "
374
                                               " WHERE tenant_id = %s "
375
                                               "     AND energy_category_id = %s "
376
                                               "     AND start_datetime_utc >= %s "
377
                                               "     AND start_datetime_utc < %s "
378
                                               " ORDER BY start_datetime_utc ",
379
                                               (tenant['id'],
380
                                                energy_category_id,
381
                                                base_start_datetime_utc,
382
                                                base_end_datetime_utc))
383
                rows_tenant_hourly = cursor_energy_baseline.fetchall()
384
385
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
386
                                                                                     base_start_datetime_utc,
387
                                                                                     base_end_datetime_utc,
388
                                                                                     period_type)
389
                for row_tenant_periodically in rows_tenant_periodically:
390
                    current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \
391
                                             timedelta(minutes=timezone_offset)
392
                    if period_type == 'hourly':
393
                        current_datetime = current_datetime_local.isoformat()[0:19]
394
                    elif period_type == 'daily':
395
                        current_datetime = current_datetime_local.isoformat()[0:10]
396
                    elif period_type == 'weekly':
397
                        current_datetime = current_datetime_local.isoformat()[0:10]
398
                    elif period_type == 'monthly':
399
                        current_datetime = current_datetime_local.isoformat()[0:7]
400
                    elif period_type == 'yearly':
401
                        current_datetime = current_datetime_local.isoformat()[0:4]
402
403
                    baseline_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1]
404
                    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...
405
                    base[energy_category_id]['values_baseline'].append(baseline_value)
406
                    base[energy_category_id]['subtotal_baseline'] += baseline_value
407
                    base[energy_category_id]['subtotal_in_kgce_baseline'] += baseline_value * kgce
408
                    base[energy_category_id]['subtotal_in_kgco2e_baseline'] += baseline_value * kgco2e
409
410
                # query base period's energy actual
411
                cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
412
                                      " FROM tbl_tenant_input_category_hourly "
413
                                      " WHERE tenant_id = %s "
414
                                      "     AND energy_category_id = %s "
415
                                      "     AND start_datetime_utc >= %s "
416
                                      "     AND start_datetime_utc < %s "
417
                                      " ORDER BY start_datetime_utc ",
418
                                      (tenant['id'],
419
                                       energy_category_id,
420
                                       base_start_datetime_utc,
421
                                       base_end_datetime_utc))
422
                rows_tenant_hourly = cursor_energy.fetchall()
423
424
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
425
                                                                                     base_start_datetime_utc,
426
                                                                                     base_end_datetime_utc,
427
                                                                                     period_type)
428
                for row_tenant_periodically in rows_tenant_periodically:
429
                    current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \
430
                                             timedelta(minutes=timezone_offset)
431
                    if period_type == 'hourly':
432
                        current_datetime = current_datetime_local.isoformat()[0:19]
433
                    elif period_type == 'daily':
434
                        current_datetime = current_datetime_local.isoformat()[0:10]
435
                    elif period_type == 'weekly':
436
                        current_datetime = current_datetime_local.isoformat()[0:10]
437
                    elif period_type == 'monthly':
438
                        current_datetime = current_datetime_local.isoformat()[0:7]
439
                    elif period_type == 'yearly':
440
                        current_datetime = current_datetime_local.isoformat()[0:4]
441
442
                    actual_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1]
443
                    base[energy_category_id]['values_actual'].append(actual_value)
444
                    base[energy_category_id]['subtotal_actual'] += actual_value
445
                    base[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce
446
                    base[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e
447
448
                # calculate base period's energy saving
449
                for i in range(len(base[energy_category_id]['values_baseline'])):
450
                    base[energy_category_id]['values_saving'].append(
451
                        base[energy_category_id]['values_baseline'][i] -
452
                        base[energy_category_id]['values_actual'][i])
453
454
                base[energy_category_id]['subtotal_saving'] = \
455
                    base[energy_category_id]['subtotal_baseline'] - \
456
                    base[energy_category_id]['subtotal_actual']
457
                base[energy_category_id]['subtotal_in_kgce_saving'] = \
458
                    base[energy_category_id]['subtotal_in_kgce_baseline'] - \
459
                    base[energy_category_id]['subtotal_in_kgce_actual']
460
                base[energy_category_id]['subtotal_in_kgco2e_saving'] = \
461
                    base[energy_category_id]['subtotal_in_kgco2e_baseline'] - \
462
                    base[energy_category_id]['subtotal_in_kgco2e_actual']
463
        ################################################################################################################
464
        # Step 7: query reporting period energy saving
465
        ################################################################################################################
466
        reporting = dict()
467
        if energy_category_set is not None and len(energy_category_set) > 0:
468
            for energy_category_id in energy_category_set:
469
                kgce = energy_category_dict[energy_category_id]['kgce']
470
                kgco2e = energy_category_dict[energy_category_id]['kgco2e']
471
472
                reporting[energy_category_id] = dict()
473
                reporting[energy_category_id]['timestamps'] = list()
474
                reporting[energy_category_id]['values_baseline'] = list()
475
                reporting[energy_category_id]['values_actual'] = list()
476
                reporting[energy_category_id]['values_saving'] = list()
477
                reporting[energy_category_id]['subtotal_baseline'] = Decimal(0.0)
478
                reporting[energy_category_id]['subtotal_actual'] = Decimal(0.0)
479
                reporting[energy_category_id]['subtotal_saving'] = Decimal(0.0)
480
                reporting[energy_category_id]['subtotal_in_kgce_baseline'] = Decimal(0.0)
481
                reporting[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0)
482
                reporting[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0)
483
                reporting[energy_category_id]['subtotal_in_kgco2e_baseline'] = Decimal(0.0)
484
                reporting[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0)
485
                reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0)
486
                # query reporting period's energy baseline
487
                cursor_energy_baseline.execute(" SELECT start_datetime_utc, actual_value "
488
                                               " FROM tbl_tenant_input_category_hourly "
489
                                               " WHERE tenant_id = %s "
490
                                               "     AND energy_category_id = %s "
491
                                               "     AND start_datetime_utc >= %s "
492
                                               "     AND start_datetime_utc < %s "
493
                                               " ORDER BY start_datetime_utc ",
494
                                               (tenant['id'],
495
                                                energy_category_id,
496
                                                reporting_start_datetime_utc,
497
                                                reporting_end_datetime_utc))
498
                rows_tenant_hourly = cursor_energy_baseline.fetchall()
499
500
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
501
                                                                                     reporting_start_datetime_utc,
502
                                                                                     reporting_end_datetime_utc,
503
                                                                                     period_type)
504
                for row_tenant_periodically in rows_tenant_periodically:
505
                    current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \
506
                                             timedelta(minutes=timezone_offset)
507
                    if period_type == 'hourly':
508
                        current_datetime = current_datetime_local.isoformat()[0:19]
509
                    elif period_type == 'daily':
510
                        current_datetime = current_datetime_local.isoformat()[0:10]
511
                    elif period_type == 'weekly':
512
                        current_datetime = current_datetime_local.isoformat()[0:10]
513
                    elif period_type == 'monthly':
514
                        current_datetime = current_datetime_local.isoformat()[0:7]
515
                    elif period_type == 'yearly':
516
                        current_datetime = current_datetime_local.isoformat()[0:4]
517
518
                    baseline_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1]
519
                    reporting[energy_category_id]['timestamps'].append(current_datetime)
520
                    reporting[energy_category_id]['values_baseline'].append(baseline_value)
521
                    reporting[energy_category_id]['subtotal_baseline'] += baseline_value
522
                    reporting[energy_category_id]['subtotal_in_kgce_baseline'] += baseline_value * kgce
523
                    reporting[energy_category_id]['subtotal_in_kgco2e_baseline'] += baseline_value * kgco2e
524
525
                # query reporting period's energy actual
526
                cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
527
                                      " FROM tbl_tenant_input_category_hourly "
528
                                      " WHERE tenant_id = %s "
529
                                      "     AND energy_category_id = %s "
530
                                      "     AND start_datetime_utc >= %s "
531
                                      "     AND start_datetime_utc < %s "
532
                                      " ORDER BY start_datetime_utc ",
533
                                      (tenant['id'],
534
                                       energy_category_id,
535
                                       reporting_start_datetime_utc,
536
                                       reporting_end_datetime_utc))
537
                rows_tenant_hourly = cursor_energy.fetchall()
538
539
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
540
                                                                                     reporting_start_datetime_utc,
541
                                                                                     reporting_end_datetime_utc,
542
                                                                                     period_type)
543
                for row_tenant_periodically in rows_tenant_periodically:
544
                    current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \
545
                                             timedelta(minutes=timezone_offset)
546
                    if period_type == 'hourly':
547
                        current_datetime = current_datetime_local.isoformat()[0:19]
548
                    elif period_type == 'daily':
549
                        current_datetime = current_datetime_local.isoformat()[0:10]
550
                    elif period_type == 'weekly':
551
                        current_datetime = current_datetime_local.isoformat()[0:10]
552
                    elif period_type == 'monthly':
553
                        current_datetime = current_datetime_local.isoformat()[0:7]
554
                    elif period_type == 'yearly':
555
                        current_datetime = current_datetime_local.isoformat()[0:4]
556
557
                    actual_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1]
558
                    reporting[energy_category_id]['values_actual'].append(actual_value)
559
                    reporting[energy_category_id]['subtotal_actual'] += actual_value
560
                    reporting[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce
561
                    reporting[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e
562
563
                # calculate reporting period's energy savings
564
                for i in range(len(reporting[energy_category_id]['values_baseline'])):
565
                    reporting[energy_category_id]['values_saving'].append(
566
                        reporting[energy_category_id]['values_baseline'][i] -
567
                        reporting[energy_category_id]['values_actual'][i])
568
569
                reporting[energy_category_id]['subtotal_saving'] = \
570
                    reporting[energy_category_id]['subtotal_baseline'] - \
571
                    reporting[energy_category_id]['subtotal_actual']
572
                reporting[energy_category_id]['subtotal_in_kgce_saving'] = \
573
                    reporting[energy_category_id]['subtotal_in_kgce_baseline'] - \
574
                    reporting[energy_category_id]['subtotal_in_kgce_actual']
575
                reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = \
576
                    reporting[energy_category_id]['subtotal_in_kgco2e_baseline'] - \
577
                    reporting[energy_category_id]['subtotal_in_kgco2e_actual']
578
        ################################################################################################################
579
        # Step 8: query tariff data
580
        ################################################################################################################
581
        parameters_data = dict()
582
        parameters_data['names'] = list()
583
        parameters_data['timestamps'] = list()
584
        parameters_data['values'] = list()
585
        if config.is_tariff_appended and energy_category_set is not None and len(energy_category_set) > 0 \
586
                and not is_quick_mode:
587
            for energy_category_id in energy_category_set:
588
                energy_category_tariff_dict = utilities.get_energy_category_tariffs(tenant['cost_center_id'],
589
                                                                                    energy_category_id,
590
                                                                                    reporting_start_datetime_utc,
591
                                                                                    reporting_end_datetime_utc)
592
                tariff_timestamp_list = list()
593
                tariff_value_list = list()
594
                for k, v in energy_category_tariff_dict.items():
595
                    # convert k from utc to local
596
                    k = k + timedelta(minutes=timezone_offset)
597
                    tariff_timestamp_list.append(k.isoformat()[0:19])
598
                    tariff_value_list.append(v)
599
600
                parameters_data['names'].append(_('Tariff') + '-' + energy_category_dict[energy_category_id]['name'])
601
                parameters_data['timestamps'].append(tariff_timestamp_list)
602
                parameters_data['values'].append(tariff_value_list)
603
604
        ################################################################################################################
605
        # Step 9: query associated sensors and points data
606
        ################################################################################################################
607
        if not is_quick_mode:
608
            for point in point_list:
609
                point_values = []
610
                point_timestamps = []
611
                if point['object_type'] == 'ENERGY_VALUE':
612
                    query = (" SELECT utc_date_time, actual_value "
613
                             " FROM tbl_energy_value "
614
                             " WHERE point_id = %s "
615
                             "       AND utc_date_time BETWEEN %s AND %s "
616
                             " ORDER BY utc_date_time ")
617
                    cursor_historical.execute(query, (point['id'],
618
                                                      reporting_start_datetime_utc,
619
                                                      reporting_end_datetime_utc))
620
                    rows = cursor_historical.fetchall()
621
622
                    if rows is not None and len(rows) > 0:
623
                        for row in rows:
624
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
625
                                                     timedelta(minutes=timezone_offset)
626
                            current_datetime = current_datetime_local.isoformat()[0:19]
627
                            point_timestamps.append(current_datetime)
628
                            point_values.append(row[1])
629
                elif point['object_type'] == 'ANALOG_VALUE':
630
                    query = (" SELECT utc_date_time, actual_value "
631
                             " FROM tbl_analog_value "
632
                             " WHERE point_id = %s "
633
                             "       AND utc_date_time BETWEEN %s AND %s "
634
                             " ORDER BY utc_date_time ")
635
                    cursor_historical.execute(query, (point['id'],
636
                                                      reporting_start_datetime_utc,
637
                                                      reporting_end_datetime_utc))
638
                    rows = cursor_historical.fetchall()
639
640
                    if rows is not None and len(rows) > 0:
641
                        for row in rows:
642
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
643
                                                     timedelta(minutes=timezone_offset)
644
                            current_datetime = current_datetime_local.isoformat()[0:19]
645
                            point_timestamps.append(current_datetime)
646
                            point_values.append(row[1])
647
                elif point['object_type'] == 'DIGITAL_VALUE':
648
                    query = (" SELECT utc_date_time, actual_value "
649
                             " FROM tbl_digital_value "
650
                             " WHERE point_id = %s "
651
                             "       AND utc_date_time BETWEEN %s AND %s "
652
                             " ORDER BY utc_date_time ")
653
                    cursor_historical.execute(query, (point['id'],
654
                                                      reporting_start_datetime_utc,
655
                                                      reporting_end_datetime_utc))
656
                    rows = cursor_historical.fetchall()
657
658
                    if rows is not None and len(rows) > 0:
659
                        for row in rows:
660
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
661
                                                     timedelta(minutes=timezone_offset)
662
                            current_datetime = current_datetime_local.isoformat()[0:19]
663
                            point_timestamps.append(current_datetime)
664
                            point_values.append(row[1])
665
666
                parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')')
667
                parameters_data['timestamps'].append(point_timestamps)
668
                parameters_data['values'].append(point_values)
669
670
        ################################################################################################################
671
        # Step 10: construct the report
672
        ################################################################################################################
673
        if cursor_system:
674
            cursor_system.close()
675
        if cnx_system:
676
            cnx_system.close()
677
678
        if cursor_energy:
679
            cursor_energy.close()
680
        if cnx_energy:
681
            cnx_energy.close()
682
683
        if cursor_energy_baseline:
684
            cursor_energy_baseline.close()
685
        if cnx_energy_baseline:
686
            cnx_energy_baseline.close()
687
688
        if cursor_historical:
689
            cursor_historical.close()
690
        if cnx_historical:
691
            cnx_historical.close()
692
693
        result = dict()
694
695
        result['tenant'] = dict()
696
        result['tenant']['name'] = tenant['name']
697
        result['tenant']['area'] = tenant['area']
698
699
        result['base_period'] = dict()
700
        result['base_period']['names'] = list()
701
        result['base_period']['units'] = list()
702
        result['base_period']['timestamps'] = list()
703
        result['base_period']['values_saving'] = list()
704
        result['base_period']['subtotals_saving'] = list()
705
        result['base_period']['subtotals_in_kgce_saving'] = list()
706
        result['base_period']['subtotals_in_kgco2e_saving'] = list()
707
        result['base_period']['total_in_kgce_saving'] = Decimal(0.0)
708
        result['base_period']['total_in_kgco2e_saving'] = Decimal(0.0)
709
        if energy_category_set is not None and len(energy_category_set) > 0:
710
            for energy_category_id in energy_category_set:
711
                result['base_period']['names'].append(energy_category_dict[energy_category_id]['name'])
712
                result['base_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure'])
713
                result['base_period']['timestamps'].append(base[energy_category_id]['timestamps'])
714
                result['base_period']['values_saving'].append(base[energy_category_id]['values_saving'])
715
                result['base_period']['subtotals_saving'].append(base[energy_category_id]['subtotal_saving'])
716
                result['base_period']['subtotals_in_kgce_saving'].append(
717
                    base[energy_category_id]['subtotal_in_kgce_saving'])
718
                result['base_period']['subtotals_in_kgco2e_saving'].append(
719
                    base[energy_category_id]['subtotal_in_kgco2e_saving'])
720
                result['base_period']['total_in_kgce_saving'] += base[energy_category_id]['subtotal_in_kgce_saving']
721
                result['base_period']['total_in_kgco2e_saving'] += base[energy_category_id]['subtotal_in_kgco2e_saving']
722
723
        result['reporting_period'] = dict()
724
        result['reporting_period']['names'] = list()
725
        result['reporting_period']['energy_category_ids'] = list()
726
        result['reporting_period']['units'] = list()
727
        result['reporting_period']['timestamps'] = list()
728
        result['reporting_period']['values_saving'] = list()
729
        result['reporting_period']['rates_saving'] = list()
730
        result['reporting_period']['subtotals_saving'] = list()
731
        result['reporting_period']['subtotals_in_kgce_saving'] = list()
732
        result['reporting_period']['subtotals_in_kgco2e_saving'] = list()
733
        result['reporting_period']['subtotals_per_unit_area_saving'] = list()
734
        result['reporting_period']['increment_rates_saving'] = list()
735
        result['reporting_period']['total_in_kgce_saving'] = Decimal(0.0)
736
        result['reporting_period']['total_in_kgco2e_saving'] = Decimal(0.0)
737
        result['reporting_period']['increment_rate_in_kgce_saving'] = Decimal(0.0)
738
        result['reporting_period']['increment_rate_in_kgco2e_saving'] = Decimal(0.0)
739
740
        if energy_category_set is not None and len(energy_category_set) > 0:
741
            for energy_category_id in energy_category_set:
742
                result['reporting_period']['names'].append(energy_category_dict[energy_category_id]['name'])
743
                result['reporting_period']['energy_category_ids'].append(energy_category_id)
744
                result['reporting_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure'])
745
                result['reporting_period']['timestamps'].append(reporting[energy_category_id]['timestamps'])
746
                result['reporting_period']['values_saving'].append(reporting[energy_category_id]['values_saving'])
747
                result['reporting_period']['subtotals_saving'].append(reporting[energy_category_id]['subtotal_saving'])
748
                result['reporting_period']['subtotals_in_kgce_saving'].append(
749
                    reporting[energy_category_id]['subtotal_in_kgce_saving'])
750
                result['reporting_period']['subtotals_in_kgco2e_saving'].append(
751
                    reporting[energy_category_id]['subtotal_in_kgco2e_saving'])
752
                result['reporting_period']['subtotals_per_unit_area_saving'].append(
753
                    reporting[energy_category_id]['subtotal_saving'] / tenant['area']
754
                    if tenant['area'] != Decimal(0.0) else None)
755
                result['reporting_period']['increment_rates_saving'].append(
756
                    (reporting[energy_category_id]['subtotal_saving'] - base[energy_category_id]['subtotal_saving']) /
757
                    base[energy_category_id]['subtotal_saving']
758
                    if base[energy_category_id]['subtotal_saving'] != Decimal(0.0) else None)
759
                result['reporting_period']['total_in_kgce_saving'] += \
760
                    reporting[energy_category_id]['subtotal_in_kgce_saving']
761
                result['reporting_period']['total_in_kgco2e_saving'] += \
762
                    reporting[energy_category_id]['subtotal_in_kgco2e_saving']
763
764
                rate = list()
765
                for index, value in enumerate(reporting[energy_category_id]['values_saving']):
766
                    if index < len(base[energy_category_id]['values_saving']) \
767
                            and base[energy_category_id]['values_saving'][index] != Decimal(0.0) \
768
                            and value != Decimal(0.0):
769
                        rate.append((value - base[energy_category_id]['values_saving'][index])
770
                                    / base[energy_category_id]['values_saving'][index])
771
                    else:
772
                        rate.append(None)
773
                result['reporting_period']['rates_saving'].append(rate)
774
775
        result['reporting_period']['total_in_kgco2e_per_unit_area_saving'] = \
776
            result['reporting_period']['total_in_kgce_saving'] / tenant['area'] \
777
            if tenant['area'] != Decimal(0.0) else None
778
779
        result['reporting_period']['increment_rate_in_kgce_saving'] = \
780
            (result['reporting_period']['total_in_kgce_saving'] - result['base_period']['total_in_kgce_saving']) / \
781
            result['base_period']['total_in_kgce_saving'] \
782
            if result['base_period']['total_in_kgce_saving'] != Decimal(0.0) else None
783
784
        result['reporting_period']['total_in_kgce_per_unit_area_saving'] = \
785
            result['reporting_period']['total_in_kgco2e_saving'] / tenant['area'] if tenant['area'] > 0.0 else None
786
787
        result['reporting_period']['increment_rate_in_kgco2e_saving'] = \
788
            (result['reporting_period']['total_in_kgco2e_saving'] - result['base_period']['total_in_kgco2e_saving']) / \
789
            result['base_period']['total_in_kgco2e_saving'] \
790
            if result['base_period']['total_in_kgco2e_saving'] != Decimal(0.0) else None
791
792
        result['parameters'] = {
793
            "names": parameters_data['names'],
794
            "timestamps": parameters_data['timestamps'],
795
            "values": parameters_data['values']
796
        }
797
798
        # export result to Excel file and then encode the file to base64 string
799
        if not is_quick_mode:
800
            result['excel_bytes_base64'] = excelexporters.tenantsaving.export(result,
801
                                                                              tenant['name'],
802
                                                                              base_period_start_datetime_local,
803
                                                                              base_period_end_datetime_local,
804
                                                                              reporting_period_start_datetime_local,
805
                                                                              reporting_period_end_datetime_local,
806
                                                                              period_type,
807
                                                                              language)
808
809
        resp.text = json.dumps(result)
810