attachment
attachment

Give 10 heart disease with possible chages in ECG

Reading File
Searching the Web

10 heart diseases ECG changes classic findings

Finding Sources
Finding Sources
Finding Sources
Reading File
Now I have enough to deliver a thorough, well-sourced answer. Let me also look at the ECG image carefully and compile the full response.

10 Heart Diseases and Their ECG Changes

Based on Braunwald's Heart Disease, Harrison's Principles of Internal Medicine (2025), Tintinalli's Emergency Medicine, and Ganong's Review of Medical Physiology:

1. ST-Elevation Myocardial Infarction (STEMI)

PhaseECG Finding
Hyperacute (minutes)Tall, peaked (hyperacute) T waves
Acute (hours)ST-segment elevation ≥1 mm in 2+ contiguous leads; reciprocal ST depression in opposing leads
Evolving (hours-days)T-wave inversion
Old MIPathological Q waves (>1 mm wide, >25% of R-wave height)
  • Inferior MI (RCA): ST elevation in II, III, aVF
  • Anterior MI (LAD): ST elevation in V1-V4
  • Lateral MI (LCx): ST elevation in I, aVL, V5-V6

2. Non-ST-Elevation MI (NSTEMI) / Unstable Angina

  • ST depression (horizontal or downsloping) in affected leads
  • T-wave inversion (deep, symmetrical)
  • No pathological Q waves
  • Biomarker elevation distinguishes NSTEMI from unstable angina (ECG alone cannot)

3. Left Ventricular Hypertrophy (LVH)

  • Sokolow-Lyon criteria: S in V1 + R in V5 or V6 > 35 mm
  • Cornell criteria: R in aVL + S in V3 > 28 mm (men) / >20 mm (women)
  • Left axis deviation
  • ST depression and T-wave inversion in lateral leads (V5-V6, I, aVL) - the "strain pattern"
  • Broad, notched P waves (P mitrale) if left atrial enlargement coexists

4. Right Ventricular Hypertrophy (RVH)

  • Right axis deviation (>+110°)
  • Tall R wave in V1 (R > S in V1)
  • Deep S waves in V5-V6
  • T-wave inversion in V1-V3 (right ventricular strain)
  • P pulmonale: tall, peaked P waves >2.5 mm in lead II

5. Atrial Fibrillation (AF)

  • Absent P waves replaced by irregular fibrillatory baseline (f waves, 350-600/min)
  • Irregularly irregular R-R intervals
  • No distinct P waves before QRS complexes
  • Normal QRS morphology (unless aberrant conduction)

6. Wolff-Parkinson-White Syndrome (WPW)

  • Short PR interval (<120 ms)
  • Delta wave (slurred upstroke of QRS)
  • Wide QRS complex (>120 ms)
  • Secondary ST-T changes (discordant to delta wave)
  • Risk: can rapidly conduct AF to ventricles (dangerous)

7. Complete Heart Block (3rd Degree AV Block)

  • P waves and QRS complexes completely dissociated (no relationship between P and QRS)
  • Atrial rate is normal (60-100 bpm); ventricular rate is slow (20-60 bpm)
  • Escape rhythm: junctional (narrow QRS, 40-60 bpm) or ventricular (wide QRS, 20-40 bpm)
  • Regular P-P intervals and regular R-R intervals - but independent of each other

8. Hypertrophic Cardiomyopathy (HCM)

  • LVH voltage criteria (most common finding)
  • Deep, narrow Q waves in lateral leads (I, aVL, V5-V6) and inferior leads - "dagger Q waves"
  • Giant negative T waves in precordial leads (especially apical variant - Yamaguchi syndrome)
  • Left axis deviation
  • Shortened PR interval (in some)
  • AF is common in advanced disease

9. Pericarditis (Acute)

ECG evolves in 4 classic stages:
StageTimingFinding
IDay 1-2Diffuse saddle-shaped ST elevation in most leads (except aVR, V1); PR depression
IIDays 3-7ST normalizes; T waves flatten
III1-3 weeksT-wave inversion (diffuse)
IVWeeks-monthsECG normalizes
Key differentiator from MI: PR depression is highly specific; ST elevation is diffuse (not territorial), concave (saddle-shaped).

10. Dilated Cardiomyopathy (DCM)

  • Left bundle branch block (LBBB) is very common
  • Poor R-wave progression in precordial leads (V1-V4)
  • Pathological Q waves (mimicking old MI - "pseudo-infarct" pattern)
  • Left axis deviation
  • Non-specific ST-T wave changes
  • P mitrale (left atrial enlargement)
  • Frequent ventricular ectopy and ventricular tachycardia on monitoring

Analysis of the Attached ECG

Looking at the ECG strip you've shared (patient appears to be female, "F", from the header):
Visible findings:
  • The machine interpretation at the bottom right appears to read: "Left Ventricular Involvement" and references ST changes
  • The printed computer interpretation notes mention QTCD and Left Ventricular Abnormality
  • Rhythm appears to be sinus rhythm (regular P waves visible)
  • There appear to be prominent S waves in right precordial leads (V1-V2) and tall R waves in lateral leads (V5-V6) consistent with LVH
  • The ST-T segment changes visible in lateral leads may represent LV strain pattern
Note: This is a preliminary visual impression only. The resolution and orientation of the ECG makes definitive interpretation difficult. A qualified physician should review the original tracing with full clinical context. The machine-generated interpretation at the bottom of the strip should be confirmed by a clinician.

Summary Table:
#DiseaseKey ECG Feature
1STEMIST elevation + Q waves
2NSTEMI/UAST depression + T-wave inversion
3LVHHigh voltage + strain pattern
4RVHR>S in V1 + right axis deviation
5Atrial FibrillationNo P waves + irregular rhythm
6WPWDelta wave + short PR
7Complete Heart BlockP-QRS dissociation
8HCMDagger Q waves + giant T inversions
9PericarditisDiffuse saddle ST + PR depression
10DCMLBBB + pseudo-infarct Q waves
Sources: Braunwald's Heart Disease; Harrison's Principles of Internal Medicine 22E (2025); Tintinalli's Emergency Medicine; Ganong's Review of Medical Physiology

Create a quick-reference PDF guide to ECG patterns in heart disease

Reading File
Reading File
Running Command

ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"

Running Command

mkdir -p /home/daytona/workspace/ecg-guide && python3 -c "import reportlab; prin…

Writing File

~/ecg-guide/build_ecg_guide.py

"""
ECG Quick-Reference Guide PDF Builder
Uses reportlab to produce a professional multi-page PDF
with colour-coded sections, ECG waveform diagrams, and tables.
"""

import math
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    PageBreak, HRFlowable, KeepTogether
)
from reportlab.platypus.flowables import Flowable
from reportlab.pdfgen import canvas as pdfcanvas

# ──────────────────────────────────────────────────────────────────────────────
# COLOUR PALETTE
# ──────────────────────────────────────────────────────────────────────────────
RED      = colors.HexColor("#C0392B")
ORANGE   = colors.HexColor("#E67E22")
BLUE     = colors.HexColor("#1A5276")
TEAL     = colors.HexColor("#117A65")
PURPLE   = colors.HexColor("#6C3483")
DARK     = colors.HexColor("#1C2833")
MID_GREY = colors.HexColor("#7F8C8D")
LIGHT_BG = colors.HexColor("#F8F9FA")
PALE_RED = colors.HexColor("#FADBD8")
PALE_BLU = colors.HexColor("#D6EAF8")
PALE_GRN = colors.HexColor("#D5F5E3")
PALE_ORG = colors.HexColor("#FDEBD0")
PALE_PUR = colors.HexColor("#E8DAEF")
PALE_TEA = colors.HexColor("#D0ECE7")
ECG_GRN  = colors.HexColor("#27AE60")
GRID_COL = colors.HexColor("#FADBD8")   # faint pink ECG grid
TRACE    = colors.HexColor("#C0392B")   # red ECG trace

