Skip to content

Commit

Permalink
Expose NHTSA API accessors
Browse files Browse the repository at this point in the history
Presently the NHTSA DB results are trapped and filtered for the specific data points required by the VIN decoder, but this can be generalized in order to permit other variables exposed by the NHTSA API to be directly queried.

Resolves adaptant-labs#8
  • Loading branch information
pmundt committed Aug 26, 2021
1 parent 2ce1ced commit dc082f1
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 54 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.0

- Expose helpers for querying NHTSA DB and accessing extended vehicle information (requested by @ride4sun, issue #8)

## 0.1.4+1

- Revert null-safety changes from stable release to satisfy SDK constraints
Expand Down
7 changes: 7 additions & 0 deletions example/vin_decoder_example.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:vin_decoder/nhtsa.dart';
import 'package:vin_decoder/vin_decoder.dart';

void main() async {
Expand Down Expand Up @@ -25,6 +26,12 @@ void main() async {
var type = await vin.getVehicleTypeAsync();
print("Type is ${type}");

var info = await NHTSA.decodeVin(vin.number);
print('Plant Country is ' + info.value('Plant Country'));

var values = await NHTSA.decodeVinValues(vin.number);
print('Manufacturer from NHTSA DB is ' + values['Manufacturer']);

var generated = VINGenerator().generate();
print('Randomly Generated VIN is ${generated}');
}
5 changes: 5 additions & 0 deletions lib/nhtsa.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Support for querying NHTSA database by VIN.
// @dart=2.1
library nhtsa;

export 'src/nhtsa_model.dart';
113 changes: 67 additions & 46 deletions lib/src/nhtsa_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,63 @@ import 'package:basic_utils/basic_utils.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

// NHTSA Results not relevant for a specific vehicle can be either null or N/A
const String _RESULT_NOT_APPLICABLE = 'Not Applicable';

/// A wrapper for the NHTSA REST API
class NHTSA {
static const String _uriBase = 'https://vpic.nhtsa.dot.gov/api/vehicles';

/// Obtain information about a given [vin] from the NHTSA DB.
static Future<NHTSAVehicleInfo> decodeVin(String vin) async {
var path = _uriBase + '/DecodeVin/' + vin + '?format=json';
final response = await http.get(Uri.parse(path));

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

return null;
}

/// Obtain a map of key/value pairs containing known values for a given [vin]
static Future<Map<String, dynamic>> decodeVinValues(String vin) async {
var path = _uriBase + '/DecodeVinValues/' + vin + '?format=json';
final response = await http.get(Uri.parse(path));

// The DecodeVinValues endpoint returns a single Results object with all
// variables and values as an array of encapsulated key/value pairs.
// Manually unpack this in order to provide the caller a populated Dart map.
if (response.statusCode == 200) {
Map<String, dynamic> data = jsonDecode(response.body);
Map<String, dynamic> map = data['Results'][0];
// Discard empty and not applicable entries from map
map.removeWhere((key, value) =>
value == null || value == _RESULT_NOT_APPLICABLE || value == '');
return map;
}

return null;
}
}

/// The result of a single data point from the NHTSA DB for a specific variable.
class NHTSAResult {
/// The value associated with a given [variable] or [variableId]
String value;

/// The ID number associated with a given [value]
String valueId;

/// The variable name
String variable;

/// The ID number of a given [variable]
int variableId;

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

/// Create a new [NHTSAResult] instance from a fixed JSON payload
NHTSAResult.fromJson(Map<String, dynamic> json) {
value = json['Value'];
valueId = json['ValueId'];
Expand All @@ -23,81 +72,53 @@ class NHTSAResult {
}
}

/// Extended vehicle information for a specific VIN obtained from the NHTSA DB.
class NHTSAVehicleInfo {
int count;
String message;
String searchCriteria;
List<NHTSAResult> results;
List<NHTSAResult> results = [];

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

/// Create a new [NHTSAVehicleInfo] instance from a fixed JSON payload
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) {
if (v['Value'] != null &&
v['Value'] != _RESULT_NOT_APPLICABLE &&
v['Value'] != '') {
results.add(NHTSAResult.fromJson(v));
}
});
}
}

static String normalizeStringValue(String s) {
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]';
/// Lookup the value of a variable by its [variableId] in the NHTSA DB results
String valueFromId(int variableId) {
var result = results.singleWhere((e) => e.variableId == variableId,
orElse: () => null);
return result != null ? _normalizeStringValue(result.value) : null;
}
}

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(Uri.parse(path));

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

return null;
/// Lookup the value of a named [variable] in the NHTSA DB results
String value(String variable) {
var result =
results.singleWhere((e) => e.variable == variable, orElse: () => null);
return result != null ? _normalizeStringValue(result.value) : null;
}

@override
String toString() {
return 'ExtendedVehicleInfo[make=$make, model=$model, vehicleType=$vehicleType]';
return 'NHTSAVehicleInfo[count=$count, message=$message, searchCriteria=$searchCriteria, results=$results]';
}
}
15 changes: 7 additions & 8 deletions lib/src/vin_decoder_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class VIN {

/// Try to obtain extended information for the VIN from the NHTSA database.
final bool extended;
ExtendedVehicleInfo _info;
Map<String, dynamic> _vehicleInfo = {};

VIN({@required this.number, this.extended = false})
: wmi = normalize(number).substring(0, 3),
Expand Down Expand Up @@ -98,32 +98,31 @@ class VIN {
/// Extract the serial number from the [number].
String serialNumber() => normalize(this.number).substring(12, 17);

Future _fetchExtendedVehicleInfo() async {
if (this._info == null && extended == true) {
this._info =
await ExtendedVehicleInfo.getExtendedVehicleInfo(this.number);
Future<void> _fetchExtendedVehicleInfo() async {
if (this._vehicleInfo.isEmpty && extended == true) {
this._vehicleInfo = await NHTSA.decodeVinValues(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;
return this._vehicleInfo['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;
return this._vehicleInfo['Model'];
}

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

@override
Expand Down

0 comments on commit dc082f1

Please sign in to comment.