Skip to content

Commit 219685f

Browse files
authored
INTG-1733 allow jp chassis number decoding (#282)
* change table structure, add unit test * decoding japan chasis numbers with existing vin in db working * lint * refactor and improve code around decoding with existing db vin match
1 parent f64c2db commit 219685f

5 files changed

Lines changed: 173 additions & 59 deletions

File tree

cmd/device-definitions-api/add_vin.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ func (p *addVINCmd) Execute(ctx context.Context, _ *flag.FlagSet, _ ...interface
9090
}
9191
vinNumber := models.VinNumber{
9292
Vin: vin,
93-
Wmi: processedVIN.Wmi(),
94-
VDS: processedVIN.VDS(),
95-
CheckDigit: processedVIN.CheckDigit(),
93+
Wmi: null.StringFrom(processedVIN.Wmi()),
94+
VDS: null.StringFrom(processedVIN.VDS()),
9695
SerialNumber: processedVIN.SerialNumber(),
97-
Vis: processedVIN.VIS(),
96+
CheckDigit: null.StringFrom(processedVIN.CheckDigit()),
97+
Vis: null.StringFrom(processedVIN.VIS()),
9898
ManufacturerName: wmi.R.DeviceMake.Name,
9999
DecodeProvider: null.StringFrom("manual"),
100100
Year: processedVIN.Year(),

internal/core/queries/decode_vin.go

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func NewDecodeVINQueryHandler(dbs func() *db.ReaderWriter, vinDecodingService se
7373

7474
func (dc DecodeVINQueryHandler) Handle(ctx context.Context, query mediator.Message) (interface{}, error) {
7575
qry := query.(*DecodeVINQuery)
76-
if len(qry.VIN) != 17 {
76+
if !(len(qry.VIN) >= 13 && len(qry.VIN) <= 17) {
7777
return nil, &exceptions.ValidationError{Err: fmt.Errorf("invalid vin %s", qry.VIN)}
7878
}
7979
resp := &p_grpc.DecodeVinResponse{}
@@ -111,30 +111,10 @@ func (dc DecodeVINQueryHandler) Handle(ctx context.Context, query mediator.Messa
111111
metrics.InternalError.With(prometheus.Labels{"method": VinErrors}).Inc()
112112
return nil, errors.Wrap(err, "error when querying for existing VIN number")
113113
}
114-
// todo refactor: func hydrateResponseFromVinNumber(...)
115-
if vinDecodeNumber != nil {
116-
// get from tableland, probably don't need this here anymore
117-
//tblDef, errTbl := dc.deviceDefinitionOnChainService.GetDefinitionByID(ctx, vinDecodeNumber.DefinitionID, dc.dbs().Reader)
118-
// should mark this deprecated
119-
//resp.DeviceMakeId = vinDecodeNumber.DeviceMakeID
120-
resp.Manufacturer = vinDecodeNumber.ManufacturerName
121-
resp.Year = int32(vinDecodeNumber.Year)
122-
resp.DeviceStyleId = vinDecodeNumber.StyleID.String
123-
resp.Source = vinDecodeNumber.DecodeProvider.String
124-
resp.DefinitionId = vinDecodeNumber.DefinitionID
125-
split := strings.Split(vinDecodeNumber.DefinitionID, "_")
126-
if len(split) != 3 {
127-
return nil, errors.New("invalid definition ID encountered: " + vinDecodeNumber.DefinitionID)
128-
}
129-
pt, err := dc.powerTrainTypeService.ResolvePowerTrainType(split[0], split[1], vinDecodeNumber.DrivlyData, vinDecodeNumber.VincarioData)
130-
if err != nil {
131-
pt = coremodels.ICE.String()
132-
}
133-
resp.Powertrain = pt
134-
114+
// if database vin_number match found, just return it here
115+
if r := dc.hydrateResponseFromVinNumber(vinDecodeNumber); r != nil {
135116
metrics.Success.With(prometheus.Labels{"method": VinExists}).Inc()
136-
137-
return resp, nil
117+
return r, nil
138118
}
139119

140120
// todo: this should be a separate specific gRPC endpoint for setting or updating vin number to DD mapping
@@ -161,10 +141,10 @@ func (dc DecodeVINQueryHandler) Handle(ctx context.Context, query mediator.Messa
161141
vinDecodeNumber = &models.VinNumber{
162142
Vin: vin.String(),
163143
ManufacturerName: dm.Name,
164-
Wmi: dbWMI.Wmi,
165-
VDS: vin.VDS(),
166-
Vis: vin.VIS(),
167-
CheckDigit: vin.CheckDigit(),
144+
Wmi: null.StringFrom(dbWMI.Wmi),
145+
VDS: null.StringFrom(vin.VDS()),
146+
Vis: null.StringFrom(vin.VIS()),
147+
CheckDigit: null.StringFrom(vin.CheckDigit()),
168148
SerialNumber: vin.SerialNumber(),
169149
DecodeProvider: null.StringFrom("manual"),
170150
Year: tblDef.Year,
@@ -373,6 +353,38 @@ func resolveMetadataFromInfo(powertrain string, _ *coremodels.VINDecodingInfoDat
373353
return &md
374354
}
375355

356+
// hydrateResponseFromVinNumber pass in a vin_number database object and converts to vin decode response
357+
func (dc DecodeVINQueryHandler) hydrateResponseFromVinNumber(vn *models.VinNumber) *p_grpc.DecodeVinResponse {
358+
if vn == nil {
359+
return nil
360+
}
361+
// call on-chain svc to get the DD and pull out the powertrain
362+
pt := ""
363+
tblDef, manufID, err := dc.deviceDefinitionOnChainService.GetDefinitionByID(context.Background(), vn.DefinitionID, dc.dbs().Reader)
364+
if err == nil && tblDef != nil {
365+
for _, attribute := range tblDef.Metadata.DeviceAttributes {
366+
if attribute.Name == common.PowerTrainType {
367+
pt = attribute.Value
368+
}
369+
}
370+
if pt == "" {
371+
makeName, _ := dc.deviceDefinitionOnChainService.GetManufacturerNameByID(context.Background(), manufID)
372+
pt, _ = dc.powerTrainTypeService.ResolvePowerTrainType(shared.SlugString(makeName), shared.SlugString(tblDef.Model), null.JSON{}, null.JSON{})
373+
}
374+
}
375+
376+
resp := &p_grpc.DecodeVinResponse{
377+
Manufacturer: vn.ManufacturerName,
378+
Year: int32(vn.Year),
379+
DeviceStyleId: vn.StyleID.String,
380+
Source: vn.DecodeProvider.String,
381+
DefinitionId: vn.DefinitionID,
382+
Powertrain: pt,
383+
}
384+
385+
return resp
386+
}
387+
376388
// processDeviceStyle saves new styles if needed to db and returns the style database ID
377389
func (dc DecodeVINQueryHandler) processDeviceStyle(ctx context.Context, vinInfo *coremodels.VINDecodingInfoData, definitionID, powertrain string) (string, error) {
378390
externalStyleID := shared.SlugString(vinInfo.StyleName)
@@ -425,10 +437,10 @@ func (dc DecodeVINQueryHandler) saveVinDecodeNumber(ctx context.Context, vin sha
425437
vinDecodeNumber := &models.VinNumber{
426438
Vin: vin.String(),
427439
ManufacturerName: resp.Manufacturer,
428-
Wmi: vin.Wmi(),
429-
VDS: vin.VDS(),
430-
Vis: vin.VIS(),
431-
CheckDigit: vin.CheckDigit(),
440+
Wmi: null.StringFrom(vin.Wmi()),
441+
VDS: null.StringFrom(vin.VDS()),
442+
Vis: null.StringFrom(vin.VIS()),
443+
CheckDigit: null.StringFrom(vin.CheckDigit()),
432444
SerialNumber: vin.SerialNumber(),
433445
DecodeProvider: null.StringFrom(string(vinInfo.Source)),
434446
Year: int(resp.Year),

internal/core/queries/decode_vin_test.go

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"math/big"
78
"strconv"
89
"strings"
910
"testing"
@@ -190,8 +191,48 @@ func (s *DecodeVINQueryHandlerSuite) TestHandle_Success_WithExistingDD_UpdatesAt
190191

191192
}
192193

193-
// using existing WMI
194+
// todo two new test: WMI oem conflict, same WMI for different Make name
195+
196+
// Japan
197+
func (s *DecodeVINQueryHandlerSuite) TestHandle_Success_JapanChassisNumber_existingVIN() {
198+
const vin = "ZWR90-8000186" // toyota something or other
199+
200+
dm := dbtesthelper.SetupCreateMake(s.T(), "Toyota", s.pdb)
201+
dd := dbtesthelper.SetupCreateDeviceDefinitionWithVehicleInfo(s.T(), dm, "Yaris", 2024, s.pdb)
202+
203+
vinNumb := models.VinNumber{
204+
Vin: vin,
205+
SerialNumber: "8000186",
206+
ManufacturerName: dm.Name,
207+
DefinitionID: dd.ID,
208+
Year: 2024,
209+
DecodeProvider: null.StringFrom("manual"),
210+
}
211+
err := vinNumb.Insert(s.ctx, s.pdb.DBS().Writer, boil.Infer())
212+
s.Require().NoError(err)
213+
// mock setup for powertrain lookup, which is in the vin decode response
214+
s.mockDeviceDefinitionOnChainService.EXPECT().GetDefinitionByID(gomock.Any(), dd.ID, gomock.Any()).Return(
215+
&coremodels.DeviceDefinitionTablelandModel{
216+
ID: dd.ID,
217+
Model: dd.Model,
218+
Year: dd.Year,
219+
DeviceType: common.DefaultDeviceType,
220+
Metadata: &coremodels.DeviceDefinitionMetadata{DeviceAttributes: []coremodels.DeviceTypeAttribute{
221+
{Name: "powertrain_type", Value: "ICE"},
222+
}},
223+
}, big.NewInt(1), nil)
194224

225+
qryResult, err := s.queryHandler.Handle(s.ctx, &DecodeVINQuery{VIN: vin, Country: country})
226+
s.NoError(err)
227+
result := qryResult.(*p_grpc.DecodeVinResponse)
228+
229+
s.NotNil(result, "expected result not nil")
230+
s.Assert().Equal(int32(2024), result.Year)
231+
s.Assert().Equal(dd.ID, result.DefinitionId)
232+
s.Assert().Equal(dm.Name, result.Manufacturer)
233+
}
234+
235+
// using existing WMI
195236
func (s *DecodeVINQueryHandlerSuite) TestHandle_Success_CreatesDD() {
196237
ctx := context.Background()
197238
const vin = "1FMCU0G61MUA52727" // ford escape 2021
@@ -300,7 +341,7 @@ func (s *DecodeVINQueryHandlerSuite) TestHandle_Success_CreatesDD() {
300341
s.Assert().True(vn.DrivlyData.Valid)
301342
s.Assert().Equal(2021, vn.Year)
302343
s.Assert().Equal(definitionID, vn.DefinitionID)
303-
s.Assert().Equal(wmi, vn.Wmi)
344+
s.Assert().Equal(wmi, vn.Wmi.String)
304345
s.Assert().Equal("drivly", vn.DecodeProvider.String)
305346
s.Assert().Equal(vin, vn.Vin)
306347
s.Assert().Equal(vinInfoResp.Model, gjson.GetBytes(vn.DrivlyData.JSON, "model").String())
@@ -585,20 +626,29 @@ func (s *DecodeVINQueryHandlerSuite) TestHandle_Success_WithExistingVINNumber()
585626
v := shared.VIN(vin)
586627
vinNumb := models.VinNumber{
587628
Vin: vin,
588-
Wmi: v.Wmi(),
589-
VDS: v.VDS(),
590-
CheckDigit: v.CheckDigit(),
629+
Wmi: null.StringFrom(v.Wmi()),
630+
VDS: null.StringFrom(v.VDS()),
631+
CheckDigit: null.StringFrom(v.CheckDigit()),
591632
SerialNumber: v.SerialNumber(),
592-
Vis: v.VIS(),
633+
Vis: null.StringFrom(v.VIS()),
593634
ManufacturerName: dm.Name,
594635
DefinitionID: dd.ID,
595636
Year: 2021,
596637
DecodeProvider: null.StringFrom("drivly"),
597638
}
598639
err = vinNumb.Insert(s.ctx, s.pdb.DBS().Writer, boil.Infer())
599640
s.Require().NoError(err)
600-
// when we get the vin already found, we lookup the powertrain using powertrain service
601-
s.mockPowerTrainTypeService.EXPECT().ResolvePowerTrainType("ford", "escape", vinNumb.DrivlyData, vinNumb.VincarioData)
641+
// mock needed for powertrain lookup
642+
s.mockDeviceDefinitionOnChainService.EXPECT().GetDefinitionByID(gomock.Any(), dd.ID, gomock.Any()).Return(
643+
&coremodels.DeviceDefinitionTablelandModel{
644+
ID: dd.ID,
645+
Model: dd.Model,
646+
Year: dd.Year,
647+
DeviceType: common.DefaultDeviceType,
648+
Metadata: &coremodels.DeviceDefinitionMetadata{DeviceAttributes: []coremodels.DeviceTypeAttribute{
649+
{Name: "powertrain_type", Value: "ICE"},
650+
}},
651+
}, big.NewInt(1), nil)
602652

603653
qryResult, err := s.queryHandler.Handle(s.ctx, &DecodeVINQuery{VIN: vin, Country: country})
604654
s.NoError(err)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
-- +goose Up
2+
-- +goose StatementBegin
3+
SELECT 'up SQL query';
4+
alter table vin_numbers
5+
alter column vin type varchar(17) using vin::varchar(17);
6+
7+
alter table vin_numbers
8+
alter column wmi drop not null;
9+
10+
alter table vin_numbers
11+
alter column vds drop not null;
12+
13+
alter table vin_numbers
14+
alter column check_digit drop not null;
15+
16+
alter table vin_numbers
17+
alter column serial_number type varchar(10) using serial_number::varchar(10);
18+
19+
alter table vin_numbers
20+
alter column vis drop not null;
21+
22+
alter table vin_numbers
23+
add vin17_data jsonb;
24+
25+
-- +goose StatementEnd
26+
27+
-- +goose Down
28+
-- +goose StatementBegin
29+
SELECT 'down SQL query';
30+
31+
alter table vin_numbers
32+
drop vin17_data;
33+
34+
alter table vin_numbers
35+
alter column wmi set not null;
36+
37+
alter table vin_numbers
38+
alter column vds set not null;
39+
40+
alter table vin_numbers
41+
alter column check_digit set not null;
42+
43+
alter table vin_numbers
44+
alter column vis set not null;
45+
-- +goose StatementEnd

internal/infrastructure/db/models/vin_numbers.go

Lines changed: 22 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)