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.
| Section | Content |
|---|---|
| Part 1 | Cardiac electrophysiology - why the heart generates electricity, action potentials, conduction system (SA node → AV node → His-Purkinje) |
| Part 2 | ECG paper - the grid, time/voltage calibration, 3 methods to calculate heart rate |
| Part 3 | All waves explained: P, PR, QRS, ST, T, QT, U - with normal values, what each represents, when they go abnormal |
| Part 4 | 12-lead ECG - Einthoven's triangle, all 6 limb leads, all 6 precordial leads, anatomical territories |
| Part 5 | QRS axis - quick determination using leads I and aVF |
| Part 6 | Systematic reading approach (PRISCE method) |
| Part 7 | All 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 8 | Expert ECG read-through demonstrated step-by-step |
| Part 9 | Quick reference table of 20+ diagnoses |
| Part 10 | Expert mindset and study strategy |
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
| Section | Content | Diagrams Included |
|---|---|---|
| Part 1 | Cardiac action potential, conduction system anatomy, blood supply | - |
| Part 2 | ECG paper grid (time/voltage), all 3 HR calculation methods, 12-lead format | ECG grid with all waves labeled (Goldman-Cecil) |
| Part 3 | All waves explained wave-by-wave with mechanism and normal values; QRS two-phase genesis | Ventricular depolarization vectors V1 vs V6 (Harrison's) |
| Part 4 | All 12 leads - positions, what they view, Einthoven's triangle, hexaxial axis diagram | Lead placement on chest (Harrison's); Frontal + horizontal plane leads; Hexaxial axis wheel; Normal 12-lead ECG |
| Part 5 | 14-parameter systematic reading approach | - |
| Part 6.1 | Sinus rhythms - NSR, bradycardia, tachycardia, arrhythmia with ASCII ECG | ASCII diagrams |
| Part 6.2 | Atrial enlargement - P-pulmonale, P-mitrale | P-wave comparison diagram (Harrison's) |
| Part 6.3 | LVH and RVH with voltage criteria | LVH/RVH QRS diagram (Harrison's) |
| Part 6.4 | All 4 degrees of AV block with ASCII ECGs and criteria | ASCII ECG patterns |
| Part 6.5 | RBBB, LBBB, LAFB, LPFB - mechanism, criteria, clinical traps | RBBB/LBBB comparison V1 vs V6 (Harrison's) |
| Part 6.6 | Ischemia mechanism, STEMI evolution table, Wellens, Q waves, localization table | ST depression vs elevation mechanism; anterior STEMI sequence; inferior STEMI sequence; Wellens T-waves |
| Part 6.7 | Atrial flutter (sawtooth), AF (irregularly irregular) with ASCII ECGs | ASCII ECG patterns |
| Part 6.8 | PVCs, VT (AV dissociation, fusion beats), VF, Torsades de Pointes | - |
| Part 6.9 | AVNRT, WPW (orthodromic/antidromic), WPW+AF danger | - |
| Part 6.11 | Pericarditis vs STEMI comparison table | - |
| Part 6.12 | Hyperkalemia (all stages), hypokalemia, hypothermia Osborn waves, Ca²⁺ disorders | Hyperkalemia ECG sequence; Hypokalemia U-waves; Osborn wave; Ca²⁺ QT changes |
| Part 6.13 | Drug effects (digoxin, TCAs, antiarrhythmics) | TCA overdose ECG |
| Part 6.14 | Cardiac tamponade | Tamponade triad: tachycardia + low voltage + alternans |
| Part 6.15 | Pulmonary embolism S1Q3T3 | - |
| Part 6.16 | Brugada syndrome | - |
| Part 6.17 | SAH and neurological ECG changes | CVA T-waves (SAH) |
| Part 7 | 40+ condition quick reference table | - |
| Part 8 | Expert mindset, 14-parameter approach, 5 life-threatening diagnoses | - |
Generate a PDF reference card summarizing all 40+ ECG abnormalities with their diagnostic criteria
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace not found"
mkdir -p /home/daytona/workspace/ecg-reference && echo "created"
~/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>•</bullet> {line}" for line in criteria_lines
)
crit_cell = Paragraph(criteria_text, S['card_body'])
pearl_cell = Paragraph(f"<i>★ {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>•</bullet> {line}" for line in criteria_lines
)
crit_cell = Paragraph(criteria_text, S['card_body'])
pearl_cell = Paragraph(f"<i>★ {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 & Infarction | Electrolyte Disorders | Special Syndromes<br/>"
"<br/>Based on: Harrison's Principles 22E | Goldman-Cecil Medicine | Medical Physiology (Boron & 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 (< 3 small boxes)", S['normal_body']),
],
[
Paragraph("<b>QTc (Bazett)</b><br/>Men: <450 ms | Women: <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()
cd /home/daytona/workspace/ecg-reference && python3 build_ecg_reference.py 2>&1
~/ecg-reference/build_ecg_reference.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ecg-reference/build_ecg_reference.py'
~/ecg-reference/build_ecg_reference.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ecg-reference/build_ecg_reference.py'
cd /home/daytona/workspace/ecg-reference && python3 build_ecg_reference.py 2>&1
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') "
ECG Reference Card
PDF Document · PDF
| Page | Content |
|---|---|
| 1 - Cover | Title, colour-coded legend, Normal ECG Values quick box (heart rate, PR, QRS, QTc, axis, paper calibration) |
| 2 - Sinus + Atrial | Cards 01-06: Normal sinus rhythm, sinus brady/tachy, sinus arrhythmia, P-pulmonale, P-mitrale |
| 3 - Hypertrophy + AV Blocks | Cards 07-12: LVH, RVH, all 4 degrees of AV block (1st, Mobitz I, Mobitz II, complete) |
| 4 - Bundle Branch Blocks | Cards 13-16: RBBB, LBBB, LAFB, LPFB with criteria |
| 5 - Ischemia Part 1 | Cards 17-21: Hyperacute T, Anterior STEMI, Inferior STEMI, Lateral STEMI, Posterior STEMI |
| 6 - Ischemia Part 2 + Arrhythmias | Cards 22-28: RV infarct, NSTEMI, Wellens, De Winter; Atrial arrhythmias (APCs, flutter, AF) |
| 7 - Ventricular + SVT + Electrolytes | Cards 29-39: PVCs, VT, VF, TdP, AVNRT, WPW, hyperkalemia, hypokalemia, Ca disorders |
| 8 - Special Syndromes | Cards 40-48: Pericarditis, tamponade, PE, Brugada, Long QT, hypothermia, SAH, digoxin, TCA toxicity |
| 9 - Critical Tables + Systematic Approach | STEMI 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 |