/home/user/pulmonary_hypertension_case.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
from pptx.util import Inches, Pt
import copy
# ── Colour palette ────────────────────────────────────────────────────────────
DARK_BLUE = RGBColor(0x0D, 0x2D, 0x5E) # slide background / title bg
MID_BLUE = RGBColor(0x1B, 0x5E, 0xA7) # accent bar / heading
LIGHT_BLUE = RGBColor(0xD6, 0xE8, 0xF7) # table header fill
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
ORANGE = RGBColor(0xE8, 0x7C, 0x1E) # highlight / key value
DARK_TEXT = RGBColor(0x1A, 0x1A, 0x2E)
LIGHT_GREY = RGBColor(0xF4, 0xF6, 0xF9) # table row alt
prs = Presentation()
prs.slide_width = Inches(13.33)
prs.slide_height = Inches(7.5)
BLANK_LAYOUT = prs.slide_layouts[6] # completely blank
# ══════════════════════════════════════════════════════════════════════════════
# HELPER FUNCTIONS
# ══════════════════════════════════════════════════════════════════════════════
def add_rect(slide, l, t, w, h, fill_rgb=None, line_rgb=None, line_width_pt=0):
shape = slide.shapes.add_shape(1, Inches(l), Inches(t), Inches(w), Inches(h))
shape.line.fill.background()
if fill_rgb:
shape.fill.solid()
shape.fill.fore_color.rgb = fill_rgb
else:
shape.fill.background()
if line_rgb and line_width_pt:
shape.line.color.rgb = line_rgb
shape.line.width = Pt(line_width_pt)
else:
shape.line.fill.background()
return shape
def add_textbox(slide, l, t, w, h, text, font_size=18, bold=False,
color=WHITE, align=PP_ALIGN.LEFT, italic=False, wrap=True):
txBox = slide.shapes.add_textbox(Inches(l), Inches(t), Inches(w), Inches(h))
txBox.word_wrap = wrap
tf = txBox.text_frame
tf.word_wrap = wrap
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
return txBox
def add_para(tf, text, font_size=16, bold=False, color=DARK_TEXT,
align=PP_ALIGN.LEFT, italic=False, space_before=0, level=0,
bullet=False):
p = tf.add_paragraph()
p.level = level
p.alignment = align
if space_before:
p.space_before = Pt(space_before)
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
return p
def make_slide_bg(slide, header_height=1.1):
"""White slide with dark-blue top bar."""
# Full white bg
add_rect(slide, 0, 0, 13.33, 7.5, fill_rgb=WHITE)
# Top bar
add_rect(slide, 0, 0, 13.33, header_height, fill_rgb=DARK_BLUE)
# Thin orange accent line below bar
add_rect(slide, 0, header_height, 13.33, 0.06, fill_rgb=ORANGE)
def slide_title(slide, title, subtitle=None):
"""Write title into the top bar."""
add_textbox(slide, 0.3, 0.1, 12.5, 0.85, title,
font_size=28, bold=True, color=WHITE, align=PP_ALIGN.LEFT)
if subtitle:
add_textbox(slide, 0.3, 0.82, 12.5, 0.35, subtitle,
font_size=14, bold=False, color=LIGHT_BLUE, align=PP_ALIGN.LEFT)
def content_box(slide, l, t, w, h):
"""Return a text-frame inside a white box with subtle border."""
shape = slide.shapes.add_textbox(Inches(l), Inches(t), Inches(w), Inches(h))
shape.word_wrap = True
shape.text_frame.word_wrap = True
return shape.text_frame
def bullet_section(tf, heading, items, h_size=17, b_size=15,
h_color=MID_BLUE, b_color=DARK_TEXT, space=6):
"""Add a bold heading then indented bullets to a text frame."""
add_para(tf, heading, font_size=h_size, bold=True, color=h_color,
space_before=space)
for item in items:
add_para(tf, f" \u2022 {item}", font_size=b_size, color=b_color)
def simple_table(slide, headers, rows, l, t, w, h,
col_widths=None, h_font=13, r_font=12):
"""Draw a lightweight table."""
n_cols = len(headers)
n_rows = len(rows)
total_rows = n_rows + 1
row_h = h / total_rows
if col_widths is None:
col_widths = [w / n_cols] * n_cols
# Header row
x = l
for i, hdr in enumerate(headers):
add_rect(slide, x, t, col_widths[i], row_h, fill_rgb=MID_BLUE)
add_textbox(slide, x + 0.05, t + 0.03, col_widths[i] - 0.1, row_h - 0.05,
hdr, font_size=h_font, bold=True, color=WHITE)
x += col_widths[i]
# Data rows
for r_idx, row in enumerate(rows):
y = t + row_h * (r_idx + 1)
fill = LIGHT_GREY if r_idx % 2 == 0 else WHITE
x = l
for c_idx, cell in enumerate(row):
add_rect(slide, x, y, col_widths[c_idx], row_h, fill_rgb=fill,
line_rgb=RGBColor(0xCC, 0xCC, 0xCC), line_width_pt=0.5)
add_textbox(slide, x + 0.05, y + 0.02, col_widths[c_idx] - 0.1,
row_h - 0.04, str(cell), font_size=r_font, color=DARK_TEXT)
x += col_widths[c_idx]
def footer(slide, text="Case Presentation: Pulmonary Hypertension | Source: Harrison's Principles of Internal Medicine 22E"):
add_rect(slide, 0, 7.2, 13.33, 0.3, fill_rgb=DARK_BLUE)
add_textbox(slide, 0.2, 7.21, 12.9, 0.28, text,
font_size=9, color=LIGHT_BLUE, align=PP_ALIGN.LEFT)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 1 — TITLE SLIDE
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
add_rect(slide, 0, 0, 13.33, 7.5, fill_rgb=DARK_BLUE)
add_rect(slide, 0, 2.9, 13.33, 0.08, fill_rgb=ORANGE)
add_rect(slide, 0, 3.0, 13.33, 0.08, fill_rgb=ORANGE)
add_textbox(slide, 1.0, 1.2, 11.0, 1.2,
"PULMONARY HYPERTENSION",
font_size=44, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
add_textbox(slide, 1.0, 2.4, 11.0, 0.55,
"A Clinical Case Presentation",
font_size=24, bold=False, color=LIGHT_BLUE, align=PP_ALIGN.CENTER)
add_textbox(slide, 1.0, 3.2, 11.0, 0.5,
"Patient: S.M. | 34-year-old Female | WHO FC III",
font_size=18, bold=False, color=ORANGE, align=PP_ALIGN.CENTER)
add_textbox(slide, 1.0, 3.85, 11.0, 0.45,
"Based on Harrison's Principles of Internal Medicine, 22nd Edition (2025)",
font_size=14, italic=True, color=LIGHT_BLUE, align=PP_ALIGN.CENTER)
add_textbox(slide, 1.0, 6.9, 11.0, 0.4,
"Pulmonary Hypertension Specialty Centre",
font_size=13, color=RGBColor(0xAA, 0xCC, 0xEE), align=PP_ALIGN.CENTER)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 2 — PATIENT PROFILE & CHIEF COMPLAINT
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Patient Profile & Chief Complaint")
footer(slide)
# Left column — profile
tf = content_box(slide, 0.3, 1.25, 5.8, 5.8)
bullet_section(tf, "Patient Details", [
"Name: S.M.",
"Age: 34 years",
"Sex: Female",
"Occupation: Schoolteacher",
"Referral: Cardiology → PH Specialty Centre",
])
bullet_section(tf, "Chief Complaint", [
'"I have been getting more and more breathless',
' over the past 18 months, even walking short distances."',
])
# Right column — key timeline box
add_rect(slide, 6.5, 1.25, 6.5, 2.5, fill_rgb=LIGHT_BLUE,
line_rgb=MID_BLUE, line_width_pt=1.5)
add_textbox(slide, 6.7, 1.3, 6.1, 0.45, "Symptom Timeline",
font_size=15, bold=True, color=MID_BLUE)
timeline = [
("18 months ago", "Progressive exertional dyspnoea begins"),
("12 months ago", "Symptoms worsen — single flight of stairs"),
("6 months ago", "Pre-syncope (×2), chest tightness, ankle oedema"),
("Now", "WHO FC III — referred to specialist"),
]
y = 1.78
for t_label, t_text in timeline:
add_rect(slide, 6.6, y, 1.3, 0.38, fill_rgb=MID_BLUE)
add_textbox(slide, 6.62, y + 0.02, 1.26, 0.34, t_label,
font_size=10, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
add_textbox(slide, 8.0, y + 0.04, 4.8, 0.34, t_text,
font_size=12, color=DARK_TEXT)
y += 0.46
# Diagnostic delay highlight box
add_rect(slide, 6.5, 3.95, 6.5, 0.9, fill_rgb=ORANGE)
add_textbox(slide, 6.6, 3.98, 6.2, 0.85,
"⚠ Mean diagnostic delay in PAH: up to 2 years\n"
" Early recognition is critical for outcomes",
font_size=13, bold=True, color=WHITE)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 3 — HISTORY
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "History")
footer(slide)
tf_l = content_box(slide, 0.3, 1.25, 6.2, 5.8)
bullet_section(tf_l, "History of Present Illness", [
"Progressive exertional dyspnoea × 18 months (now WHO FC III)",
"Exertional pre-syncope × 2 episodes",
"Non-exertional central chest tightness",
"Bilateral ankle oedema (worse evenings)",
"Disproportionate fatigue",
"No orthopnoea, PND, haemoptysis, or cough",
])
bullet_section(tf_l, "Past Medical History", [
"Raynaud's phenomenon (diagnosed age 28)",
"No prior DVT, PE, or cardiac disease",
"No formally diagnosed connective tissue disease",
])
bullet_section(tf_l, "Medications", [
"Combined oral contraceptive pill (since age 22)",
"Nifedipine 10 mg PRN for Raynaud's",
])
tf_r = content_box(slide, 6.8, 1.25, 6.2, 5.8)
bullet_section(tf_r, "Family History", [
"Paternal aunt: died of 'unknown heart failure' aged 41",
"Raises suspicion for heritable PAH / BMPR2 variant",
])
bullet_section(tf_r, "Social History", [
"Non-smoker",
"Occasional alcohol",
"No illicit drugs; no appetite suppressants",
])
bullet_section(tf_r, "Review of Systems", [
"Mild arthralgia — small joints of hands (non-deforming)",
"Intermittent dry eyes and dry mouth",
"No skin rash, photosensitivity, or dysphagia",
])
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 4 — PHYSICAL EXAMINATION
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Physical Examination")
footer(slide)
# Vitals table
simple_table(slide,
["Parameter", "Value", "Significance"],
[
["BP", "108/72 mmHg", "Low-normal — reduced cardiac output"],
["HR", "96 bpm (regular)", "Compensatory tachycardia"],
["RR", "18/min", "—"],
["SpO₂", "94% (room air)", "Mild hypoxaemia"],
["Temperature", "37.0°C", "—"],
],
l=0.3, t=1.25, w=8.5, h=2.15,
col_widths=[2.2, 2.5, 3.8], h_font=13, r_font=12)
# Key exam findings
tf = content_box(slide, 0.3, 3.55, 12.7, 3.6)
bullet_section(tf, "Cardiovascular (Key Findings)", [
"Elevated JVP with prominent a-wave — raised RA pressure",
"Left parasternal heave — RV hypertrophy / dilatation",
"Loud P₂ (palpable) — pulmonary hypertension",
"RV S₄ gallop at left lower sternal border",
"Pansystolic murmur ↑ with inspiration — tricuspid regurgitation",
"Hepatomegaly (3 cm) + bilateral pitting oedema — RV failure",
], h_size=16, b_size=14)
add_rect(slide, 9.0, 1.25, 4.1, 2.15, fill_rgb=LIGHT_BLUE,
line_rgb=MID_BLUE, line_width_pt=1)
add_textbox(slide, 9.1, 1.3, 3.9, 0.4, "Respiratory", font_size=14,
bold=True, color=MID_BLUE)
add_textbox(slide, 9.1, 1.7, 3.9, 0.5,
"Lungs clear bilaterally\nNo wheeze, crackles, or rub",
font_size=13, color=DARK_TEXT)
add_textbox(slide, 9.1, 2.25, 3.9, 0.4, "Skin / Joints", font_size=14,
bold=True, color=MID_BLUE)
add_textbox(slide, 9.1, 2.65, 3.9, 0.6,
"No sclerodactyly or telangiectasiae\nCool peripheries; Raynaud's changes",
font_size=13, color=DARK_TEXT)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 5 — INVESTIGATIONS: BLOODS & ECG
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Investigations — Bloods & ECG")
footer(slide)
simple_table(slide,
["Investigation", "Result", "Interpretation"],
[
["BNP", "820 pg/mL ↑↑", "RV wall stress / impending failure"],
["ANA", "Positive 1:160 (speckled)", "Autoimmune screen positive"],
["Anti-centromere Ab", "POSITIVE", "Strongly suggests limited SSc (CREST)"],
["dsDNA / Ro / La", "Negative", "SLE / Sjögren's less likely"],
["HIV serology", "Negative", "Group 1 cause excluded"],
["Hepatitis B/C", "Negative", "Porto-pulmonary PH less likely"],
["Thrombophilia screen", "Negative", "CTEPH less likely"],
["TFTs / FBC / U&E / LFTs", "Normal (mild ↑ ALP)", "Background organ function"],
],
l=0.3, t=1.25, w=9.5, h=3.4,
col_widths=[3.2, 2.9, 3.4], h_font=13, r_font=11)
add_rect(slide, 10.0, 1.25, 3.1, 3.4, fill_rgb=LIGHT_BLUE,
line_rgb=MID_BLUE, line_width_pt=1)
add_textbox(slide, 10.1, 1.3, 2.9, 0.4, "ECG Findings",
font_size=15, bold=True, color=MID_BLUE)
ecg_items = [
"Sinus tachycardia",
"Right axis deviation",
"RV hypertrophy",
" (R > S in V1)",
"P pulmonale",
" (tall peaked P in II)",
"S1Q3T3 pattern",
]
y = 1.78
for item in ecg_items:
bold = not item.startswith(" ")
add_textbox(slide, 10.15, y, 2.85, 0.33, item,
font_size=12, bold=bold, color=DARK_TEXT if bold else MID_BLUE)
y += 0.33
# Key box
add_rect(slide, 0.3, 4.75, 12.7, 0.75, fill_rgb=ORANGE)
add_textbox(slide, 0.5, 4.78, 12.3, 0.7,
"Anti-centromere Ab + Raynaud's + arthralgia = Limited Systemic Sclerosis (lcSSc/CREST) "
"→ CTD-associated PAH (Group 1)",
font_size=14, bold=True, color=WHITE)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 6 — INVESTIGATIONS: IMAGING & PFTs
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Investigations — Imaging & Pulmonary Function Tests")
footer(slide)
tf_l = content_box(slide, 0.3, 1.25, 6.0, 5.8)
bullet_section(tf_l, "Chest X-Ray", [
"Enlarged main pulmonary artery",
"Bilateral hilar prominence",
"Peripheral oligaemia — 'pruning' of vessels",
"Right-heart predominant cardiomegaly",
"No parenchymal infiltrates",
"No pleural effusion",
], h_size=16, b_size=14)
bullet_section(tf_l, "CTPA", [
"Main PA diameter 34 mm (normal <29 mm) ↑",
"No filling defects — CTEPH excluded",
"No interstitial fibrosis / honeycombing",
"No significant parenchymal lung disease",
], h_size=16, b_size=14)
# PFT table right
simple_table(slide,
["PFT Parameter", "Result", "% Predicted"],
[
["FEV₁", "2.4 L", "88% (Normal)"],
["FVC", "3.0 L", "90% (Normal)"],
["FEV₁/FVC", "0.80", "Normal"],
["TLC", "4.8 L", "86% (Normal)"],
["DLCO", "52%", "REDUCED ↓↓"],
],
l=6.6, t=1.25, w=6.4, h=2.6,
col_widths=[2.6, 1.8, 2.0], h_font=13, r_font=12)
add_rect(slide, 6.6, 4.0, 6.4, 1.0, fill_rgb=LIGHT_BLUE,
line_rgb=MID_BLUE, line_width_pt=1.5)
add_textbox(slide, 6.75, 4.05, 6.1, 0.9,
"Clinical Pearl: Normal spirometry + normal lung volumes\n"
"+ isolated ↓DLCO → Always investigate pulmonary vasculature",
font_size=13, bold=False, color=MID_BLUE)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 7 — RIGHT HEART CATHETERISATION
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Right Heart Catheterisation — Gold Standard Diagnosis")
footer(slide)
simple_table(slide,
["Haemodynamic Parameter", "Value", "Threshold", "Interpretation"],
[
["Mean PAP (mPAP)", "46 mmHg", ">20 mmHg", "ELEVATED — PH confirmed"],
["Pulmonary Wedge (PCWP)", "9 mmHg", "≤15 mmHg", "Precapillary pattern"],
["Pulmonary Vasc. Resistance","7.2 Wood units", ">2 WU", "Significantly elevated"],
["Cardiac Output", "3.8 L/min", "Normal 4–8", "Low-normal"],
["Cardiac Index", "2.2 L/min/m²", "Normal >2.5", "Borderline low"],
],
l=0.3, t=1.25, w=12.7, h=2.6,
col_widths=[3.5, 2.2, 2.0, 5.0], h_font=13, r_font=12)
# Vasoreactivity
add_rect(slide, 0.3, 4.0, 6.1, 1.2, fill_rgb=LIGHT_BLUE,
line_rgb=MID_BLUE, line_width_pt=1)
add_textbox(slide, 0.45, 4.05, 5.8, 1.1,
"Vasoreactivity Test (inhaled NO)\n"
"mPAP fell by only 4 mmHg → NEGATIVE\n"
"Calcium channel blockers NOT indicated",
font_size=13, color=MID_BLUE, bold=False)
# Updated thresholds
add_rect(slide, 6.6, 4.0, 6.4, 1.2, fill_rgb=ORANGE)
add_textbox(slide, 6.75, 4.05, 6.1, 1.1,
"2022 Updated Diagnostic Thresholds:\n"
"PH: mPAP >20 mmHg (was ≥25 mmHg)\n"
"Precapillary PH: PVR >2 WU (was ≥3 WU)",
font_size=13, bold=True, color=WHITE)
# Echocardiogram summary
tf = content_box(slide, 0.3, 5.35, 12.7, 1.8)
bullet_section(tf, "Echocardiogram Summary", [
"RV dilatation & hypertrophy; TAPSE 14 mm (reduced RV function)",
"Septal D-sign — RV pressure overload",
"Estimated RVSP: 72 mmHg | Normal LV function (EF 62%)",
"No intracardiac shunt | Small pericardial effusion",
], h_size=15, b_size=13)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 8 — DIAGNOSIS
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Diagnosis")
footer(slide)
# Large diagnosis box
add_rect(slide, 1.0, 1.3, 11.0, 1.4, fill_rgb=MID_BLUE)
add_textbox(slide, 1.1, 1.35, 10.8, 1.3,
"Pulmonary Arterial Hypertension (PAH) — Group 1\n"
"Associated with Limited Systemic Sclerosis (lcSSc) | WHO Functional Class III",
font_size=22, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
# Classification table
simple_table(slide,
["WHO Group", "Cause", "This Patient?"],
[
["Group 1 — PAH", "Idiopathic, heritable, CTD, drugs/toxins, HIV, porto-PH", "YES — lcSSc-PAH"],
["Group 2 — LHD", "Left heart disease (systolic/diastolic dysfunction)", "No (PCWP = 9)"],
["Group 3 — Lung Dz", "COPD, ILD, sleep-disordered breathing", "No (PFTs normal)"],
["Group 4 — CTEPH", "Chronic thromboembolic PH", "No (CTPA clear)"],
["Group 5 — Misc", "Sarcoid, myeloproliferative, metabolic", "No"],
],
l=0.3, t=2.85, w=12.7, h=3.0,
col_widths=[2.8, 6.8, 3.1], h_font=13, r_font=12)
add_rect(slide, 0.3, 6.0, 12.7, 0.6, fill_rgb=LIGHT_BLUE,
line_rgb=MID_BLUE, line_width_pt=1)
add_textbox(slide, 0.5, 6.03, 12.3, 0.55,
"Confirmed precapillary PH: mPAP 46 mmHg | PCWP 9 mmHg | PVR 7.2 WU | No secondary cause identified",
font_size=13, bold=True, color=MID_BLUE)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 9 — PATHOPHYSIOLOGY
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Pathophysiology of PAH")
footer(slide)
steps = [
("1", "Endothelial Injury", "Autoimmune (SSc) → endothelial dysfunction\n↓ Prostacyclin / NO | ↑ Endothelin-1 / TXA₂"),
("2", "Obliterative Arteriopathy","Apoptosis resistance + SMC & fibroblast proliferation\n→ Hypertrophic, fibrotic, plexogenic remodelling"),
("3", "ECM Expansion", "Integrins, nonfibrillar collagens, fibronectin\n→ Vessel stiffening & reduced compliance"),
("4", "In situ Thrombosis", "Platelet activation + ↓ prostacyclin\n→ Microthrombus formation in arterioles"),
("5", "RV Pressure Overload", "↑ PVR → RV hypertrophy → RV dilatation\n→ Reduced CO → systemic venous congestion"),
]
x_positions = [0.25, 2.8, 5.35, 7.9, 10.45]
for i, (num, heading, body) in enumerate(steps):
x = x_positions[i]
add_rect(slide, x, 1.25, 2.3, 0.55, fill_rgb=MID_BLUE)
add_textbox(slide, x + 0.05, 1.27, 2.2, 0.5, f"{num}. {heading}",
font_size=13, bold=True, color=WHITE)
add_rect(slide, x, 1.8, 2.3, 2.2, fill_rgb=LIGHT_BLUE,
line_rgb=MID_BLUE, line_width_pt=1)
add_textbox(slide, x + 0.08, 1.85, 2.15, 2.1, body,
font_size=11, color=DARK_TEXT)
if i < 4:
add_textbox(slide, x + 2.32, 2.4, 0.4, 0.5, "→",
font_size=22, bold=True, color=ORANGE)
# Genetic drivers box
add_rect(slide, 0.3, 4.2, 12.7, 1.1, fill_rgb=WHITE,
line_rgb=MID_BLUE, line_width_pt=1.5)
add_textbox(slide, 0.5, 4.25, 12.3, 1.0,
"Genetic Drivers: BMPR2 (most common — TGF-β superfamily) • TBX4 • SOX17\n"
"BMPR2 variant: hereditary PAH — penetrance as low as 20% | "
"Imbalance of TGF-β receptor signalling → therapeutic target (sotatercept)",
font_size=13, color=DARK_TEXT)
# RV failure note
add_rect(slide, 0.3, 5.45, 12.7, 0.95, fill_rgb=ORANGE)
add_textbox(slide, 0.5, 5.5, 12.3, 0.85,
"RV Dysfunction: Occurs due to ↑ afterload AND depressed sarcomere function "
"from chronic inflammation / autoimmune mechanisms\n"
"→ If untreated, PH carries high mortality from decompensated right heart failure",
font_size=13, bold=False, color=WHITE)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 10 — MANAGEMENT: GENERAL MEASURES
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Management — General & Supportive Measures")
footer(slide)
tf_l = content_box(slide, 0.3, 1.25, 6.2, 5.8)
bullet_section(tf_l, "Supportive Measures", [
"Supplemental O₂ → maintain SpO₂ ≥90–92%",
" (Hypoxia = potent pulmonary vasoconstrictor)",
"Diuretics (furosemide) — RV volume overload",
"Anticoagulation — warfarin (INR 1.5–2.5)",
" (Evidence-based for idiopathic/heritable PAH)",
"Supervised exercise rehabilitation",
"Avoid strenuous exertion",
], h_size=16, b_size=14)
bullet_section(tf_l, "Vaccinations", [
"Influenza (yearly)",
"Pneumococcal (regardless of age)",
"COVID-19 + RSV vaccines",
], h_size=16, b_size=14)
tf_r = content_box(slide, 6.8, 1.25, 6.2, 5.8)
bullet_section(tf_r, "Patient-Specific Actions", [
"STOP combined oral contraceptive pill",
" (Oestrogens are a PAH risk factor)",
"Avoid pregnancy — carries very high mortality",
"Genetic counselling — BMPR2 testing",
" (Family history of early cardiac death)",
"Screen first-degree relatives",
], h_size=16, b_size=14, h_color=ORANGE)
bullet_section(tf_r, "Monitoring Targets", [
"6-minute walk distance (6MWD)",
"BNP/NT-proBNP — target normalisation",
"Echocardiography — RV function trend",
"Repeat RHC at 3–6 months",
"WHO FC reassessment every visit",
], h_size=16, b_size=14)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 11 — PHARMACOLOGIC TREATMENT
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Pharmacologic Treatment of PAH — Three Pathways")
footer(slide)
# Three pathway boxes
pathways = [
("Endothelin\nPathway", "ERAs", ["Ambrisentan", "Bosentan", "Macitentan"],
"ET-1 = potent vasoconstrictor + mitogen\nERAs block ETA / ETB receptors",
MID_BLUE),
("Nitric Oxide\nPathway", "PDE-5i / sGC Stimulators",
["Sildenafil", "Tadalafil", "Riociguat (sGC)"],
"Enhance cGMP-mediated\npulmonary vasodilation",
RGBColor(0x17, 0x7D, 0x7D)),
("Prostacyclin\nPathway", "Prostanoids / IP Agonists",
["Epoprostenol (IV)", "Treprostinil (IV/SC/inhaled/oral)",
"Iloprost (inhaled)", "Selexipag (oral)"],
"Replaces deficient PGI₂\nVasodilation + antiproliferation\n+ platelet inhibition",
RGBColor(0x5B, 0x2D, 0x8E)),
]
for i, (pw, drug_class, drugs, mech, col) in enumerate(pathways):
x = 0.3 + i * 4.3
add_rect(slide, x, 1.25, 4.1, 0.6, fill_rgb=col)
add_textbox(slide, x + 0.05, 1.27, 4.0, 0.56, pw,
font_size=14, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
add_rect(slide, x, 1.85, 4.1, 0.4, fill_rgb=LIGHT_BLUE,
line_rgb=col, line_width_pt=1)
add_textbox(slide, x + 0.08, 1.88, 3.95, 0.36, drug_class,
font_size=13, bold=True, color=col)
y = 2.35
for d in drugs:
add_textbox(slide, x + 0.15, y, 3.9, 0.35, f"• {d}",
font_size=12, color=DARK_TEXT)
y += 0.35
add_rect(slide, x, y + 0.05, 4.1, 1.1, fill_rgb=LIGHT_GREY,
line_rgb=col, line_width_pt=0.5)
add_textbox(slide, x + 0.1, y + 0.08, 3.9, 1.0, mech,
font_size=11, italic=True, color=col)
# Recommended strategy
add_rect(slide, 0.3, 5.85, 12.7, 0.9, fill_rgb=ORANGE)
add_textbox(slide, 0.5, 5.88, 12.3, 0.85,
"Recommended for S.M. (WHO FC III, vasoreactivity NEGATIVE): "
"Dual oral therapy → ERA (Macitentan) + PDE-5i (Tadalafil)\n"
"Escalate to triple therapy (+ prostanoid) if insufficient response at 3–4 months",
font_size=13, bold=True, color=WHITE)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 12 — NOVEL THERAPY & ESCALATION
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Novel Therapy, Escalation & Prognosis")
footer(slide)
tf_l = content_box(slide, 0.3, 1.25, 6.2, 5.6)
bullet_section(tf_l, "Novel Agent: Sotatercept (2024)", [
"Activin signal inhibitor",
"Rebalances TGF-β / BMPR2 signalling",
"Approved as add-on therapy for PAH",
"Directly targets the genetic driver (BMPR2 axis)",
], h_size=16, b_size=14, h_color=ORANGE)
bullet_section(tf_l, "Refractory / Advanced Disease", [
"Bilateral lung transplantation",
" (Refer early given young age + CTD-PAH)",
"Atrial septostomy",
" (Palliative for refractory RV failure)",
], h_size=16, b_size=14)
# Prognosis table
simple_table(slide,
["Survival Timepoint", "Estimated Survival"],
[
["1 year", "~82%"],
["3 years", "~67%"],
["5 years", "~58%"],
],
l=6.8, t=1.25, w=6.1, h=1.8,
col_widths=[3.5, 2.6], h_font=14, r_font=13)
add_textbox(slide, 6.8, 3.15, 6.1, 0.35,
"Adverse Prognostic Indicators in S.M.:",
font_size=14, bold=True, color=MID_BLUE)
poor_prog = [
"WHO Functional Class III",
"Reduced cardiac index (2.2 L/min/m²)",
"Markedly elevated BNP (820 pg/mL)",
"Pericardial effusion",
"RV systolic dysfunction (TAPSE 14 mm)",
]
y = 3.55
for item in poor_prog:
add_textbox(slide, 7.0, y, 5.8, 0.35, f" ✕ {item}",
font_size=13, color=DARK_TEXT)
y += 0.36
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 13 — CLINICAL PEARLS
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Clinical Pearls")
footer(slide)
pearls = [
("Diagnostic Delay",
"Mean delay up to 2 years in PAH — maintain suspicion in young women with unexplained dyspnoea"),
("New mPAP Threshold",
"PH now defined as mPAP >20 mmHg (was ≥25 mmHg) — enables earlier diagnosis"),
("New PVR Threshold",
"Precapillary PH: PVR >2 Wood units (was ≥3 WU)"),
("Isolated ↓ DLCO",
"Normal spirometry + normal lung volumes + low DLCO → always evaluate pulmonary vasculature"),
("BMPR2",
"Most common hereditary PAH gene; family history of early unexplained cardiac death is a clue"),
("Vasoreactivity",
"Only ~10–15% of PAH patients respond; non-responders must NOT receive calcium channel blockers"),
("OCP",
"Combined oral contraceptive pill is a modifiable PAH risk factor — must be stopped"),
("Pregnancy",
"Contraindicated in PAH — associated with very high maternal mortality"),
]
x_positions_p = [0.3, 6.65]
y_start = 1.3
for i, (title_p, body_p) in enumerate(pearls):
col = i % 2
row = i // 2
x = x_positions_p[col]
y = y_start + row * 1.35
add_rect(slide, x, y, 6.1, 0.42, fill_rgb=MID_BLUE)
add_textbox(slide, x + 0.1, y + 0.03, 5.9, 0.38, title_p,
font_size=13, bold=True, color=WHITE)
add_rect(slide, x, y + 0.42, 6.1, 0.82, fill_rgb=LIGHT_GREY,
line_rgb=MID_BLUE, line_width_pt=0.5)
add_textbox(slide, x + 0.12, y + 0.46, 5.86, 0.78, body_p,
font_size=12, color=DARK_TEXT)
# ══════════════════════════════════════════════════════════════════════════════
# SLIDE 14 — SUMMARY
# ══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(BLANK_LAYOUT)
make_slide_bg(slide)
slide_title(slide, "Case Summary")
footer(slide)
simple_table(slide,
["Domain", "Finding"],
[
["Patient", "34F, lcSSc, Raynaud's, family history of early cardiac death"],
["Presentation", "18 months progressive dyspnoea, pre-syncope, oedema — WHO FC III"],
["Key Tests", "Anti-centromere Ab+, BNP 820, ECG RVH, Echo RVSP 72 mmHg, ↓DLCO"],
["RHC", "mPAP 46 | PCWP 9 | PVR 7.2 WU — Precapillary PH confirmed"],
["Diagnosis", "PAH Group 1 — Limited Systemic Sclerosis-associated"],
["Pathophysiology","Endothelial injury → obliterative arteriopathy → RV failure"],
["Genetic Driver", "BMPR2 pathway (TGF-β superfamily) disruption"],
["Treatment", "Dual oral therapy: ERA + PDE-5i; add prostanoid if FC III persists"],
["Novel Rx", "Sotatercept — activin signal inhibitor targeting BMPR2 axis"],
["Prognosis", "5-year survival ~58%; early specialist referral & dual therapy critical"],
],
l=0.3, t=1.25, w=12.7, h=5.3,
col_widths=[2.8, 9.9], h_font=14, r_font=12)
# ══════════════════════════════════════════════════════════════════════════════
# SAVE
# ══════════════════════════════════════════════════════════════════════════════
output_path = "/home/user/Pulmonary_Hypertension_Case_Presentation.pptx"
prs.save(output_path)
print(f"Saved: {output_path}")