Skip to content

Commit 8e860fe

Browse files
authored
INTG-1849: implement eleva api for kaufmann vin decode (#302)
* implement eleva api * add eleva api to vin decoding svc, update test * secrets, linting, add test, fix * fix test, make casing match, add console for eleva api * kaufmann fixes from manual validation * lint fix
1 parent 4376ebd commit 8e860fe

10 files changed

Lines changed: 363 additions & 17 deletions

File tree

charts/device-definitions-api/templates/secret.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ spec:
9595
- remoteRef:
9696
key: {{ .Release.Namespace }}/definitions/carvx/apikey
9797
secretKey: CAR_VX_API_KEY
98+
- remoteRef:
99+
key: {{ .Release.Namespace }}/definitions/kaufmann/username
100+
secretKey: ELEVA_USERNAME
101+
- remoteRef:
102+
key: {{ .Release.Namespace }}/definitions/kaufmann/password
103+
secretKey: ELEVA_PASSWORD
98104
secretStoreRef:
99105
kind: ClusterSecretStore
100106
name: aws-secretsmanager-secret-store

cmd/device-definitions-api/decode_vin.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@ type decodeVINCmd struct {
3737
fromFile bool
3838
persistToDB bool
3939
carvx bool
40+
eleva bool
4041
}
4142

4243
func (*decodeVINCmd) Name() string { return "decodevin" }
4344
func (*decodeVINCmd) Synopsis() string {
4445
return "tries decoding a vin with chosen provider - does not insert in our db"
4546
}
4647
func (*decodeVINCmd) Usage() string {
47-
return `decodevin [-dat|-drivly|-vincario|-japan17vin|carvx|-from-file] <vin 17 chars OR filaname in /tmp> <country two letter iso>`
48+
return `decodevin [-dat|-drivly|-vincario|-japan17vin|-carvx|-eleva|-from-file] <vin 17 chars OR filaname in /tmp> <country three letter iso>`
4849
}
4950

5051
func (p *decodeVINCmd) SetFlags(f *flag.FlagSet) {
@@ -53,6 +54,7 @@ func (p *decodeVINCmd) SetFlags(f *flag.FlagSet) {
5354
f.BoolVar(&p.vincario, "vincario", false, "use vincario vin decoder")
5455
f.BoolVar(&p.japan17vin, "japan17vin", false, "use japan17vin vin decoder")
5556
f.BoolVar(&p.carvx, "carvx", false, "use carvx vin decoder")
57+
f.BoolVar(&p.eleva, "eleva", false, "use eleva kaufmann vin decoder")
5658
f.BoolVar(&p.fromFile, "from-file", false, "read vin from file in /tmp directory")
5759
f.BoolVar(&p.persistToDB, "persist-to-db", false, "persist successful vin decodings to db, table vin_numbers")
5860
}
@@ -160,6 +162,14 @@ func (p *decodeVINCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interf
160162
fmt.Println("VIN Info:")
161163
fmt.Println(string(jsonBytes))
162164
}
165+
if p.eleva {
166+
vinInfo, _, err = vinDecodingService.GetVIN(ctx, vin, coremodels.ElevaKaufmannProvider, country)
167+
if err != nil {
168+
fmt.Println(err.Error())
169+
continue
170+
}
171+
fmt.Printf("VIN Response: \n %+v\n", vinInfo)
172+
}
163173
fmt.Println()
164174
if p.persistToDB {
165175
if vinInfo == nil || vinInfo.Model == "" {
@@ -250,6 +260,10 @@ func instantiateVINDecodingSvc(ctx context.Context, settings *config.Settings, l
250260
vincarioAPI := gateways.NewVincarioAPIService(settings, logger)
251261
jp17vinAPI := gateways.NewJapan17VINAPI(logger, settings)
252262
carvxAPI := gateways.NewCarVxVINAPI(logger, settings)
263+
elevaAPI := gateways.NewElevaAPI(settings)
264+
if settings.Environment == "local" {
265+
return services.NewVINDecodingService(drivlyAPI, vincarioAPI, nil, logger, nil, datAPI, pdb.DBS, jp17vinAPI, carvxAPI, elevaAPI)
266+
}
253267

254268
send, err := createSender(ctx, settings, logger)
255269
if err != nil {
@@ -267,7 +281,5 @@ func instantiateVINDecodingSvc(ctx context.Context, settings *config.Settings, l
267281
}
268282
deviceDefinitionOnChainService := gateways.NewDeviceDefinitionOnChainService(settings, logger, ethClient, chainID, send, pdb.DBS)
269283

270-
vinDecodingService := services.NewVINDecodingService(drivlyAPI, vincarioAPI, nil, logger, deviceDefinitionOnChainService, datAPI, pdb.DBS, jp17vinAPI, carvxAPI)
271-
272-
return vinDecodingService
284+
return services.NewVINDecodingService(drivlyAPI, vincarioAPI, nil, logger, deviceDefinitionOnChainService, datAPI, pdb.DBS, jp17vinAPI, carvxAPI, elevaAPI)
273285
}

internal/api/api.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ func Run(ctx context.Context, logger zerolog.Logger, settings *config.Settings,
6262
autoIsoAPIService := gateways.NewAutoIsoAPIService(settings.AutoIsoAPIUid, settings.AutoIsoAPIKey)
6363
japan17VINAPI := gateways.NewJapan17VINAPI(&logger, settings)
6464
carvxAPI := gateways.NewCarVxVINAPI(&logger, settings)
65+
elevaAPI := gateways.NewElevaAPI(settings)
6566
registryInstance, err := contracts.NewRegistry(settings.EthereumRegistryAddress, ethClient)
6667
if err != nil {
6768
logger.Fatal().Err(err).Msg("Failed to create registry query instance.")
@@ -75,7 +76,7 @@ func Run(ctx context.Context, logger zerolog.Logger, settings *config.Settings,
7576
vinRepository := repositories.NewVINRepository(pdb.DBS, registryInstance, identityAPI)
7677

7778
//cache services
78-
vincDecodingService := services.NewVINDecodingService(drivlyAPIService, vincarioAPIService, autoIsoAPIService, &logger, ddOnChainService, datGroupWSService, pdb.DBS, japan17VINAPI, carvxAPI)
79+
vincDecodingService := services.NewVINDecodingService(drivlyAPIService, vincarioAPIService, autoIsoAPIService, &logger, ddOnChainService, datGroupWSService, pdb.DBS, japan17VINAPI, carvxAPI, elevaAPI)
7980
powerTrainTypeService, err := services.NewPowerTrainTypeService("powertrain_type_rule.yaml", &logger, ddOnChainService)
8081
searchService := search.NewTypesenseAPIService(settings, &logger)
8182
if err != nil {

internal/config/settings.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ type Settings struct {
5555
Japan17VINPassword string `yaml:"JAPAN17_VIN_PASSWORD"`
5656
CarVxUserID string `yaml:"CAR_VX_USER_ID"`
5757
CarVxAPIKey string `yaml:"CAR_VX_API_KEY"`
58+
// used for Kaufmann API
59+
ElevaUsername string `yaml:"ELEVA_USERNAME"`
60+
ElevaPassword string `yaml:"ELEVA_PASSWORD"`
5861
}
5962

6063
func (s *Settings) IsProd() bool {

internal/core/models/vin_decoding_models.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import (
1212
type DecodeProviderEnum string
1313

1414
const (
15-
DrivlyProvider DecodeProviderEnum = "drivly"
16-
VincarioProvider DecodeProviderEnum = "vincario"
17-
AutoIsoProvider DecodeProviderEnum = "autoiso"
18-
DATGroupProvider DecodeProviderEnum = "dat"
19-
AllProviders DecodeProviderEnum = ""
20-
TeslaProvider DecodeProviderEnum = "tesla"
21-
Japan17VIN DecodeProviderEnum = "japan17vin"
22-
CarVXVIN DecodeProviderEnum = "carvxvin"
15+
DrivlyProvider DecodeProviderEnum = "drivly"
16+
VincarioProvider DecodeProviderEnum = "vincario"
17+
AutoIsoProvider DecodeProviderEnum = "autoiso"
18+
DATGroupProvider DecodeProviderEnum = "dat"
19+
AllProviders DecodeProviderEnum = ""
20+
TeslaProvider DecodeProviderEnum = "tesla"
21+
Japan17VIN DecodeProviderEnum = "japan17vin"
22+
CarVXVIN DecodeProviderEnum = "carvxvin"
23+
ElevaKaufmannProvider DecodeProviderEnum = "eleva"
2324
)
2425

2526
type VINDecodingInfoData struct {
@@ -444,3 +445,28 @@ type CarVxResponse struct {
444445
} `json:"data"`
445446
Error string `json:"error"`
446447
}
448+
449+
// nolint
450+
type ElevaVINResponse struct {
451+
Error int `json:"error"`
452+
Message string `json:"message"`
453+
Data struct {
454+
Client struct {
455+
Rut string `json:"rut"`
456+
ClientId int `json:"clientId"`
457+
Name string `json:"name"`
458+
Lastname string `json:"lastname"`
459+
Email string `json:"email"`
460+
Phone string `json:"phone"`
461+
BusinessName string `json:"businessName"`
462+
} `json:"client"`
463+
Vehicle struct {
464+
Plate string `json:"plate"`
465+
Chassis string `json:"chassis"`
466+
Model string `json:"model"`
467+
Kms int `json:"kms"`
468+
Baumuster string `json:"baumuster"`
469+
Brand string `json:"brand"`
470+
} `json:"vehicle"`
471+
} `json:"data"`
472+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"error": 0,
3+
"message": "Success",
4+
"data": {
5+
"client": {
6+
"rut": "12866851-9",
7+
"clientId": 13,
8+
"name": "MACARENA",
9+
"lastname": "RAMOS ALERCE",
10+
"email": "maxhoja@gmail.com",
11+
"phone": "995339163",
12+
"businessName": "MACARENA RAMOS ALERCE"
13+
},
14+
"vehicle": {
15+
"plate": "SC-SS29",
16+
"chassis": "W1K3F4GB9NN286196",
17+
"model": "A 250",
18+
"kms": 43879,
19+
"baumuster": "177046",
20+
"brand": "MERCEDES-BENZ"
21+
}
22+
}
23+
}

internal/core/services/vin_decoding_service.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strconv"
1111
"strings"
1212
"time"
13+
"unicode"
1314

1415
"github.com/DIMO-Network/shared/pkg/logfields"
1516

@@ -37,15 +38,17 @@ type vinDecodingService struct {
3738
DATGroupAPIService gateways.DATGroupAPIService
3839
japan17VINAPI gateways.Japan17VINAPI
3940
carvxAPI gateways.CarVxVINAPI
41+
elevaAPI gateways.ElevaAPI
4042
onChainSvc gateways.DeviceDefinitionOnChainService
4143
dbs func() *db.ReaderWriter
4244
}
4345

4446
func NewVINDecodingService(drivlyAPISvc gateways.DrivlyAPIService, vincarioAPISvc gateways.VincarioAPIService, autoIso gateways.AutoIsoAPIService, logger *zerolog.Logger,
4547
onChainSvc gateways.DeviceDefinitionOnChainService, datGroupAPIService gateways.DATGroupAPIService, dbs func() *db.ReaderWriter,
46-
japan17VINAPI gateways.Japan17VINAPI, carvxAPI gateways.CarVxVINAPI) VINDecodingService {
48+
japan17VINAPI gateways.Japan17VINAPI, carvxAPI gateways.CarVxVINAPI, elevaAPI gateways.ElevaAPI) VINDecodingService {
4749
return &vinDecodingService{drivlyAPISvc: drivlyAPISvc, vincarioAPISvc: vincarioAPISvc, autoIsoAPIService: autoIso,
48-
japan17VINAPI: japan17VINAPI, carvxAPI: carvxAPI, logger: logger, onChainSvc: onChainSvc, DATGroupAPIService: datGroupAPIService, dbs: dbs}
50+
japan17VINAPI: japan17VINAPI, carvxAPI: carvxAPI, logger: logger, onChainSvc: onChainSvc,
51+
DATGroupAPIService: datGroupAPIService, dbs: dbs, elevaAPI: elevaAPI}
4952
}
5053

5154
func (c vinDecodingService) GetVIN(ctx context.Context, vin string, provider coremodels.DecodeProviderEnum, country string) (*coremodels.VINDecodingInfoData, *coremodels.VINDecodingVendorExtra, error) {
@@ -56,7 +59,10 @@ func (c vinDecodingService) GetVIN(ctx context.Context, vin string, provider cor
5659
vin = strings.ToUpper(strings.TrimSpace(vin))
5760
providersToTry := make([]coremodels.DecodeProviderEnum, 0)
5861
// check for japan chasis if all providers
59-
if provider == coremodels.AllProviders && ((len(vin) < 17 && len(vin) > 10) || country == "JPN") {
62+
if country == "CHL" {
63+
providersToTry = append(providersToTry, coremodels.ElevaKaufmannProvider)
64+
providersToTry = append(providersToTry, coremodels.VincarioProvider) // sometimes works in latam as backup
65+
} else if provider == coremodels.AllProviders && ((len(vin) < 17 && len(vin) > 10) || country == "JPN") {
6066
providersToTry = append(providersToTry, coremodels.CarVXVIN)
6167
providersToTry = append(providersToTry, coremodels.Japan17VIN)
6268
} else if !ValidateVIN(vin) {
@@ -227,6 +233,33 @@ func (c vinDecodingService) GetVIN(ctx context.Context, vin string, provider cor
227233
continue
228234
}
229235
return result, resultVendorExtra, nil
236+
case coremodels.ElevaKaufmannProvider:
237+
resultVendorExtra.VendorsTried = append(resultVendorExtra.VendorsTried, string(coremodels.ElevaKaufmannProvider))
238+
localLog.Info().Msgf("trying to decode VIN: %s with eleva", vin)
239+
240+
vinInfo, err := c.elevaAPI.GetVINInfo(vin)
241+
if err != nil {
242+
errFinal = errors.Wrapf(err, "unable to decode vin: %s with eleva", vin)
243+
continue
244+
}
245+
vinObj := vinutil.VIN(vin)
246+
yr := vinObj.Year()
247+
if yr < 2018 {
248+
yr = time.Now().Year() - 1
249+
localLog.Info().Msgf("vin year is less than 2019 for eleva kaufmann, using %d instead", yr)
250+
}
251+
252+
result = &coremodels.VINDecodingInfoData{
253+
VIN: vin,
254+
Make: titleCase(vinInfo.Data.Vehicle.Brand), // MERCEDES-BENZ to Mercedes-Benz
255+
Model: vinInfo.Data.Vehicle.Model,
256+
SubModel: "",
257+
Year: int32(yr),
258+
StyleName: "",
259+
Source: coremodels.ElevaKaufmannProvider,
260+
Raw: nil,
261+
}
262+
return result, resultVendorExtra, nil
230263
case coremodels.AllProviders:
231264
// this should never hit
232265
errFinal = fmt.Errorf("all providers - invalid option reached")
@@ -395,3 +428,20 @@ func validateVinDecoding(vdi *coremodels.VINDecodingInfoData) error {
395428

396429
return nil
397430
}
431+
432+
func titleCase(s string) string {
433+
parts := strings.Split(s, "-")
434+
435+
for i, part := range parts {
436+
if len(part) == 0 {
437+
continue
438+
}
439+
part = strings.ToLower(part)
440+
441+
runes := []rune(part)
442+
runes[0] = unicode.ToUpper(runes[0])
443+
444+
parts[i] = string(runes)
445+
}
446+
return strings.Join(parts, "-")
447+
}

internal/core/services/vin_decoding_service_test.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type VINDecodingServiceSuite struct {
3737

3838
mockOnChainSvc *mock_gateways.MockDeviceDefinitionOnChainService
3939
vinDecodingService VINDecodingService
40+
mockElevaAPI *mock_gateways.MockElevaAPI
4041
}
4142

4243
func TestVINDecodingService(t *testing.T) {
@@ -62,10 +63,11 @@ func (s *VINDecodingServiceSuite) SetupTest() {
6263
s.mockDATGroupAPIService = mock_gateways.NewMockDATGroupAPIService(s.ctrl)
6364
s.mockJapan17VINAPI = mock_gateways.NewMockJapan17VINAPI(s.ctrl)
6465
s.mockCarvxAPI = mock_gateways.NewMockCarVxVINAPI(s.ctrl)
66+
s.mockElevaAPI = mock_gateways.NewMockElevaAPI(s.ctrl)
6567
s.mockOnChainSvc = mock_gateways.NewMockDeviceDefinitionOnChainService(s.ctrl)
6668

6769
s.vinDecodingService = NewVINDecodingService(s.mockDrivlyAPISvc, s.mockVincarioAPISvc, s.mockAutoIsoAPISvc, dbtesthelper.Logger(),
68-
s.mockOnChainSvc, s.mockDATGroupAPIService, s.pdb.DBS, s.mockJapan17VINAPI, s.mockCarvxAPI)
70+
s.mockOnChainSvc, s.mockDATGroupAPIService, s.pdb.DBS, s.mockJapan17VINAPI, s.mockCarvxAPI, s.mockElevaAPI)
6971
}
7072

7173
func (s *VINDecodingServiceSuite) TearDownTest() {
@@ -108,6 +110,31 @@ func (s *VINDecodingServiceSuite) Test_VINDecodingService_Japan17VIN_Success() {
108110
assert.Equal(s.T(), result.Year, int32(2022))
109111
}
110112

113+
//go:embed eleva_resp.json
114+
var elevaAPIResponse []byte
115+
116+
func (s *VINDecodingServiceSuite) Test_VINDecodingService_KaufmannEleva_Success() {
117+
ctx := context.Background()
118+
const vin = "W1K3F4GB9NN286196"
119+
const country = "CHL" // chile only
120+
121+
vinInfoResp := &coremodels.ElevaVINResponse{}
122+
err := json.Unmarshal(elevaAPIResponse, vinInfoResp)
123+
require.NoError(s.T(), err)
124+
s.mockElevaAPI.EXPECT().GetVINInfo(vin).Times(1).Return(vinInfoResp, nil)
125+
126+
_ = dbtesthelper.SetupCreateDeviceType(s.T(), s.pdb)
127+
128+
result, _, err := s.vinDecodingService.GetVIN(ctx, vin, coremodels.AllProviders, country)
129+
130+
s.NoError(err)
131+
assert.Equal(s.T(), result.VIN, vin)
132+
assert.Equal(s.T(), result.Source, coremodels.ElevaKaufmannProvider)
133+
assert.Equal(s.T(), result.Make, "Mercedes-Benz")
134+
assert.Equal(s.T(), result.Model, "A 250")
135+
assert.Equal(s.T(), result.Year, int32(2022))
136+
}
137+
111138
func (s *VINDecodingServiceSuite) Test_VINDecodingService_Drivly_Success() {
112139
ctx := context.Background()
113140
const vin = "1FMCU0G61MUA52727" // ford escape 2021

0 commit comments

Comments
 (0)