Palpitations approach mwficine harrisosn flow chaet pdf

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

mkdir -p /tmp/workspace/palpitations && ls /tmp/workspace/palpitations/

Writing File

~/palpitations/palpitations_flowchart.py

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.lib import colors
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

W, H = A4  # 595 x 842

# ── Color palette ──────────────────────────────────────────
DARK_BLUE   = colors.HexColor("#1A3557")
MED_BLUE    = colors.HexColor("#2E6DA4")
LIGHT_BLUE  = colors.HexColor("#D6E8F7")
ORANGE      = colors.HexColor("#E07B39")
ORANGE_LITE = colors.HexColor("#FDECD9")
GREEN       = colors.HexColor("#2E7D53")
GREEN_LITE  = colors.HexColor("#D4EDDA")
RED         = colors.HexColor("#C0392B")
RED_LITE    = colors.HexColor("#FAD7D3")
YELLOW      = colors.HexColor("#F0C040")
YELLOW_LITE = colors.HexColor("#FEF9E7")
GRAY_LITE   = colors.HexColor("#F0F4F8")
GRAY_MID    = colors.HexColor("#B0BEC5")
WHITE       = colors.white
BLACK       = colors.black

def wrap_text(c, text, x, y, max_width, font, size, color=BLACK, align="center"):
    """Simple word-wrap text draw, returns final y."""
    c.setFont(font, size)
    c.setFillColor(color)
    words = text.split()
    line = ""
    lines = []
    for w in words:
        test = (line + " " + w).strip()
        if c.stringWidth(test, font, size) <= max_width:
            line = test
        else:
            lines.append(line)
            line = w
    if line:
        lines.append(line)
    line_h = size * 1.35
    total_h = len(lines) * line_h
    start_y = y + total_h / 2 - line_h / 2
    for i, l in enumerate(lines):
        lw = c.stringWidth(l, font, size)
        if align == "center":
            c.drawString(x - lw / 2, start_y - i * line_h, l)
        else:
            c.drawString(x, start_y - i * line_h, l)
    return start_y - len(lines) * line_h

def rounded_box(c, x, y, w, h, fill, stroke, text, font="Helvetica-Bold",
                fsize=8.5, tcolor=BLACK, radius=8, text2="", fsize2=7.5):
    c.setFillColor(fill)
    c.setStrokeColor(stroke)
    c.setLineWidth(1.4)
    c.roundRect(x, y, w, h, radius, fill=1, stroke=1)
    # primary text
    mid_x = x + w / 2
    mid_y = y + h / 2 + (fsize * 0.6 if text2 else 0)
    wrap_text(c, text, mid_x, mid_y, w - 10, font, fsize, tcolor)
    if text2:
        wrap_text(c, text2, mid_x, y + h / 2 - fsize2 * 0.5, w - 10,
                  "Helvetica", fsize2, tcolor)

def arrow(c, x1, y1, x2, y2, label="", color=DARK_BLUE):
    c.setStrokeColor(color)
    c.setLineWidth(1.3)
    c.line(x1, y1, x2, y2)
    # arrowhead
    import math
    angle = math.atan2(y2 - y1, x2 - x1)
    al = 7
    c.setFillColor(color)
    p = c.beginPath()
    p.moveTo(x2, y2)
    p.lineTo(x2 - al * math.cos(angle - 0.4), y2 - al * math.sin(angle - 0.4))
    p.lineTo(x2 - al * math.cos(angle + 0.4), y2 - al * math.sin(angle + 0.4))
    p.close()
    c.drawPath(p, fill=1, stroke=0)
    if label:
        mx, my = (x1 + x2) / 2, (y1 + y2) / 2
        c.setFont("Helvetica-Oblique", 7)
        c.setFillColor(color)
        lw = c.stringWidth(label, "Helvetica-Oblique", 7)
        c.drawString(mx - lw / 2, my + 2, label)

def horiz_arrow(c, x1, y1, x2, label="", color=DARK_BLUE):
    arrow(c, x1, y1, x2, y1, label, color)

def vert_arrow(c, x1, y1, y2, label="", color=DARK_BLUE):
    arrow(c, x1, y1, x1, y2, label, color)

# ── Build PDF ──────────────────────────────────────────────
out = "/tmp/workspace/palpitations/Palpitations_Harrison_Flowchart.pdf"
c = canvas.Canvas(out, pagesize=A4)

# ══ PAGE 1  ─ APPROACH FLOWCHART ══════════════════════════
# Background
c.setFillColor(GRAY_LITE)
c.rect(0, 0, W, H, fill=1, stroke=0)

# Header bar
c.setFillColor(DARK_BLUE)
c.rect(0, H - 62, W, 62, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 17)
c.drawCentredString(W / 2, H - 30, "APPROACH TO PALPITATIONS")
c.setFont("Helvetica", 9)
c.drawCentredString(W / 2, H - 48, "Harrison's Principles of Internal Medicine, 22nd Ed (2025) — Chapter 45")

# ── ROW 0: Patient presents ──────────────────────────────
bw, bh = 220, 38
bx = W / 2 - bw / 2
by = H - 110
rounded_box(c, bx, by, bw, bh, MED_BLUE, DARK_BLUE,
            "Patient Presents with PALPITATIONS",
            fsize=9.5, tcolor=WHITE)

vert_arrow(c, W / 2, by, by - 20)

# ── ROW 1: History box ──────────────────────────────────
by1 = by - 20 - 72
bw1 = 460
bx1 = W / 2 - bw1 / 2
rounded_box(c, bx1, by1, bw1, 72, LIGHT_BLUE, MED_BLUE,
            "HISTORY & CHARACTERISATION", fsize=9.5, tcolor=DARK_BLUE)
items_h = ["Character: thumping / pounding / fluttering",
           "Pattern: intermittent vs sustained | regular vs irregular",
           "Triggers: exercise, stress, position, substances",
           "Associated Sx: syncope, presyncope, chest pain, dyspnea"]
c.setFont("Helvetica", 7.5)
c.setFillColor(DARK_BLUE)
for i, it in enumerate(items_h):
    c.drawString(bx1 + 14, by1 + 58 - i * 13, u"\u2022 " + it)

vert_arrow(c, W / 2, by1, by1 - 16)

# ── ROW 2: Physical exam ─────────────────────────────────
by2 = by1 - 16 - 55
rounded_box(c, bx1, by2, bw1, 55, LIGHT_BLUE, MED_BLUE,
            "PHYSICAL EXAMINATION", fsize=9.5, tcolor=DARK_BLUE)
items_p = ["Vital signs (HR, BP, temperature)",
           "JVP and peripheral pulse character",
           "Precordium / auscultation (murmurs, gallops)"]
