ecopipeline 0.9.4__py3-none-any.whl → 0.10.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
1
  from .event_tracking import *
2
- __all__ = ['flag_boundary_alarms','power_ratio_alarm']
2
+ __all__ = ['central_alarm_df_creator','flag_boundary_alarms','power_ratio_alarm','flag_abnormal_COP']
@@ -2,6 +2,107 @@ import pandas as pd
2
2
  import numpy as np
3
3
  import datetime as dt
4
4
  from ecopipeline import ConfigManager
5
+ import re
6
+ import mysql.connector.errors as mysqlerrors
7
+
8
+ def central_alarm_df_creator(df: pd.DataFrame, daily_data : pd.DataFrame, config : ConfigManager, system: str = "",
9
+ default_cop_high_bound : float = 4.5, default_cop_low_bound : float = 0,
10
+ default_boundary_fault_time : int = 15, site_name : str = None) -> pd.DataFrame:
11
+ day_list = daily_data.index.to_list()
12
+ print('Checking for alarms...')
13
+ alarm_df = _convert_silent_alarm_dict_to_df({})
14
+ boundary_alarm_df = flag_boundary_alarms(df, config, full_days=day_list, system=system, default_fault_time= default_boundary_fault_time)
15
+ pwr_alarm_df = power_ratio_alarm(daily_data, config, system=system)
16
+ abnormal_COP_df = flag_abnormal_COP(daily_data, config, system = system, default_high_bound=default_cop_high_bound, default_low_bound=default_cop_low_bound)
17
+
18
+ if len(boundary_alarm_df) > 0:
19
+ print("Boundary alarms detected. Adding them to event df...")
20
+ alarm_df = boundary_alarm_df
21
+ else:
22
+ print("No boundary alarms detected.")
23
+
24
+ if len(pwr_alarm_df) > 0:
25
+ print("Power alarms detected. Adding them to event df...")
26
+ alarm_df = pd.concat([alarm_df, pwr_alarm_df])
27
+ else:
28
+ print("No power alarms detected.")
29
+
30
+ if _check_if_during_ongoing_cop_alarm(daily_data, config, site_name):
31
+ print("Ongoing DATA_LOSS_COP detected. No further DATA_LOSS_COP events will be uploaded")
32
+ elif len(abnormal_COP_df) > 0:
33
+ print("Abnormal COPs detected. Adding them to event df...")
34
+ alarm_df = pd.concat([alarm_df, abnormal_COP_df])
35
+ else:
36
+ print("No abnormal COPs.")
37
+
38
+ return alarm_df
39
+
40
+ def flag_abnormal_COP(daily_data: pd.DataFrame, config : ConfigManager, system: str = "", default_high_bound : float = 4.5, default_low_bound : float = 0) -> pd.DataFrame:
41
+ variable_names_path = config.get_var_names_path()
42
+ try:
43
+ bounds_df = pd.read_csv(variable_names_path)
44
+ except FileNotFoundError:
45
+ print("File Not Found: ", variable_names_path)
46
+ return pd.DataFrame()
47
+
48
+ if (system != ""):
49
+ if not 'system' in bounds_df.columns:
50
+ raise Exception("system parameter is non null, however, system is not present in Variable_Names.csv")
51
+ bounds_df = bounds_df.loc[bounds_df['system'] == system]
52
+ if not "variable_name" in bounds_df.columns:
53
+ raise Exception(f"variable_name is not present in Variable_Names.csv")
54
+ if not 'pretty_name' in bounds_df.columns:
55
+ bounds_df['pretty_name'] = bounds_df['variable_name']
56
+ else:
57
+ bounds_df['pretty_name'] = bounds_df['pretty_name'].fillna(bounds_df['variable_name'])
58
+ if not 'high_alarm' in bounds_df.columns:
59
+ bounds_df['high_alarm'] = default_high_bound
60
+ else:
61
+ bounds_df['high_alarm'] = bounds_df['high_alarm'].fillna(default_high_bound)
62
+ if not 'low_alarm' in bounds_df.columns:
63
+ bounds_df['low_alarm'] = default_low_bound
64
+ else:
65
+ bounds_df['low_alarm'] = bounds_df['low_alarm'].fillna(default_low_bound)
66
+
67
+ bounds_df = bounds_df.loc[:, ["variable_name", "high_alarm", "low_alarm", "pretty_name"]]
68
+ bounds_df.dropna(axis=0, thresh=2, inplace=True)
69
+ bounds_df.set_index(['variable_name'], inplace=True)
70
+
71
+ cop_pattern = re.compile(r'^(COP\w*|SystemCOP\w*)$')
72
+ cop_columns = [col for col in daily_data.columns if re.match(cop_pattern, col)]
73
+
74
+ alarms_dict = {}
75
+ if not daily_data.empty and len(cop_columns) > 0:
76
+ for bound_var, bounds in bounds_df.iterrows():
77
+ if bound_var in cop_columns:
78
+ for day, day_values in daily_data.iterrows():
79
+ if day_values[bound_var] > bounds['high_alarm'] or day_values[bound_var] < bounds['low_alarm']:
80
+ alarm_str = f"{bounds['pretty_name']} = {round(day_values[bound_var],2)}"
81
+ if day in alarms_dict:
82
+ alarms_dict[day] = alarms_dict[day] + f", {alarm_str}"
83
+ else:
84
+ alarms_dict[day] = f"Unexpected COP Value(s) detected: {alarm_str}"
85
+ return _convert_event_type_dict_to_df(alarms_dict)
86
+
87
+ def _check_if_during_ongoing_cop_alarm(daily_df : pd.DataFrame, config : ConfigManager, site_name : str = None) -> bool:
88
+ if site_name is None:
89
+ site_name = config.get_site_name()
90
+ connection, cursor = config.connect_db()
91
+ on_going_cop = False
92
+ try:
93
+ # find existing times in database for upsert statement
94
+ cursor.execute(
95
+ f"SELECT id FROM site_events WHERE start_time_pt <= '{daily_df.index.min()}' AND (end_time_pt IS NULL OR end_time_pt >= '{daily_df.index.max()}') AND site_name = '{site_name}' AND event_type = 'DATA_LOSS_COP'")
96
+ # Fetch the results into a DataFrame
97
+ existing_rows = pd.DataFrame(cursor.fetchall(), columns=['id'])
98
+ if not existing_rows.empty:
99
+ on_going_cop = True
100
+
101
+ except mysqlerrors.Error as e:
102
+ print(f"Retrieving data from site_events caused exception: {e}")
103
+ connection.close()
104
+ cursor.close()
105
+ return on_going_cop
5
106
 
