Repository: agrc/sweeper

Issue:
State Routes SR
Addresses like 910 S SR 22 or 1910 N US HWY 89
will return 22 and 89 for the street_name

Discussion:
ZachBeck: I'll check the address points but we might also be dealing with cases like S.R., State Route, U.S. Highway etc
steveoh: I added some more tests. I think once these pass We should be good to go.
stdavis: Thanks, @steveoh!
stdavis: > I'll check the address points but we might also be dealing with cases like S.R., State Route, U.S. Highway etc

These should all work now.

Patch:
diff --git a/.gitignore b/.gitignore
index 9fbddef..1fe895c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
 __pycache__
 dist/
 build/
+.env/
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1d21cf6..40c0fd3 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -12,6 +12,7 @@
         "pformat",
         "posix",
         "pypi",
+        "pytest",
         "sdist",
         "usaddress",
         "xxhash"
diff --git a/readme.md b/readme.md
index b9dcff1..d9d1c5f 100644
--- a/readme.md
+++ b/readme.md
@@ -80,7 +80,7 @@ A normalized string representing the entire address that was passed into the con
 1. install required dependencies to work on sweeper
     - `pip install -e ".[develop]"`
 1. install required dependencies to run sweeper tests
-    - `pip install -e ".[test]"`
+    - `pip install -e ".[tests]"`
 1. run tests: `pytest`
 
 ### Uploading to pypi.org
diff --git a/src/sweeper/address_parser.py b/src/sweeper/address_parser.py
index 5e61825..5cccd1d 100644
--- a/src/sweeper/address_parser.py
+++ b/src/sweeper/address_parser.py
@@ -7,19 +7,20 @@ A module that parses street addresses into their various parts.
 import json
 import pprint
 from os.path import dirname, join, realpath
+from re import compile
 
 import usaddress
 
 TAG_MAPPING = {
     'AddressNumber': 'address_number',
-    # 'AddressNumberPrefix': 'address1',
+    'AddressNumberPrefix': 'address_number',
     'AddressNumberSuffix': 'address_number_suffix',
     'StreetNamePreDirectional': 'prefix_direction',
     'StreetName': 'street_name',
-    # 'StreetNamePreModifier': 'address1',
-    # 'StreetNamePreType': 'address1',
+    # 'StreetNamePreModifier': 'street_name', #: handled in class below
+    # 'StreetNamePreType': 'street_name', #: handled in class below
     'StreetNamePostDirectional': 'street_direction',
-    # 'StreetNamePostModifier': 'address1',
+    'StreetNamePostModifier': 'street_type',
     'StreetNamePostType': 'street_type',
     # 'CornerOf': 'address1',
     # 'IntersectionSeparator': 'address1',
@@ -28,7 +29,7 @@ TAG_MAPPING = {
     # 'USPSBoxGroupType': 'address1',
     # 'USPSBoxID': 'address1',
     # 'USPSBoxType': 'address1',
-    # 'BuildingName': 'address2',
+    'BuildingName': 'unit_id',
     'OccupancyType': 'unit_type',
     'OccupancyIdentifier': 'unit_id',
     # 'SubaddressIdentifier': 'address2',
@@ -41,6 +42,7 @@ TAG_MAPPING = {
 TWO_CHAR_DIRECTIONS = ['NO', 'SO', 'EA', 'WE']
 with open(join(dirname(realpath(__file__)), 'street_types.json'), 'r') as file:
     STREET_TYPES = json.loads(file.read())
+HWY_REGEX = compile('(SR|STATE ROUTE|HIGHWAY)')
 
 
 class Address():
@@ -77,6 +79,19 @@ class Address():
         if self.po_box is not None:
             return
 
+        try:
+            #: e.g. US HWY
+            self.street_name = f'{normalize_street_name_pre_type(self.StreetNamePreType)} {self.street_name}'
+            del self.StreetNamePreType
+        except AttributeError:
+            pass
+
+        try:
+            self.street_name = f'{self.StreetNamePreModifier} {self.street_name}'
+            del self.StreetNamePreModifier
+        except AttributeError:
+            pass
+
         #: look for two-character prefix directions which usaddress does not handle
         if self.street_name:
             street_name_parts = self.street_name.split(' ')
@@ -135,6 +150,7 @@ def normalize_direction(direction_text):
 
     return direction_text[0].upper()
 
+
 def normalize_street_type(type_text):
     '''
     returns the standard abbreviation for the input street type
@@ -148,6 +164,14 @@ def normalize_street_type(type_text):
     raise InvalidStreetTypeError(type_text)
 
 
+def normalize_street_name_pre_type(text):
+    '''normalizes highways by doing things like replaces SR with HWY and removes US
+
+    No need to worried about casing or "."s because usaddress has already taken care of them by this point.
+    '''
+    return HWY_REGEX.sub('HWY', text).replace('US ', '')
+
+
 class InvalidStreetTypeError(Exception):
     '''
     exception for when the street type does not have a corresponding value in street_types.json


Test patch:
diff --git a/src/sweeper/tests/test_address_parser.py b/src/sweeper/tests/test_address_parser.py
index c4f2caf..ae08739 100644
--- a/src/sweeper/tests/test_address_parser.py
+++ b/src/sweeper/tests/test_address_parser.py
@@ -386,3 +386,89 @@ class TestBadAddresses():
         assert address.street_name is None
         assert address.address_number == '100'
         assert address.prefix_direction == 'S'
+
+
+class TestHighways():
+    '''tests to make sure that state routes and us highways are parsed correctly
+    '''
+
+    def test_state_routes(self):
+        address = Address('910 S SR 22')
+
+        assert address.address_number == '910'
+        assert address.prefix_direction == 'S'
+        assert address.street_name == 'HWY 22'
+        assert address.normalized == '910 S HWY 22'
+
+    def test_state_route_expanded(self):
+        address = Address('910 S State Route 22')
+
+        assert address.address_number == '910'
+        assert address.prefix_direction == 'S'
+        assert address.street_name == 'HWY 22'
+        assert address.normalized == '910 S HWY 22'
+
+    def test_state_route_with_punctuation(self):
+        address = Address('910 S S.R. 22')
+
+        assert address.address_number == '910'
+        assert address.prefix_direction == 'S'
+        assert address.street_name == 'HWY 22'
+        assert address.normalized == '910 S HWY 22'
+
+    def test_state_route_casing(self):
+        address = Address('910 S sr 22')
+
+        assert address.address_number == '910'
+        assert address.prefix_direction == 'S'
+        assert address.street_name == 'HWY 22'
+        assert address.normalized == '910 S HWY 22'
+
+
+    def test_highways(self):
+        address = Address('1910 N US HWY 89')
+
+        assert address.address_number == '1910'
+        assert address.prefix_direction == 'N'
+        assert address.street_name == 'HWY 89'
+        assert address.normalized == '1910 N HWY 89'
+
+        address = Address('1106 S OLD HWY 89')
+
+        assert address.address_number == '1106'
+        assert address.prefix_direction == 'S'
+        assert address.street_name == 'OLD HWY 89'
+        assert address.normalized == '1106 S OLD HWY 89'
+
+    def test_highway_expanded(self):
+        address = Address('1910 N US highway 89')
+
+        assert address.address_number == '1910'
+        assert address.prefix_direction == 'N'
+        assert address.street_name == 'HWY 89'
+        assert address.normalized == '1910 N HWY 89'
+
+    def test_highway_with_punctuation(self):
+        address = Address('1910 N U.S. highway 89')
+
+        assert address.address_number == '1910'
+        assert address.prefix_direction == 'N'
+        assert address.street_name == 'HWY 89'
+        assert address.normalized == '1910 N HWY 89'
+
+    def test_highway_casing(self):
+        address = Address('1910 N u.s. highway 89')
+
+        assert address.address_number == '1910'
+        assert address.prefix_direction == 'N'
+        assert address.street_name == 'HWY 89'
+        assert address.normalized == '1910 N HWY 89'
+
+    def test_street_name_with_sr(self):
+        address = Address('1910 s woodsrow dr')
+
+        assert address.address_number == '1910'
+        assert address.prefix_direction == 'S'
+        assert address.street_name == 'WOODSROW'
+        assert address.street_type == 'DR'
+        assert address.normalized == '1910 S WOODSROW DR'