Passed
Push — master ( fa1e9d...3fb1f5 )
by Guangyu
08:16 queued 13s
created

reports.metertracking   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 55
eloc 214
dl 0
loc 308
rs 6
c 0
b 0
f 0

3 Methods

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

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