Refactoring calculator code
This commit is contained in:
+150
-187
@@ -1,7 +1,5 @@
|
|||||||
import { Activity, Attribute, attributes, BenchmarkPerformance, findNearestPoints, Gender, getActivityAttribute, getAttributeActivities, kgToLb, Levels, msToTime, Player, StandardsItem } from "./util";
|
import { Activity, Attribute, attributes, ActivityPerformance, findNearestPoints, getActivityAttribute, getAttributeActivities, Player, StandardsMap, BaseLevel, Standard } from "./util";
|
||||||
import rawStandards from "./standards.json" assert { type: "json" }
|
import rawStandards from "./newStandards.json" assert { type: "json" }
|
||||||
|
|
||||||
const standards = rawStandards as StandardsItem[];
|
|
||||||
|
|
||||||
// SOURCES
|
// SOURCES
|
||||||
// Squat, Bench, Dead Lift:
|
// Squat, Bench, Dead Lift:
|
||||||
@@ -13,136 +11,32 @@ const standards = rawStandards as StandardsItem[];
|
|||||||
// Broad Jump:
|
// Broad Jump:
|
||||||
//
|
//
|
||||||
|
|
||||||
|
const standards = rawStandards as StandardsMap;
|
||||||
|
|
||||||
|
type BaseLevelValues = Record<BaseLevel, number>
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
// Level calculations
|
// Level utilities (helpers)
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
export const calcBenchmarkLevel = (performance: number, levels: Record<number, number>): number => {
|
|
||||||
let playerLevel = 1;
|
function findLevel(levels: Record<number, number>, performance: number): number {
|
||||||
let playerLevelDiff = Infinity;
|
let resultLevel = 1;
|
||||||
|
let resultLevelDiff = Infinity;
|
||||||
|
|
||||||
for (const level in levels) {
|
for (const level in levels) {
|
||||||
const diff = Math.abs(performance - levels[level as unknown as number]);
|
const diff = Math.abs(performance - levels[level as unknown as number]);
|
||||||
if (diff < playerLevelDiff) {
|
if (diff < resultLevelDiff) {
|
||||||
playerLevel = +level;
|
resultLevel = +level;
|
||||||
playerLevelDiff = diff;
|
resultLevelDiff = diff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return playerLevel;
|
return resultLevel;
|
||||||
};
|
|
||||||
|
|
||||||
export const calcAttributeLevel = (
|
|
||||||
attribute: Attribute,
|
|
||||||
benchmarkPerformances: BenchmarkPerformance[],
|
|
||||||
): number => {
|
|
||||||
// must be activities of the given attribute
|
|
||||||
if (benchmarkPerformances.some((bp) => getActivityAttribute(bp.activity) !== attribute)) {
|
|
||||||
throw new Error("Wrong benchmark performance attribute");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// must perform all of an attributes activities
|
function compressLevels(
|
||||||
for (const activity of getAttributeActivities(attribute)) {
|
|
||||||
const benchmarkActivites = benchmarkPerformances.map((bp) => bp.activity);
|
|
||||||
if (!benchmarkActivites.includes(activity))
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// must have all benchmarks using a valid performance value (> 0)
|
|
||||||
for (const benchmarkPerformance of benchmarkPerformances) {
|
|
||||||
if (benchmarkPerformance.performance <= 0)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const benchmarkLevels = benchmarkPerformances.map((abp) =>
|
|
||||||
calcBenchmarkLevel(abp.performance, abp.levels),
|
|
||||||
);
|
|
||||||
|
|
||||||
const benchmarkLevelsAvg = Math.round(
|
|
||||||
benchmarkLevels.reduce((sum, curr) => sum + curr, 0) / benchmarkLevels.length,
|
|
||||||
);
|
|
||||||
|
|
||||||
return benchmarkLevelsAvg;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const calcAttributeLevels = (
|
|
||||||
player: Player,
|
|
||||||
benchmarkPerformances: BenchmarkPerformance[],
|
|
||||||
): Record<Attribute, number> => {
|
|
||||||
const attrLevels: Record<Attribute, number> = {
|
|
||||||
[attributes.STRENGTH]: 0,
|
|
||||||
[attributes.POWER]: 0,
|
|
||||||
[attributes.ENDURANCE]: 0,
|
|
||||||
// [attributes.SPEED]: 0,
|
|
||||||
[attributes.AGILITY]: 0,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
for (const value of Object.values(player)) {
|
|
||||||
if (!value)
|
|
||||||
return attrLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
(Object.values(attributes) as Attribute[]).forEach((attribute) => {
|
|
||||||
const attrBenchmarkPerformances = benchmarkPerformances.filter(
|
|
||||||
(bp) => getActivityAttribute(bp.activity) === attribute,
|
|
||||||
);
|
|
||||||
attrLevels[attribute] = calcAttributeLevel(attribute, attrBenchmarkPerformances);
|
|
||||||
});
|
|
||||||
|
|
||||||
return attrLevels;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const calcPlayerLevel = (player: Player, benchmarkPerformances: BenchmarkPerformance[]): number => {
|
|
||||||
for (const value of Object.values(player)) {
|
|
||||||
if (!value)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attrLevels = calcAttributeLevels(player, benchmarkPerformances);
|
|
||||||
for (const level of Object.values(attrLevels)) {
|
|
||||||
if (!level)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attrLevelsSum = Object.values(attrLevels).reduce((sum, lvl) => sum + lvl, 0);
|
|
||||||
return Math.round(attrLevelsSum / Object.keys(attributes).length);
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Presentation helpers
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// export const formatLevelValues = (
|
|
||||||
// levels: Record<number, number>,
|
|
||||||
// activity: Activity,
|
|
||||||
// ): Record<number, string> => {
|
|
||||||
// const timedActivities: Activity[] = [activities.RUN, activities.DASH, activities.CONE_DRILL];
|
|
||||||
//
|
|
||||||
// const formattedLevels: Record<number, string> = {};
|
|
||||||
// for (const level in levels) {
|
|
||||||
// const lvl = +level as number;
|
|
||||||
// let displayValue = "";
|
|
||||||
// if (timedActivities.includes(activity)) {
|
|
||||||
// displayValue = msToTime(levels[lvl]);
|
|
||||||
// } else if (activity === activities.TREADMILL_DASH) {
|
|
||||||
// displayValue = `${(levels[lvl] * 1000).toFixed(2)} m/s`;
|
|
||||||
// } else if (activity === activities.BROAD_JUMP) {
|
|
||||||
// displayValue = `${(levels[lvl] * 0.0328084).toFixed(2)} ft.`;
|
|
||||||
// } else {
|
|
||||||
// displayValue = kgToLb(levels[lvl]).toFixed(0);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// formattedLevels[lvl] = displayValue;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return formattedLevels;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Level utilities
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
const compressLevels = (
|
|
||||||
levels: Record<number, number>,
|
levels: Record<number, number>,
|
||||||
targetLevelsAmount: number,
|
targetLevelsAmount: number,
|
||||||
): Record<number, number> => {
|
): Record<number, number> {
|
||||||
const levelsAmount = Object.keys(levels).length;
|
const levelsAmount = Object.keys(levels).length;
|
||||||
if (levelsAmount < targetLevelsAmount) {
|
if (levelsAmount < targetLevelsAmount) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -166,12 +60,12 @@ const compressLevels = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return compressedLevels;
|
return compressedLevels;
|
||||||
};
|
}
|
||||||
|
|
||||||
const expandLevels = (
|
function expandLevels(
|
||||||
levels: Levels | Record<number, number>,
|
levels: BaseLevel | Record<number, number>,
|
||||||
i: number
|
i: number
|
||||||
): Record<number, number> => {
|
): Record<number, number> {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
// ensure we always use number keys
|
// ensure we always use number keys
|
||||||
const newLevels = {};
|
const newLevels = {};
|
||||||
@@ -198,15 +92,70 @@ const expandLevels = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
// Main level function
|
// Standards interpolation (heavy math bits)
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
export const computeLevels = (
|
|
||||||
age: number,
|
function interpolateStandardsValues(
|
||||||
weightKG: number,
|
lower: BaseLevelValues,
|
||||||
gender: Gender,
|
upper: BaseLevelValues,
|
||||||
|
ratio: number,
|
||||||
|
): BaseLevelValues {
|
||||||
|
function lerp(k: BaseLevel): number {
|
||||||
|
return (lower[k] as number) + ((upper[k] as number) - (lower[k] as number)) * ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
physicallyActive: lerp("physicallyActive"),
|
||||||
|
beginner: lerp("beginner"),
|
||||||
|
intermediate: lerp("intermediate"),
|
||||||
|
advanced: lerp("advanced"),
|
||||||
|
elite: lerp("elite"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function interpolateByBodyWeight(
|
||||||
|
targetAge: number,
|
||||||
|
bodyWeightKG: number,
|
||||||
|
standardsArr: Standard[],
|
||||||
|
): BaseLevelValues | null {
|
||||||
|
const ageFilteredStandards = standardsArr.filter((s) => s.metrics.age === targetAge);
|
||||||
|
if (ageFilteredStandards[0].metrics.weight === -1) return ageFilteredStandards[0].levels;
|
||||||
|
|
||||||
|
const weightArray = [...new Set(ageFilteredStandards.map((s) => s.metrics.weight))].sort((a, b) => a - b);
|
||||||
|
const { lower, upper } = findNearestPoints(bodyWeightKG, weightArray);
|
||||||
|
|
||||||
|
const weightLower = ageFilteredStandards.find((s) => s.metrics.weight === lower);
|
||||||
|
const weightUpper = ageFilteredStandards.find((s) => s.metrics.weight === upper);
|
||||||
|
if (!weightLower || !weightUpper) return null;
|
||||||
|
|
||||||
|
let weightRatio = upper === lower ? 1 : (bodyWeightKG - lower) / (upper - lower);
|
||||||
|
weightRatio = Math.max(0, Math.min(1, weightRatio));
|
||||||
|
|
||||||
|
return interpolateStandardsValues(weightLower.levels, weightUpper.levels, weightRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
function interpolateActivityLevels(
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
) => {
|
player: Player,
|
||||||
const levels = calculateLevels(age, weightKG, gender, activity, standards);
|
): Record<number, number> {
|
||||||
|
// Filter dataset
|
||||||
|
const filteredStandards = standards[activity]
|
||||||
|
.filter(a => a.metrics.gender === player.gender);
|
||||||
|
if (filteredStandards.length === 0) return {};
|
||||||
|
|
||||||
|
// Age interpolation
|
||||||
|
const ageArray = [...new Set(filteredStandards.map((s) => s.metrics.age))].sort((a, b) => a - b);
|
||||||
|
const { lower: ageLower, upper: ageUpper } = findNearestPoints(player.age, ageArray);
|
||||||
|
|
||||||
|
const lowerValues = interpolateByBodyWeight(ageLower, player.weightKG, filteredStandards);
|
||||||
|
const upperValues = interpolateByBodyWeight(ageUpper, player.weightKG, filteredStandards);
|
||||||
|
if (!lowerValues || !upperValues) return {};
|
||||||
|
|
||||||
|
let ageRatio = ageUpper === ageLower ? 1 : (player.age - ageLower) / (ageUpper - ageLower);
|
||||||
|
ageRatio = Math.max(0, Math.min(1, ageRatio));
|
||||||
|
|
||||||
|
const levels = interpolateStandardsValues(lowerValues, upperValues, ageRatio);
|
||||||
|
|
||||||
if (levels) {
|
if (levels) {
|
||||||
const expandedLevels = expandLevels(levels, 5);
|
const expandedLevels = expandLevels(levels, 5);
|
||||||
const compressedLevels = compressLevels(expandedLevels, 100);
|
const compressedLevels = compressLevels(expandedLevels, 100);
|
||||||
@@ -217,69 +166,83 @@ export const computeLevels = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
// Standards interpolation (heavy math bits)
|
// Level calculations (public API)
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
const calculateLevels = (
|
|
||||||
age: number,
|
export function calcAttributeLevel(
|
||||||
weightKG: number,
|
attribute: Attribute,
|
||||||
gender: Gender,
|
player: Player,
|
||||||
activity: Activity,
|
activityPerformances: ActivityPerformance[],
|
||||||
standards: StandardsItem[],
|
): number {
|
||||||
): Levels | null => {
|
// must be activities of the given attribute
|
||||||
// Filter dataset
|
if (activityPerformances.some((bp) => getActivityAttribute(bp.activity) !== attribute)) {
|
||||||
const filtered = standards.filter(
|
throw new Error("Wrong activity performance attribute");
|
||||||
(item) => item.gender === gender && item.activityType === activity,
|
}
|
||||||
|
|
||||||
|
// must perform all of an attributes activities
|
||||||
|
for (const activity of getAttributeActivities(attribute)) {
|
||||||
|
const filteredActivites = activityPerformances.map((bp) => bp.activity);
|
||||||
|
if (!filteredActivites.includes(activity))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// must have all activties using a valid performance value (> 0)
|
||||||
|
for (const activityPerformance of activityPerformances) {
|
||||||
|
if (activityPerformance.performance <= 0)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const activityLevels = activityPerformances.map((abp) =>
|
||||||
|
findLevel(interpolateActivityLevels(abp.activity, player), abp.performance),
|
||||||
);
|
);
|
||||||
if (filtered.length === 0) return null;
|
|
||||||
|
|
||||||
// Age interpolation
|
const activityLevelsAvg = Math.round(
|
||||||
const ageArray = [...new Set(filtered.map((s) => s.age))].sort((a, b) => a - b);
|
activityLevels.reduce((sum, curr) => sum + curr, 0) / activityLevels.length,
|
||||||
const { lower: ageLower, upper: ageUpper } = findNearestPoints(age, ageArray);
|
);
|
||||||
|
|
||||||
const lowerValues = interpolateByBodyWeight(ageLower, weightKG, filtered);
|
return activityLevelsAvg;
|
||||||
const upperValues = interpolateByBodyWeight(ageUpper, weightKG, filtered);
|
}
|
||||||
if (!lowerValues || !upperValues) return null;
|
|
||||||
|
|
||||||
let ageRatio = ageUpper === ageLower ? 1 : (age - ageLower) / (ageUpper - ageLower);
|
export function calcAllAttributeLevels(
|
||||||
ageRatio = Math.max(0, Math.min(1, ageRatio));
|
player: Player,
|
||||||
|
activityPerformances: ActivityPerformance[],
|
||||||
|
): Record<Attribute, number> {
|
||||||
|
const attrLevels: Record<Attribute, number> = {
|
||||||
|
[attributes.STRENGTH]: 0,
|
||||||
|
[attributes.POWER]: 0,
|
||||||
|
[attributes.ENDURANCE]: 0,
|
||||||
|
// [attributes.SPEED]: 0,
|
||||||
|
[attributes.AGILITY]: 0,
|
||||||
|
} as const;
|
||||||
|
|
||||||
return interpolateStandardsValues(lowerValues, upperValues, ageRatio);
|
for (const value of Object.values(player)) {
|
||||||
};
|
if (!value)
|
||||||
|
return attrLevels;
|
||||||
|
}
|
||||||
|
|
||||||
const interpolateByBodyWeight = (
|
(Object.values(attributes) as Attribute[]).forEach((attribute) => {
|
||||||
agePoint: number,
|
const attrActivityPerformances = activityPerformances.filter(
|
||||||
bodyWeightKG: number,
|
(bp) => getActivityAttribute(bp.activity) === attribute,
|
||||||
filtered: StandardsItem[],
|
);
|
||||||
): Levels | null => {
|
attrLevels[attribute] = calcAttributeLevel(attribute, player, attrActivityPerformances);
|
||||||
const ageFiltered = filtered.filter((item) => item.age === agePoint);
|
});
|
||||||
if (ageFiltered[0].bodyWeight === -1) return ageFiltered[0];
|
|
||||||
|
|
||||||
const weightArray = [...new Set(ageFiltered.map((obj) => obj.bodyWeight))].sort((a, b) => a - b);
|
return attrLevels;
|
||||||
const { lower, upper } = findNearestPoints(bodyWeightKG, weightArray);
|
}
|
||||||
|
|
||||||
const weightLower = ageFiltered.find((i) => i.bodyWeight === lower);
|
export function calcPlayerLevel(player: Player, activityPerformances: ActivityPerformance[]): number {
|
||||||
const weightUpper = ageFiltered.find((i) => i.bodyWeight === upper);
|
for (const value of Object.values(player)) {
|
||||||
if (!weightLower || !weightUpper) return null;
|
if (!value)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
let weightRatio = upper === lower ? 1 : (bodyWeightKG - lower) / (upper - lower);
|
const attrLevels = calcAllAttributeLevels(player, activityPerformances);
|
||||||
weightRatio = Math.max(0, Math.min(1, weightRatio));
|
for (const level of Object.values(attrLevels)) {
|
||||||
|
if (!level)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return interpolateStandardsValues(weightLower, weightUpper, weightRatio);
|
const attrLevelsSum = Object.values(attrLevels).reduce((sum, lvl) => sum + lvl, 0);
|
||||||
};
|
return Math.round(attrLevelsSum / Object.keys(attributes).length);
|
||||||
|
}
|
||||||
|
|
||||||
const interpolateStandardsValues = (
|
|
||||||
lower: Levels,
|
|
||||||
upper: Levels,
|
|
||||||
ratio: number,
|
|
||||||
): Levels => {
|
|
||||||
const lerp = (k: keyof Levels): number =>
|
|
||||||
(lower[k] as number) + ((upper[k] as number) - (lower[k] as number)) * ratio;
|
|
||||||
|
|
||||||
return {
|
|
||||||
physicallyActive: lerp("physicallyActive"),
|
|
||||||
beginner: lerp("beginner"),
|
|
||||||
intermediate: lerp("intermediate"),
|
|
||||||
advanced: lerp("advanced"),
|
|
||||||
elite: lerp("elite"),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+38
-51
@@ -1,14 +1,5 @@
|
|||||||
import { activities, BenchmarkPerformance, genders, Player } from "./util";
|
import { activities, ActivityPerformance, feetToCm, genders, inchesToCm, lbToKg, minToMs, Player, secToMs } from "./util";
|
||||||
import { calcAttributeLevels, calcPlayerLevel, computeLevels } from "./calc.ts";
|
import { calcAllAttributeLevels, calcPlayerLevel } from "./calc.ts";
|
||||||
|
|
||||||
const lbToKg = (lb) => +lb * 0.453592;
|
|
||||||
const kgToLb = (kg) => +kg * 2.20462;
|
|
||||||
|
|
||||||
const minToMs = (min) => min * 60000;
|
|
||||||
const secToMs = (sec) => sec * 1000;
|
|
||||||
|
|
||||||
const feetToCm = (feet) => feet * 30.48;
|
|
||||||
const inchesToCm = (inches) => inches * 2.54;
|
|
||||||
|
|
||||||
const player: Player = {
|
const player: Player = {
|
||||||
age: 18,
|
age: 18,
|
||||||
@@ -16,77 +7,73 @@ const player: Player = {
|
|||||||
gender: genders.MALE
|
gender: genders.MALE
|
||||||
};
|
};
|
||||||
|
|
||||||
const computedPerformances: BenchmarkPerformance[] = [
|
const computedPerformances: ActivityPerformance[] = [
|
||||||
// STRENGTH
|
// STRENGTH
|
||||||
{
|
{
|
||||||
activity: activities.BENCH_PRESS,
|
activity: activities.BENCH_PRESS,
|
||||||
performance: lbToKg(200),
|
performance: lbToKg(200)
|
||||||
levels: computeLevels(
|
|
||||||
player.age,
|
|
||||||
player.weightKG,
|
|
||||||
player.gender,
|
|
||||||
activities.BENCH_PRESS
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
activity: activities.DEADLIFT,
|
activity: activities.DEADLIFT,
|
||||||
performance: lbToKg(200),
|
performance: lbToKg(200),
|
||||||
levels: computeLevels(
|
|
||||||
player.age,
|
|
||||||
player.weightKG,
|
|
||||||
player.gender,
|
|
||||||
activities.DEADLIFT
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
activity: activities.BACK_SQUAT,
|
activity: activities.BACK_SQUAT,
|
||||||
performance: lbToKg(200),
|
performance: lbToKg(200),
|
||||||
levels: computeLevels(
|
|
||||||
player.age,
|
|
||||||
player.weightKG,
|
|
||||||
player.gender,
|
|
||||||
activities.BACK_SQUAT
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
// POWER
|
// POWER
|
||||||
{
|
{
|
||||||
activity: activities.BROAD_JUMP,
|
activity: activities.BROAD_JUMP,
|
||||||
performance: feetToCm(5) + inchesToCm(2),
|
performance: feetToCm(10) + inchesToCm(2),
|
||||||
levels: computeLevels(
|
|
||||||
player.age,
|
|
||||||
player.weightKG,
|
|
||||||
player.gender,
|
|
||||||
activities.BROAD_JUMP
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
// ENDURANCE
|
// ENDURANCE
|
||||||
{
|
{
|
||||||
activity: activities.RUN,
|
activity: activities.RUN,
|
||||||
performance: minToMs(5) + secToMs(20),
|
performance: minToMs(5) + secToMs(20),
|
||||||
levels: computeLevels(
|
|
||||||
player.age,
|
|
||||||
player.weightKG,
|
|
||||||
player.gender,
|
|
||||||
activities.RUN
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
// AGILITY
|
// AGILITY
|
||||||
{
|
{
|
||||||
activity: activities.CONE_DRILL,
|
activity: activities.CONE_DRILL,
|
||||||
performance: secToMs(5),
|
performance: secToMs(5),
|
||||||
levels: computeLevels(
|
|
||||||
player.age,
|
|
||||||
player.weightKG,
|
|
||||||
player.gender,
|
|
||||||
activities.CONE_DRILL
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const levels = {
|
const levels = {
|
||||||
attributes: calcAttributeLevels(player, computedPerformances),
|
attributes: calcAllAttributeLevels(player, computedPerformances),
|
||||||
player: calcPlayerLevel(player, computedPerformances)
|
player: calcPlayerLevel(player, computedPerformances)
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(levels);
|
console.log(levels);
|
||||||
|
|
||||||
|
// const newStandards: Standards = {
|
||||||
|
// "Back Squat": [],
|
||||||
|
// "Deadlift": [],
|
||||||
|
// "Bench Press": [],
|
||||||
|
// "Run": [],
|
||||||
|
// "Dash": [],
|
||||||
|
// "Treadmill Dash": [],
|
||||||
|
// "Broad Jump": [],
|
||||||
|
// "Cone Drill": [],
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// for (const item of standards) {
|
||||||
|
// if (!newStandards[item.activityType])
|
||||||
|
// continue;
|
||||||
|
//
|
||||||
|
// newStandards[item.activityType].push({
|
||||||
|
// metrics: {
|
||||||
|
// age: item.age,
|
||||||
|
// weight: item.bodyWeight,
|
||||||
|
// gender: item.gender
|
||||||
|
// },
|
||||||
|
// levels: {
|
||||||
|
// physicallyActive: item.physicallyActive,
|
||||||
|
// beginner: item.beginner,
|
||||||
|
// intermediate: item.intermediate,
|
||||||
|
// advanced: item.advanced,
|
||||||
|
// elite: item.elite
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Bun.write("./newStandards.json", JSON.stringify(newStandards, null, 2));
|
||||||
|
|||||||
+25
-18
@@ -70,38 +70,45 @@ export type Gender = typeof genders[keyof typeof genders];
|
|||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
// Data model interfaces
|
// Data model interfaces
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
export interface BenchmarkPerformance {
|
export interface ActivityPerformance {
|
||||||
activity: Activity;
|
activity: Activity;
|
||||||
performance: number;
|
performance: number;
|
||||||
/**
|
|
||||||
* Key: level number, Value: performance value (kg, ms, m/s, etc.)
|
|
||||||
*/
|
|
||||||
levels: Record<number, number>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Levels {
|
export type BaseLevel =
|
||||||
/** KG or -1 for body‑weight‑agnostic entries */
|
| "physicallyActive"
|
||||||
physicallyActive: number;
|
| "beginner"
|
||||||
beginner: number;
|
| "intermediate"
|
||||||
intermediate: number;
|
| "advanced"
|
||||||
advanced: number;
|
| "elite";
|
||||||
elite: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StandardsItem extends Levels {
|
interface Metrics {
|
||||||
bodyWeight: number;
|
|
||||||
gender: Gender;
|
|
||||||
activityType: Activity;
|
|
||||||
age: number;
|
age: number;
|
||||||
|
weight: number;
|
||||||
|
gender: Gender;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Standard {
|
||||||
|
metrics: Metrics;
|
||||||
|
levels: Record<BaseLevel, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StandardsMap = Record<
|
||||||
|
Activity,
|
||||||
|
Standard[]
|
||||||
|
>;
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
// Conversion utilities
|
// Conversion utilities
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
export const lbToKg = (lb: number | string): number => +lb * 0.453592;
|
export const lbToKg = (lb: number | string): number => +lb * 0.453592;
|
||||||
export const kgToLb = (kg: number | string): number => +kg * 2.20462;
|
export const kgToLb = (kg: number | string): number => +kg * 2.20462;
|
||||||
|
|
||||||
export const feetToCm = (feet: number): number => feet * 30.48;
|
export const minToMs = (min) => min * 60000;
|
||||||
|
export const secToMs = (sec) => sec * 1000;
|
||||||
|
|
||||||
|
export const feetToCm = (feet) => feet * 30.48;
|
||||||
|
export const inchesToCm = (inches) => inches * 2.54;
|
||||||
|
|
||||||
export const mphToMtrsPerMs = (mph: number): number => (mph * 0.44704) / 1000;
|
export const mphToMtrsPerMs = (mph: number): number => (mph * 0.44704) / 1000;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { calcAttributeLevels, calcPlayerLevel, computeLevels } from "./calculator/calc.ts";
|
import { calcAllAttributeLevels, calcPlayerLevel } from "./calculator/calc.ts";
|
||||||
import { Activity, BenchmarkPerformance, Player } from "./calculator/util.ts";
|
import { ActivityPerformance, Player } from "./calculator/util.ts";
|
||||||
import { Printful } from "./commerce/printful.ts"
|
import { Printful } from "./commerce/printful.ts"
|
||||||
import { Webflow } from "./commerce/webflow.ts";
|
import { Webflow } from "./commerce/webflow.ts";
|
||||||
|
|
||||||
interface CalcRequest {
|
interface CalcRequest {
|
||||||
player: Player,
|
player: Player,
|
||||||
performances: {
|
performances: ActivityPerformance[]
|
||||||
activity: Activity,
|
|
||||||
performance: number,
|
|
||||||
}[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClientResponse extends Response {
|
export class ClientResponse extends Response {
|
||||||
@@ -39,24 +36,10 @@ const server = Bun.serve({
|
|||||||
const calcReq: CalcRequest = await req.json();
|
const calcReq: CalcRequest = await req.json();
|
||||||
const { player, performances } = calcReq;
|
const { player, performances } = calcReq;
|
||||||
|
|
||||||
const computedPerformances: BenchmarkPerformance[] = [];
|
|
||||||
for (const p of performances) {
|
|
||||||
computedPerformances.push({
|
|
||||||
activity: p.activity,
|
|
||||||
performance: p.performance,
|
|
||||||
levels: computeLevels(
|
|
||||||
player.age,
|
|
||||||
player.weightKG,
|
|
||||||
player.gender,
|
|
||||||
p.activity
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return ClientResponse.json({
|
return ClientResponse.json({
|
||||||
levels: {
|
levels: {
|
||||||
attributes: calcAttributeLevels(player, computedPerformances),
|
attributes: calcAllAttributeLevels(player, performances),
|
||||||
player: calcPlayerLevel(player, computedPerformances)
|
player: calcPlayerLevel(player, performances)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user