Passed
Push — master ( dc4cf6...d9c3b0 )
by Guangyu
02:47 queued 10s
created

reports.tenantbill   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 346
Duplicated Lines 5.49 %

Importance

Changes 0
Metric Value
eloc 246
dl 19
loc 346
rs 3.36
c 0
b 0
f 0
wmc 63

3 Methods

Rating   Name   Duplication   Size   Complexity  
A Reporting.on_options() 0 3 1
A Reporting.__init__() 0 3 1
F Reporting.on_get() 19 317 61

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.tenantbill 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
import utilities
7
from decimal import *
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 tenant
23
    # Step 3: query energy categories
24
    # Step 4: query reporting period energy input
25
    # Step 5: query reporting period energy cost
26
    # Step 6: query tariff data
27
    # Step 7: construct the report
28
    ####################################################################################################################
29
    @staticmethod
30
    def on_get(req, resp):
31
        print(req.params)
32
        tenant_id = req.params.get('tenantid')
33
        reporting_start_datetime_local = req.params.get('reportingperiodstartdatetime')
34
        reporting_end_datetime_local = req.params.get('reportingperiodenddatetime')
35
36
        period_type = 'daily'
37
38
        ################################################################################################################
39
        # Step 1: valid parameters
40
        ################################################################################################################
41
        if tenant_id is None:
42
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_TENANT_ID')
43
        else:
44
            tenant_id = str.strip(tenant_id)
45
            if not tenant_id.isdigit() or int(tenant_id) <= 0:
46
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_TENANT_ID')
47
48
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
49
        if config.utc_offset[0] == '-':
50
            timezone_offset = -timezone_offset
51
52
        if reporting_start_datetime_local is None:
53
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
54
                                   description="API.INVALID_REPORTING_PERIOD_BEGINS_DATETIME")
55
        else:
56
            reporting_start_datetime_local = str.strip(reporting_start_datetime_local)
57
            try:
58
                reporting_start_datetime_utc = datetime.strptime(reporting_start_datetime_local,
59
                                                                 '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
60
                    timedelta(minutes=timezone_offset)
61
            except ValueError:
62
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
63
                                       description="API.INVALID_REPORTING_PERIOD_BEGINS_DATETIME")
64
65
        if reporting_end_datetime_local is None:
66
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
67
                                   description="API.INVALID_REPORTING_PERIOD_ENDS_DATETIME")
68
        else:
69
            reporting_end_datetime_local = str.strip(reporting_end_datetime_local)
70
            try:
71
                reporting_end_datetime_utc = datetime.strptime(reporting_end_datetime_local,
72
                                                               '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
73
                    timedelta(minutes=timezone_offset)
74
            except ValueError:
75
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
76
                                       description="API.INVALID_REPORTING_PERIOD_ENDS_DATETIME")
77
78
        if reporting_start_datetime_utc >= reporting_end_datetime_utc:
79
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
80
                                   description='API.INVALID_REPORTING_PERIOD_ENDS_DATETIME')
81
82
        ################################################################################################################
83
        # Step 2: query the tenant
84
        ################################################################################################################
85
        cnx_system = mysql.connector.connect(**config.myems_system_db)
86
        cursor_system = cnx_system.cursor()
87
88
        cnx_energy = mysql.connector.connect(**config.myems_energy_db)
89
        cursor_energy = cnx_energy.cursor()
90
91
        cnx_billing = mysql.connector.connect(**config.myems_billing_db)
92
        cursor_billing = cnx_billing.cursor()
93
94
        cursor_system.execute(" SELECT t.id, t.name, t.buildings, t.floors, t.rooms, t.lease_number, "
95
                              "        c.email, c.phone, cost_center_id "
96
                              " FROM tbl_tenants t, tbl_contacts c "
97
                              " WHERE t.id = %s AND t.contact_id = c.id ", (tenant_id,))
98
        row_tenant = cursor_system.fetchone()
99
        if row_tenant is None:
100
            if cursor_system:
101
                cursor_system.close()
102
            if cnx_system:
103
                cnx_system.disconnect()
104
105
            if cursor_energy:
106
                cursor_energy.close()
107
            if cnx_energy:
108
                cnx_energy.disconnect()
109
110
            if cursor_billing:
111
                cursor_billing.close()
112
            if cnx_billing:
113
                cnx_billing.disconnect()
114
115
            raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND', description='API.TENANT_NOT_FOUND')
116
117
        tenant = dict()
118
        tenant['id'] = row_tenant[0]
119
        tenant['name'] = row_tenant[1]
120
        tenant['buildings'] = row_tenant[2]
121
        tenant['floors'] = row_tenant[3]
122
        tenant['rooms'] = row_tenant[4]
123
        tenant['lease_number'] = row_tenant[5]
124
        tenant['email'] = row_tenant[6]
125
        tenant['phone'] = row_tenant[7]
126
        tenant['cost_center_id'] = row_tenant[8]
127
128
        ################################################################################################################
129
        # Step 3: query energy categories
130
        ################################################################################################################
131
        energy_category_set = set()
