Files
knur-app/lib/strength/stats.ts
Dominik Klarkowski 21e5db3409 init
2026-06-16 11:11:19 +02:00

61 lines
2.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}