Explain common 10 heart diseases with ECG changes and ECG images of those diseases
ECG ST elevation myocardial infarction STEMI
ECG atrial fibrillation electrocardiogram
ECG ventricular tachycardia electrocardiogram
ECG complete heart block atrioventricular block
ECG Wolff-Parkinson-White delta wave pre-excitation
ECG left ventricular hypertrophy hypertrophic cardiomyopathy
ECG pericarditis saddle-shaped ST elevation
ECG pulmonary embolism S1Q3T3 right heart strain
ECG Brugada syndrome coved ST elevation V1 V2
ECG long QT syndrome torsades de pointes






| Stage | Timing | ECG Finding |
|---|---|---|
| I | Days 1–2 | Diffuse concave ("saddle-shaped") ST elevation in all leads except aVR and V1; PR depression (most sensitive sign) in II, V4–V6; PR elevation in aVR |
| II | Days 3–7 | ST returns to baseline; T waves flatten |
| III | Weeks 1–3 | T-wave inversion in previously elevated leads |
| IV | Weeks–months | ECG normalises |


| Type | ST Morphology | T Wave | Diagnostic? |
|---|---|---|---|
| Type 1 (Coved) | J-point ≥2 mm, convex/coved downsloping ST | Negative (inverted) | ✅ Yes |
| Type 2 (Saddle-back) | J-point ≥2 mm, saddle-back ST | Positive | ❌ Requires pharmacological challenge |
| Type 3 | <2 mm ST elevation | Positive | ❌ Non-diagnostic |


