~/mnd-pptx/create_mnd_pptx.py
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.oxml.ns import qn
from lxml import etree
from io import BytesIO
# ── Colour palette ──────────────────────────────────────────────────────────
NAVY = RGBColor(0x0D, 0x2B, 0x55) # dark header bg
TEAL = RGBColor(0x00, 0x7A, 0x8A) # accent / stage bars
GREEN = RGBColor(0x2E, 0x86, 0x48) # Stage 1
AMBER = RGBColor(0xE8, 0x8C, 0x17) # Stage 2
ORANGE = RGBColor(0xD4, 0x4F, 0x1C) # Stage 3
PURPLE = RGBColor(0x6A, 0x35, 0x8A) # Stage 4 / palliative
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
LIGHT_GREY = RGBColor(0xF2, 0xF4, 0xF7)
MID_GREY = RGBColor(0xCC, 0xD0, 0xD8)
DARK_GREY = RGBColor(0x3C, 0x3C, 0x3C)
STAGE_COLORS = [GREEN, AMBER, ORANGE, PURPLE]
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
blank = prs.slide_layouts[6]
# ── Helpers ──────────────────────────────────────────────────────────────────
def add_rect(slide, x, y, w, h, fill_rgb, alpha_pct=None):
shape = slide.shapes.add_shape(1, Inches(x), Inches(y), Inches(w), Inches(h))
shape.line.fill.background()
shape.fill.solid()
shape.fill.fore_color.rgb = fill_rgb
return shape
def add_text(slide, text, x, y, w, h,
font_size=16, bold=False, color=DARK_GREY,
align=PP_ALIGN.LEFT, wrap=True, italic=False,
v_anchor=MSO_ANCHOR.TOP):
tb = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
tf = tb.text_frame
tf.word_wrap = wrap
tf.margin_left = Inches(0.05)
tf.margin_right = Inches(0.05)
tf.margin_top = Pt(2)
tf.margin_bottom = Pt(2)
tf.vertical_anchor = v_anchor
p = tf.paragraphs[0]
p.alignment = align
run = p.add_run()
run.text = text
run.font.size = Pt(font_size)
run.font.bold = bold
run.font.italic = italic
run.font.color.rgb = color
run.font.name = "Calibri"
return tb
def add_bullet_box(slide, items, x, y, w, h,
font_size=13, color=DARK_GREY, title=None,
title_color=NAVY, title_size=14, bg=None, pad=0.12):
if bg:
add_rect(slide, x, y, w, h, bg)
tb = slide.shapes.add_textbox(Inches(x + pad), Inches(y + pad),
Inches(w - 2*pad), Inches(h - 2*pad))
tf = tb.text_frame
tf.word_wrap = True
tf.margin_left = Inches(0.05)
tf.margin_right = Inches(0.05)
tf.margin_top = Pt(2)
tf.margin_bottom = Pt(2)
first = True
if title:
p = tf.paragraphs[0] if first else tf.add_paragraph()
first = False
run = p.add_run()
run.text = title
run.font.size = Pt(title_size)
run.font.bold = True
run.font.color.rgb = title_color
run.font.name = "Calibri"
for item in items:
p = tf.add_paragraph() if not first else tf.paragraphs[0]
first = False
p.space_before = Pt(2)
run = p.add_run()
run.text = f"• {item}"
run.font.size = Pt(font_size)
run.font.color.rgb = color
run.font.name = "Calibri"
def header_bar(slide, title, subtitle=None):
"""Dark navy top bar with title."""
add_rect(slide, 0, 0, 13.333, 1.1, NAVY)
add_text(slide, title,
0.3, 0.08, 12.5, 0.65,
font_size=28, bold=True, color=WHITE,
align=PP_ALIGN.LEFT, v_anchor=MSO_ANCHOR.MIDDLE)
if subtitle:
add_text(slide, subtitle,
0.3, 0.72, 12.5, 0.35,
font_size=14, bold=False, color=MID_GREY,
align=PP_ALIGN.LEFT)
# teal accent line below header
add_rect(slide, 0, 1.1, 13.333, 0.06, TEAL)
def stage_label(slide, label, x, y, w, h, color):
add_rect(slide, x, y, w, h, color)
add_text(slide, label, x, y, w, h,
font_size=12, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 1 – Title Slide
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
# Full background gradient simulation with two rects
add_rect(slide, 0, 0, 13.333, 7.5, NAVY)
add_rect(slide, 0, 4.8, 13.333, 2.7, TEAL)
# Decorative accent
add_rect(slide, 0, 4.6, 13.333, 0.22, RGBColor(0xFF, 0xFF, 0xFF))
# Main title
add_text(slide,
"Physiotherapy Progression\nThrough MND Stages",
1.0, 1.0, 11.0, 3.0,
font_size=40, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
# Subtitle
add_text(slide,
"Motor Neuron Disease · Stage-by-Stage Rehabilitation Framework",
1.0, 3.8, 11.0, 0.6,
font_size=18, bold=False, color=MID_GREY,
align=PP_ALIGN.CENTER)
# Stage pills at bottom
stage_labels = ["Stage 1\nEarly", "Stage 2\nMiddle", "Stage 3\nLate", "Stage 4\nPalliative"]
for i, (lbl, col) in enumerate(zip(stage_labels, STAGE_COLORS)):
x = 1.0 + i * 2.9
add_rect(slide, x, 5.2, 2.5, 0.9, col)
add_text(slide, lbl, x, 5.2, 2.5, 0.9,
font_size=13, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
add_text(slide, "Based on NICE NG42 | Irish MND Guidelines | Goldman-Cecil Medicine",
0, 6.9, 13.333, 0.4,
font_size=10, color=RGBColor(0xCC, 0xDD, 0xFF),
align=PP_ALIGN.CENTER)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 2 – MND Overview: What is MND?
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
header_bar(slide, "What is Motor Neuron Disease (MND)?",
"Understanding the disease before planning rehabilitation")
# Left column: key facts
add_bullet_box(slide,
["Progressive degeneration of UMN (motor cortex) & LMN (brainstem, spinal cord)",
"Incidence ~2/100,000 per year | Prevalence 5–8/100,000",
"Mean age of onset: 55–60 years | Male > Female",
"Median survival: 2–5 years (bulbar onset worst; PLS best)",
"Cause of death: respiratory failure in vast majority"],
x=0.3, y=1.3, w=6.5, h=3.5,
font_size=13, title="Key Facts", title_color=NAVY, bg=WHITE)
# Right column: types
add_bullet_box(slide,
["Classic ALS – mixed UMN + LMN (most common, 80%)",
"Progressive Bulbar Palsy (PBP) – bulbar onset, worst prognosis",
"Primary Lateral Sclerosis (PLS) – UMN only, best prognosis",
"Progressive Muscular Atrophy (PMA) – LMN only",
"Flail Arm / Flail Leg – segmental; longer survival"],
x=7.0, y=1.3, w=6.0, h=3.5,
font_size=13, title="Clinical Variants", title_color=TEAL, bg=WHITE)
# Bottom bar: PT role
add_rect(slide, 0.3, 5.1, 12.7, 2.0, NAVY)
add_text(slide,
"Role of Physiotherapy in MND",
0.5, 5.15, 12.3, 0.45,
font_size=14, bold=True, color=WHITE)
add_text(slide,
"Physiotherapy does NOT cure MND, but significantly improves quality of life, prolongs function, prevents "
"complications, and supports respiratory health across all stages. Management is multidisciplinary: "
"neurologist, PT, OT, SLT, dietitian, respiratory physician, palliative care.",
0.5, 5.6, 12.3, 1.4,
font_size=12, color=LIGHT_GREY, wrap=True)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 3 – Disease Progression Timeline / Stages Overview
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
header_bar(slide, "MND Stages & Physiotherapy Framework",
"Staging guides goal-setting, exercise intensity, and equipment provision")
# Timeline arrow base
add_rect(slide, 0.3, 2.15, 12.4, 0.22, MID_GREY)
stage_data = [
("STAGE 1", "Early / Diagnosis", GREEN,
["Mild focal weakness", "Near-normal mobility", "Diagnosis shock"],
0.3),
("STAGE 2", "Moderate / Functional Loss", AMBER,
["Spreading weakness", "Mobility aids needed", "Fatigue prominent"],
3.55),
("STAGE 3", "Advanced / Dependence", ORANGE,
["Severe weakness", "Wheelchair use", "Respiratory compromise"],
6.8),
("STAGE 4", "End-Stage / Palliative", PURPLE,
["Minimal movement", "Ventilatory support", "Comfort focus"],
10.05),
]
for label, sub, color, features, x in stage_data:
w = 3.0
# Stage box
add_rect(slide, x, 1.3, w, 0.75, color)
add_text(slide, f"{label}\n{sub}", x, 1.3, w, 0.75,
font_size=11, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
# Arrow node
add_rect(slide, x + w/2 - 0.12, 2.02, 0.24, 0.38, color)
# Features below arrow
feat_text = "\n".join(f"› {f}" for f in features)
add_text(slide, feat_text, x, 2.5, w, 1.5,
font_size=11, color=DARK_GREY, wrap=True)
# ALSFRS-R scale indicator
add_rect(slide, 0.3, 4.15, 12.4, 0.06, MID_GREY)
add_text(slide, "ALSFRS-R Scale (48 → 0)", 0.3, 4.25, 3.0, 0.35,
font_size=11, bold=True, color=DARK_GREY)
vals = ["~40–48", "~25–39", "~10–24", "~0–9"]
for i, (val, x) in enumerate(zip(vals, [0.3, 3.55, 6.8, 10.05])):
add_rect(slide, x, 4.25, 3.0, 0.35, STAGE_COLORS[i])
add_text(slide, val, x, 4.25, 3.0, 0.35,
font_size=12, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
# Bottom: general PT principle
add_rect(slide, 0.3, 4.8, 12.7, 2.4, WHITE)
add_text(slide,
"Core PT Principle: Never Cause Overwork Weakness",
0.5, 4.88, 12.3, 0.45,
font_size=14, bold=True, color=TEAL)
add_text(slide,
"Denervated muscles are vulnerable to eccentric damage. Exercise must be SUBMAXIMAL. "
"As the disease progresses, shift emphasis: early (strengthen & mobilise) → middle (maintain & compensate) "
"→ late (prevent complications & assist) → palliative (comfort & dignity).",
0.5, 5.32, 12.3, 1.8,
font_size=12, color=DARK_GREY, wrap=True)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 4 – Stage 1: Early MND
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
# Coloured top bar with stage label
add_rect(slide, 0, 0, 13.333, 1.25, GREEN)
add_rect(slide, 0, 1.25, 13.333, 0.06, TEAL)
add_text(slide, "STAGE 1 · Early MND", 0.3, 0.05, 9.0, 0.7,
font_size=28, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
add_text(slide, "ALSFRS-R ~40–48 | Mild focal weakness | Near-normal independence",
0.3, 0.75, 11.0, 0.45, font_size=13, color=RGBColor(0xCC, 0xFF, 0xCC))
col_w = 4.0
col_y = 1.45
col_h = 4.7
gaps = [0.25, 4.45, 8.65]
add_bullet_box(slide,
["Asymmetric distal weakness (hand or foot)",
"Fatigue with prolonged activity",
"Near-normal gait; minor foot drop",
"Fasciculations / cramps",
"Mild dysarthria (bulbar onset)",
"Anxiety, adjustment to diagnosis"],
x=gaps[0], y=col_y, w=col_w, h=col_h,
font_size=12, title="Clinical Picture", title_color=GREEN, bg=WHITE)
add_bullet_box(slide,
["Submaximal resistance training (2–3×/week)",
"Aerobic exercise: cycling / swimming at 60–70% HRmax",
"Stretching: daily full ROM, Achilles + hip flexors",
"Gait analysis: AFO for foot drop if needed",
"Postural correction and ergonomic advice",
"Energy conservation strategies",
"Balance training: single-leg stance, foam pad"],
x=gaps[1], y=col_y, w=col_w, h=col_h,
font_size=12, title="Physiotherapy Goals & Interventions", title_color=GREEN, bg=WHITE)
add_bullet_box(slide,
["Baseline FVC, SNIP, PCF",
"Incentive spirometry",
"Diaphragmatic breathing technique",
"Educate on early NIV signs to watch",
"Walking stick (if unstable)",
"Wrist / ankle splints as needed",
"Home exercise programme (HEP)",
"Psychological support + peer referral"],
x=gaps[2], y=col_y, w=col_w, h=col_h,
font_size=12, title="Respiratory, Equipment & Education", title_color=TEAL, bg=WHITE)
# Bottom banner
add_rect(slide, 0.25, 6.3, 12.8, 0.9, GREEN)
add_text(slide,
"Goals: Maximise strength & endurance | Prevent deconditioning | Educate patient & family | Baseline documentation",
0.35, 6.3, 12.6, 0.9,
font_size=12, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 5 – Stage 2: Middle MND
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
add_rect(slide, 0, 0, 13.333, 1.25, AMBER)
add_rect(slide, 0, 1.25, 13.333, 0.06, TEAL)
add_text(slide, "STAGE 2 · Middle / Moderate MND", 0.3, 0.05, 9.0, 0.7,
font_size=28, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
add_text(slide, "ALSFRS-R ~25–39 | Spreading weakness | Mobility aids required",
0.3, 0.75, 11.0, 0.45, font_size=13, color=WHITE)
col_w = 4.0
col_y = 1.45
col_h = 4.7
add_bullet_box(slide,
["Bilateral limb involvement",
"Fatigue limits ADLs significantly",
"Rollator / walking frame required",
"Dysphagia / dysarthria progressing",
"Dropped head in some patients",
"Nocturnal hypoventilation beginning",
"Depression / grief common"],
x=gaps[0], y=col_y, w=col_w, h=col_h,
font_size=12, title="Clinical Picture", title_color=AMBER, bg=WHITE)
add_bullet_box(slide,
["Maintain ROM: daily passive + active-assisted stretching",
"Reduce resistance training intensity; prioritise function",
"Aquatic therapy: buoyancy-assisted strengthening",
"Spasticity management: stretching, positioning, splinting",
"Falls prevention: home hazard assessment",
"Cervical collar for dropped head syndrome",
"Wheelchair assessment (manual → consider powered)",
"Adaptive ADL training with OT"],
x=gaps[1], y=col_y, w=col_w, h=col_h,
font_size=12, title="Physiotherapy Interventions", title_color=AMBER, bg=WHITE)
add_bullet_box(slide,
["Serial FVC every 3 months (SNIP, PCF)",
"Nocturnal oximetry if symptomatic",
"Initiate BiPAP/NIV when FVC <50%",
"Manual cough assist technique",
"Rollator → walking frame → wheelchair",
"Hospital bed + pressure-relief mattress",
"Ceiling hoist / transfer belt",
"PEG discussion if dysphagia + weight loss"],
x=gaps[2], y=col_y, w=col_w, h=col_h,
font_size=12, title="Respiratory, Equipment & Education", title_color=TEAL, bg=WHITE)
add_rect(slide, 0.25, 6.3, 12.8, 0.9, AMBER)
add_text(slide,
"Goals: Maintain independence as long as possible | Prevent contractures & falls | Initiate respiratory support | Timely equipment",
0.35, 6.3, 12.6, 0.9,
font_size=12, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 6 – Stage 3: Late / Advanced MND
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
add_rect(slide, 0, 0, 13.333, 1.25, ORANGE)
add_rect(slide, 0, 1.25, 13.333, 0.06, TEAL)
add_text(slide, "STAGE 3 · Advanced / Late MND", 0.3, 0.05, 9.0, 0.7,
font_size=28, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
add_text(slide, "ALSFRS-R ~10–24 | Severe weakness | Full wheelchair dependence",
0.3, 0.75, 11.0, 0.45, font_size=13, color=WHITE)
col_w = 4.0
col_y = 1.45
col_h = 4.7
add_bullet_box(slide,
["Minimal or no voluntary limb movement",
"Wheelchair bound; needs hoisting",
"Severe dysarthria → AAC devices",
"Aspiration risk + weight loss",
"Significant respiratory compromise",
"Orthostatic hypotension risk",
"Skin integrity / pressure ulcer risk"],
x=gaps[0], y=col_y, w=col_w, h=col_h,
font_size=12, title="Clinical Picture", title_color=ORANGE, bg=WHITE)
add_bullet_box(slide,
["Passive ROM to ALL limbs: prevent pain + contracture",
"Positioning: anti-contracture splints; seating assessment",
"Pressure relief: 2-hourly turns if in bed",
"Oedema management: elevation + compression",
"Trunk support in wheelchair (customised seating)",
"Transfer training for carers (safe handling)",
"Minimal active exercise as tolerated",
"Hydrotherapy only if safe (aspiration risk)"],
x=gaps[1], y=col_y, w=col_w, h=col_h,
font_size=12, title="Physiotherapy Interventions", title_color=ORANGE, bg=WHITE)
add_bullet_box(slide,
["NIV: titrate to 24hr use as tolerated",
"Mechanical insufflation-exsufflation (CoughAssist)",
"Manual chest physiotherapy + suctioning",
"Discuss tracheostomy (patient decision)",
"Power wheelchair with head/eye control",
"Electric profiling bed; ceiling hoist",
"Specialised pressure-relieving cushion",
"Begin advance care planning discussions"],
x=gaps[2], y=col_y, w=col_w, h=col_h,
font_size=12, title="Respiratory, Equipment & Education", title_color=TEAL, bg=WHITE)
add_rect(slide, 0.25, 6.3, 12.8, 0.9, ORANGE)
add_text(slide,
"Goals: Prevent pressure ulcers & contractures | Airway clearance | Carer training | Advance care planning",
0.35, 6.3, 12.6, 0.9,
font_size=12, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 7 – Stage 4: Palliative / End-Stage MND
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
add_rect(slide, 0, 0, 13.333, 1.25, PURPLE)
add_rect(slide, 0, 1.25, 13.333, 0.06, TEAL)
add_text(slide, "STAGE 4 · Palliative / End-Stage MND", 0.3, 0.05, 9.0, 0.7,
font_size=28, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
add_text(slide, "ALSFRS-R ~0–9 | Ventilator dependent | Comfort and dignity",
0.3, 0.75, 11.0, 0.45, font_size=13, color=RGBColor(0xCC, 0xCC, 0xFF))
col_w = 4.0
col_y = 1.45
col_h = 4.7
add_bullet_box(slide,
["Locked-in or near-locked-in state",
"Completely ventilator dependent",
"Eye movements ± preserved",
"Communication via eye tracking only",
"Severe pain, dyspnoea, anxiety",
"End-of-life decisions imminent"],
x=gaps[0], y=col_y, w=col_w, h=col_h,
font_size=12, title="Clinical Picture", title_color=PURPLE, bg=WHITE)
add_bullet_box(slide,
["Gentle passive movements for comfort",
"Positioning: pain-free postures; pressure relief",
"Lymphoedema management (elevation, gentle massage)",
"Breathlessness: fan to face (trigeminal stimulation)",
"Pursed-lip breathing / pacing as able",
"Family / carer education: positioning, turns",
"Coordinate with palliative team for pain control",
"Psychological & spiritual support"],
x=gaps[1], y=col_y, w=col_w, h=col_h,
font_size=12, title="Physiotherapy Interventions", title_color=PURPLE, bg=WHITE)
add_bullet_box(slide,
["Opioids for refractory dyspnoea (do NOT hasten death)",
"Suctioning for secretion management",
"Withdrawal of ventilation: patient-led decision",
"Symptom comfort: benzodiazepines for anxiety",
"Eye-gaze device for communication",
"Community / hospice co-ordination",
"Bereavement support for family",
"Physiotherapy role = comfort + dignity"],
x=gaps[2], y=col_y, w=col_w, h=col_h,
font_size=12, title="Respiratory, Symptom & Psychosocial", title_color=TEAL, bg=WHITE)
add_rect(slide, 0.25, 6.3, 12.8, 0.9, PURPLE)
add_text(slide,
"Goals: Dignity & comfort | Symptom management | Family support | Peaceful end-of-life care",
0.35, 6.3, 12.6, 0.9,
font_size=12, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 8 – Respiratory Physiotherapy Across All Stages
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
header_bar(slide, "Respiratory Physiotherapy Across MND Stages",
"Respiratory failure is the most common cause of death — early monitoring is life-saving")
# Four stage columns
resp_data = [
("Stage 1", GREEN,
["Baseline FVC, SNIP, PCF",
"Teach diaphragmatic breathing",
"Incentive spirometry",
"Educate on warning signs",
"Glossopharyngeal breathing intro"]),
("Stage 2", AMBER,
["Serial FVC every 3 months",
"Nocturnal oximetry if needed",
"Initiate BiPAP / NIV at FVC <50%",
"Manual assisted cough",
"Mucolytics / nebulised saline"]),
("Stage 3", ORANGE,
["NIV up to 24hr / day",
"CoughAssist (MI-E) device",
"Chest physiotherapy + suctioning",
"Tracheostomy decision making",
"ICU liaison if acute deterioration"]),
("Stage 4", PURPLE,
["Comfort ventilation only",
"Opioids for dyspnoea",
"Suctioning as needed",
"Fan therapy for air hunger",
"Ventilator withdrawal (patient choice)"]),
]
for i, (stg, col, items) in enumerate(resp_data):
x = 0.3 + i * 3.25
add_rect(slide, x, 1.28, 3.0, 0.5, col)
add_text(slide, stg, x, 1.28, 3.0, 0.5,
font_size=14, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
add_bullet_box(slide, items,
x=x, y=1.82, w=3.0, h=4.2,
font_size=12, bg=WHITE)
# Key thresholds box
add_rect(slide, 0.3, 6.15, 12.7, 1.05, NAVY)
thresholds = ("Key Thresholds: FVC <50% → initiate NIV | "
"SNIP <40 cmH₂O → NIV urgently | "
"PCF <270 L/min → cough assist | "
"FVC <20 mL/kg → ICU/intubation (GBS-style if acute)")
add_text(slide, thresholds, 0.5, 6.18, 12.3, 1.0,
font_size=11, bold=False, color=WHITE, wrap=True)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 9 – Exercise Prescription Across Stages
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
header_bar(slide, "Exercise Prescription Across MND Stages",
"Submaximal loading — avoid overwork weakness at all stages")
# Table header
headers = ["Parameter", "Stage 1 (Early)", "Stage 2 (Middle)", "Stage 3 (Late)", "Stage 4 (Palliative)"]
col_colors = [NAVY, GREEN, AMBER, ORANGE, PURPLE]
col_x = [0.25, 2.65, 5.05, 7.45, 9.85]
col_w_vals = [2.3, 2.3, 2.3, 2.3, 3.2]
row_h = 0.62
for i, (hdr, col, x, w) in enumerate(zip(headers, col_colors, col_x, col_w_vals)):
add_rect(slide, x, 1.28, w, 0.5, col)
add_text(slide, hdr, x, 1.28, w, 0.5,
font_size=11, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
rows = [
("Resistance\nTraining",
"Low-mod resistance\n2–3×/week, 3×10 reps\nSubmax intensity",
"Light resistance only\nFocus on function\nAvoid fatigue",
"Passive/active-assisted\nGravity-eliminated\nAs tolerated",
"None / gentle\npassive movements"),
("Aerobic\nExercise",
"Cycling / swimming\n60–70% HRmax\n20–30 min, 3×/week",
"Aquatic therapy\nStationary cycling\nReduce duration",
"Supported seated\nexercise only\nShort sessions",
"Not applicable"),
("Stretching\n& ROM",
"Active: full ROM daily\nAchilles / hip flexors\nPosture correction",
"Active-assisted\n+ passive stretching\nAnti-spasticity",
"Passive ROM all limbs\nSplinting at night\nTurning schedule",
"Gentle passive\nmovements for\ncomfort only"),
("Balance\n& Gait",
"Single-leg stance\nFoam pad training\nAFO if foot drop",
"Walking frame\nFalls prevention\nGait re-education",
"Wheelchair mobility\nTransfer training\nCarer training",
"Positioning\nin bed / chair\nonly"),
("Key\nPrinciple",
"Strengthen &\nenergise",
"Maintain &\ncompensate",
"Prevent\ncomplications",
"Comfort\n& dignity"),
]
row_bgs = [WHITE, LIGHT_GREY, WHITE, LIGHT_GREY, MID_GREY]
for r_idx, (row, bg) in enumerate(zip(rows, row_bgs)):
y = 1.82 + r_idx * row_h
for c_idx, (cell, x, w) in enumerate(zip(row, col_x, col_w_vals)):
add_rect(slide, x, y, w, row_h, bg)
txt_color = DARK_GREY if c_idx == 0 else DARK_GREY
if c_idx == 0:
txt_color = NAVY
add_text(slide, cell, x + 0.05, y + 0.04, w - 0.1, row_h - 0.08,
font_size=9.5, color=txt_color,
bold=(c_idx == 0), wrap=True,
v_anchor=MSO_ANCHOR.TOP)
add_rect(slide, 0.25, 6.98, 12.8, 0.32, TEAL)
add_text(slide,
"⚠ OVERWORK WEAKNESS WARNING: Never exercise to exhaustion. If fatigue persists >30 min post-exercise, reduce intensity immediately.",
0.4, 6.98, 12.5, 0.32,
font_size=10, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 10 – Equipment Progression Timeline
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
header_bar(slide, "Equipment & Assistive Device Progression",
"Timely provision prevents falls, maintains independence, and reduces carer burden")
categories = [
("Mobility", ["Walking stick", "Crutch / Nordic poles", "Rollator frame", "Manual wheelchair", "Power wheelchair", "Eye-gaze chair control"]),
("Seating\n& Bed", ["Ergonomic chair", "Riser-recliner chair", "Customised wheelchair seating", "Hospital bed + rails", "Profiling bed", "Pressure-relief mattress"]),
("Respiratory", ["Incentive spirometer", "NIV/BiPAP mask + machine", "CoughAssist (MI-E)", "Suction machine", "Tracheostomy (if chosen)", "Ventilator"]),
("Transfers\n& Hoisting", ["Grab rails + bath aids", "Transfer belt", "Rotunda turn table", "Mobile hoist", "Ceiling track hoist", "Full-body sling"]),
("Communication", ["Voice amplifier", "Voice banking app", "Speech-to-text app", "AAC tablet device", "Eye-gaze device", "Brain-computer interface"]),
]
cat_x = [0.25, 2.9, 5.55, 8.2, 10.85]
cat_w = 2.4
stage_bands = [(GREEN, "S1"), (AMBER, "S1–2"), (AMBER, "S2"), (ORANGE, "S2–3"), (ORANGE, "S3"), (PURPLE, "S3–4")]
for cat_idx, ((cat_name, items), x) in enumerate(zip(categories, cat_x)):
# Header
add_rect(slide, x, 1.28, cat_w, 0.48, NAVY)
add_text(slide, cat_name, x, 1.28, cat_w, 0.48,
font_size=12, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
# Items
for item_idx, (item, (col, stg)) in enumerate(zip(items, stage_bands)):
iy = 1.82 + item_idx * 0.87
add_rect(slide, x, iy, cat_w, 0.82, WHITE)
add_rect(slide, x, iy, 0.35, 0.82, col)
add_text(slide, stg, x, iy, 0.35, 0.82,
font_size=8, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
add_text(slide, item, x + 0.38, iy + 0.05, cat_w - 0.42, 0.72,
font_size=10.5, color=DARK_GREY, wrap=True,
v_anchor=MSO_ANCHOR.MIDDLE)
# Legend
lx = 0.25
for col, stg, label in [(GREEN, "S1", "Stage 1"), (AMBER, "S2", "Stage 2"),
(ORANGE, "S3", "Stage 3"), (PURPLE, "S4", "Stage 4")]:
add_rect(slide, lx, 7.18, 0.35, 0.22, col)
add_text(slide, f"{stg} = {label}", lx + 0.38, 7.18, 1.8, 0.22,
font_size=10, color=DARK_GREY)
lx += 2.3
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 11 – Outcome Measures & Monitoring
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, LIGHT_GREY)
header_bar(slide, "Outcome Measures & Monitoring in MND",
"Serial measurement guides treatment decisions and triggers timely interventions")
om_left = [
("ALSFRS-R\n(ALS Functional Rating Scale-Revised)",
"48-item scale covering speech, swallowing, handwriting, dressing, turning in bed, walking, stairs, dyspnoea, orthopnoea, respiratory sufficiency. Score 0–48. Track rate of decline (≥1 pt/month = faster progression)."),
("FVC – Forced Vital Capacity",
"Primary respiratory predictor. Serial 3-monthly. FVC <50% → initiate NIV. FVC <30% → urgent NIV. Major independent predictor of survival."),
("SNIP – Sniff Nasal Inspiratory Pressure",
"More sensitive for early diaphragm weakness than FVC. SNIP <40 cmH₂O = poor prognosis; initiate NIV if symptomatic."),
("PCF – Peak Cough Flow",
"Measures cough effectiveness. <270 L/min = risk of secretion retention; <160 L/min = critical, CoughAssist essential."),
]
om_right = [
("Berg Balance Scale (BBS)",
"14-item balance assessment (0–56). Track fall risk in Stage 1–2 MND. Score <45 = high fall risk."),
("Timed Up and Go (TUG)",
"Functional mobility marker. Increasing time indicates mobility decline. Trigger for walking aid upgrade."),
("Grip / Pinch Dynamometry",
"Handheld dynamometry: track hand strength serially. Key for planning assistive devices."),
("ALSAQ-40 / ALS Assessment Questionnaire",
"Quality-of-life tool specific to ALS: 40 items across mobility, ADL, eating, communication, and emotional wellbeing."),
]
for i, (title, desc) in enumerate(om_left):
y = 1.35 + i * 1.45
add_rect(slide, 0.25, y, 6.1, 1.35, WHITE)
add_rect(slide, 0.25, y, 0.22, 1.35, TEAL)
add_text(slide, title, 0.55, y + 0.05, 5.6, 0.48,
font_size=11, bold=True, color=NAVY, wrap=True)
add_text(slide, desc, 0.55, y + 0.5, 5.6, 0.82,
font_size=10, color=DARK_GREY, wrap=True)
for i, (title, desc) in enumerate(om_right):
y = 1.35 + i * 1.45
add_rect(slide, 6.7, y, 6.4, 1.35, WHITE)
add_rect(slide, 6.7, y, 0.22, 1.35, TEAL)
add_text(slide, title, 7.0, y + 0.05, 5.9, 0.48,
font_size=11, bold=True, color=NAVY, wrap=True)
add_text(slide, desc, 7.0, y + 0.5, 5.9, 0.82,
font_size=10, color=DARK_GREY, wrap=True)
add_rect(slide, 0.25, 7.1, 12.8, 0.28, NAVY)
add_text(slide,
"Monitor at every visit: weight, pain score (NRS), fatigue, sleep quality, mood. Trigger MDT meeting if ALSFRS-R drops >2 points/month.",
0.4, 7.1, 12.5, 0.28,
font_size=10, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
# ═══════════════════════════════════════════════════════════════════════════
# SLIDE 12 – Summary & Key Principles
# ═══════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, 13.333, 7.5, NAVY)
add_rect(slide, 0, 0, 13.333, 1.3, TEAL)
add_text(slide, "Summary: Physiotherapy in MND", 0.4, 0.1, 12.5, 0.95,
font_size=30, bold=True, color=WHITE,
align=PP_ALIGN.CENTER, v_anchor=MSO_ANCHOR.MIDDLE)
add_text(slide, "A stage-guided, patient-centred, multidisciplinary approach",
0.4, 1.0, 12.5, 0.35,
font_size=14, color=WHITE, align=PP_ALIGN.CENTER)
principles = [
("Do No Harm", GREEN, "Never exercise to exhaustion. Submaximal is the rule throughout MND."),
("Adapt Early", AMBER, "Provide equipment proactively, before falls and crises occur."),
("Breathe First", ORANGE, "Respiratory monitoring and NIV initiation are the highest priority as disease progresses."),
("Family-Centred", PURPLE, "Train carers at every stage. Their confidence improves patient safety at home."),
("Dignity in Palliation",TEAL, "In Stage 4, physiotherapy is not about recovery — it is about comfort, dignity, and peaceful end-of-life."),
("MDT Always", RGBColor(0x4A, 0x90, 0xD9), "No physiotherapist works alone. Coordinate with SLT, OT, dietitian, palliative team at every stage."),
]
for i, (title, col, desc) in enumerate(principles):
row = i // 3
col_pos = i % 3
x = 0.3 + col_pos * 4.35
y = 1.55 + row * 2.5
add_rect(slide, x, y, 4.0, 2.2, RGBColor(0x1A, 0x3A, 0x6A))
add_rect(slide, x, y, 4.0, 0.38, col)
add_text(slide, title, x + 0.1, y, 3.8, 0.38,
font_size=13, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
add_text(slide, desc, x + 0.1, y + 0.42, 3.8, 1.7,
font_size=11, color=RGBColor(0xCC, 0xDD, 0xFF), wrap=True)
add_text(slide,
"References: Goldman-Cecil Medicine 22e · Bradley & Daroff's Neurology · Adams & Victor's Neurology 12e · NICE NG42 · Hospice Foundation Ireland MND Guidelines",
0.3, 7.18, 12.7, 0.28,
font_size=8.5, color=MID_GREY, align=PP_ALIGN.CENTER)
# ─── Save ───────────────────────────────────────────────────────────────────
out_path = "/tmp/workspace/mnd-pptx/Physiotherapy_Progression_MND_Stages.pptx"
buf = BytesIO()
prs.save(buf)
with open(out_path, "wb") as f:
f.write(buf.getvalue())
print(f"Saved: {out_path}")
import os
print(f"Size: {os.path.getsize(out_path):,} bytes")
print(f"Slides: {len(prs.slides)}")