Palpitations approach mwficine harrisosn flow chaet pdf

Reading File
The user wants a palpitations approach flowchart from Harrison's Medicine. Let me search the library for this content.
Finding Sources
Finding Sources
Found Harrison's palpitations section. Let me read it in full.
Reading File
I have excellent content. Let me read a bit more to capture the full approach and any flowchart/figure.
Reading File
I see the section ends around line 399. Let me go back and read the key "Approach to the Patient" section more completely - I saw it was cut off at line 339. Let me also check if there's a flowchart figure.
Reading File
I now have the complete Harrison's palpitations chapter content. The textbook has the full narrative approach but no separate flowchart image in the digital version. Let me now generate a clean, well-organized flowchart-style PDF based on Harrison's content.
Reading File
Now I'll create a comprehensive palpitations approach flowchart PDF based on Harrison's content using reportlab.
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
Excellent source material. Now I'll build the quick reference card PDF - a dense, clinically structured single A4 card organized by rhythm pattern.
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.