c.setFont("Helvetica", 7.5)
c.setFillColor(DARK_BLUE)
for i, it in enumerate(items_p):
    c.drawString(bx1 + 14, by2 + 44 - i * 13, u"\u2022 " + it)

vert_arrow(c, W / 2, by2, by2 - 16)

# ── ROW 3: Resting ECG ───────────────────────────────────
by3 = by2 - 16 - 38
bw3 = 220
bx3 = W / 2 - bw3 / 2
rounded_box(c, bx3, by3, bw3, 38, YELLOW_LITE, ORANGE,
            "RESTING 12-LEAD ECG", fsize=9.5, tcolor=DARK_BLUE)

vert_arrow(c, W / 2, by3, by3 - 16)

# ── ROW 4: Decision — Arrhythmia documented? ─────────────
by4 = by3 - 16 - 42
bw4 = 260
bx4 = W / 2 - bw4 / 2
rounded_box(c, bx4, by4, bw4, 42, ORANGE_LITE, ORANGE,
            "Arrhythmia documented on ECG?", fsize=9.5, tcolor=DARK_BLUE)

# YES branch (left)
arrow(c, bx4, by4 + 21, bx4 - 90, by4 + 21, "YES", ORANGE)
bwY = 130
bxY = bx4 - 90 - bwY
rounded_box(c, bxY, by4 + 3, bwY, 36, GREEN_LITE, GREEN,
            "Treat specific arrhythmia", fsize=8.5, tcolor=GREEN)

# NO branch (right)
arrow(c, bx4 + bw4, by4 + 21, bx4 + bw4 + 90, by4 + 21, "NO", ORANGE)
bxN = bx4 + bw4 + 90
bwN = 130
rounded_box(c, bxN, by4 + 3, bwN, 36, ORANGE_LITE, ORANGE,
            "Further monitoring required", fsize=8.5, tcolor=DARK_BLUE)

vert_arrow(c, W / 2, by4, by4 - 16)

# ── ROW 5: Risk stratification ───────────────────────────
by5 = by4 - 16 - 70
bw5 = 460
bx5 = W / 2 - bw5 / 2
rounded_box(c, bx5, by5, bw5, 70, RED_LITE, RED,
            "RISK STRATIFICATION — High-Risk Features?", fsize=9.5, tcolor=RED)
risk_items = ["Structural heart disease / CAD / prior MI",
              "Syncope or pre-syncope with palpitations",
              "Angina / dyspnea during palpitation episode",
              "Ventricular dysfunction, AS, HCM, or MS"]
c.setFont("Helvetica", 7.5)
c.setFillColor(RED)
for i, it in enumerate(risk_items):
    c.drawString(bx5 + 14, by5 + 58 - i * 13, u"\u25B6 " + it)

# ── ROW 6: Two branches ──────────────────────────────────
vert_arrow(c, W / 2, by5, by5 - 16)

by6 = by5 - 16 - 75

# LEFT: High risk
bwL = 205
bxL = 30
rounded_box(c, bxL, by6, bwL, 75, RED_LITE, RED,
            "HIGH RISK", fsize=9.5, tcolor=RED)
risk_mgmt = ["Urgent cardiology referral",
             "Inpatient monitoring",
             "Echo, EP study if indicated",
             "Treat underlying cause"]
c.setFont("Helvetica", 7.5)
c.setFillColor(RED)
for i, it in enumerate(risk_mgmt):
    c.drawString(bxL + 12, by6 + 63 - i * 13, u"\u2022 " + it)
arrow(c, W / 2 - 20, by5 - 16, bxL + bwL / 2, by6 + 75, "YES", RED)

# RIGHT: Low risk
bxR = W - 30 - bwL
rounded_box(c, bxR, by6, bwL, 75, GREEN_LITE, GREEN,
            "LOW RISK", fsize=9.5, tcolor=GREEN)
low_mgmt = ["Ambulatory Holter (24-48 h)",
            "Event / loop recorder (infrequent Sx)",
            "Mobile cardiac telemetry",
            "Exercise ECG if exertion-induced"]
c.setFont("Helvetica", 7.5)
c.setFillColor(GREEN)
for i, it in enumerate(low_mgmt):
    c.drawString(bxR + 12, by6 + 63 - i * 13, u"\u2022 " + it)
arrow(c, W / 2 + 20, by5 - 16, bxR + bwL / 2, by6 + 75, "NO", GREEN)

