I want to understand ECG completely. So give me all basic and advance ecg knowledge with full detail it mbbs level, also explain with ecg paper with diagram then goes for abnormally and change in ecg patten with that abnormalities, then goes as for all condition from simpler to complex one with diagram. Use otentic source and explain such a why that i become the expert of ecg interpretation.

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Finding Sources
Reading File
Reading File
Reading File
Loading Image
Loading Image
Reading File
Reading File
Loading Image
Reading File
Loading Image
Reading File
Finding Sources
Reading File
Finding Sources
Reading File
Loading Image
Reading File
Finding Sources
Reading File
Loading Image
Loading Image
Loading Image
This is a complete, MBBS-level ECG mastery guide sourced from the best medical textbooks. Here's a summary of what's covered:

What This Guide Covers:
SectionContent
Part 1Cardiac electrophysiology - why the heart generates electricity, action potentials, conduction system (SA node → AV node → His-Purkinje)
Part 2ECG paper - the grid, time/voltage calibration, 3 methods to calculate heart rate
Part 3All waves explained: P, PR, QRS, ST, T, QT, U - with normal values, what each represents, when they go abnormal
Part 412-lead ECG - Einthoven's triangle, all 6 limb leads, all 6 precordial leads, anatomical territories
Part 5QRS axis - quick determination using leads I and aVF
Part 6Systematic reading approach (PRISCE method)
Part 7All abnormalities simple to complex: sinus arrhythmias, AF/flutter, all 3 degrees of AV block, LBBB/RBBB, fascicular blocks, LVH/RVH, STEMI/NSTEMI evolution with lead localization, VT/VF, SVT/WPW, hyperkalemia, hypokalemia, calcium disorders, pericarditis, PE, hypothermia, drugs, Brugada, Long QT
Part 8Expert ECG read-through demonstrated step-by-step
Part 9Quick reference table of 20+ diagnoses
Part 10Expert mindset and study strategy
All diagrams are from authentic textbook sources (Goldman-Cecil, Harrison's, Medical Physiology, Miller's Anesthesia).

I want to understand ECG completely. So give me all basic and advance ecg knowledge with full detail it mbbs level, also explain with ecg paper with diagram then goes for abnormally and change in ecg patten with that abnormalities, then goes as for all condition from simpler to complex one with diagram. Use otentic source and explain such a why that i become the expert of ecg interpretation.

I want to understand ECG completely. So give me all basic and advance ecg knowledge with full detail it mbbs level, also explain with ecg paper with diagram then goes for abnormally and change in ecg patten with that abnormalities, then goes as for all condition from simpler to complex one with diagram. Use otentic source and explain such a why that i become the expert of ecg interpretation. Give diagram with each abnormally of ecg

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Loading Image
Loading Image
Loading Image
Reading File
Loading Image
Loading Image
Reading File
Loading Image
Loading Image
Reading File
Loading Image
Loading Image
Reading File
Loading Image
Reading File
Reading File
Reading File
Finding Sources
Reading File
Reading File
Finding Sources
Reading File
Finding Sources
Finding Sources
Reading File
Reading File
This is the complete, expert-level ECG guide built from the most authoritative medical textbooks. Here is a summary of every section and what diagram is embedded within it:

What This Guide Contains

SectionContentDiagrams Included
Part 1Cardiac action potential, conduction system anatomy, blood supply-
Part 2ECG paper grid (time/voltage), all 3 HR calculation methods, 12-lead formatECG grid with all waves labeled (Goldman-Cecil)
Part 3All waves explained wave-by-wave with mechanism and normal values; QRS two-phase genesisVentricular depolarization vectors V1 vs V6 (Harrison's)
Part 4All 12 leads - positions, what they view, Einthoven's triangle, hexaxial axis diagramLead placement on chest (Harrison's); Frontal + horizontal plane leads; Hexaxial axis wheel; Normal 12-lead ECG
Part 514-parameter systematic reading approach-
Part 6.1Sinus rhythms - NSR, bradycardia, tachycardia, arrhythmia with ASCII ECGASCII diagrams
Part 6.2Atrial enlargement - P-pulmonale, P-mitraleP-wave comparison diagram (Harrison's)
Part 6.3LVH and RVH with voltage criteriaLVH/RVH QRS diagram (Harrison's)
Part 6.4All 4 degrees of AV block with ASCII ECGs and criteriaASCII ECG patterns
Part 6.5RBBB, LBBB, LAFB, LPFB - mechanism, criteria, clinical trapsRBBB/LBBB comparison V1 vs V6 (Harrison's)
Part 6.6Ischemia mechanism, STEMI evolution table, Wellens, Q waves, localization tableST depression vs elevation mechanism; anterior STEMI sequence; inferior STEMI sequence; Wellens T-waves
Part 6.7Atrial flutter (sawtooth), AF (irregularly irregular) with ASCII ECGsASCII ECG patterns
Part 6.8PVCs, VT (AV dissociation, fusion beats), VF, Torsades de Pointes-
Part 6.9AVNRT, WPW (orthodromic/antidromic), WPW+AF danger-
Part 6.11Pericarditis vs STEMI comparison table-
Part 6.12Hyperkalemia (all stages), hypokalemia, hypothermia Osborn waves, Ca²⁺ disordersHyperkalemia ECG sequence; Hypokalemia U-waves; Osborn wave; Ca²⁺ QT changes
Part 6.13Drug effects (digoxin, TCAs, antiarrhythmics)TCA overdose ECG
Part 6.14Cardiac tamponadeTamponade triad: tachycardia + low voltage + alternans
Part 6.15Pulmonary embolism S1Q3T3-
Part 6.16Brugada syndrome-
Part 6.17SAH and neurological ECG changesCVA T-waves (SAH)
Part 740+ condition quick reference table-
Part 8Expert mindset, 14-parameter approach, 5 life-threatening diagnoses-

Generate a PDF reference card summarizing all 40+ ECG abnormalities with their diagnostic criteria

Reading File
Reading File
Running Command

ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace not found"

Running Command

mkdir -p /home/daytona/workspace/ecg-reference && echo "created"

Writing File

~/ecg-reference/build_ecg_reference.py

#!/usr/bin/env python3
"""
ECG Reference Card - 40+ Abnormalities with Diagnostic Criteria
Builds a professional multi-page PDF reference card using ReportLab.
"""

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import BalancedColumns
from reportlab.lib.colors import HexColor, white, black
import os

OUTPUT = "/home/daytona/workspace/ecg-reference/ECG_Reference_Card.pdf"

# ── Colour palette ────────────────────────────────────────────────────────────
C_DARK_RED   = HexColor("#8B0000")
C_RED        = HexColor("#C0392B")
C_ORANGE     = HexColor("#E67E22")
C_AMBER      = HexColor("#F39C12")
C_BLUE_DARK  = HexColor("#1A3A5C")
C_BLUE       = HexColor("#2980B9")
C_BLUE_LIGHT = HexColor("#EBF5FB")
C_TEAL       = HexColor("#117A65")
C_TEAL_LIGHT = HexColor("#E8F8F5")
C_PURPLE     = HexColor("#6C3483")
C_PURPLE_LIGHT = HexColor("#F5EEF8")
C_GREEN      = HexColor("#1E8449")
C_GREEN_LIGHT = HexColor("#EAFAF1")
C_GREY_DARK  = HexColor("#2C3E50")
C_GREY       = HexColor("#566573")
C_GREY_LIGHT = HexColor("#F2F3F4")
C_GREY_MID   = HexColor("#BDC3C7")
C_YELLOW_LIGHT = HexColor("#FDFAE6")
C_PINK_LIGHT = HexColor("#FDEDEC")

# ── Page setup ────────────────────────────────────────────────────────────────
PAGE_W, PAGE_H = A4
MARGIN = 14 * mm
DOC_W  = PAGE_W - 2 * MARGIN

# ── Styles ────────────────────────────────────────────────────────────────────
def make_styles():
    base = getSampleStyleSheet()
    S = {}

    S['cover_title'] = ParagraphStyle(
        'cover_title', fontName='Helvetica-Bold',
        fontSize=32, textColor=white, alignment=TA_CENTER, leading=40,
        spaceAfter=6
    )
    S['cover_sub'] = ParagraphStyle(
        'cover_sub', fontName='Helvetica',
        fontSize=13, textColor=HexColor("#D6EAF8"), alignment=TA_CENTER, leading=18
    )
    S['cover_source'] = ParagraphStyle(
        'cover_source', fontName='Helvetica-Oblique',
        fontSize=9, textColor=HexColor("#AED6F1"), alignment=TA_CENTER, leading=12
    )
    S['section_head'] = ParagraphStyle(
        'section_head', fontName='Helvetica-Bold',
        fontSize=12, textColor=white, alignment=TA_LEFT, leading=15,
        leftIndent=4
    )
    S['card_title'] = ParagraphStyle(
        'card_title', fontName='Helvetica-Bold',
        fontSize=8.5, textColor=C_GREY_DARK, leading=11, spaceAfter=1
    )
    S['card_body'] = ParagraphStyle(
        'card_body', fontName='Helvetica',
        fontSize=7.5, textColor=C_GREY_DARK, leading=10.5, spaceAfter=0
    )
    S['card_crit'] = ParagraphStyle(
        'card_crit', fontName='Helvetica-Bold',
        fontSize=7.5, textColor=C_BLUE_DARK, leading=10.5
    )
    S['card_key'] = ParagraphStyle(
        'card_key', fontName='Helvetica-BoldOblique',
        fontSize=7.2, textColor=C_RED, leading=10
    )
    S['tag'] = ParagraphStyle(
        'tag', fontName='Helvetica-Bold',
        fontSize=6.5, textColor=white, leading=9, alignment=TA_CENTER
    )
    S['footer'] = ParagraphStyle(
        'footer', fontName='Helvetica',
        fontSize=7, textColor=C_GREY, alignment=TA_CENTER, leading=9
    )
    S['toc_head'] = ParagraphStyle(
        'toc_head', fontName='Helvetica-Bold',
        fontSize=10, textColor=C_BLUE_DARK, leading=13, spaceAfter=2
    )
    S['toc_item'] = ParagraphStyle(
        'toc_item', fontName='Helvetica',
        fontSize=8.5, textColor=C_GREY_DARK, leading=12, leftIndent=8
    )
    S['normal_head'] = ParagraphStyle(
        'normal_head', fontName='Helvetica-Bold',
        fontSize=9, textColor=C_BLUE_DARK, leading=12, spaceAfter=2, spaceBefore=4
    )
    S['normal_body'] = ParagraphStyle(
        'normal_body', fontName='Helvetica',
        fontSize=8, textColor=C_GREY_DARK, leading=11, spaceAfter=1
    )
    S['table_head'] = ParagraphStyle(
        'table_head', fontName='Helvetica-Bold',
        fontSize=7.5, textColor=white, leading=10, alignment=TA_CENTER
    )
    S['table_cell'] = ParagraphStyle(
        'table_cell', fontName='Helvetica',
        fontSize=7, textColor=C_GREY_DARK, leading=9.5
    )
    S['table_cell_bold'] = ParagraphStyle(
        'table_cell_bold', fontName='Helvetica-Bold',
        fontSize=7, textColor=C_GREY_DARK, leading=9.5
    )
    return S

S = make_styles()

# ── Helper: coloured section banner ──────────────────────────────────────────
def section_banner(title, color=C_BLUE_DARK, icon=""):
    label = f"{icon}  {title}" if icon else title
    t = Table([[Paragraph(label, S['section_head'])]],
              colWidths=[DOC_W])
    t.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), color),
        ('ROWBACKGROUNDS', (0, 0), (-1, -1), [color]),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('LEFTPADDING', (0, 0), (-1, -1), 8),
        ('RIGHTPADDING', (0, 0), (-1, -1), 8),
        ('ROUNDEDCORNERS', [3, 3, 3, 3]),
    ]))
    return t

