61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
import type { StrengthExercise, StrengthWorkout } from "@/lib/models/strength";
|
||
|
||
|
||
export function exerciseVolumeKg(exercise: StrengthExercise): number {
|
||
return exercise.sets.reduce((sum, set) => sum + (set.weightKg ?? 0) * (set.reps ?? 0), 0);
|
||
}
|
||
|
||
export function exerciseTopWeightKg(exercise: StrengthExercise): number | undefined {
|
||
const weights = exercise.sets
|
||
.map((set) => set.weightKg)
|
||
.filter((weight): weight is number => weight !== undefined);
|
||
return weights.length > 0 ? Math.max(...weights) : undefined;
|
||
}
|
||
|
||
// Epley formula: weight × (1 + reps / 30). Returns best e1rm across all sets.
|
||
export function exerciseE1rm(exercise: StrengthExercise): number | undefined {
|
||
let best: number | undefined;
|
||
for (const set of exercise.sets) {
|
||
if (set.weightKg == null || set.reps == null || set.reps < 1) continue;
|
||
const e1rm = set.weightKg * (1 + set.reps / 30);
|
||
if (best === undefined || e1rm > best) best = e1rm;
|
||
}
|
||
return best !== undefined ? Math.round(best * 10) / 10 : undefined;
|
||
}
|
||
|
||
export function workoutVolumeKg(workout: StrengthWorkout): number {
|
||
return workout.exercises.reduce((sum, exercise) => sum + exerciseVolumeKg(exercise), 0);
|
||
}
|
||
|
||
export type ExerciseHistoryPoint = {
|
||
date: Date;
|
||
volumeKg: number;
|
||
topWeightKg?: number;
|
||
e1rmKg?: number;
|
||
};
|
||
|
||
/**
|
||
* History of a single exercise across past workouts (oldest first, including
|
||
* the workout it was found in), used to chart progression.
|
||
*/
|
||
export function getExerciseHistory(
|
||
exerciseName: string,
|
||
workouts: StrengthWorkout[],
|
||
limit: number
|
||
): ExerciseHistoryPoint[] {
|
||
const points: ExerciseHistoryPoint[] = [];
|
||
for (const workout of workouts) {
|
||
const exercise = workout.exercises.find((e) => e.name === exerciseName);
|
||
if (!exercise) continue;
|
||
points.push({
|
||
date: workout.date,
|
||
volumeKg: exerciseVolumeKg(exercise),
|
||
topWeightKg: exerciseTopWeightKg(exercise),
|
||
e1rmKg: exerciseE1rm(exercise),
|
||
});
|
||
}
|
||
|
||
points.sort((a, b) => a.date.getTime() - b.date.getTime());
|
||
return points.slice(-limit);
|
||
}
|