Initial cloud-services repo - gateway service + pkg modules

This commit is contained in:
Chris Rai
2026-01-30 23:14:52 -05:00
commit fbb820d7b3
1037 changed files with 171318 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
package flashpackversion
import (
"sort"
"strconv"
"fiskerinc.com/modules/common"
"fiskerinc.com/modules/db/queries"
"github.com/pkg/errors"
"golang.org/x/exp/maps"
)
// Any time we insert new CarECUs, we want to see if the flashpack number for the car has changed
// If so, we save it on the car, send it to SAP, and update the Version Log.
func InsertCarECUsAndUpdateFlashpackVersion(cars queries.CarsInterface, cvl queries.CarVersionsLogInterface, vin string, ecus []common.CarECU) error {
// Convert the ecu names to cloud compatible ecus
for _, e := range ecus {
e.TransformECUName()
}
// Insert the new CarECUs
err := cars.InsertCarECUs(ecus)
if err != nil {
return err
}
var previousFlashpackNumber, currentFlashpackNumber string
// Find the previous flashpack version of the car
car, err := cars.SelectByVIN(vin)
if err != nil {
return err
}
previousFlashpackNumber = car.Flashpack
// Find the current flashpack number of the car
currentFlashpackNumber, err = FindCurrentFlashpackVersionForCar(cars, *car)
if err != nil {
return err
}
// If the flashpack has changed, update the car and SAP
return updateFlashpackNumber(cars, cvl, vin, currentFlashpackNumber, previousFlashpackNumber)
}
// Given the latest ECUs for a car, find out what the flashpack number for the car is
func FindCurrentFlashpackVersionForCar(cars queries.CarsInterface, car common.Car) (currentFlashpackNumber string, err error) {
currentFlashpackNumber = ""
// Get a map of key-value pairs, latest updated ECUs for the car, ECU names as keys and ECU Supplier SW Versions as values
latestCarECUsToVersionsMap, err := getLatestCarECUsMap(cars, car.VIN)
if err != nil {
return currentFlashpackNumber, err
}
// Get a map of all flashpack version mappings, and an array of the flashpack numbers sorted in descending numerical order
flashpackMappings, flashpackNumbers, err := getAllFlashpackMappingsByModelTrimMap(cars, car.Model, car.Trim)
if err != nil {
return currentFlashpackNumber, err
}
// Here is the logic to calculate the current flashpack number for the car
// More than one version for an ECU may map to the same flashpack number
// A car may be several versions ahead on one ECU, and several versions behind on another ECU
// Hence the following ugly code
// For each flashpack number in descending order
var ecusUpdated []string
for _, flashpackNumber := range flashpackNumbers {
var flashpackNumberComplete = true
outer: // For each ECU version mapping for the flashpack number
for _, flashpackMapping := range flashpackMappings[flashpackNumber] {
var ecuUpdated = false
inner: // Determine whether this ECU is updated on the car to this flashpack number or a higher one
for _, e := range ecusUpdated {
if e == flashpackMapping.ECU {
ecuUpdated = true
break inner
}
}
if !ecuUpdated {
ecuVersion, ok := latestCarECUsToVersionsMap[flashpackMapping.ECU]
// If the ECU has not been updated at all, or it is not at this version, then the flashpack number is incomplete and the car has not achieved it
if !ok || ecuVersion != flashpackMapping.SupplierSWVersion {
flashpackNumberComplete = false
break outer
} else {
// We have proved that this ECU is updated on the car to this flashpack number
ecusUpdated = append(ecusUpdated, flashpackMapping.ECU)
}
}
}
if flashpackNumberComplete && flashpackNumber > currentFlashpackNumber {
currentFlashpackNumber = flashpackNumber
}
}
return currentFlashpackNumber, err
}
func FindCarECUsToUpdateForNextFlashpackNumber(cars queries.CarsInterface, car common.Car, nextFlashpackNumber string) ([]common.CarECUVersion, error) {
ecusToUpdateForNextFlashpackNumber := []common.CarECUVersion{}
// Get a map of key-value pairs, latest updated ECUs for the car, ECU names as keys and ECU Supplier SW Versions as values
latestCarECUsToVersionsMap, err := getLatestCarECUsMap(cars, car.VIN)
if err != nil {
return nil, err
}
// Get a map of all flashpack version mappings, and an array of the flashpack numbers sorted in descending numerical order
allFlashpackMappings, allFlashpackNumbers, err := getAllFlashpackMappingsByModelTrimMap(cars, car.Model, car.Trim)
if err != nil {
return nil, err
}
// Get the mappings specifically for the next flashpack version
nextFlashpackVersionMappings := allFlashpackMappings[nextFlashpackNumber]
// Here is the logic to find which ECUs on the car need to be updated in order to achieve nextFlashpackNumber
// A car may be several versions ahead on one ECU, and several versions behind on another ECU
// Hence the following ugly code
// For each mapping for the next flashpack version
for _, nextFlashpackVersionMapping := range nextFlashpackVersionMappings {
ecuVersion, ok := latestCarECUsToVersionsMap[nextFlashpackVersionMapping.ECU]
if !ok {
// If the ECU has not been updated at all on this car, we know it needs to be updated to achieve the next flashpack version
ecusToUpdateForNextFlashpackNumber = append(ecusToUpdateForNextFlashpackNumber, common.CarECUVersion{
CarECUName: nextFlashpackVersionMapping.ECU,
CarECUVersion: nextFlashpackVersionMapping.SupplierSWVersion,
})
} else {
// If the ECU has been updated, we need to determine whether its version applies to this flashpack number or a higher one
// That means we need to check all the mappings for the next flashpack number and all higher ones as well
found := false
outer: // For each flashpack number in descending order
for _, flashpackNumber := range allFlashpackNumbers {
inner: // For each ECU mapping for this flashpack number
for _, carECU := range allFlashpackMappings[flashpackNumber] {
if ecuVersion == carECU.SupplierSWVersion {
found = true
break inner
}
}
// Don't check flashpack numbers lower than the next flashpack number
if flashpackNumber == nextFlashpackNumber {
break outer
}
}
if !found {
// We have proved that the ECU has not been updated for the next flashpack number
ecusToUpdateForNextFlashpackNumber = append(ecusToUpdateForNextFlashpackNumber, common.CarECUVersion{
CarECUName: nextFlashpackVersionMapping.ECU,
CarECUVersion: nextFlashpackVersionMapping.SupplierSWVersion,
})
}
}
}
return ecusToUpdateForNextFlashpackNumber, err
}
// Save the flashpack number in the database, display it in the version log, and send it to SAP
func updateFlashpackNumber(cars queries.CarsInterface, cvl queries.CarVersionsLogInterface, vin, currentFlashpack string, previousFlashpack string) error {
if currentFlashpack != previousFlashpack {
_, err := cars.UpdateCarFlashpackVersion(vin, currentFlashpack)
if err != nil {
return errors.WithStack(err)
}
_, err = cvl.LogVersionChange(&common.CarVersionLogs{
VIN: vin,
VersionSource: common.FlashpackVersionSource,
Version: currentFlashpack,
})
if err != nil {
return errors.WithStack(err)
}
}
return nil
}
func getLatestCarECUsMap(cars queries.CarsInterface, vin string) (map[string]string, error) {
allLatestCarECUs, err := cars.GetCarECUs(common.CarECUFilter{VIN: vin, Unique: true}, nil)
if err != nil {
return nil, err
}
// Put into a map by ECU name
var latestCarECUsToVersionsMap = make(map[string]string)
for _, e := range allLatestCarECUs {
latestCarECUsToVersionsMap[e.ECU] = e.SupplierSWVersion
}
// Sometimes the TBOX version is applied to MCU, or the PDU version is applied to OBC, etc.
for ecuName, ecuReplacementName := range common.FlashpackCalculationECUReplacement {
_, hasECUName := latestCarECUsToVersionsMap[ecuName]
_, hasECUReplacementName := latestCarECUsToVersionsMap[ecuReplacementName]
if hasECUName && !hasECUReplacementName {
latestCarECUsToVersionsMap[ecuReplacementName] = latestCarECUsToVersionsMap[ecuName]
}
}
return latestCarECUsToVersionsMap, nil
}
func getAllFlashpackMappingsByModelTrimMap(cars queries.CarsInterface, model string, trim string) (map[string][]common.CarECU, []string, error) {
allFlashpackMappingsByModelTrim, err := cars.GetCarFlashpackVersionMappingsByModelTrim(model, trim, &queries.PageQueryOptions{Order: "flashpack DESC"})
if err != nil {
return nil, nil, err
}
// Map flashpack numbers to an array of ECU names and versions
var flashpackMappings = make(map[string][]common.CarECU)
for _, f := range allFlashpackMappingsByModelTrim {
flashpackMappings[f.Flashpack] =
append(flashpackMappings[f.Flashpack],
common.CarECU{
ECU: f.CarECUName,
SupplierSWVersion: f.CarECUVersion,
})
}
// Sort the flashpack numbers in descending order
var flashpackNumbers = maps.Keys(flashpackMappings)
sort.Slice(flashpackNumbers, func(i, j int) bool {
iFp, _ := strconv.ParseFloat(flashpackNumbers[i], 64) // guaranteed numeric
jFp, _ := strconv.ParseFloat(flashpackNumbers[j], 64) // guaranteed numeric
return iFp > jFp
})
return flashpackMappings, flashpackNumbers, nil
}

