Skip to content

Commit

Permalink
feat(backend): Use fixed-point WeightKg for weights. Closes euanwm#363
Browse files Browse the repository at this point in the history
This patch ports the OpenPowerlifting weight representation to Go.

The new WeightKg type represents numbers like `123.45` by storing them
in a fixed-point integer representation, like `12345`. The advantage of
this representation is that integer comparisons and arithmetic are
significantly faster than the corresponding floating-point operations,
because integers do not require loads into XMM registers.
  • Loading branch information
sstangl committed May 27, 2024
1 parent 8929313 commit aece7c8
Show file tree
Hide file tree
Showing 15 changed files with 505 additions and 174 deletions.
13 changes: 13 additions & 0 deletions backend/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Backend Makefile.

.DEFAULT_GOAL := run

# Compile and launch the backend server in local development mode.
.PHONY: run
run:
go run backend local

# Execute backend unit tests.
.PHONY: test
test:
go test ./...
44 changes: 30 additions & 14 deletions backend/dbtools/dbtools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ func TestFilter(t *testing.T) {
{
name: "FilterByFederation",
args: args{
bigData: []structs.Entry{{Date: "2023-06-01", Name: "John Smith", Total: 100, Federation: "BWL", Gender: enum.Male, Bodyweight: 109.00}, {Date: "2023-06-01", Name: "Dave Smith", Total: 200, Federation: "BWL", Gender: enum.Male, Bodyweight: 109.00}, {Date: "2023-06-01", Name: "Ethan Smith", Total: 300, Federation: "BWL", Gender: enum.Male, Bodyweight: 109.00}},
bigData: []structs.Entry{
{Date: "2023-06-01", Name: "John Smith", Total: structs.NewWeightKg(100), Federation: "BWL", Gender: enum.Male, Bodyweight: structs.NewWeightKg(109.00)},
{Date: "2023-06-01", Name: "Dave Smith", Total: structs.NewWeightKg(200), Federation: "BWL", Gender: enum.Male, Bodyweight: structs.NewWeightKg(109.00)},
{Date: "2023-06-01", Name: "Ethan Smith", Total: structs.NewWeightKg(300), Federation: "BWL", Gender: enum.Male, Bodyweight: structs.NewWeightKg(109.00)},
},
filterQuery: structs.LeaderboardPayload{
Start: 0,
Stop: 10,
Expand All @@ -72,7 +76,11 @@ func TestFilter(t *testing.T) {
},
wantFilteredData: structs.LeaderboardResponse{
Size: 3,
Data: []structs.Entry{{Date: "2023-06-01", Name: "John Smith", Total: 100, Federation: "BWL", Gender: enum.Male, Bodyweight: 109.00}, {Date: "2023-06-01", Name: "Dave Smith", Total: 200, Federation: "BWL", Gender: enum.Male, Bodyweight: 109.00}, {Date: "2023-06-01", Name: "Ethan Smith", Total: 300, Federation: "BWL", Gender: enum.Male, Bodyweight: 109.00}},
Data: []structs.Entry{
{Date: "2023-06-01", Name: "John Smith", Total: structs.NewWeightKg(100), Federation: "BWL", Gender: enum.Male, Bodyweight: structs.NewWeightKg(109.00)},
{Date: "2023-06-01", Name: "Dave Smith", Total: structs.NewWeightKg(200), Federation: "BWL", Gender: enum.Male, Bodyweight: structs.NewWeightKg(109.00)},
{Date: "2023-06-01", Name: "Ethan Smith", Total: structs.NewWeightKg(300), Federation: "BWL", Gender: enum.Male, Bodyweight: structs.NewWeightKg(109.00)},
},
},
},
}
Expand Down Expand Up @@ -144,7 +152,7 @@ func TestSortLiftsBy(t *testing.T) {
wantFinalData []structs.Entry
}{
{name: "SortBySinclair", args: args{bigData: []structs.Entry{{Sinclair: 300}, {Sinclair: 100}, {Sinclair: 200}}, sortBy: enum.Sinclair}, wantFinalData: []structs.Entry{{Sinclair: 300}, {Sinclair: 200}, {Sinclair: 100}}},
{name: "SortByTotal", args: args{bigData: []structs.Entry{{Total: 300}, {Total: 100}, {Total: 200}}, sortBy: enum.Total}, wantFinalData: []structs.Entry{{Total: 300}, {Total: 200}, {Total: 100}}},
{name: "SortByTotal", args: args{bigData: []structs.Entry{{Total: structs.NewWeightKg(300)}, {Total: structs.NewWeightKg(100)}, {Total: structs.NewWeightKg(200)}}, sortBy: enum.Total}, wantFinalData: []structs.Entry{{Total: structs.NewWeightKg(300)}, {Total: structs.NewWeightKg(200)}, {Total: structs.NewWeightKg(100)}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -182,7 +190,15 @@ func TestSortTotal(t *testing.T) {
name string
args args
}{
{name: "NormalSort", args: args{sliceStructs: []structs.Entry{{Total: 300}, {Total: 100}, {Total: 200}}, wantedSlice: []structs.Entry{{Total: 100}, {Total: 200}, {Total: 300}}}},
{name: "NormalSort", args: args{sliceStructs: []structs.Entry{
{Total: structs.NewWeightKg(300)},
{Total: structs.NewWeightKg(100)},
{Total: structs.NewWeightKg(200)},
}, wantedSlice: []structs.Entry{
{Total: structs.NewWeightKg(100)},
{Total: structs.NewWeightKg(200)},
{Total: structs.NewWeightKg(300)},
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -208,16 +224,16 @@ func Test_assignStruct(t *testing.T) {
Date: "2017-10-01",
Gender: "Men's Under 23 94Kg",
Name: "Edmon avetisyan",
Bodyweight: 93.8,
Sn1: -146,
Sn2: 150,
Sn3: -156,
CJ1: 180,
CJ2: -190,
CJ3: -192,
BestSn: 150,
BestCJ: 180,
Total: 330,
Bodyweight: structs.NewWeightKg(93.8),
Sn1: structs.NewWeightKg(-146),
Sn2: structs.NewWeightKg(150),
Sn3: structs.NewWeightKg(-156),
CJ1: structs.NewWeightKg(180),
CJ2: structs.NewWeightKg(-190),
CJ3: structs.NewWeightKg(-192),
BestSn: structs.NewWeightKg(150),
BestCJ: structs.NewWeightKg(180),
Total: structs.NewWeightKg(330),
Sinclair: 0,
Federation: "BWL",
Instagram: "",
Expand Down
2 changes: 1 addition & 1 deletion backend/dbtools/sortby.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func SortSinclair(sliceStructs []structs.Entry) {
// SortTotal Descending order by entry total
func SortTotal(sliceStructs []structs.Entry) {
sort.Slice(sliceStructs, func(i, j int) bool {
return sliceStructs[i].Total > sliceStructs[j].Total
return sliceStructs[i].Total.GreaterThan(sliceStructs[j].Total)
})
}

Expand Down
62 changes: 35 additions & 27 deletions backend/dbtools/sortgender.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,40 @@ import (
"backend/enum"
"backend/sinclair"
"backend/structs"
"backend/utilities"
"log"
"strings"
)

// ParseData Splits results into 3 categories, male, female, and unknown.
func ParseData(bigData [][]string) (allLifts structs.AllData, unknown structs.AllData) {
max_total := structs.NewWeightKg(float64(enum.MaxTotal))
min_bodyweight := structs.NewWeightKg(float64(enum.MinimumBodyweight))

for _, contents := range bigData {
dataStruct, valid := assignStruct(contents)
if valid {
gender := getGender(&dataStruct)
switch gender {
case enum.Male:
if dataStruct.Total > 0 && dataStruct.Total < enum.MaxTotal && dataStruct.Bodyweight > enum.MinimumBodyweight {
// todo: add in error handling for CalcSinclair
sinclair.CalcSinclair(&dataStruct, true)
}
allLifts.Lifts = append(allLifts.Lifts, dataStruct)
case enum.Female:
if dataStruct.Total > 0 && dataStruct.Total < enum.MaxTotal && dataStruct.Bodyweight > enum.MinimumBodyweight {
sinclair.CalcSinclair(&dataStruct, false)
}
allLifts.Lifts = append(allLifts.Lifts, dataStruct)
case enum.Unknown:
unknown.Lifts = append(unknown.Lifts, dataStruct)
if !valid {
continue
}

gender := getGender(&dataStruct)
switch gender {
case enum.Male:
if dataStruct.Total.IsPositive() &&
dataStruct.Total.LessThan(max_total) &&
dataStruct.Bodyweight.GreaterThan(min_bodyweight) {
// todo: add in error handling for CalcSinclair
sinclair.CalcSinclair(&dataStruct, true)
}
allLifts.Lifts = append(allLifts.Lifts, dataStruct)
case enum.Female:
if dataStruct.Total.IsPositive() &&
dataStruct.Total.LessThan(max_total) &&
dataStruct.Bodyweight.GreaterThan(min_bodyweight) {
sinclair.CalcSinclair(&dataStruct, false)
}
allLifts.Lifts = append(allLifts.Lifts, dataStruct)
case enum.Unknown:
unknown.Lifts = append(unknown.Lifts, dataStruct)
}
}
return
Expand Down Expand Up @@ -60,16 +68,16 @@ func assignStruct(line []string) (lineStruct structs.Entry, valid bool) {
Date: line[1],
Gender: line[2],
Name: line[3],
Bodyweight: utilities.Float(line[4]),
Sn1: utilities.Float(line[5]),
Sn2: utilities.Float(line[6]),
Sn3: utilities.Float(line[7]),
CJ1: utilities.Float(line[8]),
CJ2: utilities.Float(line[9]),
CJ3: utilities.Float(line[10]),
BestSn: utilities.Float(line[11]),
BestCJ: utilities.Float(line[12]),
Total: utilities.Float(line[13]),
Bodyweight: structs.NewWeightKgFromString(line[4]),
Sn1: structs.NewWeightKgFromString(line[5]),
Sn2: structs.NewWeightKgFromString(line[6]),
Sn3: structs.NewWeightKgFromString(line[7]),
CJ1: structs.NewWeightKgFromString(line[8]),
CJ2: structs.NewWeightKgFromString(line[9]),
CJ3: structs.NewWeightKgFromString(line[10]),
BestSn: structs.NewWeightKgFromString(line[11]),
BestCJ: structs.NewWeightKgFromString(line[12]),
Total: structs.NewWeightKgFromString(line[13]),
Sinclair: 0.0,
Federation: line[14],
}
Expand Down
44 changes: 22 additions & 22 deletions backend/dbtools/weightcats.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@ import (
)

var WeightClassList = map[string]structs.WeightClass{
"MALL": {Gender: enum.Male, Upper: enum.MaximumBodyweight, Lower: 0},
"M55": {Gender: enum.Male, Upper: 55.00, Lower: 0},
"M61": {Gender: enum.Male, Upper: 61.00, Lower: 55.01},
"M67": {Gender: enum.Male, Upper: 67.00, Lower: 61.01},
"M73": {Gender: enum.Male, Upper: 73.00, Lower: 67.01},
"M81": {Gender: enum.Male, Upper: 81.00, Lower: 73.01},
"M89": {Gender: enum.Male, Upper: 89.00, Lower: 81.01},
"M96": {Gender: enum.Male, Upper: 96.00, Lower: 89.01},
"M102": {Gender: enum.Male, Upper: 102.00, Lower: 96.01},
"M109": {Gender: enum.Male, Upper: 109.00, Lower: 102.01},
"M109+": {Gender: enum.Male, Upper: enum.MaximumBodyweight, Lower: 109.01},
"FALL": {Gender: enum.Female, Upper: enum.MaximumBodyweight, Lower: 0},
"F45": {Gender: enum.Female, Upper: 45.00, Lower: 0},
"F49": {Gender: enum.Female, Upper: 49.00, Lower: 45.01},
"F55": {Gender: enum.Female, Upper: 55.00, Lower: 49.01},
"F59": {Gender: enum.Female, Upper: 59.00, Lower: 55.01},
"F64": {Gender: enum.Female, Upper: 64.00, Lower: 59.01},
"F71": {Gender: enum.Female, Upper: 71.00, Lower: 64.01},
"F76": {Gender: enum.Female, Upper: 76.00, Lower: 71.01},
"F81": {Gender: enum.Female, Upper: 81.00, Lower: 76.01},
"F87": {Gender: enum.Female, Upper: 87.00, Lower: 81.01},
"F87+": {Gender: enum.Female, Upper: enum.MaximumBodyweight, Lower: 87.01},
"MALL": {Gender: enum.Male, Upper: structs.NewWeightKg(float64(enum.MaximumBodyweight)), Lower: structs.NewWeightKg(0)},
"M55": {Gender: enum.Male, Upper: structs.NewWeightKg(55.00), Lower: structs.NewWeightKg(0)},
"M61": {Gender: enum.Male, Upper: structs.NewWeightKg(61.00), Lower: structs.NewWeightKg(55.01)},
"M67": {Gender: enum.Male, Upper: structs.NewWeightKg(67.00), Lower: structs.NewWeightKg(61.01)},
"M73": {Gender: enum.Male, Upper: structs.NewWeightKg(73.00), Lower: structs.NewWeightKg(67.01)},
"M81": {Gender: enum.Male, Upper: structs.NewWeightKg(81.00), Lower: structs.NewWeightKg(73.01)},
"M89": {Gender: enum.Male, Upper: structs.NewWeightKg(89.00), Lower: structs.NewWeightKg(81.01)},
"M96": {Gender: enum.Male, Upper: structs.NewWeightKg(96.00), Lower: structs.NewWeightKg(89.01)},
"M102": {Gender: enum.Male, Upper: structs.NewWeightKg(102.00), Lower: structs.NewWeightKg(96.01)},
"M109": {Gender: enum.Male, Upper: structs.NewWeightKg(109.00), Lower: structs.NewWeightKg(102.01)},
"M109+": {Gender: enum.Male, Upper: structs.NewWeightKg(float64(enum.MaximumBodyweight)), Lower: structs.NewWeightKg(109.01)},
"FALL": {Gender: enum.Female, Upper: structs.NewWeightKg(float64(enum.MaximumBodyweight)), Lower: structs.NewWeightKg(0)},
"F45": {Gender: enum.Female, Upper: structs.NewWeightKg(45.00), Lower: structs.NewWeightKg(0)},
"F49": {Gender: enum.Female, Upper: structs.NewWeightKg(49.00), Lower: structs.NewWeightKg(45.01)},
"F55": {Gender: enum.Female, Upper: structs.NewWeightKg(55.00), Lower: structs.NewWeightKg(49.01)},
"F59": {Gender: enum.Female, Upper: structs.NewWeightKg(59.00), Lower: structs.NewWeightKg(55.01)},
"F64": {Gender: enum.Female, Upper: structs.NewWeightKg(64.00), Lower: structs.NewWeightKg(59.01)},
"F71": {Gender: enum.Female, Upper: structs.NewWeightKg(71.00), Lower: structs.NewWeightKg(64.01)},
"F76": {Gender: enum.Female, Upper: structs.NewWeightKg(76.00), Lower: structs.NewWeightKg(71.01)},
"F81": {Gender: enum.Female, Upper: structs.NewWeightKg(81.00), Lower: structs.NewWeightKg(76.01)},
"F87": {Gender: enum.Female, Upper: structs.NewWeightKg(87.00), Lower: structs.NewWeightKg(81.01)},
"F87+": {Gender: enum.Female, Upper: structs.NewWeightKg(float64(enum.MaximumBodyweight)), Lower: structs.NewWeightKg(87.01)},
}
14 changes: 7 additions & 7 deletions backend/lifter/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import (
// todo: add more details to allow more strict testing
var sampleLeaderboardData = &structs.LeaderboardData{
AllTotals: []structs.Entry{
{Name: "John Smith", Total: 123},
{Name: "john smith", Total: 234},
{Name: "John smoth", Total: 345},
{Name: "Joanne Smith", Total: 123},
{Name: "joanne smith", Total: 234},
{Name: "joanne smith", Total: 235},
{Name: "joanne Smoth", Total: 345},
{Name: "John Smith", Total: structs.NewWeightKg(123)},
{Name: "john smith", Total: structs.NewWeightKg(234)},
{Name: "John smoth", Total: structs.NewWeightKg(345)},
{Name: "Joanne Smith", Total: structs.NewWeightKg(123)},
{Name: "joanne smith", Total: structs.NewWeightKg(234)},
{Name: "joanne smith", Total: structs.NewWeightKg(235)},
{Name: "joanne Smoth", Total: structs.NewWeightKg(345)},
},
}

Expand Down
38 changes: 26 additions & 12 deletions backend/sinclair/sinclair.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,39 @@ const (
// the Masters coefficient is absolute nonsense. You'll see there's a lot of switching between float types.
// It's frustrating but it serves a purpose.
func CalcSinclair(result *structs.Entry, male bool) {
// Fast path: a zero or negative total has a zero Sinclair.
if !result.Total.IsPositive() {
result.Sinclair = 0
return
}

// A bodyweight below the cutoff also receives a zero score.
if result.Bodyweight.LessThanOrEqual(structs.NewWeightKgFromInt32(minBW)) {
result.Sinclair = 0
return
}

var coEffA = aMale
var coEffB = bMale
if !male {
coEffA = aFemale
coEffB = bFemale
}

total := result.Total.Float64()
bodyweight := result.Bodyweight.Float64()

// todo: add in error handling
if result.Total != 0 && result.Bodyweight > minBW {
if float64(result.Bodyweight) <= coEffB {
var X = math.Log10(float64(result.Bodyweight) / coEffB)
var expX = math.Pow(X, 2)
var coEffExp = coEffA * expX
var expSum = math.Pow(10, coEffExp)
var sinclair = float32(float64(result.Total) * expSum)
if sinclair <= naimSinclair {
result.Sinclair = sinclair
}
} else if result.Total <= naimSinclair {
result.Sinclair = result.Total
if bodyweight <= coEffB {
var X = math.Log10(bodyweight / coEffB)
var expX = math.Pow(X, 2)
var coEffExp = coEffA * expX
var expSum = math.Pow(10, coEffExp)
var sinclair = float32(total * expSum)
if sinclair <= naimSinclair {
result.Sinclair = sinclair
}
} else if total <= naimSinclair {
result.Sinclair = float32(total)
}
}
12 changes: 6 additions & 6 deletions backend/sinclair/sinclair_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,32 @@ func TestCalcSinclair(t *testing.T) {
}{
{
name: "NormalSinclairMale",
args: args{result: &structs.Entry{Bodyweight: 81, Total: 235, Sinclair: 0}, male: true},
args: args{result: &structs.Entry{Bodyweight: structs.NewWeightKg(81), Total: structs.NewWeightKg(235), Sinclair: 0}, male: true},
expectedSinclair: 285.66986,
},
{
name: "Over-rangeSinclairMale",
args: args{result: &structs.Entry{Bodyweight: 160, Total: 510, Sinclair: 0}, male: true},
args: args{result: &structs.Entry{Bodyweight: structs.NewWeightKg(160), Total: structs.NewWeightKg(510), Sinclair: 0}, male: true},
expectedSinclair: 0,
},
{
name: "NormalSinclairFemale",
args: args{result: &structs.Entry{Bodyweight: 81, Total: 235, Sinclair: 0}, male: false},
args: args{result: &structs.Entry{Bodyweight: structs.NewWeightKg(81), Total: structs.NewWeightKg(235), Sinclair: 0}, male: false},
expectedSinclair: 270.17587,
},
{
name: "Over-rangeSinclairFemale",
args: args{result: &structs.Entry{Bodyweight: 160, Total: 510, Sinclair: 0}, male: false},
args: args{result: &structs.Entry{Bodyweight: structs.NewWeightKg(160), Total: structs.NewWeightKg(510), Sinclair: 0}, male: false},
expectedSinclair: 0,
},
{
name: "SuperHeavySinclairMale",
args: args{result: &structs.Entry{Bodyweight: 200, Total: 400, Sinclair: 0}, male: true},
args: args{result: &structs.Entry{Bodyweight: structs.NewWeightKg(200), Total: structs.NewWeightKg(400), Sinclair: 0}, male: true},
expectedSinclair: 400,
},
{
name: "SuperHeavySinclairFemale",
args: args{result: &structs.Entry{Bodyweight: 200, Total: 400, Sinclair: 0}, male: false},
args: args{result: &structs.Entry{Bodyweight: structs.NewWeightKg(200), Total: structs.NewWeightKg(400), Sinclair: 0}, male: false},
expectedSinclair: 400,
},
}
Expand Down
8 changes: 4 additions & 4 deletions backend/structs/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ func IterateFloatSlice(data []Entry, item string) (floatSl []float32) {
switch item {
case enum.Total:
for _, lift := range data {
floatSl = append(floatSl, lift.Total)
floatSl = append(floatSl, lift.Total.Float32())
}
case enum.BestSnatch:
for _, lift := range data {
floatSl = append(floatSl, lift.BestSn)
floatSl = append(floatSl, lift.BestSn.Float32())
}
case enum.BestCJ:
for _, lift := range data {
floatSl = append(floatSl, lift.BestCJ)
floatSl = append(floatSl, lift.BestCJ.Float32())
}
case enum.Bodyweight:
for _, lift := range data {
floatSl = append(floatSl, lift.Bodyweight)
floatSl = append(floatSl, lift.Bodyweight.Float32())
}
}
return
Expand Down
10 changes: 5 additions & 5 deletions backend/structs/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ func TestIterateFloatSlice(t *testing.T) {
args args
wantFloatSl []float32
}{
{name: "IterateTotals", args: args{data: []Entry{{Total: 1}, {Total: 2}, {Total: 3}}, item: enum.Total}, wantFloatSl: []float32{1, 2, 3}},
{name: "IterateBestSnatch", args: args{data: []Entry{{BestSn: 1}, {BestSn: 2}, {BestSn: 3}}, item: enum.BestSnatch}, wantFloatSl: []float32{1, 2, 3}},
{name: "IterateBestCJ", args: args{data: []Entry{{BestCJ: 1}, {BestCJ: 2}, {BestCJ: 3}}, item: enum.BestCJ}, wantFloatSl: []float32{1, 2, 3}},
{name: "IterateBodyweight", args: args{data: []Entry{{Bodyweight: 1}, {Bodyweight: 2}, {Bodyweight: 3}}, item: enum.Bodyweight}, wantFloatSl: []float32{1, 2, 3}},
{name: "IterateNothing", args: args{data: []Entry{{Bodyweight: 1}, {Bodyweight: 2}, {Bodyweight: 3}}, item: ""}, wantFloatSl: nil},
{name: "IterateTotals", args: args{data: []Entry{{Total: NewWeightKg(1)}, {Total: NewWeightKg(2)}, {Total: NewWeightKg(3)}}, item: enum.Total}, wantFloatSl: []float32{1, 2, 3}},
{name: "IterateBestSnatch", args: args{data: []Entry{{BestSn: NewWeightKg(1)}, {BestSn: NewWeightKg(2)}, {BestSn: NewWeightKg(3)}}, item: enum.BestSnatch}, wantFloatSl: []float32{1, 2, 3}},
{name: "IterateBestCJ", args: args{data: []Entry{{BestCJ: NewWeightKg(1)}, {BestCJ: NewWeightKg(2)}, {BestCJ: NewWeightKg(3)}}, item: enum.BestCJ}, wantFloatSl: []float32{1, 2, 3}},
{name: "IterateBodyweight", args: args{data: []Entry{{Bodyweight: NewWeightKg(1)}, {Bodyweight: NewWeightKg(2)}, {Bodyweight: NewWeightKg(3)}}, item: enum.Bodyweight}, wantFloatSl: []float32{1, 2, 3}},
{name: "IterateNothing", args: args{data: []Entry{{Bodyweight: NewWeightKg(1)}, {Bodyweight: NewWeightKg(2)}, {Bodyweight: NewWeightKg(3)}}, item: ""}, wantFloatSl: nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading

0 comments on commit aece7c8

Please sign in to comment.