Skip to content

Commit

Permalink
Add NHTSA DB make/model querying, bump version to 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
pmundt committed Sep 20, 2019
1 parent f1b3bbd commit d95357f
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 25 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.1.0

- Extend decoder to optionally query the `NHTSA Vehicle API` to provide make, model,
and vehicle type information.
- Elaborate `dartdoc` comments and documentation

## 0.0.1+1

- Restrict the `meta` package version to 1.1.6 to match the Flutter SDK limitations.
Expand Down
62 changes: 48 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ A VIN decoding and validation library for Dart.

`vin_decoder` provides a simple decoding and validation library for Vehicle Identification Numbers (VINs) based on
ISO 3779:2009 and World Manufacturer Identifiers (WMIs) based on ISO 3780:2009.

The decoder can be used standalone in an offline mode (the default behaviour, as per earlier versions of the API), or
can be further enriched by querying additional VIN information from the [NHTSA Vehicle API][nhtsa], such as the precise
make, model, and vehicle type in extended mode.

[nhtsa]: https://vpic.nhtsa.dot.gov/api/Home

## Usage

Expand All @@ -16,23 +22,51 @@ A simple usage example:
```dart
import 'package:vin_decoder/vin_decoder.dart';
main() {
VIN vin = VIN(number: 'WP0ZZZ99ZTS392124');
print('WMI: ${vin.wmi}');
print('VDS: ${vin.vds}');
print('VIS: ${vin.vis}');
print("Model year is " + vin.modelYear());
print("Serial number is " + vin.serialNumber());
print("Assembly plant is " + vin.assemblyPlant());
print("Manufacturer is " + vin.getManufacturer());
print("Year is " + vin.getYear().toString());
print("Region is " + vin.getRegion());
print("VIN string is " + vin.toString());
void main() async {
var vin = VIN(number: 'WP0ZZZ99ZTS392124', extended: true);
print('WMI: ${vin.wmi}');
print('VDS: ${vin.vds}');
print('VIS: ${vin.vis}');
print("Model year is " + vin.modelYear());
print("Serial number is " + vin.serialNumber());
print("Assembly plant is " + vin.assemblyPlant());
print("Manufacturer is " + vin.getManufacturer());
print("Year is " + vin.getYear().toString());
print("Region is " + vin.getRegion());
print("VIN string is " + vin.toString());
// The following calls are to the NHTSA DB, and are carried out asynchronously
var make = await vin.getMakeAsync();
print("Make is ${make}");
var model = await vin.getModelAsync();
print("Model is ${model}");
var type = await vin.getVehicleTypeAsync();
print("Type is ${type}");
}
```

which produces the following:

```shell script
WMI: WP0
VDS: ZZZ99Z
VIS: TS392124
Model year is T
Serial number is 92124
Assembly plant is S
Manufacturer is Porsche
Year is 1996
Region is EU
VIN string is WP0ZZZ99ZTS392124
Make is Porsche
Model is 911
Type is Passenger Car
```

## Features and bugs

Please file feature requests and bugs at the [issue tracker][tracker].
Expand Down
14 changes: 12 additions & 2 deletions example/vin_decoder_example.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:vin_decoder/vin_decoder.dart';

main() {
var vin = VIN(number: 'WP0ZZZ99ZTS392124');
void main() async {
var vin = VIN(number: 'WP0ZZZ99ZTS392124', extended: true);

print('WMI: ${vin.wmi}');
print('VDS: ${vin.vds}');
Expand All @@ -14,4 +14,14 @@ main() {
print("Year is " + vin.getYear().toString());
print("Region is " + vin.getRegion());
print("VIN string is " + vin.toString());

// The following calls are to the NHTSA DB, and are carried out asynchronously
var make = await vin.getMakeAsync();
print("Make is ${make}");

var model = await vin.getModelAsync();
print("Model is ${model}");

var type = await vin.getVehicleTypeAsync();
print("Type is ${type}");
}
101 changes: 101 additions & 0 deletions lib/src/nhtsa_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import 'package:basic_utils/basic_utils.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class NHTSAResult {
String value;
String valueId;
String variable;
int variableId;

NHTSAResult({this.value, this.valueId, this.variable, this.variableId});

NHTSAResult.fromJson(Map<String, dynamic> json) {
value = json['Value'];
valueId = json['ValueId'];
variable = json['Variable'];
variableId = json['VariableId'];
}

@override
String toString() {
return 'NHTSAResult[value=$value, valueId=$valueId, variable=$variable, variableId=$variableId]';
}
}

class NHTSAVehicleInfo {
int count;
String message;
String searchCriteria;
List<NHTSAResult> results;

NHTSAVehicleInfo(
{this.count, this.message, this.searchCriteria, this.results});

NHTSAVehicleInfo.fromJson(Map<String, dynamic> json) {
count = json['Count'];
message = json['Message'];
searchCriteria = json['SearchCriteria'];
if (json['Results'] != null) {
results = List<NHTSAResult>();
json['Results'].forEach((v) {
if (v['Value'] != null) {
results.add(NHTSAResult.fromJson(v));
}
});
}
}

static String normalizeStringValue(String s) {
return s.splitMapJoin(' ',
onNonMatch: (m) => StringUtils.capitalize(m.toLowerCase()));
}

ExtendedVehicleInfo toExtendedVehicleInfo() {
final ExtendedVehicleInfo info = ExtendedVehicleInfo();

results.forEach((f) {
switch (f.variable) {
case "Vehicle Type":
info.vehicleType = normalizeStringValue(f.value);
break;
case "Make":
info.make = normalizeStringValue(f.value);
break;
case "Model":
info.model = normalizeStringValue(f.value);
break;
}
});

return info;
}

@override
String toString() {
return 'NHTSAVehicleInfo[count=$count, message=$message, searchCriteria=$searchCriteria, results=$results]';
}
}

class ExtendedVehicleInfo {
String make;
String model;
String vehicleType;

static Future<ExtendedVehicleInfo> getExtendedVehicleInfo(String vin) async {
var path = 'https://vpic.nhtsa.dot.gov/api//vehicles/DecodeVin/' + vin + '?format=json';
final response = await http.get(path);

if (response.statusCode == 200) {
var vehicleInfo = NHTSAVehicleInfo.fromJson(jsonDecode(response.body));
return vehicleInfo.toExtendedVehicleInfo();
}

return null;
}

@override
String toString() {
return 'ExtendedVehicleInfo[make=$make, model=$model, vehicleType=$vehicleType]';
}
}
67 changes: 60 additions & 7 deletions lib/src/vin_decoder_base.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import 'package:meta/meta.dart';
import 'dart:collection';
import 'manufacturers.dart';
import 'nhtsa_model.dart';

class VIN {
/// The VIN that the class was instantiated with.
final String number;

/// The World Manufacturer Identifier (WMI) code for the specified [number].
final String wmi;

/// The Vehicle Descriptor Section (VDS) code for the specified [number].
final String vds;

/// The Vehicle Identifier Section (VIS) code for the specified [number].
final String vis;

VIN({@required this.number})
/// Try to obtain extended information for the VIN from the NHTSA database.
final bool extended;
ExtendedVehicleInfo _info;

VIN({@required this.number, this.extended = false})
: wmi = normalize(number).substring(0, 3),
vds = normalize(number).substring(3, 9),
vis = normalize(number).substring(9, 17);
Expand All @@ -21,9 +32,11 @@ class VIN {
return RegExp(r"^[a-zA-Z0-9]+$").hasMatch(value) && value.length == 17;
}

/// Provide a normalized VIN string, regardless of the input format.
static String normalize(String number) =>
number.toUpperCase().replaceAll('-', '');

/// Obtain the encoded manufacturing year in YYYY format.
int getYear() {
Map<String, int> map = HashMap<String, int>();

Expand Down Expand Up @@ -61,22 +74,30 @@ class VIN {
return map[modelYear()];
}

/// Obtain the 2-character region code for the manufacturing region.
String getRegion() {
if (RegExp(r"[A-H]", caseSensitive: false).hasMatch(this.number[0]))
if (RegExp(r"[A-H]", caseSensitive: false).hasMatch(this.number[0])) {
return "AF";
if (RegExp(r"[J-R]", caseSensitive: false).hasMatch(this.number[0]))
}
if (RegExp(r"[J-R]", caseSensitive: false).hasMatch(this.number[0])) {
return "AS";
if (RegExp(r"[S-Z]", caseSensitive: false).hasMatch(this.number[0]))
}
if (RegExp(r"[S-Z]", caseSensitive: false).hasMatch(this.number[0])) {
return "EU";
if (RegExp(r"[1-5]", caseSensitive: false).hasMatch(this.number[0]))
}
if (RegExp(r"[1-5]", caseSensitive: false).hasMatch(this.number[0])) {
return "NA";
if (RegExp(r"[6-7]", caseSensitive: false).hasMatch(this.number[0]))
}
if (RegExp(r"[6-7]", caseSensitive: false).hasMatch(this.number[0])) {
return "OC";
if (RegExp(r"[8-9]", caseSensitive: false).hasMatch(this.number[0]))
}
if (RegExp(r"[8-9]", caseSensitive: false).hasMatch(this.number[0])) {
return "SA";
}
return "Unknown";
}

/// Get the full name of the vehicle manufacturer as defined by the [wmi].
String getManufacturer() {
return manufacturers[this.wmi];
}
Expand All @@ -88,10 +109,42 @@ class VIN {
return (getRegion() != "EU") ? normalize(this.number)[8] : null;
}

/// Extract the single-character model year from the [number].
String modelYear() => normalize(this.number)[9];

/// Extract the single-character assembly plant designator from the [number].
String assemblyPlant() => normalize(this.number)[10];

/// Extract the serial number from the [number].
String serialNumber() => normalize(this.number).substring(12, 17);

void _fetchExtendedVehicleInfo() async {
if (this._info == null && extended == true) {
this._info = await ExtendedVehicleInfo.getExtendedVehicleInfo(this.number);
}
}

/// Get the Make of the vehicle from the NHTSA database if [extended] mode
/// is enabled.
Future<String> getMakeAsync() async {
await _fetchExtendedVehicleInfo();
return this._info?.make;
}

/// Get the Model of the vehicle from the NHTSA database if [extended] mode
/// is enabled.
Future<String> getModelAsync() async {
await _fetchExtendedVehicleInfo();
return this._info?.model;
}

/// Get the Vehicle Type from the NHTSA database if [extended] mode is
/// enabled.
Future<String> getVehicleTypeAsync() async {
await _fetchExtendedVehicleInfo();
return this._info?.vehicleType;
}

@override
String toString() => this.wmi + this.vds + this.vis;
}
7 changes: 5 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: vin_decoder
description:
A simple Dart library for decoding and validating Vehicle Identification Numbers (VINs) based on ISO 3779:2009 and
World Manufacturer Identifiers (WMIs) based on ISO 3780:2009.
World Manufacturer Identifiers (WMIs) based on ISO 3780:2009. Further enriched by data obtained from the NHTSA
database.

version: 0.0.1+1
version: 0.1.0
homepage: https://www.github.com/adaptant-labs/vin-decoder-dart
author: Adaptant Labs <[email protected]>

Expand All @@ -14,6 +15,8 @@ dependencies:
# The Flutter SDK presently depends on v1.1.6 of the meta package, so we artificially
# constrain the version number until this limitation is addressed in a future SDK update.
meta: ^1.1.6
basic_utils: ^2.0.1
http: ^0.12.0+2

dev_dependencies:
test: ^1.0.0

0 comments on commit d95357f

Please sign in to comment.