Hi

Hi! How can I help you today?

Can you make easily and step by step ECG approach on flash card form easily to understand and best explain

Asking for Preferences
Reading File
Reading File
Finding Sources
Finding Sources
Running Command

ls /home/daytona/workspace/ 2>/dev/null || echo "empty"

Finding Sources
Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Running Command

mkdir -p /home/daytona/workspace/ecg-flashcards && python3 -c "import reportlab; print('reportlab ok')"

Writing File

~/ecg-flashcards/make_flashcards.py

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import Paragraph
from reportlab.lib.enums import TA_CENTER, TA_LEFT
import textwrap

W, H = A4   # 210 x 297 mm  (595.27 x 841.89 pt)

# ── colour palette ──────────────────────────────────────────────────────────
NAVY    = colors.HexColor("#1A2C5B")
TEAL    = colors.HexColor("#0D7377")
AMBER   = colors.HexColor("#F5A623")
LIGHT   = colors.HexColor("#EAF4FB")
WHITE   = colors.white
RED     = colors.HexColor("#D0021B")
GREEN   = colors.HexColor("#2E7D32")
ORANGE  = colors.HexColor("#E65100")
GREY    = colors.HexColor("#455A64")
LGREEN  = colors.HexColor("#E8F5E9")
LYELLOW = colors.HexColor("#FFFDE7")
LORANGE = colors.HexColor("#FFF3E0")
LRED    = colors.HexColor("#FFEBEE")

# ── helper: rounded rectangle ───────────────────────────────────────────────
def rr(c, x, y, w, h, r=8, fill_color=WHITE, stroke_color=NAVY, lw=1.2):
    c.setLineWidth(lw)
    c.setFillColor(fill_color)
    c.setStrokeColor(stroke_color)
    c.roundRect(x, y, w, h, r, stroke=1, fill=1)

# ── helper: wrapped text block ───────────────────────────────────────────────
def text_block(c, txt, x, y, max_w, font="Helvetica", size=10, color=GREY,
               line_h=14, align="left"):
    c.setFont(font, size)
    c.setFillColor(color)
    words = txt.split()
    lines, line = [], []
    for w in words:
        probe = " ".join(line + [w])
        if c.stringWidth(probe, font, size) <= max_w:
            line.append(w)
        else:
            if line:
                lines.append(" ".join(line))
            line = [w]
    if line:
        lines.append(" ".join(line))
    cur_y = y
    for ln in lines:
        if align == "center":
            c.drawCentredString(x, cur_y, ln)
        else:
            c.drawString(x, cur_y, ln)
        cur_y -= line_h
    return cur_y   # returns y after last line

# ── helper: bullet list ──────────────────────────────────────────────────────
def bullet_list(c, items, x, y, max_w, font="Helvetica", size=10,
                color=GREY, line_h=14, bullet="•", indent=12):
    cur_y = y
    for item in items:
        c.setFont("Helvetica-Bold", size)
        c.setFillColor(TEAL)
        c.drawString(x, cur_y, bullet)
        c.setFont(font, size)
        c.setFillColor(color)
        cur_y = text_block(c, item, x + indent, cur_y, max_w - indent,
                           font=font, size=size, color=color,
                           line_h=line_h) + line_h - line_h  # advance
        cur_y -= 2
    return cur_y