6
107
  def flag_boundary_alarms(df: pd.DataFrame, config : ConfigManager, default_fault_time : int = 15, system: str = "", full_days : list = None) -> pd.DataFrame:
7
108
  """
@@ -102,6 +203,25 @@ def _convert_silent_alarm_dict_to_df(alarm_dict : dict) -> pd.DataFrame:
102
203
  event_df.set_index('start_time_pt', inplace=True)
103
204
  return event_df
104
205
 
206
+ def _convert_event_type_dict_to_df(alarm_dict : dict, event_type = 'DATA_LOSS_COP') -> pd.DataFrame:
207
+ events = {
208
+ 'start_time_pt' : [],
209
+ 'end_time_pt' : [],
210
+ 'event_type' : [],
211
+ 'event_detail' : [],
212
+ 'variable_name' : []
213
+ }
214
+ for key, value in alarm_dict.items():
215
+ events['start_time_pt'].append(key)
216
+ events['end_time_pt'].append(key)
217
+ events['event_type'].append(event_type)
218
+ events['event_detail'].append(value)
219
+ events['variable_name'].append(None)
220
+
221
+ event_df = pd.DataFrame(events)
222
+ event_df.set_index('start_time_pt', inplace=True)
223
+ return event_df
224
+
105
225
  def _check_and_add_alarm(df : pd.DataFrame, mask : pd.Series, alarms_dict, day, fault_time : int, var_name : str, pretty_name : str, alarm_type : str = 'Lower'):
106
226
  # KNOWN BUG : Avg value during fault time excludes the first (fault_time-1) minutes of each fault window
107
227
  next_day = day + pd.Timedelta(days=1)
@@ -130,7 +250,7 @@ def _check_and_add_alarm(df : pd.DataFrame, mask : pd.Series, alarms_dict, day,
130
250
  else:
131
251
  alarms_dict[day] = [[var_name, alarm_string]]
132
252
 
133
- def power_ratio_alarm(daily_df: pd.DataFrame, config : ConfigManager, system: str = "") -> pd.DataFrame:
253
+ def power_ratio_alarm(daily_df: pd.DataFrame, config : ConfigManager, system: str = "", verbose : bool = False) -> pd.DataFrame:
134
254
  daily_df_copy = daily_df.copy()
135
255
  variable_names_path = config.get_var_names_path()
136
256
  try:
@@ -148,6 +268,8 @@ def power_ratio_alarm(daily_df: pd.DataFrame, config : ConfigManager, system: st
148
268
  raise Exception(f"{required_column} is not present in Variable_Names.csv")
149
269
  if not 'pretty_name' in ratios_df.columns:
150
270
  ratios_df['pretty_name'] = ratios_df['variable_name']
271
+ else:
272
+ ratios_df['pretty_name'] = ratios_df['pretty_name'].fillna(ratios_df['variable_name'])
151
273
  ratios_df = ratios_df.loc[:, ["variable_name", "alarm_codes", "pretty_name"]]
152
274
  ratios_df = ratios_df[ratios_df['alarm_codes'].str.contains('PR', na=False)]
153
275
  ratios_df.dropna(axis=0, thresh=2, inplace=True)
@@ -172,13 +294,16 @@ def power_ratio_alarm(daily_df: pd.DataFrame, config : ConfigManager, system: st
172
294
  ratio_dict[pr_id][3].append(ratios['pretty_name'])
173
295
  else:
174
296
  ratio_dict[pr_id] = [[ratios_var],[float(low_high[0])],[float(low_high[1])],[ratios['pretty_name']]]
175
-
297
+ if verbose:
298
+ print("ratio_dict keys:", ratio_dict.keys())
176
299
  alarms = {}
177
300
  for key, value_list in ratio_dict.items():
178
301
  daily_df_copy[key] = daily_df_copy[value_list[0]].sum(axis=1)
179
302
  for i in range(len(value_list[0])):
180
303
  column_name = value_list[0][i]
181
304
  daily_df_copy[f'{column_name}_{key}'] = (daily_df_copy[column_name]/daily_df_copy[key]) * 100
305
+ if verbose:
306
+ print(f"Ratios for {column_name}_{key}",daily_df_copy[f'{column_name}_{key}'])
182
307
  _check_and_add_ratio_alarm(daily_df_copy, key, column_name, value_list[3][i], alarms, value_list[2][i], value_list[1][i])
183
308
  return _convert_silent_alarm_dict_to_df(alarms)
184
309
 
ecopipeline/load/load.py CHANGED
@@ -302,6 +302,11 @@ def load_event_table(config : ConfigManager, event_df: pd.DataFrame, site_name :
302
302
  bool:
303
303
  A boolean value indicating if the data was successfully written to the database.
304
304
  """
