street_sweeper 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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