# ── flashcard data ───────────────────────────────────────────────────────────
cards = [

# ═══════════════════════════════ OVERVIEW ═══════════════════════════════════
{
 "step": "OVERVIEW",
 "icon": "★",
 "title": "The 8-Step ECG Approach",
 "accent": NAVY,
 "bg": LIGHT,
 "type": "overview",
 "steps": [
     ("1", "Rate",        "How fast is the heart?",            TEAL),
     ("2", "Rhythm",      "Regular or irregular?",             GREEN),
     ("3", "P Waves",     "Present? Morphology?",              ORANGE),
     ("4", "PR Interval", "AV conduction time",                AMBER),
     ("5", "QRS Complex", "Width & morphology",                RED),
     ("6", "Axis",        "Direction of depolarisation",       TEAL),
     ("7", "ST Segment",  "Elevation / depression?",           RED),
     ("8", "T & U Waves", "Repolarisation changes",            GREEN),
 ],
 "source": "Harrison's 22E, p.1919 | Kaplan & Sadock's p.6823"
},

# ═══════════════════════════════ STEP 1 - RATE ══════════════════════════════
{
 "step": "STEP 1",
 "icon": "❤",
 "title": "Heart Rate",
 "accent": TEAL,
 "bg": colors.HexColor("#E0F7FA"),
 "type": "detail",
 "question": "How do I calculate heart rate?",
 "key_fact": "Normal HR = 60–100 bpm",
 "methods": [
     ("300 ÷ Rule (regular rhythm)",
      "Count large boxes between two consecutive R waves → Divide 300 by that number.\n"
      "Example: 4 large boxes apart → 300÷4 = 75 bpm"),
     ("6-Second Rule (irregular rhythm)",
      "Count all QRS complexes in a 10-second strip → Multiply by 6.\n"
      "Best for AF or irregular rhythms."),
     ("Memorise the sequence",
      "1 box=300, 2=150, 3=100, 4=75, 5=60, 6=50 bpm"),
 ],
 "normal_ranges": [
     "Bradycardia: < 60 bpm",
     "Normal:   60 – 100 bpm",
     "Tachycardia: > 100 bpm",
 ],
 "tip": "Each small box = 0.04 s  |  Each large box = 0.2 s  |  5 small = 1 large",
 "source": "Kaplan & Sadock's p.6824"
},

# ══════════════════════════════ STEP 2 - RHYTHM ═════════════════════════════
{
 "step": "STEP 2",
 "icon": "~",
 "title": "Rhythm",
 "accent": GREEN,
 "bg": LGREEN,
 "type": "detail",
 "question": "Is the rhythm regular or irregular?",
 "key_fact": "Normal: Regular Sinus Rhythm (RSR)",
 "methods": [
     ("Check R-R intervals",
      "Measure distance between consecutive R peaks. Regular = all equal."),
     ("Identify the pacemaker",
      "Is there a P wave before every QRS? → Sinus origin.\n"
      "No P waves + irregular → Atrial Fibrillation.\n"
      "Saw-tooth baseline → Atrial Flutter."),
     ("Regularly irregular vs Irregularly irregular",
      "Regularly irregular: pattern repeats (e.g. Wenckebach).\n"
      "Irregularly irregular: no pattern (e.g. AF)."),
 ],
 "normal_ranges": [
     "Sinus Rhythm: P before every QRS, rate 60-100",
     "Sinus Brady: < 60 bpm, normal P & QRS",
     "Sinus Tachy: > 100 bpm, normal P & QRS",
 ],
 "tip": "Use calipers or fold paper to compare R-R intervals quickly!",
 "source": "Kaplan & Sadock's p.6824"
},

# ═══════════════════════════════ STEP 3 - P WAVES ═══════════════════════════
{
 "step": "STEP 3",
 "icon": "P",
 "title": "P Waves",
 "accent": ORANGE,
 "bg": LORANGE,
 "type": "detail",
 "question": "Are P waves present, and what do they look like?",
 "key_fact": "P wave = Atrial Depolarisation",
 "methods": [
     ("Normal P wave",
      "Upright in I, II, aVF. Inverted in aVR.\n"
      "Duration < 0.12 s (3 small boxes). Amplitude < 2.5 mm."),
     ("P before every QRS?",
      "Yes → rhythm is atrial in origin.\n"
      "No P waves → AF, junctional, or ventricular rhythm.\n"
      "More P than QRS → 2nd or 3rd degree AV block."),
     ("P wave morphology clues",
      "Broad, notched P (P mitrale) → Left atrial enlargement.\n"
      "Tall, peaked P (P pulmonale) → Right atrial enlargement."),
 ],
 "normal_ranges": [
     "Duration: < 0.12 s (< 3 small boxes)",
     "Amplitude: < 2.5 mm in limb leads",
     "Axis: upright in I & II",
 ],
 "tip": "Lead II is the best lead for seeing P waves clearly.",
 "source": "Harrison's 22E, p.1919"
},

# ═══════════════════════════════ STEP 4 - PR INTERVAL ═══════════════════════
{
 "step": "STEP 4",
 "icon": "PR",
 "title": "PR Interval",
 "accent": AMBER,
 "bg": LYELLOW,
 "type": "detail",
 "question": "How long does it take the impulse to travel from atria to ventricles?",
 "key_fact": "PR = AV Nodal Conduction Time",
 "methods": [
     ("Where to measure",
      "From start of P wave to start of QRS complex."),
     ("Normal PR interval",
      "0.12 – 0.20 s  (3 – 5 small boxes)."),
     ("Abnormal PR",
      "Long PR (> 0.20 s) → 1st degree AV block.\n"
      "Progressively lengthening PR → 2nd degree (Wenckebach/Mobitz I).\n"
      "Fixed long PR with dropped beats → Mobitz II.\n"
      "No PR relationship → 3rd degree (complete) AV block.\n"
      "Short PR (< 0.12 s) → Pre-excitation (WPW syndrome)."),
 ],
 "normal_ranges": [
     "Normal: 0.12–0.20 s (3–5 small boxes)",
     "> 0.20 s  = 1st Degree AV Block",
     "< 0.12 s  = Pre-excitation / WPW",
 ],
 "tip": "Think of the PR as the time the AV node 'pauses' to let atria empty.",
 "source": "Harrison's 22E, p.1919"
},

# ═══════════════════════════════ STEP 5 - QRS ═══════════════════════════════
{
 "step": "STEP 5",
 "icon": "QRS",
 "title": "QRS Complex",
 "accent": RED,
 "bg": LRED,
 "type": "detail",
 "question": "How wide is the QRS, and what does it look like?",
 "key_fact": "QRS = Ventricular Depolarisation",
 "methods": [
     ("Normal QRS",
      "Duration < 0.12 s (< 3 small boxes). Narrow and sharp."),
     ("Wide QRS (≥ 0.12 s)",
      "RBBB: RSR' pattern in V1 ('M shape'), wide S in I & V6.\n"
      "LBBB: broad notched R in I, V5, V6; QS in V1.\n"
      "Ventricular rhythm / PVC: bizarre wide complex."),
     ("Pathological Q waves",
      "Width > 0.04 s OR depth > 1/4 of R wave height.\n"
      "Indicates previous myocardial infarction."),
     ("R wave progression",
      "R wave should grow V1→V6. Loss of progression = anterior MI."),
 ],
 "normal_ranges": [
     "Duration: < 0.12 s (< 3 small boxes)",
     "Q wave: < 0.04 s wide, < 25% of R height",
     "Voltage: R in V5/V6 < 25 mm",
 ],
 "tip": "Narrow QRS = supraventricular origin. Wide QRS = ventricular or aberrant conduction.",
 "source": "Harrison's 22E, p.1919 | Kaplan & Sadock's p.6825"
},

# ═══════════════════════════════ STEP 6 - AXIS ══════════════════════════════
{
 "step": "STEP 6",
 "icon": "↗",
 "title": "Electrical Axis",
 "accent": TEAL,
 "bg": colors.HexColor("#E0F7FA"),
 "type": "detail",
 "question": "What direction is the heart's net electrical force?",
 "key_fact": "Normal Axis: -30° to +90°",
 "methods": [
     ("Quick 2-lead method (Lead I + aVF)",
      "Both positive → Normal axis.\n"
      "Lead I positive, aVF negative → Left Axis Deviation (LAD).\n"
      "Lead I negative, aVF positive → Right Axis Deviation (RAD).\n"
      "Both negative → Extreme/Northwest axis."),
     ("Left Axis Deviation (< -30°)",
      "Left anterior fascicular block, inferior MI, LVH, LBBB."),
     ("Right Axis Deviation (> +90°)",
      "RVH, pulmonary HTN / PE, lateral MI, RBBB, dextrocardia."),
 ],
 "normal_ranges": [
     "Normal:  -30° to +90°",
     "LAD:     < -30°",
     "RAD:     > +90°",
     "Extreme: < -90° (both leads negative)",
 ],
 "tip": "Mnemonic: 'Leave (LAD) the Right (RAD) behind' — Lead I tells you.",
 "source": "Kaplan & Sadock's p.6825 | Harrison's 22E p.1919"
},

# ══════════════════════════════ STEP 7 - ST SEGMENT ═════════════════════════
{
 "step": "STEP 7",
 "icon": "ST",
 "title": "ST Segment",
 "accent": RED,
 "bg": LRED,
 "type": "detail",
 "question": "Is the ST segment elevated or depressed?",
 "key_fact": "ST = Ventricular Repolarisation — most clinically critical!",
 "methods": [
     ("ST Elevation (≥ 1 mm in ≥ 2 contiguous leads)",
      "STEMI: localised elevation (see territory table).\n"
      "Pericarditis: diffuse saddle-shaped elevation + PR depression.\n"
      "Early Repolarisation: concave elevation, benign in young.\n"
      "Also: LBBB, Brugada, stress cardiomyopathy, LV aneurysm."),
     ("ST Depression",
      "Subendocardial ischaemia / NSTEMI.\n"
      "Reciprocal change opposite a STEMI.\n"
      "Digoxin effect: 'reverse tick / Salvador Dali' pattern.\n"
      "LVH strain pattern."),
     ("MI Territory Guide",
      "II, III, aVF → Inferior (RCA).\n"
      "I, aVL, V1-V4 → Anterior/Lateral (LAD).\n"
      "V1-V3 → Septal. V4-V6, I, aVL → Lateral (LCx)."),
 ],
 "normal_ranges": [
     "Isoelectric (flat at baseline)",
     "Elevation: ≥ 1 mm (limb) / ≥ 2 mm (precordial) = abnormal",
     "Depression: ≥ 0.5–1 mm = abnormal",
 ],
 "tip": "Always compare with old ECG. ST changes + clinical context = diagnosis!",
 "source": "Kaplan & Sadock's p.6825 | Harrison's 22E p.1919"
},

# ══════════════════════════════ STEP 8 - T & U WAVES ════════════════════════
{
 "step": "STEP 8",
 "icon": "TU",
 "title": "T Waves & U Waves",
 "accent": GREEN,
 "bg": LGREEN,
 "type": "detail",
 "question": "Are the T waves normal? Any U waves?",
 "key_fact": "T wave = Ventricular Repolarisation (active, ATP-dependent)",
 "methods": [
     ("Normal T wave",
      "Upright in I, II, V3-V6. Inverted in aVR normally.\n"
      "Height < 5 mm (limb) or < 10 mm (precordial)."),
     ("T wave inversions",
      "Ischaemia / NSTEMI: deep symmetric inversions.\n"
      "RVH / PE: V1-V4 inversions ('right heart strain').\n"
      "RBBB/LBBB: secondary T changes (expected).\n"
      "Normal: V1, aVR, III in some people."),
     ("Peaked T waves",
      "Tall, symmetrical, narrow → Hyperkalaemia (early sign).\n"
      "Hyperacute T waves → Very early STEMI."),
     ("U waves",
      "Small deflection after T wave, same direction as T.\n"
      "Prominent U waves → Hypokalaemia, bradycardia, drugs.\n"
      "Inverted U waves → LVH, ischaemia."),
 ],
 "normal_ranges": [
     "T: upright I, II, V3-V6; inverted aVR",
     "T amplitude: < 5 mm limb / < 10 mm precordial",
     "QTc (Bazett): < 440 ms men / < 460 ms women",
 ],
 "tip": "A prolonged QT (after T wave) = risk of Torsades de Pointes (TdP)!",
 "source": "Harrison's 22E, p.1919 | Kaplan & Sadock's p.6825"
},

# ═══════════════════════════ BONUS: INTERVALS QUICK REF ═════════════════════
{
 "step": "BONUS",
 "icon": "⌛",
 "title": "Normal ECG Intervals — Quick Reference",
 "accent": NAVY,
 "bg": LIGHT,
 "type": "table",
 "rows": [
     ("Interval / Wave", "Normal Value",        "Abnormal → Think..."),
     ("P wave duration", "< 0.12 s (< 3 boxes)","Broad = LAE / LBBB"),
     ("P amplitude",     "< 2.5 mm",            "Tall = RAE (P pulmonale)"),
     ("PR interval",     "0.12–0.20 s",         "Long=AV block; Short=WPW"),
     ("QRS duration",    "< 0.12 s (< 3 boxes)","Wide = BBB / VT / WPW"),
     ("QT interval",     "0.35–0.45 s",         "Long = TdP risk"),
     ("QTc (Bazett)",    "< 440 ms (M) / < 460 ms (F)","Drug-induced/Electrolytes"),
     ("ST segment",      "Isoelectric",         "Elevation/Depression = Ischaemia"),
     ("T wave height",   "< 5 mm (limb)",       "Peaked = HyperK; Flat = HypoK"),
 ],
 "source": "Harrison's 22E, p.1919"
},

# ═══════════════════════════ BONUS: COMMON PATTERNS ═════════════════════════
{
 "step": "CHEAT SHEET",
 "icon": "⚡",
 "title": "Common ECG Patterns at a Glance",
 "accent": NAVY,
 "bg": LIGHT,
 "type": "patterns",
 "patterns": [
     ("Atrial Fibrillation",  "No P waves, irregularly irregular RR, fibrillatory baseline",  TEAL),
     ("Atrial Flutter",       "Saw-tooth flutter waves at ~300/min, regular QRS 150/min",    TEAL),
     ("1st Degree AV Block",  "PR > 0.20 s, every P conducts, regular rhythm",               ORANGE),
     ("2nd Degree (Wenckebach)","PR progressively lengthens until a QRS is dropped",         ORANGE),
     ("3rd Degree AV Block",  "Complete P-QRS dissociation, bradycardia, wide QRS",          RED),
     ("RBBB",                 "RSR' in V1 ('M'), wide S in I & V6, QRS ≥ 0.12 s",           AMBER),
     ("LBBB",                 "Broad R in I/V6, QS in V1, no septal Q in I/V6",              AMBER),
     ("STEMI (Inferior)",     "STE ≥ 1 mm in II, III, aVF — reciprocal in I, aVL",          RED),
     ("STEMI (Anterior)",     "STE in V1-V4, loss of R progression",                         RED),
     ("Hyperkalaemia",        "Peaked T → wide P → wide QRS → sine wave",                   GREEN),
     ("Hypokalaemia",         "Flattened T, prominent U waves, ST depression",              GREEN),
     ("PE (Right heart strain)","S1Q3T3: S in I, Q in III, T inversion in III + V1-V4",    TEAL),
     ("WPW",                  "Short PR, delta wave, wide QRS — risk of SVT",               AMBER),
     ("Long QT",              "QTc > 440 ms (M) or > 460 ms (F) — risk of TdP",            RED),
 ],
 "source": "Harrison's 22E | Kaplan & Sadock's | Braunwald's"
},
]

