Passed
Push — master ( 8a9faa...4be142 )
by Guangyu
02:22 queued 10s
created

reports.metersubmeterloss   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 366
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 261
dl 0
loc 366
rs 6
c 0
b 0
f 0
wmc 55

3 Methods

Rating   Name   Duplication   Size   Complexity  
F Reporting.on_get() 0 339 53
A Reporting.__init__() 0 3 1
A Reporting.on_options() 0 3 1

How to fix   Complexity   

Complexity

Complex classes like reports.metersubmeterloss 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
9
10
class Reporting:
11
    @staticmethod
12
    def __init__():
13
        pass
14
15
    @staticmethod
16
    def on_options(req, resp):
17
        resp.status = falcon.HTTP_200
18
19
    ####################################################################################################################
20
    # PROCEDURES
21
    # Step 1: valid parameters
22
    # Step 2: query the meter and energy category
23
    # Step 3: query associated submeters
24
    # Step 4: query reporting period energy consumption and loss
25
    # Step 5: construct the report
26
    ####################################################################################################################
27
    @staticmethod
28
    def on_get(req, resp):
29
        print(req.params)
30
        meter_id = req.params.get('meterid')
31
        period_type = req.params.get('periodtype')
32
        reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime')
33
        reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime')
34
35
        ################################################################################################################
36
        # Step 1: valid parameters
37
        ################################################################################################################
38
        if meter_id is None:
39
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_METER_ID')
40
        else:
41
            meter_id = str.strip(meter_id)
42
            if not meter_id.isdigit() or int(meter_id) <= 0:
43
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_METER_ID')
44
45
        if period_type is None:
46
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_PERIOD_TYPE')
47
        else:
48
            period_type = str.strip(period_type)
49
            if period_type not in ['hourly', 'daily', 'monthly', 'yearly']:
50
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_PERIOD_TYPE')
51
52
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
53
        if config.utc_offset[0] == '-':
54
            timezone_offset = -timezone_offset
55
56
        if reporting_period_start_datetime_local is None:
57
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
58
                                   description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
59
        else:
60
            reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local)
61
            try:
62
                reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local,
63
                                                                 '%Y-%m-%dT%H:%M:%S')
64
            except ValueError:
65
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
66
                                       description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
67
            reporting_start_datetime_utc = reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - \
68
                timedelta(minutes=timezone_offset)
69
70
        if reporting_period_end_datetime_local is None:
71
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
72
                                   description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
73
        else:
74
            reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local)
75
            try:
76
                reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local,
77
                                                               '%Y-%m-%dT%H:%M:%S')
78
            except ValueError:
79
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
80
                                       description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
81
            reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \
82
                timedelta(minutes=timezone_offset)
83
84
        if reporting_start_datetime_utc >= reporting_end_datetime_utc:
85
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
86
                                   description='API.INVALID_REPORTING_PERIOD_END_DATETIME')
87
88
        ################################################################################################################
89
        # Step 2: query the meter and energy category
90
        ################################################################################################################
91
        cnx_system = mysql.connector.connect(**config.myems_system_db)
92
        cursor_system = cnx_system.cursor()
93
94
        cnx_energy = mysql.connector.connect(**config.myems_energy_db)
95
        cursor_energy = cnx_energy.cursor()
96
97
        cursor_system.execute(" SELECT m.id, m.name, m.cost_center_id, m.energy_category_id, "
98
                              "        ec.name, ec.unit_of_measure, ec.kgce, ec.kgco2e "
99
                              " FROM tbl_meters m, tbl_energy_categories ec "
100
                              " WHERE m.id = %s AND m.energy_category_id = ec.id ", (meter_id,))
101
        row_meter = cursor_system.fetchone()
102
        if row_meter is None:
103
            if cursor_system:
104
                cursor_system.close()
105
            if cnx_system:
106
                cnx_system.disconnect()
107
108
            if cursor_energy:
