Files
knur-app/lib/strength/stats.ts

61 lines
2.0 KiB
TypeScript
Raw Normal View History

2026-06-16 09:43:48 +02:00
import type { StrengthExercise, StrengthWorkout } from "@/lib/models/strength";
2026-06-16 11:11:19 +02:00
2026-06-16 09:43:48 +02:00
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;
}
2026-06-16 11:11:19 +02:00
// 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;
}
2026-06-16 09:43:48 +02:00
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;
2026-06-16 11:11:19 +02:00
e1rmKg?: number;
2026-06-16 09:43:48 +02:00
};
/**
* 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),
2026-06-16 11:11:19 +02:00
e1rmKg: exerciseE1rm(exercise),
2026-06-16 09:43:48 +02:00
});
}
points.sort((a, b) => a.date.getTime() - b.date.getTime());
return points.slice(-limit);
}