# ── DRAWING FUNCTIONS ────────────────────────────────────────────────────────

def draw_header(c, card):
    accent = card["accent"]
    # top bar
    c.setFillColor(accent)
    c.rect(0, H - 60, W, 60, fill=1, stroke=0)
    # step badge
    c.setFillColor(WHITE)
    c.setFont("Helvetica-Bold", 11)
    c.drawString(18, H - 22, card["step"])
    # icon circle
    c.setFillColor(WHITE)
    c.circle(W - 38, H - 30, 22, fill=1, stroke=0)
    c.setFillColor(accent)
    c.setFont("Helvetica-Bold", 18)
    c.drawCentredString(W - 38, H - 36, card["icon"])
    # title
    c.setFillColor(WHITE)
    c.setFont("Helvetica-Bold", 22)
    c.drawString(18, H - 48, card["title"])

def draw_footer(c, card):
    c.setFillColor(card["accent"])
    c.rect(0, 0, W, 28, fill=1, stroke=0)
    c.setFillColor(WHITE)
    c.setFont("Helvetica-Oblique", 8)
    c.drawString(12, 10, "Source: " + card["source"])
    c.setFont("Helvetica", 8)
    c.drawRightString(W - 12, 10, "ECG Flashcards for Medical Students")

def draw_overview(c, card):
    c.setFillColor(card["bg"])
    c.rect(0, 28, W, H - 88, fill=1, stroke=0)
    c.setFont("Helvetica-Bold", 14)
    c.setFillColor(NAVY)
    c.drawCentredString(W / 2, H - 80, "Follow these 8 steps for EVERY ECG you read")
    y = H - 110
    col_w = (W - 40) / 2
    for i, (num, name, desc, col) in enumerate(card["steps"]):
        col_x = 20 + (i % 2) * (col_w + 20)
        if i % 2 == 0 and i > 0:
            y -= 64
        elif i == 0:
            pass
        # card box
        rr(c, col_x, y - 52, col_w, 54, r=6,
           fill_color=colors.HexColor("#FFFFFF"), stroke_color=col, lw=2)
        # number circle
        c.setFillColor(col)
        c.circle(col_x + 24, y - 24, 14, fill=1, stroke=0)
        c.setFillColor(WHITE)
        c.setFont("Helvetica-Bold", 14)
        c.drawCentredString(col_x + 24, y - 29, num)
        # step name
        c.setFillColor(col)
        c.setFont("Helvetica-Bold", 12)
        c.drawString(col_x + 44, y - 20, name)
        # description
        c.setFillColor(GREY)
        c.setFont("Helvetica", 9)
        c.drawString(col_x + 44, y - 35, desc)

