wfrmls 0.3.1 → 0.3.2
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.
- data/History.txt +15 -4
- data/bin/wfrmls +12 -1
- data/lib/wfrmls.rb +1 -1
- data/lib/wfrmls/cli/options.rb +14 -8
- data/lib/wfrmls/ie.rb +161 -107
- metadata +3 -3
data/History.txt
CHANGED
@@ -1,9 +1,20 @@
|
|
1
|
-
=== 0.3.
|
2
|
-
|
3
|
-
* 1 major enhancement
|
1
|
+
=== 0.3.2 / 2010-02-25
|
4
2
|
|
5
|
-
|
3
|
+
* Short sale is included except for comps
|
4
|
+
* Tax data is finding fewer false positives
|
5
|
+
* Added CLI option -o # no tax data
|
6
|
+
* Clear search before starting new address search
|
7
|
+
* Show house details when comping
|
8
|
+
* No address will take you to search page
|
9
|
+
* Show Parking type and size
|
6
10
|
|
7
11
|
=== 0.3.1 / 2010-01-27
|
8
12
|
|
9
13
|
* Added ability to comp
|
14
|
+
|
15
|
+
=== 0.3.0 / 2010-01-22
|
16
|
+
|
17
|
+
* Login to site
|
18
|
+
* Search for property
|
19
|
+
* Show results in browser
|
20
|
+
|
data/bin/wfrmls
CHANGED
@@ -22,6 +22,13 @@ password = config['password']
|
|
22
22
|
ie = Watir::Browser.new
|
23
23
|
mls = Wfrmls::IE.new(ie, username, password)
|
24
24
|
|
25
|
+
if options.address.empty?
|
26
|
+
begin
|
27
|
+
mls.lookup_address(nil)
|
28
|
+
rescue
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
end
|
25
32
|
|
26
33
|
addr = StreetAddress::US.parse(options.address)
|
27
34
|
if addr.nil?
|
@@ -31,7 +38,11 @@ end
|
|
31
38
|
puts addr
|
32
39
|
|
33
40
|
if options.comp?
|
34
|
-
mls.
|
41
|
+
details = mls.collect_property_details(addr)
|
42
|
+
puts details.to_yaml
|
43
|
+
mls.comp(addr, details)
|
44
|
+
elsif options.opposition?
|
45
|
+
mls.lookup_opposition(addr)
|
35
46
|
else
|
36
47
|
puts mls.collect_property_details(addr).to_yaml
|
37
48
|
mls.lookup_address(addr)
|
data/lib/wfrmls.rb
CHANGED
data/lib/wfrmls/cli/options.rb
CHANGED
@@ -11,10 +11,8 @@ module Wfrmls #:nodoc:
|
|
11
11
|
attr_reader :address
|
12
12
|
|
13
13
|
def initialize(*args)
|
14
|
-
address = ''
|
15
|
-
|
16
14
|
argv = args.flatten
|
17
|
-
|
15
|
+
|
18
16
|
opt = OptionParser.new do |opt|
|
19
17
|
opt.banner = "Usage: #{script_name} [options] <address>"
|
20
18
|
opt.version = version
|
@@ -27,25 +25,33 @@ module Wfrmls #:nodoc:
|
|
27
25
|
@comp = true
|
28
26
|
end
|
29
27
|
|
28
|
+
opt.on("-o", "Check opposition's sale") do
|
29
|
+
@opposition = true
|
30
|
+
end
|
31
|
+
|
30
32
|
opt.on("-h", "-?", "--help", "Show this message") do
|
31
33
|
puts opt
|
32
34
|
exit
|
33
35
|
end
|
34
|
-
|
36
|
+
|
35
37
|
opt.on("-v", "--version", "Show #{script_name}'s version (#{version})") do
|
36
|
-
|
38
|
+
puts version
|
37
39
|
exit
|
38
|
-
end
|
40
|
+
end
|
39
41
|
end
|
40
|
-
|
42
|
+
|
41
43
|
opt.parse!(argv)
|
42
44
|
@address = argv.dup.join(' ')
|
43
|
-
end
|
45
|
+
end
|
44
46
|
|
45
47
|
def comp?
|
46
48
|
@comp
|
47
49
|
end
|
48
50
|
|
51
|
+
def opposition?
|
52
|
+
@opposition
|
53
|
+
end
|
54
|
+
|
49
55
|
private
|
50
56
|
def version
|
51
57
|
Wfrmls::VERSION
|
data/lib/wfrmls/ie.rb
CHANGED
@@ -11,6 +11,132 @@ module Wfrmls
|
|
11
11
|
@ie = ie
|
12
12
|
end
|
13
13
|
|
14
|
+
def lookup_address(addr)
|
15
|
+
find_address_on_search_page(addr)
|
16
|
+
|
17
|
+
if search_results_availible?
|
18
|
+
show_full_listings
|
19
|
+
else
|
20
|
+
show_tax_data(addr)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def lookup_opposition(addr)
|
25
|
+
find_address_on_search_page(addr)
|
26
|
+
show_listings
|
27
|
+
end
|
28
|
+
|
29
|
+
def comp(addr, house_details = collect_property_details(addr))
|
30
|
+
goto_search_page
|
31
|
+
historical_data
|
32
|
+
status
|
33
|
+
city(addr.city)
|
34
|
+
short_sale(false)
|
35
|
+
|
36
|
+
@ie.text_field(:id, 'days_back_status').set('120')
|
37
|
+
|
38
|
+
|
39
|
+
@ie.text_field(:name,'tot_sqf1').set((house_details[:house_size]-200).to_s)
|
40
|
+
@ie.text_field(:name,'tot_sqf2').set((house_details[:house_size]+200).to_s)
|
41
|
+
|
42
|
+
@ie.text_field(:name,'yearblt1').set((house_details[:year_built]-6).to_s)
|
43
|
+
@ie.text_field(:name,'yearblt2').set((house_details[:year_built]+6).to_s)
|
44
|
+
|
45
|
+
sleep_until(3) {
|
46
|
+
@ie.dd(:id,'left_search_criteria').text.include? 'Year Built at most'
|
47
|
+
}
|
48
|
+
|
49
|
+
show_full_listings
|
50
|
+
end
|
51
|
+
|
52
|
+
def collect_property_details(addr)
|
53
|
+
show_tax_data(addr) unless @ie.url.include? 'taxdata/details'
|
54
|
+
|
55
|
+
doc = @ie.xmlparser_document_object
|
56
|
+
|
57
|
+
details = {}
|
58
|
+
|
59
|
+
doc.search('tr/th').each do |item|
|
60
|
+
case nbsp2sp(item.text)
|
61
|
+
when /NAME:/
|
62
|
+
details[:owner] = item.parent.search('td').text
|
63
|
+
when /ADDRESS:/
|
64
|
+
details[:address] = item.parent.search('td').text.strip
|
65
|
+
when /PARCEL SPECIFIC INFO:/
|
66
|
+
nbsp2sp(item.parent.search('td').text) =~ /Total Acres: ([.0-9]+)/
|
67
|
+
details[:lot_size] = $1
|
68
|
+
when /VALUATION SPECIFIC INFO:/
|
69
|
+
nbsp2sp(item.parent.search('td').text) =~ /Final Value: (\$[0-9,]+)/
|
70
|
+
details[:tax_value] = $1
|
71
|
+
when /GENERAL INFO:/
|
72
|
+
nbsp2sp(item.parent.search('td').text) =~ /Yr Built: ([0-9]+)/
|
73
|
+
details[:year_built] = $1.to_i
|
74
|
+
when /AREA INFO:/
|
75
|
+
house_size = 0
|
76
|
+
data = nbsp2sp(item.parent.search('td').text)
|
77
|
+
data =~ /Main Floor Area: ([,0-9]+)/
|
78
|
+
house_size += $1.sub(',','').to_i if $1
|
79
|
+
data =~ /Basement Area: ([,0-9]+)/
|
80
|
+
house_size += $1.sub(',','').to_i if $1
|
81
|
+
data =~ /Upper Floor Area: ([,0-9]+)/
|
82
|
+
house_size += $1.sub(',','').to_i if $1
|
83
|
+
details[:house_size] = house_size
|
84
|
+
when /EXTERIOR:/
|
85
|
+
details[:exterior] ||= {}
|
86
|
+
data = nbsp2sp(item.parent.search('td').text)
|
87
|
+
data =~ /Ext. Wall Type: (\w+)/
|
88
|
+
details[:exterior][:wall] = $1
|
89
|
+
data =~ /Masonry Trim: (\w+)/
|
90
|
+
details[:exterior][:masonry_trim] = $1
|
91
|
+
when /CARPORT & GARAGE INFO:/
|
92
|
+
data = nbsp2sp(item.parent.search('td').text)
|
93
|
+
data =~ /(.*): ([0-9]+)/
|
94
|
+
details[:parking] = nil
|
95
|
+
if $1
|
96
|
+
details[:parking] = {}
|
97
|
+
details[:parking][$1] = $2.to_i
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
details
|
103
|
+
end
|
104
|
+
|
105
|
+
def show_tax_data(addr)
|
106
|
+
goto "http://www.utahrealestate.com/taxdata/index?county%5B%5D=2&county%5B%5D=8&searchtype=house&searchbox=#{addr.number}"
|
107
|
+
|
108
|
+
rows = find_tax_data_rows_by_house_and_street(addr)
|
109
|
+
|
110
|
+
case rows.size
|
111
|
+
when 0
|
112
|
+
puts "#{addr} not found in tax data"
|
113
|
+
when 1
|
114
|
+
click_link rows[0]
|
115
|
+
else
|
116
|
+
puts 'Possible matches:'
|
117
|
+
rows.each do |item|
|
118
|
+
puts item.cell(:class, 'last-col').text
|
119
|
+
end
|
120
|
+
regex = /\b#{addr.prefix}\b.*\b#{addr.suffix}/
|
121
|
+
rows = rows.inject([]) do |c, item|
|
122
|
+
c << item if regex.match item.cell(:class, 'last-col').text
|
123
|
+
c
|
124
|
+
end
|
125
|
+
case rows.size
|
126
|
+
when 0
|
127
|
+
puts "#{addr} not found with #{regex}"
|
128
|
+
when 1
|
129
|
+
click_link rows[0]
|
130
|
+
else
|
131
|
+
puts 'Possible matches:'
|
132
|
+
rows.each do |item|
|
133
|
+
puts item.cell(:class, 'last-col').text
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
14
140
|
def login
|
15
141
|
@ie.goto 'http://www.utahrealestate.com/auth/login/login_redirect//force_redirect/1'
|
16
142
|
begin
|
@@ -24,57 +150,55 @@ module Wfrmls
|
|
24
150
|
end
|
25
151
|
end
|
26
152
|
|
27
|
-
def
|
153
|
+
def historical_data
|
28
154
|
@ie.radio(:id, 'historical_data_yes').set
|
29
155
|
end
|
30
156
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
157
|
+
def find_address_on_search_page(addr)
|
158
|
+
goto_search_page
|
159
|
+
historical_data
|
34
160
|
status
|
161
|
+
short_sale
|
35
162
|
address(addr)
|
163
|
+
end
|
36
164
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
show_full_listings
|
165
|
+
def short_sale(includes=true)
|
166
|
+
if includes
|
167
|
+
@ie.radio(:id, 'o_shortsale_4').click
|
41
168
|
else
|
42
|
-
|
169
|
+
@ie.radio(:id, 'o_shortsale_8').click
|
43
170
|
end
|
171
|
+
@ie.checkbox(:id, 'shortsale_2').click
|
172
|
+
@ie.checkbox(:id, 'shortsale_4').click
|
44
173
|
end
|
45
174
|
|
46
|
-
def
|
175
|
+
def search_results_availible?
|
176
|
+
result_count = @ie.span(:id, 'action_search_count').text.to_i > 0
|
177
|
+
end
|
178
|
+
|
179
|
+
def show_listings
|
180
|
+
return false unless search_results_availible?
|
47
181
|
@ie.button(:id, 'SEARCH_button').click
|
48
182
|
sleep_until { @ie.checkbox(:id, 'ListingController').exists? }
|
183
|
+
true
|
184
|
+
end
|
185
|
+
|
186
|
+
def show_full_listings
|
187
|
+
return unless show_listings
|
49
188
|
@ie.checkbox(:id, 'ListingController').click
|
50
189
|
@ie.select_list(:id, 'report-selector').set('Full Report')
|
51
190
|
end
|
52
191
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
sleep 3
|
61
|
-
|
62
|
-
residential_full_search
|
63
|
-
status
|
64
|
-
city(addr.city)
|
65
|
-
|
66
|
-
@ie.text_field(:id, 'days_back_status').set('120')
|
67
|
-
|
68
|
-
|
69
|
-
@ie.text_field(:name, 'tot_sqf1').set((house_details[:house_size] - 200).to_s)
|
70
|
-
@ie.text_field(:name, 'tot_sqf2').set((house_details[:house_size] + 200).to_s)
|
71
|
-
|
72
|
-
@ie.text_field(:name, 'yearblt1').set((house_details[:year_built] - 6).to_s)
|
73
|
-
@ie.text_field(:name, 'yearblt2').set((house_details[:year_built] + 6).to_s)
|
74
|
-
|
75
|
-
sleep 3
|
192
|
+
def goto_search_page
|
193
|
+
url = 'http://www.utahrealestate.com/search/form/type/1/name/full?advanced_search=1'
|
194
|
+
goto url
|
195
|
+
clear_search
|
196
|
+
goto url
|
197
|
+
end
|
76
198
|
|
77
|
-
|
199
|
+
def clear_search
|
200
|
+
@ie.button(:id,'CLEAR_button').click
|
201
|
+
sleep_until(3) { @ie.dd(:id, 'left_search_criteria').text !~ /City/ }
|
78
202
|
end
|
79
203
|
|
80
204
|
def status
|
@@ -106,30 +230,12 @@ module Wfrmls
|
|
106
230
|
city(addr.city)
|
107
231
|
end
|
108
232
|
|
109
|
-
def sleep_until &block
|
233
|
+
def sleep_until max=10, &block
|
110
234
|
count = 0
|
111
235
|
until yield block
|
112
236
|
sleep 1
|
113
237
|
count += 1
|
114
|
-
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def show_tax_data(addr)
|
119
|
-
goto "http://www.utahrealestate.com/taxdata/index?county%5B%5D=2&county%5B%5D=8&searchtype=house&searchbox=#{addr.number}"
|
120
|
-
|
121
|
-
rows = find_tax_data_rows_by_house_and_street(addr)
|
122
|
-
|
123
|
-
case rows.size
|
124
|
-
when 0
|
125
|
-
puts "#{addr} not found in tax data"
|
126
|
-
when 1
|
127
|
-
click_link rows[0]
|
128
|
-
else
|
129
|
-
puts 'Possible matches:'
|
130
|
-
rows.each do |item|
|
131
|
-
puts item.cell(:class, 'last-col').text
|
132
|
-
end
|
238
|
+
return if count > max
|
133
239
|
end
|
134
240
|
end
|
135
241
|
|
@@ -144,7 +250,7 @@ module Wfrmls
|
|
144
250
|
rows << row
|
145
251
|
end
|
146
252
|
end
|
147
|
-
rows
|
253
|
+
rows
|
148
254
|
end
|
149
255
|
|
150
256
|
def goto(url)
|
@@ -155,58 +261,6 @@ module Wfrmls
|
|
155
261
|
end
|
156
262
|
end
|
157
263
|
|
158
|
-
def collect_property_details(addr)
|
159
|
-
show_tax_data(addr) unless @ie.url.include? 'taxdata/details'
|
160
|
-
|
161
|
-
doc = @ie.xmlparser_document_object
|
162
|
-
|
163
|
-
details = {}
|
164
|
-
|
165
|
-
doc.search('tr/th').each do |item|
|
166
|
-
case nbsp2sp(item.text)
|
167
|
-
when /NAME:/
|
168
|
-
details[:owner] = item.parent.search('td').text
|
169
|
-
when /ADDRESS:/
|
170
|
-
details[:address] = item.parent.search('td').text.strip
|
171
|
-
when /PARCEL SPECIFIC INFO:/
|
172
|
-
nbsp2sp(item.parent.search('td').text) =~ /Total Acres: ([.0-9]+)/
|
173
|
-
details[:lot_size] = $1
|
174
|
-
when /VALUATION SPECIFIC INFO:/
|
175
|
-
nbsp2sp(item.parent.search('td').text) =~ /Final Value: (\$[0-9,]+)/
|
176
|
-
details[:tax_value] = $1
|
177
|
-
when /GENERAL INFO:/
|
178
|
-
nbsp2sp(item.parent.search('td').text) =~ /Yr Built: ([0-9]+)/
|
179
|
-
details[:year_built] = $1.to_i
|
180
|
-
when /AREA INFO:/
|
181
|
-
house_size = 0
|
182
|
-
data = nbsp2sp(item.parent.search('td').text)
|
183
|
-
data =~ /Main Floor Area: ([,0-9]+)/
|
184
|
-
house_size += $1.sub(',','').to_i if $1
|
185
|
-
data =~ /Basement Area: ([,0-9]+)/
|
186
|
-
house_size += $1.sub(',','').to_i if $1
|
187
|
-
data =~ /Upper Floor Area: ([,0-9]+)/
|
188
|
-
house_size += $1.sub(',','').to_i if $1
|
189
|
-
details[:house_size] = house_size
|
190
|
-
when /EXTERIOR:/
|
191
|
-
details[:exterior] ||= {}
|
192
|
-
data = nbsp2sp(item.parent.search('td').text)
|
193
|
-
data =~ /Ext. Wall Type: (\w+)/
|
194
|
-
details[:exterior][:wall] = $1
|
195
|
-
data =~ /Masonry Trim: (\w+)/
|
196
|
-
details[:exterior][:masonry_trim] = $1
|
197
|
-
when /CARPORT & GARAGE INFO:/
|
198
|
-
data = nbsp2sp(item.parent.search('td').text)
|
199
|
-
data =~ /(.*): ([,0-9]+)/
|
200
|
-
if $1
|
201
|
-
key = $1.gsub(/[- ]/, '_').downcase.to_sym
|
202
|
-
details[key] = $2
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
details
|
208
|
-
end
|
209
|
-
|
210
264
|
def nbsp2sp(s)
|
211
265
|
s.gsub("\xC2\xA0", ' ')
|
212
266
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wfrmls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- zhon
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-02-25 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.4.1
|
34
34
|
version:
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: hoe
|