Ecg puri padhni batao
ECG reading systematic approach Hindi explained

- Standardization & Technical features
- Rhythm
- Heart Rate
- PR Interval / AV Conduction
- QRS Interval
- QT/QTc Interval
- Mean QRS Electrical Axis
- P Waves
- QRS Voltages
- Precordial R-wave Progression
- Abnormal Q Waves
- ST Segments
- T Waves
- U Waves
| Rhythm | Feature |
|---|---|
| Normal Sinus | P wave → QRS, regular, 60-100 bpm |
| Sinus Brady | Wohi par rate <60 |
| Sinus Tachy | Wohi par rate >100 |
| AF | P waves absent, irregular irregular |
| Atrial Flutter | Sawtooth P waves, 300/min |
| PR | Matlab |
|---|---|
| <120 ms | Pre-excitation (WPW) ya junctional rhythm |
| >200 ms | 1st Degree AV Block |
| Progressive lengthening | 2nd Degree AV Block (Wenckebach/Mobitz I) |
| Fixed PR + dropped QRS | 2nd Degree AV Block (Mobitz II) |
| No relation P to QRS | Complete (3rd Degree) AV Block |
120 ms → Bundle Branch Block (BBB)
40 ms wide (1 small box) ya >25% of QRS height → Pathological Q wave (old MI ka sign)