109
                cursor_energy.close()
110
            if cnx_energy:
111
                cnx_energy.disconnect()
112
113
            raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND', description='API.METER_NOT_FOUND')
114
115
        meter = dict()
116
        meter['id'] = row_meter[0]
117
        meter['name'] = row_meter[1]
118
        meter['cost_center_id'] = row_meter[2]
119
        meter['energy_category_id'] = row_meter[3]
120
        meter['energy_category_name'] = row_meter[4]
121
        meter['unit_of_measure'] = row_meter[5]
122
        meter['kgce'] = row_meter[6]
123
        meter['kgco2e'] = row_meter[7]
124
125
        ################################################################################################################
126
        # Step 3: query associated points
127
        ################################################################################################################
128
        point_list = list()
129
        cursor_system.execute(" SELECT p.id, p.name, p.units, p.object_type  "
130
                              " FROM tbl_meters m, tbl_meters_points mp, tbl_points p "
131
                              " WHERE m.id = %s AND m.id = mp.meter_id AND mp.point_id = p.id "
132
                              " ORDER BY p.id ", (meter['id'],))
133
        rows_points = cursor_system.fetchall()
134
        if rows_points is not None and len(rows_points) > 0:
135
            for row in rows_points:
136
                point_list.append({"id": row[0], "name": row[1], "units": row[2], "object_type": row[3]})
137
138
        ################################################################################################################
139
        # Step 4: query base period energy consumption
140
        ################################################################################################################
141
        cnx_energy = mysql.connector.connect(**config.myems_energy_db)
142
        cursor_energy = cnx_energy.cursor()
143
        query = (" SELECT start_datetime_utc, actual_value "
144
                 " FROM tbl_meter_hourly "
145
                 " WHERE meter_id = %s "
146
                 " AND start_datetime_utc >= %s "
147
                 " AND start_datetime_utc < %s "
148
                 " ORDER BY start_datetime_utc ")
149
        cursor_energy.execute(query, (meter['id'], base_start_datetime_utc, base_end_datetime_utc))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable base_end_datetime_utc does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable base_start_datetime_utc does not seem to be defined.
Loading history...
150
        rows_meter_hourly = cursor_energy.fetchall()
151
152
        rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly,
153
                                                                            base_start_datetime_utc,
154
                                                                            base_end_datetime_utc,
155
                                                                            period_type)
156
        base = dict()
157
        base['timestamps'] = list()
158
        base['values'] = list()
159
        base['total_in_category'] = Decimal(0.0)
160
        base['total_in_kgce'] = Decimal(0.0)
161
        base['total_in_kgco2e'] = Decimal(0.0)
162
163
        for row_meter_periodically in rows_meter_periodically:
164
            current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \
165
                                     timedelta(minutes=timezone_offset)
166
            if period_type == 'hourly':
167
                current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
168
            elif period_type == 'daily':
169
                current_datetime = current_datetime_local.strftime('%Y-%m-%d')
170
            elif period_type == 'monthly':
171
                current_datetime = current_datetime_local.strftime('%Y-%m')
172
            elif period_type == 'yearly':
173
                current_datetime = current_datetime_local.strftime('%Y')
174
175
            actual_value = Decimal(0.0) if row_meter_periodically[1] is None else row_meter_periodically[1]
176
            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...
177
            base['values'].append(actual_value)
178
            base['total_in_category'] += actual_value
179
            base['total_in_kgce'] += actual_value * meter['kgce']
180
            base['total_in_kgco2e'] += actual_value * meter['kgco2e']
181
182
        ################################################################################################################
183
        # Step 5: query reporting period energy consumption
184
        ################################################################################################################
185
        query = (" SELECT start_datetime_utc, actual_value "
186
                 " FROM tbl_meter_hourly "
187
                 " WHERE meter_id = %s "
188
                 " AND start_datetime_utc >= %s "
189
                 " AND start_datetime_utc < %s "
190
                 " ORDER BY start_datetime_utc ")
