diff --git a/pyseed/seed_client.py b/pyseed/seed_client.py index 5a89c90..0cc0cfa 100644 --- a/pyseed/seed_client.py +++ b/pyseed/seed_client.py @@ -5,7 +5,6 @@ import json import logging -import os import time from collections import Counter from csv import DictReader @@ -247,7 +246,7 @@ def create_organization(self, org_name: str) -> dict: org = self.client.post(endpoint="organizations", json=payload) return org - def get_buildings(self) -> list[dict]: + def get_buildings(self, filters: dict = {}) -> list[dict]: total_qry = self.client.list(endpoint="properties", data_name="pagination", per_page=100) # step through each page of the results @@ -259,6 +258,7 @@ def get_buildings(self) -> list[dict]: per_page=100, page=i, cycle=self.cycle_id, + **filters, ) # print(f"number of buildings retrieved: {len(buildings)}") @@ -1117,7 +1117,14 @@ def get_meter(self, property_view_id: int, meter_type: str, source: str, source_ return meter return None - def get_or_create_meter(self, property_view_id: int, meter_type: str, source: str, source_id: str) -> Optional[dict[Any, Any]]: + def get_or_create_meter( + self, + property_view_id: int, + meter_type: str, + source: str, + source_id: str, + connection_type="Imported", + ) -> Optional[dict[Any, Any]]: """get or create a meter for a property view. Args: @@ -1139,6 +1146,7 @@ def get_or_create_meter(self, property_view_id: int, meter_type: str, source: st "type": meter_type, "source": source, "source_id": source_id, + "connection_type": connection_type, } meter = self.client.post(endpoint="properties_meters", url_args={"PK": property_view_id}, json=payload) @@ -1359,7 +1367,7 @@ def get_pm_report_template_names(self, pm_username: str, pm_password: str) -> di # Return the report templates return response - def download_pm_report(self, pm_username: str, pm_password: str, pm_template: dict) -> str: + def download_pm_report(self, pm_username: str, pm_password: str, pm_template: dict, to_filepath) -> None: """Download a PM report. Args: @@ -1367,8 +1375,6 @@ def download_pm_report(self, pm_username: str, pm_password: str, pm_template: di pm_password (str): password for Energystar Portfolio Manager pm_template (dict): the full template object dict returned from get_pm_report_template_names - Sample return shown below. - Returns the path to the report template workbook file """ response = self.client.post( endpoint="portfolio_manager_report", @@ -1403,32 +1409,8 @@ def download_pm_report(self, pm_username: str, pm_password: str, pm_template: di if sheet: sheet.append(row) - # Report Template name - report_template_name = pm_template["name"] - - # Filename - file_name = f"{pm_username}_{report_template_name}.xlsx" - - # Folder name - folder_name = "reports" - - if not os.path.exists(folder_name): - os.mkdir(folder_name) - - # Set the file path. - file_path = os.path.join(folder_name, file_name) - # Save the workbook object. - workbook.save(file_path) - - # Current directory - curdir = os.getcwd() - - # Define the datafile path - datafile_path = os.path.join(curdir, file_path) - - # Return the report templates - return datafile_path + workbook.save(to_filepath) def import_files_reuse_inventory_file_for_meters(self, import_file_id: int) -> dict: """Reuse an import file to create all the meter entries. This method is used @@ -1449,6 +1431,27 @@ def import_files_reuse_inventory_file_for_meters(self, import_file_id: int) -> d response = self.client.post(endpoint="import_files_reuse_inventory_file_for_meters", json=payload) return response + def upload_meters_datafile( + self, + dataset_name: str, + datafile: str, + **kwargs, + ) -> dict: + # upload file + datafile_type = kwargs.pop("datafile_type", "PM Meter Usage") + dataset = self.get_or_create_dataset(dataset_name) + result = self.upload_datafile(dataset["id"], datafile, datafile_type) + import_file_id = result["import_file_id"] + + # start processing + save_result = self.start_save_data(import_file_id) + progress_key = save_result.get("progress_key", None) + + # wait until upload is complete + result = self.track_progress_result(progress_key) + + return result + def upload_and_match_datafile( self, dataset_name: str, @@ -1663,6 +1666,50 @@ def retrieve_portfolio_manager_property(self, username: str, password: str, pm_p result["status"] = "success" return result + def retrieve_portfolio_manager_meters( + self, + username: str, + password: str, + pm_property_ids: list[str], + start_date: date, + endtime_date: date, + save_file_name: Path, + ) -> dict: + """Connect to portfolio manager and download an individual meters data in Excel format + + Args: + username (str): ESPM login username + password (str): ESPM password + pm_property_ids (list(str)): property whoms meters to download + start_date (date): start_date from meter readings + endtime_date (date): end_date from meter readings + save_file_name (Path): Location to save the file, preferably an absolute path + + Returns: + dict: Did the file download? + """ + if save_file_name.exists(): + raise Exception(f"Save filename already exists, save to a new file name: {save_file_name}") + + response = self.client.post( + "portfolio_manager_meter_download", + json={ + "username": username, + "password": password, + "property_ids": pm_property_ids, + "start_date": start_date.strftime("%m/%d/%Y"), + "end_date": endtime_date.strftime("%m/%d/%Y"), + }, + ) + result = {"status": "error"} + # save the file to the location that was passed + # note that the data are returned directly (the ESPM URL directly downloads the file) + if isinstance(response, bytes): + with open(save_file_name, "wb") as f: + f.write(response) + result["status"] = "success" + return result + def import_portfolio_manager_property(self, seed_id: int, cycle_id: int, mapping_profile_id: int, file_path: str) -> dict: """Import the downloaded xlsx file into SEED on a specific propertyID Args: @@ -1745,3 +1792,43 @@ def get_cross_cycle_data(self, property_view_id: int) -> dict: url_args={"PK": property_view_id}, include_org_id_query_param=True, ) + + def update_organizations_access_level_names(self, access_level_names: list[str]) -> list: + return self.client.post( + endpoint="update_org_access_level_names", + json={"access_level_names": access_level_names}, + url_args={"PK": self.client.org_id}, + ) + + def update_organizations_access_level_instances(self, file: str): + # upload file + with open(file, "rb") as f: + response = self.client.put( + None, + required_pk=False, + url_args={"PK": self.client.org_id}, + endpoint="upload_org_access_level_instances_file", + files={ + "file": ( + file, + f, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ), + }, + ) + + if not response["success"]: + raise ValueError("could not upload access level instances") + + # start saving the file + response = self.client.post( + endpoint="start_org_access_level_instances_file_save", + url_args={"PK": self.client.org_id}, + json={"filename": response["tempfile"]}, + ) + progress_key = response.get("progress_key", None) + + # wait until save is complete + result = self.track_progress_result(progress_key) + + return result diff --git a/pyseed/seed_client_base.py b/pyseed/seed_client_base.py index 1d898e2..bfbf3f3 100644 --- a/pyseed/seed_client_base.py +++ b/pyseed/seed_client_base.py @@ -63,10 +63,14 @@ "import_files_start_save_data_pk": "/api/v3/import_files/PK/start_save_data/", "org_column_mapping_import_file": "api/v3/organizations/ORG_ID/column_mappings/", "portfolio_manager_property_download": "/api/v3/portfolio_manager/PK/download/", + "portfolio_manager_meter_download": "/api/v3/portfolio_manager/meter_download/", + "update_org_access_level_names": "/api/v3/organizations/PK/access_levels/access_level_names/", + "start_org_access_level_instances_file_save": "/api/v3/organizations/PK/access_levels/start_save_data/", # PUTs with replaceable keys: "properties_update_with_buildingsync": "api/v3/properties/PK/update_with_building_sync/", "properties_upload_inventory_document": "api/v3/properties/PK/upload_inventory_document", "property_update_with_espm": "api/v3/properties/PK/update_with_espm/", + "upload_org_access_level_instances_file": "/api/v3/organizations/PK/access_levels/importer/", # GETs with replaceable keys "analyses_views": "/api/v3/analyses/PK/views/ANALYSIS_VIEW_PK/", "audit_template_building_xml": "/api/v3/audit_template/PK/get_building_xml",