| Lead I | aVF | Axis |
|---|---|---|
| Positive | Positive | Normal (-30° to +90°) |
| Positive | Negative | Left Axis Deviation (LAD) |
| Negative | Positive | Right Axis Deviation (RAD) |
| Negative | Negative | Extreme / Northwest Axis |
| Parameter | Normal Value |
|---|---|
| Heart Rate | 60-100 bpm |
| PR Interval | 120-200 ms (3-5 small boxes) |
| QRS Duration | <120 ms (<3 small boxes) |
| QT Interval (QTc) | <440 ms (men), <460 ms (women) |
| QRS Axis | -30° to +100° |
| P wave amplitude | <2.5 mm |
| P wave duration | <120 ms |
| Leads | Dil Ka Hissa |
|---|---|
| II, III, aVF | Inferior wall (RCA territory) |
| I, aVL, V5-V6 | Lateral wall (LCx territory) |
| V1-V4 | Anterior wall (LAD territory) |
| V1-V2 | Septal |
| aVR | Right atrium / aortic root |
| V4R | Right ventricle (special lead) |
| Pattern | Kya Socho |
|---|---|
| Diffuse ST elevation + PR depression | Acute Pericarditis |
| STEMI + reciprocal changes | Acute MI |
| Delta wave + short PR | WPW Syndrome |
| Tall R in V1 + RAD | RVH / RBBB / Posterior MI |
| Peaked P in II + RAD | Cor Pulmonale |
| Electrical alternans | Cardiac Tamponade |
| Sine wave pattern | Severe Hyperkalemia |
| Osborne (J) wave | Hypothermia |
| "S1Q3T3" | Pulmonary Embolism |
Create a quick reference PDF for ECG normal values and diagnostic criteria
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace not found"
mkdir -p /home/daytona/workspace/ecg-pdf
~/ecg-pdf/create_ecg_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.pdfgen import canvas
from reportlab.graphics.shapes import Drawing, Rect, Line, String, Circle
from reportlab.graphics import renderPDF
import os
OUTPUT_PATH = "/home/daytona/workspace/ecg-pdf/ECG_Quick_Reference.pdf"
# ── Color Palette ──────────────────────────────────────────────────────────────
C_RED = colors.HexColor("#C0392B")
C_DARK = colors.HexColor("#1A1A2E")
C_BLUE = colors.HexColor("#1565C0")
C_TEAL = colors.HexColor("#00695C")
C_ORANGE = colors.HexColor("#E65100")
C_PURPLE = colors.HexColor("#6A1B9A")
C_BG_LIGHT = colors.HexColor("#F5F5F5")
C_BG_RED = colors.HexColor("#FFEBEE")
C_BG_BLUE = colors.HexColor("#E3F2FD")
C_BG_TEAL = colors.HexColor("#E0F2F1")
C_BG_ORANGE = colors.HexColor("#FFF3E0")
C_BG_PURPLE = colors.HexColor("#F3E5F5")
C_BG_GREEN = colors.HexColor("#E8F5E9")
C_HEADER_BG = colors.HexColor("#B71C1C")
C_WHITE = colors.white
C_GRAY = colors.HexColor("#757575")
C_LGRAY = colors.HexColor("#EEEEEE")
C_YELLOW = colors.HexColor("#FFF176")
# ── Page Layout ────────────────────────────────────────────────────────────────
PAGE_W, PAGE_H = A4
MARGIN = 14 * mm
# ── Styles ─────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
TITLE_STYLE = S("MainTitle",
fontName="Helvetica-Bold", fontSize=22, textColor=C_WHITE,
alignment=TA_CENTER, spaceAfter=0, leading=26)
SUBTITLE_STYLE = S("SubTitle",
fontName="Helvetica", fontSize=11, textColor=colors.HexColor("#FFCDD2"),
alignment=TA_CENTER, spaceAfter=0, leading=14)
SEC_HEADER = S("SectionHeader",
fontName="Helvetica-Bold", fontSize=9.5, textColor=C_WHITE,
alignment=TA_LEFT, leading=12, leftIndent=3)
CELL_BOLD = S("CellBold",
fontName="Helvetica-Bold", fontSize=8.2, textColor=C_DARK,
alignment=TA_LEFT, leading=10)
CELL_NORMAL = S("CellNormal",
fontName="Helvetica", fontSize=8.0, textColor=C_DARK,
alignment=TA_LEFT, leading=10)
CELL_GREEN = S("CellGreen",
fontName="Helvetica-Bold", fontSize=8.2, textColor=C_TEAL,
alignment=TA_LEFT, leading=10)
CELL_RED = S("CellRed",
fontName="Helvetica-Bold", fontSize=8.2, textColor=C_RED,
alignment=TA_LEFT, leading=10)
CELL_CENTER = S("CellCenter",
fontName="Helvetica", fontSize=8.0, textColor=C_DARK,
alignment=TA_CENTER, leading=10)
CELL_BOLD_CENTER = S("CellBoldCenter",
fontName="Helvetica-Bold", fontSize=8.2, textColor=C_DARK,
alignment=TA_CENTER, leading=10)
TINY = S("Tiny",
fontName="Helvetica", fontSize=7.0, textColor=C_GRAY,
alignment=TA_CENTER, leading=9)
FOOTER_STYLE = S("Footer",
fontName="Helvetica-Oblique", fontSize=7.0, textColor=C_GRAY,
alignment=TA_CENTER, leading=9)
# ── Table style helper ─────────────────────────────────────────────────────────
def base_ts(header_bg=C_BLUE, header_fg=C_WHITE):
return TableStyle([
('BACKGROUND', (0, 0), (-1, 0), header_bg),
('TEXTCOLOR', (0, 0), (-1, 0), header_fg),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 8.5),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [C_WHITE, C_BG_LIGHT]),
('GRID', (0, 0), (-1, -1), 0.4, colors.HexColor("#BDBDBD")),
('TOPPADDING', (0, 0), (-1, -1), 3),
('BOTTOMPADDING',(0, 0), (-1, -1), 3),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
])
def section_label(text, bg=C_BLUE):
"""Returns a single-cell table used as a section header bar."""
t = Table([[Paragraph(text, SEC_HEADER)]], colWidths=[PAGE_W - 2*MARGIN])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), bg),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('ROUNDEDCORNERS', [3, 3, 3, 3]),
]))
return t
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 1 – Normal Values + Lead Anatomy + Rate Calculation
# ══════════════════════════════════════════════════════════════════════════════
def build_page1():
story = []
W = PAGE_W - 2*MARGIN
# ── HEADER BANNER ──────────────────────────────────────────────────────────
header_table = Table(
[[Paragraph("ECG Quick Reference Guide", TITLE_STYLE)],
[Paragraph("Normal Values • Diagnostic Criteria • Systematic Interpretation", SUBTITLE_STYLE)]],
colWidths=[W]
)
header_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), C_HEADER_BG),
('TOPPADDING', (0,0), (-1,-1), 10),
('BOTTOMPADDING', (0,0), (-1,-1), 10),
('LEFTPADDING', (0,0), (-1,-1), 8),
('RIGHTPADDING', (0,0), (-1,-1), 8),
('ROUNDEDCORNERS',[4,4,4,4]),
]))
story.append(header_table)
story.append(Spacer(1, 5*mm))
# ── ECG PAPER BASICS ──────────────────────────────────────────────────────
story.append(section_label("📋 ECG PAPER BASICS", C_DARK))
story.append(Spacer(1, 2*mm))
basics_data = [
[Paragraph("Parameter", CELL_BOLD_CENTER), Paragraph("Value", CELL_BOLD_CENTER),
Paragraph("Parameter", CELL_BOLD_CENTER), Paragraph("Value", CELL_BOLD_CENTER)],
[Paragraph("Paper speed", CELL_BOLD), Paragraph("25 mm/sec", CELL_GREEN),
Paragraph("1 small box (horiz)", CELL_BOLD), Paragraph("0.04 sec / 1 mm", CELL_NORMAL)],
[Paragraph("Standard gain", CELL_BOLD), Paragraph("10 mm = 1 mV", CELL_GREEN),
Paragraph("1 large box (horiz)", CELL_BOLD), Paragraph("0.20 sec / 5 mm", CELL_NORMAL)],
[Paragraph("1 small box (vert)", CELL_BOLD), Paragraph("0.1 mV", CELL_NORMAL),
Paragraph("1 large box (vert)", CELL_BOLD), Paragraph("0.5 mV", CELL_NORMAL)],
]
col_w = W / 4
basics_t = Table(basics_data, colWidths=[col_w]*4)
ts = base_ts(header_bg=C_DARK)
ts.add('BACKGROUND', (0,0), (-1,0), C_DARK)
basics_t.setStyle(ts)
story.append(basics_t)
story.append(Spacer(1, 4*mm))
# ── NORMAL INTERVALS TABLE ─────────────────────────────────────────────────
story.append(section_label("⏱ NORMAL ECG INTERVALS & AMPLITUDES", C_BLUE))
story.append(Spacer(1, 2*mm))
intervals_data = [
[Paragraph("Wave / Interval", CELL_BOLD), Paragraph("Normal Range", CELL_BOLD),
Paragraph("Boxes", CELL_BOLD), Paragraph("Clinical Note", CELL_BOLD)],
[Paragraph("Heart Rate", CELL_BOLD), Paragraph("60 – 100 bpm", CELL_GREEN),
Paragraph("—", CELL_CENTER), Paragraph("Sinus bradycardia <60, tachycardia >100", CELL_NORMAL)],
[Paragraph("P wave duration", CELL_BOLD), Paragraph("< 120 ms", CELL_GREEN),
Paragraph("< 3 small", CELL_CENTER), Paragraph(">120 ms notched = Left atrial enlargement", CELL_NORMAL)],
[Paragraph("P wave amplitude", CELL_BOLD), Paragraph("< 2.5 mm", CELL_GREEN),
Paragraph("< 2.5 small", CELL_CENTER), Paragraph(">2.5 mm peaked = Right atrial enlargement", CELL_NORMAL)],
[Paragraph("PR interval", CELL_BOLD), Paragraph("120 – 200 ms", CELL_GREEN),
Paragraph("3 – 5 large", CELL_CENTER), Paragraph("<120 WPW/junctional; >200 1st-degree AV block", CELL_NORMAL)],
[Paragraph("QRS duration", CELL_BOLD), Paragraph("< 120 ms", CELL_GREEN),
Paragraph("< 3 small", CELL_CENTER), Paragraph(">120 ms = Bundle Branch Block", CELL_NORMAL)],
[Paragraph("QT interval (QTc)", CELL_BOLD), Paragraph("Male <440 ms\nFemale <460 ms", CELL_GREEN),
Paragraph("—", CELL_CENTER), Paragraph("Bazett: QTc = QT / sqrt(RR in sec)", CELL_NORMAL)],
[Paragraph("QRS Axis", CELL_BOLD), Paragraph("-30° to +100°", CELL_GREEN),
Paragraph("—", CELL_CENTER), Paragraph("LAD < -30°; RAD > +100°", CELL_NORMAL)],
[Paragraph("R-wave progression", CELL_BOLD), Paragraph("V1 → V6 increasing", CELL_GREEN),
Paragraph("—", CELL_CENTER), Paragraph("Transition (R=S) normally at V3/V4", CELL_NORMAL)],
[Paragraph("U wave", CELL_BOLD), Paragraph("< 1 mm", CELL_GREEN),
Paragraph("< 1 small", CELL_CENTER), Paragraph("Prominent U = hypokalemia, amiodarone", CELL_NORMAL)],
]
cw = [W*0.19, W*0.18, W*0.13, W*0.50]
iv_t = Table(intervals_data, colWidths=cw)
ts2 = base_ts(header_bg=C_BLUE)
ts2.add('ROWBACKGROUNDS', (0,1), (-1,-1), [C_WHITE, C_BG_BLUE])
iv_t.setStyle(ts2)
story.append(iv_t)
story.append(Spacer(1, 4*mm))
# ── TWO-COLUMN ROW: Leads Anatomy + Heart Rate ─────────────────────────────
story.append(section_label("🗺 LEAD TERRITORIES & CULPRIT VESSELS", C_TEAL))
story.append(Spacer(1, 2*mm))
lead_data = [
[Paragraph("Leads", CELL_BOLD_CENTER), Paragraph("Wall", CELL_BOLD_CENTER), Paragraph("Artery", CELL_BOLD_CENTER)],
[Paragraph("II, III, aVF", CELL_BOLD), Paragraph("Inferior", CELL_NORMAL), Paragraph("RCA", CELL_NORMAL)],
[Paragraph("V1 – V4", CELL_BOLD), Paragraph("Anterior", CELL_NORMAL), Paragraph("LAD", CELL_NORMAL)],
[Paragraph("V1 – V2", CELL_BOLD), Paragraph("Septal", CELL_NORMAL), Paragraph("LAD (septal perfs)", CELL_NORMAL)],
[Paragraph("I, aVL, V5–V6", CELL_BOLD), Paragraph("Lateral", CELL_NORMAL), Paragraph("LCx", CELL_NORMAL)],
[Paragraph("V1–V2 tall R", CELL_BOLD), Paragraph("Posterior", CELL_NORMAL), Paragraph("RCA / LCx", CELL_NORMAL)],
[Paragraph("V4R", CELL_BOLD), Paragraph("Right ventricle", CELL_NORMAL), Paragraph("RCA (proximal)", CELL_NORMAL)],
[Paragraph("aVR", CELL_BOLD), Paragraph("RV outflow/aortic root", CELL_NORMAL), Paragraph("LMCA / LAD", CELL_NORMAL)],
]
lead_t = Table(lead_data, colWidths=[W*0.3*0.35, W*0.3*0.35, W*0.3*0.30])
ts3 = base_ts(header_bg=C_TEAL)
ts3.add('ROWBACKGROUNDS', (0,1), (-1,-1), [C_WHITE, C_BG_TEAL])
lead_t.setStyle(ts3)
# Heart Rate Quick Calc
hr_data = [
[Paragraph("HEART RATE QUICK CALCULATION", CELL_BOLD_CENTER)],
[Paragraph("<b>Regular rhythm:</b> 300 ÷ (large boxes between R-R)", CELL_NORMAL)],
[Paragraph("<b>Mnemonic ladder:</b> 1 box=300 | 2=150 | 3=100\n4=75 | 5=60 | 6=50 | 7=43", CELL_NORMAL)],
[Paragraph("<b>Irregular rhythm:</b> Count QRS in 10-sec strip × 6", CELL_NORMAL)],
[Paragraph("<b>300-method tip:</b> Find an R on a heavy line, count boxes to next R", CELL_NORMAL)],
]
hr_t = Table(hr_data, colWidths=[W*0.68])
hr_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), C_ORANGE),
('TEXTCOLOR', (0,0), (-1,0), C_WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('GRID', (0,0), (-1,-1), 0.4, colors.HexColor("#BDBDBD")),
('ROWBACKGROUNDS',(0,1), (-1,-1), [C_BG_ORANGE, C_WHITE]),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
]))
two_col = Table(
[[lead_t, Spacer(3*mm, 1), hr_t]],
colWidths=[W*0.30, 3*mm, W*0.68]
)
two_col.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), 0),
]))
story.append(two_col)
story.append(Spacer(1, 4*mm))
# ── SOURCE FOOTER ──────────────────────────────────────────────────────────
story.append(HRFlowable(width=W, thickness=0.5, color=C_GRAY))
story.append(Spacer(1, 1*mm))
story.append(Paragraph(
"Sources: Harrison's Principles of Internal Medicine 22E (2025) | Ganong's Review of Medical Physiology 26E | Braunwald's Heart Disease 12E • Page 1 of 2",
FOOTER_STYLE))
return story
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 2 – Diagnostic Criteria
# ══════════════════════════════════════════════════════════════════════════════
def build_page2():
story = []
W = PAGE_W - 2*MARGIN
# Mini header
mini_h = Table([[Paragraph("ECG Diagnostic Criteria — Quick Reference", TITLE_STYLE)]],
colWidths=[W])
mini_h.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), C_HEADER_BG),
('TOPPADDING', (0,0), (-1,-1), 8),
('BOTTOMPADDING', (0,0), (-1,-1), 8),
('LEFTPADDING', (0,0), (-1,-1), 8),
('RIGHTPADDING', (0,0), (-1,-1), 8),
('ROUNDEDCORNERS',[4,4,4,4]),
]))
story.append(mini_h)
story.append(Spacer(1, 4*mm))
# ── AV BLOCKS ─────────────────────────────────────────────────────────────
story.append(section_label("💔 AV CONDUCTION BLOCKS", C_RED))
story.append(Spacer(1, 2*mm))
av_data = [
[Paragraph("Block Type", CELL_BOLD), Paragraph("PR Interval", CELL_BOLD),
Paragraph("Dropped QRS?", CELL_BOLD), Paragraph("P:QRS Ratio", CELL_BOLD), Paragraph("Key Feature", CELL_BOLD)],
[Paragraph("1st Degree", CELL_NORMAL), Paragraph(">200 ms (fixed)", CELL_RED),
Paragraph("No", CELL_NORMAL), Paragraph("1:1", CELL_CENTER),
Paragraph("Prolonged but all conduct", CELL_NORMAL)],
[Paragraph("2nd Degree – Mobitz I\n(Wenckebach)", CELL_NORMAL), Paragraph("Progressive lengthening", CELL_RED),
Paragraph("Yes (periodic)", CELL_NORMAL), Paragraph(">1:1", CELL_CENTER),
Paragraph("Grouped beating; PR lengthens until drop", CELL_NORMAL)],
[Paragraph("2nd Degree – Mobitz II", CELL_NORMAL), Paragraph("Fixed PR", CELL_RED),
Paragraph("Yes (sudden)", CELL_NORMAL), Paragraph(">1:1", CELL_CENTER),
Paragraph("Sudden non-conducted P; often needs pacemaker", CELL_NORMAL)],
[Paragraph("3rd Degree (Complete)", CELL_BOLD), Paragraph("No relation (dissociation)", CELL_RED),
Paragraph("P & QRS independent", CELL_NORMAL), Paragraph("None", CELL_CENTER),
Paragraph("P rate > QRS rate; escape rhythm present", CELL_NORMAL)],
]
cw_av = [W*0.20, W*0.18, W*0.14, W*0.12, W*0.36]
av_t = Table(av_data, colWidths=cw_av)
ts_av = base_ts(header_bg=C_RED)
ts_av.add('ROWBACKGROUNDS', (0,1), (-1,-1), [C_WHITE, C_BG_RED])
av_t.setStyle(ts_av)
story.append(av_t)
story.append(Spacer(1, 4*mm))
# ── BUNDLE BRANCH BLOCKS ──────────────────────────────────────────────────
story.append(section_label("🔀 BUNDLE BRANCH BLOCKS (QRS > 120 ms)", C_PURPLE))
story.append(Spacer(1, 2*mm))
bbb_data = [
[Paragraph("Feature", CELL_BOLD), Paragraph("RBBB", CELL_BOLD), Paragraph("LBBB", CELL_BOLD)],
[Paragraph("V1 pattern", CELL_BOLD), Paragraph("RSR' (rabbit ears) / rSR'", CELL_NORMAL), Paragraph("QS or rS (broad notched)", CELL_NORMAL)],
[Paragraph("V6 pattern", CELL_BOLD), Paragraph("Wide slurred S wave", CELL_NORMAL), Paragraph("Broad monophasic R (no Q/S)", CELL_NORMAL)],
[Paragraph("Lead I", CELL_BOLD), Paragraph("Wide S wave", CELL_NORMAL), Paragraph("Broad R, no Q", CELL_NORMAL)],
[Paragraph("ST-T changes", CELL_BOLD), Paragraph("Discordant to QRS terminal", CELL_NORMAL), Paragraph("Discordant to QRS (ST opp. to R)", CELL_NORMAL)],
[Paragraph("Sgarbossa criteria", CELL_BOLD), Paragraph("Not typically applied", CELL_NORMAL), Paragraph("Used to detect MI in LBBB", CELL_NORMAL)],
]
cw_bbb = [W*0.22, W*0.39, W*0.39]
bbb_t = Table(bbb_data, colWidths=cw_bbb)
ts_bbb = base_ts(header_bg=C_PURPLE)
ts_bbb.add('ROWBACKGROUNDS', (0,1), (-1,-1), [C_WHITE, C_BG_PURPLE])
bbb_t.setStyle(ts_bbb)
story.append(bbb_t)
story.append(Spacer(1, 4*mm))
# ── TWO-COLUMN: Axis + Hypertrophy ─────────────────────────────────────────
col_left_w = W * 0.46
col_right_w = W * 0.50
gap = W * 0.04
# LEFT: QRS Axis
story.append(section_label("🧭 QRS AXIS (Frontal Plane)", C_TEAL))
story.append(Spacer(1, 2*mm))
axis_data = [
[Paragraph("Lead I", CELL_BOLD_CENTER), Paragraph("aVF", CELL_BOLD_CENTER),
Paragraph("Axis", CELL_BOLD_CENTER), Paragraph("Causes", CELL_BOLD)],
[Paragraph("Positive (+)", CELL_CENTER), Paragraph("Positive (+)", CELL_CENTER),
Paragraph("NORMAL\n(-30 to +90)", CELL_GREEN), Paragraph("Normal", CELL_NORMAL)],
[Paragraph("Positive (+)", CELL_CENTER), Paragraph("Negative (-)", CELL_CENTER),
Paragraph("LAD\n(< -30°)", CELL_RED), Paragraph("LVH, LAFB, inferior MI", CELL_NORMAL)],
[Paragraph("Negative (-)", CELL_CENTER), Paragraph("Positive (+)", CELL_CENTER),
Paragraph("RAD\n(> +100°)", CELL_RED), Paragraph("RVH, LPFB, lateral MI, PE", CELL_NORMAL)],
[Paragraph("Negative (-)", CELL_CENTER), Paragraph("Negative (-)", CELL_CENTER),
Paragraph("EXTREME\n(NW axis)", CELL_RED), Paragraph("Severe RVH, dextrocardia, artifact", CELL_NORMAL)],
]
cw_axis = [col_left_w*0.22, col_left_w*0.22, col_left_w*0.22, col_left_w*0.34]
axis_t = Table(axis_data, colWidths=cw_axis)
ts_axis = base_ts(header_bg=C_TEAL)
ts_axis.add('ROWBACKGROUNDS', (0,1), (-1,-1), [C_WHITE, C_BG_TEAL])
axis_t.setStyle(ts_axis)
# RIGHT: Hypertrophy Criteria
hyp_data = [
[Paragraph("Hypertrophy Criteria", CELL_BOLD_CENTER), Paragraph("Criterion", CELL_BOLD_CENTER)],
[Paragraph("LVH – Sokolow-Lyon", CELL_BOLD), Paragraph("S(V1) + R(V5 or V6) > 35 mm", CELL_NORMAL)],
[Paragraph("LVH – Cornell (men)", CELL_BOLD), Paragraph("R(aVL) + S(V3) > 28 mm", CELL_NORMAL)],
[Paragraph("LVH – Cornell (women)", CELL_BOLD), Paragraph("R(aVL) + S(V3) > 20 mm", CELL_NORMAL)],
[Paragraph("LVH – aVL alone", CELL_BOLD), Paragraph("R(aVL) > 11 mm", CELL_NORMAL)],
[Paragraph("RVH criteria", CELL_BOLD), Paragraph("R>S in V1, RAD, R(V1)+S(V5/6)>11mm", CELL_NORMAL)],
[Paragraph("LAE (P-mitrale)", CELL_BOLD), Paragraph("P duration >120ms or biphasic in V1", CELL_NORMAL)],
[Paragraph("RAE (P-pulmonale)", CELL_BOLD), Paragraph("P amplitude >2.5mm in II, III, aVF", CELL_NORMAL)],
]
cw_hyp = [col_right_w*0.45, col_right_w*0.55]
hyp_t = Table(hyp_data, colWidths=cw_hyp)
ts_hyp = base_ts(header_bg=C_ORANGE)
ts_hyp.add('ROWBACKGROUNDS', (0,1), (-1,-1), [C_WHITE, C_BG_ORANGE])
hyp_t.setStyle(ts_hyp)
two_col2 = Table(
[[axis_t, Spacer(gap, 1), hyp_t]],
colWidths=[col_left_w, gap, col_right_w]
)
two_col2.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), 0),
]))
story.append(two_col2)
story.append(Spacer(1, 4*mm))
# ── CLASSIC ECG PATTERNS ──────────────────────────────────────────────────
story.append(section_label("🔍 CLASSIC / HIGH-YIELD ECG PATTERNS", C_DARK))
story.append(Spacer(1, 2*mm))
pat_data = [
[Paragraph("Pattern", CELL_BOLD), Paragraph("ECG Finding", CELL_BOLD), Paragraph("Diagnosis", CELL_BOLD)],
[Paragraph("Saddle-shaped ST elevation + PR depression", CELL_NORMAL), Paragraph("Diffuse concave STE + PR depression in I,II,V2-6", CELL_NORMAL), Paragraph("Acute Pericarditis", CELL_BOLD)],
[Paragraph("Electrical alternans", CELL_NORMAL), Paragraph("Alternating QRS height beat-to-beat", CELL_NORMAL), Paragraph("Cardiac Tamponade", CELL_BOLD)],
[Paragraph("Delta wave + short PR (<120ms)", CELL_NORMAL), Paragraph("Slurred upstroke of QRS, short PR, wide QRS", CELL_NORMAL), Paragraph("WPW Syndrome", CELL_BOLD)],
[Paragraph("S1Q3T3", CELL_NORMAL), Paragraph("S in I, Q in III, T inversion in III", CELL_NORMAL), Paragraph("Pulmonary Embolism (non-specific)", CELL_BOLD)],
[Paragraph("Osborne (J) waves", CELL_NORMAL), Paragraph("Positive deflection at J point (best in V4-V6)", CELL_NORMAL), Paragraph("Hypothermia", CELL_BOLD)],
[Paragraph("Sine wave / peaked T + wide QRS", CELL_NORMAL), Paragraph("Broad bizarre QRS; peaked T waves", CELL_NORMAL), Paragraph("Severe Hyperkalemia", CELL_BOLD)],
[Paragraph("Deep T-inversion V1-V4 (Wellens')", CELL_NORMAL), Paragraph("Biphasic or deep symmetric T inversion V1-V4", CELL_NORMAL), Paragraph("Critical LAD stenosis — Emergency!", CELL_RED)],
[Paragraph("Short QT + down-sloping ST", CELL_NORMAL), Paragraph("QTc <340ms, shortened ST segment", CELL_NORMAL), Paragraph("Hypercalcemia / Digoxin toxicity", CELL_BOLD)],
[Paragraph("STEMI + reciprocal ST depression", CELL_NORMAL), Paragraph("Convex STE in territory + mirror depression", CELL_NORMAL), Paragraph("Acute Myocardial Infarction (STEMI)", CELL_RED)],
[Paragraph("Tall R in V1 + RAD + no septal Q in V6", CELL_NORMAL), Paragraph("Dominant R in V1 (R>S), axis deviation", CELL_NORMAL), Paragraph("RBBB / RVH / Posterior MI", CELL_BOLD)],
[Paragraph("Epsilon wave (V1-V3)", CELL_NORMAL), Paragraph("Small positive deflection after QRS in V1-V3", CELL_NORMAL), Paragraph("ARVC (Arrhythmogenic RV Cardiomyopathy)", CELL_BOLD)],
]
cw_p = [W*0.30, W*0.38, W*0.32]
pat_t = Table(pat_data, colWidths=cw_p)
ts_pat = base_ts(header_bg=C_DARK)
ts_pat.add('ROWBACKGROUNDS', (0,1), (-1,-1), [C_WHITE, C_BG_LIGHT])
pat_t.setStyle(ts_pat)
story.append(pat_t)
story.append(Spacer(1, 3*mm))
# ── STEMI TERRITORIES TABLE ───────────────────────────────────────────────
story.append(section_label("❤️ STEMI LOCALISATION", C_RED))
story.append(Spacer(1, 2*mm))
stemi_data = [
[Paragraph("Leads with STE", CELL_BOLD), Paragraph("Territory", CELL_BOLD),
Paragraph("Culprit", CELL_BOLD), Paragraph("Reciprocal Changes", CELL_BOLD)],
[Paragraph("II, III, aVF", CELL_NORMAL), Paragraph("Inferior", CELL_NORMAL), Paragraph("RCA (85%)", CELL_NORMAL), Paragraph("I, aVL", CELL_NORMAL)],
[Paragraph("V1 – V4", CELL_NORMAL), Paragraph("Anterior", CELL_NORMAL), Paragraph("LAD", CELL_NORMAL), Paragraph("II, III, aVF", CELL_NORMAL)],
[Paragraph("V1 – V2", CELL_NORMAL), Paragraph("Septal", CELL_NORMAL), Paragraph("LAD proximal", CELL_NORMAL), Paragraph("None typical", CELL_NORMAL)],
[Paragraph("I, aVL, V5–V6", CELL_NORMAL), Paragraph("Lateral", CELL_NORMAL), Paragraph("LCx", CELL_NORMAL), Paragraph("II, III, aVF", CELL_NORMAL)],
[Paragraph("V1-V3 tall R + ST depression", CELL_NORMAL), Paragraph("Posterior", CELL_NORMAL), Paragraph("RCA/LCx", CELL_NORMAL), Paragraph("(Mirror image in V1-V3)", CELL_NORMAL)],
[Paragraph("aVR STE + diffuse depression", CELL_NORMAL), Paragraph("LMCA/prox LAD", CELL_NORMAL), Paragraph("LMCA or LAD", CELL_NORMAL), Paragraph("Diffuse ST depression", CELL_NORMAL)],
]
cw_s = [W*0.25, W*0.20, W*0.22, W*0.33]
stemi_t = Table(stemi_data, colWidths=cw_s)
ts_s = base_ts(header_bg=C_RED)
ts_s.add('ROWBACKGROUNDS', (0,1), (-1,-1), [C_WHITE, C_BG_RED])
stemi_t.setStyle(ts_s)
story.append(stemi_t)
story.append(Spacer(1, 3*mm))
# ── FOOTER ────────────────────────────────────────────────────────────────
story.append(HRFlowable(width=W, thickness=0.5, color=C_GRAY))
story.append(Spacer(1, 1*mm))
story.append(Paragraph(
"Sources: Harrison's Principles of Internal Medicine 22E (2025) | Ganong's Review of Medical Physiology 26E | Braunwald's Heart Disease 12E • Page 2 of 2",
FOOTER_STYLE))
return story
# ══════════════════════════════════════════════════════════════════════════════
# BUILD PDF
# ══════════════════════════════════════════════════════════════════════════════
def build_pdf():
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN, bottomMargin=MARGIN,
title="ECG Quick Reference Guide",
author="Orris Medical AI",
subject="ECG Normal Values and Diagnostic Criteria",
)
story = []
story += build_page1()
from reportlab.platypus import PageBreak
story.append(PageBreak())
story += build_page2()
doc.build(story)
size_kb = os.path.getsize(OUTPUT_PATH) // 1024
print(f"PDF created: {OUTPUT_PATH} ({size_kb} KB)")
if __name__ == "__main__":
build_pdf()
cd /home/daytona/workspace/ecg-pdf && python create_ecg_pdf.py 2>&1