ecopipeline 1.0.1__tar.gz → 1.0.2__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 (32) hide show
  1. {ecopipeline-1.0.1/src/ecopipeline.egg-info → ecopipeline-1.0.2}/PKG-INFO +1 -1
  2. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/setup.cfg +1 -1
  3. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/extract/__init__.py +2 -2
  4. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/extract/extract.py +74 -0
  5. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/transform/transform.py +3 -0
  6. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/utils/ConfigManager.py +20 -1
  7. {ecopipeline-1.0.1 → ecopipeline-1.0.2/src/ecopipeline.egg-info}/PKG-INFO +1 -1
  8. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/LICENSE +0 -0
  9. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/MANIFEST.in +0 -0
  10. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/README.md +0 -0
  11. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/pyproject.toml +0 -0
  12. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/setup.py +0 -0
  13. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/__init__.py +0 -0
  14. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/event_tracking/__init__.py +0 -0
  15. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/event_tracking/event_tracking.py +0 -0
  16. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/load/__init__.py +0 -0
  17. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/load/load.py +0 -0
  18. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/transform/__init__.py +0 -0
  19. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/transform/bayview.py +0 -0
  20. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/transform/lbnl.py +0 -0
  21. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/utils/NOAADataDownloader.py +0 -0
  22. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/utils/__init__.py +0 -0
  23. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/utils/pkls/__init__.py +0 -0
  24. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/utils/pkls/tasseron_resistance_to_temp_3.pkl +0 -0
  25. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/utils/pkls/tasseron_temp_to_resistance_2.pkl +0 -0
  26. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/utils/pkls/veris_resistance_to_temp_3.pkl +0 -0
  27. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/utils/pkls/veris_temp_to_resistance_2.pkl +0 -0
  28. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline/utils/unit_convert.py +0 -0
  29. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline.egg-info/SOURCES.txt +0 -0
  30. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline.egg-info/dependency_links.txt +0 -0
  31. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/src/ecopipeline.egg-info/requires.txt +0 -0
  32. {ecopipeline-1.0.1 → ecopipeline-1.0.2}/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: 1.0.1
3
+ Version: 1.0.2
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 = 1.0.1
3
+ version = 1.0.2
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
@@ -1,3 +1,3 @@
1
- from .extract import get_noaa_data, json_to_df, extract_files, get_last_full_day_from_db, get_db_row_from_time, extract_new, csv_to_df, get_sub_dirs, msa_to_df, fm_api_to_df, small_planet_control_to_df, dent_csv_to_df, flow_csv_to_df, pull_egauge_data, egauge_csv_to_df, remove_char_sequence_from_csv_header, tb_api_to_df
1
+ from .extract import get_noaa_data, json_to_df, extract_files, get_last_full_day_from_db, get_db_row_from_time, extract_new, csv_to_df, get_sub_dirs, msa_to_df, fm_api_to_df, small_planet_control_to_df, dent_csv_to_df, flow_csv_to_df, pull_egauge_data, egauge_csv_to_df, remove_char_sequence_from_csv_header, tb_api_to_df, skycentrics_api_to_df
2
2
  __all__ = ["get_noaa_data", "json_to_df", "extract_files", "get_last_full_day_from_db", "get_db_row_from_time", 'extract_new', "csv_to_df", "get_sub_dirs", "msa_to_df", "fm_api_to_df",
3
- "small_planet_control_to_df","dent_csv_to_df","flow_csv_to_df","pull_egauge_data", "egauge_csv_to_df","remove_char_sequence_from_csv_header", "tb_api_to_df"]
3
+ "small_planet_control_to_df","dent_csv_to_df","flow_csv_to_df","pull_egauge_data", "egauge_csv_to_df","remove_char_sequence_from_csv_header", "tb_api_to_df", "skycentrics_api_to_df"]
@@ -15,6 +15,7 @@ import mysql.connector.errors as mysqlerrors
15
15
  import requests
16
16
  import subprocess
17
17
  import traceback
18
+ import time
18
19
 
19
20
 
20
21
  def get_last_full_day_from_db(config : ConfigManager, table_identifier : str = "minute") -> datetime:
@@ -661,6 +662,79 @@ def egauge_csv_to_df(csv_filenames: List[str]) -> pd.DataFrame:
661
662
 
662
663
  return df_diff
663
664
 