W, H = A4   # 595.27 x 841.89 pts

# ──────────────────────────────────────────────────────────────────────────────
# ECG WAVEFORM FLOWABLE  – draws a stylised trace with a grid background
# ──────────────────────────────────────────────────────────────────────────────
class ECGWaveform(Flowable):
    """
    Draw a schematic ECG waveform for a given condition.
    wave_type controls which morphology to render.
    """
    TYPES = {
        "normal":      "normal",
        "stemi":       "stemi",
        "nstemi":      "nstemi",
        "lvh":         "lvh",
        "rvh":         "rvh",
        "afib":        "afib",
        "wpw":         "wpw",
        "chb":         "chb",
        "hcm":         "hcm",
        "pericarditis":"pericarditis",
        "dcm":         "dcm",
    }

    def __init__(self, wave_type="normal", width=420, height=80, label=None):
        super().__init__()
        self.wave_type = wave_type
        self.width  = width
        self.height = height
        self.label  = label

    def wrap(self, *args):
        return self.width, self.height + 4

    def draw(self):
        c = self._canvas
        w, h = self.width, self.height

        # --- grid background ---
        c.saveState()
        c.setFillColor(colors.HexColor("#FFF5F5"))
        c.rect(0, 0, w, h, fill=1, stroke=0)

        # minor grid (1 mm equivalent = 2.83 pt → use 5pt squares)
        sq = 5
        c.setStrokeColor(colors.HexColor("#FFCCCC"))
        c.setLineWidth(0.3)
        x = sq
        while x < w:
            c.line(x, 0, x, h)
            x += sq
        y = sq
        while y < h:
            c.line(0, y, w, y)
            y += sq

        # major grid (every 5 squares = 25pt)
        c.setStrokeColor(colors.HexColor("#FF9999"))
        c.setLineWidth(0.6)
        x = 25
        while x < w:
            c.line(x, 0, x, h)
            x += 25
        y = 25
        while y < h:
            c.line(0, y, w, y)
            y += 25

        c.restoreState()

        # --- trace ---
        baseline = h * 0.38
        c.setStrokeColor(TRACE)
        c.setLineWidth(1.4)
        c.setLineCap(1)

        path = c.beginPath()

        def pt(x, y):
            return x, baseline + y

        wt = self.wave_type

        # We'll draw 2–3 beats depending on wave type
        if wt == "normal":
            beats = self._normal_beat()
            self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)

        elif wt == "stemi":
            beats = self._stemi_beat()
            self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)

        elif wt == "nstemi":
            beats = self._nstemi_beat()
            self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)

        elif wt == "lvh":
            beats = self._lvh_beat()
            self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)

        elif wt == "rvh":
            beats = self._rvh_beat()
            self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)

        elif wt == "afib":
            self._draw_afib(c, w, baseline)
            return

        elif wt == "wpw":
            beats = self._wpw_beat()
            self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)

        elif wt == "chb":
            self._draw_chb(c, w, baseline, h)
            return

        elif wt == "hcm":
            beats = self._hcm_beat()
            self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)

        elif wt == "pericarditis":
            beats = self._pericarditis_beat()
            self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)

        elif wt == "dcm":
            beats = self._dcm_beat()
            self._draw_repeated(c, path, beats, w, baseline, repeats=2, gap=15)

        c.drawPath(path, stroke=1, fill=0)

    # ── helper: draw a sequence of (dx, dy) segments repeatedly ──────────────
    def _draw_repeated(self, c, path, segments, total_w, baseline, repeats=3, gap=10):
        beat_w = sum(abs(dx) for dx, dy in segments)
        spacing = (total_w - gap * 2) / repeats
        x = gap
        first = True
        for _ in range(repeats):
            cx = x
            if first:
                path.moveTo(cx, baseline)
                first = False
            else:
                path.lineTo(cx, baseline)
            for dx, dy in segments:
                cx += dx
                path.lineTo(cx, baseline + dy)
            x += spacing

    # ── NORMAL beat ──────────────────────────────────────────────────────────
    def _normal_beat(self):
        # P-Q-R-S-T
        return [
            (8,  0),    # flat → P start
            (4,  6),    # P up
            (4,  0),    # P down → isoelectric
            (5,  0),    # PR segment
            (2, -4),    # Q
            (3, 28),    # R up
            (3,-28),    # S down
            (2,  4),    # return to base
            (6,  0),    # ST segment
            (5,  8),    # T up
            (6,  0),    # T peak
            (5, -8),    # T down
            (15, 0),    # TP interval
        ]

    # ── STEMI beat ────────────────────────────────────────────────────────────
    def _stemi_beat(self):
        return [
            (8,  0),
            (4,  5),    # P
            (4,  0),
            (4,  0),    # PR
            (2, -4),    # Q
            (3, 28),    # R
            (3,-22),    # S (partial return – ST elevated)
            (2,  8),    # ST elevation slope up
            (8, 12),    # ST elevation plateau
            (5,  0),
            (5, -4),    # T beginning
            (6,  6),    # T peak
            (5, -12),   # T down to elevated baseline
            (2, -8),    # return baseline
            (10, 0),    # TP
        ]

    # ── NSTEMI beat ───────────────────────────────────────────────────────────
    def _nstemi_beat(self):
        return [
            (8,  0),
            (4,  5),
            (4,  0),
            (4,  0),
            (2, -4),
            (3, 26),
            (3,-26),
            (2,  4),
            (8, -8),    # ST depression
            (5, -5),
            (5, -8),    # inverted T
            (6,  0),
            (5,  8),
            (2, 13),    # return to baseline
            (10, 0),
        ]

    # ── LVH beat ──────────────────────────────────────────────────────────────
    def _lvh_beat(self):
        return [
            (8,  0),
            (5,  7),    # prominent P
            (5,  0),
            (4,  0),
            (2, -5),
            (3, 38),    # very tall R
            (3,-38),
            (2,  5),
            (6,  0),
            (4, -6),    # ST depression (strain)
            (6, -8),    # inverted T
            (6,  0),
            (5,  14),
            (12, 0),
        ]

    # ── RVH beat ──────────────────────────────────────────────────────────────
    def _rvh_beat(self):
        return [
            (8,  0),
            (4,  6),
            (4,  0),
            (4,  0),
            (2,  0),    # no Q
            (3, 22),    # R in V1 tall
            (3, -5),    # S small (R>S)
            (3,-17),
            (2,  0),
            (6,  0),
            (5, -7),    # T inversion (strain)
            (6,  0),
            (5,  7),
            (12, 0),
        ]

    # ── WPW beat ──────────────────────────────────────────────────────────────
    def _wpw_beat(self):
        return [
            (4,  0),
            (4,  5),    # P
            (2,  0),    # short PR
            (4,  4),    # delta wave slurred upstroke
            (2, 22),    # R
            (3,-26),    # S
            (2,  0),
            (8,  0),
            (5,  7),
            (5,  0),
            (5, -7),
            (10, 0),
        ]

    # ── HCM beat ──────────────────────────────────────────────────────────────
    def _hcm_beat(self):
        return [
            (8,  0),
            (4,  6),
            (4,  0),
            (4,  0),
            (3,-10),    # deep narrow dagger Q
            (2, 10),
            (2, 28),    # tall R
            (3,-28),
            (2,  0),
            (6,  0),
            (4, -12),   # giant negative T
            (6,-10),
            (6,  0),
            (5,  22),
            (10, 0),
        ]

    # ── Pericarditis beat ─────────────────────────────────────────────────────
    def _pericarditis_beat(self):
        return [
            (4,  0),
            (3,  5),    # P
            (3,  0),
            (2, -2),    # PR depression (below baseline)
            (4, -4),    # PR depression segment
            (2, -3),
            (2,  3),    # Q minimal
            (3, 22),
            (3,-22),
            (2,  3),
            (2,  3),    # slight ST elevation (diffuse, concave)
            (6,  8),    # saddle-shaped ST elevation
            (4,  5),
            (4, -3),
            (5, -8),
            (2, -8),    # back to baseline
            (10, 0),
        ]

    # ── DCM beat ──────────────────────────────────────────────────────────────
    def _dcm_beat(self):
        # LBBB morphology: wide, notched QRS, no Q in lateral
        return [
            (8,  0),
            (5,  6),
            (5,  0),
            (5,  0),    # long PR (prolonged conduction)
            (2,  0),    # no Q
            (4, 14),    # initial slurred upstroke (LBBB)
            (3,  8),    # notch
            (3, 10),    # R peak – wide
            (4,-32),    # S descent
            (3,  0),
            (8,  0),    # wide ST
            (5, -7),    # discordant T
            (6,  0),
            (4,  7),
            (15, 0),
        ]

    # ── AF: irregular, no P, fibrillatory baseline ───────────────────────────
    def _draw_afib(self, c, w, baseline):
        import random
        random.seed(42)
        path = c.beginPath()
        x = 5
        path.moveTo(x, baseline)

        # fibrillatory baseline with irregular QRS complexes
        beats_x = [55, 115, 155, 220, 265, 340, 380]
        bx_set = set(beats_x)
        beat_idx = 0

        while x < w - 5:
            if beat_idx < len(beats_x) and x >= beats_x[beat_idx] - 2:
                # QRS complex
                path.lineTo(x,     baseline)
                path.lineTo(x+2,   baseline - 4)
                path.lineTo(x+3,   baseline + 26)
                path.lineTo(x+5,   baseline - 26)
                path.lineTo(x+7,   baseline + 4)
                path.lineTo(x+8,   baseline)
                x += 8
                beat_idx += 1
            else:
                # fibrillatory f-waves
                fib_y = baseline + random.uniform(-3, 3)
                path.lineTo(x, fib_y)
                x += random.uniform(1.5, 3)

        path.lineTo(x, baseline)
        c.drawPath(path, stroke=1, fill=0)

    # ── Complete Heart Block: independent P and slow QRS ─────────────────────
    def _draw_chb(self, c, w, baseline, h):
        # P waves at ~70 bpm (fast), QRS at ~35 bpm (slow escape)
        p_interval   = 52   # pts between P waves
        qrs_interval = 105  # pts between QRS complexes

        path = c.beginPath()
        path.moveTo(5, baseline)

        # flat line first
        path.lineTo(w - 5, baseline)
        c.drawPath(path, stroke=1, fill=0)

        # P waves (atrial, regular fast)
        x = 18
        c.setStrokeColor(colors.HexColor("#2980B9"))
        c.setLineWidth(1.2)
        while x + 8 < w:
            p = c.beginPath()
            p.moveTo(x,   baseline)
            p.lineTo(x+3, baseline + 5)
            p.lineTo(x+6, baseline)
            c.drawPath(p, stroke=1, fill=0)
            x += p_interval

        # QRS complexes (ventricular escape, regular slow, independent)
        c.setStrokeColor(TRACE)
        c.setLineWidth(1.6)
        x = 45
        while x + 10 < w:
            q = c.beginPath()
            q.moveTo(x,   baseline)
            q.lineTo(x+2, baseline - 4)
            q.lineTo(x+3, baseline + 24)
            q.lineTo(x+5, baseline - 22)
            q.lineTo(x+7, baseline + 3)
            q.lineTo(x+8, baseline)
            # ST-T
            q.lineTo(x+12, baseline)
            q.lineTo(x+16, baseline + 7)
            q.lineTo(x+19, baseline)
            c.drawPath(q, stroke=1, fill=0)
            x += qrs_interval

        # legend note
        c.setFont("Helvetica-Oblique", 6.5)
        c.setFillColor(colors.HexColor("#2980B9"))
        c.drawString(5, h - 10, "P (atrial, regular)")
        c.setFillColor(TRACE)
        c.drawString(5, h - 20, "QRS (ventricular escape, regular, independent)")


