fitness: compute kcal server-side and store in session document
All checks were successful
CI / update (push) Successful in 3m43s

Previously kcal was computed on-the-fly in 3 places with inconsistent
inputs (hardcoded 80kg, missing GPS data, no demographics). Now a
shared computeSessionKcal() helper runs server-side using the best
available method (GPS + real demographics) and stores the result in
a new kcalEstimate field on WorkoutSession.

Kcal is recomputed on save, recalculate, GPX upload, and GPX delete.
The stats overview uses stored values with a legacy fallback for
sessions saved before this change.
This commit is contained in:
2026-04-03 08:24:42 +02:00
parent cee20f6bb3
commit eda8502568
8 changed files with 207 additions and 149 deletions

View File

@@ -29,6 +29,13 @@ export interface ICompletedExercise {
totalDistance?: number; // km
}
export interface IKcalEstimate {
kcal: number;
lower: number;
upper: number;
methods: string[];
}
export interface IPr {
exerciseId: string;
type: string; // 'est1rm' | 'maxWeight' | 'bestSetVolume' | 'repMax' | 'longestDistance' | 'fastestPace:<min>:<max>'
@@ -52,6 +59,7 @@ export interface IWorkoutSession {
gpsTrack?: IGpsPoint[]; // Top-level GPS track for GPS-only workouts
gpsPreview?: number[][]; // Downsampled [[lat,lng], ...] for card preview
prs?: IPr[];
kcalEstimate?: IKcalEstimate;
notes?: string;
createdBy: string; // username/nickname of the person who performed the workout
createdAt?: Date;
@@ -207,6 +215,16 @@ const WorkoutSessionSchema = new mongoose.Schema(
reps: Number,
_id: false
}],
kcalEstimate: {
type: {
kcal: { type: Number, required: true },
lower: { type: Number, required: true },
upper: { type: Number, required: true },
methods: { type: [String], required: true },
},
default: undefined,
_id: false,
},
notes: {
type: String,
trim: true,