# ── Helper: single condition card ────────────────────────────────────────────
def condition_card(num, name, criteria_lines, key_pearl, bg=C_BLUE_LIGHT, tag_color=C_BLUE):
    """Returns a Table that looks like a card."""
    num_cell = Paragraph(f"<b>{num}</b>", ParagraphStyle(
        'num', fontName='Helvetica-Bold', fontSize=10,
        textColor=tag_color, alignment=TA_CENTER, leading=13
    ))
    title_cell = Paragraph(f"<b>{name}</b>", S['card_title'])
    criteria_text = "<br/>".join(
        f"<bullet>&bull;</bullet> {line}" for line in criteria_lines
    )
    crit_cell = Paragraph(criteria_text, S['card_body'])
    pearl_cell = Paragraph(f"<i>&#9733; {key_pearl}</i>", S['card_key'])

    inner = Table(
        [[title_cell], [crit_cell], [pearl_cell]],
        colWidths=[DOC_W * 0.87]
    )
    inner.setStyle(TableStyle([
        ('TOPPADDING', (0, 0), (-1, -1), 1),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 1),
        ('LEFTPADDING', (0, 0), (-1, -1), 0),
        ('RIGHTPADDING', (0, 0), (-1, -1), 0),
    ]))

    outer = Table(
        [[num_cell, inner]],
        colWidths=[DOC_W * 0.07, DOC_W * 0.93]
    )
    outer.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), bg),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('LEFTPADDING', (0, 0), (0, 0), 4),
        ('LEFTPADDING', (1, 0), (1, 0), 4),
        ('RIGHTPADDING', (0, 0), (-1, -1), 6),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('BOX', (0, 0), (-1, -1), 0.5, C_GREY_MID),
        ('LINEBELOW', (0, 0), (-1, -1), 0.3, C_GREY_MID),
    ]))
    return outer

# ── Helper: two-column card row ───────────────────────────────────────────────
def two_col_cards(card_a, card_b):
    gap = 3 * mm
    col_w = (DOC_W - gap) / 2
    t = Table([[card_a, card_b]], colWidths=[col_w, col_w])
    t.setStyle(TableStyle([
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('LEFTPADDING', (0, 0), (-1, -1), 0),
        ('RIGHTPADDING', (0, 0), (-1, -1), 0),
        ('TOPPADDING', (0, 0), (-1, -1), 0),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 2),
        ('INNERGRID', (0, 0), (-1, -1), 0, white),
        ('COLUMNPADDING', (1, 0), (1, 0), gap / 2),
    ]))
    return t

# ── Helper: mini single-column card (for 2-wide layout) ──────────────────────
def mini_card(num, name, criteria_lines, key_pearl, bg=C_BLUE_LIGHT, tag_color=C_BLUE):
    col_w = (DOC_W - 3 * mm) / 2
    num_cell = Paragraph(f"<b>{num}</b>", ParagraphStyle(
        'num2', fontName='Helvetica-Bold', fontSize=9,
        textColor=tag_color, alignment=TA_CENTER, leading=12
    ))
    title_cell = Paragraph(f"<b>{name}</b>", S['card_title'])
    criteria_text = "<br/>".join(
        f"<bullet>&bull;</bullet> {line}" for line in criteria_lines
    )
    crit_cell = Paragraph(criteria_text, S['card_body'])
    pearl_cell = Paragraph(f"<i>&#9733; {key_pearl}</i>", S['card_key'])

    inner = Table(
        [[title_cell], [crit_cell], [pearl_cell]],
        colWidths=[col_w * 0.86]
    )
    inner.setStyle(TableStyle([
        ('TOPPADDING', (0, 0), (-1, -1), 1),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 1),
        ('LEFTPADDING', (0, 0), (-1, -1), 0),
        ('RIGHTPADDING', (0, 0), (-1, -1), 0),
    ]))
    outer = Table(
        [[num_cell, inner]],
        colWidths=[col_w * 0.09, col_w * 0.91]
    )
    outer.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), bg),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('LEFTPADDING', (0, 0), (0, 0), 3),
        ('LEFTPADDING', (1, 0), (1, 0), 3),
        ('RIGHTPADDING', (0, 0), (-1, -1), 4),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('BOX', (0, 0), (-1, -1), 0.5, C_GREY_MID),
    ]))
    return outer

# ── Helper: comparison table ──────────────────────────────────────────────────
def comparison_table(headers, rows, col_colors=None):
    header_row = [Paragraph(h, S['table_head']) for h in headers]
    data = [header_row]
    for row in rows:
        data.append([Paragraph(str(cell), S['table_cell']) for cell in row])

    col_count = len(headers)
    col_w = DOC_W / col_count

    t = Table(data, colWidths=[col_w] * col_count, repeatRows=1)
    style_cmds = [
        ('BACKGROUND', (0, 0), (-1, 0), C_BLUE_DARK),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, 0), 7.5),
        ('TEXTCOLOR', (0, 0), (-1, 0), white),
        ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('TOPPADDING', (0, 0), (-1, -1), 3),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 3),
        ('LEFTPADDING', (0, 0), (-1, -1), 4),
        ('RIGHTPADDING', (0, 0), (-1, -1), 4),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, C_GREY_LIGHT]),
        ('GRID', (0, 0), (-1, -1), 0.3, C_GREY_MID),
        ('FONTSIZE', (0, 1), (-1, -1), 7),
    ]
    if col_colors:
        for col_idx, col_color in col_colors.items():
            style_cmds.append(('BACKGROUND', (col_idx, 0), (col_idx, 0), col_color))
    t.setStyle(TableStyle(style_cmds))
    return t