# ──────────────────────────────────────────────────────────────────────────────
# COLOUR BAR FLOWABLE
# ──────────────────────────────────────────────────────────────────────────────
class ColourBar(Flowable):
    def __init__(self, colour, width=None, height=6):
        super().__init__()
        self._colour = colour
        self._width  = width or (W - 4*cm)
        self._height = height

    def wrap(self, *args):
        return self._width, self._height

    def draw(self):
        c = self._canvas
        c.setFillColor(self._colour)
        c.rect(0, 0, self._width, self._height, fill=1, stroke=0)


# ──────────────────────────────────────────────────────────────────────────────
# PAGE TEMPLATES  – header / footer via onPage callbacks
# ──────────────────────────────────────────────────────────────────────────────
def on_first_page(canvas, doc):
    pass   # cover page handled by story content

def on_later_pages(canvas, doc):
    canvas.saveState()
    # header bar
    canvas.setFillColor(BLUE)
    canvas.rect(0, H - 28, W, 28, fill=1, stroke=0)
    canvas.setFillColor(colors.white)
    canvas.setFont("Helvetica-Bold", 9)
    canvas.drawString(1.5*cm, H - 18, "ECG QUICK-REFERENCE GUIDE  |  Heart Disease Patterns")
    canvas.setFont("Helvetica", 8)
    canvas.drawRightString(W - 1.5*cm, H - 18, f"Page {doc.page}")

    # footer
    canvas.setFillColor(MID_GREY)
    canvas.setFont("Helvetica-Oblique", 7)
    canvas.drawString(1.5*cm, 12, "For clinical education only. Always correlate with patient history and full clinical assessment.")
    canvas.drawRightString(W - 1.5*cm, 12, "Sources: Braunwald's Heart Disease | Harrison's Principles | Tintinalli's EM")
    canvas.restoreState()