132
133
        # query energy categories in reporting period
134
        cursor_billing.execute(" SELECT DISTINCT(energy_category_id) "
135
                               " FROM tbl_tenant_input_category_hourly "
136
                               " WHERE tenant_id = %s "
137
                               "     AND start_datetime_utc >= %s "
138
                               "     AND start_datetime_utc < %s ",
139
                               (tenant['id'], reporting_start_datetime_utc, reporting_end_datetime_utc))
140
        rows_energy_categories = cursor_billing.fetchall()
141
        if rows_energy_categories is not None or len(rows_energy_categories) > 0:
142
            for row_energy_category in rows_energy_categories:
143
                energy_category_set.add(row_energy_category[0])
144
145
        # query all energy categories
146
        cursor_system.execute(" SELECT id, name, unit_of_measure, kgce, kgco2e "
147
                              " FROM tbl_energy_categories "
148
                              " ORDER BY id ", )
149
        rows_energy_categories = cursor_system.fetchall()
150 View Code Duplication
        if rows_energy_categories is None or len(rows_energy_categories) == 0:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
151
            if cursor_system:
152
                cursor_system.close()
153
            if cnx_system:
154
                cnx_system.disconnect()
155
156
            if cursor_energy:
157
                cursor_energy.close()
158
            if cnx_energy:
159
                cnx_energy.disconnect()
160
161
            if cursor_billing:
162
                cursor_billing.close()
163
            if cnx_billing:
164
                cnx_billing.disconnect()
165
166
            raise falcon.HTTPError(falcon.HTTP_404,
167
                                   title='API.NOT_FOUND',
168
                                   description='API.ENERGY_CATEGORY_NOT_FOUND')
169
        energy_category_dict = dict()
170
        for row_energy_category in rows_energy_categories:
171
            if row_energy_category[0] in energy_category_set:
172
                energy_category_dict[row_energy_category[0]] = {"name": row_energy_category[1],
173
                                                                "unit_of_measure": row_energy_category[2],
174
                                                                "kgce": row_energy_category[3],
175
                                                                "kgco2e": row_energy_category[4]}
176
177
        ################################################################################################################
178
        # Step 4: query reporting period energy input
179
        ################################################################################################################
180
        reporting_input = dict()
181
        if energy_category_set is not None and len(energy_category_set) > 0:
182
            for energy_category_id in energy_category_set:
183
                reporting_input[energy_category_id] = dict()
184
                reporting_input[energy_category_id]['timestamps'] = list()
185
                reporting_input[energy_category_id]['values'] = list()
186
                reporting_input[energy_category_id]['subtotal'] = Decimal(0.0)
187
188
                cursor_energy.execute(" SELECT start_datetime_utc, actual_value "
189
                                      " FROM tbl_tenant_input_category_hourly "
190
                                      " WHERE tenant_id = %s "
191
                                      "     AND energy_category_id = %s "
192
                                      "     AND start_datetime_utc >= %s "
193
                                      "     AND start_datetime_utc < %s "
194
                                      " ORDER BY start_datetime_utc ",
195
                                      (tenant['id'],
196
                                       energy_category_id,
197
                                       reporting_start_datetime_utc,
198
                                       reporting_end_datetime_utc))
199
                rows_tenant_hourly = cursor_energy.fetchall()
200
201
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
202
                                                                                     reporting_start_datetime_utc,
203
                                                                                     reporting_end_datetime_utc,
204
                                                                                     period_type)
205
                for row_tenant_periodically in rows_tenant_periodically:
206
                    current_datetime_local = row_tenant_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_tenant_periodically[1] is None else row_tenant_periodically[1]
218
                    reporting_input[energy_category_id]['timestamps'].append(current_datetime)
0 ignored issues
show
introduced by
The variable current_datetime does not seem to be defined for all execution paths.
Loading history...
219
                    reporting_input[energy_category_id]['values'].append(actual_value)
220
                    reporting_input[energy_category_id]['subtotal'] += actual_value
221
222
        ################################################################################################################
223
        # Step 5: query reporting period energy cost
224
        ################################################################################################################
225
        reporting_cost = dict()
226
        if energy_category_set is not None and len(energy_category_set) > 0:
227
            for energy_category_id in energy_category_set:
228
                reporting_cost[energy_category_id] = dict()
229
                reporting_cost[energy_category_id]['timestamps'] = list()
230
                reporting_cost[energy_category_id]['values'] = list()
231
                reporting_cost[energy_category_id]['subtotal'] = Decimal(0.0)
232
233
                cursor_billing.execute(" SELECT start_datetime_utc, actual_value "
234
                                       " FROM tbl_tenant_input_category_hourly "
235
                                       " WHERE tenant_id = %s "
236
                                       "     AND energy_category_id = %s "
237
                                       "     AND start_datetime_utc >= %s "
238
                                       "     AND start_datetime_utc < %s "
239
                                       " ORDER BY start_datetime_utc ",
240
                                       (tenant['id'],
241
                                        energy_category_id,
242
                                        reporting_start_datetime_utc,
243
                                        reporting_end_datetime_utc))
