moto_recall 0.0.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +68 -0
- data/Rakefile +46 -0
- data/data/class_map.yml +41 -0
- data/data/sample_queries.yml +17 -0
- data/lib/moto_recall.rb +38 -0
- data/lib/moto_recall/class_map.rb +24 -0
- data/lib/moto_recall/client/acura.rb +32 -0
- data/lib/moto_recall/client/audi.rb +29 -0
- data/lib/moto_recall/client/bmw.rb +29 -0
- data/lib/moto_recall/client/chrysler.rb +51 -0
- data/lib/moto_recall/client/ford.rb +49 -0
- data/lib/moto_recall/client/general_motors.rb +40 -0
- data/lib/moto_recall/client/generic_client.rb +44 -0
- data/lib/moto_recall/client/honda.rb +32 -0
- data/lib/moto_recall/client/hyundai.rb +46 -0
- data/lib/moto_recall/client/infiniti.rb +29 -0
- data/lib/moto_recall/client/isuzu.rb +52 -0
- data/lib/moto_recall/client/jaguar.rb +29 -0
- data/lib/moto_recall/client/kia.rb +30 -0
- data/lib/moto_recall/client/mazda.rb +26 -0
- data/lib/moto_recall/client/mitsubishi.rb +46 -0
- data/lib/moto_recall/client/nissan.rb +29 -0
- data/lib/moto_recall/client/subaru.rb +29 -0
- data/lib/moto_recall/client/volkswagen.rb +29 -0
- data/lib/moto_recall/client/volvo.rb +78 -0
- data/lib/moto_recall/recall.rb +40 -0
- data/lib/moto_recall/recall_set.rb +22 -0
- data/lib/moto_recall/sampler.rb +18 -0
- data/lib/moto_recall/sampler/runner.rb +22 -0
- data/lib/moto_recall/sampler/sample.rb +30 -0
- data/lib/moto_recall/sampler/util.rb +40 -0
- data/lib/moto_recall/version.rb +3 -0
- data/moto_recall.gemspec +31 -0
- data/samples-example.csv +191 -0
- data/spec/moto_recall/class_map_spec.rb +24 -0
- data/spec/moto_recall/recall_set_spec.rb +27 -0
- data/spec/moto_recall/recall_spec.rb +24 -0
- data/spec/moto_recall_spec.rb +28 -0
- data/spec/spec_helper.rb +20 -0
- 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
data/Gemfile
ADDED
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
|
+
|
data/data/class_map.yml
ADDED
@@ -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
|
data/lib/moto_recall.rb
ADDED
@@ -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
|