665
+ def skycentrics_api_to_df(config: ConfigManager, startTime: datetime = None, endTime: datetime = None, create_csv : bool = True, time_zone: str = 'US/Pacific'):
666
+ """
667
+ Function connects to the field manager api to pull data and returns a dataframe.
668
+
669
+ Parameters
670
+ ----------
671
+ config : ecopipeline.ConfigManager
672
+ The ConfigManager object that holds configuration data for the pipeline. The config manager
673
+ must contain information to connect to the api, i.e. the api user name and password as well as
674
+ the device id for the device the data is being pulled from.
675
+ startTime: datetime
676
+ The point in time for which we want to start the data extraction from. This
677
+ is local time from the data's index.
678
+ endTime: datetime
679
+ The point in time for which we want to end the data extraction. This
680
+ is local time from the data's index.
681
+ create_csv : bool
682
+ create csv files as you process such that API need not be relied upon for reprocessing
683
+ time_zone: str
684
+ The timezone for the indexes in the output dataframe as a string. Must be a string recognized as a
685
+ time stamp by the pandas tz_localize() function https://pandas.pydata.org/docs/reference/api/pandas.Series.tz_localize.html
686
+ defaults to 'US/Pacific'
687
+
688
+ Returns
689
+ -------
690
+ pd.DataFrame:
691
+ Pandas Dataframe containing data from the API pull with column headers the same as the variable names in the data from the pull
692
+ """
693
+ #temporary solution while no date range available
694
+
695
+ try:
696
+ df = pd.DataFrame()
697
+ temp_dfs = []
698
+ time_parser = startTime
699
+ while time_parser < endTime:
700
+ start_time_str = time_parser.strftime('%a, %d %b %H:%M:%S GMT')
701
+ skycentrics_token, date_str = config.get_skycentrics_token(request_str=f'GET /api/devices/{config.api_device_id}/data HTTP/1.1',date_str=start_time_str)
702
+ response = requests.get(f'https://api.skycentrics.com/api/devices/{config.api_device_id}/data',
703
+ headers={'Date': date_str, 'x-sc-api-token': skycentrics_token, 'Accept': 'application/json'})
704
+ if response.status_code == 200:
705
+ norm_data = pd.json_normalize(response.json(), record_path=['sensors'], meta=['time'], meta_prefix='response_')
706
+ if len(norm_data) != 0:
707
+
708
+ norm_data["time_pt"] = pd.to_datetime(norm_data["response_time"])
709
+
710
+ norm_data["time_pt"] = norm_data["time_pt"].dt.tz_convert(time_zone)
711
+ norm_data = pd.pivot_table(norm_data, index="time_pt", columns="id", values="data")
712
+ # Iterate over the index and round up if necessary (work around for json format from sensors)
713
+ for i in range(len(norm_data.index)):
714
+ if norm_data.index[i].minute == 59 and norm_data.index[i].second == 59:
715
+ norm_data.index.values[i] = norm_data.index[i] + pd.Timedelta(seconds=1)
716
+ temp_dfs.append(norm_data)
717
+ else:
718
+ print(f"Failed to make GET request. Status code: {response.status_code} {response.json()}")
719
+ time.sleep(60)
720
+ time_parser = time_parser + timedelta(minutes=1)
721
+ if len(temp_dfs) > 0:
722
+ df = pd.concat(temp_dfs, ignore_index=False)
723
+ if create_csv:
724
+ filename = f"{startTime.strftime('%Y%m%d%H%M%S')}.csv"
725
+ original_directory = os.getcwd()
726
+ os.chdir(config.data_directory)
727
+ df.to_csv(filename, index_label='time_pt')
728
+ os.chdir(original_directory)
729
+ else:
730
+ print("No skycentrics data retieved for time frame.")
731
+ return df
732
+
733
+ except Exception as e:
734
+ print(f"An error occurred: {e}")
735
+ raise e
736
+ return pd.DataFrame()
737
+
664
738
  def fm_api_to_df(config: ConfigManager, startTime: datetime = None, endTime: datetime = None, create_csv : bool = True) -> pd.DataFrame:
665
739
  """
666
740
  Function connects to the field manager api to pull data and returns a dataframe.
@@ -1184,6 +1184,9 @@ def join_to_hourly(hourly_data: pd.DataFrame, noaa_data: pd.DataFrame) -> pd.Dat
1184
1184
  pd.DataFrame:
1185
1185
  A single, joined dataframe
1186
1186
  """
1187
+ #fixing pipelines for new years
1188
+ if 'OAT_NOAA' in noaa_data.columns and not noaa_data['OAT_NOAA'].notnull().any():
1189
+ return hourly_data
1187
1190
  out_df = hourly_data.join(noaa_data)
1188
1191
  return out_df
1189
1192
 
@@ -4,6 +4,9 @@ import mysql.connector
4
4
  import mysql.connector.cursor
5
5
  import requests
6
6
  from datetime import datetime
7
+ import base64
8
+ import hashlib
9
+ import hmac
7
10
 
8
11
  class ConfigManager:
9
12
  """
@@ -56,6 +59,8 @@ class ConfigManager:
56
59
  self.data_directory = data_directory
57
60
  self.api_usr = None
58
61
  self.api_pw = None
62
+ self.api_token = None
63
+ self.api_secret = None
59
64
  self.api_device_id = None
60
65
  if self.data_directory is None:
61
66
  configured_data_method = False
@@ -74,6 +79,11 @@ class ConfigManager:
74
79
  self.api_pw = configure.get('data', 'api_pw')
75
80
  self.api_device_id = configure.get('data','device_id')
76
81
  configured_data_method = True
82
+ elif 'api_token' in configure['data'] and 'api_secret' in configure['data']:
83
+ self.api_token = configure.get('data', 'api_token')
84
+ self.api_secret = configure.get('data', 'api_secret')
85
+ self.api_device_id = configure.get('data','device_id')
86
+ configured_data_method = True
77
87
  if not configured_data_method:
78
88
  raise Exception('data configuration section missing or incomplete in configuration file.')
79
89
 
@@ -261,4 +271,13 @@ class ConfigManager:
261
271
  def get_fm_device_id(self) -> str:
262
272
  if self.api_device_id is None:
263
273
  raise Exception("Field Manager device ID has not been configured.")
264
- return self.api_device_id
274
+ return self.api_device_id
275
+
276
+ def get_skycentrics_token(self, request_str = 'GET /api/devices/ HTTP/1.', date_str : str = None) -> tuple:
277
+ if date_str is None:
278
+ date_str = datetime.utcnow().strftime('%a, %d %b %H:%M:%S GMT')
279
+ signature = base64.b64encode(hmac.new(self.api_secret.encode(),
280
+ '{}\n{}\n{}\n{}'.format(request_str, date_str, '', hashlib.md5(''.encode()).hexdigest()).encode(),
281
+ hashlib.sha1).digest())
282
+ token = '{}:{}'.format(self.api_token, signature.decode())
283
+ return token, date_str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecopipeline
3
- Version: 1.0.1
3
+ Version: 1.0.2
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)
File without changes
File without changes
File without changes
File without changes
File without changes