ecopipeline 0.11.0__py3-none-any.whl → 0.11.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.
@@ -4,15 +4,17 @@ import datetime as dt
4
4
  from ecopipeline import ConfigManager
5
5
  import re
6
6
  import mysql.connector.errors as mysqlerrors
7
+ from datetime import timedelta
7
8
 
8
9
  def central_alarm_df_creator(df: pd.DataFrame, daily_data : pd.DataFrame, config : ConfigManager, system: str = "",
9
10
  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
+ default_boundary_fault_time : int = 15, site_name : str = None, day_table_name_header : str = "day",
12
+ power_ratio_period_days : int = 7) -> pd.DataFrame:
11
13
  day_list = daily_data.index.to_list()
12
14
  print('Checking for alarms...')
13
15
  alarm_df = _convert_silent_alarm_dict_to_df({})
14
16
  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)
17
+ pwr_alarm_df = power_ratio_alarm(daily_data, config, day_table_name = config.get_table_name(day_table_name_header), system=system, ratio_period_days=power_ratio_period_days)
16
18
  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
19
 
18
20
  if len(boundary_alarm_df) > 0:
@@ -251,7 +253,31 @@ def _check_and_add_alarm(df : pd.DataFrame, mask : pd.Series, alarms_dict, day,
251
253
  else:
252
254
  alarms_dict[day] = [[var_name, alarm_string]]
253
255
 
254
- def power_ratio_alarm(daily_df: pd.DataFrame, config : ConfigManager, system: str = "", verbose : bool = False) -> pd.DataFrame:
256
+ def power_ratio_alarm(daily_df: pd.DataFrame, config : ConfigManager, day_table_name : str, system: str = "", verbose : bool = False, ratio_period_days : int = 7) -> pd.DataFrame:
257
+ """
258
+ Function will take a pandas dataframe of daily data and location of alarm information in a csv,
259
+ and create an dataframe with applicable alarm events
260
+
261
+ Parameters
262
+ ----------
263
+ daily_df: pd.DataFrame
264
+ post-transformed dataframe for daily data. It should be noted that this function expects consecutive, in order days. If days
265
+ are out of order or have gaps, the function may return erroneous alarms.
266
+ config : ecopipeline.ConfigManager
267
+ The ConfigManager object that holds configuration data for the pipeline. Among other things, this object will point to a file
268
+ called Varriable_Names.csv in the input folder of the pipeline (e.g. "full/path/to/pipeline/input/Variable_Names.csv").
269
+ The file must have at least two columns which must be titled "variable_name", "alarm_codes" which should contain the
270
+ name of each variable in the dataframe that requires the alarming and the ratio alarm code in the form "PR_{Power Ratio Name}:{low percentage}-{high percentage}
271
+ system: str
272
+ string of system name if processing a particular system in a Variable_Names.csv file with multiple systems. Leave as an empty string if not aplicable.
273
+ verbose : bool
274
+ add print statements in power ratio
275
+
276
+ Returns
277
+ -------
278
+ pd.DataFrame:
279
+ Pandas dataframe with alarm events, empty if no alarms triggered
280
+ """
255
281
  daily_df_copy = daily_df.copy()
256
282
  variable_names_path = config.get_var_names_path()
257
283
  try:
@@ -274,8 +300,15 @@ def power_ratio_alarm(daily_df: pd.DataFrame, config : ConfigManager, system: st
274
300
  ratios_df = ratios_df.loc[:, ["variable_name", "alarm_codes", "pretty_name"]]
275
301
  ratios_df = ratios_df[ratios_df['alarm_codes'].str.contains('PR', na=False)]
276
302
  ratios_df.dropna(axis=0, thresh=2, inplace=True)
277
- ratios_df.set_index(['variable_name'], inplace=True)
303
+ if ratio_period_days > 1:
304
+ if verbose:
305
+ print(f"adding last {ratio_period_days} to daily_df")
306
+ daily_df_copy = _append_previous_days_to_df(daily_df_copy, config, ratio_period_days, day_table_name)
307
+ elif ratio_period_days < 1:
308
+ print("power ratio alarm period, ratio_period_days, must be more than 1")
309
+ return pd.DataFrame()
278
310
 
311
+ ratios_df.set_index(['variable_name'], inplace=True)
279
312
  ratio_dict = {}
280
313
  for ratios_var, ratios in ratios_df.iterrows():
281
314
  if not ratios_var in daily_df_copy.columns:
@@ -297,26 +330,111 @@ def power_ratio_alarm(daily_df: pd.DataFrame, config : ConfigManager, system: st
297
330
  ratio_dict[pr_id] = [[ratios_var],[float(low_high[0])],[float(low_high[1])],[ratios['pretty_name']]]
298
331
  if verbose:
299
332
  print("ratio_dict keys:", ratio_dict.keys())
333
+ # Create blocks of ratio_period_days
334
+ blocks_df = _create_period_blocks(daily_df_copy, ratio_period_days, verbose)
335
+
336
+ if blocks_df.empty:
337
+ print("No complete blocks available for analysis")
338
+ return pd.DataFrame()
339
+
300
340
  alarms = {}
301
341
  for key, value_list in ratio_dict.items():
302
- daily_df_copy[key] = daily_df_copy[value_list[0]].sum(axis=1)
342
+ # Calculate total for each block
343
+ blocks_df[key] = blocks_df[value_list[0]].sum(axis=1)
303
344
  for i in range(len(value_list[0])):
304
345
  column_name = value_list[0][i]
305
- daily_df_copy[f'{column_name}_{key}'] = (daily_df_copy[column_name]/daily_df_copy[key]) * 100
346
+ # Calculate ratio for each block
347
+ blocks_df[f'{column_name}_{key}'] = (blocks_df[column_name]/blocks_df[key]) * 100
306
348
  if verbose:
307
- print(f"Ratios for {column_name}_{key}",daily_df_copy[f'{column_name}_{key}'])
308
- _check_and_add_ratio_alarm(daily_df_copy, key, column_name, value_list[3][i], alarms, value_list[2][i], value_list[1][i])
309
- return _convert_silent_alarm_dict_to_df(alarms)
310
-
311
- def _check_and_add_ratio_alarm(daily_df: pd.DataFrame, alarm_key : str, column_name : str, pretty_name : str, alarms_dict : dict, high_bound : float, low_bound : float):
312
- alarm_daily_df = daily_df.loc[(daily_df[f"{column_name}_{alarm_key}"] < low_bound) | (daily_df[f"{column_name}_{alarm_key}"] > high_bound)]
313
- if not alarm_daily_df.empty:
314
- for day, values in alarm_daily_df.iterrows():
315
- alarm_str = f"Power ratio alarm: {pretty_name} accounted for {round(values[f'{column_name}_{alarm_key}'], 2)}% of {alarm_key} energy use. {round(low_bound, 2)}-{round(high_bound, 2)}% of {alarm_key} energy use expected."
316
- if day in alarms_dict:
317
- alarms_dict[day].append([column_name, alarm_str])
349
+ print(f"Block ratios for {column_name}_{key}:", blocks_df[f'{column_name}_{key}'])
350
+ _check_and_add_ratio_alarm_blocks(blocks_df, key, column_name, value_list[3][i], alarms, value_list[2][i], value_list[1][i], ratio_period_days)
351
+ return _convert_silent_alarm_dict_to_df(alarms)
352
+ # alarms = {}
353
+ # for key, value_list in ratio_dict.items():
354
+ # daily_df_copy[key] = daily_df_copy[value_list[0]].sum(axis=1)
355
+ # for i in range(len(value_list[0])):
356
+ # column_name = value_list[0][i]
357
+ # daily_df_copy[f'{column_name}_{key}'] = (daily_df_copy[column_name]/daily_df_copy[key]) * 100
358
+ # if verbose:
359
+ # print(f"Ratios for {column_name}_{key}",daily_df_copy[f'{column_name}_{key}'])
360
+ # _check_and_add_ratio_alarm(daily_df_copy, key, column_name, value_list[3][i], alarms, value_list[2][i], value_list[1][i])
361
+ # return _convert_silent_alarm_dict_to_df(alarms)
362
+
363
+ # def _check_and_add_ratio_alarm(daily_df: pd.DataFrame, alarm_key : str, column_name : str, pretty_name : str, alarms_dict : dict, high_bound : float, low_bound : float):
364
+ # alarm_daily_df = daily_df.loc[(daily_df[f"{column_name}_{alarm_key}"] < low_bound) | (daily_df[f"{column_name}_{alarm_key}"] > high_bound)]
365
+ # if not alarm_daily_df.empty:
366
+ # for day, values in alarm_daily_df.iterrows():
367
+ # alarm_str = f"Power ratio alarm: {pretty_name} accounted for {round(values[f'{column_name}_{alarm_key}'], 2)}% of {alarm_key} energy use. {round(low_bound, 2)}-{round(high_bound, 2)}% of {alarm_key} energy use expected."
368
+ # if day in alarms_dict:
369
+ # alarms_dict[day].append([column_name, alarm_str])
370
+ # else:
371
+ # alarms_dict[day] = [[column_name, alarm_str]]
372
+ def _check_and_add_ratio_alarm_blocks(blocks_df: pd.DataFrame, alarm_key: str, column_name: str, pretty_name: str, alarms_dict: dict, high_bound: float, low_bound: float, ratio_period_days: int):
373
+ """
374
+ Check for alarms in block-based ratios and add to alarms dictionary.
375
+ """
376
+ alarm_blocks_df = blocks_df.loc[(blocks_df[f"{column_name}_{alarm_key}"] < low_bound) | (blocks_df[f"{column_name}_{alarm_key}"] > high_bound)]
377
+ if not alarm_blocks_df.empty:
378
+ for block_end_date, values in alarm_blocks_df.iterrows():
379
+ alarm_str = f"Power ratio alarm ({ratio_period_days}-day block ending {block_end_date.strftime('%Y-%m-%d')}): {pretty_name} accounted for {round(values[f'{column_name}_{alarm_key}'], 2)}% of {alarm_key} energy use. {round(low_bound, 2)}-{round(high_bound, 2)}% of {alarm_key} energy use expected."
380
+ if block_end_date in alarms_dict:
381
+ alarms_dict[block_end_date].append([column_name, alarm_str])
318
382
  else:
319
- alarms_dict[day] = [[column_name, alarm_str]]
383
+ alarms_dict[block_end_date] = [[column_name, alarm_str]]
384
+
385
+ def _create_period_blocks(daily_df: pd.DataFrame, ratio_period_days: int, verbose: bool = False) -> pd.DataFrame:
386
+ """
387
+ Create blocks of ratio_period_days by summing values within each block.
388
+ Each block will be represented by its end date.
389
+ """
390
+ if len(daily_df) < ratio_period_days:
391
+ if verbose:
392
+ print(f"Not enough data for {ratio_period_days}-day blocks. Need at least {ratio_period_days} days, have {len(daily_df)}")
393
+ return pd.DataFrame()
394
+
395
+ blocks = []
396
+ block_dates = []
397
+
398
+ # Create blocks by summing consecutive groups of ratio_period_days
399
+ for i in range(ratio_period_days - 1, len(daily_df)):
400
+ start_idx = i - ratio_period_days + 1
401
+ end_idx = i + 1
402
+
403
+ block_data = daily_df.iloc[start_idx:end_idx].sum()
404
+ blocks.append(block_data)
405
+ # Use the end date of the block as the identifier
406
+ block_dates.append(daily_df.index[i])
407
+
408
+ if not blocks:
409
+ return pd.DataFrame()
410
+
411
+ blocks_df = pd.DataFrame(blocks, index=block_dates)
412
+
413
+ if verbose:
414
+ print(f"Created {len(blocks_df)} blocks of {ratio_period_days} days each")
415
+ print(f"Block date range: {blocks_df.index.min()} to {blocks_df.index.max()}")
416
+
417
+ return blocks_df
418
+
419
+ def _append_previous_days_to_df(daily_df: pd.DataFrame, config : ConfigManager, ratio_period_days : int, day_table_name : str, primary_key : str = "time_pt") -> pd.DataFrame:
420
+ db_connection, cursor = config.connect_db()
421
+ period_start = daily_df.index.min() - timedelta(ratio_period_days)
422
+ try:
423
+ # find existing times in database for upsert statement
424
+ cursor.execute(
425
+ f"SELECT * FROM {day_table_name} WHERE {primary_key} < '{daily_df.index.min()}' AND {primary_key} >= '{period_start}'")
426
+ result = cursor.fetchall()
427
+ column_names = [desc[0] for desc in cursor.description]
428
+ old_days_df = pd.DataFrame(result, columns=column_names)
429
+ old_days_df = old_days_df.set_index(primary_key)
430
+ daily_df = pd.concat([daily_df, old_days_df])
431
+ daily_df = daily_df.sort_index(ascending=True)
432
+ except mysqlerrors.Error:
433
+ print(f"Table {day_table_name} has no data.")
434
+
435
+ db_connection.close()
436
+ cursor.close()
437
+ return daily_df
320
438
 
321
439
  # def flag_dhw_outage(df: pd.DataFrame, daily_df : pd.DataFrame, dhw_outlet_column : str, supply_temp : int = 110, consecutive_minutes : int = 15) -> pd.DataFrame:
322
440
  # """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecopipeline
3
- Version: 0.11.0
3
+ Version: 0.11.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,6 +1,6 @@
1
1
  ecopipeline/__init__.py,sha256=pjC00JWsjVAhS0jUKHD-wyi4UIpTsWbIg9JaxLS1mlc,275
2
2
  ecopipeline/event_tracking/__init__.py,sha256=SV2kkvJgptjeyLQlqHWcDRpQO6-JC433_dRZ3H9-ZNU,131
3
- ecopipeline/event_tracking/event_tracking.py,sha256=LhL2qffLbEqpRE2PmRvZjj67oUxTKC2MKSqawI4XqEA,23264
3
+ ecopipeline/event_tracking/event_tracking.py,sha256=HffWAIAkNJ8INdG3_86RnDgw2bpHwv9hhkZ5oiiugZY,29653
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
@@ -13,8 +13,8 @@ ecopipeline/utils/ConfigManager.py,sha256=-g1wtExdvhYO5Y6Q3cRbywa__DxRMFruLrB4Ya
13
13
  ecopipeline/utils/NOAADataDownloader.py,sha256=iC2nl_O4PS1KFrchcPXRZxshwZwUMSqXy6BQBUwnOUU,20927
14
14
  ecopipeline/utils/__init__.py,sha256=7dT3tP6SMK4uBW6NBmQ8i6LaNTTuV6fpAZToBBlJ904,62
15
15
  ecopipeline/utils/unit_convert.py,sha256=VFh1we2Y8KV3u21BeWb-U3TlZJXo83q5vdxxkpgcuME,3064
16
- ecopipeline-0.11.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- ecopipeline-0.11.0.dist-info/METADATA,sha256=NqFlEBM6dYQK6CQ3dlKdiA5XvJe5P_c5J40Chv17SgQ,2330
18
- ecopipeline-0.11.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- ecopipeline-0.11.0.dist-info/top_level.txt,sha256=WOPFJH2LIgKqm4lk2OnFF5cgVkYibkaBxIxgvLgO7y0,12
20
- ecopipeline-0.11.0.dist-info/RECORD,,
16
+ ecopipeline-0.11.1.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ ecopipeline-0.11.1.dist-info/METADATA,sha256=_-HP7vfIrz6JBltdDkX4obF-AUJGrbxZfnFtrUBQ49k,2330
18
+ ecopipeline-0.11.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ ecopipeline-0.11.1.dist-info/top_level.txt,sha256=WOPFJH2LIgKqm4lk2OnFF5cgVkYibkaBxIxgvLgO7y0,12
20
+ ecopipeline-0.11.1.dist-info/RECORD,,