# ── Page numbering callback ───────────────────────────────────────────────────
def on_page(canvas, doc):
    canvas.saveState()
    # Footer line
    canvas.setStrokeColor(C_BLUE_DARK)
    canvas.setLineWidth(0.5)
    canvas.line(MARGIN, 18 * mm, PAGE_W - MARGIN, 18 * mm)
    # Footer text
    canvas.setFont('Helvetica', 6.5)
    canvas.setFillColor(C_GREY)
    canvas.drawString(MARGIN, 13 * mm,
        "ECG Reference Card  |  Based on Harrison's 22E, Goldman-Cecil Medicine, Medical Physiology")
    canvas.drawRightString(PAGE_W - MARGIN, 13 * mm,
        f"Page {doc.page}")
    canvas.restoreState()

def on_first_page(canvas, doc):
    # Full cover background
    canvas.setFillColor(C_BLUE_DARK)
    canvas.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
    # ECG waveform decoration strip (simulated)
    canvas.setStrokeColor(HexColor("#1A5276"))
    canvas.setLineWidth(1)
    canvas.line(0, PAGE_H * 0.52, PAGE_W, PAGE_H * 0.52)

# ── All 40+ ECG conditions data ───────────────────────────────────────────────
CONDITIONS = {
    "SINUS RHYTHMS": [
        {
            "num": "01", "name": "Normal Sinus Rhythm",
            "criteria": [
                "Rate 60-100 bpm",
                "P wave before every QRS; every QRS after a P",
                "P upright in I, II, aVF; inverted in aVR",
                "PR 120-200 ms; QRS < 120 ms",
            ],
            "pearl": "The reference standard. Deviation from this = abnormal.",
            "bg": C_GREEN_LIGHT, "tag": C_GREEN
        },
        {
            "num": "02", "name": "Sinus Bradycardia",
            "criteria": [
                "Rate < 60 bpm",
                "All morphologies (P, PR, QRS) completely NORMAL",
                "Just slow",
            ],
            "pearl": "Athletes, beta-blockers, hypothyroidism, inferior MI, sick sinus.",
            "bg": C_BLUE_LIGHT, "tag": C_BLUE
        },
        {
            "num": "03", "name": "Sinus Tachycardia",
            "criteria": [
                "Rate > 100 bpm",
                "All morphologies (P, PR, QRS) completely NORMAL",
                "P wave always upright in II",
            ],
            "pearl": "Always find the CAUSE. It is a symptom, never a primary diagnosis.",
            "bg": C_BLUE_LIGHT, "tag": C_BLUE
        },
        {
            "num": "04", "name": "Sinus Arrhythmia",
            "criteria": [
                "Rate varies with respiration (increases inspiration, decreases expiration)",
                "R-R variation > 10% between fastest and slowest",
                "All P wave morphologies and PR intervals identical",
            ],
            "pearl": "Normal finding; common in young patients and athletes.",
            "bg": C_BLUE_LIGHT, "tag": C_BLUE
        },
    ],
    "ATRIAL ABNORMALITIES": [
        {
            "num": "05", "name": "Right Atrial Overload (P-pulmonale)",
            "criteria": [
                "P wave amplitude >= 2.5 mm in II, III, aVF (tall peaked P)",
                "P wave duration NORMAL (< 120 ms)",
                "Large initial positive component of P in V1",
            ],
            "pearl": "COPD, pulmonary HTN, PE, tricuspid stenosis.",
            "bg": C_TEAL_LIGHT, "tag": C_TEAL
        },
        {
            "num": "06", "name": "Left Atrial Abnormality (P-mitrale)",
            "criteria": [
                "P wave duration >= 120 ms with notched 'M-shape' in II",
                "Biphasic P in V1: prominent DEEP negative terminal component (> 1 mm deep AND > 1 mm wide)",
                "P wave amplitude usually normal",
            ],
            "pearl": "Mitral stenosis, LV failure, hypertension, aortic stenosis.",
            "bg": C_TEAL_LIGHT, "tag": C_TEAL
        },
    ],
    "CHAMBER HYPERTROPHY": [
        {
            "num": "07", "name": "Left Ventricular Hypertrophy (LVH)",
            "criteria": [
                "Sokolow-Lyon: S(V1) + R(V5 or V6) > 35 mm",
                "Cornell: R(aVL) + S(V3) > 28 mm (M) or > 20 mm (F)",
                "R in aVL alone > 11 mm",
                "LV strain: ST depression + asymmetric T inversion in I, aVL, V5-V6",
                "Often: LAD, left atrial abnormality",
            ],
            "pearl": "Sensitivity only 30-50%. Specificity 85-95%. Echocardiography is definitive.",
            "bg": C_PURPLE_LIGHT, "tag": C_PURPLE
        },
        {
            "num": "08", "name": "Right Ventricular Hypertrophy (RVH)",
            "criteria": [
                "R >= S wave in V1 (tall R in V1) - HALLMARK",
                "Right axis deviation (QRS axis > +90 degrees)",
                "ST depression + T-wave inversion V1-V3 (RV strain)",
                "Deep S waves in I, V5, V6",
                "Right atrial overload often accompanies",
            ],
            "pearl": "Pulmonary HTN, pulmonary stenosis, COPD, ASD, Tetralogy of Fallot.",
            "bg": C_PURPLE_LIGHT, "tag": C_PURPLE
        },
    ],
    "AV CONDUCTION BLOCKS": [
        {
            "num": "09", "name": "1st Degree AV Block",
            "criteria": [
                "PR interval > 200 ms (> 5 small boxes)",
                "Every P wave is followed by a QRS - NOTHING DROPPED",
                "PR interval prolonged but CONSTANT",
                "QRS usually narrow and normal",
            ],
            "pearl": "Usually benign. Digoxin, beta-blockers, inferior MI, high vagal tone.",
            "bg": C_YELLOW_LIGHT, "tag": C_AMBER
        },
        {
            "num": "10", "name": "2nd Degree AV Block - Mobitz I (Wenckebach)",
            "criteria": [
                "PR interval progressively LENGTHENS with each beat",
                "Until one P wave is not conducted (QRS dropped)",
                "After dropped beat, PR resets to shortest, cycle repeats",
                "RR intervals progressively SHORTEN before pause",
                "GROUPED BEATING pattern - characteristic",
            ],
            "pearl": "Benign; AV nodal level. Inferior MI (RCA). Usually no pacemaker.",
            "bg": C_YELLOW_LIGHT, "tag": C_AMBER
        },
        {
            "num": "11", "name": "2nd Degree AV Block - Mobitz II",
            "criteria": [
                "PR interval is CONSTANT (does NOT prolong)",
                "Sudden unexpected non-conducted P wave (QRS dropped without warning)",
                "Often associated with WIDE QRS complex",
                "May be 2:1, 3:1 pattern",
            ],
            "pearl": "DANGEROUS - infranodal. High risk progression to complete block. Pacemaker needed.",
            "bg": C_PINK_LIGHT, "tag": C_RED
        },
        {
            "num": "12", "name": "3rd Degree (Complete) AV Block",
            "criteria": [
                "COMPLETE AV dissociation - P and QRS are INDEPENDENT",
                "P wave rate > QRS rate (P marches faster)",
                "Narrow QRS escape (junctional, 40-60 bpm) = proximal block",
                "Wide QRS escape (ventricular, 20-40 bpm) = distal block",
                "No fixed PR relationship anywhere on strip",
            ],
            "pearl": "EMERGENCY. Stokes-Adams attack (syncope). Urgent temporary pacemaker.",
            "bg": C_PINK_LIGHT, "tag": C_DARK_RED
        },
    ],
    "BUNDLE BRANCH BLOCKS": [
        {
            "num": "13", "name": "Right Bundle Branch Block (RBBB)",
            "criteria": [
                "QRS >= 120 ms (wide)",
                "rSR' pattern in V1 ('rabbit ears' / M-shape) - HALLMARK",
                "Wide slurred S wave in I, aVL, V5, V6",
                "T-wave inversion in V1-V2 (secondary change - NORMAL)",
            ],
            "pearl": "T-wave inversion in V1-V2 is NORMAL in RBBB - not ischemia. New RBBB = consider PE.",
            "bg": C_BLUE_LIGHT, "tag": C_BLUE
        },
        {
            "num": "14", "name": "Left Bundle Branch Block (LBBB)",
            "criteria": [
                "QRS >= 120 ms (wide)",
                "Broad, monophasic R in I, aVL, V5-V6 (no septal q waves!)",
                "rS or QS pattern in V1 (deep broad S or entirely negative)",
                "T-wave inversion in I, aVL, V5-V6 (secondary - NORMAL)",
                "Left axis deviation common",
            ],
            "pearl": "New LBBB + chest pain = STEMI equivalent. No septal Q waves is the key clue.",
            "bg": C_BLUE_LIGHT, "tag": C_BLUE_DARK
        },
        {
            "num": "15", "name": "Left Anterior Fascicular Block (LAFB)",
            "criteria": [
                "Left axis deviation (axis more negative than -45 degrees)",
                "qR pattern in leads I and aVL",
                "rS pattern in leads II, III, aVF",
                "QRS slightly prolonged but < 120 ms",
            ],
            "pearl": "Most common cause of marked LAD in adults.",
            "bg": C_BLUE_LIGHT, "tag": C_BLUE
        },
        {
            "num": "16", "name": "Left Posterior Fascicular Block (LPFB)",
            "criteria": [
                "Right axis deviation (axis > +110 degrees)",
                "rS pattern in I and aVL; qR in II, III, aVF",
                "QRS < 120 ms",
                "Diagnosis of EXCLUSION (rule out RVH, PE, lateral MI first)",
            ],
            "pearl": "Very rare as isolated finding. Dual blood supply makes it resistant to ischemia.",
            "bg": C_BLUE_LIGHT, "tag": C_BLUE
        },
    ],
    "ISCHEMIA AND INFARCTION": [
        {
            "num": "17", "name": "Hyperacute T Waves (Earliest STEMI)",
            "criteria": [
                "Tall, broad-based, symmetric peaked T waves in ischemic territory",
                "T wave taller than R wave in same lead",
                "Occur within MINUTES of coronary occlusion",
                "Regional distribution (not diffuse)",
            ],
            "pearl": "Earliest ECG sign of STEMI. Can revert to normal if reperfusion is rapid.",
            "bg": C_PINK_LIGHT, "tag": C_RED
        },
        {
            "num": "18", "name": "STEMI - Anterior (LAD)",
            "criteria": [
                "ST elevation (convex/tombstone) in V1-V4 (+/- I, aVL)",
                "Reciprocal ST depression in II, III, aVF",
                "Evolves: hyperacute T -> ST elevation -> T inversion -> Q waves",
                "Pathological Q waves in V1-V4 (width >= 40 ms OR depth >= 25% R)",
            ],
            "pearl": "Proximal LAD occlusion = V1-V6 + I + aVL elevation (widowmaker).",
            "bg": C_PINK_LIGHT, "tag": C_DARK_RED
        },
        {
            "num": "19", "name": "STEMI - Inferior (RCA/LCx)",
            "criteria": [
                "ST elevation in II, III, aVF",
                "Reciprocal ST depression in I, aVL (often prominent)",
                "III elevation > II elevation suggests RCA",
                "Always check right-sided leads (V3R/V4R) for RV infarct",
            ],
            "pearl": "Do right-sided leads in ALL inferior MI. RV infarct changes management entirely.",
            "bg": C_PINK_LIGHT, "tag": C_DARK_RED
        },
        {
            "num": "20", "name": "STEMI - Lateral (LCx)",
            "criteria": [
                "ST elevation in I, aVL, V5, V6",
                "Reciprocal ST depression in V1-V3 (and sometimes II, III, aVF)",
            ],
            "pearl": "High lateral MI (I, aVL only) can have very subtle, easily missed changes.",
            "bg": C_PINK_LIGHT, "tag": C_RED
        },
        {
            "num": "21", "name": "STEMI - Posterior",
            "criteria": [
                "NO direct ST elevation in standard leads",
                "Tall R wave in V1-V2 (posterior Q wave mirror image)",
                "ST depression in V1-V3 (posterior ST elevation, mirror image)",
                "Confirm with posterior leads V7-V9 (ST elevation >= 0.5 mm)",
            ],
            "pearl": "The 'hidden STEMI'. Always flip V1-V3 upside down to visualise posterior MI.",
            "bg": C_PINK_LIGHT, "tag": C_RED
        },
        {
            "num": "22", "name": "Right Ventricular Infarction",
            "criteria": [
                "In context of inferior STEMI (proximal RCA)",
                "ST elevation >= 1 mm in V3R and/or V4R",
                "Clinical: hypotension + JVD + clear lungs (Bezold-Jarisch triad)",
                "Contra-indicated: Nitrates (preload-dependent ventricle)",
            ],
            "pearl": "Fluids, NOT nitrates. Give IV fluids to maintain RV preload.",
            "bg": C_PINK_LIGHT, "tag": C_RED
        },
        {
            "num": "23", "name": "NSTEMI / Unstable Angina",
            "criteria": [
                "ST depression (horizontal or downsloping >= 1 mm) in >= 2 contiguous leads",
                "T-wave inversions in ischemic territory",
                "Troponin elevated (NSTEMI) or normal (unstable angina)",
                "No ST elevation, no Q waves",
            ],
            "pearl": "Up to 40% of NSTEMIs have a NORMAL ECG. Troponin is essential.",
            "bg": C_PINK_LIGHT, "tag": C_ORANGE
        },
        {
            "num": "24", "name": "Wellens Syndrome (LAD Warning)",
            "criteria": [
                "Biphasic T waves (Type A) or deep symmetric T inversions (Type B) in V2-V3",
                "Little or no chest pain at time of ECG",
                "No significant ST elevation; no Q waves",
                "Normal or slightly elevated troponin",
            ],
            "pearl": "Critical proximal LAD stenosis. High risk of anterior STEMI. URGENT cath.",
            "bg": C_PINK_LIGHT, "tag": C_ORANGE
        },
        {
            "num": "25", "name": "De Winter T Waves (LAD Occlusion Equivalent)",
            "criteria": [
                "Upsloping ST depression >= 1-3 mm at J-point in V1-V6",
                "Followed by tall, peaked, symmetric positive T waves",
                "ST elevation in aVR (1-2 mm)",
                "No classic ST elevation - but represents STEMI equivalent",
            ],
            "pearl": "Seen in ~2% of LAD occlusions. Treat as anterior STEMI. Activate cath lab.",
            "bg": C_PINK_LIGHT, "tag": C_ORANGE
        },
    ],
    "ATRIAL ARRHYTHMIAS": [
        {
            "num": "26", "name": "Atrial Premature Contractions (APCs)",
            "criteria": [
                "Early (premature) P wave with DIFFERENT morphology from sinus P",
                "Followed by normal QRS (usually narrow)",
                "Incomplete compensatory pause (sinus is reset)",
            ],
            "pearl": "Benign. Triggered by caffeine, alcohol, stress, electrolyte imbalance.",
            "bg": C_TEAL_LIGHT, "tag": C_TEAL
        },
        {
            "num": "27", "name": "Atrial Flutter",
            "criteria": [
                "Sawtooth flutter waves (F waves) at 250-350 bpm (classically 300 bpm)",
                "Best seen in II, III, aVF (negative sawtooth) and V1",
                "Regular or regularly irregular ventricular rate (2:1, 3:1, 4:1)",
                "Classic 2:1 flutter: ventricular rate exactly 150 bpm",
            ],
            "pearl": "Any narrow tachycardia at EXACTLY 150 bpm = flutter until proven otherwise.",
            "bg": C_TEAL_LIGHT, "tag": C_TEAL
        },
        {
            "num": "28", "name": "Atrial Fibrillation (AF)",
            "criteria": [
                "No distinct P waves - chaotic fibrillatory baseline (fine or coarse)",
                "IRREGULARLY IRREGULAR RR intervals - absolute hallmark",
                "Narrow QRS (usually) unless aberrant conduction",
                "Ventricular rate 100-170 bpm if uncontrolled",
            ],
            "pearl": "The only 'truly irregular' rhythm. If RR is irregular + wide QRS = AF with BBB or WPW.",
            "bg": C_TEAL_LIGHT, "tag": C_TEAL
        },
    ],
    "VENTRICULAR ARRHYTHMIAS": [
        {
            "num": "29", "name": "Premature Ventricular Contractions (PVCs)",
            "criteria": [
                "Wide QRS >= 120 ms with bizarre morphology",
                "No preceding P wave",
                "T wave DISCORDANT (opposite) to QRS main deflection",
                "Full compensatory pause (P-P marches through undisturbed)",
            ],
            "pearl": "Bigeminy = every other beat PVC; trigeminy = every third. Unifocal vs multifocal.",
            "bg": C_PINK_LIGHT, "tag": C_RED
        },
        {
            "num": "30", "name": "Ventricular Tachycardia (VT)",
            "criteria": [
                "Wide QRS >= 120 ms tachycardia, rate > 100 bpm (usually 140-220 bpm)",
                "AV dissociation (P waves march independently, SLOWER than QRS) - pathognomonic",
                "Fusion beats (hybrid P+VT morphology) - confirms AV dissociation",
                "Capture beats (narrow QRS amidst wide) - confirms AV dissociation",
                "QRS concordance in precordial leads (all positive or all negative V1-V6)",
            ],
            "pearl": "ALWAYS assume wide-complex tachycardia = VT. NEVER give verapamil empirically.",
            "bg": C_PINK_LIGHT, "tag": C_DARK_RED
        },
        {
            "num": "31", "name": "Ventricular Fibrillation (VF)",
            "criteria": [
                "Completely chaotic, irregular, high-frequency undulations",
                "No recognisable QRS, ST, T waves",
                "Rate: indeterminate (truly chaotic)",
            ],
            "pearl": "Cardiac arrest. IMMEDIATE unsynchronised defibrillation. CPR concurrently.",
            "bg": C_PINK_LIGHT, "tag": C_DARK_RED
        },
        {
            "num": "32", "name": "Torsades de Pointes (TdP)",
            "criteria": [
                "Polymorphic VT with twisting QRS axis around isoelectric baseline",
                "QRS amplitude oscillates - 'twisting of the points'",
                "Occurs in context of PROLONGED QT interval",
                "Rate 150-250 bpm; may self-terminate or degenerate to VF",
            ],
            "pearl": "Treat with IV Magnesium FIRST, even if Mg level is normal. Correct K+ simultaneously.",
            "bg": C_PINK_LIGHT, "tag": C_DARK_RED
        },
    ],
    "SVT / ACCESSORY PATHWAYS": [
        {
            "num": "33", "name": "AVNRT (Most Common SVT)",
            "criteria": [
                "Narrow QRS tachycardia, rate 150-250 bpm",
                "P waves buried IN or immediately after QRS (retrograde P)",
                "Pseudo-S wave in inferior leads; pseudo-R' in V1",
                "Regular rhythm; abrupt onset and termination",
                "RP interval < 70 ms",
            ],
            "pearl": "Vagal manoeuvres or adenosine terminates it. Reentry within the AV node.",
            "bg": C_PURPLE_LIGHT, "tag": C_PURPLE
        },
        {
            "num": "34", "name": "WPW Syndrome (Resting ECG)",
            "criteria": [
                "Short PR < 120 ms (pre-excitation bypasses AV node)",
                "Delta wave (slurred upstroke of QRS) - direct ventricular pre-excitation",
                "Widened QRS > 120 ms (fusion: normal + accessory pathway activation)",
                "Secondary ST-T changes (discordant to delta/QRS)",
            ],
            "pearl": "WPW + AF = life-threatening (rate >300 bpm). NEVER give digoxin or verapamil.",
            "bg": C_PURPLE_LIGHT, "tag": C_PURPLE
        },
        {
            "num": "35", "name": "Orthodromic AVRT (WPW tachycardia)",
            "criteria": [
                "Narrow QRS tachycardia (down AV node, up accessory pathway)",
                "Retrograde P wave AFTER QRS (RP > 70 ms, separated from QRS)",
                "Regular rate 150-250 bpm",
                "Rate 95% of WPW tachycardias",
            ],
            "pearl": "Narrow QRS in WPW tachycardia. Adenosine often terminates it (terminates AV conduction).",
            "bg": C_PURPLE_LIGHT, "tag": C_PURPLE
        },
    ],
    "ELECTROLYTE DISTURBANCES": [
        {
            "num": "36", "name": "Hyperkalemia",
            "criteria": [
                "K+ 5.5-6.5: Tall narrow symmetric PEAKED (tented) T waves - FIRST sign",
                "K+ 6.5-7.5: PR prolongation, P wave flattening/disappearance",
                "K+ 7.0-7.5: QRS widening begins",
                "K+ 7.5-8.0: Markedly wide bizarre QRS",
                "K+ > 8.0: Sine wave pattern -> VF -> asystole",
            ],
            "pearl": "Peaked T + wide QRS + no P = emergency. IV calcium gluconate IMMEDIATELY.",
            "bg": C_YELLOW_LIGHT, "tag": C_ORANGE
        },
        {
            "num": "37", "name": "Hypokalemia",
            "criteria": [
                "Flat or inverted T waves",
                "Prominent U waves (U wave taller than T wave in V2-V3) - HALLMARK",
                "Apparent QT prolongation (actually QU prolongation)",
                "ST depression",
            ],
            "pearl": "Predisposes to Torsades de Pointes and digitalis toxicity. Replete K+ to > 4.0 mEq/L.",
            "bg": C_YELLOW_LIGHT, "tag": C_AMBER
        },
        {
            "num": "38", "name": "Hypercalcemia",
            "criteria": [
                "Shortened QT interval (abbreviated ST segment)",
                "QTc < 360 ms",
                "Osborn waves may appear in severe cases",
                "T wave shape relatively normal",
            ],
            "pearl": "Short QT = high calcium. Classic in hyperparathyroidism, malignancy.",
            "bg": C_YELLOW_LIGHT, "tag": C_AMBER
        },
        {
            "num": "39", "name": "Hypocalcemia",
            "criteria": [
                "Prolonged QT interval (specifically prolonged ST SEGMENT)",
                "T wave shape itself remains relatively normal",
                "QTc prolonged (> 450 ms men, > 460 ms women)",
            ],
            "pearl": "QT is long but T-wave shape is normal - unlike drug QT where T wave is deformed.",
            "bg": C_YELLOW_LIGHT, "tag": C_AMBER
        },
    ],
    "SPECIAL SYNDROMES": [
        {
            "num": "40", "name": "Acute Pericarditis",
            "criteria": [
                "Diffuse CONCAVE (saddle-shaped) ST elevation in ALMOST ALL leads",
                "ST depression only in aVR and V1 (+ PR elevation in aVR)",
                "PR DEPRESSION - pathognomonic sign",
                "NO reciprocal ST depression (unlike MI)",
                "NO Q waves",
            ],
            "pearl": "Saddle shape + PR depression + no reciprocal changes = pericarditis, NOT STEMI.",
            "bg": C_TEAL_LIGHT, "tag": C_TEAL
        },
        {
            "num": "41", "name": "Cardiac Tamponade",
            "criteria": [
                "Sinus tachycardia (compensatory)",
                "LOW QRS voltage (< 5 mm all limb leads; < 10 mm all precordial leads)",
                "ELECTRICAL ALTERNANS - beat-to-beat alternation in QRS height/axis",
                "All three together = highly specific for tamponade",
            ],
            "pearl": "Alternans is caused by the heart swinging inside the pericardial effusion.",
            "bg": C_TEAL_LIGHT, "tag": C_TEAL
        },
        {
            "num": "42", "name": "Pulmonary Embolism",
            "criteria": [
                "Sinus tachycardia - most common (nonspecific)",
                "S1Q3T3: S wave in I + Q wave in III + T inversion in III",
                "New RBBB (acute RV pressure overload)",
                "Right axis deviation; T inversions V1-V4",
                "Sinus tachycardia may be the ONLY finding",
            ],
            "pearl": "S1Q3T3 is specific but seen in only ~20%. Sinus tachycardia is the most common ECG sign.",
            "bg": C_TEAL_LIGHT, "tag": C_TEAL
        },
        {
            "num": "43", "name": "Brugada Syndrome",
            "criteria": [
                "Type 1 (diagnostic): >= 2 mm COVED (dome-shape) ST elevation in V1-V2",
                "RBBB-like pattern in V1",
                "Spontaneous or drug-provoked (flecainide challenge)",
                "Type 2: Saddle-back pattern - NOT diagnostic without provocation",
            ],
            "pearl": "SCN5A Na+ channel mutation. Causes VF during sleep/fever. ICD is treatment.",
            "bg": C_PINK_LIGHT, "tag": C_RED
        },
        {
            "num": "44", "name": "Long QT Syndrome",
            "criteria": [
                "QTc >= 450 ms (men) or >= 460 ms (women)",
                "LQT1: Broad-based wide T waves; triggered by exercise/swimming",
                "LQT2: Bifid (notched) T waves; triggered by emotion/sudden sounds",
                "LQT3: Late-onset narrow T wave; events during sleep",
            ],
            "pearl": "Risk = Torsades de Pointes -> VF -> sudden death. Avoid QT-prolonging drugs.",
            "bg": C_PINK_LIGHT, "tag": C_RED
        },
        {
            "num": "45", "name": "Hypothermia (Osborn Waves)",
            "criteria": [
                "Osborn (J) waves: positive convex hump at J-point (after QRS end)",
                "Sinus bradycardia",
                "All intervals prolonged (PR, QRS, QT)",
                "J wave size correlates with degree of hypothermia",
            ],
            "pearl": "Core temp < 35C. J wave amplitude increases as temperature drops.",
            "bg": C_BLUE_LIGHT, "tag": C_BLUE
        },
        {
            "num": "46", "name": "Subarachnoid Hemorrhage (SAH)",
            "criteria": [
                "Deep, wide, symmetric T-wave inversions in multiple leads ('giant T waves')",
                "Prolonged QT interval",
                "May mimic anterior STEMI",
                "Sinus bradycardia common",
            ],
            "pearl": "CVA T-waves. Mediated by catecholamine surge. Check for headache ('thunderclap').",
            "bg": C_BLUE_LIGHT, "tag": C_BLUE
        },
        {
            "num": "47", "name": "Digoxin Effect/Toxicity",
            "criteria": [
                "Effect: Scooped ST-T ('reverse tick' sign); shortened QT; PR prolongation",
                "Toxicity: Virtually any arrhythmia possible",
                "Classic toxic rhythms: PVCs, AV block + junctional tachycardia, AF with regularised rhythm",
                "Bidirectional VT = pathognomonic for digoxin toxicity",
            ],
            "pearl": "Digoxin EFFECT (therapeutic) is not toxicity. Toxicity requires clinical correlation.",
            "bg": C_YELLOW_LIGHT, "tag": C_AMBER
        },
        {
            "num": "48", "name": "Tricyclic Antidepressant Toxicity",
            "criteria": [
                "QRS widening >= 100 ms (severe >= 160 ms)",
                "Right axis deviation of terminal QRS forces",
                "Tall R wave in aVR (> 3 mm); R:S in aVR > 0.7",
                "QT prolongation; sinus tachycardia",
            ],
            "pearl": "QRS > 100 ms = seizure risk. QRS > 160 ms = VT risk. Na bicarb is antidote.",
            "bg": C_PINK_LIGHT, "tag": C_RED
        },
    ]
}

