Skip to content

Commit 4a59f13

Browse files
committed
Create Hvac Metrics
1 parent c0960f0 commit 4a59f13

File tree

7 files changed

+192
-0
lines changed

7 files changed

+192
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# !/usr/bin/env python
2+
"""
3+
SEED Platform (TM), Copyright (c) Alliance for Sustainable Energy, LLC, and other contributors.
4+
See also https://github.com/SEED-platform/seed/blob/main/LICENSE.md
5+
"""
6+
7+
import logging
8+
from collections import Counter
9+
10+
from celery import chain, shared_task
11+
from django.db.models import F
12+
13+
from seed.analysis_pipelines.pipeline import (
14+
AnalysisPipeline,
15+
AnalysisPipelineError,
16+
analysis_pipeline_task,
17+
task_create_analysis_property_views,
18+
)
19+
from seed.models import Analysis, AnalysisMessage, AnalysisPropertyView, Column, Element, PropertyView
20+
21+
logger = logging.getLogger(__name__)
22+
23+
24+
class HVACMetricsPipeline(AnalysisPipeline):
25+
def _prepare_analysis(self, property_view_ids, start_analysis=True):
26+
# if theres not elements, just exit
27+
views = PropertyView.objects.filter(id__in=property_view_ids)
28+
if not Element.objects.filter(property__in=views.values_list("property", flat=True)).exists():
29+
AnalysisMessage.log_and_create(
30+
logger=logger,
31+
type_=AnalysisMessage.ERROR,
32+
analysis_id=self._analysis_id,
33+
analysis_property_view_id=None,
34+
user_message="None of the selected properties have elements, which are requied for this analysis.",
35+
debug_message="",
36+
)
37+
analysis = Analysis.objects.get(id=self._analysis_id)
38+
analysis.status = Analysis.FAILED
39+
analysis.save()
40+
raise AnalysisPipelineError("None of the selected properties have elements, which are requied for this analysis.")
41+
42+
progress_data = self.get_progress_data()
43+
progress_data.total = 3
44+
progress_data.save()
45+
46+
chain(
47+
task_create_analysis_property_views.si(self._analysis_id, property_view_ids),
48+
_finish_preparation.s(self._analysis_id),
49+
_run_analysis.s(self._analysis_id),
50+
).apply_async()
51+
52+
def _start_analysis(self):
53+
return None
54+
55+
56+
@shared_task(bind=True)
57+
@analysis_pipeline_task(Analysis.CREATING)
58+
def _finish_preparation(self, analysis_view_ids_by_property_view_id, analysis_id):
59+
pipeline = HVACMetricsPipeline(analysis_id)
60+
pipeline.set_analysis_status_to_ready("Ready to run HVAC Metrics analysis")
61+
62+
# here is where errors would be filtered out
63+
64+
return list(analysis_view_ids_by_property_view_id.values())
65+
66+
67+
@shared_task(bind=True)
68+
@analysis_pipeline_task(Analysis.READY)
69+
def _run_analysis(self, analysis_property_view_ids, analysis_id):
70+
pipeline = HVACMetricsPipeline(analysis_id)
71+
progress_data = pipeline.set_analysis_status_to_running()
72+
progress_data.step("Generating Numbers")
73+
analysis = Analysis.objects.get(id=analysis_id)
74+
75+
# get/create relevant columns
76+
existing_columns = _create_analysis_columns(analysis)
77+
78+
analysis_property_views = AnalysisPropertyView.objects.filter(id__in=analysis_property_view_ids)
79+
property_views_by_apv_id = AnalysisPropertyView.get_property_views(analysis_property_views)
80+
for analysis_property_view in analysis_property_views:
81+
# get property view and its elements
82+
property_view = property_views_by_apv_id[analysis_property_view.id]
83+
elements = Element.objects.filter(property=property_view.property)
84+
85+
# do calculations
86+
cooling_caps = elements.annotate(cooling_cap=F("extra_data__Nominal Cooling Cap. (Tons)")).values_list("cooling_cap", flat=True)
87+
total_cooling_cap = sum([c for c in cooling_caps if c is not None])
88+
refrigeration_on_types = elements.annotate(refrigeration_on_type=F("extra_data__Refrigeration on Type")).values_list(
89+
"refrigeration_on_type", flat=True
90+
)
91+
most_common_refrigeration_on_type = Counter(refrigeration_on_types).most_common(1)[0][0] if refrigeration_on_types else None
92+
93+
# update the analysis_property_view
94+
analysis_property_view.parsed_results = {
95+
"Total Nominal Cooling Cap. (Tons)": total_cooling_cap,
96+
"Most Common Refrigeration On Type": most_common_refrigeration_on_type,
97+
}
98+
analysis_property_view.save()
99+
100+
# write to property columns
101+
if "total_nominal_cooling_cap" in existing_columns:
102+
property_view.state.extra_data.update({"total_nominal_cooling_cap": total_cooling_cap})
103+
if "most_common_refrigeration_on_type" in existing_columns:
104+
property_view.state.extra_data.update({"most_common_refrigeration_on_type": most_common_refrigeration_on_type})
105+
106+
property_view.state.save()
107+
108+
# all done!
109+
pipeline.set_analysis_status_to_completed()
110+
111+
112+
def _create_analysis_columns(analysis):
113+
existing_columns = []
114+
column_meta = [
115+
{
116+
"column_name": "total_nominal_cooling_cap",
117+
"display_name": "Total Nominal Cooling Cap.",
118+
"description": "created by HVAC Metric analysis",
119+
},
120+
{
121+
"column_name": "most_common_refrigeration_on_type",
122+
"display_name": "Most Common Refrigeration On Type",
123+
"description": "created by HVAC Metric analysis",
124+
},
125+
]
126+
127+
for col in column_meta:
128+
try:
129+
Column.objects.get(
130+
column_name=col["column_name"],
131+
organization=analysis.organization,
132+
table_name="PropertyState",
133+
)
134+
existing_columns.append(col["column_name"])
135+
except Exception:
136+
if analysis.can_create():
137+
column = Column.objects.create(
138+
is_extra_data=True,
139+
column_name=col["column_name"],
140+
organization=analysis.organization,
141+
table_name="PropertyState",
142+
)
143+
column.display_name = col["display_name"]
144+
column.column_description = col["description"]
145+
column.save()
146+
existing_columns.append(col["column_name"])
147+
148+
return existing_columns