191
        cursor_energy.execute(query, (meter['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
192
        rows_meter_hourly = cursor_energy.fetchall()
193
194
        rows_meter_periodically = utilities.aggregate_hourly_data_by_period(rows_meter_hourly,
195
                                                                            reporting_start_datetime_utc,
196
                                                                            reporting_end_datetime_utc,
197
                                                                            period_type)
198
        reporting = dict()
199
        reporting['timestamps'] = list()
200
        reporting['values'] = list()
201
        reporting['total_in_category'] = Decimal(0.0)
202
        reporting['total_in_kgce'] = Decimal(0.0)
203
        reporting['total_in_kgco2e'] = Decimal(0.0)
204
205
        for row_meter_periodically in rows_meter_periodically:
206
            current_datetime_local = row_meter_periodically[0].replace(tzinfo=timezone.utc) + \
207
                timedelta(minutes=timezone_offset)
208
            if period_type == 'hourly':
209
                current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
210
            elif period_type == 'daily':
211
                current_datetime = current_datetime_local.strftime('%Y-%m-%d')
212
            elif period_type == 'monthly':
213
                current_datetime = current_datetime_local.strftime('%Y-%m')
214
            elif period_type == 'yearly':
215
                current_datetime = current_datetime_local.strftime('%Y')
216
217
            actual_value = Decimal(0.0) if row_meter_periodically[1] is None else row_meter_periodically[1]
218
219
            reporting['timestamps'].append(current_datetime)
220
            reporting['values'].append(actual_value)
221
            reporting['total_in_category'] += actual_value
222
            reporting['total_in_kgce'] += actual_value * meter['kgce']
223
            reporting['total_in_kgco2e'] += actual_value * meter['kgco2e']
224
225
        ################################################################################################################
226
        # Step 6: query tariff data
227
        ################################################################################################################
228
        parameters_data = dict()
229
        parameters_data['names'] = list()
230
        parameters_data['timestamps'] = list()
231
        parameters_data['values'] = list()
232
233
        tariff_dict = utilities.get_energy_category_tariffs(meter['cost_center_id'],
234
                                                            meter['energy_category_id'],
235
                                                            reporting_start_datetime_utc,
236
                                                            reporting_end_datetime_utc)
237
        print(tariff_dict)
238
        tariff_timestamp_list = list()
239
        tariff_value_list = list()
240
        for k, v in tariff_dict.items():
241
            # convert k from utc to local
242
            k = k + timedelta(minutes=timezone_offset)
243
            tariff_timestamp_list.append(k.isoformat()[0:19])
244
            tariff_value_list.append(v)
245
246
        parameters_data['names'].append('TARIFF-' + meter['energy_category_name'])
247
        parameters_data['timestamps'].append(tariff_timestamp_list)
248
        parameters_data['values'].append(tariff_value_list)
249
250
        ################################################################################################################
251
        # Step 7: query associated points data
252
        ################################################################################################################
253
        for point in point_list:
254
            point_values = []
255
            point_timestamps = []
256
            if point['object_type'] == 'ANALOG_VALUE':
257
                query = (" SELECT utc_date_time, actual_value "
258
                         " FROM tbl_analog_value "
259
                         " WHERE point_id = %s "
260
                         "       AND utc_date_time BETWEEN %s AND %s "
261
                         " ORDER BY utc_date_time ")
262
                cursor_historical.execute(query, (point['id'],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable cursor_historical does not seem to be defined.
Loading history...
263
                                                  reporting_start_datetime_utc,
264
                                                  reporting_end_datetime_utc))
265
                rows = cursor_historical.fetchall()
266
267
                if rows is not None and len(rows) > 0:
268
                    for row in rows:
269
                        current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
270
                                                 timedelta(minutes=timezone_offset)
271
                        current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
272
                        point_timestamps.append(current_datetime)
273
                        point_values.append(row[1])
274
275
            elif point['object_type'] == 'ENERGY_VALUE':
276
                query = (" SELECT utc_date_time, actual_value "
277
                         " FROM tbl_energy_value "
278
                         " WHERE point_id = %s "
279
                         "       AND utc_date_time BETWEEN %s AND %s "
280
                         " ORDER BY utc_date_time ")
281
                cursor_historical.execute(query, (point['id'],
282
                                                  reporting_start_datetime_utc,
283
                                                  reporting_end_datetime_utc))
284
                rows = cursor_historical.fetchall()
285
286
                if rows is not None and len(rows) > 0:
287
                    for row in rows:
288
                        current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
289
                                                 timedelta(minutes=timezone_offset)
290
                        current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
291
                        point_timestamps.append(current_datetime)
292
                        point_values.append(row[1])
293
            elif point['object_type'] == 'DIGITAL_VALUE':
294
                query = (" SELECT utc_date_time, actual_value "
295
                         " FROM tbl_digital_value "
296
                         " WHERE point_id = %s "
297
                         "       AND utc_date_time BETWEEN %s AND %s ")
298
                cursor_historical.execute(query, (point['id'],
299
                                                  reporting_start_datetime_utc,
300
                                                  reporting_end_datetime_utc))
301
                rows = cursor_historical.fetchall()
302
303
                if rows is not None and len(rows) > 0:
304
                    for row in rows:
305
                        current_datetime_local = row[0].replace(tzinfo=timezone.utc) + \
306
                                                 timedelta(minutes=timezone_offset)
307
                        current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
308
                        point_timestamps.append(current_datetime)
309
                        point_values.append(row[1])
310
311
            parameters_data['names'].append(point['name'] + ' (' + point['units'] + ')')
312
            parameters_data['timestamps'].append(point_timestamps)
313
            parameters_data['values'].append(point_values)
314
315
        ################################################################################################################
316
        # Step 8: construct the report
317
        ################################################################################################################
318
        if cursor_system:
319
            cursor_system.close()
320
        if cnx_system:
321
            cnx_system.disconnect()
322
323
        if cursor_energy:
324
            cursor_energy.close()
325
        if cnx_energy:
326
            cnx_energy.disconnect()
327
328
        if cursor_historical:
329
            cursor_historical.close()
330
        if cnx_historical:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable cnx_historical does not seem to be defined.
Loading history...
331
            cnx_historical.disconnect()
332
        result = {
333
            "meter": {
334
                "cost_center_id": meter['cost_center_id'],
335
                "energy_category_id": meter['energy_category_id'],
336
                "energy_category_name": meter['energy_category_name'],
337
                "unit_of_measure": meter['unit_of_measure'],
338
                "kgce": meter['kgce'],
339
                "kgco2e": meter['kgco2e'],
340
            },
341
            "base_period": {
342
                "total_in_category": base['total_in_category'],
343
                "total_in_kgce": base['total_in_kgce'],
344
                "total_in_kgco2e": base['total_in_kgco2e'],
345
                "timestamps": base['timestamps'],
346
                "values": base['values']
347
            },
348
            "reporting_period": {
349
                "increment_rate":
350
                    (reporting['total_in_category'] - base['total_in_category'])/base['total_in_category']
351
                    if base['total_in_category'] > 0 else None,
352
                "total_in_category": reporting['total_in_category'],
353
                "total_in_kgce": reporting['total_in_kgce'],
354
                "total_in_kgco2e": reporting['total_in_kgco2e'],
355
                "timestamps": reporting['timestamps'],
356
                "values": reporting['values'],
357
            },
358
            "parameters": {
359
                "names": parameters_data['names'],
360
                "timestamps": parameters_data['timestamps'],
361
                "values": parameters_data['values']
362
            },
363
        }
364
365
        resp.body = json.dumps(result)
366