reports.metertracking   D
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 324
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 59
eloc 223
dl 0
loc 324
rs 4.08
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
F Reporting.on_get() 0 294 57
A Reporting.__init__() 0 3 1
A Reporting.on_options() 0 4 1

How to fix   Complexity   

Complexity

Complex classes like reports.metertracking 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
from datetime import datetime, timedelta, timezone
2
from decimal import Decimal
3
import falcon
4
import mysql.connector
5
import simplejson as json
6
from anytree import AnyNode, LevelOrderIter
7
import config
8
import excelexporters.metertracking
9
from core.useractivity import access_control, api_key_control
10
11
12
class Reporting:
13
    def __init__(self):
14
        """"Initializes Reporting"""
15
        pass
16
17
    @staticmethod
18
    def on_options(req, resp):
19
        _ = req
20
        resp.status = falcon.HTTP_200
21
22
    ####################################################################################################################
23
    # PROCEDURES
24
    # Step 1: valid parameters
25
    # Step 2: build a space tree
26
    # Step 3: query all meters in the space tree
27
    # Step 4: query start value and end value
28
    # Step 5: construct the report
29
    ####################################################################################################################
30
    @staticmethod
31
    def on_get(req, resp):
32
        if 'API-KEY' not in req.headers or \
33
                not isinstance(req.headers['API-KEY'], str) or \
34
                len(str.strip(req.headers['API-KEY'])) == 0:
35
            access_control(req)
36
        else:
37
            api_key_control(req)
38
        print(req.params)
39
        space_id = req.params.get('spaceid')
40
        energy_category = req.params.get('energyCategory')
41
        reporting_period_start_datetime_local = req.params.get('reportingperiodstartdatetime')
42
        reporting_period_end_datetime_local = req.params.get('reportingperiodenddatetime')
43
        language = req.params.get('language')
44
        quick_mode = req.params.get('quickmode')
45
46
        ################################################################################################################
47
        # Step 1: valid parameters
48
        ################################################################################################################
49
        if space_id is None:
50
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_SPACE_ID')
51
        else:
52
            space_id = str.strip(space_id)
53
            if not space_id.isdigit() or int(space_id) <= 0:
54
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
55
                                       description='API.INVALID_SPACE_ID')
56
            else:
57
                space_id = int(space_id)
58
59
        if energy_category is None:
60
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
61
                                   description='API.INVALID_ENERGY_CATEGORY_ID')
62
        else:
63
            if energy_category == 'all':
64
                energy_category_query = ""
65
                energy_category_name = None
66
            else:
67
                energy_category = str.strip(energy_category)
68
                if not energy_category.isdigit() or int(energy_category) <= 0:
69
                    raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
70
                                           description='API.INVALID_ENERGY_CATEGORY_ID')
71
                else:
72
                    cnx_system_db = mysql.connector.connect(**config.myems_system_db)
73
                    cursor_system_db = cnx_system_db.cursor()
74
                    energy_category_query = "AND m.energy_category_id = '" + energy_category + "' "
75
                    cursor_system_db.execute(" SELECT name "
76
                                             " FROM tbl_energy_categories "
77
                                             " WHERE id = %s ", (energy_category,))
78
                    row = cursor_system_db.fetchone()
79
80
                    if row is None:
81
                        if cursor_system_db:
82
                            cursor_system_db.close()
83
                        if cnx_system_db:
84
                            cnx_system_db.close()
85
                        raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
86
                                               description='API.ENERGY_CATEGORY_NOT_FOUND')
87
                    else:
88
                        energy_category_name = row[0]
89
90
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
91
        if config.utc_offset[0] == '-':
92
            timezone_offset = -timezone_offset
93
94
        if reporting_period_start_datetime_local is None:
95
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
96
                                   description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
97
        else:
98
            reporting_period_start_datetime_local = str.strip(reporting_period_start_datetime_local)
99
            try:
100
                reporting_start_datetime_utc = datetime.strptime(reporting_period_start_datetime_local,
101
                                                                 '%Y-%m-%dT%H:%M:%S')
102
            except ValueError:
103
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
104
                                       description="API.INVALID_REPORTING_PERIOD_START_DATETIME")
105
            reporting_start_datetime_utc = \
106
                reporting_start_datetime_utc.replace(tzinfo=timezone.utc) - timedelta(minutes=timezone_offset)
107
            # nomalize the start datetime
108
            if config.minutes_to_count == 30 and reporting_start_datetime_utc.minute >= 30:
109
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=30, second=0, microsecond=0)
110
            else:
111
                reporting_start_datetime_utc = reporting_start_datetime_utc.replace(minute=0, second=0, microsecond=0)
112
113
        if reporting_period_end_datetime_local is None:
114
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
115
                                   description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
116
        else:
117
            reporting_period_end_datetime_local = str.strip(reporting_period_end_datetime_local)
118
            try:
119
                reporting_end_datetime_utc = datetime.strptime(reporting_period_end_datetime_local,
120
                                                               '%Y-%m-%dT%H:%M:%S')
121
            except ValueError:
122
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
123
                                       description="API.INVALID_REPORTING_PERIOD_END_DATETIME")
124
            reporting_end_datetime_utc = reporting_end_datetime_utc.replace(tzinfo=timezone.utc) - \
125
                timedelta(minutes=timezone_offset)
126
127
        if reporting_start_datetime_utc >= reporting_end_datetime_utc:
128
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
129
                                   description='API.INVALID_REPORTING_PERIOD_END_DATETIME')
130
131
        if reporting_start_datetime_utc + timedelta(minutes=15) >= reporting_end_datetime_utc:
132
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
133
                                   description='API.THE_REPORTING_PERIOD_MUST_BE_LONGER_THAN_15_MINUTES')
134
135
        # if turn quick mode on, do not return parameters data and excel file
136
        is_quick_mode = False
137
        if quick_mode is not None and \
138
            len(str.strip(quick_mode)) > 0 and \
139
                str.lower(str.strip(quick_mode)) in ('true', 't', 'on', 'yes', 'y'):
140
            is_quick_mode = True
141
142
        cnx_system_db = mysql.connector.connect(**config.myems_system_db)
143
        cursor_system_db = cnx_system_db.cursor()
144
145
        cnx_historical = mysql.connector.connect(**config.myems_historical_db)
146
        cursor_historical = cnx_historical.cursor()
147
148
        cursor_system_db.execute(" SELECT name "
149
                                 " FROM tbl_spaces "
150
                                 " WHERE id = %s ", (space_id,))
151
        row = cursor_system_db.fetchone()
152
153
        if row is None:
154
            if cursor_system_db:
155
                cursor_system_db.close()
156
            if cnx_system_db:
157
                cnx_system_db.close()
158
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
159
                                   description='API.SPACE_NOT_FOUND')
160
        else:
161
            space_name = row[0]
162
163
        ################################################################################################################
164
        # Step 2: build a space tree
165
        ################################################################################################################
166
167
        query = (" SELECT id, name, parent_space_id "
168
                 " FROM tbl_spaces "
169
                 " ORDER BY id ")
170
        cursor_system_db.execute(query)
171
        rows_spaces = cursor_system_db.fetchall()
172
        node_dict = dict()
173
        if rows_spaces is not None and len(rows_spaces) > 0:
174
            for row in rows_spaces:
175
                parent_node = node_dict[row[2]] if row[2] is not None else None
176
                node_dict[row[0]] = AnyNode(id=row[0], parent=parent_node, name=row[1])
177
178
        ################################################################################################################
179
        # Step 3: query all meters in the space tree
180
        ################################################################################################################
181
        meter_dict = dict()
182
        space_dict = dict()
183
184
        if config.is_recursive:
185
            for node in LevelOrderIter(node_dict[space_id]):
186
                space_dict[node.id] = node.name
187
188
            cursor_system_db.execute(" SELECT m.id, m.name AS meter_name, s.name AS space_name, "
189
                                     "        cc.name AS cost_center_name, ec.name AS energy_category_name, "
190
                                     "         m.description, m.uuid AS meter_uuid "
191
                                     " FROM tbl_spaces s, tbl_spaces_meters sm, tbl_meters m, tbl_cost_centers cc, "
192
                                     "      tbl_energy_categories ec "
193
                                     " WHERE s.id IN ( " + ', '.join(map(str, space_dict.keys())) + ") "
194
                                     "       AND sm.space_id = s.id AND sm.meter_id = m.id "
195
                                     + energy_category_query +
196
                                     " AND m.cost_center_id = cc.id AND m.energy_category_id = ec.id "
197
                                     " ORDER BY meter_id ", )
198
        else:
199
            cursor_system_db.execute(" SELECT m.id, m.name AS meter_name, s.name AS space_name, "
200
                                     "        cc.name AS cost_center_name, ec.name AS energy_category_name, "
201
                                     "         m.description, m.uuid AS meter_uuid "
202
                                     " FROM tbl_spaces s, tbl_spaces_meters sm, tbl_meters m, tbl_cost_centers cc, "
203
                                     "      tbl_energy_categories ec "
204
                                     " WHERE s.id = %s AND sm.space_id = s.id AND sm.meter_id = m.id "
205
                                     + energy_category_query +
206
                                     " AND m.cost_center_id = cc.id AND m.energy_category_id = ec.id  "
207
                                     " ORDER BY meter_id ", (space_id,))
208
209
        rows_meters = cursor_system_db.fetchall()
210
        if rows_meters is not None and len(rows_meters) > 0:
211
            for row in rows_meters:
