Repository: NREL/hescore-hpxml

Issue:
Translate AirInfiltrationMeasurement without TypeOfInfiltrationMeasurement
**Is your feature request related to a problem? Please describe.**
Currently, the translator requires `TypeOfInfiltrationMeasurement` to translate `AirInfiltrationMeasurement`. The translator does not correctly handle `AirInfiltrationMeasurement` in HPXML files generated by `BuildResidentialHPXML` because they do not have `TypeOfInfiltrationMeasurement`.

**Describe the solution you'd like**
Translate `AirInfiltrationMeasurement` without `TypeOfInfiltrationMeasurement` into "blower_door_test" in HEScore JSON files.

**Describe alternatives you've considered**
Translate `AirInfiltrationMeasurement` if there is a CFM50 or ACH50 measurement even without `TypeOfInfiltrationMeasurement`.

**Additional context**
Add any other context or screenshots about the feature request here.


Discussion:

Patch:
diff --git a/docs/source/translation/about.rst b/docs/source/translation/about.rst
index 6eebb65c..637f8561 100644
--- a/docs/source/translation/about.rst
+++ b/docs/source/translation/about.rst
@@ -131,7 +131,7 @@ is available in HPXML.
 
 Blower Door Test
 ================
-The translator first looks for a blower door test (not an estimate) with units
+The translator first looks for an ``AirInfiltrationMeasurement`` with units
 of :term:`CFM50`. If more than one of the ``AirInfiltrationMeasurement``
 elements have units in :term:`CFM50`, the last one to appear in the document is
 used. If there are no measurements in :term:`CFM50`, it will look for one in
@@ -146,7 +146,6 @@ An example of the minimum expected elements in HPXML follows:
 
    <AirInfiltrationMeasurement>
       <SystemIdentifier id="infilt1"/>
-      <TypeOfInfiltrationMeasurement>blower door</TypeOfInfiltrationMeasurement>
       <HousePressure>50</HousePressure><!-- Must be 50 -->
       <BuildingAirLeakage>
          <UnitofMeasure>CFM<!-- or ACH --></UnitofMeasure>
@@ -165,7 +164,6 @@ example of the minimum expected elements in that case looks like:
 
    <AirInfiltrationMeasurement>
       <SystemIdentifier id="infilt2"/>
-      <TypeOfInfiltrationMeasurement>estimate</TypeOfInfiltrationMeasurement>
       <LeakinessDescription>tight</LeakinessDescription>
    </AirInfiltrationMeasurement>
 
@@ -188,12 +186,8 @@ mapping from ``LeakinessDescription``:
 
 If none of the ``AirInfiltrationMeasurement`` elements meet the criteria above
 to specify an estimate, the building is assumed to not have air sealing present.
-
-.. note::
-
-   If a building has no ``AirInfiltrationMeasurement`` elements in the 
-   HPXML document, the house is assumed to not be air sealed.
-   
+If a building has no ``AirInfiltrationMeasurement`` elements in the HPXML
+document, the house is assumed to not be air sealed.
 
 Comments
 ********
