Eccentric quadriceps exercises (gold standard). Heavy slow resistance training. Stretching of quadriceps, hamstrings, and calf. Hip and core strengthening.

Reading File
Finding Sources
Finding Sources
Searching PubMed

"patellar tendinopathy" AND "eccentric exercise" AND rehabilitation

Reading File
Reading File
Finding Sources
Finding Sources
Finding Sources
Searching PubMed

"patellar tendinopathy" AND "heavy slow resistance"

Reading Article

PMID: 42192475

Reading Article

PMID: 41452311

Finding Sources
Here is a thorough breakdown of the conservative rehabilitation protocol for Patellar Tendinopathy (Jumper's Knee):

Patellar Tendinopathy - Conservative Rehabilitation Protocol

Patellar tendinopathy involves a range of cellular changes to the patellar tendon - typically microscopic tears that initiate an inflammatory cascade - resulting from chronic, repetitive loading (running, jumping). The condition is also called "jumper's knee" and is most prevalent in basketball and volleyball athletes. (- ROSEN's Emergency Medicine, p. 723; - Miller's Review of Orthopaedics, 9th Ed., p. 364)
The four components listed form the backbone of evidence-based conservative management:

1. Eccentric Quadriceps Exercises (Gold Standard)

Mechanism: Eccentric loading applies tensile stress to the tendon during the lengthening phase of contraction. This stimulates tenocyte activity, promotes collagen realignment, and progressively loads the tendon toward structural remodeling.
The Decline Squat Protocol (most studied):
  • Performed on a 25-degree decline board - this angle preferentially loads the patellar tendon throughout the full ROM
  • 3 sets x 15 reps, twice daily, including through pain (up to ~5/10 on NRS)
  • Progress by adding load (weighted vest, barbell) as tolerated
  • Typically continued over 12 weeks
Why "gold standard":
  • Multiple trials have demonstrated that eccentric exercise significantly improves VISA-P scores (the standard outcome measure for patellar tendinopathy)
  • The single-leg decline squat (SLDS) is both a treatment tool and a pain provocation test for monitoring progress
  • Miller's Review of Orthopaedics explicitly lists "strengthening including eccentric exercise" as the primary physical therapy approach (p. 364)

2. Heavy Slow Resistance (HSR) Training

Mechanism: HSR uses slow tempo (typically 3 seconds concentric / 3 seconds eccentric) at high loads (70-85% of 1 rep max), allowing both tendon and muscle adaptation without the ballistic stress of sport-specific loading. It stimulates collagen synthesis and tendon stiffness.
Protocol:
  • Exercises: leg press, hack squat, knee extension
  • Load: 15 RM (week 1-2) → 12 RM (week 3-4) → 10 RM (week 5-6) → 8 RM (week 7-8) → 6 RM (week 9-12)
  • 3-4 sets, 3x per week with rest days between sessions
Evidence vs. Eccentric:
A 2026 network meta-analysis (Liu et al., BMC Sports Sci Med Rehabil) - the most current high-quality synthesis (17 RCTs, 456 participants) - found that no exercise intervention demonstrated statistical superiority over HSR for short-term VISA-P improvement. Eccentric overload showed the most favorable point estimate (SMD 0.33, 95% CI -0.87 to 1.52), but this was not statistically significant. HSR is therefore now considered an equivalent reference standard, not inferior to eccentric training. (PMID: 42192475)
A 2025 RCT (Hjortshoej et al., Scand J Med Sci Sports) confirmed that HSR produces comparable pain reduction and VISA-P improvement to blood flow restriction training at 12 weeks and 52 weeks. (PMID: 41452311)
Clinical implication: HSR is increasingly preferred over pure eccentric training because it is better tolerated, easier to progressively overload, and produces equivalent outcomes. Many current protocols combine both.

3. Stretching of Quadriceps, Hamstrings, and Calves

Rationale: Tightness in the posterior chain (hamstrings, calf/gastrocnemius-soleus) and anterior thigh (quadriceps) increases the mechanical load on the patellar tendon by:
  • Altering knee kinematics and patellofemoral contact pressures
  • Reducing shock absorption at the ankle (tight calves → more knee stress during landing)
  • Decreasing overall lower limb flexibility, causing compensatory overload of the patellar tendon
Program:
MuscleStretchDuration
QuadricepsStanding heel-to-buttock or prone knee flexion3 x 30-60 sec
HamstringsSupine straight-leg raise or seated forward bend3 x 30-60 sec
GastrocnemiusStraight-leg wall stretch3 x 30-60 sec
SoleusBent-knee wall stretch3 x 30-60 sec
Stretching is performed after warm-up or exercise, not as a standalone treatment. It is an adjunct to loading, not a substitute. Campbell's Operative Orthopaedics notes that tightness of posterior lower limb muscles has an established relationship with overuse tendinopathies (p. 1224).

4. Hip and Core Strengthening

Rationale: Patellar tendinopathy often has an underlying biomechanical driver:
  • Weak hip abductors/external rotators → increased dynamic valgus at the knee → altered patellar tracking → excess patellar tendon stress
  • Weak core → poor pelvic stability during single-leg loading → increased ground reaction force transmitted to the knee
Key exercises:
  • Hip abductors/ER: Clamshells, side-lying hip abduction, resistance band walks, single-leg hip thrust
  • Gluteus medius/maximus: Single-leg squats, lateral band walks, Romanian deadlift
  • Core: Dead bug, plank variations, Pallof press
Proximal control directly reduces the peak tendon load during landing and deceleration - which are the primary injury mechanisms in jumping athletes.

Full Protocol Summary

PhaseWeeksFocus
1 - Isometric loading1-2Pain management; wall sits, isometric leg press (5 x 45 sec) - high analgesic effect
2 - Isotonic loading (HSR/eccentric)3-12Progressive loading per above; decline squats and/or HSR
3 - Energy storage8-16Plyometrics, sport-specific loading (hopping, bounding)
4 - Return to sport12-24Gradual reintroduction; sport-specific drills, maintenance loading
Adjuncts used alongside (not instead of) exercise:
  • Patellar tendon strap / infrapatellar brace (offloads the tendon insertion during activity)
  • NSAIDs for short-term pain relief in the acute phase (Miller's Review, p. 364)
  • Extracorporeal shockwave therapy (ESWT) - evidence supports it as an adjunct for recalcitrant cases (PMID: 37662911)
  • No role for corticosteroid injection in patellar tendinopathy - may weaken the tendon (ROSEN's EM, p. 723)
  • Surgical debridement reserved for truly recalcitrant cases only

Key sources:

Create a 12-week patellar tendinopathy rehab tracker with exercise prescription sheets

Reading File
Running Command

mkdir -p /home/daytona/workspace/patellar-rehab && ls /home/daytona/workspace/pa…

Writing File

~/patellar-rehab/build_tracker.py

import openpyxl
from openpyxl.styles import (
    Font, PatternFill, Alignment, Border, Side, GradientFill
)
from openpyxl.utils import get_column_letter
from openpyxl.chart import BarChart, LineChart, Reference
from openpyxl.chart.series import SeriesLabel
from openpyxl.worksheet.datavalidation import DataValidation
import openpyxl.drawing.image

wb = openpyxl.Workbook()

# ─── Color Palette ───────────────────────────────────────────────────────────
C_NAVY      = "1B3A6B"   # dark navy (headers)
C_TEAL      = "2E86AB"   # mid teal (sub-headers)
C_SKYBLUE   = "A8DADC"   # light blue (row highlights)
C_ORANGE    = "E76F51"   # accent orange (warnings / important)
C_GREEN     = "52B788"   # green (progress / complete)
C_YELLOW    = "FFD166"   # yellow (input cells)
C_LTGRAY    = "F4F4F4"   # light gray (alternate rows)
C_WHITE     = "FFFFFF"
C_DARKTEXT  = "1A1A2E"
C_PHASE1    = "D4E8F0"   # phase 1 bg
C_PHASE2    = "D4EDD4"   # phase 2 bg
C_PHASE3    = "FFF3CD"   # phase 3 bg
C_PHASE4    = "FDDCDC"   # phase 4 bg

def fill(hex_color):
    return PatternFill("solid", fgColor=hex_color)

def font(bold=False, size=11, color=C_DARKTEXT, italic=False):
    return Font(name="Arial", bold=bold, size=size, color=color, italic=italic)

def center():
    return Alignment(horizontal="center", vertical="center", wrap_text=True)

def left():
    return Alignment(horizontal="left", vertical="center", wrap_text=True)

def thin_border():
    s = Side(style="thin", color="CCCCCC")
    return Border(left=s, right=s, top=s, bottom=s)

def thick_border():
    s = Side(style="medium", color=C_NAVY)
    return Border(left=s, right=s, top=s, bottom=s)

def apply_header(ws, row, col, text, bg=C_NAVY, fg=C_WHITE, sz=12, bold=True,
                 colspan=1, align="center"):
    cell = ws.cell(row=row, column=col, value=text)
    cell.font = Font(name="Arial", bold=bold, size=sz, color=fg)
    cell.fill = fill(bg)
    cell.alignment = Alignment(horizontal=align, vertical="center", wrap_text=True)
    cell.border = thin_border()
    if colspan > 1:
        ws.merge_cells(
            start_row=row, start_column=col,
            end_row=row, end_column=col + colspan - 1
        )

def set_col_widths(ws, widths_dict):
    for col_letter, width in widths_dict.items():
        ws.column_dimensions[col_letter].width = width

def alt_row_fill(ws, row, cols, even_row, color1=C_WHITE, color2=C_LTGRAY):
    bg = color2 if even_row else color1
    for c in range(1, cols + 1):
        cell = ws.cell(row=row, column=c)
        if not cell.fill or cell.fill.fgColor.rgb in ("00000000", C_WHITE, "FFFFFFFF"):
            cell.fill = fill(bg)

# ═════════════════════════════════════════════════════════════════════════════
# SHEET 1 — COVER
# ═════════════════════════════════════════════════════════════════════════════
ws1 = wb.active
ws1.title = "Cover"
ws1.sheet_view.showGridLines = False

ws1.row_dimensions[1].height = 10
ws1.row_dimensions[2].height = 60
ws1.row_dimensions[3].height = 30
ws1.row_dimensions[4].height = 18

# Title banner
ws1.merge_cells("A2:H2")
c = ws1["A2"]
c.value = "PATELLAR TENDINOPATHY  |  12-WEEK REHABILITATION TRACKER"
c.font = Font(name="Arial", bold=True, size=20, color=C_WHITE)
c.fill = fill(C_NAVY)
c.alignment = Alignment(horizontal="center", vertical="center")

ws1.merge_cells("A3:H3")
c = ws1["A3"]
c.value = "Evidence-Based Conservative Management Protocol"
c.font = Font(name="Arial", italic=True, size=13, color=C_WHITE)
c.fill = fill(C_TEAL)
c.alignment = Alignment(horizontal="center", vertical="center")

# Patient info section
info_start = 5
ws1.merge_cells(f"A{info_start}:H{info_start}")
c = ws1.cell(row=info_start, column=1, value="PATIENT INFORMATION")
c.font = Font(name="Arial", bold=True, size=12, color=C_WHITE)
c.fill = fill(C_NAVY)
c.alignment = Alignment(horizontal="left", vertical="center")
c.border = thin_border()
ws1.row_dimensions[info_start].height = 22

fields = [
    ("Patient Name:", "", "Date of Birth:", ""),
    ("Clinician / Physio:", "", "Start Date:", ""),
    ("Dominant Leg:", "", "Affected Side:", ""),
    ("Diagnosis Confirmed By:", "", "Imaging (US/MRI):", ""),
    ("VISA-P Score (Baseline):", "", "Pain NRS (Baseline):", ""),
    ("Short-Term Goal:", "", "Long-Term Goal:", ""),
]
for i, (l1, v1, l2, v2) in enumerate(fields):
    r = info_start + 1 + i
    ws1.row_dimensions[r].height = 20
    for col, val, is_label in [(1, l1, True),(2, v1, False),(5, l2, True),(6, v2, False)]:
        cell = ws1.cell(row=r, column=col, value=val)
        cell.font = Font(name="Arial", bold=is_label, size=11,
                         color=C_DARKTEXT if is_label else C_NAVY)
        cell.fill = fill(C_LTGRAY if is_label else C_YELLOW)
        cell.alignment = left()
        cell.border = thin_border()
    ws1.merge_cells(start_row=r, start_column=2, end_row=r, end_column=4)
    ws1.merge_cells(start_row=r, start_column=6, end_row=r, end_column=8)

# Protocol overview box
pr = info_start + len(fields) + 2
ws1.merge_cells(f"A{pr}:H{pr}")
c = ws1.cell(row=pr, column=1, value="PROTOCOL OVERVIEW")
c.font = Font(name="Arial", bold=True, size=12, color=C_WHITE)
c.fill = fill(C_TEAL)
c.alignment = Alignment(horizontal="left", vertical="center")
ws1.row_dimensions[pr].height = 22

phases = [
    ("PHASE 1", "Weeks 1-2", "Isometric Loading", "Pain modulation; tendon activation", C_PHASE1),
    ("PHASE 2", "Weeks 3-6", "Heavy Slow Resistance (HSR)", "Tendon loading & strength building", C_PHASE2),
    ("PHASE 3", "Weeks 7-10", "Eccentric Overload + HSR", "Collagen remodeling; progressive overload", C_PHASE3),
    ("PHASE 4", "Weeks 11-12", "Energy Storage / Return to Sport Prep", "Plyometrics; sport-specific loading", C_PHASE4),
]
headers = ["Phase", "Weeks", "Focus", "Goal", ""]
apply_header(ws1, pr+1, 1, "Phase", bg=C_NAVY, sz=11)
apply_header(ws1, pr+1, 2, "Weeks", bg=C_NAVY, sz=11)
apply_header(ws1, pr+1, 3, "Focus", bg=C_NAVY, sz=11, colspan=3)
apply_header(ws1, pr+1, 6, "Goal", bg=C_NAVY, sz=11, colspan=3)
ws1.row_dimensions[pr+1].height = 20

for i, (ph, wk, focus, goal, bg_col) in enumerate(phases):
    r = pr + 2 + i
    ws1.row_dimensions[r].height = 22
    for col, val in [(1, ph),(2, wk)]:
        cell = ws1.cell(row=r, column=col, value=val)
        cell.font = Font(name="Arial", bold=True, size=11, color=C_DARKTEXT)
        cell.fill = fill(bg_col)
        cell.alignment = center()
        cell.border = thin_border()
    ws1.merge_cells(start_row=r, start_column=3, end_row=r, end_column=5)
    c = ws1.cell(row=r, column=3, value=focus)
    c.font = Font(name="Arial", size=11, color=C_DARKTEXT)
    c.fill = fill(bg_col)
    c.alignment = left()
    c.border = thin_border()
    ws1.merge_cells(start_row=r, start_column=6, end_row=r, end_column=8)
    c = ws1.cell(row=r, column=6, value=goal)
    c.font = Font(name="Arial", size=11, color=C_DARKTEXT)
    c.fill = fill(bg_col)
    c.alignment = left()
    c.border = thin_border()

# Legend
lr = pr + len(phases) + 3
ws1.merge_cells(f"A{lr}:H{lr}")
c = ws1.cell(row=lr, column=1,
    value="INPUT CELLS (yellow) are for patient/clinician data entry. All other cells are locked reference content.")
c.font = Font(name="Arial", italic=True, size=10, color="888888")
c.alignment = left()

# Note
nr = lr + 1
ws1.merge_cells(f"A{nr}:H{nr}")
c = ws1.cell(row=nr, column=1,
    value="Reference: Liu et al. 2026 (BMC Sports Sci Med Rehabil) | Hjortshoej et al. 2025 (Scand J Med Sci Sports) | Rosen's EM | Miller's Orthopaedics")
c.font = Font(name="Arial", italic=True, size=9, color="AAAAAA")
c.alignment = left()

# Tabs guide
tabs_r = nr + 2
ws1.merge_cells(f"A{tabs_r}:H{tabs_r}")
c = ws1.cell(row=tabs_r, column=1, value="WORKBOOK TABS")
c.font = Font(name="Arial", bold=True, size=12, color=C_WHITE)
c.fill = fill(C_ORANGE)
c.alignment = left()
ws1.row_dimensions[tabs_r].height = 22

tabs = [
    ("Cover", "This sheet — patient info and protocol overview"),
    ("Phase Overview", "All 4 phases at a glance with progression rules"),
    ("Rx Phase 1 — Isometric", "Detailed exercise prescription: weeks 1-2"),
    ("Rx Phase 2 — HSR", "Detailed exercise prescription: weeks 3-6"),
    ("Rx Phase 3 — Eccentric+HSR", "Detailed exercise prescription: weeks 7-10"),
    ("Rx Phase 4 — Return to Sport", "Detailed exercise prescription: weeks 11-12"),
    ("Stretching & Hip-Core", "Full stretching and hip/core strengthening protocol"),
    ("12-Week Session Tracker", "Log every session: pain, load, sets, reps, notes"),
    ("Progress Dashboard", "Auto-calculated charts: VISA-P trend and pain trend"),
]
for i, (tab, desc) in enumerate(tabs):
    r = tabs_r + 1 + i
    ws1.row_dimensions[r].height = 18
    c1 = ws1.cell(row=r, column=1, value=tab)
    c1.font = Font(name="Arial", bold=True, size=11, color=C_TEAL)
    c1.fill = fill(C_LTGRAY if i % 2 == 0 else C_WHITE)
    c1.alignment = left()
    c1.border = thin_border()
    ws1.merge_cells(start_row=r, start_column=2, end_row=r, end_column=8)
    c2 = ws1.cell(row=r, column=2, value=desc)
    c2.font = Font(name="Arial", size=11, color=C_DARKTEXT)
    c2.fill = fill(C_LTGRAY if i % 2 == 0 else C_WHITE)
    c2.alignment = left()
    c2.border = thin_border()

set_col_widths(ws1, {"A":18,"B":22,"C":22,"D":22,"E":14,"F":22,"G":22,"H":18})

# ═════════════════════════════════════════════════════════════════════════════
# SHEET 2 — PHASE OVERVIEW
# ═════════════════════════════════════════════════════════════════════════════
ws2 = wb.create_sheet("Phase Overview")
ws2.sheet_view.showGridLines = False

# Title
ws2.merge_cells("A1:I1")
c = ws2["A1"]
c.value = "12-WEEK REHABILITATION PHASE OVERVIEW — Patellar Tendinopathy"
c.font = Font(name="Arial", bold=True, size=16, color=C_WHITE)
c.fill = fill(C_NAVY)
c.alignment = center()
ws2.row_dimensions[1].height = 36

# Phase detail table
phase_details = [
    # (phase, weeks, sessions/wk, exercises, sets_reps, load, progression_rule, pain_limit, color)
    (
        "Phase 1\nIsometric Loading",
        "1 - 2",
        "Daily (5-7x/week)",
        "Wall sit\nIsometric leg press\nIsometric single-leg extension",
        "5 sets x 45 sec hold",
        "Bodyweight / light load on leg press",
        "Increase hold duration to 60 sec in week 2\nProgress to Phase 2 if pain <3/10 on SLDS",
        "Pain DURING exercise: up to 5/10 allowed\nPain AFTER exercise: must return to baseline within 24h",
        C_PHASE1
    ),
    (
        "Phase 2\nHeavy Slow Resistance",
        "3 - 6",
        "3x/week (M-W-F)",
        "Leg press (bilateral)\nHack squat / Goblet squat\nKnee extension machine\nRomanian deadlift",
        "Week 3-4: 4x15 (15RM)\nWeek 5-6: 4x12 (12RM)",
        "70-75% 1RM\nTempo: 3 sec down / 2 sec up",
        "Increase load 5-10% when reps feel easy\nProgress to Phase 3 if pain <3/10 at 12RM",
        "Pain DURING: up to 5/10\nPain NEXT DAY: must be ≤ baseline",
        C_PHASE2
    ),
    (
        "Phase 3\nEccentric Overload + HSR",
        "7 - 10",
        "3x/week (M-W-F)",
        "Single-leg decline squat (25° board)\nSingle-leg leg press\nNordic hamstring curl\nStep-down",
        "Week 7-8: 4x10 (10RM)\nWeek 9-10: 4x8 (8RM)\nDecline squat: 3x15 eccentric",
        "80-85% 1RM for HSR\nDecline squat: add weight when pain-free",
        "Add 2.5-5kg/week to decline squat\nProgress to Phase 4 if VISA-P ≥ 70",
        "Pain DURING: up to 5/10\nNO increase in resting pain",
        C_PHASE3
    ),
    (
        "Phase 4\nReturn to Sport Prep",
        "11 - 12",
        "3-4x/week",
        "Plyometrics: double-leg hop, single-leg hop\nBound & stick\nDepth jump (week 12 only)\nSport-specific drills",
        "Week 11: 3x10 DL hop\nWeek 12: 3x8 SL hop + depth jump 3x6",
        "Bodyweight initially\nAdd resistance when pain-free",
        "Full return to sport ONLY if:\n- VISA-P ≥ 80\n- Pain <2/10 on SLDS\n- Limb symmetry ≥ 90%",
        "STOP if pain >5/10\nReturn to Phase 3 if pain flares",
        C_PHASE4
    ),
]

col_headers = ["Phase","Weeks","Sessions/Week","Exercises","Sets & Reps","Load / Tempo",
               "Progression Rule","Pain Tolerance",""]
col_widths = {"A":18,"B":9,"C":16,"D":24,"E":24,"F":24,"G":32,"H":32,"I":5}

# Header row
ws2.row_dimensions[2].height = 10
ws2.row_dimensions[3].height = 26
for col, hdr in enumerate(col_headers, 1):
    apply_header(ws2, 3, col, hdr, bg=C_TEAL, sz=11)

# Data rows
for i, row_data in enumerate(phase_details):
    r = 4 + i * 2
    ws2.row_dimensions[r].height = 90
    ws2.row_dimensions[r+1].height = 4
    bg_c = row_data[8]
    for col, val in enumerate(row_data[:8], 1):
        cell = ws2.cell(row=r, column=col, value=val)
        cell.font = Font(name="Arial", size=10, bold=(col == 1), color=C_DARKTEXT)
        cell.fill = fill(bg_c)
        cell.alignment = Alignment(horizontal="left" if col > 1 else "center",
                                   vertical="top", wrap_text=True)
        cell.border = thin_border()

# Phase progression flowchart note
nr = 4 + len(phase_details) * 2 + 1
ws2.merge_cells(f"A{nr}:I{nr}")
c = ws2.cell(row=nr, column=1,
    value="PROGRESSION RULE: Move to the next phase only when pain criteria are met. If pain exceeds limits, "
          "reduce load by 20% and retry after 3 sessions. Do not rush progression — tendon adaptation lags "
          "behind muscle strength gains by 4-6 weeks.")
c.font = Font(name="Arial", italic=True, size=10, color=C_ORANGE)
c.fill = fill("FFF8F0")
c.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
c.border = Border(left=Side(style="medium", color=C_ORANGE),
                  right=Side(style="medium", color=C_ORANGE),
                  top=Side(style="medium", color=C_ORANGE),
                  bottom=Side(style="medium", color=C_ORANGE))
ws2.row_dimensions[nr].height = 50

set_col_widths(ws2, col_widths)

# ═════════════════════════════════════════════════════════════════════════════
# HELPER: Build an Exercise Prescription Sheet
# ═════════════════════════════════════════════════════════════════════════════
def build_rx_sheet(wb, title, subtitle, week_range, phase_color, exercises):
    """
    exercises: list of dicts with keys:
      name, description, sets, reps, tempo, load, cues, progression, image_note
    """
    ws = wb.create_sheet(title)
    ws.sheet_view.showGridLines = False

    # Title
    ws.merge_cells("A1:J1")
    c = ws["A1"]
    c.value = f"EXERCISE PRESCRIPTION — {subtitle}"
    c.font = Font(name="Arial", bold=True, size=15, color=C_WHITE)
    c.fill = fill(C_NAVY)
    c.alignment = center()
    ws.row_dimensions[1].height = 36

    ws.merge_cells("A2:J2")
    c = ws["A2"]
    c.value = f"Patellar Tendinopathy  |  {week_range}"
    c.font = Font(name="Arial", italic=True, size=11, color=C_WHITE)
    c.fill = fill(C_TEAL)
    c.alignment = center()
    ws.row_dimensions[2].height = 20

    ws.row_dimensions[3].height = 8

    # Column headers
    col_hdrs = ["#","Exercise","Description / Execution",
                "Sets","Reps / Duration","Tempo","Load","Key Cues","Progression Trigger","Notes"]
    ws.row_dimensions[4].height = 28
    for col, hdr in enumerate(col_hdrs, 1):
        apply_header(ws, 4, col, hdr, bg=C_TEAL, sz=11)

    for i, ex in enumerate(exercises):
        r = 5 + i
        ws.row_dimensions[r].height = 65
        bg = phase_color if i % 2 == 0 else C_WHITE
        data_row = [
            i+1,
            ex["name"],
            ex["description"],
            ex["sets"],
            ex["reps"],
            ex["tempo"],
            ex["load"],
            ex["cues"],
            ex["progression"],
            ex.get("notes",""),
        ]
        for col, val in enumerate(data_row, 1):
            cell = ws.cell(row=r, column=col, value=val)
            cell.font = Font(name="Arial", bold=(col==2), size=10,
                             color=C_NAVY if col==2 else C_DARKTEXT)
            cell.fill = fill(bg)
            cell.alignment = Alignment(
                horizontal="center" if col in (1,4,5,6) else "left",
                vertical="top", wrap_text=True)
            cell.border = thin_border()

    # Footer
    fr = 5 + len(exercises) + 1
    ws.merge_cells(f"A{fr}:J{fr}")
    c = ws.cell(row=fr, column=1,
        value="Pain monitoring: Stop if pain exceeds 5/10 during exercise. "
              "Post-exercise soreness >24h = reduce load next session by 20%.")
    c.font = Font(name="Arial", italic=True, size=10, color=C_ORANGE)
    c.alignment = left()
    c.fill = fill("FFF8F0")
    c.border = Border(left=Side(style="medium", color=C_ORANGE),
                      bottom=Side(style="medium", color=C_ORANGE),
                      top=Side(style="medium", color=C_ORANGE),
                      right=Side(style="medium", color=C_ORANGE))
    ws.row_dimensions[fr].height = 30

    set_col_widths(ws, {"A":5,"B":20,"C":34,"D":7,"E":14,"F":14,"G":18,"H":30,"I":26,"J":20})
    return ws

# ═════════════════════════════════════════════════════════════════════════════
# SHEET 3 — Rx Phase 1: ISOMETRIC
# ═════════════════════════════════════════════════════════════════════════════
ex_p1 = [
    {
        "name": "Wall Sit (Bilateral)",
        "description": "Stand with back flat against wall. Slide down until knees at ~70° flexion. Hold position statically. Keep feet hip-width apart, toes forward.",
        "sets": "5",
        "reps": "45 sec hold\n(→ 60 sec wk 2)",
        "tempo": "Static hold",
        "load": "Bodyweight\nAdd vest if pain <2/10",
        "cues": "- Knees over 2nd toe\n- No bouncing / movement\n- Breathe normally\n- Quad squeeze throughout",
        "progression": "Progress when pain <3/10\nduring all 5 sets",
        "notes": "2 min rest between sets\nPerform morning + evening"
    },
    {
        "name": "Isometric Leg Press",
        "description": "Sit in leg press machine. Push plate to mid-range position. Hold without locking knee. Apply submaximal force (70% effort) against the immovable plate.",
        "sets": "5",
        "reps": "45 sec hold",
        "tempo": "Static hold",
        "load": "Light to moderate plate load (~30-50% BW)",
        "cues": "- 70° knee flexion\n- Equal pressure both legs\n- No knee valgus\n- Consistent pressure throughout",
        "progression": "Increase load by 10-20% when\nall sets pain-free",
        "notes": "Strong analgesic effect — often reduces tendon pain immediately\nCan be done 2x daily"
    },
    {
        "name": "Isometric Single-Leg Extension (Seated)",
        "description": "Sit on edge of chair or extension machine. Extend knee to ~30° short of full extension. Hold. Do NOT lock knee fully.",
        "sets": "5",
        "reps": "45 sec hold",
        "tempo": "Static hold",
        "load": "Ankle weight or machine resistance",
        "cues": "- Stop 30° from full extension\n- Avoid painful range\n- Keep thigh flat on surface\n- Controlled breathing",
        "progression": "Perform on affected leg only;\nprogress when NRS <3/10",
        "notes": "Only if leg press unavailable\nAlt for home use"
    },
    {
        "name": "Isometric Step Hold",
        "description": "Stand on a 10-15cm step, affected leg only. Slowly lower to mid-range (30° knee flexion). Hold position. Maintain upright trunk.",
        "sets": "4",
        "reps": "30-45 sec hold",
        "tempo": "Static hold",
        "load": "Bodyweight",
        "cues": "- Hip-knee-toe alignment\n- No trunk lean\n- Arms by side or lightly touch wall for balance\n- Quad dominant — not hip hinge",
        "progression": "Increase step height or hold duration\nonce pain-free for 3 sessions",
        "notes": "Excellent for monitoring: pain on SLDS during this exercise predicts tolerance to Phase 2"
    },
]
build_rx_sheet(wb, "Rx Phase 1 — Isometric",
               "PHASE 1: ISOMETRIC LOADING", "Weeks 1-2", C_PHASE1, ex_p1)

# ═════════════════════════════════════════════════════════════════════════════
# SHEET 4 — Rx Phase 2: HSR
# ═════════════════════════════════════════════════════════════════════════════
ex_p2 = [
    {
        "name": "Bilateral Leg Press",
        "description": "Feet hip-width on platform. Lower platform to 90° knee flexion, press back to near-full extension. Control the descent (3 sec down). Both legs equally loaded.",
        "sets": "4",
        "reps": "Wk3-4: 15 reps\nWk5-6: 12 reps",
        "tempo": "3 sec down\n1 sec pause\n2 sec up",
        "load": "Wk3-4: 15RM load\nWk5-6: 12RM load (~75% 1RM)",
        "cues": "- Heels on platform\n- No knee collapse\n- Full ROM\n- Breathe: exhale on press",
        "progression": "Increase load 5% when\nlast set feels <8/10 RPE",
        "notes": "Primary HSR exercise\nCore of phase 2"
    },
    {
        "name": "Hack Squat / Goblet Squat",
        "description": "Hack squat: feet shoulder-width on platform. Goblet squat (home): hold dumbbell/kettlebell at chest. Squat to parallel or below. Slow controlled descent.",
        "sets": "4",
        "reps": "Wk3-4: 15 reps\nWk5-6: 12 reps",
        "tempo": "3 sec down\n2 sec up",
        "load": "Wk3-4: 15RM\nWk5-6: 12RM",
        "cues": "- Chest up, neutral spine\n- Knees track over toes\n- Drive through heels on ascent\n- Controlled descent",
        "progression": "Add 5-10kg plates once\nall 4 sets completed pain-free",
        "notes": "Goblet squat requires no\nmachine — good home option"
    },
    {
        "name": "Knee Extension Machine",
        "description": "Adjust seat so knee joint aligns with machine pivot. Extend knee from 90° to full extension. Slowly lower back. Keep thigh pressed into pad.",
        "sets": "3",
        "reps": "Wk3-4: 15 reps\nWk5-6: 12 reps",
        "tempo": "2 sec up\n3 sec down",
        "load": "Moderate (~65-70% 1RM)",
        "cues": "- Full ROM extension\n- No compensatory hip shift\n- Controlled eccentric phase\n- Do NOT swing leg",
        "progression": "Increase stack by 5lbs/session\nif NRS <3/10",
        "notes": "Use with caution if pain\nat terminal extension;\nadjust ROM accordingly"
    },
    {
        "name": "Romanian Deadlift (RDL)",
        "description": "Hold barbell or dumbbells at hip. Hinge at hip, maintaining neutral spine, lowering weight along thighs until mild hamstring stretch. Drive hips forward to return.",
        "sets": "4",
        "reps": "12-15 reps",
        "tempo": "3 sec down\n2 sec up",
        "load": "Moderate to heavy (15RM load)",
        "cues": "- Neutral spine throughout\n- Slight knee bend\n- Weight stays close to body\n- Glutes squeeze at top",
        "progression": "Add 5kg when all sets\ncompleted with good form",
        "notes": "Posterior chain loading\nreduces anterior knee stress"
    },
    {
        "name": "Hip Thrust (Barbell or Bodyweight)",
        "description": "Shoulders on bench, feet flat, barbell across hips. Push hips up to form table-top position. Lower slowly. Focus on glute contraction.",
        "sets": "3",
        "reps": "15 reps",
        "tempo": "2 sec up\n1 sec hold\n3 sec down",
        "load": "BW → add barbell load progressively",
        "cues": "- Chin tucked\n- Neutral pelvis at top\n- Drive through heels\n- Avoid hyperextension",
        "progression": "Add 10kg when form is perfect\nand no pain",
        "notes": "Key hip strengthening exercise\nfor patellar tendon offloading"
    },
]
build_rx_sheet(wb, "Rx Phase 2 — HSR",
               "PHASE 2: HEAVY SLOW RESISTANCE", "Weeks 3-6", C_PHASE2, ex_p2)

# ═════════════════════════════════════════════════════════════════════════════
# SHEET 5 — Rx Phase 3: Eccentric + HSR
# ═════════════════════════════════════════════════════════════════════════════
ex_p3 = [
    {
        "name": "Single-Leg Decline Squat (SLDS)",
        "description": "Stand on 25° decline board, affected leg only. Slowly lower into single-leg squat to ~60° knee flexion. Return using BOTH legs. Repeat eccentric only on single leg.",
        "sets": "3",
        "reps": "15 reps\n(eccentric only on\naffected leg)",
        "tempo": "4 sec down\n(eccentric)\nBilateral up",
        "load": "BW first;\nadd vest/backpack\nonce pain <3/10",
        "cues": "- Slow controlled descent\n- Knee tracks over 2nd toe\n- Upright trunk\n- Push through pain up to 5/10",
        "progression": "Add 2.5-5kg/week once\nall 3 sets completed",
        "notes": "GOLD STANDARD exercise\nMost evidence-based\nExact protocol: Alfredson/Purdam"
    },
    {
        "name": "Single-Leg Leg Press",
        "description": "Same as bilateral leg press but single leg. Foot centered on platform. Full ROM. Use uninvolved leg only to return weight.",
        "sets": "4",
        "reps": "Wk7-8: 10 reps\nWk9-10: 8 reps",
        "tempo": "3 sec down\n2 sec up",
        "load": "Wk7-8: 10RM\nWk9-10: 8RM (~80-85% 1RM)",
        "cues": "- Same cues as bilateral\n- Note any asymmetry\n- Match load to BW/contralateral",
        "progression": "Increase load 5% when last\nset RPE <8",
        "notes": "Tests and builds side-to-side\nstrength symmetry (target ≥90%)"
    },
    {
        "name": "Step-Down (Eccentric)",
        "description": "Stand on 20cm step. Slowly lower uninvolved foot toward floor (eccentric quad loading on standing leg). Tap floor gently and return. Focus on controlling descent.",
        "sets": "3",
        "reps": "10-12 reps per leg",
        "tempo": "4 sec down\n2 sec up",
        "load": "Bodyweight\n→ add weight vest",
        "cues": "- Hip-knee-ankle alignment\n- No trunk lean forward\n- Maintain level pelvis\n- Controlled, deliberate movement",
        "progression": "Increase step height 5cm\nonce pain-free",
        "notes": "Functional eccentric loading;\nclose to sport biomechanics"
    },
    {
        "name": "Nordic Hamstring Curl",
        "description": "Kneel on pad (feet anchored under barbell or partner holds ankles). Lower body toward ground by slowly straightening knees. Catch with hands, push back. Hamstring emphasis.",
        "sets": "3",
        "reps": "Wk7-8: 6 reps\nWk9-10: 8 reps",
        "tempo": "4-5 sec lowering phase",
        "load": "Bodyweight",
        "cues": "- Hips extended\n- Neutral spine\n- Lower as slowly as possible\n- Arms catch body at floor",
        "progression": "Increase reps by 1-2\nper session when RFD improves",
        "notes": "Builds posterior chain\nReduced anterior knee shear force"
    },
    {
        "name": "Bulgarian Split Squat",
        "description": "Rear foot elevated on bench (45cm). Front foot forward. Lower into single-leg squat. Primarily quad-loaded exercise with hip flexor stretch.",
        "sets": "4",
        "reps": "Wk7-8: 10 reps\nWk9-10: 8 reps",
        "tempo": "3 sec down\n2 sec up",
        "load": "Dumbbells at sides\n(10RM → 8RM)",
        "cues": "- Front shin slightly forward\n- Knee tracks over toe\n- Upright torso\n- Full ROM",
        "progression": "Add 2.5-5kg when\nform is maintained",
        "notes": "Excellent single-leg loading;\nexposes asymmetries"
    },
]
build_rx_sheet(wb, "Rx Phase 3 — Eccentric+HSR",
               "PHASE 3: ECCENTRIC OVERLOAD + HSR", "Weeks 7-10", C_PHASE3, ex_p3)

# ═════════════════════════════════════════════════════════════════════════════
# SHEET 6 — Rx Phase 4: Return to Sport
# ═════════════════════════════════════════════════════════════════════════════
ex_p4 = [
    {
        "name": "Double-Leg Countermovement Jump (CMJ)",
        "description": "Stand feet hip-width. Countermovement (quick dip), then jump maximally. Land softly with knees bent absorbing force. Focus on controlled landing.",
        "sets": "3",
        "reps": "Wk11: 10 reps\nWk12: 10 reps",
        "tempo": "Explosive up\nSoft landing",
        "load": "Bodyweight",
        "cues": "- Quiet landing\n- Knee over toe on landing\n- Equal weight both legs\n- 30 sec between reps",
        "progression": "Add single-leg variant once\ndouble-leg pain-free",
        "notes": "Entry-level plyometric\nMonitor for pain flare"
    },
    {
        "name": "Single-Leg Hop & Stick",
        "description": "Hop forward on one leg, land and HOLD for 2 seconds. Assess stability and pain on landing. Perform on both legs and compare.",
        "sets": "3",
        "reps": "Wk11: 8 reps\nWk12: 8 reps",
        "tempo": "Explosive hop\n2 sec stick",
        "load": "Bodyweight",
        "cues": "- Stick the landing\n- Knee alignment on landing\n- No trunk compensation\n- Assess pain on EACH rep",
        "progression": "Progress to repeated hops\nwhen stick is controlled and pain-free",
        "notes": "Key limb symmetry test:\ncompare distance both sides\n(target ≥ 90% symmetry)"
    },
    {
        "name": "Depth Jump",
        "description": "Step off box (20cm). Land, then immediately jump up (minimize ground contact time). Advanced plyometric — reactive tendon loading.",
        "sets": "3",
        "reps": "Wk12 only: 6 reps",
        "tempo": "Minimal ground\ncontact time",
        "load": "Bodyweight\n20cm box",
        "cues": "- Brief ground contact\n- Immediate rebound\n- Soft knees on landing\n- Forward lean minimized",
        "progression": "Increase box height to 30cm\nonce >3 sessions pain-free",
        "notes": "Week 12 ONLY\nHigh tendon load — use carefully"
    },
    {
        "name": "Lateral Bound",
        "description": "Push off one leg laterally, land on opposite single leg. Hold. Tests frontal plane control and lateral power, replicating court sport demands.",
        "sets": "3",
        "reps": "8 per side",
        "tempo": "Explosive push\nControlled landing",
        "load": "Bodyweight",
        "cues": "- Drive hip abductors\n- Land toe-heel\n- No knee cave\n- Hold 2 sec on landing",
        "progression": "Increase distance\nor add weight vest",
        "notes": "Key for basketball/volleyball\nplayers returning to sport"
    },
    {
        "name": "Sport-Specific Running / Sprints",
        "description": "Progressive running program: jog → run → sprint. Start at 50% max speed, increase 10% per session if pain-free. Include cutting and change-of-direction.",
        "sets": "3-4",
        "reps": "30-40m per rep\n(gradual speed increase)",
        "tempo": "Submaximal\n(build to 90%+)",
        "load": "Bodyweight",
        "cues": "- Monitor pain during AND after\n- Stop if pain >4/10\n- Allow full recovery between reps",
        "progression": "Full return to sport when\nVISA-P ≥80 + LSI ≥90%\n+ NRS <2 on SLDS",
        "notes": "Final clearance requires:\nVISA-P ≥80\nPain <2/10 on SLDS\nLimb Symmetry Index ≥90%"
    },
]
build_rx_sheet(wb, "Rx Phase 4 — Return to Sport",
               "PHASE 4: ENERGY STORAGE & RETURN TO SPORT", "Weeks 11-12", C_PHASE4, ex_p4)

# ═════════════════════════════════════════════════════════════════════════════
# SHEET 7 — Stretching & Hip-Core
# ═════════════════════════════════════════════════════════════════════════════
ws7 = wb.create_sheet("Stretching & Hip-Core")
ws7.sheet_view.showGridLines = False

ws7.merge_cells("A1:I1")
c = ws7["A1"]
c.value = "STRETCHING & HIP / CORE STRENGTHENING PROTOCOL — All 12 Weeks"
c.font = Font(name="Arial", bold=True, size=15, color=C_WHITE)
c.fill = fill(C_NAVY)
c.alignment = center()
ws7.row_dimensions[1].height = 36

ws7.merge_cells("A2:I2")
c = ws7["A2"]
c.value = "Performed DAILY (or minimum 5x/week) — after main exercise session or as standalone warm-up/cool-down"
c.font = Font(name="Arial", italic=True, size=11, color=C_WHITE)
c.fill = fill(C_TEAL)
c.alignment = center()
ws7.row_dimensions[2].height = 20
ws7.row_dimensions[3].height = 8

# SECTION A: STRETCHING
ws7.merge_cells("A4:I4")
c = ws7.cell(row=4, column=1, value="SECTION A — FLEXIBILITY / STRETCHING")
c.font = Font(name="Arial", bold=True, size=12, color=C_WHITE)
c.fill = fill(C_TEAL)
c.alignment = left()
ws7.row_dimensions[4].height = 24

stretch_headers = ["#", "Stretch", "Target Muscle", "Technique", "Sets", "Duration", "Frequency", "Notes", ""]
ws7.row_dimensions[5].height = 24
for col, hdr in enumerate(stretch_headers, 1):
    apply_header(ws7, 5, col, hdr, bg=C_NAVY, sz=11)

stretches = [
    (1, "Standing Quad Stretch", "Quadriceps", "Stand on one leg. Bend knee behind, hold ankle. Pull heel toward glute. Keep knees together. Upright posture.", "3", "45-60 sec", "Daily", "Use wall for balance if needed"),
    (2, "Prone Quad Stretch", "Quadriceps (deeper)", "Lie face down. Bend knee, hold ankle. Pull gently. Place pillow under thigh if tight. Presses quad into full stretch.", "2", "60 sec", "Daily", "Superior stretch if standing version is limited by balance"),
    (3, "Supine Hamstring Stretch", "Hamstrings", "Lie on back. Lift one leg, hold behind thigh. Gently straighten knee. Keep opposite leg flat. Dorsiflex foot for extra stretch.", "3", "45-60 sec per leg", "Daily", "Avoid overpulling — mild stretch only"),
    (4, "Seated Forward Bend", "Hamstrings / lumbar", "Sit with legs extended. Hinge forward at hips, reach toward feet. Keep back flat (not rounded). Flex feet.", "2", "45 sec", "Daily", "Hip hinge is key — avoid spinal flexion"),
    (5, "Gastrocnemius Stretch\n(Straight-leg)", "Gastrocnemius", "Place hands on wall. Step one foot back, heel flat on floor, knee straight. Lean forward until calf stretch felt.", "3", "45 sec per leg", "Daily", "Tightness here increases patellar tendon load"),
    (6, "Soleus Stretch\n(Bent-knee)", "Soleus / deep calf", "Same wall position but with back knee bent. Lean forward. Targets deep soleus — different to gastrocnemius stretch.", "3", "45 sec per leg", "Daily", "Often neglected; critical for ankle dorsiflexion"),
    (7, "Hip Flexor Stretch\n(Kneeling Lunge)", "Hip flexors / iliopsoas", "Kneeling lunge position. Back knee down. Push hips forward until anterior hip stretch felt. Upright trunk. Tuck pelvis.", "3", "45 sec per leg", "Daily", "Tight hip flexors alter pelvic tilt and increase patellar tendon loading"),
    (8, "Figure-4 Piriformis Stretch", "Piriformis / hip ER", "Lie on back. Cross ankle over opposite knee. Pull both toward chest. Or seated pigeon pose.", "2", "45 sec per side", "Daily", "Hip ER tightness contributes to valgus collapse during landing"),
]
for i, s in enumerate(stretches):
    r = 6 + i
    ws7.row_dimensions[r].height = 55
    bg = C_SKYBLUE if i % 2 == 0 else C_WHITE
    for col, val in enumerate(s, 1):
        cell = ws7.cell(row=r, column=col, value=val)
        cell.font = Font(name="Arial", bold=(col==2), size=10, color=C_DARKTEXT)
        cell.fill = fill(bg)
        cell.alignment = Alignment(horizontal="center" if col in (1,5,6,7) else "left",
                                   vertical="top", wrap_text=True)
        cell.border = thin_border()

# SECTION B: HIP & CORE
sec_b_row = 6 + len(stretches) + 2
ws7.merge_cells(f"A{sec_b_row}:I{sec_b_row}")
c = ws7.cell(row=sec_b_row, column=1, value="SECTION B — HIP & CORE STRENGTHENING")
c.font = Font(name="Arial", bold=True, size=12, color=C_WHITE)
c.fill = fill(C_TEAL)
c.alignment = left()
ws7.row_dimensions[sec_b_row].height = 24

ws7.row_dimensions[sec_b_row+1].height = 24
for col, hdr in enumerate(stretch_headers, 1):
    apply_header(ws7, sec_b_row+1, col, hdr, bg=C_NAVY, sz=11)

hip_core_exercises = [
    (1, "Clamshells", "Glute medius / hip ER", "Lie on side, hips flexed 45°, knees bent 90°. Feet together. Open top knee like a clamshell (external rotation). Resistance band around knees.", "3", "15-20 reps", "3x/week", "Foundation exercise for hip abductor activation"),
    (2, "Side-Lying Hip Abduction", "Glute medius", "Lie on side, lower leg bent for stability. Top leg straight. Lift top leg 30-40°. Control return. Can add ankle weight.", "3", "15 reps per side", "3x/week", "Keep pelvis stacked — avoid rolling back"),
    (3, "Lateral Band Walk", "Glute medius / minimus", "Resistance band around ankles. Semi-squat position. Step laterally maintaining tension. 10 steps each direction.", "3", "10 steps each way", "3x/week", "Maintain constant band tension; don't let knees collapse inward"),
    (4, "Single-Leg Glute Bridge", "Glute max / hamstrings", "Lie on back, one knee bent, other leg extended. Push through bent heel to lift hips to table-top. Squeeze glutes at top.", "3", "12-15 reps each leg", "3x/week", "Prevents Trendelenburg gait; critical for patellar tracking"),
    (5, "Dead Bug", "Deep core stabilizers", "Lie on back, arms up, knees bent 90° in air. Simultaneously lower opposite arm and leg toward floor while keeping lower back flat. Return and repeat.", "3", "10 reps per side", "3x/week", "Lower back must stay pressed to floor throughout — stop if back arches"),
    (6, "Plank (Front)", "Core (transverse abdominis, rectus)", "Elbows under shoulders, toes on floor. Body forms straight line. Hold. Brace core, squeeze glutes. No hip sagging.", "3", "30-45 sec\n(build to 60s)", "3x/week", "Progress to plank with leg lift once 45s is stable"),
    (7, "Side Plank", "Obliques / glute medius", "Side-lying on elbow. Stack feet. Lift hips to form straight line. Hold. Can progress to side plank with hip abduction.", "3", "20-30 sec per side", "3x/week", "Hip dip indicates weakness — cue hip push to ceiling"),
    (8, "Pallof Press", "Anti-rotation core", "Resistance band anchored at chest height. Stand sideways to anchor. Press band straight out, hold 2 sec, return. Anti-rotation challenge.", "3", "10 reps per side", "3x/week", "Functional core stability — directly applicable to sport landing mechanics"),
    (9, "Single-Leg Balance / Proprioception", "Ankle, knee, hip stabilizers", "Stand on one leg. Maintain balance for 30-60 sec. Progress: eyes closed, unstable surface (BOSU), add arm movements.", "3", "30-60 sec per leg", "Daily", "Neuromuscular control; critical for return to sport and injury prevention"),
]
for i, ex in enumerate(hip_core_exercises):
    r = sec_b_row + 2 + i
    ws7.row_dimensions[r].height = 55
    bg = "E8F5E9" if i % 2 == 0 else C_WHITE
    for col, val in enumerate(ex, 1):
        cell = ws7.cell(row=r, column=col, value=val)
        cell.font = Font(name="Arial", bold=(col==2), size=10, color=C_DARKTEXT)
        cell.fill = fill(bg)
        cell.alignment = Alignment(horizontal="center" if col in (1,5,6,7) else "left",
                                   vertical="top", wrap_text=True)
        cell.border = thin_border()

set_col_widths(ws7, {"A":5,"B":22,"C":20,"D":36,"E":7,"F":14,"G":12,"H":30,"I":5})

# ═════════════════════════════════════════════════════════════════════════════
# SHEET 8 — 12-WEEK SESSION TRACKER
# ═════════════════════════════════════════════════════════════════════════════
ws8 = wb.create_sheet("12-Week Session Tracker")
ws8.sheet_view.showGridLines = False

ws8.merge_cells("A1:P1")
c = ws8["A1"]
c.value = "12-WEEK SESSION TRACKER — Patellar Tendinopathy Rehabilitation"
c.font = Font(name="Arial", bold=True, size=15, color=C_WHITE)
c.fill = fill(C_NAVY)
c.alignment = center()
ws8.row_dimensions[1].height = 36

ws8.merge_cells("A2:P2")
c = ws8["A2"]
c.value = "Enter data after each session. Yellow cells = data entry. Pain scale: 0 = no pain, 10 = worst imaginable."
c.font = Font(name="Arial", italic=True, size=11, color=C_WHITE)
c.fill = fill(C_TEAL)
c.alignment = center()
ws8.row_dimensions[2].height = 20

ws8.row_dimensions[3].height = 8

# Column headers
tracker_headers = [
    "Week","Day","Date","Phase","Session\nType",
    "Pre-Session\nPain (NRS 0-10)",
    "Pain During\nExercise (NRS)",
    "Post-Session\nPain (NRS)",
    "24h Post\nPain (NRS)",
    "Main Exercise","Sets\nCompleted","Reps\nCompleted",
    "Load Used\n(kg / BW)","VISA-P\n(if assessed)","Session RPE\n(0-10)","Notes / Observations"
]
ws8.row_dimensions[4].height = 40
for col, hdr in enumerate(tracker_headers, 1):
    apply_header(ws8, 4, col, hdr, bg=C_TEAL, sz=10)

# Phase color map
phase_colors = {1: C_PHASE1, 2: C_PHASE2, 3: C_PHASE3, 4: C_PHASE4}
phase_session_map = {
    1: ("Isometric", 1),
    2: ("Isometric", 1),
    3: ("HSR", 2),
    4: ("HSR", 2),
    5: ("HSR", 2),
    6: ("HSR", 2),
    7: ("Eccentric+HSR", 3),
    8: ("Eccentric+HSR", 3),
    9: ("Eccentric+HSR", 3),
    10: ("Eccentric+HSR", 3),
    11: ("Return to Sport", 4),
    12: ("Return to Sport", 4),
}

# 3 sessions per week for 12 weeks = 36 sessions
row = 5
for week in range(1, 13):
    session_type, phase_num = phase_session_map[week]
    bg = phase_colors[phase_num]
    # Week header row (spans all cols)
    ws8.merge_cells(start_row=row, start_column=1, end_row=row, end_column=16)
    wc = ws8.cell(row=row, column=1,
        value=f"WEEK {week}  —  Phase {phase_num}: {session_type}  "
              f"({'Weeks 1-2' if week <= 2 else 'Weeks 3-6' if week <= 6 else 'Weeks 7-10' if week <= 10 else 'Weeks 11-12'})")
    wc.font = Font(name="Arial", bold=True, size=11, color=C_WHITE)
    wc.fill = fill({1:C_NAVY, 2:C_TEAL, 3:"2D6A4F", 4:C_ORANGE}[phase_num])
    wc.alignment = left()
    wc.border = thin_border()
    ws8.row_dimensions[row].height = 22
    row += 1

    sessions_per_week = 3 if phase_num >= 2 else 2
    day_labels = {2: ["Day 1 (Mon)", "Day 2 (Thu)"],
                  3: ["Day 1 (Mon)", "Day 2 (Wed)", "Day 3 (Fri)"]}
    days = day_labels.get(sessions_per_week, ["Day 1","Day 2","Day 3"])

    for day_label in days:
        ws8.row_dimensions[row].height = 22
        row_bg = bg if (row % 2 == 0) else C_WHITE
        values = [week, day_label, "", f"Phase {phase_num}", session_type,
                  "", "", "", "", "", "", "", "", "", "", ""]
        for col, val in enumerate(values, 1):
            cell = ws8.cell(row=row, column=col, value=val)
            is_entry = col in (3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
            cell.font = Font(name="Arial", size=10,
                             bold=(col in (1,2,4,5)),
                             color=C_DARKTEXT)
            cell.fill = fill(C_YELLOW if is_entry else row_bg)
            cell.alignment = Alignment(
                horizontal="center" if col not in (10,16) else "left",
                vertical="center", wrap_text=True)
            cell.border = thin_border()
        row += 1

    # Weekly summary row
    ws8.row_dimensions[row].height = 24
    ws8.merge_cells(start_row=row, start_column=1, end_row=row, end_column=5)
    sc = ws8.cell(row=row, column=1, value=f"WEEK {week} SUMMARY")
    sc.font = Font(name="Arial", bold=True, size=10, color=C_NAVY)
    sc.fill = fill(C_LTGRAY)
    sc.alignment = left()
    sc.border = thin_border()

    # Average pain formula (pre-session pain column = col 6)
    start_data = row - sessions_per_week
    end_data = row - 1
    for col, label, formula in [
        (6, "Avg Pre-Pain:", f"=IFERROR(AVERAGE(F{start_data}:F{end_data}),\"-\")"),
        (8, "Avg Post-Pain:", f"=IFERROR(AVERAGE(H{start_data}:H{end_data}),\"-\")"),
        (15, "Avg RPE:", f"=IFERROR(AVERAGE(O{start_data}:O{end_data}),\"-\")"),
    ]:
        # label in col-1, formula in col
        lc = ws8.cell(row=row, column=col-1, value=label)
        lc.font = Font(name="Arial", bold=True, size=9, color=C_DARKTEXT)
        lc.fill = fill(C_LTGRAY)
        lc.alignment = right = Alignment(horizontal="right", vertical="center")
        lc.border = thin_border()
        fc = ws8.cell(row=row, column=col, value=formula)
        fc.font = Font(name="Arial", size=10, color=C_TEAL, bold=True)
        fc.fill = fill(C_LTGRAY)
        fc.alignment = Alignment(horizontal="center", vertical="center")
        fc.border = thin_border()

    # Fill remaining cols with gray
    filled_cols = {1,5,6,7,8,15}
    for col in range(6, 17):
        if col not in {6, 8, 15, 5, 7, 14}:
            cell = ws8.cell(row=row, column=col)
            cell.fill = fill(C_LTGRAY)
            cell.border = thin_border()

    row += 2  # spacer

set_col_widths(ws8, {
    "A":7,"B":14,"C":13,"D":10,"E":14,
    "F":12,"G":12,"H":12,"I":12,
    "J":18,"K":8,"L":8,"M":14,"N":12,"O":10,"P":28
})

# ═════════════════════════════════════════════════════════════════════════════
# SHEET 9 — PROGRESS DASHBOARD
# ═════════════════════════════════════════════════════════════════════════════
ws9 = wb.create_sheet("Progress Dashboard")
ws9.sheet_view.showGridLines = False

ws9.merge_cells("A1:L1")
c = ws9["A1"]
c.value = "PROGRESS DASHBOARD — Patellar Tendinopathy Rehabilitation"
c.font = Font(name="Arial", bold=True, size=15, color=C_WHITE)
c.fill = fill(C_NAVY)
c.alignment = center()
ws9.row_dimensions[1].height = 36

ws9.merge_cells("A2:L2")
c = ws9["A2"]
c.value = "Enter weekly data below. Charts update automatically."
c.font = Font(name="Arial", italic=True, size=11, color=C_WHITE)
c.fill = fill(C_TEAL)
c.alignment = center()
ws9.row_dimensions[2].height = 20
ws9.row_dimensions[3].height = 10

# VISA-P Reference scores
ws9.merge_cells("A4:L4")
c = ws9.cell(row=4, column=1, value="VISA-P SCORING REFERENCE:  0-30 = Severe  |  31-50 = Moderate-Severe  |  51-70 = Moderate  |  71-80 = Mild-Moderate  |  81-100 = Minimal / Return to Sport Eligible")
c.font = Font(name="Arial", italic=True, size=10, color=C_DARKTEXT)
c.fill = fill("FFF3CD")
c.alignment = left()
c.border = Border(left=Side(style="medium",color=C_YELLOW),right=Side(style="medium",color=C_YELLOW),
                  top=Side(style="medium",color=C_YELLOW),bottom=Side(style="medium",color=C_YELLOW))
ws9.row_dimensions[4].height = 22

ws9.row_dimensions[5].height = 8

# Weekly data entry table
ws9.row_dimensions[6].height = 28
dash_headers = ["Week","Phase","Date\n(Assessment)","VISA-P\nScore (0-100)",
                "Pre-Session\nPain NRS","Post-Session\nPain NRS","24h Post\nPain NRS",
                "Main Load\n(kg)","Reps\n(last set)","Single-Leg\nDecline Squat Pain","Notes",""]
for col, hdr in enumerate(dash_headers, 1):
    apply_header(ws9, 6, col, hdr, bg=C_NAVY, sz=11)

phase_labels = {1:"Phase 1", 2:"Phase 1", 3:"Phase 2", 4:"Phase 2",
                5:"Phase 2", 6:"Phase 2", 7:"Phase 3", 8:"Phase 3",
                9:"Phase 3", 10:"Phase 3", 11:"Phase 4", 12:"Phase 4"}
phase_bg = {1:C_PHASE1,2:C_PHASE1,3:C_PHASE2,4:C_PHASE2,
            5:C_PHASE2,6:C_PHASE2,7:C_PHASE3,8:C_PHASE3,
            9:C_PHASE3,10:C_PHASE3,11:C_PHASE4,12:C_PHASE4}

for w in range(1, 13):
    r = 6 + w
    ws9.row_dimensions[r].height = 22
    bg = phase_bg[w]
    row_vals = [w, phase_labels[w], "", "", "", "", "", "", "", "", "", ""]
    for col, val in enumerate(row_vals, 1):
        cell = ws9.cell(row=r, column=col, value=val)
        is_input = col in (3,4,5,6,7,8,9,10,11)
        cell.font = Font(name="Arial", bold=(col in (1,2)), size=11, color=C_DARKTEXT)
        cell.fill = fill(C_YELLOW if is_input else bg)
        cell.alignment = Alignment(
            horizontal="center" if col not in (11,) else "left",
            vertical="center")
        cell.border = thin_border()

# Add charts
# Line chart: VISA-P score by week
chart_visa = LineChart()
chart_visa.title = "VISA-P Score Progress Over 12 Weeks"
chart_visa.style = 10
chart_visa.y_axis.title = "VISA-P Score (0-100)"
chart_visa.x_axis.title = "Week"
chart_visa.y_axis.scaling.min = 0
chart_visa.y_axis.scaling.max = 100
chart_visa.height = 12
chart_visa.width = 22

data_visa = Reference(ws9, min_col=4, max_col=4, min_row=6, max_row=18)
weeks_ref = Reference(ws9, min_col=1, max_col=1, min_row=7, max_row=18)
chart_visa.add_data(data_visa, titles_from_data=True)
chart_visa.set_categories(weeks_ref)
chart_visa.series[0].graphicalProperties.line.solidFill = C_TEAL
chart_visa.series[0].graphicalProperties.line.width = 25000
chart_visa.series[0].marker.symbol = "circle"
chart_visa.series[0].marker.size = 8

# Add target line at 80
from openpyxl.chart.series import Series
ws9.cell(row=6, column=12, value="Target (80)")
for w in range(1, 13):
    ws9.cell(row=6+w, column=12, value=80)
target_ref = Reference(ws9, min_col=12, max_col=12, min_row=6, max_row=18)
chart_visa.add_data(target_ref, titles_from_data=True)
chart_visa.series[1].graphicalProperties.line.solidFill = C_GREEN
chart_visa.series[1].graphicalProperties.line.dashDot = "dash"
chart_visa.series[1].graphicalProperties.line.width = 15000

ws9.add_chart(chart_visa, "A21")

# Bar chart: Pain scores by week
chart_pain = BarChart()
chart_pain.type = "col"
chart_pain.style = 10
chart_pain.title = "Weekly Pain Scores (NRS 0-10)"
chart_pain.y_axis.title = "Pain NRS"
chart_pain.x_axis.title = "Week"
chart_pain.y_axis.scaling.min = 0
chart_pain.y_axis.scaling.max = 10
chart_pain.height = 12
chart_pain.width = 22

data_pre = Reference(ws9, min_col=5, max_col=5, min_row=6, max_row=18)
data_post = Reference(ws9, min_col=7, max_col=7, min_row=6, max_row=18)
chart_pain.add_data(data_pre, titles_from_data=True)
chart_pain.add_data(data_post, titles_from_data=True)
chart_pain.set_categories(weeks_ref)
chart_pain.series[0].graphicalProperties.solidFill = C_ORANGE
chart_pain.series[1].graphicalProperties.solidFill = C_GREEN

ws9.add_chart(chart_pain, "G21")

# Summary stats row
sr = 20
ws9.merge_cells(f"A{sr}:L{sr}")
c = ws9.cell(row=sr, column=1,
    value="CHARTS AUTO-UPDATE as you enter weekly data above. Target: VISA-P ≥80 and NRS <2 on SLDS for return to sport clearance.")
c.font = Font(name="Arial", italic=True, size=10, color=C_DARKTEXT)
c.fill = fill("F0F8FF")
c.alignment = left()
ws9.row_dimensions[sr].height = 22

set_col_widths(ws9, {"A":7,"B":10,"C":14,"D":14,"E":13,"F":13,"G":13,
                      "H":12,"I":10,"J":16,"K":22,"L":14})

# ═════════════════════════════════════════════════════════════════════════════
# Tab colors
# ═════════════════════════════════════════════════════════════════════════════
ws1.sheet_properties.tabColor = C_NAVY
ws2.sheet_properties.tabColor = C_TEAL
for sh_name, col in [
    ("Rx Phase 1 — Isometric", "A8DADC"),
    ("Rx Phase 2 — HSR", "52B788"),
    ("Rx Phase 3 — Eccentric+HSR", "FFD166"),
    ("Rx Phase 4 — Return to Sport", "E76F51"),
    ("Stretching & Hip-Core", "84A98C"),
    ("12-Week Session Tracker", "4A90D9"),
    ("Progress Dashboard", "9B59B6"),
]:
    wb[sh_name].sheet_properties.tabColor = col

# ═════════════════════════════════════════════════════════════════════════════
# SAVE
# ═════════════════════════════════════════════════════════════════════════════
out_path = "/home/daytona/workspace/patellar-rehab/Patellar_Tendinopathy_12Week_Tracker.xlsx"
wb.save(out_path)
print(f"Saved: {out_path}")
Running Command

cd /home/daytona/workspace/patellar-rehab && python build_tracker.py 2>&1

Editing File

~/patellar-rehab/build_tracker.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/patellar-rehab/build_tracker.py'
Running Command

cd /home/daytona/workspace/patellar-rehab && python build_tracker.py 2>&1

Editing File

~/patellar-rehab/build_tracker.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/patellar-rehab/build_tracker.py'
Editing File

~/patellar-rehab/build_tracker.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/patellar-rehab/build_tracker.py'
Running Command

cd /home/daytona/workspace/patellar-rehab && python build_tracker.py 2>&1

Running Command

python3 -c " import openpyxl wb = openpyxl.Workbook() ws = wb.active ws['A1'] = …

Running Command

# Check openpyxl version and try with io.BytesIO workaround python3 -c "import o…

Editing File

~/patellar-rehab/build_tracker.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/patellar-rehab/build_tracker.py'
Editing File

~/patellar-rehab/build_tracker.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/patellar-rehab/build_tracker.py'
Running Command

cd /home/daytona/workspace/patellar-rehab && python build_tracker.py 2>&1

Running Command

ls -lh /home/daytona/workspace/patellar-rehab/Patellar_Tendinopathy_12Week_Track…

Your 12-week tracker is ready:

Patellar Tendinopathy 12Week Tracker

Spreadsheet · XLSX


What's inside (9 color-coded tabs)

TabColorContents
CoverNavyPatient info form (yellow input cells), protocol overview, tabs guide
Phase OverviewTealAll 4 phases in one table - weeks, sessions/week, exercises, sets/reps, load, progression rules, pain tolerance
Rx Phase 1 - IsometricSky blueWks 1-2: Wall sit, isometric leg press, isometric step hold - full prescription
Rx Phase 2 - HSRGreenWks 3-6: Bilateral leg press, hack squat, knee extension, RDL, hip thrust
Rx Phase 3 - Eccentric+HSRYellowWks 7-10: Single-leg decline squat, step-down, Nordic curl, Bulgarian split squat
Rx Phase 4 - Return to SportOrangeWks 11-12: CMJ, single-leg hop & stick, depth jump, lateral bound, sprints
Stretching & Hip-CoreSage8 stretches + 9 hip/core exercises with sets, duration, frequency, cues
12-Week Session TrackerBlue36 sessions logged - date, pre/during/post/24h NRS, load, reps, weekly averages auto-calculated
Progress DashboardPurpleWeekly VISA-P + pain NRS data entry table; auto-generates line + bar charts
Each exercise prescription sheet includes: execution description, sets, reps, tempo, load guidelines, coaching cues, and progression trigger. Yellow cells throughout are input-only. The tracker auto-calculates weekly average pre- and post-session pain to show trends at a glance.

Generate image of how to do exercises

Asking for Preferences
This is a shared conversation. Sign in to Orris to start your own chat.