518 lines
13 KiB
Markdown
518 lines
13 KiB
Markdown
# Progressive Overload-algoritmer — Research för Gravl
|
||
|
||
## Vad är Progressive Overload?
|
||
|
||
> "Progressive overload is the gradual increase of stress placed on the body during training. To continue building strength and muscle, you must progressively increase the demands on your musculoskeletal system."
|
||
|
||
**Grundprincipen:** Om du gör samma träning med samma vikter, reps och sets vecka efter vecka har kroppen ingen anledning att anpassa sig.
|
||
|
||
---
|
||
|
||
## Progressionsmetoder
|
||
|
||
### 1. Vikt-progression (Linear)
|
||
|
||
**Enklast och mest effektiv för nybörjare/intermediates**
|
||
|
||
```
|
||
Vecka 1: Bänkpress 60kg x 8,8,8
|
||
Vecka 2: Bänkpress 62.5kg x 8,8,8
|
||
Vecka 3: Bänkpress 65kg x 8,8,8
|
||
...
|
||
```
|
||
|
||
**Typiska ökningar:**
|
||
| Övning | Ökning per pass |
|
||
|--------|-----------------|
|
||
| Squat/Deadlift | +2.5-5 kg |
|
||
| Bench/Row/OHP | +1.25-2.5 kg |
|
||
| Isolation (curls, etc.) | +1-2 kg |
|
||
|
||
### 2. Rep-progression (Double Progression)
|
||
|
||
**När du inte kan öka vikt varje vecka**
|
||
|
||
```
|
||
Mål: 3x8-12 reps
|
||
|
||
Vecka 1: 60kg x 8,8,8 (låg end)
|
||
Vecka 2: 60kg x 9,9,8
|
||
Vecka 3: 60kg x 10,10,10
|
||
Vecka 4: 60kg x 12,11,11
|
||
Vecka 5: 62.5kg x 8,8,8 (öka vikt, börja om)
|
||
```
|
||
|
||
**Regel:** Öka vikt när alla sets når övre rep-gränsen.
|
||
|
||
### 3. Set-progression
|
||
|
||
```
|
||
Vecka 1: 60kg x 8,8,8 (3 sets)
|
||
Vecka 2: 60kg x 8,8,8,8 (4 sets)
|
||
Vecka 3: 62.5kg x 8,8,8 (tillbaka till 3 sets, ny vikt)
|
||
```
|
||
|
||
### 4. RPE/RIR-baserad Autoregulation
|
||
|
||
**RPE = Rate of Perceived Exertion (1-10)**
|
||
**RIR = Reps in Reserve**
|
||
|
||
| RPE | RIR | Beskrivning |
|
||
|-----|-----|-------------|
|
||
| 10 | 0 | Failure (kunde inte gjort fler) |
|
||
| 9.5 | 0.5 | Kanske 1 till med dålig form |
|
||
| 9 | 1 | 1 rep kvar |
|
||
| 8.5 | 1.5 | 1-2 reps kvar |
|
||
| 8 | 2 | 2 reps kvar |
|
||
| 7 | 3 | 3 reps kvar |
|
||
| 6 | 4 | Uppvärmning |
|
||
|
||
**Konvertering:** `RPE = 10 - RIR`
|
||
|
||
**Användning:**
|
||
```
|
||
Målsättning: 3x8 @ RPE 8
|
||
|
||
Set 1: 80kg x 8 @ RPE 7 → för lätt, öka
|
||
Set 2: 82.5kg x 8 @ RPE 8 → perfekt
|
||
Set 3: 82.5kg x 8 @ RPE 9 → trötthet, behåll vikt
|
||
```
|
||
|
||
---
|
||
|
||
## 1RM-beräkning
|
||
|
||
### Populära formler
|
||
|
||
#### Epley Formula (mest använd)
|
||
|
||
```
|
||
1RM = weight × (1 + reps/30)
|
||
```
|
||
|
||
**Exempel:** 80kg × 10 reps
|
||
```
|
||
1RM = 80 × (1 + 10/30) = 80 × 1.333 = 106.7 kg
|
||
```
|
||
|
||
#### Brzycki Formula
|
||
|
||
```
|
||
1RM = weight × (36 / (37 - reps))
|
||
```
|
||
|
||
**Exempel:** 80kg × 10 reps
|
||
```
|
||
1RM = 80 × (36 / (37 - 10)) = 80 × 1.333 = 106.7 kg
|
||
```
|
||
|
||
#### Lander Formula
|
||
|
||
```
|
||
1RM = weight × (100 / (101.3 - 2.67 × reps))
|
||
```
|
||
|
||
### Rep Max Tabell (% av 1RM)
|
||
|
||
| Reps | % av 1RM | Vikt (om 1RM = 100kg) |
|
||
|------|----------|----------------------|
|
||
| 1 | 100% | 100 kg |
|
||
| 2 | 94% | 94 kg |
|
||
| 3 | 91% | 91 kg |
|
||
| 4 | 88% | 88 kg |
|
||
| 5 | 86% | 86 kg |
|
||
| 6 | 83% | 83 kg |
|
||
| 7 | 81% | 81 kg |
|
||
| 8 | 79% | 79 kg |
|
||
| 9 | 77% | 77 kg |
|
||
| 10 | 75% | 75 kg |
|
||
| 12 | 70% | 70 kg |
|
||
| 15 | 65% | 65 kg |
|
||
|
||
---
|
||
|
||
## Progressionsalgoritmer för Gravl
|
||
|
||
### Algoritm 1: Simple Linear (Nybörjare)
|
||
|
||
```python
|
||
def calculate_next_weight(exercise, last_workout):
|
||
"""
|
||
Enkel linjär progression.
|
||
Om alla sets klarades → öka vikt.
|
||
"""
|
||
target_reps = exercise.target_reps # ex: 8
|
||
achieved_reps = last_workout.reps # ex: [8, 8, 8]
|
||
|
||
# Alla sets klarade?
|
||
if all(r >= target_reps for r in achieved_reps):
|
||
increment = get_increment(exercise.type)
|
||
return last_workout.weight + increment
|
||
else:
|
||
return last_workout.weight # Repetera samma vikt
|
||
|
||
def get_increment(exercise_type):
|
||
"""Standardökningar baserat på övningstyp."""
|
||
increments = {
|
||
'compound_lower': 2.5, # Squat, Deadlift
|
||
'compound_upper': 1.25, # Bench, OHP, Row
|
||
'isolation': 1.0, # Curls, Extensions
|
||
}
|
||
return increments.get(exercise_type, 1.25)
|
||
```
|
||
|
||
### Algoritm 2: Double Progression (Rep Range)
|
||
|
||
```python
|
||
def calculate_next_weight_double(exercise, last_workout):
|
||
"""
|
||
Double progression med rep range (ex: 8-12 reps).
|
||
Öka vikt när alla sets når övre gränsen.
|
||
"""
|
||
min_reps = exercise.min_reps # ex: 8
|
||
max_reps = exercise.max_reps # ex: 12
|
||
achieved_reps = last_workout.reps
|
||
|
||
# Alla sets på max reps?
|
||
if all(r >= max_reps for r in achieved_reps):
|
||
increment = get_increment(exercise.type)
|
||
return {
|
||
'weight': last_workout.weight + increment,
|
||
'target_reps': min_reps # Börja om på min_reps
|
||
}
|
||
# Alla sets klarade min_reps?
|
||
elif all(r >= min_reps for r in achieved_reps):
|
||
return {
|
||
'weight': last_workout.weight,
|
||
'target_reps': min(max(achieved_reps) + 1, max_reps)
|
||
}
|
||
else:
|
||
# Missade reps, behåll allt
|
||
return {
|
||
'weight': last_workout.weight,
|
||
'target_reps': min_reps
|
||
}
|
||
```
|
||
|
||
### Algoritm 3: RPE-baserad Autoregulation
|
||
|
||
```python
|
||
def calculate_next_weight_rpe(exercise, last_workout):
|
||
"""
|
||
RPE-baserad progression.
|
||
Justerar vikt baserat på hur hårt det kändes.
|
||
"""
|
||
target_rpe = exercise.target_rpe # ex: 8
|
||
achieved_rpe = last_workout.rpe # ex: [7, 8, 9]
|
||
avg_rpe = sum(achieved_rpe) / len(achieved_rpe)
|
||
|
||
# Under target RPE → för lätt, öka
|
||
if avg_rpe < target_rpe - 0.5:
|
||
adjustment = (target_rpe - avg_rpe) * 2.5 # ~2.5kg per RPE
|
||
return last_workout.weight + adjustment
|
||
|
||
# Över target RPE → för tungt, minska
|
||
elif avg_rpe > target_rpe + 0.5:
|
||
adjustment = (avg_rpe - target_rpe) * 2.5
|
||
return last_workout.weight - adjustment
|
||
|
||
# Inom range → perfekt, små ökning
|
||
else:
|
||
return last_workout.weight + get_increment(exercise.type)
|
||
```
|
||
|
||
### Algoritm 4: Hybrid (Gravl Recommendation)
|
||
|
||
```python
|
||
def calculate_progression(exercise, history, user):
|
||
"""
|
||
Hybrid-algoritm som kombinerar flera metoder.
|
||
|
||
1. Nybörjare: Linear progression
|
||
2. Intermediate: Double progression
|
||
3. Avancerad: RPE-baserad
|
||
|
||
Med säkerhetschecks och platå-hantering.
|
||
"""
|
||
last_workout = history[-1] if history else None
|
||
|
||
if not last_workout:
|
||
return estimate_starting_weight(exercise, user)
|
||
|
||
# Välj metod baserat på erfarenhet
|
||
if user.experience == 'beginner':
|
||
return linear_progression(exercise, last_workout)
|
||
elif user.experience == 'intermediate':
|
||
return double_progression(exercise, last_workout)
|
||
else:
|
||
return rpe_progression(exercise, last_workout)
|
||
|
||
def estimate_starting_weight(exercise, user):
|
||
"""
|
||
Estimera startvikt för ny användare.
|
||
Baserat på kroppsvikt och erfarenhet.
|
||
"""
|
||
bodyweight = user.weight_kg
|
||
|
||
# Typiska ratio för 1RM baserat på erfarenhet
|
||
ratios = {
|
||
'beginner': {
|
||
'squat': 0.5,
|
||
'bench': 0.4,
|
||
'deadlift': 0.6,
|
||
'ohp': 0.25,
|
||
'row': 0.35,
|
||
},
|
||
'intermediate': {
|
||
'squat': 1.0,
|
||
'bench': 0.75,
|
||
'deadlift': 1.25,
|
||
'ohp': 0.5,
|
||
'row': 0.6,
|
||
}
|
||
}
|
||
|
||
ratio = ratios.get(user.experience, ratios['beginner'])
|
||
estimated_1rm = bodyweight * ratio.get(exercise.base_type, 0.5)
|
||
|
||
# Börja på ~65% av estimated 1RM (för 10 reps)
|
||
starting_weight = estimated_1rm * 0.65
|
||
|
||
# Avrunda till närmaste 2.5kg
|
||
return round(starting_weight / 2.5) * 2.5
|
||
```
|
||
|
||
---
|
||
|
||
## Platå-hantering
|
||
|
||
### Detektera platå
|
||
|
||
```python
|
||
def detect_plateau(history, window=4):
|
||
"""
|
||
Platå = ingen progress under [window] pass.
|
||
"""
|
||
if len(history) < window:
|
||
return False
|
||
|
||
recent = history[-window:]
|
||
weights = [w.weight for w in recent]
|
||
|
||
# Ingen viktökning?
|
||
if max(weights) <= min(weights):
|
||
# Kolla även reps
|
||
total_reps = [sum(w.reps) for w in recent]
|
||
if max(total_reps) <= min(total_reps):
|
||
return True
|
||
|
||
return False
|
||
```
|
||
|
||
### Platå-strategier
|
||
|
||
```python
|
||
def handle_plateau(exercise, history, strategy='deload'):
|
||
"""
|
||
Hantera platå med olika strategier.
|
||
"""
|
||
last_weight = history[-1].weight
|
||
|
||
if strategy == 'deload':
|
||
# Sänk vikt med 10-15%, bygg upp igen
|
||
return {
|
||
'weight': last_weight * 0.85,
|
||
'reason': 'Deload: Sänker vikt för att bygga upp igen'
|
||
}
|
||
|
||
elif strategy == 'rep_change':
|
||
# Byt rep-range (ex: 5x5 → 3x8)
|
||
return {
|
||
'weight': last_weight * 0.9,
|
||
'reps': 8,
|
||
'sets': 3,
|
||
'reason': 'Ny rep-range för att bryta platå'
|
||
}
|
||
|
||
elif strategy == 'exercise_swap':
|
||
# Byt övning temporärt
|
||
alternatives = get_alternatives(exercise)
|
||
return {
|
||
'exercise': alternatives[0],
|
||
'reason': 'Byter övning för variation'
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Deload-strategier
|
||
|
||
### Vad är Deload?
|
||
|
||
En planerad period med reducerad intensitet för recovery.
|
||
|
||
### Typer av Deload
|
||
|
||
| Typ | Vikt | Volym | När |
|
||
|-----|------|-------|-----|
|
||
| **Light Deload** | -10% | Same | Var 4:e vecka |
|
||
| **Volume Deload** | Same | -40% | Vid trött |
|
||
| **Full Deload** | -20% | -50% | Efter tuffa block |
|
||
|
||
### Automatisk Deload
|
||
|
||
```python
|
||
def should_deload(user, history):
|
||
"""
|
||
Avgör om deload behövs.
|
||
"""
|
||
weeks_since_deload = user.weeks_since_deload
|
||
|
||
# Schemalagd deload var 4-6 vecka
|
||
if weeks_since_deload >= 5:
|
||
return True
|
||
|
||
# RPE konsekvent hög
|
||
recent_rpe = [h.avg_rpe for h in history[-4:]]
|
||
if len(recent_rpe) >= 4 and all(r >= 9 for r in recent_rpe):
|
||
return True
|
||
|
||
# Missade reps ökar
|
||
recent_misses = count_missed_reps(history[-4:])
|
||
if recent_misses > 5:
|
||
return True
|
||
|
||
return False
|
||
```
|
||
|
||
---
|
||
|
||
## UX för Progression
|
||
|
||
### Visa progression transparent
|
||
|
||
```
|
||
┌────────────────────────────────────────────────┐
|
||
│ Bänkpress Nästa: 85kg │
|
||
├────────────────────────────────────────────────┤
|
||
│ │
|
||
│ Förra passet: 82.5kg x 8, 8, 8 │
|
||
│ Alla sets klarade! → Ökar med 2.5kg │
|
||
│ │
|
||
│ ┌──────────────────────────────────────────┐ │
|
||
│ │ [Progressionsgraf senaste 8 veckor] │ │
|
||
│ │ 85 ─ ● │ │
|
||
│ │ 80 ─ ● ● │ │
|
||
│ │ 75 ─ ● ● │ │
|
||
│ │ 70 ─ ● ● │ │
|
||
│ │ W1 W2 W3 W4 W5 W6 W7 W8 │ │
|
||
│ └──────────────────────────────────────────┘ │
|
||
│ │
|
||
│ [Godkänn 85kg] [Justera manuellt] │
|
||
└────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Förklara logiken
|
||
|
||
```
|
||
💡 Varför ökar vikten?
|
||
───────────────────────
|
||
Du tog 82.5kg x 8, 8, 8 förra passet.
|
||
Mål var 8-10 reps.
|
||
→ Alla sets klarade → Dags att öka!
|
||
→ +2.5kg är standard för överkropps-compound.
|
||
```
|
||
|
||
---
|
||
|
||
## Implementation för Gravl
|
||
|
||
### Database Schema
|
||
|
||
```sql
|
||
CREATE TABLE progression_settings (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INT REFERENCES users(id),
|
||
exercise_id INT REFERENCES exercises(id),
|
||
|
||
-- Progression method
|
||
method VARCHAR(20) DEFAULT 'double', -- 'linear', 'double', 'rpe'
|
||
|
||
-- Rep range
|
||
min_reps INT DEFAULT 8,
|
||
max_reps INT DEFAULT 12,
|
||
target_sets INT DEFAULT 3,
|
||
|
||
-- Increments
|
||
weight_increment DECIMAL(4,2) DEFAULT 2.5,
|
||
|
||
-- Deload settings
|
||
deload_frequency_weeks INT DEFAULT 5,
|
||
deload_percentage DECIMAL(3,2) DEFAULT 0.85,
|
||
|
||
-- RPE settings
|
||
target_rpe DECIMAL(3,1) DEFAULT 8.0,
|
||
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
CREATE TABLE progression_history (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INT REFERENCES users(id),
|
||
exercise_id INT REFERENCES exercises(id),
|
||
workout_id INT REFERENCES workouts(id),
|
||
|
||
weight DECIMAL(6,2),
|
||
reps INT[],
|
||
rpe DECIMAL(3,1)[],
|
||
|
||
-- Computed
|
||
estimated_1rm DECIMAL(6,2),
|
||
total_volume DECIMAL(10,2), -- weight × total_reps
|
||
|
||
performed_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### API Endpoint
|
||
|
||
```python
|
||
@app.get("/api/exercises/{exercise_id}/next-weight")
|
||
def get_next_weight(exercise_id: int, user: User):
|
||
"""
|
||
Returnerar nästa rekommenderade vikt för en övning.
|
||
"""
|
||
history = get_exercise_history(user.id, exercise_id)
|
||
settings = get_progression_settings(user.id, exercise_id)
|
||
|
||
next_weight = calculate_progression(
|
||
exercise=get_exercise(exercise_id),
|
||
history=history,
|
||
settings=settings,
|
||
user=user
|
||
)
|
||
|
||
return {
|
||
"exercise_id": exercise_id,
|
||
"recommended_weight": next_weight.weight,
|
||
"recommended_reps": next_weight.reps,
|
||
"reason": next_weight.reason,
|
||
"previous": history[-1] if history else None,
|
||
"progression_graph": get_progression_graph(history)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Källor
|
||
|
||
- Setgraph, Zing Coach, FitnessAI — Progressive overload calculators
|
||
- JEFIT, RippedBody — RPE/RIR guides
|
||
- Stronglifts — Increment settings
|
||
- NASM, VBTCoach — 1RM formulas
|
||
- Alpha Progression, StrengthLog — Rep max tables
|
||
|
||
---
|
||
|
||
*Sammanställt 2026-02-15 av Bumblebee 🐝*
|