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

Complexity

Conditions 72

Size

Total Lines 444
Code Lines 318

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 72
eloc 318
nop 2
dl 0
loc 444
rs 0
c 0
b 0
f 0

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.metercost.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
import falcon
2
import simplejson as json
3
import mysql.connector
4
import config
5
from datetime import datetime, timedelta, timezone
6
from core import utilities
7
from decimal import Decimal
8
import excelexporters.metercost
9
10
11
class Reporting:
12
    @staticmethod
13
    def __init__():
14
        pass
15
16
    @staticmethod
17
    def on_options(req, resp):
18
        resp.status = falcon.HTTP_200
19
20
    ####################################################################################################################
21
    # PROCEDURES
22
    # Step 1: valid parameters
23
    # Step 2: query the meter and energy category
24
    # Step 3: query associated points
25
    # Step 4: query base period energy consumption
26
    # Step 5: query base period energy cost
27
    # Step 6: query reporting period energy consumption
28
    # Step 7: query reporting period energy cost
29
    # Step 8: query tariff data
30
    # Step 9: query associated points data
31
    # Step 10: construct the report
32
    ####################################################################################################################
33
    @staticmethod
34
    def on_get(req, resp):
35
        print(req.params)
36
        meter_id = req.params.get('meterid')
37
        period_type = req.params.get('periodtype')
38
        base_period_start_datetime_local = req.params.get('baseperiodstartdatetime')
39
        base_period_end_datetime_local = req.params.get('baseperiodenddatetime')
40
        reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime')
41
        reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime')
42
43
        ################################################################################################################
44
        # Step 1: valid parameters
45
        ################################################################################################################
46
        if meter_id is None:
47
            raise falcon.HTTPError(falcon.HTTP_400,
48
                                   title='API.BAD_REQUEST',
49
                                   description='API.INVALID_METER_ID')
50
        else:
51
            meter_id = str.strip(meter_id)
52
            if not meter_id.isdigit() or int(meter_id) <= 0:
53
                raise falcon.HTTPError(falcon.HTTP_400,
54
                                       title='API.BAD_REQUEST',
55
                                       description='API.INVALID_METER_ID')
56
57
        if period_type is None:
58
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_PERIOD_TYPE')
59
        else:
60
            period_type = str.strip(period_type)
61
            if period_type not in ['hourly', 'daily', 'monthly', 'yearly']:
62
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_PERIOD_TYPE')
63
64
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
65
        if config.utc_offset[0] == '-':
66
            timezone_offset = -timezone_offset
67
68
        base_start_datetime_utc = None
69
        if base_period_start_datetime_local is not None and len(str.strip(base_period_start_datetime_local)) > 0:
70
            base_period_start_datetime_local = str.strip(base_period_start_datetime_local)
71
            try:
72
                base_start_datetime_utc = datetime.strptime(base_period_start_datetime_local, '%Y-%m-%dT%H:%M:%S')
73
            except ValueError:
74
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
75
                                       description="API.INVALID_BASE_PERIOD_START_DATETIME")
76
            base_start_datetime_utc = base_start_datetime_utc.replace(tzinfo=timezone.utc) - \
77
                timedelta(minutes=timezone_offset)
78
79
        base_end_datetime_utc = None
80
        if base_period_end_datetime_local is not None and len(str.strip(base_period_end_datetime_local)) > 0:
81
            base_period_end_datetime_local = str.strip(base_period_end_datetime_local)
82
            try:
83
                base_end_datetime_utc = datetime.strptime(base_period_end_datetime_local, '%Y-%m-%dT%H:%M:%S')
84
            except ValueError:
85
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
86
                                       description="API.INVALID_BASE_PERIOD_END_DATETIME")
87
            base_end_datetime_utc = base_end_datetime_utc.replace(tzinfo=timezone.utc) - \
88
                timedelta(minutes=timezone_offset)
89
90
        if base_start_datetime_utc is not None and base_end_datetime_utc is not None and \
