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" } import rawStandards from "./standards.json" assert { type: "json" }
const standards = rawStandards as StandardsItem[]; const standards = rawStandards as StandardsItem[];
@@ -35,10 +35,24 @@ export const calcAttributeLevel = (
attribute: Attribute, attribute: Attribute,
benchmarkPerformances: BenchmarkPerformance[], benchmarkPerformances: BenchmarkPerformance[],
): number => { ): number => {
// must be activities of the given attribute
if (benchmarkPerformances.some((bp) => getActivityAttribute(bp.activity) !== attribute)) { if (benchmarkPerformances.some((bp) => getActivityAttribute(bp.activity) !== attribute)) {
throw new Error("Wrong benchmark performance 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) => const benchmarkLevels = benchmarkPerformances.map((abp) =>
calcBenchmarkLevel(abp.performance, abp.levels), calcBenchmarkLevel(abp.performance, abp.levels),
); );
@@ -51,16 +65,22 @@ export const calcAttributeLevel = (
}; };
export const calcAttributeLevels = ( export const calcAttributeLevels = (
player: Player,
benchmarkPerformances: BenchmarkPerformance[], benchmarkPerformances: BenchmarkPerformance[],
): Record<Attribute, number> => { ): Record<Attribute, number> => {
const attrLevels: Record<Attribute, number> = { const attrLevels: Record<Attribute, number> = {
[attributes.STRENGTH]: 1, [attributes.STRENGTH]: 0,
[attributes.POWER]: 1, [attributes.POWER]: 0,
[attributes.ENDURANCE]: 1, [attributes.ENDURANCE]: 0,
[attributes.SPEED]: 1, // [attributes.SPEED]: 0,
[attributes.AGILITY]: 1, [attributes.AGILITY]: 0,
} as const; } as const;
for (const value of Object.values(player)) {
if (!value)
return attrLevels;
}
(Object.values(attributes) as Attribute[]).forEach((attribute) => { (Object.values(attributes) as Attribute[]).forEach((attribute) => {
const attrBenchmarkPerformances = benchmarkPerformances.filter( const attrBenchmarkPerformances = benchmarkPerformances.filter(
(bp) => getActivityAttribute(bp.activity) === attribute, (bp) => getActivityAttribute(bp.activity) === attribute,
@@ -71,8 +91,18 @@ export const calcAttributeLevels = (
return attrLevels; return attrLevels;
}; };
export const calcPlayerLevel = (benchmarkPerformances: BenchmarkPerformance[]): number => { export const calcPlayerLevel = (player: Player, benchmarkPerformances: BenchmarkPerformance[]): number => {
const attrLevels = calcAttributeLevels(benchmarkPerformances); 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); const attrLevelsSum = Object.values(attrLevels).reduce((sum, lvl) => sum + lvl, 0);
return Math.round(attrLevelsSum / Object.keys(attributes).length); return Math.round(attrLevelsSum / Object.keys(attributes).length);
}; };
@@ -80,31 +110,31 @@ export const calcPlayerLevel = (benchmarkPerformances: BenchmarkPerformance[]):
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
// Presentation helpers // Presentation helpers
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
export const formatLevelValues = ( // export const formatLevelValues = (
levels: Record<number, number>, // levels: Record<number, number>,
activity: Activity, // activity: Activity,
): Record<number, string> => { // ): Record<number, string> => {
const timedActivities: Activity[] = [activities.RUN, activities.DASH, activities.CONE_DRILL]; // const timedActivities: Activity[] = [activities.RUN, activities.DASH, activities.CONE_DRILL];
//
const formattedLevels: Record<number, string> = {}; // const formattedLevels: Record<number, string> = {};
for (const level in levels) { // for (const level in levels) {
const lvl = +level as number; // const lvl = +level as number;
let displayValue = ""; // let displayValue = "";
if (timedActivities.includes(activity)) { // if (timedActivities.includes(activity)) {
displayValue = msToTime(levels[lvl]); // displayValue = msToTime(levels[lvl]);
} else if (activity === activities.TREADMILL_DASH) { // } else if (activity === activities.TREADMILL_DASH) {
displayValue = `${(levels[lvl] * 1000).toFixed(2)} m/s`; // displayValue = `${(levels[lvl] * 1000).toFixed(2)} m/s`;
} else if (activity === activities.BROAD_JUMP) { // } else if (activity === activities.BROAD_JUMP) {
displayValue = `${(levels[lvl] * 0.0328084).toFixed(2)} ft.`; // displayValue = `${(levels[lvl] * 0.0328084).toFixed(2)} ft.`;
} else { // } else {
displayValue = kgToLb(levels[lvl]).toFixed(0); // displayValue = kgToLb(levels[lvl]).toFixed(0);
} // }
//
formattedLevels[lvl] = displayValue; // formattedLevels[lvl] = displayValue;
} // }
//
return formattedLevels; // return formattedLevels;
}; // };
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
// Level utilities // Level utilities
+39 -22
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 // Enumerations & basic value objects
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
export interface Player {
age: number,
weightKG: number,
gender: Gender
}
export const activities = Object.freeze({ export const activities = Object.freeze({
BACK_SQUAT: "Back Squat", BACK_SQUAT: "Back Squat",
DEADLIFT: "Deadlift", DEADLIFT: "Deadlift",
@@ -14,30 +34,10 @@ export const activities = Object.freeze({
export type Activity = typeof activities[keyof typeof activities]; export type Activity = typeof activities[keyof typeof 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> = { const activityAttrMap: Record<Activity, Attribute> = {
[activities.RUN]: attributes.ENDURANCE, [activities.RUN]: attributes.ENDURANCE,
[activities.TREADMILL_DASH]: attributes.SPEED, // [activities.TREADMILL_DASH]: attributes.SPEED,
[activities.DASH]: attributes.SPEED, // [activities.DASH]: attributes.SPEED,
[activities.DEADLIFT]: attributes.STRENGTH, [activities.DEADLIFT]: attributes.STRENGTH,
[activities.BACK_SQUAT]: attributes.STRENGTH, [activities.BACK_SQUAT]: attributes.STRENGTH,
[activities.BENCH_PRESS]: attributes.STRENGTH, [activities.BENCH_PRESS]: attributes.STRENGTH,
@@ -45,11 +45,28 @@ export const getActivityAttribute = (activity: Activity): Attribute => {
[activities.CONE_DRILL]: attributes.AGILITY, [activities.CONE_DRILL]: attributes.AGILITY,
} as const; } as const;
export const getActivityAttribute = (activity: Activity): Attribute => {
const attribute = activityAttrMap[activity]; const attribute = activityAttrMap[activity];
if (!attribute) throw new Error(`${activity} does not have an associated attribute`); if (!attribute) throw new Error(`${activity} does not have an associated attribute`);
return 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];
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
// Data model interfaces // Data model interfaces
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
+4 -8
View File
@@ -1,15 +1,11 @@
import { calcAttributeLevels, calcPlayerLevel, computeLevels } from "./calculator/calc.ts"; 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 { productRecords } from "./commerce/product-records.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,
age: number,
weightKG: number,
gender: Gender
}
performances: { performances: {
activity: Activity, activity: Activity,
performance: number, performance: number,
@@ -59,8 +55,8 @@ const server = Bun.serve({
return ClientResponse.json({ return ClientResponse.json({
levels: { levels: {
attributes: calcAttributeLevels(computedPerformances), attributes: calcAttributeLevels(player, computedPerformances),
player: calcPlayerLevel(computedPerformances) player: calcPlayerLevel(player, computedPerformances)
} }
}) })
} }