diff --git a/examples/house9.json b/examples/house9.json
index 97013905..7875bb77 100644
--- a/examples/house9.json
+++ b/examples/house9.json
@@ -8,7 +8,7 @@
   },
   "building": {
     "about": {
-      "assessment_date": "2022-02-15",
+      "assessment_date": "2014-12-02",
       "shape": "rectangle",
       "year_built": 1950,
       "number_bedrooms": 4,
@@ -16,8 +16,8 @@
       "floor_to_ceiling_height": 8,
       "conditioned_floor_area": 2600,
       "orientation": "south",
-      "blower_door_test": false,
-      "air_sealing_present": false
+      "blower_door_test": true,
+      "envelope_leakage": 2600
     },
     "zone": {
       "zone_roof": [
diff --git a/examples/house9.xml b/examples/house9.xml
index 005f3b24..f10a3b8b 100644
--- a/examples/house9.xml
+++ b/examples/house9.xml
@@ -44,6 +44,7 @@
     </Site>
     <ProjectStatus>
       <EventType>proposed workscope</EventType>
+      <Date>2014-12-02</Date>
     </ProjectStatus>
     <BuildingDetails>
       <BuildingSummary>
diff --git a/hescorehpxml/base.py b/hescorehpxml/base.py
index 355e712a..bab2bf3e 100644
--- a/hescorehpxml/base.py
+++ b/hescorehpxml/base.py
@@ -1034,14 +1034,16 @@ class HPXMLtoHEScoreTranslatorBase(object):
         for air_infilt_meas in b.xpath('h:BuildingDetails/h:Enclosure/h:AirInfiltration/h:AirInfiltrationMeasurement',
                                        namespaces=ns):
             # Take the last blower door test that is in CFM50, or if that's not available, ACH50
-            if xpath(air_infilt_meas, 'h:TypeOfInfiltrationMeasurement/text()') == 'blower door':
-                house_pressure = convert_to_type(int, xpath(air_infilt_meas, 'h:HousePressure/text()'))
-                blower_door_test_units = xpath(air_infilt_meas, 'h:BuildingAirLeakage/h:UnitofMeasure/text()')
-                if house_pressure == 50 and (blower_door_test_units == 'CFM' or
-                                             (blower_door_test_units == 'ACH' and blower_door_test is None)):
-                    blower_door_test = air_infilt_meas
-            elif xpath(air_infilt_meas, 'h:TypeOfInfiltrationMeasurement/text()') == 'estimate':
+            house_pressure = convert_to_type(float, xpath(air_infilt_meas, 'h:HousePressure/text()'))
+            blower_door_test_units = xpath(air_infilt_meas, 'h:BuildingAirLeakage/h:UnitofMeasure/text()')
+            if house_pressure == 50 and (blower_door_test_units == 'CFM' or
+                                         (blower_door_test_units == 'ACH' and blower_door_test is None)):
+                blower_door_test = air_infilt_meas
+            else:
                 air_infilt_est = air_infilt_meas
+        if b.xpath('count(h:BuildingDetails/h:Enclosure/h:AirInfiltration/h:AirInfiltrationMeasurement\
+                /h:BuildingAirLeakage)', namespaces=ns) > 0 and blower_door_test is None:
+            raise TranslationError('BuildingAirLeakage/UnitofMeasure must be either "CFM50" or "ACH50"')
         if blower_door_test is not None:
             bldg_about['blower_door_test'] = True
             if xpath(blower_door_test, 'h:BuildingAirLeakage/h:UnitofMeasure/text()') == 'CFM':
@@ -1053,8 +1055,6 @@ class HPXMLtoHEScoreTranslatorBase(object):
                                                  float(xpath(blower_door_test,
                                                              'h:BuildingAirLeakage/h:AirLeakage/text()',
                                                              raise_err=True)) / 60.
-            else:
-                raise TranslationError('BuildingAirLeakage/UnitofMeasure must be either "CFM" or "ACH"')
             bldg_about['envelope_leakage'] = int(python2round(bldg_about['envelope_leakage']))
         else:
             bldg_about['blower_door_test'] = False


Test patch:
diff --git a/tests/test_translation.py b/tests/test_translation.py
index 214cf134..e35c8833 100644
--- a/tests/test_translation.py
+++ b/tests/test_translation.py
@@ -106,6 +106,9 @@ class TestAPIHouses(unittest.TestCase, ComparatorBase):
     def test_house8(self):
         self._do_full_compare('house8')
 
+    def test_house9(self):
+        self._do_full_compare('house9')
+
     def test_assembly_rvalue(self):
         self._do_full_compare('hescore_min_assembly_rvalue')
 
@@ -309,6 +312,14 @@ class TestOtherHouses(unittest.TestCase, ComparatorBase):
                                r'Cannot translate HPXML ResidentialFacilityType of .+ into HEScore building shape',
                                tr_v3.hpxml_to_hescore)
 
+    def test_invalid_infiltration_unit_of_measure(self):
+        tr = self._load_xmlfile('hescore_min')
+        el = self.xpath('//h:BuildingAirLeakage/h:UnitofMeasure')
+        el.text = 'CFMnatural'
+        self.assertRaisesRegex(TranslationError,
+                               r'BuildingAirLeakage/UnitofMeasure must be either "CFM50" or "ACH50"',
+                               tr.hpxml_to_hescore)
+
     def test_missing_surroundings(self):
         tr = self._load_xmlfile('townhouse_walls')
         el = self.xpath('//h:Surroundings')