reports.virtualmetersaving   F
last analyzed

Complexity

Total Complexity 89

Size/Duplication

Total Lines 553
Duplicated Lines 91.86 %

Importance

Changes 0
Metric Value
wmc 89
eloc 393
dl 508
loc 553
rs 2
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A Reporting.__init__() 3 3 1
A Reporting.on_options() 4 4 1
F Reporting.on_get() 489 489 87

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like reports.virtualmetersaving 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
Virtual Meter Saving Report API
3
4
This module provides REST API endpoints for generating virtual meter saving reports.
5
It analyzes energy savings achieved through virtual meter optimization and efficiency
6
improvements, providing insights into saving opportunities and ROI calculations.
7
8
Key Features:
9
- Virtual meter 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
- Virtual meter saving summary
18
- Base period comparison data
19
- Saving calculation details
20
- ROI analysis and metrics
21
- Saving optimization recommendations
22
- Cost-benefit analysis
23
24
The module uses Falcon framework for REST API and includes:
25
- Database queries for saving data
26
- Saving calculation algorithms
27
- ROI analysis tools
28
- Excel export via excelexporters
29
- Multi-language support
30
- User authentication and authorization
31
"""
32
33
import re
34
from datetime import datetime, timedelta, timezone
35
from decimal import Decimal
36
import falcon
37
import mysql.connector
38
import simplejson as json
39
import config
40
import excelexporters.virtualmetersaving
41
from core import utilities
42
from core.useractivity import access_control, api_key_control
43
44
45 View Code Duplication
class Reporting:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
46
    def __init__(self):
47
        """"Initializes Reporting"""
48
        pass
49
50
    @staticmethod
51
    def on_options(req, resp):
52
        _ = req
53
        resp.status = falcon.HTTP_200
54
55
    ####################################################################################################################
56
    # PROCEDURES
57
    # Step 1: valid parameters
58
    # Step 2: query the virtual meter and energy category
59
    # Step 3: query base period energy saving
60
    # Step 4: query reporting period energy saving
61
    # Step 5: query tariff data
62
    # Step 6: construct the report
63
    ####################################################################################################################
64
    @staticmethod
65
    def on_get(req, resp):
66
        if 'API-KEY' not in req.headers or \
67
                not isinstance(req.headers['API-KEY'], str) or \
68
                len(str.strip(req.headers['API-KEY'])) == 0:
69
            access_control(req)
70
        else:
71
            api_key_control(req)
72
        print(req.params)
73
        virtual_meter_id = req.params.get('virtualmeterid')
74
        virtual_meter_uuid = req.params.get('virtualmeteruuid')
75
        period_type = req.params.get('periodtype')
76
        base_period_start_datetime_local = req.params.get('baseperiodstartdatetime')
77
        base_period_end_datetime_local = req.params.get('baseperiodenddatetime')
78
        reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime')
79
        reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime')
80
        language = req.params.get('language')
81
        quick_mode = req.params.get('quickmode')
82
83
        ################################################################################################################
84
        # Step 1: valid parameters
85
        ################################################################################################################
86
        if virtual_meter_id is None and virtual_meter_uuid is None:
87
            raise falcon.HTTPError(status=falcon.HTTP_400,
88
                                   title='API.BAD_REQUEST',
89
                                   description='API.INVALID_VIRTUAL_METER_ID')
90
91
        if virtual_meter_id is not None:
92
            virtual_meter_id = str.strip(virtual_meter_id)
93
            if not virtual_meter_id.isdigit() or int(virtual_meter_id) <= 0:
94
                raise falcon.HTTPError(status=falcon.HTTP_400,
95
                                       title='API.BAD_REQUEST',
96
                                       description='API.INVALID_VIRTUAL_METER_ID')
97
98
        if virtual_meter_uuid is not None:
99
            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)
100
            match = regex.match(str.strip(virtual_meter_uuid))
101
            if not bool(match):
102
                raise falcon.HTTPError(status=falcon.HTTP_400,
103
                                       title='API.BAD_REQUEST',
104
                                       description='API.INVALID_VIRTUAL_METER_UUID')
