moto_recall 0.0.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +68 -0
  7. data/Rakefile +46 -0
  8. data/data/class_map.yml +41 -0
  9. data/data/sample_queries.yml +17 -0
  10. data/lib/moto_recall.rb +38 -0
  11. data/lib/moto_recall/class_map.rb +24 -0
  12. data/lib/moto_recall/client/acura.rb +32 -0
  13. data/lib/moto_recall/client/audi.rb +29 -0
  14. data/lib/moto_recall/client/bmw.rb +29 -0
  15. data/lib/moto_recall/client/chrysler.rb +51 -0
  16. data/lib/moto_recall/client/ford.rb +49 -0
  17. data/lib/moto_recall/client/general_motors.rb +40 -0
  18. data/lib/moto_recall/client/generic_client.rb +44 -0
  19. data/lib/moto_recall/client/honda.rb +32 -0
  20. data/lib/moto_recall/client/hyundai.rb +46 -0
  21. data/lib/moto_recall/client/infiniti.rb +29 -0
  22. data/lib/moto_recall/client/isuzu.rb +52 -0
  23. data/lib/moto_recall/client/jaguar.rb +29 -0
  24. data/lib/moto_recall/client/kia.rb +30 -0
  25. data/lib/moto_recall/client/mazda.rb +26 -0
  26. data/lib/moto_recall/client/mitsubishi.rb +46 -0
  27. data/lib/moto_recall/client/nissan.rb +29 -0
  28. data/lib/moto_recall/client/subaru.rb +29 -0
  29. data/lib/moto_recall/client/volkswagen.rb +29 -0
  30. data/lib/moto_recall/client/volvo.rb +78 -0
  31. data/lib/moto_recall/recall.rb +40 -0
  32. data/lib/moto_recall/recall_set.rb +22 -0
  33. data/lib/moto_recall/sampler.rb +18 -0
  34. data/lib/moto_recall/sampler/runner.rb +22 -0
  35. data/lib/moto_recall/sampler/sample.rb +30 -0
  36. data/lib/moto_recall/sampler/util.rb +40 -0
  37. data/lib/moto_recall/version.rb +3 -0
  38. data/moto_recall.gemspec +31 -0
  39. data/samples-example.csv +191 -0
  40. data/spec/moto_recall/class_map_spec.rb +24 -0
  41. data/spec/moto_recall/recall_set_spec.rb +27 -0
  42. data/spec/moto_recall/recall_spec.rb +24 -0
  43. data/spec/moto_recall_spec.rb +28 -0
  44. data/spec/spec_helper.rb +20 -0
  45. metadata +230 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b3047d39b482c25da37c2be06e7dc56f78d16974