# ── Build document ────────────────────────────────────────────────────────────
def build_pdf():
    doc = SimpleDocTemplate(
        OUTPUT,
        pagesize=A4,
        leftMargin=MARGIN,
        rightMargin=MARGIN,
        topMargin=22 * mm,
        bottomMargin=22 * mm,
        title="ECG Reference Card - 48 Abnormalities",
        author="Orris Medical AI",
        subject="ECG Diagnostic Criteria Reference",
    )

    story = []

    # ── COVER PAGE ────────────────────────────────────────────────────────────
    story.append(Spacer(1, 30 * mm))

    cover_title_t = Table(
        [[Paragraph("ECG Reference Card", S['cover_title'])]],
        colWidths=[DOC_W]
    )
    cover_title_t.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), C_BLUE_DARK),
        ('TOPPADDING', (0, 0), (-1, -1), 12),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
        ('LEFTPADDING', (0, 0), (-1, -1), 8),
        ('RIGHTPADDING', (0, 0), (-1, -1), 8),
    ]))
    story.append(cover_title_t)
    story.append(Spacer(1, 4 * mm))

    subtitle_t = Table(
        [[Paragraph("48 ECG Abnormalities with Diagnostic Criteria", S['cover_sub'])]],
        colWidths=[DOC_W]
    )
    subtitle_t.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), C_BLUE),
        ('TOPPADDING', (0, 0), (-1, -1), 8),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
    ]))
    story.append(subtitle_t)
    story.append(Spacer(1, 6 * mm))

    desc_t = Table([[Paragraph(
        "MBBS-Level Quick Reference  |  From Sinus Rhythms to Complex Arrhythmias<br/>"
        "Ischemia &amp; Infarction  |  Electrolyte Disorders  |  Special Syndromes<br/>"
        "<br/>Based on: Harrison's Principles 22E  |  Goldman-Cecil Medicine  |  Medical Physiology (Boron &amp; Boulpaep)",
        S['cover_source']
    )]], colWidths=[DOC_W])
    desc_t.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), C_BLUE_DARK),
        ('TOPPADDING', (0, 0), (-1, -1), 10),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 10),
    ]))
    story.append(desc_t)

    story.append(Spacer(1, 20 * mm))

    # Colour legend on cover
    legend_data = [
        [
            Paragraph("<b>Sinus / Normal</b>", ParagraphStyle('lg', fontName='Helvetica-Bold', fontSize=8, textColor=white)),
            Paragraph("<b>Atrial Abnormalities</b>", ParagraphStyle('lg', fontName='Helvetica-Bold', fontSize=8, textColor=white)),
            Paragraph("<b>Hypertrophy</b>", ParagraphStyle('lg', fontName='Helvetica-Bold', fontSize=8, textColor=white)),
            Paragraph("<b>AV Blocks</b>", ParagraphStyle('lg', fontName='Helvetica-Bold', fontSize=8, textColor=white)),
        ],
        [
            Paragraph("<b>Bundle Branch</b>", ParagraphStyle('lg', fontName='Helvetica-Bold', fontSize=8, textColor=white)),
            Paragraph("<b>Ischemia / MI</b>", ParagraphStyle('lg', fontName='Helvetica-Bold', fontSize=8, textColor=white)),
            Paragraph("<b>Arrhythmias</b>", ParagraphStyle('lg', fontName='Helvetica-Bold', fontSize=8, textColor=white)),
            Paragraph("<b>Special Syndromes</b>", ParagraphStyle('lg', fontName='Helvetica-Bold', fontSize=8, textColor=white)),
        ]
    ]
    legend_t = Table(legend_data, colWidths=[DOC_W / 4] * 4)
    legend_t.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (0, 0), C_GREEN),
        ('BACKGROUND', (1, 0), (1, 0), C_TEAL),
        ('BACKGROUND', (2, 0), (2, 0), C_PURPLE),
        ('BACKGROUND', (3, 0), (3, 0), C_AMBER),
        ('BACKGROUND', (0, 1), (0, 1), C_BLUE),
        ('BACKGROUND', (1, 1), (1, 1), C_DARK_RED),
        ('BACKGROUND', (2, 1), (2, 1), C_RED),
        ('BACKGROUND', (3, 1), (3, 1), C_TEAL),
        ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ('TOPPADDING', (0, 0), (-1, -1), 7),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 7),
        ('INNERGRID', (0, 0), (-1, -1), 0.5, white),
        ('BOX', (0, 0), (-1, -1), 0.5, white),
    ]))
    story.append(legend_t)

    story.append(Spacer(1, 10 * mm))

    # Normal values box on cover
    nv_data = [
        [Paragraph("<b>NORMAL ECG VALUES QUICK REFERENCE</b>", ParagraphStyle(
            'nvh', fontName='Helvetica-Bold', fontSize=9, textColor=white, alignment=TA_CENTER
        ))],
        [Table([
            [
                Paragraph("<b>Heart Rate</b><br/>60-100 bpm", S['normal_body']),
                Paragraph("<b>PR Interval</b><br/>120-200 ms (3-5 small boxes)", S['normal_body']),
                Paragraph("<b>QRS Duration</b><br/>70-110 ms (&lt; 3 small boxes)", S['normal_body']),
            ],
            [
                Paragraph("<b>QTc (Bazett)</b><br/>Men: &lt;450 ms | Women: &lt;460 ms", S['normal_body']),
                Paragraph("<b>QRS Axis</b><br/>-30 to +90 degrees", S['normal_body']),
                Paragraph("<b>ECG Paper</b><br/>1 small box = 0.04 s / 0.1 mV", S['normal_body']),
            ],
        ], colWidths=[DOC_W / 3] * 3)]
    ]
    nv_outer = Table(nv_data, colWidths=[DOC_W])
    nv_outer.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), C_BLUE_DARK),
        ('BACKGROUND', (0, 1), (-1, 1), HexColor("#EBF5FB")),
        ('TOPPADDING', (0, 0), (-1, 0), 6),
        ('BOTTOMPADDING', (0, 0), (-1, 0), 6),
        ('TOPPADDING', (0, 1), (-1, 1), 4),
        ('BOTTOMPADDING', (0, 1), (-1, 1), 4),
        ('BOX', (0, 0), (-1, -1), 0.8, C_BLUE_DARK),
    ]))
    story.append(nv_outer)

    story.append(PageBreak())

    # ── CONDITION PAGES ───────────────────────────────────────────────────────
    section_colors = {
        "SINUS RHYTHMS": C_GREEN,
        "ATRIAL ABNORMALITIES": C_TEAL,
        "CHAMBER HYPERTROPHY": C_PURPLE,
        "AV CONDUCTION BLOCKS": C_AMBER,
        "BUNDLE BRANCH BLOCKS": C_BLUE_DARK,
        "ISCHEMIA AND INFARCTION": C_DARK_RED,
        "ATRIAL ARRHYTHMIAS": C_TEAL,
        "VENTRICULAR ARRHYTHMIAS": C_RED,
        "SVT / ACCESSORY PATHWAYS": C_PURPLE,
        "ELECTROLYTE DISTURBANCES": C_ORANGE,
        "SPECIAL SYNDROMES": C_BLUE_DARK,
    }

    section_icons = {
        "SINUS RHYTHMS": "01",
        "ATRIAL ABNORMALITIES": "02",
        "CHAMBER HYPERTROPHY": "03",
        "AV CONDUCTION BLOCKS": "04",
        "BUNDLE BRANCH BLOCKS": "05",
        "ISCHEMIA AND INFARCTION": "06",
        "ATRIAL ARRHYTHMIAS": "07",
        "VENTRICULAR ARRHYTHMIAS": "08",
        "SVT / ACCESSORY PATHWAYS": "09",
        "ELECTROLYTE DISTURBANCES": "10",
        "SPECIAL SYNDROMES": "11",
    }

    for section_name, conditions in CONDITIONS.items():
        sec_color = section_colors.get(section_name, C_BLUE_DARK)
        story.append(section_banner(
            f"SECTION {section_icons.get(section_name, '')}  |  {section_name}",
            color=sec_color
        ))
        story.append(Spacer(1, 2 * mm))

        # Render cards in two-column pairs
        cards = []
        for cond in conditions:
            card = mini_card(
                num=cond["num"],
                name=cond["name"],
                criteria_lines=cond["criteria"],
                key_pearl=cond["pearl"],
                bg=cond.get("bg", C_BLUE_LIGHT),
                tag_color=cond.get("tag", C_BLUE)
            )
            cards.append(card)

        # Pair up cards into two-column rows
        i = 0
        while i < len(cards):
            if i + 1 < len(cards):
                row_t = Table(
                    [[cards[i], Spacer(3 * mm, 1), cards[i + 1]]],
                    colWidths=[(DOC_W - 3 * mm) / 2, 3 * mm, (DOC_W - 3 * mm) / 2]
                )
                row_t.setStyle(TableStyle([
                    ('VALIGN', (0, 0), (-1, -1), 'TOP'),
                    ('LEFTPADDING', (0, 0), (-1, -1), 0),
                    ('RIGHTPADDING', (0, 0), (-1, -1), 0),
                    ('TOPPADDING', (0, 0), (-1, -1), 0),
                    ('BOTTOMPADDING', (0, 0), (-1, -1), 2),
                ]))
                story.append(row_t)
                i += 2
            else:
                # Odd card - full width
                full_card = condition_card(
                    num=conditions[i]["num"],
                    name=conditions[i]["name"],
                    criteria_lines=conditions[i]["criteria"],
                    key_pearl=conditions[i]["pearl"],
                    bg=conditions[i].get("bg", C_BLUE_LIGHT),
                    tag_color=conditions[i].get("tag", C_BLUE)
                )
                story.append(full_card)
                story.append(Spacer(1, 1.5 * mm))
                i += 1

        story.append(Spacer(1, 4 * mm))

    # ── COMPARISON TABLES PAGE ────────────────────────────────────────────────
    story.append(PageBreak())
    story.append(section_banner("CRITICAL COMPARISON TABLES", color=C_BLUE_DARK))
    story.append(Spacer(1, 3 * mm))

    # Table 1: STEMI localisation
    story.append(Paragraph("STEMI Localisation by Lead Territory", S['normal_head']))
    t1 = comparison_table(
        headers=["MI Territory", "ST Elevation in", "Reciprocal Depression", "Culprit Artery"],
        rows=[
            ["Anterior", "V1-V4", "II, III, aVF", "LAD (mid-distal)"],
            ["Septal", "V1-V2", "None typically", "LAD (septal branches)"],
            ["Anterolateral", "V1-V6, I, aVL", "II, III, aVF", "Proximal LAD"],
            ["Lateral", "I, aVL, V5-V6", "V1-V3", "LCx or Diagonal"],
            ["Inferior", "II, III, aVF", "I, aVL", "RCA (80%) or LCx (20%)"],
            ["Posterior", "Tall R + ST depression V1-V3", "None (V1-V3 shows depression)", "RCA or LCx"],
            ["Right Ventricle", "V3R, V4R elevation", "—", "Proximal RCA"],
        ]
    )
    story.append(t1)
    story.append(Spacer(1, 4 * mm))

    # Table 2: AV blocks
    story.append(Paragraph("AV Block Differentiation", S['normal_head']))
    t2 = comparison_table(
        headers=["Block Type", "PR Interval", "Dropped QRS?", "Location", "Prognosis"],
        rows=[
            ["1st Degree", "Fixed, > 200 ms", "Never", "AV node", "Benign"],
            ["2nd Degree - Mobitz I", "Progressively lengthens", "Yes (after longest PR)", "AV node", "Usually benign"],
            ["2nd Degree - Mobitz II", "Fixed, constant", "Yes (sudden, no warning)", "His/Bundle branch", "Dangerous - pace"],
            ["3rd Degree (Complete)", "No relationship (AV dissociation)", "Essentially all", "Anywhere", "Emergency - pace"],
        ]
    )
    story.append(t2)
    story.append(Spacer(1, 4 * mm))

    # Table 3: Pericarditis vs STEMI
    story.append(Paragraph("Pericarditis vs STEMI - Key Differentiators", S['normal_head']))
    t3 = comparison_table(
        headers=["Feature", "Acute Pericarditis", "STEMI"],
        rows=[
            ["ST elevation shape", "Concave/saddle-shaped (upward concave)", "Convex/tombstone (upward dome)"],
            ["Distribution", "Diffuse (almost ALL leads)", "Regional/territorial (contiguous leads)"],
            ["Reciprocal ST depression", "ABSENT (only aVR/V1 show depression)", "PRESENT in opposite territory"],
            ["PR changes", "PR DEPRESSION (pathognomonic) + PR elevation in aVR", "None"],
            ["Q waves", "Absent", "Develop (mark necrosis)"],
            ["Evolution", "4 staged over days-weeks", "Hours-to-days"],
        ]
    )
    story.append(t3)
    story.append(Spacer(1, 4 * mm))

    # Table 4: Wide-complex tachycardia
    story.append(Paragraph("Wide-Complex Tachycardia: VT vs SVT with Aberrancy", S['normal_head']))
    t4 = comparison_table(
        headers=["Feature", "Favours VT", "Favours SVT + Aberrancy"],
        rows=[
            ["AV dissociation", "YES - pathognomonic for VT", "Absent"],
            ["Fusion/capture beats", "YES - confirm VT", "Absent"],
            ["QRS concordance (V1-V6)", "All positive or all negative = VT", "Mixed polarity"],
            ["QRS morphology", "Unusual/bizarre (does not fit RBBB or LBBB)", "Typical RBBB or LBBB pattern"],
            ["History", "Structural heart disease, prior MI, cardiomyopathy", "Young patient, prior SVT episodes"],
            ["Default rule", "ALWAYS assume VT first!", "Only after VT excluded"],
        ]
    )
    story.append(t4)
    story.append(Spacer(1, 4 * mm))

    # Table 5: Electrolytes and QT
    story.append(Paragraph("Electrolyte Effects on ECG", S['normal_head']))
    t5 = comparison_table(
        headers=["Electrolyte", "QT Effect", "T Wave", "P Wave/QRS", "Key Sign"],
        rows=[
            ["Hyperkalemia (mild)", "Short/normal", "Peaked, tented, narrow, symmetric", "Normal", "Tent-shaped T"],
            ["Hyperkalemia (severe)", "QRS widens/absorbs QT", "Disappears into QRS", "P flattens, disappears; QRS widens", "Sine wave pattern"],
            ["Hypokalemia", "Prolonged (QU)", "Flat/inverted", "Normal", "Prominent U wave > T wave"],
            ["Hypercalcemia", "SHORTENED", "Normal or early takeoff", "Normal", "Short QTc < 360 ms"],
            ["Hypocalcemia", "PROLONGED (ST lengthened)", "Normal shape, just delayed onset", "Normal", "Long QTc, normal T shape"],
            ["Hypomagnesemia", "Prolonged", "Flat, may invert", "Normal", "Predisposes to TdP like hypoK+"],
        ]
    )
    story.append(t5)

    # ── LIFE-THREATENING DIAGNOSES PAGE ──────────────────────────────────────
    story.append(PageBreak())
    story.append(section_banner(
        "THE 5 IMMEDIATELY LIFE-THREATENING ECG DIAGNOSES",
        color=C_DARK_RED
    ))
    story.append(Spacer(1, 3 * mm))

    life_data = [
        [
            Paragraph("<b>#</b>", S['table_head']),
            Paragraph("<b>Diagnosis</b>", S['table_head']),
            Paragraph("<b>ECG Key Finding</b>", S['table_head']),
            Paragraph("<b>Immediate Action</b>", S['table_head']),
        ],
        ["1", "STEMI",
         "Regional convex ST elevation in contiguous leads + reciprocal changes",
         "Activate cath lab NOW. Aspirin + heparin + P2Y12 inhibitor. PCI within 90 min."],
        ["2", "Ventricular Fibrillation",
         "Chaotic irregular undulations; no recognisable QRS/P/T",
         "CPR + immediate unsynchronised DEFIBRILLATION 200J biphasic. Adrenaline 1mg IV."],
        ["3", "Ventricular Tachycardia (haemodynamically unstable)",
         "Wide QRS > 120 ms tachycardia; AV dissociation; fusion/capture beats",
         "Synchronised DC cardioversion. If pulseless: defibrillate. Amiodarone 300mg IV."],
        ["4", "Complete Heart Block",
         "AV dissociation (P rate > QRS rate); wide slow escape rhythm",
         "Atropine 0.5-1mg IV (if narrow QRS escape). Transcutaneous/transvenous pacing urgently."],
        ["5", "Severe Hyperkalemia (> 7.5 mEq/L)",
         "Wide QRS + absent P waves + sine wave pattern",
         "IV Calcium gluconate 10 mL 10% IMMEDIATELY. Insulin-dextrose. Bicarbonate. Dialysis."],
    ]

    life_t = Table(life_data,
                   colWidths=[DOC_W * 0.04, DOC_W * 0.15, DOC_W * 0.40, DOC_W * 0.41],
                   repeatRows=1)
    life_style = [
        ('BACKGROUND', (0, 0), (-1, 0), C_DARK_RED),
        ('TEXTCOLOR', (0, 0), (-1, 0), white),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, 0), 8),
        ('FONTSIZE', (0, 1), (-1, -1), 7.5),
        ('ALIGN', (0, 0), (0, -1), 'CENTER'),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('LEFTPADDING', (0, 0), (-1, -1), 5),
        ('RIGHTPADDING', (0, 0), (-1, -1), 5),
        ('GRID', (0, 0), (-1, -1), 0.5, C_GREY_MID),
        ('BACKGROUND', (0, 1), (-1, 1), HexColor("#FDEDEC")),
        ('BACKGROUND', (0, 2), (-1, 2), HexColor("#FEF9E7")),
        ('BACKGROUND', (0, 3), (-1, 3), HexColor("#FDEDEC")),
        ('BACKGROUND', (0, 4), (-1, 4), HexColor("#FEF9E7")),
        ('BACKGROUND', (0, 5), (-1, 5), HexColor("#FDEDEC")),
        ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
        ('FONTNAME', (1, 1), (1, -1), 'Helvetica-Bold'),
        ('TEXTCOLOR', (1, 1), (1, -1), C_DARK_RED),
    ]
    life_t.setStyle(TableStyle(life_style))
    story.append(life_t)

    story.append(Spacer(1, 5 * mm))
    story.append(section_banner("SYSTEMATIC ECG READING APPROACH (Harrison's 22E - 14 Parameters)", color=C_BLUE_DARK))
    story.append(Spacer(1, 3 * mm))

    steps = [
        ("1", "Calibration & Technical Quality", "Confirm 25 mm/sec speed, 10 mm/mV gain. Check lead placement. Look for artefacts."),
        ("2", "Rhythm", "Regular or irregular? Any pattern to irregularity? Identify all wave types."),
        ("3", "Heart Rate", "300 / large boxes between RRs (regular). Or count QRS in 10 sec x 6 (irregular)."),
        ("4", "PR Interval / AV Conduction", "120-200 ms. Fixed? Lengthening? Every P followed by QRS?"),
        ("5", "QRS Duration", "< 120 ms = narrow. >= 120 ms = wide (BBB, VT, paced, hyperK+, toxic)."),
        ("6", "QT / QTc", "QTc = QT / sqrt(RR in seconds). Men < 450 ms; Women < 460 ms."),
        ("7", "Mean QRS Axis", "Check leads I and aVF: +/+ = normal; +/- = LAD; -/+ = RAD; -/- = extreme."),
        ("8", "P Wave Morphology", "Upright I, II, aVF. Peaked? Broad/notched? Biphasic V1?"),
        ("9", "QRS Voltages", "LVH criteria? Low voltage (< 5 mm limb, < 10 mm precordial)?"),
        ("10", "R-Wave Progression", "R grows from V1 to V5. Transition at V3-V4. Absent = anterior pathology."),
        ("11", "Abnormal Q Waves", "Width >= 40 ms OR depth >= 25% R wave height = pathological."),
        ("12", "ST Segments", "Elevation? Depression? Shape? Regional or diffuse? Measure at J-point."),
        ("13", "T Waves", "Inverted (ischaemia, strain, BBB)? Peaked (hyperK+)? Bifid (LQT2)?"),
        ("14", "U Waves", "Prominent U > T wave = hypoK+. Inverted U wave = ischaemia."),
    ]

    step_data = [[
        Paragraph(f"<b>{s}</b>", ParagraphStyle('sn', fontName='Helvetica-Bold', fontSize=8, textColor=white, alignment=TA_CENTER)),
        Paragraph(f"<b>{n}</b>", ParagraphStyle('sc', fontName='Helvetica-Bold', fontSize=7.5, textColor=C_BLUE_DARK)),
        Paragraph(desc, S['table_cell'])
    ] for s, n, desc in steps]

    # Add header
    step_data.insert(0, [
        Paragraph("<b>#</b>", S['table_head']),
        Paragraph("<b>Parameter</b>", S['table_head']),
        Paragraph("<b>What to Look For</b>", S['table_head']),
    ])

    step_t = Table(step_data,
                   colWidths=[DOC_W * 0.05, DOC_W * 0.25, DOC_W * 0.70],
                   repeatRows=1)
    step_t.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), C_BLUE_DARK),
        ('TEXTCOLOR', (0, 0), (-1, 0), white),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, 0), 8),
        ('FONTSIZE', (0, 1), (-1, -1), 7.5),
        ('BACKGROUND', (0, 1), (0, -1), C_BLUE),
        ('TEXTCOLOR', (0, 1), (0, -1), white),
        ('ALIGN', (0, 0), (0, -1), 'CENTER'),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ('TOPPADDING', (0, 0), (-1, -1), 4),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
        ('LEFTPADDING', (0, 0), (-1, -1), 4),
        ('RIGHTPADDING', (0, 0), (-1, -1), 4),
        ('ROWBACKGROUNDS', (1, 1), (-1, -1), [white, C_GREY_LIGHT]),
        ('GRID', (0, 0), (-1, -1), 0.3, C_GREY_MID),
    ]))
    story.append(step_t)

    # ── BUILD ─────────────────────────────────────────────────────────────────
    doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
    print(f"PDF saved to: {OUTPUT}")
    return OUTPUT