105
106
        if period_type is None:
107
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
108
                                   description='API.INVALID_PERIOD_TYPE')
109
        else:
110
            period_type = str.strip(period_type)
111
            if period_type not in ['hourly', 'daily', 'weekly', 'monthly', 'yearly']:
112
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
113
                                       description='API.INVALID_PERIOD_TYPE')
114
115
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
116
        if config.utc_offset[0] == '-':
117
            timezone_offset = -timezone_offset
118
119
        base_start_datetime_utc = None
120
        if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0:
121
            base_period_start_datetime_local = str.strip(base_period_start_datetime_local)
122
            try:
123
                base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S')
124
            except ValueError:
125
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
126
                                       description="API.INVALID_BASE_PERIOD_START_DATETIME")
127
            base_start_datetime_utc = \
128
                base_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
129
            # nomalize the start datetime
130
            if config.minutes_to_count == 30 and base_start_datetime_utc.minute >= 30:
131
                base_start_datetime_utc = base_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
132
            else:
133
                base_start_datetime_utc = base_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
134
135
        base_end_datetime_utc = None
136
        if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0:
137
            base_period_end_datetime_local = str.strip(base_period_end_datetime_local)
138
            try:
139
                base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S')
140
            except ValueError:
141
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
142
                                       description="API.INVALID_BASE_PERIOD_END_DATETIME")
143
            base_end_datetime_utc = \
144
                base_end_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
145
146
        if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \
147
                base_start_datetime_utc >= base_end_datetime_utc:
148
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
149
                                   description='API.INVALID_BASE_PERIOD_END_DATETIME')
150
151
        if reporting_period_start_datetime_local is None:
152
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
153
                                   description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
154
        else:
155
            reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local)
156
            try:
157
                reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local,
158
                                                                 '%Y-%m-%dT%H:%M:%S')
159
            except ValueError:
160
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
161
                                       description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
162
            reporting_start_datetime_utc = \
163
                reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
164
            # nomalize the start datetime
165
            if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30:
166
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
167
            else:
168
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
169
170
        if reporting_period_end_datetime_local is None:
171
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
172
                                   description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
173
        else:
174
            reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local)
175
            try:
176
                reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local,
177
                                                               '%Y-%m-%dT%H:%M:%S')
178
            except ValueError:
179
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
180
                                       description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
181
            reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \
182
                timedelta(minutes=timezone_offset)
183
184
        if reporting_start_datetime_utc >= reporting_end_datetime_utc:
185
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
186
                                   description='API.INVALID_REPORTING_PERIOD_END_DATETIME')
187
188
        # if turn quick mode on, do not return parameters data and excel file
189
        is_quick_mode = False
190
        if quick_mode is not None and \
191
                len(str.strip(quick_mode)) > 0 and \
192
                str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'):
193
            is_quick_mode = True
194
195
        trans = utilities.get_translation(language)
196
        trans.install()
197
        _ = trans.gettext
198
199
        ################################################################################################################
200
        # Step 2: query the virtual meter and energy category
201
        ################################################################################################################
202
        cnx_system = mysql.connector.connect(**config.myems_system_db)
203
        cursor_system = cnx_system.cursor()
204
205
        cnx_energy = mysql.connector.connect(**config.myems_energy_db)
206
        cursor_energy = cnx_energy.cursor()
207
208
        cnx_energy_baseline = mysql.connector.connect(**config.myems_energy_baseline_db)
209
        cursor_energy_baseline = cnx_energy_baseline.cursor()
210
211
        cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, "
212
                              "        ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e "
213
                              " FROM tbl_virtual_meters m, tbl_energy_categories ec "
214
                              " WHERE m.id = %s AND m.energy_category_id = ec.id ", (virtual_meter_id,))
215
        row_virtual_meter = cursor_system.fetchone()
216
        if row_virtual_meter is None:
217
            if cursor_system:
218
                cursor_system.close()
219
            if cnx_system:
220
                cnx_system.close()
