street_sweeper 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,173 @@
1
+ module StreetSweeper
2
+ module Matchers
3
+ class << self
4
+ attr_accessor(
5
+ :street_type_regexp,
6
+ :street_type_matches,
7
+ :number_regexp,
8
+ :fraction_regexp,
9
+ :state_regexp,
10
+ :city_and_state_regexp,
11
+ :direct_regexp,
12
+ :zip_regexp,
13
+ :corner_regexp,
14
+ :unit_regexp,
15
+ :street_regexp,
16
+ :po_street_regexp,
17
+ :place_regexp,
18
+ :address_regexp,
19
+ :po_address_regexp,
20
+ :informal_address_regexp,
21
+ :dircode_regexp,
22
+ :unit_prefix_numbered_regexp,
23
+ :unit_prefix_unnumbered_regexp,
24
+ :unit_regexp,
25
+ :sep_regexp,
26
+ :sep_avoid_unit_regexp,
27
+ :intersection_regexp
28
+ )
29
+ end
30
+
31
+ self.street_type_matches = {}
32
+ Constants::STREET_TYPES.each_pair do |type, abbrv|
33
+ street_type_matches[abbrv] = /\b (?: #{abbrv}|#{Regexp.quote(type)} ) \b/ix
34
+ end
35
+
36
+ self.street_type_regexp = Regexp.new(Constants::STREET_TYPES_LIST.keys.join('|'), Regexp::IGNORECASE)
37
+ self.fraction_regexp = /\d+\/\d+/
38
+ self.state_regexp = Regexp.new(
39
+ '\b' + Constants::STATE_CODES.flatten.map { |code| Regexp.quote(code) }.join('|') + '\b',
40
+ Regexp::IGNORECASE
41
+ )
42
+ self.direct_regexp = Regexp.new(
43
+ (Constants::DIRECTIONAL.keys +
44
+ Constants::DIRECTIONAL.values.sort do |a, b|
45
+ b.length <=> a.length
46
+ end.map do |c|
47
+ f = c.gsub(/(\w)/, '\1.')
48
+ [Regexp.quote(f), Regexp.quote(c)]
49
+ end
50
+ ).join('|'),
51
+ Regexp::IGNORECASE
52
+ )
53
+ self.dircode_regexp = Regexp.new(Constants::DIRECTION_CODES.keys.join('|'), Regexp::IGNORECASE)
54
+ self.zip_regexp = /(?:(?<postal_code>\d{5})(?:-?(?<postal_code_ext>\d{4}))?)/
55
+ self.corner_regexp = /(?:\band\b|\bat\b|&|\@)/i
56
+
57
+ # we don't include letters in the number regex because we want to
58
+ # treat "42S" as "42 S" (42 South). For example,
59
+ # Utah and Wisconsin have a more elaborate system of block numbering
60
+ # http://en.wikipedia.org/wiki/House_number#Block_numbers
61
+ self.number_regexp = /(?<number>\d+-?\d*)(?=\D)/ix
62
+
63
+ # note that expressions like [^,]+ may scan more than you expect
64
+ self.street_regexp = /
65
+ (?:
66
+ # special case for addresses like 14168 W River Rd and 3301 N Park
67
+ # Blvd, where the street name matches one of the street types
68
+ (?:
69
+ (?<prefix> #{direct_regexp})\W+
70
+ (?<street> [^\d]+)\W+
71
+ (?<street_type> #{street_type_regexp})\b
72
+ )
73
+ |
74
+ # special case for addresses like 100 South Street
75
+ (?:(?<street> #{direct_regexp})\W+
76
+ (?<street_type> #{street_type_regexp})\b
77
+ )
78
+ |
79
+ (?:(?<prefix> #{direct_regexp})\W+)?
80
+ (?:
81
+ (?<street> [^,]*\d)
82
+ (?:[^\w,]* (?<suffix> #{direct_regexp})\b)
83
+ |
84
+ (?<street> [^,]+)
85
+ (?:[^\w,]+(?<street_type> #{street_type_regexp})\b)
86
+ (?:[^\w,]+(?<suffix> #{direct_regexp})\b)?
87
+ |
88
+ (?<street> [^,]+?)
89
+ (?:[^\w,]+(?<street_type> #{street_type_regexp})\b)?
90
+ (?:[^\w,]+(?<suffix> #{direct_regexp})\b)?
91
+ )
92
+ )
93
+ /ix
94
+
95
+ self.po_street_regexp = /^(?<street>p\.?o\.?\s?(?:box|\#)?\s\d\d*[-a-z]*)/ix
96
+
97
+ # http://pe.usps.com/text/pub28/pub28c2_003.htm
98
+ self.unit_prefix_numbered_regexp = /
99
+ (?<unit_prefix>
100
+ #{Constants::UNIT_ABBREVIATIONS_NUMBERED.keys.join("|")}
101
+ )(?![a-z])/ix
102
+
103
+ self.unit_prefix_unnumbered_regexp = /
104
+ (?<unit_prefix>
105
+ #{Constants::UNIT_ABBREVIATIONS_UNNUMBERED.keys.join("|")}
106
+ )\b/ix
107
+
108
+ self.unit_regexp = /
109
+ (?:
110
+ (?: (?:#{unit_prefix_numbered_regexp} \W*)
111
+ | (?<unit_prefix> \#)\W*
112
+ )
113
+ (?<unit> [\w-]+)
114
+ )
115
+ |
116
+ #{unit_prefix_unnumbered_regexp}
117
+ /ix
118
+
119
+ self.city_and_state_regexp = /
120
+ (?:
121
+ (?<city> [^\d,]+?)\W+
122
+ (?<state> #{state_regexp})
123
+ )
124
+ /ix
125
+
126
+ self.place_regexp = /
127
+ (?:#{city_and_state_regexp}\W*)? (?:#{zip_regexp})?
128
+ /ix
129
+
130
+ self.address_regexp = /
131
+ \A
132
+ [^\w\x23]* # skip non-word chars except # (eg unit)
133
+ #{number_regexp} \W*
134
+ (?:#{fraction_regexp}\W*)?
135
+ #{street_regexp}\W+
136
+ (?:#{unit_regexp}\W+)?
137
+ #{place_regexp}
138
+ \W* # require on non-word chars at end
139
+ \z # right up to end of string
140
+ /ix
141
+
142
+ self.po_address_regexp = /
143
+ \A
144
+ #{po_street_regexp} \W*
145
+ #{place_regexp}
146
+ \W* # require on non-word chars at end
147
+ \z # right up to end of string
148
+ /ix
149
+
150
+ self.sep_regexp = /(?:\W+|\Z)/
151
+ self.sep_avoid_unit_regexp = /(?:[^\#\w]+|\Z)/
152
+
153
+ self.informal_address_regexp = /
154
+ \A
155
+ \s* # skip leading whitespace
156
+ (?:#{unit_regexp} #{sep_regexp})?
157
+ (?:#{number_regexp})? \W*
158
+ (?:#{fraction_regexp} \W*)?
159
+ #{street_regexp} #{sep_avoid_unit_regexp}
160
+ (?:#{unit_regexp} #{sep_regexp})?
161
+ (?:#{place_regexp})?
162
+ # don't require match to reach end of string
163
+ /ix
164
+
165
+ self.intersection_regexp = /\A\W*
166
+ #{street_regexp}\W*?
167
+ \s+#{corner_regexp}\s+
168
+ #{street_regexp}\W+
169
+ #{place_regexp}
170
+ \W*\z
171
+ /ix
172
+ end
173
+ end
@@ -0,0 +1,3 @@
1
+ module StreetSweeper
2
+ VERSION = '1.0.0'.freeze
3
+ end
@@ -0,0 +1,530 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe StreetSweeper::Address do
6
+ NORMAL_ADDRESSES = {
7
+ '1005 Gravenstein Hwy 95472' => {
8
+ line1: '1005 Gravenstein Hwy',
9
+ line2: '95472',
10
+ street_address_1: '1005 Gravenstein Hwy',
11
+ street_address_2: '',
12
+ full_street_address: '1005 Gravenstein Hwy, 95472',
13
+ intersection?: false
14
+ },
15
+ '1005 Gravenstein Hwy, 95472' => {
16
+ line1: '1005 Gravenstein Hwy',
17
+ line2: '95472',
18
+ street_address_1: '1005 Gravenstein Hwy',
19
+ street_address_2: '',
20
+ full_street_address: '1005 Gravenstein Hwy, 95472',
21
+ intersection?: false
22
+ },
23
+ '1005 Gravenstein Hwy N, 95472' => {
24
+ line1: '1005 Gravenstein Hwy N',
25
+ line2: '95472',
26
+ street_address_1: '1005 Gravenstein Hwy N',
27
+ street_address_2: '',
28
+ full_street_address: '1005 Gravenstein Hwy N, 95472',
29
+ intersection?: false
30
+ },
31
+ '1005 Gravenstein Highway North, 95472' => {
32
+ line1: '1005 Gravenstein Hwy N',
33
+ line2: '95472',
34
+ street_address_1: '1005 Gravenstein Hwy N',
35
+ street_address_2: '',
36
+ full_street_address: '1005 Gravenstein Hwy N, 95472',
37
+ intersection?: false
38
+ },
39
+ '1005 N Gravenstein Highway, Sebastopol, CA' => {
40
+ line1: '1005 N Gravenstein Hwy',
41
+ line2: 'Sebastopol, CA',
42
+ street_address_1: '1005 N Gravenstein Hwy',
43
+ street_address_2: '',
44
+ full_street_address: '1005 N Gravenstein Hwy, Sebastopol, CA',
45
+ intersection?: false
46
+ },
47
+ '1005 N Gravenstein Highway, Suite 500, Sebastopol, CA' => {
48
+ line1: '1005 N Gravenstein Hwy Ste 500',
49
+ line2: 'Sebastopol, CA',
50
+ street_address_1: '1005 N Gravenstein Hwy',
51
+ street_address_2: 'Ste 500',
52
+ full_street_address: '1005 N Gravenstein Hwy Ste 500, Sebastopol, CA',
53
+ intersection?: false
54
+ },
55
+ '1005 N Gravenstein Hwy Suite 500 Sebastopol, CA' => {
56
+ line1: '1005 N Gravenstein Hwy Ste 500',
57
+ line2: 'Sebastopol, CA',
58
+ street_address_1: '1005 N Gravenstein Hwy',
59
+ street_address_2: 'Ste 500',
60
+ full_street_address: '1005 N Gravenstein Hwy Ste 500, Sebastopol, CA',
61
+ intersection?: false
62
+ },
63
+ '1005 N Gravenstein Highway, Sebastopol, CA, 95472' => {
64
+ line1: '1005 N Gravenstein Hwy',
65
+ line2: 'Sebastopol, CA 95472',
66
+ street_address_1: '1005 N Gravenstein Hwy',
67
+ street_address_2: '',
68
+ full_street_address: '1005 N Gravenstein Hwy, Sebastopol, CA 95472',
69
+ intersection?: false
70
+ },
71
+ '1005 N Gravenstein Highway Sebastopol CA 95472' => {
72
+ line1: '1005 N Gravenstein Hwy',
73
+ line2: 'Sebastopol, CA 95472',
74
+ street_address_1: '1005 N Gravenstein Hwy',
75
+ street_address_2: '',
76
+ full_street_address: '1005 N Gravenstein Hwy, Sebastopol, CA 95472',
77
+ intersection?: false
78
+ },
79
+ '1005 Gravenstein Hwy N Sebastopol CA' => {
80
+ line1: '1005 Gravenstein Hwy N',
81
+ line2: 'Sebastopol, CA',
82
+ street_address_1: '1005 Gravenstein Hwy N',
83
+ street_address_2: '',
84
+ full_street_address: '1005 Gravenstein Hwy N, Sebastopol, CA',
85
+ intersection?: false
86
+ },
87
+ '1005 Gravenstein Hwy N, Sebastopol CA' => {
88
+ line1: '1005 Gravenstein Hwy N',
89
+ line2: 'Sebastopol, CA',
90
+ street_address_1: '1005 Gravenstein Hwy N',
91
+ street_address_2: '',
92
+ full_street_address: '1005 Gravenstein Hwy N, Sebastopol, CA',
93
+ intersection?: false
94
+ },
95
+ '1005 Gravenstein Hwy, N Sebastopol CA' => {
96
+ line1: '1005 Gravenstein Hwy',
97
+ line2: 'North Sebastopol, CA',
98
+ street_address_1: '1005 Gravenstein Hwy',
99
+ street_address_2: '',
100
+ full_street_address: '1005 Gravenstein Hwy, North Sebastopol, CA',
101
+ intersection?: false
102
+ },
103
+ '1005 Gravenstein Hwy Sebastopol CA' => {
104
+ line1: '1005 Gravenstein Hwy',
105
+ line2: 'Sebastopol, CA',
106
+ street_address_1: '1005 Gravenstein Hwy',
107
+ street_address_2: '',
108
+ full_street_address: '1005 Gravenstein Hwy, Sebastopol, CA',
109
+ intersection?: false
110
+ },
111
+ '115 Broadway San Francisco CA' => {
112
+ line1: '115 Broadway',
113
+ line2: 'San Francisco, CA',
114
+ street_address_1: '115 Broadway',
115
+ street_address_2: '',
116
+ full_street_address: '115 Broadway, San Francisco, CA',
117
+ intersection?: false
118
+ },
119
+ '7800 Mill Station Rd, Sebastopol, CA 95472' => {
120
+ line1: '7800 Mill Station Rd',
121
+ line2: 'Sebastopol, CA 95472',
122
+ street_address_1: '7800 Mill Station Rd',
123
+ street_address_2: '',
124
+ full_street_address: '7800 Mill Station Rd, Sebastopol, CA 95472',
125
+ intersection?: false
126
+ },
127
+ '7800 Mill Station Rd Sebastopol CA 95472' => {
128
+ line1: '7800 Mill Station Rd',
129
+ line2: 'Sebastopol, CA 95472',
130
+ street_address_1: '7800 Mill Station Rd',
131
+ street_address_2: '',
132
+ full_street_address: '7800 Mill Station Rd, Sebastopol, CA 95472',
133
+ intersection?: false
134
+ },
135
+ '1005 State Highway 116 Sebastopol CA 95472' => {
136
+ line1: '1005 State Highway 116',
137
+ line2: 'Sebastopol, CA 95472',
138
+ street_address_1: '1005 State Highway 116',
139
+ street_address_2: '',
140
+ full_street_address: '1005 State Highway 116, Sebastopol, CA 95472',
141
+ intersection?: false
142
+ },
143
+ '1600 Pennsylvania Ave. NW Washington DC' => {
144
+ line1: '1600 Pennsylvania Ave NW',
145
+ line2: 'Washington, DC',
146
+ street_address_1: '1600 Pennsylvania Ave NW',
147
+ street_address_2: '',
148
+ full_street_address: '1600 Pennsylvania Ave NW, Washington, DC',
149
+ intersection?: false
150
+ },
151
+ '1600 Pennsylvania Avenue NW Washington DC' => {
152
+ line1: '1600 Pennsylvania Ave NW',
153
+ line2: 'Washington, DC',
154
+ street_address_1: '1600 Pennsylvania Ave NW',
155
+ street_address_2: '',
156
+ full_street_address: '1600 Pennsylvania Ave NW, Washington, DC',
157
+ intersection?: false
158
+ },
159
+ '48S 400E, Salt Lake City UT' => {
160
+ line1: '48 S 400 E',
161
+ line2: 'Salt Lake City, UT',
162
+ street_address_1: '48 S 400 E',
163
+ street_address_2: '',
164
+ full_street_address: '48 S 400 E, Salt Lake City, UT',
165
+ intersection?: false
166
+ },
167
+ '550 S 400 E #3206, Salt Lake City UT 84111' => {
168
+ line1: '550 S 400 E # 3206',
169
+ line2: 'Salt Lake City, UT 84111',
170
+ street_address_1: '550 S 400 E',
171
+ street_address_2: '# 3206',
172
+ full_street_address: '550 S 400 E # 3206, Salt Lake City, UT 84111',
173
+ intersection?: false
174
+ },
175
+ '6641 N 2200 W Apt D304 Park City, UT 84098' => {
176
+ line1: '6641 N 2200 W Apt D304',
177
+ line2: 'Park City, UT 84098',
178
+ street_address_1: '6641 N 2200 W',
179
+ street_address_2: 'Apt D304',
180
+ full_street_address: '6641 N 2200 W Apt D304, Park City, UT 84098',
181
+ intersection?: false
182
+ },
183
+ '100 South St, Philadelphia, PA' => {
184
+ line1: '100 South St',
185
+ line2: 'Philadelphia, PA',
186
+ street_address_1: '100 South St',
187
+ street_address_2: '',
188
+ full_street_address: '100 South St, Philadelphia, PA',
189
+ intersection?: false
190
+ },
191
+ '100 S.E. Washington Ave, Minneapolis, MN' => {
192
+ line1: '100 SE Washington Ave',
193
+ line2: 'Minneapolis, MN',
194
+ street_address_1: '100 SE Washington Ave',
195
+ street_address_2: '',
196
+ full_street_address: '100 SE Washington Ave, Minneapolis, MN',
197
+ intersection?: false
198
+ },
199
+ '3813 1/2 Some Road, Los Angeles, CA' => {
200
+ line1: '3813 Some Rd',
201
+ line2: 'Los Angeles, CA',
202
+ street_address_1: '3813 Some Rd',
203
+ street_address_2: '',
204
+ full_street_address: '3813 Some Rd, Los Angeles, CA',
205
+ intersection?: false
206
+ },
207
+ '1 First St, e San Jose CA' => {
208
+ line1: '1 1st St',
209
+ line2: 'East San Jose, CA',
210
+ street_address_1: '1 1st St',
211
+ street_address_2: '',
212
+ full_street_address: '1 1st St, East San Jose, CA',
213
+ intersection?: false
214
+ },
215
+ 'lt42 99 Some Road, Some City LA' => {
216
+ line1: '99 Some Rd Lot 42',
217
+ line2: 'Some City, LA',
218
+ street_address_1: '99 Some Rd',
219
+ street_address_2: 'Lot 42',
220
+ full_street_address: '99 Some Rd Lot 42, Some City, LA',
221
+ intersection?: false
222
+ },
223
+ '36401 County Road 43, Eaton, CO 80615' => {
224
+ line1: '36401 County Road 43',
225
+ line2: 'Eaton, CO 80615',
226
+ street_address_1: '36401 County Road 43',
227
+ street_address_2: '',
228
+ full_street_address: '36401 County Road 43, Eaton, CO 80615',
229
+ intersection?: false
230
+ },
231
+ '1234 COUNTY HWY 60E, Town, CO 12345' => {
232
+ line1: '1234 County Hwy 60 E',
233
+ line2: 'Town, CO 12345',
234
+ street_address_1: '1234 County Hwy 60 E',
235
+ street_address_2: '',
236
+ full_street_address: '1234 County Hwy 60 E, Town, CO 12345',
237
+ intersection?: false
238
+ },
239
+ "'45 Quaker Ave, Ste 105'" => {
240
+ line1: '45 Quaker Ave Ste 105',
241
+ line2: '',
242
+ street_address_1: '45 Quaker Ave',
243
+ street_address_2: 'Ste 105',
244
+ full_street_address: '45 Quaker Ave Ste 105',
245
+ intersection?: false
246
+ },
247
+ '2730 S Veitch St Apt 207, Arlington, VA 22206' => {
248
+ line1: '2730 S Veitch St Apt 207',
249
+ line2: 'Arlington, VA 22206',
250
+ street_address_1: '2730 S Veitch St',
251
+ street_address_2: 'Apt 207',
252
+ full_street_address: '2730 S Veitch St Apt 207, Arlington, VA 22206',
253
+ intersection?: false
254
+ },
255
+ '2730 S Veitch St #207, Arlington, VA 22206' => {
256
+ line1: '2730 S Veitch St # 207',
257
+ line2: 'Arlington, VA 22206',
258
+ street_address_1: '2730 S Veitch St',
259
+ street_address_2: '# 207',
260
+ full_street_address: '2730 S Veitch St # 207, Arlington, VA 22206',
261
+ intersection?: false
262
+ },
263
+ '44 Canal Center Plaza Suite 500, Alexandria, VA 22314' => {
264
+ line1: '44 Canal Center Plz Ste 500',
265
+ line2: 'Alexandria, VA 22314',
266
+ street_address_1: '44 Canal Center Plz',
267
+ street_address_2: 'Ste 500',
268
+ full_street_address: '44 Canal Center Plz Ste 500, Alexandria, VA 22314',
269
+ intersection?: false
270
+ },
271
+ 'One East 161st Street, Bronx, NY 10451' => {
272
+ line1: 'One East 161st St',
273
+ line2: 'Bronx, NY 10451',
274
+ street_address_1: 'One East 161st St',
275
+ street_address_2: '',
276
+ full_street_address: 'One East 161st St, Bronx, NY 10451',
277
+ intersection?: false
278
+ },
279
+ 'One East 161st Street Suite 10, Bronx, NY 10451' => {
280
+ line1: 'One East 161st St Ste 10',
281
+ line2: 'Bronx, NY 10451',
282
+ street_address_1: 'One East 161st St',
283
+ street_address_2: 'Ste 10',
284
+ full_street_address: 'One East 161st St Ste 10, Bronx, NY 10451',
285
+ intersection?: false
286
+ },
287
+ 'P.O. Box 280568 Queens Village, New York 11428' => {
288
+ line1: 'PO Box 280568',
289
+ line2: 'Queens Village, NY 11428',
290
+ street_address_1: 'PO Box 280568',
291
+ street_address_2: '',
292
+ full_street_address: 'PO Box 280568, Queens Village, NY 11428',
293
+ intersection?: false
294
+ },
295
+ 'PO BOX 280568 Queens Village, New York 11428' => {
296
+ line1: 'PO Box 280568',
297
+ line2: 'Queens Village, NY 11428',
298
+ street_address_1: 'PO Box 280568',
299
+ street_address_2: '',
300
+ full_street_address: 'PO Box 280568, Queens Village, NY 11428',
301
+ intersection?: false
302
+ },
303
+ 'PO 280568 Queens Village, New York 11428' => {
304
+ line1: 'PO 280568',
305
+ line2: 'Queens Village, NY 11428',
306
+ street_address_1: 'PO 280568',
307
+ street_address_2: '',
308
+ full_street_address: 'PO 280568, Queens Village, NY 11428',
309
+ intersection?: false
310
+ },
311
+ 'Two Pennsylvania Plaza New York, NY 10121-0091' => {
312
+ line1: 'Two Pennsylvania Plz',
313
+ line2: 'New York, NY 10121-0091',
314
+ street_address_1: 'Two Pennsylvania Plz',
315
+ street_address_2: '',
316
+ full_street_address: 'Two Pennsylvania Plz, New York, NY 10121-0091',
317
+ intersection?: false
318
+ },
319
+ '1400 CONNECTICUT AVE NW, WASHINGTON, DC 20036' => {
320
+ line1: '1400 Connecticut Ave NW',
321
+ line2: 'Washington, DC 20036',
322
+ street_address_1: '1400 Connecticut Ave NW',
323
+ street_address_2: '',
324
+ full_street_address: '1400 Connecticut Ave NW, Washington, DC 20036',
325
+ intersection?: false
326
+ }
327
+ }.freeze
328
+
329
+ INTERSECTIONS = {
330
+ 'Mission & Valencia San Francisco CA' => {
331
+ line1: 'Mission and Valencia',
332
+ line2: 'San Francisco, CA',
333
+ street_address_1: 'Mission and Valencia',
334
+ street_address_2: '',
335
+ full_street_address: 'Mission and Valencia, San Francisco, CA',
336
+ intersection?: true
337
+ },
338
+ 'Mission & Valencia, San Francisco CA' => {
339
+ line1: 'Mission and Valencia',
340
+ line2: 'San Francisco, CA',
341
+ street_address_1: 'Mission and Valencia',
342
+ street_address_2: '',
343
+ full_street_address: 'Mission and Valencia, San Francisco, CA',
344
+ intersection?: true
345
+ },
346
+ 'Mission St and Valencia St San Francisco CA' => {
347
+ line1: 'Mission St and Valencia St',
348
+ line2: 'San Francisco, CA',
349
+ street_address_1: 'Mission St and Valencia St',
350
+ street_address_2: '',
351
+ full_street_address: 'Mission St and Valencia St, San Francisco, CA',
352
+ intersection?: true
353
+ },
354
+ 'Hollywood Blvd and Vine St Los Angeles, CA' => {
355
+ line1: 'Hollywood Blvd and Vine St',
356
+ line2: 'Los Angeles, CA',
357
+ street_address_1: 'Hollywood Blvd and Vine St',
358
+ street_address_2: '',
359
+ full_street_address: 'Hollywood Blvd and Vine St, Los Angeles, CA',
360
+ intersection?: true
361
+ },
362
+ 'Mission St & Valencia St San Francisco CA' => {
363
+ line1: 'Mission St and Valencia St',
364
+ line2: 'San Francisco, CA',
365
+ street_address_1: 'Mission St and Valencia St',
366
+ street_address_2: '',
367
+ full_street_address: 'Mission St and Valencia St, San Francisco, CA',
368
+ intersection?: true
369
+ },
370
+ 'Mission and Valencia Sts San Francisco CA' => {
371
+ line1: 'Mission St and Valencia St',
372
+ line2: 'San Francisco, CA',
373
+ street_address_1: 'Mission St and Valencia St',
374
+ street_address_2: '',
375
+ full_street_address: 'Mission St and Valencia St, San Francisco, CA',
376
+ intersection?: true
377
+ },
378
+ 'Mission & Valencia Sts. San Francisco CA' => {
379
+ line1: 'Mission St and Valencia St',
380
+ line2: 'San Francisco, CA',
381
+ street_address_1: 'Mission St and Valencia St',
382
+ street_address_2: '',
383
+ full_street_address: 'Mission St and Valencia St, San Francisco, CA',
384
+ intersection?: true
385
+ },
386
+ 'Mission & Valencia Streets San Francisco CA' => {
387
+ line1: 'Mission St and Valencia St',
388
+ line2: 'San Francisco, CA',
389
+ street_address_1: 'Mission St and Valencia St',
390
+ street_address_2: '',
391
+ full_street_address: 'Mission St and Valencia St, San Francisco, CA',
392
+ intersection?: true
393
+ },
394
+ 'Mission Avenue and Valencia Street San Francisco CA' => {
395
+ line1: 'Mission Ave and Valencia St',
396
+ line2: 'San Francisco, CA',
397
+ street_address_1: 'Mission Ave and Valencia St',
398
+ street_address_2: '',
399
+ full_street_address: 'Mission Ave and Valencia St, San Francisco, CA',
400
+ intersection?: true
401
+ }
402
+ }.freeze
403
+
404
+ INFORMAL_ADDRESSES = {
405
+ '#42 233 S Wacker Dr 60606' => {
406
+ line1: '233 S Wacker Dr # 42',
407
+ line2: '60606',
408
+ street_address_1: '233 S Wacker Dr',
409
+ street_address_2: '# 42',
410
+ full_street_address: '233 S Wacker Dr # 42, 60606',
411
+ intersection?: false
412
+ },
413
+ 'Apt. 42, 233 S Wacker Dr 60606' => {
414
+ line1: '233 S Wacker Dr Apt 42',
415
+ line2: '60606',
416
+ street_address_1: '233 S Wacker Dr',
417
+ street_address_2: 'Apt 42',
418
+ full_street_address: '233 S Wacker Dr Apt 42, 60606',
419
+ intersection?: false
420
+ },
421
+ '2730 S Veitch St #207' => {
422
+ line1: '2730 S Veitch St # 207',
423
+ line2: '',
424
+ street_address_1: '2730 S Veitch St',
425
+ street_address_2: '# 207',
426
+ full_street_address: '2730 S Veitch St # 207',
427
+ intersection?: false
428
+ },
429
+ '321 S. Washington' => {
430
+ line1: '321 S Washington',
431
+ line2: '',
432
+ street_address_1: '321 S Washington',
433
+ street_address_2: '',
434
+ full_street_address: '321 S Washington',
435
+ intersection?: false
436
+ },
437
+ '233 S Wacker Dr lobby 60606' => {
438
+ line1: '233 S Wacker Dr Lbby',
439
+ line2: '60606',
440
+ street_address_1: '233 S Wacker Dr',
441
+ street_address_2: 'Lbby',
442
+ full_street_address: '233 S Wacker Dr Lbby, 60606',
443
+ intersection?: false
444
+ }
445
+ }.freeze
446
+
447
+ METHODS = %w[line1 line2 street_address_1 street_address_2 full_street_address intersection?].freeze\
448
+
449
+ ADDRS = NORMAL_ADDRESSES.merge(INFORMAL_ADDRESSES)
450
+ ALL = ADDRS.merge(INTERSECTIONS)
451
+
452
+ ALL.each_pair do |address, expected|
453
+ context address.to_s do
454
+ METHODS.each do |method_name|
455
+ next if expected[method_name.to_sym].to_s == ''
456
+ it "#{method_name}: #{expected[method_name.to_sym]}" do
457
+ compare_expected_to_actual(expected, address, method_name)
458
+ end
459
+ end
460
+ end
461
+ end
462
+
463
+ describe '#full_postal_code' do
464
+ it 'without postal code' do
465
+ addr = StreetSweeper.parse('7800 Mill Station Rd Sebastopol CA')
466
+ expect(addr.full_postal_code).to be_nil
467
+ end
468
+
469
+ it 'with only the first five digits' do
470
+ addr = StreetSweeper.parse('7800 Mill Station Rd Sebastopol CA 95472')
471
+ expect(addr.full_postal_code).to eq('95472')
472
+ end
473
+
474
+ it 'with valid zip plus 4 with dash' do
475
+ addr = StreetSweeper.parse('2730 S Veitch St, Arlington, VA 22206-3333')
476
+ expect(addr.full_postal_code).to eq('22206-3333')
477
+ end
478
+
479
+ it 'with valid zip plus 4 without dash' do
480
+ addr = StreetSweeper.parse('2730 S Veitch St, Arlington, VA 222064444')
481
+ expect(addr.full_postal_code).to eq('22206-4444')
482
+ end
483
+ end
484
+
485
+ describe '#postal_code_ext' do
486
+ it 'without postal code' do
487
+ addr = StreetSweeper.parse('7800 Mill Station Rd Sebastopol CA')
488
+ expect(addr.full_postal_code).to be_nil
489
+ end
490
+
491
+ it 'with valid zip plus 4 with dash' do
492
+ addr = StreetSweeper.parse('2730 S Veitch St, Arlington, VA 22206-3333')
493
+ expect(addr.postal_code_ext).to eq('3333')
494
+ end
495
+
496
+ it 'with valid zip plus 4 without dash' do
497
+ addr = StreetSweeper.parse('2730 S Veitch St, Arlington, VA 222064444')
498
+ expect(addr.postal_code_ext).to eq('4444')
499
+ end
500
+ end
501
+
502
+ describe '#state_name' do
503
+ it 'with a vaild address' do
504
+ addr = StreetSweeper.parse('7800 Mill Station Rd Sebastopol CA 95472')
505
+ expect(addr.state_name).to eq('California')
506
+ end
507
+
508
+ it 'with an invaild address' do
509
+ addr = StreetSweeper.parse('7800 Mill Station Rd Sebastopol 95472')
510
+ expect(addr.state_name).to be_nil
511
+ end
512
+ end
513
+
514
+ describe '#state_fips' do
515
+ it 'with a vaild address' do
516
+ addr = StreetSweeper.parse('7800 Mill Station Rd Sebastopol CA 95472')
517
+ expect(addr.state_fips).to eq('06')
518
+ end
519
+
520
+ it 'with an invaild address' do
521
+ addr = StreetSweeper.parse('7800 Mill Station Rd Sebastopol 95472')
522
+ expect(addr.state_fips).to be_nil
523
+ end
524
+ end
525
+
526
+ def compare_expected_to_actual(expected, address, method_name)
527
+ addr = StreetSweeper.parse(address)
528
+ expect(addr.send(method_name)).to eq(expected[method_name.to_sym])
529
+ end
530
+ end