Skip to content

Commit 9b6c4da

Browse files
authored
Intg-1794: decode vins from file with option to persist (#292)
* implement option to read vins from file * implement database persistance option
1 parent d78996b commit 9b6c4da

1 file changed

Lines changed: 201 additions & 41 deletions

File tree

cmd/device-definitions-api/decode_vin.go

Lines changed: 201 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,21 @@ package main
22

33
import (
44
"context"
5+
"encoding/csv"
56
"flag"
67
"fmt"
8+
"io"
9+
"os"
10+
11+
"github.com/DIMO-Network/device-definitions-api/internal/core/common"
12+
coremodels "github.com/DIMO-Network/device-definitions-api/internal/core/models"
13+
"github.com/DIMO-Network/device-definitions-api/internal/core/services"
14+
"github.com/DIMO-Network/device-definitions-api/internal/infrastructure/db/models"
15+
"github.com/DIMO-Network/shared/pkg/db"
16+
vinutil "github.com/DIMO-Network/shared/pkg/vin"
17+
"github.com/ethereum/go-ethereum/ethclient"
18+
"github.com/volatiletech/null/v8"
19+
"github.com/volatiletech/sqlboiler/v4/boil"
720

821
"github.com/goccy/go-json"
922

@@ -17,86 +30,233 @@ type decodeVINCmd struct {
1730
logger *zerolog.Logger
1831
settings *config.Settings
1932

20-
datGroup bool
21-
drivly bool
22-
vincario bool
23-
japan17vin bool
33+
datGroup bool
34+
drivly bool
35+
vincario bool
36+
japan17vin bool
37+
fromFile bool
38+
persistToDB bool
2439
}
2540

2641
func (*decodeVINCmd) Name() string { return "decodevin" }
2742
func (*decodeVINCmd) Synopsis() string {
2843
return "tries decoding a vin with chosen provider - does not insert in our db"
2944
}
3045
func (*decodeVINCmd) Usage() string {
31-
return `decodevin [-dat|-drivly|-vincario|-japan17vin] <vin 17 chars> <country two letter iso>`
46+
return `decodevin [-dat|-drivly|-vincario|-japan17vin|-from-file] <vin 17 chars OR filaname in /tmp> <country two letter iso>`
3247
}
3348

3449
func (p *decodeVINCmd) SetFlags(f *flag.FlagSet) {
3550
f.BoolVar(&p.datGroup, "dat", false, "use dat group vin decoder")
3651
f.BoolVar(&p.drivly, "drivly", false, "use drivly vin decoder")
3752
f.BoolVar(&p.vincario, "vincario", false, "use vincario vin decoder")
3853
f.BoolVar(&p.japan17vin, "japan17vin", false, "use japan17vin vin decoder")
54+
f.BoolVar(&p.fromFile, "from-file", false, "read vin from file in /tmp directory")
55+
f.BoolVar(&p.persistToDB, "persist-to-db", false, "persist successful vin decodings to db, table vin_numbers")
3956
}
4057

41-
func (p *decodeVINCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
58+
func (p *decodeVINCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
4259
if len(f.Args()) == 0 {
43-
fmt.Println("missing vin parameter")
60+
if p.fromFile {
61+
fmt.Println("missing filename parameter")
62+
} else {
63+
fmt.Println("missing vin parameter")
64+
}
4465
return subcommands.ExitUsageError
4566
}
46-
vin := f.Args()[0]
67+
vinOrFile := f.Args()[0]
68+
69+
pdb := db.NewDbConnectionFromSettings(context.Background(), &p.settings.DB, true)
70+
pdb.WaitForDB(*p.logger)
4771

4872
country := "USA"
4973
if len(f.Args()) == 2 {
5074
country = f.Args()[1]
5175
}
52-
fmt.Printf("VIN: %s\n", vin)
76+
vins := []string{}
77+
if p.fromFile {
78+
fmt.Printf("Filename: %s\n", vinOrFile)
79+
vins = loadVINsFromFile(vinOrFile)
80+
} else {
81+
fmt.Printf("VIN: %s\n", vinOrFile)
82+
vins = append(vins, vinOrFile)
83+
}
5384
fmt.Printf("Country: %s\n", country)
5485

55-
if p.datGroup {
56-
// use the dat group service to decode
57-
datAPI := gateways.NewDATGroupAPIService(p.settings, p.logger)
58-
vinInfo, err := datAPI.GetVINv2(vin, country)
86+
fmt.Printf("total VINs found: %d\n", len(vins))
87+
if len(vins) == 0 {
88+
fmt.Println("no vins found")
89+
return subcommands.ExitFailure
90+
}
91+
92+
vinDecodingService := instantiateVINDecodingSvc(ctx, p.settings, p.logger, pdb)
93+
94+
for _, vin := range vins {
95+
// in case want to insert
96+
vinObj := vinutil.VIN(vin)
97+
dbVin := &models.VinNumber{
98+
Vin: vin,
99+
Wmi: null.StringFrom(vinObj.Wmi()),
100+
VDS: null.StringFrom(vinObj.VDS()),
101+
SerialNumber: vinObj.SerialNumber(),
102+
CheckDigit: null.StringFrom(vinObj.CheckDigit()),
103+
Vis: null.StringFrom(vinObj.VIS()),
104+
}
105+
wmi, _ := models.Wmis(models.WmiWhere.Wmi.EQ(vinObj.Wmi())).One(ctx, pdb.DBS().Reader)
106+
if wmi != nil {
107+
dbVin.ManufacturerName = wmi.ManufacturerName
108+
}
109+
dt, err := models.DeviceTypes(models.DeviceTypeWhere.ID.EQ(common.DefaultDeviceType)).One(ctx, pdb.DBS().Reader)
59110
if err != nil {
60111
fmt.Println(err.Error())
61112
return subcommands.ExitFailure
62113
}
114+
vinInfo := &coremodels.VINDecodingInfoData{VIN: vin}
63115

64-
fmt.Printf("\n\nVIN Response: %+v\n", *vinInfo)
65-
}
66-
if p.drivly {
67-
drivlyAPI := gateways.NewDrivlyAPIService(p.settings)
68-
vinInfo, err := drivlyAPI.GetVINInfo(vin)
69-
if err != nil {
70-
fmt.Println(err.Error())
71-
return subcommands.ExitFailure
116+
if p.datGroup {
117+
vinInfo, err = vinDecodingService.GetVIN(ctx, vin, dt, coremodels.DATGroupProvider, country)
118+
// use the dat group service to decode
119+
if err != nil {
120+
fmt.Println(err.Error())
121+
}
122+
123+
fmt.Printf("\n\nVIN Response: %+v\n", vinInfo)
124+
}
125+
if p.drivly {
126+
vinInfo, err = vinDecodingService.GetVIN(ctx, vin, dt, coremodels.DrivlyProvider, country)
127+
if err != nil {
128+
fmt.Println(err.Error())
129+
return subcommands.ExitFailure
130+
}
131+
132+
fmt.Printf("VIN Response: %+v\n", vinInfo)
72133
}
134+
if p.vincario {
135+
vinInfo, err = vinDecodingService.GetVIN(ctx, vin, dt, coremodels.VincarioProvider, country)
136+
if err != nil {
137+
fmt.Println(err.Error())
138+
return subcommands.ExitFailure
139+
}
73140

74-
fmt.Printf("VIN Response: %+v\n", vinInfo)
141+
fmt.Printf("VIN Response: %+v\n", vinInfo)
142+
}
143+
if p.japan17vin {
144+
vinInfo, err = vinDecodingService.GetVIN(ctx, vin, dt, coremodels.Japan17VIN, country)
145+
if err != nil {
146+
fmt.Println(err.Error())
147+
return subcommands.ExitFailure
148+
}
149+
jsonBytes, _ := json.MarshalIndent(vinInfo, "", " ")
150+
fmt.Println("VIN Info:")
151+
fmt.Println(string(jsonBytes))
152+
}
153+
fmt.Println()
154+
if p.persistToDB {
155+
if vinInfo == nil || vinInfo.Model == "" {
156+
fmt.Println("no decoding info found, skipping: " + vin)
157+
continue
158+
}
159+
dbVin.Year = int(vinInfo.Year)
160+
if dbVin.ManufacturerName == "" {
161+
dbVin.ManufacturerName = vinInfo.Make
162+
}
163+
dbVin.DatgroupData = null.JSONFrom(vinInfo.Raw)
164+
dbVin.DefinitionID = common.DeviceDefinitionSlug(vinInfo.Make, vinInfo.Model, int16(vinInfo.Year))
165+
dbVin.DecodeProvider = null.StringFrom(string(vinInfo.Source))
166+
// todo future change to add field with StyleName
167+
168+
err := dbVin.Insert(ctx, pdb.DBS().Writer, boil.Infer())
169+
if err != nil {
170+
fmt.Println(err.Error())
171+
return subcommands.ExitFailure
172+
}
173+
}
75174
}
76-
if p.vincario {
77-
vincarioAPI := gateways.NewVincarioAPIService(p.settings, p.logger)
78-
vinInfo, err := vincarioAPI.DecodeVIN(vin)
79-
if err != nil {
80-
fmt.Println(err.Error())
81-
return subcommands.ExitFailure
175+
return subcommands.ExitSuccess
176+
}
177+
178+
func loadVINsFromFile(file string) []string {
179+
// files are assumed to be in the tmp directory
180+
// pull out vins from csv file from column "vin"
181+
vinFile := "/tmp/" + file
182+
vinFileContents, err := readVINFile(vinFile)
183+
if err != nil {
184+
fmt.Println(err.Error())
185+
return []string{}
186+
}
187+
return vinFileContents
188+
}
189+
190+
func readVINFile(filename string) ([]string, error) {
191+
file, err := os.Open(filename)
192+
if err != nil {
193+
return nil, fmt.Errorf("failed to open file: %w", err)
194+
}
195+
defer file.Close()
196+
197+
reader := csv.NewReader(file)
198+
199+
// Read header row to find the index of "csv" column
200+
header, err := reader.Read()
201+
if err != nil {
202+
return nil, fmt.Errorf("failed to read CSV header: %w", err)
203+
}
204+
205+
csvColumnIndex := -1
206+
for i, columnName := range header {
207+
if columnName == "csv" {
208+
csvColumnIndex = i
209+
break
82210
}
211+
}
83212

84-
fmt.Printf("VIN Response: %+v\n", vinInfo)
213+
if csvColumnIndex == -1 {
214+
csvColumnIndex = 0 // default to first column if "csv" column not found
215+
fmt.Println("defaulting to first column as 'csv' column not found, please ensure your CSV file has a column named 'csv' with VINs in it.")
85216
}
86-
if p.japan17vin {
87-
jp17vinAPI := gateways.NewJapan17VINAPI(p.logger, p.settings)
88-
vinInfo, payload, err := jp17vinAPI.GetVINInfo(vin)
217+
218+
// Read all rows and extract values from the "csv" column
219+
var values []string
220+
for {
221+
row, err := reader.Read()
222+
if err == io.EOF {
223+
break
224+
}
89225
if err != nil {
90-
fmt.Println(err.Error())
91-
return subcommands.ExitFailure
226+
return nil, fmt.Errorf("failed to read CSV row: %w", err)
227+
}
228+
229+
if csvColumnIndex < len(row) {
230+
values = append(values, row[csvColumnIndex])
92231
}
93-
jsonBytes, _ := json.MarshalIndent(vinInfo, "", " ")
94-
fmt.Println("VIN Info:")
95-
fmt.Println(string(jsonBytes))
96-
fmt.Println("Raw JSON Payload:")
97-
fmt.Println(string(payload))
98232
}
99233

100-
fmt.Println()
101-
return subcommands.ExitSuccess
234+
return values, nil
235+
}
236+
237+
func instantiateVINDecodingSvc(ctx context.Context, settings *config.Settings, logger *zerolog.Logger, pdb db.Store) services.VINDecodingService {
238+
datAPI := gateways.NewDATGroupAPIService(settings, logger)
239+
drivlyAPI := gateways.NewDrivlyAPIService(settings)
240+
vincarioAPI := gateways.NewVincarioAPIService(settings, logger)
241+
jp17vinAPI := gateways.NewJapan17VINAPI(logger, settings)
242+
243+
send, err := createSender(ctx, settings, logger)
244+
if err != nil {
245+
logger.Fatal().Err(err).Msg("Failed to create sender.")
246+
}
247+
248+
ethClient, err := ethclient.Dial(settings.EthereumRPCURL.String())
249+
if err != nil {
250+
logger.Fatal().Err(err).Msg("Failed to create Ethereum client.")
251+
}
252+
253+
chainID, err := ethClient.ChainID(ctx)
254+
if err != nil {
255+
logger.Fatal().Err(err).Msg("Couldn't retrieve chain id.")
256+
}
257+
deviceDefinitionOnChainService := gateways.NewDeviceDefinitionOnChainService(settings, logger, ethClient, chainID, send, pdb.DBS)
258+
259+
vinDecodingService := services.NewVINDecodingService(drivlyAPI, vincarioAPI, nil, logger, deviceDefinitionOnChainService, datAPI, pdb.DBS, jp17vinAPI)
260+
261+
return vinDecodingService
102262
}

0 commit comments

Comments
 (0)