From da21e4e988a80d78dd6cee49d63a0d0bf685c242 Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Fri, 19 Sep 2025 00:39:40 -0400 Subject: [PATCH] Finish up weight generation --- src/lib/data/avg-weights.json | 97 +++++++++++++++++++++++++++++ src/lib/data/standards.json | 20 +++++- src/lib/services/calculator/main.ts | 52 ++++++++++------ src/lib/services/calculator/util.ts | 67 +++++++++++++++++++- src/lib/test/test-calculator.ts | 7 ++- src/routes/+page.svelte | 8 ++- 6 files changed, 222 insertions(+), 29 deletions(-) create mode 100644 src/lib/data/avg-weights.json diff --git a/src/lib/data/avg-weights.json b/src/lib/data/avg-weights.json new file mode 100644 index 0000000..0795ebb --- /dev/null +++ b/src/lib/data/avg-weights.json @@ -0,0 +1,97 @@ +{ + "metadata": { + "unit": "kg" + }, + "weights": [ + { + "gender": "Male", + "age": 10, + "weight": 38.7 + }, + { + "gender": "Male", + "age": 15, + "weight": 66.1 + }, + { + "gender": "Male", + "age": 20, + "weight": 81.3 + }, + { + "gender": "Male", + "age": 30, + "weight": 89.7 + }, + { + "gender": "Male", + "age": 40, + "weight": 90.5 + }, + { + "gender": "Male", + "age": 50, + "weight": 89.6 + }, + { + "gender": "Male", + "age": 60, + "weight": 89.5 + }, + { + "gender": "Male", + "age": 70, + "weight": 89.3 + }, + { + "gender": "Male", + "age": 80, + "weight": 79.6 + }, + { + "gender": "Female", + "age": 10, + "weight": 39.5 + }, + { + "gender": "Female", + "age": 15, + "weight": 58.1 + }, + { + "gender": "Female", + "age": 20, + "weight": 69.5 + }, + { + "gender": "Female", + "age": 30, + "weight": 73.3 + }, + { + "gender": "Female", + "age": 40, + "weight": 75.2 + }, + { + "gender": "Female", + "age": 50, + "weight": 74.6 + }, + { + "gender": "Female", + "age": 60, + "weight": 75.1 + }, + { + "gender": "Female", + "age": 70, + "weight": 73.2 + }, + { + "gender": "Female", + "age": 80, + "weight": 66.3 + } + ] +} diff --git a/src/lib/data/standards.json b/src/lib/data/standards.json index af8a1f2..97a6157 100644 --- a/src/lib/data/standards.json +++ b/src/lib/data/standards.json @@ -4739,7 +4739,7 @@ "generators": [ { "metric": "weight", - "spread": 4, + "spread": 3, "step": 12, "ratio": "inverse" } @@ -5229,7 +5229,14 @@ "BroadJump": { "metadata": { "attribute": "Power", - "generators": [], + "generators": [ + { + "metric": "weight", + "spread": 3, + "step": 12, + "ratio": "inverse" + } + ], "unit": "cm", "name": "Broad Jump" }, @@ -5267,7 +5274,14 @@ "ConeDrill": { "metadata": { "attribute": "Agility", - "generators": [], + "generators": [ + { + "metric": "weight", + "spread": 3, + "step": 12, + "ratio": "inverse" + } + ], "unit": "ms", "name": "3 Cone Drill" }, diff --git a/src/lib/services/calculator/main.ts b/src/lib/services/calculator/main.ts index d2d5bd2..685b0ed 100644 --- a/src/lib/services/calculator/main.ts +++ b/src/lib/services/calculator/main.ts @@ -1,4 +1,4 @@ -import { Activity, Attribute, Gender, lbToKg, type ActivityPerformance, type Player, type StandardUnit } from "./util"; +import { Activity, Attribute, Gender, getAvgWeight, lbToKg, type ActivityPerformance, type Player, type StandardUnit } from "./util"; // SOURCES // Squat, Bench, Dead Lift: @@ -216,19 +216,16 @@ export class Standards { const weightGenerators = allGenerators.filter(g => g.metric === "weight"); for (const weightGenerator of weightGenerators) { for (const gender of Object.values(Gender)) { - const standardsByGender = this.byActivity(activity).byGender(gender).getAll(); - const ages = [...new Set(standardsByGender.map(s => s.metrics.age))]; + for (const age of this.agesFor(activity, gender)) { + const referenceWeight = getAvgWeight(gender, age); - for (const age of ages) { - const referenceWeight = gender === Gender.Male ? - lbToKg(170) : - lbToKg(137); - const minWeight = referenceWeight - (weightGenerator.step * weightGenerator.spread); - const maxWeight = referenceWeight + (weightGenerator.step * weightGenerator.spread); + const skewRatio = 0.4; // 0 = normal, 1 = max skew + const minWeight = referenceWeight - (weightGenerator.step * weightGenerator.spread * (1 - skewRatio)); + const maxWeight = referenceWeight + (weightGenerator.step * weightGenerator.spread * (1 + skewRatio)); const referenceStandard = this .byActivity(activity) - .byMetrics({ weight: referenceWeight, gender: gender, age: age }) + .byMetrics({ weight: referenceWeight, gender, age }) .getOneInterpolated(); let currWeight = minWeight; @@ -236,8 +233,8 @@ export class Standards { const newStandard: Standard = { metrics: { weight: currWeight, - age: age, - gender: gender + age, + gender }, levels: {} }; @@ -340,6 +337,8 @@ export class Standards { let weightRatio = weightUpper === weightLower ? 1 : (metrics.weight - weightLower) / (weightUpper - weightLower); weightRatio = Math.max(0, Math.min(1, weightRatio)); + // metrics.weight = lowerAgeWeightStandard.metrics.weight + (upperAgeWeightStandard.metrics.weight - lowerAgeWeightStandard.metrics.weight) * weightRatio; + return this.interpolateLevels(lowerAgeWeightStandard.levels, upperAgeWeightStandard.levels, weightRatio); } @@ -371,13 +370,8 @@ export class Standards { const getAllInterpolated = (): Standard[] => { const metrics: Metrics = state as Metrics; - const weights = [... new Set(this - .byActivity(activity) - .byGender(metrics.gender) - .getAll().map((s) => s.metrics.weight))]; - - const interpolatedStandards: Standard[] = weights - .map((w) => interpolateByAgeAndWeight({ gender: metrics.gender, age: metrics.age, weight: w })); + const interpolatedStandards: Standard[] = this.weightsFor(activity, metrics) + .map(weight => interpolateByAgeAndWeight({ ...metrics, weight })); return interpolatedStandards; }; @@ -426,6 +420,26 @@ export class Standards { return activities; } + public weightsFor(activity: Activity, metrics: Metrics) { + const { lower } = this + .byActivity(activity) + .byGender(metrics.gender) + .getNearest("age", metrics.age); + + const weights = this + .byActivity(activity) + .byGender(metrics.gender) + .byAge(lower.metrics.age) + .getAll().map((s) => s.metrics.weight); + return weights; + } + + public agesFor(activity: Activity, gender: Gender) { + const standardsByGender = this.byActivity(activity).byGender(gender).getAll(); + const ages = [...new Set(standardsByGender.map(s => s.metrics.age))]; + return ages; + } + private expandLevels( levels: Levels, i: number diff --git a/src/lib/services/calculator/util.ts b/src/lib/services/calculator/util.ts index 83b18d4..c708f47 100644 --- a/src/lib/services/calculator/util.ts +++ b/src/lib/services/calculator/util.ts @@ -1,4 +1,5 @@ import { type Metrics } from "$lib/services/calculator/main"; +import avgWeightDataRaw from "$lib/data/avg-weights.json" // ------------------------------------------------------------------------------------------------- // DATA MODELS @@ -47,8 +48,9 @@ export const minToMs = (min: number) => min * 60000; export const secToMs = (sec: number) => sec * 1000; export const msToMin = (ms: number) => ms / 60000; -export const feetToCm = (feet: number) => feet * 30.48; -export const inchesToCm = (inches: number) => inches * 2.54; +export const ftToCm = (ft: number) => ft * 30.48; +export const inToCm = (inches: number) => inches * 2.54; +export const cmToIn = (cm: number) => cm / 2.54; export const msToTime = (ms: number, includeMs = false): string => { const minutes = Math.floor(ms / 60000); @@ -64,4 +66,65 @@ export const msToTime = (ms: number, includeMs = false): string => { export const range = (length: number) => Array.from({ length: length }, (_, i) => i + 1); +// export const getAvgWeight = (gender: Gender, age: number) => { +// type AvgWeightData = { +// metadata: { +// unit: StandardUnit +// }, +// weights: Metrics[] +// } +// let avgWeightData = avgWeightDataRaw as AvgWeightData; +// +// const avgWeights = avgWeightData.weights.filter(a => a.gender === gender); +// +// let lower = avgWeights[0]; // best <= target +// let upper = avgWeights[0]; // best >= target +// let hasLower = false, hasUpper = false; +// +// for (const avg of avgWeights) { +// if (avg.age <= age) { +// if (!hasLower || avg.age > lower.age) { lower = avg; hasLower = true; } +// } +// if (avg.age >= age) { +// if (!hasUpper || avg.age < upper.age) { upper = avg; hasUpper = true; } +// } +// } +// +// // clamp to ends if one side missing +// if (!hasLower) lower = avgWeights[0]; +// if (!hasUpper) upper = avgWeights[avgWeights.length - 1]; +// +// // lerp +// let ageRatio = upper === lower ? 1 : (age - lower.age) / (upper.age - lower.age); +// ageRatio = Math.max(0, Math.min(1, ageRatio)); +// const interpolatedAvg = lower.weight + (upper.weight - lower.weight) * ageRatio; +// +// return interpolatedAvg; +// } +export const getAvgWeight = (gender: Gender, age: number) => { + type AvgWeightData = { + metadata: { + unit: StandardUnit + }, + weights: Metrics[] + } + let avgWeightData = avgWeightDataRaw as AvgWeightData; + + const avgWeights = avgWeightData.weights.filter(a => a.gender === gender); + if (!avgWeights.length) throw new Error("No average weights"); + + const closest = { + diff: Math.abs(avgWeights[0].age - age), + avg: avgWeights[0] + }; + for (const avg of avgWeights) { + const diff = Math.abs(avg.age - age); + if (diff < closest.diff) { + closest.diff = diff; + closest.avg = avg; + } + } + + return closest.avg.weight; +} diff --git a/src/lib/test/test-calculator.ts b/src/lib/test/test-calculator.ts index c506c7e..6249ec0 100644 --- a/src/lib/test/test-calculator.ts +++ b/src/lib/test/test-calculator.ts @@ -1,4 +1,4 @@ -import { Activity, feetToCm, Gender, inchesToCm, lbToKg, minToMs, secToMs, type ActivityPerformance, type Player } from "$lib/services/calculator/util"; +import { Activity, feetToCm, Gender, getAvgWeight, inchesToCm, kgToLb, lbToKg, minToMs, secToMs, type ActivityPerformance, type Player } from "$lib/services/calculator/util"; const player: Player = { metrics: { @@ -39,7 +39,10 @@ const computedPerformances: ActivityPerformance[] = [ }, ]; -const standards = new Stand(); +for (let age = 15; age < 80; ++age) { + const avgWeightKG = getAvgWeight(Gender.Female, age); + console.log(`${age}: ${kgToLb(avgWeightKG).toFixed(1)}`); +} // const newStandards: Standards = { // "Back Squat": [], diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d47b7cf..8db6b11 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -7,6 +7,7 @@ import allStandardsRaw from "$lib/data/standards.json" assert { type: "json" }; import { Activity, + cmToIn, Gender, kgToLb, lbToKg, @@ -37,9 +38,10 @@ const unit = allStandards.byActivity(activity).getMetadata().unit; switch (unit) { case "ms": - return msToTime(rawValue); - case "cm": - return String(Math.round(rawValue)); + return msToTime(rawValue, activity === Activity.ConeDrill); + case "cm": { + return String(Math.round(cmToIn(rawValue) * 10) / 10); + } case "kg": return String(Math.round(kgToLb(rawValue))); default: