Skip to content

Commit

Permalink
Add building groups functionality (#4899)
Browse files Browse the repository at this point in the history
* Inventory groups (#4726)

* Added inventory groups

* fixed formatting

* Add in AH

* Groups Details page

* Lint

* rp edits

* migration

* migration order

precommit

* lint

* fix tests

---------

Co-authored-by: Hannah Eslinger <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Ross Perry <[email protected]>

* Inventory groups frontend edits (#4820)

* Added inventory groups

* fixed formatting

* Add in AH

* Groups Details page

* Lint

* rp edits

* fix migrations

* add group buttons to inv detail

* groups to look like labels/dcs

* least common ancestor

* add all alis to group modal

* move lowest common to org view

* development

* migration

* member list default

* optimize lowest_common

* functional pre same node constraint

* validate inventorygroup for name and org

* fe error handling on group create

* mapping constraint, inventory and group must share ali

* precommit

* overhaul of the serializer and member list

* add and remove button bugs

* precommit

* merge conflicts

* merge conflicts

* Fix `hiredis` not found error (#4821)

Fix hiredis not found error

* navigation bugs

* navigation bugs

* troubleshooting

* id bug

* migration order

precommit

* lint

* fix tests

* integrate with develop

fix frontend tests

lint

* fix tests, simplify migration, formatting

precommit

* lint

* dry mapping presave

* group meters page

cleanup

cleanup

cleanup

broken

ali typo

meters page functional

* fix test

---------

Co-authored-by: Elizabeth <[email protected]>
Co-authored-by: Hannah Eslinger <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Alex Swindler <[email protected]>

* Add various inventory group features (#4837)

* descendant tree endpoint

* refactor group merges

---------

Co-authored-by: Ross Perry <[email protected]>

* Systems and services fe edits (#4860)

* Init systems and services

* Add Create Service UI and API

* create factories for groups and systems

* system test

* systems uigrids functional

documentation

* partial for system table

* Add connection type to meters

* refactor modal

* building update endpoint

* system endpoints

* service crud functional

precommit

* service sad path tests

error catching

service tests

* system sad path tests

* front end error handling

* Add meter import to sytems (#4857)

* Fix rollup table (#4835)

* default to float data type for axis data if data_type is not set on the selected column

---------

Co-authored-by: kflemin <[email protected]>

* More cts fixes (#4830)

Co-authored-by: Katherine Fleming <[email protected]>

* Init system meter upload

* Lock sass dependency and enforce Node v20 (#4855)

* use node version 20 for unittests

---------

Co-authored-by: Caleb Rutan <[email protected]>
Co-authored-by: kflemin <[email protected]>
Co-authored-by: Alex Swindler <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Ross Perry <[email protected]>

* fix test conflict

---------

Co-authored-by: Hannah Eslinger <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Caleb Rutan <[email protected]>
Co-authored-by: kflemin <[email protected]>
Co-authored-by: Alex Swindler <[email protected]>

* Updates to meter connection modal (#4868)

* Add modal to update meter connections

* Create update system meter connection endpoint

* modal development

* hookup group meter update

* Fix tests

* add config to serailized meter

* precommit

* comments

* fix test

* Add dashbard endpoint

* group header styling

* readable keys

* sort options

precommit

precommit

* migration

* precommit

* fix test

* hookup group meter delete

---------

Co-authored-by: Hannah Eslinger <[email protected]>
Co-authored-by: Ross Perry <[email protected]>

* Systems and services improvements (#4879)

* systen service modal mods

* update meter enums

* property display field

* alphabetical actions

* nav error and modal arrangement

* precommit

* unit refactors

* precommit

* fix tests

---------

Co-authored-by: Ross Perry <[email protected]>

* Redo group meter creation (#4889)

* Make create group meters modal

* Remove add meter button from systems and services page

* Init Group meter readings upload

* Fix dupe error

* Fix small things

* Make little UI fixes

* Add import/export to dashboard

* Remove meter attched to other services from groups meter/meterreading list

* Format

* Format

* Format

* linter

* translations and cleanup

* lint

* precommit

* dismiss action

* Fix meter usage

* Add message to mismatched meter readings

* Fix docker

* styles & linter

styles & linter

* fix group meter usage test

---------

Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: kflemin <[email protected]>

* fix migration order

* Format

* precommit

* add service options to modal when meter already configured

* fix meter import tz issue

* lint

* add description text to group meters page

* make-aware for meter reading query

* precommit

* small bugs

lint

* adding missing migration

* precommit

---------

Co-authored-by: ebeers-png <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Elizabeth <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Alex Swindler <[email protected]>
Co-authored-by: Caleb Rutan <[email protected]>
Co-authored-by: kflemin <[email protected]>
  • Loading branch information
10 people authored Dec 13, 2024
1 parent ab5519a commit d462bd8
Show file tree
Hide file tree
Showing 104 changed files with 6,031 additions and 121 deletions.
Binary file modified locale/en_US/LC_MESSAGES/django.mo
Binary file not shown.
6 changes: 6 additions & 0 deletions locale/en_US/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,9 @@ msgstr "Confirm GreenButton File Contents"
msgid "GROUPS_DESC_TEXT"
msgstr "SEED supports the creation of groups. Groups can include one or more buildings as well as systems such as Batteries, District Energy Systems (DES) or Electric Vehicle Supply Equipment (EVSE). Services can be associated with the systems and meters associated with those services."

msgid "GROUP_METERS_DESC"
msgstr "The table below shows all property and system meters related to the group. However, only system meters can be created from this page. To create a property meter, go to the property's meter page (for GreenButton format) or go to the data page, add a data file, and click the Meter Data tab in the modal that will open (for Portfolio Manager format)."

msgid "GSA SFTool Guidance"
msgstr "GSA SFTool Guidance"

Expand Down Expand Up @@ -3689,6 +3692,9 @@ msgstr "Select Properties to Update"
msgid "Select a Custom Report to get started!"
msgstr "Select a Custom Report to get started!"

msgid "Select a Goal Type. A Standard goal will calculate a standard EUI per sqft. A Transaction goal will also calculate an EUI per transaction."
msgstr "Select a Goal Type. A Standard goal will calculate a standard EUI per sqft. A Transaction goal will also calculate an EUI per transaction."

msgid "Select a Program to get started!"
msgstr "Select a Program to get started!"

Expand Down
Binary file modified locale/es/LC_MESSAGES/django.mo
Binary file not shown.
8 changes: 7 additions & 1 deletion locale/es/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: lokalise.com\n"
"Project-Id-Version: SEED Platform\n"
"PO-Revision-Date: 2024-12-11 18:12\n"
"PO-Revision-Date: 2024-12-13 17:24\n"
"Last-Translator: lokalise.com\n"
"Language-Team: lokalise.com\n\n"
"Language: es\n"
Expand Down Expand Up @@ -2388,6 +2388,9 @@ msgstr "Confirmar contenido del archivo GreenButton"
msgid "GROUPS_DESC_TEXT"
msgstr "SEED permite la creación de grupos. Los grupos pueden incluir uno o más edificios, así como sistemas como baterías, sistemas de energía urbana (DES) o equipos de suministro de vehículos eléctricos (EVSE). Los servicios se pueden asociar a los sistemas y los medidores asociados a esos servicios."

msgid "GROUP_METERS_DESC"
msgstr "La siguiente tabla muestra todos los medidores de propiedades y sistemas relacionados con el grupo. Sin embargo, solo se pueden crear medidores de sistemas desde esta página. Para crear un medidor de propiedades, vaya a la página de medidores de la propiedad (para el formato GreenButton) o vaya a la página de datos, agregue un archivo de datos y haga clic en la pestaña Datos de medidor en el modal que se abrirá (para el formato Portfolio Manager)."

#, fuzzy
msgid "GSA SFTool Guidance"
msgstr "Guía SFTool de la GSA"
Expand Down Expand Up @@ -4788,6 +4791,9 @@ msgstr "Seleccione las propiedades que desea actualizar"
msgid "Select a Custom Report to get started!"
msgstr "Seleccione un informe personalizado para empezar"

msgid "Select a Goal Type. A Standard goal will calculate a standard EUI per sqft. A Transaction goal will also calculate an EUI per transaction."
msgstr "Seleccione un tipo de objetivo. Un objetivo estándar calculará un EUI estándar por pie cuadrado. Un objetivo de transacción también calculará un EUI por transacción."

#, fuzzy
msgid "Select a Program to get started!"
msgstr "Seleccione un programa para empezar"
Expand Down
Binary file modified locale/fr_CA/LC_MESSAGES/django.mo
Binary file not shown.
6 changes: 6 additions & 0 deletions locale/fr_CA/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -1867,6 +1867,9 @@ msgstr "Confirmer le contenu du fichier GreenButton"
msgid "GROUPS_DESC_TEXT"
msgstr "SEED prend en charge la création de groupes. Les groupes peuvent inclure un ou plusieurs bâtiments ainsi que des systèmes tels que des batteries, des systèmes énergétiques urbains (DES) ou des équipements d'alimentation de véhicules électriques (EVSE). Les services peuvent être associés aux systèmes et aux compteurs associés à ces services."

msgid "GROUP_METERS_DESC"
msgstr "Le tableau ci-dessous présente tous les compteurs de propriété et de système liés au groupe. Cependant, seuls les compteurs de système peuvent être créés à partir de cette page. Pour créer un compteur de propriété, accédez à la page du compteur de la propriété (pour le format GreenButton) ou accédez à la page de données, ajoutez un fichier de données et cliquez sur l'onglet Données du compteur dans la fenêtre modale qui s'ouvre (pour le format Portfolio Manager)."

msgid "GSA SFTool Guidance"
msgstr "Guide de l'outil SFTool de la GSA"

Expand Down Expand Up @@ -3718,6 +3721,9 @@ msgstr "Sélectionnez les propriétés à mettre à jour"
msgid "Select a Custom Report to get started!"
msgstr "Sélectionnez un rapport personnalisé pour commencer !"

msgid "Select a Goal Type. A Standard goal will calculate a standard EUI per sqft. A Transaction goal will also calculate an EUI per transaction."
msgstr "Sélectionnez un type d'objectif. Un objectif standard calculera un EUI standard par pied carré. Un objectif de transaction calculera également un EUI par transaction."

msgid "Select a Program to get started!"
msgstr "Sélectionnez un programme pour commencer !"

Expand Down
15 changes: 15 additions & 0 deletions seed/api/v3/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
from seed.views.v3.green_assessments import GreenAssessmentViewSet
from seed.views.v3.historical_notes import HistoricalNoteViewSet
from seed.views.v3.import_files import ImportFileViewSet
from seed.views.v3.inventory_group_mappings import InventoryGroupMappingViewSet
from seed.views.v3.inventory_groups import InventoryGroupMetersViewSet, InventoryGroupViewSet
from seed.views.v3.label_inventories import LabelInventoryViewSet
from seed.views.v3.labels import LabelViewSet
from seed.views.v3.measures import MeasureViewSet
Expand All @@ -61,6 +63,8 @@
from seed.views.v3.salesforce_configs import SalesforceConfigViewSet
from seed.views.v3.salesforce_mappings import SalesforceMappingViewSet
from seed.views.v3.sensors import SensorViewSet
from seed.views.v3.services import ServiceViewSet
from seed.views.v3.systems import SystemViewSet
from seed.views.v3.tax_lot_properties import TaxLotPropertyViewSet
from seed.views.v3.taxlot_views import TaxlotViewViewSet
from seed.views.v3.taxlots import TaxlotViewSet
Expand Down Expand Up @@ -95,6 +99,8 @@
api_v3_router.register(r"green_assessment_urls", GreenAssessmentURLViewSet, basename="green_assessment_urls")
api_v3_router.register(r"green_assessments", GreenAssessmentViewSet, basename="green_assessments")
api_v3_router.register(r"import_files", ImportFileViewSet, basename="import_files")
api_v3_router.register(r"inventory_groups", InventoryGroupViewSet, basename="inventory_groups")
api_v3_router.register(r"inventory_group_mappings", InventoryGroupMappingViewSet, basename="inventory_group_mappings")
api_v3_router.register(r"labels", LabelViewSet, basename="labels")
api_v3_router.register(r"measures", MeasureViewSet, basename="measures")
api_v3_router.register(r"organizations", OrganizationViewSet, basename="organizations")
Expand Down Expand Up @@ -143,6 +149,13 @@
properties_router.register(r"historical_notes", HistoricalNoteViewSet, basename="property-historical-notes")
properties_router.register(r"sensors", SensorViewSet, basename="property-sensors")

inventory_group_router = nested_routers.NestedSimpleRouter(api_v3_router, r"inventory_groups", lookup="inventory_group")
inventory_group_router.register(r"systems", SystemViewSet, basename="inventory_group-systems")
inventory_group_router.register(r"meters", InventoryGroupMetersViewSet, basename="inventory_group-meters")

system_router = nested_routers.NestedSimpleRouter(inventory_group_router, r"systems", lookup="system")
system_router.register(r"services", ServiceViewSet, basename="system-services")

# This is a third level router, so we need to register it with the second level router
meters_router = nested_routers.NestedSimpleRouter(properties_router, r"meters", lookup="meter")
meters_router.register(r"readings", MeterReadingViewSet, basename="property-meter-readings")
Expand Down Expand Up @@ -185,6 +198,8 @@
re_path(r"^", include(meters_router.urls)),
re_path(r"^", include(property_measures_router.urls)),
re_path(r"^", include(taxlots_router.urls)),
re_path(r"^", include(inventory_group_router.urls)),
re_path(r"^", include(system_router.urls)),
re_path(r"^", include(public_organizations_router.urls)),
re_path(r"^", include(public_cycles_router.urls)),
re_path(r"^celery_queue/$", celery_queue, name="celery_queue"),
Expand Down
46 changes: 33 additions & 13 deletions seed/data_importer/meters_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class MetersParser:

_tz = timezone(TIME_ZONE)

def __init__(self, org_id, meters_and_readings_details, source_type=Meter.PORTFOLIO_MANAGER, property_id=None):
def __init__(self, org_id, meters_and_readings_details, source_type=Meter.PORTFOLIO_MANAGER, property_id=None, system_id=None):
# defaulted to None to show it hasn't been cached yet
self._cache_meter_and_reading_objs = None
self._cache_org_country = None
Expand All @@ -67,30 +67,32 @@ def __init__(self, org_id, meters_and_readings_details, source_type=Meter.PORTFO
self._meters_and_readings_details = meters_and_readings_details
self._org_id = org_id
self._property_id = property_id
self._system_id = system_id
self._source_type = source_type
self._unique_meters = {}

self._source_to_property_ids = {} # tracked to reduce the number of database queries

# The following are only relevant/used if property_id isn't explicitly specified
if property_id is None:
if property_id is None and system_id is None:
self._property_link = "Portfolio Manager ID"
self._unlinkable_pm_ids = set() # to avoid duplicates

@classmethod
def factory(cls, meters_file, org_id, source_type=Meter.PORTFOLIO_MANAGER, property_id=None):
def factory(cls, meters_file, org_id, source_type=Meter.PORTFOLIO_MANAGER, property_id=None, system_id=None):
"""Factory function for MetersParser
:param meters_file: File
:param org_id: int
:param source_type: int, type of meter data
:param property_id: int, id of property - required if meter data is for a specific property (e.g., GreenButton)
:param property_id: int, id of system - required if meter data is for a specific system (e.g., GreenButton)
:return: MetersParser
"""
if source_type == Meter.GREENBUTTON:
parser = reader.GreenButtonParser(meters_file)
raw_meter_data = list(parser.data)
return cls(org_id, raw_meter_data, source_type=Meter.GREENBUTTON, property_id=property_id)
return cls(org_id, raw_meter_data, source_type=Meter.GREENBUTTON, property_id=property_id, system_id=system_id)

try:
# try to parse the file as if it came from "Download your entire portfolio"
Expand Down Expand Up @@ -211,6 +213,7 @@ def proposed_imports(self):
"type": Meter.ENERGY_TYPE_BY_METER_TYPE[meter["type"]],
"incoming": len(meter.get("readings")),
"property_id": meter["property_id"],
"system_id": meter["system_id"],
}

id = meter.get("source_id")
Expand Down Expand Up @@ -312,7 +315,8 @@ def _parse_gb_meter_details(self):
meter_details = {
"source": self._source_type,
"source_id": raw_details["source_id"],
"property_ids": [self._property_id],
"property_ids": [self._property_id] if self._property_id is not None else None,
"system_ids": [self._system_id] if self._system_id is not None else None,
}

# Define start_time and end_time
Expand Down Expand Up @@ -391,21 +395,37 @@ def distribute_meter_reading(self, meter_reading, meter_details):
in a list. If a meter was previously parsed and has readings already,
any new readings are appended to that list.
"""
for property_id in meter_details.get("property_ids", []):
property_ids = meter_details.get("property_ids")
system_ids = meter_details.get("system_ids")

# choose property or systems]
if property_ids is not None and system_ids is not None:
raise ValueError("must have `property_ids` or `systems_ids`, not both")
elif property_ids is not None:
id_name = "property_id"
ids = property_ids
elif system_ids is not None:
id_name = "system_id"
ids = system_ids
else:
raise ValueError("must have either `property_ids` or `systems_ids`")

for id_ in ids:
meter_details_copy = meter_details.copy()
del meter_details_copy["property_ids"]
meter_details_copy["property_id"] = property_id

meter_identifier = "-".join([str(meter_details_copy[k]) for k in sorted(meter_details_copy)])
meter_details_copy.pop("property_ids", None)
meter_details_copy.pop("system_ids", None)
meter_details_copy["property_id"] = id_ if id_name == "property_id" else None
meter_details_copy["system_id"] = id_ if id_name == "system_id" else None

existing_property_meter = self._unique_meters.get(meter_identifier, None)
meter_identifier = "-".join([str(meter_details_copy[k]) for k in sorted(meter_details_copy)])
existing_meter = self._unique_meters.get(meter_identifier, None)

if existing_property_meter is None:
if existing_meter is None:
meter_details_copy["readings"] = [meter_reading]

self._unique_meters[meter_identifier] = meter_details_copy
else:
existing_property_meter["readings"].append(meter_reading)
existing_meter["readings"].append(meter_reading)

def validated_type_units(self):
"""
Expand Down
5 changes: 3 additions & 2 deletions seed/data_importer/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,8 @@ def _save_greenbutton_data_create_tasks(file_pk, progress_key):

import_file = ImportFile.objects.get(pk=file_pk)
org_id = import_file.cycle.organization.id
property_id = import_file.matching_results_data["property_id"]
property_id = import_file.matching_results_data.get("property_id")
system_id = import_file.matching_results_data.get("system_id")

# matching_results_data gets cleared out since the field wasn't meant for this
import_file.matching_results_data = {}
Expand All @@ -923,7 +924,7 @@ def _save_greenbutton_data_create_tasks(file_pk, progress_key):
parser = reader.GreenButtonParser(import_file.local_file)
raw_meter_data = list(parser.data)

meters_parser = MetersParser(org_id, raw_meter_data, source_type=Meter.GREENBUTTON, property_id=property_id)
meters_parser = MetersParser(org_id, raw_meter_data, source_type=Meter.GREENBUTTON, property_id=property_id, system_id=system_id)
meter_readings = meters_parser.meter_and_reading_objs[0] # there should only be one meter (1 property, 1 type/unit)

readings = meter_readings["readings"]
Expand Down
Loading

0 comments on commit d462bd8

Please sign in to comment.