~/stemi-pdf/create_stemi_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus.flowables import Flowable
import os
OUTPUT = "/home/daytona/workspace/stemi-pdf/STEMI_ED_Algorithm.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
RED = colors.HexColor("#C0392B")
DARK_RED = colors.HexColor("#922B21")
ORANGE = colors.HexColor("#E67E22")
YELLOW_BG = colors.HexColor("#FEF9E7")
GREEN = colors.HexColor("#1E8449")
GREEN_BG = colors.HexColor("#EAFAF1")
BLUE = colors.HexColor("#1A5276")
BLUE_LIGHT = colors.HexColor("#D6EAF8")
BLUE_BG = colors.HexColor("#EBF5FB")
GREY_BG = colors.HexColor("#F2F3F4")
GREY_DARK = colors.HexColor("#566573")
WHITE = colors.white
BLACK = colors.HexColor("#1C2833")
# ── Document setup ───────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=14*mm, rightMargin=14*mm,
topMargin=14*mm, bottomMargin=14*mm,
title="STEMI ED Management Algorithm",
author="Orris Clinical Reference",
)
W, H = A4
CW = W - 28*mm # content width
styles = getSampleStyleSheet()
def S(name, **kw):
"""Quick style factory."""
base = styles["Normal"]
return ParagraphStyle(name, parent=base, **kw)
# ── Reusable styles ──────────────────────────────────────────────────────────
h_banner = S("h_banner", fontSize=18, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica-Bold", leading=22)
h_sub = S("h_sub", fontSize=10, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica", leading=13)
step_hdr = S("step_hdr", fontSize=9.5, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=12)
body = S("body", fontSize=8.2, textColor=BLACK, leading=11)
body_bold = S("body_bold", fontSize=8.2, textColor=BLACK, fontName="Helvetica-Bold", leading=11)
cell_hdr = S("cell_hdr", fontSize=8, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=10)
cell_body = S("cell_body", fontSize=7.8, textColor=BLACK, leading=10)
cell_bold = S("cell_bold", fontSize=7.8, textColor=BLACK, fontName="Helvetica-Bold", leading=10)
small = S("small", fontSize=7, textColor=GREY_DARK, leading=9)
warn_text = S("warn_text", fontSize=8, textColor=DARK_RED, fontName="Helvetica-Bold", leading=10)
green_txt = S("green_txt", fontSize=8, textColor=GREEN, fontName="Helvetica-Bold", leading=10)
footer_s = S("footer_s", fontSize=6.5, textColor=GREY_DARK, alignment=TA_CENTER, leading=9)
# ── Helper: section header box ───────────────────────────────────────────────
def section_box(title, subtitle=None, bg=BLUE):
rows = [[Paragraph(title, step_hdr)]]
if subtitle:
rows.append([Paragraph(subtitle, h_sub)])
t = Table(rows, colWidths=[CW])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0), (-1,-1), 6),
("ROUNDEDCORNERS", [3]),
]))
return t
# ── Helper: alert box ────────────────────────────────────────────────────────
def alert_box(text, bg=YELLOW_BG, tc=DARK_RED):
st = S("_al", fontSize=8, textColor=tc, fontName="Helvetica-Bold",
leading=10, alignment=TA_CENTER)
t = Table([[Paragraph(text, st)]], colWidths=[CW])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("BOX", (0,0),(-1,-1), 1.2, tc),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 8),
("RIGHTPADDING",(0,0),(-1,-1), 8),
]))
return t
# ── Helper: two-col table ────────────────────────────────────────────────────
def two_col(headers, rows_data, col_w=None, hdr_bg=BLUE):
if col_w is None:
col_w = [CW*0.38, CW*0.62]
data = [[Paragraph(h, cell_hdr) for h in headers]]
for r in rows_data:
data.append([Paragraph(str(c), cell_body) for c in r])
t = Table(data, colWidths=col_w)
style = [
("BACKGROUND", (0,0), (-1,0), hdr_bg),
("BACKGROUND", (0,1), (-1,-1), GREY_BG),
("ROWBACKGROUNDS",(0,1),(-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
t.setStyle(TableStyle(style))
return t
# ── Helper: three-col table ──────────────────────────────────────────────────
def three_col(headers, rows_data, col_w=None, hdr_bg=BLUE):
if col_w is None:
col_w = [CW*0.30, CW*0.36, CW*0.34]
data = [[Paragraph(h, cell_hdr) for h in headers]]
for r in rows_data:
data.append([Paragraph(str(c), cell_body) for c in r])
t = Table(data, colWidths=col_w)
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), hdr_bg),
("ROWBACKGROUNDS",(0,1),(-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
return t
# ── Helper: bullet list ──────────────────────────────────────────────────────
def bullet_table(items, bg=BLUE_BG, tc=BLACK):
rows = []
for item in items:
rows.append([
Paragraph("•", S("_b", fontSize=8, textColor=RED, fontName="Helvetica-Bold", leading=10)),
Paragraph(item, S("_bi", fontSize=7.8, textColor=tc, leading=10))
])
t = Table(rows, colWidths=[6*mm, CW - 6*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("TOPPADDING", (0,0),(-1,-1), 2),
("BOTTOMPADDING",(0,0),(-1,-1), 2),
("LEFTPADDING", (0,0),(-1,-1), 3),
("RIGHTPADDING", (0,0),(-1,-1), 3),
("VALIGN", (0,0),(-1,-1), "TOP"),
]))
return t
sp = lambda n=4: Spacer(1, n*mm)
# ════════════════════════════════════════════════════════════════════════════
# BUILD STORY
# ════════════════════════════════════════════════════════════════════════════
story = []
# ── BANNER ───────────────────────────────────────────────────────────────────
banner_data = [
[Paragraph("🫀 STEMI MANAGEMENT ALGORITHM", h_banner)],
[Paragraph("Emergency Department Quick Reference • AHA/ACC Guidelines", h_sub)],
]
banner = Table(banner_data, colWidths=[CW])
banner.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), DARK_RED),
("TOPPADDING", (0,0),(-1,-1), 8),
("BOTTOMPADDING",(0,0),(-1,-1), 8),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 10),
("ROUNDEDCORNERS", [5]),
]))
story.append(banner)
story.append(sp(3))
# ── KEY PRINCIPLE ────────────────────────────────────────────────────────────
story.append(alert_box(
"⏱ TIME IS MUSCLE — Mortality is directly proportional to total ischemia time. "
"Every 30-minute delay in reperfusion = ~7.5% increase in 1-year mortality.",
bg=colors.HexColor("#FDEDEC"), tc=DARK_RED
))
story.append(sp(3))
# ════════════════ STEP 1 ════════════════════════════════════════════════════
story.append(section_box("STEP 1 — IMMEDIATE RECOGNITION & STABILIZATION (First 10 Minutes)", bg=RED))
story.append(sp(2))
step1_rows = [
["12-lead ECG", "<b>Within 10 minutes of arrival</b> — paramount to diagnosis"],
["IV Access", "Two large-bore IVs; draw bloods simultaneously"],
["Supplemental O₂", "Only if SpO₂ <90% — avoid routine hyperoxia"],
["Continuous Telemetry", "VF causes ~50% of STEMI deaths — monitor immediately"],
["Portable CXR", "Rule out pulmonary oedema, aortic dissection"],
["Urine Pregnancy Test", "Mandatory in all women of reproductive age"],
["Labs", "Troponin, CBC, aPTT/PT/INR, BMP, Mg²⁺, lipid profile, type & screen"],
]
# render with bold in second col
step1_data = [[Paragraph("Action", cell_hdr), Paragraph("Detail", cell_hdr)]]
for r in step1_rows:
step1_data.append([
Paragraph(r[0], cell_bold),
Paragraph(r[1], cell_body),
])
step1_t = Table(step1_data, colWidths=[CW*0.30, CW*0.70])
step1_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), RED),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0),(-1,-1), 3), ("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(step1_t)
story.append(sp(2))
story.append(alert_box(
"⚠ Do NOT wait for troponin results to initiate reperfusion — ECG + clinical picture is sufficient.",
bg=YELLOW_BG, tc=colors.HexColor("#784212")
))
story.append(sp(3))
# ════════════════ ECG LOCALISATION ══════════════════════════════════════════
story.append(section_box("ECG-BASED TERRITORY LOCALISATION", bg=GREY_DARK))
story.append(sp(2))
ecg_rows = [
["V₁–V₆ or new LBBB", "Anterior + Septal (large)", "Proximal LAD or Left Main"],
["V₂–V₄", "Anterior wall", "LAD"],
["V₅–V₆", "Lateral wall", "LCX"],
["II, III, aVF", "Inferior wall", "RCA or LCX"],
["I, aVL", "High lateral wall", "Diagonal / Proximal LCX"],
["V₁–V₂ depression → get V₇–V₉", "Posterior", "RCA or LCX"],
]
story.append(three_col(
["ST Elevation In", "Territory", "Culprit Artery"],
ecg_rows,
col_w=[CW*0.36, CW*0.32, CW*0.32],
hdr_bg=GREY_DARK
))
story.append(sp(1))
story.append(Paragraph(
"⚠ For <b>inferior STEMI</b>: always obtain right-sided leads (V3R / V4R) — "
"RV infarction changes management (IV fluids, avoid nitrates).",
S("_inf", fontSize=7.5, textColor=DARK_RED, fontName="Helvetica-Bold", leading=10)
))
story.append(sp(3))
# ════════════════ STEP 2 ════════════════════════════════════════════════════
story.append(section_box("STEP 2 — UPSTREAM MEDICAL THERAPY (Give Immediately)", bg=BLUE))
story.append(sp(2))
# Antiplatelets
story.append(Paragraph("DUAL ANTIPLATELET THERAPY (DAPT)", S("_sh2", fontSize=8.5,
fontName="Helvetica-Bold", textColor=BLUE, leading=11)))
story.append(sp(1))
apt_rows = [
["<b>Aspirin (ASA)</b>", "162–325 mg chewed/crushed", "81 mg/day indefinitely"],
["<b>Ticagrelor</b> (preferred for PCI)", "180 mg loading", "90 mg twice daily + ASA 81 mg"],
["<b>Prasugrel</b> (alt for PCI)", "60 mg loading", "10 mg/day — Avoid if >75y, <60kg, stroke/TIA"],
["<b>Clopidogrel</b> (if fibrinolysis)", "300–600 mg loading", "75 mg/day"],
]
apt_data = [[Paragraph("Drug", cell_hdr), Paragraph("Loading Dose", cell_hdr),
Paragraph("Maintenance / Notes", cell_hdr)]]
for r in apt_rows:
apt_data.append([Paragraph(c, cell_body) for c in r])
apt_t = Table(apt_data, colWidths=[CW*0.30, CW*0.25, CW*0.45])
apt_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), BLUE),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0),(-1,-1), 3), ("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(apt_t)
story.append(sp(2))
# Anticoagulation
story.append(Paragraph("ANTICOAGULATION", S("_sh3", fontSize=8.5,
fontName="Helvetica-Bold", textColor=BLUE, leading=11)))
story.append(sp(1))
ac_rows = [
["<b>UFH</b> (preferred for PCI)", "60 U/kg IV bolus (max 4000 U)\nthen 12 U/kg/h", "PCI or fibrinolysis; real-time monitoring with ACT"],
["<b>Enoxaparin (LMWH)</b>", "30 mg IV bolus → 1 mg/kg SC q12h", "Preferred with fibrinolysis"],
["<b>Bivalirudin</b>", "0.75 mg/kg bolus → 1.75 mg/kg/h", "PCI; agent of choice in HIT; lower bleeding vs GPIIb/IIIa"],
["<b>Fondaparinux</b>", "2.5 mg IV → 2.5 mg SC daily", "Fibrinolysis when no PCI planned"],
]
ac_data = [[Paragraph("Drug", cell_hdr), Paragraph("Dose", cell_hdr),
Paragraph("Notes", cell_hdr)]]
for r in ac_rows:
ac_data.append([Paragraph(c, cell_body) for c in r])
ac_t = Table(ac_data, colWidths=[CW*0.28, CW*0.33, CW*0.39])
ac_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), BLUE),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0),(-1,-1), 3), ("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(ac_t)
story.append(sp(2))
# Anti-ischaemic
story.append(Paragraph("ANTI-ISCHAEMIC THERAPY", S("_sh4", fontSize=8.5,
fontName="Helvetica-Bold", textColor=BLUE, leading=11)))
story.append(sp(1))
ai_rows = [
["<b>Nitroglycerin</b>", "SL or IV infusion (titrate to SBP 100–140)",
"<b>AVOID</b> if: SBP <90, HR >100 or <50, RV infarct, PDE5-inhibitor use in 48h"],
["<b>Morphine</b>", "2–4 mg IV (titrate)",
"Refractory pain only — may mask ischaemic symptoms; use judiciously"],
["<b>Beta-blocker</b> (Metoprolol)", "12.5–25 mg oral q6h",
"<b>AVOID</b> if: HF, cardiogenic shock, bradycardia, or hypotension (SBP <100)"],
]
ai_data = [[Paragraph("Drug", cell_hdr), Paragraph("Dose", cell_hdr),
Paragraph("Notes / Contraindications", cell_hdr)]]
for r in ai_rows:
ai_data.append([Paragraph(c, cell_body) for c in r])
ai_t = Table(ai_data, colWidths=[CW*0.28, CW*0.30, CW*0.42])
ai_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), BLUE),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0),(-1,-1), 3), ("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(ai_t)
story.append(sp(3))
# ════════════════ STEP 3 ════════════════════════════════════════════════════
story.append(section_box("STEP 3 — REPERFUSION STRATEGY (Most Critical Decision)", bg=GREEN))
story.append(sp(2))
# Reperfusion decision flowchart as a table
rep_decision = [
[
# PCI column
Table([
[Paragraph("PRIMARY PCI", S("_pcih", fontSize=9, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_CENTER, leading=11))],
[Paragraph("Gold Standard", S("_pcis", fontSize=7.5, textColor=WHITE,
alignment=TA_CENTER, leading=9))],
], colWidths=[(CW-4*mm)*0.5],
style=[("BACKGROUND",(0,0),(-1,-1),GREEN),
("TOPPADDING",(0,0),(-1,-1),4),
("BOTTOMPADDING",(0,0),(-1,-1),4)]),
# Fibrinolysis column
Table([
[Paragraph("FIBRINOLYSIS", S("_fibh", fontSize=9, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_CENTER, leading=11))],
[Paragraph("When PCI not available in time", S("_fibs", fontSize=7.5,
textColor=WHITE, alignment=TA_CENTER, leading=9))],
], colWidths=[(CW-4*mm)*0.5],
style=[("BACKGROUND",(0,0),(-1,-1),ORANGE),
("TOPPADDING",(0,0),(-1,-1),4),
("BOTTOMPADDING",(0,0),(-1,-1),4)]),
]
]
rep_hdr = Table(rep_decision, colWidths=[(CW-4*mm)*0.5, (CW-4*mm)*0.5],
style=[("TOPPADDING",(0,0),(-1,-1),0),("BOTTOMPADDING",(0,0),(-1,-1),0),
("LEFTPADDING",(0,0),(-1,-1),2),("RIGHTPADDING",(0,0),(-1,-1),2)])
story.append(rep_hdr)
story.append(sp(1))
rep_body = [
[
# PCI bullets
Table([
[Paragraph("• Door-to-balloon ≤ <b>90 min</b> from first medical contact", cell_body)],
[Paragraph("• Transfer centres: first medical contact to PCI ≤ <b>120 min</b>", cell_body)],
[Paragraph("• Superior vessel patency (TIMI-3), less reinfarction, less ICH", cell_body)],
[Paragraph("• Symptoms <b>12–24 h</b>: PCI still beneficial if ongoing symptoms", cell_body)],
[Paragraph("• <b>Always preferred</b>: cardiogenic shock, severe HF, fibrinolysis CI, uncertain diagnosis", cell_body)],
], colWidths=[(CW-4*mm)*0.5],
style=[("BACKGROUND",(0,0),(-1,-1),GREEN_BG),
("TOPPADDING",(0,0),(-1,-1),2),("BOTTOMPADDING",(0,0),(-1,-1),2),
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5)]),
# Fibrinolysis bullets
Table([
[Paragraph("• Use when PCI not available within <b>90–120 min</b>", cell_body)],
[Paragraph("• Symptoms onset <b>< 12 hours</b>", cell_body)],
[Paragraph("• Door-to-needle ≤ <b>30 min</b>", cell_body)],
[Paragraph("• After fibrinolysis: transfer for angiography within <b>3–24 h</b>", cell_body)],
[Paragraph("• <b>Rescue PCI</b> if: chest pain persists, <50% ST reduction at 90 min", cell_body)],
], colWidths=[(CW-4*mm)*0.5],
style=[("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#FEF5E7")),
("TOPPADDING",(0,0),(-1,-1),2),("BOTTOMPADDING",(0,0),(-1,-1),2),
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5)]),
]
]
rep_body_t = Table(rep_body, colWidths=[(CW-4*mm)*0.5, (CW-4*mm)*0.5],
style=[("TOPPADDING",(0,0),(-1,-1),0),("BOTTOMPADDING",(0,0),(-1,-1),0),
("LEFTPADDING",(0,0),(-1,-1),2),("RIGHTPADDING",(0,0),(-1,-1),2),
("VALIGN",(0,0),(-1,-1),"TOP")])
story.append(rep_body_t)
story.append(sp(2))
# ── Fibrinolytic agents ───────────────────────────────────────────────────────
story.append(Paragraph("FIBRINOLYTIC AGENTS", S("_fib_hdr", fontSize=8.5,
fontName="Helvetica-Bold", textColor=ORANGE, leading=11)))
story.append(sp(1))
fib_rows = [
["<b>Tenecteplase (TNK-tPA)</b> ★ Preferred", "0.5 mg/kg IV single bolus (30–50 mg)",
"Single bolus — convenient; equivalent mortality benefit, lowest bleeding"],
["Alteplase (rt-PA)", "15 mg IV bolus → 0.75 mg/kg/30 min → 0.5 mg/kg/60 min", "Fibrin-selective; no allergic reactions"],
["Reteplase (r-PA)", "Two 10-unit IV boluses 30 min apart", "Double bolus; equivalent to rt-PA"],
["Streptokinase", "1.5 million units IV over 60 min", "Cheapest; non-selective; allergic reactions 1–2%; avoid if prior use"],
]
fib_data = [[Paragraph("Agent", cell_hdr), Paragraph("Dose", cell_hdr),
Paragraph("Notes", cell_hdr)]]
for r in fib_rows:
fib_data.append([Paragraph(c, cell_body) for c in r])
fib_t = Table(fib_data, colWidths=[CW*0.30, CW*0.34, CW*0.36])
fib_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), ORANGE),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0),(-1,-1), 3), ("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(fib_t)
story.append(sp(2))
# ── Fibrinolysis contraindications ────────────────────────────────────────────
ci_left = [
"<b>ABSOLUTE CONTRAINDICATIONS</b>",
"Prior intracranial haemorrhage / haemorrhagic stroke",
"Ischaemic stroke within 3 months",
"Known cerebrovascular AVM / aneurysm / tumour",
"Closed head injury within 3 months",
"Active aortic dissection",
"Active bleeding or bleeding diathesis",
"Acute pericarditis",
"SBP >180 or DBP >110 mmHg (uncontrolled)",
]
ci_right = [
"<b>RELATIVE CONTRAINDICATIONS</b>",
"Prior ischaemic stroke >3 months ago",
"Recent internal bleeding (2–4 weeks)",
"Major surgery within 3 weeks",
"Prolonged / traumatic CPR >10 min",
"Active peptic ulcer disease",
"Non-compressible vascular punctures",
"Pregnancy",
"Oral anticoagulant use",
]
ci_rows = []
for i in range(max(len(ci_left), len(ci_right))):
l = ci_left[i] if i < len(ci_left) else ""
r = ci_right[i] if i < len(ci_right) else ""
lc = S("_cil", fontSize=7.5, textColor=DARK_RED if i==0 else BLACK,
fontName="Helvetica-Bold" if i==0 else "Helvetica", leading=10)
rc = S("_cir", fontSize=7.5, textColor=DARK_RED if i==0 else BLACK,
fontName="Helvetica-Bold" if i==0 else "Helvetica", leading=10)
ci_rows.append([Paragraph(l, lc), Paragraph(r, rc)])
ci_t = Table(ci_rows, colWidths=[CW*0.50, CW*0.50])
ci_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(0,-1), colors.HexColor("#FDEDEC")),
("BACKGROUND", (1,0),(1,-1), colors.HexColor("#FEF9E7")),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0),(-1,-1), 2), ("BOTTOMPADDING",(0,0),(-1,-1), 2),
("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(Paragraph("CONTRAINDICATIONS TO FIBRINOLYSIS", S("_ci_hdr", fontSize=8.5,
fontName="Helvetica-Bold", textColor=DARK_RED, leading=11)))
story.append(sp(1))
story.append(ci_t)
story.append(sp(2))
# ── Signs of successful reperfusion ──────────────────────────────────────────
rep_signs = [
"Relief of chest pain / angina",
">50% reduction in ST-segment elevation at 90 minutes",
"Reperfusion arrhythmia (accelerated idioventricular rhythm)",
]
story.append(Paragraph("SIGNS OF SUCCESSFUL REPERFUSION", S("_rep_hdr", fontSize=8.5,
fontName="Helvetica-Bold", textColor=GREEN, leading=11)))
story.append(sp(1))
story.append(bullet_table(rep_signs, bg=GREEN_BG))
story.append(sp(3))
# ════════════════ STEP 4 — POST-STEMI THERAPY ════════════════════════════
story.append(section_box("STEP 4 — POST-STEMI LONG-TERM MEDICAL THERAPY", bg=DARK_RED))
story.append(sp(2))
post_rows = [
["<b>Aspirin (ASA)</b>", "81 mg/day", "Indefinitely"],
["<b>P2Y12 Inhibitor (DAPT)</b>", "Ticagrelor 90 mg BD OR Clopidogrel 75 mg/day", "Minimum <b>12 months</b>"],
["<b>Beta-blocker</b>", "Metoprolol succinate (titrate up)", "Indefinitely — start within 24 h"],
["<b>ACE Inhibitor</b>", "Captopril 6.25 mg → titrate OR Ramipril", "All patients; within 24 h; EF <40% derives most benefit"],
["<b>ARB</b>", "Valsartan / Losartan", "If ACE-I intolerant"],
["<b>High-intensity Statin</b>", "Atorvastatin 40–80 mg OR Rosuvastatin 20–40 mg",
"Target LDL <70 mg/dL or ≥50% reduction; add PCSK9i / ezetimibe if needed"],
["<b>Aldosterone Antagonist</b>", "Eplerenone / Spironolactone", "EF <40% + HF or diabetes; caution in hyperkalaemia / renal impairment"],
]
post_data = [[Paragraph("Drug Class", cell_hdr), Paragraph("Agent / Dose", cell_hdr),
Paragraph("Duration / Notes", cell_hdr)]]
for r in post_rows:
post_data.append([Paragraph(c, cell_body) for c in r])
post_t = Table(post_data, colWidths=[CW*0.25, CW*0.37, CW*0.38])
post_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), DARK_RED),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0),(-1,-1), 3), ("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(post_t)
story.append(sp(3))
# ════════════════ KEY TIME TARGETS ══════════════════════════════════════════
story.append(section_box("KEY TIME TARGETS — AHA/ACC", bg=colors.HexColor("#117A65")))
story.append(sp(2))
time_data = [
[Paragraph("Target", cell_hdr), Paragraph("Time", cell_hdr), Paragraph("Notes", cell_hdr)],
[Paragraph("ECG from patient arrival", cell_body),
Paragraph("<b>≤ 10 min</b>", S("_tm1",fontSize=8,fontName="Helvetica-Bold",
textColor=DARK_RED,leading=10)),
Paragraph("Paramount — initiate MI protocol immediately", cell_body)],
[Paragraph("Door-to-balloon (PCI at same centre)", cell_body),
Paragraph("<b>≤ 90 min</b>", S("_tm2",fontSize=8,fontName="Helvetica-Bold",
textColor=DARK_RED,leading=10)),
Paragraph("From first medical contact", cell_body)],
[Paragraph("First medical contact to PCI (transfer)", cell_body),
Paragraph("<b>≤ 120 min</b>", S("_tm3",fontSize=8,fontName="Helvetica-Bold",
textColor=DARK_RED,leading=10)),
Paragraph("Transfer if PCI achievable within this window", cell_body)],
[Paragraph("Door-to-needle (fibrinolysis)", cell_body),
Paragraph("<b>≤ 30 min</b>", S("_tm4",fontSize=8,fontName="Helvetica-Bold",
textColor=DARK_RED,leading=10)),
Paragraph("If PCI not available within 90–120 min window", cell_body)],
[Paragraph("Symptom onset — fibrinolysis benefit", cell_body),
Paragraph("<b>< 12 h</b>", S("_tm5",fontSize=8,fontName="Helvetica-Bold",
textColor=DARK_RED,leading=10)),
Paragraph("Greatest benefit in first 3 hours", cell_body)],
]
time_t = Table(time_data, colWidths=[CW*0.40, CW*0.20, CW*0.40])
time_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), colors.HexColor("#117A65")),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0),(-1,-1), 3), ("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("ALIGN", (1,0),(1,-1), "CENTER"),
]))
story.append(time_t)
story.append(sp(3))
# ════════════════ CCU / PERI-INFARCT CARE ══════════════════════════════════
story.append(section_box("CCU ADMISSION ORDERS & MONITORING", bg=GREY_DARK))
story.append(sp(2))
ccu_items = [
"Admit to CCU / coronary care with continuous telemetry for ≥24 hours",
"Vital signs q1h until stable; notify if HR >100 or <50, SBP <90 or >150, SpO₂ <90%",
"Activity: bed rest >12 h; light activity when stable and pain-free",
"Diet: NPO (sips of water) until pain-free; then 2 g sodium heart-healthy diet",
"Serial ECGs daily; assess for recurrent chest pain, new murmurs, HF signs",
"Baseline echocardiogram: document EF, wall motion, valvular lesions, LV thrombus",
"Pacing readiness: AV block in anterior MI is unstable — requires temporary then permanent pacemaker",
"Stool softener, anxiolytic/hypnotic as needed",
]
story.append(bullet_table(ccu_items, bg=BLUE_BG))
story.append(sp(3))
# ════════════════ COMPLICATIONS TO WATCH ═══════════════════════════════════
story.append(section_box("MECHANICAL COMPLICATIONS TO WATCH", bg=colors.HexColor("#6C3483")))
story.append(sp(2))
comp_rows = [
["<b>Free wall rupture</b>", "Sudden haemodynamic collapse, tamponade", "Emergency surgery — mortality 90%"],
["<b>VSD (papillary / septal)</b>","New harsh pan-systolic murmur + haemodynamic deterioration", "Emergency surgery / percutaneous closure"],
["<b>Papillary muscle rupture</b>","Sudden pulmonary oedema, new mitral regurgitation murmur", "Emergency mitral valve surgery"],
["<b>RV infarction</b>", "Inferior STEMI + hypotension + JVD + clear lungs", "IV fluids, avoid nitrates / diuretics"],
["<b>LV thrombus</b>", "EF <40%, anterior MI — echocardiography", "Anticoagulation 3–6 months"],
["<b>Ventricular fibrillation</b>","Accounts for ~50% early STEMI mortality", "Immediate defibrillation; telemetry mandatory"],
]
comp_data = [[Paragraph("Complication", cell_hdr), Paragraph("Presentation", cell_hdr),
Paragraph("Management", cell_hdr)]]
for r in comp_rows:
comp_data.append([Paragraph(c, cell_body) for c in r])
comp_t = Table(comp_data, colWidths=[CW*0.27, CW*0.37, CW*0.36])
comp_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), colors.HexColor("#6C3483")),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0),(-1,-1), 3), ("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(comp_t)
story.append(sp(3))
# ════════════════ FOOTER ════════════════════════════════════════════════════
story.append(HRFlowable(width=CW, thickness=0.5, color=GREY_DARK))
story.append(sp(1))
story.append(Paragraph(
"Sources: The Washington Manual of Medical Therapeutics • Goldman-Cecil Medicine • "
"Fuster & Hurst's The Heart 15E • AHA/ACC STEMI Guidelines | "
"Generated by Orris Clinical AI • For clinical decision support only — always apply individual patient judgement.",
footer_s
))
# ── BUILD ──────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")