Conditions | 51 |
Total Lines | 323 |
Code Lines | 166 |
Lines | 21 |
Ratio | 6.5 % |
Changes | 0 |
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:
If many parameters/temporary variables are present:
Complex classes like meter.worker() 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 time |
||
108 | def worker(meter): |
||
109 | print("Start to process meter: " + "'" + meter['name'] + "'") |
||
110 | #################################################################################################################### |
||
111 | # Step 1: Determine the start datetime and end datetime |
||
112 | #################################################################################################################### |
||
113 | cnx_energy_db = None |
||
114 | cursor_energy_db = None |
||
115 | try: |
||
116 | cnx_energy_db = mysql.connector.connect(**config.myems_energy_db) |
||
117 | cursor_energy_db = cnx_energy_db.cursor() |
||
118 | except Exception as e: |
||
119 | error_string = "Error in step 1.1 of meter.worker " + str(e) + " for '" + meter['name'] + "'" |
||
120 | if cursor_energy_db: |
||
121 | cursor_energy_db.close() |
||
122 | if cnx_energy_db: |
||
123 | cnx_energy_db.close() |
||
124 | print(error_string) |
||
125 | return error_string |
||
126 | |||
127 | # get the initial start datetime from config file in case there is no energy data |
||
128 | start_datetime_utc = datetime.strptime(config.start_datetime_utc, '%Y-%m-%d %H:%M:%S') |
||
129 | start_datetime_utc = start_datetime_utc.replace(tzinfo=timezone.utc) |
||
130 | start_datetime_utc = start_datetime_utc.replace(minute=0, second=0, microsecond=0) |
||
131 | |||
132 | try: |
||
133 | query = (" SELECT MAX(start_datetime_utc) " |
||
134 | " FROM tbl_meter_hourly " |
||
135 | " WHERE meter_id = %s ") |
||
136 | cursor_energy_db.execute(query, (meter['id'],)) |
||
137 | row_datetime = cursor_energy_db.fetchone() |
||
138 | except Exception as e: |
||
139 | error_string = "Error in step 1.3 of meter.worker " + str(e) + " for '" + meter['name'] + "'" |
||
140 | if cursor_energy_db: |
||
141 | cursor_energy_db.close() |
||
142 | if cnx_energy_db: |
||
143 | cnx_energy_db.close() |
||
144 | print(error_string) |
||
145 | return error_string |
||
146 | |||
147 | if row_datetime is not None and len(row_datetime) > 0 and isinstance(row_datetime[0], datetime): |
||
148 | start_datetime_utc = row_datetime[0].replace(tzinfo=timezone.utc) |
||
149 | # replace second and microsecond with 0 |
||
150 | # NOTE: DO NOT replace minute in case of calculating in half hourly |
||
151 | start_datetime_utc = start_datetime_utc.replace(second=0, microsecond=0) |
||
152 | # start from the next time slot |
||
153 | start_datetime_utc += timedelta(minutes=config.minutes_to_count) |
||
154 | |||
155 | end_datetime_utc = datetime.utcnow().replace(tzinfo=timezone.utc) |
||
156 | # we should allow myems-cleaning service to take at most [minutes_to_clean] minutes to clean the data |
||
157 | end_datetime_utc -= timedelta(minutes=config.minutes_to_clean) |
||
158 | |||
159 | time_difference = end_datetime_utc - start_datetime_utc |
||
160 | time_difference_in_minutes = time_difference / timedelta(minutes=1) |
||
161 | if time_difference_in_minutes < config.minutes_to_count: |
||
162 | error_string = "it's too early to calculate" + " for '" + meter['name'] + "'" |
||
163 | print(error_string) |
||
164 | return error_string |
||
165 | |||
166 | # trim end_datetime_utc |
||
167 | trimmed_end_datetime_utc = start_datetime_utc + timedelta(minutes=config.minutes_to_count) |
||
168 | while trimmed_end_datetime_utc <= end_datetime_utc: |
||
169 | trimmed_end_datetime_utc += timedelta(minutes=config.minutes_to_count) |
||
170 | |||
171 | end_datetime_utc = trimmed_end_datetime_utc - timedelta(minutes=config.minutes_to_count) |
||
172 | |||
173 | if end_datetime_utc <= start_datetime_utc: |
||
174 | error_string = "it's too early to calculate" + " for '" + meter['name'] + "'" |
||
175 | print(error_string) |
||
176 | return error_string |
||
177 | |||
178 | print("start_datetime_utc: " + start_datetime_utc.isoformat()[0:19] |
||
179 | + "end_datetime_utc: " + end_datetime_utc.isoformat()[0:19]) |
||
180 | |||
181 | #################################################################################################################### |
||
182 | # Step 2: Get raw data from historical database between start_datetime_utc and end_datetime_utc |
||
183 | #################################################################################################################### |
||
184 | |||
185 | cnx_historical_db = None |
||
186 | cursor_historical_db = None |
||
187 | try: |
||
188 | cnx_historical_db = mysql.connector.connect(**config.myems_historical_db) |
||
189 | cursor_historical_db = cnx_historical_db.cursor() |
||
190 | except Exception as e: |
||
191 | error_string = "Error in step 1.2 of meter.worker " + str(e) + " for '" + meter['name'] + "'" |
||
192 | if cursor_historical_db: |
||
193 | cursor_historical_db.close() |
||
194 | if cnx_historical_db: |
||
195 | cnx_historical_db.close() |
||
196 | |||
197 | if cursor_energy_db: |
||
198 | cursor_energy_db.close() |
||
199 | if cnx_energy_db: |
||
200 | cnx_energy_db.close() |
||
201 | |||
202 | print(error_string) |
||
203 | return error_string |
||
204 | |||
205 | # query latest record before start_datetime_utc |
||
206 | energy_value_just_before_start = dict() |
||
207 | try: |
||
208 | query = (" SELECT utc_date_time, actual_value " |
||
209 | " FROM tbl_energy_value " |
||
210 | " WHERE point_id = %s AND utc_date_time < %s AND is_bad = FALSE " |
||
211 | " ORDER BY utc_date_time DESC " |
||
212 | " LIMIT 1 ") |
||
213 | cursor_historical_db.execute(query, (meter['point_id'], start_datetime_utc,)) |
||
214 | row_energy_value_before_start = cursor_historical_db.fetchone() |
||
215 | |||
216 | if row_energy_value_before_start is not None and len(row_energy_value_before_start) > 0: |
||
217 | energy_value_just_before_start = {"utc_date_time": row_energy_value_before_start[0], |
||
218 | "actual_value": row_energy_value_before_start[1]} |
||
219 | except Exception as e: |
||
220 | error_string = "Error in step 2.2 of meter.worker " + str(e) + " for '" + meter['name'] + "'" |
||
221 | if cursor_historical_db: |
||
222 | cursor_historical_db.close() |
||
223 | if cnx_historical_db: |
||
224 | cnx_historical_db.close() |
||
225 | |||
226 | if cursor_energy_db: |
||
227 | cursor_energy_db.close() |
||
228 | if cnx_energy_db: |
||
229 | cnx_energy_db.close() |
||
230 | |||
231 | print(error_string) |
||
232 | return error_string |
||
233 | |||
234 | # query energy values to be normalized |
||
235 | try: |
||
236 | query = (" SELECT utc_date_time, actual_value " |
||
237 | " FROM tbl_energy_value " |
||
238 | " WHERE point_id = %s AND utc_date_time >= %s AND utc_date_time < %s AND is_bad = FALSE " |
||
239 | " ORDER BY utc_date_time ") |
||
240 | cursor_historical_db.execute(query, (meter['point_id'], start_datetime_utc, end_datetime_utc)) |
||
241 | rows_energy_values = cursor_historical_db.fetchall() |
||
242 | except Exception as e: |
||
243 | error_string = "Error in step 2.3 of meter.worker " + str(e) + " for '" + meter['name'] + "'" |
||
244 | |||
245 | if cursor_energy_db: |
||
246 | cursor_energy_db.close() |
||
247 | if cnx_energy_db: |
||
248 | cnx_energy_db.close() |
||
249 | |||
250 | print(error_string) |
||
251 | return error_string |
||
252 | finally: |
||
253 | if cursor_historical_db: |
||
254 | cursor_historical_db.close() |
||
255 | if cnx_historical_db: |
||
256 | cnx_historical_db.close() |
||
257 | |||
258 | #################################################################################################################### |
||
259 | # Step 3: Normalize energy values by minutes_to_count |
||
260 | #################################################################################################################### |
||
261 | |||
262 | #################################################################################################################### |
||
263 | # special test case 1 (disconnected) |
||
264 | # id point_id utc_date_time actual_value |
||
265 | # '878152', '3315', '2016-12-05 23:58:46', '38312088' |
||
266 | # '878183', '3315', '2016-12-05 23:59:48', '38312088' |
||
267 | # '878205', '3315', '2016-12-06 06:14:49', '38315900' |
||
268 | # '878281', '3315', '2016-12-06 06:15:50', '38315928' |
||
269 | # '878357', '3315', '2016-12-06 06:16:52', '38315928' |
||
270 | #################################################################################################################### |
||
271 | |||
272 | #################################################################################################################### |
||
273 | # special test case 2 (a new added used meter) |
||
274 | # id, point_id, utc_date_time, actual_value |
||
275 | # '19070111', '1734', '2017-03-27 02:36:07', '56842220.77297248' |
||
276 | # '19069943', '1734', '2017-03-27 02:35:04', '56842208.420127675' |
||
277 | # '19069775', '1734', '2017-03-27 02:34:01', '56842195.95270827' |
||
278 | # '19069608', '1734', '2017-03-27 02:32:58', '56842183.48610827' |
||
279 | # '19069439', '1734', '2017-03-27 02:31:53', '56842170.812365524' |
||
280 | # '19069270', '1734', '2017-03-27 02:30:48', '56842157.90797222' |
||
281 | # null, null, null, , null |
||
282 | |||
283 | #################################################################################################################### |
||
284 | |||
285 | #################################################################################################################### |
||
286 | # special test case 3 (hi_limit exceeded) |
||
287 | # id point_id utc_date_time actual_value |
||
288 | # '3230282', '3336', '2016-12-24 08:26:14', '999984.0625' |
||
289 | # '3230401', '3336', '2016-12-24 08:27:15', '999984.0625' |
||
290 | # '3230519', '3336', '2016-12-24 08:28:17', '999984.0625' |
||
291 | # '3230638', '3336', '2016-12-24 08:29:18', '20' |
||
292 | # '3230758', '3336', '2016-12-24 08:30:20', '20' |
||
293 | # '3230878', '3336', '2016-12-24 08:31:21', '20' |
||
294 | #################################################################################################################### |
||
295 | |||
296 | #################################################################################################################### |
||
297 | # test case 4 (recovered from bad zeroes) |
||
298 | # id point_id utc_date_time actual_value is_bad |
||
299 | # 300366736 1003344 2019-03-14 02:03:20 1103860.625 |
||
300 | # 300366195 1003344 2019-03-14 02:02:19 1103845 |
||
301 | # 300365654 1003344 2019-03-14 02:01:19 1103825.5 |
||
302 | # 300365106 1003344 2019-03-14 02:00:18 1103804.25 |
||
303 | # 300364562 1003344 2019-03-14 01:59:17 1103785.625 |
||
304 | # 300364021 1003344 2019-03-14 01:58:17 1103770.875 |
||
305 | # 300363478 1003344 2019-03-14 01:57:16 1103755.125 |
||
306 | # 300362936 1003344 2019-03-14 01:56:16 1103739.375 |
||
307 | # 300362393 1003344 2019-03-14 01:55:15 1103720.625 |
||
308 | # 300361851 1003344 2019-03-14 01:54:15 1103698.125 |
||
309 | # 300361305 1003344 2019-03-14 01:53:14 1103674.75 |
||
310 | # 300360764 1003344 2019-03-14 01:52:14 1103649 |
||
311 | # 300360221 1003344 2019-03-14 01:51:13 1103628.25 |
||
312 | # 300359676 1003344 2019-03-14 01:50:13 1103608.625 |
||
313 | # 300359133 1003344 2019-03-14 01:49:12 1103586.75 |
||
314 | # 300358592 1003344 2019-03-14 01:48:12 1103564 |
||
315 | # 300358050 1003344 2019-03-14 01:47:11 1103542 |
||
316 | # 300357509 1003344 2019-03-14 01:46:11 1103520.625 |
||
317 | # 300356966 1003344 2019-03-14 01:45:10 1103499.375 |
||
318 | # 300356509 1003344 2019-03-14 01:44:10 1103478.25 |
||
319 | # 300355964 1003344 2019-03-14 01:43:09 1103456.25 |
||
320 | # 300355419 1003344 2019-03-14 01:42:09 1103435.5 |
||
321 | # 300354878 1003344 2019-03-14 01:41:08 1103414.625 |
||
322 | # 300354335 1003344 2019-03-14 01:40:08 1103391.875 |
||
323 | # 300353793 1003344 2019-03-14 01:39:07 1103373 |
||
324 | # 300353248 1003344 2019-03-14 01:38:07 1103349 |
||
325 | # 300352705 1003344 2019-03-14 01:37:06 1103325.75 |
||
326 | # 300352163 1003344 2019-03-14 01:36:06 0 1 |
||
327 | # 300351621 1003344 2019-03-14 01:35:05 0 1 |
||
328 | # 300351080 1003344 2019-03-14 01:34:05 0 1 |
||
329 | # 300350532 1003344 2019-03-14 01:33:04 0 1 |
||
330 | # 300349988 1003344 2019-03-14 01:32:04 0 1 |
||
331 | # 300349446 1003344 2019-03-14 01:31:03 0 1 |
||
332 | # 300348903 1003344 2019-03-14 01:30:02 0 1 |
||
333 | # 300348359 1003344 2019-03-14 01:29:02 0 1 |
||
334 | # 300347819 1003344 2019-03-14 01:28:01 0 1 |
||
335 | # 300347277 1003344 2019-03-14 01:27:01 0 1 |
||
336 | # 300346733 1003344 2019-03-14 01:26:00 0 1 |
||
337 | # 300346191 1003344 2019-03-14 01:25:00 0 1 |
||
338 | #################################################################################################################### |
||
339 | |||
340 | normalized_values = list() |
||
341 | if rows_energy_values is None or len(rows_energy_values) == 0: |
||
342 | # NOTE: there isn't any value to be normalized |
||
343 | # that means the meter is offline or all values are bad |
||
344 | current_datetime_utc = start_datetime_utc |
||
345 | while current_datetime_utc < end_datetime_utc: |
||
346 | normalized_values.append({'start_datetime_utc': current_datetime_utc, 'actual_value': Decimal(0.0)}) |
||
347 | current_datetime_utc += timedelta(minutes=config.minutes_to_count) |
||
348 | else: |
||
349 | maximum = Decimal(0.0) |
||
350 | if energy_value_just_before_start is not None and \ |
||
351 | len(energy_value_just_before_start) > 0 and \ |
||
352 | energy_value_just_before_start['actual_value'] > Decimal(0.0): |
||
353 | maximum = energy_value_just_before_start['actual_value'] |
||
354 | |||
355 | current_datetime_utc = start_datetime_utc |
||
356 | while current_datetime_utc < end_datetime_utc: |
||
357 | initial_maximum = maximum |
||
358 | # get all energy values in current time slot |
||
359 | current_energy_values = list() |
||
360 | while len(rows_energy_values) > 0: |
||
361 | row_energy_value = rows_energy_values.pop(0) |
||
362 | energy_value_datetime = row_energy_value[0].replace(tzinfo=timezone.utc) |
||
363 | if energy_value_datetime < current_datetime_utc + timedelta(minutes=config.minutes_to_count): |
||
364 | current_energy_values.append(row_energy_value) |
||
365 | else: |
||
366 | rows_energy_values.insert(0, row_energy_value) |
||
367 | break |
||
368 | |||
369 | # get the energy increment one by one in current time slot |
||
370 | increment = Decimal(0.0) |
||
371 | # maximum should be equal to the maximum value of last time here |
||
372 | for index in range(len(current_energy_values)): |
||
373 | current_energy_value = current_energy_values[index] |
||
374 | if maximum < current_energy_value[1]: |
||
375 | increment += current_energy_value[1] - maximum |
||
376 | maximum = current_energy_value[1] |
||
377 | |||
378 | # omit huge initial value for a new meter |
||
379 | # or omit huge value for a recovered meter with zero values during failure |
||
380 | # NOTE: this method may cause the lose of energy consumption in this time slot |
||
381 | if initial_maximum <= Decimal(0.1): |
||
382 | increment = Decimal(0.0) |
||
383 | |||
384 | # check with hourly low limit |
||
385 | if increment < meter['hourly_low_limit']: |
||
386 | increment = Decimal(0.0) |
||
387 | |||
388 | # check with hourly high limit |
||
389 | # NOTE: this method may cause the lose of energy consumption in this time slot |
||
390 | if increment > meter['hourly_high_limit']: |
||
391 | increment = Decimal(0.0) |
||
392 | |||
393 | meta_data = {'start_datetime_utc': current_datetime_utc, |
||
394 | 'actual_value': increment} |
||
395 | # append mete_data |
||
396 | normalized_values.append(meta_data) |
||
397 | current_datetime_utc += timedelta(minutes=config.minutes_to_count) |
||
398 | |||
399 | #################################################################################################################### |
||
400 | # Step 4: Insert into energy database |
||
401 | #################################################################################################################### |
||
402 | View Code Duplication | if len(normalized_values) > 0: |
|
403 | try: |
||
404 | add_values = (" INSERT INTO tbl_meter_hourly (meter_id, start_datetime_utc, actual_value) " |
||
405 | " VALUES ") |
||
406 | |||
407 | for meta_data in normalized_values: |
||
408 | add_values += " (" + str(meter['id']) + "," |
||
409 | add_values += "'" + meta_data['start_datetime_utc'].isoformat()[0:19] + "'," |
||
410 | add_values += str(meta_data['actual_value']) + "), " |
||
411 | # trim ", " at the end of string and then execute |
||
412 | cursor_energy_db.execute(add_values[:-2]) |
||
413 | cnx_energy_db.commit() |
||
414 | except Exception as e: |
||
415 | error_string = "Error in step 4.1 of meter.worker " + str(e) + " for '" + meter['name'] + "'" |
||
416 | if cursor_energy_db: |
||
417 | cursor_energy_db.close() |
||
418 | if cnx_energy_db: |
||
419 | cnx_energy_db.close() |
||
420 | |||
421 | print(error_string) |
||
422 | return error_string |
||
423 | |||
424 | if cursor_energy_db: |
||
425 | cursor_energy_db.close() |
||
426 | if cnx_energy_db: |
||
427 | cnx_energy_db.close() |
||
428 | |||
429 | print("End of processing meter: " + "'" + meter['name'] + "'") |
||
430 | return None |
||
431 |