# ──────────────────────────────────────────────────────────────────────────────
# ECG CONDITION DATA
# ──────────────────────────────────────────────────────────────────────────────
CONDITIONS = [
    {
        "num": 1,
        "name": "ST-Elevation Myocardial Infarction (STEMI)",
        "colour": RED,
        "bg": PALE_RED,
        "wave": "stemi",
        "category": "Ischaemic",
        "urgency": "EMERGENCY",
        "urgency_col": RED,
        "key_findings": [
            ("ST-segment elevation", ">=1 mm in >=2 contiguous leads (>=2 mm in V1-V3)"),
            ("Hyperacute T waves", "Tall peaked T waves - earliest sign (minutes)"),
            ("Reciprocal ST depression", "In leads electrically opposite to infarct territory"),
            ("Pathological Q waves", ">40 ms wide or >25% R-wave height - evolve over hours"),
            ("T-wave inversion", "Develops hours to days after onset"),
        ],
        "territories": [
            ("Inferior (RCA)", "ST elevation in II, III, aVF"),
            ("Anterior (LAD)", "ST elevation in V1-V4"),
            ("Lateral (LCx)", "ST elevation in I, aVL, V5-V6"),
            ("Posterior (RCA/LCx)", "Tall R, ST depression V1-V3 (mirror image)"),
        ],
        "pearls": "ST elevation in aVR > V1 plus ST depression in >=7 leads suggests left main occlusion.",
    },
    {
        "num": 2,
        "name": "NSTEMI / Unstable Angina",
        "colour": ORANGE,
        "bg": PALE_ORG,
        "wave": "nstemi",
        "category": "Ischaemic",
        "urgency": "URGENT",
        "urgency_col": ORANGE,
        "key_findings": [
            ("ST depression", "Horizontal or downsloping >=0.5 mm; most specific for ischaemia"),
            ("T-wave inversion", "Deep, symmetrical; Wellens pattern in LAD disease"),
            ("No Q waves", "No pathological Q waves (differentiates from STEMI evolution)"),
            ("Transient ST changes", "May normalise between episodes (dynamic ischaemia)"),
            ("Normal ECG possible", "Up to 5% of NSTEMI have a normal presenting ECG"),
        ],
        "territories": [
            ("Wellens Type A", "Biphasic T waves V2-V3 - proximal LAD disease"),
            ("Wellens Type B", "Deep symmetrical T inversion V2-V3 - proximal LAD disease"),
            ("De Winter T waves", "Upsloping ST depression + tall T waves V1-V6 (LAD equivalent)"),
        ],
        "pearls": "Biomarker elevation (troponin) distinguishes NSTEMI from unstable angina - ECG cannot.",
    },
    {
        "num": 3,
        "name": "Left Ventricular Hypertrophy (LVH)",
        "colour": BLUE,
        "bg": PALE_BLU,
        "wave": "lvh",
        "category": "Structural",
        "urgency": "MONITOR",
        "urgency_col": BLUE,
        "key_findings": [
            ("Sokolow-Lyon", "S(V1) + R(V5 or V6) > 35 mm"),
            ("Cornell criteria", "R(aVL) + S(V3) > 28 mm (men) / >20 mm (women)"),
            ("Left axis deviation", "QRS axis -30 degrees to -90 degrees"),
            ("Strain pattern", "ST depression + asymmetric T inversion in I, aVL, V5-V6"),
            ("P mitrale", "Broad notched P wave >120 ms in II if LA enlargement"),
        ],
        "territories": [
            ("Lateral leads", "ST depression and T-wave inversion (strain)"),
            ("Precordial", "Voltage criteria best seen V1-V6"),
        ],
        "pearls": "LVH on ECG is specific but insensitive (~50%). Echo is gold standard for LV mass.",
    },
    {
        "num": 4,
        "name": "Right Ventricular Hypertrophy (RVH)",
        "colour": TEAL,
        "bg": PALE_TEA,
        "wave": "rvh",
        "category": "Structural",
        "urgency": "MONITOR",
        "urgency_col": TEAL,
        "key_findings": [
            ("Right axis deviation", "QRS axis > +110 degrees"),
            ("Tall R in V1", "R > S in V1; R wave >7 mm in V1"),
            ("Deep S in V5-V6", "S wave >7 mm in V5-V6"),
            ("RV strain pattern", "T-wave inversion V1-V3; sometimes V4"),
            ("P pulmonale", "Peaked P waves >2.5 mm in lead II (RA enlargement)"),
        ],
        "territories": [
            ("Right precordial", "V1-V3: tall R, T inversion"),
            ("Inferior leads", "Peaked P waves in II, III, aVF"),
        ],
        "pearls": "Causes: pulmonary hypertension, PE, mitral stenosis, COPD. S1Q3T3 pattern in acute PE.",
    },
    {
        "num": 5,
        "name": "Atrial Fibrillation (AF)",
        "colour": PURPLE,
        "bg": PALE_PUR,
        "wave": "afib",
        "category": "Arrhythmia",
        "urgency": "URGENT",
        "urgency_col": ORANGE,
        "key_findings": [
            ("Absent P waves", "Replaced by irregular fibrillatory f-waves (350-600/min)"),
            ("Irregularly irregular RR", "No pattern to QRS intervals - hallmark finding"),
            ("Normal QRS", "Narrow QRS unless aberrant conduction or pre-excitation"),
            ("Variable ventricular rate", "Rate depends on AV nodal conduction"),
            ("Fine vs coarse AF", "Coarse f-waves often from valvular disease"),
        ],
        "territories": [
            ("All leads", "Absent discrete P waves; chaotic baseline"),
            ("AF + WPW", "Wide bizarre QRS - life-threatening - avoid AV nodal drugs"),
        ],
        "pearls": "AF with pre-excitation (WPW): avoid adenosine/verapamil/digoxin - risk of VF.",
    },
    {
        "num": 6,
        "name": "Wolff-Parkinson-White Syndrome (WPW)",
        "colour": colors.HexColor("#8E44AD"),
        "bg": PALE_PUR,
        "wave": "wpw",
        "category": "Conduction",
        "urgency": "MONITOR/URGENT",
        "urgency_col": ORANGE,
        "key_findings": [
            ("Short PR interval", "< 120 ms - early ventricular activation via accessory pathway"),
            ("Delta wave", "Slurred initial QRS upstroke - pre-excitation of ventricle"),
            ("Widened QRS", "> 120 ms total QRS duration"),
            ("Discordant ST-T", "Secondary repolarisation changes opposite to delta wave"),
            ("Pseudo-infarct patterns", "Delta waves can simulate Q waves - 'pseudo-infarct'"),
        ],
        "territories": [
            ("Type A (posterior)", "Positive delta wave V1 - left-sided accessory pathway"),
            ("Type B (anterior)", "Negative delta wave V1 - right-sided accessory pathway"),
        ],
        "pearls": "Risk of sudden death via AF conducting rapidly through accessory pathway to ventricles (rate >250 bpm).",
    },
    {
        "num": 7,
        "name": "Complete (3rd Degree) Heart Block",
        "colour": colors.HexColor("#1A5276"),
        "bg": PALE_BLU,
        "wave": "chb",
        "category": "Conduction",
        "urgency": "EMERGENCY",
        "urgency_col": RED,
        "key_findings": [
            ("AV dissociation", "P waves and QRS complexes completely independent"),
            ("Regular P waves", "Atrial rate normal 60-100 bpm; regular P-P intervals"),
            ("Slow escape rhythm", "Ventricular rate 20-60 bpm; regular R-R intervals"),
            ("Junctional escape", "Narrow QRS at 40-60 bpm if block at AV node"),
            ("Ventricular escape", "Wide QRS at 20-40 bpm if block below His bundle"),
        ],
        "territories": [
            ("Junctional escape", "Narrow QRS, rate 40-60 - block at AV node level"),
            ("Ventricular escape", "Wide QRS, rate 20-40 - infranodal block (worse prognosis)"),
        ],
        "pearls": "Never treat by speeding up the atrial rate - treat the ventricular rate. Transvenous pacing is definitive.",
    },
    {
        "num": 8,
        "name": "Hypertrophic Cardiomyopathy (HCM)",
        "colour": colors.HexColor("#1E8449"),
        "bg": PALE_GRN,
        "wave": "hcm",
        "category": "Structural",
        "urgency": "MONITOR",
        "urgency_col": TEAL,
        "key_findings": [
            ("LVH voltage", "Most common finding - tall R waves with voltage criteria met"),
            ("Dagger Q waves", "Deep narrow Q waves in I, aVL, V5-V6 and inferior leads"),
            ("Giant T inversions", "Massive precordial T-wave inversion (apical HCM/Yamaguchi)"),
            ("Left axis deviation", "Common; may have left anterior fascicular block"),
            ("Short PR interval", "Occasionally; AF is a major complication in 20% patients"),
        ],
        "territories": [
            ("Classic HCM", "LVH + dagger Q waves in lateral/inferior leads"),
            ("Apical HCM", "Giant negative T waves V3-V5 (>10 mm), minimal Q waves"),
        ],
        "pearls": "ECG abnormality may precede structural changes by years. Dagger Q waves are septal depolarisation, not infarction.",
    },
    {
        "num": 9,
        "name": "Acute Pericarditis",
        "colour": colors.HexColor("#BA4A00"),
        "bg": PALE_ORG,
        "wave": "pericarditis",
        "category": "Inflammatory",
        "urgency": "URGENT",
        "urgency_col": ORANGE,
        "key_findings": [
            ("Stage I - PR depression", "Downsloping PR in most leads except aVR - highly specific"),
            ("Stage I - ST elevation", "Diffuse saddle-shaped (concave up) ST elevation; all territories"),
            ("Stage II", "ST normalises; T waves flatten (days 3-7)"),
            ("Stage III", "Diffuse T-wave inversion (weeks 1-3)"),
            ("Stage IV", "ECG returns to normal (weeks-months)"),
        ],
        "territories": [
            ("Diffuse (all leads)", "ST elevation in I, II, III, aVL, aVF, V2-V6 simultaneously"),
            ("aVR reciprocal", "ST depression in aVR and sometimes V1"),
        ],
        "pearls": "Key differentiators from STEMI: PR depression, saddle shape, diffuse (not territorial), no reciprocal changes (except aVR).",
    },
    {
        "num": 10,
        "name": "Dilated Cardiomyopathy (DCM)",
        "colour": colors.HexColor("#515A5A"),
        "bg": LIGHT_BG,
        "wave": "dcm",
        "category": "Structural",
        "urgency": "MONITOR",
        "urgency_col": TEAL,
        "key_findings": [
            ("LBBB", "Left bundle branch block: QRS >120 ms, broad notched R in I/V6, rS in V1"),
            ("Poor R progression", "Loss of R waves V1-V4 - pseudo-anterior MI pattern"),
            ("Pseudo-infarct Q waves", "Pathological Q waves despite no ischaemic history"),
            ("Left axis deviation", "Common; may have LAFB"),
            ("P mitrale + LVH", "Left atrial and ventricular enlargement pattern"),
        ],
        "territories": [
            ("Lateral leads", "LBBB pattern with broad notched R in I, aVL, V5-V6"),
            ("Right precordial", "rS or QS pattern in V1-V3 (pseudo-infarct)"),
        ],
        "pearls": "LBBB of new onset in heart failure carries poor prognosis and is an indication for CRT (cardiac resynchronisation therapy).",
    },
]