221
222
            if cursor_energy:
223
                cursor_energy.close()
224
            if cnx_energy:
225
                cnx_energy.close()
226
227
            if cursor_energy_baseline:
228
                cursor_energy_baseline.close()
229
            if cnx_energy_baseline:
230
                cnx_energy_baseline.close()
231
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
232
                                   description='API.VIRTUAL_METER_NOT_FOUND')
233
234
        virtual_meter = dict()
235
        virtual_meter['id'] = row_virtual_meter[0]
236
        virtual_meter['name'] = row_virtual_meter[1]
237
        virtual_meter['cost_center_id'] = row_virtual_meter[2]
238
        virtual_meter['energy_category_id'] = row_virtual_meter[3]
239
        virtual_meter['energy_category_name'] = row_virtual_meter[4]
240
        virtual_meter['unit_of_measure'] = row_virtual_meter[5]
241
        virtual_meter['kgce'] = row_virtual_meter[6]
242
        virtual_meter['kgco2e'] = row_virtual_meter[7]
243
244
        ################################################################################################################
245
        # Step 3: query base period energy saving
246
        ################################################################################################################
247
        base = dict()
248
        base['timestamps'] = list()
249
        base['values_baseline'] = list()
250
        base['values_actual'] = list()
251
        base['values_saving'] = list()
252
        base['total_in_category_baseline'] = Decimal(0.0)
253
        base['total_in_category_actual'] = Decimal(0.0)
254
        base['total_in_category_saving'] = Decimal(0.0)
255
        base['total_in_kgce_baseline'] = Decimal(0.0)
256
        base['total_in_kgce_actual'] = Decimal(0.0)
257
        base['total_in_kgce_saving'] = Decimal(0.0)
258
        base['total_in_kgco2e_baseline'] = Decimal(0.0)
259
        base['total_in_kgco2e_actual'] = Decimal(0.0)
260
        base['total_in_kgco2e_saving'] = Decimal(0.0)
261
262
        # query base period baseline
263
        cursor_energy_baseline.execute(" SELECT start_datetime_utc, actual_value "
264
                                       " FROM tbl_virtual_meter_hourly "
265
                                       " WHERE virtual_meter_id = %s "
266
                                       " AND start_datetime_utc >= %s "
267
                                       " AND start_datetime_utc < %s "
268
                                       " ORDER BY start_datetime_utc ",
269
                                       (virtual_meter['id'],
270
                                        base_start_datetime_utc,
271
                                        base_end_datetime_utc))
272
        rows_virtual_meter_hourly = cursor_energy_baseline.fetchall()
273
274
        rows_virtual_meter_periodically = \
275
            utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly,
276
                                                      base_start_datetime_utc,
277
                                                      base_end_datetime_utc,
278
                                                      period_type)
279
280
        for row_virtual_meter_periodically in rows_virtual_meter_periodically:
281
            current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \
282
                                     timedelta(minutes=timezone_offset)
283
            if period_type == 'hourly':
284
                current_datetime = current_datetime_local.isoformat()[0:19]
285
            elif period_type == 'daily':
286
                current_datetime = current_datetime_local.isoformat()[0:10]
287
            elif period_type == 'weekly':
288
                current_datetime = current_datetime_local.isoformat()[0:10]
289
            elif period_type == 'monthly':
290
                current_datetime = current_datetime_local.isoformat()[0:7]
291
            elif period_type == 'yearly':
292
                current_datetime = current_datetime_local.isoformat()[0:4]
293
294
            actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \
295
                else row_virtual_meter_periodically[1]
296
            base['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...
297
            base['values_baseline'].append(actual_value)
298
            base['total_in_category_baseline'] += actual_value
299
            base['total_in_kgce_baseline'] += actual_value * virtual_meter['kgce']
300
            base['total_in_kgco2e_baseline'] += actual_value * virtual_meter['kgco2e']
301
302
        # query base period actual
303
        cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
304
                              " FROM tbl_virtual_meter_hourly "
305
                              " WHERE virtual_meter_id = %s "
306
                              " AND start_datetime_utc >= %s "
307
                              " AND start_datetime_utc < %s "
308
                              " ORDER BY start_datetime_utc ",
309
                              (virtual_meter['id'],
310
                               base_start_datetime_utc,
311
                               base_end_datetime_utc))