91
                base_start_datetime_utc >= base_end_datetime_utc:
92
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
93
                                   description='API.INVALID_BASE_PERIOD_END_DATETIME')
94
95
        if reporting_period_start_datetime_local is None:
96
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
97
                                   description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
98
        else:
99
            reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local)
100
            try:
101
                reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local,
102
                                                                 '%Y-%m-%dT%H:%M:%S')
103
            except ValueError:
104
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
105
                                       description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
106
            reporting_start_datetime_utc = reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - \
107
                timedelta(minutes=timezone_offset)
108
109
        if reporting_period_end_datetime_local is None:
110
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
111
                                   description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
112
        else:
113
            reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local)
114
            try:
115
                reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local,
116
                                                               '%Y-%m-%dT%H:%M:%S')
117
            except ValueError:
118
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
119
                                       description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
120
            reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \
121
                timedelta(minutes=timezone_offset)
122
123
        if reporting_start_datetime_utc >= reporting_end_datetime_utc:
124
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
125
                                   description='API.INVALID_REPORTING_PERIOD_END_DATETIME')
126
127
        ################################################################################################################
128
        # Step 2: query the meter and energy category
129
        ################################################################################################################
130
        cnx_system = mysql.connector.connect(**config.myems_system_db)
131
        cursor_system = cnx_system.cursor()
132
133
        cnx_energy = mysql.connector.connect(**config.myems_energy_db)
134
        cursor_energy = cnx_energy.cursor()
135
136
        cnx_billing = mysql.connector.connect(**config.myems_billing_db)
137
        cursor_billing = cnx_billing.cursor()
138
139
        cnx_historical = mysql.connector.connect(**config.myems_historical_db)
140
        cursor_historical = cnx_historical.cursor()
141
142
        cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, "
143
                              "        ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e "
144
                              " FROM tbl_meters m, tbl_energy_categories ec "
145
                              " WHERE m.id = %s AND m.energy_category_id = ec.id ", (meter_id,))
146
        row_meter = cursor_system.fetchone()
147
        if row_meter is None:
148
            if cursor_system:
149
                cursor_system.close()
150
            if cnx_system:
151
                cnx_system.disconnect()
152
153
            if cursor_energy:
154
                cursor_energy.close()
155
            if cnx_energy:
156
                cnx_energy.disconnect()
157
158
            if cursor_billing:
159
                cursor_billing.close()
160
            if cnx_billing:
161
                cnx_billing.disconnect()
162
163
            if cursor_historical:
164
                cursor_historical.close()
165
            if cnx_historical:
166
                cnx_historical.disconnect()
167
            raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND', description='API.METER_NOT_FOUND')
168
169
        meter = dict()
170
        meter['id'] = row_meter[0]
171
        meter['name'] = row_meter[1]
172
        meter['cost_center_id'] = row_meter[2]
173
        meter['energy_category_id'] = row_meter[3]
174
        meter['energy_category_name'] = row_meter[4]
175
        meter['unit_of_measure'] = config.currency_unit
176
        meter['kgce'] = row_meter[6]
177
        meter['kgco2e'] = row_meter[7]
178
179
        ################################################################################################################
180
        # Step 3: query associated points
181
        ################################################################################################################
182
        point_list = list()
183
        cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type  "
184
                              " FROM tbl_meters m, tbl_meters_points mp, tbl_points p "
185
                              " WHERE m.id = %s AND m.id = mp.meter_id AND mp.point_id = p.id "
186
                              " ORDER BY p.id ", (meter['id'],))
187
        rows_points = cursor_system.fetchall()
188
        if rows_points is not None and len(rows_points) > 0:
189
            for row in rows_points:
190
                point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]})
191
192
        ################################################################################################################
193
        # Step 4: query base period energy consumption
194
        ################################################################################################################