# ──────────────────────────────────────────────────────────────────────────────
# STYLES
# ──────────────────────────────────────────────────────────────────────────────
def build_styles():
    base = getSampleStyleSheet()

    styles = {
        "cover_title": ParagraphStyle(
            "cover_title", parent=base["Title"],
            fontSize=32, leading=38,
            textColor=colors.white, alignment=TA_CENTER,
            fontName="Helvetica-Bold",
        ),
        "cover_sub": ParagraphStyle(
            "cover_sub", parent=base["Normal"],
            fontSize=13, leading=18,
            textColor=colors.HexColor("#AED6F1"), alignment=TA_CENTER,
            fontName="Helvetica",
        ),
        "cover_note": ParagraphStyle(
            "cover_note", parent=base["Normal"],
            fontSize=9, leading=13,
            textColor=colors.HexColor("#85929E"), alignment=TA_CENTER,
        ),
        "section_num": ParagraphStyle(
            "section_num", parent=base["Normal"],
            fontSize=28, leading=30,
            textColor=colors.white, alignment=TA_CENTER,
            fontName="Helvetica-Bold",
        ),
        "section_title": ParagraphStyle(
            "section_title", parent=base["Heading1"],
            fontSize=15, leading=18,
            textColor=colors.white,
            fontName="Helvetica-Bold",
            spaceAfter=0,
        ),
        "section_cat": ParagraphStyle(
            "section_cat", parent=base["Normal"],
            fontSize=8.5, leading=11,
            textColor=colors.HexColor("#AED6F1"),
            fontName="Helvetica",
        ),
        "body": ParagraphStyle(
            "body", parent=base["Normal"],
            fontSize=9, leading=13,
            textColor=DARK, alignment=TA_LEFT,
        ),
        "body_bold": ParagraphStyle(
            "body_bold", parent=base["Normal"],
            fontSize=9, leading=13,
            textColor=DARK, fontName="Helvetica-Bold",
        ),
        "pearl": ParagraphStyle(
            "pearl", parent=base["Normal"],
            fontSize=8.5, leading=12,
            textColor=colors.HexColor("#1A5276"),
            fontName="Helvetica-Oblique",
            leftIndent=6,
        ),
        "toc_title": ParagraphStyle(
            "toc_title", parent=base["Normal"],
            fontSize=11, leading=14,
            textColor=DARK, fontName="Helvetica-Bold",
            spaceAfter=2,
        ),
        "toc_entry": ParagraphStyle(
            "toc_entry", parent=base["Normal"],
            fontSize=9.5, leading=15,
            textColor=DARK,
        ),
        "wave_label": ParagraphStyle(
            "wave_label", parent=base["Normal"],
            fontSize=7.5, leading=10,
            textColor=MID_GREY, alignment=TA_CENTER,
            fontName="Helvetica-Oblique",
        ),
        "summary_head": ParagraphStyle(
            "summary_head", parent=base["Normal"],
            fontSize=10, leading=13,
            textColor=colors.white, fontName="Helvetica-Bold",
            alignment=TA_CENTER,
        ),
    }
    return styles


