ecopipeline 0.9.4__tar.gz → 0.10.0__tar.gz

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.
Files changed (26) hide show
  1. {ecopipeline-0.9.4/src/ecopipeline.egg-info → ecopipeline-0.10.0}/PKG-INFO +1 -1
  2. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/setup.cfg +1 -1
  3. ecopipeline-0.10.0/src/ecopipeline/event_tracking/__init__.py +2 -0
  4. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/event_tracking/event_tracking.py +127 -2
  5. {ecopipeline-0.9.4 → ecopipeline-0.10.0/src/ecopipeline.egg-info}/PKG-INFO +1 -1
  6. ecopipeline-0.9.4/src/ecopipeline/event_tracking/__init__.py +0 -2
  7. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/LICENSE +0 -0
  8. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/README.md +0 -0
  9. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/pyproject.toml +0 -0
  10. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/setup.py +0 -0
  11. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/__init__.py +0 -0
  12. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/extract/__init__.py +0 -0
  13. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/extract/extract.py +0 -0
  14. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/load/__init__.py +0 -0
  15. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/load/load.py +0 -0
  16. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/transform/__init__.py +0 -0
  17. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/transform/bayview.py +0 -0
  18. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/transform/lbnl.py +0 -0
  19. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/transform/transform.py +0 -0
  20. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/utils/ConfigManager.py +0 -0
  21. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/utils/__init__.py +0 -0
  22. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline/utils/unit_convert.py +0 -0
  23. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline.egg-info/SOURCES.txt +0 -0
  24. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline.egg-info/dependency_links.txt +0 -0
  25. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline.egg-info/requires.txt +0 -0
  26. {ecopipeline-0.9.4 → ecopipeline-0.10.0}/src/ecopipeline.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecopipeline
3
- Version: 0.9.4
3
+ Version: 0.10.0
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,6 +1,6 @@
1
1
  [metadata]
2
2
  name = ecopipeline
3
- version = 0.9.4
3
+ version = 0.10.0
4
4
  authors = ["Carlos Bello, <bellocarlos@seattleu.edu>, Emil Fahrig <fahrigemil@seattleu.edu>, Casey Mang <cmang@seattleu.edu>, Julian Harris <harrisjulian@seattleu.edu>, Roger Tram <rtram@seattleu.edu>, Nolan Price <nolan@ecotope.com>"]
5
5
  description = Contains functions for use in Ecotope Datapipelines
6
6
  long_description = file: README.md
@@ -0,0 +1,2 @@
1
+ from .event_tracking import *
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecopipeline
3
- Version: 0.9.4
3
+ Version: 0.10.0
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,2 +0,0 @@
1
- from .event_tracking import *
2
- __all__ = ['flag_boundary_alarms','power_ratio_alarm']
File without changes
File without changes
File without changes
File without changes