Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions pyseed/seed_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def create_organization(self, org_name: str, allow_exist: bool = False) -> 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]:
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_buildings uses a mutable default argument (filters: dict = {}), which can leak state across calls if the dict is mutated. Use filters: Optional[dict] = None and set filters = filters or {} inside the method.

Suggested change
def get_buildings(self, filters: dict = {}) -> list[dict]:
def get_buildings(self, filters: Optional[dict] = None) -> list[dict]:
filters = filters or {}

Copilot uses AI. Check for mistakes.
total_qry = self.client.list(endpoint="properties", data_name="pagination", per_page=100)

# step through each page of the results
Expand All @@ -268,6 +268,7 @@ def get_buildings(self) -> list[dict]:
per_page=100,
page=i,
cycle=self.cycle_id,
**filters,
Comment on lines 260 to +271
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

total_qry is computed without cycle (and will also ignore any new filters), but the paged requests include cycle=self.cycle_id (and filters). This can inflate num_pages and cause unnecessary extra API calls; consider using the same query params for the initial pagination request as for the subsequent page fetches.

Copilot uses AI. Check for mistakes.
)
# print(f"number of buildings retrieved: {len(buildings)}")

Expand Down Expand Up @@ -1335,7 +1336,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]]:
Comment on lines +1339 to +1346
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_or_create_meter adds connection_type but it is missing a type annotation and is not documented in the docstring Args section. Please type it (e.g., str) and document what values are accepted/expected by the SEED API so callers know how to use it.

Copilot uses AI. Check for mistakes.
"""get or create a meter for a property view.

Args:
Expand All @@ -1357,6 +1365,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)
Expand Down Expand Up @@ -1394,6 +1403,24 @@ def upsert_meter_readings_bulk(self, property_view_id: int, meter_id: int, data:
)
return readings

def create_element(self, property_id: int, data: dict[Any, Any]) -> dict:
"""Upsert an element for a property.

Args:
property_id (int): property id
data (dict[Any, Any]): dictionary of element data

Returns:
dict: element object
"""
# get the element data for the property
element = self.client.post(
endpoint="properties_elements",
url_args={"PK": property_id},
json=data,
)
return element

def get_meter_data(self, property_id, interval: str = "Exact", excluded_meter_ids: list = []):
"""Return the meter data from the property.

Expand Down
1 change: 1 addition & 0 deletions pyseed/seed_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"properties_analyses": "/api/v3/properties/PK/analyses/",
"properties_meter_usage": "/api/v3/properties/PK/meter_usage/",
"properties_meters": "/api/v3/properties/PK/meters/",
"properties_elements": "/api/v3/properties/PK/elements/",
# GET & POST with replaceable keys
"properties_meters_reading": "/api/v3/properties/PK/meters/METER_PK/readings/",
# DELETES with replaceable keys
Expand Down
57 changes: 57 additions & 0 deletions tests/test_seed_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,63 @@ def setup_class(cls):
def teardown_class(cls):
cls.seed_client.delete_inventory()

def test_create_element(self):
# Test the create_element method by creating an element for a property

# First, create a property to attach the element to
completion_date = "02/02/2023"
year = "2023"
cycle = self.seed_client.get_or_create_cycle(
"pyseed-element-test",
date(int(year), 1, 1),
date(int(year), 12, 31),
set_cycle_id=True,
)

state = {
"organization_id": self.seed_client.client.org_id,
"custom_id_1": "element-test-building-123ABC",
"address_line_1": "456 Element Test St",
"city": "Element City",
"state": "CA",
"postal_code": "90211",
"property_name": "Element Test Building",
"extra_data": {"pathway": "new", "completion_date": completion_date},
}

params = {"state": state, "cycle_id": cycle["id"]}

# Create the building first
result = self.seed_client.create_building(params=params)
assert result["status"] == "success"
assert result["view"]["id"] is not None
property_view_id = result["view"]["id"]

# Get the property ID (not view ID) for the element creation
property_result = self.seed_client.get_property_view(property_view_id)
property_id = property_result["property_id"]

# Create test element data
# Element Data needs an id, a "code", and then extra data for the element itself
# Note: extra_data must be a flat JSON object, not a list
element_data = {
"id": "RTU-123",
"code": "D3050",
"extra_data": {
"name": "Rooftop Unit 123",
"refrigerant_type": "R-410A",
"capacity_tons": 15.0,
"efficiency_eer": 12.2,
},
}

# Test the create_element method
element_result = self.seed_client.create_element(property_id, element_data)

# Verify the element was created successfully
assert element_result is not None
# Additional assertions can be added based on the actual API response structure

def test_upload_multiple_cycles_and_read_back(self):
# Get/create the new cycle and upload the data. Make sure to set the cycle ID so that the
# data end up in the correct cycle
Expand Down
Loading