305
+ # define constants
306
+ proj_cop_filters = ['MV_COMMISSIONED','PLANT_COMMISSIONED','DATA_LOSS_COP','SYSTEM_MAINTENANCE','SYSTEM_TESTING']
307
+ optim_cop_filters = ['MV_COMMISSIONED','PLANT_COMMISSIONED','DATA_LOSS_COP','INSTALLATION_ERROR_COP',
308
+ 'PARTIAL_OCCUPANCY','SOO_PERIOD_COP','SYSTEM_TESTING','EQUIPMENT_MALFUNCTION',
309
+ 'SYSTEM_MAINTENANCE']
305
310
  # Drop empty columns
306
311
  event_df = event_df.dropna(axis=1, how='all')
307
312
 
@@ -329,7 +334,7 @@ def load_event_table(config : ConfigManager, event_df: pd.DataFrame, site_name :
329
334
  column_names += "," + column
330
335
 
331
336
  # create SQL statement
332
- insert_str = "INSERT INTO " + table_name + " (" + column_names + ", variable_name, last_modified_date, last_modified_by) VALUES (%s,%s,%s,%s,%s,%s,'"+datetime.now().strftime('%Y-%m-%d %H:%M:%S')+"','automatic_upload')"
337
+ insert_str = "INSERT INTO " + table_name + " (" + column_names + ", variable_name, summary_filtered, optim_filtered, last_modified_date, last_modified_by) VALUES (%s, %s, %s,%s,%s,%s,%s,%s,'"+datetime.now().strftime('%Y-%m-%d %H:%M:%S')+"','automatic_upload')"
333
338
 
334
339
  if not 'variable_name' in event_df.columns:
335
340
  event_df['variable_name'] = None
@@ -338,11 +343,15 @@ def load_event_table(config : ConfigManager, event_df: pd.DataFrame, site_name :
338
343
  full_column_names.append('last_modified_date')
339
344
  full_column_names.append('last_modified_by')
340
345
  full_column_names.append('variable_name')
346
+ full_column_names.append('summary_filtered')
347
+ full_column_names.append('optim_filtered')
348
+
341
349
  full_column_types = column_types[1:]
342
350
  full_column_types.append('datetime')
343
351
  full_column_types.append('varchar(60)')
344
352
  full_column_types.append('varchar(70)')
345
-
353
+ full_column_types.append('tinyint(1)')
354
+ full_column_types.append('tinyint(1)')
346
355
 
347
356
  existing_rows = pd.DataFrame({
348
357
  'start_time_pt' : [],
@@ -376,7 +385,7 @@ def load_event_table(config : ConfigManager, event_df: pd.DataFrame, site_name :
376
385
  ignoredRows = 0
377
386
  try:
378
387
  for index, row in event_df.iterrows():
379
- time_data = [index,site_name,row['end_time_pt'],row['event_type'],row['event_detail'],row['variable_name']]
388
+ time_data = [index,site_name,row['end_time_pt'],row['event_type'],row['event_detail'],row['variable_name'], row['event_type'] in proj_cop_filters, row['event_type'] in optim_cop_filters]
380
389
  #remove nans and infinites
381
390
  time_data = [None if (x is None or pd.isna(x)) else x for x in time_data]
382
391
  time_data = [None if (x == float('inf') or x == float('-inf')) else x for x in time_data]
@@ -436,8 +445,8 @@ def report_data_loss(config : ConfigManager, site_name : str = None):
436
445
  print(f"logging DATA_LOSS_COP into {table_name}")
437
446
 
438
447
  # create SQL statement
439
- insert_str = "INSERT INTO " + table_name + " (start_time_pt, site_name, event_detail, event_type, last_modified_date, last_modified_by) VALUES "
440
- insert_str += f"('{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}','{site_name}','{error_string}','DATA_LOSS_COP','{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}','automatic_upload')"
448
+ insert_str = "INSERT INTO " + table_name + " (start_time_pt, site_name, event_detail, event_type, summary_filtered, optim_filtered, last_modified_date, last_modified_by) VALUES "
449
+ insert_str += f"('{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}','{site_name}','{error_string}','DATA_LOSS_COP', true, true, '{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}','automatic_upload')"
441
450
 
442
451
  existing_rows = pd.DataFrame({
443
452
  'id' : []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecopipeline
3
- Version: 0.9.4
3
+ Version: 0.10.1
4
4
  Summary: Contains functions for use in Ecotope Datapipelines
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: License :: OSI Approved :: GNU General Public License (GPL)
@@ -1,10 +1,10 @@
1
1
  ecopipeline/__init__.py,sha256=d48mO5La6OrQDkRe_qqoY6lUx7x-e8krOH388jmWjwU,218
2
- ecopipeline/event_tracking/__init__.py,sha256=hVV2IHyt3pLSIOESpINr8-sQBmK98t7CiVb1IlvE5xQ,84
3
- ecopipeline/event_tracking/event_tracking.py,sha256=0w-dgsziC-JolqCPxiyryA9PiWKETQYZ3Gbt0faVgbI,17088
2
+ ecopipeline/event_tracking/__init__.py,sha256=SV2kkvJgptjeyLQlqHWcDRpQO6-JC433_dRZ3H9-ZNU,131
3
+ ecopipeline/event_tracking/event_tracking.py,sha256=ly6y6IM8_jlwa9dZ-FlAYEC-rbNKPc1NByYD12lyl2w,23168
4
4
  ecopipeline/extract/__init__.py,sha256=gQ3sak6NJ63Gpo-hZXrtZfeKOTHLRyAVXfTgxxRpqPo,675
5
5
  ecopipeline/extract/extract.py,sha256=y32feIIzgABwrwfduNQM1hICmkVOU4PYu6-M07zCLpU,51422
6
6
  ecopipeline/load/__init__.py,sha256=NLa_efQJZ8aP-J0Y5xx9DP7mtfRH9jY6Jz1ZMZN_BAA,292
7
- ecopipeline/load/load.py,sha256=Ptxr0MOjns_HeVSmZsLLApHJGB-z6XOB2m8LNiVaD7E,23860
7
+ ecopipeline/load/load.py,sha256=PaSGWOZI0Xg44_SWN7htn2DPIAU_s8mOtCGibXq25tM,24614
8
8
  ecopipeline/transform/__init__.py,sha256=YveBLBsNhfI4qZP04doa0NrTbEKvjDAUDEKtEPdFPfU,2545
9
9
  ecopipeline/transform/bayview.py,sha256=TP24dnTsUD95X-f6732egPZKjepFLJgDm9ImGr-fppY,17899
10
10
  ecopipeline/transform/lbnl.py,sha256=EQ54G4rJXaZ7pwVusKcdK2KBehSdCsNo2ybphtMGs7o,33400
@@ -12,8 +12,8 @@ ecopipeline/transform/transform.py,sha256=wL4B00XBwLWVlf7goOLSHKgLFmIsXprQNepGLL
12
12
  ecopipeline/utils/ConfigManager.py,sha256=-g1wtExdvhYO5Y6Q3cRbywa__DxRMFruLrB4YanwaPY,12168
13
13
  ecopipeline/utils/__init__.py,sha256=ccWUR0m7gD9DfcgsxBCLOfi4lho6RdYuB2Ugy_g6ZdQ,28
14
14
  ecopipeline/utils/unit_convert.py,sha256=VFh1we2Y8KV3u21BeWb-U3TlZJXo83q5vdxxkpgcuME,3064
15
- ecopipeline-0.9.4.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- ecopipeline-0.9.4.dist-info/METADATA,sha256=W1-fbJlxCCnlkqlSV5KgSvQlKuQTLYYxFvCgtQY8XFM,2329
17
- ecopipeline-0.9.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
- ecopipeline-0.9.4.dist-info/top_level.txt,sha256=WOPFJH2LIgKqm4lk2OnFF5cgVkYibkaBxIxgvLgO7y0,12
19
- ecopipeline-0.9.4.dist-info/RECORD,,
15
+ ecopipeline-0.10.1.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ ecopipeline-0.10.1.dist-info/METADATA,sha256=LqVlpLeTsKuQTy_LhAcziKYvS_NgAiquydrzALKypDc,2330
17
+ ecopipeline-0.10.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ ecopipeline-0.10.1.dist-info/top_level.txt,sha256=WOPFJH2LIgKqm4lk2OnFF5cgVkYibkaBxIxgvLgO7y0,12
19
+ ecopipeline-0.10.1.dist-info/RECORD,,