def draw_detail(c, card):
    c.setFillColor(card["bg"])
    c.rect(0, 28, W, H - 88, fill=1, stroke=0)
    y = H - 78

    # question bubble
    rr(c, 12, y - 30, W - 24, 32, r=6,
       fill_color=card["accent"], stroke_color=card["accent"])
    c.setFillColor(WHITE)
    c.setFont("Helvetica-BoldOblique", 11)
    c.drawString(20, y - 18, card["question"])
    y -= 42

    # key fact
    c.setFillColor(WHITE)
    c.setFont("Helvetica-Bold", 11)
    c.drawString(20, y, "Key fact: ")
    c.setFillColor(card["accent"])
    c.setFont("Helvetica-Bold", 11)
    c.drawString(80, y, card["key_fact"])
    y -= 20

    # methods
    for method_title, method_body in card["methods"]:
        rr(c, 12, y - 52, W - 24, 55, r=5,
           fill_color=WHITE, stroke_color=card["accent"], lw=1)
        c.setFillColor(card["accent"])
        c.setFont("Helvetica-Bold", 10)
        c.drawString(20, y - 14, method_title)
        c.setFillColor(GREY)
        c.setFont("Helvetica", 9.5)
        lines = method_body.split("\n")
        ly = y - 28
        for ln in lines:
            c.drawString(24, ly, ln)
            ly -= 12
        y -= 64

    # normal ranges box
    rr(c, 12, y - 58, W / 2 - 16, 60, r=5,
       fill_color=LGREEN, stroke_color=GREEN, lw=1)
    c.setFillColor(GREEN)
    c.setFont("Helvetica-Bold", 10)
    c.drawString(20, y - 14, "Normal Ranges")
    c.setFillColor(GREY)
    c.setFont("Helvetica", 9)
    ny = y - 27
    for r_ in card["normal_ranges"]:
        c.drawString(24, ny, r_)
        ny -= 12

    # tip box
    tx = W / 2 + 4
    rr(c, tx, y - 58, W / 2 - 16, 60, r=5,
       fill_color=LYELLOW, stroke_color=AMBER, lw=1)
    c.setFillColor(AMBER)
    c.setFont("Helvetica-Bold", 10)
    c.drawString(tx + 8, y - 14, "Tip")
    c.setFillColor(GREY)
    c.setFont("Helvetica", 9)
    text_block(c, card["tip"], tx + 8, y - 28, W / 2 - 32,
               size=9, line_h=12)

