Can u suggest me a best pdf for understanding complete ecg for different disorders
best free PDF ECG interpretation complete guide disorders download
| Book | Best For |
|---|---|
| The ECG Made Easy - Hamp ton | Absolute beginners |
| The ECG in Practice - Hamp ton | Intermediate, clinical correlation |
| Marriott's Practical Electrocardiography | Advanced/comprehensive |
| ECG Interpretation: The Self-Assessment Approach - Thaler | Self-study with Q&A format |
| The Only EKG Book You'll Ever Need - Thaler | Popular all-in-one text |
Create a summary PDF guide on ECG disorders & interpretation
mkdir -p /tmp/workspace/ecg-guide && ls /tmp/workspace/
~/ecg-guide/create_ecg_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfgen import canvas
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate
import os
OUTPUT_PATH = "/tmp/workspace/ecg-guide/ECG_Complete_Guide.pdf"
# ── Colour palette ────────────────────────────────────────────────────────────
RED = colors.HexColor("#C0392B")
DARK_RED = colors.HexColor("#96281B")
NAVY = colors.HexColor("#1A2E4A")
BLUE = colors.HexColor("#2980B9")
LIGHT_BLUE= colors.HexColor("#D6EAF8")
TEAL = colors.HexColor("#148F77")
LIGHT_TEAL= colors.HexColor("#D1F2EB")
ORANGE = colors.HexColor("#E67E22")
LIGHT_ORANGE = colors.HexColor("#FDEBD0")
PURPLE = colors.HexColor("#6C3483")
LIGHT_PURPLE = colors.HexColor("#E8DAEF")
GREEN = colors.HexColor("#1E8449")
LIGHT_GREEN = colors.HexColor("#D5F5E3")
YELLOW_BG = colors.HexColor("#FEF9E7")
GRAY_LIGHT= colors.HexColor("#F2F3F4")
GRAY_MID = colors.HexColor("#BFC9CA")
WHITE = colors.white
BLACK = colors.HexColor("#1C1C1C")
# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def style(name, **kw):
s = ParagraphStyle(name, **kw)
return s
TITLE_STYLE = style("MyTitle",
fontName="Helvetica-Bold", fontSize=28, textColor=WHITE,
alignment=TA_CENTER, spaceAfter=6, leading=34)
SUBTITLE_STYLE = style("MySubtitle",
fontName="Helvetica", fontSize=13, textColor=colors.HexColor("#D6EAF8"),
alignment=TA_CENTER, spaceAfter=4, leading=16)
CHAPTER_STYLE = style("Chapter",
fontName="Helvetica-Bold", fontSize=16, textColor=WHITE,
alignment=TA_LEFT, spaceAfter=4, spaceBefore=4, leading=20)
SECTION_STYLE = style("Section",
fontName="Helvetica-Bold", fontSize=12, textColor=NAVY,
alignment=TA_LEFT, spaceAfter=3, spaceBefore=6, leading=15)
SUBSECTION_STYLE = style("Subsection",
fontName="Helvetica-Bold", fontSize=10.5, textColor=TEAL,
alignment=TA_LEFT, spaceAfter=2, spaceBefore=4, leading=13)
BODY_STYLE = style("Body",
fontName="Helvetica", fontSize=9.5, textColor=BLACK,
alignment=TA_JUSTIFY, spaceAfter=3, leading=14)
BULLET_STYLE = style("Bullet",
fontName="Helvetica", fontSize=9.5, textColor=BLACK,
alignment=TA_LEFT, spaceAfter=2, leading=13,
leftIndent=14, bulletIndent=4)
BOLD_BODY = style("BoldBody",
fontName="Helvetica-Bold", fontSize=9.5, textColor=BLACK,
alignment=TA_LEFT, spaceAfter=2, leading=13)
TABLE_HEADER = style("TH",
fontName="Helvetica-Bold", fontSize=9, textColor=WHITE,
alignment=TA_CENTER, leading=12)
TABLE_CELL = style("TC",
fontName="Helvetica", fontSize=9, textColor=BLACK,
alignment=TA_LEFT, leading=12)
TABLE_CELL_C = style("TCC",
fontName="Helvetica", fontSize=9, textColor=BLACK,
alignment=TA_CENTER, leading=12)
CAPTION_STYLE = style("Caption",
fontName="Helvetica-Oblique", fontSize=8.5, textColor=colors.HexColor("#555555"),
alignment=TA_CENTER, spaceAfter=4, leading=11)
WARNING_STYLE = style("Warning",
fontName="Helvetica-Bold", fontSize=9.5, textColor=DARK_RED,
alignment=TA_LEFT, spaceAfter=2, leading=13, leftIndent=10)
# ── Helper functions ──────────────────────────────────────────────────────────
def chapter_header(title, color=NAVY):
"""Returns a styled chapter header block."""
tbl = Table([[Paragraph(title, CHAPTER_STYLE)]], colWidths=[170*mm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("ROUNDEDCORNERS", [4,4,4,4]),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 12),
]))
return tbl
def section_box(title, color=LIGHT_BLUE, tcolor=NAVY):
s = style("sb", fontName="Helvetica-Bold", fontSize=11,
textColor=tcolor, alignment=TA_LEFT, leading=14)
tbl = Table([[Paragraph(title, s)]], colWidths=[170*mm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 10),
("LINEBELOW", (0,0), (-1,-1), 1, tcolor),
]))
return tbl
def info_box(text, bg=YELLOW_BG, border=ORANGE):
s = style("ib", fontName="Helvetica", fontSize=9.5, textColor=BLACK,
alignment=TA_LEFT, leading=13, leftIndent=4)
tbl = Table([[Paragraph(text, s)]], colWidths=[165*mm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 1.2, border),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
]))
return tbl
def bullet(text, indent=14):
return Paragraph(f"<bullet>•</bullet> {text}", BULLET_STYLE)
def subbullet(text):
s = style("sb2", fontName="Helvetica", fontSize=9, textColor=BLACK,
alignment=TA_LEFT, leading=12, leftIndent=26, bulletIndent=16)
return Paragraph(f"<bullet>◦</bullet> {text}", s)
def sp(h=4):
return Spacer(1, h*mm)
def hr(color=GRAY_MID, thickness=0.5):
return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=2*mm, spaceBefore=2*mm)
def make_table(headers, rows, col_widths, header_bg=NAVY, alt_bg=GRAY_LIGHT):
header_row = [Paragraph(h, TABLE_HEADER) for h in headers]
data = [header_row]
for i, row in enumerate(rows):
bg = WHITE if i % 2 == 0 else alt_bg
data.append([Paragraph(str(c), TABLE_CELL) for c in row])
tbl = Table(data, colWidths=col_widths)
style_cmds = [
("BACKGROUND", (0,0), (-1,0), header_bg),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, alt_bg]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
tbl.setStyle(TableStyle(style_cmds))
return tbl
# ── Page callbacks ────────────────────────────────────────────────────────────
def cover_page(canvas_obj, doc):
canvas_obj.saveState()
w, h = A4
# Background gradient simulation with rectangles
canvas_obj.setFillColor(NAVY)
canvas_obj.rect(0, 0, w, h, fill=1, stroke=0)
# Top accent bar
canvas_obj.setFillColor(RED)
canvas_obj.rect(0, h-12*mm, w, 12*mm, fill=1, stroke=0)
# Bottom accent bar
canvas_obj.setFillColor(TEAL)
canvas_obj.rect(0, 0, w, 8*mm, fill=1, stroke=0)
# ECG waveform decoration (simplified)
canvas_obj.setStrokeColor(colors.HexColor("#E74C3C"))
canvas_obj.setLineWidth(2)
canvas_obj.setFillColor(colors.HexColor("#E74C3C"))
# Draw a simple ECG trace across the page
y_base = h * 0.42
pts = [
(10, y_base), (50, y_base), (60, y_base+5), (65, y_base-30),
(70, y_base+60), (75, y_base-30), (82, y_base+8), (90, y_base),
(130, y_base), (140, y_base+4), (148, y_base-12),
(155, y_base+4), (165, y_base), (210, y_base),
(220, y_base+4), (228, y_base-12), (235, y_base+4), (245, y_base),
(290, y_base), (300, y_base+5), (308, y_base-30),
(313, y_base+60), (318, y_base-30), (325, y_base+8), (333, y_base),
(375, y_base), (385, y_base+4), (393, y_base-12),
(400, y_base+4), (410, y_base), (455, y_base),
(465, y_base+5), (473, y_base-30), (478, y_base+60),
(483, y_base-30), (490, y_base+8), (500, y_base), (595, y_base),
]
p = canvas_obj.beginPath()
p.moveTo(pts[0][0], pts[0][1])
for x, y in pts[1:]:
p.lineTo(x, y)
canvas_obj.drawPath(p, stroke=1, fill=0)
canvas_obj.setStrokeColor(colors.HexColor("#2980B9"))
canvas_obj.setLineWidth(0.5)
canvas_obj.setFillColor(colors.HexColor("#2980B9"))
y_base2 = y_base - 50
pts2 = [
(10, y_base2), (50, y_base2), (56, y_base2+3), (60, y_base2-18),
(64, y_base2+36), (68, y_base2-18), (74, y_base2+5), (80, y_base2),
(120, y_base2), (126, y_base2+3), (130, y_base2-18),
(134, y_base2+36), (138, y_base2-18), (144, y_base2+5), (150, y_base2),
(595, y_base2),
]
p2 = canvas_obj.beginPath()
p2.moveTo(pts2[0][0], pts2[0][1])
for x, y in pts2[1:]:
p2.lineTo(x, y)
canvas_obj.drawPath(p2, stroke=1, fill=0)
canvas_obj.restoreState()
def normal_page(canvas_obj, doc):
canvas_obj.saveState()
w, h = A4
# Top bar
canvas_obj.setFillColor(NAVY)
canvas_obj.rect(0, h-10*mm, w, 10*mm, fill=1, stroke=0)
# Header text
canvas_obj.setFont("Helvetica-Bold", 8)
canvas_obj.setFillColor(WHITE)
canvas_obj.drawString(15*mm, h-6.5*mm, "ECG COMPLETE GUIDE: Disorders & Interpretation")
canvas_obj.setFont("Helvetica", 8)
canvas_obj.drawRightString(w-15*mm, h-6.5*mm, "Orris Medical Reference")
# Bottom bar
canvas_obj.setFillColor(NAVY)
canvas_obj.rect(0, 0, w, 8*mm, fill=1, stroke=0)
# Page number
canvas_obj.setFont("Helvetica", 8)
canvas_obj.setFillColor(WHITE)
canvas_obj.drawCentredString(w/2, 2.8*mm, f"Page {doc.page}")
# Left accent line
canvas_obj.setFillColor(RED)
canvas_obj.rect(0, 8*mm, 3*mm, h-18*mm, fill=1, stroke=0)
canvas_obj.restoreState()
# ── Build content ─────────────────────────────────────────────────────────────
def build_pdf():
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
rightMargin=18*mm, leftMargin=22*mm,
topMargin=18*mm, bottomMargin=16*mm,
title="ECG Complete Guide: Disorders & Interpretation",
author="Orris Medical Reference",
subject="ECG Interpretation for Clinical Practice",
)
story = []
# ═══════════════════════════════════════════════════════
# COVER PAGE (uses cover_page callback via first page)
# ═══════════════════════════════════════════════════════
story.append(Spacer(1, 45*mm))
cover_title = style("ct", fontName="Helvetica-Bold", fontSize=34,
textColor=WHITE, alignment=TA_CENTER, leading=40)
cover_sub = style("cs", fontName="Helvetica", fontSize=15,
textColor=colors.HexColor("#AED6F1"), alignment=TA_CENTER, leading=20)
cover_tag = style("ctag", fontName="Helvetica-Oblique", fontSize=11,
textColor=colors.HexColor("#A9DFBF"), alignment=TA_CENTER, leading=14)
story.append(Paragraph("ECG Complete Guide", cover_title))
story.append(sp(3))
story.append(Paragraph("Disorders & Interpretation", cover_title))
story.append(sp(8))
story.append(Paragraph("A Systematic Reference for Medical Students, Nurses & Clinicians", cover_sub))
story.append(sp(35))
story.append(Paragraph("Covering: Normal ECG · Arrhythmias · Conduction Blocks · Ischemia / MI", cover_tag))
story.append(Paragraph("Hypertrophy · Electrolyte Disorders · Drug Effects · Systematic Approach", cover_tag))
story.append(sp(10))
cover_byline = style("cby", fontName="Helvetica", fontSize=10,
textColor=colors.HexColor("#85C1E9"), alignment=TA_CENTER, leading=13)
story.append(Paragraph("Orris Medical Reference | 2026", cover_byline))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 1 – ECG Basics & Systematic Approach
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 1: ECG Basics & Systematic Approach", NAVY))
story.append(sp(3))
story.append(section_box("What is an ECG?", LIGHT_BLUE, NAVY))
story.append(sp(2))
story.append(Paragraph(
"An electrocardiogram (ECG/EKG) records the electrical activity of the heart "
"over time via electrodes placed on the skin. A standard 12-lead ECG captures "
"electrical vectors from 12 different angles, providing information about rhythm, "
"conduction, ischemia, hypertrophy, and metabolic abnormalities.", BODY_STYLE))
story.append(sp(3))
story.append(section_box("ECG Paper & Measurements", LIGHT_TEAL, TEAL))
story.append(sp(2))
measurements = [
["Parameter", "Small Box", "Large Box", "Standard Value"],
["Time (horizontal)", "0.04 sec", "0.20 sec", "Paper speed: 25 mm/s"],
["Voltage (vertical)", "0.1 mV", "0.5 mV", "Calibration: 1 mV = 10 mm"],
["PR Interval", "—", "—", "0.12 – 0.20 sec (3–5 small boxes)"],
["QRS Duration", "—", "—", "< 0.12 sec (< 3 small boxes)"],
["QT Interval", "—", "—", "< 0.44 sec (corrected QTc)"],
["P Wave", "—", "—", "< 0.12 sec, < 2.5 mm height"],
]
story.append(make_table(
measurements[0], measurements[1:],
[42*mm, 28*mm, 28*mm, 72*mm], TEAL))
story.append(sp(3))
story.append(section_box("12-Lead ECG: Lead Groups & Views", LIGHT_BLUE, NAVY))
story.append(sp(2))
lead_data = [
["Lead Group", "Leads", "Heart Region Viewed"],
["Inferior", "II, III, aVF", "Inferior wall (RCA territory)"],
["Lateral", "I, aVL, V5, V6", "Lateral wall (LCx territory)"],
["Anterior (Septal)", "V1, V2", "Interventricular septum"],
["Anterior (Anterior)", "V3, V4", "Anterior wall (LAD territory)"],
["Right-sided", "V1, V3R–V6R", "Right ventricle"],
["Posterior", "V7–V9 (or reciprocal V1–V3)", "Posterior wall"],
["Augmented Limb", "aVR", "Cavity / global ischemia detector"],
]
story.append(make_table(lead_data[0], lead_data[1:], [32*mm, 42*mm, 96*mm], NAVY))
story.append(sp(3))
story.append(section_box("The 7-Step Systematic Approach", LIGHT_ORANGE, ORANGE))
story.append(sp(2))
steps = [
("<b>Step 1 – Rate:</b>", "Count R–R intervals. 300 ÷ number of large boxes between R waves. Normal: 60–100 bpm."),
("<b>Step 2 – Rhythm:</b>", "Regular or irregular? P before every QRS? QRS after every P?"),
("<b>Step 3 – P Wave:</b>", "Present, morphology, axis (upright in I & II = sinus). Duration & amplitude."),
("<b>Step 4 – PR Interval:</b>", "0.12–0.20 s. Short = WPW or junctional. Long = 1st degree block."),
("<b>Step 5 – QRS Complex:</b>", "Duration < 0.12 s. Check axis, bundle branch blocks, delta waves."),
("<b>Step 6 – ST Segment & T Wave:</b>", "Elevation or depression? T wave inversion, peaked, or biphasic?"),
("<b>Step 7 – QT Interval:</b>", "Correct with Bazett formula: QTc = QT / √RR. Normal QTc < 440 ms (men) / < 460 ms (women)."),
]
for label, desc in steps:
row_tbl = Table([[Paragraph(label, BOLD_BODY), Paragraph(desc, BODY_STYLE)]],
colWidths=[40*mm, 128*mm])
row_tbl.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
]))
story.append(row_tbl)
story.append(sp(2))
story.append(info_box(
"⚡ REMEMBER: Always check: rate · rhythm · P wave · PR · QRS · ST-T · QTc",
YELLOW_BG, ORANGE))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 2 – Normal ECG
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 2: Normal ECG & Key Waveforms", TEAL))
story.append(sp(3))
story.append(section_box("Normal Sinus Rhythm Criteria", LIGHT_TEAL, TEAL))
story.append(sp(2))
nsr = [
"Rate 60–100 bpm",
"Regular rhythm (R–R intervals vary < 10%)",
"Upright P wave in leads I, II, aVF",
"Inverted P wave in aVR",
"Constant PR interval (0.12–0.20 s)",
"Every P followed by a QRS; every QRS preceded by a P",
"QRS duration < 0.12 s (narrow complex)",
"Normal axis: –30° to +90°",
]
for b in nsr:
story.append(bullet(b))
story.append(sp(3))
story.append(section_box("Normal Waveform Characteristics", LIGHT_BLUE, NAVY))
story.append(sp(2))
wave_data = [
["Wave / Segment", "Normal Features", "Clinical Significance"],
["P Wave", "< 0.12 s, < 2.5 mm, biphasic in V1", "Atrial depolarisation"],
["PR Segment", "Isoelectric, 0.12–0.20 s", "AV node conduction delay"],
["Q Wave", "< 0.04 s, < 25% of R height", "Septal or pathological (wide/deep)"],
["R Wave", "Progressive increase V1→V5", "Ventricular depolarisation"],
["S Wave", "Decreases V4→V6", "Late ventricular activation"],
["ST Segment", "Isoelectric (±0.5 mm in limb; ±1 mm in precordial)", "Repolarisation; elevation/depression = ischaemia"],
["T Wave", "Upright except aVR, V1 (±III); ≥ 1/8 but ≤ 2/3 of R", "Ventricular repolarisation"],
["U Wave", "Small positive deflection after T (best seen V2–V3)", "Often hypokalaemia; inverted = ischaemia"],
["QTc", "< 440 ms ♂, < 460 ms ♀", "Prolonged = risk of Torsades de Pointes"],
]
story.append(make_table(wave_data[0], wave_data[1:], [38*mm, 58*mm, 74*mm], NAVY))
story.append(sp(3))
story.append(section_box("Axis Interpretation", LIGHT_PURPLE, PURPLE))
story.append(sp(2))
axis_data = [
["Axis", "Degrees", "Leads I & aVF", "Common Causes"],
["Normal", "−30° to +90°", "Both positive", "Normal"],
["Left Axis Deviation (LAD)", "−30° to −90°", "I +ve, aVF –ve", "LBBB, LAHB, inferior MI, LVH"],
["Right Axis Deviation (RAD)", "+90° to +180°", "I –ve, aVF +ve", "RBBB, RVH, lateral MI, PE"],
["Extreme / NW", "−90° to ±180°", "Both negative", "VT, severe emphysema, dextrocardia"],
]
story.append(make_table(axis_data[0], axis_data[1:], [38*mm, 24*mm, 36*mm, 72*mm], PURPLE))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 3 – Arrhythmias
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 3: Arrhythmias", RED))
story.append(sp(3))
# ── Sinus arrhythmias
story.append(section_box("3.1 Sinus Arrhythmias", LIGHT_BLUE, NAVY))
story.append(sp(2))
sinus_data = [
["Arrhythmia", "Rate", "Key ECG Feature", "Clinical Note"],
["Sinus Bradycardia", "< 60 bpm", "Normal P–QRS; slow rate", "Athletes, hypothyroidism, beta-blockers, vasovagal"],
["Sinus Tachycardia", "> 100 bpm", "Normal P–QRS; fast rate", "Pain, fever, PE, heart failure, anaemia, drugs"],
["Sinus Arrhythmia", "60–100 bpm", "Irregular R–R, varies with breathing", "Normal variant; prominent in young/athletes"],
["Sick Sinus Syndrome", "Variable", "Brady-tachy alternation, pauses > 2 s, SA block", "Requires pacemaker if symptomatic"],
]
story.append(make_table(sinus_data[0], sinus_data[1:], [38*mm, 20*mm, 58*mm, 54*mm], RED))
story.append(sp(3))
# ── Supraventricular
story.append(section_box("3.2 Supraventricular Arrhythmias", LIGHT_TEAL, TEAL))
story.append(sp(2))
sva_data = [
["Arrhythmia", "Rate", "Key ECG Features", "Notes"],
["PAC (Premature Atrial Complex)", "—", "Early P' wave, abnormal morphology, compensatory pause incomplete", "Benign; can trigger SVT/AF"],
["AVNRT (SVT)", "150–250 bpm", "Regular narrow QRS; P buried in / just after QRS (pseudo-r' in V1, pseudo-s in II)", "Most common paroxysmal SVT; vagal manoeuvres / adenosine"],
["AVRT (WPW-related SVT)", "150–300 bpm", "Narrow QRS (orthodromic) or wide (antidromic); pre-excitation during sinus = delta wave, short PR", "Avoid AV nodal agents in pre-excited AF"],
["Atrial Flutter", "Atrial 300 bpm; ventricular 150 bpm (2:1)", "Sawtooth flutter waves in II, III, aVF; regular ventricular response", "Ablation highly effective; anticoagulate"],
["Atrial Fibrillation (AF)", "Atrial 350–600 bpm; ventricular variable", "Irregularly irregular rhythm; absent P waves; fibrillatory baseline", "Most common sustained arrhythmia; stroke risk → CHA₂DS₂-VASc"],
["Multifocal AT (MAT)", "100–200 bpm", "Irregular; ≥ 3 distinct P wave morphologies; variable PR", "COPD, electrolyte disorders, elderly"],
["Junctional Rhythm", "40–60 bpm", "Absent or inverted P (before/during/after QRS); narrow QRS", "AV nodal escape; seen in inferior MI, digoxin toxicity"],
]
story.append(make_table(sva_data[0], sva_data[1:], [40*mm, 22*mm, 58*mm, 50*mm], TEAL))
story.append(sp(3))
# ── Ventricular arrhythmias
story.append(section_box("3.3 Ventricular Arrhythmias", LIGHT_ORANGE, ORANGE))
story.append(sp(2))
va_data = [
["Arrhythmia", "Rate", "Key ECG Features", "Management"],
["PVC (Premature Ventricular Complex)", "—", "Wide bizarre QRS ≥ 0.12 s; full compensatory pause; no P before", "Treat underlying cause; frequent PVCs (> 10,000/day) can cause CMP"],
["Accelerated Idioventricular Rhythm (AIVR)", "40–120 bpm", "Wide QRS, regular, no P preceding; often post-MI reperfusion", "Usually benign & self-limiting"],
["Ventricular Tachycardia (VT)", "> 100 bpm (usually 130–250)", "Wide complex tachycardia (QRS ≥ 0.12 s); AV dissociation, fusion / capture beats", "If pulse: amiodarone/cardioversion. Pulseless: defib"],
["Ventricular Fibrillation (VF)", "Chaotic", "Chaotic undulations; no identifiable QRS; no pulse", "Immediate defibrillation + CPR (ACLS)"],
["Torsades de Pointes (TdP)", "200–250 bpm", "Polymorphic VT; QRS twists around isoelectric axis; preceded by long QTc", "Magnesium IV; remove QT-prolonging drugs; overdrive pacing"],
["Brugada Syndrome", "—", "Coved-type ST elevation V1–V2; RBBB pattern; can trigger sudden VF", "ICD implantation; avoid sodium channel blockers"],
]
story.append(make_table(va_data[0], va_data[1:], [42*mm, 22*mm, 60*mm, 46*mm], ORANGE))
story.append(sp(2))
story.append(info_box(
"⚠ VT vs SVT with aberrancy: Brugada criteria, Vereckei algorithm. "
"Key clues for VT: AV dissociation, fusion beats, concordance, extreme axis, QRS ≥ 0.16 s.",
colors.HexColor("#FDEDEC"), DARK_RED))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 4 – Conduction Blocks
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 4: Conduction Blocks", PURPLE))
story.append(sp(3))
story.append(section_box("4.1 AV Blocks", LIGHT_PURPLE, PURPLE))
story.append(sp(2))
av_data = [
["Block", "PR Interval", "QRS Dropped?", "Rhythm", "Clinical Significance"],
["1st Degree AV Block", "> 0.20 s; constant", "No", "Regular", "Often benign; monitor; can be caused by vagal tone, inferior MI, drugs"],
["2nd Degree Mobitz I (Wenckebach)", "Progressive lengthening", "Yes – periodically", "Irregular grouped beating", "AV node dysfunction; usually benign; inferior MI; vagal"],
["2nd Degree Mobitz II", "Constant (normal or long)", "Yes – suddenly, without warning", "Regular except for dropped beats", "HIS/Purkinje disease; often progresses to complete heart block → pacemaker"],
["2:1 AV Block", "Constant", "Every alternate P dropped", "Regular at half atrial rate", "Cannot classify as Mobitz I or II without ≥ 3 P waves together; needs EP study"],
["3rd Degree (Complete) AV Block", "No relationship (AV dissociation)", "QRS unrelated to P waves; escape rhythm", "Atrial regular; ventricular regular but independent", "Emergency: junctional escape (narrow, 40–60) or ventricular escape (wide, < 40); requires pacing"],
]
story.append(make_table(av_data[0], av_data[1:], [38*mm, 30*mm, 26*mm, 26*mm, 50*mm], PURPLE))
story.append(sp(3))
story.append(section_box("4.2 Bundle Branch Blocks", LIGHT_BLUE, NAVY))
story.append(sp(2))
story.append(Paragraph("<b>Right Bundle Branch Block (RBBB)</b>", SUBSECTION_STYLE))
for b in [
"QRS ≥ 0.12 s (complete RBBB) or 0.10–0.12 s (incomplete RBBB)",
"rSR' ('M' pattern / rabbit ears) in V1–V2",
"Wide, slurred S wave in I, aVL, V5–V6",
"Secondary ST-T changes (ST depression + T inversion in V1–V3)",
"Causes: normal variant, PE, RVH, ASD, anterior MI, post-cardiac surgery",
]:
story.append(bullet(b))
story.append(sp(2))
story.append(Paragraph("<b>Left Bundle Branch Block (LBBB)</b>", SUBSECTION_STYLE))
for b in [
"QRS ≥ 0.12 s",
"Broad, notched R wave ('M' shape) in I, aVL, V5–V6 (no septal Q waves)",
"rS or QS pattern in V1–V3",
"Discordant ST-T changes (opposite to main QRS deflection)",
"New LBBB + chest pain = treat as STEMI equivalent (Sgarbossa criteria)",
"Causes: IHD, dilated CMP, hypertension, aortic valve disease",
]:
story.append(bullet(b))
story.append(sp(2))
story.append(Paragraph("<b>Fascicular Blocks (Hemiblocks)</b>", SUBSECTION_STYLE))
fascicular_data = [
["Block", "Axis", "Lead I", "Lead II/III", "Notes"],
["Left Anterior Hemiblock (LAHB)", "LAD (−30° to −90°)", "+ve (qR)", "rS", "Most common; narrow QRS; no right axis shift"],
["Left Posterior Hemiblock (LPHB)", "RAD (+90° to +120°)", "rS", "+ve (qR)", "Rare; must exclude other RAD causes"],
["Bifascicular Block", "LAD", "RBBB + LAHB pattern", "—", "RBBB + LAHB; risk of complete block"],
["Trifascicular Block", "Variable", "1st-degree block + bifascicular", "—", "High risk of complete heart block; consider pacing"],
]
story.append(make_table(fascicular_data[0], fascicular_data[1:], [40*mm, 24*mm, 24*mm, 24*mm, 58*mm], NAVY))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 5 – Ischaemia & MI
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 5: Ischaemia, Injury & Myocardial Infarction", DARK_RED))
story.append(sp(3))
story.append(section_box("5.1 Spectrum of Ischaemic ECG Changes", LIGHT_ORANGE, ORANGE))
story.append(sp(2))
isch_data = [
["Stage", "ECG Findings", "Pathophysiology"],
["Hyper-Acute (minutes)", "Tall, peaked 'hyperacute' T waves; ST straightening", "Acute transmural ischaemia; earliest sign"],
["Acute Injury (minutes–hours)", "ST elevation ≥ 1 mm in ≥ 2 contiguous leads (≥ 2 mm in V1–V4)", "Transmural injury current; STEMI criteria"],
["Established Infarction (hours–days)", "Pathological Q waves (≥ 0.04 s, ≥ 25% of R); ST normalises", "Myocyte necrosis; Q wave = dead tissue"],
["Chronic / Resolved (weeks–months)", "Persistent Q waves; T wave normalisation (may invert)", "Scar tissue; Q waves often permanent"],
["Subendocardial Ischaemia (NSTEMI/UA)", "Horizontal/downsloping ST depression ≥ 0.5 mm; T inversion", "Partial thickness ischaemia; no Q waves typically"],
]
story.append(make_table(isch_data[0], isch_data[1:], [40*mm, 72*mm, 58*mm], ORANGE))
story.append(sp(3))
story.append(section_box("5.2 MI Localisation by Territory", LIGHT_BLUE, NAVY))
story.append(sp(2))
mi_data = [
["Territory", "Artery (Usual)", "Leads with ST Changes", "Reciprocal Changes"],
["Inferior MI", "RCA (85%) / LCx (15%)", "II, III, aVF", "I, aVL"],
["Anterior MI", "LAD (proximal)", "V1–V4 (or V1–V6)", "II, III, aVF (sometimes)"],
["Anteroseptal MI", "LAD (septal branches)", "V1–V3", "None specific"],
["Anterolateral MI", "LAD or LCx", "V1–V6, I, aVL", "II, III, aVF"],
["Lateral MI", "LCx / diagonal branch", "I, aVL, V5–V6", "V1–V3, II, III, aVF"],
["Posterior MI", "RCA / LCx", "V1–V3 ST depression; tall R wave", "ST elevation V7–V9 (posterior leads)"],
["Right Ventricular MI", "Proximal RCA", "V1 ST elevation; ST elevation V3R–V4R", "Usually with inferior MI (II, III, aVF)"],
["High Lateral / Apical", "Diagonal / OM branch", "I, aVL only (or none)", "II, III"],
]
story.append(make_table(mi_data[0], mi_data[1:], [36*mm, 36*mm, 50*mm, 48*mm], DARK_RED))
story.append(sp(3))
story.append(section_box("5.3 Sgarbossa Criteria (MI in LBBB)", LIGHT_ORANGE, ORANGE))
story.append(sp(2))
sgarbossa = [
("<b>Criterion 1 (5 pts):</b>", "ST elevation ≥ 1 mm concordant with QRS direction"),
("<b>Criterion 2 (3 pts):</b>", "ST depression ≥ 1 mm in V1, V2, or V3 (concordant negative)"),
("<b>Criterion 3 (2 pts):</b>", "ST elevation ≥ 5 mm discordant with QRS direction"),
]
for label, desc in sgarbossa:
row = Table([[Paragraph(label, BOLD_BODY), Paragraph(desc, BODY_STYLE)]],
colWidths=[40*mm, 128*mm])
row.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
]))
story.append(row)
story.append(sp(1))
story.append(info_box("Score ≥ 3 = high specificity for AMI. Modified Sgarbossa: use proportional criterion (ST/S ratio ≥ 0.25) instead of absolute 5 mm rule.", YELLOW_BG, ORANGE))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 6 – Hypertrophy & Enlargement
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 6: Chamber Hypertrophy & Enlargement", TEAL))
story.append(sp(3))
story.append(section_box("6.1 Ventricular Hypertrophy", LIGHT_TEAL, TEAL))
story.append(sp(2))
vh_data = [
["Criteria", "LVH", "RVH"],
["Voltage (main)", "Sokolow-Lyon: S(V1) + R(V5/V6) ≥ 35 mm\nCornell: R(aVL) ≥ 11 mm\nR(aVL) + S(V3) > 28 mm (M) / > 20 mm (F)", "R > S in V1; R in V1 ≥ 7 mm\nS in V5/V6 > 7 mm\nR:S ratio in V1 ≥ 1"],
["Axis", "LAD or normal", "RAD > +90°"],
["ST-T Changes", "Strain pattern: ST depression + T inversion in I, aVL, V5–V6", "Strain: ST depression + T inversion in V1–V4"],
["P Wave", "Often normal; may have P mitrale if raised LVEDP", "May show P pulmonale (tall P ≥ 2.5 mm)"],
["Causes", "HTN, aortic stenosis, HCM, AR", "Cor pulmonale, PE, pulmonary HTN, PS"],
]
tbl = Table(
[[Paragraph(vh_data[0][0], TABLE_HEADER), Paragraph(vh_data[0][1], TABLE_HEADER), Paragraph(vh_data[0][2], TABLE_HEADER)]] +
[[Paragraph(r[0], TABLE_CELL), Paragraph(r[1], TABLE_CELL), Paragraph(r[2], TABLE_CELL)] for r in vh_data[1:]],
colWidths=[30*mm, 72*mm, 68*mm]
)
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_TEAL]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6), ("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(tbl)
story.append(sp(3))
story.append(section_box("6.2 Atrial Enlargement", LIGHT_BLUE, NAVY))
story.append(sp(2))
atrial_data = [
["Feature", "Left Atrial Enlargement (LAE)", "Right Atrial Enlargement (RAE)"],
["P Wave Duration", "> 0.12 s (P mitrale)", "Normal or short"],
["P Wave Height", "Normal (or terminal negative portion in V1 > 1 mm × 1 mm box)", "≥ 2.5 mm in II, III, or aVF (P pulmonale)"],
["P Wave Morphology", "Bifid/notched P in II; biphasic in V1 with broad negative terminal", "Peaked, tall, narrow P"],
["Common Causes", "Mitral stenosis/regurgitation, HTN, LHF", "COPD, pulmonary HTN, tricuspid disease, RHF"],
]
story.append(make_table(atrial_data[0], atrial_data[1:], [34*mm, 68*mm, 68*mm], NAVY))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 7 – Electrolyte & Metabolic Disorders
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 7: Electrolyte & Metabolic Disorders", colors.HexColor("#1A5276")))
story.append(sp(3))
story.append(section_box("7.1 Potassium Disorders", LIGHT_BLUE, NAVY))
story.append(sp(2))
k_data = [
["K+ Level", "ECG Changes", "Clinical Sequence"],
["Hypokalaemia\n< 3.5 mEq/L", "U wave prominence (V2–V3); T–U fusion; T wave flattening; QTU prolongation; ST depression; PVCs; TdP", "K 3–3.5: U waves → K 2.5–3: flat T, U > T → K < 2.5: T/U fusion, ST depression → K < 2: VF risk"],
["Hyperkalaemia\n> 5.5 mEq/L", "Peaked narrow T waves → PR prolongation → P wave flattening/disappearance → QRS widening → sine wave pattern → VF/asystole", "K 5.5–6.5: tall T → K 6.5–7.5: PR long, P flat → K > 7.5: wide QRS, sine wave → K > 9: cardiac arrest"],
]
tbl_k = Table(
[[Paragraph(k_data[0][0], TABLE_HEADER), Paragraph(k_data[0][1], TABLE_HEADER), Paragraph(k_data[0][2], TABLE_HEADER)]] +
[[Paragraph(r[0], TABLE_CELL), Paragraph(r[1], TABLE_CELL), Paragraph(r[2], TABLE_CELL)] for r in k_data[1:]],
colWidths=[30*mm, 72*mm, 68*mm]
)
tbl_k.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("TOPPADDING", (0,0), (-1,-1), 6), ("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 6), ("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(tbl_k)
story.append(sp(3))
story.append(section_box("7.2 Other Electrolyte & Metabolic Disorders", LIGHT_TEAL, TEAL))
story.append(sp(2))
met_data = [
["Disorder", "ECG Findings", "Memory Aid"],
["Hypercalcaemia", "Short QT interval; short ST segment; prolonged PR; Osborn wave (rarely)", "Ca UP → QT DOWN"],
["Hypocalcaemia", "Prolonged QT (prolonged ST segment); T wave normal or inverted", "Ca DOWN → QT UP"],
["Hypomagnesaemia", "Prolonged QTc; T wave changes; TdP risk; similar to hypokalaemia", "Often coexists with hypokalaemia"],
["Hypothyroidism", "Sinus bradycardia; low voltage; prolonged QTc; T wave flattening/inversion; pericardial effusion pattern", "Think slow + low"],
["Hyperthyroidism", "Sinus tachycardia; AF; shortened QTc; high voltage", "Think fast + irregular"],
["Hypothermia", "Sinus bradycardia; Osborn (J) wave at QRS/ST junction; AF; prolonged intervals; VF risk", "Osborn waves pathognomonic"],
["Digoxin Effect", "Sagging ('reverse tick' or 'Salvador Dali moustache') ST depression; T wave inversion; shortened QT; PR prolongation", "ST scooping = digoxin effect (not toxicity)"],
["Digoxin Toxicity", "Any arrhythmia: PAT with block, regularised AF, VT/VF, junctional rhythms, PVCs (bigeminy)", "Classic: PAT with 2:1 block"],
]
story.append(make_table(met_data[0], met_data[1:], [36*mm, 80*mm, 54*mm], TEAL))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 8 – Special Conditions
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 8: Special & Specific Conditions", colors.HexColor("#117A65")))
story.append(sp(3))
story.append(section_box("8.1 Pericarditis", LIGHT_TEAL, TEAL))
story.append(sp(2))
for b in [
"<b>Saddle-shaped (concave up) diffuse ST elevation</b> in most leads except aVR and V1 (which show ST depression)",
"<b>PR depression</b> – most specific finding; best seen in II and aVL",
"<b>No reciprocal changes</b> (unlike MI) – a key differentiator",
"<b>T wave inversion</b> occurs later (after ST normalises; Stage III)",
"<b>Stages:</b> I (ST elevation, PR depression) → II (normalisation) → III (T inversion) → IV (normalisation)",
"<b>Spodick's sign:</b> downsloping TP segment (best in II); sensitive early finding",
]:
story.append(bullet(b))
story.append(sp(3))
story.append(section_box("8.2 Pulmonary Embolism (PE)", LIGHT_ORANGE, ORANGE))
story.append(sp(2))
for b in [
"<b>Sinus tachycardia</b> – most common ECG finding in PE (present in ~40%)",
"<b>S1Q3T3 pattern:</b> S wave in I + Q wave in III + T inversion in III (present in ~20% – not sensitive or specific alone)",
"<b>Right heart strain:</b> new RBBB (complete or incomplete), RAD, right axis shift",
"<b>T wave inversions V1–V4</b> – suggests RV strain; can mimic anterior ischaemia",
"<b>AF or atrial flutter</b> may precipitate",
"<b>Low voltage / sinus tachycardia alone</b> – should prompt CT-PA if clinically suspicious",
]:
story.append(bullet(b))
story.append(sp(3))
story.append(section_box("8.3 Wolff-Parkinson-White (WPW) Syndrome", LIGHT_PURPLE, PURPLE))
story.append(sp(2))
for b in [
"<b>Short PR interval</b> (< 0.12 s) – accessory pathway bypasses AV node",
"<b>Delta wave</b> – slurred upstroke of QRS; initial slow conduction via accessory pathway",
"<b>Wide QRS</b> (> 0.12 s) due to delta wave",
"<b>Secondary ST-T changes</b> (discordant to QRS)",
"<b>Risk:</b> AF with rapid ventricular response → VF if accessory pathway has short refractory period",
"<b>AVOID:</b> Adenosine, digoxin, verapamil, beta-blockers in pre-excited AF (can accelerate conduction)",
]:
story.append(bullet(b))
story.append(sp(3))
story.append(section_box("8.4 Long QT Syndromes", LIGHT_ORANGE, ORANGE))
story.append(sp(2))
lqt_data = [
["Type", "Gene", "Trigger", "T Wave Morphology"],
["LQT1 (KCNQ1)", "IKs loss of function", "Exercise / emotion", "Broad-based T wave"],
["LQT2 (KCNH2 / hERG)", "IKr loss of function", "Sudden sound / auditory startle", "Notched / bifid T wave"],
["LQT3 (SCN5A)", "INa gain of function", "Sleep / rest / bradycardia", "Long flat ST, late T wave"],
["Acquired (drugs, electrolytes)", "Multiple channels", "Drug initiation, hypokalaemia, hypomagnesaemia", "Variable QT prolongation"],
]
story.append(make_table(lqt_data[0], lqt_data[1:], [38*mm, 36*mm, 44*mm, 52*mm], ORANGE))
story.append(sp(2))
story.append(info_box(
"Common QT-prolonging drugs: antiarrhythmics (sotalol, amiodarone, quinidine), "
"antibiotics (azithromycin, fluoroquinolones), antipsychotics (haloperidol, quetiapine), "
"antidepressants (tricyclics, citalopram), antiemetics (ondansetron, metoclopramide).",
YELLOW_BG, ORANGE))
story.append(sp(3))
story.append(section_box("8.5 Early Repolarisation vs. STEMI", LIGHT_GREEN, GREEN))
story.append(sp(2))
er_data = [
["Feature", "Early Repolarisation", "STEMI"],
["ST Morphology", "Concave (smiley face) upward", "Convex (frowning face) or flat upward"],
["Distribution", "Inferior and lateral leads; often widespread", "Localised to specific territory"],
["Reciprocal Changes", "Absent", "Present (key differentiator)"],
["J-Point Notching", "Characteristic notch or slur at J-point", "Usually absent"],
["Evolution", "Stable; no dynamic change", "Dynamic: evolving over time"],
["ST:T ratio (V6)", "< 0.25", "≥ 0.25 suggests STEMI/pericarditis"],
["Clinical Context", "Young, athletic, asymptomatic (usually)", "Chest pain, troponin rise, symptoms"],
]
story.append(make_table(er_data[0], er_data[1:], [40*mm, 65*mm, 65*mm], GREEN))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 9 – Quick Reference & Differentials
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 9: Quick Reference & Clinical Differentials", NAVY))
story.append(sp(3))
story.append(section_box("ST Elevation Differential Diagnosis (STEMI Mimics)", LIGHT_ORANGE, ORANGE))
story.append(sp(2))
stemi_dd = [
["Cause", "Distinguishing ECG Feature", "Clinical Clue"],
["STEMI", "Convex ST elevation, reciprocal changes, evolution, Q waves", "Chest pain, troponin +ve"],
["Pericarditis", "Diffuse concave ST elevation, PR depression, no reciprocals", "Pleuritic pain, friction rub, viral prodrome"],
["Early Repolarisation", "Concave ST, J-point notch, stable, young", "Asymptomatic, athlete"],
["LBBB (new)", "Sgarbossa criteria; convex ST in concordant leads", "May need primary PCI if new LBBB + chest pain"],
["Brugada Pattern", "Coved ST in V1–V2 only, RBBB morphology", "Family history of sudden death, fever precipitates"],
["LVH Strain", "V5–V6 ST elevation possible; high voltage", "Hypertension, no acute symptoms"],
["Vasospasm / Prinzmetal", "Transient ST elevation, resolves with nitrates", "Occurs at rest, often at night"],
["Hyperkalaemia", "Wide QRS, peaked T, sine wave; may mimic STEMI", "Renal failure, peaked T waves universally"],
["Takotsubo (Stress) CMP", "Initial: ST elevation V1–V4; later: diffuse T inversion, QTc prolongation", "Emotional stress, post-menopausal women, apical ballooning"],
]
story.append(make_table(stemi_dd[0], stemi_dd[1:], [36*mm, 66*mm, 68*mm], ORANGE))
story.append(sp(3))
story.append(section_box("Wide Complex Tachycardia (WCT) Differential", LIGHT_PURPLE, PURPLE))
story.append(sp(2))
wct_data = [
["Criterion", "Favours VT", "Favours SVT with Aberrancy"],
["AV Dissociation", "Present (P waves unrelated to QRS)", "Absent"],
["Fusion Beats", "Present", "Absent"],
["Capture Beats", "Present", "Absent"],
["QRS Duration", "> 0.16 s", "Usually < 0.14 s"],
["Axis", "Extreme NW axis (−90° to ±180°)", "LAD possible (LBBB morphology)"],
["Onset", "Abrupt; no initiating PAC", "Often initiated by PAC; RP < PR"],
["V Lead Concordance", "All positive or all negative V1–V6", "Mixed"],
["RS Nadir to S", "> 100 ms in any V lead (Brugada sign)", "< 100 ms"],
["Prior ECG", "Sinus ECG different from WCT morphology", "WCT matches pre-existing BBB"],
["History", "Structural heart disease strongly favours VT", "No structural heart disease"],
]
story.append(make_table(wct_data[0], wct_data[1:], [40*mm, 70*mm, 60*mm], PURPLE))
story.append(sp(3))
story.append(section_box("Common Drug Effects on ECG", LIGHT_TEAL, TEAL))
story.append(sp(2))
drug_data = [
["Drug / Class", "ECG Effects", "Key Concern"],
["Digoxin (therapeutic)", "Scooping ST depression ('reverse tick'); short QT; PR prolongation; T inversion", "Toxicity: any arrhythmia; PAT with block classic"],
["Beta-Blockers", "Sinus bradycardia; PR prolongation; AV block", "Overdose: severe bradycardia, hypotension"],
["Calcium Channel Blockers (non-DHP)", "Sinus bradycardia; PR prolongation; AV block (verapamil > diltiazem)", "Overdose: fatal bradycardia; may cause AF"],
["Tricyclic Antidepressants (TCA)", "Wide QRS; prolonged QTc; right axis; deep S in I; tall R in aVR", "R:S ratio in aVR > 0.7 predicts seizure/arrhythmia"],
["Amiodarone", "Sinus bradycardia; prolonged PR, QRS, QTc; T wave changes; corneal microdeposits", "Pro-arrhythmic despite anti-arrhythmic classification"],
["Adenosine", "Transient AV block; brief asystole (1–3 s); sinus bradycardia", "Diagnostic/therapeutic in SVT; CI in WPW pre-excited AF"],
["Cocaine", "Sinus tachycardia; STEMI (coronary spasm); prolonged QTc; VT/VF", "Cocaine + beta-blocker = unopposed alpha → dangerous"],
]
story.append(make_table(drug_data[0], drug_data[1:], [38*mm, 72*mm, 60*mm], TEAL))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════
# CHAPTER 10 – Summary Cheatsheet
# ═══════════════════════════════════════════════════════
story.append(chapter_header("Chapter 10: Rapid ECG Cheatsheet", RED))
story.append(sp(3))
story.append(section_box("One-Line Summaries – High-Yield for Exams & Clinics", LIGHT_BLUE, NAVY))
story.append(sp(2))
cheat_data = [
["Finding", "Diagnosis / Condition"],
["Irregularly irregular + no P waves", "Atrial Fibrillation"],
["Sawtooth waves at 300 bpm + regular ventricular response", "Atrial Flutter (2:1 block → 150 bpm)"],
["P buried in QRS or pseudo-r' V1 + pseudo-s II (narrow, fast)", "AVNRT (commonest SVT)"],
["Short PR + delta wave + wide QRS", "WPW (pre-excitation)"],
["Wide QRS > 0.12 s + rSR' in V1 + wide S in I, V6", "Right Bundle Branch Block (RBBB)"],
["Wide QRS + broad notched R in I/V6 + no septal Q waves", "Left Bundle Branch Block (LBBB)"],
["ST elevation + concave + PR depression + no reciprocals", "Pericarditis"],
["ST elevation + convex + reciprocal changes + Q waves", "STEMI"],
["Horizontal ST depression + T inversion (no Q waves)", "NSTEMI / Subendocardial ischaemia"],
["Progressive PR lengthening → dropped QRS (grouped beating)", "2nd degree AV block – Mobitz I (Wenckebach)"],
["Constant PR + sudden dropped QRS (no warning)", "2nd degree AV block – Mobitz II"],
["P waves unrelated to QRS + escape rhythm", "3rd degree (complete) AV block"],
["Peaked T → wide QRS → sine wave + hyperkalaemia history", "Hyperkalaemia"],
["Prominent U waves + flat T (U > T)", "Hypokalaemia"],
["Short QT interval", "Hypercalcaemia (or congenital short QT syndrome)"],
["Prolonged QT + polymorphic VT twisting axis", "Torsades de Pointes (Long QT)"],
["Coved ST elevation V1–V2 + RBBB morphology", "Brugada Syndrome"],
["S1Q3T3 + tachycardia + right heart strain", "Pulmonary Embolism (PE)"],
["Osborn (J) wave at J point + bradycardia + prolonged intervals", "Hypothermia"],
["Sagging ST ('reverse tick') + short QT + PR prolongation", "Digoxin Effect (therapeutic)"],
["Sokolow-Lyon voltage ≥ 35 mm + strain pattern", "Left Ventricular Hypertrophy (LVH)"],
["R > S in V1 + RAD + RV strain pattern", "Right Ventricular Hypertrophy (RVH)"],
["Diffuse T wave inversion + QTc prolongation + apical ballooning", "Takotsubo (Stress) Cardiomyopathy"],
["Delta + wide QRS + AF with rapid irregular wide QRS", "WPW + AF → risk of VF"],
["Wide complex tachycardia + AV dissociation + fusion beats", "Ventricular Tachycardia (VT) – proven"],
]
story.append(make_table(cheat_data[0], cheat_data[1:],
[100*mm, 70*mm], DARK_RED, colors.HexColor("#FDEDEC")))
story.append(sp(3))
story.append(info_box(
"📌 GOLDEN RULE: In any wide complex tachycardia with haemodynamic instability → "
"treat as VT and cardiovert. Never delay treatment to achieve a diagnosis.",
colors.HexColor("#FDEDEC"), DARK_RED))
story.append(sp(4))
story.append(info_box(
"📚 Recommended Reading:\n"
"• The ECG Made Easy – John Hampton (beginner)\n"
"• The ECG in Practice – John Hampton (intermediate)\n"
"• 150 ECG Problems – Hampton (case-based practice)\n"
"• Marriott's Practical Electrocardiography (advanced)\n"
"• LITFL ECG Library – litfl.com/ecg-library (free online, disorder-by-disorder)",
LIGHT_TEAL, TEAL))
# ── Final page ──
story.append(sp(6))
story.append(hr(NAVY, 1))
footer_style = style("footer", fontName="Helvetica-Oblique", fontSize=8.5,
textColor=colors.HexColor("#555555"), alignment=TA_CENTER, leading=12)
story.append(Paragraph(
"This guide is intended as an educational reference only. "
"Always correlate ECG findings with clinical history, physical examination, and other investigations. "
"Orris Medical Reference | ECG Complete Guide | 2026",
footer_style))
# ── Build with page callbacks ──────────────────────────────────────────────
doc.build(story,
onFirstPage=cover_page,
onLaterPages=normal_page)
print(f"PDF created: {OUTPUT_PATH}")
print(f"File size: {os.path.getsize(OUTPUT_PATH):,} bytes")
if __name__ == "__main__":
build_pdf()
cd /tmp/workspace/ecg-guide && python create_ecg_pdf.py
ECG Complete Guide
PDF Document · PDF
| Chapter | Content |
|---|---|
| 1 - ECG Basics | Paper measurements, 12-lead views, 7-step systematic approach |
| 2 - Normal ECG | NSR criteria, waveform characteristics, axis interpretation |
| 3 - Arrhythmias | Sinus, supraventricular (AF, flutter, SVT, WPW), ventricular (VT, VF, TdP, Brugada) |
| 4 - Conduction Blocks | AV blocks (1st/2nd/3rd degree), RBBB, LBBB, hemiblocks, trifascicular |
| 5 - Ischaemia & MI | Hyper-acute to chronic stages, MI localisation by territory, Sgarbossa criteria |
| 6 - Hypertrophy | LVH, RVH, LAE, RAE with voltage criteria and strain patterns |
| 7 - Electrolytes & Metabolic | Hypo/hyperkalaemia, calcium, magnesium, thyroid, hypothermia, digoxin |
| 8 - Special Conditions | Pericarditis, PE, WPW, Long QT syndromes, early repolarisation vs STEMI |
| 9 - Quick Reference | STEMI mimics, WCT differential (VT vs SVT), drug effects on ECG |
| 10 - Cheatsheet | 25 one-line high-yield summaries for exams and clinical practice |