4
+ data.tar.gz: c492a504c914cd7b7d9c4999957218431165f81a
5
+ SHA512:
6
+ metadata.gz: cb197f7ae24a62a2971a5f06a697a499126791acd550ae260d84cfe00440bc2a856ea3da3e590981f8d9d1195f6002ac25ee4a484cbd631ff3fbf929873510ca
7
+ data.tar.gz: a7237eda7bbb1abe5496ea403db1e4b200d3eeb964b039527c42d741f3a88065785526ec3ea349bcf3a7c08bb94ce810160df4ae2731e7e990b9f0dbd07e7732
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ samples.csv
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in moto_recall.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jordan Stephens
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # MotoRecall
2
+
3
+ Find vehicle recall info by manufacturer and VIN.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'moto_recall'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install moto_recall
18
+
19
+ ## Usage
20
+
21
+ Find vehicle recall data by make and VIN.
22
+
23
+ MotoRecall.find(:chevrolet, "1G1ZC5E14BF265574")
24
+
25
+ > **NOTE**: Toyota/Lexus/Scion are not supported right now because they require a CAPTCHA to be solved.
26
+
27
+ ## Maintenance and Sampling
28
+
29
+ The problem of presenting uniform data from a disparate set of remote services is difficult—particularly from the perspective of maintenance, considering that breaking changes are likely to occur without warning. To help with monitoring the parity of the code and the remotes, we have included a rake task to aid in sampling responses from each of the remotes.
30
+
31
+ ```
32
+ $ bundle exec rake samples
33
+ ```
34
+
35
+ ### Defining Samples
36
+
37
+ This task uses a data in the file `data/sample_queries.yml` as input with the following format:
38
+
39
+ ```yaml
40
+ ---
41
+ chevrolet: 2011 Chevrolet Malibu
42
+ ford: 2013 Ford Explorer XLT
43
+ chrysler: 2013 Chrysler 300
44
+ # make: vehicle-description
45
+ # ...
46
+ ```
47
+
48
+ Each row in this file has two components: a make name, and a vehicle description. The vehicle description is used to find a set of VINs for that vehicle, each VIN is then passed to `MotoRecall.find` along with the make to get a list of recalls for that VIN.
49
+
50
+ Because finding recalls are directly coupled to a specific VIN, it can be tedious to find appropriate VINs to use for testing. This setup is designed to minimize friction when needing to test MotoRecall with a particular manufacturer's service.
51
+
52
+ ### Examining Samples
53
+
54
+ The `samples` task will write output to a file called `samples.csv`, which will contain a table of attributes for each of the found recalls. See [`samples-example.csv`](https://github.com/motologic/moto_recall/blob/master/samples-example.csv) for an example.
55
+
56
+ ### Adding New Samples
57
+
58
+ When you want to test MotoRecall with a new manufacturer, it is recommended to start by identifying a class of vehicle with recently announced recalls. Head to Google with something like "_volkswagen recalls_". Somewhere in the first handful of results, you are likely to come across an article announcing that VW has announced recalls for some set of it's lineup. For example, let's say this set includes the _2014 Passat_. You would want to add a line to `data/sample_queries.yml` which looks like
59
+
60
+ ```yaml
61
+ volkswagen: 2014 Volkswagen Passat
62
+ ```
63
+
64
+ > **NOTE**: The _description_ component of the entry (the part after the make) is freeform text. This text is used to identify sample VINs to be tested.
65
+
66
+ Now the next time you run the `samples` task, some VINs will be tested which belong to 2014 Passats. If no recalls are found for your query, try another class of vehicle.
67
+
68
+
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require "bundler/gem_tasks"
2
+ require "moto_recall/sampler"
3
+ require "pry-byebug"
4
+ require "yaml"
5
+ require "csv"
6
+
7
+ task :samples do
8
+ include MotoRecall::Sampler::Util
9
+
10
+ MotoRecall::Sampler::CACHE.clear if ENV["NO_CACHE"] == "true"
11
+
12
+ queries_path = "data/sample_queries.yml"
13
+ output_path = "samples.csv"
14
+
15
+ counts = {}
16
+
17
+ queries = YAML.load_file(queries_path)
18
+ samples = MotoRecall::Sampler.run(queries)
19
+
20
+ CSV.open(output_path, "wb") do |csv|
21
+ csv << ["VIN", "make"] + MotoRecall::Recall::KEYS
22
+ samples.each do |sample|
23
+ sample.recalls.each do |recall|
24
+ counts[sample.make] ||= 0
25
+ counts[sample.make] += 1
26
+
27
+ csv << [sample.vin, sample.make] + recall.values
28
+ end
29
+ end
30
+ end
31
+
32
+ puts "\nSampled Counts by Make"
33
+
34
+ counts.each do |make, count|
35
+ log_colored_count(count, "\t#{make}")
36
+ end
37
+
38
+ puts
39
+ puts %Q(*** To clear cache, run with NO_CACHE=true)
40
+ puts
41
+ puts "*" * 60
42
+ puts %Q(Output written to "#{output_path}")
43
+ puts "*" * 60
44
+ puts
45
+ end
46
+
@@ -0,0 +1,41 @@
1
+ ---
2
+ gm: &gm
3
+ GeneralMotors
4
+ fd: &fd
5
+ Ford
6
+ cy: &cy
7
+ Chrysler
8
+ bmw: &bmw
9
+ Bmw
10
+ jg: &jg
11
+ Jaguar
12
+
13
+ buick: *gm
14
+ cadillac: *gm
15
+ chevrolet: *gm
16
+ gmc_truck: *gm
17
+ gmc: *gm
18
+ geo: *gm
19
+ hummer: *gm
20
+ oldsmobile: *gm
21
+ pontiac: *gm
22
+ saab: *gm
23
+ saturn: *gm
24
+
25
+ ford: *fd
26
+ lincoln: *fd
27
+ mercury: *fd
28
+
29
+ chrysler: *cy
30
+ dodge: *cy
31
+ jeep: *cy
32
+ plymouth: *cy
33
+ eagle: *cy
34
+ ram: *cy
35
+ fiat: *cy
36
+
37
+ mini: *bmw
38
+ bmw: *bmw
39
+ b_m_w: *bmw
40
+
41
+ land_rover: *jg
@@ -0,0 +1,17 @@
1
+ ---
2
+ chevrolet: 2011 Chevrolet Malibu
3
+ ford: 2013 Ford Explorer XLT
4
+ chrysler: 2013 Chrysler 300
5
+ honda: 2008 Honda Pilot
6
+ nissan: 2014 Nissan Altima SV
7
+ infiniti: 2014 Infiniti Q70
8
+ volkswagen: 2014 Volkswagen Passat
9
+ audi: 2013 Audi A4
10
+ hyundai: 2009 Hyundai Elantra
11
+ kia: 2009 Kia Optima
12
+ mazda: 2008 Mazda Mazda6
13
+ mercedes: 2015 Mercedes CLS 400
14
+ bmw: 2001 BMW 325i
15
+ subaru: 2011 Subaru Impreza
16
+ jaguar: 2004 Jaguar XJ
17
+ volvo: 2012 Volvo S60
@@ -0,0 +1,38 @@
1
+ require "moto_recall/version"
2
+ require "moto_recall/class_map"
3
+ require "moto_recall/recall"
4
+ require "moto_recall/recall_set"
5
+ require "moto_recall/client/generic_client"
6
+ require "moto_recall/client/acura"
7
+ require "moto_recall/client/audi"
8
+ require "moto_recall/client/bmw"
9
+ require "moto_recall/client/chrysler"
10
+ require "moto_recall/client/ford"
11
+ require "moto_recall/client/general_motors"
12
+ require "moto_recall/client/honda"
13
+ require "moto_recall/client/hyundai"
14
+ require "moto_recall/client/infiniti"
15
+ require "moto_recall/client/isuzu"
16
+ require "moto_recall/client/jaguar"
17
+ require "moto_recall/client/kia"
18
+ require "moto_recall/client/mazda"
19
+ require "moto_recall/client/mitsubishi"
20
+ require "moto_recall/client/nissan"
21
+ require "moto_recall/client/subaru"
22
+ require "moto_recall/client/volkswagen"
23
+ require "moto_recall/client/volvo"
24
+
25
+ module MotoRecall
26
+ class ClientException < StandardError; end
27
+ class InvalidVinError < StandardError; end
28
+
29
+ def self.find(make, vin)
30
+ raise InvalidVinError, "VIN is not 17 chars" if vin.length != 17
31
+ client_class = ClassMap.class_for_make(make)
32
+ begin
33
+ client_class.new.find(vin)
34
+ rescue StandardError => error
35
+ raise MotoRecall::ClientException, "(#{client_class}) #{error.class}: #{error.inspect}\n#{error.backtrace.join("\n")}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ require "yaml"
2
+
3
+ module MotoRecall
4
+ class UnsupportedMakeError < StandardError; end
5
+ module ClassMap
6
+ CLASS_MAP_PATH = "data/class_map.yml"
7
+
8
+ def self.class_for_make(make)
9
+ make_class_name = data[make.to_s.downcase] || make.capitalize
10
+ begin
11
+ Object.const_get("MotoRecall::Client::#{make_class_name}")
12
+ rescue NameError
13
+ raise UnsupportedMakeError, "Invalid Make: #{make}"
14
+ end
15
+ end
16
+
17
+ def self.data
18
+ path = File.join(File.dirname(__FILE__), "..", "..", CLASS_MAP_PATH)
19
+ @data || begin
20
+ YAML.load_file(path)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ require "json"
2
+
3
+ # If you make any changes to this file, make sure to also make them in the
4
+ # Honda client as they are pretty much the same running on different domains
5
+
6
+ module MotoRecall::Client
7
+ class Acura < GenericClient
8
+ def self.url(vin)
9
+ "http://owners.acura.com/Recalls/GetRecallsByVin/#{vin}/true"
10
+ end
11
+
12
+ def process(response)
13
+ parsed_response = JSON.parse(response)
14
+ (parsed_response["CampaignTypes"][0]["Campaigns"] || []) rescue []
15
+ end
16
+
17
+ def format(recall)
18
+ {
19
+ type: recall["AgencyType"],
20
+ nhtsa_number: recall["RecallNumber"],
21
+ oem_number: recall["Id"],
22
+ date: recall["RecallDate"],
23
+ title: recall["Description"],
24
+ description: recall["RecallDescription"],
25
+ safety_risk: recall["SafetyRiskDescription"],
26
+ remedy: recall["RemedyDescription"],
27
+ status: recall["RecallStatus"],
28
+ notes: nil
29
+ }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ require "json"
2
+
3
+ module MotoRecall::Client
4
+ class Audi < GenericClient
5
+ def self.url(vin = nil)
6
+ "http://web.audiusa.com/audirecall/vin/#{vin}"
7
+ end
8
+
9
+ def process(response)
10
+ parsed_response = JSON.parse(response)
11
+ parsed_response.has_key?("recalls") ? parsed_response["recalls"] : []
12
+ end
13
+
14
+ def format(recall)
15
+ {
16
+ type: recall["vwgoaActionType"],
17
+ nhtsa_number: recall["nhtsaRecallNumber"],
18
+ oem_number: recall["mfrRecallNumber"],
19
+ date: recall["recallDate"],
20
+ title: recall["vwgoaActionTitle"],
21
+ description: recall["recallDescription"],
22
+ safety_risk: recall["safetyRiskDescription"],
23
+ remedy: recall["remedyDescription"],
24
+ status: recall["mfrRecallStatus"],
25
+ notes: recall["mfrNotes"]
26
+ }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require "json"
2
+
3
+ module MotoRecall::Client
4
+ class Bmw < GenericClient
5
+ def self.url(vin)
6
+ "http://www.bmwusa.com/Services/VinRecallService.svc/GetRecallCampaignsForVin/#{vin}"
7
+ end
8
+
9
+ def process(response)
10
+ parsed_data = JSON.parse(response)
11
+ parsed_data["ViewModel"]["RecallCampaigns"] rescue []
12
+ end
13
+
14
+ def format(recall)
15
+ {
16
+ type: nil,
17
+ nhtsa_number: recall["RecallNumber"],
18
+ oem_number: recall["ManufacturerRecallNumber"],
19
+ date: recall["RecallDate"],
20
+ title: recall["Title"],
21
+ description: recall["Description"],
22
+ safety_risk: recall["SafetyRiskDescription"],
23
+ remedy: recall["RemedyDescription"],
24
+ status: recall["ManufacturerRecallStatus"],
25
+ notes: recall["ManufacturerNotes"]
26
+ }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,51 @@
1
+ module MotoRecall::Client
2
+ class Chrysler < GenericClient
3
+ def self.url(vin)
4
+ "https://www.moparownerconnect.com/oc/us/en-us/sub/Pages/RecallsResults.aspx?Vin=#{vin}"
5
+ end
6
+
7
+ def process(response)
8
+ doc = Nokogiri::HTML(response)
9
+ content = doc.css("#centerbodyContentWrp")
10
+ recalls = []
11
+
12
+ unless content.text.include?("No Outstanding Recalls or Customer Satisfaction Notifications Exist")
13
+ # Chrysler's response has two tables, one for "Safety Recalls"
14
+ # which is at this selector (`#divSafetyRecallsTable table`)
15
+ # and one for "Campaigns" (`#divCampaignTable table`).
16
+ # `2C4RC1BG9ER209216` shows an example of a "Safety Recall"
17
+ # but I haven't yet seen an example of a "Campaign." We will
18
+ # want to make sure that we return content from both tables.
19
+ tables = doc.css("#divSafetyRecallsTable table, #divCampaignTable table")
20
+
21
+ tables.each do |table|
22
+ headers = table.css("tr:first-child th").map { |th| th.text }
23
+ recalls << table.css("tr:not(:first-child)").map do |tr|
24
+ recall = {}
25
+ tr.css("td").each_with_index do |td, i|
26
+ recall[headers[i]] = td.text.strip
27
+ end
28
+ recall
29
+ end
30
+ end
31
+ end
32
+
33
+ recalls.flatten
34
+ end
35
+
36
+ def format(recall)
37
+ {
38
+ type: nil,
39
+ nhtsa_number: recall["NHTSA Recall #"],
40
+ oem_number: recall["Chrysler Recall #"],
41
+ date: recall["Recall Date"],
42
+ title: nil,
43
+ description: recall["Repair Description"],
44
+ safety_risk: recall["Safety Defect/Non Compliance Description and Safety Risk"],
45
+ remedy: nil,
46
+ status: recall["Recall Status"],
47
+ notes: nil
48
+ }
49
+ end
50
+ end
51
+ end