# ── Footer ───────────────────────────────────────────────
c.setFillColor(DARK_BLUE)
c.rect(0, 0, W, 22, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica", 7)
c.drawCentredString(W / 2, 7, "Based on Harrison's Principles of Internal Medicine, 22nd Ed — Chapter 45: Palpitations (Joseph Loscalzo)")

c.showPage()

# ══ PAGE 2  ─ CAUSES & MONITORING TABLE ═══════════════════
c.setFillColor(GRAY_LITE)
c.rect(0, 0, W, H, fill=1, stroke=0)

# Header
c.setFillColor(DARK_BLUE)
c.rect(0, H - 62, W, 62, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 16)
c.drawCentredString(W / 2, H - 30, "CAUSES OF PALPITATIONS & MONITORING MODALITIES")
c.setFont("Helvetica", 9)
c.drawCentredString(W / 2, H - 48, "Harrison's Principles of Internal Medicine, 22nd Ed (2025)")

# ── Causes grid ──────────────────────────────────────────
causes = [
    ("CARDIOVASCULAR  43%", MED_BLUE, LIGHT_BLUE, [
        "Premature atrial contractions (PAC)",
        "Premature ventricular contractions (PVC)",
        "Supraventricular tachycardias (SVT)",
        "Ventricular tachyarrhythmias",
        "Atrial fibrillation / flutter",
        "Mitral valve prolapse",
        "Aortic regurgitation (hyperdynamic)",
        "Atrial myxoma",
        "Pulmonary embolism",
        "Myocarditis",
    ]),
    ("PSYCHIATRIC  31%", colors.HexColor("#7B3FA0"), colors.HexColor("#EDD9F7"), [
        "Panic attacks / panic disorder",
        "Generalized anxiety states",
        "Somatization disorder",
        "NOTE: Duration often >15 min",
        "More associated symptoms",
    ]),
    ("MISCELLANEOUS  10%", ORANGE, ORANGE_LITE, [
        "Thyrotoxicosis",
        "Pheochromocytoma",
        "Systemic mastocytosis",
        "Post-COVID syndrome",
        "Caffeine / alcohol / tobacco",
        "Drugs: aminophylline, atropine,",
        "  thyroxine, cocaine, amphetamines",
        "Chest wall muscle contractions",
        "Athletes (endurance sports)",
    ]),
    ("UNKNOWN  16%", GRAY_MID, colors.HexColor("#ECEFF1"), [
        "No identifiable cause found",
        "After full workup",
    ]),
]

col_w = (W - 40) / 4
row_start = H - 90
box_h = 185
for i, (title, stroke_col, fill_col, items) in enumerate(causes):
    bx_c = 20 + i * col_w
    c.setFillColor(fill_col)
    c.setStrokeColor(stroke_col)
    c.setLineWidth(1.5)
    c.roundRect(bx_c, row_start - box_h, col_w - 6, box_h, 8, fill=1, stroke=1)
    c.setFillColor(stroke_col)
    c.roundRect(bx_c, row_start - 28, col_w - 6, 28, 8, fill=1, stroke=0)
    c.setFillColor(WHITE)
    c.setFont("Helvetica-Bold", 8)
    tw = c.stringWidth(title, "Helvetica-Bold", 8)
    c.drawString(bx_c + (col_w - 6 - tw) / 2, row_start - 18, title)
    c.setFillColor(BLACK)
    c.setFont("Helvetica", 7.5)
    for j, it in enumerate(items):
        c.drawString(bx_c + 8, row_start - 44 - j * 14, u"\u2022 " + it)

# ── Monitoring modalities ─────────────────────────────────
my = row_start - box_h - 30
c.setFillColor(DARK_BLUE)
c.setFont("Helvetica-Bold", 11)
c.drawCentredString(W / 2, my, "MONITORING MODALITIES (choose based on frequency of symptoms)")
my -= 15

mod_data = [
    ("Resting 12-Lead ECG", "Always first-line\nDocuments arrhythmia if present during recording", MED_BLUE, LIGHT_BLUE),
    ("Exercise ECG", "When symptoms triggered by exertion", GREEN, GREEN_LITE),
    ("Holter Monitor\n24-48 hours", "Daily or near-daily palpitations\nNote: Limited clinical utility for infrequent Sx", ORANGE, ORANGE_LITE),
    ("Event / Loop Recorder\n(External)", "Weekly or less frequent episodes\nPatient activates during symptoms", colors.HexColor("#7B3FA0"), colors.HexColor("#EDD9F7")),
    ("Implantable Loop Recorder", "Very infrequent / unexplained syncope+palpitations\nSafe, cost-effective; long-term monitoring", RED, RED_LITE),
    ("Mobile Cardiac Telemetry\n(Outpatient)", "Real-time remote ECG transmission\nPossibly most cost-effective for recurrent Sx", colors.HexColor("#1A7A4A"), GREEN_LITE),
]

mod_bw = (W - 40) / 3
mod_bh = 68
for i, (title, desc, stroke_c, fill_c) in enumerate(mod_data):
    col = i % 3
    row = i // 3
    mx_box = 20 + col * mod_bw
    my_box = my - row * (mod_bh + 8) - mod_bh
    c.setFillColor(fill_c)
    c.setStrokeColor(stroke_c)
    c.setLineWidth(1.2)
    c.roundRect(mx_box, my_box, mod_bw - 6, mod_bh, 7, fill=1, stroke=1)
    c.setFillColor(stroke_c)
    c.setFont("Helvetica-Bold", 8.5)
    lines_t = title.split("\n")
    for li, lt in enumerate(lines_t):
        c.drawString(mx_box + 8, my_box + mod_bh - 14 - li * 11, lt)
    c.setFillColor(BLACK)
    c.setFont("Helvetica", 7.5)
    lines_d = desc.split("\n")
    for li, ld in enumerate(lines_d):
        c.drawString(mx_box + 8, my_box + mod_bh - 14 - len(lines_t) * 11 - li * 11 - 4, u"\u2022 " + ld)

# ── Management notes ──────────────────────────────────────
note_y = my - 2 * (mod_bh + 8) - mod_bh - 22
c.setFillColor(YELLOW_LITE)
c.setStrokeColor(YELLOW)
c.setLineWidth(1.5)
c.roundRect(20, note_y - 72, W - 40, 72, 8, fill=1, stroke=1)
c.setFillColor(DARK_BLUE)
c.setFont("Helvetica-Bold", 9.5)
c.drawString(28, note_y - 16, "MANAGEMENT PRINCIPLES (Harrison's)")
c.setFont("Helvetica", 8)
notes = [
    "Most patients do NOT have serious arrhythmias or structural heart disease — reassure once serious causes excluded.",
    "Benign PACs/PVCs: beta-blocker therapy if sufficiently troubling to the patient.",
    "Substance-induced (alcohol, tobacco, caffeine, cocaine): abstinence is the treatment.",
    "Drug-induced: consider alternative agents when clinically possible.",
    "Psychiatric causes: cognitive behavioural therapy and/or pharmacotherapy.",
    "Patient diary or electronic marker essential for correlation with monitoring studies.",
]
c.setFillColor(BLACK)
for i, n in enumerate(notes):
    c.drawString(28, note_y - 32 - i * 11, u"\u2022 " + n)

# Footer
c.setFillColor(DARK_BLUE)
c.rect(0, 0, W, 22, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica", 7)
c.drawCentredString(W / 2, 7, "Based on Harrison's Principles of Internal Medicine, 22nd Ed — Chapter 45: Palpitations (Joseph Loscalzo)")

c.showPage()
c.save()
print("PDF saved:", out)
Running Command

cd /tmp/workspace/palpitations && python palpitations_flowchart.py

The PDF was generated successfully. Here is your complete reference:

Palpitations Approach - Harrison's Medicine (22nd Ed, 2025)

Your 2-page flowchart PDF is ready for download:

Palpitations Harrison Flowchart

PDF Document · PDF


What's inside (Chapter 45, Joseph Loscalzo):

Page 1 - Clinical Approach Flowchart:
  • Patient presents with palpitations
  • History characterization (character, pattern, triggers, associated symptoms)
  • Physical examination (vitals, JVP, auscultation)
  • Resting 12-lead ECG - first-line test
  • Decision point: arrhythmia documented?
  • Risk stratification with high-risk features (CAD, structural disease, syncope, hemodynamic compromise)
  • Separate high-risk vs low-risk management branches
Page 2 - Causes & Monitoring Modalities:
CategoryFrequencyExamples
Cardiovascular43%PAC, PVC, SVT, VT, AF, MVP, AR, PE
Psychiatric31%Panic disorder, anxiety, somatization
Miscellaneous10%Thyrotoxicosis, pheo, drugs, post-COVID
Unknown16%No cause found
Monitoring selection:
  • Holter 24-48h - daily symptoms (limited utility for infrequent events)
  • Event/loop recorder - weekly or less frequent episodes
  • Implantable loop recorder - very infrequent/unexplained syncope + palpitations
  • Mobile cardiac telemetry - possibly most cost-effective for recurrent symptoms
Key management pearl from Harrison's: Most patients do NOT have serious arrhythmias. Once serious causes are excluded, reassure the patient that palpitations will not adversely affect overall prognosis.

Create a quick reference card (PDF) with differential diagnosis by rhythm pattern

Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Writing File

~/palpitations/qrc_rhythm_ddx.py

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm
from reportlab.lib import colors
from reportlab.pdfgen import canvas
import math

W, H = A4  # 595 x 842

# ── Palette ────────────────────────────────────────────────
C = {
    "dark":    colors.HexColor("#0D2B45"),
    "navy":    colors.HexColor("#1A3F63"),
    "blue":    colors.HexColor("#2E6DA4"),
    "lblue":   colors.HexColor("#D6EAF8"),
    "teal":    colors.HexColor("#117A65"),
    "lteal":   colors.HexColor("#D1F2EB"),
    "purple":  colors.HexColor("#6C3483"),
    "lpurple": colors.HexColor("#E8DAEF"),
    "orange":  colors.HexColor("#CA6F1E"),
    "lorange": colors.HexColor("#FDEBD0"),
    "red":     colors.HexColor("#A93226"),
    "lred":    colors.HexColor("#FADBD8"),
    "green":   colors.HexColor("#196F3D"),
    "lgreen":  colors.HexColor("#D5F5E3"),
    "gold":    colors.HexColor("#B7950B"),
    "lgold":   colors.HexColor("#FEF9E7"),
    "gray":    colors.HexColor("#717D7E"),
    "lgray":   colors.HexColor("#F2F3F4"),
    "white":   colors.white,
    "black":   colors.black,
}

def rbox(c, x, y, w, h, fill, stroke, r=6, lw=1.2):
    c.setFillColor(fill); c.setStrokeColor(stroke); c.setLineWidth(lw)
    c.roundRect(x, y, w, h, r, fill=1, stroke=1)

def hdr(c, x, y, w, h, fill, text, fsize=8.5, tcolor=None):
    tcolor = tcolor or C["white"]
    rbox(c, x, y, w, h, fill, fill, r=5)
    c.setFillColor(tcolor); c.setFont("Helvetica-Bold", fsize)
    c.drawCentredString(x + w/2, y + h/2 - fsize*0.35, text)

def bullet(c, x, y, text, fsize=7, color=None, indent=6, sym=u"\u2022"):
    color = color or C["black"]
    c.setFont("Helvetica", fsize); c.setFillColor(color)
    c.drawString(x, y, sym + " " + text)

def tag(c, x, y, text, fill, tcolor=None, fsize=6.5):
    tcolor = tcolor or C["white"]
    tw = c.stringWidth(text, "Helvetica-Bold", fsize) + 8
    rbox(c, x, y - fsize, tw, fsize + 4, fill, fill, r=3, lw=0)
    c.setFillColor(tcolor); c.setFont("Helvetica-Bold", fsize)
    c.drawString(x + 4, y - fsize*0.2, text)
    return tw + 4

# ═══════════════════════════════════════════════════════════
# PAGE 1  — Main Quick Reference Card
# ═══════════════════════════════════════════════════════════
out = "/tmp/workspace/palpitations/Palpitations_Rhythm_DDx_QRC.pdf"
cv = canvas.Canvas(out, pagesize=A4)

# Background
cv.setFillColor(C["lgray"]); cv.rect(0, 0, W, H, fill=1, stroke=0)

# ── HEADER ─────────────────────────────────────────────────
cv.setFillColor(C["dark"]); cv.rect(0, H-58, W, 58, fill=1, stroke=0)
cv.setFillColor(C["white"]); cv.setFont("Helvetica-Bold", 16)
cv.drawCentredString(W/2, H-26, "PALPITATIONS: DIFFERENTIAL DIAGNOSIS BY RHYTHM PATTERN")
cv.setFont("Helvetica", 8)
cv.drawCentredString(W/2, H-44, "Quick Reference Card  |  Based on Harrison's 22e, Braunwald's Heart Disease, Goldman-Cecil Medicine")

# ── COLUMN LAYOUT: 3 equal cols ───────────────────────────
MARGIN = 10
GAP = 7
col_w = (W - 2*MARGIN - 2*GAP) / 3
col_x = [MARGIN, MARGIN + col_w + GAP, MARGIN + 2*(col_w + GAP)]
TOP = H - 68

# ────────────────────────────────────────────────────────────
# SECTION 1: INTERMITTENT SINGLE BEATS  (col 0)
# ────────────────────────────────────────────────────────────
y = TOP
sh = 17  # section header height
rbox(cv, col_x[0], y - 175, col_w, 175, C["lblue"], C["blue"], r=7)
hdr(cv, col_x[0], y - sh, col_w, sh, C["blue"],
    "INTERMITTENT / SINGLE BEATS", 8)

rows = [
    ("Premature Atrial Contraction",  "PAC",  C["blue"],
     ["\"Skipped beat\" or flip-flop sensation",
      "Pause followed by forceful beat",
      "Common, usually benign",
      "Worse with caffeine/stress/fatigue"]),
    ("Premature Ventricular Contraction", "PVC", C["navy"],
     ["Similar to PAC but often more forceful",
      "\"Thump\" or \"kick\" in chest",
      "Post-extrasystolic potentiation",
      "High burden (>10%) may cause CMP"]),
]
ry = y - sh - 6
for name, abbr, acol, pts in rows:
    cv.setFont("Helvetica-Bold", 7.5); cv.setFillColor(acol)
    cv.drawString(col_x[0]+6, ry, name + f"  [{abbr}]")
    ry -= 2
    for pt in pts:
        bullet(cv, col_x[0]+8, ry, pt, fsize=7, color=C["dark"])
        ry -= 11
    ry -= 4

# ── ECG clue box
ry -= 2
cv.setFillColor(C["lgold"]); cv.setStrokeColor(C["gold"]); cv.setLineWidth(1)
cv.roundRect(col_x[0]+4, ry-26, col_w-8, 26, 4, fill=1, stroke=1)
cv.setFillColor(C["gold"]); cv.setFont("Helvetica-Bold", 7)
cv.drawString(col_x[0]+8, ry-10, u"\u26A1  ECG: Isolated early beats; compensatory pause")
cv.setFont("Helvetica", 7); cv.setFillColor(C["dark"])
cv.drawString(col_x[0]+8, ry-21, "PVC: wide aberrant QRS. PAC: narrow, premature P wave")

# ────────────────────────────────────────────────────────────
# SECTION 2: REGULAR SUSTAINED (col 0, lower)
# ────────────────────────────────────────────────────────────
y2 = y - 175 - GAP
rbox(cv, col_x[0], y2 - 265, col_w, 265, C["lteal"], C["teal"], r=7)
hdr(cv, col_x[0], y2 - sh, col_w, sh, C["teal"],
    "REGULAR SUSTAINED PALPITATIONS", 8)

reg_rows = [
    ("Sinus Tachycardia", "ST", C["teal"],
     ["Gradual onset & offset",
      "Rate 100-130 bpm (can be higher)",
      "Causes: exercise, pain, fever, anxiety,",
      "  hypovolemia, anemia, thyrotoxicosis,",
      "  PE, drugs, pheochromocytoma"]),
    ("AVNRT / AVRT (SVT)", "SVT", C["green"],
     ["Sudden onset & sudden offset",
      "\"Heart jumping out of chest\"",
      "Rate 150-250 bpm, too fast to count",
      "Responds to vagal maneuvers",
      "Polyuria after episode (ANP release)",
      "Triggers: sudden movement, bending"]),
    ("Atrial Tachycardia (Focal)", "AT", C["teal"],
     ["Short bursts seconds-minutes",
      "Normal rhythm between episodes",
      "Automatic focal mechanism"]),
    ("Ventricular Tachycardia", "VT", C["red"],
     ["High risk — structural heart disease",
      "Syncope / near-syncope common",
      "Wide complex on ECG",
      "Urgent evaluation required"]),
]
ry2 = y2 - sh - 6
for name, abbr, acol, pts in reg_rows:
    cv.setFont("Helvetica-Bold", 7.5); cv.setFillColor(acol)
    cv.drawString(col_x[0]+6, ry2, name + f"  [{abbr}]")
    ry2 -= 2
    for pt in pts:
        bullet(cv, col_x[0]+8, ry2, pt, fsize=7, color=C["dark"])
        ry2 -= 11
    ry2 -= 3

# ECG clue
ry2 -= 2
cv.setFillColor(C["lgold"]); cv.setStrokeColor(C["gold"]); cv.setLineWidth(1)
cv.roundRect(col_x[0]+4, ry2-26, col_w-8, 26, 4, fill=1, stroke=1)
cv.setFillColor(C["gold"]); cv.setFont("Helvetica-Bold", 7)
cv.drawString(col_x[0]+8, ry2-10, u"\u26A1  ECG: Narrow QRS = SVT likely")
cv.setFont("Helvetica", 7); cv.setFillColor(C["dark"])
cv.drawString(col_x[0]+8, ry2-21, "Wide QRS = VT until proven otherwise. Check prior ECG")

# ────────────────────────────────────────────────────────────
# COLUMN 1: IRREGULAR PATTERNS
# ────────────────────────────────────────────────────────────
y = TOP
rbox(cv, col_x[1], y - 265, col_w, 265, C["lpurple"], C["purple"], r=7)
hdr(cv, col_x[1], y - sh, col_w, sh, C["purple"],
    "IRREGULAR PALPITATIONS", 8)

irr_rows = [
    ("Atrial Fibrillation", "AF", C["purple"],
     ["Irregularly irregular rhythm",
      "Prolonged racing, hours to days",
      "Rate typically 100-160 bpm",
      "Dyspnea, fatigue, reduced exercise tolerance",
      "Risk: hypertension, valvular disease,",
      "  hyperthyroidism, OSA, alcohol",
      "Stroke risk — requires anticoagulation",
      "First presentation may be incidental"]),
    ("Atrial Flutter", "AFL", C["purple"],
     ["Usually regular 2:1 block → ~150 bpm",
      "Can be irregular with variable block",
      "\"Sawtooth\" flutter waves on ECG",
      "Often co-exists with AF"]),
    ("Multifocal Atrial Tachycardia", "MAT", C["purple"],
     ["≥3 different P wave morphologies",
      "Irregular, rate 100-180 bpm",
      "Classic: severe COPD, critical illness",
      "Correct hypoxia / electrolytes first"]),
    ("High-burden PAC/PVC", "", C["gray"],
     ["Felt as \"constantly missing beats\"",
      "Continuous irregular sensation",
      "Differentiate from AF with Holter"]),
]
ry = y - sh - 6
for name, abbr, acol, pts in irr_rows:
    lbl = f"  [{abbr}]" if abbr else ""
    cv.setFont("Helvetica-Bold", 7.5); cv.setFillColor(acol)
    cv.drawString(col_x[1]+6, ry, name + lbl)
    ry -= 2
    for pt in pts:
        bullet(cv, col_x[1]+8, ry, pt, fsize=7, color=C["dark"])
        ry -= 11
    ry -= 3

# ECG clue
ry -= 2
cv.setFillColor(C["lgold"]); cv.setStrokeColor(C["gold"]); cv.setLineWidth(1)
cv.roundRect(col_x[1]+4, ry-26, col_w-8, 26, 4, fill=1, stroke=1)
cv.setFillColor(C["gold"]); cv.setFont("Helvetica-Bold", 7)
cv.drawString(col_x[1]+8, ry-10, u"\u26A1  ECG: Absent P waves + irregular = AF")
cv.setFont("Helvetica", 7); cv.setFillColor(C["dark"])
cv.drawString(col_x[1]+8, ry-21, "Sawtooth at 300 bpm = flutter. Varying P morphology = MAT")

# ── NON-CARDIAC DDx (col 1, lower) ──────────────────────
y2 = y - 265 - GAP
rbox(cv, col_x[1], y2 - 175, col_w, 175, C["lorange"], C["orange"], r=7)
hdr(cv, col_x[1], y2 - sh, col_w, sh, C["orange"],
    "NON-CARDIAC DIFFERENTIAL (31-26%)", 8)

noncard = [
    ("Panic Disorder / Anxiety", C["orange"],
     ["Duration often >15 min",
      "Multiple somatic symptoms",
      "Fear of dying during episode",
      "Hyperventilation, tingling, derealization"]),
    ("Thyrotoxicosis", C["orange"],
     ["Heat intolerance, weight loss",
      "Tremor, diarrhea, eye signs",
      "Sinus tach or AF as first presentation",
      "Check TSH + free T4"]),
    ("Pheochromocytoma", C["red"],
     ["Episodic headache + sweating + palpitations",
      "Hypertensive crisis during episodes",
      "24h urine metanephrines / catecholamines"]),
    ("Post-COVID Syndrome", C["gray"],
     ["POTS features common",
      "Tachycardia on standing",
      "Fatigue, brain fog"]),
]
ry2 = y2 - sh - 6
for name, acol, pts in noncard:
    cv.setFont("Helvetica-Bold", 7.5); cv.setFillColor(acol)
    cv.drawString(col_x[1]+6, ry2, name)
    ry2 -= 2
    for pt in pts:
        bullet(cv, col_x[1]+8, ry2, pt, fsize=7, color=C["dark"])
        ry2 -= 11
    ry2 -= 3

# ────────────────────────────────────────────────────────────
# COLUMN 2: WIDE-COMPLEX TACHYCARDIA TREE + SPECIAL PATTERNS
# ────────────────────────────────────────────────────────────
y = TOP
bh_tree = 210
rbox(cv, col_x[2], y - bh_tree, col_w, bh_tree, C["lred"], C["red"], r=7)
hdr(cv, col_x[2], y - sh, col_w, sh, C["red"],
    "WIDE-COMPLEX TACHYCARDIA (QRS ≥120 ms)", 8)

# Tree structure
ty = y - sh - 12
cv.setFont("Helvetica-Bold", 8); cv.setFillColor(C["red"])
cv.drawString(col_x[2]+6, ty, "REGULAR")
ty -= 11
wc_reg = [
    (u"\u2192 Ventricular Tachycardia (VT)", C["red"],
     ["Monomorphic — fixed QRS morphology",
      "Structural heart disease common",
      "Brugada / ARVC / ischemic scar"]),
    (u"\u2192 SVT with aberrancy", C["navy"],
     ["Bundle branch block pattern",
      "Previous BBB or rate-related BBB"]),
    (u"\u2192 SVT via accessory pathway", C["navy"],
     ["WPW: delta wave in sinus rhythm",
      "Pre-excited AF → irregular wide-complex"]),
]
for lbl, lc, subs in wc_reg:
    cv.setFont("Helvetica-Bold", 7); cv.setFillColor(lc)
    cv.drawString(col_x[2]+8, ty, lbl); ty -= 2
    for s in subs:
        bullet(cv, col_x[2]+16, ty, s, fsize=6.5, color=C["dark"]); ty -= 10
    ty -= 2

cv.setFont("Helvetica-Bold", 8); cv.setFillColor(C["red"])
cv.drawString(col_x[2]+6, ty, "IRREGULAR / POLYMORPHIC")
ty -= 11
wc_irr = [
    (u"\u2192 Torsades de Pointes (TdP)", C["red"],
     ["Prolonged QT (congenital or acquired)",
      "Drugs: antiarrhythmics, macrolides,",
      "  antipsychotics, fluoroquinolones",
      "Electrolyte: \u2193K\u207a, \u2193Mg\u00b2\u207a, \u2193Ca\u00b2\u207a"]),
    (u"\u2192 Pre-excited AF (WPW)", C["purple"],
     ["Irregular wide-complex tachycardia",
      "AVOID AV nodal blockers (digoxin, CCB)"]),
]
for lbl, lc, subs in wc_irr:
    cv.setFont("Helvetica-Bold", 7); cv.setFillColor(lc)
    cv.drawString(col_x[2]+8, ty, lbl); ty -= 2
    for s in subs:
        bullet(cv, col_x[2]+16, ty, s, fsize=6.5, color=C["dark"]); ty -= 10
    ty -= 2

# ECG rule-of-thumb
cv.setFillColor(C["lgold"]); cv.setStrokeColor(C["gold"]); cv.setLineWidth(1)
cv.roundRect(col_x[2]+4, ty-24, col_w-8, 24, 4, fill=1, stroke=1)
cv.setFillColor(C["gold"]); cv.setFont("Helvetica-Bold", 7)
cv.drawString(col_x[2]+8, ty-10, u"\u26A1  Wide + hemodynamic instability = VT until proven")
cv.setFont("Helvetica", 7); cv.setFillColor(C["dark"])
cv.drawString(col_x[2]+8, ty-20, "Use Brugada / Vereckei criteria to differentiate VT vs SVT")

# ── BRADYCARDIA / PAUSES (col 2, middle) ────────────────
y2b = y - bh_tree - GAP
bh_brad = 120
rbox(cv, col_x[2], y2b - bh_brad, col_w, bh_brad, C["lgold"], C["gold"], r=7)
hdr(cv, col_x[2], y2b - sh, col_w, sh, C["gold"],
    "BRADYCARDIA / PAUSES", 8, tcolor=C["dark"])

brad_data = [
    ("Sinus Bradycardia / SA Block", C["gold"],
     ["\"Thump\" followed by pause",
      "Athletes, vasovagal, sick sinus syndrome"]),
    ("AV Block (2nd / 3rd degree)", C["orange"],
     ["Dropped beats — irregular rhythm",
      "Syncope in complete heart block",
      "Check for Lyme disease, sarcoidosis"]),
    ("Sinus Arrest", C["red"],
     ["Sudden pause — dizziness/near-syncope",
      "Sick sinus syndrome"]),
]
ry2b = y2b - sh - 8
for name, acol, pts in brad_data:
    cv.setFont("Helvetica-Bold", 7); cv.setFillColor(acol)
    cv.drawString(col_x[2]+6, ry2b, name); ry2b -= 2
    for pt in pts:
        bullet(cv, col_x[2]+8, ry2b, pt, fsize=6.5, color=C["dark"]); ry2b -= 10
    ry2b -= 3

# ── POSITIONAL / STRUCTURAL (col 2, lower) ──────────────
y3 = y2b - bh_brad - GAP
bh3 = 93
rbox(cv, col_x[2], y3 - bh3, col_w, bh3, C["lgreen"], C["green"], r=7)
hdr(cv, col_x[2], y3 - sh, col_w, sh, C["green"],
    "POSITIONAL / STRUCTURAL", 8)
struct_data = [
    ("Atrial Myxoma", ["Positional palpitations", "Tumor plop on auscultation", "Echo diagnostic"]),
    ("Mediastinal Mass", ["Positional component", "CXR / CT chest needed"]),
    ("Mitral Valve Prolapse", ["Mid-systolic click", "Associated with PAC/SVT", "Usually benign"]),
    ("Hyperdynamic States", ["Aortic regurgitation", "Anaemia, thyrotoxicosis", "Pregnancy"]),
]
ry3 = y3 - sh - 8
for name, pts in struct_data:
    cv.setFont("Helvetica-Bold", 7); cv.setFillColor(C["green"])
    cv.drawString(col_x[2]+6, ry3, name); ry3 -= 2
    for pt in pts:
        bullet(cv, col_x[2]+8, ry3, pt, fsize=6.5, color=C["dark"]); ry3 -= 10
    ry3 -= 2

# ────────────────────────────────────────────────────────────
# BOTTOM BAND: Key Clinical Questions + Red Flags
# ────────────────────────────────────────────────────────────
bot_h = 68
bot_y = 24
rbox(cv, MARGIN, bot_y, W - 2*MARGIN, bot_h, C["dark"], C["dark"], r=6)

# Red flags column
cv.setFillColor(C["lred"]); cv.setFont("Helvetica-Bold", 8)
cv.drawString(MARGIN+8, bot_y + bot_h - 14, u"\U0001F6A8  RED FLAGS — Urgent Cardiology")
red_flags = ["Syncope/presyncope with palpitations",
             "Structural heart disease / prior MI",
             "Wide-complex or polymorphic tachycardia",
             "Family Hx sudden death <50 yrs"]
cv.setFont("Helvetica", 7); cv.setFillColor(C["lred"])
for i, rf in enumerate(red_flags):
    cv.drawString(MARGIN+8, bot_y + bot_h - 26 - i*10, u"\u25B6 " + rf)

# Divider
cv.setStrokeColor(C["blue"]); cv.setLineWidth(0.5)
cv.line(MARGIN + col_w + 4, bot_y+6, MARGIN + col_w + 4, bot_y + bot_h - 4)

# Ask-the-patient column
cv.setFillColor(C["lblue"]); cv.setFont("Helvetica-Bold", 8)
cv.drawString(col_x[1], bot_y + bot_h - 14, u"\U0001F4CB  ASK: Characterise the Rhythm")
ask_q = ["Regular or irregular? Fast or slow?",
         "Sudden or gradual onset/offset?",
         "Tap out the rhythm with your finger",
         "Duration of each episode?"]
cv.setFont("Helvetica", 7); cv.setFillColor(C["lblue"])
for i, q in enumerate(ask_q):
    cv.drawString(col_x[1], bot_y + bot_h - 26 - i*10, u"\u2022 " + q)

# Divider
cv.line(col_x[2], bot_y+6, col_x[2], bot_y + bot_h - 4)

# Investigations column
cv.setFillColor(C["lgreen"]); cv.setFont("Helvetica-Bold", 8)
cv.drawString(col_x[2], bot_y + bot_h - 14, u"\U0001F4C8  INVESTIGATIONS")
inv = ["ECG (always)  |  Holter / event recorder",
       "Echo if structural disease suspected",
       "TSH, FBC, electrolytes, glucose",
       "24h urine metanephrines if pheo suspected"]
cv.setFont("Helvetica", 7); cv.setFillColor(C["lgreen"])
for i, inv_item in enumerate(inv):
    cv.drawString(col_x[2], bot_y + bot_h - 26 - i*10, u"\u2022 " + inv_item)

# Footer line
cv.setFillColor(C["dark"]); cv.rect(0, 0, W, 22, fill=1, stroke=0)
cv.setFillColor(C["white"]); cv.setFont("Helvetica", 7)
cv.drawCentredString(W/2, 7,
    "Sources: Harrison's 22e (Loscalzo), Braunwald's Heart Disease (Calkins et al.), Goldman-Cecil Medicine, Frameworks for Internal Medicine")

cv.showPage()

# ═══════════════════════════════════════════════════════════
# PAGE 2  — SUMMARY TABLE: Rhythm Pattern at a Glance
# ═══════════════════════════════════════════════════════════
cv.setFillColor(C["lgray"]); cv.rect(0, 0, W, H, fill=1, stroke=0)
cv.setFillColor(C["dark"]); cv.rect(0, H-55, W, 55, fill=1, stroke=0)
cv.setFillColor(C["white"]); cv.setFont("Helvetica-Bold", 14)
cv.drawCentredString(W/2, H-28, "PALPITATIONS: AT-A-GLANCE RHYTHM PATTERN TABLE")
cv.setFont("Helvetica", 8)
cv.drawCentredString(W/2, H-44, "Harrison's 22e · Braunwald's · Goldman-Cecil")

# Table columns
cols_def = [
    ("RHYTHM",         80),
    ("PATTERN",        70),
    ("ONSET/OFFSET",   72),
    ("RATE (bpm)",     60),
    ("KEY FEATURES",   160),
    ("ECG CLUE",       120),
]
total_w = sum(v for _, v in cols_def)
scale = (W - 20) / total_w
col_widths = [v * scale for _, v in cols_def]
col_labels = [l for l, _ in cols_def]

TABLE_X = 10
TABLE_TOP = H - 62
ROW_H = 14

# Header row
cv.setFillColor(C["navy"])
xc = TABLE_X
for i, (lbl, cw) in enumerate(zip(col_labels, col_widths)):
    cv.setFillColor(C["navy"]); cv.rect(xc, TABLE_TOP - ROW_H, cw, ROW_H, fill=1, stroke=0)
    cv.setFillColor(C["white"]); cv.setFont("Helvetica-Bold", 7)
    cv.drawCentredString(xc + cw/2, TABLE_TOP - ROW_H + 4, lbl)
    xc += cw

# Data rows
table_data = [
    # Rhythm,           Pattern,          Onset/Off,        Rate,         KeyFeatures,                                  ECG
    ("PAC / PVC",       "Intermittent",   "Spontaneous",    "N/A",        "Skipped beat; forceful post-ectopic beat; caffeine/stress trigger; high PVC burden may cause CMP",  "Isolated early beat; wide (PVC) or narrow (PAC) QRS; compensatory pause", C["lblue"]),
    ("Sinus Tachycardia","Regular",       "Gradual / Gradual","100-180",  "Secondary cause always; fever, anaemia, hypovolemia, thyrotoxicosis, PE, pain, anxiety",          "Normal P waves; PR/QRS normal; rate slows with Valsalva", C["lteal"]),
    ("AVNRT",           "Regular",        "Sudden / Sudden", "150-250",   "Rapid racing too fast to count; responds to vagal; polyuria after; women > men",                  "Narrow QRS; retrograde P in/after QRS (\"pseudo-R'\" in V1)", C["lteal"]),
    ("AVRT (WPW)",      "Regular",        "Sudden / Sudden", "150-250",   "Young patients; orthodromic = narrow; antidromic = wide; risk of pre-excited AF",                 "Delta wave in sinus rhythm; short PR. Wide-complex if antidromic", C["lteal"]),
    ("Focal AT",        "Regular/bursts", "Sudden / Gradual","100-200",   "Short bursts seconds-minutes; automatic focus; warmup phenomenon",                                "Abnormal P wave morphology; may have variable AV block", C["lteal"]),
    ("Atrial Flutter",  "Regular*",       "Sudden / Sudden", "~150 (2:1)","Regular at ~150 = flutter until proven; can be irregular with variable block",                    "Sawtooth flutter waves 300 bpm; variable block pattern", C["lpurple"]),
    ("Atrial Fibrillation","Irregular",   "Gradual or sudden","100-160",  "Irregularly irregular; loss of atrial kick; stroke risk; palpitations ± dyspnea",                "Absent P waves; irregular RR; fibrillatory baseline", C["lpurple"]),
    ("MAT",             "Irregular",      "Gradual",         "100-180",   "COPD / critical illness; ≥3 P morphologies; treat underlying hypoxia first",                      "≥3 P-wave morphologies; varying PR, PP, RR intervals", C["lpurple"]),
    ("VT (Monomorphic)","Regular",        "Sudden / Sudden", "120-200",   "Structural HD; prior MI; syncope; haemodynamic compromise; EMERGENCY",                           "Wide QRS; AV dissociation; fusion/capture beats; Brugada criteria", C["lred"]),
    ("Torsades (TdP)",  "Irregular/poly", "Sudden",          "200-250",   "Prolonged QT; drugs (QT-prolonging); electrolyte deficiency (K, Mg, Ca)",                        "Twisting QRS around isoelectric line; long QT at baseline", C["lred"]),
    ("Pre-excited AF",  "Irregular",      "Sudden",          "200-300",   "LIFE-THREATENING; WPW + AF; AVOID digoxin/CCB/adenosine; use procainamide/DC cardioversion",      "Irregular wide-complex; varying delta waves; fast ventricular rate", C["lred"]),
    ("SA Block/Arrest", "Pause",          "Sudden pause",    "Slow",      "Sick sinus syndrome; vagal; athletes; dizziness/presyncope; thump then pause",                   "Long pause (multiple of PP); junctional escape; sinus arrest = no P", C["lgold"]),
    ("2nd/3rd AV Block","Irregular/slow", "Gradual",         "30-60",     "Dropped beats; Lyme disease; sarcoidosis; inferior MI; syncope in CHB",                          "Mobitz I: lengthening PR. Mobitz II: fixed PR/dropped. CHB: AV diss.", C["lgold"]),
    ("Anxiety/Panic",   "Variable",       "Gradual or sudden",">100",     "Duration >15 min; hyperventilation; tingling; multiple somatic symptoms; no ECG correlate",      "Sinus tachycardia during episode; normal between", C["lorange"]),
    ("Thyrotoxicosis",  "Regular",        "Gradual",         "100-140",   "Weight loss, heat intolerance, tremor, diarrhea; sinus tach or AF; check TSH",                   "Sinus tachycardia or AF; tall T waves", C["lorange"]),
    ("Pheochromocytoma","Episodic",       "Sudden / Sudden", "100-200",   "Classic triad: headache + sweating + palpitations; hypertensive crisis",                         "Sinus tachycardia during crisis; 24h urine metanephrines", C["lorange"]),
    ("MVP",             "Irregular",      "Variable",        "Variable",  "Mid-systolic click on auscultation; PAC/SVT association; usually benign",                         "PAC or SVT; may have ST changes in inferior leads", C["lgreen"]),
    ("Atrial Myxoma",   "Positional",     "Positional change","Variable", "Positional palpitations; tumor plop; constitutional symptoms; echo diagnostic",                   "Variable; may mimic AF or flutter; echocardiogram is key test", C["lgreen"]),
]

alt_colors = [C["white"], C["lgray"]]
row_y = TABLE_TOP - ROW_H
for ri, row in enumerate(table_data):
    *cells, row_color = row
    xc = TABLE_X
    bg = row_color
    for ci, (cell, cw) in enumerate(zip(cells, col_widths)):
        cv.setFillColor(bg)
        cv.setStrokeColor(C["gray"]); cv.setLineWidth(0.3)
        cv.rect(xc, row_y - ROW_H, cw, ROW_H, fill=1, stroke=1)
        cv.setFillColor(C["dark"])
        fn = "Helvetica-Bold" if ci == 0 else "Helvetica"
        fs = 6.5 if ci >= 4 else 7
        cv.setFont(fn, fs)
        # truncate to fit
        txt = cell
        while cv.stringWidth(txt, fn, fs) > cw - 4 and len(txt) > 3:
            txt = txt[:-4] + "..."
        cv.drawString(xc + 3, row_y - ROW_H + 4, txt)
        xc += cw
    row_y -= ROW_H

# Color legend
legend_y = row_y - 14
cv.setFont("Helvetica-Bold", 8); cv.setFillColor(C["dark"])
cv.drawString(TABLE_X, legend_y, "COLOR KEY:")
legend_items = [
    (C["lteal"],   "Regular SVT"),
    (C["lpurple"], "Atrial / Irregular"),
    (C["lred"],    "URGENT / Wide-complex"),
    (C["lgold"],   "Bradycardia / Conduction"),
    (C["lorange"], "Non-cardiac"),
    (C["lgreen"],  "Structural"),
]
lx = TABLE_X + 75
for fill, lbl in legend_items:
    cv.setFillColor(fill); cv.setStrokeColor(C["gray"]); cv.setLineWidth(0.5)
    cv.rect(lx, legend_y - 8, 10, 10, fill=1, stroke=1)
    cv.setFillColor(C["dark"]); cv.setFont("Helvetica", 7)
    cv.drawString(lx + 12, legend_y - 2, lbl)
    lx += cv.stringWidth(lbl, "Helvetica", 7) + 26

# Footer
cv.setFillColor(C["dark"]); cv.rect(0, 0, W, 22, fill=1, stroke=0)
cv.setFillColor(C["white"]); cv.setFont("Helvetica", 7)
cv.drawCentredString(W/2, 7,
    "Sources: Harrison's 22e (Loscalzo), Braunwald's Heart Disease, Goldman-Cecil Medicine, Frameworks for Internal Medicine")

cv.showPage()
cv.save()
print("Saved:", out)
Running Command

cd /tmp/workspace/palpitations && python qrc_rhythm_ddx.py

Your quick reference card is ready:

Palpitations Rhythm DDx QRC

PDF Document · PDF


What's on each page:

Page 1 - Quick Reference Card (3-column layout)
ColumnContent
LeftIntermittent/single beats (PAC, PVC) + Regular sustained (ST, SVT, AT, VT)
MiddleIrregular rhythms (AF, AFL, MAT) + Non-cardiac DDx (panic, thyrotoxicosis, pheo, post-COVID)
RightWide-complex tachycardia tree (regular/irregular, VT vs SVT with aberrancy vs WPW) + Bradycardia/pauses + Positional/structural
Bottom band across all columns: red flags for urgent referral, key history questions ("tap out the rhythm"), and first-line investigations.
Page 2 - At-a-Glance Table (18 diagnoses)
Each row covers: Rhythm | Pattern | Onset/Offset | Rate | Key Features | ECG Clue - color-coded by category:
  • Teal = regular SVT group
  • Purple = atrial/irregular group
  • Red = urgent/wide-complex (VT, TdP, pre-excited AF)
  • Gold = bradycardia/conduction disease
  • Orange = non-cardiac causes
  • Green = structural causes
This is a shared conversation. Sign in to Orris to start your own chat.