Experiencing pain and burning sensation over the plantar aspect of left foot on little and next time upto heel. Which interverbral disc is causing problem, what are the posture on excercise stretching should be avoided
L5 S1 disc herniation plantar foot burning pain little toe heel dermatome
L5-S1 disc herniation S1 radiculopathy exercises to avoid postures contraindicated
"With unilateral rupture of the disc at L5-S1, the findings of an S1 radiculopathy are noted. Pain and numbness involve the dermatome of S1. The S1 dermatome includes the lateral malleolus and the lateral and plantar surface of the foot, occasionally including the heel. There is numbness over the lateral aspect of the leg and, more importantly, over the lateral aspect of the foot, including the lateral three toes. The autonomous zone for this root is the dorsum of the fifth toe."

| Disc Level | Nerve Root | Key Sensory Area |
|---|---|---|
| L3-L4 | L4 | Medial leg, medial malleolus |
| L4-L5 | L5 | Dorsum of foot, big toe, 1st web space |
| L5-S1 | S1 | Lateral and plantar foot, 4th-5th toes, heel |
| Avoid | Safer Alternative |
|---|---|
| Sit-ups / crunches | Planks, bird-dog, dead bug |
| Toe touches | Supine knee-to-chest stretch (gentle) |
| Deadlifts | Glute bridges |
| Running | Walking, swimming, cycling (with upright posture) |
| Deep squats | Wall-supported mini squats |
Important: An MRI of the lumbosacral spine is needed to confirm the diagnosis, assess the degree of disc herniation, and rule out other causes (e.g., piriformis syndrome, tarsal tunnel syndrome). These recommendations are general; you should see a spine specialist or neurologist for a proper evaluation before starting or stopping any exercise program. If you develop weakness in the foot, loss of bladder/bowel control, or worsening numbness, seek urgent medical attention as these may indicate cauda equina syndrome.
Create a PDF guide with safe stretches and exercises for L5-S1 recovery
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace not found"
mkdir -p /home/daytona/workspace/l5s1-guide
~/l5s1-guide/generate_guide.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.graphics.shapes import Drawing, Rect, Circle, Ellipse, Line, String, Polygon
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import Drawing, Group
import os
OUTPUT = "/home/daytona/workspace/l5s1-guide/L5S1_Recovery_Guide.pdf"
# ── Colour palette ───────────────────────────────────────────────────────────
TEAL = colors.HexColor("#1A6B72")
TEAL_LIGHT = colors.HexColor("#E6F4F5")
ORANGE = colors.HexColor("#E8703A")
ORANGE_LIGHT = colors.HexColor("#FDF0EB")
RED = colors.HexColor("#C0392B")
RED_LIGHT = colors.HexColor("#FDECEA")
GREEN = colors.HexColor("#27AE60")
GREEN_LIGHT= colors.HexColor("#EAF7EF")
GRAY_DARK = colors.HexColor("#2C3E50")
GRAY_MID = colors.HexColor("#7F8C8D")
GRAY_LIGHT = colors.HexColor("#ECF0F1")
WHITE = colors.white
W, H = A4
# ── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def style(name, **kw):
base = styles.get(name) or styles["Normal"]
return ParagraphStyle(name + "_custom_" + str(id(kw)), parent=base, **kw)
S_TITLE = style("Title",
fontSize=28, leading=34, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER)
S_SUBTITLE = style("Normal",
fontSize=13, leading=18, textColor=colors.HexColor("#D0EEF0"),
fontName="Helvetica", alignment=TA_CENTER)
S_SECTION = style("Heading1",
fontSize=16, leading=20, textColor=TEAL,
fontName="Helvetica-Bold", spaceBefore=14, spaceAfter=6)
S_EXERCISE_TITLE = style("Heading2",
fontSize=13, leading=17, textColor=GRAY_DARK,
fontName="Helvetica-Bold", spaceBefore=4, spaceAfter=2)
S_BODY = style("Normal",
fontSize=10, leading=15, textColor=GRAY_DARK,
fontName="Helvetica", alignment=TA_JUSTIFY)
S_BULLET = style("Normal",
fontSize=10, leading=15, textColor=GRAY_DARK,
fontName="Helvetica", leftIndent=14, firstLineIndent=-8, spaceAfter=2)
S_LABEL = style("Normal",
fontSize=9, leading=12, textColor=GRAY_MID,
fontName="Helvetica-Bold")
S_NOTE = style("Normal",
fontSize=9, leading=13, textColor=RED,
fontName="Helvetica-Bold")
S_WARNING_BODY = style("Normal",
fontSize=9, leading=13, textColor=colors.HexColor("#8B0000"),
fontName="Helvetica")
S_BADGE = style("Normal",
fontSize=8, leading=10, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER)
S_SMALL_GRAY = style("Normal",
fontSize=8, leading=11, textColor=GRAY_MID,
fontName="Helvetica", alignment=TA_CENTER)
S_TOC = style("Normal",
fontSize=11, leading=18, textColor=GRAY_DARK,
fontName="Helvetica")
S_TOC_NUM = style("Normal",
fontSize=11, leading=18, textColor=TEAL,
fontName="Helvetica-Bold")
# ── Stick-figure exercise diagrams ───────────────────────────────────────────
def _person(g, cx, cy, scale=1.0,
spine_angle=0, hip_angle=0, knee_angle=0,
arm_l_angle=0, arm_r_angle=0):
"""Draw a minimal stick figure. All angles in degrees."""
import math
s = scale
head_r = 10 * s
torso_len = 28 * s
upper_leg = 22 * s
lower_leg = 20 * s
upper_arm = 18 * s
lower_arm = 16 * s
# head
g.add(Circle(cx, cy, head_r, fillColor=TEAL, strokeColor=TEAL_LIGHT, strokeWidth=1))
# neck → shoulder
neck_y = cy - head_r
shoulder_y = neck_y - 6 * s
g.add(Line(cx, neck_y, cx, shoulder_y, strokeColor=GRAY_DARK, strokeWidth=2*s))
# torso (slight forward lean via spine_angle)
rad_s = math.radians(spine_angle)
torso_end_x = cx + math.sin(rad_s) * torso_len
torso_end_y = shoulder_y - math.cos(rad_s) * torso_len
g.add(Line(cx, shoulder_y, torso_end_x, torso_end_y,
strokeColor=GRAY_DARK, strokeWidth=2.5*s))
# hips
hip_x, hip_y = torso_end_x, torso_end_y
hw = 7 * s
g.add(Line(hip_x - hw, hip_y, hip_x + hw, hip_y,
strokeColor=GRAY_DARK, strokeWidth=2*s))
# arms
for side, a in [(-1, arm_l_angle), (1, arm_r_angle)]:
rad_a = math.radians(a)
elbow_x = cx + side * math.cos(rad_a) * upper_arm
elbow_y = shoulder_y - math.sin(rad_a) * upper_arm
hand_x = elbow_x + side * math.cos(rad_a) * lower_arm
hand_y = elbow_y - math.sin(rad_a) * lower_arm
g.add(Line(cx, shoulder_y, elbow_x, elbow_y,
strokeColor=GRAY_DARK, strokeWidth=1.8*s))
g.add(Line(elbow_x, elbow_y, hand_x, hand_y,
strokeColor=GRAY_DARK, strokeWidth=1.5*s))
# legs
for side in [-1, 1]:
rad_h = math.radians(hip_angle * side)
knee_x = hip_x + side * hw + math.sin(rad_h) * upper_leg
knee_y = hip_y - math.cos(rad_h) * upper_leg
rad_k = math.radians(knee_angle)
foot_x = knee_x - math.sin(rad_k) * lower_leg
foot_y = knee_y - math.cos(rad_k) * lower_leg
g.add(Line(hip_x + side * hw, hip_y, knee_x, knee_y,
strokeColor=GRAY_DARK, strokeWidth=2*s))
g.add(Line(knee_x, knee_y, foot_x, foot_y,
strokeColor=GRAY_DARK, strokeWidth=1.8*s))
def _ground(g, y, x1, x2):
g.add(Line(x1, y, x2, y, strokeColor=GRAY_MID, strokeWidth=1,
strokeDashArray=[3, 3]))
def _arrow(g, x1, y1, x2, y2, col=ORANGE):
import math
g.add(Line(x1, y1, x2, y2, strokeColor=col, strokeWidth=2))
angle = math.atan2(y2 - y1, x2 - x1)
size = 6
pts = [
x2, y2,
x2 - size * math.cos(angle - 0.4), y2 - size * math.sin(angle - 0.4),
x2 - size * math.cos(angle + 0.4), y2 - size * math.sin(angle + 0.4),
]
g.add(Polygon(pts, fillColor=col, strokeColor=col, strokeWidth=0))
def make_diagram(exercise_key):
"""Return a Drawing for the given exercise."""
dw, dh = 160, 110
d = Drawing(dw, dh)
# background card
d.add(Rect(0, 0, dw, dh, rx=8, ry=8,
fillColor=TEAL_LIGHT, strokeColor=TEAL, strokeWidth=1))
g = Group()
if exercise_key == "pelvic_tilt":
# Lying on back, knees bent, lower back flattened
# Floor line
g.add(Line(10, 22, dw-10, 22, strokeColor=GRAY_MID, strokeWidth=1, strokeDashArray=[3,3]))
# Body lying: head circle
g.add(Circle(130, 32, 10, fillColor=TEAL, strokeColor=TEAL_LIGHT, strokeWidth=1))
# Torso horizontal
g.add(Line(120, 28, 60, 28, strokeColor=GRAY_DARK, strokeWidth=2.5))
# pelvis
g.add(Line(60, 28, 58, 25, strokeColor=GRAY_DARK, strokeWidth=2.5))
# upper legs at ~45 deg
g.add(Line(58, 25, 42, 45, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(58, 25, 50, 46, strokeColor=GRAY_DARK, strokeWidth=2))
# lower legs vertical
g.add(Line(42, 45, 25, 24, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(50, 46, 33, 25, strokeColor=GRAY_DARK, strokeWidth=2))
# arms along side
g.add(Line(95, 28, 100, 22, strokeColor=GRAY_DARK, strokeWidth=1.5))
g.add(Line(95, 28, 80, 22, strokeColor=GRAY_DARK, strokeWidth=1.5))
# Arrow showing lower back pressing down
_arrow(g, 75, 55, 75, 33, col=ORANGE)
# Label
g.add(String(75, 65, "Press back DOWN", fontSize=7, fillColor=ORANGE,
fontName="Helvetica-Bold", textAnchor="middle"))
elif exercise_key == "knee_to_chest":
g.add(Line(10, 22, dw-10, 22, strokeColor=GRAY_MID, strokeWidth=1, strokeDashArray=[3,3]))
# Lying, one knee pulled to chest
g.add(Circle(130, 38, 10, fillColor=TEAL, strokeColor=TEAL_LIGHT, strokeWidth=1))
g.add(Line(120, 34, 55, 30, strokeColor=GRAY_DARK, strokeWidth=2.5))
# straight leg
g.add(Line(55, 28, 20, 24, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(20, 24, 10, 24, strokeColor=GRAY_DARK, strokeWidth=2))
# bent knee to chest
g.add(Line(55, 30, 65, 55, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(65, 55, 90, 55, strokeColor=GRAY_DARK, strokeWidth=2))
# hands holding knee
g.add(Circle(90, 55, 5, fillColor=ORANGE, strokeColor=ORANGE, strokeWidth=1))
g.add(Circle(85, 57, 4, fillColor=ORANGE, strokeColor=ORANGE, strokeWidth=1))
# arms
g.add(Line(90, 32, 90, 50, strokeColor=GRAY_DARK, strokeWidth=1.5))
_arrow(g, 90, 50, 90, 55, col=ORANGE)
g.add(String(80, 75, "Hold 20-30 sec", fontSize=7, fillColor=ORANGE,
fontName="Helvetica-Bold", textAnchor="middle"))
elif exercise_key == "cat_cow":
# Quadruped - cat pose (arch up)
g.add(Line(10, 18, dw-10, 18, strokeColor=GRAY_MID, strokeWidth=1, strokeDashArray=[3,3]))
# hands
g.add(Circle(20, 28, 5, fillColor=GRAY_MID, strokeWidth=0))
g.add(Circle(20, 22, 5, fillColor=GRAY_MID, strokeWidth=0))
# knees
g.add(Circle(130, 28, 5, fillColor=GRAY_MID, strokeWidth=0))
g.add(Circle(130, 22, 5, fillColor=GRAY_MID, strokeWidth=0))
# arched spine (cat)
import math
pts = []
for i in range(21):
t = i / 20
x = 20 + t * 110
y = 30 - 22 * math.sin(math.pi * t)
pts.append((x, y))
for i in range(len(pts)-1):
g.add(Line(pts[i][0], pts[i][1], pts[i+1][0], pts[i+1][1],
strokeColor=TEAL, strokeWidth=3))
# head down
g.add(Line(20, 28, 15, 40, strokeColor=GRAY_DARK, strokeWidth=1.5))
g.add(Circle(12, 44, 8, fillColor=TEAL, strokeColor=TEAL_LIGHT, strokeWidth=1))
# tail up arrow
_arrow(g, 130, 30, 130, 52, col=ORANGE)
g.add(String(80, 75, "Cat: arch UP", fontSize=7, fillColor=TEAL,
fontName="Helvetica-Bold", textAnchor="middle"))
g.add(String(80, 66, "Cow: sag DOWN", fontSize=7, fillColor=ORANGE,
fontName="Helvetica-Bold", textAnchor="middle"))
elif exercise_key == "bird_dog":
g.add(Line(10, 18, dw-10, 18, strokeColor=GRAY_MID, strokeWidth=1, strokeDashArray=[3,3]))
# quadruped base
g.add(Circle(22, 28, 5, fillColor=GRAY_MID, strokeWidth=0)) # hand L
# torso
g.add(Line(22, 30, 100, 30, strokeColor=GRAY_DARK, strokeWidth=3))
# left knee down
g.add(Line(100, 30, 108, 18, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Circle(108, 15, 5, fillColor=GRAY_MID, strokeWidth=0))
# right leg extended back
g.add(Line(100, 30, 140, 42, strokeColor=TEAL, strokeWidth=2.5))
g.add(Line(140, 42, 155, 42, strokeColor=TEAL, strokeWidth=2))
# left arm extended forward
g.add(Line(22, 30, 0, 42, strokeColor=TEAL, strokeWidth=2.5))
# head
g.add(Circle(10, 46, 8, fillColor=TEAL, strokeColor=TEAL_LIGHT, strokeWidth=1))
_arrow(g, 155, 42, 162, 42, col=ORANGE)
_arrow(g, 0, 42, -5, 42, col=ORANGE)
g.add(String(80, 75, "Opposite arm + leg", fontSize=7, fillColor=TEAL,
fontName="Helvetica-Bold", textAnchor="middle"))
g.add(String(80, 66, "Keep spine neutral", fontSize=7, fillColor=ORANGE,
fontName="Helvetica-Bold", textAnchor="middle"))
elif exercise_key == "glute_bridge":
g.add(Line(10, 20, dw-10, 20, strokeColor=GRAY_MID, strokeWidth=1, strokeDashArray=[3,3]))
# Lying, knees bent, hips raised
g.add(Circle(135, 32, 10, fillColor=TEAL, strokeColor=TEAL_LIGHT, strokeWidth=1))
# torso diagonal up
g.add(Line(125, 28, 80, 48, strokeColor=GRAY_DARK, strokeWidth=2.5))
# arms flat
g.add(Line(105, 35, 115, 26, strokeColor=GRAY_DARK, strokeWidth=1.5))
g.add(Line(105, 35, 95, 26, strokeColor=GRAY_DARK, strokeWidth=1.5))
# hips up
g.add(Circle(80, 50, 6, fillColor=ORANGE, strokeWidth=0))
# upper legs
g.add(Line(80, 50, 55, 30, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(80, 50, 65, 30, strokeColor=GRAY_DARK, strokeWidth=2))
# lower legs vertical
g.add(Line(55, 30, 48, 20, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(65, 30, 58, 20, strokeColor=GRAY_DARK, strokeWidth=2))
_arrow(g, 80, 65, 80, 52, col=ORANGE)
g.add(String(80, 75, "Lift hips UP", fontSize=7, fillColor=ORANGE,
fontName="Helvetica-Bold", textAnchor="middle"))
elif exercise_key == "child_pose":
g.add(Line(10, 18, dw-10, 18, strokeColor=GRAY_MID, strokeWidth=1, strokeDashArray=[3,3]))
# Kneeling, sitting back on heels, arms forward
# buttocks on heels
g.add(Ellipse(110, 24, 20, 10, fillColor=GRAY_LIGHT, strokeColor=GRAY_MID, strokeWidth=1))
# torso forward/down
g.add(Line(110, 28, 45, 40, strokeColor=GRAY_DARK, strokeWidth=2.5))
# arms extended
g.add(Line(45, 40, 15, 36, strokeColor=GRAY_DARK, strokeWidth=1.5))
g.add(Line(45, 40, 12, 38, strokeColor=GRAY_DARK, strokeWidth=1.5))
# hands
g.add(Circle(12, 36, 4, fillColor=GRAY_MID, strokeWidth=0))
# head resting
g.add(Circle(42, 48, 9, fillColor=TEAL, strokeColor=TEAL_LIGHT, strokeWidth=1))
g.add(String(80, 75, "Arms forward, sit back", fontSize=7, fillColor=TEAL,
fontName="Helvetica-Bold", textAnchor="middle"))
g.add(String(80, 66, "Breathe & relax", fontSize=7, fillColor=ORANGE,
fontName="Helvetica-Bold", textAnchor="middle"))
elif exercise_key == "wall_hamstring":
g.add(Line(10, 18, dw-10, 18, strokeColor=GRAY_MID, strokeWidth=1, strokeDashArray=[3,3]))
# Lying, one leg up against wall (supine hamstring stretch)
g.add(Line(dw-10, 18, dw-10, 100, strokeColor=GRAY_MID, strokeWidth=2)) # wall
g.add(Circle(20, 30, 10, fillColor=TEAL, strokeColor=TEAL_LIGHT, strokeWidth=1))
g.add(Line(30, 26, 100, 26, strokeColor=GRAY_DARK, strokeWidth=2.5))
# bent knee (resting leg)
g.add(Line(100, 26, 115, 40, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(115, 40, 110, 20, strokeColor=GRAY_DARK, strokeWidth=2))
# straight leg up the wall
g.add(Line(100, 26, 140, 65, strokeColor=TEAL, strokeWidth=2.5))
g.add(Line(140, 65, dw-10, 65, strokeColor=TEAL, strokeWidth=2))
_arrow(g, 148, 18, 148, 63, col=ORANGE)
g.add(String(80, 78, "Leg up wall - straight", fontSize=7, fillColor=TEAL,
fontName="Helvetica-Bold", textAnchor="middle"))
elif exercise_key == "standing_posture":
# Standing upright neutral spine
cx = dw // 2
# head
g.add(Circle(cx, 95, 10, fillColor=TEAL, strokeColor=TEAL_LIGHT, strokeWidth=1))
# spine (straight)
g.add(Line(cx, 84, cx, 55, strokeColor=GRAY_DARK, strokeWidth=3))
# shoulders
g.add(Line(cx-18, 80, cx+18, 80, strokeColor=GRAY_DARK, strokeWidth=2))
# arms down
g.add(Line(cx-18, 80, cx-20, 65, strokeColor=GRAY_DARK, strokeWidth=1.5))
g.add(Line(cx-20, 65, cx-19, 56, strokeColor=GRAY_DARK, strokeWidth=1.5))
g.add(Line(cx+18, 80, cx+20, 65, strokeColor=GRAY_DARK, strokeWidth=1.5))
g.add(Line(cx+20, 65, cx+19, 56, strokeColor=GRAY_DARK, strokeWidth=1.5))
# pelvis
g.add(Line(cx-10, 55, cx+10, 55, strokeColor=GRAY_DARK, strokeWidth=2))
# legs
g.add(Line(cx-8, 55, cx-10, 30, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(cx-10, 30, cx-11, 18, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(cx+8, 55, cx+10, 30, strokeColor=GRAY_DARK, strokeWidth=2))
g.add(Line(cx+10, 30, cx+11, 18, strokeColor=GRAY_DARK, strokeWidth=2))
# plumb line
g.add(Line(cx+28, 95, cx+28, 18, strokeColor=ORANGE, strokeWidth=1, strokeDashArray=[2,2]))
g.add(String(cx, 8, "Neutral spine", fontSize=7, fillColor=TEAL,
fontName="Helvetica-Bold", textAnchor="middle"))
d.add(g)
return d
# ── Exercise data ─────────────────────────────────────────────────────────────
PHASE1 = [
{
"key": "pelvic_tilt",
"name": "1. Pelvic Tilt",
"sets": "2 sets x 10 reps",
"hold": "5 seconds each",
"position": "Lie on your back, knees bent, feet flat on floor.",
"steps": [
"Gently flatten your lower back against the floor by tightening your abdominals.",
"Tilt your pelvis slightly toward your navel.",
"Hold 5 seconds, then slowly release.",
"Do NOT arch your back or hold your breath.",
],
"benefit": "Activates deep core stabilisers, reduces intradiscal pressure at L5-S1.",
"avoid": "Do not forcefully push your back down.",
"tag": "FOUNDATION",
"tag_col": TEAL,
},
{
"key": "knee_to_chest",
"name": "2. Single Knee-to-Chest Stretch",
"sets": "3 reps each leg",
"hold": "20-30 seconds",
"position": "Lie on your back, both knees bent.",
"steps": [
"Bring one knee slowly toward your chest using both hands.",
"Keep the other foot flat on the floor.",
"Hold the stretch - you should feel mild tension, not pain.",
"Lower slowly and repeat with the other leg.",
],
"benefit": "Gently decompresses L5-S1 and lengthens the piriformis and lumbar extensors.",
"avoid": "Do not pull both knees simultaneously if it causes back pain.",
"tag": "FOUNDATION",
"tag_col": TEAL,
},
{
"key": "cat_cow",
"name": "3. Cat-Cow Mobilisation",
"sets": "2 sets x 10 cycles",
"hold": "Slow, controlled breathing",
"position": "Start on hands and knees (quadruped), wrists under shoulders, knees under hips.",
"steps": [
"CAT: Exhale, round your spine toward the ceiling, tuck chin and tailbone under.",
"COW: Inhale, let belly drop, lift chest and tailbone gently upward.",
"Move slowly between positions - do NOT force range of motion.",
"Stop if you feel sharp or radiating pain.",
],
"benefit": "Restores segmental mobility at L5-S1, lubricates facet joints, reduces morning stiffness.",
"avoid": "Avoid if quadruped position itself causes leg pain - try gentle supine rocking instead.",
"tag": "MOBILITY",
"tag_col": ORANGE,
},
]
PHASE2 = [
{
"key": "glute_bridge",
"name": "4. Glute Bridge",
"sets": "3 sets x 12 reps",
"hold": "2-second hold at top",
"position": "Lie on your back, knees bent, feet hip-width apart, arms by sides.",
"steps": [
"Engage core (pelvic tilt first), then press through heels to lift hips.",
"Lift until shoulders, hips, and knees form a straight line.",
"Squeeze glutes at the top - hold 2 seconds.",
"Lower slowly (3 seconds down).",
],
"benefit": "Strengthens gluteus maximus (S1 innervated), reduces load on the disc.",
"avoid": "Do not hyperextend the lower back at the top. Stop if you feel hamstring cramps.",
"tag": "STRENGTHENING",
"tag_col": GREEN,
},
{
"key": "bird_dog",
"name": "5. Bird-Dog",
"sets": "3 sets x 8 reps each side",
"hold": "5 seconds at top",
"position": "On hands and knees - neutral spine (not arched, not rounded).",
"steps": [
"Brace your core gently before moving.",
"Slowly extend the RIGHT arm and LEFT leg simultaneously.",
"Keep hips level - imagine balancing a glass of water on your lower back.",
"Hold 5 seconds, return, then switch sides.",
],
"benefit": "Targets multifidus and transversus abdominis - the key stabilisers of L5-S1.",
"avoid": "Do not let your hips rotate or your lower back arch. Move slowly.",
"tag": "STRENGTHENING",
"tag_col": GREEN,
},
{
"key": "child_pose",
"name": "6. Child's Pose (Modified)",
"sets": "3 reps",
"hold": "30-45 seconds",
"position": "Start kneeling, sit back toward heels (place a pillow under knees if needed).",
"steps": [
"Walk hands forward until forehead rests on the floor (or a folded towel).",
"Let your lower back gently stretch.",
"Breathe slowly and deeply - relax on each exhale.",
"Walk hands slightly to each side to stretch each side of the spine.",
],
"benefit": "Gentle lumbar flexion traction; relieves facet joint compression and muscle spasm.",
"avoid": "Avoid if sitting back on heels causes more foot/leg pain. Use a wider knee stance.",
"tag": "FLEXIBILITY",
"tag_col": ORANGE,
},
{
"key": "wall_hamstring",
"name": "7. Supine Hamstring Stretch (Wall-Assisted)",
"sets": "3 reps each leg",
"hold": "30 seconds",
"position": "Lie on your back near a wall.",
"steps": [
"Place one heel up against the wall - keep the knee as straight as comfortable.",
"Other knee remains bent with foot flat on floor.",
"Slide closer to the wall to increase stretch gradually.",
"Feel gentle pull behind the thigh - STOP if calf or foot pain increases.",
],
"benefit": "Lengthens the hamstrings safely without loading the lumbar spine in flexion.",
"avoid": "Do NOT do standing toe-touches - that compresses the herniated disc. This is the safe alternative.",
"tag": "FLEXIBILITY",
"tag_col": ORANGE,
},
]
POSTURE_TIPS = [
("Sitting", [
"Use a lumbar roll or rolled towel to support the natural curve of your lower back.",
"Hips and knees at 90 degrees, feet flat on floor.",
"Screen at eye level - avoid slumping forward.",
"Stand up and walk 2-3 minutes every 30-40 minutes.",
]),
("Standing", [
"Maintain the natural inward curve (lordosis) of the lower back - do not flatten or exaggerate.",
"Keep weight evenly on both feet.",
"Avoid standing with one hip shifted out (avoid the 'S' posture).",
"If standing long periods, place one foot on a low stool alternately.",
]),
("Sleeping", [
"BEST: On your side with a pillow between your knees - keeps the pelvis neutral.",
"ACCEPTABLE: On your back with a pillow under your knees - reduces lumbar extension.",
"AVOID: Sleeping face-down (prone) - extends the lumbar spine and stresses the nerve.",
]),
("Lifting", [
"Squat down using your legs, NOT your back.",
"Hold the object close to your body.",
"Brace your core BEFORE lifting.",
"NEVER twist while lifting - turn your whole body by stepping.",
]),
]
AVOID_LIST = [
("Deadlifts & heavy squats", "Axial compressive load + flexion - worst combination for L5-S1"),
("Sit-ups & crunches", "Repetitive lumbar flexion pushes disc material into nerve root"),
("Toe touches (standing)", "Deep spinal flexion under gravity loads the posterior disc"),
("Running & jumping", "High-impact force transmitted directly to L5-S1"),
("Russian twists", "Rotational load on an already irritated segment"),
("Prone lying (face down)", "Lumbar hyperextension can worsen S1 nerve traction"),
("Prolonged sitting (slumped)", "Sustained flexion increases intradiscal pressure 40%"),
("Leg press (deep angle)", "Knees-to-chest position creates lumbar flexion under load"),
]
WARNING_SIGNS = [
"Sudden weakness in foot or leg (foot drop)",
"Loss of bladder or bowel control",
"Rapidly worsening numbness in both legs",
"Severe pain that does not ease at all with rest",
]
# ── Page templates ─────────────────────────────────────────────────────────────
def cover_page(canvas, doc):
canvas.saveState()
# Full-bleed gradient background
canvas.setFillColor(TEAL)
canvas.rect(0, 0, W, H, fill=1, stroke=0)
# Decorative circles
canvas.setFillColor(colors.HexColor("#164F55"))
canvas.circle(W - 60, H - 60, 140, fill=1, stroke=0)
canvas.setFillColor(colors.HexColor("#12474D"))
canvas.circle(60, 80, 100, fill=1, stroke=0)
canvas.setFillColor(colors.HexColor("#1E7A82"))
canvas.circle(W/2, H*0.38, 180, fill=1, stroke=0)
# Title block
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 32)
canvas.drawCentredString(W/2, H*0.72, "L5-S1 Recovery Guide")
canvas.setFont("Helvetica-Bold", 18)
canvas.setFillColor(colors.HexColor("#A8DADC"))
canvas.drawCentredString(W/2, H*0.65, "Safe Stretches & Exercises")
canvas.drawCentredString(W/2, H*0.61, "for S1 Nerve Root Radiculopathy")
# Divider
canvas.setStrokeColor(ORANGE)
canvas.setLineWidth(2)
canvas.line(W*0.25, H*0.58, W*0.75, H*0.58)
# Sub-info
canvas.setFont("Helvetica", 11)
canvas.setFillColor(colors.HexColor("#D0EEF0"))
canvas.drawCentredString(W/2, H*0.54, "For burning pain & numbness along the lateral foot,")
canvas.drawCentredString(W/2, H*0.51, "4th/5th toes and heel (S1 dermatome)")
# Bottom badge
canvas.setFillColor(ORANGE)
canvas.roundRect(W*0.3, 55, W*0.4, 34, 8, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 10)
canvas.drawCentredString(W/2, 78, "CONSULT YOUR PHYSICIAN BEFORE STARTING")
canvas.drawCentredString(W/2, 64, "This guide is educational, not a substitute for medical advice")
canvas.restoreState()
def page_header_footer(canvas, doc):
canvas.saveState()
# Header bar
canvas.setFillColor(TEAL)
canvas.rect(0, H - 32, W, 32, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 10)
canvas.drawString(20, H - 20, "L5-S1 Recovery Guide")
canvas.setFont("Helvetica", 9)
canvas.drawRightString(W - 20, H - 20, f"Page {doc.page - 1}")
# Footer
canvas.setFillColor(TEAL_LIGHT)
canvas.rect(0, 0, W, 24, fill=1, stroke=0)
canvas.setFillColor(GRAY_MID)
canvas.setFont("Helvetica", 7.5)
canvas.drawCentredString(W/2, 8,
"Educational guide only. Always seek professional medical advice before exercising.")
canvas.restoreState()
# ── Build content ─────────────────────────────────────────────────────────────
def badge(text, col):
data = [[Paragraph(f'<font color="white"><b>{text}</b></font>',
ParagraphStyle("b", fontSize=7, leading=9,
fontName="Helvetica-Bold", alignment=TA_CENTER))]]
t = Table(data, colWidths=[70])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), col),
("ROUNDEDCORNERS", [4]),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
return t
def exercise_card(ex):
diagram = make_diagram(ex["key"])
# Info column
info = []
info.append(Paragraph(ex["name"], S_EXERCISE_TITLE))
info.append(Spacer(1, 2))
sets_rep = f'<b>Sets/Reps:</b> {ex["sets"]} | <b>Hold:</b> {ex["hold"]}'
info.append(Paragraph(sets_rep, style("Normal", fontSize=9, leading=13,
textColor=TEAL, fontName="Helvetica")))
info.append(Spacer(1, 4))
info.append(Paragraph(f'<b>Starting Position:</b> {ex["position"]}',
style("Normal", fontSize=9, leading=13, textColor=GRAY_DARK,
fontName="Helvetica")))
info.append(Spacer(1, 3))
for step in ex["steps"]:
info.append(Paragraph(f"• {step}", S_BULLET))
info.append(Spacer(1, 3))
benefit_p = Paragraph(
f'<font color="#27AE60"><b>Benefit:</b></font> <font color="#2C3E50">{ex["benefit"]}</font>',
style("Normal", fontSize=9, leading=13, fontName="Helvetica"))
info.append(benefit_p)
avoid_p = Paragraph(
f'<font color="#C0392B"><b>Note:</b></font> <font color="#C0392B">{ex["avoid"]}</font>',
style("Normal", fontSize=9, leading=13, fontName="Helvetica"))
info.append(avoid_p)
# Card table
card_data = [[diagram, info]]
card = Table(card_data, colWidths=[168, 330])
card.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), WHITE),
("BOX", (0,0), (-1,-1), 1, TEAL_LIGHT),
("ROUNDEDCORNERS",[6]),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (0,-1), 8),
("RIGHTPADDING", (0,0), (0,-1), 4),
("LEFTPADDING", (1,0), (1,-1), 10),
("RIGHTPADDING", (1,0), (1,-1), 10),
]))
return card
def build_story():
story = []
# ── Cover (first page, no header) ────────────────────────────────────────
story.append(Spacer(1, H * 0.9)) # placeholder - cover is drawn in onFirstPage
story.append(PageBreak())
# ── Page 2: About This Guide + Nerve overview ─────────────────────────────
story.append(Paragraph("About This Guide", S_SECTION))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL_LIGHT, spaceAfter=8))
intro = (
"This guide is designed for individuals experiencing pain, burning, or numbness "
"along the <b>S1 dermatome</b> - the lateral and plantar surface of the foot, "
"4th and 5th toes, and heel - caused by a herniated disc at the "
"<b>L5-S1 intervertebral level</b>. "
"The programme follows a progressive two-phase approach: Phase 1 focuses on "
"pain relief and gentle mobilisation; Phase 2 introduces stabilisation and "
"strengthening once symptoms allow."
)
story.append(Paragraph(intro, S_BODY))
story.append(Spacer(1, 10))
# Anatomy quick-ref table
anat_data = [
[Paragraph("<b>Disc</b>", S_LABEL),
Paragraph("<b>Nerve Root</b>", S_LABEL),
Paragraph("<b>Sensory Area</b>", S_LABEL),
Paragraph("<b>Reflex</b>", S_LABEL)],
["L3-L4", "L4", "Medial leg, medial malleolus", "Knee jerk"],
["L4-L5", "L5", "Dorsum of foot, big toe", "Posterior tibial (variable)"],
["L5-S1", "S1", "Lateral/plantar foot, 4th-5th toes, heel", "Ankle jerk (absent/reduced)"],
]
anat_t = Table(anat_data, colWidths=[60, 70, 230, 120])
anat_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9),
("ROWBACKGROUNDS",(0,1),(-1,-1), [WHITE, TEAL_LIGHT]),
("ROWBACKGROUNDS",(0,3),(-1,3), [ORANGE_LIGHT]),
("FONTNAME", (0,3), (-1,3), "Helvetica-Bold"),
("BOX", (0,0), (-1,-1), 0.5, TEAL),
("INNERGRID", (0,0), (-1,-1), 0.25, GRAY_LIGHT),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("ALIGN", (0,0), (-1,-1), "LEFT"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(anat_t)
story.append(Spacer(1, 8))
story.append(Paragraph(
"The highlighted row (orange) shows the level causing your symptoms.",
style("Normal", fontSize=8, leading=11, textColor=ORANGE, fontName="Helvetica-Oblique")
))
story.append(Spacer(1, 14))
# General rules
story.append(Paragraph("General Rules Before You Begin", S_SECTION))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL_LIGHT, spaceAfter=8))
rules = [
"Always warm up with 5 minutes of gentle walking before stretching.",
"Pain should NOT exceed 3/10 during any exercise. Stop if it reaches 4/10.",
"Never push through numbness or tingling that increases in your foot.",
"Perform exercises on a firm, non-slip surface (yoga mat on floor).",
"Consistency matters more than intensity - do these daily, gently.",
"Progress gradually - add sets/reps only when pain-free for 3 consecutive days.",
]
for r in rules:
story.append(Paragraph(f"✓ {r}", S_BULLET))
story.append(Spacer(1, 6))
# Warning box
warn_data = [[
Paragraph('<b>⚠ STOP IMMEDIATELY and seek urgent medical care if you experience:</b>',
style("Normal", fontSize=9.5, leading=14, textColor=RED, fontName="Helvetica-Bold")),
]]
for w in WARNING_SIGNS:
warn_data.append([Paragraph(f"• {w}",
style("Normal", fontSize=9, leading=13,
textColor=colors.HexColor("#8B0000"),
fontName="Helvetica"))])
warn_t = Table(warn_data, colWidths=[490])
warn_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), RED_LIGHT),
("BOX", (0,0), (-1,-1), 1.5, RED),
("ROUNDEDCORNERS",[6]),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 10),
]))
story.append(warn_t)
story.append(PageBreak())
# ── Phase 1 ───────────────────────────────────────────────────────────────
phase1_header = Table(
[[Paragraph("Phase 1: Pain Relief & Gentle Mobilisation",
style("Heading1", fontSize=15, leading=20, textColor=WHITE,
fontName="Helvetica-Bold"))]],
colWidths=[490]
)
phase1_header.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("ROUNDEDCORNERS",[8]),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING",(0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 14),
]))
story.append(phase1_header)
story.append(Spacer(1, 4))
story.append(Paragraph(
"Perform these exercises daily from Day 1. Focus on reducing nerve irritation and "
"restoring gentle range of motion. Do NOT force any movement.",
style("Normal", fontSize=9.5, leading=14, textColor=GRAY_MID,
fontName="Helvetica-Oblique")
))
story.append(Spacer(1, 10))
for ex in PHASE1:
story.append(KeepTogether([exercise_card(ex), Spacer(1, 12)]))
story.append(PageBreak())
# ── Phase 2 ───────────────────────────────────────────────────────────────
phase2_header = Table(
[[Paragraph("Phase 2: Core Stabilisation & Strengthening",
style("Heading1", fontSize=15, leading=20, textColor=WHITE,
fontName="Helvetica-Bold"))]],
colWidths=[490]
)
phase2_header.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GREEN),
("ROUNDEDCORNERS",[8]),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING",(0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 14),
]))
story.append(phase2_header)
story.append(Spacer(1, 4))
story.append(Paragraph(
"Begin Phase 2 when resting pain is below 3/10 and Phase 1 exercises are pain-free "
"(typically 2-4 weeks in). Continue Phase 1 alongside these.",
style("Normal", fontSize=9.5, leading=14, textColor=GRAY_MID,
fontName="Helvetica-Oblique")
))
story.append(Spacer(1, 10))
for ex in PHASE2:
story.append(KeepTogether([exercise_card(ex), Spacer(1, 12)]))
story.append(PageBreak())
# ── Posture Guide ─────────────────────────────────────────────────────────
story.append(Paragraph("Posture & Daily Life Guide", S_SECTION))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL_LIGHT, spaceAfter=8))
for section_name, tips in POSTURE_TIPS:
section_data = [[
Paragraph(section_name, style("Heading3", fontSize=11, leading=14,
textColor=TEAL, fontName="Helvetica-Bold")),
]]
for t in tips:
col = RED if t.startswith("AVOID") else (GREEN if t.startswith("BEST") else GRAY_DARK)
prefix = "⚠" if t.startswith("AVOID") else ("✓" if t.startswith("BEST") or t.startswith("ACCEPTABLE") else "•")
section_data.append([
Paragraph(f'{prefix} {t}',
style("Normal", fontSize=9, leading=13, textColor=col,
fontName="Helvetica", leftIndent=12))
])
pt = Table(section_data, colWidths=[490])
pt.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), TEAL_LIGHT),
("BACKGROUND", (0,1), (0,-1), WHITE),
("BOX", (0,0), (-1,-1), 0.5, TEAL_LIGHT),
("ROUNDEDCORNERS",[4]),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 10),
]))
story.append(pt)
story.append(Spacer(1, 8))
story.append(PageBreak())
# ── Avoid List ────────────────────────────────────────────────────────────
story.append(Paragraph("Exercises & Postures to AVOID", S_SECTION))
story.append(HRFlowable(width="100%", thickness=1.5, color=RED, spaceAfter=8))
story.append(Paragraph(
"The following movements significantly increase intradiscal pressure or nerve "
"tension at L5-S1. Avoid until cleared by your spine specialist.",
S_BODY
))
story.append(Spacer(1, 8))
avoid_header = [
[Paragraph("<b>Exercise / Activity</b>", S_LABEL),
Paragraph("<b>Why it Harms L5-S1</b>", S_LABEL)]
]
avoid_rows = [[
Paragraph(f"✘ {a}", style("Normal", fontSize=9.5, leading=13,
textColor=RED, fontName="Helvetica-Bold")),
Paragraph(r, style("Normal", fontSize=9.5, leading=13,
textColor=GRAY_DARK, fontName="Helvetica"))
] for a, r in AVOID_LIST]
avoid_t = Table(avoid_header + avoid_rows, colWidths=[200, 290])
avoid_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1),(-1,-1), [RED_LIGHT, WHITE]),
("BOX", (0,0), (-1,-1), 0.5, RED),
("INNERGRID", (0,0), (-1,-1), 0.25, colors.HexColor("#F5B7B1")),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("FONTSIZE", (0,0), (-1,-1), 9.5),
]))
story.append(avoid_t)
story.append(Spacer(1, 14))
# ── Weekly Schedule table ──────────────────────────────────────────────────
story.append(Paragraph("Suggested Weekly Schedule", S_SECTION))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL_LIGHT, spaceAfter=8))
sched_data = [
[Paragraph("<b>Day</b>", S_LABEL),
Paragraph("<b>Activity</b>", S_LABEL),
Paragraph("<b>Duration</b>", S_LABEL)],
["Mon / Wed / Fri", "Phase 1 full + gentle walking", "20-25 min"],
["Tue / Thu", "Phase 1 + Phase 2 (when ready)", "30-35 min"],
["Sat", "Phase 1 only + posture review", "15-20 min"],
["Sun", "Rest / gentle walk only", "10-15 min"],
]
sched_t = Table(sched_data, colWidths=[160, 230, 100])
sched_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1),(-1,-1), [WHITE, TEAL_LIGHT]),
("BOX", (0,0), (-1,-1), 0.5, TEAL),
("INNERGRID", (0,0), (-1,-1), 0.25, GRAY_LIGHT),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ALIGN", (0,0), (-1,-1), "LEFT"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(sched_t)
story.append(Spacer(1, 20))
# Disclaimer
disc_data = [[Paragraph(
"<b>Medical Disclaimer:</b> This guide is for educational purposes only and is based on "
"established physiotherapy principles for L5-S1 disc herniation with S1 radiculopathy. "
"It does not replace a formal clinical assessment, MRI confirmation, or individualised "
"treatment programme. Always consult a qualified physiotherapist, orthopaedic surgeon, "
"or neurologist before starting any exercise programme. If symptoms worsen, stop "
"immediately and seek medical attention.",
style("Normal", fontSize=8.5, leading=13, textColor=GRAY_DARK, fontName="Helvetica")
)]]
disc_t = Table(disc_data, colWidths=[490])
disc_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GRAY_LIGHT),
("BOX", (0,0), (-1,-1), 0.5, GRAY_MID),
("ROUNDEDCORNERS",[4]),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING",(0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
]))
story.append(disc_t)
return story
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=2.5*cm,
rightMargin=2.5*cm,
topMargin=1.5*cm,
bottomMargin=2*cm,
)
story = build_story()
def on_first_page(canvas, doc):
cover_page(canvas, doc)
def on_later_pages(canvas, doc):
page_header_footer(canvas, doc)
doc.build(story,
onFirstPage=on_first_page,
onLaterPages=on_later_pages)
print(f"PDF created: {OUTPUT}")
if __name__ == "__main__":
main()
cd /home/daytona/workspace/l5s1-guide && python generate_guide.py 2>&1
~/l5s1-guide/generate_guide.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/l5s1-guide/generate_guide.py'
cd /home/daytona/workspace/l5s1-guide && python generate_guide.py 2>&1
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/l5s1-guide/L5S1_Recovery_Guide.pdf') print(f'Pages: {len(r.pages)}') import os size = os.path.getsize('/home/daytona/workspace/l5s1-guide/L5S1_Recovery_Guide.pdf') print(f'Size: {size/1024:.1f} KB') "
L5S1 Recovery Guide
PDF Document · PDF