Skip to content

Commit

Permalink
Merge pull request #113 from WhyAsh5114/85-mini-sets
Browse files Browse the repository at this point in the history
feat: Mini set comparison and volume inclusion
  • Loading branch information
WhyAsh5114 authored Oct 14, 2024
2 parents 8b11148 + 8171bfd commit ce3129f
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 46 deletions.
59 changes: 45 additions & 14 deletions src/lib/utils/workoutUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,21 @@ export function getExerciseVolume(workoutExercise: WorkoutExercise, userBodyweig
);
}

export function cleanupInProgressMiniSets(miniSets: WorkoutExerciseInProgress['sets'][number]['miniSets']) {
return miniSets.map((miniSet) => {
return {
reps: miniSet.reps ?? 0,
load: miniSet.load ?? 0,
RIR: miniSet.RIR ?? 0
};
});
}

export type SetDetails = {
reps: number;
load: number;
RIR: number;
miniSets?: {
miniSets: {
reps: number;
load: number;
RIR: number;
Expand All @@ -34,13 +44,13 @@ type CommonBergerType = {
bodyweightFraction: number | null;
oldUserBodyweight?: number;
newUserBodyweight?: number;
overloadPercentage?: number;
oldSet: SetDetails;
};

type BergerNewReps = {
variableToSolve: 'NewReps';
knownValues: CommonBergerType & {
overloadPercentage: number;
newSet: Omit<SetDetails, 'reps'> & { reps?: number };
};
};
Expand All @@ -56,14 +66,7 @@ type BergerInput = BergerNewReps | BergerOverloadPercentage;

export function solveBergerFormula(input: BergerInput) {
const { variableToSolve, knownValues } = input;
const {
oldSet,
newSet,
bodyweightFraction = null,
oldUserBodyweight = 0,
newUserBodyweight = 0,
overloadPercentage = 0
} = knownValues;
const { oldSet, newSet, bodyweightFraction = null, oldUserBodyweight = 0, newUserBodyweight = 0 } = knownValues;

const oldLoad = oldSet.load + (bodyweightFraction ?? 0) * oldUserBodyweight;
const newLoad = newSet.load + (bodyweightFraction ?? 0) * newUserBodyweight;
Expand All @@ -72,7 +75,8 @@ export function solveBergerFormula(input: BergerInput) {

switch (variableToSolve) {
case 'NewReps': {
const numerator = (1 + overloadPercentage / 100) * (9745640 * oldLoad - 423641) * exponentialMultiplier;
const numerator =
(1 + knownValues.overloadPercentage / 100) * (9745640 * oldLoad - 423641) * exponentialMultiplier;
const denominator = 9745640 * newLoad - 423641;
return 38.1679 * Math.log(numerator / denominator) - newSet.RIR;
}
Expand All @@ -81,7 +85,33 @@ export function solveBergerFormula(input: BergerInput) {
const numeratorMultiplier = Math.pow(Math.E, (knownValues.newSet.reps + newSet.RIR) / 38.1679);
const numerator = numeratorMultiplier * (9745640 * newLoad - 423641);
const denominator = exponentialMultiplier * (9745640 * oldLoad - 423641);
return (numerator / denominator - 1) * 100;
const overloadPercentage = (numerator / denominator - 1) * 100;

let miniSetsCompared = 0;
let totalMiniSetsOverload = 0;
for (let i = 0; i < Math.max(newSet.miniSets.length, oldSet.miniSets.length); i++) {
const prevMiniSet = oldSet.miniSets[i];
const newMiniSet = newSet.miniSets[i];
if (!prevMiniSet || !newMiniSet) break;

totalMiniSetsOverload += solveBergerFormula({
variableToSolve: 'OverloadPercentage',
knownValues: {
newSet: { ...newMiniSet, miniSets: [] },
oldSet: { ...prevMiniSet, miniSets: [] },
bodyweightFraction,
newUserBodyweight,
oldUserBodyweight
}
});
miniSetsCompared++;
}

if (miniSetsCompared === 0) {
return overloadPercentage;
}

return (overloadPercentage + totalMiniSetsOverload) / (miniSetsCompared + 1);
}
}
}
Expand Down Expand Up @@ -283,11 +313,12 @@ function increaseLoadOfSets(ex: WorkoutExerciseInProgress, userBodyweight: numbe
newLoad += ex.minimumWeightChange ?? 5;
}

const cleanedMiniSets = cleanupInProgressMiniSets(set.miniSets);
const newReps = solveBergerFormula({
variableToSolve: 'NewReps',
knownValues: {
oldSet: { reps: set.reps, load: set.load, RIR: set.RIR },
newSet: { load: newLoad, RIR: set.RIR },
oldSet: { reps: set.reps, load: set.load, RIR: set.RIR, miniSets: cleanedMiniSets },
newSet: { load: newLoad, RIR: set.RIR, miniSets: cleanedMiniSets },
bodyweightFraction: ex.bodyweightFraction ?? null,
newUserBodyweight: userBodyweight,
oldUserBodyweight: userBodyweight,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
<script lang="ts">
import { solveBergerFormula, type WorkoutExerciseInProgress } from '$lib/utils/workoutUtils';
import * as Popover from '$lib/components/ui/popover';
import { workoutRunes } from '../../workoutRunes.svelte';
import TrendUpIcon from 'virtual:icons/lucide/trending-up';
import UpIcon from 'virtual:icons/lucide/chevron-up';
import TrendDownIcon from 'virtual:icons/lucide/trending-down';
import DownIcon from 'virtual:icons/lucide/chevron-down';
import Minus from 'virtual:icons/lucide/minus';
import Separator from '$lib/components/ui/separator/separator.svelte';
import { arrayAverage } from '$lib/utils';
import {
cleanupInProgressMiniSets,
solveBergerFormula,
type WorkoutExerciseInProgress
} from '$lib/utils/workoutUtils';
import DownIcon from 'virtual:icons/lucide/chevron-down';
import UpIcon from 'virtual:icons/lucide/chevron-up';
import Minus from 'virtual:icons/lucide/minus';
import TrendDownIcon from 'virtual:icons/lucide/trending-down';
import TrendUpIcon from 'virtual:icons/lucide/trending-up';
import { workoutRunes } from '../../workoutRunes.svelte';
type PropsType = { exercise: WorkoutExerciseInProgress };
let { exercise }: PropsType = $props();
Expand All @@ -17,23 +21,26 @@
function getTheoreticalVolumeChange(setIdx: number) {
const prevSet = prevExercise?.sets[setIdx];
const currentSet = exercise.sets[setIdx];
if (!prevSet) return;
if (!exercise.sets[setIdx]) return;
if (exercise.sets[setIdx].skipped || prevSet.skipped) return;
if (!currentSet) return;
if (currentSet.skipped || prevSet.skipped) return;
let { reps, load, RIR } = exercise.sets[setIdx];
if (reps === undefined || load === undefined || RIR === undefined) return;
if (!isSetCompleted(currentSet)) return;
let { reps, load, RIR, miniSets } = currentSet;
const actualOverload = solveBergerFormula({
variableToSolve: 'OverloadPercentage',
knownValues: {
oldSet: prevSet,
newSet: { reps, load, RIR },
newSet: { reps, load, RIR, miniSets: cleanupInProgressMiniSets(miniSets) },
newUserBodyweight: workoutRunes.workoutData?.userBodyweight as number,
oldUserBodyweight: workoutRunes.previousWorkoutData?.userBodyweight,
bodyweightFraction: exercise.bodyweightFraction ?? null
}
});
console.log(actualOverload);
return actualOverload;
}
Expand All @@ -45,6 +52,30 @@
);
}
type InProgressSet = { reps?: number; load?: number; RIR?: number; completed: boolean };
type CompletedSet = { reps: number; load: number; RIR: number; completed: boolean };
function isSetCompleted(miniSet: InProgressSet): miniSet is CompletedSet {
const { reps, load, RIR } = miniSet;
return reps !== undefined && load !== undefined && RIR !== undefined;
}
function getTheoreticalVolumeChangeOfMiniSet(prev: Omit<CompletedSet, 'completed'>, current: InProgressSet) {
if (!isSetCompleted(current)) return;
const actualOverload = solveBergerFormula({
variableToSolve: 'OverloadPercentage',
knownValues: {
oldSet: { ...prev, miniSets: [] },
newSet: { ...current, miniSets: [] },
newUserBodyweight: workoutRunes.workoutData?.userBodyweight as number,
oldUserBodyweight: workoutRunes.previousWorkoutData?.userBodyweight,
bodyweightFraction: exercise.bodyweightFraction ?? null
}
});
return actualOverload;
}
let totalVolumeChange = $derived(getAverageVolumeChangeOfAllSets());
</script>

Expand All @@ -67,7 +98,7 @@
</span>
<span class="text-sm font-medium">RIR</span>
<span class="flex w-full items-center justify-end gap-1 text-sm font-semibold">
{#if totalVolumeChange !== undefined}
{#if !isNaN(Number(totalVolumeChange)) && totalVolumeChange !== undefined}
{totalVolumeChange.toFixed(2)}%
{#if totalVolumeChange < 0}
<TrendDownIcon class="justify-self-end" />
Expand All @@ -85,26 +116,20 @@
<p>
{#if prevSet.reps !== set.reps}
<span class="text-muted-foreground">{prevSet.reps} -&gt;</span>
{set.reps}
{:else}
{set.reps}
{/if}
{set.reps}
</p>
<p>
{#if prevSet.load !== set.load}
<span class="text-muted-foreground">{prevSet.load} -&gt;</span>
{set.load}
{:else}
{set.load}
{/if}
{set.load}
</p>
<p>
{#if prevSet.RIR !== set.RIR}
<span class="text-muted-foreground">{prevSet.RIR} -&gt;</span>
{set.RIR}
{:else}
{set.RIR}
{/if}
{set.RIR}
</p>
<span class="flex w-full items-center justify-end gap-1 text-sm font-light">
{#if typeof volumeChange === 'number'}
Expand All @@ -118,7 +143,47 @@
{/if}
{/if}
</span>
<!-- TODO: #85 -->
{#each set.miniSets as miniSet, miniSetIdx}
{@const prevMiniSet = prevExercise.sets[idx].miniSets[miniSetIdx]}
{@const miniSetVolumeChange = getTheoreticalVolumeChangeOfMiniSet(prevMiniSet, miniSet)}
{#if prevMiniSet}
<p class="text-sm font-light italic">
{#if prevMiniSet.reps !== miniSet.reps}
<span class="text-muted-foreground">{prevMiniSet.reps} -&gt;</span>
{/if}
{miniSet.reps}
</p>
<p class="text-sm font-light italic">
{#if prevMiniSet.load !== miniSet.load}
<span class="text-muted-foreground">{prevMiniSet.load} -&gt;</span>
{/if}
{miniSet.load}
</p>
<p class="text-sm font-light italic">
{#if prevMiniSet.RIR !== miniSet.RIR}
<span class="text-muted-foreground">{prevMiniSet.RIR} -&gt;</span>
{/if}
{miniSet.RIR}
</p>
<span class="flex w-full items-center justify-end gap-1 text-xs font-light italic">
{#if typeof miniSetVolumeChange === 'number'}
<span>{miniSetVolumeChange?.toFixed(2)}%</span>
{#if miniSetVolumeChange < 0}
<DownIcon class="justify-self-end" />
{:else if miniSetVolumeChange > 0}
<UpIcon class="justify-self-end" />
{:else}
<Minus class="justify-self-end" />
{/if}
{/if}
</span>
{:else}
<Separator />
<span class="text-center text-sm text-muted-foreground">new mini set</span>
<Separator />
<span></span>
{/if}
{/each}
{:else}
<Separator />
<span class="text-center text-sm text-muted-foreground">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Separator } from '$lib/components/ui/separator';
import { solveBergerFormula, type WorkoutExerciseInProgress } from '$lib/utils/workoutUtils';
import {
cleanupInProgressMiniSets,
solveBergerFormula,
type WorkoutExerciseInProgress
} from '$lib/utils/workoutUtils';
import CheckIcon from 'virtual:icons/lucide/check';
import RemoveIcon from 'virtual:icons/lucide/minus';
import EditIcon from 'virtual:icons/lucide/pencil';
Expand Down Expand Up @@ -111,11 +115,17 @@
solveBergerFormula({
variableToSolve: 'NewReps',
knownValues: {
oldSet: { reps: exerciseSet.reps, load: oldLoad, RIR: exerciseSet.RIR },
newSet: { load: newLoad, RIR: exerciseSet.RIR },
oldSet: {
reps: exerciseSet.reps,
load: oldLoad,
RIR: exerciseSet.RIR,
miniSets: cleanupInProgressMiniSets(exerciseSet.miniSets)
},
newSet: { load: newLoad, RIR: exerciseSet.RIR, miniSets: cleanupInProgressMiniSets(exerciseSet.miniSets) },
oldUserBodyweight: workoutRunes.previousWorkoutData?.userBodyweight,
newUserBodyweight: workoutRunes.workoutData?.userBodyweight as number,
bodyweightFraction: exercise.bodyweightFraction ?? null
bodyweightFraction: exercise.bodyweightFraction ?? null,
overloadPercentage: 0
}
})
);
Expand All @@ -131,8 +141,8 @@
solveBergerFormula({
variableToSolve: 'NewReps',
knownValues: {
oldSet: { reps: set.reps, load: oldLoad, RIR: set.RIR },
newSet: { load: newLoad, RIR: set.RIR },
oldSet: { reps: set.reps, load: oldLoad, RIR: set.RIR, miniSets: cleanupInProgressMiniSets(set.miniSets) },
newSet: { load: newLoad, RIR: set.RIR, miniSets: cleanupInProgressMiniSets(set.miniSets) },
oldUserBodyweight: workoutRunes.previousWorkoutData?.userBodyweight,
newUserBodyweight: workoutRunes.workoutData?.userBodyweight as number,
bodyweightFraction: exercise.bodyweightFraction ?? null,
Expand All @@ -144,8 +154,8 @@
extraOverloadAchieved += solveBergerFormula({
variableToSolve: 'OverloadPercentage',
knownValues: {
oldSet: { reps: set.reps, load: oldLoad, RIR: set.RIR },
newSet: { reps: newReps, load: newLoad, RIR: set.RIR },
oldSet: { reps: set.reps, load: oldLoad, RIR: set.RIR, miniSets: cleanupInProgressMiniSets(set.miniSets) },
newSet: { reps: newReps, load: newLoad, RIR: set.RIR, miniSets: cleanupInProgressMiniSets(set.miniSets) },
oldUserBodyweight: workoutRunes.previousWorkoutData?.userBodyweight,
newUserBodyweight: workoutRunes.workoutData?.userBodyweight as number,
bodyweightFraction: exercise.bodyweightFraction ?? null
Expand Down
2 changes: 1 addition & 1 deletion src/routes/workouts/manage/overview/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
}}
/>
{:else}
<span class="muted-textbox">No previous workout available to compare</span>
<div class="muted-text-box">No previous workout available to compare</div>
{/if}
</Tabs.Content>
<Tabs.Content class="rounded-md border bg-card p-4" value="basic">
Expand Down

0 comments on commit ce3129f

Please sign in to comment.