312
        rows_virtual_meter_hourly = cursor_energy.fetchall()
313
314
        rows_virtual_meter_periodically = \
315
            utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly,
316
                                                      base_start_datetime_utc,
317
                                                      base_end_datetime_utc,
318
                                                      period_type)
319
320
        for row_virtual_meter_periodically in rows_virtual_meter_periodically:
321
            current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \
322
                                     timedelta(minutes=timezone_offset)
323
            if period_type == 'hourly':
324
                current_datetime = current_datetime_local.isoformat()[0:19]
325
            elif period_type == 'daily':
326
                current_datetime = current_datetime_local.isoformat()[0:10]
327
            elif period_type == 'weekly':
328
                current_datetime = current_datetime_local.isoformat()[0:10]
329
            elif period_type == 'monthly':
330
                current_datetime = current_datetime_local.isoformat()[0:7]
331
            elif period_type == 'yearly':
332
                current_datetime = current_datetime_local.isoformat()[0:4]
333
334
            actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \
335
                else row_virtual_meter_periodically[1]
336
            base['values_actual'].append(actual_value)
337
            base['total_in_category_actual'] += actual_value
338
            base['total_in_kgce_actual'] += actual_value * virtual_meter['kgce']
339
            base['total_in_kgco2e_actual'] += actual_value * virtual_meter['kgco2e']
340
341
        # calculate base period saving
342
        for i in range(len(base['values_baseline'])):
343
            base['values_saving'].append(base['values_baseline'][i] - base['values_actual'][i])
344
345
        base['total_in_category_saving'] = base['total_in_category_baseline'] - base['total_in_category_actual']
346
        base['total_in_kgce_saving'] = base['total_in_kgce_baseline'] - base['total_in_kgce_actual']
347
        base['total_in_kgco2e_saving'] = base['total_in_kgco2e_baseline'] - base['total_in_kgco2e_actual']
348
349
        ################################################################################################################
350
        # Step 3: query reporting period energy saving
351
        ################################################################################################################
352
        reporting = dict()
353
        reporting['timestamps'] = list()
354
        reporting['values_baseline'] = list()
355
        reporting['values_actual'] = list()
356
        reporting['values_saving'] = list()
357
        reporting['values_rates'] = list()
358
        reporting['total_in_category_baseline'] = Decimal(0.0)
359
        reporting['total_in_category_actual'] = Decimal(0.0)
360
        reporting['total_in_category_saving'] = Decimal(0.0)
361
        reporting['total_in_kgce_baseline'] = Decimal(0.0)
362
        reporting['total_in_kgce_actual'] = Decimal(0.0)
363
        reporting['total_in_kgce_saving'] = Decimal(0.0)
364
        reporting['total_in_kgco2e_baseline'] = Decimal(0.0)
365
        reporting['total_in_kgco2e_actual'] = Decimal(0.0)
366
        reporting['total_in_kgco2e_saving'] = Decimal(0.0)
367
        # query reporting period baseline
368
        cursor_energy_baseline.execute(" SELECT start_datetime_utc, actual_value "
369
                                       " FROM tbl_virtual_meter_hourly "
370
                                       " WHERE virtual_meter_id = %s "
371
                                       " AND start_datetime_utc >= %s "
372
                                       " AND start_datetime_utc < %s "
373
                                       " ORDER BY start_datetime_utc ",
374
                                       (virtual_meter['id'],
375
                                        reporting_start_datetime_utc,
376
                                        reporting_end_datetime_utc))
377
        rows_virtual_meter_hourly = cursor_energy_baseline.fetchall()
378
379
        rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly,
380
                                                                                    reporting_start_datetime_utc,
381
                                                                                    reporting_end_datetime_utc,
382
                                                                                    period_type)