def draw_table(c, card):
    c.setFillColor(card["bg"])
    c.rect(0, 28, W, H - 88, fill=1, stroke=0)
    y = H - 78
    c.setFillColor(NAVY)
    c.setFont("Helvetica-Bold", 13)
    c.drawCentredString(W / 2, y, "Normal ECG Values — Memorise These!")
    y -= 22
    col_widths = [150, 130, 200]
    col_x = [18, 168, 298]
    row_h = 26
    for i, row in enumerate(card["rows"]):
        if i == 0:
            c.setFillColor(NAVY)
        elif i % 2 == 0:
            c.setFillColor(colors.HexColor("#F0F4FF"))
        else:
            c.setFillColor(WHITE)
        c.rect(col_x[0], y - row_h + 4, W - 36, row_h, fill=1, stroke=0)
        for j, (cx, cw, cell) in enumerate(zip(col_x, col_widths, row)):
            if i == 0:
                c.setFillColor(WHITE)
                c.setFont("Helvetica-Bold", 9.5)
            else:
                c.setFillColor(GREY if j != 2 else RED)
                c.setFont("Helvetica" if j != 0 else "Helvetica-Bold", 9)
            c.drawString(cx + 4, y - row_h + 12, cell)
        y -= row_h

def draw_patterns(c, card):
    c.setFillColor(card["bg"])
    c.rect(0, 28, W, H - 88, fill=1, stroke=0)
    y = H - 78
    c.setFillColor(NAVY)
    c.setFont("Helvetica-Bold", 13)
    c.drawCentredString(W / 2, y, "Learn to recognise these common patterns")
    y -= 18
    col_w = (W - 36) / 2
    for i, (name, desc, col) in enumerate(card["patterns"]):
        cx = 12 + (i % 2) * (col_w + 12)
        if i % 2 == 0 and i > 0:
            y -= 34
        rr(c, cx, y - 28, col_w, 30, r=4,
           fill_color=WHITE, stroke_color=col, lw=1.5)
        c.setFillColor(col)
        c.setFont("Helvetica-Bold", 9)
        c.drawString(cx + 6, y - 11, name)
        c.setFillColor(GREY)
        c.setFont("Helvetica", 8)
        c.drawString(cx + 6, y - 22, desc[:70] + ("..." if len(desc) > 70 else ""))