seed/analysis_pipelines/pipeline.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ def factory(cls, analysis):
273273
from seed.analysis_pipelines.eeej import EEEJPipeline
274274
from seed.analysis_pipelines.element_statistics import ElementStatisticsPipeline
275275
from seed.analysis_pipelines.eui import EUIPipeline
276+
from seed.analysis_pipelines.hvac_metrics import HVACMetricsPipeline
276277
from seed.analysis_pipelines.upgrade_recommendation import UpgradeRecommendationPipeline
277278

278279
if analysis.service == Analysis.BSYNCR:
@@ -289,6 +290,8 @@ def factory(cls, analysis):
289290
return ElementStatisticsPipeline(analysis.id)
290291
elif analysis.service == Analysis.UPGRADERECOMMENDATION:
291292
return UpgradeRecommendationPipeline(analysis.id)
293+
elif analysis.service == Analysis.HVACMETRICS:
294+
return HVACMetricsPipeline(analysis.id)
292295
else:
293296
raise AnalysisPipelineError(f'Analysis service type is unknown/unhandled. Service ID "{analysis.service}"')
294297

seed/analysis_pipelines/tasks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@
2020
from seed.analysis_pipelines.eeej import _finish_preparation, _run_analysis # noqa: F811
2121
from seed.analysis_pipelines.element_statistics import _finish_preparation, _run_analysis # noqa: F811
2222
from seed.analysis_pipelines.eui import _finish_preparation, _run_analysis # noqa: F811
23+
from seed.analysis_pipelines.hvac_metrics import _finish_preparation, _run_analysis # noqa: F811
2324
from seed.analysis_pipelines.upgrade_recommendation import _finish_preparation, _run_analysis # noqa: F811, F401
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 4.2.24 on 2025-10-09 18:54
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("seed", "0247_aggregatemetersystem"),
9+
]
10+
11+
operations = [
12+
migrations.AlterField(
13+
model_name="analysis",
14+
name="service",
15+
field=models.IntegerField(
16+
choices=[
17+
(1, "BSyncr"),
18+
(2, "BETTER"),
19+
(3, "EUI"),
20+
(4, "CO2"),
21+
(5, "EEEJ"),
22+
(6, "Element Statistics"),
23+
(7, "Building Upgrade Recommendation"),
24+
(8, "HVAC Metrics"),
25+
]
26+
),
27+
),
28+
]

seed/models/analyses.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Analysis(models.Model):
2626
EEEJ = 5
2727
ELEMENTSTATISTICS = 6
2828
UPGRADERECOMMENDATION = 7
29+
HVACMETRICS = 8
2930

3031
SERVICE_TYPES = (
3132
(BSYNCR, "BSyncr"),
@@ -35,6 +36,7 @@ class Analysis(models.Model):
3536
(EEEJ, "EEEJ"),
3637
(ELEMENTSTATISTICS, "Element Statistics"),
3738
(UPGRADERECOMMENDATION, "Building Upgrade Recommendation"),
39+
(HVACMETRICS, "HVAC Metrics"),
3840
)
3941

4042
PENDING_CREATION = 8
@@ -197,6 +199,12 @@ def get_highlights(self, property_id=None):
197199
return [
198200
{"name": "Building Upgrade Recommendation", "value": recommendation},
199201
]
202+
# HVAC Metrics
203+
elif self.service == self.HVACMETRICS:
204+
return [
205+
{"name": "Total Nominal Cooling Cap. (Tons)", "value": results.get("Total Nominal Cooling Cap. (Tons)")},
206+
{"name": "Most Common Refrigeration On Type", "value": results.get("Most Common Refrigeration On Type")},
207+
]
200208

201209
# Unexpected
202210
return [{"name": "Unexpected Analysis Type", "value": "Oops!"}]

seed/static/seed/js/controllers/inventory_detail_analyses_modal_controller.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ angular.module('SEED.controller.inventory_detail_analyses_modal', []).controller
138138
ff_fired_equipment_rsl_threshold: null
139139
};
140140
break;
141+
case 'HVAC Metrics':
142+
$scope.new_analysis.configuration = {};
143+
break;
141144
default:
142145
$log.error('Unknown analysis type.', $scope.new_analysis.service);
143146
Notification.error(`Unknown analysis type: ${$scope.new_analysis.service}`);

seed/static/seed/partials/inventory_detail_analyses_modal.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ <h4 class="modal-title" id="manageLabelsModalLabel" translate>
6060
>
6161
Building Upgrade Recommendation
6262
</option>
63+
<option ng-option value="HVAC Metrics" selected="selected">HVAC Metrics</option>
6364
</select>
6465
</div>
6566
<div

0 commit comments

Comments
 (0)