Calculator updates

This commit is contained in:
Dominic Ferrando
2025-09-11 20:01:34 -04:00
parent 7368259d8b
commit 3f9c2ae2e6
3 changed files with 114 additions and 71 deletions
+63 -33
View File
@@ -1,4 +1,4 @@
import { activities, Activity, Attribute, attributes, BenchmarkPerformance, findNearestPoints, Gender, getActivityAttribute, kgToLb, Levels, msToTime, StandardsItem } from "./util";
import { Activity, Attribute, attributes, BenchmarkPerformance, findNearestPoints, Gender, getActivityAttribute, getAttributeActivities, kgToLb, Levels, msToTime, Player, StandardsItem } from "./util";
import rawStandards from "./standards.json" assert { type: "json" }
const standards = rawStandards as StandardsItem[];
@@ -35,10 +35,24 @@ 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
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),
);
@@ -51,16 +65,22 @@ export const calcAttributeLevel = (
};
export const calcAttributeLevels = (
player: Player,
benchmarkPerformances: BenchmarkPerformance[],
): Record<Attribute, number> => {
const attrLevels: Record<Attribute, number> = {
[attributes.STRENGTH]: 1,
[attributes.POWER]: 1,
[attributes.ENDURANCE]: 1,
[attributes.SPEED]: 1,
[attributes.AGILITY]: 1,
[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,
@@ -71,8 +91,18 @@ export const calcAttributeLevels = (
return attrLevels;
};
export const calcPlayerLevel = (benchmarkPerformances: BenchmarkPerformance[]): number => {
const attrLevels = calcAttributeLevels(benchmarkPerformances);
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);
};
@@ -80,31 +110,31 @@ export const calcPlayerLevel = (benchmarkPerformances: BenchmarkPerformance[]):
// -------------------------------------------------------------------------------------------------
// 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;
};
// 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
+47 -30
View File
@@ -1,6 +1,26 @@
// -------------------------------------------------------------------------------------------------
// Attributes
// -------------------------------------------------------------------------------------------------
export const attributes = Object.freeze({
STRENGTH: "Strength",
POWER: "Power",
ENDURANCE: "Endurance",
// SPEED: "Speed",
AGILITY: "Agility",
} as const);
export type Attribute = typeof attributes[keyof typeof attributes];
// -------------------------------------------------------------------------------------------------
// Enumerations & basic value objects
// -------------------------------------------------------------------------------------------------
export interface Player {
age: number,
weightKG: number,
gender: Gender
}
export const activities = Object.freeze({
BACK_SQUAT: "Back Squat",
DEADLIFT: "Deadlift",
@@ -14,42 +34,39 @@ export const activities = Object.freeze({
export type Activity = typeof activities[keyof typeof activities];
const activityAttrMap: Record<Activity, Attribute> = {
[activities.RUN]: attributes.ENDURANCE,
// [activities.TREADMILL_DASH]: attributes.SPEED,
// [activities.DASH]: attributes.SPEED,
[activities.DEADLIFT]: attributes.STRENGTH,
[activities.BACK_SQUAT]: attributes.STRENGTH,
[activities.BENCH_PRESS]: attributes.STRENGTH,
[activities.BROAD_JUMP]: attributes.POWER,
[activities.CONE_DRILL]: attributes.AGILITY,
} as const;
export const getActivityAttribute = (activity: Activity): Attribute => {
const attribute = activityAttrMap[activity];
if (!attribute) throw new Error(`${activity} does not have an associated attribute`);
return attribute;
};
export const getAttributeActivities = (attribute: Attribute): Activity[] => {
const activities: Activity[] = [];
for (const activity in activityAttrMap) {
if (activityAttrMap[activity] === attribute) {
activities.push(activity as Activity);
}
}
return activities;
}
export const genders = Object.freeze({
MALE: "male",
FEMALE: "female",
} as const);
export type Gender = typeof genders[keyof typeof genders];
// -------------------------------------------------------------------------------------------------
// Attributes
// -------------------------------------------------------------------------------------------------
export const attributes = Object.freeze({
STRENGTH: "Strength",
POWER: "Power",
ENDURANCE: "Endurance",
SPEED: "Speed",
AGILITY: "Agility",
} as const);
export type Attribute = typeof attributes[keyof typeof attributes];
export const getActivityAttribute = (activity: Activity): Attribute => {
const activityAttrMap: Record<Activity, Attribute> = {
[activities.RUN]: attributes.ENDURANCE,
[activities.TREADMILL_DASH]: attributes.SPEED,
[activities.DASH]: attributes.SPEED,
[activities.DEADLIFT]: attributes.STRENGTH,
[activities.BACK_SQUAT]: attributes.STRENGTH,
[activities.BENCH_PRESS]: attributes.STRENGTH,
[activities.BROAD_JUMP]: attributes.POWER,
[activities.CONE_DRILL]: attributes.AGILITY,
} as const;
const attribute = activityAttrMap[activity];
if (!attribute) throw new Error(`${activity} does not have an associated attribute`);
return attribute;
};
// -------------------------------------------------------------------------------------------------
// Data model interfaces
// -------------------------------------------------------------------------------------------------
+4 -8
View File
@@ -1,15 +1,11 @@
import { calcAttributeLevels, calcPlayerLevel, computeLevels } from "./calculator/calc.ts";
import { Activity, BenchmarkPerformance, Gender } from "./calculator/util.ts";
import { Activity, BenchmarkPerformance, Player } from "./calculator/util.ts";
import { productRecords } from "./commerce/product-records.ts";
import { Printful } from "./commerce/printful.ts"
import { Webflow } from "./commerce/webflow.ts";
interface CalcRequest {
player: {
age: number,
weightKG: number,
gender: Gender
}
player: Player,
performances: {
activity: Activity,
performance: number,
@@ -59,8 +55,8 @@ const server = Bun.serve({
return ClientResponse.json({
levels: {
attributes: calcAttributeLevels(computedPerformances),
player: calcPlayerLevel(computedPerformances)
attributes: calcAttributeLevels(player, computedPerformances),
player: calcPlayerLevel(player, computedPerformances)
}
})
}