Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
210
pkg/vindecoder/vin_check_test.go
Normal file
210
pkg/vindecoder/vin_check_test.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package vindecoder_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/vindecoder"
|
||||
)
|
||||
|
||||
var vwGer2013Valid = "WVWBN7AN6DE546002"
|
||||
var vwGer2013Invalid1 = "WVWBN7AN1DE546002"
|
||||
var vwGer2013Invalid2 = "WVWBN7AN6DE54600"
|
||||
var miniGer2009Valid = "WMWMS335X9TY38985"
|
||||
var bugatFra1998Valid = "VF9SP3V31JM795073"
|
||||
var dodgeUsa1998Valid = "1B3ES42C4WD736523"
|
||||
var fiskerVinValid = "VCF1ZBU23PG001209"
|
||||
|
||||
// TEMP
|
||||
var fskUsa2021Valid = "1F1BN7AN7MA000001"
|
||||
|
||||
// TEMP
|
||||
|
||||
//var unkJap2013Valid = "JS1GR7MA7D2101136"
|
||||
//var audiGer2012Valid = "WAUFFAFM3CA000000"
|
||||
|
||||
func TestVinDecoderCountry(t *testing.T) {
|
||||
// there should be more tests for cases where second character is 1-9, 0, but it's hard to find valid vins
|
||||
|
||||
// USA
|
||||
vi1, _ := vindecoder.DecodeVIN(dodgeUsa1998Valid)
|
||||
if !strings.Contains(vi1.Country, "United States") {
|
||||
t.Errorf(testhelper.TestErrorTemplate, dodgeUsa1998Valid, "United States", vi1.Country)
|
||||
}
|
||||
|
||||
// Germany
|
||||
vi2, _ := vindecoder.DecodeVIN(vwGer2013Valid)
|
||||
if !strings.Contains(vi2.Country, "Germany") {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Valid, "Germany", vi2.Country)
|
||||
}
|
||||
valid := vindecoder.ValidateVINSimple(fiskerVinValid)
|
||||
if !valid {
|
||||
t.Errorf(testhelper.TestErrorTemplate, fiskerVinValid, "Invalid", vi2.Country)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestVinDecoderWmi(t *testing.T) {
|
||||
// normal
|
||||
vi1, _ := vindecoder.DecodeVIN(vwGer2013Valid)
|
||||
if !strings.Contains(vi1.Manufacturer, "Volkswagen") {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Valid, "Volkswagen", vi1.Manufacturer)
|
||||
}
|
||||
|
||||
// small production
|
||||
vi2, _ := vindecoder.DecodeVIN(bugatFra1998Valid)
|
||||
if !strings.Contains(vi2.Manufacturer, "Bugatti") {
|
||||
t.Errorf(testhelper.TestErrorTemplate, bugatFra1998Valid, "Bugatti", vi2.Manufacturer)
|
||||
}
|
||||
|
||||
// wild card
|
||||
mfg1 := vindecoder.LookupManufacturerByWmiCode("4F3")
|
||||
if !strings.Contains(mfg1, "Mazda") {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "4F3", "Mazda", mfg1)
|
||||
}
|
||||
|
||||
// normal unknown wmi
|
||||
mfg2 := vindecoder.LookupManufacturerByWmiCode("JM2")
|
||||
if mfg2 != "JM2" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "JM2", "JM2", mfg2)
|
||||
}
|
||||
|
||||
// TEMP
|
||||
// normal
|
||||
viFsk, _ := vindecoder.DecodeVIN(fskUsa2021Valid)
|
||||
if !strings.Contains(viFsk.Manufacturer, "Fisker") {
|
||||
t.Errorf(testhelper.TestErrorTemplate, fskUsa2021Valid, "Fisker", viFsk.Manufacturer)
|
||||
}
|
||||
// TEMP
|
||||
}
|
||||
|
||||
func TestVinDecoderSerialNumber(t *testing.T) {
|
||||
// normal
|
||||
vi1, _ := vindecoder.DecodeVIN(vwGer2013Valid)
|
||||
if vi1.SerialNumber != "546002" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Valid, "546002", vi1.SerialNumber)
|
||||
}
|
||||
|
||||
// small production
|
||||
vi2, _ := vindecoder.DecodeVIN(bugatFra1998Valid)
|
||||
if vi2.SerialNumber != "073" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, bugatFra1998Valid, "073", vi2.SerialNumber)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVinDecoderCheckDigit(t *testing.T) {
|
||||
// valid
|
||||
vi1, ok := vindecoder.DecodeVIN(vwGer2013Valid)
|
||||
if ok == false {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Valid, true, ok)
|
||||
}
|
||||
if !vi1.IsValid {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Valid, true, vi1.IsValid)
|
||||
}
|
||||
|
||||
// valid - lowercase
|
||||
var vwGer2013Valid_lower = strings.ToLower(vwGer2013Valid)
|
||||
vi1a, ok := vindecoder.DecodeVIN(vwGer2013Valid_lower)
|
||||
if ok == false {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Valid_lower, true, ok)
|
||||
}
|
||||
if !vi1a.IsValid {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Valid_lower, true, vi1a.IsValid)
|
||||
}
|
||||
|
||||
// invalid
|
||||
vi2, ok := vindecoder.DecodeVIN(vwGer2013Invalid1)
|
||||
if ok == true {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Invalid1, false, ok)
|
||||
}
|
||||
if vi2.IsValid {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Invalid1, false, vi2.IsValid)
|
||||
}
|
||||
|
||||
vi3, ok := vindecoder.DecodeVIN(vwGer2013Invalid2)
|
||||
if ok == true {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Invalid2, false, ok)
|
||||
}
|
||||
if vi3.IsValid {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Invalid2, false, vi3.IsValid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVinDecoderYear(t *testing.T) {
|
||||
// before 2010
|
||||
vi1, _ := vindecoder.DecodeVIN(miniGer2009Valid)
|
||||
if vi1.Year != 2009 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, miniGer2009Valid, 2013, vi1.Year)
|
||||
}
|
||||
|
||||
// after 2010
|
||||
vi2, _ := vindecoder.DecodeVIN(vwGer2013Valid)
|
||||
if vi2.Year != 2013 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Valid, 2013, vi2.Year)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVinDecoderModelDetails(t *testing.T) {
|
||||
vi, _ := vindecoder.DecodeVIN(vwGer2013Valid)
|
||||
if vi.ModelDetails != "BN7AN" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, vwGer2013Valid, "BN7AN", vi.ModelDetails)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextVin(t *testing.T) {
|
||||
// test vin generation
|
||||
startVin := "WVWBN7AN6DE546002"
|
||||
nextVin, err := vindecoder.NextVIN(startVin)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, "nil", err)
|
||||
}
|
||||
if strings.LastIndex(nextVin, "546003") < 0 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, "WVWBN7AN*DE546003", nextVin)
|
||||
}
|
||||
|
||||
startVin = "WVWBN7AN6DE546XX9"
|
||||
nextVin, err = vindecoder.NextVIN(startVin)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, "nil", err)
|
||||
}
|
||||
if strings.LastIndex(nextVin, "546X00") < 0 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, "WVWBN7AN6DE546X00", nextVin)
|
||||
}
|
||||
|
||||
startVin = "WVWBN7AN6DE546999"
|
||||
nextVin, err = vindecoder.NextVIN(startVin)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, "nil", err)
|
||||
}
|
||||
if strings.LastIndex(nextVin, "547000") < 0 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, "WVWBN7AN6DE547000", nextVin)
|
||||
}
|
||||
|
||||
startVin = "WVWBN7AN6DE999999"
|
||||
nextVin, err = vindecoder.NextVIN(startVin)
|
||||
if err == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, "overflow", "nil")
|
||||
}
|
||||
if strings.LastIndex(nextVin, "000000") < 0 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, "WVWBN7AN6DE000000", nextVin)
|
||||
}
|
||||
|
||||
startVin = "WVWBN7AN6DE999995"
|
||||
nextVins, err := vindecoder.NextNVINs(startVin, 10)
|
||||
if err == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, "rollover", "nil")
|
||||
}
|
||||
if len(nextVins) != 10 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, startVin, 10, len(nextVins))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestEmptyVIN(t *testing.T){
|
||||
ok := vindecoder.ValidateVINSimple("")
|
||||
if ok {
|
||||
t.Error("Expected Invalid VIN")
|
||||
}
|
||||
}
|
||||
249
pkg/vindecoder/vin_country_lookup.go
Normal file
249
pkg/vindecoder/vin_country_lookup.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package vindecoder
|
||||
|
||||
type rangeSegment struct {
|
||||
low byte
|
||||
high byte
|
||||
country string
|
||||
}
|
||||
|
||||
// source: https://en.wikipedia.org/wiki/Vehicle_identification_number
|
||||
//
|
||||
// note ascending sort order: A-Z. then 1-9, then 0
|
||||
var vinCountryCodeRangeMap = map[byte][]rangeSegment{
|
||||
'A': {
|
||||
rangeSegment{low: 'A', high: 'H', country: "South Africa"},
|
||||
rangeSegment{low: 'J', high: 'K', country: "Cote d'Ivoire"},
|
||||
rangeSegment{low: 'L', high: 'L', country: "algeria"},
|
||||
rangeSegment{low: 'M', high: 'N', country: "Cote d'Ivoire"},
|
||||
// rangeSegment{low: 'P', high: 'P', country: ""},
|
||||
rangeSegment{low: 'R', high: 'R', country: "algeria"},
|
||||
// rangeSegment{low: 'S', high: 'Z', country: ""},
|
||||
// rangeSegment{low: '1', high: '9', country: ""},
|
||||
// rangeSegment{low: '0', high: '0', country: ""}
|
||||
},
|
||||
'B': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "SAngola"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Kenya"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Tanzania"},
|
||||
// rangeSegment{low: 'S', high: '0', country: ""},
|
||||
},
|
||||
'C': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Benin"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Madagascar"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Tunisia"},
|
||||
// rangeSegment{low: 'S', high: '0", "" },
|
||||
},
|
||||
'D': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Egypt"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Morocco"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Zambia"},
|
||||
// rangeSegment{low: 'S', high: '0', country: ""},
|
||||
},
|
||||
'E': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Ethiopia"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Mozambique"},
|
||||
// rangeSegment{low: 'L', high: '0', country: ""},
|
||||
},
|
||||
'F': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Ghana"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Nigeria"},
|
||||
// rangeSegment{low: 'L', high: '0', country: ""},
|
||||
},
|
||||
// 'G': {
|
||||
// rangeSegment{low: 'A', high: '0', country: ""},
|
||||
// },
|
||||
// 'H': {
|
||||
// rangeSegment{low: 'A', high: '0', country: ""},
|
||||
// },
|
||||
'J': {
|
||||
rangeSegment{low: 'A', high: 'Z', country: "Japan"},
|
||||
rangeSegment{low: '1', high: '9', country: "Japan"},
|
||||
rangeSegment{low: '0', high: '0', country: "Japan"},
|
||||
},
|
||||
'K': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Sri Lanka"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Israel"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Korea (South)"},
|
||||
rangeSegment{low: 'S', high: 'S', country: "Jordan"},
|
||||
rangeSegment{low: 'T', high: 'Z', country: "Kazakhstan"},
|
||||
rangeSegment{low: '1', high: '9', country: "Kazakhstan"},
|
||||
rangeSegment{low: '0', high: '0', country: "Kazakhstan"},
|
||||
},
|
||||
'L': {
|
||||
rangeSegment{low: 'A', high: 'Z', country: "China"},
|
||||
rangeSegment{low: '1', high: '9', country: "China"},
|
||||
rangeSegment{low: '0', high: '0', country: "China"},
|
||||
},
|
||||
'M': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "India"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Indonesia"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Thailand"},
|
||||
rangeSegment{low: 'S', high: 'Y', country: "Myanmar"},
|
||||
rangeSegment{low: 'Z', high: 'Z', country: "India"},
|
||||
rangeSegment{low: '1', high: '9', country: "Myanmar"},
|
||||
rangeSegment{low: '0', high: '0', country: "Myanmar"},
|
||||
},
|
||||
'N': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Iran"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Pakistan"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Turkey"},
|
||||
// rangeSegment{low: 'S', high: '0', country: "" },
|
||||
},
|
||||
'P': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Philippines"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Singapore"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Malaysia"},
|
||||
// rangeSegment{low: 'S', high: '0', country: "" },
|
||||
rangeSegment{low: 'A', high: 'E', country: "United Arab Emirates"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Taiwan"},
|
||||
},
|
||||
'R': {
|
||||
rangeSegment{low: 'L', high: 'R', country: "Vietnam"},
|
||||
rangeSegment{low: 'S', high: 'Z', country: "Saudi Arabia"},
|
||||
rangeSegment{low: '1', high: '9', country: "Saudi Arabia"},
|
||||
rangeSegment{low: '0', high: '0', country: "Saudi Arabia"},
|
||||
},
|
||||
'S': {
|
||||
rangeSegment{low: 'A', high: 'M', country: "United Kingdom"},
|
||||
rangeSegment{low: 'N', high: 'T', country: "Germany"},
|
||||
rangeSegment{low: 'U', high: 'Z', country: "Poland"},
|
||||
rangeSegment{low: '1', high: '4', country: "Latvia"},
|
||||
// rangeSegment{low: '5', high: '0', country: "" },
|
||||
},
|
||||
'T': {
|
||||
rangeSegment{low: 'A', high: 'H', country: "Switzerland"},
|
||||
rangeSegment{low: 'J', high: 'P', country: "Czech Republic"},
|
||||
rangeSegment{low: 'R', high: 'V', country: "Hungary"},
|
||||
rangeSegment{low: 'W', high: 'Z', country: "Portugal"},
|
||||
rangeSegment{low: '1', high: '1', country: "Portugal"},
|
||||
// rangeSegment{low: '2', high: '0', country: "" },
|
||||
},
|
||||
'U': {
|
||||
// rangeSegment{low: 'A', high: 'G', country: "" },
|
||||
rangeSegment{low: 'H', high: 'M', country: "Denmark"},
|
||||
rangeSegment{low: 'N', high: 'T', country: "Ireland"},
|
||||
rangeSegment{low: 'U', high: 'Z', country: "Romania"},
|
||||
// rangeSegment{low: '1', high: '4', country: "" },
|
||||
rangeSegment{low: '5', high: '7', country: "Slovakia"},
|
||||
// rangeSegment{low: '8', high: '0', country: "" },
|
||||
},
|
||||
'V': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Austria"},
|
||||
rangeSegment{low: 'F', high: 'R', country: "France"},
|
||||
rangeSegment{low: 'S', high: 'W', country: "Spain"},
|
||||
rangeSegment{low: 'X', high: 'Z', country: "Serbia"},
|
||||
rangeSegment{low: '1', high: '2', country: "Serbia"},
|
||||
rangeSegment{low: '3', high: '5', country: "Croatia"},
|
||||
rangeSegment{low: '6', high: '9', country: "Estonia"},
|
||||
rangeSegment{low: '0', high: '0', country: "Estonia"},
|
||||
},
|
||||
'W': {
|
||||
rangeSegment{low: 'A', high: 'Z', country: "Germany"},
|
||||
rangeSegment{low: '1', high: '9', country: "Germany"},
|
||||
rangeSegment{low: '0', high: '0', country: "Germany"},
|
||||
},
|
||||
'X': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Bulgaria"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Greece"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Netherlands"},
|
||||
rangeSegment{low: 'S', high: 'W', country: "Russia"},
|
||||
rangeSegment{low: 'X', high: 'Z', country: "Luxembourg"},
|
||||
rangeSegment{low: '1', high: '2', country: "Luxembourg"},
|
||||
rangeSegment{low: '3', high: '9', country: "Russia"},
|
||||
rangeSegment{low: '0', high: '0', country: "Russia"},
|
||||
},
|
||||
'Y': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Belgium"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Finland"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Malta"},
|
||||
rangeSegment{low: 'S', high: 'W', country: "Sweden"},
|
||||
rangeSegment{low: 'X', high: 'Z', country: "Norway"},
|
||||
rangeSegment{low: '1', high: '2', country: "Norway"},
|
||||
rangeSegment{low: '3', high: '5', country: "Belarus"},
|
||||
rangeSegment{low: '6', high: '9', country: "Ukraine"},
|
||||
rangeSegment{low: '0', high: '0', country: "Ukraine"},
|
||||
},
|
||||
'Z': {
|
||||
rangeSegment{low: 'A', high: 'R', country: "Italy"},
|
||||
// rangeSegment{low: 'S', high: 'W', country: "" },
|
||||
rangeSegment{low: 'X', high: 'Z', country: "Slovenia"},
|
||||
rangeSegment{low: '1', high: '2', country: "Slovenia"},
|
||||
rangeSegment{low: '3', high: '5', country: "Lithuania"},
|
||||
// rangeSegment{low: '6', high: '0', country: "" },
|
||||
},
|
||||
'1': {
|
||||
rangeSegment{low: 'A', high: 'Z', country: "United States"},
|
||||
rangeSegment{low: '1', high: '9', country: "United States"},
|
||||
rangeSegment{low: '0', high: '0', country: "United States"},
|
||||
},
|
||||
'2': {
|
||||
rangeSegment{low: 'A', high: 'Z', country: "Canada"},
|
||||
rangeSegment{low: '1', high: '9', country: "Canada"},
|
||||
rangeSegment{low: '0', high: '0', country: "Canada"},
|
||||
},
|
||||
'3': {
|
||||
rangeSegment{low: 'A', high: 'W', country: "Mexico"},
|
||||
rangeSegment{low: 'X', high: 'Z', country: "Costa Rica"},
|
||||
rangeSegment{low: '1', high: '7', country: "Costa Rica"},
|
||||
rangeSegment{low: '8', high: '9', country: "Cayman Islands"},
|
||||
// rangeSegment{low: '0', high: '0', country: "" },
|
||||
},
|
||||
'4': {
|
||||
rangeSegment{low: 'A', high: 'Z', country: "United States"},
|
||||
rangeSegment{low: '1', high: '9', country: "United States"},
|
||||
rangeSegment{low: '0', high: '0', country: "United States"},
|
||||
},
|
||||
'5': {
|
||||
rangeSegment{low: 'A', high: 'Z', country: "United States"},
|
||||
rangeSegment{low: '1', high: '9', country: "United States"},
|
||||
rangeSegment{low: '0', high: '0', country: "United States"},
|
||||
},
|
||||
'6': {
|
||||
rangeSegment{low: 'A', high: 'Z', country: "Australia"},
|
||||
rangeSegment{low: '1', high: '9', country: "Australia"},
|
||||
rangeSegment{low: '0', high: '0', country: "Australia"},
|
||||
},
|
||||
'7': {
|
||||
rangeSegment{low: 'A', high: 'Z', country: "New Zealand"},
|
||||
rangeSegment{low: '1', high: '9', country: "New Zealand"},
|
||||
rangeSegment{low: '0', high: '0', country: "New Zealand"},
|
||||
},
|
||||
'8': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Argentina"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Chile"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Ecuador"},
|
||||
rangeSegment{low: 'S', high: 'W', country: "Peru"},
|
||||
rangeSegment{low: 'X', high: 'Z', country: "Venezuela"},
|
||||
rangeSegment{low: '1', high: '2', country: "Venezuela"},
|
||||
rangeSegment{low: '2', high: '2', country: "Bolivia"},
|
||||
// rangeSegment{low: '3', high: '0', country: "" },
|
||||
},
|
||||
'9': {
|
||||
rangeSegment{low: 'A', high: 'E', country: "Brazil"},
|
||||
rangeSegment{low: 'F', high: 'K', country: "Colombia"},
|
||||
rangeSegment{low: 'L', high: 'R', country: "Paraguay"},
|
||||
rangeSegment{low: 'S', high: 'W', country: "Uruguay"},
|
||||
rangeSegment{low: 'X', high: 'Z', country: "Trinidad & Tobago"},
|
||||
rangeSegment{low: '1', high: '2', country: "Trinidad & Tobago"},
|
||||
rangeSegment{low: '3', high: '9', country: "Brazil"},
|
||||
// rangeSegment{low: '0', high: '0', country: "" }
|
||||
},
|
||||
}
|
||||
|
||||
func LookupCountry(countryCode string) string {
|
||||
var key = countryCode[0]
|
||||
var elem = countryCode[1]
|
||||
|
||||
segments, found := vinCountryCodeRangeMap[key]
|
||||
if !found {
|
||||
return countryCode
|
||||
}
|
||||
|
||||
for _, segment := range segments {
|
||||
if segment.low <= elem && elem <= segment.high {
|
||||
return segment.country
|
||||
}
|
||||
}
|
||||
|
||||
return countryCode
|
||||
}
|
||||
459
pkg/vindecoder/vin_decoder.go
Normal file
459
pkg/vindecoder/vin_decoder.go
Normal file
@@ -0,0 +1,459 @@
|
||||
package vindecoder
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const vin_size = 17
|
||||
|
||||
const made_in_start = 0
|
||||
const made_in_size = 2
|
||||
const manufacturer_start = 0
|
||||
const manufacturer_size = 3
|
||||
const manufacturer_small_start = 11
|
||||
const manufacturer_small_size = 3
|
||||
const small_manufacturer_indicator_index = 2
|
||||
const details_start = 3
|
||||
const details_size = 5
|
||||
const check_digit_index = 8
|
||||
const year_index = 9
|
||||
const assembly_plant_index = 10
|
||||
const serial_number_start = 11
|
||||
const serial_number_size = 6
|
||||
const serial_number_small_start = 14
|
||||
const serial_number_small_size = 3
|
||||
|
||||
type vinRawInfo struct {
|
||||
country string
|
||||
manufacturer string
|
||||
details string
|
||||
checkDigit string
|
||||
year string
|
||||
assemblyPlant string
|
||||
serialNumber string
|
||||
smallManufacturer bool
|
||||
}
|
||||
|
||||
// source: https://en.wikipedia.org/wiki/Vehicle_identification_number
|
||||
var vinPositionWeights = []int{8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2}
|
||||
|
||||
// source: https://en.wikipedia.org/wiki/Vehicle_identification_number
|
||||
var transliterationArray = []int{
|
||||
1 /* A */, 2 /* B */, 3, /* C */
|
||||
4 /* D */, 5 /* E */, 6, /* F */
|
||||
7 /* G */, 8 /* H */, 0, /* I */
|
||||
1 /* J */, 2 /* K */, 3, /* L */
|
||||
4 /* M */, 5 /* N */, 0, /* O */
|
||||
7 /* P */, 0 /* Q */, 9, /* R */
|
||||
/* */ 2 /* S */, 3, /* T */
|
||||
4 /* U */, 5 /* V */, 6, /* W */
|
||||
7 /* X */, 8 /* Y */, 9, /* Z */
|
||||
}
|
||||
|
||||
// source: https://en.wikipedia.org/wiki/Vehicle_identification_number
|
||||
var yearOffsetTable = map[byte]int{
|
||||
'A': 0,
|
||||
'B': 1,
|
||||
'C': 2,
|
||||
'D': 3,
|
||||
'E': 4,
|
||||
'F': 5,
|
||||
'G': 6,
|
||||
'H': 7,
|
||||
'J': 8,
|
||||
'K': 9,
|
||||
'L': 10,
|
||||
'M': 11,
|
||||
'N': 12,
|
||||
'P': 13,
|
||||
'R': 14,
|
||||
'S': 15,
|
||||
'T': 16,
|
||||
'V': 17,
|
||||
'W': 18,
|
||||
'X': 19,
|
||||
'Y': 20,
|
||||
'1': 21,
|
||||
'2': 22,
|
||||
'3': 23,
|
||||
'4': 24,
|
||||
'5': 25,
|
||||
'6': 26,
|
||||
'7': 27,
|
||||
'8': 28,
|
||||
'9': 29,
|
||||
}
|
||||
|
||||
func getData(vin string) vinRawInfo {
|
||||
// split VIN into fields
|
||||
var rawInfo vinRawInfo
|
||||
|
||||
rawInfo.smallManufacturer = (vin[small_manufacturer_indicator_index] == '9')
|
||||
rawInfo.country = vin[made_in_start : made_in_start+made_in_size]
|
||||
rawInfo.details = vin[details_start : details_start+details_size]
|
||||
rawInfo.checkDigit = vin[check_digit_index : check_digit_index+1]
|
||||
rawInfo.year = vin[year_index : year_index+1]
|
||||
rawInfo.assemblyPlant = vin[assembly_plant_index : assembly_plant_index+1]
|
||||
|
||||
if rawInfo.smallManufacturer {
|
||||
rawInfo.manufacturer = vin[manufacturer_start:manufacturer_start+manufacturer_size] + "-" + vin[manufacturer_small_start:manufacturer_small_start+manufacturer_small_size]
|
||||
rawInfo.serialNumber = vin[serial_number_small_start : serial_number_small_start+serial_number_small_size]
|
||||
} else {
|
||||
rawInfo.manufacturer = vin[manufacturer_start : manufacturer_start+manufacturer_size]
|
||||
rawInfo.serialNumber = vin[serial_number_start : serial_number_start+serial_number_size]
|
||||
}
|
||||
|
||||
return rawInfo
|
||||
}
|
||||
|
||||
func getYear(vin string) int {
|
||||
// decode year
|
||||
var year = 0
|
||||
|
||||
var yearChar = vin[9]
|
||||
var pos7Char = vin[6]
|
||||
|
||||
offset, found := yearOffsetTable[yearChar]
|
||||
if found {
|
||||
if unicode.IsDigit(rune(pos7Char)) {
|
||||
year = 1980 + offset
|
||||
} else {
|
||||
year = 2010 + offset
|
||||
}
|
||||
}
|
||||
|
||||
return year
|
||||
}
|
||||
|
||||
func getModel(vin string) string {
|
||||
switch vin[3] {
|
||||
case '1':
|
||||
return "Ocean"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
MODEL_OCEAN = "Ocean"
|
||||
)
|
||||
|
||||
func getTrim(vin string) string {
|
||||
switch vin[4] {
|
||||
case 'S':
|
||||
return TRIM_SPORT
|
||||
case 'U':
|
||||
return TRIM_ULTRA
|
||||
case 'E':
|
||||
return TRIM_EXTREME
|
||||
case 'Z':
|
||||
return TRIM_OCEAN_ONE
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Equivalent to trim level
|
||||
const (
|
||||
TRIM_SPORT = "Sport"
|
||||
TRIM_ULTRA = "Ultra"
|
||||
TRIM_EXTREME = "Extreme"
|
||||
TRIM_OCEAN_ONE = "Ocean One"
|
||||
)
|
||||
|
||||
func getPowertrainType(vin string) string {
|
||||
switch vin[5] {
|
||||
case 'A':
|
||||
return POWERTRAIN_TYPE_SBP_SM_FWD
|
||||
case 'B':
|
||||
return POWERTRAIN_TYPE_LBP_DM_AWD
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
POWERTRAIN_TYPE_SBP_SM_FWD = "SBP/SM/FWD"
|
||||
POWERTRAIN_TYPE_LBP_DM_AWD = "LBP/DM/AWD"
|
||||
)
|
||||
|
||||
func getRestraint(vin string) string {
|
||||
switch vin[6] {
|
||||
case 'U':
|
||||
return RESTRAIN_US_SPECS
|
||||
case 'E':
|
||||
return RESTRAIN_EU_SPECS
|
||||
case 'C':
|
||||
return RESTRAIN_CN_SPECS
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
RESTRAIN_US_SPECS = "US Specs"
|
||||
RESTRAIN_EU_SPECS = "EU Specs"
|
||||
RESTRAIN_CN_SPECS = "CN Specs"
|
||||
)
|
||||
|
||||
func getBodyTypeAndGVWR(vin string) string {
|
||||
switch vin[7] {
|
||||
case '1':
|
||||
return "5-Door MPV, 5-Seater, Class D"
|
||||
case '2':
|
||||
return "5-Door MPV, 5-Seater, Class E"
|
||||
case '3':
|
||||
return "5-Door MPV, 7-Seater, Class D"
|
||||
case '4':
|
||||
return "5-Door MPV, 7-Seater, Class E"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func translitChar(character byte) int {
|
||||
// numeric digits as their value
|
||||
if character >= '0' && character <= '9' {
|
||||
return int(character - '0')
|
||||
}
|
||||
|
||||
if character >= 'A' && character <= 'Z' {
|
||||
var translitAlpha = transliterationArray[character-'A']
|
||||
if translitAlpha > 0 {
|
||||
return translitAlpha
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func validate(vin string) bool {
|
||||
// compare calculated check digit with value found
|
||||
calculatedCheckDigit, err := calculateCheckDigit(vin)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
checkDigit := vin[8]
|
||||
return (calculatedCheckDigit == checkDigit)
|
||||
}
|
||||
|
||||
func calculateCheckDigit(vin string) (byte, error) {
|
||||
// calculate check digit
|
||||
if len(vin) != vin_size {
|
||||
return '0', errors.New("invalid vin size")
|
||||
}
|
||||
|
||||
sum := 0
|
||||
for i := range vin {
|
||||
|
||||
value := translitChar(vin[i])
|
||||
if value < 0 {
|
||||
return '0', errors.New("invalid vin character " + string(vin[i]))
|
||||
}
|
||||
|
||||
product := vinPositionWeights[i] * value
|
||||
sum += product
|
||||
}
|
||||
|
||||
// find the divisor
|
||||
calculatedCheckDigit := byte(sum % 11)
|
||||
|
||||
if calculatedCheckDigit == 10 {
|
||||
calculatedCheckDigit = 'X'
|
||||
} else {
|
||||
calculatedCheckDigit = '0' + calculatedCheckDigit
|
||||
}
|
||||
|
||||
return calculatedCheckDigit, nil
|
||||
}
|
||||
|
||||
// in north america, last 5 digits must be numeric, here we'll create all numeric digits
|
||||
// also we'll use natural numeric sort order where 0 is the lowest digit value
|
||||
func getNextNumber(vinNumber string) (string, error) {
|
||||
// cannot process empty string
|
||||
if len(vinNumber) == 0 {
|
||||
return vinNumber, errors.New("empty vin number")
|
||||
}
|
||||
|
||||
nextChars := []byte(vinNumber)
|
||||
var i = 0
|
||||
for i = len(vinNumber) - 1; i >= 0; i-- {
|
||||
num := vinNumber[i]
|
||||
// if we encounter a non-numeric SN digit, convert to numeric
|
||||
if 'A' <= num && num < 'Z' {
|
||||
nextChars[i] = '0'
|
||||
break
|
||||
}
|
||||
if '0' <= num && num < '9' {
|
||||
nextChars[i] = num + 1
|
||||
break
|
||||
}
|
||||
nextChars[i] = '0'
|
||||
}
|
||||
|
||||
// overflow
|
||||
err := (error)(nil)
|
||||
if i < 0 {
|
||||
err = errors.New("overflow")
|
||||
}
|
||||
|
||||
next := string(nextChars)
|
||||
return next, err
|
||||
}
|
||||
|
||||
func getNextVin(vin string) (string, error) {
|
||||
// get next SN number and mae part of base vin
|
||||
data := getData(vin)
|
||||
sn := data.serialNumber
|
||||
overflow := (error)(nil)
|
||||
nextSn, err := getNextNumber(sn)
|
||||
if err != nil {
|
||||
if err.Error() == "overflow" {
|
||||
overflow = err
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
nextVin := vin[:strings.LastIndex(vin, sn)] + nextSn
|
||||
checkDigit, err := calculateCheckDigit(nextVin)
|
||||
if err != nil {
|
||||
return vin, err
|
||||
}
|
||||
|
||||
nextVin = vin[:check_digit_index] + string(checkDigit) + nextVin[check_digit_index+1:]
|
||||
|
||||
return nextVin, overflow
|
||||
}
|
||||
|
||||
func getNextNVins(startVin string, count int) ([]string, error) {
|
||||
// check that base vin is valid
|
||||
_, err := calculateCheckDigit(startVin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count < 1 {
|
||||
return nil, errors.New("count is negative")
|
||||
}
|
||||
|
||||
vins := make([]string, count)
|
||||
rollover := (error)(nil)
|
||||
nextVin, err := getNextVin(startVin)
|
||||
for i := 0; i < count; i++ {
|
||||
if err != nil {
|
||||
if err.Error() == "overflow" {
|
||||
rollover = errors.New("rollover")
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
vins[i] = nextVin
|
||||
nextVin, err = getNextVin(nextVin)
|
||||
}
|
||||
|
||||
return vins, rollover
|
||||
}
|
||||
|
||||
type VinInfo struct {
|
||||
Manufacturer string
|
||||
Country string
|
||||
SerialNumber string
|
||||
ModelDetails string
|
||||
Model string
|
||||
Trim string
|
||||
Year int
|
||||
Powertrain string
|
||||
Restraint string
|
||||
BodyType string
|
||||
IsValid bool
|
||||
}
|
||||
|
||||
func decode(vin string) VinInfo {
|
||||
// ensure valid vin
|
||||
var result VinInfo
|
||||
|
||||
result.IsValid = true
|
||||
|
||||
// simple regex validation
|
||||
vs := ValidateVINSimple(vin)
|
||||
if !vs {
|
||||
result.IsValid = false
|
||||
return result
|
||||
}
|
||||
|
||||
var data vinRawInfo = getData(vin)
|
||||
result.SerialNumber = data.serialNumber
|
||||
result.Manufacturer = LookupManufacturerByWmiCode(data.manufacturer)
|
||||
result.Country = LookupCountry(data.country)
|
||||
result.ModelDetails = data.details
|
||||
|
||||
result.Model = getModel(vin)
|
||||
result.Trim = getTrim(vin)
|
||||
result.Powertrain = getPowertrainType(vin)
|
||||
result.Restraint = getRestraint(vin)
|
||||
result.BodyType = getBodyTypeAndGVWR(vin)
|
||||
|
||||
var year = getYear(vin)
|
||||
if year >= 1980 {
|
||||
result.Year = year
|
||||
} else {
|
||||
result.IsValid = false
|
||||
}
|
||||
|
||||
// checksum digit validation
|
||||
if !validate(vin) {
|
||||
result.IsValid = false
|
||||
return result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// entry point
|
||||
|
||||
// VIN should be uppercase
|
||||
func DecodeVIN(vin string) (info VinInfo, ok bool) {
|
||||
vin = strings.ToUpper(vin)
|
||||
info = decode(vin)
|
||||
return info, info.IsValid
|
||||
}
|
||||
|
||||
func CalculateCheckDigit(vin string) (byte, error) {
|
||||
return calculateCheckDigit(vin)
|
||||
}
|
||||
|
||||
func VerifyVinCheckDigit(vin string) bool {
|
||||
vin = strings.ToUpper(vin)
|
||||
return validate(vin)
|
||||
}
|
||||
|
||||
func NextVIN(startVin string) (string, error) {
|
||||
return getNextVin(startVin)
|
||||
}
|
||||
|
||||
func NextNVINs(startVin string, count int) ([]string, error) {
|
||||
return getNextNVins(startVin, count)
|
||||
}
|
||||
|
||||
func IsEU(vin string) bool {
|
||||
return getRestraint(vin) == "EU Specs"
|
||||
}
|
||||
|
||||
// Pre instantiate vin match regex
|
||||
var VINSimpleRegexMatch *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
VINSimpleRegexMatch = regexp.MustCompile(`^[a-hj-npr-zA-HJ-NPR-Z0-9]{17}$`)
|
||||
}
|
||||
|
||||
func ValidateVINSimple(vin string) (matched bool) {
|
||||
matched = VINSimpleRegexMatch.Match([]byte(vin))
|
||||
return matched
|
||||
}
|
||||
591
pkg/vindecoder/vin_wmi_lookup.go
Normal file
591
pkg/vindecoder/vin_wmi_lookup.go
Normal file
@@ -0,0 +1,591 @@
|
||||
package vindecoder
|
||||
|
||||
// source: https://en.wikibooks.org/wiki/Vehicle_Identification_Numbers_(VIN_codes)/World_Manufacturer_Identifier_(WMI)
|
||||
var vinWmiCodeMap = map[string]string{
|
||||
|
||||
// Africa
|
||||
"AAV": "Volkswagen South Africa",
|
||||
"AC5": "Hyundai South Africa",
|
||||
"ADD": "Hyundai South Africa",
|
||||
"AFA": "Ford South Africa",
|
||||
"AHT": "Toyota South Africa",
|
||||
|
||||
// Japan
|
||||
"JA3": "Mitsubishi",
|
||||
"JA4": "Mitsubishi",
|
||||
"JA*": "Isuzu",
|
||||
"JD*": "Daihatsu",
|
||||
"JF*": "Fuji Heavy Industries (Subaru)",
|
||||
"JHA": "Hino",
|
||||
"JHB": "Hino",
|
||||
"JHC": "Hino",
|
||||
"JHD": "Hino",
|
||||
"JHE": "Hino",
|
||||
"JHF": "Honda",
|
||||
"JHG": "Honda",
|
||||
"JHL": "Honda",
|
||||
"JHM": "Honda",
|
||||
"JHN": "Honda",
|
||||
"JHZ": "Honda",
|
||||
"JH1": "Honda",
|
||||
"JH2": "Honda",
|
||||
"JH3": "Honda",
|
||||
"JH4": "Honda",
|
||||
"JH5": "Honda",
|
||||
"JK*": "Kawasaki (motorcycles)",
|
||||
"JL5": "Mitsubishi Fuso",
|
||||
"JM1": "Mazda",
|
||||
"JMB": "Mitsubishi Motors",
|
||||
"JMY": "Mitsubishi Motors",
|
||||
"JMZ": "Mazda",
|
||||
"JN*": "Nissan",
|
||||
"JS*": "Suzuki",
|
||||
"JT*": "Toyota",
|
||||
"JY*": "Yamaha (motorcycles)",
|
||||
|
||||
// Korea
|
||||
"KL*": "Daewoo General Motors South Korea",
|
||||
"KM*": "Hyundai",
|
||||
"KMY": "Daelim (motorcycles)",
|
||||
"KM1": "Hyosung (motorcycles)",
|
||||
"KN*": "Kia",
|
||||
"KNM": "Renault Samsung",
|
||||
"KPA": "SsangYong",
|
||||
"KPT": "SsangYong",
|
||||
|
||||
// China
|
||||
"LAE": "Jinan Qingqi Motorcycle",
|
||||
"LAL": "Sundiro Honda Motorcycle",
|
||||
"LAN": "Changzhou Yamasaki Motorcycle",
|
||||
"LBB": "Zhejiang Qianjiang Motorcycle (Keeway/Generic)",
|
||||
"LBE": "Beijing Hyundai",
|
||||
"LBM": "Zongshen Piaggio",
|
||||
"LBP": "Chongqing Jainshe Yamaha (motorcycles)",
|
||||
"LB2": "Geely Motorcycles",
|
||||
"LCE": "Hangzhou Chunfeng Motorcycles (CFMOTO)",
|
||||
"LDC": "Dong Feng Peugeot Citroen (DPCA), China",
|
||||
"LDD": "Dandong Huanghai Automobile",
|
||||
"LDF": "Dezhou Fulu Vehicle (motorcycles)",
|
||||
"LDN": "SouEast Motor",
|
||||
"LDY": "Zhongtong Coach, China",
|
||||
"LET": "Jiangling-Isuzu Motors, China",
|
||||
"LE4": "Beijing Benz, China",
|
||||
"LFB": "FAW, China (busses)",
|
||||
"LFG": "Taizhou Chuanl Motorcycle Manufacturing",
|
||||
"LFP": "FAW, China (passenger vehicles)",
|
||||
"LFT": "FAW, China (trailers)",
|
||||
"LFV": "FAW-Volkswagen, China",
|
||||
"LFW": "FAW JieFang, China",
|
||||
"LFY": "Changshu Light Motorcycle Factory",
|
||||
"LGB": "Dong Feng (DFM), China",
|
||||
"LGH": "Qoros (formerly Dong Feng (DFM)), China",
|
||||
"LGX": "BYD Auto, China",
|
||||
"LHB": "Beijing Automotive Industry Holding",
|
||||
"LH1": "FAW-Haima, China",
|
||||
"LJC": "JAC, China",
|
||||
"LJ1": "JAC, China",
|
||||
"LKL": "Suzhou King Long, China",
|
||||
"LL6": "Hunan Changfeng Manufacture Joint-Stock",
|
||||
"LL8": "Linhai (ATV)",
|
||||
"LMC": "Suzuki Hong Kong (motorcycles)",
|
||||
"LPR": "Yamaha Hong Kong (motorcycles)",
|
||||
"LPS": "Polestar (Volvo) (Sweden)",
|
||||
"LSG": "Shanghai General Motors, China",
|
||||
"LSJ": "MG Motor UK Limited - SAIC Motor, Shanghai, China",
|
||||
"LSV": "Shanghai Volkswagen, China",
|
||||
"LSY": "Brilliance Zhonghua",
|
||||
"LTP": "National Electric Vehicle Sweden AB (NEVS)",
|
||||
"LTV": "Toyota Tian Jin",
|
||||
"LUC": "Guangqi Honda, China",
|
||||
"LVS": "Ford Chang An",
|
||||
"LVV": "Chery, China",
|
||||
"LVZ": "Dong Feng Sokon Motor Company (DFSK)",
|
||||
"LV3": "National Electric Vehicle Sweden AB (NEVS)",
|
||||
"LZM": "MAN China",
|
||||
"LZE": "Isuzu Guangzhou, China",
|
||||
"LZG": "Shaanxi Automobile Group, China",
|
||||
"LZP": "Zhongshan Guochi Motorcycle (Baotian)",
|
||||
"LZY": "Yutong Zhengzhou, China",
|
||||
"LZZ": "Chongqing Shuangzing Mech & Elec (Howo)",
|
||||
"L4B": "Xingyue Group (motorcycles)",
|
||||
"L5C": "KangDi (ATV)",
|
||||
"L5K": "Zhejiang Yongkang Easy Vehicle",
|
||||
"L5N": "Zhejiang Taotao, China (ATV & motorcycles)",
|
||||
"L5Y": "Merato Motorcycle Taizhou Zhongneng",
|
||||
"L85": "Zhejiang Yongkang Huabao Electric Appliance",
|
||||
"L8X": "Zhejiang Summit Huawin Motorcycle",
|
||||
|
||||
// India
|
||||
"MAB": "Mahindra & Mahindra",
|
||||
"MAC": "Mahindra & Mahindra",
|
||||
"MAJ": "Ford India",
|
||||
"MAK": "Honda Siel Cars India",
|
||||
"MAL": "Hyundai India",
|
||||
"MAT": "Tata Motors",
|
||||
"MA1": "Mahindra & Mahindra",
|
||||
"MA3": "Suzuki India (Maruti)",
|
||||
"MA6": "GM India",
|
||||
"MA7": "Mitsubishi India (formerly Honda)",
|
||||
"MB8": "Suzuki India Motorcycles",
|
||||
"MBH": "Suzuki India (Maruti)",
|
||||
"MBJ": "Toyota India",
|
||||
"MBR": "Mercedes-Benz Indiav",
|
||||
"MB1": "Ashok Leyland",
|
||||
"MCA": "Fiat India",
|
||||
"MCB": "GM India",
|
||||
"MC2": "Volvo Eicher commercial vehicles limited.",
|
||||
"MDH": "Nissan India",
|
||||
"MD2": "Bajaj Auto",
|
||||
"MD9": "Shuttle Cars India",
|
||||
"MEC": "Daimler India Commercial Vehicles",
|
||||
"MEE": "Renault India",
|
||||
"MEX": "Volkswagen India",
|
||||
|
||||
// Asia
|
||||
"MHF": "Toyota Indonesia",
|
||||
"MHR": "Honda Indonesia",
|
||||
"MLC": "Suzuki Thailand",
|
||||
"NAA": "Iran Khodro (Peugeot Iran)",
|
||||
"NAP": "Pars Khodro",
|
||||
"MLH": "Honda Thailand",
|
||||
"MMA": "Mitsubishi Thailand",
|
||||
"MMB": "Mitsubishi Thailand",
|
||||
"MMC": "Mitsubishi Thailand",
|
||||
"MMM": "Chevrolet Thailand",
|
||||
"MMS": "Suzuki Thailand",
|
||||
"MMT": "Mitsubishi Thailand",
|
||||
"MMU": "Holden Thailand",
|
||||
"MM8": "Mazda Thailand",
|
||||
"MNB": "Ford Thailand",
|
||||
"MNT": "Nissan Thailand",
|
||||
"MPA": "Isuzu Thailand",
|
||||
"MP1": "Isuzu Thailand",
|
||||
"MRH": "Honda Thailand",
|
||||
"MR0": "Toyota Thailand",
|
||||
|
||||
"MS0": "SSS MOTORS Myanmar",
|
||||
"MS3": "Suzuki Myanmar Motor Co.,Ltd.",
|
||||
"NLA": "Honda Türkiye",
|
||||
"NLE": "Mercedes-Benz Türk Truck",
|
||||
"NLH": "Hyundai Assan",
|
||||
"NLN": "Karsan",
|
||||
"NLR": "OTOKAR",
|
||||
"NLT": "TEMSA",
|
||||
"NMB": "Mercedes-Benz Türk Buses",
|
||||
"NMC": "BMC",
|
||||
"NM0": "Ford Turkey",
|
||||
"NM4": "Tofaş Türk",
|
||||
"NMT": "Toyota Türkiye",
|
||||
"NNA": "Isuzu Turkey",
|
||||
"PE1": "Ford Philippines",
|
||||
"PE3": "Mazda Philippines",
|
||||
"PL1": "Proton, Malaysia",
|
||||
"PNA": "NAZA, Malaysia (Peugeot)",
|
||||
"R2P": "Evoke Electric Motorcycles HK",
|
||||
"RA1": "Steyr Trucks International FZE, UAE",
|
||||
"RFB": "Kymco, Taiwan",
|
||||
"RFG": "Sanyang SYM, Taiwan",
|
||||
"RFL": "Adly, Taiwan",
|
||||
"RFT": "CPI, Taiwan",
|
||||
"RF3": "Aeon Motor, Taiwan",
|
||||
|
||||
// UK
|
||||
"SAB": "Optare",
|
||||
"SAD": "Jaguar (F-Pace, I-Pace)",
|
||||
"SAL": "Land Rover",
|
||||
"SAJ": "Jaguar",
|
||||
"SAR": "Rover",
|
||||
"SAX": "Austin-Rover",
|
||||
"SB1": "Toyota UK",
|
||||
"SBM": "McLaren",
|
||||
"SCA": "Rolls Royce",
|
||||
"SCB": "Bentley",
|
||||
"SCC": "Lotus Cars",
|
||||
"SCE": "DeLorean Motor Cars N. Ireland (UK)",
|
||||
"SCF": "Aston",
|
||||
"SCK": "iFor Williams",
|
||||
"SDB": "Peugeot UK (formerly Talbot)",
|
||||
"SED": "General Motors Luton Plant",
|
||||
"SEY": "LDV",
|
||||
"SFA": "Ford UK",
|
||||
"SFD": "Alexander Dennis UK",
|
||||
"SHH": "Honda UK",
|
||||
"SHS": "Honda UK",
|
||||
"SJN": "Nissan UK",
|
||||
"SKF": "Vauxhall",
|
||||
"SLP": "JCB Research UK",
|
||||
"SMT": "Triumph Motorcycles",
|
||||
|
||||
// Europe
|
||||
"SUF": "Fiat Auto Poland",
|
||||
"SUL": "FSC (Poland)",
|
||||
"SUP": "FSO-Daewoo (Poland)",
|
||||
"SU9": "Solaris Bus & Coach (Poland)",
|
||||
"SUU": "Solaris Bus & Coach (Poland)",
|
||||
"SWV": "TA-NO (Poland)",
|
||||
"TCC": "Micro Compact Car AG (smart 1998-1999)",
|
||||
"TDM": "QUANTYA Swiss Electric Movement (Switzerland)",
|
||||
"TK9": "SOR buses (Czech Republic)",
|
||||
"TMA": "Hyundai Motor Manufacturing Czech",
|
||||
"TMB": "Škoda (Czech Republic)",
|
||||
"TMK": "Karosa (Czech Republic)",
|
||||
"TMP": "Škoda trolleybuses (Czech Republic)",
|
||||
"TMT": "Tatra (Czech Republic)",
|
||||
"TM9": "Škoda trolleybuses (Czech Republic)",
|
||||
"TNE": "TAZ",
|
||||
"TN9": "Karosa (Czech Republic)",
|
||||
"TRA": "Ikarus Bus",
|
||||
"TRU": "Audi Hungary",
|
||||
"TSB": "Ikarus Bus",
|
||||
"TSE": "Ikarus Egyedi Autobuszgyar, (Hungary)",
|
||||
"TSM": "Suzuki Hungary",
|
||||
"TW1": "Toyota Caetano Portugal",
|
||||
"TYA": "Mitsubishi Trucks Portugal",
|
||||
"TYB": "Mitsubishi Trucks Portugal",
|
||||
"UU1": "Renault Dacia, (Romania)",
|
||||
"UU2": "Oltcit",
|
||||
"UU3": "ARO",
|
||||
"UU4": "Roman SA",
|
||||
"UU5": "Rocar",
|
||||
"UU6": "Daewoo Romania",
|
||||
"UU7": "Euro Bus Diamond",
|
||||
"UU9": "Astra Bus",
|
||||
"UZT": "UTB (Uzina de Tractoare Brașov)",
|
||||
"U5Y": "Kia Motors Slovakia",
|
||||
"U6Y": "Kia Motors Slovakia",
|
||||
"VAG": "Magna Steyr Puch",
|
||||
"VAN": "MAN Austria",
|
||||
"VBK": "KTM (Motorcycles)",
|
||||
"VCF": "Fisker GmbH",
|
||||
"VF1": "Renault",
|
||||
"VF2": "Renault",
|
||||
"VF3": "Peugeot",
|
||||
"VF4": "Talbot",
|
||||
"VF6": "Renault (Trucks & Buses)",
|
||||
"VF7": "Citroën",
|
||||
"VF8": "Matra",
|
||||
"VG5": "MBK (motorcycles)",
|
||||
"VLU": "Scania France",
|
||||
"VN1": "SOVAB (France)",
|
||||
"VNE": "Irisbus (France)",
|
||||
"VNK": "Toyota France",
|
||||
"VNV": "Renault-Nissan",
|
||||
"VSA": "Mercedes-Benz Spain",
|
||||
"VSE": "Suzuki Spain (Santana Motors)",
|
||||
"VSK": "Nissan Spain",
|
||||
"VSS": "SEAT",
|
||||
"VSX": "Opel Spain",
|
||||
"VS6": "Ford Spain",
|
||||
"VS7": "Citroën Spain",
|
||||
"VS9": "Carrocerias Ayats (Spain)",
|
||||
"VTH": "Derbi (motorcycles)",
|
||||
"VTL": "Yamaha Spain (motorcycles)",
|
||||
"VTT": "Suzuki Spain (motorcycles)",
|
||||
"VV9": "TAURO Spain",
|
||||
"VWA": "Nissan Spain",
|
||||
"VWV": "Volkswagen Spain",
|
||||
"VX1": "Zastava / Yugo Serbia",
|
||||
|
||||
// Germany - formerly West, former East has other codes
|
||||
"WAG": "Neoplan",
|
||||
"WAU": "Audi",
|
||||
"WA1": "Audi SUV",
|
||||
"WBA": "BMW",
|
||||
"WBS": "BMW M",
|
||||
"WBW": "BMW",
|
||||
"WBY": "BMW",
|
||||
"WDA": "Daimler",
|
||||
"WDB": "Mercedes-Benz",
|
||||
"WDC": "DaimlerChrysler",
|
||||
"WDD": "Mercedes-Benz",
|
||||
"WDF": "Mercedes-Benz (commercial vehicles)",
|
||||
"WEB": "Evobus GmbH (Mercedes-Bus)",
|
||||
"WJM": "Iveco Magirus",
|
||||
"WF0": "Ford Germany",
|
||||
"WKE": "Fahrzeugwerk Bernard Krone (truck trailers)",
|
||||
"WKK": "Kässbohrer/Setra",
|
||||
"WMA": "MAN Germany",
|
||||
"WME": "smart",
|
||||
"WMW": "MINI",
|
||||
"WMX": "Mercedes-AMG",
|
||||
"WP0": "Porsche",
|
||||
"WP1": "Porsche SUV",
|
||||
"WSM": "Schmitz-Cargobull (truck trailers)",
|
||||
"W09": "RUF",
|
||||
"W0L": "Opel",
|
||||
"W0V": "Opel (since 2017)",
|
||||
"WUA": "Audi Sport GmbH (formerly quattro GmbH)",
|
||||
"WVG": "Volkswagen MPV/SUV",
|
||||
"WVW": "Volkswagen",
|
||||
"WV1": "Volkswagen Commercial Vehicles",
|
||||
"WV2": "Volkswagen Bus/Van",
|
||||
"WV3": "Volkswagen Trucks",
|
||||
|
||||
// Europe
|
||||
"XLB": "Volvo (NedCar)",
|
||||
"XLE": "Scania Netherlands",
|
||||
"XLR": "DAF (trucks)",
|
||||
"XMC": "Mitsubishi (NedCar)",
|
||||
"XMG": "VDL Bus & Coach",
|
||||
"XTA": "Lada/AvtoVAZ (Russia)",
|
||||
"XTC": "KAMAZ (Russia)",
|
||||
"XTH": "GAZ (Russia)",
|
||||
"XTT": "UAZ/Sollers (Russia)",
|
||||
"XTU": "Trolza (Russia)",
|
||||
"XTY": "LiAZ (Russia)",
|
||||
"XUF": "General Motors Russia",
|
||||
"XUU": "AvtoTor (Russia, General Motors SKD)",
|
||||
"XW8": "Volkswagen Group Russia",
|
||||
"XWB": "UZ-Daewoo (Uzbekistan)",
|
||||
"XWE": "AvtoTor (Russia, Hyundai-Kia SKD)",
|
||||
"X1M": "PAZ (Russia)",
|
||||
"X4X": "AvtoTor (Russia, BMW SKD)",
|
||||
"X7L": "Renault AvtoFramos (Russia)",
|
||||
"X7M": "Hyundai TagAZ (Russia)",
|
||||
"YBW": "Volkswagen Belgium",
|
||||
"YB1": "Volvo Trucks Belgium",
|
||||
"YCM": "Mazda Belgium",
|
||||
"YE2": "Van Hool (buses)",
|
||||
"YH2": "BRP Finland (Lynx snowmobiles)",
|
||||
"YK1": "Saab-Valmet Finland",
|
||||
"YSC": "Cadillac (Saab)",
|
||||
"YS2": "Scania AB",
|
||||
"YS3": "Saab",
|
||||
"YS4": "Scania Bus",
|
||||
"YTN": "Saab NEVS",
|
||||
"YU7": "Husaberg (motorcycles)",
|
||||
"YVV": "Polestar (Volvo) (Sweden)",
|
||||
"YV1": "Volvo Cars",
|
||||
"YV4": "Volvo Cars",
|
||||
"YV2": "Volvo Trucks",
|
||||
"YV3": "Volvo Buses",
|
||||
"Y3M": "MAZ (Belarus)",
|
||||
"Y6D": "Zaporozhets/AvtoZAZ (Ukraine)",
|
||||
|
||||
"ZAA": "Autobianchi",
|
||||
"ZAM": "Maserati",
|
||||
"ZAP": "Piaggio/Vespa/Gilera",
|
||||
"ZAR": "Alfa Romeo",
|
||||
"ZBN": "Benelli",
|
||||
"ZCG": "Cagiva SpA / MV Agusta",
|
||||
"ZCF": "Iveco",
|
||||
"ZDC": "Honda Italia Industriale SpA",
|
||||
"ZDM": "Ducati Motor Holdings SpA",
|
||||
"ZDF": "Ferrari Dino",
|
||||
"ZD0": "Yamaha Italy",
|
||||
"ZD3": "Beta Motor",
|
||||
"ZD4": "Aprilia",
|
||||
"ZFA": "Fiat",
|
||||
"ZFC": "Fiat V.I.",
|
||||
"ZFF": "Ferrari",
|
||||
"ZGU": "Moto Guzzi",
|
||||
"ZHW": "Lamborghini",
|
||||
"ZJM": "Malaguti",
|
||||
"ZJN": "Innocenti",
|
||||
"ZKH": "Husqvarna Motorcycles Italy",
|
||||
"ZLA": "Lancia",
|
||||
"Z8M": "Marussia (Russia)",
|
||||
|
||||
// European small production
|
||||
"VF9-795": "Bugatti",
|
||||
"XL9-363": "Spyker",
|
||||
"YT9-007": "Koenigsegg",
|
||||
"YT9-034": "Carvia",
|
||||
|
||||
// USA
|
||||
"1B3": "Dodge",
|
||||
"1C3": "Chrysler",
|
||||
"1C4": "Chrysler",
|
||||
"1C6": "Chrysler",
|
||||
"1D3": "Dodge",
|
||||
"1FA": "Ford Motor Company",
|
||||
"1FB": "Ford Motor Company",
|
||||
"1FC": "Ford Motor Company",
|
||||
"1FD": "Ford Motor Company",
|
||||
"1FM": "Ford Motor Company",
|
||||
"1FT": "Ford Motor Company",
|
||||
"1FU": "Freightliner",
|
||||
"1FV": "Freightliner",
|
||||
"1F9": "FWD Corp.",
|
||||
"1G*": "General Motors USA",
|
||||
"1GC": "Chevrolet Truck USA",
|
||||
"1GT": "GMC Truck USA",
|
||||
"1G1": "Chevrolet USA",
|
||||
"1G2": "Pontiac USA",
|
||||
"1G3": "Oldsmobile USA",
|
||||
"1G4": "Buick USA",
|
||||
"1G6": "Cadillac USA",
|
||||
"1G8": "Saturn USA",
|
||||
"1GM": "Pontiac USA",
|
||||
"1GY": "Cadillac USA",
|
||||
"1H*": "Honda USA",
|
||||
"1HD": "Harley-Davidson",
|
||||
"1HT": "International Truck and Engine Corp. USA",
|
||||
"1J4": "Jeep",
|
||||
"1J8": "Jeep",
|
||||
"1L*": "Lincoln USA",
|
||||
"1ME": "Mercury USA",
|
||||
"1M1": "Mack Truck USA",
|
||||
"1M2": "Mack Truck USA",
|
||||
"1M3": "Mack Truck USA",
|
||||
"1M4": "Mack Truck USA",
|
||||
"1M9": "Mynatt Truck & Equipment",
|
||||
"1N*": "Nissan USA",
|
||||
"1NX": "NUMMI USA",
|
||||
"1P3": "Plymouth USA",
|
||||
"1PY": "John Deere USA",
|
||||
"1R9": "Roadrunner Hay Squeeze USA",
|
||||
"1VW": "Volkswagen USA",
|
||||
"1XK": "Kenworth USA",
|
||||
"1XP": "Peterbilt USA",
|
||||
"1YV": "Mazda USA (AutoAlliance International)",
|
||||
"1ZV": "Ford (AutoAlliance International)",
|
||||
|
||||
// Canada
|
||||
"2A4": "Chrysler Canada",
|
||||
"2BP": "Bombardier Recreational Products",
|
||||
"2B3": "Dodge Canada",
|
||||
"2B7": "Dodge Canada",
|
||||
"2C3": "Chrysler Canada",
|
||||
"2CN": "CAMI",
|
||||
"2D3": "Dodge Canada",
|
||||
"2FA": "Ford Motor Company Canada",
|
||||
"2FB": "Ford Motor Company Canada",
|
||||
"2FC": "Ford Motor Company Canada",
|
||||
"2FM": "Ford Motor Company Canada",
|
||||
"2FT": "Ford Motor Company Canada",
|
||||
"2FU": "Freightliner",
|
||||
"2FV": "Freightliner",
|
||||
"2FZ": "Sterling",
|
||||
"2Gx": "General Motors Canada",
|
||||
"2G1": "Chevrolet Canada",
|
||||
"2G2": "Pontiac Canada",
|
||||
"2G3": "Oldsmobile Canada",
|
||||
"2G4": "Buick Canada",
|
||||
"2G9": "mfr. of less than 1000/ yr. Canada",
|
||||
"2HG": "Honda Canada",
|
||||
"2HK": "Honda Canada",
|
||||
"2HJ": "Honda Canada",
|
||||
"2HM": "Hyundai Canada",
|
||||
"2M*": "Mercury",
|
||||
"2NV": "Nova Bus Canada",
|
||||
"2P3": "Plymouth Canada",
|
||||
"2T*": "Toyota Canada",
|
||||
"2TP": "Triple E Canada LTD",
|
||||
"2V4": "Volkswagen Canada",
|
||||
"2V8": "Volkswagen Canada",
|
||||
"2WK": "Western Star",
|
||||
"2WL": "Western Star",
|
||||
"2WM": "Western Star",
|
||||
|
||||
// Mexico and other North America
|
||||
"3C4": "Chrysler Mexico",
|
||||
"3C6": "RAM Mexico",
|
||||
"3D3": "Dodge Mexico",
|
||||
"3D4": "Dodge Mexico",
|
||||
"3FA": "Ford Motor Company Mexico",
|
||||
"3FE": "Ford Motor Company Mexico",
|
||||
"3G*": "General Motors Mexico",
|
||||
"3H*": "Honda Mexico",
|
||||
"3JB": "BRP Mexico (all-terrain vehicles)",
|
||||
"3MD": "Mazda Mexico",
|
||||
"3MZ": "Mazda Mexico",
|
||||
"3N*": "Nissan Mexico",
|
||||
"3NS": "Polaris Industries USA",
|
||||
"3NE": "Polaris Industries USA",
|
||||
"3P3": "Plymouth Mexico",
|
||||
"3VW": "Volkswagen Mexico",
|
||||
|
||||
// USA
|
||||
"46J": "Federal Motors Inc. USA",
|
||||
"4EN": "Emergency One USA",
|
||||
"4F*": "Mazda USA",
|
||||
"4JG": "Mercedes-Benz USA",
|
||||
"4M*": "Mercury",
|
||||
"4P1": "Pierce Manufacturing Inc. USA",
|
||||
"4RK": "Nova Bus USA",
|
||||
"4S*": "Subaru-Isuzu Automotive",
|
||||
"4T*": "Toyota",
|
||||
"4T9": "Lumen Motors",
|
||||
"4UF": "Arctic Cat Inc.",
|
||||
"4US": "BMW USA",
|
||||
"4UZ": "Frt-Thomas Bus",
|
||||
"4V1": "Volvo",
|
||||
"4V2": "Volvo",
|
||||
"4V3": "Volvo",
|
||||
"4V4": "Volvo",
|
||||
"4V5": "Volvo",
|
||||
"4V6": "Volvo",
|
||||
"4VL": "Volvo",
|
||||
"4VM": "Volvo",
|
||||
"4VZ": "Volvo",
|
||||
"538": "Zero Motorcycles (USA)",
|
||||
"5F(": "Honda USA-Alabama",
|
||||
"5J*": "Honda USA-Ohio",
|
||||
"5L*": "Lincoln",
|
||||
"5N1": "Nissan USA",
|
||||
"5NP": "Hyundai USA",
|
||||
"5T*": "Toyota USA - trucks",
|
||||
"5YJ": "Tesla, Inc.",
|
||||
"56K": "Indian Motorcycle USA",
|
||||
|
||||
// Australia and Oceanea
|
||||
"6AB": "MAN Australia",
|
||||
"6F4": "Nissan Motor Company Australia",
|
||||
"6F5": "Kenworth Australia",
|
||||
"6FP": "Ford Motor Company Australia",
|
||||
"6G1": "General Motors-Holden (post Nov 2002)",
|
||||
"6G2": "Pontiac Australia (GTO & G8)",
|
||||
"6H8": "General Motors-Holden (pre Nov 2002)",
|
||||
"6MM": "Mitsubishi Motors Australia",
|
||||
"6T1": "Toyota Motor Corporation Australia",
|
||||
"6U9": "Privately Imported car in Australia",
|
||||
|
||||
// South America
|
||||
"8AD": "Peugeot Argentina",
|
||||
"8AF": "Ford Motor Company Argentina",
|
||||
"8AG": "Chevrolet Argentina",
|
||||
"8AJ": "Toyota Argentina",
|
||||
"8AK": "Suzuki Argentina",
|
||||
"8AP": "Fiat Argentina",
|
||||
"8AW": "Volkswagen Argentina",
|
||||
"8A1": "Renault Argentina",
|
||||
"8GD": "Peugeot Chile",
|
||||
"8GG": "Chevrolet Chile",
|
||||
"8LD": "Chevrolet Ecuador",
|
||||
"935": "Citroën Brazil",
|
||||
"936": "Peugeot Brazil",
|
||||
"93H": "Honda Brazil",
|
||||
"93R": "Toyota Brazil",
|
||||
"93U": "Audi Brazil",
|
||||
"93V": "Audi Brazil",
|
||||
"93X": "Mitsubishi Motors Brazil",
|
||||
"93Y": "Renault Brazil",
|
||||
"94D": "Nissan Brazil",
|
||||
"9BF": "Ford Motor Company Brazil",
|
||||
"9BG": "Chevrolet Brazil",
|
||||
"9BM": "Mercedes-Benz Brazil",
|
||||
"9BR": "Toyota Brazil",
|
||||
"9BS": "Scania Brazil",
|
||||
"9BW": "Volkswagen Brazil",
|
||||
"9FB": "Renault Colombia",
|
||||
"WB1": "BMW Motorrad of North America",
|
||||
|
||||
// TEMP
|
||||
"1F1": "Fisker Inc.",
|
||||
// TEMP
|
||||
}
|
||||
|
||||
func LookupManufacturerByWmiCode(wmiCode string) string {
|
||||
|
||||
mfg, found := vinWmiCodeMap[wmiCode]
|
||||
if !found {
|
||||
var wmiWildcard = wmiCode[0:len(wmiCode)-1] + "*"
|
||||
mfg, found = vinWmiCodeMap[wmiWildcard]
|
||||
if !found {
|
||||
mfg = wmiCode
|
||||
}
|
||||
}
|
||||
|
||||
return mfg
|
||||
}
|
||||
Reference in New Issue
Block a user