383
384
        for row_virtual_meter_periodically in rows_virtual_meter_periodically:
385
            current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \
386
                                     timedelta(minutes=timezone_offset)
387
            if period_type == 'hourly':
388
                current_datetime = current_datetime_local.isoformat()[0:19]
389
            elif period_type == 'daily':
390
                current_datetime = current_datetime_local.isoformat()[0:10]
391
            elif period_type == 'weekly':
392
                current_datetime = current_datetime_local.isoformat()[0:10]
393
            elif period_type == 'monthly':
394
                current_datetime = current_datetime_local.isoformat()[0:7]
395
            elif period_type == 'yearly':
396
                current_datetime = current_datetime_local.isoformat()[0:4]
397
398
            actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \
399
                else row_virtual_meter_periodically[1]
400
401
            reporting['timestamps'].append(current_datetime)
402
            reporting['values_baseline'].append(actual_value)
403
            reporting['total_in_category_baseline'] += actual_value
404
            reporting['total_in_kgce_baseline'] += actual_value * virtual_meter['kgce']
405
            reporting['total_in_kgco2e_baseline'] += actual_value * virtual_meter['kgco2e']
406
407
        # query reporting period actual
408
        cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
409
                              " FROM tbl_virtual_meter_hourly "
410
                              " WHERE virtual_meter_id = %s "
411
                              " AND start_datetime_utc >= %s "
412
                              " AND start_datetime_utc < %s "
413
                              " ORDER BY start_datetime_utc ",
414
                              (virtual_meter['id'],
415
                               reporting_start_datetime_utc,
416
                               reporting_end_datetime_utc))
417
        rows_virtual_meter_hourly = cursor_energy.fetchall()
418
419
        rows_virtual_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_virtual_meter_hourly,
420
                                                                                    reporting_start_datetime_utc,
421
                                                                                    reporting_end_datetime_utc,
422
                                                                                    period_type)
423
424
        for row_virtual_meter_periodically in rows_virtual_meter_periodically:
425
            current_datetime_local = row_virtual_meter_periodically[0].replace(tzinfo=timezone.utc) + \
426
                                     timedelta(minutes=timezone_offset)
427
            if period_type == 'hourly':
428
                current_datetime = current_datetime_local.isoformat()[0:19]
429
            elif period_type == 'daily':
430
                current_datetime = current_datetime_local.isoformat()[0:10]
431
            elif period_type == 'weekly':
432
                current_datetime = current_datetime_local.isoformat()[0:10]
433
            elif period_type == 'monthly':
434
                current_datetime = current_datetime_local.isoformat()[0:7]
435
            elif period_type == 'yearly':
436
                current_datetime = current_datetime_local.isoformat()[0:4]
437
438
            actual_value = Decimal(0.0) if row_virtual_meter_periodically[1] is None \
439
                else row_virtual_meter_periodically[1]
440
441
            reporting['values_actual'].append(actual_value)
442
            reporting['total_in_category_actual'] += actual_value
443
            reporting['total_in_kgce_actual'] += actual_value * virtual_meter['kgce']
444
            reporting['total_in_kgco2e_actual'] += actual_value * virtual_meter['kgco2e']
445
446
        # calculate base period saving
447
        for i in range(len(reporting['values_baseline'])):
448
            reporting['values_saving'].append(reporting['values_baseline'][i] - reporting['values_actual'][i])
449
450
        reporting['total_in_category_saving'] = \
451
            reporting['total_in_category_baseline'] - reporting['total_in_category_actual']
452
        reporting['total_in_kgce_saving'] = \
453
            reporting['total_in_kgce_baseline'] - reporting['total_in_kgce_actual']
454
        reporting['total_in_kgco2e_saving'] = \
455
            reporting['total_in_kgco2e_baseline'] - reporting['total_in_kgco2e_actual']
456
457
        for index, value in enumerate(reporting['values_saving']):
458
            if index < len(base['values_saving']) and base['values_saving'][index] != 0 and value != 0:
459
                reporting['values_rates'].append((value - base['values_saving'][index]) / base['values_saving'][index])
460
            else:
461
                reporting['values_rates'].append(None)
462
463
        ################################################################################################################
464
        # Step 5: query tariff data
465
        ################################################################################################################
466
        parameters_data = dict()
467
        parameters_data['names'] = list()
468
        parameters_data['timestamps'] = list()
469
        parameters_data['values'] = list()
470
        if config.is_tariff_appended and not is_quick_mode:
471
            tariff_dict = utilities.get_energy_category_tariffs(virtual_meter['cost_center_id'],
472
                                                                virtual_meter['energy_category_id'],
473
                                                                reporting_start_datetime_utc,
474
                                                                reporting_end_datetime_utc)
475
            tariff_timestamp_list = list()
476
            tariff_value_list = list()
477
            for k, v in tariff_dict.items():
478
                # convert k from utc to local
479
                k = k + timedelta(minutes=timezone_offset)
480
                tariff_timestamp_list.append(k.isoformat()[0:19])
481
                tariff_value_list.append(v)
482
483
            parameters_data['names'].append(_('Tariff') + '-' + virtual_meter['energy_category_name'])
484
            parameters_data['timestamps'].append(tariff_timestamp_list)
485
            parameters_data['values'].append(tariff_value_list)
486
487
        ################################################################################################################
488
        # Step 6: construct the report
489
        ################################################################################################################
490
        if cursor_system:
491
            cursor_system.close()
492
        if cnx_system:
493
            cnx_system.close()
494
495
        if cursor_energy:
496
            cursor_energy.close()
497
        if cnx_energy:
498
            cnx_energy.close()
499
500
        if cursor_energy_baseline:
501
            cursor_energy_baseline.close()
502
        if cnx_energy_baseline:
503
            cnx_energy_baseline.close()
504
505
        result = {
506
            "virtual_meter": {
507
                "cost_center_id": virtual_meter['cost_center_id'],
508
                "energy_category_id": virtual_meter['energy_category_id'],
509
                "energy_category_name": virtual_meter['energy_category_name'],
510
                "unit_of_measure": virtual_meter['unit_of_measure'],
511
                "kgce": virtual_meter['kgce'],
512
                "kgco2e": virtual_meter['kgco2e'],
513
            },
514
            "base_period": {
515
                "total_in_category_saving": base['total_in_category_saving'],
516
                "total_in_kgce_saving": base['total_in_kgce_saving'],
517
                "total_in_kgco2e_saving": base['total_in_kgco2e_saving'],
518
                "timestamps": base['timestamps'],
519
                "values_saving": base['values_saving'],
520
            },
521
            "reporting_period": {
522
                "increment_rate_saving":
523
                    (reporting['total_in_category_saving'] - base['total_in_category_saving']) /
524
                    base['total_in_category_saving']
525
                    if base['total_in_category_saving'] != Decimal(0.0) else None,
526
                "total_in_category_saving": reporting['total_in_category_saving'],
527
                "total_in_kgce_saving": reporting['total_in_kgce_saving'],
528
                "total_in_kgco2e_saving": reporting['total_in_kgco2e_saving'],
529
                "timestamps": reporting['timestamps'],
530
                "values_saving": reporting['values_saving'],
531
                "values_rates": reporting['values_rates'],
532
            },
533
            "parameters": {
534
                "names": parameters_data['names'],
535
                "timestamps": parameters_data['timestamps'],
536
                "values": parameters_data['values']
537
            },
538
        }
539
540
        # export result to Excel file and then encode the file to base64 string
541
        if not is_quick_mode:
542
            result['excel_bytes_base64'] = \
543
                excelexporters.virtualmetersaving.export(result,
544
                                                         virtual_meter['name'],
545
                                                         base_period_start_datetime_local,
546
                                                         base_period_end_datetime_local,
547
                                                         reporting_period_start_datetime_local,
548
                                                         reporting_period_end_datetime_local,
549
                                                         period_type,
550
                                                         language)
551
552
        resp.text = json.dumps(result)
553