diff --git a/seed/models/compliance_metrics.py b/seed/models/compliance_metrics.py index 27e06dd01b..cc2b50ca03 100644 --- a/seed/models/compliance_metrics.py +++ b/seed/models/compliance_metrics.py @@ -66,29 +66,26 @@ def evaluate(self, user_ali): # get properties (no filter) # property_response = properties_across_cycles(self.organization_id, -1, cycle_ids) # get properties (applies filter group) - display_field_id = Column.objects.get( + display_field = Column.objects.get( table_name="PropertyState", column_name=self.organization.property_display_field, organization=self.organization - ).id + ) # array of columns to return - column_ids = [display_field_id] + columns = [display_field] if self.actual_energy_column is not None: - column_ids.append(self.actual_energy_column.id) + columns.append(self.actual_energy_column) if self.target_energy_column is not None: - column_ids.append(self.target_energy_column.id) + columns.append(self.target_energy_column) if self.actual_emission_column is not None: - column_ids.append(self.actual_emission_column.id) + columns.append(self.actual_emission_column) if self.target_emission_column is not None: - column_ids.append(self.target_emission_column.id) + columns.append(self.target_emission_column) for col in self.x_axis_columns.all(): - column_ids.append(col.id) + columns.append(col) - # Unique ids - column_ids = [*set(column_ids)] - - property_response = properties_across_cycles_with_filters(self.organization_id, user_ali, cycle_ids, query_dict, column_ids) + property_response = properties_across_cycles_with_filters(self.organization_id, user_ali, cycle_ids, query_dict, columns) datasets = { "y": {"data": [], "label": "compliant"}, @@ -141,24 +138,24 @@ def evaluate(self, user_ali): for p in property_response[cyc]: # initialize - properties[p["property_view_id"]] = None + properties[p["id"]] = None # energy metric if metric["energy_metric"]: - properties[p["property_view_id"]] = self._calculate_compliance(p, metric["energy_bool"], "energy") + properties[p["id"]] = self._calculate_compliance(p, metric["energy_bool"], "energy") # emission metric - if metric["emission_metric"] and properties[p["property_view_id"]] != "u": + if metric["emission_metric"] and properties[p["id"]] != "u": temp_val = self._calculate_compliance(p, metric["emission_bool"], "emission") # reconcile if temp_val == "u": # unknown stays unknown (missing data) - properties[p["property_view_id"]] = "u" - elif properties[p["property_view_id"]] is None: + properties[p["id"]] = "u" + elif properties[p["id"]] is None: # only emission metric (not energy metric) - properties[p["property_view_id"]] = temp_val + properties[p["id"]] = temp_val else: # compliant if both are compliant - properties[p["property_view_id"]] = temp_val if temp_val == "n" else properties[p["property_view_id"]] + properties[p["id"]] = temp_val if temp_val == "n" else properties[p["id"]] # count compliant, non-compliant, unknown for each property with data for key in cnts: diff --git a/seed/static/seed/js/controllers/insights_property_controller.js b/seed/static/seed/js/controllers/insights_property_controller.js index d6089f2915..bf1ad20974 100644 --- a/seed/static/seed/js/controllers/insights_property_controller.js +++ b/seed/static/seed/js/controllers/insights_property_controller.js @@ -307,7 +307,7 @@ angular.module('SEED.controller.insights_property', []).controller('insights_pro $scope.annotations = {}; _.forEach($scope.data.properties_by_cycles[$scope.configs.chart_cycle], (prop) => { - const item = { id: prop.property_view_id }; + const item = { id: prop.id }; item.name = _.find(prop, (v, k) => k.startsWith($scope.organization.property_display_field)); // x axis is easy item.x = _.find(prop, (v, k) => k.endsWith(`_${String($scope.configs.chart_xaxis)}`)); @@ -333,10 +333,10 @@ angular.module('SEED.controller.insights_property', []).controller('insights_pro } // place in appropriate dataset - if (_.includes($scope.data.results_by_cycles[$scope.configs.chart_cycle].y, prop.property_view_id)) { + if (_.includes($scope.data.results_by_cycles[$scope.configs.chart_cycle].y, prop.id)) { // compliant dataset datasets[0].data.push(item); - } else if (_.includes($scope.data.results_by_cycles[$scope.configs.chart_cycle].n, prop.property_view_id)) { + } else if (_.includes($scope.data.results_by_cycles[$scope.configs.chart_cycle].n, prop.id)) { // non-compliant dataset datasets[1].data.push(item); } else { diff --git a/seed/utils/properties.py b/seed/utils/properties.py index b3964de5f7..0d466b686d 100644 --- a/seed/utils/properties.py +++ b/seed/utils/properties.py @@ -7,6 +7,8 @@ import json import logging +from django.db.models import F + # Imports from Django from django.http import JsonResponse from rest_framework import status @@ -24,7 +26,6 @@ TaxLotView, ) from seed.serializers.pint import apply_display_unit_preferences -from seed.utils.search import build_view_filters_and_sorts logging.basicConfig(format="%(asctime)s %(levelname)-8s %(message)s", level=logging.ERROR, datefmt="%Y-%m-%d %H:%M:%S") @@ -170,41 +171,50 @@ def properties_across_cycles(org_id, ali, profile_id, cycle_ids=[]): return results -def properties_across_cycles_with_filters(org_id, user_ali, cycle_ids=[], query_dict={}, column_ids=[]): - # Identify column preferences to be used to scope fields/values - columns_from_database = Column.retrieve_all(org_id, "property", False) - org = Organization.objects.get(pk=org_id) +def properties_across_cycles_with_filters(org_id, user_ali, cycle_ids=[], query_dict={}, columns=[]): + # get relevant views + views_list = PropertyView.objects.select_related("property", "state", "cycle").filter( + property__organization_id=org_id, + cycle_id__in=cycle_ids, + property__access_level_instance__lft__gte=user_ali.lft, + property__access_level_instance__rgt__lte=user_ali.rgt, + ) + views_list = _serialize_views(views_list, columns, org_id) + # group by cycle results = {cycle_id: [] for cycle_id in cycle_ids} - property_views = _get_filter_group_views(org_id, cycle_ids, query_dict, user_ali) - views_cycle_ids = [v.cycle_id for v in property_views] - related_results = TaxLotProperty.serialize(property_views, column_ids, columns_from_database, include_related=False) - unit_collapsed_results = [apply_display_unit_preferences(org, x) for x in related_results] - - for cycle_id, unit_collapsed_result in zip(views_cycle_ids, unit_collapsed_results): - results[cycle_id].append(unit_collapsed_result) + for view in views_list: + cycle_id = view["cycle_id"] + del view["cycle_id"] + results[cycle_id].append(view) return results -# helper function for getting filtered properties -def _get_filter_group_views(org_id, cycles, query_dict, user_ali): - columns = Column.retrieve_all(org_id=org_id, inventory_type="property", only_used=False, include_related=False) +def _serialize_views(views_list, columns, org_id): + org = Organization.objects.get(pk=org_id) + # build annotations annotations = {} - try: - filters, annotations, _order_by = build_view_filters_and_sorts(query_dict, columns, "property") - except Exception: - return JsonResponse({"status": "error", "message": "error with filter group"}, status=status.HTTP_404_NOT_FOUND) - - views_list = PropertyView.objects.select_related("property", "state", "cycle").filter( - property__organization_id=org_id, - cycle__in=cycles, - property__access_level_instance__lft__gte=user_ali.lft, - property__access_level_instance__rgt__lte=user_ali.rgt, - ) - - views_list = views_list.annotate(**annotations).filter(filters).order_by("id") + values_list = ["id", "cycle_id"] # django readable names + returned_name = ["id", "cycle_id"] # actual api names + for column in columns: + if column.is_extra_data: + anno_value = F("state__extra_data__" + column.column_name) + elif column.derived_column: + anno_value = F("state__derived_data__" + column.column_name) + else: + anno_value = F("state__" + column.column_name) + + name = f"{column.column_name.replace(' ', '_')}_{column.id}" # django readable name + annotations[name] = anno_value + values_list.append(name) + returned_name.append(f"{column.column_name}_{column.id}") + + # use api names and add units + views_list = views_list.annotate(**annotations).values_list(*values_list) + views_list = [dict(zip(returned_name, view)) for view in views_list] # replace django readable name with api name + views_list = [apply_display_unit_preferences(org, view) for view in views_list] return views_list