Finish up weight generation

This commit is contained in:
Dominic Ferrando
2025-09-19 00:39:40 -04:00
parent 5133379b9c
commit da21e4e988
6 changed files with 222 additions and 29 deletions
+97
View File
@@ -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
}
]
}
+17 -3
View File
@@ -4739,7 +4739,7 @@
"generators": [ "generators": [
{ {
"metric": "weight", "metric": "weight",
"spread": 4, "spread": 3,
"step": 12, "step": 12,
"ratio": "inverse" "ratio": "inverse"
} }
@@ -5229,7 +5229,14 @@
"BroadJump": { "BroadJump": {
"metadata": { "metadata": {
"attribute": "Power", "attribute": "Power",
"generators": [], "generators": [
{
"metric": "weight",
"spread": 3,
"step": 12,
"ratio": "inverse"
}
],
"unit": "cm", "unit": "cm",
"name": "Broad Jump" "name": "Broad Jump"
}, },
@@ -5267,7 +5274,14 @@
"ConeDrill": { "ConeDrill": {
"metadata": { "metadata": {
"attribute": "Agility", "attribute": "Agility",
"generators": [], "generators": [
{
"metric": "weight",
"spread": 3,
"step": 12,
"ratio": "inverse"
}
],
"unit": "ms", "unit": "ms",
"name": "3 Cone Drill" "name": "3 Cone Drill"
}, },
+33 -19
View File
@@ -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 // SOURCES
// Squat, Bench, Dead Lift: // Squat, Bench, Dead Lift:
@@ -216,19 +216,16 @@ export class Standards {
const weightGenerators = allGenerators.filter(g => g.metric === "weight"); const weightGenerators = allGenerators.filter(g => g.metric === "weight");
for (const weightGenerator of weightGenerators) { for (const weightGenerator of weightGenerators) {
for (const gender of Object.values(Gender)) { for (const gender of Object.values(Gender)) {
const standardsByGender = this.byActivity(activity).byGender(gender).getAll(); for (const age of this.agesFor(activity, gender)) {
const ages = [...new Set(standardsByGender.map(s => s.metrics.age))]; const referenceWeight = getAvgWeight(gender, age);
for (const age of ages) { const skewRatio = 0.4; // 0 = normal, 1 = max skew
const referenceWeight = gender === Gender.Male ? const minWeight = referenceWeight - (weightGenerator.step * weightGenerator.spread * (1 - skewRatio));
lbToKg(170) : const maxWeight = referenceWeight + (weightGenerator.step * weightGenerator.spread * (1 + skewRatio));
lbToKg(137);
const minWeight = referenceWeight - (weightGenerator.step * weightGenerator.spread);
const maxWeight = referenceWeight + (weightGenerator.step * weightGenerator.spread);
const referenceStandard = this const referenceStandard = this
.byActivity(activity) .byActivity(activity)
.byMetrics({ weight: referenceWeight, gender: gender, age: age }) .byMetrics({ weight: referenceWeight, gender, age })
.getOneInterpolated(); .getOneInterpolated();
let currWeight = minWeight; let currWeight = minWeight;
@@ -236,8 +233,8 @@ export class Standards {
const newStandard: Standard = { const newStandard: Standard = {
metrics: { metrics: {
weight: currWeight, weight: currWeight,
age: age, age,
gender: gender gender
}, },
levels: {} levels: {}
}; };
@@ -340,6 +337,8 @@ export class Standards {
let weightRatio = weightUpper === weightLower ? 1 : (metrics.weight - weightLower) / (weightUpper - weightLower); let weightRatio = weightUpper === weightLower ? 1 : (metrics.weight - weightLower) / (weightUpper - weightLower);
weightRatio = Math.max(0, Math.min(1, weightRatio)); 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); return this.interpolateLevels(lowerAgeWeightStandard.levels, upperAgeWeightStandard.levels, weightRatio);
} }
@@ -371,13 +370,8 @@ export class Standards {
const getAllInterpolated = (): Standard[] => { const getAllInterpolated = (): Standard[] => {
const metrics: Metrics = state as Metrics; const metrics: Metrics = state as Metrics;
const weights = [... new Set(this const interpolatedStandards: Standard[] = this.weightsFor(activity, metrics)
.byActivity(activity) .map(weight => interpolateByAgeAndWeight({ ...metrics, weight }));
.byGender(metrics.gender)
.getAll().map((s) => s.metrics.weight))];
const interpolatedStandards: Standard[] = weights
.map((w) => interpolateByAgeAndWeight({ gender: metrics.gender, age: metrics.age, weight: w }));
return interpolatedStandards; return interpolatedStandards;
}; };
@@ -426,6 +420,26 @@ export class Standards {
return activities; 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( private expandLevels(
levels: Levels, levels: Levels,
i: number i: number
+65 -2
View File
@@ -1,4 +1,5 @@
import { type Metrics } from "$lib/services/calculator/main"; import { type Metrics } from "$lib/services/calculator/main";
import avgWeightDataRaw from "$lib/data/avg-weights.json"
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
// DATA MODELS // DATA MODELS
@@ -47,8 +48,9 @@ export const minToMs = (min: number) => min * 60000;
export const secToMs = (sec: number) => sec * 1000; export const secToMs = (sec: number) => sec * 1000;
export const msToMin = (ms: number) => ms / 60000; export const msToMin = (ms: number) => ms / 60000;
export const feetToCm = (feet: number) => feet * 30.48; export const ftToCm = (ft: number) => ft * 30.48;
export const inchesToCm = (inches: number) => inches * 2.54; export const inToCm = (inches: number) => inches * 2.54;
export const cmToIn = (cm: number) => cm / 2.54;
export const msToTime = (ms: number, includeMs = false): string => { export const msToTime = (ms: number, includeMs = false): string => {
const minutes = Math.floor(ms / 60000); 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 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;
}
+5 -2
View File
@@ -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 = { const player: Player = {
metrics: { 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 = { // const newStandards: Standards = {
// "Back Squat": [], // "Back Squat": [],
+5 -3
View File
@@ -7,6 +7,7 @@
import allStandardsRaw from "$lib/data/standards.json" assert { type: "json" }; import allStandardsRaw from "$lib/data/standards.json" assert { type: "json" };
import { import {
Activity, Activity,
cmToIn,
Gender, Gender,
kgToLb, kgToLb,
lbToKg, lbToKg,
@@ -37,9 +38,10 @@
const unit = allStandards.byActivity(activity).getMetadata().unit; const unit = allStandards.byActivity(activity).getMetadata().unit;
switch (unit) { switch (unit) {
case "ms": case "ms":
return msToTime(rawValue); return msToTime(rawValue, activity === Activity.ConeDrill);
case "cm": case "cm": {
return String(Math.round(rawValue)); return String(Math.round(cmToIn(rawValue) * 10) / 10);
}
case "kg": case "kg":
return String(Math.round(kgToLb(rawValue))); return String(Math.round(kgToLb(rawValue)));
default: default: