Repository: openaddresses/machine
Pull request #647 (2017-06-08 03:27:22)

added support for fractional house numbers
Modifies `prefixed_number` and `postfixed_street` regexes to parse fractional house numbers:

- `123 1/2 Main Street` into `123 1/2` (number) and `Main Street` (street)
- `123-3/4 Main Street` into `123-3/4` (number) and `Main Street` (street)

Also reorganizes regexes for clarity.  


Patch:
diff --git a/CHANGELOG b/CHANGELOG
index 0de0fd7..b220b60 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,3 @@
-2017-06-07: 6.3.2
-- Muffled GDAL warnings in openaddr-calculate-coverage. (#646)
-
 2017-06-01: 6.3.1
 - Updated to use new-style Docker Hub tags. (#644)
 
diff --git a/openaddr/VERSION b/openaddr/VERSION
index 91e4a9f..dc0208a 100644
--- a/openaddr/VERSION
+++ b/openaddr/VERSION
@@ -1,1 +1,1 @@
-6.3.2
+6.3.1
diff --git a/openaddr/ci/coverage/calculate.py b/openaddr/ci/coverage/calculate.py
index 1e57254..b121bf7 100644
--- a/openaddr/ci/coverage/calculate.py
+++ b/openaddr/ci/coverage/calculate.py
@@ -6,7 +6,7 @@ from urllib.parse import urljoin
 
 import requests
 import psycopg2
-from osgeo import ogr, gdal
+from osgeo import ogr
 
 from .. import setup_logger
 
@@ -245,9 +245,6 @@ parser.add_argument('-q', '--quiet', help='Turn off most logging',
 def main():
     '''
     '''
-    # This should silence OGR's annoying geometry warnings.
-    gdal.PushErrorHandler('CPLQuietErrorHandler')
-
     args = parser.parse_args()
     setup_logger(args.sns_arn, None, log_level=args.loglevel)
     calculate(args.database_url)
diff --git a/openaddr/conform.py b/openaddr/conform.py
index f0c4e86..0eae56c 100644
--- a/openaddr/conform.py
+++ b/openaddr/conform.py
@@ -85,17 +85,30 @@ geometry_types = {
 
 # extracts:
 # - '123' from '123 Main St'
+# - '123 1/2' from '123 1/2 Main St'
+# - '123-1/2' from '123-1/2 Main St'
+# - '123-1' from '123-1 Main St'
 # - '123a' from '123a Main St'
 # - '123-a' from '123-a Main St'
 # - '' from '3rd St' (the 3 belongs to the street, it's not a house number)
-prefixed_number_pattern = re.compile("^\s*(\d+-?(?:[A-Z]|\d+)?\\b)", re.IGNORECASE)
+# 
+# this regex can be optimized but number scenarios are much cleaner this way:
+# - just digits with optional fractional
+# - two groups of digits separated by a hyphen (for queens-style addresses, eg - 69-15 51st Ave) 
+# - digits and a letter, optionally separated by a hyphen
+prefixed_number_pattern = re.compile("^\s*(\d+(?:[ -]\d/\d)?|\d+-\d+|\d+-?[A-Z])\s+", re.IGNORECASE)
 
 # extracts:
 # - 'Main St' from '123 Main St'
+# - 'Main St' from '123 1/2 Main St'
+# - 'Main St' from '123-1/2 Main St'
+# - 'Main St' from '123-1 Main St'
 # - 'Main St' from '123a Main St'
 # - 'Main St' from '123-a Main St'
 # - 'Main St' from 'Main St'
-postfixed_street_pattern = re.compile("^\s*(?:\d+-?(?:[A-Z]|\d+)?\\b)?\s*(.*)", re.IGNORECASE)
+#
+# like prefixed_number_pattern, this regex can be optimized but this is cleaner
+postfixed_street_pattern = re.compile("^(?:\s*(?:\d+(?:[ -]\d/\d)?|\d+-\d+|\d+-?[A-Z])\s+)?(.*)", re.IGNORECASE)
 
 def mkdirsp(path):
     try:


Test patch:
diff --git a/openaddr/tests/conform.py b/openaddr/tests/conform.py
index 08433bd..741f8f1 100644
--- a/openaddr/tests/conform.py
+++ b/openaddr/tests/conform.py
@@ -557,6 +557,158 @@ class TestConformTransforms (unittest.TestCase):
         d = row_fxn_prefixed_number(c, d, "number", c["conform"]["number"])
         d = row_fxn_postfixed_street(c, d, "street", c["conform"]["street"])
         self.assertEqual(e, d)
+        
+        "Regex prefixed_number and postfixed_street - should honor space+1/2"
+        c = { "conform": {
+            "number": {
+                "function": "prefixed_number",
+                "field": "ADDRESS"
+            },
+            "street": {
+                "function": "postfixed_street",
+                "field": "ADDRESS"
+            }
+        } }
+        d = { "ADDRESS": "123 1/2 3rD St" }
+        e = copy.deepcopy(d)
+        e.update({ "OA:number": "123 1/2", "OA:street": "3rD St" })
+        
+        d = row_fxn_prefixed_number(c, d, "number", c["conform"]["number"])
+        d = row_fxn_postfixed_street(c, d, "street", c["conform"]["street"])
+        self.assertEqual(e, d)        
+
+        "Regex prefixed_number and postfixed_street - should honor hyphen+1/2"
+        c = { "conform": {
+            "number": {
+                "function": "prefixed_number",
+                "field": "ADDRESS"
+            },
+            "street": {
+                "function": "postfixed_street",
+                "field": "ADDRESS"
+            }
+        } }
+        d = { "ADDRESS": "123-1/2 3rD St" }
+        e = copy.deepcopy(d)
+        e.update({ "OA:number": "123-1/2", "OA:street": "3rD St" })
+        
+        d = row_fxn_prefixed_number(c, d, "number", c["conform"]["number"])
+        d = row_fxn_postfixed_street(c, d, "street", c["conform"]["street"])
+        self.assertEqual(e, d)        
+
+        "Regex prefixed_number and postfixed_street - should honor space+1/3"
+        c = { "conform": {
+            "number": {
+                "function": "prefixed_number",
+                "field": "ADDRESS"
+            },
+            "street": {
+                "function": "postfixed_street",
+                "field": "ADDRESS"
+            }
+        } }
+        d = { "ADDRESS": "123 1/3 3rD St" }
+        e = copy.deepcopy(d)
+        e.update({ "OA:number": "123 1/3", "OA:street": "3rD St" })
+        
+        d = row_fxn_prefixed_number(c, d, "number", c["conform"]["number"])
+        d = row_fxn_postfixed_street(c, d, "street", c["conform"]["street"])
+        self.assertEqual(e, d)        
+
+        "Regex prefixed_number and postfixed_street - should honor hyphen+1/3"
+        c = { "conform": {
+            "number": {
+                "function": "prefixed_number",
+                "field": "ADDRESS"
+            },
+            "street": {
+                "function": "postfixed_street",
+                "field": "ADDRESS"
+            }
+        } }
+        d = { "ADDRESS": "123-1/3 3rD St" }
+        e = copy.deepcopy(d)
+        e.update({ "OA:number": "123-1/3", "OA:street": "3rD St" })
+        
+        d = row_fxn_prefixed_number(c, d, "number", c["conform"]["number"])
+        d = row_fxn_postfixed_street(c, d, "street", c["conform"]["street"])
+        self.assertEqual(e, d)        
+
+        "Regex prefixed_number and postfixed_street - should honor space+1/4"
+        c = { "conform": {
+            "number": {
+                "function": "prefixed_number",
+                "field": "ADDRESS"
+            },
+            "street": {
+                "function": "postfixed_street",
+                "field": "ADDRESS"
+            }
+        } }
+        d = { "ADDRESS": "123 1/4 3rD St" }
+        e = copy.deepcopy(d)
+        e.update({ "OA:number": "123 1/4", "OA:street": "3rD St" })
+        
+        d = row_fxn_prefixed_number(c, d, "number", c["conform"]["number"])
+        d = row_fxn_postfixed_street(c, d, "street", c["conform"]["street"])
+        self.assertEqual(e, d)        
+
+        "Regex prefixed_number and postfixed_street - should honor hyphen+1/4"
+        c = { "conform": {
+            "number": {
+                "function": "prefixed_number",
+                "field": "ADDRESS"
+            },
+            "street": {
+                "function": "postfixed_street",
+                "field": "ADDRESS"
+            }
+        } }
+        d = { "ADDRESS": "123-1/4 3rD St" }
+        e = copy.deepcopy(d)
+        e.update({ "OA:number": "123-1/4", "OA:street": "3rD St" })
+        
+        d = row_fxn_prefixed_number(c, d, "number", c["conform"]["number"])
+        d = row_fxn_postfixed_street(c, d, "street", c["conform"]["street"])
+        self.assertEqual(e, d)        
+
+        "Regex prefixed_number and postfixed_street - should honor space+3/4"
+        c = { "conform": {
+            "number": {
+                "function": "prefixed_number",
+                "field": "ADDRESS"
+            },
+            "street": {
+                "function": "postfixed_street",
+                "field": "ADDRESS"
+            }
+        } }
+        d = { "ADDRESS": "123 3/4 3rD St" }
+        e = copy.deepcopy(d)
+        e.update({ "OA:number": "123 3/4", "OA:street": "3rD St" })
+        
+        d = row_fxn_prefixed_number(c, d, "number", c["conform"]["number"])
+        d = row_fxn_postfixed_street(c, d, "street", c["conform"]["street"])
+        self.assertEqual(e, d)        
+
+        "Regex prefixed_number and postfixed_street - should honor hyphen+3/4"
+        c = { "conform": {
+            "number": {
+                "function": "prefixed_number",
+                "field": "ADDRESS"
+            },
+            "street": {
+                "function": "postfixed_street",
+                "field": "ADDRESS"
+            }
+        } }
+        d = { "ADDRESS": "123-3/4 3rD St" }
+        e = copy.deepcopy(d)
+        e.update({ "OA:number": "123-3/4", "OA:street": "3rD St" })
+        
+        d = row_fxn_prefixed_number(c, d, "number", c["conform"]["number"])
+        d = row_fxn_postfixed_street(c, d, "street", c["conform"]["street"])
+        self.assertEqual(e, d)        
 
     def test_row_fxn_remove_prefix(self):
         "remove_prefix - field_to_remove is a prefix"