# ──────────────────────────────────────────────────────────────────────────────
# BUILD STORY
# ──────────────────────────────────────────────────────────────────────────────
def build_story(styles):
    story = []
    COL_W = W - 4*cm   # usable width

    # ── COVER PAGE ────────────────────────────────────────────────────────────
    story.append(Spacer(1, 1.8*cm))

    # Big header block
    cover_data = [[
        Paragraph("ECG QUICK-REFERENCE GUIDE", styles["cover_title"]),
    ]]
    cover_tbl = Table(cover_data, colWidths=[COL_W])
    cover_tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), BLUE),
        ("TOPPADDING",    (0,0),(-1,-1), 28),
        ("BOTTOMPADDING", (0,0),(-1,-1), 28),
        ("LEFTPADDING",   (0,0),(-1,-1), 20),
        ("RIGHTPADDING",  (0,0),(-1,-1), 20),
        ("ROUNDEDCORNERS", [8]),
    ]))
    story.append(cover_tbl)
    story.append(Spacer(1, 0.5*cm))

    story.append(Paragraph(
        "10 Heart Diseases &amp; Their Classic ECG Patterns",
        styles["cover_sub"]
    ))
    story.append(Spacer(1, 0.3*cm))
    story.append(HRFlowable(width=COL_W, color=BLUE, thickness=1.5))
    story.append(Spacer(1, 0.4*cm))

    # Mini overview waveform strip on cover
    story.append(ECGWaveform("stemi", width=COL_W, height=70))
    story.append(Paragraph("Schematic ECG - STEMI pattern shown", styles["wave_label"]))
    story.append(Spacer(1, 0.5*cm))

    # Category summary boxes
    cat_data = [
        [Paragraph("ISCHAEMIC", styles["summary_head"]),
         Paragraph("ARRHYTHMIA", styles["summary_head"]),
         Paragraph("CONDUCTION", styles["summary_head"]),
         Paragraph("STRUCTURAL", styles["summary_head"]),
         Paragraph("INFLAMMATORY", styles["summary_head"])],
        [Paragraph("STEMI\nNSTEMI/UA", styles["body"]),
         Paragraph("Atrial\nFibrillation", styles["body"]),
         Paragraph("WPW\n3rd Degree HB", styles["body"]),
         Paragraph("LVH | RVH\nHCM | DCM", styles["body"]),
         Paragraph("Acute\nPericarditis", styles["body"])],
    ]
    cat_col = COL_W / 5
    cat_tbl = Table(cat_data, colWidths=[cat_col]*5)
    cat_tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0),(0,0), RED),
        ("BACKGROUND",    (1,0),(1,0), PURPLE),
        ("BACKGROUND",    (2,0),(2,0), BLUE),
        ("BACKGROUND",    (3,0),(3,0), TEAL),
        ("BACKGROUND",    (4,0),(4,0), ORANGE),
        ("FONTNAME",      (0,1),(-1,1), "Helvetica"),
        ("FONTSIZE",      (0,1),(-1,1), 8),
        ("ALIGN",         (0,0),(-1,-1), "CENTER"),
        ("VALIGN",        (0,0),(-1,-1), "MIDDLE"),
        ("TOPPADDING",    (0,0),(-1,0), 6),
        ("BOTTOMPADDING", (0,0),(-1,0), 6),
        ("TOPPADDING",    (0,1),(-1,1), 6),
        ("BOTTOMPADDING", (0,1),(-1,1), 6),
        ("GRID",          (0,0),(-1,-1), 0.5, colors.white),
        ("BACKGROUND",    (0,1),(4,1), LIGHT_BG),
    ]))
    story.append(cat_tbl)
    story.append(Spacer(1, 0.6*cm))

    story.append(Paragraph(
        "For clinical education and quick-reference use only. "
        "All findings should be correlated with full clinical history and examination. "
        "Sources: Braunwald's Heart Disease | Harrison's Principles of Internal Medicine 22E | "
        "Tintinalli's Emergency Medicine | Ganong's Physiology",
        styles["cover_note"]
    ))

    story.append(PageBreak())

    # ── TABLE OF CONTENTS ─────────────────────────────────────────────────────
    story.append(Spacer(1, 0.8*cm))
    toc_header = Table([[Paragraph("TABLE OF CONTENTS", styles["section_title"])]],
                       colWidths=[COL_W])
    toc_header.setStyle(TableStyle([
        ("BACKGROUND",    (0,0),(-1,-1), BLUE),
        ("TOPPADDING",    (0,0),(-1,-1), 10),
        ("BOTTOMPADDING", (0,0),(-1,-1), 10),
        ("LEFTPADDING",   (0,0),(-1,-1), 14),
    ]))
    story.append(toc_header)
    story.append(Spacer(1, 0.4*cm))

    for cond in CONDITIONS:
        row_bg = cond["bg"]
        toc_row = Table([
            [
                Paragraph(f"<b>{cond['num']:02d}</b>", styles["body_bold"]),
                Paragraph(cond["name"], styles["toc_entry"]),
                Paragraph(f"<font color='#{cond['category'] and '555555'}'>{cond['category']}</font>", styles["body"]),
                Paragraph(
                    f"<b><font color='#{cond['urgency_col'].hexval()[1:]}'>{cond['urgency']}</font></b>",
                    styles["body_bold"]
                ),
            ]
        ], colWidths=[1.2*cm, 10.5*cm, 3.2*cm, 2.7*cm])
        toc_row.setStyle(TableStyle([
            ("BACKGROUND",  (0,0),(-1,-1), row_bg),
            ("TOPPADDING",  (0,0),(-1,-1), 5),
            ("BOTTOMPADDING",(0,0),(-1,-1),5),
            ("LEFTPADDING", (0,0),(-1,-1), 8),
            ("LINEBELOW",   (0,0),(-1,-1), 0.5, colors.white),
            ("ALIGN",       (0,0),(0,0), "CENTER"),
            ("VALIGN",      (0,0),(-1,-1), "MIDDLE"),
        ]))
        story.append(toc_row)

    story.append(Spacer(1, 0.5*cm))

    # ECG basics legend
    story.append(Paragraph("<b>ECG Waveform Basics</b>", styles["toc_title"]))
    legend_data = [
        ["Component", "Normal Duration", "Normal Amplitude", "Significance"],
        ["P wave",   "80-120 ms",  "<2.5 mm", "Atrial depolarisation"],
        ["PR interval", "120-200 ms", "N/A", "AV nodal conduction time"],
        ["QRS complex", "60-100 ms", "Variable by lead", "Ventricular depolarisation"],
        ["ST segment", "Isoelectric", "+/-1 mm", "Early ventricular repolarisation"],
        ["T wave", "160 ms", "<6 mm limb / <10 mm precordial", "Ventricular repolarisation"],
        ["QT interval", "QTc <440 ms (M) / <460 ms (F)", "N/A", "Total ventricular activity"],
    ]
    legend_tbl = Table(legend_data, colWidths=[3.2*cm, 3.2*cm, 5.8*cm, 5.4*cm])
    legend_tbl.setStyle(TableStyle([
        ("BACKGROUND",  (0,0),(-1,0), DARK),
        ("FONTNAME",    (0,0),(-1,0), "Helvetica-Bold"),
        ("FONTSIZE",    (0,0),(-1,0), 8.5),
        ("TEXTCOLOR",   (0,0),(-1,0), colors.white),
        ("FONTNAME",    (0,1),(-1,-1), "Helvetica"),
        ("FONTSIZE",    (0,1),(-1,-1), 8),
        ("ROWBACKGROUNDS", (0,1),(-1,-1), [LIGHT_BG, colors.white]),
        ("GRID",        (0,0),(-1,-1), 0.5, colors.HexColor("#CCCCCC")),
        ("TOPPADDING",  (0,0),(-1,-1), 4),
        ("BOTTOMPADDING",(0,0),(-1,-1),4),
        ("LEFTPADDING", (0,0),(-1,-1), 6),
        ("ALIGN",       (0,0),(-1,-1), "LEFT"),
        ("VALIGN",      (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(legend_tbl)

    story.append(PageBreak())

    # ── CONDITION PAGES ───────────────────────────────────────────────────────
    for cond in CONDITIONS:
        colour   = cond["colour"]
        bg       = cond["bg"]

        # Section header
        header_data = [[
            Paragraph(f"{cond['num']:02d}", styles["section_num"]),
            [
                Paragraph(cond["name"], styles["section_title"]),
                Paragraph(f"{cond['category']}  |  Urgency: {cond['urgency']}", styles["section_cat"]),
            ]
        ]]
        header_tbl = Table(header_data, colWidths=[2.2*cm, COL_W - 2.2*cm])
        header_tbl.setStyle(TableStyle([
            ("BACKGROUND",    (0,0),(-1,-1), colour),
            ("TOPPADDING",    (0,0),(-1,-1), 10),
            ("BOTTOMPADDING", (0,0),(-1,-1), 10),
            ("LEFTPADDING",   (0,0),(-1,-1), 10),
            ("VALIGN",        (0,0),(-1,-1), "MIDDLE"),
            ("ALIGN",         (0,0),(0,0), "CENTER"),
        ]))
        story.append(KeepTogether([header_tbl, Spacer(1, 0.3*cm)]))

        # Waveform
        story.append(ECGWaveform(cond["wave"], width=COL_W, height=80))
        story.append(Paragraph(
            f"Schematic ECG trace - {cond['name']} pattern",
            styles["wave_label"]
        ))
        story.append(Spacer(1, 0.35*cm))

        # Key ECG Findings table
        findings_header = Table(
            [[Paragraph("<b>KEY ECG FINDINGS</b>", styles["summary_head"])]],
            colWidths=[COL_W]
        )
        findings_header.setStyle(TableStyle([
            ("BACKGROUND",    (0,0),(-1,-1), colour),
            ("TOPPADDING",    (0,0),(-1,-1), 5),
            ("BOTTOMPADDING", (0,0),(-1,-1), 5),
            ("LEFTPADDING",   (0,0),(-1,-1), 10),
        ]))
        story.append(findings_header)

        find_rows = [["ECG Feature", "Detail"]]
        for feat, detail in cond["key_findings"]:
            find_rows.append([
                Paragraph(f"<b>{feat}</b>", styles["body_bold"]),
                Paragraph(detail, styles["body"]),
            ])

        find_col1 = 5.5*cm
        find_col2 = COL_W - find_col1
        find_tbl = Table(find_rows, colWidths=[find_col1, find_col2])
        find_tbl.setStyle(TableStyle([
            ("BACKGROUND",     (0,0),(-1,0), DARK),
            ("TEXTCOLOR",      (0,0),(-1,0), colors.white),
            ("FONTNAME",       (0,0),(-1,0), "Helvetica-Bold"),
            ("FONTSIZE",       (0,0),(-1,0), 8),
            ("ROWBACKGROUNDS", (0,1),(-1,-1), [bg, colors.white]),
            ("GRID",           (0,0),(-1,-1), 0.4, colors.HexColor("#CCCCCC")),
            ("TOPPADDING",     (0,0),(-1,-1), 4),
            ("BOTTOMPADDING",  (0,0),(-1,-1), 4),
            ("LEFTPADDING",    (0,0),(-1,-1), 7),
            ("VALIGN",         (0,0),(-1,-1), "MIDDLE"),
            ("FONTSIZE",       (0,1),(-1,-1), 8.5),
        ]))
        story.append(find_tbl)
        story.append(Spacer(1, 0.3*cm))

        # Territory / pattern table
        if cond.get("territories"):
            terr_header = Table(
                [[Paragraph("<b>LEAD TERRITORIES / PATTERNS</b>", styles["summary_head"])]],
                colWidths=[COL_W]
            )
            terr_header.setStyle(TableStyle([
                ("BACKGROUND",    (0,0),(-1,-1), colour),
                ("TOPPADDING",    (0,0),(-1,-1), 5),
                ("BOTTOMPADDING", (0,0),(-1,-1), 5),
                ("LEFTPADDING",   (0,0),(-1,-1), 10),
            ]))
            story.append(terr_header)

            terr_rows = [["Territory / Pattern", "ECG Leads / Finding"]]
            for terr, finding in cond["territories"]:
                terr_rows.append([
                    Paragraph(f"<b>{terr}</b>", styles["body_bold"]),
                    Paragraph(finding, styles["body"]),
                ])

            terr_tbl = Table(terr_rows, colWidths=[5.5*cm, COL_W - 5.5*cm])
            terr_tbl.setStyle(TableStyle([
                ("BACKGROUND",     (0,0),(-1,0), DARK),
                ("TEXTCOLOR",      (0,0),(-1,0), colors.white),
                ("FONTNAME",       (0,0),(-1,0), "Helvetica-Bold"),
                ("FONTSIZE",       (0,0),(-1,0), 8),
                ("ROWBACKGROUNDS", (0,1),(-1,-1), [bg, colors.white]),
                ("GRID",           (0,0),(-1,-1), 0.4, colors.HexColor("#CCCCCC")),
                ("TOPPADDING",     (0,0),(-1,-1), 4),
                ("BOTTOMPADDING",  (0,0),(-1,-1), 4),
                ("LEFTPADDING",    (0,0),(-1,-1), 7),
                ("VALIGN",         (0,0),(-1,-1), "MIDDLE"),
                ("FONTSIZE",       (0,1),(-1,-1), 8.5),
            ]))
            story.append(terr_tbl)
            story.append(Spacer(1, 0.3*cm))

        # Clinical pearl
        pearl_box = Table(
            [[Paragraph(f"<b>Clinical Pearl:</b>  {cond['pearls']}", styles["pearl"])]],
            colWidths=[COL_W]
        )
        pearl_box.setStyle(TableStyle([
            ("BACKGROUND",    (0,0),(-1,-1), colors.HexColor("#EBF5FB")),
            ("LEFTPADDING",   (0,0),(-1,-1), 10),
            ("TOPPADDING",    (0,0),(-1,-1), 6),
            ("BOTTOMPADDING", (0,0),(-1,-1), 6),
            ("BOX",           (0,0),(-1,-1), 1, colors.HexColor("#2980B9")),
        ]))
        story.append(pearl_box)
        story.append(Spacer(1, 0.5*cm))

        story.append(PageBreak())

    # ── SUMMARY COMPARISON TABLE ──────────────────────────────────────────────
    story.append(Spacer(1, 0.5*cm))
    sum_header = Table(
        [[Paragraph("QUICK-REFERENCE SUMMARY TABLE", styles["section_title"])]],
        colWidths=[COL_W]
    )
    sum_header.setStyle(TableStyle([
        ("BACKGROUND",    (0,0),(-1,-1), DARK),
        ("TOPPADDING",    (0,0),(-1,-1), 10),
        ("BOTTOMPADDING", (0,0),(-1,-1), 10),
        ("LEFTPADDING",   (0,0),(-1,-1), 14),
    ]))
    story.append(sum_header)
    story.append(Spacer(1, 0.3*cm))

    sum_rows = [
        ["#", "Condition", "Category", "Key ECG Feature", "Urgency"],
    ]
    for c in CONDITIONS:
        sum_rows.append([
            str(c["num"]),
            c["name"],
            c["category"],
            c["key_findings"][0][0] + ": " + c["key_findings"][0][1],
            c["urgency"],
        ])

    sum_col_w = [0.7*cm, 5.5*cm, 2.8*cm, 6.8*cm, 2.0*cm]
    sum_tbl = Table(
        [[Paragraph(cell if i==0 else str(cell), ParagraphStyle(
            "sh", parent=getSampleStyleSheet()["Normal"],
            fontSize=7.5 if r > 0 else 8,
            fontName="Helvetica-Bold" if r == 0 else "Helvetica",
            leading=10,
            textColor=colors.white if r == 0 else DARK,
        )) for i, cell in enumerate(row)] for r, row in enumerate(sum_rows)],
        colWidths=sum_col_w
    )
    row_colours = []
    for i, c in enumerate(CONDITIONS):
        row_colours.append(("BACKGROUND", (0, i+1), (-1, i+1), c["bg"]))

    sum_tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0),(-1,0), DARK),
        ("GRID",          (0,0),(-1,-1), 0.4, colors.HexColor("#CCCCCC")),
        ("TOPPADDING",    (0,0),(-1,-1), 4),
        ("BOTTOMPADDING", (0,0),(-1,-1), 4),
        ("LEFTPADDING",   (0,0),(-1,-1), 5),
        ("ALIGN",         (0,0),(0,-1), "CENTER"),
        ("VALIGN",        (0,0),(-1,-1), "MIDDLE"),
    ] + row_colours))
    story.append(sum_tbl)

    story.append(Spacer(1, 1*cm))

    # Footer note
    story.append(HRFlowable(width=COL_W, color=MID_GREY, thickness=0.5))
    story.append(Spacer(1, 0.2*cm))
    story.append(Paragraph(
        "References: Braunwald's Heart Disease (Elsevier) | "
        "Harrison's Principles of Internal Medicine 22E (McGraw Hill, 2025) | "
        "Tintinalli's Emergency Medicine (McGraw Hill) | "
        "Ganong's Review of Medical Physiology | "
        "Roberts and Hedges' Clinical Procedures in Emergency Medicine (Elsevier)",
        ParagraphStyle("ref", parent=getSampleStyleSheet()["Normal"],
                       fontSize=7, textColor=MID_GREY, leading=10,
                       alignment=TA_CENTER)
    ))

    return story