244
                rows_tenant_hourly = cursor_billing.fetchall()
245
246
                rows_tenant_periodically = utilities.aggregate_hourly_data_by_period(rows_tenant_hourly,
247
                                                                                     reporting_start_datetime_utc,
248
                                                                                     reporting_end_datetime_utc,
249
                                                                                     period_type)
250
                for row_tenant_periodically in rows_tenant_periodically:
251
                    current_datetime_local = row_tenant_periodically[0].replace(tzinfo=timezone.utc) + \
252
                                             timedelta(minutes=timezone_offset)
253
                    if period_type == 'hourly':
254
                        current_datetime = current_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')
255
                    elif period_type == 'daily':
256
                        current_datetime = current_datetime_local.strftime('%Y-%m-%d')
257
                    elif period_type == 'monthly':
258
                        current_datetime = current_datetime_local.strftime('%Y-%m')
259
                    elif period_type == 'yearly':
260
                        current_datetime = current_datetime_local.strftime('%Y')
261
262
                    actual_value = Decimal(0.0) if row_tenant_periodically[1] is None else row_tenant_periodically[1]
263
                    reporting_cost[energy_category_id]['timestamps'].append(current_datetime)
264
                    reporting_cost[energy_category_id]['values'].append(actual_value)
265
                    reporting_cost[energy_category_id]['subtotal'] += actual_value
266
267
        ################################################################################################################
268
        # Step 6: query tariff data
269
        ################################################################################################################
270
        parameters_data = dict()
271
        parameters_data['names'] = list()
272
        parameters_data['timestamps'] = list()
273
        parameters_data['values'] = list()
274
        if energy_category_set is not None and len(energy_category_set) > 0:
275
            for energy_category_id in energy_category_set:
276
                energy_category_tariff_dict = utilities.get_energy_category_tariffs(tenant['cost_center_id'],
277
                                                                                    energy_category_id,
278
                                                                                    reporting_start_datetime_utc,
279
                                                                                    reporting_end_datetime_utc)
280
                tariff_timestamp_list = list()
281
                tariff_value_list = list()
282
                for k, v in energy_category_tariff_dict.items():
283
                    # convert k from utc to local
284
                    k = k + timedelta(minutes=timezone_offset)
285
                    tariff_timestamp_list.append(k.isoformat()[0:19][0:19])
286
                    tariff_value_list.append(v)
287
288
                parameters_data['names'].append('TARIFF-' + energy_category_dict[energy_category_id]['name'])
289
                parameters_data['timestamps'].append(tariff_timestamp_list)
290
                parameters_data['values'].append(tariff_value_list)
291
292
        ################################################################################################################
293
        # Step 7: construct the report
294
        ################################################################################################################
295
        if cursor_system:
296
            cursor_system.close()
297
        if cnx_system:
298
            cnx_system.disconnect()
299
300
        if cursor_energy:
301
            cursor_energy.close()
302
        if cnx_energy:
303
            cnx_energy.disconnect()
304
305
        if cursor_billing:
306
            cursor_billing.close()
307
        if cnx_billing:
308
            cnx_billing.disconnect()
309
310
        result = dict()
311
312
        result['tenant'] = dict()
313
        result['tenant']['name'] = tenant['name']
314
        result['tenant']['buildings'] = tenant['buildings']
315
        result['tenant']['floors'] = tenant['floors']
316
        result['tenant']['rooms'] = tenant['rooms']
317
        result['tenant']['lease_number'] = tenant['lease_number']
318
        result['tenant']['email'] = tenant['email']
319
        result['tenant']['phone'] = tenant['phone']
320
321
        result['reporting_period'] = dict()
322
        result['reporting_period']['names'] = list()
323
        result['reporting_period']['energy_category_ids'] = list()
324
        result['reporting_period']['units'] = list()
325
        result['reporting_period']['subtotals_input'] = list()
326
        result['reporting_period']['subtotals_cost'] = list()
327
        result['reporting_period']['total_cost'] = Decimal(0.0)
328
        result['reporting_period']['currency_unit'] = config.currency_unit
329
330
        if energy_category_set is not None and len(energy_category_set) > 0:
331
            for energy_category_id in energy_category_set:
332
                result['reporting_period']['names'].append(energy_category_dict[energy_category_id]['name'])
333
                result['reporting_period']['energy_category_ids'].append(energy_category_id)
334
                result['reporting_period']['units'].append(energy_category_dict[energy_category_id]['unit_of_measure'])
335
                result['reporting_period']['subtotals_input'].append(reporting_input[energy_category_id]['subtotal'])
336
                result['reporting_period']['subtotals_cost'].append(reporting_cost[energy_category_id]['subtotal'])
337
                result['reporting_period']['total_cost'] += reporting_cost[energy_category_id]['subtotal']
338
339
        result['parameters'] = {
340
            "names": parameters_data['names'],
341
            "timestamps": parameters_data['timestamps'],
342
            "values": parameters_data['values']
343
        }
344
345
        resp.body = json.dumps(result)
346