| # | Disease | Key ECG Finding | Critical Clue |
|---|---|---|---|
| 1 | STEMI | Convex ST elevation + Q waves | Territorial distribution + reciprocal changes |
| 2 | Atrial Fibrillation | No P waves, irregular R-R | "Irregularly irregular" |
| 3 | Complete Heart Block | P-wave/QRS dissociation, slow escape | No PR relationship |
| 4 | Ventricular Tachycardia | Wide QRS tachycardia, AV dissociation | Fusion/capture beats |
| 5 | WPW Syndrome | Short PR + delta wave + wide QRS | Slurred QRS upstroke |
| 6 | HCM | LVH voltage + giant T inversions | Deep V2–V5 T inversions (apical type) |
| 7 | Pericarditis | Diffuse saddle ST + PR depression | All leads (not territorial) |
| 8 | Pulmonary Embolism | S1Q3T3 + sinus tachycardia + RBBB | Right heart strain pattern |
| 9 | Brugada Syndrome | Coved ST elevation V1–V2 + inverted T | Only Type 1 is diagnostic |
| 10 | Long QT Syndrome | QTc >500 ms + T-wave changes | Torsades de pointes risk |
Sources: Harrison's Principles of Internal Medicine 22E (2025); Braunwald's Heart Disease; Tintinalli's Emergency Medicine; Goldman-Cecil Medicine; Fuster and Hurst's The Heart, 15th Ed; Ganong's Review of Medical Physiology
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"
mkdir -p /home/daytona/workspace/heart-ecg-pdf
~/heart-ecg-pdf/generate_pdf.py
import json
import subprocess
import base64
import os
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, Image as RLImage, HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import BalancedColumns
from io import BytesIO
OUTPUT = "/home/daytona/workspace/heart-ecg-pdf/Heart_Diseases_ECG.pdf"
IMG_DIR = "/home/daytona/workspace/heart-ecg-pdf/imgs"
os.makedirs(IMG_DIR, exist_ok=True)
# ── Image URLs ──────────────────────────────────────────────────────────────
IMAGE_URLS = {
"stemi": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_16738d4b125195130b5e53a811645d4064182fc2bbd0de16b0e00a57af7ff6ef.jpg",
"af": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_55b3585ba004b0ca69f159c22eb5fbb4d8995fc007e77627c0d37922e4d88973.jpg",
"chb": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_755c449b940e537dbbe6dd7bf9c73561fc7bed3c059859efcb9b52c8878d99b4.jpg",
"vt": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_d0c8263390a11db77a98f37c73e35131257aee921845e98272875e8c3fe8caec.jpg",
"wpw": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_53c53d9ed7100de15d6d512e52e2a269252a79c85c12a60e54a4ce2fc4d574ec.jpg",
"hcm": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_2910b31808487d7cd1b8678e2a6942b2ca7518030d03f48787ac4b092bd7e92e.jpg",
"pericarditis": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_cd4008531c1b1a2a99ab6d9b4282dcdcedfcf77c302b6ecbb7f7e01c5c985cf9.jpg",
"pe": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_5f391caa5a24ec425e9f48d4d9f73620a3b91f98df1f92cea400318772fda12e.jpg",
"brugada": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_6a00d07e5f8f18709c0b945773550567b91d5cce1d6e873c08d66dcc8caa5600.jpg",
"lqts": "https://cdn.orris.care/cdss_images/HARRISON_1763034143291_d1a2f9c1-7f66-4dec-a3f8-19b81f2ba927.png",
}
# ── Download images ──────────────────────────────────────────────────────────
print("Downloading images...")
urls = list(IMAGE_URLS.values())
result = json.loads(subprocess.check_output(
["node", "/home/daytona/skills/shared/scripts/fetch_images.js"] + urls
))
img_paths = {}
keys = list(IMAGE_URLS.keys())
for i, item in enumerate(result):
key = keys[i]
if item.get("base64"):
raw = base64.b64decode(item["base64"].split(",")[1])
ext = "png" if "png" in item["base64"] else "jpg"
path = f"{IMG_DIR}/{key}.{ext}"
with open(path, "wb") as f:
f.write(raw)
img_paths[key] = path
print(f" ✓ {key}")
else:
print(f" ✗ {key}: {item.get('error')}")
# ── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
DARK_RED = colors.HexColor("#8B0000")
MED_RED = colors.HexColor("#C0392B")
LIGHT_RED = colors.HexColor("#FADBD8")
DARK_GRAY = colors.HexColor("#2C3E50")
MID_GRAY = colors.HexColor("#566573")
LIGHT_GRAY = colors.HexColor("#F2F3F4")
WHITE = colors.white
GOLD = colors.HexColor("#D4AC0D")
title_style = ParagraphStyle(
"TitleStyle", parent=styles["Title"],
fontSize=26, textColor=WHITE, alignment=TA_CENTER,
spaceAfter=6, fontName="Helvetica-Bold", leading=32,
)
subtitle_style = ParagraphStyle(
"SubtitleStyle", parent=styles["Normal"],
fontSize=12, textColor=colors.HexColor("#F0B27A"),
alignment=TA_CENTER, spaceAfter=4, fontName="Helvetica",
)
source_style = ParagraphStyle(
"SourceStyle", parent=styles["Normal"],
fontSize=8, textColor=colors.HexColor("#A9CCE3"),
alignment=TA_CENTER, fontName="Helvetica-Oblique",
)
disease_num_style = ParagraphStyle(
"DiseaseNum", parent=styles["Normal"],
fontSize=10, textColor=MED_RED,
fontName="Helvetica-Bold", spaceAfter=2,
)
disease_title_style = ParagraphStyle(
"DiseaseTitle", parent=styles["Heading1"],
fontSize=18, textColor=DARK_RED,
fontName="Helvetica-Bold", spaceAfter=6, spaceBefore=4,
borderPad=4,
)
section_head_style = ParagraphStyle(
"SectionHead", parent=styles["Normal"],
fontSize=11, textColor=WHITE, fontName="Helvetica-Bold",
backColor=MED_RED, leftIndent=6, rightIndent=6,
spaceAfter=4, spaceBefore=8, leading=16,
borderPad=4,
)
body_style = ParagraphStyle(
"Body", parent=styles["Normal"],
fontSize=9.5, textColor=DARK_GRAY, fontName="Helvetica",
spaceAfter=3, leading=14, alignment=TA_JUSTIFY,
)
bullet_style = ParagraphStyle(
"Bullet", parent=styles["Normal"],
fontSize=9.5, textColor=DARK_GRAY, fontName="Helvetica",
leftIndent=14, bulletIndent=4, spaceAfter=2, leading=13,
bulletFontName="Helvetica", bulletFontSize=10,
)
caption_style = ParagraphStyle(
"Caption", parent=styles["Normal"],
fontSize=8, textColor=MID_GRAY, fontName="Helvetica-Oblique",
alignment=TA_CENTER, spaceAfter=6, spaceBefore=2, leading=11,
)
table_header_style = ParagraphStyle(
"TableHeader", parent=styles["Normal"],
fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_CENTER,
)
table_cell_style = ParagraphStyle(
"TableCell", parent=styles["Normal"],
fontSize=8.5, textColor=DARK_GRAY, fontName="Helvetica",
alignment=TA_LEFT, leading=11,
)
# ── Helper: add image ────────────────────────────────────────────────────────
def ecg_image(key, caption_text, width=15*cm):
items = []
if key in img_paths:
try:
img = RLImage(img_paths[key], width=width, height=width*0.45)
img.hAlign = "CENTER"
items.append(img)
except Exception as e:
print(f" Image error {key}: {e}")
items.append(Paragraph(caption_text, caption_style))
return items
def section_header(text):
return Paragraph(f" {text}", section_head_style)
def bullet(text):
return Paragraph(f"• {text}", bullet_style)
def hr():
return HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#D5D8DC"), spaceAfter=4, spaceBefore=4)
# ── Cover Page ───────────────────────────────────────────────────────────────
def cover_page(story):
story.append(Spacer(1, 2.5*cm))
# Title block with dark background table
cover_data = [[
Paragraph("10 Common Heart Diseases", title_style),
], [
Paragraph("ECG Changes & Electrocardiographic Findings", subtitle_style),
], [
Spacer(1, 0.3*cm),
], [
Paragraph("A Comprehensive Clinical Reference", subtitle_style),
], [
Spacer(1, 0.3*cm),
], [
Paragraph("Sources: Harrison's Principles of Internal Medicine 22E · Braunwald's Heart Disease · Tintinalli's Emergency Medicine · Goldman-Cecil Medicine · Fuster and Hurst's The Heart 15E", source_style),
]]
cover_table = Table(cover_data, colWidths=[17*cm])
cover_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_GRAY),
("ROWPADDING", (0,0), (-1,-1), 10),
("BOX", (0,0), (-1,-1), 2, MED_RED),
("ROUNDEDCORNERS", [8]),
]))
story.append(cover_table)
story.append(Spacer(1, 1.2*cm))
# Quick reference summary table on cover
story.append(Paragraph(" Quick Reference Summary", ParagraphStyle(
"QRTitle", parent=styles["Normal"], fontSize=13,
textColor=DARK_RED, fontName="Helvetica-Bold", spaceAfter=6,
)))
qr_headers = ["#", "Disease", "Key ECG Finding", "Critical Clue"]
qr_rows = [
["1", "STEMI", "Convex ST elevation + Q waves", "Territorial + reciprocal changes"],
["2", "Atrial Fibrillation", "No P waves, irregular RR", '"Irregularly irregular"'],
["3", "Complete Heart Block", "P/QRS dissociation, slow escape", "No fixed PR relationship"],
["4", "Ventricular Tachycardia", "Wide QRS tachycardia, AV dissociation", "Fusion/capture beats"],
["5", "WPW Syndrome", "Short PR + delta wave + wide QRS", "Slurred QRS upstroke"],
["6", "Hypertrophic Cardiomyopathy", "LVH voltage + giant T inversions", "Deep V2-V5 T inversions (apical)"],
["7", "Acute Pericarditis", "Diffuse saddle ST + PR depression", "All leads, not territorial"],
["8", "Pulmonary Embolism", "S1Q3T3 + sinus tachycardia + RBBB", "Right heart strain pattern"],
["9", "Brugada Syndrome", "Coved ST elevation V1-V2 + inv T", "Only Type 1 is diagnostic"],
["10", "Long QT Syndrome", "QTc >500 ms + T-wave changes", "Torsades de pointes risk"],
]
table_data = [[Paragraph(h, table_header_style) for h in qr_headers]]
for row in qr_rows:
table_data.append([Paragraph(cell, table_cell_style) for cell in row])
qr_table = Table(table_data, colWidths=[1*cm, 4.5*cm, 6.5*cm, 5*cm])
qr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_RED),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_GRAY]),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("ALIGN", (0,0), (0,-1), "CENTER"),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#D5D8DC")),
("ROWPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(qr_table)
story.append(PageBreak())
# ── Disease Entries ──────────────────────────────────────────────────────────
diseases = [
{
"num": "1",
"title": "ST-Elevation Myocardial Infarction (STEMI)",
"img_key": "stemi",
"caption": "Anterior STEMI: Convex 'tombstone' ST-segment elevation across V1-V6, most pronounced in V2-V4, with reciprocal ST depression in inferior leads (II, III, aVF). Sinus rhythm with hyperacute T waves.",
"overview": "STEMI results from complete occlusion of a coronary artery causing transmural myocardial injury. Immediate reperfusion (primary PCI within 90 min) is life-saving.",
"ecg_points": [
"Hyperacute T waves — earliest sign; tall, broad, symmetric peaked T waves",
"ST-segment elevation ≥1 mm in ≥2 contiguous limb leads or ≥2 mm in ≥2 precordial leads (convex/tombstone morphology)",
"Reciprocal ST depression in mirror-image leads (e.g., inferior STEMI → depression in aVL/I)",
"Pathological Q waves (>40 ms wide, >1/4 R-wave height) — indicate transmural necrosis",
"T-wave inversion follows as infarct evolves (hours to days)",
"Localisation: Anterior (V1-V4 = LAD), Inferior (II,III,aVF = RCA), Lateral (I,aVL,V5-V6 = LCx)",
],
"extra": None,
},
{
"num": "2",
"title": "Atrial Fibrillation (AF)",
"img_key": "af",
"caption": "Atrial fibrillation with rapid ventricular response: absent P waves replaced by fine fibrillatory baseline (best seen V1, lead II), and an irregularly irregular R-R interval pattern throughout all leads.",
"overview": "AF is the most common sustained cardiac arrhythmia (~1-2% prevalence), caused by disorganised atrial electrical activity. It carries a 5x stroke risk, managed with rate/rhythm control and anticoagulation.",
"ecg_points": [
"Absent P waves — replaced by chaotic fibrillatory (f) waves at 350-600 impulses/min; best seen in V1 and II",
"Irregularly irregular R-R intervals — the hallmark finding",
"Narrow QRS complexes (unless aberrant conduction/bundle branch block co-exists)",
"Ventricular rate: 100-180/min if uncontrolled; <100/min if rate-controlled",
"Low-voltage QRS in limb leads may suggest infiltrative disease (e.g., amyloidosis)",
],
"extra": None,
},
{
"num": "3",
"title": "Complete (Third-Degree) AV Block",
"img_key": "chb",
"caption": "Complete heart block: slow ventricular escape rhythm (~44 bpm) with wide QRS complexes, complete AV dissociation — P waves march at a faster independent rate through QRS complexes and T waves. Medical emergency requiring pacing.",
"overview": "Complete AV block is a life-threatening arrhythmia where no atrial impulses conduct to the ventricles. Causes include inferior MI (RCA), Lyme disease, drugs (beta-blockers, digoxin), and idiopathic fibrosis (Lenegre disease).",
"ecg_points": [
"Complete AV dissociation — P waves and QRS complexes fire independently with no fixed PR interval",
"Regular P-P intervals (atrial rate 60-100/min, faster than ventricles)",
"Regular R-R intervals at a slower escape rate (junctional: 40-60/min, narrow QRS; ventricular: 20-40/min, wide QRS)",
"Wide QRS if escape rhythm is infra-Hisian/ventricular (>120 ms)",
"Secondary ST-T changes from abnormal ventricular activation sequence",
"P waves may appear before, within, or after QRS complexes — none are conducted",
],
"extra": None,
},
{
"num": "4",
"title": "Ventricular Tachycardia (VT)",
"img_key": "vt",
"caption": "Monomorphic ventricular tachycardia at ~150 bpm: broad regular QRS complexes (>120 ms), AV dissociation. Red circle highlights a capture beat — a narrow QRS indicating transient sinus capture of the ventricles, pathognomonic of VT.",
"overview": "VT is a life-threatening arrhythmia originating below the bundle of His. Sustained VT (>30 sec) requires urgent cardioversion. Most common cause is structural heart disease (post-MI scar).",
"ecg_points": [
"Wide QRS complex tachycardia (QRS >120 ms) at rate 100-250/min",
"AV dissociation — independent P waves seen in ~50% of cases",
"Fusion beats — partial ventricular capture producing intermediate-morphology QRS",
"Capture beats — brief normal narrow QRS (pathognomonic of VT, rare but diagnostic)",
"Brugada criteria: concordance in precordials, RS interval >100 ms, no RS in any precordial lead → VT",
"Ventricular fibrillation: chaotic disorganised baseline, no QRS — cardiac arrest",
],
"extra": None,
},
{
"num": "5",
"title": "Wolff-Parkinson-White (WPW) Syndrome",
"img_key": "wpw",
"caption": "WPW syndrome: Shortened PR interval (<120 ms), delta waves (slurred initial QRS upstroke, red arrows in leads II-III), and widened QRS complexes throughout all leads. Negative delta waves in inferior leads suggest posteroseptal pathway.",
"overview": "WPW is caused by a congenital accessory pathway (Bundle of Kent) bypassing the AV node. Risk of sudden death via antidromic AF with rapid ventricular rates. Curative catheter ablation is first-line for symptomatic patients.",
"ecg_points": [
"Short PR interval (<120 ms) — early ventricular activation via accessory pathway",
"Delta wave — slurred upstroke at the start of QRS (ventricular pre-excitation)",
"Wide QRS complex (>120 ms) — fusion of pre-excited and normal ventricular activation",
"Secondary ST-T changes discordant to QRS vector",
"Pathway localisation: negative deltas in II,III,aVF → posteroseptal; positive V1 → left free wall",
"Risk: AF with rapid conduction → ventricular fibrillation (avoid AV nodal blockers in AF+WPW)",
],
"extra": None,
},
{
"num": "6",
"title": "Hypertrophic Cardiomyopathy (HCM)",
"img_key": "hcm",
"caption": "HCM (septal type): High-amplitude R waves in V2-V4 (LVH voltage criteria), ST-segment depression, and deep symmetric T-wave inversions V2-V6 with strain pattern. Absent septal Q waves in I, aVL, V5-V6. Positive T wave in aVR (arrhythmic risk marker).",
"overview": "HCM is the most common inherited cardiac condition (1:500), caused by sarcomere protein mutations. The leading cause of sudden cardiac death in young athletes. ECG is abnormal in >90% of patients.",
"ecg_points": [
"LVH voltage: Sokolow-Lyon (SV1 + RV5/V6 >35 mm); Cornell (RaVL + SV3 >28 mm males)",
"ST-segment depression and T-wave inversion (strain pattern) in I, aVL, V4-V6",
"Giant deep symmetric T-wave inversions in V2-V5 (apical HCM/Yamaguchi syndrome, >10 mm)",
"Absence of septal Q waves in lateral leads (V5, V6, I, aVL)",
"Left axis deviation; left atrial enlargement (P mitrale in lead II)",
"Positive T wave in aVR — associated with increased arrhythmic risk",
],
"extra": None,
},
{
"num": "7",
"title": "Acute Pericarditis",
"img_key": "pericarditis",
"caption": "Acute pericarditis: Diffuse concave ('saddle-shaped') ST elevation across I, II, III, aVF, V2-V6 with PR depression in lead II (most sensitive sign); reciprocal ST depression and PR elevation only in aVR. Sinus tachycardia reflects systemic inflammation.",
"overview": "Pericarditis is inflammation of the pericardium, most commonly viral. ECG evolves through 4 stages over days to weeks. Key differentiation from STEMI: diffuse non-territorial changes, concave morphology, PR depression.",
"ecg_points": [
"Stage I (Days 1-2): Diffuse concave 'saddle-shaped' ST elevation in all leads except aVR and V1; PR depression (most sensitive)",
"Stage II (Days 3-7): ST returns to baseline; T waves flatten",
"Stage III (Weeks 1-3): T-wave inversion in previously elevated leads",
"Stage IV (Weeks-months): ECG normalises",
"Key differentiators from STEMI: diffuse (not territorial), concave not convex, PR depression present, no reciprocal depression except aVR, no Q waves",
"Spodick's sign: downsloping TP segment (subtle but specific)",
],
"extra": {
"type": "stage_table",
},
},
{
"num": "8",
"title": "Pulmonary Embolism (PE)",
"img_key": "pe",
"caption": "Acute PE (S1Q3T3 pattern): Prominent deep S wave in lead I (S1), pathological Q wave in lead III (Q3), inverted T wave in lead III (T3), all annotated with circles/arrows. Sinus tachycardia >100 bpm reflecting acute right heart strain.",
"overview": "PE causes acute right ventricular pressure overload. The ECG has low sensitivity (~20-30%) but high specificity for severe PE when S1Q3T3 + RBBB are present. Sinus tachycardia is the most common finding.",
"ecg_points": [
"Sinus tachycardia — most common finding (>40% of cases)",
"S1Q3T3 pattern (McGinn-White sign): prominent S in lead I, Q wave in III, T-wave inversion in III",
"Right axis deviation and right bundle branch block (complete or incomplete, rSR' in V1)",
"T-wave inversions V1-V4 (right ventricular strain)",
"P pulmonale — peaked P waves >2.5 mm in lead II (right atrial enlargement)",
"New atrial fibrillation or flutter may appear in massive PE",
],
"extra": None,
},
{
"num": "9",
"title": "Brugada Syndrome",
"img_key": "brugada",
"caption": "Brugada syndrome Type 1 (only diagnostic pattern): Coved ST-segment elevation ≥2 mm in V1-V2 starting from an elevated J-point, descending with convex morphology into a symmetrically inverted T wave. RBBB-like rSR' morphology.",
"overview": "Brugada syndrome is an inherited SCN5A sodium channel channelopathy causing polymorphic VT and sudden cardiac death, predominantly in young men. Type 1 pattern on ECG (spontaneous or drug-induced) is required for diagnosis.",
"ecg_points": [
"Type 1 (Coved — DIAGNOSTIC): J-point ≥2 mm, convex downsloping ST segment, negative (inverted) T wave in V1-V2",
"Type 2 (Saddle-back): J-point ≥2 mm, saddle-back ST, positive T wave — requires pharmacological challenge",
"Changes confined to V1-V2; sensitivity improves with electrodes in 2nd-3rd ICS",
"RBBB-like rSR' pattern in V1; first-degree AV block; prolonged PR interval",
"Arrhythmia: polymorphic VT, often nocturnal, triggered by fever or sodium channel blockers",
"Fragmented QRS (multiple notches in QRS) indicates delayed heterogeneous depolarisation — high-risk marker",
],
"extra": {
"type": "brugada_table",
},
},
{
"num": "10",
"title": "Long QT Syndrome (LQTS)",
"img_key": "lqts",
"caption": "Long QT syndrome: Panel A — baseline ECG showing markedly prolonged QTc (680 ms) with prominent U waves (arrows in V1). Panel B — short-long-short sequence triggering polymorphic VT (torsades de pointes): rapid QRS complexes twisting around the isoelectric line.",
"overview": "LQTS is an inherited repolarisation disorder (KCNQ1, KCNH2, SCN5A mutations). Prolonged QTc delays repolarisation, creating risk of early afterdepolarisations and torsades de pointes. QTc >500 ms is high risk.",
"ecg_points": [
"Prolonged QTc: ≥450 ms (males) or ≥460 ms (females) borderline; ≥500 ms high-risk",
"Bazett formula: QTc = QT / sqrt(RR) — correct for heart rate",
"LQT1 (KCNQ1): broad-based smooth T wave",
"LQT2 (KCNH2): bifid/notched T wave (most common subtype)",
"LQT3 (SCN5A): late-onset peaked T wave with long isoelectric ST segment",
"Prominent U waves in V2-V3 (may fuse with T wave, masking true QT prolongation)",
"T-wave alternans: beat-to-beat variation in T-wave amplitude/morphology — electrical instability sign",
"Torsades de Pointes: polymorphic VT, QRS complexes twist around isoelectric baseline",
],
"extra": None,
},
]
# ── Build PDF ─────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2*cm, bottomMargin=2*cm,
title="10 Common Heart Diseases — ECG Changes",
author="Orris Medical AI",
subject="Cardiology ECG Reference",
)
story = []
cover_page(story)
for disease in diseases:
# Disease number + title
story.append(Paragraph(f"DISEASE {disease['num']} OF 10", disease_num_style))
story.append(Paragraph(disease["title"], disease_title_style))
story.append(hr())
# Overview
story.append(section_header("Overview"))
story.append(Paragraph(disease["overview"], body_style))
story.append(Spacer(1, 6))
# ECG Changes
story.append(section_header("ECG Changes"))
for point in disease["ecg_points"]:
story.append(bullet(point))
story.append(Spacer(1, 6))
# Special tables
if disease.get("extra"):
if disease["extra"]["type"] == "stage_table":
story.append(section_header("ECG Stage Evolution"))
stage_data = [
[Paragraph(h, table_header_style) for h in ["Stage", "Timing", "ECG Finding"]],
[Paragraph("I", table_cell_style), Paragraph("Days 1-2", table_cell_style), Paragraph("Diffuse concave ST elevation; PR depression", table_cell_style)],
[Paragraph("II", table_cell_style), Paragraph("Days 3-7", table_cell_style), Paragraph("ST returns to baseline; T waves flatten", table_cell_style)],
[Paragraph("III", table_cell_style), Paragraph("Weeks 1-3", table_cell_style), Paragraph("T-wave inversion in previously elevated leads", table_cell_style)],
[Paragraph("IV", table_cell_style), Paragraph("Weeks-months", table_cell_style), Paragraph("ECG normalises", table_cell_style)],
]
stage_table = Table(stage_data, colWidths=[2*cm, 3.5*cm, 9.5*cm])
stage_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_RED),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_GRAY]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#D5D8DC")),
("ROWPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(stage_table)
story.append(Spacer(1, 6))
elif disease["extra"]["type"] == "brugada_table":
story.append(section_header("Brugada ECG Pattern Classification"))
brug_data = [
[Paragraph(h, table_header_style) for h in ["Type", "ST Morphology", "T Wave", "Diagnostic?"]],
[Paragraph("Type 1 (Coved)", table_cell_style), Paragraph("J-point ≥2 mm, convex downsloping", table_cell_style), Paragraph("Negative (inverted)", table_cell_style), Paragraph("YES", ParagraphStyle("Yes", parent=table_cell_style, textColor=colors.green, fontName="Helvetica-Bold"))],
[Paragraph("Type 2 (Saddle-back)", table_cell_style), Paragraph("J-point ≥2 mm, saddle-back", table_cell_style), Paragraph("Positive", table_cell_style), Paragraph("NO (needs challenge)", ParagraphStyle("No", parent=table_cell_style, textColor=MED_RED))],
[Paragraph("Type 3", table_cell_style), Paragraph("<2 mm ST elevation", table_cell_style), Paragraph("Positive", table_cell_style), Paragraph("NO", ParagraphStyle("No2", parent=table_cell_style, textColor=MED_RED))],
]
brug_table = Table(brug_data, colWidths=[3.5*cm, 4.5*cm, 4*cm, 3*cm])
brug_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_RED),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_GRAY]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#D5D8DC")),
("ROWPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(brug_table)
story.append(Spacer(1, 6))
# ECG Image
story.append(section_header("ECG Image"))
story.append(Spacer(1, 4))
for item in ecg_image(disease["img_key"], disease["caption"]):
story.append(item)
story.append(PageBreak())
# ── Build ─────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"\nPDF saved to: {OUTPUT}")