# ──────────────────────────────────────────────────────────────────────────────
# MAIN
# ──────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
    OUT = "/home/daytona/workspace/ecg-guide/ECG_Quick_Reference_Guide.pdf"

    doc = SimpleDocTemplate(
        OUT,
        pagesize=A4,
        leftMargin=2*cm, rightMargin=2*cm,
        topMargin=2.5*cm, bottomMargin=1.8*cm,
        title="ECG Quick-Reference Guide",
        author="Orris Medical Reference",
        subject="ECG Patterns in Heart Disease",
    )

    styles = build_styles()
    story  = build_story(styles)

    doc.build(
        story,
        onFirstPage=on_first_page,
        onLaterPages=on_later_pages,
    )

    print(f"PDF saved: {OUT}")
Running Command

cd /home/daytona/workspace/ecg-guide && python3 build_ecg_guide.py 2>&1

Editing File

~/ecg-guide/build_ecg_guide.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ecg-guide/build_ecg_guide.py'
Running Command

cd /home/daytona/workspace/ecg-guide && python3 build_ecg_guide.py 2>&1

Running Command

grep -n "_canvas\|self\.canv" /home/daytona/workspace/ecg-guide/build_ecg_guide.…

Running Command

sed -i 's/self\._canvas/self\.canv/g' /home/daytona/workspace/ecg-guide/build_ec…

