This commit is contained in:
Dominic Ferrando
2025-09-15 20:58:58 -04:00
parent ea353e2185
commit 466f2095ba
5 changed files with 5364 additions and 5920 deletions
+55 -20
View File
@@ -1,4 +1,4 @@
import { Activity, Attribute, attributes, ActivityPerformance, getActivityAttribute, getAttributeActivities, Player, StandardsMap, Levels, Standard, Metrics, Gender } from "./util"; import { Activity, Attribute, Player, Gender, ActivityPerformance } from "./util";
// SOURCES // SOURCES
// Squat, Bench, Dead Lift: // Squat, Bench, Dead Lift:
@@ -20,6 +20,26 @@ type LevelCalculatorOutput = {
attributes: Record<Attribute, number> attributes: Record<Attribute, number>
} }
export type Levels = Record<string, number>
export interface Metrics {
age: number;
weight: number;
gender: Gender;
};
export interface Standard {
metrics: Metrics;
levels: Levels;
}
export type ActivityStandards = Record<Activity, {
metadata: {
attribute: Attribute
},
standards: Standard[]
}>;
export class LevelCalculator { export class LevelCalculator {
cfg: Required<LevelCalculatorConfig>; cfg: Required<LevelCalculatorConfig>;
standards: Standards; standards: Standards;
@@ -40,10 +60,10 @@ export class LevelCalculator {
const levels: LevelCalculatorOutput = { const levels: LevelCalculatorOutput = {
player: 0, player: 0,
attributes: { attributes: {
[attributes.STRENGTH]: 0, [Attribute.Strength]: 0,
[attributes.POWER]: 0, [Attribute.Power]: 0,
[attributes.ENDURANCE]: 0, [Attribute.Endurance]: 0,
[attributes.AGILITY]: 0, [Attribute.Agility]: 0,
} as const } as const
}; };
@@ -59,7 +79,7 @@ export class LevelCalculator {
} }
const attrLevelsSum = Object.values(levels.attributes).reduce((sum, lvl) => sum + lvl, 0); const attrLevelsSum = Object.values(levels.attributes).reduce((sum, lvl) => sum + lvl, 0);
levels.player = Math.round(attrLevelsSum / Object.keys(attributes).length); levels.player = Math.round(attrLevelsSum / Object.keys(Attribute).length);
return levels; return levels;
} }
@@ -69,10 +89,10 @@ export class LevelCalculator {
activityPerformances: ActivityPerformance[], activityPerformances: ActivityPerformance[],
): Record<Attribute, number> { ): Record<Attribute, number> {
const attrLevels: Record<Attribute, number> = { const attrLevels: Record<Attribute, number> = {
[attributes.STRENGTH]: 0, [Attribute.Strength]: 0,
[attributes.POWER]: 0, [Attribute.Power]: 0,
[attributes.ENDURANCE]: 0, [Attribute.Endurance]: 0,
[attributes.AGILITY]: 0, [Attribute.Agility]: 0,
} as const; } as const;
for (const value of Object.values(player)) { for (const value of Object.values(player)) {
@@ -80,9 +100,9 @@ export class LevelCalculator {
return attrLevels; return attrLevels;
} }
(Object.values(attributes) as Attribute[]).forEach((attribute) => { (Object.values(Attribute) as Attribute[]).forEach((attribute) => {
const attrActivityPerformances = activityPerformances.filter( const attrActivityPerformances = activityPerformances.filter(
(p) => getActivityAttribute(p.activity) === attribute, (p) => this.standards.getActivityAttribute(p.activity) === attribute,
); );
attrLevels[attribute] = this.calculateAttributeLevel(attribute, player, attrActivityPerformances); attrLevels[attribute] = this.calculateAttributeLevel(attribute, player, attrActivityPerformances);
}); });
@@ -97,12 +117,12 @@ export class LevelCalculator {
activityPerformances: ActivityPerformance[], activityPerformances: ActivityPerformance[],
): number { ): number {
// must be activities of the given attribute // must be activities of the given attribute
if (activityPerformances.some((p) => getActivityAttribute(p.activity) !== attribute)) { if (activityPerformances.some((p) => this.standards.getActivityAttribute(p.activity) !== attribute)) {
throw new Error("Wrong activity performance attribute"); throw new Error("Wrong activity performance attribute");
} }
// must perform all of an attributes activities // must perform all of an attributes activities
for (const activity of getAttributeActivities(attribute)) { for (const activity of this.standards.getAttributeActivities(attribute)) {
const filteredActivites = activityPerformances.map((p) => p.activity); const filteredActivites = activityPerformances.map((p) => p.activity);
if (!filteredActivites.includes(activity)) if (!filteredActivites.includes(activity))
return 0; return 0;
@@ -213,12 +233,12 @@ export class LevelCalculator {
} }
export class Standards { export class Standards {
private standardsMap: StandardsMap; private activityStandards: ActivityStandards;
constructor(standardsMap: StandardsMap) { constructor(activityStandards: ActivityStandards) {
this.standardsMap = standardsMap; this.activityStandards = activityStandards;
for (const activity of Object.keys(this.standardsMap) as Activity[]) { for (const activity of Object.keys(this.activityStandards) as Activity[]) {
for (const standard of this.standardsMap[activity]) { for (const standard of this.activityStandards[activity].standards) {
const expandedLevels = this.expandLevels(standard.levels, 5); const expandedLevels = this.expandLevels(standard.levels, 5);
const compressedLevels = this.compressLevels(expandedLevels, 100); const compressedLevels = this.compressLevels(expandedLevels, 100);
standard.levels = compressedLevels; standard.levels = compressedLevels;
@@ -237,7 +257,7 @@ export class Standards {
const execMethods = { const execMethods = {
getAll: function() { getAll: function() {
let filtered = [...self.standardsMap[activity]]; let filtered = [...self.activityStandards[activity].standards];
if (state.gender) if (state.gender)
filtered = filtered.filter((s) => s.metrics.gender === state.gender); filtered = filtered.filter((s) => s.metrics.gender === state.gender);
if (state.age) if (state.age)
@@ -280,6 +300,21 @@ export class Standards {
return { byGender, ...execMethods } return { byGender, ...execMethods }
} }
getActivityAttribute(activity: Activity): Attribute {
return this.activityStandards[activity].metadata.attribute;
};
getAttributeActivities(attribute: Attribute): Activity[] {
const activities: Activity[] = [];
for (const activity in this.activityStandards) {
const activityStandard = this.activityStandards[activity] as ActivityStandards[Activity];
if (activityStandard.metadata.attribute === attribute) {
activities.push(activity as Activity);
}
}
return activities;
}
private expandLevels( private expandLevels(
levels: Levels, levels: Levels,
i: number i: number
+10 -10
View File
@@ -1,47 +1,47 @@
import { activities, ActivityPerformance, feetToCm, genders, inchesToCm, lbToKg, minToMs, Player, secToMs, StandardsMap } from "./util"; import { Activity, ActivityPerformance, feetToCm, Gender, inchesToCm, lbToKg, minToMs, Player, secToMs } from "./util";
import { LevelCalculator, Standards } from "./calc.ts"; import { ActivityStandards, LevelCalculator, Standards } from "./calc.ts";
import rawStandards from "../data/standards.json" assert { type: "json" } import rawStandards from "../data/standards.json" assert { type: "json" }
const player: Player = { const player: Player = {
metrics: { metrics: {
age: 25, age: 25,
weight: lbToKg(190), weight: lbToKg(190),
gender: genders.MALE gender: Gender.Male
} }
}; };
const computedPerformances: ActivityPerformance[] = [ const computedPerformances: ActivityPerformance[] = [
// STRENGTH // STRENGTH
{ {
activity: activities.BENCH_PRESS, activity: Activity.BenchPress,
performance: lbToKg(225) performance: lbToKg(225)
}, },
{ {
activity: activities.DEADLIFT, activity: Activity.Deadlift,
performance: lbToKg(270), performance: lbToKg(270),
}, },
{ {
activity: activities.BACK_SQUAT, activity: Activity.BackSquat,
performance: lbToKg(190), performance: lbToKg(190),
}, },
// POWER // POWER
{ {
activity: activities.BROAD_JUMP, activity: Activity.BroadJump,
performance: feetToCm(105) + inchesToCm(5), performance: feetToCm(105) + inchesToCm(5),
}, },
// ENDURANCE // ENDURANCE
{ {
activity: activities.RUN, activity: Activity.Run,
performance: minToMs(7) + secToMs(15), performance: minToMs(7) + secToMs(15),
}, },
// AGILITY // AGILITY
{ {
activity: activities.CONE_DRILL, activity: Activity.ConeDrill,
performance: secToMs(9), performance: secToMs(9),
}, },
]; ];
const standards = new Standards(rawStandards as StandardsMap); const standards = new Standards(rawStandards as ActivityStandards);
const levelCalculator = new LevelCalculator(standards); const levelCalculator = new LevelCalculator(standards);
console.log(levelCalculator.calculate(player, computedPerformances)); console.log(levelCalculator.calculate(player, computedPerformances));
+18 -75
View File
@@ -1,96 +1,39 @@
// ------------------------------------------------------------------------------------------------- import { Metrics } from "./calc";
// 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 // DATA MODELS
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
export interface Player { export interface Player {
metrics: Metrics; metrics: Metrics;
} }
export const activities = Object.freeze({ export enum Attribute {
BACK_SQUAT: "Back Squat", Strength = "Strength",
DEADLIFT: "Deadlift", Power = "Power",
BENCH_PRESS: "Bench Press", Endurance = "Endurance",
RUN: "Run", Agility = "Agility",
DASH: "Dash",
TREADMILL_DASH: "Treadmill Dash",
BROAD_JUMP: "Broad Jump",
CONE_DRILL: "Cone Drill",
} as const);
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({ export enum Activity {
MALE: "male", BackSquat = "BackSquat",
FEMALE: "female", Deadlift = "Deadlift",
} as const); BenchPress = "BenchPress",
export type Gender = typeof genders[keyof typeof genders]; Run = "Run",
BroadJump = "BroadJump",
ConeDrill = "ConeDrill",
}
// -------------------------------------------------------------------------------------------------
// Data model interfaces
// -------------------------------------------------------------------------------------------------
export interface ActivityPerformance { export interface ActivityPerformance {
activity: Activity; activity: Activity;
performance: number; performance: number;
} }
export type Levels = Record<string, number> export enum Gender {
Male = "Male",
export interface Metrics { Female = "Female"
age: number;
weight: number;
gender: Gender;
};
export interface Standard {
metrics: Metrics;
levels: Levels;
} }
export type StandardsMap = Record<
Activity,
Standard[]
>;
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
// Conversion utilities // Conversion utilities
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
+415 -949
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,5 +1,5 @@
import { LevelCalculator, Standards } from "./calculator/calc.ts"; import { ActivityStandards, LevelCalculator, Standards } from "./calculator/calc.ts";
import { ActivityPerformance, Player, StandardsMap } 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";
import rawStandards from "./data/standards.json" assert { type: "json" } import rawStandards from "./data/standards.json" assert { type: "json" }
@@ -26,7 +26,7 @@ export class ClientResponse extends Response {
} }
} }
const MAIN_STANDARDS = new Standards(rawStandards as StandardsMap); const MAIN_STANDARDS = new Standards(rawStandards as ActivityStandards);
const server = Bun.serve({ const server = Bun.serve({
idleTimeout: 180, idleTimeout: 180,