195
        query = (" SELECT start_datetime_utc, actual_value "
196
                 " FROM tbl_meter_hourly "
197
                 " WHERE meter_id = %s "
198
                 " AND start_datetime_utc >= %s "
199
                 " AND start_datetime_utc < %s "
200
                 " ORDER BY start_datetime_utc ")
201
        cursor_energy.execute(query, (meter['id'], base_start_datetime_utc, base_end_datetime_utc))
202
        rows_meter_hourly = cursor_energy.fetchall()
203
204
        rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly,
205
                                                                            base_start_datetime_utc,
206
                                                                            base_end_datetime_utc,
207
                                                                            period_type)
208
        base = dict()
209
        base['timestamps'] = list()
210
        base['values'] = list()
211
        base['total_in_category'] = Decimal(0.0)
212
        base['total_in_kgce'] = Decimal(0.0)
213
        base['total_in_kgco2e'] = Decimal(0.0)
214
215
        for row_meter_periodically in rows_meter_periodically:
216
            current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \
217
                                     timedelta(minutes=timezone_offset)
218
            if period_type == 'hourly':
219
                current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
220
            elif period_type == 'daily':
221
                current_datetime = current_datetime_local.strftime('%Y-%m-%d')
222
            elif period_type == 'monthly':
223
                current_datetime = current_datetime_local.strftime('%Y-%m')
224
            elif period_type == 'yearly':
225
                current_datetime = current_datetime_local.strftime('%Y')
226
227
            actual_value = Decimal(0.0) if row_meter_periodically[1] is None \
228
                else row_meter_periodically[1]
229
            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...
230
            base['total_in_kgce'] += actual_value * meter['kgce']
231
            base['total_in_kgco2e'] += actual_value * meter['kgco2e']
232
233
        ################################################################################################################
234
        # Step 5: query base period energy cost
235
        ################################################################################################################
236
        query = (" SELECT start_datetime_utc, actual_value "
237
                 " FROM tbl_meter_hourly "
238
                 " WHERE meter_id = %s "
239
                 " AND start_datetime_utc >= %s "
240
                 " AND start_datetime_utc < %s "
241
                 " ORDER BY start_datetime_utc ")
242
        cursor_billing.execute(query, (meter['id'], base_start_datetime_utc, base_end_datetime_utc))
243
        rows_meter_hourly = cursor_billing.fetchall()
244
245
        rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly,
246
                                                                            base_start_datetime_utc,
247
                                                                            base_end_datetime_utc,
248
                                                                            period_type)
249
250
        base['values'] = list()
251
        base['total_in_category'] = Decimal(0.0)
252
253
        for row_meter_periodically in rows_meter_periodically:
254
            actual_value = Decimal(0.0) if row_meter_periodically[1] is None \
255
                else row_meter_periodically[1]
256
            base['values'].append(actual_value)
257
            base['total_in_category'] += actual_value
258
259
        ################################################################################################################
260
        # Step 6: query reporting period energy consumption
261
        ################################################################################################################
262
        query = (" SELECT start_datetime_utc, actual_value "
263
                 " FROM tbl_meter_hourly "
264
                 " WHERE meter_id = %s "
265
                 " AND start_datetime_utc >= %s "
266
                 " AND start_datetime_utc < %s "
267
                 " ORDER BY start_datetime_utc ")
268
        cursor_energy.execute(query, (meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
269
        rows_meter_hourly = cursor_energy.fetchall()
270
271
        rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly,
272
                                                                            reporting_start_datetime_utc,
273
                                                                            reporting_end_datetime_utc,
274
                                                                            period_type)
275
        reporting = dict()
276
        reporting['timestamps'] = list()
277
        reporting['values'] = list()
278
        reporting['total_in_category'] = Decimal(0.0)
279
        reporting['total_in_kgce'] = Decimal(0.0)
280
        reporting['total_in_kgco2e'] = Decimal(0.0)
281
282
        for row_meter_periodically in rows_meter_periodically:
283
            current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \
284
                timedelta(minutes=timezone_offset)
285
            if period_type == 'hourly':
286
                current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
287
            elif period_type == 'daily':
288
                current_datetime = current_datetime_local.strftime('%Y-%m-%d')
289
            elif period_type == 'monthly':
290
                current_datetime = current_datetime_local.strftime('%Y-%m')
291
            elif period_type == 'yearly':
292
                current_datetime = current_datetime_local.strftime('%Y')
293
294
            actual_value = Decimal(0.0) if row_meter_periodically[1] is None \
295
                else row_meter_periodically[1]
296
297
            reporting['timestamps'].append(current_datetime)
298
            reporting['total_in_kgce'] += actual_value * meter['kgce']
299
            reporting['total_in_kgco2e'] += actual_value * meter['kgco2e']
300
301
        ################################################################################################################
302
        # Step 7: query reporting period energy cost
303
        ################################################################################################################
304
        query = (" SELECT start_datetime_utc, actual_value "
305
                 " FROM tbl_meter_hourly "
306
                 " WHERE meter_id = %s "
307
                 " AND start_datetime_utc >= %s "
308
                 " AND start_datetime_utc < %s "
309
                 " ORDER BY start_datetime_utc ")
310
        cursor_billing.execute(query, (meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
311
        rows_meter_hourly = cursor_billing.fetchall()
312
313
        rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly,
314
                                                                            reporting_start_datetime_utc,
315
                                                                            reporting_end_datetime_utc,
316
                                                                            period_type)
317
318
        for row_meter_periodically in rows_meter_periodically:
319
            actual_value = Decimal(0.0) if row_meter_periodically[1] is None \
320
                else row_meter_periodically[1]
321
322
            reporting['values'].append(actual_value)
323
            reporting['total_in_category'] += actual_value
324
325
        ################################################################################################################
326
        # Step 8: query tariff data
327
        ################################################################################################################
328
        parameters_data = dict()
329
        parameters_data['names'] = list()
330
        parameters_data['timestamps'] = list()
331
        parameters_data['values'] = list()
332
333
        tariff_dict = utilities.get_energy_category_tariffs(meter['cost_center_id'],
334
                                                            meter['energy_category_id'],
335
                                                            reporting_start_datetime_utc,
336
                                                            reporting_end_datetime_utc)
337
        tariff_timestamp_list = list()
338
        tariff_value_list = list()
339
        for k, v in tariff_dict.items():
340
            # convert k from utc to local
341
            k = k + timedelta(minutes=timezone_offset)
342
            tariff_timestamp_list.append(k.isoformat()[0:19])
343
            tariff_value_list.append(v)
344
345
        parameters_data['names'].append('TARIFF-' + meter['energy_category_name'])
346
        parameters_data['timestamps'].append(tariff_timestamp_list)
347
        parameters_data['values'].append(tariff_value_list)
348
349
        ################################################################################################################
350
        # Step 9: query associated points data
351
        ################################################################################################################
352
        for point in point_list:
353
            point_values = []
354
            point_timestamps = []
355
            if point['object_type'] == 'ANALOG_VALUE':
356
                query = (" SELECT utc_date_time, actual_value "
357
                         " FROM tbl_analog_value "
358
                         " WHERE point_id = %s "
359
                         "       AND utc_date_time BETWEEN %s AND %s "
360
                         " ORDER BY utc_date_time ")
361
                cursor_historical.execute(query, (point['id'],
362
                                                  reporting_start_datetime_utc,
363
                                                  reporting_end_datetime_utc))
364
                rows = cursor_historical.fetchall()
365
366
                if rows is not None and len(rows) > 0:
367
                    for row in rows:
368
                        current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
369
                                                 timedelta(minutes=timezone_offset)
370
                        current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
371
                        point_timestamps.append(current_datetime)
372
                        point_values.append(row[1])
373
374
            elif point['object_type'] == 'ENERGY_VALUE':
375
                query = (" SELECT utc_date_time, actual_value "
376
                         " FROM tbl_energy_value "
377
                         " WHERE point_id = %s "
378
                         "       AND utc_date_time BETWEEN %s AND %s "
379
                         " ORDER BY utc_date_time ")
380
                cursor_historical.execute(query, (point['id'],
381
                                                  reporting_start_datetime_utc,
382
                                                  reporting_end_datetime_utc))
383
                rows = cursor_historical.fetchall()
384
385
                if rows is not None and len(rows) > 0:
386
                    for row in rows:
387
                        current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
388
                                                 timedelta(minutes=timezone_offset)
389
                        current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
390
                        point_timestamps.append(current_datetime)
391
                        point_values.append(row[1])
392
            elif point['object_type'] == 'DIGITAL_VALUE':
393
                query = (" SELECT utc_date_time, actual_value "
394
                         " FROM tbl_digital_value "
395
                         " WHERE point_id = %s "
396
                         "       AND utc_date_time BETWEEN %s AND %s ")
397
                cursor_historical.execute(query, (point['id'],
398
                                                  reporting_start_datetime_utc,
399
                                                  reporting_end_datetime_utc))
400
                rows = cursor_historical.fetchall()
401
402
                if rows is not None and len(rows) > 0:
403
                    for row in rows:
404
                        current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
405
                                                 timedelta(minutes=timezone_offset)
406
                        current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
407
                        point_timestamps.append(current_datetime)
408
                        point_values.append(row[1])
409
410
            parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')')
411
            parameters_data['timestamps'].append(point_timestamps)
412
            parameters_data['values'].append(point_values)
413
414
        ################################################################################################################
415
        # Step 10: construct the report
416
        ################################################################################################################
417
        if cursor_system:
418
            cursor_system.close()
419
        if cnx_system:
420
            cnx_system.disconnect()
421
422
        if cursor_energy:
423
            cursor_energy.close()
424
        if cnx_energy:
425
            cnx_energy.disconnect()
426
427
        if cursor_billing:
428
            cursor_billing.close()
429
        if cnx_billing:
430
            cnx_billing.disconnect()
431
432
        if cursor_historical:
433
            cursor_historical.close()
434
        if cnx_historical:
435
            cnx_historical.disconnect()
436
        result = {
437
            "meter": {
438
                "cost_center_id": meter['cost_center_id'],
439
                "energy_category_id": meter['energy_category_id'],
440
                "energy_category_name": meter['energy_category_name'],
441
                "unit_of_measure": config.currency_unit,
442
                "kgce": meter['kgce'],
443
                "kgco2e": meter['kgco2e'],
444
            },
445
            "base_period": {
446
                "total_in_category": base['total_in_category'],
447
                "total_in_kgce": base['total_in_kgce'],
448
                "total_in_kgco2e": base['total_in_kgco2e'],
449
                "timestamps": base['timestamps'],
450
                "values": base['values'],
451
            },
452
            "reporting_period": {
453
                "increment_rate":
454
                    (reporting['total_in_category']-base['total_in_category'])/base['total_in_category']
455
                    if base['total_in_category'] > 0 else None,
456
                "total_in_category": reporting['total_in_category'],
457
                "total_in_kgce": reporting['total_in_kgce'],
458
                "total_in_kgco2e": reporting['total_in_kgco2e'],
459
                "timestamps": reporting['timestamps'],
460
                "values": reporting['values'],
461
            },
462
            "parameters": {
463
                "names": parameters_data['names'],
464
                "timestamps": parameters_data['timestamps'],
465
                "values": parameters_data['values']
466
            },
467
        }
468
        # export result to Excel file and then encode the file to base64 string
469
        result['excel_bytes_base64'] = \
470
            excelexporters.metercost.export(result,
471
                                            meter['name'],
472
                                            reporting_period_start_datetime_local,
473
                                            reporting_period_end_datetime_local,
474
                                            period_type)
475
476
        resp.body = json.dumps(result)
477