Files
gravl/.planning/research/11-progressive-overload.md
T

13 KiB
Raw Blame History

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)

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)

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

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)

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å

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

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

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

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

@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 🐝