Skip to content

Commit b4458a0

Browse files
committed
Fix progression model, remove forced deload, cap sets at 3, clean CSV
Progression: - Same exercises and sets every week — only RIR changes (3-4 → 2-3 → 2 → 1-2) - Load increases attempted when top of rep range hit at target RIR - No set ramping across weeks (that was wrong per the prompt) Deload: - Removed forced week 5 deload — deload is reactive per the algorithm rules (triggered by 2+ fatigue signals, not a fixed schedule) - 4 training weeks only, deload instructions remain as reference Sets: - Capped at 3 sets per exercise maximum, enforced across all splits/weeks - Verified by test_max_3_sets_per_exercise CSV: - Clean 2-column format matching user's preferred layout: Column A = exercise name, Column B = "3x4-6 @3-4 RIR" - Session headers as single-cell rows https://claude.ai/code/session_01Lo7Z7GoRzG6hZkKwrxnXVk
1 parent 5c911b4 commit b4458a0

File tree

12 files changed

+61
-107
lines changed

12 files changed

+61
-107
lines changed

ironforge/data/constants.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class VolumeLandmarks:
6969
PER_SESSION_FRACTIONAL_CEILING = 11
7070

7171
# Maximum sets per exercise per session
72-
MAX_SETS_PER_EXERCISE = 5
72+
MAX_SETS_PER_EXERCISE = 3
7373

7474
# Compound spillover contributions (fractional sets)
7575
# Maps from exercise muscle group categories to secondary contributions
@@ -124,8 +124,5 @@ class VolumeLandmarks:
124124
TrainingLevel.ADVANCED: (3, 5),
125125
}
126126

127-
# Mesocycle length (weeks including deload)
128-
MESO_LENGTH = 5
129-
130-
# Volume ramp: sets to add per muscle per week during mesocycle
131-
VOLUME_RAMP_PER_WEEK = 1.5
127+
# Mesocycle length (training weeks only — no forced deload)
128+
MESO_LENGTH = 4
-141 Bytes
Binary file not shown.
-143 Bytes
Binary file not shown.
-259 Bytes
Binary file not shown.

ironforge/engine/load.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
from ironforge.data.muscle_groups import Tier, TrainingLevel, Sex
44
from ironforge.data.constants import (
5-
REP_RANGES, REP_RANGES_FEMALE, RIR_BY_WEEK, RIR_BEGINNER, RIR_DELOAD,
6-
REST_PERIODS,
5+
REP_RANGES, REP_RANGES_FEMALE, RIR_BY_WEEK, RIR_BEGINNER, REST_PERIODS,
76
)
87
from ironforge.program.models import LoadPrescription
98

@@ -14,33 +13,27 @@ def assign_loading(
1413
sex: Sex,
1514
week: int = 1,
1615
sets: int = 3,
17-
is_deload: bool = False,
1816
) -> LoadPrescription:
1917
"""Create a LoadPrescription for a given exercise tier and context."""
20-
# Rep ranges
2118
ranges = REP_RANGES_FEMALE if sex == Sex.FEMALE else REP_RANGES
2219
rep_low, rep_high = ranges[tier]
2320

24-
# RIR
25-
if is_deload:
26-
rir = RIR_DELOAD
27-
elif level == TrainingLevel.BEGINNER:
21+
# RIR drops across the mesocycle: week 1=3, week 2=2, week 3=2, week 4=1
22+
if level == TrainingLevel.BEGINNER:
2823
rir = max(RIR_BEGINNER, RIR_BY_WEEK.get(week, 2))
2924
else:
3025
rir = RIR_BY_WEEK.get(week, 2)
3126

32-
# Compound lifts early sets get +1 RIR in week 1
33-
if tier == Tier.T1 and week == 1 and not is_deload:
27+
# T1 compounds get +1 RIR buffer in week 1
28+
if tier == Tier.T1 and week == 1:
3429
rir = min(rir + 1, 4)
3530

