From 7f46aeceefcc900b981570a1849fe95ebbb0c101 Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Tue, 7 Dec 2021 14:59:30 -0700 Subject: [PATCH 01/10] adding generic conversion function --- hpxml_version_translator/__init__.py | 6 ++++ hpxml_version_translator/converter.py | 47 ++++++++++++++++++--------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/hpxml_version_translator/__init__.py b/hpxml_version_translator/__init__.py index deb39d3..8bc18da 100644 --- a/hpxml_version_translator/__init__.py +++ b/hpxml_version_translator/__init__.py @@ -15,6 +15,12 @@ def main(argv=sys.argv[1:]): default=sys.stdout.buffer, help='Filename of output HPXML v3 file. If not provided, will go to stdout' ) + parser.add_argument( + '-v', '--to_hpxml_version', + type=int, + default=3, + help='Major version of HPXML to translate to, default: 3' + ) args = parser.parse_args(argv) convert_hpxml_to_3(args.hpxml_input, args.output) diff --git a/hpxml_version_translator/converter.py b/hpxml_version_translator/converter.py index a3ce5ac..e78a872 100644 --- a/hpxml_version_translator/converter.py +++ b/hpxml_version_translator/converter.py @@ -2,16 +2,20 @@ from copy import deepcopy import datetime as dt from lxml import etree, objectify +import os import pathlib import re import tempfile +from typing import Union, BinaryIO, List import io import warnings from hpxml_version_translator import exceptions as exc +File = Union[str, bytes, os.PathLike, BinaryIO] -def pathobj_to_str(x): + +def pathobj_to_str(x: File) -> Union[str, BinaryIO]: """Convert pathlib.Path object (if it is one) to a path string lxml doesn't like pathlib.Path objects, so change them to a string if @@ -30,14 +34,14 @@ def pathobj_to_str(x): return x.name -def detect_hpxml_version(hpxmlfilename): - doc = etree.parse(str(hpxmlfilename)) +def detect_hpxml_version(hpxmlfilename: File) -> List[int]: + doc = etree.parse(pathobj_to_str(hpxmlfilename)) schema_version = list(map(int, doc.getroot().attrib['schemaVersion'].split('.'))) schema_version.extend((3 - len(schema_version)) * [0]) return schema_version -def add_after(parent_el, list_of_el_names, el_to_add): +def add_after(parent_el: etree._Element, list_of_el_names: List[str], el_to_add: etree._Element) -> None: for sibling_name in reversed(list_of_el_names): try: sibling = getattr(parent_el, sibling_name)[-1] @@ -49,7 +53,7 @@ def add_after(parent_el, list_of_el_names, el_to_add): parent_el.insert(0, el_to_add) -def add_before(parent_el, list_of_el_names, el_to_add): +def add_before(parent_el: etree._Element, list_of_el_names: List[str], el_to_add: etree._Element) -> None: for sibling_name in list_of_el_names: try: sibling = getattr(parent_el, sibling_name)[0] @@ -61,18 +65,29 @@ def add_before(parent_el, list_of_el_names, el_to_add): parent_el.append(el_to_add) -def convert_hpxml_to_3(hpxml_file, hpxml3_file): +def convert_hpxml_to_version(hpxml_version: int, hpxml_file: File, hpxml_out_file: File) -> None: schema_version = detect_hpxml_version(hpxml_file) major_version = schema_version[0] - if major_version == 1: - hpxml2_file = tempfile.NamedTemporaryFile() - convert_hpxml1_to_2(hpxml_file, hpxml2_file) - convert_hpxml2_to_3(hpxml2_file, hpxml3_file) - elif major_version == 2: - convert_hpxml2_to_3(hpxml_file, hpxml3_file) - - -def convert_hpxml1_to_2(hpxml1_file, hpxml2_file): + if hpxml_version <= major_version: + raise RuntimeError(f"HPXML version requested is {hpxml_version} but input file version is {major_version}") + version_translator_funcs = { + 1: convert_hpxml1_to_2, + 2: convert_hpxml2_to_3 + } + current_file = hpxml_file + with tempfile.TemporaryDirectory() as tmpdir: + for current_version in range(major_version, hpxml_version): + next_version = current_version + 1 + next_file = hpxml_out_file if current_version + 1 == hpxml_version else pathlib.Path(tmpdir, f"{next_version}.xml") + version_translator_funcs[current_version](current_file, next_file) + current_file = next_file + + +def convert_hpxml_to_3(hpxml_file: File, hpxml3_file: File) -> None: + convert_hpxml_to_version(3, hpxml_file, hpxml3_file) + + +def convert_hpxml1_to_2(hpxml1_file: File, hpxml2_file: File) -> None: """Convert an HPXML v1 file to HPXML v2 :param hpxml1_file: HPXML v1 input file @@ -128,7 +143,7 @@ def convert_hpxml1_to_2(hpxml1_file, hpxml2_file): hpxml2_schema.assertValid(hpxml2_doc) -def convert_hpxml2_to_3(hpxml2_file, hpxml3_file): +def convert_hpxml2_to_3(hpxml2_file: File, hpxml3_file: File) -> None: """Convert an HPXML v2 file to HPXML v3 :param hpxml2_file: HPXML v2 input file From cfa46ddef01b72b00173174846d0e903d80ae6b2 Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Tue, 7 Dec 2021 15:00:38 -0700 Subject: [PATCH 02/10] reformatted everything using black --- hpxml_version_translator/__init__.py | 21 +- hpxml_version_translator/converter.py | 1250 +++++++++++++----------- hpxml_version_translator/exceptions.py | 2 - setup.py | 47 +- test/test_converter_cli.py | 12 +- test/test_converter_v1to3.py | 24 +- test/test_converter_v2to3.py | 835 +++++++++------- 7 files changed, 1226 insertions(+), 965 deletions(-) diff --git a/hpxml_version_translator/__init__.py b/hpxml_version_translator/__init__.py index 8bc18da..d5b40de 100644 --- a/hpxml_version_translator/__init__.py +++ b/hpxml_version_translator/__init__.py @@ -4,26 +4,27 @@ def main(argv=sys.argv[1:]): - parser = argparse.ArgumentParser(description='HPXML Version Translator, convert an HPXML file to 3.0') - parser.add_argument( - 'hpxml_input', - help='Filename of hpxml file' + parser = argparse.ArgumentParser( + description="HPXML Version Translator, convert an HPXML file to 3.0" ) + parser.add_argument("hpxml_input", help="Filename of hpxml file") parser.add_argument( - '-o', '--output', - type=argparse.FileType('wb'), + "-o", + "--output", + type=argparse.FileType("wb"), default=sys.stdout.buffer, - help='Filename of output HPXML v3 file. If not provided, will go to stdout' + help="Filename of output HPXML v3 file. If not provided, will go to stdout", ) parser.add_argument( - '-v', '--to_hpxml_version', + "-v", + "--to_hpxml_version", type=int, default=3, - help='Major version of HPXML to translate to, default: 3' + help="Major version of HPXML to translate to, default: 3", ) args = parser.parse_args(argv) convert_hpxml_to_3(args.hpxml_input, args.output) -if __name__ == '__main__': +if __name__ == "__main__": main() # pragma: no cover diff --git a/hpxml_version_translator/converter.py b/hpxml_version_translator/converter.py index e78a872..98e6820 100644 --- a/hpxml_version_translator/converter.py +++ b/hpxml_version_translator/converter.py @@ -28,7 +28,11 @@ def pathobj_to_str(x: File) -> Union[str, BinaryIO]: """ if isinstance(x, pathlib.PurePath): return str(x) - elif isinstance(x, str) or isinstance(x, io.BufferedWriter) or isinstance(x, io.BytesIO): + elif ( + isinstance(x, str) + or isinstance(x, io.BufferedWriter) + or isinstance(x, io.BytesIO) + ): return x else: # tempfile.NamedTemporaryFile return x.name @@ -36,12 +40,14 @@ def pathobj_to_str(x: File) -> Union[str, BinaryIO]: def detect_hpxml_version(hpxmlfilename: File) -> List[int]: doc = etree.parse(pathobj_to_str(hpxmlfilename)) - schema_version = list(map(int, doc.getroot().attrib['schemaVersion'].split('.'))) + schema_version = list(map(int, doc.getroot().attrib["schemaVersion"].split("."))) schema_version.extend((3 - len(schema_version)) * [0]) return schema_version -def add_after(parent_el: etree._Element, list_of_el_names: List[str], el_to_add: etree._Element) -> None: +def add_after( + parent_el: etree._Element, list_of_el_names: List[str], el_to_add: etree._Element +) -> None: for sibling_name in reversed(list_of_el_names): try: sibling = getattr(parent_el, sibling_name)[-1] @@ -53,7 +59,9 @@ def add_after(parent_el: etree._Element, list_of_el_names: List[str], el_to_add: parent_el.insert(0, el_to_add) -def add_before(parent_el: etree._Element, list_of_el_names: List[str], el_to_add: etree._Element) -> None: +def add_before( + parent_el: etree._Element, list_of_el_names: List[str], el_to_add: etree._Element +) -> None: for sibling_name in list_of_el_names: try: sibling = getattr(parent_el, sibling_name)[0] @@ -65,20 +73,25 @@ def add_before(parent_el: etree._Element, list_of_el_names: List[str], el_to_add parent_el.append(el_to_add) -def convert_hpxml_to_version(hpxml_version: int, hpxml_file: File, hpxml_out_file: File) -> None: +def convert_hpxml_to_version( + hpxml_version: int, hpxml_file: File, hpxml_out_file: File +) -> None: schema_version = detect_hpxml_version(hpxml_file) major_version = schema_version[0] if hpxml_version <= major_version: - raise RuntimeError(f"HPXML version requested is {hpxml_version} but input file version is {major_version}") - version_translator_funcs = { - 1: convert_hpxml1_to_2, - 2: convert_hpxml2_to_3 - } + raise RuntimeError( + f"HPXML version requested is {hpxml_version} but input file version is {major_version}" + ) + version_translator_funcs = {1: convert_hpxml1_to_2, 2: convert_hpxml2_to_3} current_file = hpxml_file with tempfile.TemporaryDirectory() as tmpdir: for current_version in range(major_version, hpxml_version): next_version = current_version + 1 - next_file = hpxml_out_file if current_version + 1 == hpxml_version else pathlib.Path(tmpdir, f"{next_version}.xml") + next_file = ( + hpxml_out_file + if current_version + 1 == hpxml_version + else pathlib.Path(tmpdir, f"{next_version}.xml") + ) version_translator_funcs[current_version](current_file, next_file) current_file = next_file @@ -97,16 +110,18 @@ def convert_hpxml1_to_2(hpxml1_file: File, hpxml2_file: File) -> None: """ # Load Schemas - schemas_dir = pathlib.Path(__file__).resolve().parent / 'schemas' - hpxml1_schema_doc = etree.parse(str(schemas_dir / 'v1.1.1' / 'HPXML.xsd')) - hpxml1_ns = hpxml1_schema_doc.getroot().attrib['targetNamespace'] + schemas_dir = pathlib.Path(__file__).resolve().parent / "schemas" + hpxml1_schema_doc = etree.parse(str(schemas_dir / "v1.1.1" / "HPXML.xsd")) + hpxml1_ns = hpxml1_schema_doc.getroot().attrib["targetNamespace"] hpxml1_schema = etree.XMLSchema(hpxml1_schema_doc) - hpxml2_schema_doc = etree.parse(str(schemas_dir / 'v2.3' / 'HPXML.xsd')) - hpxml2_ns = hpxml2_schema_doc.getroot().attrib['targetNamespace'] + hpxml2_schema_doc = etree.parse(str(schemas_dir / "v2.3" / "HPXML.xsd")) + hpxml2_ns = hpxml2_schema_doc.getroot().attrib["targetNamespace"] hpxml2_schema = etree.XMLSchema(hpxml2_schema_doc) - E = objectify.ElementMaker(namespace=hpxml2_ns, nsmap={None: hpxml2_ns}, annotate=False) - xpkw = {'namespaces': {'h': hpxml2_ns}} + E = objectify.ElementMaker( + namespace=hpxml2_ns, nsmap={None: hpxml2_ns}, annotate=False + ) + xpkw = {"namespaces": {"h": hpxml2_ns}} # Ensure we're working with valid HPXML v1.x (earlier versions should validate against v1.1.1 schema) hpxml1_doc = objectify.parse(pathobj_to_str(hpxml1_file)) @@ -114,32 +129,39 @@ def convert_hpxml1_to_2(hpxml1_file: File, hpxml2_file: File) -> None: # Change the namespace of every element to use the HPXML v2 namespace # https://stackoverflow.com/a/51660868/11600307 - change_ns_xslt = etree.parse(str(pathlib.Path(__file__).resolve().parent / 'change_namespace.xsl')) - hpxml2_doc = hpxml1_doc.xslt(change_ns_xslt, orig_namespace=f"'{hpxml1_ns}'", new_namespace=f"'{hpxml2_ns}'") + change_ns_xslt = etree.parse( + str(pathlib.Path(__file__).resolve().parent / "change_namespace.xsl") + ) + hpxml2_doc = hpxml1_doc.xslt( + change_ns_xslt, orig_namespace=f"'{hpxml1_ns}'", new_namespace=f"'{hpxml2_ns}'" + ) root = hpxml2_doc.getroot() # Change version - root.attrib['schemaVersion'] = '2.3' + root.attrib["schemaVersion"] = "2.3" # TODO: Moved the BPI 2400 elements and renamed/reorganized them. # Renamed element AttachedToCAZ under water heater to fix a typo. - for el in root.xpath('//h:WaterHeatingSystem/h:AtachedToCAZ', **xpkw): - el.tag = f'{{{hpxml2_ns}}}AttachedToCAZ' + for el in root.xpath("//h:WaterHeatingSystem/h:AtachedToCAZ", **xpkw): + el.tag = f"{{{hpxml2_ns}}}AttachedToCAZ" # Removed "batch heater" from SolarCollectorLoopType in lieu of the previously # added "integrated collector storage" enumeration on SolarThermalCollectorType. - for batch_heater in root.xpath('//h:SolarThermal/h:SolarThermalSystem[h:CollectorLoopType="batch heater"]', **xpkw): - if not hasattr(batch_heater, 'CollectorType'): + for batch_heater in root.xpath( + '//h:SolarThermal/h:SolarThermalSystem[h:CollectorLoopType="batch heater"]', + **xpkw, + ): + if not hasattr(batch_heater, "CollectorType"): add_after( batch_heater, - ['CollectorLoopType'], - E.CollectorType('integrated collector storage') + ["CollectorLoopType"], + E.CollectorType("integrated collector storage"), ) batch_heater.remove(batch_heater.CollectorLoopType) # Write out new file - hpxml2_doc.write(pathobj_to_str(hpxml2_file), pretty_print=True, encoding='utf-8') + hpxml2_doc.write(pathobj_to_str(hpxml2_file), pretty_print=True, encoding="utf-8") hpxml2_schema.assertValid(hpxml2_doc) @@ -153,16 +175,18 @@ def convert_hpxml2_to_3(hpxml2_file: File, hpxml3_file: File) -> None: """ # Load Schemas - schemas_dir = pathlib.Path(__file__).resolve().parent / 'schemas' - hpxml2_schema_doc = etree.parse(str(schemas_dir / 'v2.3' / 'HPXML.xsd')) - hpxml2_ns = hpxml2_schema_doc.getroot().attrib['targetNamespace'] + schemas_dir = pathlib.Path(__file__).resolve().parent / "schemas" + hpxml2_schema_doc = etree.parse(str(schemas_dir / "v2.3" / "HPXML.xsd")) + hpxml2_ns = hpxml2_schema_doc.getroot().attrib["targetNamespace"] hpxml2_schema = etree.XMLSchema(hpxml2_schema_doc) - hpxml3_schema_doc = etree.parse(str(schemas_dir / 'v3.0' / 'HPXML.xsd')) - hpxml3_ns = hpxml3_schema_doc.getroot().attrib['targetNamespace'] + hpxml3_schema_doc = etree.parse(str(schemas_dir / "v3.0" / "HPXML.xsd")) + hpxml3_ns = hpxml3_schema_doc.getroot().attrib["targetNamespace"] hpxml3_schema = etree.XMLSchema(hpxml3_schema_doc) - E = objectify.ElementMaker(namespace=hpxml3_ns, nsmap={None: hpxml3_ns}, annotate=False) - xpkw = {'namespaces': {'h': hpxml3_ns}} + E = objectify.ElementMaker( + namespace=hpxml3_ns, nsmap={None: hpxml3_ns}, annotate=False + ) + xpkw = {"namespaces": {"h": hpxml3_ns}} # Ensure we're working with valid HPXML v2.x (earlier versions should validate against v2.3 schema) hpxml2_doc = objectify.parse(pathobj_to_str(hpxml2_file)) @@ -170,22 +194,28 @@ def convert_hpxml2_to_3(hpxml2_file: File, hpxml3_file: File) -> None: # Change the namespace of every element to use the HPXML v3 namespace # https://stackoverflow.com/a/51660868/11600307 - change_ns_xslt = etree.parse(str(pathlib.Path(__file__).resolve().parent / 'change_namespace.xsl')) - hpxml3_doc = hpxml2_doc.xslt(change_ns_xslt, orig_namespace=f"'{hpxml2_ns}'", new_namespace=f"'{hpxml3_ns}'") + change_ns_xslt = etree.parse( + str(pathlib.Path(__file__).resolve().parent / "change_namespace.xsl") + ) + hpxml3_doc = hpxml2_doc.xslt( + change_ns_xslt, orig_namespace=f"'{hpxml2_ns}'", new_namespace=f"'{hpxml3_ns}'" + ) root = hpxml3_doc.getroot() # Change version - root.attrib['schemaVersion'] = '3.0' + root.attrib["schemaVersion"] = "3.0" # Standardized location mapping - location_map = {'ambient': 'outside', # 'ambient' will be mapped to 'ground' for FoundationWall - 'conditioned space': 'living space', - 'unconditioned basement': 'basement - unconditioned', - 'unconditioned attic': 'attic - unconditioned', - 'unvented crawlspace': 'crawlspace - unvented', - 'vented crawlspace': 'crawlspace - vented'} + location_map = { + "ambient": "outside", # 'ambient' will be mapped to 'ground' for FoundationWall + "conditioned space": "living space", + "unconditioned basement": "basement - unconditioned", + "unconditioned attic": "attic - unconditioned", + "unvented crawlspace": "crawlspace - unvented", + "vented crawlspace": "crawlspace - vented", + } foundation_location_map = deepcopy(location_map) - foundation_location_map['ambient'] = 'ground' + foundation_location_map["ambient"] = "ground" # Fixing project ids # https://github.com/hpxmlwg/hpxml/pull/197 @@ -193,82 +223,94 @@ def convert_hpxml2_to_3(hpxml2_file: File, hpxml3_file: File) -> None: def get_pre_post_from_building_id(building_id): event_type = root.xpath( - 'h:Building[h:BuildingID/@id=$bldgid]/h:ProjectStatus/h:EventType/text()', + "h:Building[h:BuildingID/@id=$bldgid]/h:ProjectStatus/h:EventType/text()", smart_strings=False, bldgid=building_id, - **xpkw + **xpkw, ) if len(event_type) == 1: - if event_type[0] in ('proposed workscope', 'approved workscope', - 'construction-period testing/daily test out', - 'job completion testing/final inspection', 'quality assurance/monitoring'): - return 'post' - elif event_type[0] in ('audit', 'preconstruction'): - return 'pre' + if event_type[0] in ( + "proposed workscope", + "approved workscope", + "construction-period testing/daily test out", + "job completion testing/final inspection", + "quality assurance/monitoring", + ): + return "post" + elif event_type[0] in ("audit", "preconstruction"): + return "pre" else: return None else: return None - for i, project in enumerate(root.xpath('h:Project', **xpkw), 1): + for i, project in enumerate(root.xpath("h:Project", **xpkw), 1): # Add the ProjectID element if it isn't there - if not hasattr(project, 'ProjectID'): - add_after(project, ['BuildingID'], E.ProjectID(id=f'project-{i}')) + if not hasattr(project, "ProjectID"): + add_after(project, ["BuildingID"], E.ProjectID(id=f"project-{i}")) building_ids_by_pre_post = defaultdict(set) # Gather together the buildings in BuildingID and ProjectSystemIdentifiers - building_id = project.BuildingID.attrib['id'] - building_ids_by_pre_post[get_pre_post_from_building_id(building_id)].add(building_id) - for psi in project.xpath('h:ProjectDetails/h:ProjectSystemIdentifiers', **xpkw): - building_id = psi.attrib.get('id') - building_ids_by_pre_post[get_pre_post_from_building_id(building_id)].add(building_id) + building_id = project.BuildingID.attrib["id"] + building_ids_by_pre_post[get_pre_post_from_building_id(building_id)].add( + building_id + ) + for psi in project.xpath("h:ProjectDetails/h:ProjectSystemIdentifiers", **xpkw): + building_id = psi.attrib.get("id") + building_ids_by_pre_post[get_pre_post_from_building_id(building_id)].add( + building_id + ) - for pre_post in ('pre', 'post'): + for pre_post in ("pre", "post"): if len(building_ids_by_pre_post[pre_post]) == 0: - for building_id in root.xpath('h:Building/h:BuildingID/@id', **xpkw): + for building_id in root.xpath("h:Building/h:BuildingID/@id", **xpkw): if get_pre_post_from_building_id(building_id) == pre_post: building_ids_by_pre_post[pre_post].add(building_id) # If there are more than one of each pre and post, throw an error - if len(building_ids_by_pre_post['pre']) == 0: + if len(building_ids_by_pre_post["pre"]) == 0: raise exc.HpxmlTranslationError( f"Project[{i}] has no references to Building nodes with an 'audit' or 'preconstruction' EventType." ) - elif len(building_ids_by_pre_post['pre']) > 1: + elif len(building_ids_by_pre_post["pre"]) > 1: raise exc.HpxmlTranslationError( f"Project[{i}] has more than one reference to Building nodes with an " "'audit' or 'preconstruction' EventType." ) - if len(building_ids_by_pre_post['post']) == 0: + if len(building_ids_by_pre_post["post"]) == 0: raise exc.HpxmlTranslationError( f"Project[{i}] has no references to Building nodes with a post retrofit EventType." ) - elif len(building_ids_by_pre_post['post']) > 1: + elif len(building_ids_by_pre_post["post"]) > 1: raise exc.HpxmlTranslationError( f"Project[{i}] has more than one reference to Building nodes with a post retrofit EventType." ) - pre_building_id = building_ids_by_pre_post['pre'].pop() - post_building_id = building_ids_by_pre_post['post'].pop() + pre_building_id = building_ids_by_pre_post["pre"].pop() + post_building_id = building_ids_by_pre_post["post"].pop() # Add the pre building project.ProjectID.addnext(E.PreBuildingID(id=pre_building_id)) - for el in root.xpath('h:Building/h:BuildingID[@id=$bldgid]/*', bldgid=pre_building_id, **xpkw): + for el in root.xpath( + "h:Building/h:BuildingID[@id=$bldgid]/*", bldgid=pre_building_id, **xpkw + ): project.PreBuildingID.append(deepcopy(el)) # Add the post building project.PreBuildingID.addnext(E.PostBuildingID(id=post_building_id)) - for el in root.xpath('h:Building/h:BuildingID[@id=$bldgid]/*', bldgid=post_building_id, **xpkw): + for el in root.xpath( + "h:Building/h:BuildingID[@id=$bldgid]/*", bldgid=post_building_id, **xpkw + ): project.PostBuildingID.append(deepcopy(el)) # Move the ambiguous BuildingID to an extension - if not hasattr(project, 'extension'): + if not hasattr(project, "extension"): project.append(E.extension()) project.extension.append(deepcopy(project.BuildingID)) project.remove(project.BuildingID) # Move the ProjectSystemIdentifiers to an extension - for psi in project.xpath('h:ProjectDetails/h:ProjectSystemIdentifiers', **xpkw): + for psi in project.xpath("h:ProjectDetails/h:ProjectSystemIdentifiers", **xpkw): project.extension.append(deepcopy(psi)) project.ProjectDetails.remove(psi) @@ -278,213 +320,247 @@ def get_pre_post_from_building_id(building_id): # https://github.com/hpxmlwg/hpxml/pull/210 energy_score_els = root.xpath( - 'h:Building/h:BuildingDetails/h:BuildingSummary/h:BuildingConstruction/h:EnergyScore', **xpkw + "h:Building/h:BuildingDetails/h:BuildingSummary/h:BuildingConstruction/h:EnergyScore", + **xpkw, ) for i, es in enumerate(energy_score_els, 1): bldg_details = es.getparent().getparent().getparent() - if not hasattr(bldg_details, 'GreenBuildingVerifications'): + if not hasattr(bldg_details, "GreenBuildingVerifications"): add_after( bldg_details, - ['BuildingSummary', 'ClimateandRiskZones'], - E.GreenBuildingVerifications() + ["BuildingSummary", "ClimateandRiskZones"], + E.GreenBuildingVerifications(), ) gbv = E.GreenBuildingVerification( - E.SystemIdentifier(id=f'energy-score-{i}'), - E.Type({ - 'US DOE Home Energy Score': 'Home Energy Score', - 'RESNET HERS': 'HERS Index Score', - 'other': 'other' - }[str(es.ScoreType)]), - E.Body({ - 'US DOE Home Energy Score': 'US DOE', - 'RESNET HERS': 'RESNET', - 'other': 'other' - }[str(es.ScoreType)]), - E.Metric(str(es.Score)) + E.SystemIdentifier(id=f"energy-score-{i}"), + E.Type( + { + "US DOE Home Energy Score": "Home Energy Score", + "RESNET HERS": "HERS Index Score", + "other": "other", + }[str(es.ScoreType)] + ), + E.Body( + { + "US DOE Home Energy Score": "US DOE", + "RESNET HERS": "RESNET", + "other": "other", + }[str(es.ScoreType)] + ), + E.Metric(str(es.Score)), ) - if hasattr(es, 'OtherScoreType'): + if hasattr(es, "OtherScoreType"): gbv.Type.addnext(E.OtherType(str(es.OtherScoreType))) - if hasattr(es, 'ScoreDate'): - gbv.append(E.Year(dt.datetime.strptime(str(es.ScoreDate), '%Y-%m-%d').year)) - if hasattr(es, 'extension'): + if hasattr(es, "ScoreDate"): + gbv.append(E.Year(dt.datetime.strptime(str(es.ScoreDate), "%Y-%m-%d").year)) + if hasattr(es, "extension"): gbv.append(deepcopy(es.extension)) bldg_details.GreenBuildingVerifications.append(gbv) es.getparent().remove(es) - for i, prog_cert in enumerate(root.xpath('h:Project/h:ProjectDetails/h:ProgramCertificate', **xpkw), 1): + for i, prog_cert in enumerate( + root.xpath("h:Project/h:ProjectDetails/h:ProgramCertificate", **xpkw), 1 + ): project_details = prog_cert.getparent() - bldg_id = project_details.getparent().PostBuildingID.attrib['id'] - bldg_details = root.xpath('h:Building[h:BuildingID/@id=$bldgid]/h:BuildingDetails', bldgid=bldg_id, **xpkw)[0] - if not hasattr(bldg_details, 'GreenBuildingVerifications'): + bldg_id = project_details.getparent().PostBuildingID.attrib["id"] + bldg_details = root.xpath( + "h:Building[h:BuildingID/@id=$bldgid]/h:BuildingDetails", + bldgid=bldg_id, + **xpkw, + )[0] + if not hasattr(bldg_details, "GreenBuildingVerifications"): add_after( bldg_details, - ['BuildingSummary', 'ClimateandRiskZones'], - E.GreenBuildingVerifications() + ["BuildingSummary", "ClimateandRiskZones"], + E.GreenBuildingVerifications(), ) gbv = E.GreenBuildingVerification( - E.SystemIdentifier(id=f'program-certificate-{i}'), - E.Type({ - 'Home Performance with Energy Star': 'Home Performance with ENERGY STAR', - 'LEED Certified': 'LEED For Homes', - 'LEED Silver': 'LEED For Homes', - 'LEED Gold': 'LEED For Homes', - 'LEED Platinum': 'LEED For Homes', - 'other': 'other' - }[str(prog_cert)]) + E.SystemIdentifier(id=f"program-certificate-{i}"), + E.Type( + { + "Home Performance with Energy Star": "Home Performance with ENERGY STAR", + "LEED Certified": "LEED For Homes", + "LEED Silver": "LEED For Homes", + "LEED Gold": "LEED For Homes", + "LEED Platinum": "LEED For Homes", + "other": "other", + }[str(prog_cert)] + ), ) - if hasattr(project_details, 'CertifyingOrganization'): + if hasattr(project_details, "CertifyingOrganization"): gbv.append(E.Body(str(project_details.CertifyingOrganization))) - m = re.match(r'LEED (\w+)$', str(prog_cert)) + m = re.match(r"LEED (\w+)$", str(prog_cert)) if m: gbv.append(E.Rating(m.group(1))) - if hasattr(project_details, 'CertifyingOrganizationURL'): + if hasattr(project_details, "CertifyingOrganizationURL"): gbv.append(E.URL(str(project_details.CertifyingOrganizationURL))) - if hasattr(project_details, 'YearCertified'): + if hasattr(project_details, "YearCertified"): gbv.append(E.Year(int(project_details.YearCertified))) bldg_details.GreenBuildingVerifications.append(gbv) - for i, es_home_ver in enumerate(root.xpath('h:Project/h:ProjectDetails/h:EnergyStarHomeVersion', **xpkw)): - bldg_id = es_home_ver.getparent().getparent().PostBuildingID.attrib['id'] - bldg_details = root.xpath('h:Building[h:BuildingID/@id=$bldgid]/h:BuildingDetails', bldgid=bldg_id, **xpkw)[0] - if not hasattr(bldg_details, 'GreenBuildingVerifications'): + for i, es_home_ver in enumerate( + root.xpath("h:Project/h:ProjectDetails/h:EnergyStarHomeVersion", **xpkw) + ): + bldg_id = es_home_ver.getparent().getparent().PostBuildingID.attrib["id"] + bldg_details = root.xpath( + "h:Building[h:BuildingID/@id=$bldgid]/h:BuildingDetails", + bldgid=bldg_id, + **xpkw, + )[0] + if not hasattr(bldg_details, "GreenBuildingVerifications"): add_after( bldg_details, - ['BuildingSummary', 'ClimateandRiskZones'], - E.GreenBuildingVerifications() + ["BuildingSummary", "ClimateandRiskZones"], + E.GreenBuildingVerifications(), ) gbv = E.GreenBuildingVerification( - E.SystemIdentifier(id=f'energy-star-home-{i}'), - E.Type('ENERGY STAR Certified Homes'), - E.Version(str(es_home_ver)) + E.SystemIdentifier(id=f"energy-star-home-{i}"), + E.Type("ENERGY STAR Certified Homes"), + E.Version(str(es_home_ver)), ) bldg_details.GreenBuildingVerifications.append(gbv) - for el_name in ('CertifyingOrganization', 'CertifyingOrganizationURL', 'YearCertified', 'ProgramCertificate', - 'EnergyStarHomeVersion'): - for el in root.xpath(f'//h:ProjectDetails/h:{el_name}', **xpkw): + for el_name in ( + "CertifyingOrganization", + "CertifyingOrganizationURL", + "YearCertified", + "ProgramCertificate", + "EnergyStarHomeVersion", + ): + for el in root.xpath(f"//h:ProjectDetails/h:{el_name}", **xpkw): el.getparent().remove(el) # Addressing Inconsistencies # https://github.com/hpxmlwg/hpxml/pull/124 - for el in root.xpath('//h:HeatPump/h:AnnualCoolEfficiency', **xpkw): - el.tag = f'{{{hpxml3_ns}}}AnnualCoolingEfficiency' - for el in root.xpath('//h:HeatPump/h:AnnualHeatEfficiency', **xpkw): - el.tag = f'{{{hpxml3_ns}}}AnnualHeatingEfficiency' + for el in root.xpath("//h:HeatPump/h:AnnualCoolEfficiency", **xpkw): + el.tag = f"{{{hpxml3_ns}}}AnnualCoolingEfficiency" + for el in root.xpath("//h:HeatPump/h:AnnualHeatEfficiency", **xpkw): + el.tag = f"{{{hpxml3_ns}}}AnnualHeatingEfficiency" # Replaces Measure/InstalledComponent with Measure/InstalledComponents/InstalledComponent - for i, ic in enumerate(root.xpath('h:Project/h:ProjectDetails/h:Measures/h:Measure/h:InstalledComponent', **xpkw)): + for i, ic in enumerate( + root.xpath( + "h:Project/h:ProjectDetails/h:Measures/h:Measure/h:InstalledComponent", + **xpkw, + ) + ): ms = ic.getparent() - if not hasattr(ms, 'InstalledComponents'): - add_before( - ms, - ['extension'], - E.InstalledComponents() - ) + if not hasattr(ms, "InstalledComponents"): + add_before(ms, ["extension"], E.InstalledComponents()) ms.InstalledComponents.append(deepcopy(ic)) ms.remove(ic) # Replaces WeatherStation/SystemIdentifiersInfo with WeatherStation/SystemIdentifier - for el in root.xpath('//h:WeatherStation/h:SystemIdentifiersInfo', **xpkw): - el.tag = f'{{{hpxml3_ns}}}SystemIdentifier' + for el in root.xpath("//h:WeatherStation/h:SystemIdentifiersInfo", **xpkw): + el.tag = f"{{{hpxml3_ns}}}SystemIdentifier" # Renames "central air conditioning" to "central air conditioner" for CoolingSystemType - for el in root.xpath('//h:CoolingSystem/h:CoolingSystemType', **xpkw): - if el == 'central air conditioning': - el._setText('central air conditioner') + for el in root.xpath("//h:CoolingSystem/h:CoolingSystemType", **xpkw): + if el == "central air conditioning": + el._setText("central air conditioner") # Renames HeatPump/BackupAFUE to BackupAnnualHeatingEfficiency, accepts 0-1 instead of 1-100 for bkupafue in root.xpath( - 'h:Building/h:BuildingDetails/h:Systems/h:HVAC/h:HVACPlant/h:HeatPump/h:BackupAFUE', **xpkw + "h:Building/h:BuildingDetails/h:Systems/h:HVAC/h:HVACPlant/h:HeatPump/h:BackupAFUE", + **xpkw, ): heatpump = bkupafue.getparent() add_before( heatpump, - ['BackupHeatingCapacity', - 'BackupHeatingSwitchoverTemperature', - 'FractionHeatLoadServed', - 'FractionCoolLoadServed', - 'FloorAreaServed', - 'AnnualCoolingEfficiency', - 'AnnualHeatingEfficiency', - 'extension'], + [ + "BackupHeatingCapacity", + "BackupHeatingSwitchoverTemperature", + "FractionHeatLoadServed", + "FractionCoolLoadServed", + "FloorAreaServed", + "AnnualCoolingEfficiency", + "AnnualHeatingEfficiency", + "extension", + ], E.BackupAnnualHeatingEfficiency( - E.Units('AFUE'), - E.Value(f'{float(bkupafue.text) / 100}') - ) + E.Units("AFUE"), E.Value(f"{float(bkupafue.text) / 100}") + ), ) heatpump.remove(bkupafue) # Renames FoundationWall/BelowGradeDepth to FoundationWall/DepthBelowGrade - for el in root.xpath('//h:FoundationWall/h:BelowGradeDepth', **xpkw): - el.tag = f'{{{hpxml3_ns}}}DepthBelowGrade' + for el in root.xpath("//h:FoundationWall/h:BelowGradeDepth", **xpkw): + el.tag = f"{{{hpxml3_ns}}}DepthBelowGrade" # Clothes Dryer CEF # https://github.com/hpxmlwg/hpxml/pull/145 - for el in root.xpath('//h:ClothesDryer/h:EfficiencyFactor', **xpkw): - el.tag = f'{{{hpxml3_ns}}}EnergyFactor' + for el in root.xpath("//h:ClothesDryer/h:EfficiencyFactor", **xpkw): + el.tag = f"{{{hpxml3_ns}}}EnergyFactor" # Enclosure # https://github.com/hpxmlwg/hpxml/pull/181 for fw in root.xpath( - 'h:Building/h:BuildingDetails/h:Enclosure/h:Foundations/h:Foundation/h:FoundationWall', **xpkw + "h:Building/h:BuildingDetails/h:Enclosure/h:Foundations/h:Foundation/h:FoundationWall", + **xpkw, ): enclosure = fw.getparent().getparent().getparent() foundation = fw.getparent() add_before( foundation, - ['AttachedToFrameFloor', - 'AttachedToSlab', - 'AnnualEnergyUse', - 'extension'], - E.AttachedToFoundationWall(idref=fw.SystemIdentifier.attrib['id']) + ["AttachedToFrameFloor", "AttachedToSlab", "AnnualEnergyUse", "extension"], + E.AttachedToFoundationWall(idref=fw.SystemIdentifier.attrib["id"]), ) - if not hasattr(enclosure, 'FoundationWalls'): + if not hasattr(enclosure, "FoundationWalls"): add_after( enclosure, - ['AirInfiltration', - 'Attics', - 'Foundations', - 'Garages', - 'Roofs', - 'RimJoists', - 'Walls'], - E.FoundationWalls() + [ + "AirInfiltration", + "Attics", + "Foundations", + "Garages", + "Roofs", + "RimJoists", + "Walls", + ], + E.FoundationWalls(), ) enclosure.FoundationWalls.append(deepcopy(fw)) this_fw = enclosure.FoundationWalls.FoundationWall[-1] - if hasattr(this_fw, 'AdjacentTo'): + if hasattr(this_fw, "AdjacentTo"): try: fw_boundary = foundation_location_map[str(fw.AdjacentTo)] except KeyError: fw_boundary = str(fw.AdjacentTo) # retain unchanged location name try: - boundary_v3 = {'other housing unit': 'Exterior', - 'ground': 'Exterior', - 'ambient': 'Exterior', - 'attic': 'Exterior', - 'garage': 'Exterior', - 'living space': 'Interior', - 'unconditioned basement': 'Interior', - 'crawlspace': 'Interior'}[str(fw.AdjacentTo)] - if boundary_v3 == 'Interior' and hasattr(foundation, 'FoundationType'): + boundary_v3 = { + "other housing unit": "Exterior", + "ground": "Exterior", + "ambient": "Exterior", + "attic": "Exterior", + "garage": "Exterior", + "living space": "Interior", + "unconditioned basement": "Interior", + "crawlspace": "Interior", + }[str(fw.AdjacentTo)] + if boundary_v3 == "Interior" and hasattr(foundation, "FoundationType"): # Check that this matches the Foundation/FoundationType if available - if fw.AdjacentTo == 'unconditioned basement' and\ - (foundation.xpath('count(h:FoundationType/h:Basement/h:Conditioned[text()="true"])', **xpkw) > 0 - or not hasattr(foundation.FoundationType, 'Basement')): - boundary_v3 = 'Exterior' - elif fw.AdjacentTo == 'crawlspace' and not hasattr(foundation.FoundationType, 'Crawlspace'): - boundary_v3 = 'Exterior' + if fw.AdjacentTo == "unconditioned basement" and ( + foundation.xpath( + 'count(h:FoundationType/h:Basement/h:Conditioned[text()="true"])', + **xpkw, + ) + > 0 + or not hasattr(foundation.FoundationType, "Basement") + ): + boundary_v3 = "Exterior" + elif fw.AdjacentTo == "crawlspace" and not hasattr( + foundation.FoundationType, "Crawlspace" + ): + boundary_v3 = "Exterior" add_after( this_fw, - ['SystemIdentifier', - 'ExternalResource', - 'AttachedToSpace'], - getattr(E, f'{boundary_v3}AdjacentTo')(fw_boundary) + ["SystemIdentifier", "ExternalResource", "AttachedToSpace"], + getattr(E, f"{boundary_v3}AdjacentTo")(fw_boundary), ) except KeyError: pass @@ -493,201 +569,217 @@ def get_pre_post_from_building_id(building_id): foundation.remove(fw) # Attics - for bldg_const in root.xpath('h:Building/h:BuildingDetails/h:BuildingSummary/h:BuildingConstruction', **xpkw): - if hasattr(bldg_const, 'AtticType'): - if bldg_const.AtticType == 'vented attic': + for bldg_const in root.xpath( + "h:Building/h:BuildingDetails/h:BuildingSummary/h:BuildingConstruction", **xpkw + ): + if hasattr(bldg_const, "AtticType"): + if bldg_const.AtticType == "vented attic": bldg_const.AtticType = E.AtticType(E.Attic(E.Vented(True))) - elif bldg_const.AtticType == 'unvented attic': + elif bldg_const.AtticType == "unvented attic": bldg_const.AtticType = E.AtticType(E.Attic(E.Vented(False))) - elif bldg_const.AtticType == 'flat roof': + elif bldg_const.AtticType == "flat roof": bldg_const.AtticType = E.AtticType(E.FlatRoof()) - elif bldg_const.AtticType == 'cathedral ceiling': + elif bldg_const.AtticType == "cathedral ceiling": bldg_const.AtticType = E.AtticType(E.CathedralCeiling()) - elif bldg_const.AtticType == 'cape cod': + elif bldg_const.AtticType == "cape cod": bldg_const.AtticType = E.AtticType(E.Attic(E.CapeCod(True))) - elif bldg_const.AtticType == 'other': + elif bldg_const.AtticType == "other": bldg_const.AtticType = E.AtticType(E.Other()) - elif bldg_const.AtticType == 'venting unknown attic': - bldg_const.AtticType = E.AtticType(E.Attic(E.extension(E.Vented('unknown')))) + elif bldg_const.AtticType == "venting unknown attic": + bldg_const.AtticType = E.AtticType( + E.Attic(E.extension(E.Vented("unknown"))) + ) for i, attic in enumerate( - root.xpath('h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/h:Attics/h:Attic', **xpkw) + root.xpath( + "h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/h:Attics/h:Attic", + **xpkw, + ) ): enclosure = attic.getparent().getparent().getparent() this_attic = deepcopy(attic) this_attic_type = None - if hasattr(this_attic, 'AtticType'): + if hasattr(this_attic, "AtticType"): this_attic_type = this_attic.AtticType - if this_attic.AtticType == 'vented attic': + if this_attic.AtticType == "vented attic": this_attic.AtticType = E.AtticType(E.Attic(E.Vented(True))) - elif this_attic.AtticType == 'unvented attic': + elif this_attic.AtticType == "unvented attic": this_attic.AtticType = E.AtticType(E.Attic(E.Vented(False))) - elif this_attic.AtticType == 'flat roof': + elif this_attic.AtticType == "flat roof": this_attic.AtticType = E.AtticType(E.FlatRoof()) - elif this_attic.AtticType == 'cathedral ceiling': + elif this_attic.AtticType == "cathedral ceiling": this_attic.AtticType = E.AtticType(E.CathedralCeiling()) - elif this_attic.AtticType == 'cape cod': + elif this_attic.AtticType == "cape cod": this_attic.AtticType = E.AtticType(E.Attic(E.CapeCod(True))) - elif this_attic.AtticType == 'other': + elif this_attic.AtticType == "other": this_attic.AtticType = E.AtticType(E.Other()) - elif this_attic.AtticType == 'venting unknown attic': - this_attic.AtticType = E.AtticType(E.Attic(E.extension(E.Vented('unknown')))) + elif this_attic.AtticType == "venting unknown attic": + this_attic.AtticType = E.AtticType( + E.Attic(E.extension(E.Vented("unknown"))) + ) else: raise exc.HpxmlTranslationError( f"{hpxml2_file.name} was not able to be translated " f"because 'AtticType' of {this_attic.SystemIdentifier.attrib['id']} is unknown." ) - if not hasattr(enclosure, 'Attics'): - add_after( - enclosure, - ['AirInfiltration'], - E.Attics() - ) + if not hasattr(enclosure, "Attics"): + add_after(enclosure, ["AirInfiltration"], E.Attics()) # rearrange AttachedToRoof - if hasattr(this_attic, 'AttachedToRoof'): + if hasattr(this_attic, "AttachedToRoof"): attached_to_roof = deepcopy(this_attic.AttachedToRoof) - this_attic.remove(this_attic.AttachedToRoof) # remove the AttachedToRoof of HPXML v2 + this_attic.remove( + this_attic.AttachedToRoof + ) # remove the AttachedToRoof of HPXML v2 add_after( this_attic, - ['SystemIdentifier', - 'AttachedToSpace', - 'AtticType', - 'VentilationRate'], - attached_to_roof + ["SystemIdentifier", "AttachedToSpace", "AtticType", "VentilationRate"], + attached_to_roof, ) # find the wall with the same id and add AtticWallType = knee wall - if hasattr(this_attic, 'AtticKneeWall'): - knee_wall_id = this_attic.AtticKneeWall.attrib['idref'] + if hasattr(this_attic, "AtticKneeWall"): + knee_wall_id = this_attic.AtticKneeWall.attrib["idref"] try: knee_wall = root.xpath( - 'h:Building/h:BuildingDetails/h:Enclosure/h:Walls/h:Wall[h:SystemIdentifier/@id=$sysid]', - sysid=knee_wall_id, **xpkw)[0] + "h:Building/h:BuildingDetails/h:Enclosure/h:Walls/h:Wall[h:SystemIdentifier/@id=$sysid]", + sysid=knee_wall_id, + **xpkw, + )[0] except IndexError: - warnings.warn(f"Cannot find a knee wall attached to {this_attic.SystemIdentifier.attrib['id']}.") + warnings.warn( + f"Cannot find a knee wall attached to {this_attic.SystemIdentifier.attrib['id']}." + ) else: - if not hasattr(knee_wall, 'AtticWallType'): + if not hasattr(knee_wall, "AtticWallType"): add_after( knee_wall, - ['SystemIdentifier', - 'ExteriorAdjacentTo', - 'InteriorAdjacentTo'], - E.AtticWallType('knee wall') + [ + "SystemIdentifier", + "ExteriorAdjacentTo", + "InteriorAdjacentTo", + ], + E.AtticWallType("knee wall"), ) add_before( this_attic, - ['AttachedToFrameFloor', - 'AnnualEnergyUse', - 'extension'], - E.AttachedToWall(idref=knee_wall_id) + ["AttachedToFrameFloor", "AnnualEnergyUse", "extension"], + E.AttachedToWall(idref=knee_wall_id), ) # create a FrameFloor adjacent to the attic and assign the area below to Area # and then copy AtticFloorInsulation over to Insulation of the frame floor - if hasattr(this_attic, 'AtticFloorInsulation') or ( - this_attic_type not in ['cathedral ceiling', 'flat roof', 'cape cod'] + if hasattr(this_attic, "AtticFloorInsulation") or ( + this_attic_type not in ["cathedral ceiling", "flat roof", "cape cod"] ): - if not hasattr(enclosure, 'FrameFloors'): + if not hasattr(enclosure, "FrameFloors"): add_before( enclosure, - ['Slabs', - 'Windows', - 'Skylights', - 'Doors', - 'extension'], - E.FrameFloors() + ["Slabs", "Windows", "Skylights", "Doors", "extension"], + E.FrameFloors(), ) - attic_floor_el = E.FrameFloor( - E.SystemIdentifier(id=f'attic-floor-{i}') - ) - attic_floor_id = attic_floor_el.SystemIdentifier.attrib['id'] + attic_floor_el = E.FrameFloor(E.SystemIdentifier(id=f"attic-floor-{i}")) + attic_floor_id = attic_floor_el.SystemIdentifier.attrib["id"] add_before( this_attic, - ['AnnualEnergyUse', - 'extension'], - E.AttachedToFrameFloor(idref=attic_floor_id) + ["AnnualEnergyUse", "extension"], + E.AttachedToFrameFloor(idref=attic_floor_id), ) - if hasattr(this_attic, 'Area'): + if hasattr(this_attic, "Area"): attic_floor_el.append(E.Area(float(this_attic.Area))) - if hasattr(this_attic, 'AtticFloorInsulation'): + if hasattr(this_attic, "AtticFloorInsulation"): attic_floor_insulation = deepcopy(this_attic.AtticFloorInsulation) - attic_floor_insulation.tag = f'{{{hpxml3_ns}}}Insulation' + attic_floor_insulation.tag = f"{{{hpxml3_ns}}}Insulation" attic_floor_el.append(attic_floor_insulation) enclosure.FrameFloors.append(attic_floor_el) # find Roof attached to Attic and move Insulation to Roof # add insulation to v2 Roofs and these roofs will be converted into hpxml v3 later - if hasattr(this_attic, 'AtticRoofInsulation'): + if hasattr(this_attic, "AtticRoofInsulation"): roof_insulation = deepcopy(this_attic.AtticRoofInsulation) - roof_insulation.tag = f'{{{hpxml3_ns}}}Insulation' - roof_idref = this_attic.AttachedToRoof.attrib['idref'] + roof_insulation.tag = f"{{{hpxml3_ns}}}Insulation" + roof_idref = this_attic.AttachedToRoof.attrib["idref"] try: roof_attached_to_this_attic = root.xpath( - 'h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/\ - h:Roofs/h:Roof[h:SystemIdentifier/@id=$sysid]', - sysid=roof_idref, **xpkw)[0] + "h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/\ + h:Roofs/h:Roof[h:SystemIdentifier/@id=$sysid]", + sysid=roof_idref, + **xpkw, + )[0] except IndexError: - warnings.warn(f"Cannot find a roof attached to {this_attic.SystemIdentifier.attrib['id']}.") - else: - add_before( - roof_attached_to_this_attic, - ['extension'], - roof_insulation + warnings.warn( + f"Cannot find a roof attached to {this_attic.SystemIdentifier.attrib['id']}." ) + else: + add_before(roof_attached_to_this_attic, ["extension"], roof_insulation) # translate v2 Attic/Area to the v3 Roof/Area for "cathedral ceiling" and "flat roof" - if hasattr(this_attic, 'Area') and this_attic_type in ['cathedral ceiling', 'flat roof']: + if hasattr(this_attic, "Area") and this_attic_type in [ + "cathedral ceiling", + "flat roof", + ]: try: - roof_idref = this_attic.AttachedToRoof.attrib['idref'] + roof_idref = this_attic.AttachedToRoof.attrib["idref"] roof_attached_to_this_attic = root.xpath( - 'h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/\ - h:Roofs/h:Roof[h:SystemIdentifier/@id=$sysid]', - sysid=roof_idref, **xpkw)[0] + "h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/\ + h:Roofs/h:Roof[h:SystemIdentifier/@id=$sysid]", + sysid=roof_idref, + **xpkw, + )[0] except IndexError: - warnings.warn(f"Cannot find a roof attached to {this_attic.SystemIdentifier.attrib['id']}.") + warnings.warn( + f"Cannot find a roof attached to {this_attic.SystemIdentifier.attrib['id']}." + ) else: - if not hasattr(roof_attached_to_this_attic, 'RoofArea'): + if not hasattr(roof_attached_to_this_attic, "RoofArea"): add_before( roof_attached_to_this_attic, - ['RadiantBarrier', - 'RadiantBarrierLocation', - 'extension'], - E.RoofArea(this_attic.Area.text) + ["RadiantBarrier", "RadiantBarrierLocation", "extension"], + E.RoofArea(this_attic.Area.text), ) # move Rafters to v2 Roofs and these roofs will be converted into hpxml v3 later - if hasattr(this_attic, 'Rafters'): + if hasattr(this_attic, "Rafters"): rafters = deepcopy(this_attic.Rafters) - roof_idref = this_attic.AttachedToRoof.attrib['idref'] + roof_idref = this_attic.AttachedToRoof.attrib["idref"] try: roof_attached_to_this_attic = root.xpath( - 'h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/\ - h:Roofs/h:Roof[h:SystemIdentifier/@id=$sysid]', - sysid=roof_idref, **xpkw)[0] + "h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/\ + h:Roofs/h:Roof[h:SystemIdentifier/@id=$sysid]", + sysid=roof_idref, + **xpkw, + )[0] except IndexError: - warnings.warn(f"Cannot find a roof attached to {this_attic.SystemIdentifier.attrib['id']}.") + warnings.warn( + f"Cannot find a roof attached to {this_attic.SystemIdentifier.attrib['id']}." + ) else: add_after( roof_attached_to_this_attic, - ['SystemIdentifier', - 'ExternalResource', - 'AttachedToSpace', - 'RoofColor', - 'SolarAbsorptance', - 'Emittance'], - rafters + [ + "SystemIdentifier", + "ExternalResource", + "AttachedToSpace", + "RoofColor", + "SolarAbsorptance", + "Emittance", + ], + rafters, ) - if hasattr(this_attic, 'InteriorAdjacentTo') and hasattr(this_attic, 'AtticType'): - if this_attic_type in ['cathedral ceiling', 'flat roof', 'cape cod']: + if hasattr(this_attic, "InteriorAdjacentTo") and hasattr( + this_attic, "AtticType" + ): + if this_attic_type in ["cathedral ceiling", "flat roof", "cape cod"]: try: - roof_idref = this_attic.AttachedToRoof.attrib['idref'] + roof_idref = this_attic.AttachedToRoof.attrib["idref"] roof_attached_to_this_attic = root.xpath( - 'h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/h:Roofs/\ - h:Roof[h:SystemIdentifier/@id=$sysid]', - sysid=roof_idref, **xpkw)[0] + "h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/h:Roofs/\ + h:Roof[h:SystemIdentifier/@id=$sysid]", + sysid=roof_idref, + **xpkw, + )[0] except (AttributeError, IndexError): warnings.warn( f"Cannot find a roof attached to {this_attic.SystemIdentifier.attrib['id']}." @@ -695,18 +787,18 @@ def get_pre_post_from_building_id(building_id): else: add_after( roof_attached_to_this_attic, - ['SystemIdentifier', - 'ExternalResource', - 'AttachedToSpace'], - E.InteriorAdjacentTo(this_attic.InteriorAdjacentTo.text) + ["SystemIdentifier", "ExternalResource", "AttachedToSpace"], + E.InteriorAdjacentTo(this_attic.InteriorAdjacentTo.text), ) else: try: - floor_idref = this_attic.AttachedToFrameFloor.attrib['idref'] + floor_idref = this_attic.AttachedToFrameFloor.attrib["idref"] floor_attached_to_this_attic = root.xpath( - 'h:Building/h:BuildingDetails/h:Enclosure/h:FrameFloors/\ - h:FrameFloor[h:SystemIdentifier/@id=$sysid]', - sysid=floor_idref, **xpkw)[0] + "h:Building/h:BuildingDetails/h:Enclosure/h:FrameFloors/\ + h:FrameFloor[h:SystemIdentifier/@id=$sysid]", + sysid=floor_idref, + **xpkw, + )[0] except (AttributeError, IndexError): warnings.warn( f"Cannot find a frame floor attached to {this_attic.SystemIdentifier.attrib['id']}." @@ -714,20 +806,24 @@ def get_pre_post_from_building_id(building_id): else: add_after( floor_attached_to_this_attic, - ['SystemIdentifier', - 'ExternalResource', - 'AttachedToSpace', - 'ExteriorAdjacentTo'], - E.InteriorAdjacentTo(this_attic.InteriorAdjacentTo.text) + [ + "SystemIdentifier", + "ExternalResource", + "AttachedToSpace", + "ExteriorAdjacentTo", + ], + E.InteriorAdjacentTo(this_attic.InteriorAdjacentTo.text), ) - el_not_in_v3 = ['ExteriorAdjacentTo', - 'InteriorAdjacentTo', - 'AtticKneeWall', - 'AtticFloorInsulation', - 'AtticRoofInsulation', - 'Area', - 'Rafters'] + el_not_in_v3 = [ + "ExteriorAdjacentTo", + "InteriorAdjacentTo", + "AtticKneeWall", + "AtticFloorInsulation", + "AtticRoofInsulation", + "Area", + "Rafters", + ] for el in el_not_in_v3: if hasattr(this_attic, el): this_attic.remove(this_attic[el]) @@ -735,307 +831,325 @@ def get_pre_post_from_building_id(building_id): enclosure.Attics.append(this_attic) # Roofs - for roof in root.xpath('h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/h:Roofs/h:Roof', **xpkw): + for roof in root.xpath( + "h:Building/h:BuildingDetails/h:Enclosure/h:AtticAndRoof/h:Roofs/h:Roof", **xpkw + ): enclosure = roof.getparent().getparent().getparent() - if not hasattr(enclosure, 'Roofs'): + if not hasattr(enclosure, "Roofs"): add_after( enclosure, - ['AirInfiltration', - 'Attics', - 'Foundations', - 'Garages'], - E.Roofs() + ["AirInfiltration", "Attics", "Foundations", "Garages"], + E.Roofs(), ) enclosure.Roofs.append(deepcopy(roof)) this_roof = enclosure.Roofs.Roof[-1] - if hasattr(roof, 'RoofArea'): + if hasattr(roof, "RoofArea"): add_after( this_roof, - ['SystemIdentifier', - 'ExternalResource', - 'AttachedToSpace', - 'InteriorAdjacentTo'], - E.Area(float(roof.RoofArea)) + [ + "SystemIdentifier", + "ExternalResource", + "AttachedToSpace", + "InteriorAdjacentTo", + ], + E.Area(float(roof.RoofArea)), ) this_roof.remove(this_roof.RoofArea) - if hasattr(roof, 'RoofType'): + if hasattr(roof, "RoofType"): roof_type = str(roof.RoofType) this_roof.remove(this_roof.RoofType) # remove the RoofType of HPXML v2 add_after( this_roof, - ['SystemIdentifier', - 'ExternalResource', - 'AttachedToSpace', - 'InteriorAdjacentTo', - 'Area', - 'Orientation', - 'Azimuth'], - E.RoofType(roof_type) + [ + "SystemIdentifier", + "ExternalResource", + "AttachedToSpace", + "InteriorAdjacentTo", + "Area", + "Orientation", + "Azimuth", + ], + E.RoofType(roof_type), ) # remove AtticAndRoof after rearranging all attics and roofs - for enclosure in root.xpath('h:Building/h:BuildingDetails/h:Enclosure', **xpkw): + for enclosure in root.xpath("h:Building/h:BuildingDetails/h:Enclosure", **xpkw): try: enclosure.remove(enclosure.AtticAndRoof) except AttributeError: pass # Frame Floors - for ff in root.xpath('h:Building/h:BuildingDetails/h:Enclosure/h:Foundations/h:Foundation/h:FrameFloor', **xpkw): + for ff in root.xpath( + "h:Building/h:BuildingDetails/h:Enclosure/h:Foundations/h:Foundation/h:FrameFloor", + **xpkw, + ): enclosure = ff.getparent().getparent().getparent() foundation = ff.getparent() add_before( foundation, - ['AttachedToSlab', - 'AnnualEnergyUse', - 'extension'], - E.AttachedToFrameFloor(idref=ff.SystemIdentifier.attrib['id']) + ["AttachedToSlab", "AnnualEnergyUse", "extension"], + E.AttachedToFrameFloor(idref=ff.SystemIdentifier.attrib["id"]), ) - if not hasattr(enclosure, 'FrameFloors'): + if not hasattr(enclosure, "FrameFloors"): add_before( enclosure, - ['Slabs', - 'Windows', - 'Skylights', - 'Doors', - 'extension'], - E.FrameFloors() + ["Slabs", "Windows", "Skylights", "Doors", "extension"], + E.FrameFloors(), ) this_ff = deepcopy(ff) enclosure.FrameFloors.append(this_ff) foundation.remove(ff) # Slabs - for slab in root.xpath('h:Building/h:BuildingDetails/h:Enclosure/h:Foundations/h:Foundation/h:Slab', **xpkw): + for slab in root.xpath( + "h:Building/h:BuildingDetails/h:Enclosure/h:Foundations/h:Foundation/h:Slab", + **xpkw, + ): enclosure = slab.getparent().getparent().getparent() foundation = slab.getparent() add_before( foundation, - ['AnnualEnergyUse', - 'extension'], - E.AttachedToSlab(idref=slab.SystemIdentifier.attrib['id']) + ["AnnualEnergyUse", "extension"], + E.AttachedToSlab(idref=slab.SystemIdentifier.attrib["id"]), ) - if not hasattr(enclosure, 'Slabs'): + if not hasattr(enclosure, "Slabs"): add_before( - enclosure, - ['Windows', - 'Skylights', - 'Doors', - 'extension'], - E.Slabs() + enclosure, ["Windows", "Skylights", "Doors", "extension"], E.Slabs() ) enclosure.Slabs.append(deepcopy(slab)) foundation.remove(slab) # Remove 'Insulation/InsulationLocation' - for insulation_location in root.xpath('//h:Insulation/h:InsulationLocation', **xpkw): + for insulation_location in root.xpath( + "//h:Insulation/h:InsulationLocation", **xpkw + ): # Insulation location to be layer-specific insulation = insulation_location.getparent() - if hasattr(insulation, 'Layer'): + if hasattr(insulation, "Layer"): for layer in insulation.Layer: - if layer.InstallationType == 'continuous': - layer.InstallationType._setText(f'continuous - {str(insulation.InsulationLocation)}') + if layer.InstallationType == "continuous": + layer.InstallationType._setText( + f"continuous - {str(insulation.InsulationLocation)}" + ) insulation.remove(insulation.InsulationLocation) # Windows and Skylights - for i, win in enumerate(root.xpath('//h:Window|//h:Skylight', **xpkw)): - if hasattr(win, 'VisibleTransmittance'): + for i, win in enumerate(root.xpath("//h:Window|//h:Skylight", **xpkw)): + if hasattr(win, "VisibleTransmittance"): vis_trans = float(win.VisibleTransmittance) - win.remove(win.VisibleTransmittance) # remove VisibleTransmittance of HPXML v2 + win.remove( + win.VisibleTransmittance + ) # remove VisibleTransmittance of HPXML v2 add_after( win, - ['SystemIdentifier', - 'ExternalResource', - 'Area', - 'Quantity', - 'Azimuth', - 'Orientation', - 'FrameType', - 'GlassLayers', - 'GlassType', - 'GasFill', - 'Condition', - 'UFactor', - 'SHGC'], - E.VisibleTransmittance(vis_trans) + [ + "SystemIdentifier", + "ExternalResource", + "Area", + "Quantity", + "Azimuth", + "Orientation", + "FrameType", + "GlassLayers", + "GlassType", + "GasFill", + "Condition", + "UFactor", + "SHGC", + ], + E.VisibleTransmittance(vis_trans), ) - if hasattr(win, 'ExteriorShading'): + if hasattr(win, "ExteriorShading"): ext_shade = str(win.ExteriorShading) win.remove(win.ExteriorShading) # remove ExteriorShading of HPXML v2 add_after( win, - ['SystemIdentifier', - 'ExternalResource', - 'Area', - 'Quantity', - 'Azimuth', - 'Orientation', - 'FrameType', - 'GlassLayers', - 'GlassType', - 'GasFill', - 'Condition', - 'UFactor', - 'SHGC', - 'VisibleTransmittance', - 'NFRCCertified', - 'ThirdPartyCertification', - 'WindowFilm'], + [ + "SystemIdentifier", + "ExternalResource", + "Area", + "Quantity", + "Azimuth", + "Orientation", + "FrameType", + "GlassLayers", + "GlassType", + "GasFill", + "Condition", + "UFactor", + "SHGC", + "VisibleTransmittance", + "NFRCCertified", + "ThirdPartyCertification", + "WindowFilm", + ], E.ExteriorShading( - E.SystemIdentifier(id=f'exterior-shading-{i}'), - E.Type(ext_shade) - ) + E.SystemIdentifier(id=f"exterior-shading-{i}"), E.Type(ext_shade) + ), ) - if hasattr(win, 'Treatments'): - if win.Treatments in ['shading', 'solar screen']: + if hasattr(win, "Treatments"): + if win.Treatments in ["shading", "solar screen"]: treatment_shade = E.ExteriorShading( - E.SystemIdentifier(id=f'treatment-shading-{i}'), + E.SystemIdentifier(id=f"treatment-shading-{i}"), ) - if win.Treatments == 'solar screen': - treatment_shade.append(E.Type('solar screens')) + if win.Treatments == "solar screen": + treatment_shade.append(E.Type("solar screens")) add_after( win, - ['SystemIdentifier', - 'ExternalResource', - 'Area', - 'Quantity', - 'Azimuth', - 'Orientation', - 'FrameType', - 'GlassLayers', - 'GlassType', - 'GasFill', - 'Condition', - 'UFactor', - 'SHGC', - 'VisibleTransmittance', - 'NFRCCertified', - 'ThirdPartyCertification', - 'WindowFilm'], - treatment_shade + [ + "SystemIdentifier", + "ExternalResource", + "Area", + "Quantity", + "Azimuth", + "Orientation", + "FrameType", + "GlassLayers", + "GlassType", + "GasFill", + "Condition", + "UFactor", + "SHGC", + "VisibleTransmittance", + "NFRCCertified", + "ThirdPartyCertification", + "WindowFilm", + ], + treatment_shade, ) - elif win.Treatments == 'window film': + elif win.Treatments == "window film": add_after( win, - ['SystemIdentifier', - 'ExternalResource', - 'Area', - 'Quantity', - 'Azimuth', - 'Orientation', - 'FrameType', - 'GlassLayers', - 'GlassType', - 'GasFill', - 'Condition', - 'UFactor', - 'SHGC', - 'VisibleTransmittance', - 'NFRCCertified', - 'ThirdPartyCertification'], - E.WindowFilm( - E.SystemIdentifier(id=f'window-film-{i}') - ) + [ + "SystemIdentifier", + "ExternalResource", + "Area", + "Quantity", + "Azimuth", + "Orientation", + "FrameType", + "GlassLayers", + "GlassType", + "GasFill", + "Condition", + "UFactor", + "SHGC", + "VisibleTransmittance", + "NFRCCertified", + "ThirdPartyCertification", + ], + E.WindowFilm(E.SystemIdentifier(id=f"window-film-{i}")), ) win.remove(win.Treatments) - if hasattr(win, 'InteriorShading'): + if hasattr(win, "InteriorShading"): cache_interior_shading_type = str(win.InteriorShading) win.InteriorShading.clear() - win.InteriorShading.append(E.SystemIdentifier(id=f'interior-shading-{i}')) + win.InteriorShading.append(E.SystemIdentifier(id=f"interior-shading-{i}")) win.InteriorShading.append(E.Type(cache_interior_shading_type)) - if hasattr(win, 'InteriorShadingFactor'): + if hasattr(win, "InteriorShadingFactor"): # handles a case where `InteriorShadingFactor` is specified without `InteriorShading` - if not hasattr(win, 'InteriorShading'): + if not hasattr(win, "InteriorShading"): add_before( win, - ['StormWindow', - 'MoveableInsulation', - 'Overhangs', - 'WeatherStripping', - 'Operable', - 'LeakinessDescription', - 'WindowtoWallRatio', - 'AttachedToWall', - 'AnnualEnergyUse', - 'extension'], - E.InteriorShading( - E.SystemIdentifier(id=f'interior-shading-{i}') - ) + [ + "StormWindow", + "MoveableInsulation", + "Overhangs", + "WeatherStripping", + "Operable", + "LeakinessDescription", + "WindowtoWallRatio", + "AttachedToWall", + "AnnualEnergyUse", + "extension", + ], + E.InteriorShading(E.SystemIdentifier(id=f"interior-shading-{i}")), ) - win.InteriorShading.extend([ - E.SummerShadingCoefficient(float(win.InteriorShadingFactor)), - E.WinterShadingCoefficient(float(win.InteriorShadingFactor)) - ]) + win.InteriorShading.extend( + [ + E.SummerShadingCoefficient(float(win.InteriorShadingFactor)), + E.WinterShadingCoefficient(float(win.InteriorShadingFactor)), + ] + ) win.remove(win.InteriorShadingFactor) - if hasattr(win, 'MovableInsulationRValue'): + if hasattr(win, "MovableInsulationRValue"): add_after( win, - ['SystemIdentifier', - 'ExternalResource', - 'Area', - 'Quantity', - 'Azimuth', - 'Orientation', - 'FrameType', - 'GlassLayers', - 'GlassType', - 'GasFill', - 'Condition', - 'UFactor', - 'SHGC', - 'VisibleTransmittance', - 'NFRCCertified', - 'ThirdPartyCertification', - 'WindowFilm', - 'ExteriorShading', - 'InteriorShading', - 'StormWindow'], + [ + "SystemIdentifier", + "ExternalResource", + "Area", + "Quantity", + "Azimuth", + "Orientation", + "FrameType", + "GlassLayers", + "GlassType", + "GasFill", + "Condition", + "UFactor", + "SHGC", + "VisibleTransmittance", + "NFRCCertified", + "ThirdPartyCertification", + "WindowFilm", + "ExteriorShading", + "InteriorShading", + "StormWindow", + ], E.MoveableInsulation( - E.SystemIdentifier(id=f'moveable-insulation-{i}'), - E.RValue(float(win.MovableInsulationRValue)) - ) + E.SystemIdentifier(id=f"moveable-insulation-{i}"), + E.RValue(float(win.MovableInsulationRValue)), + ), ) win.remove(win.MovableInsulationRValue) - if hasattr(win, 'GlassLayers'): - if win.GlassLayers in ['single-paned with low-e storms', 'single-paned with storms']: - storm_window = E.StormWindow( - E.SystemIdentifier(id=f'storm-window-{i}') - ) - if win.GlassLayers == 'single-paned with low-e storms': - storm_window.append(E.GlassType('low-e')) - win.GlassLayers._setText('single-pane') + if hasattr(win, "GlassLayers"): + if win.GlassLayers in [ + "single-paned with low-e storms", + "single-paned with storms", + ]: + storm_window = E.StormWindow(E.SystemIdentifier(id=f"storm-window-{i}")) + if win.GlassLayers == "single-paned with low-e storms": + storm_window.append(E.GlassType("low-e")) + win.GlassLayers._setText("single-pane") add_after( win, - ['SystemIdentifier', - 'ExternalResource', - 'Area', - 'Quantity', - 'Azimuth', - 'Orientation', - 'FrameType', - 'GlassLayers', - 'GlassType', - 'GasFill', - 'Condition', - 'UFactor', - 'SHGC', - 'VisibleTransmittance', - 'NFRCCertified', - 'ThirdPartyCertification', - 'WindowFilm', - 'ExteriorShading', - 'InteriorShading'], - storm_window + [ + "SystemIdentifier", + "ExternalResource", + "Area", + "Quantity", + "Azimuth", + "Orientation", + "FrameType", + "GlassLayers", + "GlassType", + "GasFill", + "Condition", + "UFactor", + "SHGC", + "VisibleTransmittance", + "NFRCCertified", + "ThirdPartyCertification", + "WindowFilm", + "ExteriorShading", + "InteriorShading", + ], + storm_window, ) # Standardize Locations # https://github.com/hpxmlwg/hpxml/pull/156 - for el in root.xpath('//h:InteriorAdjacentTo|//h:ExteriorAdjacentTo|//h:DuctLocation|//h:HVACPlant/h:*/h:UnitLocation|//h:WaterHeatingSystem/h:Location|//h:Measure/h:Location', **xpkw): # noqa E501 + for el in root.xpath( + "//h:InteriorAdjacentTo|//h:ExteriorAdjacentTo|//h:DuctLocation|//h:HVACPlant/h:*/h:UnitLocation|//h:WaterHeatingSystem/h:Location|//h:Measure/h:Location", # noqa E501 + **xpkw, + ): try: el._setText(location_map[el.text]) except (KeyError, AttributeError): @@ -1045,63 +1159,61 @@ def get_pre_post_from_building_id(building_id): # https://github.com/hpxmlwg/hpxml/pull/165 ltgidx = 0 - for ltgfracs in root.xpath('h:Building/h:BuildingDetails/h:Lighting/h:LightingFractions', **xpkw): + for ltgfracs in root.xpath( + "h:Building/h:BuildingDetails/h:Lighting/h:LightingFractions", **xpkw + ): ltg = ltgfracs.getparent() for ltgfrac in ltgfracs.getchildren(): ltgidx += 1 ltggroup = E.LightingGroup( - E.SystemIdentifier(id=f'lighting-fraction-{ltgidx}'), + E.SystemIdentifier(id=f"lighting-fraction-{ltgidx}"), E.FractionofUnitsInLocation(ltgfrac.text), - E.LightingType() + E.LightingType(), ) - if ltgfrac.tag == f'{{{hpxml3_ns}}}FractionIncandescent': + if ltgfrac.tag == f"{{{hpxml3_ns}}}FractionIncandescent": ltggroup.LightingType.append(E.Incandescent()) - elif ltgfrac.tag == f'{{{hpxml3_ns}}}FractionCFL': + elif ltgfrac.tag == f"{{{hpxml3_ns}}}FractionCFL": ltggroup.LightingType.append(E.CompactFluorescent()) - elif ltgfrac.tag == f'{{{hpxml3_ns}}}FractionLFL': + elif ltgfrac.tag == f"{{{hpxml3_ns}}}FractionLFL": ltggroup.LightingType.append(E.FluorescentTube()) - elif ltgfrac.tag == f'{{{hpxml3_ns}}}FractionLED': + elif ltgfrac.tag == f"{{{hpxml3_ns}}}FractionLED": ltggroup.LightingType.append(E.LightEmittingDiode()) - add_after( - ltg, - ['LightingGroup'], - ltggroup - ) + add_after(ltg, ["LightingGroup"], ltggroup) ltg.remove(ltgfracs) # Deprecated items # https://github.com/hpxmlwg/hpxml/pull/167 # Removes WaterHeaterInsulation/Pipe; use HotWaterDistribution/PipeInsulation instead - for i, pipe in enumerate(root.xpath('//h:WaterHeaterInsulation/h:Pipe', **xpkw), 1): + for i, pipe in enumerate(root.xpath("//h:WaterHeaterInsulation/h:Pipe", **xpkw), 1): waterheating = pipe.getparent().getparent().getparent() waterheatingsystem = pipe.getparent().getparent() - waterheatingsystem_idref = str(waterheatingsystem.SystemIdentifier.attrib['id']) + waterheatingsystem_idref = str(waterheatingsystem.SystemIdentifier.attrib["id"]) try: - hw_dist = waterheating.xpath('h:HotWaterDistribution[h:AttachedToWaterHeatingSystem/@idref=$sysid]', - sysid=waterheatingsystem_idref, **xpkw)[0] + hw_dist = waterheating.xpath( + "h:HotWaterDistribution[h:AttachedToWaterHeatingSystem/@idref=$sysid]", + sysid=waterheatingsystem_idref, + **xpkw, + )[0] add_after( hw_dist, - ['SystemIdentifier', - 'ExternalResource', - 'AttachedToWaterHeatingSystem', - 'SystemType'], - E.PipeInsulation( - E.PipeRValue(float(pipe.PipeRValue)) - ) + [ + "SystemIdentifier", + "ExternalResource", + "AttachedToWaterHeatingSystem", + "SystemType", + ], + E.PipeInsulation(E.PipeRValue(float(pipe.PipeRValue))), ) except IndexError: # handles when there is no attached hot water distribution system add_after( waterheating, - ['WaterHeatingSystem', - 'WaterHeatingControl'], + ["WaterHeatingSystem", "WaterHeatingControl"], E.HotWaterDistribution( - E.SystemIdentifier(id=f'hotwater-distribution-{i}'), + E.SystemIdentifier(id=f"hotwater-distribution-{i}"), E.AttachedToWaterHeatingSystem(idref=waterheatingsystem_idref), - E.PipeInsulation( - E.PipeRValue(float(pipe.PipeRValue)) - ) - ) + E.PipeInsulation(E.PipeRValue(float(pipe.PipeRValue))), + ), ) waterheaterinsualtion = pipe.getparent() waterheaterinsualtion.remove(pipe) @@ -1109,41 +1221,37 @@ def get_pre_post_from_building_id(building_id): waterheaterinsualtion.getparent().remove(waterheaterinsualtion) # Removes PoolPump/HoursPerDay; use PoolPump/PumpSpeed/HoursPerDay instead - for poolpump_hour in root.xpath('//h:PoolPump/h:HoursPerDay', **xpkw): + for poolpump_hour in root.xpath("//h:PoolPump/h:HoursPerDay", **xpkw): poolpump = poolpump_hour.getparent() - if not hasattr(poolpump, 'PumpSpeed'): + if not hasattr(poolpump, "PumpSpeed"): add_before( poolpump, - ['extension'], - E.PumpSpeed( - E.HoursPerDay(float(poolpump_hour)) - ) + ["extension"], + E.PumpSpeed(E.HoursPerDay(float(poolpump_hour))), ) else: add_before( - poolpump.PumpSpeed, - ['extension'], - E.HoursPerDay(float(poolpump_hour)) + poolpump.PumpSpeed, ["extension"], E.HoursPerDay(float(poolpump_hour)) ) poolpump.remove(poolpump_hour) # Removes "indoor water " (note extra trailing space) enumeration from WaterType - for watertype in root.xpath('//h:WaterType', **xpkw): - if watertype == 'indoor water ': + for watertype in root.xpath("//h:WaterType", **xpkw): + if watertype == "indoor water ": watertype._setText(str(watertype).rstrip()) # Adds desuperheater flexibility # https://github.com/hpxmlwg/hpxml/pull/184 - for el in root.xpath('//h:WaterHeatingSystem/h:RelatedHeatingSystem', **xpkw): - el.tag = f'{{{hpxml3_ns}}}RelatedHVACSystem' - for el in root.xpath('//h:WaterHeatingSystem/h:HasGeothermalDesuperheater', **xpkw): - el.tag = f'{{{hpxml3_ns}}}UsesDesuperheater' + for el in root.xpath("//h:WaterHeatingSystem/h:RelatedHeatingSystem", **xpkw): + el.tag = f"{{{hpxml3_ns}}}RelatedHVACSystem" + for el in root.xpath("//h:WaterHeatingSystem/h:HasGeothermalDesuperheater", **xpkw): + el.tag = f"{{{hpxml3_ns}}}UsesDesuperheater" # Handle PV inverter efficiency value # https://github.com/hpxmlwg/hpxml/pull/207 - for inverter_efficiency in root.xpath('//h:InverterEfficiency', **xpkw): + for inverter_efficiency in root.xpath("//h:InverterEfficiency", **xpkw): if float(inverter_efficiency) > 1: inverter_efficiency._setText(str(float(inverter_efficiency) / 100.0)) @@ -1157,5 +1265,5 @@ def get_pre_post_from_building_id(building_id): # https://github.com/hpxmlwg/hpxml/pull/202 # Write out new file - hpxml3_doc.write(pathobj_to_str(hpxml3_file), pretty_print=True, encoding='utf-8') + hpxml3_doc.write(pathobj_to_str(hpxml3_file), pretty_print=True, encoding="utf-8") hpxml3_schema.assertValid(hpxml3_doc) diff --git a/hpxml_version_translator/exceptions.py b/hpxml_version_translator/exceptions.py index 5d4bdc8..501dc64 100644 --- a/hpxml_version_translator/exceptions.py +++ b/hpxml_version_translator/exceptions.py @@ -1,4 +1,2 @@ - - class HpxmlTranslationError(Exception): pass diff --git a/setup.py b/setup.py index b1e7eb2..ccab689 100644 --- a/setup.py +++ b/setup.py @@ -1,39 +1,36 @@ import setuptools -with open('README.md', 'r', encoding='utf-8') as f: +with open("README.md", "r", encoding="utf-8") as f: long_description = f.read() setuptools.setup( - name='hpxml_version_translator', - version='0.1', - author='Ben Park (NREL), Noel Merket (NREL), Scott Horowitz (NREL)', - author_email='ben.park@nrel.gov', - description='Convert HPXML to v3', + name="hpxml_version_translator", + version="0.1", + author="Ben Park (NREL), Noel Merket (NREL), Scott Horowitz (NREL)", + author_email="ben.park@nrel.gov", + description="Convert HPXML to v3", long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/NREL/hpxml_version_translator', - packages=setuptools.find_packages(include=['hpxml_version_translator']), - package_data={ - 'hpxml_version_translator': ['schemas/*/*.xsd', '*.xsl'] - }, + long_description_content_type="text/markdown", + url="https://github.com/NREL/hpxml_version_translator", + packages=setuptools.find_packages(include=["hpxml_version_translator"]), + package_data={"hpxml_version_translator": ["schemas/*/*.xsd", "*.xsl"]}, install_requires=[ - 'lxml', + "lxml", ], extras_require={ - 'dev': [ - 'pytest>=6.2', - 'pytest-mock', - 'pytest-xdist', - 'pytest-cov', - 'flake8', - 'rope', + "dev": [ + "pytest>=6.2", + "pytest-mock", + "pytest-xdist", + "pytest-cov", + "flake8", + "rope", + "black", ] }, - python_requires='>=3.6', + python_requires=">=3.6", entry_points={ - 'console_scripts': [ - 'hpxml_version_translator=hpxml_version_translator:main' - ] - } + "console_scripts": ["hpxml_version_translator=hpxml_version_translator:main"] + }, ) diff --git a/test/test_converter_cli.py b/test/test_converter_cli.py index 01a59ad..d7d1011 100644 --- a/test/test_converter_cli.py +++ b/test/test_converter_cli.py @@ -6,19 +6,19 @@ from hpxml_version_translator import main -hpxml_dir = pathlib.Path(__file__).resolve().parent / 'hpxml_files' +hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_files" def test_cli(capsysbinary): with tempfile.TemporaryDirectory() as tmpdir: tmppath = pathlib.Path(tmpdir).resolve() - input_filename = str(hpxml_dir / 'version_change.xml') - output_filename = str(tmppath / 'out.xml') - main([input_filename, '-o', output_filename]) + input_filename = str(hpxml_dir / "version_change.xml") + output_filename = str(tmppath / "out.xml") + main([input_filename, "-o", output_filename]) root = objectify.parse(output_filename).getroot() - assert root.attrib['schemaVersion'] == '3.0' + assert root.attrib["schemaVersion"] == "3.0" main([input_filename]) f = io.BytesIO(capsysbinary.readouterr().out) root = objectify.parse(f).getroot() - assert root.attrib['schemaVersion'] == '3.0' + assert root.attrib["schemaVersion"] == "3.0" diff --git a/test/test_converter_v1to3.py b/test/test_converter_v1to3.py index a3b3ecb..9ef4e79 100644 --- a/test/test_converter_v1to3.py +++ b/test/test_converter_v1to3.py @@ -5,11 +5,11 @@ from hpxml_version_translator.converter import convert_hpxml_to_3 -hpxml_dir = pathlib.Path(__file__).resolve().parent / 'hpxml_v1_files' +hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_v1_files" def convert_hpxml_and_parse(input_filename): - with tempfile.NamedTemporaryFile('w+b') as f_out: + with tempfile.NamedTemporaryFile("w+b") as f_out: convert_hpxml_to_3(input_filename, f_out) f_out.seek(0) root = objectify.parse(f_out).getroot() @@ -17,27 +17,27 @@ def convert_hpxml_and_parse(input_filename): def test_version_change(): - root = convert_hpxml_and_parse(hpxml_dir / 'version_change.xml') - assert root.attrib['schemaVersion'] == '3.0' + root = convert_hpxml_and_parse(hpxml_dir / "version_change.xml") + assert root.attrib["schemaVersion"] == "3.0" def test_water_heater_caz(): - root = convert_hpxml_and_parse(hpxml_dir / 'water_heater_caz.xml') + root = convert_hpxml_and_parse(hpxml_dir / "water_heater_caz.xml") whs1 = root.Building.BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[0] - assert whs1.AttachedToCAZ.attrib['idref'] == 'water-heater-caz' + assert whs1.AttachedToCAZ.attrib["idref"] == "water-heater-caz" whs2 = root.Building.BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[1] - assert whs2.AttachedToCAZ.attrib['idref'] == 'water-heater-caz-2' + assert whs2.AttachedToCAZ.attrib["idref"] == "water-heater-caz-2" def test_solar_thermal(): - root = convert_hpxml_and_parse(hpxml_dir / 'solar_thermal.xml') + root = convert_hpxml_and_parse(hpxml_dir / "solar_thermal.xml") sts1 = root.Building.BuildingDetails.Systems.SolarThermal.SolarThermalSystem[0] - assert not hasattr(sts1, 'CollectorLoopType') - assert sts1.CollectorType == 'integrated collector storage' + assert not hasattr(sts1, "CollectorLoopType") + assert sts1.CollectorType == "integrated collector storage" sts2 = root.Building.BuildingDetails.Systems.SolarThermal.SolarThermalSystem[1] - assert not hasattr(sts2, 'CollectorLoopType') - assert sts2.CollectorType == 'single glazing black' + assert not hasattr(sts2, "CollectorLoopType") + assert sts2.CollectorType == "single glazing black" diff --git a/test/test_converter_v2to3.py b/test/test_converter_v2to3.py index e8ed118..8d46d84 100644 --- a/test/test_converter_v2to3.py +++ b/test/test_converter_v2to3.py @@ -7,16 +7,17 @@ from hpxml_version_translator import exceptions as exc -hpxml_dir = pathlib.Path(__file__).resolve().parent / 'hpxml_files' +hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_files" def convert_hpxml_and_parse(input_filename): - f_out = tempfile.NamedTemporaryFile('w+b', delete=False) + f_out = tempfile.NamedTemporaryFile("w+b", delete=False) convert_hpxml_to_3(input_filename, f_out) f_out.seek(0) root = objectify.parse(f_out).getroot() f_out.close() import os + os.unlink(f_out.name) # with tempfile.NamedTemporaryFile('w+b') as f_out: # convert_hpxml_to_3(input_filename, f_out) @@ -26,370 +27,477 @@ def convert_hpxml_and_parse(input_filename): def test_version_change(): - root = convert_hpxml_and_parse(hpxml_dir / 'version_change.xml') - assert root.attrib['schemaVersion'] == '3.0' + root = convert_hpxml_and_parse(hpxml_dir / "version_change.xml") + assert root.attrib["schemaVersion"] == "3.0" def test_project_ids(): - root = convert_hpxml_and_parse(hpxml_dir / 'project_ids.xml') - assert root.Project.PreBuildingID.attrib['id'] == 'bldg1' - assert root.Project.PostBuildingID.attrib['id'] == 'bldg2' + root = convert_hpxml_and_parse(hpxml_dir / "project_ids.xml") + assert root.Project.PreBuildingID.attrib["id"] == "bldg1" + assert root.Project.PostBuildingID.attrib["id"] == "bldg2" def test_project_ids2(): - root = convert_hpxml_and_parse(hpxml_dir / 'project_ids2.xml') - assert root.Project.PreBuildingID.attrib['id'] == 'bldg1' - assert root.Project.PostBuildingID.attrib['id'] == 'bldg2' + root = convert_hpxml_and_parse(hpxml_dir / "project_ids2.xml") + assert root.Project.PreBuildingID.attrib["id"] == "bldg1" + assert root.Project.PostBuildingID.attrib["id"] == "bldg2" def test_project_ids_fail1(): - with pytest.raises(exc.HpxmlTranslationError, match=r"Project\[\d\] has more than one reference.*audit"): - convert_hpxml_and_parse(hpxml_dir / 'project_ids_fail1.xml') + with pytest.raises( + exc.HpxmlTranslationError, + match=r"Project\[\d\] has more than one reference.*audit", + ): + convert_hpxml_and_parse(hpxml_dir / "project_ids_fail1.xml") def test_project_ids_fail2(): - with pytest.raises(exc.HpxmlTranslationError, match=r"Project\[\d\] has no references.*audit"): - convert_hpxml_and_parse(hpxml_dir / 'project_ids_fail2.xml') + with pytest.raises( + exc.HpxmlTranslationError, match=r"Project\[\d\] has no references.*audit" + ): + convert_hpxml_and_parse(hpxml_dir / "project_ids_fail2.xml") def test_project_ids_fail3(): - with pytest.raises(exc.HpxmlTranslationError, match=r"Project\[\d\] has more than one reference.*post retrofit"): - convert_hpxml_and_parse(hpxml_dir / 'project_ids_fail3.xml') + with pytest.raises( + exc.HpxmlTranslationError, + match=r"Project\[\d\] has more than one reference.*post retrofit", + ): + convert_hpxml_and_parse(hpxml_dir / "project_ids_fail3.xml") def test_project_ids_fail4(): - with pytest.raises(exc.HpxmlTranslationError, match=r"Project\[\d\] has no references.*post retrofit"): - convert_hpxml_and_parse(hpxml_dir / 'project_ids_fail4.xml') + with pytest.raises( + exc.HpxmlTranslationError, + match=r"Project\[\d\] has no references.*post retrofit", + ): + convert_hpxml_and_parse(hpxml_dir / "project_ids_fail4.xml") def test_green_building_verification(): - root = convert_hpxml_and_parse(hpxml_dir / 'green_building_verification.xml') + root = convert_hpxml_and_parse(hpxml_dir / "green_building_verification.xml") - gbv0 = root.Building[0].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[0] - assert gbv0.Type == 'Home Energy Score' - assert gbv0.Body == 'US DOE' + gbv0 = root.Building[ + 0 + ].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[0] + assert gbv0.Type == "Home Energy Score" + assert gbv0.Body == "US DOE" assert gbv0.Year == 2021 assert gbv0.Metric == 5 - assert gbv0.extension.asdf == 'jkl' - - gbv1 = root.Building[0].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[1] - assert gbv1.Type == 'HERS Index Score' - assert gbv1.Body == 'RESNET' - assert not hasattr(gbv1, 'Year') + assert gbv0.extension.asdf == "jkl" + + gbv1 = root.Building[ + 0 + ].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[1] + assert gbv1.Type == "HERS Index Score" + assert gbv1.Body == "RESNET" + assert not hasattr(gbv1, "Year") assert gbv1.Metric == 62 - gbv3 = root.Building[0].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[2] - assert gbv3.Type == 'other' - assert gbv3.Body == 'other' - assert gbv3.OtherType == 'My own special scoring system' + gbv3 = root.Building[ + 0 + ].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[2] + assert gbv3.Type == "other" + assert gbv3.Body == "other" + assert gbv3.OtherType == "My own special scoring system" assert gbv3.Metric == 11 - gbv4 = root.Building[1].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[0] - assert gbv4.Type == 'Home Performance with ENERGY STAR' - assert gbv4.Body == 'local program' - assert gbv4.URL == 'http://energy.gov' + gbv4 = root.Building[ + 1 + ].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[0] + assert gbv4.Type == "Home Performance with ENERGY STAR" + assert gbv4.Body == "local program" + assert gbv4.URL == "http://energy.gov" assert gbv4.Year == 2020 - gbv5 = root.Building[1].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[2] - assert gbv5.Type == 'ENERGY STAR Certified Homes' + gbv5 = root.Building[ + 1 + ].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[2] + assert gbv5.Type == "ENERGY STAR Certified Homes" assert gbv5.Version == 3.1 - gbv6 = root.Building[1].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[1] - assert gbv6.Type == 'LEED For Homes' - assert gbv6.Body == 'USGBC' - assert gbv6.Rating == 'Gold' - assert gbv6.URL == 'http://usgbc.org' + gbv6 = root.Building[ + 1 + ].BuildingDetails.GreenBuildingVerifications.GreenBuildingVerification[1] + assert gbv6.Type == "LEED For Homes" + assert gbv6.Body == "USGBC" + assert gbv6.Rating == "Gold" + assert gbv6.URL == "http://usgbc.org" assert gbv6.Year == 2019 def test_inconsistencies(): - root = convert_hpxml_and_parse(hpxml_dir / 'inconsistencies.xml') + root = convert_hpxml_and_parse(hpxml_dir / "inconsistencies.xml") ws = root.Building.BuildingDetails.ClimateandRiskZones.WeatherStation[0] - assert ws.SystemIdentifier.attrib['id'] == 'weather-station-1' + assert ws.SystemIdentifier.attrib["id"] == "weather-station-1" clgsys = root.Building.BuildingDetails.Systems.HVAC.HVACPlant.CoolingSystem[0] - assert clgsys.CoolingSystemType.text == 'central air conditioner' + assert clgsys.CoolingSystemType.text == "central air conditioner" htpump = root.Building.BuildingDetails.Systems.HVAC.HVACPlant.HeatPump[0] - assert htpump.AnnualCoolingEfficiency.Units == 'SEER' + assert htpump.AnnualCoolingEfficiency.Units == "SEER" assert htpump.AnnualCoolingEfficiency.Value == 13.0 - assert htpump.AnnualHeatingEfficiency.Units == 'HSPF' + assert htpump.AnnualHeatingEfficiency.Units == "HSPF" assert htpump.AnnualHeatingEfficiency.Value == 7.7 - assert htpump.BackupAnnualHeatingEfficiency.Units == 'AFUE' + assert htpump.BackupAnnualHeatingEfficiency.Units == "AFUE" assert htpump.BackupAnnualHeatingEfficiency.Value == 0.98 measure1 = root.Project.ProjectDetails.Measures.Measure[0] - assert measure1.InstalledComponents.InstalledComponent[0].attrib['id'] == 'installed-component-1' - assert measure1.InstalledComponents.InstalledComponent[1].attrib['id'] == 'installed-component-2' - assert not hasattr(measure1, 'InstalledComponent') + assert ( + measure1.InstalledComponents.InstalledComponent[0].attrib["id"] + == "installed-component-1" + ) + assert ( + measure1.InstalledComponents.InstalledComponent[1].attrib["id"] + == "installed-component-2" + ) + assert not hasattr(measure1, "InstalledComponent") assert measure1.InstalledComponents.getnext() == measure1.extension measure2 = root.Project.ProjectDetails.Measures.Measure[1] - assert measure2.InstalledComponents.InstalledComponent[0].attrib['id'] == 'installed-component-3' - assert measure2.InstalledComponents.InstalledComponent[1].attrib['id'] == 'installed-component-4' - assert not hasattr(measure2, 'InstalledComponent') + assert ( + measure2.InstalledComponents.InstalledComponent[0].attrib["id"] + == "installed-component-3" + ) + assert ( + measure2.InstalledComponents.InstalledComponent[1].attrib["id"] + == "installed-component-4" + ) + assert not hasattr(measure2, "InstalledComponent") def test_clothes_dryer(): - root = convert_hpxml_and_parse(hpxml_dir / 'clothes_dryer.xml') + root = convert_hpxml_and_parse(hpxml_dir / "clothes_dryer.xml") dryer1 = root.Building.BuildingDetails.Appliances.ClothesDryer[0] - assert dryer1.Type == 'dryer' - assert dryer1.Location == 'laundry room' - assert dryer1.FuelType == 'natural gas' + assert dryer1.Type == "dryer" + assert dryer1.Location == "laundry room" + assert dryer1.FuelType == "natural gas" assert dryer1.EnergyFactor == 2.5 - assert dryer1.ControlType == 'timer' - assert not hasattr(dryer1, 'EfficiencyFactor') + assert dryer1.ControlType == "timer" + assert not hasattr(dryer1, "EfficiencyFactor") dryer2 = root.Building.BuildingDetails.Appliances.ClothesDryer[1] - assert dryer2.Type == 'all-in-one combination washer/dryer' - assert dryer2.Location == 'basement' - assert dryer2.FuelType == 'electricity' + assert dryer2.Type == "all-in-one combination washer/dryer" + assert dryer2.Location == "basement" + assert dryer2.FuelType == "electricity" assert dryer2.EnergyFactor == 5.0 - assert dryer2.ControlType == 'temperature' - assert not hasattr(dryer2, 'EfficiencyFactor') + assert dryer2.ControlType == "temperature" + assert not hasattr(dryer2, "EfficiencyFactor") def test_enclosure_attics_and_roofs(): with pytest.warns(None) as record: - root = convert_hpxml_and_parse(hpxml_dir / 'enclosure_attics_and_roofs.xml') + root = convert_hpxml_and_parse(hpxml_dir / "enclosure_attics_and_roofs.xml") assert len(record) == 5 - assert record[0].message.args[0] == 'Cannot find a roof attached to attic-3.' - assert record[1].message.args[0] == 'Cannot find a roof attached to attic-3.' - assert record[2].message.args[0] == 'Cannot find a roof attached to attic-5.' - assert record[3].message.args[0] == 'Cannot find a knee wall attached to attic-9.' - assert record[4].message.args[0] == 'Cannot find a roof attached to attic-11.' + assert record[0].message.args[0] == "Cannot find a roof attached to attic-3." + assert record[1].message.args[0] == "Cannot find a roof attached to attic-3." + assert record[2].message.args[0] == "Cannot find a roof attached to attic-5." + assert record[3].message.args[0] == "Cannot find a knee wall attached to attic-9." + assert record[4].message.args[0] == "Cannot find a roof attached to attic-11." enclosure1 = root.Building[0].BuildingDetails.Enclosure - assert not hasattr(enclosure1, 'AtticAndRoof') - assert not hasattr(enclosure1, 'ExteriorAdjacentTo') + assert not hasattr(enclosure1, "AtticAndRoof") + assert not hasattr(enclosure1, "ExteriorAdjacentTo") attic1 = enclosure1.Attics.Attic[0] assert not attic1.AtticType.Attic.Vented # unvented attic - assert attic1.AttachedToRoof.attrib['idref'] == 'roof-1' - assert attic1.AttachedToWall.attrib['idref'] == 'wall-1' + assert attic1.AttachedToRoof.attrib["idref"] == "roof-1" + assert attic1.AttachedToWall.attrib["idref"] == "wall-1" attic2 = enclosure1.Attics.Attic[1] - assert attic2.AtticType.Attic.extension.Vented == 'unknown' # venting unknown attic - assert attic2.AttachedToFrameFloor.attrib['idref'] == 'attic-floor-1' + assert attic2.AtticType.Attic.extension.Vented == "unknown" # venting unknown attic + assert attic2.AttachedToFrameFloor.attrib["idref"] == "attic-floor-1" attic3 = enclosure1.Attics.Attic[2] assert attic3.AtticType.Attic.Vented # vented attic attic4 = enclosure1.Attics.Attic[3] - assert hasattr(attic4.AtticType, 'FlatRoof') + assert hasattr(attic4.AtticType, "FlatRoof") attic5 = enclosure1.Attics.Attic[4] - assert hasattr(attic5.AtticType, 'CathedralCeiling') + assert hasattr(attic5.AtticType, "CathedralCeiling") attic6 = enclosure1.Attics.Attic[5] assert attic6.AtticType.Attic.CapeCod attic7 = enclosure1.Attics.Attic[6] - assert hasattr(attic7.AtticType, 'Other') + assert hasattr(attic7.AtticType, "Other") roof1 = enclosure1.Roofs.Roof[0] assert roof1.Area == 1118.5 - assert roof1.RoofType == 'shingles' - assert roof1.RoofColor == 'dark' + assert roof1.RoofType == "shingles" + assert roof1.RoofColor == "dark" assert roof1.SolarAbsorptance == 0.7 assert roof1.Emittance == 0.9 assert roof1.Pitch == 6.0 - assert roof1.Rafters.Size == '2x4' - assert roof1.Rafters.Material == 'wood' - assert not hasattr(roof1, 'RoofArea') - assert not hasattr(roof1, 'Insulation') + assert roof1.Rafters.Size == "2x4" + assert roof1.Rafters.Material == "wood" + assert not hasattr(roof1, "RoofArea") + assert not hasattr(roof1, "Insulation") roof2 = enclosure1.Roofs.Roof[1] assert roof2.Area == 559.25 - assert roof2.RoofType == 'shingles' - assert roof2.RoofColor == 'medium' + assert roof2.RoofType == "shingles" + assert roof2.RoofColor == "medium" assert roof2.SolarAbsorptance == 0.6 assert roof2.Emittance == 0.7 assert roof2.Pitch == 6.0 - assert roof2.Insulation.SystemIdentifier.attrib['id'] == 'attic-roof-insulation-1' + assert roof2.Insulation.SystemIdentifier.attrib["id"] == "attic-roof-insulation-1" assert roof2.Insulation.InsulationGrade == 3 - assert roof2.Insulation.InsulationCondition == 'good' - assert roof2.Insulation.Layer.InstallationType == 'cavity' + assert roof2.Insulation.InsulationCondition == "good" + assert roof2.Insulation.Layer.InstallationType == "cavity" assert roof2.Insulation.Layer.NominalRValue == 7.5 - assert not hasattr(roof2, 'Rafters') + assert not hasattr(roof2, "Rafters") - assert enclosure1.Walls.Wall[0].AtticWallType == 'knee wall' - assert not hasattr(enclosure1.Walls.Wall[1], 'AtticWallType') + assert enclosure1.Walls.Wall[0].AtticWallType == "knee wall" + assert not hasattr(enclosure1.Walls.Wall[1], "AtticWallType") - assert enclosure1.FrameFloors.FrameFloor[0].SystemIdentifier.attrib['id'] == 'attic-floor-0' - assert enclosure1.FrameFloors.FrameFloor[0].InteriorAdjacentTo == 'garage' + assert ( + enclosure1.FrameFloors.FrameFloor[0].SystemIdentifier.attrib["id"] + == "attic-floor-0" + ) + assert enclosure1.FrameFloors.FrameFloor[0].InteriorAdjacentTo == "garage" assert enclosure1.FrameFloors.FrameFloor[0].Area == 1000.0 - assert not hasattr(enclosure1.FrameFloors.FrameFloor[0], 'Insulation') - assert enclosure1.FrameFloors.FrameFloor[1].SystemIdentifier.attrib['id'] == 'attic-floor-1' - assert enclosure1.FrameFloors.FrameFloor[1].InteriorAdjacentTo == 'living space' + assert not hasattr(enclosure1.FrameFloors.FrameFloor[0], "Insulation") + assert ( + enclosure1.FrameFloors.FrameFloor[1].SystemIdentifier.attrib["id"] + == "attic-floor-1" + ) + assert enclosure1.FrameFloors.FrameFloor[1].InteriorAdjacentTo == "living space" assert enclosure1.FrameFloors.FrameFloor[1].Area == 500.0 - assert enclosure1.FrameFloors.FrameFloor[1].Insulation.SystemIdentifier.attrib['id'] == 'attic-floor-insulation-1' + assert ( + enclosure1.FrameFloors.FrameFloor[1].Insulation.SystemIdentifier.attrib["id"] + == "attic-floor-insulation-1" + ) assert enclosure1.FrameFloors.FrameFloor[1].Insulation.InsulationGrade == 1 - assert enclosure1.FrameFloors.FrameFloor[1].Insulation.InsulationCondition == 'poor' - assert enclosure1.FrameFloors.FrameFloor[1].Insulation.AssemblyEffectiveRValue == 5.5 + assert enclosure1.FrameFloors.FrameFloor[1].Insulation.InsulationCondition == "poor" + assert ( + enclosure1.FrameFloors.FrameFloor[1].Insulation.AssemblyEffectiveRValue == 5.5 + ) enclosure2 = root.Building[1].BuildingDetails.Enclosure - assert not hasattr(enclosure2, 'AtticAndRoof') - assert not hasattr(enclosure2, 'ExteriorAdjacentTo') + assert not hasattr(enclosure2, "AtticAndRoof") + assert not hasattr(enclosure2, "ExteriorAdjacentTo") attic8 = enclosure2.Attics.Attic[0] assert attic8.AtticType.Attic.CapeCod # cape cod - assert attic8.AttachedToRoof.attrib['idref'] == 'roof-3' - assert attic8.AttachedToWall.attrib['idref'] == 'wall-3' + assert attic8.AttachedToRoof.attrib["idref"] == "roof-3" + assert attic8.AttachedToWall.attrib["idref"] == "wall-3" attic9 = enclosure2.Attics.Attic[1] - assert attic9.AtticType.Attic.extension.Vented == 'unknown' # venting unknown attic - assert attic9.AttachedToFrameFloor.attrib['idref'] == 'attic-floor-8' + assert attic9.AtticType.Attic.extension.Vented == "unknown" # venting unknown attic + assert attic9.AttachedToFrameFloor.attrib["idref"] == "attic-floor-8" roof3 = enclosure2.Roofs.Roof[0] - assert roof3.Rafters.Size == '2x6' - assert roof3.Rafters.Material == 'wood' - assert not hasattr(roof3, 'RoofArea') - assert not hasattr(roof3, 'Insulation') + assert roof3.Rafters.Size == "2x6" + assert roof3.Rafters.Material == "wood" + assert not hasattr(roof3, "RoofArea") + assert not hasattr(roof3, "Insulation") roof4 = enclosure2.Roofs.Roof[1] - assert roof4.Insulation.SystemIdentifier.attrib['id'] == 'attic-roof-insulation-2' + assert roof4.Insulation.SystemIdentifier.attrib["id"] == "attic-roof-insulation-2" assert roof4.Insulation.InsulationGrade == 2 - assert roof4.Insulation.InsulationCondition == 'fair' - assert roof4.Insulation.Layer.InstallationType == 'cavity' + assert roof4.Insulation.InsulationCondition == "fair" + assert roof4.Insulation.Layer.InstallationType == "cavity" assert roof4.Insulation.Layer.NominalRValue == 7.5 - assert not hasattr(roof4, 'Rafters') + assert not hasattr(roof4, "Rafters") roof5 = enclosure2.Roofs.Roof[2] - assert roof5.InteriorAdjacentTo == 'living space' + assert roof5.InteriorAdjacentTo == "living space" assert roof5.Area == 140.0 - assert enclosure2.Walls.Wall[0].AtticWallType == 'knee wall' - assert not hasattr(enclosure2.Walls.Wall[1], 'AtticWallType') + assert enclosure2.Walls.Wall[0].AtticWallType == "knee wall" + assert not hasattr(enclosure2.Walls.Wall[1], "AtticWallType") - assert enclosure2.FrameFloors.FrameFloor[0].SystemIdentifier.attrib['id'] == 'attic-floor-8' - assert enclosure2.FrameFloors.FrameFloor[0].InteriorAdjacentTo == 'living space' + assert ( + enclosure2.FrameFloors.FrameFloor[0].SystemIdentifier.attrib["id"] + == "attic-floor-8" + ) + assert enclosure2.FrameFloors.FrameFloor[0].InteriorAdjacentTo == "living space" assert enclosure2.FrameFloors.FrameFloor[0].Area == 700.0 - assert enclosure2.FrameFloors.FrameFloor[0].Insulation.SystemIdentifier.attrib['id'] == 'attic-floor-insulation-2' + assert ( + enclosure2.FrameFloors.FrameFloor[0].Insulation.SystemIdentifier.attrib["id"] + == "attic-floor-insulation-2" + ) assert enclosure2.FrameFloors.FrameFloor[0].Insulation.InsulationGrade == 1 - assert enclosure2.FrameFloors.FrameFloor[0].Insulation.InsulationCondition == 'poor' - assert enclosure2.FrameFloors.FrameFloor[0].Insulation.AssemblyEffectiveRValue == 5.5 - - buildingconstruction1 = root.Building[0].BuildingDetails.BuildingSummary.BuildingConstruction - buildingconstruction2 = root.Building[1].BuildingDetails.BuildingSummary.BuildingConstruction - buildingconstruction3 = root.Building[2].BuildingDetails.BuildingSummary.BuildingConstruction - buildingconstruction4 = root.Building[3].BuildingDetails.BuildingSummary.BuildingConstruction - buildingconstruction5 = root.Building[4].BuildingDetails.BuildingSummary.BuildingConstruction - buildingconstruction6 = root.Building[5].BuildingDetails.BuildingSummary.BuildingConstruction - buildingconstruction7 = root.Building[6].BuildingDetails.BuildingSummary.BuildingConstruction - assert buildingconstruction1.AtticType.Attic.extension.Vented == 'unknown' # venting unknown attic - assert hasattr(buildingconstruction2.AtticType, 'CathedralCeiling') + assert enclosure2.FrameFloors.FrameFloor[0].Insulation.InsulationCondition == "poor" + assert ( + enclosure2.FrameFloors.FrameFloor[0].Insulation.AssemblyEffectiveRValue == 5.5 + ) + + buildingconstruction1 = root.Building[ + 0 + ].BuildingDetails.BuildingSummary.BuildingConstruction + buildingconstruction2 = root.Building[ + 1 + ].BuildingDetails.BuildingSummary.BuildingConstruction + buildingconstruction3 = root.Building[ + 2 + ].BuildingDetails.BuildingSummary.BuildingConstruction + buildingconstruction4 = root.Building[ + 3 + ].BuildingDetails.BuildingSummary.BuildingConstruction + buildingconstruction5 = root.Building[ + 4 + ].BuildingDetails.BuildingSummary.BuildingConstruction + buildingconstruction6 = root.Building[ + 5 + ].BuildingDetails.BuildingSummary.BuildingConstruction + buildingconstruction7 = root.Building[ + 6 + ].BuildingDetails.BuildingSummary.BuildingConstruction + assert ( + buildingconstruction1.AtticType.Attic.extension.Vented == "unknown" + ) # venting unknown attic + assert hasattr(buildingconstruction2.AtticType, "CathedralCeiling") assert buildingconstruction3.AtticType.Attic.Vented # vented attic assert not buildingconstruction4.AtticType.Attic.Vented # unvented attic - assert hasattr(buildingconstruction5.AtticType, 'FlatRoof') + assert hasattr(buildingconstruction5.AtticType, "FlatRoof") assert buildingconstruction6.AtticType.Attic.CapeCod # cape cod - assert hasattr(buildingconstruction7.AtticType, 'Other') + assert hasattr(buildingconstruction7.AtticType, "Other") with pytest.raises(Exception) as execinfo: - convert_hpxml_and_parse(hpxml_dir / 'enclosure_missing_attic_type.xml') - assert execinfo.value.args[0] == ("enclosure_missing_attic_type.xml was not able to be translated " - "because 'AtticType' of attic-1 is unknown.") + convert_hpxml_and_parse(hpxml_dir / "enclosure_missing_attic_type.xml") + assert execinfo.value.args[0] == ( + "enclosure_missing_attic_type.xml was not able to be translated " + "because 'AtticType' of attic-1 is unknown." + ) def test_enclosure_foundation(): - root = convert_hpxml_and_parse(hpxml_dir / 'enclosure_foundation.xml') + root = convert_hpxml_and_parse(hpxml_dir / "enclosure_foundation.xml") ff1 = root.Building.BuildingDetails.Enclosure.FrameFloors.FrameFloor[0] - assert ff1.getparent().getparent().Foundations.Foundation.AttachedToFrameFloor[0].attrib['idref'] == 'framefloor-1' - assert ff1.FloorCovering == 'hardwood' + assert ( + ff1.getparent() + .getparent() + .Foundations.Foundation.AttachedToFrameFloor[0] + .attrib["idref"] + == "framefloor-1" + ) + assert ff1.FloorCovering == "hardwood" assert ff1.Area == 1350.0 assert ff1.Insulation.AssemblyEffectiveRValue == 39.3 - assert not hasattr(ff1.Insulation, 'InsulationLocation') - assert not hasattr(ff1.Insulation, 'Layer') + assert not hasattr(ff1.Insulation, "InsulationLocation") + assert not hasattr(ff1.Insulation, "Layer") ff2 = root.Building.BuildingDetails.Enclosure.FrameFloors.FrameFloor[1] - assert ff2.getparent().getparent().Foundations.Foundation.AttachedToFrameFloor[1].attrib['idref'] == 'framefloor-2' - assert ff2.FloorCovering == 'carpet' + assert ( + ff2.getparent() + .getparent() + .Foundations.Foundation.AttachedToFrameFloor[1] + .attrib["idref"] + == "framefloor-2" + ) + assert ff2.FloorCovering == "carpet" assert ff2.Area == 1350.0 assert ff2.Insulation.InsulationGrade == 1 - assert ff2.Insulation.InsulationCondition == 'poor' - assert ff2.Insulation.Layer[0].InstallationType == 'continuous - exterior' + assert ff2.Insulation.InsulationCondition == "poor" + assert ff2.Insulation.Layer[0].InstallationType == "continuous - exterior" assert ff2.Insulation.Layer[0].NominalRValue == 30.0 assert ff2.Insulation.Layer[0].Thickness == 1.5 - assert ff2.Insulation.Layer[1].InstallationType == 'continuous - exterior' + assert ff2.Insulation.Layer[1].InstallationType == "continuous - exterior" assert ff2.Insulation.Layer[1].NominalRValue == 8.0 assert ff2.Insulation.Layer[1].Thickness == 0.25 - assert not hasattr(ff2.Insulation, 'InsulationLocation') + assert not hasattr(ff2.Insulation, "InsulationLocation") fw1 = root.Building[0].BuildingDetails.Enclosure.FoundationWalls.FoundationWall[0] - assert fw1.getparent().getparent().Foundations.Foundation.AttachedToFoundationWall[0].attrib['idref'] ==\ - 'foundationwall-1' - assert not hasattr(fw1, 'ExteriorAdjacentTo') - assert fw1.InteriorAdjacentTo == 'basement - unconditioned' - assert fw1.Type == 'concrete block' + assert ( + fw1.getparent() + .getparent() + .Foundations.Foundation.AttachedToFoundationWall[0] + .attrib["idref"] + == "foundationwall-1" + ) + assert not hasattr(fw1, "ExteriorAdjacentTo") + assert fw1.InteriorAdjacentTo == "basement - unconditioned" + assert fw1.Type == "concrete block" assert fw1.Length == 120 assert fw1.Height == 8 assert fw1.Area == 960 assert fw1.Thickness == 4 assert fw1.DepthBelowGrade == 6 - assert not hasattr(fw1, 'AdjacentTo') + assert not hasattr(fw1, "AdjacentTo") assert fw1.Insulation.InsulationGrade == 3 - assert fw1.Insulation.InsulationCondition == 'good' + assert fw1.Insulation.InsulationCondition == "good" assert fw1.Insulation.AssemblyEffectiveRValue == 5.0 - assert not hasattr(fw1.Insulation, 'Location') + assert not hasattr(fw1.Insulation, "Location") fw2 = root.Building[0].BuildingDetails.Enclosure.FoundationWalls.FoundationWall[1] - assert fw2.getparent().getparent().Foundations.Foundation.AttachedToFoundationWall[1].attrib['idref'] ==\ - 'foundationwall-2' - assert not hasattr(fw2, 'ExteriorAdjacentTo') - assert fw2.InteriorAdjacentTo == 'living space' - assert fw2.Type == 'concrete block' + assert ( + fw2.getparent() + .getparent() + .Foundations.Foundation.AttachedToFoundationWall[1] + .attrib["idref"] + == "foundationwall-2" + ) + assert not hasattr(fw2, "ExteriorAdjacentTo") + assert fw2.InteriorAdjacentTo == "living space" + assert fw2.Type == "concrete block" assert fw2.Length == 60 assert fw2.Height == 8 assert fw2.Area == 480 assert fw2.Thickness == 7 assert fw2.DepthBelowGrade == 8 - assert not hasattr(fw2, 'AdjacentTo') + assert not hasattr(fw2, "AdjacentTo") assert fw2.Insulation.InsulationGrade == 1 - assert fw2.Insulation.InsulationCondition == 'poor' - assert not hasattr(fw2.Insulation, 'Location') - assert fw2.Insulation.Layer[0].InstallationType == 'continuous - exterior' - assert fw2.Insulation.Layer[0].InsulationMaterial.Batt == 'fiberglass' + assert fw2.Insulation.InsulationCondition == "poor" + assert not hasattr(fw2.Insulation, "Location") + assert fw2.Insulation.Layer[0].InstallationType == "continuous - exterior" + assert fw2.Insulation.Layer[0].InsulationMaterial.Batt == "fiberglass" assert fw2.Insulation.Layer[0].NominalRValue == 8.9 assert fw2.Insulation.Layer[0].Thickness == 1.5 - assert fw2.Insulation.Layer[1].InstallationType == 'cavity' - assert fw2.Insulation.Layer[1].InsulationMaterial.Rigid == 'eps' + assert fw2.Insulation.Layer[1].InstallationType == "cavity" + assert fw2.Insulation.Layer[1].InsulationMaterial.Rigid == "eps" assert fw2.Insulation.Layer[1].NominalRValue == 15.0 assert fw2.Insulation.Layer[1].Thickness == 3.0 fw3 = root.Building[1].BuildingDetails.Enclosure.FoundationWalls.FoundationWall[0] - assert fw3.getparent().getparent().Foundations.Foundation[0].AttachedToFoundationWall[0].attrib['idref'] ==\ - 'foundationwall-3' - assert fw3.ExteriorAdjacentTo == 'ground' # make sure that 'ambient' maps to 'ground' - assert not hasattr(fw3, 'InteriorAdjacentTo') - assert fw3.Type == 'solid concrete' + assert ( + fw3.getparent() + .getparent() + .Foundations.Foundation[0] + .AttachedToFoundationWall[0] + .attrib["idref"] + == "foundationwall-3" + ) + assert ( + fw3.ExteriorAdjacentTo == "ground" + ) # make sure that 'ambient' maps to 'ground' + assert not hasattr(fw3, "InteriorAdjacentTo") + assert fw3.Type == "solid concrete" assert fw3.Length == 40 assert fw3.Height == 10 assert fw3.Area == 400 assert fw3.Thickness == 3 assert fw3.DepthBelowGrade == 10 - assert not hasattr(fw3, 'AdjacentTo') + assert not hasattr(fw3, "AdjacentTo") assert fw3.Insulation.InsulationGrade == 2 - assert fw3.Insulation.InsulationCondition == 'fair' - assert not hasattr(fw3.Insulation, 'Location') + assert fw3.Insulation.InsulationCondition == "fair" + assert not hasattr(fw3.Insulation, "Location") fw4 = root.Building[1].BuildingDetails.Enclosure.FoundationWalls.FoundationWall[1] - assert fw4.ExteriorAdjacentTo == 'crawlspace' - assert not hasattr(fw4, 'InteriorAdjacentTo') - assert not hasattr(fw4, 'AdjacentTo') + assert fw4.ExteriorAdjacentTo == "crawlspace" + assert not hasattr(fw4, "InteriorAdjacentTo") + assert not hasattr(fw4, "AdjacentTo") fw5 = root.Building[1].BuildingDetails.Enclosure.FoundationWalls.FoundationWall[2] - assert fw5.ExteriorAdjacentTo == 'basement - unconditioned' - assert not hasattr(fw5, 'InteriorAdjacentTo') - assert not hasattr(fw5, 'AdjacentTo') + assert fw5.ExteriorAdjacentTo == "basement - unconditioned" + assert not hasattr(fw5, "InteriorAdjacentTo") + assert not hasattr(fw5, "AdjacentTo") fw6 = root.Building[1].BuildingDetails.Enclosure.FoundationWalls.FoundationWall[3] - assert fw6.InteriorAdjacentTo == 'crawlspace' - assert not hasattr(fw6, 'ExteriorAdjacentTo') - assert not hasattr(fw6, 'AdjacentTo') + assert fw6.InteriorAdjacentTo == "crawlspace" + assert not hasattr(fw6, "ExteriorAdjacentTo") + assert not hasattr(fw6, "AdjacentTo") fw7 = root.Building[1].BuildingDetails.Enclosure.FoundationWalls.FoundationWall[4] - assert fw7.ExteriorAdjacentTo == 'basement - unconditioned' - assert not hasattr(fw7, 'InteriorAdjacentTo') - assert not hasattr(fw7, 'AdjacentTo') + assert fw7.ExteriorAdjacentTo == "basement - unconditioned" + assert not hasattr(fw7, "InteriorAdjacentTo") + assert not hasattr(fw7, "AdjacentTo") slab1 = root.Building.BuildingDetails.Enclosure.Slabs.Slab[0] - assert slab1.getparent().getparent().Foundations.Foundation.AttachedToSlab.attrib['idref'] == 'slab-1' + assert ( + slab1.getparent() + .getparent() + .Foundations.Foundation.AttachedToSlab.attrib["idref"] + == "slab-1" + ) assert slab1.Area == 1350.0 assert slab1.Thickness == 4.0 assert slab1.ExposedPerimeter == 150.0 @@ -402,33 +510,33 @@ def test_enclosure_foundation(): def test_walls(): - root = convert_hpxml_and_parse(hpxml_dir / 'enclosure_walls.xml') + root = convert_hpxml_and_parse(hpxml_dir / "enclosure_walls.xml") wall1 = root.Building.BuildingDetails.Enclosure.Walls.Wall[0] - assert wall1.ExteriorAdjacentTo == 'outside' - assert wall1.InteriorAdjacentTo == 'living space' - assert hasattr(wall1.WallType, 'WoodStud') + assert wall1.ExteriorAdjacentTo == "outside" + assert wall1.InteriorAdjacentTo == "living space" + assert hasattr(wall1.WallType, "WoodStud") assert wall1.Thickness == 0.5 assert wall1.Area == 750 assert wall1.Azimuth == 0 - assert wall1.Siding == 'wood siding' + assert wall1.Siding == "wood siding" assert wall1.SolarAbsorptance == 0.6 assert wall1.Emittance == 0.7 assert wall1.Insulation.InsulationGrade == 1 - assert wall1.Insulation.InsulationCondition == 'good' - assert not hasattr(wall1.Insulation, 'InsulationLocation') - assert wall1.Insulation.Layer[0].InstallationType == 'continuous - exterior' - assert wall1.Insulation.Layer[0].InsulationMaterial.Rigid == 'xps' + assert wall1.Insulation.InsulationCondition == "good" + assert not hasattr(wall1.Insulation, "InsulationLocation") + assert wall1.Insulation.Layer[0].InstallationType == "continuous - exterior" + assert wall1.Insulation.Layer[0].InsulationMaterial.Rigid == "xps" assert wall1.Insulation.Layer[0].NominalRValue == 5.5 assert wall1.Insulation.Layer[0].Thickness == 1.5 - assert wall1.Insulation.Layer[1].InstallationType == 'cavity' - assert wall1.Insulation.Layer[1].InsulationMaterial.Batt == 'fiberglass' + assert wall1.Insulation.Layer[1].InstallationType == "cavity" + assert wall1.Insulation.Layer[1].InsulationMaterial.Batt == "fiberglass" assert wall1.Insulation.Layer[1].NominalRValue == 12.0 assert wall1.Insulation.Layer[1].Thickness == 3.5 def test_windows(): - root = convert_hpxml_and_parse(hpxml_dir / 'enclosure_windows_skylights.xml') + root = convert_hpxml_and_parse(hpxml_dir / "enclosure_windows_skylights.xml") win1 = root.Building[0].BuildingDetails.Enclosure.Windows.Window[0] assert win1.Area == 108.0 @@ -437,42 +545,44 @@ def test_windows(): assert win1.SHGC == 0.45 assert win1.NFRCCertified assert win1.VisibleTransmittance == 0.9 - assert win1.ExteriorShading[0].Type == 'solar screens' - assert win1.ExteriorShading[1].Type == 'evergreen tree' - assert win1.InteriorShading.SystemIdentifier.attrib['id'] == 'interior-shading-0' - assert win1.InteriorShading.Type == 'light shades' + assert win1.ExteriorShading[0].Type == "solar screens" + assert win1.ExteriorShading[1].Type == "evergreen tree" + assert win1.InteriorShading.SystemIdentifier.attrib["id"] == "interior-shading-0" + assert win1.InteriorShading.Type == "light shades" assert win1.InteriorShading.SummerShadingCoefficient == 0.7 assert win1.InteriorShading.WinterShadingCoefficient == 0.7 assert win1.MoveableInsulation.RValue == 5.5 - assert win1.AttachedToWall.attrib['idref'] == 'wall-1' - assert not hasattr(win1, 'Treatments') - assert not hasattr(win1, 'InteriorShadingFactor') - assert not hasattr(win1, 'MovableInsulationRValue') + assert win1.AttachedToWall.attrib["idref"] == "wall-1" + assert not hasattr(win1, "Treatments") + assert not hasattr(win1, "InteriorShadingFactor") + assert not hasattr(win1, "MovableInsulationRValue") win2 = root.Building[0].BuildingDetails.Enclosure.Windows.Window[1] - assert win2.GlassLayers == 'single-pane' - assert win2.StormWindow.GlassType == 'low-e' - assert win2.ExteriorShading[0].Type == 'solar film' + assert win2.GlassLayers == "single-pane" + assert win2.StormWindow.GlassType == "low-e" + assert win2.ExteriorShading[0].Type == "solar film" win3 = root.Building[0].BuildingDetails.Enclosure.Windows.Window[2] - assert hasattr(win3, 'WindowFilm') + assert hasattr(win3, "WindowFilm") skylight1 = root.Building[0].BuildingDetails.Enclosure.Skylights.Skylight[0] assert skylight1.Area == 20.0 assert skylight1.UFactor == 0.25 assert skylight1.SHGC == 0.60 - assert skylight1.ExteriorShading[0].Type == 'solar screens' - assert skylight1.ExteriorShading[1].Type == 'building' - assert skylight1.InteriorShading.SystemIdentifier.attrib['id'] == 'interior-shading-3' - assert skylight1.InteriorShading.Type == 'dark shades' + assert skylight1.ExteriorShading[0].Type == "solar screens" + assert skylight1.ExteriorShading[1].Type == "building" + assert ( + skylight1.InteriorShading.SystemIdentifier.attrib["id"] == "interior-shading-3" + ) + assert skylight1.InteriorShading.Type == "dark shades" assert skylight1.InteriorShading.SummerShadingCoefficient == 0.65 assert skylight1.InteriorShading.WinterShadingCoefficient == 0.65 assert skylight1.MoveableInsulation.RValue == 3.5 - assert skylight1.AttachedToRoof.attrib['idref'] == 'roof-1' + assert skylight1.AttachedToRoof.attrib["idref"] == "roof-1" assert skylight1.Pitch == 6.0 - assert not hasattr(skylight1, 'Treatments') - assert not hasattr(skylight1, 'InteriorShadingFactor') - assert not hasattr(skylight1, 'MovableInsulationRValue') + assert not hasattr(skylight1, "Treatments") + assert not hasattr(skylight1, "InteriorShadingFactor") + assert not hasattr(skylight1, "MovableInsulationRValue") win4 = root.Building[1].BuildingDetails.Enclosure.Windows.Window[0] assert win4.Area == 108.0 @@ -481,217 +591,264 @@ def test_windows(): assert win4.SHGC == 0.45 assert win4.NFRCCertified assert win4.VisibleTransmittance == 0.9 - assert win4.ExteriorShading[0].Type == 'evergreen tree' - assert win4.InteriorShading.SystemIdentifier.attrib['id'] == 'interior-shading-4' + assert win4.ExteriorShading[0].Type == "evergreen tree" + assert win4.InteriorShading.SystemIdentifier.attrib["id"] == "interior-shading-4" assert win4.InteriorShading.SummerShadingCoefficient == 0.7 assert win4.InteriorShading.WinterShadingCoefficient == 0.7 assert win4.MoveableInsulation.RValue == 5.5 - assert win4.AttachedToWall.attrib['idref'] == 'wall-2' - assert not hasattr(win4, 'Treatments') - assert not hasattr(win4, 'InteriorShadingFactor') - assert not hasattr(win4, 'MovableInsulationRValue') + assert win4.AttachedToWall.attrib["idref"] == "wall-2" + assert not hasattr(win4, "Treatments") + assert not hasattr(win4, "InteriorShadingFactor") + assert not hasattr(win4, "MovableInsulationRValue") skylight2 = root.Building[1].BuildingDetails.Enclosure.Skylights.Skylight[0] - assert skylight2.ExteriorShading[0].Type == 'solar film' - assert skylight2.InteriorShading.SystemIdentifier.attrib['id'] == 'interior-shading-5' - assert skylight2.InteriorShading.Type == 'light shades' + assert skylight2.ExteriorShading[0].Type == "solar film" + assert ( + skylight2.InteriorShading.SystemIdentifier.attrib["id"] == "interior-shading-5" + ) + assert skylight2.InteriorShading.Type == "light shades" assert skylight2.InteriorShading.SummerShadingCoefficient == 0.55 assert skylight2.InteriorShading.WinterShadingCoefficient == 0.55 - assert skylight2.AttachedToRoof.attrib['idref'] == 'roof-2' - assert not hasattr(skylight2, 'InteriorShadingFactor') + assert skylight2.AttachedToRoof.attrib["idref"] == "roof-2" + assert not hasattr(skylight2, "InteriorShadingFactor") def test_standard_locations(): - root = convert_hpxml_and_parse(hpxml_dir / 'standard_locations.xml') + root = convert_hpxml_and_parse(hpxml_dir / "standard_locations.xml") wall1 = root.Building[0].BuildingDetails.Enclosure.Walls.Wall[0] - assert wall1.ExteriorAdjacentTo == 'outside' - assert wall1.InteriorAdjacentTo == 'living space' + assert wall1.ExteriorAdjacentTo == "outside" + assert wall1.InteriorAdjacentTo == "living space" wall2 = root.Building[0].BuildingDetails.Enclosure.Walls.Wall[1] - assert wall2.ExteriorAdjacentTo == 'ground' - assert wall2.InteriorAdjacentTo == 'basement - unconditioned' + assert wall2.ExteriorAdjacentTo == "ground" + assert wall2.InteriorAdjacentTo == "basement - unconditioned" wall3 = root.Building[0].BuildingDetails.Enclosure.Walls.Wall[2] - assert wall3.ExteriorAdjacentTo == 'other housing unit' - assert wall3.InteriorAdjacentTo == 'crawlspace' + assert wall3.ExteriorAdjacentTo == "other housing unit" + assert wall3.InteriorAdjacentTo == "crawlspace" wall4 = root.Building[0].BuildingDetails.Enclosure.Walls.Wall[3] - assert wall4.ExteriorAdjacentTo == 'garage' - assert wall4.InteriorAdjacentTo == 'other' + assert wall4.ExteriorAdjacentTo == "garage" + assert wall4.InteriorAdjacentTo == "other" hvac_dist1 = root.Building[0].BuildingDetails.Systems.HVAC.HVACDistribution[0] - assert hvac_dist1.DistributionSystemType.AirDistribution.AirDistributionType == 'regular velocity' + assert ( + hvac_dist1.DistributionSystemType.AirDistribution.AirDistributionType + == "regular velocity" + ) duct1 = hvac_dist1.DistributionSystemType.AirDistribution.Ducts[0] - assert duct1.DuctType == 'supply' - assert duct1.DuctLocation == 'attic - unconditioned' + assert duct1.DuctType == "supply" + assert duct1.DuctLocation == "attic - unconditioned" duct2 = hvac_dist1.DistributionSystemType.AirDistribution.Ducts[1] - assert duct2.DuctType == 'supply' - assert duct2.DuctLocation == 'basement - unconditioned' + assert duct2.DuctType == "supply" + assert duct2.DuctLocation == "basement - unconditioned" duct3 = hvac_dist1.DistributionSystemType.AirDistribution.Ducts[2] - assert duct3.DuctType == 'supply' - assert duct3.DuctLocation == 'living space' + assert duct3.DuctType == "supply" + assert duct3.DuctLocation == "living space" duct4 = hvac_dist1.DistributionSystemType.AirDistribution.Ducts[3] - assert duct4.DuctType == 'return' - assert duct4.DuctLocation == 'crawlspace - unvented' + assert duct4.DuctType == "return" + assert duct4.DuctLocation == "crawlspace - unvented" duct5 = hvac_dist1.DistributionSystemType.AirDistribution.Ducts[4] - assert duct5.DuctType == 'return' - assert duct5.DuctLocation == 'crawlspace - vented' + assert duct5.DuctType == "return" + assert duct5.DuctLocation == "crawlspace - vented" duct6 = hvac_dist1.DistributionSystemType.AirDistribution.Ducts[5] - assert duct6.DuctType == 'return' - assert duct6.DuctLocation == 'unconditioned space' + assert duct6.DuctType == "return" + assert duct6.DuctLocation == "unconditioned space" wall5 = root.Building[1].BuildingDetails.Enclosure.Walls.Wall[0] - assert wall5.ExteriorAdjacentTo == 'outside' - assert wall5.InteriorAdjacentTo == 'living space' + assert wall5.ExteriorAdjacentTo == "outside" + assert wall5.InteriorAdjacentTo == "living space" wall6 = root.Building[1].BuildingDetails.Enclosure.Walls.Wall[1] - assert wall6.ExteriorAdjacentTo == 'ground' - assert wall6.InteriorAdjacentTo == 'basement - unconditioned' + assert wall6.ExteriorAdjacentTo == "ground" + assert wall6.InteriorAdjacentTo == "basement - unconditioned" wall7 = root.Building[1].BuildingDetails.Enclosure.Walls.Wall[2] - assert wall7.ExteriorAdjacentTo == 'other housing unit' - assert wall7.InteriorAdjacentTo == 'crawlspace' + assert wall7.ExteriorAdjacentTo == "other housing unit" + assert wall7.InteriorAdjacentTo == "crawlspace" wall8 = root.Building[1].BuildingDetails.Enclosure.Walls.Wall[3] - assert wall8.ExteriorAdjacentTo == 'garage' - assert wall8.InteriorAdjacentTo == 'other' + assert wall8.ExteriorAdjacentTo == "garage" + assert wall8.InteriorAdjacentTo == "other" hvac_dist2 = root.Building[1].BuildingDetails.Systems.HVAC.HVACDistribution[0] - assert hvac_dist2.DistributionSystemType.AirDistribution.AirDistributionType == 'regular velocity' + assert ( + hvac_dist2.DistributionSystemType.AirDistribution.AirDistributionType + == "regular velocity" + ) duct7 = hvac_dist2.DistributionSystemType.AirDistribution.Ducts[0] - assert duct7.DuctType == 'supply' - assert duct7.DuctLocation == 'attic - unconditioned' + assert duct7.DuctType == "supply" + assert duct7.DuctLocation == "attic - unconditioned" duct8 = hvac_dist2.DistributionSystemType.AirDistribution.Ducts[1] - assert duct8.DuctType == 'supply' - assert duct8.DuctLocation == 'basement - unconditioned' + assert duct8.DuctType == "supply" + assert duct8.DuctLocation == "basement - unconditioned" duct9 = hvac_dist2.DistributionSystemType.AirDistribution.Ducts[2] - assert duct9.DuctType == 'supply' - assert duct9.DuctLocation == 'living space' + assert duct9.DuctType == "supply" + assert duct9.DuctLocation == "living space" duct10 = hvac_dist2.DistributionSystemType.AirDistribution.Ducts[3] - assert duct10.DuctType == 'return' - assert duct10.DuctLocation == 'crawlspace - unvented' + assert duct10.DuctType == "return" + assert duct10.DuctLocation == "crawlspace - unvented" duct11 = hvac_dist2.DistributionSystemType.AirDistribution.Ducts[4] - assert duct11.DuctType == 'return' - assert duct11.DuctLocation == 'crawlspace - vented' + assert duct11.DuctType == "return" + assert duct11.DuctLocation == "crawlspace - vented" duct12 = hvac_dist2.DistributionSystemType.AirDistribution.Ducts[5] - assert duct12.DuctType == 'return' - assert duct12.DuctLocation == 'unconditioned space' + assert duct12.DuctType == "return" + assert duct12.DuctLocation == "unconditioned space" # Make sure we're not unintentionally changing elements that shouldn't be - assert root.XMLTransactionHeaderInformation.XMLGeneratedBy == 'unconditioned basement' + assert ( + root.XMLTransactionHeaderInformation.XMLGeneratedBy == "unconditioned basement" + ) def test_lighting(): - root = convert_hpxml_and_parse(hpxml_dir / 'lighting.xml') + root = convert_hpxml_and_parse(hpxml_dir / "lighting.xml") ltg1 = root.Building[0].BuildingDetails.Lighting - assert not hasattr(ltg1, 'LightingFractions') + assert not hasattr(ltg1, "LightingFractions") ltg_grp1 = ltg1.LightingGroup[0] - assert ltg_grp1.SystemIdentifier.attrib['id'] == 'lighting-fraction-1' + assert ltg_grp1.SystemIdentifier.attrib["id"] == "lighting-fraction-1" assert ltg_grp1.FractionofUnitsInLocation == 0.5 - assert hasattr(ltg_grp1.LightingType, 'Incandescent') + assert hasattr(ltg_grp1.LightingType, "Incandescent") ltg_grp2 = ltg1.LightingGroup[1] - assert ltg_grp2.SystemIdentifier.attrib['id'] == 'lighting-fraction-2' + assert ltg_grp2.SystemIdentifier.attrib["id"] == "lighting-fraction-2" assert ltg_grp2.FractionofUnitsInLocation == 0.1 - assert hasattr(ltg_grp2.LightingType, 'CompactFluorescent') + assert hasattr(ltg_grp2.LightingType, "CompactFluorescent") ltg_grp3 = ltg1.LightingGroup[2] - assert ltg_grp3.SystemIdentifier.attrib['id'] == 'lighting-fraction-3' + assert ltg_grp3.SystemIdentifier.attrib["id"] == "lighting-fraction-3" assert ltg_grp3.FractionofUnitsInLocation == 0.4 - assert hasattr(ltg_grp3.LightingType, 'FluorescentTube') + assert hasattr(ltg_grp3.LightingType, "FluorescentTube") ltg2 = root.Building[1].BuildingDetails.Lighting - assert not hasattr(ltg2, 'LightingFractions') + assert not hasattr(ltg2, "LightingFractions") ltg_grp5 = ltg2.LightingGroup[0] - assert ltg_grp5.SystemIdentifier.attrib['id'] == 'lighting-fraction-4' + assert ltg_grp5.SystemIdentifier.attrib["id"] == "lighting-fraction-4" assert ltg_grp5.FractionofUnitsInLocation == 0.1 - assert hasattr(ltg_grp5.LightingType, 'Incandescent') + assert hasattr(ltg_grp5.LightingType, "Incandescent") ltg_grp6 = ltg2.LightingGroup[1] - assert ltg_grp6.SystemIdentifier.attrib['id'] == 'lighting-fraction-5' + assert ltg_grp6.SystemIdentifier.attrib["id"] == "lighting-fraction-5" assert ltg_grp6.FractionofUnitsInLocation == 0.2 - assert hasattr(ltg_grp6.LightingType, 'CompactFluorescent') + assert hasattr(ltg_grp6.LightingType, "CompactFluorescent") ltg_grp7 = ltg2.LightingGroup[2] - assert ltg_grp7.SystemIdentifier.attrib['id'] == 'lighting-fraction-6' + assert ltg_grp7.SystemIdentifier.attrib["id"] == "lighting-fraction-6" assert ltg_grp7.FractionofUnitsInLocation == 0.2 - assert hasattr(ltg_grp7.LightingType, 'FluorescentTube') + assert hasattr(ltg_grp7.LightingType, "FluorescentTube") ltg_grp8 = ltg2.LightingGroup[3] - assert ltg_grp8.SystemIdentifier.attrib['id'] == 'lighting-fraction-7' + assert ltg_grp8.SystemIdentifier.attrib["id"] == "lighting-fraction-7" assert ltg_grp8.FractionofUnitsInLocation == 0.5 - assert hasattr(ltg_grp8.LightingType, 'LightEmittingDiode') + assert hasattr(ltg_grp8.LightingType, "LightEmittingDiode") def test_deprecated_items(): - root = convert_hpxml_and_parse(hpxml_dir / 'deprecated_items.xml') + root = convert_hpxml_and_parse(hpxml_dir / "deprecated_items.xml") - whsystem1 = root.Building[0].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[0] + whsystem1 = root.Building[ + 0 + ].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[0] assert whsystem1.WaterHeaterInsulation.Jacket.JacketRValue == 5 - assert not hasattr(whsystem1.WaterHeaterInsulation, 'Pipe') - hw_dist1 = root.Building[0].BuildingDetails.Systems.WaterHeating.HotWaterDistribution[0] + assert not hasattr(whsystem1.WaterHeaterInsulation, "Pipe") + hw_dist1 = root.Building[ + 0 + ].BuildingDetails.Systems.WaterHeating.HotWaterDistribution[0] assert hw_dist1.PipeInsulation.PipeRValue == 3.0 - whsystem2 = root.Building[0].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[1] + whsystem2 = root.Building[ + 0 + ].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[1] assert whsystem2.WaterHeaterInsulation.Jacket.JacketRValue == 5.5 - assert not hasattr(whsystem2.WaterHeaterInsulation, 'Pipe') - hw_dist2 = root.Building[0].BuildingDetails.Systems.WaterHeating.HotWaterDistribution[1] + assert not hasattr(whsystem2.WaterHeaterInsulation, "Pipe") + hw_dist2 = root.Building[ + 0 + ].BuildingDetails.Systems.WaterHeating.HotWaterDistribution[1] assert hw_dist2.PipeInsulation.PipeRValue == 3.5 - whsystem3 = root.Building[1].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[0] - assert not hasattr(whsystem3, 'WaterHeaterInsulation') - hw_dist3 = root.Building[1].BuildingDetails.Systems.WaterHeating.HotWaterDistribution[0] + whsystem3 = root.Building[ + 1 + ].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[0] + assert not hasattr(whsystem3, "WaterHeaterInsulation") + hw_dist3 = root.Building[ + 1 + ].BuildingDetails.Systems.WaterHeating.HotWaterDistribution[0] assert hw_dist3.PipeInsulation.PipeRValue == 5.0 pp1 = root.Building[0].BuildingDetails.Pools.Pool.PoolPumps.PoolPump[0] assert pp1.PumpSpeed.HoursPerDay == 3 - assert not hasattr(pp1, 'HoursPerDay') + assert not hasattr(pp1, "HoursPerDay") pp2 = root.Building[0].BuildingDetails.Pools.Pool.PoolPumps.PoolPump[1] assert pp2.PumpSpeed.HoursPerDay == 4 - assert not hasattr(pp2, 'HoursPerDay') + assert not hasattr(pp2, "HoursPerDay") pp3 = root.Building[1].BuildingDetails.Pools.Pool.PoolPumps.PoolPump[0] assert pp3.PumpSpeed.Power == 250 assert pp3.PumpSpeed.HoursPerDay == 5 - assert not hasattr(pp3, 'HoursPerDay') + assert not hasattr(pp3, "HoursPerDay") - consumption1 = root.Building[0].BuildingDetails.BuildingSummary.AnnualEnergyUse.ConsumptionInfo[0] - assert consumption1.ConsumptionType.Water.WaterType == 'indoor water' - assert consumption1.ConsumptionType.Water.UnitofMeasure == 'kcf' + consumption1 = root.Building[ + 0 + ].BuildingDetails.BuildingSummary.AnnualEnergyUse.ConsumptionInfo[0] + assert consumption1.ConsumptionType.Water.WaterType == "indoor water" + assert consumption1.ConsumptionType.Water.UnitofMeasure == "kcf" assert consumption1.ConsumptionDetail.Consumption == 100 - consumption2 = root.Building[0].BuildingDetails.BuildingSummary.AnnualEnergyUse.ConsumptionInfo[1] - assert consumption2.ConsumptionType.Water.WaterType == 'outdoor water' - assert consumption2.ConsumptionType.Water.UnitofMeasure == 'ccf' + consumption2 = root.Building[ + 0 + ].BuildingDetails.BuildingSummary.AnnualEnergyUse.ConsumptionInfo[1] + assert consumption2.ConsumptionType.Water.WaterType == "outdoor water" + assert consumption2.ConsumptionType.Water.UnitofMeasure == "ccf" assert consumption2.ConsumptionDetail.Consumption == 200 - consumption3 = root.Building[0].BuildingDetails.BuildingSummary.AnnualEnergyUse.ConsumptionInfo[2] - assert consumption3.ConsumptionType.Water.WaterType == 'indoor water' - assert consumption3.ConsumptionType.Water.UnitofMeasure == 'gal' + consumption3 = root.Building[ + 0 + ].BuildingDetails.BuildingSummary.AnnualEnergyUse.ConsumptionInfo[2] + assert consumption3.ConsumptionType.Water.WaterType == "indoor water" + assert consumption3.ConsumptionType.Water.UnitofMeasure == "gal" assert consumption3.ConsumptionDetail.Consumption == 300 - consumption4 = root.Building[1].BuildingDetails.BuildingSummary.AnnualEnergyUse.ConsumptionInfo[0] - assert consumption4.ConsumptionType.Water.WaterType == 'indoor water' - assert consumption4.ConsumptionType.Water.UnitofMeasure == 'cf' + consumption4 = root.Building[ + 1 + ].BuildingDetails.BuildingSummary.AnnualEnergyUse.ConsumptionInfo[0] + assert consumption4.ConsumptionType.Water.WaterType == "indoor water" + assert consumption4.ConsumptionType.Water.UnitofMeasure == "cf" assert consumption4.ConsumptionDetail.Consumption == 400 wh1 = root.Building[0].BuildingDetails.Systems.WaterHeating - assert wh1.AnnualEnergyUse.ConsumptionInfo.ConsumptionType.Water.WaterType == 'indoor and outdoor water' - assert wh1.AnnualEnergyUse.ConsumptionInfo.ConsumptionType.Water.UnitofMeasure == 'Mgal' + assert ( + wh1.AnnualEnergyUse.ConsumptionInfo.ConsumptionType.Water.WaterType + == "indoor and outdoor water" + ) + assert ( + wh1.AnnualEnergyUse.ConsumptionInfo.ConsumptionType.Water.UnitofMeasure + == "Mgal" + ) assert wh1.AnnualEnergyUse.ConsumptionInfo.ConsumptionDetail.Consumption == 500 wh2 = root.Building[1].BuildingDetails.Systems.WaterHeating - assert wh2.AnnualEnergyUse.ConsumptionInfo.ConsumptionType.Water.WaterType == 'indoor water' - assert wh2.AnnualEnergyUse.ConsumptionInfo.ConsumptionType.Water.UnitofMeasure == 'gal' + assert ( + wh2.AnnualEnergyUse.ConsumptionInfo.ConsumptionType.Water.WaterType + == "indoor water" + ) + assert ( + wh2.AnnualEnergyUse.ConsumptionInfo.ConsumptionType.Water.UnitofMeasure == "gal" + ) assert wh2.AnnualEnergyUse.ConsumptionInfo.ConsumptionDetail.Consumption == 600 def test_desuperheater_flexibility(): - root = convert_hpxml_and_parse(hpxml_dir / 'desuperheater_flexibility.xml') + root = convert_hpxml_and_parse(hpxml_dir / "desuperheater_flexibility.xml") - whsystem1 = root.Building[0].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[0] - assert not hasattr(whsystem1, 'HasGeothermalDesuperheater') + whsystem1 = root.Building[ + 0 + ].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[0] + assert not hasattr(whsystem1, "HasGeothermalDesuperheater") assert whsystem1.UsesDesuperheater - assert not hasattr(whsystem1, 'RelatedHeatingSystem') - assert whsystem1.RelatedHVACSystem.attrib['idref'] == 'heating-system-1' - whsystem2 = root.Building[0].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[1] - assert not hasattr(whsystem2, 'HasGeothermalDesuperheater') + assert not hasattr(whsystem1, "RelatedHeatingSystem") + assert whsystem1.RelatedHVACSystem.attrib["idref"] == "heating-system-1" + whsystem2 = root.Building[ + 0 + ].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[1] + assert not hasattr(whsystem2, "HasGeothermalDesuperheater") assert not whsystem2.UsesDesuperheater - assert not hasattr(whsystem2, 'RelatedHeatingSystem') - assert whsystem2.RelatedHVACSystem.attrib['idref'] == 'heatpump-1' - whsystem3 = root.Building[1].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[0] - assert not hasattr(whsystem3, 'HasGeothermalDesuperheater') + assert not hasattr(whsystem2, "RelatedHeatingSystem") + assert whsystem2.RelatedHVACSystem.attrib["idref"] == "heatpump-1" + whsystem3 = root.Building[ + 1 + ].BuildingDetails.Systems.WaterHeating.WaterHeatingSystem[0] + assert not hasattr(whsystem3, "HasGeothermalDesuperheater") assert whsystem3.UsesDesuperheater - assert not hasattr(whsystem3, 'RelatedHeatingSystem') - assert whsystem3.RelatedHVACSystem.attrib['idref'] == 'heating-system-2' + assert not hasattr(whsystem3, "RelatedHeatingSystem") + assert whsystem3.RelatedHVACSystem.attrib["idref"] == "heating-system-2" def test_inverter_efficiency(): - root = convert_hpxml_and_parse(hpxml_dir / 'inverter_efficiency.xml') + root = convert_hpxml_and_parse(hpxml_dir / "inverter_efficiency.xml") for pv_system in root.Building[0].BuildingDetails.Systems.Photovoltaics.PVSystem: assert pv_system.InverterEfficiency == 0.9 From 260bbc7969465aaae40300b91b72d1a7d4ba17bc Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Tue, 7 Dec 2021 15:45:17 -0700 Subject: [PATCH 03/10] adding tests and deprecating old function --- hpxml_version_translator/__init__.py | 4 +-- hpxml_version_translator/converter.py | 6 +++- setup.py | 1 + test/test_converter_cli.py | 12 +++++++ test/test_converter_v1to3.py | 11 +++++-- test/test_converter_v2to3.py | 47 +++++++++++++++++++-------- 6 files changed, 61 insertions(+), 20 deletions(-) diff --git a/hpxml_version_translator/__init__.py b/hpxml_version_translator/__init__.py index d5b40de..ce314ba 100644 --- a/hpxml_version_translator/__init__.py +++ b/hpxml_version_translator/__init__.py @@ -1,5 +1,5 @@ import argparse -from hpxml_version_translator.converter import convert_hpxml_to_3 +from hpxml_version_translator.converter import convert_hpxml_to_version import sys @@ -23,7 +23,7 @@ def main(argv=sys.argv[1:]): help="Major version of HPXML to translate to, default: 3", ) args = parser.parse_args(argv) - convert_hpxml_to_3(args.hpxml_input, args.output) + convert_hpxml_to_version(args.to_hpxml_version, args.hpxml_input, args.output) if __name__ == "__main__": diff --git a/hpxml_version_translator/converter.py b/hpxml_version_translator/converter.py index 98e6820..3c5bb76 100644 --- a/hpxml_version_translator/converter.py +++ b/hpxml_version_translator/converter.py @@ -1,6 +1,7 @@ from collections import defaultdict from copy import deepcopy import datetime as dt +from deprecated import deprecated from lxml import etree, objectify import os import pathlib @@ -79,10 +80,12 @@ def convert_hpxml_to_version( schema_version = detect_hpxml_version(hpxml_file) major_version = schema_version[0] if hpxml_version <= major_version: - raise RuntimeError( + raise exc.HpxmlTranslationError( f"HPXML version requested is {hpxml_version} but input file version is {major_version}" ) version_translator_funcs = {1: convert_hpxml1_to_2, 2: convert_hpxml2_to_3} + if hpxml_version - 1 not in version_translator_funcs.keys(): + raise exc.HpxmlTranslationError(f"HPXML version {hpxml_version} not available") current_file = hpxml_file with tempfile.TemporaryDirectory() as tmpdir: for current_version in range(major_version, hpxml_version): @@ -96,6 +99,7 @@ def convert_hpxml_to_version( current_file = next_file +@deprecated(version="0.2", reason="Use convert_hpxml_to_version instead") def convert_hpxml_to_3(hpxml_file: File, hpxml3_file: File) -> None: convert_hpxml_to_version(3, hpxml_file, hpxml3_file) diff --git a/setup.py b/setup.py index ccab689..b946f47 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ package_data={"hpxml_version_translator": ["schemas/*/*.xsd", "*.xsl"]}, install_requires=[ "lxml", + "deprecated", ], extras_require={ "dev": [ diff --git a/test/test_converter_cli.py b/test/test_converter_cli.py index d7d1011..a5d5bcb 100644 --- a/test/test_converter_cli.py +++ b/test/test_converter_cli.py @@ -22,3 +22,15 @@ def test_cli(capsysbinary): f = io.BytesIO(capsysbinary.readouterr().out) root = objectify.parse(f).getroot() assert root.attrib["schemaVersion"] == "3.0" + + +def test_cli_to_v2(capsysbinary): + input_filename = str( + pathlib.Path(__file__).resolve().parent + / "hpxml_v1_files" + / "version_change.xml" + ) + main([input_filename, "-v", "2"]) + f = io.BytesIO(capsysbinary.readouterr().out) + root = objectify.parse(f).getroot() + assert root.attrib["schemaVersion"] == "2.3" diff --git a/test/test_converter_v1to3.py b/test/test_converter_v1to3.py index 9ef4e79..98dd823 100644 --- a/test/test_converter_v1to3.py +++ b/test/test_converter_v1to3.py @@ -2,20 +2,25 @@ import pathlib import tempfile -from hpxml_version_translator.converter import convert_hpxml_to_3 +from hpxml_version_translator.converter import convert_hpxml_to_version hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_v1_files" -def convert_hpxml_and_parse(input_filename): +def convert_hpxml_and_parse(input_filename, version=3): with tempfile.NamedTemporaryFile("w+b") as f_out: - convert_hpxml_to_3(input_filename, f_out) + convert_hpxml_to_version(version, input_filename, f_out) f_out.seek(0) root = objectify.parse(f_out).getroot() return root +def test_version_change_to_2(): + root = convert_hpxml_and_parse(hpxml_dir / "version_change.xml", 2) + assert root.attrib["schemaVersion"] == "2.3" + + def test_version_change(): root = convert_hpxml_and_parse(hpxml_dir / "version_change.xml") assert root.attrib["schemaVersion"] == "3.0" diff --git a/test/test_converter_v2to3.py b/test/test_converter_v2to3.py index 8d46d84..717383c 100644 --- a/test/test_converter_v2to3.py +++ b/test/test_converter_v2to3.py @@ -3,26 +3,21 @@ import pytest import tempfile -from hpxml_version_translator.converter import convert_hpxml_to_3 +from hpxml_version_translator.converter import ( + convert_hpxml_to_3, + convert_hpxml_to_version, +) from hpxml_version_translator import exceptions as exc hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_files" -def convert_hpxml_and_parse(input_filename): - f_out = tempfile.NamedTemporaryFile("w+b", delete=False) - convert_hpxml_to_3(input_filename, f_out) - f_out.seek(0) - root = objectify.parse(f_out).getroot() - f_out.close() - import os - - os.unlink(f_out.name) - # with tempfile.NamedTemporaryFile('w+b') as f_out: - # convert_hpxml_to_3(input_filename, f_out) - # f_out.seek(0) - # root = objectify.parse(f_out).getroot() +def convert_hpxml_and_parse(input_filename, version=3): + with tempfile.NamedTemporaryFile("w+b") as f_out: + convert_hpxml_to_version(version, input_filename, f_out) + f_out.seek(0) + root = objectify.parse(f_out).getroot() return root @@ -31,6 +26,30 @@ def test_version_change(): assert root.attrib["schemaVersion"] == "3.0" +def test_attempt_to_change_to_same_version(): + with pytest.raises( + exc.HpxmlTranslationError, + match=r"HPXML version requested is 2 but input file version is 2", + ): + convert_hpxml_and_parse(hpxml_dir / "version_change.xml", version=2) + + +def test_attempt_to_use_nonexistent_version(): + with pytest.raises( + exc.HpxmlTranslationError, match=r"HPXML version \d+ not available" + ): + convert_hpxml_and_parse(hpxml_dir / "version_change.xml", version=5) + + +def test_convert_hpxml_to_3(): + with tempfile.NamedTemporaryFile("w+b") as f_out: + with pytest.deprecated_call(): + convert_hpxml_to_3(hpxml_dir / "version_change.xml", f_out) + f_out.seek(0) + root = objectify.parse(f_out).getroot() + assert root.attrib["schemaVersion"] == "3.0" + + def test_project_ids(): root = convert_hpxml_and_parse(hpxml_dir / "project_ids.xml") assert root.Project.PreBuildingID.attrib["id"] == "bldg1" From 16b241cb4d85f73380350df7b827945962004ac0 Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Wed, 8 Dec 2021 10:34:38 -0700 Subject: [PATCH 04/10] Apply suggestions from code review Co-authored-by: Scott Horowitz --- hpxml_version_translator/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hpxml_version_translator/__init__.py b/hpxml_version_translator/__init__.py index ce314ba..fe73f82 100644 --- a/hpxml_version_translator/__init__.py +++ b/hpxml_version_translator/__init__.py @@ -5,7 +5,7 @@ def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser( - description="HPXML Version Translator, convert an HPXML file to 3.0" + description="HPXML Version Translator, convert an HPXML file to a newer version" ) parser.add_argument("hpxml_input", help="Filename of hpxml file") parser.add_argument( diff --git a/setup.py b/setup.py index b946f47..a0780ec 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ version="0.1", author="Ben Park (NREL), Noel Merket (NREL), Scott Horowitz (NREL)", author_email="ben.park@nrel.gov", - description="Convert HPXML to v3", + description="Convert HPXML to newer version", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/NREL/hpxml_version_translator", From a383185f42c51e52ef177ebd61a8d452e85fcda0 Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Fri, 10 Dec 2021 14:48:12 -0700 Subject: [PATCH 05/10] moving hpxml_files -> hpxml_v2_files --- test/{hpxml_files => hpxml_v2_files}/clothes_dryer.xml | 0 test/{hpxml_files => hpxml_v2_files}/deprecated_items.xml | 0 .../desuperheater_flexibility.xml | 0 .../enclosure_attics_and_roofs.xml | 0 test/{hpxml_files => hpxml_v2_files}/enclosure_foundation.xml | 0 .../enclosure_missing_attic_type.xml | 0 test/{hpxml_files => hpxml_v2_files}/enclosure_walls.xml | 0 .../enclosure_windows_skylights.xml | 0 .../green_building_verification.xml | 0 test/{hpxml_files => hpxml_v2_files}/inconsistencies.xml | 0 test/{hpxml_files => hpxml_v2_files}/inverter_efficiency.xml | 0 test/{hpxml_files => hpxml_v2_files}/lighting.xml | 0 test/{hpxml_files => hpxml_v2_files}/project_ids.xml | 0 test/{hpxml_files => hpxml_v2_files}/project_ids2.xml | 0 test/{hpxml_files => hpxml_v2_files}/project_ids_fail1.xml | 0 test/{hpxml_files => hpxml_v2_files}/project_ids_fail2.xml | 0 test/{hpxml_files => hpxml_v2_files}/project_ids_fail3.xml | 0 test/{hpxml_files => hpxml_v2_files}/project_ids_fail4.xml | 0 test/{hpxml_files => hpxml_v2_files}/standard_locations.xml | 0 test/{hpxml_files => hpxml_v2_files}/version_change.xml | 0 test/test_converter_cli.py | 2 +- test/test_converter_v2to3.py | 2 +- 22 files changed, 2 insertions(+), 2 deletions(-) rename test/{hpxml_files => hpxml_v2_files}/clothes_dryer.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/deprecated_items.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/desuperheater_flexibility.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/enclosure_attics_and_roofs.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/enclosure_foundation.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/enclosure_missing_attic_type.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/enclosure_walls.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/enclosure_windows_skylights.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/green_building_verification.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/inconsistencies.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/inverter_efficiency.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/lighting.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/project_ids.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/project_ids2.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/project_ids_fail1.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/project_ids_fail2.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/project_ids_fail3.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/project_ids_fail4.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/standard_locations.xml (100%) rename test/{hpxml_files => hpxml_v2_files}/version_change.xml (100%) diff --git a/test/hpxml_files/clothes_dryer.xml b/test/hpxml_v2_files/clothes_dryer.xml similarity index 100% rename from test/hpxml_files/clothes_dryer.xml rename to test/hpxml_v2_files/clothes_dryer.xml diff --git a/test/hpxml_files/deprecated_items.xml b/test/hpxml_v2_files/deprecated_items.xml similarity index 100% rename from test/hpxml_files/deprecated_items.xml rename to test/hpxml_v2_files/deprecated_items.xml diff --git a/test/hpxml_files/desuperheater_flexibility.xml b/test/hpxml_v2_files/desuperheater_flexibility.xml similarity index 100% rename from test/hpxml_files/desuperheater_flexibility.xml rename to test/hpxml_v2_files/desuperheater_flexibility.xml diff --git a/test/hpxml_files/enclosure_attics_and_roofs.xml b/test/hpxml_v2_files/enclosure_attics_and_roofs.xml similarity index 100% rename from test/hpxml_files/enclosure_attics_and_roofs.xml rename to test/hpxml_v2_files/enclosure_attics_and_roofs.xml diff --git a/test/hpxml_files/enclosure_foundation.xml b/test/hpxml_v2_files/enclosure_foundation.xml similarity index 100% rename from test/hpxml_files/enclosure_foundation.xml rename to test/hpxml_v2_files/enclosure_foundation.xml diff --git a/test/hpxml_files/enclosure_missing_attic_type.xml b/test/hpxml_v2_files/enclosure_missing_attic_type.xml similarity index 100% rename from test/hpxml_files/enclosure_missing_attic_type.xml rename to test/hpxml_v2_files/enclosure_missing_attic_type.xml diff --git a/test/hpxml_files/enclosure_walls.xml b/test/hpxml_v2_files/enclosure_walls.xml similarity index 100% rename from test/hpxml_files/enclosure_walls.xml rename to test/hpxml_v2_files/enclosure_walls.xml diff --git a/test/hpxml_files/enclosure_windows_skylights.xml b/test/hpxml_v2_files/enclosure_windows_skylights.xml similarity index 100% rename from test/hpxml_files/enclosure_windows_skylights.xml rename to test/hpxml_v2_files/enclosure_windows_skylights.xml diff --git a/test/hpxml_files/green_building_verification.xml b/test/hpxml_v2_files/green_building_verification.xml similarity index 100% rename from test/hpxml_files/green_building_verification.xml rename to test/hpxml_v2_files/green_building_verification.xml diff --git a/test/hpxml_files/inconsistencies.xml b/test/hpxml_v2_files/inconsistencies.xml similarity index 100% rename from test/hpxml_files/inconsistencies.xml rename to test/hpxml_v2_files/inconsistencies.xml diff --git a/test/hpxml_files/inverter_efficiency.xml b/test/hpxml_v2_files/inverter_efficiency.xml similarity index 100% rename from test/hpxml_files/inverter_efficiency.xml rename to test/hpxml_v2_files/inverter_efficiency.xml diff --git a/test/hpxml_files/lighting.xml b/test/hpxml_v2_files/lighting.xml similarity index 100% rename from test/hpxml_files/lighting.xml rename to test/hpxml_v2_files/lighting.xml diff --git a/test/hpxml_files/project_ids.xml b/test/hpxml_v2_files/project_ids.xml similarity index 100% rename from test/hpxml_files/project_ids.xml rename to test/hpxml_v2_files/project_ids.xml diff --git a/test/hpxml_files/project_ids2.xml b/test/hpxml_v2_files/project_ids2.xml similarity index 100% rename from test/hpxml_files/project_ids2.xml rename to test/hpxml_v2_files/project_ids2.xml diff --git a/test/hpxml_files/project_ids_fail1.xml b/test/hpxml_v2_files/project_ids_fail1.xml similarity index 100% rename from test/hpxml_files/project_ids_fail1.xml rename to test/hpxml_v2_files/project_ids_fail1.xml diff --git a/test/hpxml_files/project_ids_fail2.xml b/test/hpxml_v2_files/project_ids_fail2.xml similarity index 100% rename from test/hpxml_files/project_ids_fail2.xml rename to test/hpxml_v2_files/project_ids_fail2.xml diff --git a/test/hpxml_files/project_ids_fail3.xml b/test/hpxml_v2_files/project_ids_fail3.xml similarity index 100% rename from test/hpxml_files/project_ids_fail3.xml rename to test/hpxml_v2_files/project_ids_fail3.xml diff --git a/test/hpxml_files/project_ids_fail4.xml b/test/hpxml_v2_files/project_ids_fail4.xml similarity index 100% rename from test/hpxml_files/project_ids_fail4.xml rename to test/hpxml_v2_files/project_ids_fail4.xml diff --git a/test/hpxml_files/standard_locations.xml b/test/hpxml_v2_files/standard_locations.xml similarity index 100% rename from test/hpxml_files/standard_locations.xml rename to test/hpxml_v2_files/standard_locations.xml diff --git a/test/hpxml_files/version_change.xml b/test/hpxml_v2_files/version_change.xml similarity index 100% rename from test/hpxml_files/version_change.xml rename to test/hpxml_v2_files/version_change.xml diff --git a/test/test_converter_cli.py b/test/test_converter_cli.py index a5d5bcb..3447dbc 100644 --- a/test/test_converter_cli.py +++ b/test/test_converter_cli.py @@ -6,7 +6,7 @@ from hpxml_version_translator import main -hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_files" +hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_v2_files" def test_cli(capsysbinary): diff --git a/test/test_converter_v2to3.py b/test/test_converter_v2to3.py index 717383c..8df1807 100644 --- a/test/test_converter_v2to3.py +++ b/test/test_converter_v2to3.py @@ -10,7 +10,7 @@ from hpxml_version_translator import exceptions as exc -hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_files" +hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_v2_files" def convert_hpxml_and_parse(input_filename, version=3): From 5dc9e27cc9463469cedfb36bb66cb8f43443efef Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Fri, 10 Dec 2021 15:07:32 -0700 Subject: [PATCH 06/10] removing todo comments for things that are done --- hpxml_version_translator/converter.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/hpxml_version_translator/converter.py b/hpxml_version_translator/converter.py index 3c5bb76..c29b1d0 100644 --- a/hpxml_version_translator/converter.py +++ b/hpxml_version_translator/converter.py @@ -928,7 +928,9 @@ def get_pre_post_from_building_id(building_id): enclosure.Slabs.append(deepcopy(slab)) foundation.remove(slab) - # Remove 'Insulation/InsulationLocation' + # Allow insulation location to be layer-specific + # https://github.com/hpxmlwg/hpxml/pull/188 + for insulation_location in root.xpath( "//h:Insulation/h:InsulationLocation", **xpkw ): @@ -943,6 +945,8 @@ def get_pre_post_from_building_id(building_id): insulation.remove(insulation.InsulationLocation) # Windows and Skylights + # Window sub-components + # https://github.com/hpxmlwg/hpxml/pull/202 for i, win in enumerate(root.xpath("//h:Window|//h:Skylight", **xpkw)): if hasattr(win, "VisibleTransmittance"): vis_trans = float(win.VisibleTransmittance) @@ -1055,6 +1059,9 @@ def get_pre_post_from_building_id(building_id): win.InteriorShading.clear() win.InteriorShading.append(E.SystemIdentifier(id=f"interior-shading-{i}")) win.InteriorShading.append(E.Type(cache_interior_shading_type)) + + # Window/Skylight Interior Shading Fraction + # https://github.com/hpxmlwg/hpxml/pull/189 if hasattr(win, "InteriorShadingFactor"): # handles a case where `InteriorShadingFactor` is specified without `InteriorShading` if not hasattr(win, "InteriorShading"): @@ -1259,15 +1266,6 @@ def get_pre_post_from_building_id(building_id): if float(inverter_efficiency) > 1: inverter_efficiency._setText(str(float(inverter_efficiency) / 100.0)) - # TODO: Allow insulation location to be layer-specific - # https://github.com/hpxmlwg/hpxml/pull/188 - - # TODO: Window/Skylight Interior Shading Fraction - # https://github.com/hpxmlwg/hpxml/pull/189 - - # TODO: Window sub-components - # https://github.com/hpxmlwg/hpxml/pull/202 - # Write out new file hpxml3_doc.write(pathobj_to_str(hpxml3_file), pretty_print=True, encoding="utf-8") hpxml3_schema.assertValid(hpxml3_doc) From 393911aef1f9090d2b70c5db72855b4ecaac9fcd Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Fri, 10 Dec 2021 15:08:22 -0700 Subject: [PATCH 07/10] adding python 3.10 to the testing matrix --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5216ba0..6a6eeb9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 6a982f39bf9abcee29b9703e0f3a93e5ff5c580b Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Fri, 10 Dec 2021 15:10:07 -0700 Subject: [PATCH 08/10] 3.10 needs to be a string --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6a6eeb9..7d77092 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9, 3.10] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 5ff98529fa53acb7b898249c012ea87642bdc880 Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Mon, 13 Dec 2021 10:18:09 -0700 Subject: [PATCH 09/10] allowing specifying minor versions for targets --- hpxml_version_translator/__init__.py | 12 ++- hpxml_version_translator/converter.py | 106 ++++++++++++++++++++------ test/test_converter_cli.py | 15 +++- test/test_converter_v1to3.py | 9 ++- test/test_converter_v2to3.py | 11 +-- 5 files changed, 118 insertions(+), 35 deletions(-) diff --git a/hpxml_version_translator/__init__.py b/hpxml_version_translator/__init__.py index fe73f82..c686b64 100644 --- a/hpxml_version_translator/__init__.py +++ b/hpxml_version_translator/__init__.py @@ -1,5 +1,8 @@ import argparse -from hpxml_version_translator.converter import convert_hpxml_to_version +from hpxml_version_translator.converter import ( + convert_hpxml_to_version, + get_hpxml_versions, +) import sys @@ -18,9 +21,10 @@ def main(argv=sys.argv[1:]): parser.add_argument( "-v", "--to_hpxml_version", - type=int, - default=3, - help="Major version of HPXML to translate to, default: 3", + type=str, + default="3.0", + choices=get_hpxml_versions(), + help="Major version of HPXML to translate to, default: 3.0", ) args = parser.parse_args(argv) convert_hpxml_to_version(args.to_hpxml_version, args.hpxml_input, args.output) diff --git a/hpxml_version_translator/converter.py b/hpxml_version_translator/converter.py index c29b1d0..bb8010b 100644 --- a/hpxml_version_translator/converter.py +++ b/hpxml_version_translator/converter.py @@ -7,7 +7,7 @@ import pathlib import re import tempfile -from typing import Union, BinaryIO, List +from typing import Tuple, Union, BinaryIO, List import io import warnings @@ -39,13 +39,43 @@ def pathobj_to_str(x: File) -> Union[str, BinaryIO]: return x.name -def detect_hpxml_version(hpxmlfilename: File) -> List[int]: - doc = etree.parse(pathobj_to_str(hpxmlfilename)) - schema_version = list(map(int, doc.getroot().attrib["schemaVersion"].split("."))) +def convert_str_version_to_tuple(version: str) -> Tuple[int]: + schema_version = list(map(int, version.split("."))) schema_version.extend((3 - len(schema_version)) * [0]) return schema_version +def detect_hpxml_version(hpxmlfilename: File) -> List[int]: + doc = etree.parse(pathobj_to_str(hpxmlfilename)) + return convert_str_version_to_tuple(doc.getroot().attrib["schemaVersion"]) + + +def get_hpxml_versions(major_version: Union[int, None] = None) -> List[str]: + schemas_dir = pathlib.Path(__file__).resolve().parent / "schemas" + schema_versions = [] + for schema_dir in schemas_dir.iterdir(): + if not schema_dir.is_dir() or schema_dir.name == "v1.1.1": + continue + tree = etree.parse(str(schema_dir / "HPXMLDataTypes.xsd")) + root = tree.getroot() + ns = {"xs": root.nsmap["xs"]} + schema_versions.extend( + root.xpath( + '//xs:simpleType[@name="schemaVersionType"]/xs:restriction/xs:enumeration/@value', + namespaces=ns, + smart_strings=False, + ) + ) + if major_version: + schema_versions = list( + filter( + lambda x: convert_str_version_to_tuple(x)[0] == major_version, + schema_versions, + ) + ) + return schema_versions + + def add_after( parent_el: etree._Element, list_of_el_names: List[str], el_to_add: etree._Element ) -> None: @@ -75,44 +105,65 @@ def add_before( def convert_hpxml_to_version( - hpxml_version: int, hpxml_file: File, hpxml_out_file: File + hpxml_version: str, hpxml_file: File, hpxml_out_file: File ) -> None: - schema_version = detect_hpxml_version(hpxml_file) - major_version = schema_version[0] - if hpxml_version <= major_version: + + # Validate that the hpxml_version requested is a valid one. + hpxml_version_strs = get_hpxml_versions() + schema_version_requested = convert_str_version_to_tuple(hpxml_version) + major_version_requested = schema_version_requested[0] + if hpxml_version not in hpxml_version_strs: raise exc.HpxmlTranslationError( - f"HPXML version requested is {hpxml_version} but input file version is {major_version}" + f"HPXML version {hpxml_version} is not valid. Must be one of {', '.join(hpxml_version_strs)}." ) + + # Validate that the hpxml_version requested is a newer one that the current one. + schema_version_file = detect_hpxml_version(hpxml_file) + major_version_file = schema_version_file[0] + if major_version_requested <= major_version_file: + raise exc.HpxmlTranslationError( + f"HPXML version requested is {hpxml_version} but input file major version is {schema_version_file[0]}" + ) + version_translator_funcs = {1: convert_hpxml1_to_2, 2: convert_hpxml2_to_3} - if hpxml_version - 1 not in version_translator_funcs.keys(): - raise exc.HpxmlTranslationError(f"HPXML version {hpxml_version} not available") current_file = hpxml_file with tempfile.TemporaryDirectory() as tmpdir: - for current_version in range(major_version, hpxml_version): + for current_version in range(major_version_file, major_version_requested): next_version = current_version + 1 - next_file = ( - hpxml_out_file - if current_version + 1 == hpxml_version - else pathlib.Path(tmpdir, f"{next_version}.xml") - ) - version_translator_funcs[current_version](current_file, next_file) + if current_version + 1 == major_version_requested: + next_file = hpxml_out_file + version_translator_funcs[current_version]( + current_file, next_file, hpxml_version + ) + else: + next_file = pathlib.Path(tmpdir, f"{next_version}.xml") + version_translator_funcs[current_version](current_file, next_file) current_file = next_file @deprecated(version="0.2", reason="Use convert_hpxml_to_version instead") def convert_hpxml_to_3(hpxml_file: File, hpxml3_file: File) -> None: - convert_hpxml_to_version(3, hpxml_file, hpxml3_file) + convert_hpxml_to_version("3.0", hpxml_file, hpxml3_file) -def convert_hpxml1_to_2(hpxml1_file: File, hpxml2_file: File) -> None: +def convert_hpxml1_to_2( + hpxml1_file: File, hpxml2_file: File, version: str = "2.3" +) -> None: """Convert an HPXML v1 file to HPXML v2 :param hpxml1_file: HPXML v1 input file :type hpxml1_file: pathlib.Path, str, or file-like :param hpxml2_file: HPXML v2 output file :type hpxml2_file: pathlib.Path, str, or file-like + :param version: Target version + :type version: str """ + if version not in get_hpxml_versions(major_version=2): + raise exc.HpxmlTranslationError( + "convert_hpxml2_to_3 must have valid target version of 3.x, got {version}." + ) + # Load Schemas schemas_dir = pathlib.Path(__file__).resolve().parent / "schemas" hpxml1_schema_doc = etree.parse(str(schemas_dir / "v1.1.1" / "HPXML.xsd")) @@ -142,7 +193,7 @@ def convert_hpxml1_to_2(hpxml1_file: File, hpxml2_file: File) -> None: root = hpxml2_doc.getroot() # Change version - root.attrib["schemaVersion"] = "2.3" + root.attrib["schemaVersion"] = version # TODO: Moved the BPI 2400 elements and renamed/reorganized them. @@ -169,15 +220,24 @@ def convert_hpxml1_to_2(hpxml1_file: File, hpxml2_file: File) -> None: hpxml2_schema.assertValid(hpxml2_doc) -def convert_hpxml2_to_3(hpxml2_file: File, hpxml3_file: File) -> None: +def convert_hpxml2_to_3( + hpxml2_file: File, hpxml3_file: File, version: str = "3.0" +) -> None: """Convert an HPXML v2 file to HPXML v3 :param hpxml2_file: HPXML v2 input file :type hpxml2_file: pathlib.Path, str, or file-like :param hpxml3_file: HPXML v3 output file :type hpxml3_file: pathlib.Path, str, or file-like + :param version: Target version + :type version: str """ + if version not in get_hpxml_versions(major_version=3): + raise exc.HpxmlTranslationError( + "convert_hpxml2_to_3 must have valid target version of 3.x, got {version}." + ) + # Load Schemas schemas_dir = pathlib.Path(__file__).resolve().parent / "schemas" hpxml2_schema_doc = etree.parse(str(schemas_dir / "v2.3" / "HPXML.xsd")) @@ -207,7 +267,7 @@ def convert_hpxml2_to_3(hpxml2_file: File, hpxml3_file: File) -> None: root = hpxml3_doc.getroot() # Change version - root.attrib["schemaVersion"] = "3.0" + root.attrib["schemaVersion"] = version # Standardized location mapping location_map = { diff --git a/test/test_converter_cli.py b/test/test_converter_cli.py index 3447dbc..cab4d39 100644 --- a/test/test_converter_cli.py +++ b/test/test_converter_cli.py @@ -4,6 +4,7 @@ import tempfile from hpxml_version_translator import main +from hpxml_version_translator.converter import get_hpxml_versions hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_v2_files" @@ -30,7 +31,19 @@ def test_cli_to_v2(capsysbinary): / "hpxml_v1_files" / "version_change.xml" ) - main([input_filename, "-v", "2"]) + main([input_filename, "-v", "2.3"]) f = io.BytesIO(capsysbinary.readouterr().out) root = objectify.parse(f).getroot() assert root.attrib["schemaVersion"] == "2.3" + + +def test_schema_versions(): + hpxml_versions = get_hpxml_versions() + assert "3.0" in hpxml_versions + assert "2.3" in hpxml_versions + assert "1.1.1" not in hpxml_versions + + hpxml_versions = get_hpxml_versions(major_version=3) + assert "3.0" in hpxml_versions + assert "2.3" not in hpxml_versions + assert "1.1.1" not in hpxml_versions diff --git a/test/test_converter_v1to3.py b/test/test_converter_v1to3.py index 98dd823..eb6652b 100644 --- a/test/test_converter_v1to3.py +++ b/test/test_converter_v1to3.py @@ -8,7 +8,7 @@ hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_v1_files" -def convert_hpxml_and_parse(input_filename, version=3): +def convert_hpxml_and_parse(input_filename, version="3.0"): with tempfile.NamedTemporaryFile("w+b") as f_out: convert_hpxml_to_version(version, input_filename, f_out) f_out.seek(0) @@ -17,10 +17,15 @@ def convert_hpxml_and_parse(input_filename, version=3): def test_version_change_to_2(): - root = convert_hpxml_and_parse(hpxml_dir / "version_change.xml", 2) + root = convert_hpxml_and_parse(hpxml_dir / "version_change.xml", "2.3") assert root.attrib["schemaVersion"] == "2.3" +def test_version_change_to_2_2(): + root = convert_hpxml_and_parse(hpxml_dir / "version_change.xml", "2.2") + assert root.attrib["schemaVersion"] == "2.2" + + def test_version_change(): root = convert_hpxml_and_parse(hpxml_dir / "version_change.xml") assert root.attrib["schemaVersion"] == "3.0" diff --git a/test/test_converter_v2to3.py b/test/test_converter_v2to3.py index 8df1807..4b94031 100644 --- a/test/test_converter_v2to3.py +++ b/test/test_converter_v2to3.py @@ -13,7 +13,7 @@ hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_v2_files" -def convert_hpxml_and_parse(input_filename, version=3): +def convert_hpxml_and_parse(input_filename, version="3.0"): with tempfile.NamedTemporaryFile("w+b") as f_out: convert_hpxml_to_version(version, input_filename, f_out) f_out.seek(0) @@ -29,16 +29,17 @@ def test_version_change(): def test_attempt_to_change_to_same_version(): with pytest.raises( exc.HpxmlTranslationError, - match=r"HPXML version requested is 2 but input file version is 2", + match=r"HPXML version requested is 2\.3 but input file major version is 2", ): - convert_hpxml_and_parse(hpxml_dir / "version_change.xml", version=2) + convert_hpxml_and_parse(hpxml_dir / "version_change.xml", version="2.3") def test_attempt_to_use_nonexistent_version(): with pytest.raises( - exc.HpxmlTranslationError, match=r"HPXML version \d+ not available" + exc.HpxmlTranslationError, + match=r"HPXML version 5\.0 is not valid\. Must be one of", ): - convert_hpxml_and_parse(hpxml_dir / "version_change.xml", version=5) + convert_hpxml_and_parse(hpxml_dir / "version_change.xml", version="5.0") def test_convert_hpxml_to_3(): From d49a19f245fe2f1e42057e94a37d3b6b43947292 Mon Sep 17 00:00:00 2001 From: Noel Merket Date: Mon, 13 Dec 2021 10:56:25 -0700 Subject: [PATCH 10/10] adding tests for version checking in sub functions --- test/test_converter_v1to3.py | 14 +++++++++++++- test/test_converter_v2to3.py | 11 +++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/test/test_converter_v1to3.py b/test/test_converter_v1to3.py index eb6652b..578c024 100644 --- a/test/test_converter_v1to3.py +++ b/test/test_converter_v1to3.py @@ -1,8 +1,11 @@ +import io from lxml import objectify import pathlib +import pytest import tempfile -from hpxml_version_translator.converter import convert_hpxml_to_version +from hpxml_version_translator.converter import convert_hpxml_to_version, convert_hpxml1_to_2 +from hpxml_version_translator import exceptions as exc hpxml_dir = pathlib.Path(__file__).resolve().parent / "hpxml_v1_files" @@ -31,6 +34,15 @@ def test_version_change(): assert root.attrib["schemaVersion"] == "3.0" +def test_mismatch_version(): + f_out = io.BytesIO() + with pytest.raises( + exc.HpxmlTranslationError, + match=r"convert_hpxml2_to_3 must have valid target version of 3\.x" + ): + convert_hpxml1_to_2(hpxml_dir / "version_change.xml", f_out, "3.0") + + def test_water_heater_caz(): root = convert_hpxml_and_parse(hpxml_dir / "water_heater_caz.xml") diff --git a/test/test_converter_v2to3.py b/test/test_converter_v2to3.py index 4b94031..01fe436 100644 --- a/test/test_converter_v2to3.py +++ b/test/test_converter_v2to3.py @@ -1,3 +1,4 @@ +import io from lxml import objectify import pathlib import pytest @@ -6,6 +7,7 @@ from hpxml_version_translator.converter import ( convert_hpxml_to_3, convert_hpxml_to_version, + convert_hpxml2_to_3 ) from hpxml_version_translator import exceptions as exc @@ -51,6 +53,15 @@ def test_convert_hpxml_to_3(): assert root.attrib["schemaVersion"] == "3.0" +def test_mismatch_version(): + f_out = io.BytesIO() + with pytest.raises( + exc.HpxmlTranslationError, + match=r"convert_hpxml2_to_3 must have valid target version of 3\.x" + ): + convert_hpxml2_to_3(hpxml_dir / "version_change.xml", f_out, "2.0") + + def test_project_ids(): root = convert_hpxml_and_parse(hpxml_dir / "project_ids.xml") assert root.Project.PreBuildingID.attrib["id"] == "bldg1"