Running Command

python3 -c " content = open('/home/daytona/workspace/ecg-guide/build_ecg_guide.p…

Running Command

cd /home/daytona/workspace/ecg-guide && python3 build_ecg_guide.py 2>&1

Running Command

ls -lh /home/daytona/workspace/ecg-guide/ECG_Quick_Reference_Guide.pdf

PDF generated successfully (46 KB). Here is your download link:

ECG Quick Reference Guide

PDF Document · PDF


Here's what's included in the guide:
Structure (13 pages, A4):
SectionContent
Cover pageTitle, category overview boxes (Ischaemic / Arrhythmia / Conduction / Structural / Inflammatory), schematic ECG trace
Table of ContentsAll 10 conditions with category and urgency level; ECG waveform basics reference table (P wave, PR, QRS, ST, T, QT normals)
10 Condition PagesOne full page per condition
Summary TableAll 10 conditions side-by-side at a glance
Each condition page contains:
  • Colour-coded header bar with condition name, category, and urgency rating
  • Programmatically drawn schematic ECG trace on an authentic pink grid background (unique morphology per condition)
  • Key ECG Findings table - 5 specific findings with explanations
  • Lead Territories / Patterns table - which leads show what
  • Clinical Pearl - the most important "don't miss" fact
Conditions covered: STEMI, NSTEMI/UA, LVH, RVH, Atrial Fibrillation, WPW, Complete Heart Block (3rd degree), HCM, Acute Pericarditis, Dilated Cardiomyopathy
Sources cited: Braunwald's Heart Disease, Harrison's Principles 22E (2025), Tintinalli's Emergency Medicine, Ganong's Physiology, Roberts & Hedges' Clinical Procedures

Search for ECG practice cases to test your knowledge

Searching the Web

ECG practice cases free online interactive self-test 2024 2025

Searching the Web

best free ECG quiz websites cardiology students cases with answers

Searching the Web

ECG practice site dr smith ECG blog 150 ECG cases free

Reading Web Page

https://litfl.com/top-100/ecg

Here's a curated, well-organised guide to the best free ECG practice resources available right now:

Best Free ECG Practice Case Resources

1. LITFL - Life in the Fast Lane ECG Quiz Library

This is arguably the single best free ECG case library on the internet. Over 150 real 12-lead ECG cases, each with a clinical scenario, a full systematic analysis, and a detailed explanation. Cases cover everything from common to rare and genuinely challenging diagnoses.
Sample cases available:
  • STEMI, NSTEMI, posterior MI, Wellens syndrome
  • Brugada syndrome, WPW, Long QT
  • Pericarditis, PE (S1Q3T3), hypothermia (J waves)
  • Takotsubo cardiomyopathy, ARVC
  • Digoxin toxicity, hyperkalemia, hypokalemia
  • Pacemaker malfunction, lead reversal
  • Left main coronary artery occlusion (aVR elevation)
Best for: All levels. Emergency medicine and critical care focus. Completely free.

2. Dr. Smith's ECG Blog

The gold standard for subtle ischaemia and occlusion-MI patterns. Dr. Stephen Smith (EM physician, Hennepin Healthcare) posts real cases with expert analysis, often featuring patterns that machines and junior doctors miss - De Winter T waves, subtle posterior STEMI, Wellens syndrome, LBBB with Sgarbossa criteria.
Best for: Intermediate to advanced learners. EM residents, registrars, anyone wanting to go beyond textbook ECGs.

3. Oxford Medical Education - ECG Quiz

15+ structured cases with real ECG images, clinical vignettes, and systematic structured answers (Rate / Rhythm / Axis / PR / QRS / ST-T / QTc). Each case has a show/hide answer format. Good for learning a structured approach.
Sample cases include: AF (holiday heart), STEMI, Brugada, WPW, posterior infarct, left main stem occlusion.
Best for: Medical students and junior doctors learning systematic ECG reading.

4. ECG Weekly - Workout Quiz / Competition Cases

A 2025 ECG competition series with real patient cases, point-scored answers, and full explanations. Cases covered in the free portion include:
  • Mobitz I (Wenckebach) AV block
  • AF with RBBB and inferior MI
  • Complete heart block with junctional escape
  • Short QT (hypercalcaemia)
  • Sinus rhythm with dextrocardia
  • Hypothermia with J waves (Osborn waves)
Best for: Competitive self-testing, senior students, residents. Some advanced content requires a subscription.

5. Practical Clinical Skills - EKG Practice Drills

Over 600 EKG tracings in a multiple-choice quiz format with immediate feedback. Organised by category (rhythms, blocks, ischaemia, paced rhythms). Has specific quizzes for nurses, ACLS prep, NCLEX, and NP/PA students. Free tier available; certificates for registered users.
Best for: Rhythm recognition drills, nursing staff, ACLS preparation.

6. SkillStat - Six Second ECG & 12-Lead Practice

Structured 15-question quizzes by category:
  • Exercise 1: Basic rhythms
  • Exercise 2A: Atrial and junctional rhythms
  • Exercise 3A: Ventricular and paced rhythms
  • ECG 360 Simulator (full-spectrum tool)
Best for: Paramedics, nurses, ACLS students. Clean interface, good for timed practice.

7. Rosh Review - ECG Qbank

Real ECG cases with one-liner clinical context, created by Pendell Meyers MD from Dr. Smith's ECG Blog content. High-yield format for exam preparation. Free trial available; paid for full access.
Best for: EM boards preparation, systematic high-yield study.

Recommended Study Approach

StageResourceFocus
BeginnerOxford Medical EducationStructured systematic reading
IntermediateLITFL Top 150 ECG LibraryBroad case variety, real tracings
AdvancedDr. Smith's ECG BlogSubtle ischaemia, occlusion-MI patterns
Rapid drillingPractical Clinical Skills / SkillStatRhythm recognition under time pressure
CompetitiveECG Weekly WorkoutHigh-difficulty mixed cases with scoring

Pro tip: The LITFL ECG library also has a companion ECG Library Basics section with over 100 topic summaries (Brugada, Sgarbossa criteria, Ashman phenomenon, etc.) that pair perfectly with the quiz cases.
This is a shared conversation. Sign in to Orris to start your own chat.