36-
# Rest periods
3731
rest_low, rest_high = REST_PERIODS[tier]
38-
rest = rest_high
3932

4033
return LoadPrescription(
4134
sets=sets,
4235
rep_low=rep_low,
4336
rep_high=rep_high,
4437
rir=rir,
45-
rest_seconds=rest,
38+
rest_seconds=rest_high,
4639
)

ironforge/engine/periodization.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,31 @@ def build_mesocycle_overview(
1010
profile: UserProfile,
1111
levels: dict,
1212
) -> list[MesocycleOverview]:
13-
"""Build the 5-week mesocycle overview (weeks 1-4 + deload week 5)."""
13+
"""Build the 4-week mesocycle overview. Deload is reactive, not scheduled."""
1414
return [
1515
MesocycleOverview(
1616
week=1,
17-
volume_description="MEV (~starting sets per muscle)",
17+
volume_description="Same sets as programmed",
1818
rir="3-4 RIR",
1919
load_note="Establish working weights",
2020
),
2121
MesocycleOverview(
2222
week=2,
23-
volume_description="+1-2 sets per muscle",
23+
volume_description="Same sets",
2424
rir="2-3 RIR",
25-
load_note="+2.5-5 lbs on compounds",
25+
load_note="Attempt +2.5-5 lbs if top of rep range hit",
2626
),
2727
MesocycleOverview(
2828
week=3,
29-
volume_description="+1-2 sets per muscle",
29+
volume_description="Same sets",
3030
rir="2 RIR",
31-
load_note="+2.5-5 lbs on compounds",
31+
load_note="Attempt +2.5-5 lbs if top of rep range hit",
3232
),
3333
MesocycleOverview(
3434
week=4,
35-
volume_description="Near MRV (peak volume)",
35+
volume_description="Same sets",
3636
rir="1-2 RIR",
37-
load_note="Maintain or micro-increase",
38-
),
39-
MesocycleOverview(
40-
week=5,
41-
volume_description="Deload: ~50% of Week 4 volume",
42-
rir="4-5 RIR",
43-
load_note="Reduce load ~10% OR add 2-3 RIR",
37+
load_note="Push hard — last week before reassessment",
4438
),
4539
]
4640

ironforge/engine/selection.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,16 @@ def select_exercises_for_session(
9696
volume_targets: list[VolumeTarget],
9797
session_variant: int = 0,
9898
week: int = 1,
99-
is_deload: bool = False,
10099
) -> list[ProgrammedExercise]:
101100
"""Select exercises for a single session based on muscle focus and rules."""
102101
equip = profile.available_equipment
103102
exercises: list[ProgrammedExercise] = []
104103
used_names: set[str] = set()
105104
overall = profile.overall_level
106105

107-
# Volume adjustment by week:
108-
# Week 1 = MEV base, Weeks 2-4 = +1 set per exercise per week, Week 5 = deload (halved)
109-
extra_sets_per_exercise = 0 if is_deload else (week - 1) # 0, 1, 2, 3 for weeks 1-4
106+
# Sets stay constant across all weeks — progression is RIR only.
107+
# Week 1-4: same exercises, same sets, RIR drops (3-4 → 2-3 → 2 → 1-2).
108+
# Load increases attempted when top of rep range is hit at target RIR.
110109

