Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add meters to at export #4938

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
102 changes: 101 additions & 1 deletion seed/audit_template/audit_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
from lxml import etree
from lxml.builder import ElementMaker
from quantityfield.units import ureg
from tkbl import bsync_by_uniformat_code

from seed.analysis_pipelines.better.buildingsync import SEED_TO_BSYNC_RESOURCE_TYPE
from seed.building_sync import validation_client
from seed.building_sync.mappings import BUILDINGSYNC_URI, NAMESPACES
from seed.lib.progress_data.progress_data import ProgressData
from seed.lib.superperms.orgs.models import Organization
from seed.models import PropertyView
from seed.lib.tkbl.tkbl import SCOPE_ONE_EMISSION_CODES
from seed.models import Element, Measure, Meter, MeterReading, PropertyView
from seed.utils.encrypt import decrypt

_log = logging.getLogger(__name__)
Expand Down Expand Up @@ -332,8 +335,17 @@ def build_xml(self, state, report_type, display_field):
),
)
),
*_build_measures_element(E, view.property),
E.Reports(
E.Report(
E.Scenarios(
{},
E.Scenario(
{"ID": "ScenarioType-69817879941680"},
*_build_resource_uses(E, view.property),
*build_time_series_data(E, view.property),
),
),
{"ID": "ReportType-69909846999993"},
E.LinkedPremisesOrSystem(
E.Building(E.LinkedBuildingID({"IDref": "BuildingType-69909846999992"})),
Expand All @@ -358,6 +370,94 @@ def update_export_results(self, view_id, results, status, **extra_fields):
results[status]["details"].append({"view_id": view_id, **extra_fields})


def build_time_series_data(E, property):
meter_readings = MeterReading.objects.filter(meter__property=property)
if len(meter_readings) == 0:
return []

return [
E.TimeSeriesData(
{},
*[
E.TimeSeries(
{"ID": f"TimeSeriesType-{i}"},
E.StartTimestamp(meter_reading.start_time.isoformat()),
E.EndTimestamp(meter_reading.end_time.isoformat()),
E.IntervalReading(str(meter_reading.reading)),
)
for i, meter_reading in enumerate(MeterReading.objects.filter(meter__property_id=property))
],
)
]


def _build_resource_uses(E, property):
meters = Meter.objects.filter(property=property)
if len(meters) == 0:
return []

return [
E.ResourceUses(
{},
*[
E.ResourceUse(
{"ID": f"ResourceUseType-{meter.id}"},
E.EnergyResource(SEED_TO_BSYNC_RESOURCE_TYPE.get(meter.type, "Other")),
E.ResourceUnits(
"kBtu"
if Meter.ENERGY_TYPE_BY_METER_TYPE.get(meter.type) in Organization._default_display_meter_units
else "Gallons"
),
)
for meter in Meter.objects.filter(property=property)
],
)
]


def _build_measures_element(E, property):
measure_tuples = _get_measures(property)
if len(measure_tuples) == 0:
return []

return [
E.Measures(
{},
*[
E.Measure(
{"ID": f"MeasureType-{i}"},
E.TechnologyCategories(
{},
E.TechnologyCategory(
getattr(E, tc)(
{},
E.MeasureName(mn),
)
),
),
)
for i, (tc, mn) in enumerate(_get_measures(property))
],
)
]


def _get_measures(property):
tkbl_elements = Element.objects.filter(property=property, code__code__in=SCOPE_ONE_EMISSION_CODES).order_by("remaining_service_life")[
:3
]
bsync_measure_dicts = [x for e in tkbl_elements for x in bsync_by_uniformat_code(e.code.code)]

bsync_measure_tuples = set()
for bsync_measure_dict in bsync_measure_dicts:
category = Measure.objects.filter(category_display_name=bsync_measure_dict["cat_lev1"]).first().category
category = "".join(word.capitalize() for word in category.split("_"))

bsync_measure_tuples.add((category, bsync_measure_dict["eem_name"]))

return bsync_measure_tuples


@shared_task
def _batch_get_city_submission_xml(org_id, city_id, view_ids, progress_key):
"""
Expand Down
124 changes: 123 additions & 1 deletion seed/tests/test_audit_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,20 @@
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils import timezone as tz
from lxml import etree

# from seed.audit_template.audit_template import build_xml
from seed.audit_template.audit_template import AuditTemplate
from seed.landing.models import SEEDUser as User
from seed.test_helpers.fake import FakeCycleFactory, FakePropertyFactory, FakePropertyStateFactory, FakePropertyViewFactory
from seed.models import Meter, MeterReading, Uniformat
from seed.test_helpers.fake import (
FakeCycleFactory,
FakeElementFactory,
FakePropertyFactory,
FakePropertyStateFactory,
FakePropertyViewFactory,
)
from seed.utils.organizations import create_organization

# from seed.utils.encrypt import encrypt
Expand Down Expand Up @@ -90,6 +99,7 @@ def setUp(self):
self.property_factory = FakePropertyFactory(organization=self.org)
self.view_factory = FakePropertyViewFactory(organization=self.org)
self.state_factory = FakePropertyStateFactory(organization=self.org)
self.element_factory = FakeElementFactory(organization=self.org)

self.state1 = self.state_factory.get_property_state(
property_name="property1",
Expand Down Expand Up @@ -163,6 +173,118 @@ def test_build_xml_from_property(self):
self.assertEqual("error", messages[0])
self.assertEqual(exp_error, messages[1])

def test_build_xml_from_property_with_meter(self):
# Set Up
self.meter = Meter.objects.create(property_id=self.view1.property_id, type=Meter.ELECTRICITY_GRID)

# Action
at = AuditTemplate(self.org.id)
response = at.build_xml(self.state1, "Demo City Report", self.state1.pm_property_id)

# Assert
# # is tree
self.assertEqual(tuple, type(response))
tree = etree.XML(response[0])

# # has 1 scenario
scenarios = tree.findall("auc:Facilities/auc:Facility/auc:Reports/auc:Report/auc:Scenarios/auc:Scenario", namespaces=tree.nsmap)
self.assertEqual(1, len(scenarios))
scenario = scenarios[0]

# # scenario has 1 meter
meters = scenario.findall("auc:ResourceUses/auc:ResourceUse", namespaces=tree.nsmap)
self.assertEqual(1, len(meters))
meter = meters[0]

# # meter type
meter_type = meter.find("auc:EnergyResource", namespaces=tree.nsmap)
meter_unit = meter.find("auc:ResourceUnits", namespaces=tree.nsmap)
self.assertEqual(meter_type.text, "Electricity")
self.assertEqual(meter_unit.text, "kBtu")

def test_build_xml_from_property_with_meter_readings(self):
# Set Up
self.meter = Meter.objects.create(property_id=self.view1.property_id, type=Meter.ELECTRICITY_GRID)
MeterReading.objects.create(
start_time=datetime(2019, 1, 1, 0, 0, 0, tzinfo=tz.utc),
end_time=datetime(2019, 2, 1, 0, 0, 0, tzinfo=tz.utc),
reading=123,
meter_id=self.meter.id,
conversion_factor=1,
)

# Action
at = AuditTemplate(self.org.id)
response = at.build_xml(self.state1, "Demo City Report", self.state1.pm_property_id)

# Assert
# # is tree
self.assertEqual(tuple, type(response))
tree = etree.XML(response[0])

# # has 1 scenario
scenarios = tree.findall("auc:Facilities/auc:Facility/auc:Reports/auc:Report/auc:Scenarios/auc:Scenario", namespaces=tree.nsmap)
self.assertEqual(1, len(scenarios))
scenario = scenarios[0]

# # scenario has 1 meter reading
meter_readings = scenario.findall("auc:TimeSeriesData/auc:TimeSeries", namespaces=tree.nsmap)
self.assertEqual(1, len(meter_readings))
meter_reading = meter_readings[0]

start_time = meter_reading.find("auc:StartTimestamp", namespaces=tree.nsmap)
end_time = meter_reading.find("auc:EndTimestamp", namespaces=tree.nsmap)
reading = meter_reading.find("auc:IntervalReading", namespaces=tree.nsmap)
self.assertEqual(start_time.text, datetime(2019, 1, 1, 0, 0, 0, tzinfo=tz.utc).isoformat())
self.assertEqual(end_time.text, datetime(2019, 2, 1, 0, 0, 0, tzinfo=tz.utc).isoformat())
self.assertEqual(float(reading.text), 123)

def test_build_xml_from_property_with_measures(self):
from seed.lib.tkbl.tkbl import SCOPE_ONE_EMISSION_CODES

# Set Up
self.element1 = self.element_factory.get_element(
property=self.view1.property, code=Uniformat.objects.filter(code__in=SCOPE_ONE_EMISSION_CODES)[1]
)
self.element2 = self.element_factory.get_element(
property=self.view1.property, code=Uniformat.objects.filter(code__in=SCOPE_ONE_EMISSION_CODES)[2]
)

# Action
at = AuditTemplate(self.org.id)
response = at.build_xml(self.state1, "Demo City Report", self.state1.pm_property_id)

# Assert
# # is tree
self.assertEqual(tuple, type(response))
tree = etree.XML(response[0])

measures = tree.find("auc:Facilities/auc:Facility/auc:Measures", namespaces=tree.nsmap)
service_hot_water_systems = measures.findall(
"auc:Measure/auc:TechnologyCategories/auc:TechnologyCategory/auc:ServiceHotWaterSystems", namespaces=tree.nsmap
)
chilled_water_hot_water_and_steam_distribution_systems = measures.findall(
"auc:Measure/auc:TechnologyCategories/auc:TechnologyCategory/auc:ChilledWaterHotWaterAndSteamDistributionSystems",
namespaces=tree.nsmap,
)

self.assertSetEqual(
{
"Separate SHW from heating",
"Install heat pump SHW system",
"Upgrade SHW boiler",
"Insulate SHW tank",
"Replace tankless coil",
"Install tankless water heaters",
},
{m.find("auc:MeasureName", namespaces=tree.nsmap).text for m in service_hot_water_systems},
)

self.assertSetEqual(
{"Replace or upgrade water heater"},
{m.find("auc:MeasureName", namespaces=tree.nsmap).text for m in chilled_water_hot_water_and_steam_distribution_systems},
)

@mock.patch("requests.request")
def test_export_to_audit_template(self, mock_request):
"""
Expand Down
Loading