if __name__ == "__main__":
    build_pdf()
Running Command

cd /home/daytona/workspace/ecg-reference && python3 build_ecg_reference.py 2>&1

Editing File

~/ecg-reference/build_ecg_reference.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ecg-reference/build_ecg_reference.py'
Editing File

~/ecg-reference/build_ecg_reference.py

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

cd /home/daytona/workspace/ecg-reference && python3 build_ecg_reference.py 2>&1

Running Command

ls -lh /home/daytona/workspace/ecg-reference/ECG_Reference_Card.pdf && python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/ecg-reference/ECG_Reference_Card.pdf') print(f'Pages: {len(r.pages)}') for i, p in enumerate(r.pages): print(f' Page {i+1}: {len(p.extract_text())} chars') "

9 pages, all populated. Your PDF reference card is ready:

✅ ECG Reference Card - Generated

ECG Reference Card

PDF Document · PDF


What's inside (9 pages, A4):

PageContent
1 - CoverTitle, colour-coded legend, Normal ECG Values quick box (heart rate, PR, QRS, QTc, axis, paper calibration)
2 - Sinus + AtrialCards 01-06: Normal sinus rhythm, sinus brady/tachy, sinus arrhythmia, P-pulmonale, P-mitrale
3 - Hypertrophy + AV BlocksCards 07-12: LVH, RVH, all 4 degrees of AV block (1st, Mobitz I, Mobitz II, complete)
4 - Bundle Branch BlocksCards 13-16: RBBB, LBBB, LAFB, LPFB with criteria
5 - Ischemia Part 1Cards 17-21: Hyperacute T, Anterior STEMI, Inferior STEMI, Lateral STEMI, Posterior STEMI
6 - Ischemia Part 2 + ArrhythmiasCards 22-28: RV infarct, NSTEMI, Wellens, De Winter; Atrial arrhythmias (APCs, flutter, AF)
7 - Ventricular + SVT + ElectrolytesCards 29-39: PVCs, VT, VF, TdP, AVNRT, WPW, hyperkalemia, hypokalemia, Ca disorders
8 - Special SyndromesCards 40-48: Pericarditis, tamponade, PE, Brugada, Long QT, hypothermia, SAH, digoxin, TCA toxicity
9 - Critical Tables + Systematic ApproachSTEMI localisation table; AV block comparison; Pericarditis vs STEMI; VT vs SVT; Electrolyte effects; Top 5 life-threatening diagnoses; Harrison's 14-parameter systematic reading guide
Each condition card uses colour coding by category and includes bullet-point diagnostic criteria + a gold clinical pearl in red italic text.
This is a shared conversation. Sign in to Orris to start your own chat.