212
                meter_dict[row[0]] = {"meter_name": row[1],
213
                                      "space_name": row[2],
214
                                      "cost_center_name": row[3],
215
                                      "energy_category_name": row[4],
216
                                      "description": row[5],
217
                                      "meter_uuid": row[6]}
218
219
        ################################################################################################################
220
        # Step 4: query start value and end value
221
        ################################################################################################################
222
        integral_start_count = int(0)
223
        integral_end_count = int(0)
224
        integral_full_count = int(0)
225
226
        for meter_id in meter_dict:
227
            cursor_system_db.execute(" SELECT point_id "
228
                                     " FROM tbl_meters_points mp, tbl_points p"
229
                                     " WHERE p.id = mp.point_id AND p.object_type = 'ENERGY_VALUE' "
230
                                     "       AND meter_id = %s ", (meter_id, ))
231
232
            rows_points_id = cursor_system_db.fetchall()
233
234
            start_value = None
235
            end_value = None
236
            is_integral_start_value = False
237
238
            if rows_points_id is not None and len(rows_points_id) > 0:
239
                query_start_value = (" SELECT actual_value "
240
                                     " FROM tbl_energy_value "
241
                                     " where point_id in ("
242
                                     + ', '.join(map(lambda x: str(x[0]), rows_points_id)) + ") "
243
                                     " AND utc_date_time BETWEEN %s AND %s "
244
                                     " ORDER BY utc_date_time DESC LIMIT 0,1")
245
                query_end_value = (" SELECT actual_value "
246
                                   " FROM tbl_energy_value "
247
                                   " where point_id in ("
248
                                   + ', '.join(map(lambda x: str(x[0]), rows_points_id)) + ") "
249
                                   " AND utc_date_time BETWEEN %s AND %s "
250
                                   " ORDER BY utc_date_time DESC LIMIT 0,1")
251
                cursor_historical.execute(query_start_value,
252
                                          (reporting_start_datetime_utc - timedelta(minutes=15),
253
                                           reporting_start_datetime_utc, ))
254
                row_start_value = cursor_historical.fetchone()
255
                if row_start_value is not None:
256
                    start_value = row_start_value[0]
257
                    integral_start_count += int(1)
258
                    is_integral_start_value = True
259
260
                cursor_historical.execute(query_end_value,
261
                                          ((reporting_end_datetime_utc - timedelta(minutes=15)),
262
                                           reporting_end_datetime_utc, ))
263
                row_end_value = cursor_historical.fetchone()
264
265
                if row_end_value is not None:
266
                    end_value = row_end_value[0]
267
                    integral_end_count += int(1)
268
                    if is_integral_start_value:
269
                        integral_full_count += int(1)
270
271
            meter_dict[meter_id]['start_value'] = start_value
272
            meter_dict[meter_id]['end_value'] = end_value
273
            if start_value is not None and end_value is not None:
274
                meter_dict[meter_id]['difference_value'] = end_value - start_value
275
            else:
276
                meter_dict[meter_id]['difference_value'] = None
277
278
        if cursor_system_db:
279
            cursor_system_db.close()
280
        if cnx_system_db:
281
            cnx_system_db.close()
282
283
        if cursor_historical:
284
            cursor_historical.close()
285
        if cnx_historical:
286
            cnx_historical.close()
287
288
        ################################################################################################################
289
        # Step 5: construct the report
290
        ################################################################################################################
291
        meter_list = list()
292
        for meter_id, meter in meter_dict.items():
293
            meter_list.append({
294
                "id": meter_id,
295
                "meter_name": meter['meter_name'],
296
                "space_name": meter['space_name'],
297
                "cost_center_name": meter['cost_center_name'],
298
                "energy_category_name": meter['energy_category_name'],
299
                "description": meter['description'],
300
                "start_value": meter['start_value'],
301
                "end_value": meter['end_value'],
302
                "difference_value": meter['difference_value'],
303
                "meter_uuid": meter['meter_uuid']
304
            })
305
306
        meter_count = len(meter_list)
307
        start_integrity_rate = Decimal(integral_start_count / meter_count) if meter_count > 0 else None
308
        end_integrity_rate = Decimal(integral_end_count / meter_count) if meter_count > 0 else None
309
        full_integrity_rate = Decimal(integral_full_count / meter_count) if meter_count > 0 else None
310
311
        result = {'meters': meter_list, 'start_integrity_rate': start_integrity_rate,
312
                  'end_integrity_rate': end_integrity_rate, 'full_integrity_rate': full_integrity_rate,
313
                  'excel_bytes_base64': None}
314
        # export result to Excel file and then encode the file to base64 string
315
        if not is_quick_mode:
316
            result['excel_bytes_base64'] = \
317
                excelexporters.metertracking.export(result,
318
                                                    space_name,
319
                                                    energy_category_name,
320
                                                    reporting_period_start_datetime_local,
321
                                                    reporting_period_end_datetime_local,
322
                                                    language)
323
        resp.text = json.dumps(result)
324