~/epinephrine-qr/build_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
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 import PageBreak
# ── Colour palette ──────────────────────────────────────────────────────────
RED = colors.HexColor("#C0392B")
DARK_RED = colors.HexColor("#922B21")
AMBER = colors.HexColor("#E67E22")
GREEN = colors.HexColor("#1E8449")
DARK_GREEN = colors.HexColor("#145A32")
BLUE = colors.HexColor("#1A5276")
LIGHT_BLUE = colors.HexColor("#D6EAF8")
LIGHT_RED = colors.HexColor("#FADBD8")
LIGHT_AMBER = colors.HexColor("#FDEBD0")
LIGHT_GREEN = colors.HexColor("#D5F5E3")
LIGHT_GREY = colors.HexColor("#F2F3F4")
MID_GREY = colors.HexColor("#BDC3C7")
DARK_GREY = colors.HexColor("#2C3E50")
WHITE = colors.white
BLACK = colors.black
W, H = A4 # 595.27 x 841.89 pt
# ── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
TITLE = S("TITLE",
fontSize=22, fontName="Helvetica-Bold", textColor=WHITE,
alignment=TA_CENTER, spaceAfter=2)
SUBTITLE = S("SUBTITLE",
fontSize=11, fontName="Helvetica", textColor=WHITE,
alignment=TA_CENTER, spaceAfter=0)
H1 = S("H1",
fontSize=11, fontName="Helvetica-Bold", textColor=WHITE,
alignment=TA_LEFT, spaceBefore=4, spaceAfter=2)
H2 = S("H2",
fontSize=9.5, fontName="Helvetica-Bold", textColor=DARK_GREY,
spaceBefore=3, spaceAfter=1)
BODY = S("BODY",
fontSize=8.5, fontName="Helvetica", textColor=DARK_GREY,
spaceBefore=1, spaceAfter=1, leading=11)
BODY_B = S("BODY_B",
fontSize=8.5, fontName="Helvetica-Bold", textColor=DARK_GREY,
spaceBefore=1, spaceAfter=1, leading=11)
WARN = S("WARN",
fontSize=8, fontName="Helvetica-Bold", textColor=RED,
alignment=TA_CENTER, spaceBefore=2, spaceAfter=2)
FOOTER = S("FOOTER",
fontSize=7, fontName="Helvetica", textColor=MID_GREY,
alignment=TA_CENTER)
TBL_HDR = S("TBL_HDR",
fontSize=8, fontName="Helvetica-Bold", textColor=WHITE,
alignment=TA_CENTER, leading=10)
TBL_CELL = S("TBL_CELL",
fontSize=8, fontName="Helvetica", textColor=DARK_GREY,
alignment=TA_LEFT, leading=10)
TBL_CELL_B = S("TBL_CELL_B",
fontSize=8, fontName="Helvetica-Bold", textColor=DARK_GREY,
alignment=TA_LEFT, leading=10)
TBL_CELL_C = S("TBL_CELL_C",
fontSize=8, fontName="Helvetica", textColor=DARK_GREY,
alignment=TA_CENTER, leading=10)
ALERT = S("ALERT",
fontSize=8.5, fontName="Helvetica-Bold", textColor=DARK_RED,
alignment=TA_CENTER, spaceBefore=3, spaceAfter=3)
# ── Helper: section header bar ───────────────────────────────────────────────
def section_bar(title, bg=BLUE, text_color=WHITE):
data = [[Paragraph(title, S("tmp", fontSize=10, fontName="Helvetica-Bold",
textColor=text_color, alignment=TA_LEFT))]]
t = Table(data, colWidths=[W - 28*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("ROUNDEDCORNERS", [3]),
]))
return t
# ── Helper: two-column layout ────────────────────────────────────────────────
def two_col(left_content, right_content, left_w=None, right_w=None):
total = W - 28*mm
if left_w is None:
left_w = total * 0.49
if right_w is None:
right_w = total * 0.49
gap = total - left_w - right_w
data = [[left_content, right_content]]
t = Table(data, colWidths=[left_w, right_w])
t.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
]))
return t
# ── Document ─────────────────────────────────────────────────────────────────
out_path = "/home/daytona/workspace/epinephrine-qr/Epinephrine_Quick_Reference.pdf"
doc = SimpleDocTemplate(
out_path,
pagesize=A4,
leftMargin=14*mm, rightMargin=14*mm,
topMargin=14*mm, bottomMargin=14*mm,
)
story = []
# ═══════════════════════════════════════════════════════════════════════════
# HEADER BANNER
# ═══════════════════════════════════════════════════════════════════════════
header_data = [
[Paragraph("⚕ EPINEPHRINE (ADRENALINE)", TITLE)],
[Paragraph("Emergency Dosing & Protocol Quick Reference", SUBTITLE)],
[Paragraph("For trained healthcare professionals only | Always confirm dose/concentration before administration", SUBTITLE)],
]
header_tbl = Table(header_data, colWidths=[W - 28*mm])
header_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), RED),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("LINEBELOW", (0,-1), (-1,-1), 2, DARK_RED),
]))
story.append(header_tbl)
story.append(Spacer(1, 4*mm))
# ═══════════════════════════════════════════════════════════════════════════
# CONCENTRATION GUIDE — CRITICAL SAFETY BOX
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("⚠ CONCENTRATION GUIDE — KNOW BEFORE YOU DRAW", bg=DARK_RED))
story.append(Spacer(1, 1.5*mm))
conc_data = [
[Paragraph("Ratio", TBL_HDR), Paragraph("Concentration", TBL_HDR),
Paragraph("Volume for 1 mg", TBL_HDR), Paragraph("Clinical Use", TBL_HDR)],
[Paragraph("1:1,000", TBL_CELL_B), Paragraph("1 mg/mL", TBL_CELL_C),
Paragraph("1 mL", TBL_CELL_C),
Paragraph("Anaphylaxis IM · Subcutaneous · Nebulisation", TBL_CELL)],
[Paragraph("1:10,000", TBL_CELL_B), Paragraph("0.1 mg/mL", TBL_CELL_C),
Paragraph("10 mL", TBL_CELL_C),
Paragraph("Cardiac arrest IV/IO · Haemodynamic infusions", TBL_CELL)],
[Paragraph("1:100,000", TBL_CELL_B), Paragraph("0.01 mg/mL", TBL_CELL_C),
Paragraph("100 mL", TBL_CELL_C),
Paragraph("Local anaesthesia additive (not emergency)", TBL_CELL)],
]
conc_tbl = Table(conc_data, colWidths=[28*mm, 32*mm, 32*mm, None])
conc_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_RED),
("BACKGROUND", (0,1), (-1,1), LIGHT_RED),
("BACKGROUND", (0,2), (-1,2), LIGHT_AMBER),
("BACKGROUND", (0,3), (-1,3), LIGHT_GREY),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(conc_tbl)
warn_data = [[Paragraph(
"⛔ NEVER give 1:1,000 IV — this is 10× the IV cardiac arrest dose and can cause fatal hypertension/arrhythmia",
WARN)]]
warn_tbl = Table(warn_data, colWidths=[W - 28*mm])
warn_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_RED),
("BOX", (0,0), (-1,-1), 1, RED),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
]))
story.append(Spacer(1, 1*mm))
story.append(warn_tbl)
story.append(Spacer(1, 3*mm))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 1 — ANAPHYLAXIS | SECTION 2 — CARDIAC ARREST (side by side)
# ═══════════════════════════════════════════════════════════════════════════
col_w = (W - 28*mm - 3*mm) / 2 # two equal columns with 3 mm gap
# ── LEFT: ANAPHYLAXIS ────────────────────────────────────────────────────
ana_story = []
# Header
ah = Table([[Paragraph("🔴 ANAPHYLAXIS", S("tmp", fontSize=10,
fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_LEFT))]],
colWidths=[col_w])
ah.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), RED),
("LEFTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
ana_story.append(ah)
ana_story.append(Spacer(1, 1.5*mm))
# Diagnosis criteria
ana_story.append(Paragraph("DIAGNOSTIC CRITERIA (any 1 of 3):", H2))
criteria = [
["1", "Urticaria/flushing/angioedema + ONE of:\n→ Respiratory distress/hypoxia\n→ Hypotension/collapse\n→ Organ dysfunction (syncope)"],
["2", "TWO or more after allergen exposure:\n→ Skin/mucosal involvement\n→ Respiratory compromise\n→ Hypotension\n→ Persistent GI symptoms"],
["3", "Hypotension after known allergen\n(even without skin signs)"],
]
crit_data = [
[Paragraph("Criterion", TBL_HDR), Paragraph("Findings", TBL_HDR)]
] + [
[Paragraph(r[0], TBL_CELL_B), Paragraph(r[1].replace("\n","<br/>"), TBL_CELL)]
for r in criteria
]
crit_tbl = Table(crit_data, colWidths=[14*mm, col_w - 14*mm - 2*mm])
crit_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("BACKGROUND", (0,1), (-1,1), LIGHT_RED),
("BACKGROUND", (0,2), (-1,2), LIGHT_GREY),
("BACKGROUND", (0,3), (-1,3), LIGHT_RED),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("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"),
]))
ana_story.append(crit_tbl)
ana_story.append(Spacer(1, 2*mm))
# Epinephrine dosing
ana_story.append(Paragraph("EPINEPHRINE — FIRST-LINE (give immediately):", H2))
epi_ana = [
[Paragraph("Patient", TBL_HDR), Paragraph("Route", TBL_HDR),
Paragraph("Dose", TBL_HDR), Paragraph("Conc.", TBL_HDR)],
[Paragraph("Adult", TBL_CELL_B), Paragraph("IM thigh", TBL_CELL_C),
Paragraph("0.3–0.5 mg", TBL_CELL_B), Paragraph("1:1,000", TBL_CELL_C)],
[Paragraph("Child", TBL_CELL_B), Paragraph("IM thigh", TBL_CELL_C),
Paragraph("0.01 mg/kg\n(max 0.5 mg)", TBL_CELL_B), Paragraph("1:1,000", TBL_CELL_C)],
[Paragraph("Collapse/\nArrest", TBL_CELL_B), Paragraph("IV slow", TBL_CELL_C),
Paragraph("0.1–0.5 mg\nbolus", TBL_CELL_B), Paragraph("1:10,000", TBL_CELL_C)],
[Paragraph("Infusion", TBL_CELL_B), Paragraph("IV", TBL_CELL_C),
Paragraph("2–10 mcg/min\ntitrate", TBL_CELL_B), Paragraph("1 mg/500 mL\nNS", TBL_CELL_C)],
]
epi_ana_tbl = Table(epi_ana, colWidths=[18*mm, 18*mm, 24*mm, None])
epi_ana_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("BACKGROUND", (0,1), (-1,1), LIGHT_GREEN),
("BACKGROUND", (0,2), (-1,2), LIGHT_GREY),
("BACKGROUND", (0,3), (-1,3), LIGHT_GREEN),
("BACKGROUND", (0,4), (-1,4), LIGHT_GREY),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("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), "MIDDLE"),
]))
ana_story.append(epi_ana_tbl)
ana_story.append(Spacer(1, 1*mm))
ana_story.append(Paragraph("→ Repeat IM every 5–15 min if no response · Site: anterolateral thigh", BODY))
ana_story.append(Spacer(1, 2*mm))
# Stepwise protocol
ana_story.append(Paragraph("TREATMENT STEPS:", H2))
steps = [
("1", "AIRWAY", "Intubate early if angioedema/stridor — DO NOT DELAY"),
("2", "EPINEPHRINE", "IM 0.3–0.5 mg (1:1,000) anterolateral thigh — IMMEDIATELY"),
("3", "O₂", "High-flow O₂ · Target SpO₂ > 90%"),
("4", "IV ACCESS", "Large-bore IV · Cardiac monitor · Pulse oximetry"),
("5", "IV FLUIDS", "NS 1–2 L bolus for hypotension (increased vascular permeability)"),
("6", "ADJUNCTS", "H₁ blocker + H₂ blocker + Corticosteroid (see below)"),
("7", "OBSERVE", "≥ 4–6 h · 8–12 h if severe (biphasic risk 3–11 h later)"),
]
step_data = [[Paragraph(s[0], TBL_CELL_B),
Paragraph(s[1], TBL_CELL_B),
Paragraph(s[2], TBL_CELL)] for s in steps]
step_tbl = Table(step_data, colWidths=[8*mm, 26*mm, None])
bg_cycle = [LIGHT_RED, LIGHT_GREY]
style_cmds = [
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("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"),
]
for i in range(len(steps)):
style_cmds.append(("BACKGROUND", (0,i), (-1,i), bg_cycle[i%2]))
step_tbl.setStyle(TableStyle(style_cmds))
ana_story.append(step_tbl)
ana_story.append(Spacer(1, 2*mm))
# Adjunct drugs
ana_story.append(Paragraph("ADJUNCT DRUGS (do NOT replace epinephrine):", H2))
adj = [
[Paragraph("Drug", TBL_HDR), Paragraph("Dose", TBL_HDR), Paragraph("Note", TBL_HDR)],
[Paragraph("Diphenhydramine", TBL_CELL), Paragraph("25–50 mg IV/IM", TBL_CELL_B), Paragraph("H₁ blocker · Urticaria/pruritus only", TBL_CELL)],
[Paragraph("Ranitidine", TBL_CELL), Paragraph("50 mg IV", TBL_CELL_B), Paragraph("H₂ blocker · Complements H₁", TBL_CELL)],
[Paragraph("Methylprednisolone", TBL_CELL), Paragraph("125 mg IV", TBL_CELL_B), Paragraph("Prevents biphasic · Onset 4–6 h", TBL_CELL)],
[Paragraph("Salbutamol", TBL_CELL), Paragraph("2.5–5 mg neb", TBL_CELL_B), Paragraph("Persistent bronchospasm", TBL_CELL)],
[Paragraph("Glucagon", TBL_CELL), Paragraph("1–2 mg IV", TBL_CELL_B), Paragraph("β-BLOCKER patients — bypasses adrenergic receptors", TBL_CELL)],
]
adj_tbl = Table(adj, colWidths=[30*mm, 26*mm, None])
adj_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("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"),
("ROWBACKGROUNDS", (0,1), (-1,-1), [LIGHT_GREY, WHITE]),
]))
ana_story.append(adj_tbl)
# Build left column as nested table
left_inner = Table([[x] for x in ana_story], colWidths=[col_w])
left_inner.setStyle(TableStyle([
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 1),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
# ── RIGHT: CARDIAC ARREST ────────────────────────────────────────────────
ca_story = []
# Header
cah = Table([[Paragraph("🔵 CARDIAC ARREST (ACLS/PALS)", S("tmp", fontSize=10,
fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_LEFT))]],
colWidths=[col_w])
cah.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), BLUE),
("LEFTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
ca_story.append(cah)
ca_story.append(Spacer(1, 1.5*mm))
# Indications
ca_story.append(Paragraph("INDICATIONS:", H2))
inds = [
"Ventricular fibrillation (VF) unresponsive to shock",
"Pulseless VT (pVT) unresponsive to shock",
"Asystole",
"Pulseless Electrical Activity (PEA)",
"Haemodynamically unstable bradycardia (atropine failed)",
]
ind_data = [[Paragraph(f"• {i}", TBL_CELL)] for i in inds]
ind_tbl = Table(ind_data, colWidths=[col_w - 2*mm])
ind_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_BLUE),
("BOX", (0,0), (-1,-1), 0.5, BLUE),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 3),
]))
ca_story.append(ind_tbl)
ca_story.append(Spacer(1, 2*mm))
# Adult dose
ca_story.append(Paragraph("ADULT CARDIAC ARREST PROTOCOL:", H2))
adult_ca = [
[Paragraph("Phase", TBL_HDR), Paragraph("Action", TBL_HDR)],
[Paragraph("0 min", TBL_CELL_B), Paragraph("CPR — 30:2 · Rate ≥ 100/min · Depth ≥ 5 cm", TBL_CELL)],
[Paragraph("ASAP", TBL_CELL_B), Paragraph("Defibrillate if VF/pVT (do not delay for drugs)", TBL_CELL)],
[Paragraph("After 2nd shock\nor PEA/Asystole", TBL_CELL_B), Paragraph("Epinephrine 1 mg IV/IO (1:10,000 = 10 mL)\nFlush ≥ 20 mL NS · Continue CPR 30–60 s", TBL_CELL)],
[Paragraph("Every 3–5 min", TBL_CELL_B), Paragraph("Repeat epinephrine 1 mg IV/IO\n(no maximum dose limit)", TBL_CELL)],
[Paragraph("Refractory\nVF/pVT", TBL_CELL_B), Paragraph("Amiodarone 300 mg IV bolus after 3rd shock", TBL_CELL)],
[Paragraph("ROSC", TBL_CELL_B), Paragraph("Post-resuscitation care · Monitor glucose · Temperature", TBL_CELL)],
]
adult_ca_tbl = Table(adult_ca, colWidths=[28*mm, None])
adult_ca_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [LIGHT_GREY, LIGHT_BLUE]),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("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"),
]))
ca_story.append(adult_ca_tbl)
ca_story.append(Spacer(1, 2*mm))
# Paediatric dose
ca_story.append(Paragraph("PAEDIATRIC CARDIAC ARREST DOSE:", H2))
ped_data = [
[Paragraph("Route", TBL_HDR), Paragraph("Dose", TBL_HDR), Paragraph("Conc.", TBL_HDR), Paragraph("Interval", TBL_HDR)],
[Paragraph("IV / IO", TBL_CELL_B), Paragraph("0.01 mg/kg\n(max 1 mg)", TBL_CELL_B), Paragraph("1:10,000", TBL_CELL_C), Paragraph("Every\n3–5 min", TBL_CELL_C)],
]
ped_tbl = Table(ped_data, colWidths=[20*mm, 26*mm, 22*mm, None])
ped_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE),
("BACKGROUND", (0,1), (-1,1), LIGHT_BLUE),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("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), "MIDDLE"),
]))
ca_story.append(ped_tbl)
ca_story.append(Paragraph("→ Give early in non-shockable arrest · Not effective if >15 min after EMS arrival", BODY))
ca_story.append(Paragraph("→ ET route no longer recommended (poor absorption)", BODY))
ca_story.append(Spacer(1, 2*mm))
# Bradycardia infusion
ca_story.append(Paragraph("BRADYCARDIA INFUSION (haemodynamically unstable, atropine failed):", H2))
brady_data = [
[Paragraph("Preparation", TBL_HDR), Paragraph("Start Rate", TBL_HDR), Paragraph("Titration", TBL_HDR)],
[Paragraph("1 mg in 500 mL NS", TBL_CELL_B), Paragraph("2–10 mcg/min", TBL_CELL_B), Paragraph("↑ every 3–5 min\nto effect", TBL_CELL)],
]
brady_tbl = Table(brady_data, colWidths=[36*mm, 28*mm, None])
brady_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE),
("BACKGROUND", (0,1), (-1,1), LIGHT_BLUE),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("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), "MIDDLE"),
]))
ca_story.append(brady_tbl)
ca_story.append(Spacer(1, 2*mm))
# Shock infusion
ca_story.append(Paragraph("SEPTIC/CARDIOGENIC SHOCK INFUSION:", H2))
shock_data = [
[Paragraph("Dose Range", TBL_HDR), Paragraph("Predominant Effect", TBL_HDR)],
[Paragraph("0.01–0.1 mcg/kg/min", TBL_CELL_B), Paragraph("β predominant → inotropy, chronotropy", TBL_CELL)],
[Paragraph("0.1–1 mcg/kg/min", TBL_CELL_B), Paragraph("α predominant → vasoconstriction, ↑ SVR", TBL_CELL)],
]
shock_tbl = Table(shock_data, colWidths=[40*mm, None])
shock_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [LIGHT_BLUE, LIGHT_GREY]),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("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), "MIDDLE"),
]))
ca_story.append(shock_tbl)
# Build right column as nested table
right_inner = Table([[x] for x in ca_story], colWidths=[col_w])
right_inner.setStyle(TableStyle([
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 1),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
# Two-column main table
main_cols = Table([[left_inner, Spacer(3*mm, 1), right_inner]],
colWidths=[col_w, 3*mm, col_w])
main_cols.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
story.append(main_cols)
story.append(Spacer(1, 3*mm))
# ═══════════════════════════════════════════════════════════════════════════
# MECHANISM SUMMARY BAR
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("MECHANISM OF ACTION — RECEPTOR SUMMARY", bg=DARK_GREY))
story.append(Spacer(1, 1.5*mm))
moa_data = [
[Paragraph("Receptor", TBL_HDR), Paragraph("Location", TBL_HDR),
Paragraph("Effect", TBL_HDR), Paragraph("Clinical Benefit", TBL_HDR)],
[Paragraph("α₁", TBL_CELL_B), Paragraph("Vascular SM", TBL_CELL),
Paragraph("Vasoconstriction ↑ SVR", TBL_CELL),
Paragraph("↑ BP · ↓ Angioedema · ↑ Coronary/cerebral perfusion pressure", TBL_CELL)],
[Paragraph("β₁", TBL_CELL_B), Paragraph("Heart", TBL_CELL),
Paragraph("↑ HR · ↑ Contractility · ↑ AV conduction", TBL_CELL),
Paragraph("↑ Cardiac output · Restores rhythm · Treats bradycardia", TBL_CELL)],
[Paragraph("β₂", TBL_CELL_B), Paragraph("Bronchi · Mast cells", TBL_CELL),
Paragraph("Bronchodilation · Inhibits degranulation · Vasodilation", TBL_CELL),
Paragraph("Reverses bronchospasm · Stops mediator release in anaphylaxis", TBL_CELL)],
[Paragraph("Signal\nPath", TBL_CELL_B), Paragraph("β-receptor → Gs protein", TBL_CELL),
Paragraph("→ Adenylyl cyclase → cAMP → PKA → Ca²⁺ handling", TBL_CELL),
Paragraph("↑ Rate of contraction · ↑ Peak force · ↑ Rate of relaxation (lusitropy)", TBL_CELL)],
]
moa_tbl = Table(moa_data, colWidths=[16*mm, 32*mm, 56*mm, None])
moa_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_GREY),
("ROWBACKGROUNDS", (0,1), (-1,-1), [LIGHT_GREY, WHITE, LIGHT_GREY, WHITE]),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(moa_tbl)
story.append(Spacer(1, 3*mm))
# ═══════════════════════════════════════════════════════════════════════════
# ADVERSE EFFECTS + CAUTIONS (bottom row, 3 columns)
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("ADVERSE EFFECTS · CAUTIONS · DISCHARGE PLANNING", bg=AMBER))
story.append(Spacer(1, 1.5*mm))
col3 = (W - 28*mm - 4*mm) / 3
# Adverse effects
ae_header = Table([[Paragraph("⚠ ADVERSE EFFECTS", S("tmp", fontSize=9, fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_LEFT))]],
colWidths=[col3])
ae_header.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1), AMBER),
("LEFTPADDING",(0,0),(-1,-1),4),("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3)]))
ae_items = [
"Tachycardia (most common)",
"Hypertension → stroke / pulm. oedema",
"Ventricular ectopy / VT (high dose)",
"Myocardial ischaemia (↑ O₂ demand)",
"Pulmonary shunting",
"Cerebral vasoconstriction (post-arrest)",
"Hyperglycaemia",
"Tissue necrosis (extravasation)",
]
ae_data = [[Paragraph(f"• {i}", TBL_CELL)] for i in ae_items]
ae_tbl = Table(ae_data, colWidths=[col3 - 2*mm])
ae_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_AMBER),
("BOX", (0,0), (-1,-1), 0.5, AMBER),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 1.5),
("BOTTOMPADDING", (0,0), (-1,-1), 1.5),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
# Cautions
caut_header = Table([[Paragraph("🔶 CAUTIONS / SPECIAL CASES", S("tmp", fontSize=9, fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_LEFT))]],
colWidths=[col3])
caut_header.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1), AMBER),
("LEFTPADDING",(0,0),(-1,-1),4),("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3)]))
cauts = [
("Anaphylaxis", "NO absolute contraindications — always give"),
("β-Blockers", "Add glucagon 1–2 mg IV; α effects may ↑"),
("Cardiac arrest", "No absolute contraindications"),
("Alkaline solutions", "DO NOT MIX — inactivated"),
("CAD / hypertension", "Caution with infusions; ↑ ischaemia risk"),
("Extravasation", "Phentolamine local infiltration"),
("Hyperthyroidism", "Enhanced sensitivity; reduce dose"),
("Phaeochromocytoma", "Relative CI — hypertensive crisis risk"),
]
caut_data = [[Paragraph(f"<b>{c[0]}:</b> {c[1]}", TBL_CELL)] for c in cauts]
caut_tbl = Table(caut_data, colWidths=[col3 - 2*mm])
caut_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_AMBER),
("BOX", (0,0), (-1,-1), 0.5, AMBER),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 1.5),
("BOTTOMPADDING", (0,0), (-1,-1), 1.5),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
# Discharge planning
disc_header = Table([[Paragraph("✅ DISCHARGE (Anaphylaxis)", S("tmp", fontSize=9, fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_LEFT))]],
colWidths=[col3])
disc_header.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1), GREEN),
("LEFTPADDING",(0,0),(-1,-1),4),("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3)]))
disc_items = [
"Observe ≥ 4–6 h (8–12 h if severe)",
"Prescribe ≥ 2 EpiPen autoinjectors",
"Antihistamine (diphenhydramine 25–50 mg PO q6–8h × 3–5 d)",
"Prednisolone 40–60 mg/day × 3–5 d",
"Written anaphylaxis action plan",
"MedicAlert bracelet advice",
"Avoid known allergen — education",
"Allergy/immunology referral if severe/recurrent",
"Switch β-blocker if patient on one",
]
disc_data = [[Paragraph(f"• {i}", TBL_CELL)] for i in disc_items]
disc_tbl = Table(disc_data, colWidths=[col3 - 2*mm])
disc_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_GREEN),
("BOX", (0,0), (-1,-1), 0.5, GREEN),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 1.5),
("BOTTOMPADDING", (0,0), (-1,-1), 1.5),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
# 3-column layout
def build_col(header, tbl):
inner = Table([[header],[tbl]], colWidths=[col3])
inner.setStyle(TableStyle([
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
return inner
bottom_row = Table([[build_col(ae_header, ae_tbl),
Spacer(2*mm, 1),
build_col(caut_header, caut_tbl),
Spacer(2*mm, 1),
build_col(disc_header, disc_tbl)]],
colWidths=[col3, 2*mm, col3, 2*mm, col3])
bottom_row.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
story.append(bottom_row)
story.append(Spacer(1, 3*mm))
# ═══════════════════════════════════════════════════════════════════════════
# FOOTER
# ═══════════════════════════════════════════════════════════════════════════
HRFlowable(width="100%", thickness=0.5, color=MID_GREY)
footer_data = [[
Paragraph(
"Sources: Tintinalli's Emergency Medicine 9e · Miller's Anesthesia 10e · Harrison's Principles 22e · Roberts & Hedges' Clinical Procedures in Emergency Medicine · ACLS/PALS Guidelines",
FOOTER),
Paragraph(
"For trained healthcare professionals only. Verify all doses and concentrations independently before administration. Not a substitute for clinical judgment.",
S("FR", fontSize=6.5, fontName="Helvetica-Oblique", textColor=MID_GREY, alignment=TA_RIGHT))
]]
footer_tbl = Table(footer_data, colWidths=[(W-28*mm)*0.55, (W-28*mm)*0.45])
footer_tbl.setStyle(TableStyle([
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LINEABOVE", (0,0), (-1,0), 0.5, MID_GREY),
]))
story.append(footer_tbl)
# ── Build ────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF written to: {out_path}")