111110
def _add(ex: ExerciseDefinition | None, muscle: VolumeMuscle,
112111
sets: int | None = None, notes: str = "") -> bool:
@@ -115,14 +114,9 @@ def _add(ex: ExerciseDefinition | None, muscle: VolumeMuscle,
115114
if sets is None:
116115
n_sessions = _count_muscle_in_sessions(muscle, all_sessions)
117116
sets = _sets_for_muscle(muscle, volume_targets, n_sessions)
118-
# Apply volume progression
119-
if is_deload:
120-
sets = max(1, sets // 2)
121-
else:
122-
sets = sets + extra_sets_per_exercise
123117
sets = min(sets, MAX_SETS_PER_EXERCISE)
124118
load = assign_loading(ex.tier, overall, profile.sex, week=week,
125-
sets=sets, is_deload=is_deload)
119+
sets=sets)
126120
# Avoid duplicate note fragments
127121
note_parts = []
128122
if notes:
32 Bytes
Binary file not shown.

ironforge/program/builder.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Program builder — orchestrates all engine modules into a complete Program."""
22

3+
from ironforge.data.constants import MESO_LENGTH
34
from ironforge.intake.profile import UserProfile
45
from ironforge.engine.classifier import classify
56
from ironforge.engine.volume import compute_volume
@@ -15,17 +16,11 @@
1516

1617
def build_program(profile: UserProfile) -> Program:
1718
"""Build a complete training program from a user profile."""
18-
# Step 1: Classify training level per movement pattern
1919
levels = classify(profile)
20-
21-
# Step 2: Compute volume targets
2220
volume_targets = compute_volume(profile, levels)
23-
24-
# Step 3: Plan frequency and split
2521
split = plan_frequency(profile, volume_targets, split_key=profile.split_key or None)
2622

27-
# Step 4: Build all 5 weeks (4 training + 1 deload)
28-
# Compute A/B variant mapping once (same exercises across weeks)
23+
# A/B variant mapping (same exercises across all weeks)
2924
focus_seen: dict[tuple, int] = {}
3025
variant_map: list[int] = []
3126
for template in split.sessions:
@@ -34,11 +29,10 @@ def build_program(profile: UserProfile) -> Program:
3429
focus_seen[focus_key] = variant + 1
3530
variant_map.append(variant)
3631

32+
# Build 4 training weeks — same exercises/sets, only RIR changes
3733
all_weeks: list[ProgramWeek] = []
38-
for week_num in range(1, 6):
39-
is_deload = week_num == 5
34+
for week_num in range(1, MESO_LENGTH + 1):
4035
sessions: list[ProgramSession] = []
41-
4236
for i, template in enumerate(split.sessions):
4337
exercises = select_exercises_for_session(
4438
template=template,
@@ -48,20 +42,15 @@ def build_program(profile: UserProfile) -> Program:
4842
volume_targets=volume_targets,
4943
session_variant=variant_map[i],
5044
week=week_num,
51-
is_deload=is_deload,
5245
)
53-
54-
if profile.prefers_supersets and not is_deload:
46+
if profile.prefers_supersets:
5547
exercises = pair_supersets(exercises, allow_supersets=True)
56-
5748
sessions.append(ProgramSession(
5849
day_label=template.day_label,
5950
exercises=exercises,
6051
))
61-
6252
all_weeks.append(ProgramWeek(week_number=week_num, sessions=sessions))
6353

64-
# Step 5: Build progression and deload instructions
6554
progression = build_progression_instructions(profile, levels)
6655
deload = build_deload_instructions(profile)
6756
meso_overview = build_mesocycle_overview(profile, levels)

ironforge/templates/program.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,17 +256,17 @@ <h1>YOUR PROGRAM</h1>
256256
<!-- 4. FULL MESOCYCLE — ALL WEEKS -->
257257
<div class="program-section">
258258
<div class="sec-header" onclick="toggleSection(this)">
259-
4. Full Mesocycle5 Weeks
259+
4. Training Program4 Weeks
260260
<span class="chevron">&#9660;</span>
261261
</div>
262262
<div class="sec-body">
263263

264264
<!-- Week tabs -->
265265
<div class="week-tabs">
266266
{% for week in program.weeks %}
267-
<div class="week-tab {% if loop.first %}active{% endif %} {% if week.week_number == 5 %}deload{% endif %}"
267+
<div class="week-tab {% if loop.first %}active{% endif %}"
268268
onclick="switchWeek(this, {{ loop.index0 }})">
269-
{% if week.week_number == 5 %}Deload{% else %}Wk {{ week.week_number }}{% endif %}
269+
Wk {{ week.week_number }}
270270
</div>
271271
{% endfor %}
272272
</div>

0 commit comments

Comments
 (0)