# ── MAIN ─────────────────────────────────────────────────────────────────────
out_path = "/home/daytona/workspace/ecg-flashcards/ECG_Flashcards.pdf"
c = canvas.Canvas(out_path, pagesize=A4)

for card in cards:
    draw_header(c, card)
    draw_footer(c, card)
    if card["type"] == "overview":
        draw_overview(c, card)
    elif card["type"] == "detail":
        draw_detail(c, card)
    elif card["type"] == "table":
        draw_table(c, card)
    elif card["type"] == "patterns":
        draw_patterns(c, card)
    c.showPage()

c.save()
print("Done:", out_path)
Running Command

cd /home/daytona/workspace/ecg-flashcards && python3 make_flashcards.py 2>&1

Running Command

ls -lh /home/daytona/workspace/ecg-flashcards/ECG_Flashcards.pdf

Your ECG flashcard PDF is ready! Here is your download link:

ECG Flashcards

PDF Document · PDF


What's inside (11 colour-coded flashcards)

CardTopicKey content
OverviewThe 8-Step ApproachRoad map — all 8 steps at a glance
Step 1Heart Rate300÷rule, 6-second rule, normal/Brady/Tachy
Step 2RhythmR-R intervals, P before QRS, AF vs Flutter
Step 3P WavesMorphology, duration, atrial enlargement
Step 4PR IntervalAV blocks (1st, 2nd, 3rd), WPW
Step 5QRS ComplexRBBB, LBBB, Q waves, R-wave progression
Step 6Electrical AxisLead I + aVF trick, LAD vs RAD causes
Step 7ST SegmentSTEMI territories, pericarditis, depression
Step 8T & U WavesInversions, peaked T, QTc, U waves
BonusNormal Intervals TableAll normal values in one quick table
Cheat Sheet14 Common PatternsAF, AV blocks, BBB, STEMI, HyperK, PE, WPW…

Each card has a colour-coded header, a key fact, explanation of methods, normal range boxes, and a clinical tip. Sources are cited from Harrison's Principles of Internal Medicine 22E and Kaplan & Sadock's Comprehensive Textbook of Psychiatry (Cardiology section).
This is a shared conversation. Sign in to Orris to start your own chat.