rubillow 0.0.1

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.
Files changed (81) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +4 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +43 -0
  6. data/LICENSE +21 -0
  7. data/README.md +64 -0
  8. data/Rakefile +38 -0
  9. data/lib/rubillow.rb +29 -0
  10. data/lib/rubillow/configuration.rb +31 -0
  11. data/lib/rubillow/home_valuation.rb +141 -0
  12. data/lib/rubillow/models.rb +83 -0
  13. data/lib/rubillow/models/addressable.rb +38 -0
  14. data/lib/rubillow/models/chart.rb +34 -0
  15. data/lib/rubillow/models/comps.rb +35 -0
  16. data/lib/rubillow/models/deep_comps.rb +35 -0
  17. data/lib/rubillow/models/deep_search_result.rb +44 -0
  18. data/lib/rubillow/models/demographic_value.rb +28 -0
  19. data/lib/rubillow/models/demographics.rb +149 -0
  20. data/lib/rubillow/models/images.rb +24 -0
  21. data/lib/rubillow/models/linkable.rb +39 -0
  22. data/lib/rubillow/models/monthly_payments.rb +64 -0
  23. data/lib/rubillow/models/posting.rb +37 -0
  24. data/lib/rubillow/models/postings.rb +64 -0
  25. data/lib/rubillow/models/property_basics.rb +36 -0
  26. data/lib/rubillow/models/property_chart.rb +26 -0
  27. data/lib/rubillow/models/rate_summary.rb +38 -0
  28. data/lib/rubillow/models/region.rb +44 -0
  29. data/lib/rubillow/models/region_chart.rb +30 -0
  30. data/lib/rubillow/models/region_children.rb +30 -0
  31. data/lib/rubillow/models/search_result.rb +19 -0
  32. data/lib/rubillow/models/updated_property_details.rb +87 -0
  33. data/lib/rubillow/models/zestimateable.rb +77 -0
  34. data/lib/rubillow/models/zpidable.rb +25 -0
  35. data/lib/rubillow/mortgage.rb +75 -0
  36. data/lib/rubillow/neighborhood.rb +129 -0
  37. data/lib/rubillow/postings.rb +46 -0
  38. data/lib/rubillow/property_details.rb +107 -0
  39. data/lib/rubillow/request.rb +67 -0
  40. data/lib/rubillow/version.rb +4 -0
  41. data/rubillow.gemspec +31 -0
  42. data/spec/rubillow/configuration_spec.rb +10 -0
  43. data/spec/rubillow/home_valuation_spec.rb +91 -0
  44. data/spec/rubillow/models/comps_spec.rb +71 -0
  45. data/spec/rubillow/models/deep_comps_spec.rb +93 -0
  46. data/spec/rubillow/models/deep_search_results_spec.rb +47 -0
  47. data/spec/rubillow/models/demographics_spec.rb +60 -0
  48. data/spec/rubillow/models/monthly_payments_spec.rb +20 -0
  49. data/spec/rubillow/models/posting_spec.rb +28 -0
  50. data/spec/rubillow/models/postings_spec.rb +15 -0
  51. data/spec/rubillow/models/property_chart_spec.rb +18 -0
  52. data/spec/rubillow/models/rate_summary_spec.rb +14 -0
  53. data/spec/rubillow/models/region_chart_spec.rb +19 -0
  54. data/spec/rubillow/models/region_children_spec.rb +11 -0
  55. data/spec/rubillow/models/search_result_spec.rb +68 -0
  56. data/spec/rubillow/models/updated_property_details_spec.rb +57 -0
  57. data/spec/rubillow/models_spec.rb +46 -0
  58. data/spec/rubillow/mortgage_spec.rb +37 -0
  59. data/spec/rubillow/neighborhood_spec.rb +55 -0
  60. data/spec/rubillow/postings_spec.rb +19 -0
  61. data/spec/rubillow/property_details_spec.rb +67 -0
  62. data/spec/rubillow/request_spec.rb +65 -0
  63. data/spec/rubillow/rubillow_spec.rb +13 -0
  64. data/spec/spec_helper.rb +15 -0
  65. data/spec/support/have_configuration_matcher.rb +23 -0
  66. data/spec/xml/general_failure.xml +12 -0
  67. data/spec/xml/get_chart.xml +18 -0
  68. data/spec/xml/get_comps.xml +252 -0
  69. data/spec/xml/get_deep_comps.xml +307 -0
  70. data/spec/xml/get_deep_search_results.xml +66 -0
  71. data/spec/xml/get_demographics.xml +791 -0
  72. data/spec/xml/get_monthly_payments.xml +33 -0
  73. data/spec/xml/get_rate_summary.xml +21 -0
  74. data/spec/xml/get_region_chart.xml +25 -0
  75. data/spec/xml/get_region_children.xml +870 -0
  76. data/spec/xml/get_region_postings.xml +2660 -0
  77. data/spec/xml/get_search_results.xml +55 -0
  78. data/spec/xml/get_updated_property_details.xml +74 -0
  79. data/spec/xml/get_zestimate.xml +56 -0
  80. data/spec/xml/near_limit.xml +13 -0
  81. metadata +324 -0
@@ -0,0 +1,38 @@
1
+ module Rubillow
2
+ module Models
3
+ # Common data for responses containing address information.
4
+ module Addressable
5
+ # @return [Hash] Address information (all are strings, keys are: :street, :city, :state, :zipcode, :latitude, :longitude).
6
+ #
7
+ # @example
8
+ # puts address[:street]
9
+ # puts address[:city]
10
+ #
11
+ attr_accessor :address
12
+
13
+ # get the full, formatted address
14
+ #
15
+ # @return [String] formatted address
16
+ def full_address
17
+ @address[:street] + ', ' + @address[:city] + ', ' + @address[:state] + ' ' + @address[:zipcode]
18
+ end
19
+
20
+ protected
21
+
22
+ # @private
23
+ def extract_address(xml)
24
+ address = xml.xpath('//address')
25
+ if !address.empty?
26
+ @address = {
27
+ :street => address.xpath('//street').text,
28
+ :city => address.xpath('//city').text,
29
+ :state => address.xpath('//state').text,
30
+ :zipcode => address.xpath('//zipcode').text,
31
+ :latitude => address.xpath('//latitude').text,
32
+ :longitude => address.xpath('//longitude').text,
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ module Rubillow
2
+ module Models
3
+ # Base chart class.
4
+ class Chart < Base
5
+ # @return [String] image height.
6
+ attr_accessor :height
7
+
8
+ # @return [String] image width.
9
+ attr_accessor :width
10
+
11
+ # @return [String] URL to image.
12
+ attr_accessor :url
13
+
14
+ # Returns HTML for the chart.
15
+ # @return [String] chart HTML.
16
+ def to_html
17
+ "<img src='#{@url}' height='#{@height}' width='#{width}' />"
18
+ end
19
+
20
+ protected
21
+
22
+ # @private
23
+ def parse
24
+ super
25
+
26
+ return if !success?
27
+
28
+ @height = @parser.xpath('//request/height').text.to_i
29
+ @width = @parser.xpath('//request/width').text.to_i
30
+ @url = @parser.xpath('//response/url').text
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ module Rubillow
2
+ module Models
3
+ # List of comps for a property.
4
+ class Comps < Base
5
+ # @return [Models::SearchResult] principal property.
6
+ attr_accessor :principal
7
+
8
+ # @return [Hash] comparables list (key => comparable's score, value => {Models::SearchResult}).
9
+ #
10
+ # @example
11
+ # comparables.each do |score, comp|
12
+ # puts score
13
+ # puts comp.price
14
+ # end
15
+ attr_accessor :comparables
16
+
17
+ protected
18
+
19
+ # @private
20
+ def parse
21
+ super
22
+
23
+ return if !success?
24
+
25
+ @principal = SearchResult.new(@parser.xpath('//principal').to_xml)
26
+
27
+ @comparables = {}
28
+ @parser.xpath('//comparables/comp').each do |elm|
29
+ key = elm.attribute('score').value
30
+ @comparables[key] = SearchResult.new(elm.to_xml)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ module Rubillow
2
+ module Models
3
+ # List of comps for a given property with deep data.
4
+ class DeepComps < Base
5
+ # @return [Models::DeepSearchResult] principal property.
6
+ attr_accessor :principal
7
+
8
+ # @return [Hash] comparables list (key => comparable's score, value => {Models::DeepSearchResult}).
9
+ #
10
+ # @example
11
+ # comparables.each do |score, comp|
12
+ # puts score
13
+ # puts comp.price
14
+ # end
15
+ attr_accessor :comparables
16
+
17
+ protected
18
+
19
+ # @private
20
+ def parse
21
+ super
22
+
23
+ return if !success?
24
+
25
+ @principal = DeepSearchResult.new(@parser.xpath('//principal').to_xml)
26
+
27
+ @comparables = {}
28
+ @parser.xpath('//comparables/comp').each do |elm|
29
+ key = elm.attribute('score').value
30
+ @comparables[key] = DeepSearchResult.new(elm.to_xml)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ module Rubillow
2
+ module Models
3
+ # Get a property's information with deeper data.
4
+ class DeepSearchResult < SearchResult
5
+ include PropertyBasics
6
+
7
+ # @return [String] FIPS county code. See {http://www.itl.nist.gov/fipspubs/fip6-4.htm}.
8
+ attr_accessor :fips_county
9
+
10
+ # @return [String] year of the last tax assessment
11
+ attr_accessor :tax_assessment_year
12
+
13
+ # @return [String] value of the last tax assessment
14
+ attr_accessor :tax_assessment
15
+
16
+ # @return [String] year home was built
17
+ attr_accessor :year_built
18
+
19
+ # @return [Date] last date property was sold
20
+ attr_accessor :last_sold_date
21
+
22
+ # @return [String] price property was last sold for
23
+ attr_accessor :last_sold_price
24
+
25
+ protected
26
+
27
+ # @private
28
+ def parse
29
+ super
30
+
31
+ return if !success?
32
+
33
+ extract_property_basics(@parser)
34
+
35
+ @fips_county = @parser.xpath('//FIPScounty').text
36
+ @tax_assessment_year = @parser.xpath('//taxAssessmentYear').text
37
+ @tax_assessment = @parser.xpath('//taxAssessment').text
38
+ @year_built = @parser.xpath('//yearBuilt').text
39
+ @last_sold_date = Date.strptime(@parser.xpath('//lastSoldDate').text, "%m/%d/%Y")
40
+ @last_sold_price = @parser.xpath('//lastSoldPrice').text
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ module Rubillow
2
+ module Models
3
+ # Demographic data point.
4
+ class DemographicValue
5
+ # @return [String] data point value.
6
+ attr_accessor :value
7
+
8
+ # @return [String] data point type.
9
+ attr_accessor :type
10
+
11
+ # create a new data point.
12
+ # @param [String] xml for point.
13
+ def initialize(xml)
14
+ if !xml.empty?
15
+ @value = xml.text
16
+ @type = xml.attribute('type').value if !xml.attribute('type').nil?
17
+ end
18
+ end
19
+
20
+ # Prints value
21
+ #
22
+ # @return [String] attribute value
23
+ def to_s
24
+ @value
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,149 @@
1
+ module Rubillow
2
+ module Models
3
+ # Demographics data
4
+ class Demographics < Base
5
+ include Linkable
6
+
7
+ # @return [Models::Region] region data.
8
+ attr_accessor :region
9
+
10
+ # @return [Hash] Charts (key: name, value: url).
11
+ #
12
+ # @example
13
+ # charts.each do |name, value|
14
+ # end
15
+ #
16
+ attr_accessor :charts
17
+
18
+ # @return [Hash] Metrics ([high level point][low level point]).
19
+ #
20
+ # @example
21
+ # puts metrics['BuiltYear']['<1900']
22
+ #
23
+ attr_accessor :metrics
24
+
25
+ # @return [Hash] Affordability metrics (key: name, value: value).
26
+ #
27
+ # @example
28
+ # puts affordability_data['Zillow Home Value Index'][:neighborhood]
29
+ #
30
+ attr_accessor :affordability_data
31
+
32
+ # @return [Hash] US Census metrics ([high level point][low level point]).
33
+ #
34
+ # @example
35
+ # puts census_data['Household']['NoKids']
36
+ attr_accessor :census_data
37
+
38
+ # @return [Hash] Segmentation metrics (key: name, value: [:name, :description])
39
+ #
40
+ # @example
41
+ # puts segmentation["Makin' It Singles"][:name]
42
+ # puts segmentation["Makin' It Singles"][:description]
43
+ #
44
+ attr_accessor :segmentation
45
+
46
+ # @return [Hash] Characteristics metrics ([high level point] -> hash)
47
+ #
48
+ # @example
49
+ # characteristics['Education']
50
+ attr_accessor :characteristics
51
+
52
+ protected
53
+
54
+ # @private
55
+ def parse
56
+ super
57
+
58
+ return if !success?
59
+
60
+ extract_links(@parser)
61
+
62
+ @region = Region.new(@parser.xpath('//region').to_xml)
63
+
64
+ @charts = {}
65
+ @parser.xpath('//charts').children.each do |elm|
66
+ if elm.xpath('name').attribute('deprecated').nil?
67
+ @charts[elm.xpath('name').text] = elm.xpath('url').text
68
+ end
69
+ end
70
+
71
+ @metrics = {}
72
+ @affordability_data = {}
73
+ @census_data = {}
74
+ @segmentation = {}
75
+ @characteristics = {}
76
+
77
+ @parser.xpath('//pages').children.each do |page|
78
+ page.xpath('tables').children.each do |table|
79
+ table_name = table.xpath('name').text
80
+
81
+ if table_name == "Affordability Data" && table.parent.parent.xpath('name').text == "Affordability"
82
+ extract_affordability_data(table)
83
+ elsif table_name[0,14] == "Census Summary"
84
+ extract_census_data(table, table_name[15, table_name.length])
85
+ else
86
+ extract_data(table, table_name)
87
+ end
88
+ end
89
+ end
90
+
91
+ @parser.xpath('//segmentation').children.each do |segment|
92
+ @segmentation[segment.xpath('title').text] = {
93
+ :name => segment.xpath('name').text,
94
+ :description => segment.xpath('description').text,
95
+ }
96
+ end
97
+
98
+ @parser.xpath('//uniqueness').children.each do |category|
99
+ key = category.attribute('type').text
100
+ @characteristics[key] = []
101
+ category.xpath('characteristic').each do |c|
102
+ @characteristics[key] << c.text
103
+ end
104
+ end
105
+ end
106
+
107
+ # @private
108
+ def extract_affordability_data(xml)
109
+ xml.xpath('data').children.each do |data|
110
+ @affordability_data[data.xpath('name').text] = extract_metrics(data)
111
+ end
112
+ end
113
+
114
+ # @private
115
+ def extract_census_data(xml, type)
116
+ @census_data[type] = {}
117
+
118
+ xml.xpath('data').children.each do |data|
119
+ @census_data[type][data.xpath('name').text] = extract_metrics(data)
120
+ end
121
+ end
122
+
123
+ # @private
124
+ def extract_data(xml, type)
125
+ if @metrics[type].nil?
126
+ @metrics[type] = {}
127
+ end
128
+
129
+ xml.xpath('data').children.each do |data|
130
+ @metrics[type][data.xpath('name').text] = extract_metrics(data)
131
+ end
132
+ end
133
+
134
+ # @private
135
+ def extract_metrics(xml)
136
+ if xml.xpath('values').empty?
137
+ DemographicValue.new(xml.xpath('value'))
138
+ else
139
+ {
140
+ :neighborhood => DemographicValue.new(xml.xpath('values/neighborhood/value')),
141
+ :city => DemographicValue.new(xml.xpath('values/city/value')),
142
+ :nation => DemographicValue.new(xml.xpath('values/nation/value')),
143
+ :zip => DemographicValue.new(xml.xpath('values/zip/value')),
144
+ }
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,24 @@
1
+ module Rubillow
2
+ module Models
3
+ # Common data for responses containing images
4
+ module Images
5
+ # @return [Array] List of image urls
6
+ attr_accessor :images
7
+
8
+ # @return [Integer] number of images available (doesn't always match @images.count)
9
+ attr_accessor :images_count
10
+
11
+ protected
12
+
13
+ # @private
14
+ def extract_images(xml)
15
+ @images_count = xml.xpath('//images/count').text
16
+
17
+ @images = []
18
+ xml.xpath('//images/image').children.each do |elm|
19
+ @images << elm.text
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ module Rubillow
2
+ module Models
3
+ # Common data for responses containing links
4
+ module Linkable
5
+ # @return [Hash] Links (format: :name => 'url')
6
+ #
7
+ # @example
8
+ # links.each do |name, url
9
+ # end
10
+ #
11
+ attr_accessor :links
12
+
13
+ protected
14
+
15
+ # @private
16
+ def extract_links(xml)
17
+ @links = {}
18
+
19
+ # TODO: clean up this logic
20
+ if !xml.xpath('//result/links').empty?
21
+ selector = '//result/links'
22
+ elsif !xml.xpath('//response/links').empty?
23
+ selector = '//response/links'
24
+ elsif !xml.xpath('//principal/links').empty?
25
+ selector = '//principal/links'
26
+ elsif !xml.xpath('//comp/links').empty?
27
+ selector = '//comp/links'
28
+ else
29
+ selector = '//links'
30
+ end
31
+
32
+ xml.xpath(selector).children.each do |link|
33
+ next if link.name == "myzestimator" # deprecated
34
+ @links[link.name.to_sym] = link.text
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,64 @@
1
+ module Rubillow
2
+ module Models
3
+ # Monthly payment information
4
+ class MonthlyPayments < Base
5
+ # @return [Hash] 30 year fixed rate data (:rate, :principal_and_interest, :mortgage_insurance).
6
+ #
7
+ # @example
8
+ # puts thirty_year_fixed[:rate]
9
+ #
10
+ attr_accessor :thirty_year_fixed
11
+
12
+ # @return [Hash] 15 year fixed rate data (:rate, :principal_and_interest, :mortgage_insurance).
13
+ #
14
+ # @example
15
+ # puts fifteen_year_fixed[:rate]
16
+ #
17
+ attr_accessor :fifteen_year_fixed
18
+
19
+ # @return [Hash] 5/1 fixed rate data (:rate, :principal_and_interest, :mortgage_insurance).
20
+ #
21
+ # @example
22
+ # puts five_one_arm[:rate]
23
+ #
24
+ attr_accessor :five_one_arm
25
+
26
+ # @return [String] down payment amount.
27
+ attr_accessor :down_payment
28
+
29
+ # @return [String] monthly property taxes (estimated).
30
+ attr_accessor :monthly_property_taxes
31
+
32
+ # @return [String] monthyly hazard insurance (estimated).
33
+ attr_accessor :monthly_hazard_insurance
34
+
35
+ protected
36
+
37
+ # @private
38
+ def parse
39
+ super
40
+
41
+ return if !success?
42
+
43
+ @thirty_year_fixed = {
44
+ :rate => @parser.xpath('//payment[@loanType="thirtyYearFixed"]/rate').text,
45
+ :principal_and_interest => @parser.xpath('//payment[@loanType="thirtyYearFixed"]/monthlyPrincipalAndInterest').text,
46
+ :mortgage_insurance => @parser.xpath('//payment[@loanType="thirtyYearFixed"]/monthlyMortgageInsurance').text,
47
+ }
48
+ @fifteen_year_fixed = {
49
+ :rate => @parser.xpath('//payment[@loanType="fifteenYearFixed"]/rate').text,
50
+ :principal_and_interest => @parser.xpath('//payment[@loanType="fifteenYearFixed"]/monthlyPrincipalAndInterest').text,
51
+ :mortgage_insurance => @parser.xpath('//payment[@loanType="fifteenYearFixed"]/monthlyMortgageInsurance').text,
52
+ }
53
+ @five_one_arm = {
54
+ :rate => @parser.xpath('//payment[@loanType="fiveOneARM"]/rate').text,
55
+ :principal_and_interest => @parser.xpath('//payment[@loanType="fiveOneARM"]/monthlyPrincipalAndInterest').text,
56
+ :mortgage_insurance => @parser.xpath('//payment[@loanType="fiveOneARM"]/monthlyMortgageInsurance').text,
57
+ }
58
+ @down_payment = @parser.xpath('//downPayment').text
59
+ @monthly_property_taxes = @parser.xpath('//monthlyPropertyTaxes').text
60
+ @monthly_hazard_insurance = @parser.xpath('//monthlyHazardInsurance').text
61
+ end
62
+ end
63
+ end
64
+ end