Repository: NREL/hescore-hpxml

Issue:
Handle ZIP+4 cleanly
HPXML allows for ZIP+4: https://hpxml.nrel.gov/datadictionary/3.0.0/Building/Site/Address/ZipCode

If an [HPXML file](https://github.com/NREL/hescore-hpxml/files/7171808/zip_plus_4.xml.txt) is submitted with a valid ZIP+4 entry then hpxml2hescore passes it with no changes to the JSON output:
```
  "building_address": {
    "address": "1 API House", 
    "city": "Minden", 
    "state": "NE", 
    "zip_code": "68959-1234", 
    "assessment_type": "initial"
  }, 
```
This results in the following error from submit_hpxml:
```
ZIP code 68959-1234 not found
```

**Expected behavior**
JSON output:
```
  "building_address": {
    "address": "1 API House", 
    "city": "Minden", 
    "state": "NE", 
    "zip_code": "68959", 
    "assessment_type": "initial"
  }, 
```
And no error from submit_hpxml

Discussion:

Patch:
diff --git a/docs/source/translation/building_address.rst b/docs/source/translation/building_address.rst
index 33449491..8fdf52c7 100644
--- a/docs/source/translation/building_address.rst
+++ b/docs/source/translation/building_address.rst
@@ -33,8 +33,10 @@ format for HEScore.
 
 HPXML allows for two lines of address elements. If both are used, the lines will
 be concatenated with a space between for submission to the HEScore
-``building_address.address`` field. All of the HPXML elements shown in the
-above code snippet are required with the exception of ``Address2``
+``building_address.address`` field. All of the HPXML elements shown in the above
+code snippet are required with the exception of ``Address2``. Additionally, if a
+zip plus 4 code is entered in HPXML, it will be trimmed to just the 5 digit zip
+code before being passed to HEScore.
 
 .. _assessment-type-mapping:
 
diff --git a/hescorehpxml/base.py b/hescorehpxml/base.py
index bfb1f4d5..169a39dd 100644
--- a/hescorehpxml/base.py
+++ b/hescorehpxml/base.py
@@ -816,7 +816,8 @@ class HPXMLtoHEScoreTranslatorBase(object):
             raise ElementNotFoundError(hpxmladdress, 'h:Address1/text() | h:Address2/text()', {})
         bldgaddr['city'] = xpath(b, 'h:Site/h:Address/h:CityMunicipality/text()', raise_err=True)
         bldgaddr['state'] = xpath(b, 'h:Site/h:Address/h:StateCode/text()', raise_err=True)
-        bldgaddr['zip_code'] = xpath(b, 'h:Site/h:Address/h:ZipCode/text()', raise_err=True)
+        hpxml_zipcode = xpath(b, 'h:Site/h:Address/h:ZipCode/text()', raise_err=True)
+        bldgaddr['zip_code'] = re.match(r"([0-9]{5})(-[0-9]{4})?", hpxml_zipcode).group(1)
         transaction_type = xpath(self.hpxmldoc, 'h:XMLTransactionHeaderInformation/h:Transaction/text()')
         is_mentor = xpath(b, 'boolean(h:ProjectStatus/h:extension/h:HEScoreMentorAssessment)')
         if is_mentor:


Test patch:
diff --git a/tests/test_translation.py b/tests/test_translation.py
index 117daa04..ff02c0ff 100644
--- a/tests/test_translation.py
+++ b/tests/test_translation.py
@@ -3048,6 +3048,14 @@ class TestHEScore2021Updates(unittest.TestCase, ComparatorBase):
         self.assertEqual(res3['building']['zone']['zone_roof'][1]['zone_skylight']['skylight_code'], 'dtab')
         self.assertFalse(res3['building']['zone']['zone_roof'][1]['zone_skylight']['solar_screen'])
 
+    def test_zip_plus4(self):
+        tr = self._load_xmlfile('hescore_min_v3')
+        el = self.xpath('//h:ZipCode')
+        orig_zipcode = str(el.text)
+        el.text = el.text + '-1234'
+        res = tr.hpxml_to_hescore()
+        self.assertEqual(res['building_address']['zip_code'], orig_zipcode)
+
 
 class TestHEScoreV3(unittest.TestCase, ComparatorBase):