View File

@@ -0,0 +1,436 @@
package flashpackversion_test
import (
"testing"
"fiskerinc.com/modules/common"
"fiskerinc.com/modules/db/queries/mocks"
fv "fiskerinc.com/modules/flashpackversion"
"github.com/stretchr/testify/assert"
)
func TestFindCurrentFlashpackVersionForCar(t *testing.T) {
mockCars := setupMockCars()
car := common.Car{
VIN: "FISKER123",
Model: "Ocean",
Trim: "Base",
Year: 2023,
}
mockCars.SelectCarECUs = []common.CarECU{
{
VIN: "FISKER123",
ECU: "ADAS",
SupplierSWVersion: "ADASVersion2",
},
{
VIN: "FISKER123",
ECU: "ACUN",
SupplierSWVersion: "ACUNVersionA",
},
{
VIN: "FISKER123",
ECU: "BCM",
SupplierSWVersion: "BCMVersion",
},
{
VIN: "FISKER123",
ECU: "PDI",
SupplierSWVersion: "PDIVersion",
},
{
VIN: "FISKER123",
ECU: "OBC",
SupplierSWVersion: "PDUVersion",
},
{
VIN: "FISKER123",
ECU: "MCU",
SupplierSWVersion: "TBOXVersion",
},
}
flashpack, err := fv.FindCurrentFlashpackVersionForCar(&mockCars, car)
assert.Nil(t, err)
assert.Equal(t, "46.14", flashpack)
mockCars.SelectCarECUs = []common.CarECU{
{
VIN: "FISKER123",
ECU: "ADAS",
SupplierSWVersion: "ADASVersion",
},
{
VIN: "FISKER123",
ECU: "ACUN",
SupplierSWVersion: "ACUNVersion",
},
{
VIN: "FISKER123",
ECU: "BCM",
SupplierSWVersion: "BCMVersion",
},
}
flashpack, err = fv.FindCurrentFlashpackVersionForCar(&mockCars, car)
assert.Nil(t, err)
assert.Equal(t, "", flashpack)
mockCars.SelectCarECUs = []common.CarECU{
{
VIN: "FISKER123",
ECU: "ADAS",
SupplierSWVersion: "ADASVersion1",
},
{
VIN: "FISKER123",
ECU: "ACUN",
SupplierSWVersion: "ACUNVersion",
},
{
VIN: "FISKER123",
ECU: "BCM",
SupplierSWVersion: "BCMVersion",
},
{
VIN: "FISKER123",
ECU: "PDI",
SupplierSWVersion: "PDIVersion",
},
}
flashpack, err = fv.FindCurrentFlashpackVersionForCar(&mockCars, car)
assert.Nil(t, err)
assert.Equal(t, "41.14", flashpack)
mockCars.SelectCarECUs = []common.CarECU{
{
VIN: "FISKER123",
ECU: "ADAS",
SupplierSWVersion: "ADASVersion1",
},
{
VIN: "FISKER123",
ECU: "ACUN",
SupplierSWVersion: "ACUNVersion0",
},
{
VIN: "FISKER123",
ECU: "BCM",
SupplierSWVersion: "BCMVersion",
},
{
VIN: "FISKER123",
ECU: "PDI",
SupplierSWVersion: "PDIVersion",
},
}
flashpack, err = fv.FindCurrentFlashpackVersionForCar(&mockCars, car)
assert.Nil(t, err)
assert.Equal(t, "39.14", flashpack)
mockCars.SelectCarECUs = []common.CarECU{
{
VIN: "FISKER123",
ECU: "PDI",
SupplierSWVersion: "PDIVersion",
},
}
flashpack, err = fv.FindCurrentFlashpackVersionForCar(&mockCars, car)
assert.Nil(t, err)
assert.Equal(t, "37.14", flashpack)
}
func TestFindCarECUsToUpdateForNextFlashpackNumber(t *testing.T) {
mockCars := setupMockCars()
car := common.Car{
VIN: "FISKER123",
Model: "Ocean",
Trim: "Base",
Year: 2023,
}
mockCars.SelectCarECUs = []common.CarECU{
{
VIN: "FISKER123",
ECU: "ADAS",
SupplierSWVersion: "ADASVersion0",
},
{
VIN: "FISKER123",
ECU: "ACUN",
SupplierSWVersion: "ACUNVersion0",
},
{
VIN: "FISKER123",
ECU: "PDI",
SupplierSWVersion: "PDIVersion",
},
}
ecus, err := fv.FindCarECUsToUpdateForNextFlashpackNumber(&mockCars, car, "41.14")
assert.Nil(t, err)
assert.Equal(t, []common.CarECUVersion{
{
CarECUName: "ADAS",
CarECUVersion: "ADASVersion",
},
{
CarECUName: "ACUN",
CarECUVersion: "ACUNVersion",
},
{
CarECUName: "BCM",
CarECUVersion: "BCMVersion",
},
}, ecus)
mockCars.SelectCarECUs = []common.CarECU{
{
VIN: "FISKER123",
ECU: "ADAS",
SupplierSWVersion: "ADASVersion",
},
{
VIN: "FISKER123",
ECU: "ACUN",
SupplierSWVersion: "ACUNVersion",
},
{
VIN: "FISKER123",
ECU: "PDI",
SupplierSWVersion: "PDIVersion",
},
{
VIN: "FISKER123",
ECU: "BCM",
SupplierSWVersion: "BCMVersion",
},
{
VIN: "FISKER123",
ECU: "OBC",
SupplierSWVersion: "PDUVersion",
},
}
ecus, err = fv.FindCarECUsToUpdateForNextFlashpackNumber(&mockCars, car, "44.14")
assert.Nil(t, err)
assert.Equal(t, []common.CarECUVersion{
{
CarECUName: "ADAS",
CarECUVersion: "ADASVersion1",
},
{
CarECUName: "ACUN",
CarECUVersion: "ACUNVersionA",
},
{
CarECUName: "ACUN",
CarECUVersion: "ACUNVersionB",
},
}, ecus)
}
func setupMockCars() mocks.MockCars {
return mocks.MockCars{
SelectResponse: &common.Car{VIN: "FISKER123", ICCID: "1111111111111111111F"},
SelectCarSettings: []common.CarSetting{},
SelectCarFlashpackVersions: []common.CarFlashpackVersion{
// 46.14
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "46.14",
CarECUName: "ADAS",
CarECUVersion: "ADASVersion2",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "46.14",
CarECUName: "ACUN",
CarECUVersion: "ACUNVersionA",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "46.14",
CarECUName: "ACUN",
CarECUVersion: "ACUNVersionB",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "46.14",
CarECUName: "BCM",
CarECUVersion: "BCMVersion",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "46.14",
CarECUName: "PDI",
CarECUVersion: "PDIVersion",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "46.14",
CarECUName: "PDU",
CarECUVersion: "PDUVersion",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "46.14",
CarECUName: "TBOX",
CarECUVersion: "TBOXVersion",
},
// 44.14
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "44.14",
CarECUName: "ADAS",
CarECUVersion: "ADASVersion1",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "44.14",
CarECUName: "ACUN",
CarECUVersion: "ACUNVersionA",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "44.14",
CarECUName: "ACUN",
CarECUVersion: "ACUNVersionB",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "44.14",
CarECUName: "BCM",
CarECUVersion: "BCMVersion",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "44.14",
CarECUName: "PDI",
CarECUVersion: "PDIVersion",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "44.14",
CarECUName: "PDU",
CarECUVersion: "PDUVersion",
},
// 41.14
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "41.14",
CarECUName: "ADAS",
CarECUVersion: "ADASVersion",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "41.14",
CarECUName: "ACUN",
CarECUVersion: "ACUNVersion",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "41.14",
CarECUName: "BCM",
CarECUVersion: "BCMVersion",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "41.14",
CarECUName: "PDI",
CarECUVersion: "PDIVersion",
},
// 39.14
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "39.14",
CarECUName: "ADAS",
CarECUVersion: "ADASVersion0",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "39.14",
CarECUName: "ACUN",
CarECUVersion: "ACUNVersion0",
},
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "39.14",
CarECUName: "PDI",
CarECUVersion: "PDIVersion",
},
// 37.14
{
CarModel: "Ocean",
CarTrim: "Base",
CarYear: 2023,
Flashpack: "37.14",
CarECUName: "PDI",
CarECUVersion: "PDIVersion",
},
},
}
}