~/coma-checklist/generate_checklist.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.graphics.shapes import Drawing, Rect, String, Line
from reportlab.graphics import renderPDF
from reportlab.pdfgen import canvas
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate
import os
OUTPUT = "/home/daytona/workspace/coma-checklist/Coma_Bedside_Assessment_Checklist.pdf"
# ── Colours ──────────────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1A3557")
MID_BLUE = colors.HexColor("#2E5FA3")
LIGHT_BLUE = colors.HexColor("#D6E4F0")
ACCENT_RED = colors.HexColor("#C0392B")
ACCENT_ORANGE = colors.HexColor("#E67E22")
ACCENT_GREEN = colors.HexColor("#1E8449")
VERY_LIGHT = colors.HexColor("#F4F8FB")
WHITE = colors.white
GREY_LINE = colors.HexColor("#BBCEDD")
TEXT_DARK = colors.HexColor("#1A1A2E")
LIGHT_GREY = colors.HexColor("#ECF0F1")
YELLOW_BG = colors.HexColor("#FEF9E7")
RED_LIGHT = colors.HexColor("#FADBD8")
W, H = A4 # 595.27 x 841.89
# ── Page header/footer callback ───────────────────────────────────────────────
def on_page(canvas_obj, doc):
canvas_obj.saveState()
# Top banner
canvas_obj.setFillColor(DARK_BLUE)
canvas_obj.rect(0, H - 28*mm, W, 28*mm, fill=1, stroke=0)
canvas_obj.setFillColor(MID_BLUE)
canvas_obj.rect(0, H - 30*mm, W, 2*mm, fill=1, stroke=0)
canvas_obj.setFillColor(WHITE)
canvas_obj.setFont("Helvetica-Bold", 16)
canvas_obj.drawString(14*mm, H - 14*mm, "RAPID BEDSIDE COMA ASSESSMENT CHECKLIST")
canvas_obj.setFont("Helvetica", 9)
canvas_obj.drawString(14*mm, H - 21*mm, "Harrison's Principles of Internal Medicine, 22nd Ed. (2025) | Chapters 29 & 30")
# Right side of banner: date/patient fields
canvas_obj.setFont("Helvetica", 8)
canvas_obj.drawRightString(W - 14*mm, H - 12*mm, "Patient: _________________________")
canvas_obj.drawRightString(W - 14*mm, H - 18*mm, "Date: ____________ Time: ________")
canvas_obj.drawRightString(W - 14*mm, H - 24*mm, "Examiner: _______________________")
# Bottom footer
canvas_obj.setFillColor(DARK_BLUE)
canvas_obj.rect(0, 0, W, 10*mm, fill=1, stroke=0)
canvas_obj.setFillColor(WHITE)
canvas_obj.setFont("Helvetica", 7.5)
canvas_obj.drawString(14*mm, 3.5*mm,
"For clinical reference only. Confirm findings with senior clinician. Source: Harrison's 22E (2025)")
canvas_obj.setFont("Helvetica-Bold", 7.5)
canvas_obj.drawRightString(W - 14*mm, 3.5*mm, f"Page {doc.page}")
canvas_obj.restoreState()
# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def s(name, **kw):
base = styles["Normal"]
return ParagraphStyle(name, parent=base, **kw)
section_title = s("SectionTitle",
fontName="Helvetica-Bold", fontSize=9.5, textColor=WHITE,
spaceAfter=0, spaceBefore=0, leading=12)
sub_head = s("SubHead",
fontName="Helvetica-Bold", fontSize=8.5, textColor=DARK_BLUE,
spaceAfter=1, spaceBefore=3, leading=11)
body = s("Body",
fontName="Helvetica", fontSize=8, textColor=TEXT_DARK,
spaceAfter=1, leading=10)
body_bold = s("BodyBold",
fontName="Helvetica-Bold", fontSize=8, textColor=TEXT_DARK,
spaceAfter=1, leading=10)
note = s("Note",
fontName="Helvetica-Oblique", fontSize=7.5, textColor=colors.HexColor("#5D6D7E"),
spaceAfter=1, leading=10)
red_note = s("RedNote",
fontName="Helvetica-Bold", fontSize=8, textColor=ACCENT_RED,
spaceAfter=1, leading=10)
small = s("Small",
fontName="Helvetica", fontSize=7.5, textColor=TEXT_DARK,
spaceAfter=0, leading=9.5)
small_bold = s("SmallBold",
fontName="Helvetica-Bold", fontSize=7.5, textColor=TEXT_DARK,
spaceAfter=0, leading=9.5)
# ── Helper: section header bar ─────────────────────────────────────────────
def section_bar(title, color=MID_BLUE, num=None):
label = f" {'0'+str(num) if num and num < 10 else num} {title}" if num else f" {title}"
t = Table([[Paragraph(label, section_title)]], colWidths=[W - 28*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0), (-1,-1), 6),
]))
return t
# ── Helper: checkbox row ────────────────────────────────────────────────────
CHECKBOX = "☐"
CHECKED = "☑"
def cb_row(label, note_text="", indent=0, bold=False):
pad = " " * indent
sty = body_bold if bold else body
cell1 = Paragraph(f"{pad}{CHECKBOX} {label}", sty)
cell2 = Paragraph(note_text, note) if note_text else Paragraph("", note)
row = [cell1, cell2]
return row
def finding_row(finding, implication, bg=None):
c1 = Paragraph(f"<b>{finding}</b>", small_bold)
c2 = Paragraph(implication, small)
return [c1, c2]
# ── Helper: two-column table ────────────────────────────────────────────────
def two_col_table(rows, col1=105*mm, col2=None, bg_alt=True, header=None):
col2 = col2 or (W - 28*mm - col1)
data = []
if header:
data.append([Paragraph(f"<b>{header[0]}</b>", small_bold),
Paragraph(f"<b>{header[1]}</b>", small_bold)])
for i, r in enumerate(rows):
data.append(r)
col_widths = [col1, col2]
t = Table(data, colWidths=col_widths)
ts = [
("GRID", (0,0), (-1,-1), 0.4, GREY_LINE),
("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), "TOP"),
]
if header:
ts += [("BACKGROUND", (0,0), (-1,0), LIGHT_BLUE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold")]
if bg_alt:
start = 1 if header else 0
for i in range(start, len(data), 2):
ts.append(("BACKGROUND", (0,i), (-1,i), VERY_LIGHT))
t.setStyle(TableStyle(ts))
return t
def cb_table(rows, bg_alt=True):
"""Table of checkbox rows: [label_para, note_para]"""
t = Table(rows, colWidths=[125*mm, W - 28*mm - 125*mm])
ts = [
("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), "TOP"),
("LINEBELOW", (0,0), (-1,-1), 0.3, GREY_LINE),
]
if bg_alt:
for i in range(0, len(rows), 2):
ts.append(("BACKGROUND", (0,i), (-1,i), VERY_LIGHT))
t.setStyle(TableStyle(ts))
return t
# ══════════════════════════════════════════════════════════════════════════════
# BUILD CONTENT
# ══════════════════════════════════════════════════════════════════════════════
story = []
SP = Spacer(1, 3*mm)
SP2 = Spacer(1, 2*mm)
# ── ALERT BOX ─────────────────────────────────────────────────────────────────
alert_data = [[
Paragraph("⚡ IMMEDIATE PRIORITIES — Before neurologic exam:",
s("AlertH", fontName="Helvetica-Bold", fontSize=9, textColor=ACCENT_RED, leading=11)),
Paragraph(
"<b>A</b> Airway (protect) <b>B</b> Breathing (O₂ / ventilate) "
"<b>C</b> Circulation (IV access, BP, HR) <b>D</b> Dextrose (fingerstick BG) "
"<b>T</b> Thiamine 100 mg IV (before dextrose in malnourished/alcoholic) "
"<b>N</b> Naloxone if opioid suspected <b>C-spine</b> immobilise if trauma",
s("AlertBody", fontName="Helvetica", fontSize=8, textColor=TEXT_DARK, leading=11))
]]
alert_t = Table(alert_data, colWidths=[70*mm, W - 28*mm - 70*mm])
alert_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), RED_LIGHT),
("GRID", (0,0), (-1,-1), 0.8, ACCENT_RED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(alert_t)
story.append(SP)
# ── GCS BOX ───────────────────────────────────────────────────────────────────
story.append(section_bar("GLASGOW COMA SCALE (GCS)", color=DARK_BLUE, num=1))
story.append(SP2)
gcs_rows = [
[Paragraph("<b>EYES (E)</b>", small_bold), Paragraph("<b>Score</b>", small_bold),
Paragraph("<b>VERBAL (V)</b>", small_bold), Paragraph("<b>Score</b>", small_bold),
Paragraph("<b>MOTOR (M)</b>", small_bold), Paragraph("<b>Score</b>", small_bold)],
[Paragraph("Spontaneous", small), Paragraph("4", small),
Paragraph("Oriented", small), Paragraph("5", small),
Paragraph("Obeys commands", small), Paragraph("6", small)],
[Paragraph("To voice", small), Paragraph("3", small),
Paragraph("Confused", small), Paragraph("4", small),
Paragraph("Localises pain", small), Paragraph("5", small)],
[Paragraph("To pain", small), Paragraph("2", small),
Paragraph("Inappropriate words", small), Paragraph("3", small),
Paragraph("Withdraws (normal flex)", small), Paragraph("4", small)],
[Paragraph("None", small), Paragraph("1", small),
Paragraph("Incomprehensible sounds", small), Paragraph("2", small),
Paragraph("Abnormal flexion (decorticate)", small), Paragraph("3", small)],
[Paragraph("", small), Paragraph("", small),
Paragraph("None", small), Paragraph("1", small),
Paragraph("Extension (decerebrate)", small), Paragraph("2", small)],
[Paragraph("", small), Paragraph("", small),
Paragraph("", small), Paragraph("", small),
Paragraph("None", small), Paragraph("1", small)],
]
cw = [(W-28*mm)/6] * 6
gcs_t = Table(gcs_rows, colWidths=cw)
gcs_ts = [
("GRID", (0,0), (-1,-1), 0.4, GREY_LINE),
("BACKGROUND", (0,0), (-1,0), LIGHT_BLUE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("ALIGN", (1,0), (1,-1), "CENTER"),
("ALIGN", (3,0), (3,-1), "CENTER"),
("ALIGN", (5,0), (5,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
for i in range(1, len(gcs_rows), 2):
gcs_ts.append(("BACKGROUND", (0,i), (-1,i), VERY_LIGHT))
gcs_t.setStyle(TableStyle(gcs_ts))
story.append(gcs_t)
# GCS score boxes
score_row = [
Paragraph("E = ___", body_bold),
Paragraph("V = ___", body_bold),
Paragraph("M = ___", body_bold),
Paragraph("<b>TOTAL GCS = ___ / 15</b>",
s("GCSTotal", fontName="Helvetica-Bold", fontSize=10, textColor=ACCENT_RED, leading=12)),
]
score_t = Table([score_row], colWidths=[(W-28*mm)/4]*4)
score_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), YELLOW_BG),
("GRID", (0,0), (-1,-1), 0.8, ACCENT_ORANGE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(SP2)
story.append(score_t)
story.append(SP)
# ── HISTORY ──────────────────────────────────────────────────────────────────
story.append(section_bar("HISTORY (from family / EMS / bystanders)", color=MID_BLUE, num=2))
story.append(SP2)
hist_rows = [
cb_row("Onset and rapidity (sudden / gradual / witnessed)", "Sudden = vascular; gradual = metabolic/toxic"),
cb_row("Antecedent symptoms", "Headache, fever, seizures, diplopia, vomiting, focal weakness"),
cb_row("Medications / drugs / alcohol", "Include OTC, herbals, illicit substances"),
cb_row("Chronic illness", "Liver, kidney, lung, heart, diabetes, epilepsy, psychiatric"),
cb_row("Head trauma", "Even minor - subdural can present delayed"),
cb_row("Last known well / last seen normal", "Critical for stroke pathway timing"),
]
story.append(cb_table(hist_rows))
story.append(SP)
# ── GENERAL EXAM ─────────────────────────────────────────────────────────────
story.append(section_bar("GENERAL PHYSICAL EXAMINATION", color=MID_BLUE, num=3))
story.append(SP2)
vital_header = ["Vital Sign / Finding", "Interpretation"]
vital_rows = [
finding_row("Fever", "Infection, meningitis, encephalitis, heat stroke, NMS, malignant hyperthermia"),
finding_row("Hypothermia (<31°C)", "Cold exposure; alcohol/barbiturate/sedative OD; hypoglycemia; circulatory failure — CAUSES coma at <31°C"),
finding_row("Hypertension", "Hypertensive encephalopathy, cerebral haemorrhage, large infarction, head injury"),
finding_row("Hypotension", "Alcohol/barbiturate OD, internal haemorrhage, MI, sepsis, hypothyroidism, Addisonian crisis"),
finding_row("Tachypnoea", "Systemic acidosis, pneumonia, brainstem respiratory pattern"),
finding_row("Papilledema (fundoscopy)", "Raised ICP — do NOT LP before imaging"),
finding_row("Subhyaloid haemorrhage", "Subarachnoid haemorrhage (Terson syndrome)"),
finding_row("Petechiae", "TTP, meningococcemia, bleeding diathesis with ICH"),
finding_row("Cyanosis", "Systemic hypoxia, carbon monoxide poisoning"),
finding_row("Nuchal rigidity", "Meningitis, SAH — test gently if no C-spine injury"),
finding_row("Jaundice / fetor hepaticus", "Hepatic encephalopathy"),
finding_row("Breath odour", "Alcohol, ketones (DKA), uraemic fetor"),
finding_row("Skin: needle tracks", "IV drug use — opioids, stimulants"),
finding_row("Head trauma signs", "Battle sign, raccoon eyes, CSF rhinorrhoea/otorrhoea → base of skull #"),
]
story.append(two_col_table(vital_rows, col1=62*mm, header=vital_header))
story.append(SP)
# ── NEUROLOGIC EXAM ──────────────────────────────────────────────────────────
story.append(section_bar("NEUROLOGIC EXAMINATION", color=DARK_BLUE, num=4))
story.append(SP2)
# 4a Spontaneous behaviour
story.append(Paragraph("4a SPONTANEOUS BEHAVIOUR (observe first)", sub_head))
behav_rows = [
cb_row("Spontaneous movements, yawning, swallowing, moaning", "Near-normal arousal"),
cb_row("Asymmetric movement / externally rotated leg", "Hemiplegia (or hip fracture)"),
cb_row("Subtle repetitive twitching (finger / foot / face)", "Seizure — obtain EEG urgently"),
cb_row("Multifocal myoclonus", "Metabolic (uraemia, hypoxia, drug OD); prion disease"),
]
story.append(cb_table(behav_rows))
story.append(SP2)
# 4b Response to stimuli
story.append(Paragraph("4b RESPONSE TO NOXIOUS STIMULI", sub_head))
stim_rows = [
cb_row("Purposeful withdrawal / abduction", "Intact corticospinal system"),
cb_row("Decorticate posturing (arm flexion, leg extension)", "Damage above midbrain / corticospinal"),
cb_row("Decerebrate posturing (arm + leg extension)", "Severe corticospinal / brainstem damage"),
cb_row("No response", "Deepest coma level"),
]
story.append(cb_table(stim_rows))
story.append(SP2)
# 4c Pupils
story.append(Paragraph("4c PUPILLARY SIGNS (use bright diffuse light)", sub_head))
pupil_header = ["Pupil Finding", "Significance"]
pupil_rows = [
finding_row("Midsize (2.5–5 mm), reactive bilaterally", "Excludes upper midbrain damage; metabolic/toxic likely"),
finding_row("One enlarged (>6 mm), poorly reactive", "CN III compression from ipsilateral cerebral mass — HERNIATION"),
finding_row("Oval, slightly eccentric", "Transitional sign — early midbrain/CN III compression"),
finding_row("Bilateral dilated, unreactive ('blown')", "Severe midbrain damage — exclude anticholinergic OD, mydriatics"),
finding_row("Small (1–2.5 mm), reactive bilateral", "Metabolic encephalopathy; hydrocephalus; thalamic haemorrhage"),
finding_row("Pinpoint (<1 mm)", "Opioid OD (give naloxone) OR extensive pontine haemorrhage"),
finding_row("Unilateral miosis", "Posterior hypothalamus/brainstem sympathetic lesion (large ICH)"),
]
story.append(two_col_table(pupil_rows, col1=72*mm, header=pupil_header))
story.append(SP2)
# 4d Eye movements
story.append(Paragraph("4d EYE MOVEMENTS", sub_head))
eye_rows = [
finding_row("Spontaneous roving conjugate", "Intact brainstem; bihemispheral coma"),
finding_row("Oculocephalic (doll's eyes): eyes deviate opposite to head turn", "Intact brainstem; ONLY test if C-spine cleared"),
finding_row("Oculocephalic: absent / dysconjugate", "Brainstem lesion"),
finding_row("Cold caloric (50 mL ice water): tonic deviation toward irrigated ear", "Intact brainstem"),
finding_row("Cold caloric: no response / dysconjugate", "Brainstem lesion"),
finding_row("Conjugate deviation toward hemiplegia side", "Ipsilateral frontal lobe lesion ('eyes look at lesion')"),
finding_row("Conjugate deviation away from hemiplegia", "Contralateral pontine lesion ('eyes look away from lesion')"),
finding_row("Downward gaze deviation", "Bilateral thalamic lesions; midbrain compression"),
]
story.append(two_col_table(eye_rows, col1=82*mm, header=["Eye Movement Finding", "Significance"]))
story.append(SP2)
# 4e Corneal reflex
story.append(Paragraph("4e CORNEAL REFLEX & OTHER BRAINSTEM REFLEXES", sub_head))
bs_rows = [
cb_row("Corneal reflex present bilaterally", "Pontine integrity (CN V afferent → CN VII efferent)"),
cb_row("Absent corneal reflex", "Pontine lesion; deep coma; prior corneal surgery"),
cb_row("Gag reflex", "Medullary function — if absent, airway at risk"),
cb_row("Deep tendon reflexes + plantar response", "Asymmetry = focal lesion"),
]
story.append(cb_table(bs_rows))
story.append(SP2)
# 4f Respiratory patterns
story.append(Paragraph("4f RESPIRATORY PATTERNS", sub_head))
resp_rows = [
finding_row("Cheyne-Stokes (waxing-waning + apnoea)", "Bihemispheral or metabolic"),
finding_row("Central neurogenic hyperventilation (deep, rapid, regular)", "Midbrain – upper pons lesion"),
finding_row("Apneustic (prolonged inspiratory pause)", "Caudal pontine damage"),
finding_row("Ataxic / Biot (chaotic, irregular)", "Medullary damage — PRE-TERMINAL"),
finding_row("Kussmaul (deep, regular, sighing)", "Metabolic acidosis (DKA, uraemia)"),
]
story.append(two_col_table(resp_rows, col1=82*mm, header=["Pattern", "Localisation"]))
story.append(SP)
# ── INVESTIGATIONS ────────────────────────────────────────────────────────────
story.append(section_bar("INVESTIGATIONS", color=MID_BLUE, num=5))
story.append(SP2)
inv_rows_l = [
cb_row("Fingerstick blood glucose", "STAT — give D50 if <60 mg/dL"),
cb_row("ECG", "Arrhythmia, MI, QTc prolongation"),
cb_row("CBC", "Infection, anaemia, TTP"),
cb_row("Electrolytes (Na, K, Cl, HCO₃)", "Hypo/hypernatraemia, acidosis"),
cb_row("Ca, Mg, Phosphate", "Electrolyte encephalopathy"),
cb_row("Glucose (serum)", "DKA, NKHH, hypoglycaemia"),
cb_row("Renal function (Cr, BUN)", "Uraemic encephalopathy"),
cb_row("LFTs + ammonia", "Hepatic encephalopathy"),
cb_row("ABG", "Hypoxia, hypercarbia, acid-base"),
cb_row("Serum + urine toxicology", "Drugs of abuse, medications — earlier in young patients"),
cb_row("Blood cultures × 2", "Before antibiotics if infection suspected"),
cb_row("Serum lactate", "Sepsis, ischaemia"),
cb_row("Thyroid function (TSH, fT4)", "Myxoedema coma, thyroid storm"),
cb_row("Cortisol / ACTH stim", "Addisonian crisis"),
cb_row("Coagulation (PT, PTT, INR)", "Bleeding diathesis, DIC"),
cb_row("Thiamine, B12, folate", "Nutritional deficiency"),
]
inv_rows_r = [
cb_row("Non-contrast CT head (STAT)", "Haemorrhage, mass, hydrocephalus, oedema — BEFORE LP"),
cb_row("MRI brain + DWI", "Ischaemia, encephalitis, PRES, demyelination — after CT"),
cb_row("CT angiography", "If basilar artery occlusion suspected"),
cb_row("Lumbar puncture (after CT excludes mass)", "Meningitis, SAH (xanthochromia), encephalitis"),
cb_row("CSF: cells, protein, glucose", ""),
cb_row("CSF: Gram stain + culture", "Bacterial meningitis"),
cb_row("CSF: India ink + cryptococcal Ag", "Fungal meningitis (immunocompromised)"),
cb_row("CSF: PCR (HSV, CMV, EBV, JC)", "Viral encephalitis"),
cb_row("EEG (STAT)", "Non-convulsive status epilepticus — ESSENTIAL if unexplained"),
cb_row("Serum autoimmune/paraneoplastic Ab panel", "Anti-NMDAR, LGI1, CASPR2, etc."),
cb_row("Urine output / urinalysis", "Renal failure, UTI source"),
cb_row("Chest X-ray", "Pneumonia, aspiration, cardiac"),
cb_row("SSEP (somatosensory EPs)", "Absent cortical responses = poor outcome post-anoxia"),
cb_row("Serum NSE (if post-arrest)", "High NSE = poor neurological outcome"),
cb_row("", ""),
cb_row("", ""),
]
# side-by-side layout
left_t = cb_table(inv_rows_l)
right_t = cb_table(inv_rows_r)
two_panel = Table([[left_t, right_t]], colWidths=[(W-28*mm)/2]*2)
two_panel.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),
]))
story.append(two_panel)
story.append(SP)
# ── DIFFERENTIAL DIAGNOSIS ───────────────────────────────────────────────────
story.append(section_bar("DIFFERENTIAL DIAGNOSIS OF COMA (Harrison's Table 30-1)", color=DARK_BLUE, num=6))
story.append(SP2)
ddx_data = [
[Paragraph("<b>Category</b>", small_bold),
Paragraph("<b>No focal signs (CT often normal)</b>", small_bold),
Paragraph("<b>Focal/brainstem signs (CT abnormal)</b>", small_bold),
Paragraph("<b>Meningeal signs (CSF abnormal)</b>", small_bold)],
[Paragraph("Examples", small),
Paragraph(
"• Intoxications (alcohol, opiates, sedatives)\n"
"• Metabolic: hypoglycaemia, DKA, NKHH, hypo/hypernatraemia, hypercalcaemia, uraemia, hepatic, hypercarbia, Addisonian, thyroid\n"
"• Severe infections: sepsis, pneumonia, typhoid, malaria, Waterhouse-Friderichsen\n"
"• Shock (any cause)\n"
"• Status epilepticus / NCSE / postictal\n"
"• PRES / hypertensive encephalopathy / eclampsia\n"
"• Hyper/hypothermia\n"
"• Concussion\n"
"• Acute hydrocephalus",
small),
Paragraph(
"• Hemispheral haemorrhage (basal ganglia, thalamic) or large MCA infarction\n"
"• Basilar artery thrombosis/embolism\n"
"• Brain abscess / subdural empyema\n"
"• Epidural / subdural haemorrhage\n"
"• Brain tumour with oedema\n"
"• Cerebellar / pontine haemorrhage or infarction\n"
"• Widespread TBI\n"
"• Metabolic coma on background of focal damage",
small),
Paragraph(
"• Bacterial meningitis\n"
"• Subarachnoid haemorrhage\n"
"• Viral meningitis / encephalitis\n"
"• Fungal / TB meningitis\n"
"• Carcinomatous meningitis",
small)],
]
ddx_cw = [(W-28*mm)*f for f in [0.12, 0.32, 0.30, 0.26]]
ddx_t = Table(ddx_data, colWidths=ddx_cw)
ddx_t.setStyle(TableStyle([
("GRID", (0,0), (-1,-1), 0.5, GREY_LINE),
("BACKGROUND", (0,0), (-1,0), LIGHT_BLUE),
("BACKGROUND", (0,1), (0,1), VERY_LIGHT),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(ddx_t)
story.append(SP)
# ── HERNIATION WARNING BOX ────────────────────────────────────────────────────
hern_data = [[
Paragraph("⚠ HERNIATION SYNDROMES — RECOGNISE IMMEDIATELY",
s("HernH", fontName="Helvetica-Bold", fontSize=9, textColor=ACCENT_RED, leading=11)),
Paragraph(
"<b>Uncal (most common):</b> Ipsilateral CN III palsy (first sign = ipsilateral pupil dilation) → ipsilateral hemiplegia → bilateral motor signs → decerebrate posturing | "
"<b>Central:</b> Rostrocaudal deterioration: drowsy → stupor → coma; pupils small → fixed dilated (midbrain compression) | "
"<b>Tonsillar (foraminal):</b> Cerebellar tonsils through foramen magnum → medullary compression → respiratory arrest",
s("HernBody", fontName="Helvetica", fontSize=7.5, textColor=TEXT_DARK, leading=10))
]]
hern_t = Table(hern_data, colWidths=[60*mm, W-28*mm-60*mm])
hern_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), RED_LIGHT),
("GRID", (0,0), (-1,-1), 1.0, ACCENT_RED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(hern_t)
story.append(SP)
# ── SPECIFIC CLINICAL PEARLS ─────────────────────────────────────────────────
story.append(section_bar("CLINICAL PEARLS & MNEMONICS", color=MID_BLUE, num=7))
story.append(SP2)
pearls_rows = [
[Paragraph("<b>AEIOU TIPS</b>", small_bold),
Paragraph(
"<b>A</b>lcohol/drugs <b>E</b>pilepsy/Electrolytes <b>I</b>nsulin (glucose) <b>O</b>piates <b>U</b>raemia | "
"<b>T</b>rauma <b>I</b>nfection <b>P</b>sychiatric/Psychiatric <b>S</b>troke/Structural",
small)],
[Paragraph("<b>Eyes look toward lesion</b>", small_bold),
Paragraph("Frontal lobe lesion (e.g. large hemisphere haemorrhage) — eyes deviate toward the damaged hemisphere, away from the hemiplegia", small)],
[Paragraph("<b>Eyes look away from lesion</b>", small_bold),
Paragraph("Pontine lesion — eyes deviate toward the hemiplegia (contralateral pontine gaze centre destroyed)", small)],
[Paragraph("<b>Give thiamine BEFORE glucose</b>", small_bold),
Paragraph("In malnourished / alcoholic patients — glucose without thiamine can precipitate Wernicke's encephalopathy", small)],
[Paragraph("<b>NCSE must be excluded</b>", small_bold),
Paragraph("Non-convulsive status epilepticus can mimic metabolic coma. EEG is essential when etiology is unclear after initial workup", small)],
[Paragraph("<b>LP after CT</b>", small_bold),
Paragraph("Always exclude a large intracranial mass on CT before LP. If bacterial meningitis is suspected → blood cultures + empirical antibiotics FIRST, then imaging, then LP", small)],
[Paragraph("<b>Locked-in syndrome</b>", small_bold),
Paragraph("Basilar artery occlusion — awake but quadriplegic; only vertical eye movement preserved. Can be mistaken for coma. Check for vertical gaze on examination", small)],
[Paragraph("<b>Psychogenic unresponsiveness</b>", small_bold),
Paragraph("Eyes resist passive opening; Bell's phenomenon present; nystagmus on cold calorics (rather than tonic deviation); normal EEG; normal brainstem reflexes", small)],
]
pearls_t = Table(pearls_rows, colWidths=[46*mm, W-28*mm-46*mm])
pearls_ts = [
("GRID", (0,0), (-1,-1), 0.4, GREY_LINE),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]
for i in range(0, len(pearls_rows), 2):
pearls_ts.append(("BACKGROUND", (0,i), (-1,i), VERY_LIGHT))
pearls_t.setStyle(TableStyle(pearls_ts))
story.append(pearls_t)
story.append(SP)
# ── PROGNOSIS ─────────────────────────────────────────────────────────────────
story.append(section_bar("PROGNOSIS & BRAIN DEATH CRITERIA", color=DARK_BLUE, num=8))
story.append(SP2)
prog_rows = [
[Paragraph("<b>Good prognostic sign</b>", small_bold),
Paragraph("Drug/metabolic coma with preserved brainstem reflexes; GCS improves over first 24 h", small)],
[Paragraph("<b>Poor prognosis (post-anoxia)</b>", small_bold),
Paragraph("Absent bilateral cortical SSEP responses; serum NSE >33 µg/L at 48–72 h post-arrest (in context); persistent absence of pupillary reflexes at 72 h", small)],
[Paragraph("<b>Multimodal prognostication</b>", small_bold),
Paragraph("Required — no single marker is sufficient. Use clinical exam + EEG + SSEP + biomarkers + MRI together (AAN guidelines 2023)", small)],
[Paragraph("<b>Brain death prerequisites</b>", small_bold),
Paragraph("Known proximate cause; exclude hypothermia (<36°C), drug effect, severe metabolic derangement, neuromuscular blockade", small)],
[Paragraph("<b>Brain death clinical criteria</b>", small_bold),
Paragraph("Coma + absent all brainstem reflexes (pupillary, corneal, oculocephalic, oculovestibular, gag, cough) + apnoea test (PaCO₂ rises to ≥60 mmHg without respiratory effort)", small)],
[Paragraph("<b>Confirmatory tests</b>", small_bold),
Paragraph("When clinical exam incomplete: EEG (isoelectric); cerebral angiography (no intracranial flow); nuclear perfusion scan; SSEP", small)],
]
prog_t = Table(prog_rows, colWidths=[46*mm, W-28*mm-46*mm])
prog_ts = [
("GRID", (0,0), (-1,-1), 0.4, GREY_LINE),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
("BACKGROUND", (0,3), (-1,5), LIGHT_BLUE),
]
for i in range(0, 3, 2):
prog_ts.append(("BACKGROUND", (0,i), (-1,i), VERY_LIGHT))
prog_t.setStyle(TableStyle(prog_ts))
story.append(prog_t)
story.append(SP)
# ── NOTES ────────────────────────────────────────────────────────────────────
story.append(section_bar("NOTES / SERIAL EXAM FINDINGS", color=MID_BLUE))
story.append(SP2)
notes_lines = [""] * 6
for _ in notes_lines:
story.append(HRFlowable(width="100%", thickness=0.5, color=GREY_LINE, spaceAfter=6, spaceBefore=1))
# ── BUILD DOC ──────────────────────────────────────────────────────────────────
doc = BaseDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=14*mm,
rightMargin=14*mm,
topMargin=33*mm,
bottomMargin=14*mm,
)
frame = Frame(
doc.leftMargin, doc.bottomMargin,
W - doc.leftMargin - doc.rightMargin,
H - doc.topMargin - doc.bottomMargin,
id="main"
)
doc.addPageTemplates([PageTemplate(id="page", frames=[frame], onPage=on_page)])
doc.build(story)
print(f"PDF created: {OUTPUT}")