Issues (1751)

myems-api/reports/tenantplan.py (3 issues)

1
"""
2
Tenant Plan Report API
3
4
This module provides REST API endpoints for generating tenant plan reports.
5
It analyzes tenant planning data and performance against planned targets,
6
providing insights into plan execution and optimization opportunities.
7
8
Key Features:
9
- Tenant plan analysis
10
- Plan vs actual performance comparison
11
- Planning accuracy assessment
12
- Plan optimization insights
13
- Excel export functionality
14
- Planning performance metrics
15
16
Report Components:
17
- Tenant plan summary
18
- Plan vs actual comparison data
19
- Planning accuracy metrics
20
- Plan execution indicators
21
- Plan optimization recommendations
22
- Planning trends and patterns
23
24
The module uses Falcon framework for REST API and includes:
25
- Database queries for plan data
26
- Plan calculation algorithms
27
- Planning 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.tenantplan
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
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 tenant
59
    # Step 3: query energy categories
60
    # Step 4: query associated sensors
61
    # Step 5: query associated points
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 sensors and points data
66
    # Step 10: construct the report
67
    ####################################################################################################################
68
    @staticmethod
69
    def on_get(req, resp):
70
        if 'API-KEY' not in req.headers or \
71
                not isinstance(req.headers['API-KEY'], str) or \
72
                len(str.strip(req.headers['API-KEY'])) == 0:
73
            access_control(req)
74
        else:
75
            api_key_control(req)
76
        print(req.params)
77
        tenant_id = req.params.get('tenantid')
78
        tenant_uuid = req.params.get('tenantuuid')
79
        period_type = req.params.get('periodtype')
80
        base_period_start_datetime_local = req.params.get('baseperiodstartdatetime')
81
        base_period_end_datetime_local = req.params.get('baseperiodenddatetime')
82
        reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime')
83
        reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime')
84
        language = req.params.get('language')
85
        quick_mode = req.params.get('quickmode')
86
87
        ################################################################################################################
88
        # Step 1: valid parameters
89
        ################################################################################################################
90
        if tenant_id is None and tenant_uuid is None:
91
            raise falcon.HTTPError(status=falcon.HTTP_400,
92
                                   title='API.BAD_REQUEST',
93
                                   description='API.INVALID_TENANT_ID')
94
95
        if tenant_id is not None:
96
            tenant_id = str.strip(tenant_id)
97
            if not tenant_id.isdigit() or int(tenant_id) <= 0:
98
                raise falcon.HTTPError(status=falcon.HTTP_400,
99
                                       title='API.BAD_REQUEST',
100
                                       description='API.INVALID_TENANT_ID')
101
102
        if tenant_uuid is not None:
103
            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)
104
            match = regex.match(str.strip(tenant_uuid))
105
            if not bool(match):
106
                raise falcon.HTTPError(status=falcon.HTTP_400,
107
                                       title='API.BAD_REQUEST',
108
                                       description='API.INVALID_TENANT_UUID')
109
110
        if period_type is None:
111
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
112
                                   description='API.INVALID_PERIOD_TYPE')
113
        else:
114
            period_type = str.strip(period_type)
115
            if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']:
116
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
117
                                       description='API.INVALID_PERIOD_TYPE')
118
119
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
120
        if config.utc_offset[0] == '-':
121
            timezone_offset = -timezone_offset
122
123
        base_start_datetime_utc = None
124
        if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0:
125
            base_period_start_datetime_local = str.strip(base_period_start_datetime_local)
126
            try:
127
                base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S')
128
            except ValueError:
129
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
130
                                       description="API.INVALID_BASE_PERIOD_START_DATETIME")
131
            base_start_datetime_utc = \
132
                base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
133
            # nomalize the start datetime
134
            if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30:
135
                base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
136
            else:
137
                base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
138
139
        base_end_datetime_utc = None
140
        if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0:
141
            base_period_end_datetime_local = str.strip(base_period_end_datetime_local)
142
            try:
143
                base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S')
144
            except ValueError:
145
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
146
                                       description="API.INVALID_BASE_PERIOD_END_DATETIME")
147
            base_end_datetime_utc = \
148
                base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
149
150
        if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \
151
                base_start_datetime_utc >= base_end_datetime_utc:
152
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
153
                                   description='API.INVALID_BASE_PERIOD_END_DATETIME')
154
155
        if reporting_period_start_datetime_local is None:
156
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
157
                                   description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
158
        else:
159
            reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local)
160
            try:
161
                reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local,
162
                                                                 '%Y-%m-%dT%H:%M:%S')
163
            except ValueError:
164
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
165
                                       description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
166
            reporting_start_datetime_utc = \
167
                reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
168
            # nomalize the start datetime
169
            if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30:
170
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
171
            else:
172
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
173
174
        if reporting_period_end_datetime_local is None:
175
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
176
                                   description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
177
        else:
178
            reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local)
179
            try:
180
                reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local,
181
                                                               '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
182
                                             timedelta(minutes=timezone_offset)
183
            except ValueError:
184
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
185
                                       description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
186
187
        if reporting_start_datetime_utc >= reporting_end_datetime_utc:
188
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
189
                                   description='API.INVALID_REPORTING_PERIOD_END_DATETIME')
190
191
        # if turn quick mode on, do not return parameters data and excel file
192
        is_quick_mode = False
193
        if quick_mode is not None and \
194
                len(str.strip(quick_mode)) > 0 and \
195
                str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'):
196
            is_quick_mode = True
197
198
        trans = utilities.get_translation(language)
199
        trans.install()
200
        _ = trans.gettext
201
202
        ################################################################################################################
203
        # Step 2: query the tenant
204
        ################################################################################################################
205
        cnx_system = mysql.connector.connect(**config.myems_system_db)
206
        cursor_system = cnx_system.cursor()
207
208
        cnx_energy = mysql.connector.connect(**config.myems_energy_db)
209
        cursor_energy = cnx_energy.cursor()
210
211
        cnx_energy_plan = mysql.connector.connect(**config.myems_energy_plan_db)
212
        cursor_energy_plan = cnx_energy_plan.cursor()
213
214
        cnx_historical = mysql.connector.connect(**config.myems_historical_db)
215
        cursor_historical = cnx_historical.cursor()
216
217
        if tenant_id is not None:
218
            cursor_system.execute(" SELECT id, name, area, cost_center_id "
219
                                  " FROM tbl_tenants "
220
                                  " WHERE id = %s ", (tenant_id,))
221
            row_tenant = cursor_system.fetchone()
222
        elif tenant_uuid is not None:
223
            cursor_system.execute(" SELECT id, name, area, cost_center_id "
224
                                  " FROM tbl_tenants "
225
                                  " WHERE uuid = %s ", (tenant_uuid,))
226
            row_tenant = cursor_system.fetchone()
227
228
        if row_tenant is None:
0 ignored issues
show
The variable row_tenant does not seem to be defined for all execution paths.
Loading history...
229
            if cursor_system:
230
                cursor_system.close()
231
            if cnx_system:
232
                cnx_system.close()
233
234
            if cursor_energy:
235
                cursor_energy.close()
236
            if cnx_energy:
237
                cnx_energy.close()
238
239
            if cursor_energy_plan:
240
                cursor_energy_plan.close()
241
            if cnx_energy_plan:
242
                cnx_energy_plan.close()
243
244
            if cursor_historical:
245
                cursor_historical.close()
246
            if cnx_historical:
247
                cnx_historical.close()
248
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.TENANT_NOT_FOUND')
249
250
        tenant = dict()
251
        tenant['id'] = row_tenant[0]
252
        tenant['name'] = row_tenant[1]
253
        tenant['area'] = row_tenant[2]
254
        tenant['cost_center_id'] = row_tenant[3]
255
256
        ################################################################################################################
257
        # Step 3: query energy categories
258
        ################################################################################################################
259
        energy_category_set = set()
260
        # query energy categories in base period
261
        cursor_energy.execute(" SELECT DISTINCT(energy_category_id) "
262
                              " FROM tbl_tenant_input_category_hourly "
263
                              " WHERE tenant_id = %s "
264
                              "     AND start_datetime_utc >= %s "
265
                              "     AND start_datetime_utc < %s ",
266
                              (tenant['id'], base_start_datetime_utc, base_end_datetime_utc))
267
        rows_energy_categories = cursor_energy.fetchall()
268
        if rows_energy_categories is not None and len(rows_energy_categories) > 0:
269
            for row_energy_category in rows_energy_categories:
270
                energy_category_set.add(row_energy_category[0])
271
272
        # query energy categories in reporting period
273
        cursor_energy.execute(" SELECT DISTINCT(energy_category_id) "
274
                              " FROM tbl_tenant_input_category_hourly "
275
                              " WHERE tenant_id = %s "
276
                              "     AND start_datetime_utc >= %s "
277
                              "     AND start_datetime_utc < %s ",
278
                              (tenant['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
279
        rows_energy_categories = cursor_energy.fetchall()
280
        if rows_energy_categories is not None and len(rows_energy_categories) > 0:
281
            for row_energy_category in rows_energy_categories:
282
                energy_category_set.add(row_energy_category[0])
283
284
        # query all energy categories in base period and reporting period
285
        cursor_system.execute(" SELECT id, name, unit_of_measure, kgce, kgco2e "
286
                              " FROM tbl_energy_categories "
287
                              " ORDER BY id ", )
288
        rows_energy_categories = cursor_system.fetchall()
289
        if rows_energy_categories is None or len(rows_energy_categories) == 0:
290
            if cursor_system:
291
                cursor_system.close()
292
            if cnx_system:
293
                cnx_system.close()
294
295
            if cursor_energy:
296
                cursor_energy.close()
297
            if cnx_energy:
298
                cnx_energy.close()
299
300
            if cursor_energy_plan:
301
                cursor_energy_plan.close()
302
            if cnx_energy_plan:
303
                cnx_energy_plan.close()
304
305
            if cursor_historical:
306
                cursor_historical.close()
307
            if cnx_historical:
308
                cnx_historical.close()
309
            raise falcon.HTTPError(status=falcon.HTTP_404,
310
                                   title='API.NOT_FOUND',
311
                                   description='API.ENERGY_CATEGORY_NOT_FOUND')
312
        energy_category_dict = dict()
313
        for row_energy_category in rows_energy_categories:
314
            if row_energy_category[0] in energy_category_set:
315
                energy_category_dict[row_energy_category[0]] = {"name": row_energy_category[1],
316
                                                                "unit_of_measure": row_energy_category[2],
317
                                                                "kgce": row_energy_category[3],
318
                                                                "kgco2e": row_energy_category[4]}
319
320
        ################################################################################################################
321
        # Step 4: query associated sensors
322
        ################################################################################################################
323
        point_list = list()
324
        cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type  "
325
                              " FROM tbl_tenants t, tbl_sensors s, tbl_tenants_sensors ts, "
326
                              "      tbl_points p, tbl_sensors_points sp "
327
                              " WHERE t.id = %s AND t.id = ts.tenant_id AND ts.sensor_id = s.id "
328
                              "       AND s.id = sp.sensor_id AND sp.point_id = p.id "
329
                              " ORDER BY p.id ", (tenant['id'],))
330
        rows_points = cursor_system.fetchall()
331
        if rows_points is not None and len(rows_points) > 0:
332
            for row in rows_points:
333
                point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]})
334
335
        ################################################################################################################
336
        # Step 5: query associated points
337
        ################################################################################################################
338
        cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type  "
339
                              " FROM tbl_tenants t, tbl_tenants_points tp, tbl_points p "
340
                              " WHERE t.id = %s AND t.id = tp.tenant_id AND tp.point_id = p.id "
341
                              " ORDER BY p.id ", (tenant['id'],))
342
        rows_points = cursor_system.fetchall()
343
        if rows_points is not None and len(rows_points) > 0:
344
            for row in rows_points:
345
                point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]})
346
347
        ################################################################################################################
348
        # Step 6: query base period energy saving
349
        ################################################################################################################
350
        base = dict()
351
        if energy_category_set is not None and len(energy_category_set) > 0:
352
            for energy_category_id in energy_category_set:
353
                kgce = energy_category_dict[energy_category_id]['kgce']
354
                kgco2e = energy_category_dict[energy_category_id]['kgco2e']
355
356
                base[energy_category_id] = dict()
357
                base[energy_category_id]['timestamps'] = list()
358
                base[energy_category_id]['values_plan'] = list()
359
                base[energy_category_id]['values_actual'] = list()
360
                base[energy_category_id]['values_saving'] = list()
361
                base[energy_category_id]['subtotal_plan'] = Decimal(0.0)
362
                base[energy_category_id]['subtotal_actual'] = Decimal(0.0)
363
                base[energy_category_id]['subtotal_saving'] = Decimal(0.0)
364
                base[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0)
365
                base[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0)
366
                base[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0)
367
                base[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0)
368
                base[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0)
369
                base[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0)
370
                # query base period's energy plan
371
                cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value "
372
                                           " FROM tbl_tenant_input_category_hourly "
373
                                           " WHERE tenant_id = %s "
374
                                           "     AND energy_category_id = %s "
375
                                           "     AND start_datetime_utc >= %s "
376
                                           "     AND start_datetime_utc < %s "
377
                                           " ORDER BY start_datetime_utc ",
378
                                           (tenant['id'],
379
                                            energy_category_id,
380
                                            base_start_datetime_utc,
381
                                            base_end_datetime_utc))
382
                rows_tenant_hourly = cursor_energy_plan.fetchall()
383
384
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
385
                                                                                     base_start_datetime_utc,
386
                                                                                     base_end_datetime_utc,
387
                                                                                     period_type)
388
                for row_tenant_periodically in rows_tenant_periodically:
389
                    current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \
390
                                             timedelta(minutes=timezone_offset)
391
                    if period_type == 'hourly':
392
                        current_datetime = current_datetime_local.isoformat()[0:19]
393
                    elif period_type == 'daily':
394
                        current_datetime = current_datetime_local.isoformat()[0:10]
395
                    elif period_type == 'weekly':
396
                        current_datetime = current_datetime_local.isoformat()[0:10]
397
                    elif period_type == 'monthly':
398
                        current_datetime = current_datetime_local.isoformat()[0:7]
399
                    elif period_type == 'yearly':
400
                        current_datetime = current_datetime_local.isoformat()[0:4]
401
402
                    plan_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1]
403
                    base[energy_category_id]['timestamps'].append(current_datetime)
0 ignored issues
show
The variable current_datetime does not seem to be defined for all execution paths.
Loading history...
404
                    base[energy_category_id]['values_plan'].append(plan_value)
405
                    base[energy_category_id]['subtotal_plan'] += plan_value
406
                    base[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce
407
                    base[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e
408
409
                # query base period's energy actual
410
                cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
411
                                      " FROM tbl_tenant_input_category_hourly "
412
                                      " WHERE tenant_id = %s "
413
                                      "     AND energy_category_id = %s "
414
                                      "     AND start_datetime_utc >= %s "
415
                                      "     AND start_datetime_utc < %s "
416
                                      " ORDER BY start_datetime_utc ",
417
                                      (tenant['id'],
418
                                       energy_category_id,
419
                                       base_start_datetime_utc,
420
                                       base_end_datetime_utc))
421
                rows_tenant_hourly = cursor_energy.fetchall()
422
423
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
424
                                                                                     base_start_datetime_utc,
425
                                                                                     base_end_datetime_utc,
426
                                                                                     period_type)
427
                for row_tenant_periodically in rows_tenant_periodically:
428
                    current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \
429
                                             timedelta(minutes=timezone_offset)
430
                    if period_type == 'hourly':
431
                        current_datetime = current_datetime_local.isoformat()[0:19]
432
                    elif period_type == 'daily':
433
                        current_datetime = current_datetime_local.isoformat()[0:10]
434
                    elif period_type == 'weekly':
435
                        current_datetime = current_datetime_local.isoformat()[0:10]
436
                    elif period_type == 'monthly':
437
                        current_datetime = current_datetime_local.isoformat()[0:7]
438
                    elif period_type == 'yearly':
439
                        current_datetime = current_datetime_local.isoformat()[0:4]
440
441
                    actual_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1]
442
                    base[energy_category_id]['values_actual'].append(actual_value)
443
                    base[energy_category_id]['subtotal_actual'] += actual_value
444
                    base[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce
445
                    base[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e
446
447
                # calculate base period's energy saving
448
                for i in range(len(base[energy_category_id]['values_plan'])):
449
                    base[energy_category_id]['values_saving'].append(
450
                        base[energy_category_id]['values_plan'][i] -
451
                        base[energy_category_id]['values_actual'][i])
452
453
                base[energy_category_id]['subtotal_saving'] = \
454
                    base[energy_category_id]['subtotal_plan'] - \
455
                    base[energy_category_id]['subtotal_actual']
456
                base[energy_category_id]['subtotal_in_kgce_saving'] = \
457
                    base[energy_category_id]['subtotal_in_kgce_plan'] - \
458
                    base[energy_category_id]['subtotal_in_kgce_actual']
459
                base[energy_category_id]['subtotal_in_kgco2e_saving'] = \
460
                    base[energy_category_id]['subtotal_in_kgco2e_plan'] - \
461
                    base[energy_category_id]['subtotal_in_kgco2e_actual']
462
        ################################################################################################################
463
        # Step 7: query reporting period energy saving
464
        ################################################################################################################
465
        reporting = dict()
466
        if energy_category_set is not None and len(energy_category_set) > 0:
467
            for energy_category_id in energy_category_set:
468
                kgce = energy_category_dict[energy_category_id]['kgce']
469
                kgco2e = energy_category_dict[energy_category_id]['kgco2e']
470
471
                reporting[energy_category_id] = dict()
472
                reporting[energy_category_id]['timestamps'] = list()
473
                reporting[energy_category_id]['values_plan'] = list()
474
                reporting[energy_category_id]['values_actual'] = list()
475
                reporting[energy_category_id]['values_saving'] = list()
476
                reporting[energy_category_id]['subtotal_plan'] = Decimal(0.0)
477
                reporting[energy_category_id]['subtotal_actual'] = Decimal(0.0)
478
                reporting[energy_category_id]['subtotal_saving'] = Decimal(0.0)
479
                reporting[energy_category_id]['subtotal_in_kgce_plan'] = Decimal(0.0)
480
                reporting[energy_category_id]['subtotal_in_kgce_actual'] = Decimal(0.0)
481
                reporting[energy_category_id]['subtotal_in_kgce_saving'] = Decimal(0.0)
482
                reporting[energy_category_id]['subtotal_in_kgco2e_plan'] = Decimal(0.0)
483
                reporting[energy_category_id]['subtotal_in_kgco2e_actual'] = Decimal(0.0)
484
                reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = Decimal(0.0)
485
                # query reporting period's energy plan
486
                cursor_energy_plan.execute(" SELECT start_datetime_utc, actual_value "
487
                                           " FROM tbl_tenant_input_category_hourly "
488
                                           " WHERE tenant_id = %s "
489
                                           "     AND energy_category_id = %s "
490
                                           "     AND start_datetime_utc >= %s "
491
                                           "     AND start_datetime_utc < %s "
492
                                           " ORDER BY start_datetime_utc ",
493
                                           (tenant['id'],
494
                                            energy_category_id,
495
                                            reporting_start_datetime_utc,
496
                                            reporting_end_datetime_utc))
497
                rows_tenant_hourly = cursor_energy_plan.fetchall()
498
499
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
500
                                                                                     reporting_start_datetime_utc,
501
                                                                                     reporting_end_datetime_utc,
502
                                                                                     period_type)
503
                for row_tenant_periodically in rows_tenant_periodically:
504
                    current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \
505
                                             timedelta(minutes=timezone_offset)
506
                    if period_type == 'hourly':
507
                        current_datetime = current_datetime_local.isoformat()[0:19]
508
                    elif period_type == 'daily':
509
                        current_datetime = current_datetime_local.isoformat()[0:10]
510
                    elif period_type == 'weekly':
511
                        current_datetime = current_datetime_local.isoformat()[0:10]
512
                    elif period_type == 'monthly':
513
                        current_datetime = current_datetime_local.isoformat()[0:7]
514
                    elif period_type == 'yearly':
515
                        current_datetime = current_datetime_local.isoformat()[0:4]
516
517
                    plan_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1]
518
                    reporting[energy_category_id]['timestamps'].append(current_datetime)
519
                    reporting[energy_category_id]['values_plan'].append(plan_value)
520
                    reporting[energy_category_id]['subtotal_plan'] += plan_value
521
                    reporting[energy_category_id]['subtotal_in_kgce_plan'] += plan_value * kgce
522
                    reporting[energy_category_id]['subtotal_in_kgco2e_plan'] += plan_value * kgco2e
523
524
                # query reporting period's energy actual
525
                cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
526
                                      " FROM tbl_tenant_input_category_hourly "
527
                                      " WHERE tenant_id = %s "
528
                                      "     AND energy_category_id = %s "
529
                                      "     AND start_datetime_utc >= %s "
530
                                      "     AND start_datetime_utc < %s "
531
                                      " ORDER BY start_datetime_utc ",
532
                                      (tenant['id'],
533
                                       energy_category_id,
534
                                       reporting_start_datetime_utc,
535
                                       reporting_end_datetime_utc))
536
                rows_tenant_hourly = cursor_energy.fetchall()
537
538
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
539
                                                                                     reporting_start_datetime_utc,
540
                                                                                     reporting_end_datetime_utc,
541
                                                                                     period_type)
542
                for row_tenant_periodically in rows_tenant_periodically:
543
                    current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \
544
                                             timedelta(minutes=timezone_offset)
545
                    if period_type == 'hourly':
546
                        current_datetime = current_datetime_local.isoformat()[0:19]
547
                    elif period_type == 'daily':
548
                        current_datetime = current_datetime_local.isoformat()[0:10]
549
                    elif period_type == 'weekly':
550
                        current_datetime = current_datetime_local.isoformat()[0:10]
551
                    elif period_type == 'monthly':
552
                        current_datetime = current_datetime_local.isoformat()[0:7]
553
                    elif period_type == 'yearly':
554
                        current_datetime = current_datetime_local.isoformat()[0:4]
555
556
                    actual_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1]
557
                    reporting[energy_category_id]['values_actual'].append(actual_value)
558
                    reporting[energy_category_id]['subtotal_actual'] += actual_value
559
                    reporting[energy_category_id]['subtotal_in_kgce_actual'] += actual_value * kgce
560
                    reporting[energy_category_id]['subtotal_in_kgco2e_actual'] += actual_value * kgco2e
561
562
                # calculate reporting period's energy savings
563
                for i in range(len(reporting[energy_category_id]['values_plan'])):
564
                    reporting[energy_category_id]['values_saving'].append(
565
                        reporting[energy_category_id]['values_plan'][i] -
566
                        reporting[energy_category_id]['values_actual'][i])
567
568
                reporting[energy_category_id]['subtotal_saving'] = \
569
                    reporting[energy_category_id]['subtotal_plan'] - \
570
                    reporting[energy_category_id]['subtotal_actual']
571
                reporting[energy_category_id]['subtotal_in_kgce_saving'] = \
572
                    reporting[energy_category_id]['subtotal_in_kgce_plan'] - \
573
                    reporting[energy_category_id]['subtotal_in_kgce_actual']
574
                reporting[energy_category_id]['subtotal_in_kgco2e_saving'] = \
575
                    reporting[energy_category_id]['subtotal_in_kgco2e_plan'] - \
576
                    reporting[energy_category_id]['subtotal_in_kgco2e_actual']
577
        ################################################################################################################
578
        # Step 8: query tariff data
579
        ################################################################################################################
580
        parameters_data = dict()
581
        parameters_data['names'] = list()
582
        parameters_data['timestamps'] = list()
583
        parameters_data['values'] = list()
584
        if config.is_tariff_appended and energy_category_set is not None and len(energy_category_set) > 0 \
585
                and not is_quick_mode:
586
            for energy_category_id in energy_category_set:
587
                energy_category_tariff_dict = utilities.get_energy_category_tariffs(tenant['cost_center_id'],
588
                                                                                    energy_category_id,
589
                                                                                    reporting_start_datetime_utc,
590
                                                                                    reporting_end_datetime_utc)
591
                tariff_timestamp_list = list()
592
                tariff_value_list = list()
593
                for k, v in energy_category_tariff_dict.items():
594
                    # convert k from utc to local
595
                    k = k + timedelta(minutes=timezone_offset)
596
                    tariff_timestamp_list.append(k.isoformat()[0:19])
597
                    tariff_value_list.append(v)
598
599
                parameters_data['names'].append(_('Tariff') + '-' + energy_category_dict[energy_category_id]['name'])
600
                parameters_data['timestamps'].append(tariff_timestamp_list)
601
                parameters_data['values'].append(tariff_value_list)
602
603
        ################################################################################################################
604
        # Step 9: query associated sensors and points data
605
        ################################################################################################################
606
        if not is_quick_mode:
607
            for point in point_list:
608
                point_values = []
609
                point_timestamps = []
610
                if point['object_type'] == 'ENERGY_VALUE':
611
                    query = (" SELECT utc_date_time, actual_value "
612
                             " FROM tbl_energy_value "
613
                             " WHERE point_id = %s "
614
                             "       AND utc_date_time BETWEEN %s AND %s "
615
                             " ORDER BY utc_date_time ")
616
                    cursor_historical.execute(query, (point['id'],
617
                                                      reporting_start_datetime_utc,
618
                                                      reporting_end_datetime_utc))
619
                    rows = cursor_historical.fetchall()
620
621
                    if rows is not None and len(rows) > 0:
622
                        for row in rows:
623
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
624
                                                     timedelta(minutes=timezone_offset)
625
                            current_datetime = current_datetime_local.isoformat()[0:19]
626
                            point_timestamps.append(current_datetime)
627
                            point_values.append(row[1])
628
                elif point['object_type'] == 'ANALOG_VALUE':
629
                    query = (" SELECT utc_date_time, actual_value "
630
                             " FROM tbl_analog_value "
631
                             " WHERE point_id = %s "
632
                             "       AND utc_date_time BETWEEN %s AND %s "
633
                             " ORDER BY utc_date_time ")
634
                    cursor_historical.execute(query, (point['id'],
635
                                                      reporting_start_datetime_utc,
636
                                                      reporting_end_datetime_utc))
637
                    rows = cursor_historical.fetchall()
638
639
                    if rows is not None and len(rows) > 0:
640
                        for row in rows:
641
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
642
                                                     timedelta(minutes=timezone_offset)
643
                            current_datetime = current_datetime_local.isoformat()[0:19]
644
                            point_timestamps.append(current_datetime)
645
                            point_values.append(row[1])
646
                elif point['object_type'] == 'DIGITAL_VALUE':
647
                    query = (" SELECT utc_date_time, actual_value "
648
                             " FROM tbl_digital_value "
649
                             " WHERE point_id = %s "
650
                             "       AND utc_date_time BETWEEN %s AND %s "
651
                             " ORDER BY utc_date_time ")
652
                    cursor_historical.execute(query, (point['id'],
653
                                                      reporting_start_datetime_utc,
654
                                                      reporting_end_datetime_utc))
655
                    rows = cursor_historical.fetchall()
656
657
                    if rows is not None and len(rows) > 0:
658
                        for row in rows:
659
                            current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
660
                                                     timedelta(minutes=timezone_offset)
661
                            current_datetime = current_datetime_local.isoformat()[0:19]
662
                            point_timestamps.append(current_datetime)
663
                            point_values.append(row[1])
664
665
                parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')')
666
                parameters_data['timestamps'].append(point_timestamps)
667
                parameters_data['values'].append(point_values)
668
669
        ################################################################################################################
670
        # Step 10: construct the report
671
        ################################################################################################################
672
        if cursor_system:
673
            cursor_system.close()
674
        if cnx_system:
675
            cnx_system.close()
676
677
        if cursor_energy:
678
            cursor_energy.close()
679
        if cnx_energy:
680
            cnx_energy.close()
681
682
        if cursor_energy_plan:
683
            cursor_energy_plan.close()
684
        if cnx_energy_plan:
685
            cnx_energy_plan.close()
686
687
        if cursor_historical:
688
            cursor_historical.close()
689
        if cnx_historical:
690
            cnx_historical.close()
691
692
        result = dict()
693
694
        result['tenant'] = dict()
695
        result['tenant']['name'] = tenant['name']
696
        result['tenant']['area'] = tenant['area']
697
698
        result['base_period'] = dict()
699
        result['base_period']['names'] = list()
700
        result['base_period']['units'] = list()
701
        result['base_period']['timestamps'] = list()
702
        result['base_period']['values_saving'] = list()
703
        result['base_period']['subtotals_saving'] = list()
704
        result['base_period']['subtotals_in_kgce_saving'] = list()
705
        result['base_period']['subtotals_in_kgco2e_saving'] = list()
706
        result['base_period']['total_in_kgce_saving'] = Decimal(0.0)
707
        result['base_period']['total_in_kgco2e_saving'] = Decimal(0.0)
708
        if energy_category_set is not None and len(energy_category_set) > 0:
709
            for energy_category_id in energy_category_set:
710
                result['base_period']['names'].append(energy_category_dict[energy_category_id]['name'])
711
                result['base_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure'])
712
                result['base_period']['timestamps'].append(base[energy_category_id]['timestamps'])
713
                result['base_period']['values_saving'].append(base[energy_category_id]['values_saving'])
714
                result['base_period']['subtotals_saving'].append(base[energy_category_id]['subtotal_saving'])
715
                result['base_period']['subtotals_in_kgce_saving'].append(
716
                    base[energy_category_id]['subtotal_in_kgce_saving'])
717
                result['base_period']['subtotals_in_kgco2e_saving'].append(
718
                    base[energy_category_id]['subtotal_in_kgco2e_saving'])
719
                result['base_period']['total_in_kgce_saving'] += base[energy_category_id]['subtotal_in_kgce_saving']
720
                result['base_period']['total_in_kgco2e_saving'] += base[energy_category_id]['subtotal_in_kgco2e_saving']
721
722
        result['reporting_period'] = dict()
723
        result['reporting_period']['names'] = list()
724
        result['reporting_period']['energy_category_ids'] = list()
725
        result['reporting_period']['units'] = list()
726
        result['reporting_period']['timestamps'] = list()
727
        result['reporting_period']['values_saving'] = list()
728
        result['reporting_period']['rates_saving'] = list()
729
        result['reporting_period']['subtotals_saving'] = list()
730
        result['reporting_period']['subtotals_in_kgce_saving'] = list()
731
        result['reporting_period']['subtotals_in_kgco2e_saving'] = list()
732
        result['reporting_period']['subtotals_per_unit_area_saving'] = list()
733
        result['reporting_period']['increment_rates_saving'] = list()
734
        result['reporting_period']['total_in_kgce_saving'] = Decimal(0.0)
735
        result['reporting_period']['total_in_kgco2e_saving'] = Decimal(0.0)
736
        result['reporting_period']['increment_rate_in_kgce_saving'] = Decimal(0.0)
737
        result['reporting_period']['increment_rate_in_kgco2e_saving'] = Decimal(0.0)
738
739
        if energy_category_set is not None and len(energy_category_set) > 0:
740
            for energy_category_id in energy_category_set:
741
                result['reporting_period']['names'].append(energy_category_dict[energy_category_id]['name'])
742
                result['reporting_period']['energy_category_ids'].append(energy_category_id)
743
                result['reporting_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure'])
744
                result['reporting_period']['timestamps'].append(reporting[energy_category_id]['timestamps'])
745
                result['reporting_period']['values_saving'].append(reporting[energy_category_id]['values_saving'])
746
                result['reporting_period']['subtotals_saving'].append(reporting[energy_category_id]['subtotal_saving'])
747
                result['reporting_period']['subtotals_in_kgce_saving'].append(
748
                    reporting[energy_category_id]['subtotal_in_kgce_saving'])
749
                result['reporting_period']['subtotals_in_kgco2e_saving'].append(
750
                    reporting[energy_category_id]['subtotal_in_kgco2e_saving'])
751
                result['reporting_period']['subtotals_per_unit_area_saving'].append(
752
                    reporting[energy_category_id]['subtotal_saving'] / tenant['area']
753
                    if tenant['area'] != Decimal(0.0) else None)
754
                result['reporting_period']['increment_rates_saving'].append(
755
                    (reporting[energy_category_id]['subtotal_saving'] - base[energy_category_id]['subtotal_saving']) /
756
                    base[energy_category_id]['subtotal_saving']
757
                    if base[energy_category_id]['subtotal_saving'] != Decimal(0.0) else None)
758
                result['reporting_period']['total_in_kgce_saving'] += \
759
                    reporting[energy_category_id]['subtotal_in_kgce_saving']
760
                result['reporting_period']['total_in_kgco2e_saving'] += \
761
                    reporting[energy_category_id]['subtotal_in_kgco2e_saving']
762
763
                rate = list()
764
                for index, value in enumerate(reporting[energy_category_id]['values_saving']):
765
                    if index < len(base[energy_category_id]['values_saving']) \
766
                            and base[energy_category_id]['values_saving'][index] != Decimal(0.0) \
767
                            and value != Decimal(0.0):
768
                        rate.append((value - base[energy_category_id]['values_saving'][index])
769
                                    / base[energy_category_id]['values_saving'][index])
770
                    else:
771
                        rate.append(None)
772
                result['reporting_period']['rates_saving'].append(rate)
773
774
        result['reporting_period']['total_in_kgco2e_per_unit_area_saving'] = \
775
            result['reporting_period']['total_in_kgce_saving'] / tenant['area'] \
776
            if tenant['area'] != Decimal(0.0) else None
777
778
        result['reporting_period']['increment_rate_in_kgce_saving'] = \
779
            (result['reporting_period']['total_in_kgce_saving'] - result['base_period']['total_in_kgce_saving']) / \
780
            result['base_period']['total_in_kgce_saving'] \
781
            if result['base_period']['total_in_kgce_saving'] != Decimal(0.0) else None
782
783
        result['reporting_period']['total_in_kgce_per_unit_area_saving'] = \
784
            result['reporting_period']['total_in_kgco2e_saving'] / tenant['area'] if tenant['area'] > 0.0 else None
785
786
        result['reporting_period']['increment_rate_in_kgco2e_saving'] = \
787
            (result['reporting_period']['total_in_kgco2e_saving'] - result['base_period']['total_in_kgco2e_saving']) / \
788
            result['base_period']['total_in_kgco2e_saving'] \
789
            if result['base_period']['total_in_kgco2e_saving'] != Decimal(0.0) else None
790
791
        result['parameters'] = {
792
            "names": parameters_data['names'],
793
            "timestamps": parameters_data['timestamps'],
794
            "values": parameters_data['values']
795
        }
796
797
        # export result to Excel file and then encode the file to base64 string
798
        if not is_quick_mode:
799
            result['excel_bytes_base64'] = excelexporters.tenantplan.export(result,
800
                                                                            tenant['name'],
801
                                                                            base_period_start_datetime_local,
802
                                                                            base_period_end_datetime_local,
803
                                                                            reporting_period_start_datetime_local,
804
                                                                            reporting_period_end_datetime_local,
805
                                                                            period_type,
806
                                                                            language)
807
808
        resp.text = json.dumps(result)
809