What is the good standard test for SAH within 6 hrs and beyond 6 hrs support your answer with reference
"subarachnoid hemorrhage" AND diagnosis AND "CT angiography" AND "lumbar puncture"
subarachnoid hemorrhage gold standard test within 6 hours beyond 6 hours CT xanthochromia 2024 guidelines
"subarachnoid hemorrhage" AND "CT sensitivity" AND "6 hours" AND "lumbar puncture"
"subarachnoid hemorrhage" AND diagnosis AND xanthochromia AND spectrophotometry
"Brain noncontrast computed tomography (NCCT) is a highly sensitive imaging modality for diagnosis of aSAH... NCCT scan confirms a SAH with very high sensitivity, close to 100%, in the first 3 days."
- Bradley and Daroff's Neurology in Clinical Practice (block15, lines 1820-1822)
"The sensitivity decreases moderately with time as the subarachnoid blood is metabolized and cleared."
- Bradley and Daroff's Neurology in Clinical Practice
"Although the sensitivity of CT for making the diagnosis of SAH can range from 92% to 98% when performed within 24 hours of the onset of symptoms, it decreases to 75% when performed 48-72 hours after..."
- Pfenninger and Fowler's Procedures for Primary Care
"The detection of xanthochromia in the CSF is still considered to be the gold standard for diagnosing SAH, especially for patients with a negative CT scan obtained >6 h from initial symptom onset."
- Norwegian observational study (Aaseth et al., Eur J Neurol, 2024 - PMID: 39092827)
| Method | Notes |
|---|---|
| Visual inspection | Unreliable - CSF may appear clear despite abnormal spectrophotometric scan |
| Spectrophotometry (gold standard) | Detects oxyhemoglobin peak (410-418 nm) and bilirubin peak (450-460 nm). UK guidelines recommend this as the definitive method. |
"Diagnosis of SAH therefore hinges on the detection of bilirubin in CSF, for which spectrophotometry is the recommended approach. Visual inspection for the yellow discoloration (xanthochromia) imparted to CSF is not considered to be reliable."
- Tietz Textbook of Laboratory Medicine, 7th Edition (block18, line 2864)
"It is worth noting that because bilirubin is produced in a time-dependent manner, the absence of detectable bilirubin does not rule out SAH, particularly if CSF is collected within 12 hours of the onset of the bleed."
- Tietz Textbook of Laboratory Medicine, 7th Edition
| Time from Ictus | Test of Choice | Sensitivity | Notes |
|---|---|---|---|
| < 6 hours | Non-contrast CT head (NCCT) | ~98-100% | Can rule out SAH alone if all criteria met; no LP needed in low-risk patients |
| 6-24 hours | NCCT + LP (if CT negative) | CT ~85-95% | LP with xanthochromia is needed; shared decision-making for very low risk |
| > 24 hours - 2 weeks | LP + CSF xanthochromia (spectrophotometry) | CT declining rapidly | LP is primary test; spectrophotometry for bilirubin is gold standard |
| Any time if CT negative | LP (spectrophotometry) | CSF bilirubin ~100% specific for SAH | Minimum 12 hrs from ictus for reliable bilirubin formation |
Clinical and radiological gradings for SAH
SAH clinical grading Hunt Hess WFNS Fisher scale modified Fisher radiological grading comparison 2024
"subarachnoid hemorrhage" AND "grading scale" AND prognosis
https://derangedphysiology.com/main/required-reading/neurolo…
"The Hunt-Hess grading system categorizes patients clinically... Presenting neurologic symptoms may range from mild headache to coma to sudden death."
- Schwartz's Principles of Surgery, 11th Edition
| Grade | Clinical Presentation | Hospital Mortality |
|---|---|---|
| 0 | Asymptomatic; unruptured aneurysm | - |
| 1 | Awake; asymptomatic or mild headache; mild nuchal rigidity | ~3% |
| 2 | Awake; moderate to severe headache, nuchal rigidity, possible cranial nerve palsy (e.g., CN III or IV) | ~3% |
| 3 | Lethargic/drowsy, confused, mild focal neurological deficit (e.g., pronator drift) | ~9% |
| 4 | Stuporous; significant neurological deficit (e.g., hemiplegia); possible early decerebrate posturing | ~24% |
| 5 | Comatose; decerebrate posturing (moribund) | ~70% |
"The WFNS SAH Grading Scale is also used for this purpose... Both scales are used in conjunction as a quick way to communicate severity of aneurysmal SAH."
- Schwartz's Principles of Surgery, 11th Edition
| Grade | GCS Score | Motor Deficit | Expected Outcome |
|---|---|---|---|
| 0 | 15 | Absent | Unruptured (some versions) |
| 1 | 15 | Absent | Good |
| 2 | 13-14 | Absent | Good |
| 3 | 13-14 | Present | Fair |
| 4 | 7-12 | Present or absent | Poor |
| 5 | 3-6 | Present or absent | Very poor |
| Feature | Hunt-Hess | WFNS |
|---|---|---|
| Year | 1968 | 1988 |
| Basis | Clinical exam | GCS + motor deficit |
| Objectivity | Lower | Higher |
| Sedated patients | Unreliable | Unreliable |
| Primary use | Surgical timing, prognosis | Universal grading, research |
"The extent of blood is a predictor of the probability of vasospasm and DCI."
- Bradley and Daroff's Neurology in Clinical Practice
| Group | CT Findings | Vasospasm Risk |
|---|---|---|
| 1 | No blood detected | Low |
| 2 | Diffuse or thin layer of subarachnoid blood; all vertical layers < 1 mm (interhemispheric, insular, ambient cistern) | Low |
| 3 | Localized clots OR vertical layers of blood ≥ 1 mm thickness | High |
| 4 | Diffuse or no subarachnoid blood, but with intraparenchymal or intraventricular clots | High |
| Grade | Subarachnoid Blood | Intraventricular Hemorrhage (IVH) | Symptomatic Vasospasm Risk |
|---|---|---|---|
| 0 | None | None | 0% |
| 1 | Focal or diffuse; thin | None | 24% |
| 2 | Focal thin or diffuse | Present | 33% |
| 3 | Thick | None | 33% |
| 4 | Thick | Present | 40% |
"After SAH, the Hijdra sum score seems to be superior to the more commonly used modified Fisher scale in assessing the amount of subarachnoid blood and in predicting the occurrence and severity of cerebral vasospasm."
- Miller's Anesthesia, 10th Edition

| Scale | Type | Year | What It Grades | Primary Use |
|---|---|---|---|---|
| Hunt-Hess | Clinical | 1968 | Neurological status (0-5) | Surgical timing, outcome prediction |
| WFNS | Clinical | 1988 | GCS + motor deficit (0-5) | Universal grading, research |
| Fisher | Radiological | 1980 | CT blood volume/distribution (1-4) | Predicts vasospasm |
| Modified Fisher | Radiological | 2006 | CT blood + IVH (0-4) | Predicts symptomatic vasospasm more accurately |
| Hijdra Sum | Radiological | 1985 | Semi-quantitative cisternal blood (0-30) | Research; superior vasospasm prediction |
?
Create a study guide PDF on SAH grading systems 20mins each for an hour; topic ecmo demystified basics to bedside; what all needs to be covered and or demonstrated - give a structured layout
ECMO basics to bedside teaching session curriculum VV VA ECPella junior doctors 2024
mkdir -p /home/daytona/workspace/sah-ecmo && ls /home/daytona/workspace/sah-ecmo/
~/sah-ecmo/build_sah_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table,
TableStyle, HRFlowable, PageBreak, KeepTogether)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import ListFlowable, ListItem
# ── colour palette ──────────────────────────────────────────────────────────
NAVY = colors.HexColor('#1a3a5c')
TEAL = colors.HexColor('#2a7f8f')
AMBER = colors.HexColor('#e07b23')
LTBLUE = colors.HexColor('#ddeef6')
LTAMBER = colors.HexColor('#fef3e2')
WHITE = colors.white
GREY = colors.HexColor('#f4f6f8')
DKGREY = colors.HexColor('#444444')
RED = colors.HexColor('#c0392b')
GREEN = colors.HexColor('#1e8449')
doc = SimpleDocTemplate(
'/home/daytona/workspace/sah-ecmo/SAH_Grading_Study_Guide.pdf',
pagesize=A4,
leftMargin=1.8*cm, rightMargin=1.8*cm,
topMargin=1.8*cm, bottomMargin=1.8*cm,
title='SAH Grading Systems – Study Guide',
author='Orris Medical Education'
)
styles = getSampleStyleSheet()
# Custom styles
def S(name, **kw):
base = kw.pop('parent', 'Normal')
return ParagraphStyle(name, parent=styles[base], **kw)
TITLE = S('TITLE', fontSize=22, textColor=WHITE, alignment=TA_CENTER, fontName='Helvetica-Bold', spaceAfter=4)
SUBTITLE = S('SUBTITLE', fontSize=12, textColor=LTBLUE, alignment=TA_CENTER, fontName='Helvetica', spaceAfter=6)
H1 = S('H1', fontSize=15, textColor=WHITE, fontName='Helvetica-Bold', spaceAfter=2, spaceBefore=2)
H2 = S('H2', fontSize=12, textColor=NAVY, fontName='Helvetica-Bold', spaceAfter=3, spaceBefore=6)
H3 = S('H3', fontSize=10, textColor=TEAL, fontName='Helvetica-Bold', spaceAfter=2, spaceBefore=4)
BODY = S('BODY', fontSize=9, textColor=DKGREY, fontName='Helvetica', leading=14, spaceAfter=3, alignment=TA_JUSTIFY)
SMALL = S('SMALL', fontSize=8, textColor=DKGREY, fontName='Helvetica', leading=11)
BADGE = S('BADGE', fontSize=8, textColor=WHITE, fontName='Helvetica-Bold', alignment=TA_CENTER)
TIMEBADGE = S('TIMEBADGE',fontSize=9, textColor=AMBER, fontName='Helvetica-Bold')
CAPTION = S('CAPTION', fontSize=8, textColor=colors.HexColor('#777777'), fontName='Helvetica-Oblique', alignment=TA_CENTER, spaceBefore=1)
BULLET = S('BULLET', fontSize=9, textColor=DKGREY, fontName='Helvetica', leading=13, leftIndent=12, spaceAfter=1)
KEY = S('KEY', fontSize=9, textColor=NAVY, fontName='Helvetica-Bold', leading=13, leftIndent=12, spaceAfter=1)
def banner(text, bg=NAVY, style=H1, height=0.7*cm):
tbl = Table([[Paragraph(text, style)]], colWidths=[17.4*cm], rowHeights=[height])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,-1), bg),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('LEFTPADDING',(0,0),(-1,-1), 8),
('RIGHTPADDING',(0,0),(-1,-1), 8),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING',(0,0),(-1,-1), 4),
('ROUNDEDCORNERS',[3,3,3,3]),
]))
return tbl
def section_banner(text, bg=TEAL):
return banner(text, bg=bg, style=H1, height=0.65*cm)
def info_box(text, bg=LTBLUE, border=TEAL):
tbl = Table([[Paragraph(text, BODY)]], colWidths=[17.4*cm])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,-1), bg),
('BOX', (0,0),(-1,-1), 1.2, border),
('LEFTPADDING',(0,0),(-1,-1), 8),
('RIGHTPADDING',(0,0),(-1,-1), 8),
('TOPPADDING', (0,0),(-1,-1), 6),
('BOTTOMPADDING',(0,0),(-1,-1), 6),
]))
return tbl
def warning_box(text):
return info_box(f'<b>⚠ Key Point:</b> {text}', bg=LTAMBER, border=AMBER)
def two_col(left, right, lw=8.4*cm, rw=8.4*cm, gap=0.6*cm):
tbl = Table([[left, right]], colWidths=[lw, rw])
tbl.setStyle(TableStyle([
('VALIGN',(0,0),(-1,-1),'TOP'),
('LEFTPADDING',(0,0),(-1,-1),0),
('RIGHTPADDING',(0,0),(-1,-1),0),
]))
return tbl
def grading_table(headers, rows, col_widths, header_bg=NAVY):
data = [headers] + rows
tbl = Table(data, colWidths=col_widths)
n = len(rows)
style = [
('BACKGROUND', (0,0), (-1,0), header_bg),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('LEADING', (0,0), (-1,-1), 12),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('GRID', (0,0), (-1,-1), 0.4, colors.HexColor('#cccccc')),
('LEFTPADDING',(0,0), (-1,-1), 5),
('RIGHTPADDING',(0,0),(-1,-1), 5),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING',(0,0),(-1,-1), 4),
]
for i in range(1, n+1):
bg = GREY if i % 2 == 0 else WHITE
style.append(('BACKGROUND', (0,i), (-1,i), bg))
tbl.setStyle(TableStyle(style))
return tbl
def time_chip(label, color=TEAL):
tbl = Table([[Paragraph(label, BADGE)]], colWidths=[3.2*cm], rowHeights=[0.45*cm])
tbl.setStyle(TableStyle([
('BACKGROUND',(0,0),(-1,-1), color),
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
('ALIGN',(0,0),(-1,-1),'CENTER'),
('LEFTPADDING',(0,0),(-1,-1),4),
('RIGHTPADDING',(0,0),(-1,-1),4),
('TOPPADDING',(0,0),(-1,-1),2),
('BOTTOMPADDING',(0,0),(-1,-1),2),
]))
return tbl
def bullet_list(items, style=BULLET):
return ListFlowable(
[ListItem(Paragraph(i, style), leftIndent=10, bulletColor=TEAL, value='bullet') for i in items],
bulletType='bullet', leftIndent=6, bulletFontSize=8
)
# ═══════════════════════════════════════════════════════════════════════════
# BUILD STORY
# ═══════════════════════════════════════════════════════════════════════════
story = []
# ────────────────────────── COVER PAGE ─────────────────────────────────────
cover_bg = Table(
[[Paragraph('SAH GRADING SYSTEMS', TITLE)],
[Paragraph('A Structured 1-Hour Study Guide for Junior Doctors & Residents', SUBTITLE)],
[Spacer(1, 0.3*cm)],
[Paragraph('3 Sessions × 20 Minutes | Clinical + Radiological Grading | Prognostication', SUBTITLE)],
],
colWidths=[17.4*cm]
)
cover_bg.setStyle(TableStyle([
('BACKGROUND',(0,0),(-1,-1), NAVY),
('TOPPADDING',(0,0),(-1,-1),14),
('BOTTOMPADDING',(0,0),(-1,-1),10),
('LEFTPADDING',(0,0),(-1,-1),16),
('RIGHTPADDING',(0,0),(-1,-1),16),
]))
story.append(cover_bg)
story.append(Spacer(1, 0.4*cm))
story.append(info_box(
'<b>Learning Objectives:</b> By the end of this guide you will be able to: '
'(1) apply the Hunt-Hess and WFNS clinical grading scales and interpret their prognostic value; '
'(2) classify SAH on CT using the Fisher and modified Fisher scales; '
'(3) correlate clinical and radiological grades to guide management decisions; '
'(4) avoid common grading pitfalls at the bedside.'
))
story.append(Spacer(1, 0.3*cm))
# Session overview strip
session_data = [
['SESSION 1 – 20 min', 'SESSION 2 – 20 min', 'SESSION 3 – 20 min'],
['Why Grade SAH?\nHunt-Hess Scale\nWFNS Scale', 'Fisher Scale (CT)\nModified Fisher Scale\nCT Anatomy Review', 'Grade Correlation\nManagement Links\nQuick Quiz + Pitfalls'],
]
session_tbl = Table(session_data, colWidths=[5.6*cm, 5.6*cm, 5.6*cm])
session_tbl.setStyle(TableStyle([
('BACKGROUND',(0,0),(-1,0), TEAL),
('TEXTCOLOR',(0,0),(-1,0), WHITE),
('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),
('FONTNAME',(0,1),(-1,-1),'Helvetica'),
('FONTSIZE',(0,0),(-1,-1),9),
('ALIGN',(0,0),(-1,-1),'CENTER'),
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
('GRID',(0,0),(-1,-1),0.5, WHITE),
('BACKGROUND',(0,1),(-1,-1), LTBLUE),
('TOPPADDING',(0,0),(-1,-1),5),
('BOTTOMPADDING',(0,0),(-1,-1),5),
('LEADING',(0,1),(-1,-1),14),
]))
story.append(session_tbl)
story.append(Spacer(1, 0.5*cm))
story.append(HRFlowable(width='100%', thickness=1, color=TEAL))
story.append(Spacer(1, 0.2*cm))
# ═══════════════════════════════════════════════════════════════════
# SESSION 1 – CLINICAL GRADING
# ═══════════════════════════════════════════════════════════════════
story.append(KeepTogether([
banner('SESSION 1 | Clinical Grading Scales', bg=NAVY),
Spacer(1, 0.15*cm),
two_col(
time_chip('20 MINUTES', TEAL),
Paragraph('Hunt-Hess Scale • WFNS Scale • Why grade at all?', H3)
),
Spacer(1, 0.2*cm),
]))
story.append(H2('1.1 Why Do We Grade SAH?'))
story.append(BODY.__class__) # placeholder - use Paragraph
story.append(Paragraph(
'Grading SAH at presentation serves three critical purposes: '
'(1) <b>communication</b> - a shared language for handovers, referrals and trials; '
'(2) <b>prognostication</b> - predicting hospital mortality and neurological outcome; '
'(3) <b>management decisions</b> - timing of surgery/coiling, ICU admission, and neuroprotection. '
'Higher grade correlates with worse outcome; missing a grade change of even 1 point can indicate an evolving complication.',
BODY
))
story.append(warning_box(
'A decline of even 1 GCS point or new motor deficit should prompt immediate reassessment – '
'it may signal rebleeding, hydrocephalus, or herniation.'
))
story.append(Spacer(1, 0.3*cm))
story.append(H2('1.2 Hunt-Hess Scale (Hunt & Hess, 1968)'))
story.append(Paragraph(
'The oldest widely used clinical grading scale. Originally designed as a guide to surgical risk. '
'Grades 1-3 = "good grade" (suitable for early intervention); grades 4-5 = "poor grade" (requires stabilisation first).',
BODY
))
story.append(Spacer(1, 0.15*cm))
hh_table = grading_table(
[Paragraph('<b>Grade</b>', SMALL), Paragraph('<b>Clinical Findings</b>', SMALL),
Paragraph('<b>Hospital Mortality</b>', SMALL), Paragraph('<b>Key Feature</b>', SMALL)],
[
[Paragraph('<b>0</b>', SMALL), Paragraph('Unruptured aneurysm – asymptomatic', SMALL), Paragraph('-', SMALL), Paragraph('Incidental finding', SMALL)],
[Paragraph('<b>1</b>', SMALL), Paragraph('Asymptomatic or mild headache; slight nuchal rigidity', SMALL), Paragraph('~3%', SMALL), Paragraph('No deficit', SMALL)],
[Paragraph('<b>2</b>', SMALL), Paragraph('Moderate-severe headache; nuchal rigidity; possible CN palsy (III/IV)', SMALL), Paragraph('~3%', SMALL), Paragraph('CN palsy only', SMALL)],
[Paragraph('<b>3</b>', SMALL), Paragraph('Drowsy/confused; mild focal deficit (e.g. pronator drift)', SMALL), Paragraph('~9%', SMALL), Paragraph('Altered consciousness', SMALL)],
[Paragraph('<b>4</b>', SMALL), Paragraph('Stupor; moderate-severe hemiparesis; early decerebrate posturing', SMALL), Paragraph('~24%', SMALL), Paragraph('Stupor + motor deficit', SMALL)],
[Paragraph('<b>5</b>', SMALL), Paragraph('Deep coma; decerebrate rigidity; moribund', SMALL), Paragraph('~70%', SMALL), Paragraph('Coma', SMALL)],
],
[1.2*cm, 7.0*cm, 3.0*cm, 4.2*cm],
header_bg=NAVY
)
story.append(hh_table)
story.append(Paragraph('Source: Goldman-Cecil Medicine; Columbia University Medical Center data (n=580)', CAPTION))
story.append(Spacer(1, 0.25*cm))
story.append(info_box(
'<b>Limitations of Hunt-Hess:</b> Subjective descriptors ("vegetative disturbance", "moribund appearance"). '
'Hunt and Hess themselves wrote: <i>"It is recognized that such classifications are arbitrary and that '
'the margins between categories may be ill defined."</i> Inter-rater variability is a known issue.'
))
story.append(Spacer(1, 0.3*cm))
story.append(H2('1.3 WFNS Scale (World Federation of Neurological Surgeons, 1988)'))
story.append(Paragraph(
'A more objective scale based on GCS score + presence of motor deficit. '
'Introduced to improve inter-rater reliability and international standardisation.',
BODY
))
story.append(Spacer(1, 0.15*cm))
wfns_table = grading_table(
[Paragraph('<b>Grade</b>', SMALL), Paragraph('<b>GCS Score</b>', SMALL),
Paragraph('<b>Motor Deficit</b>', SMALL), Paragraph('<b>Prognosis</b>', SMALL)],
[
[Paragraph('<b>1</b>', SMALL), Paragraph('15', SMALL), Paragraph('Absent', SMALL), Paragraph('Good', SMALL)],
[Paragraph('<b>2</b>', SMALL), Paragraph('13-14', SMALL), Paragraph('Absent', SMALL), Paragraph('Good-Fair', SMALL)],
[Paragraph('<b>3</b>', SMALL), Paragraph('13-14', SMALL), Paragraph('Present', SMALL), Paragraph('Fair', SMALL)],
[Paragraph('<b>4</b>', SMALL), Paragraph('7-12', SMALL), Paragraph('Present or absent', SMALL), Paragraph('Poor', SMALL)],
[Paragraph('<b>5</b>', SMALL), Paragraph('3-6', SMALL), Paragraph('Present or absent', SMALL), Paragraph('Very poor', SMALL)],
],
[2.0*cm, 3.5*cm, 4.5*cm, 7.4*cm],
header_bg=TEAL
)
story.append(wfns_table)
story.append(Spacer(1, 0.2*cm))
story.append(H2('1.4 Hunt-Hess vs WFNS – Side-by-Side'))
compare_data = [
[Paragraph('<b>Feature</b>', SMALL), Paragraph('<b>Hunt-Hess</b>', SMALL), Paragraph('<b>WFNS</b>', SMALL)],
[Paragraph('Year', SMALL), Paragraph('1968', SMALL), Paragraph('1988', SMALL)],
[Paragraph('Basis', SMALL), Paragraph('Clinical descriptors', SMALL), Paragraph('GCS + motor deficit', SMALL)],
[Paragraph('Objectivity', SMALL), Paragraph('Lower', SMALL), Paragraph('Higher', SMALL)],
[Paragraph('Sedated patients', SMALL), Paragraph('Unreliable', SMALL), Paragraph('Unreliable', SMALL)],
[Paragraph('Primary use', SMALL), Paragraph('Surgical timing, prognosis', SMALL), Paragraph('Universal grading, research', SMALL)],
[Paragraph('Good/Poor grade cut-off', SMALL), Paragraph('Grade ≤3 = good', SMALL), Paragraph('Grade ≤2 = good', SMALL)],
]
compare_tbl = Table(compare_data, colWidths=[4.0*cm, 6.5*cm, 6.5*cm])
compare_tbl.setStyle(TableStyle([
('BACKGROUND',(0,0),(-1,0), NAVY),
('TEXTCOLOR',(0,0),(-1,0), WHITE),
('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),
('FONTSIZE',(0,0),(-1,-1),8.5),
('GRID',(0,0),(-1,-1),0.4, colors.HexColor('#cccccc')),
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
('TOPPADDING',(0,0),(-1,-1),4),
('BOTTOMPADDING',(0,0),(-1,-1),4),
('LEFTPADDING',(0,0),(-1,-1),5),
('BACKGROUND',(0,2),(-1,2), GREY),
('BACKGROUND',(0,4),(-1,4), GREY),
('BACKGROUND',(0,6),(-1,6), GREY),
]))
story.append(compare_tbl)
story.append(Spacer(1, 0.3*cm))
story.append(H2('1.5 Session 1 Self-Check'))
story.append(bullet_list([
'A 54-year-old presents with thunderclap headache, GCS 15, neck stiffness, no focal deficit – what Hunt-Hess and WFNS grade? <i>[Ans: HH Grade 1-2; WFNS Grade 1]</i>',
'A patient is stuporous with right hemiplegia – what Hunt-Hess grade? <i>[Ans: HH Grade 4]</i>',
'Why might WFNS be preferred in a multi-centre trial over Hunt-Hess? <i>[Ans: objective, reproducible GCS-based scoring]</i>',
'At what Hunt-Hess grade would you consider delaying aneurysm surgery? <i>[Ans: Grade 4-5 – stabilise first]</i>',
]))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════
# SESSION 2 – RADIOLOGICAL GRADING
# ═══════════════════════════════════════════════════════════════════
story.append(KeepTogether([
banner('SESSION 2 | Radiological Grading (CT-Based)', bg=NAVY),
Spacer(1, 0.15*cm),
two_col(
time_chip('20 MINUTES', TEAL),
Paragraph('Fisher Scale • Modified Fisher Scale • CT Anatomy', H3)
),
Spacer(1, 0.2*cm),
]))
story.append(H2('2.1 What Does CT Tell Us About SAH?'))
story.append(Paragraph(
'Non-contrast CT (NCCT) is the first-line investigation for SAH. Beyond <i>diagnosing</i> the bleed, '
'CT findings predict the risk of <b>vasospasm</b> and <b>delayed cerebral ischaemia (DCI)</b> – '
'the leading cause of morbidity and mortality after the initial bleed. '
'The two key CT grading systems are the Fisher Scale (1980) and the Modified Fisher Scale (2006).',
BODY
))
story.append(info_box(
'<b>CT Anatomy Reminder – Know These Structures:</b> '
'Basal cisterns (suprasellar, ambient, quadrigeminal, sylvian fissure), '
'interhemispheric fissure, cerebral ventricles (I-IV), subarachnoid space vs parenchyma. '
'Blood in the <i>subarachnoid space</i> appears as hyperdense (bright white) filling of the cisterns/fissures.'
))
story.append(Spacer(1, 0.3*cm))
story.append(H2('2.2 Fisher Scale (Fisher et al., 1980)'))
story.append(Paragraph(
'Designed to predict the <b>risk of symptomatic vasospasm</b> based on the amount and pattern of '
'subarachnoid blood on CT. Groups 3 and 4 carry the highest vasospasm risk.',
BODY
))
story.append(Spacer(1, 0.15*cm))
fisher_table = grading_table(
[Paragraph('<b>Group</b>', SMALL), Paragraph('<b>CT Finding</b>', SMALL), Paragraph('<b>Vasospasm Risk</b>', SMALL)],
[
[Paragraph('<b>1</b>', SMALL), Paragraph('No blood detected on CT', SMALL), Paragraph('Low', SMALL)],
[Paragraph('<b>2</b>', SMALL), Paragraph('Diffuse or thin layer of subarachnoid blood; all vertical layers <1 mm thick\n(interhemispheric fissure, insular cistern, ambient cistern)', SMALL), Paragraph('Low-Moderate', SMALL)],
[Paragraph('<b>3</b>', SMALL), Paragraph('Localised clots OR vertical layers ≥1 mm thickness', SMALL), Paragraph('<b>HIGH</b>', SMALL)],
[Paragraph('<b>4</b>', SMALL), Paragraph('Diffuse or no subarachnoid blood BUT intraparenchymal or intraventricular clots present', SMALL), Paragraph('<b>HIGH</b>', SMALL)],
],
[1.8*cm, 10.6*cm, 5.0*cm],
header_bg=NAVY
)
story.append(fisher_table)
story.append(Spacer(1, 0.2*cm))
story.append(warning_box(
'Fisher Group 4 can paradoxically predict less vasospasm than Group 3 in some series – '
'this is the key limitation the Modified Fisher Scale was designed to correct.'
))
story.append(Spacer(1, 0.3*cm))
story.append(H2('2.3 Modified Fisher Scale (Frontera et al., 2006)'))
story.append(Paragraph(
'Revised to address the Fisher scale\'s failure to account for intraventricular haemorrhage (IVH) '
'as an independent risk factor for vasospasm. IVH is now a separate variable on a 0-4 scale.',
BODY
))
story.append(Spacer(1, 0.15*cm))
mf_table = grading_table(
[Paragraph('<b>Grade</b>', SMALL), Paragraph('<b>Subarachnoid Blood</b>', SMALL),
Paragraph('<b>IVH</b>', SMALL), Paragraph('<b>Symptomatic Vasospasm Risk</b>', SMALL)],
[
[Paragraph('<b>0</b>', SMALL), Paragraph('None', SMALL), Paragraph('None', SMALL), Paragraph('0%', SMALL)],
[Paragraph('<b>1</b>', SMALL), Paragraph('Focal or diffuse; thin', SMALL), Paragraph('None', SMALL), Paragraph('24%', SMALL)],
[Paragraph('<b>2</b>', SMALL), Paragraph('Focal thin or diffuse', SMALL), Paragraph('Present', SMALL), Paragraph('33%', SMALL)],
[Paragraph('<b>3</b>', SMALL), Paragraph('Thick', SMALL), Paragraph('None', SMALL), Paragraph('33%', SMALL)],
[Paragraph('<b>4</b>', SMALL), Paragraph('Thick', SMALL), Paragraph('Present', SMALL), Paragraph('40% (highest)', SMALL)],
],
[1.8*cm, 5.8*cm, 2.8*cm, 7.0*cm],
header_bg=TEAL
)
story.append(mf_table)
story.append(Paragraph('Source: Frontera JA et al. Neurosurgery. 2006;59(1):21-27', CAPTION))
story.append(Spacer(1, 0.2*cm))
story.append(H2('2.4 Fisher vs Modified Fisher – Key Differences'))
story.append(Paragraph(
'The main change: <b>IVH is now an independent risk variable</b>. A patient with IVH but no subarachnoid blood '
'scores Modified Fisher Grade 2 (33% vasospasm risk) – on the original scale they would score Group 1 (low risk), '
'drastically underestimating their risk. This is clinically significant.',
BODY
))
story.append(Spacer(1, 0.15*cm))
story.append(info_box(
'<b>Limitations of Modified Fisher:</b> '
'(1) Grades 2 and 3 have identical vasospasm incidence (33%) – insufficient separation. '
'(2) Inter-rater reliability for "thick vs thin" blood is only moderate (72-78%). '
'(3) The Hijdra Sum Score is more quantitative and may be superior for research purposes '
'(Miller\'s Anesthesia, 10th Ed.).'
))
story.append(Spacer(1, 0.2*cm))
story.append(H2('2.5 Session 2 Self-Check'))
story.append(bullet_list([
'CT shows thick blood in the basal cisterns, no IVH – what Modified Fisher grade and vasospasm risk? <i>[Ans: Grade 3, 33%]</i>',
'CT shows thin SAH + blood in both lateral ventricles – what Modified Fisher grade? <i>[Ans: Grade 2, 33%]</i>',
'Which Fisher group can have HIGH vasospasm risk despite minimal subarachnoid blood? <i>[Ans: Group 4 – due to IVH/parenchymal clot]</i>',
'A patient has Fisher Group 1 – do they need vasospasm monitoring? <i>[Ans: Low risk but clinical vigilance still required]</i>',
]))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════
# SESSION 3 – INTEGRATION + MANAGEMENT + QUIZ
# ═══════════════════════════════════════════════════════════════════
story.append(KeepTogether([
banner('SESSION 3 | Grade Correlation, Management & Pitfalls', bg=NAVY),
Spacer(1, 0.15*cm),
two_col(
time_chip('20 MINUTES', TEAL),
Paragraph('Clinical-Radiological Correlation • Management Decisions • Quick Quiz', H3)
),
Spacer(1, 0.2*cm),
]))
story.append(H2('3.1 Putting It Together – Clinical + CT Grade Correlation'))
story.append(Paragraph(
'Clinical grade and CT grade are <b>complementary</b> – use both together to guide decisions. '
'A good clinical grade with a bad CT grade (or vice versa) should raise suspicion and heighten monitoring.',
BODY
))
story.append(Spacer(1, 0.15*cm))
integration_table = grading_table(
[Paragraph('<b>Clinical Grade</b>', SMALL), Paragraph('<b>CT (Modified Fisher)</b>', SMALL),
Paragraph('<b>Interpretation & Action</b>', SMALL)],
[
[Paragraph('HH 1-2 / WFNS 1-2', SMALL), Paragraph('mFisher 0-1', SMALL), Paragraph('Good grade, low vasospasm risk – early aneurysm treatment, standard monitoring', SMALL)],
[Paragraph('HH 1-2 / WFNS 1-2', SMALL), Paragraph('mFisher 3-4', SMALL), Paragraph('Good grade but HIGH vasospasm risk – early treatment, aggressive vasospasm surveillance', SMALL)],
[Paragraph('HH 3 / WFNS 3', SMALL), Paragraph('mFisher 2-3', SMALL), Paragraph('Moderate grade – ICU admission, close neuro obs, consider early intervention', SMALL)],
[Paragraph('HH 4-5 / WFNS 4-5', SMALL), Paragraph('mFisher 3-4', SMALL), Paragraph('Poor grade + high bleed burden – intubate, ICP monitoring, stabilise before intervention', SMALL)],
],
[4.2*cm, 3.8*cm, 9.4*cm],
header_bg=NAVY
)
story.append(integration_table)
story.append(Spacer(1, 0.3*cm))
story.append(H2('3.2 Management Pearls by Grade'))
story.append(Spacer(1, 0.1*cm))
mgmt_data = [
[Paragraph('<b>Domain</b>', SMALL), Paragraph('<b>Good Grade (HH 1-3)</b>', SMALL), Paragraph('<b>Poor Grade (HH 4-5)</b>', SMALL)],
[Paragraph('Airway', SMALL), Paragraph('Self-maintaining; monitor closely', SMALL), Paragraph('Intubate and ventilate', SMALL)],
[Paragraph('Aneurysm Tx', SMALL), Paragraph('Early coiling/clipping (within 24-72h)', SMALL), Paragraph('Stabilise first; delayed if needed', SMALL)],
[Paragraph('Blood Pressure', SMALL), Paragraph('SBP 120-160 mmHg until secured', SMALL), Paragraph('Tight control; titrate to CPP 60-80', SMALL)],
[Paragraph('Nimodipine', SMALL), Paragraph('60mg q4h × 21 days (all grades)', SMALL), Paragraph('60mg q4h × 21 days (all grades)', SMALL)],
[Paragraph('Vasospasm watch', SMALL), Paragraph('TCD daily; clinical exam q4-6h', SMALL), Paragraph('TCD + continuous EEG; ICP monitoring', SMALL)],
[Paragraph('Hydrocephalus', SMALL), Paragraph('Monitor; LP or EVD if symptomatic', SMALL), Paragraph('EVD early if acute obstructive hydrocephalus', SMALL)],
[Paragraph('Seizure prophylaxis', SMALL), Paragraph('Levetiracetam if seizure occurs; stop after aneurysm secured', SMALL), Paragraph('Continued AED + EEG monitoring', SMALL)],
]
mgmt_tbl = Table(mgmt_data, colWidths=[3.5*cm, 6.6*cm, 6.6*cm])
mgmt_tbl.setStyle(TableStyle([
('BACKGROUND',(0,0),(-1,0), TEAL),
('TEXTCOLOR',(0,0),(-1,0), WHITE),
('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),
('FONTSIZE',(0,0),(-1,-1),8.5),
('GRID',(0,0),(-1,-1),0.4, colors.HexColor('#cccccc')),
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
('TOPPADDING',(0,0),(-1,-1),4),
('BOTTOMPADDING',(0,0),(-1,-1),4),
('LEFTPADDING',(0,0),(-1,-1),5),
('BACKGROUND',(0,2),(-1,2), GREY),
('BACKGROUND',(0,4),(-1,4), GREY),
('BACKGROUND',(0,6),(-1,6), GREY),
]))
story.append(mgmt_tbl)
story.append(Spacer(1, 0.3*cm))
story.append(H2('3.3 Common Pitfalls – Do Not Miss'))
story.append(bullet_list([
'<b>Pitfall 1:</b> Grading on admission only. SAH grade can change rapidly – reassess every 4-6 hours.',
'<b>Pitfall 2:</b> Using Hunt-Hess on a sedated/intubated patient. Both HH and WFNS are invalid post-sedation – document the pre-sedation grade.',
'<b>Pitfall 3:</b> Fisher Group 1 = "safe". CT-negative SAH exists; always correlate with LP if clinical suspicion is high.',
'<b>Pitfall 4:</b> Ignoring IVH on CT. A normal-looking cistern with bilateral IVH is Modified Fisher Grade 2 (33% vasospasm risk) – not low risk.',
'<b>Pitfall 5:</b> Diagnosing grade without reading the CT personally. Know the cistern anatomy – do not rely solely on the radiology report.',
'<b>Pitfall 6:</b> Equating poor grade with futility. Hunt-Hess Grade 5 carries 70% mortality but 30% can survive – do not automatically withdraw care without senior discussion.',
]))
story.append(Spacer(1, 0.25*cm))
story.append(H2('3.4 Quick Quiz – 5 Questions'))
story.append(info_box(
'<b>Q1.</b> A patient has thunderclap headache, GCS 14, right CN III palsy. No motor deficit. '
'CT shows thin diffuse SAH, no IVH. State: Hunt-Hess grade, WFNS grade, Modified Fisher grade. '
'What is the vasospasm risk? <br/><br/>'
'<b>Q2.</b> CT shows thick blood in the basal cisterns + blood in both lateral ventricles. '
'What is the Modified Fisher grade and the expected vasospasm incidence?<br/><br/>'
'<b>Q3.</b> Why is WFNS Grade 3 higher risk than Grade 2 despite the same GCS? <br/><br/>'
'<b>Q4.</b> At what time post-ictus should nimodipine be started, and for how long? <br/><br/>'
'<b>Q5.</b> A patient admitted as Hunt-Hess Grade 2 suddenly drops to Grade 3. What are the three most likely causes?'
))
story.append(Spacer(1, 0.15*cm))
story.append(info_box(
'<b>Answers:</b><br/>'
'Q1: HH Grade 2 (severe headache + CN palsy only); WFNS Grade 2 (GCS 13-14, no motor); mFisher Grade 1 (thin diffuse, no IVH) → 24% vasospasm risk.<br/>'
'Q2: Modified Fisher Grade 4 (thick SAH + IVH present) → 40% vasospasm risk.<br/>'
'Q3: WFNS Grade 3 adds motor deficit to GCS 13-14, indicating greater neurological injury.<br/>'
'Q4: Within 48 hours of haemorrhage; continued for 21 days (60 mg every 4 hours).<br/>'
'Q5: (1) Rebleeding; (2) Acute hydrocephalus; (3) Cerebral vasospasm/DCI.',
bg=LTAMBER, border=AMBER
))
story.append(Spacer(1, 0.3*cm))
story.append(H2('3.5 Full Grading Summary – Quick Reference'))
story.append(Spacer(1, 0.1*cm))
summary_data = [
[Paragraph('<b>Scale</b>', SMALL), Paragraph('<b>Type</b>', SMALL), Paragraph('<b>Year</b>', SMALL),
Paragraph('<b>What It Grades</b>', SMALL), Paragraph('<b>Primary Use</b>', SMALL)],
[Paragraph('Hunt-Hess', SMALL), Paragraph('Clinical', SMALL), Paragraph('1968', SMALL),
Paragraph('Neurological status (0-5)', SMALL), Paragraph('Surgical timing, prognosis', SMALL)],
[Paragraph('WFNS', SMALL), Paragraph('Clinical', SMALL), Paragraph('1988', SMALL),
Paragraph('GCS + motor deficit (1-5)', SMALL), Paragraph('Universal grading, research', SMALL)],
[Paragraph('Fisher', SMALL), Paragraph('Radiological', SMALL), Paragraph('1980', SMALL),
Paragraph('CT blood volume/distribution (1-4)', SMALL), Paragraph('Predicts vasospasm', SMALL)],
[Paragraph('Modified Fisher', SMALL), Paragraph('Radiological', SMALL), Paragraph('2006', SMALL),
Paragraph('CT blood + IVH (0-4)', SMALL), Paragraph('More accurate vasospasm prediction', SMALL)],
[Paragraph('Hijdra Sum Score', SMALL), Paragraph('Radiological', SMALL), Paragraph('1985', SMALL),
Paragraph('Semi-quantitative cisternal blood (0-30)', SMALL), Paragraph('Research; superior vasospasm prediction', SMALL)],
]
summary_tbl = Table(summary_data, colWidths=[3.2*cm, 2.2*cm, 1.5*cm, 5.3*cm, 5.2*cm])
summary_tbl.setStyle(TableStyle([
('BACKGROUND',(0,0),(-1,0), NAVY),
('TEXTCOLOR',(0,0),(-1,0), WHITE),
('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),
('FONTSIZE',(0,0),(-1,-1),8.5),
('GRID',(0,0),(-1,-1),0.4, colors.HexColor('#cccccc')),
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
('TOPPADDING',(0,0),(-1,-1),4),
('BOTTOMPADDING',(0,0),(-1,-1),4),
('LEFTPADDING',(0,0),(-1,-1),5),
('BACKGROUND',(0,2),(-1,2), GREY),
('BACKGROUND',(0,4),(-1,4), GREY),
]))
story.append(summary_tbl)
story.append(Spacer(1, 0.3*cm))
# Footer strip
footer_data = [[
Paragraph('References: Bradley & Daroff\'s Neurology | Tintinalli\'s Emergency Medicine | '
'Schwartz\'s Surgery 11e | Goldman-Cecil Medicine | Tietz Textbook Lab Medicine | '
'Frontera JA et al. Neurosurgery 2006', CAPTION),
]]
footer_tbl = Table(footer_data, colWidths=[17.4*cm])
footer_tbl.setStyle(TableStyle([
('BACKGROUND',(0,0),(-1,-1), NAVY),
('TOPPADDING',(0,0),(-1,-1),5),
('BOTTOMPADDING',(0,0),(-1,-1),5),
('LEFTPADDING',(0,0),(-1,-1),8),
('TEXTCOLOR',(0,0),(-1,-1), LTBLUE),
]))
story.append(footer_tbl)
# ── Build ──────────────────────────────────────────────────────────────────
# Remove stray class references
story = [s for s in story if not isinstance(s, type)]
doc.build(story)
print("PDF built successfully.")
cd /home/daytona/workspace/sah-ecmo && python build_sah_pdf.py 2>&1
cd /home/daytona/workspace/sah-ecmo && python -c " from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib import colors from reportlab.platypus import Paragraph styles = getSampleStyleSheet() NAVY = colors.HexColor('#1a3a5c') h2style = ParagraphStyle('MyH2', parent=styles['Normal'], fontSize=12, textColor=NAVY, fontName='Helvetica-Bold') p = Paragraph('Test', h2style) print('OK') "
~/sah-ecmo/build_sah_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table,
TableStyle, HRFlowable, PageBreak, KeepTogether,
ListFlowable, ListItem)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
# ── Colours ──────────────────────────────────────────────────────────────
NAVY = colors.HexColor('#1a3a5c')
TEAL = colors.HexColor('#2a7f8f')
AMBER = colors.HexColor('#e07b23')
LTBLUE = colors.HexColor('#ddeef6')
LTAMBER = colors.HexColor('#fef3e2')
WHITE = colors.white
GREY = colors.HexColor('#f4f6f8')
DKGREY = colors.HexColor('#444444')
MIDGREY = colors.HexColor('#cccccc')
# ── Styles ────────────────────────────────────────────────────────────────
_base = getSampleStyleSheet()
def sty(name, **kw):
parent = kw.pop('parent', 'Normal')
return ParagraphStyle(name, parent=_base[parent], **kw)
st_title = sty('st_title', fontSize=22, textColor=WHITE, alignment=TA_CENTER, fontName='Helvetica-Bold', spaceAfter=4)
st_subtitle = sty('st_subtitle',fontSize=11, textColor=LTBLUE, alignment=TA_CENTER, fontName='Helvetica', spaceAfter=4)
st_h1 = sty('st_h1', fontSize=14, textColor=WHITE, fontName='Helvetica-Bold', spaceAfter=2, spaceBefore=2)
st_h2 = sty('st_h2', fontSize=12, textColor=NAVY, fontName='Helvetica-Bold', spaceAfter=3, spaceBefore=6)
st_h3 = sty('st_h3', fontSize=10, textColor=TEAL, fontName='Helvetica-Bold', spaceAfter=2, spaceBefore=3)
st_body = sty('st_body', fontSize=9, textColor=DKGREY, fontName='Helvetica', leading=13, spaceAfter=3, alignment=TA_JUSTIFY)
st_small = sty('st_small', fontSize=8.5,textColor=DKGREY, fontName='Helvetica', leading=11)
st_badge = sty('st_badge', fontSize=8, textColor=WHITE, fontName='Helvetica-Bold', alignment=TA_CENTER)
st_caption = sty('st_caption',fontSize=7.5,textColor=colors.HexColor('#888888'), fontName='Helvetica-Oblique', alignment=TA_CENTER, spaceBefore=1)
st_bullet = sty('st_bullet', fontSize=9, textColor=DKGREY, fontName='Helvetica', leading=13, leftIndent=8, spaceAfter=1)
# ── Helper functions ───────────────────────────────────────────────────────
def P(txt, style=None):
return Paragraph(txt, style or st_body)
def banner(text, bg=NAVY, style=None, height=0.65*cm):
s = style or st_h1
tbl = Table([[P(text, s)]], colWidths=[17.4*cm], rowHeights=[height])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,-1), bg),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('LEFTPADDING', (0,0),(-1,-1), 10),
('RIGHTPADDING', (0,0),(-1,-1), 10),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING',(0,0),(-1,-1), 4),
]))
return tbl
def info_box(text, bg=LTBLUE, border=TEAL):
tbl = Table([[P(text, st_body)]], colWidths=[17.4*cm])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,-1), bg),
('BOX', (0,0),(-1,-1), 1.2, border),
('LEFTPADDING', (0,0),(-1,-1), 8),
('RIGHTPADDING', (0,0),(-1,-1), 8),
('TOPPADDING', (0,0),(-1,-1), 6),
('BOTTOMPADDING',(0,0),(-1,-1), 6),
]))
return tbl
def warn_box(text):
return info_box(f'<b>Key Point: </b>{text}', bg=LTAMBER, border=AMBER)
def time_chip(label, bg=TEAL):
tbl = Table([[P(label, st_badge)]], colWidths=[3.4*cm], rowHeights=[0.5*cm])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,-1), bg),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('ALIGN', (0,0),(-1,-1), 'CENTER'),
('LEFTPADDING', (0,0),(-1,-1), 4),
('RIGHTPADDING', (0,0),(-1,-1), 4),
]))
return tbl
def two_col(left, right, lw=4.0*cm, rw=13.0*cm):
tbl = Table([[left, right]], colWidths=[lw, rw])
tbl.setStyle(TableStyle([
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('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 tbl
def data_table(headers, rows, col_widths, hdr_bg=NAVY):
data = [headers] + rows
tbl = Table(data, colWidths=col_widths)
n = len(rows)
ts = [
('BACKGROUND', (0,0),(-1,0), hdr_bg),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8.5),
('LEADING', (0,0),(-1,-1), 12),
('ALIGN', (0,0),(-1,-1), 'LEFT'),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('GRID', (0,0),(-1,-1), 0.4, MIDGREY),
('LEFTPADDING', (0,0),(-1,-1), 5),
('RIGHTPADDING', (0,0),(-1,-1), 5),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING',(0,0),(-1,-1), 4),
]
for i in range(1, n+1):
if i % 2 == 0:
ts.append(('BACKGROUND', (0,i),(-1,i), GREY))
tbl.setStyle(TableStyle(ts))
return tbl
def bullets(items):
return ListFlowable(
[ListItem(P(item, st_bullet), leftIndent=10, bulletColor=TEAL, value='bullet') for item in items],
bulletType='bullet', leftIndent=6, bulletFontSize=8
)
# ── Document ──────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
'/home/daytona/workspace/sah-ecmo/SAH_Grading_Study_Guide.pdf',
pagesize=A4,
leftMargin=1.8*cm, rightMargin=1.8*cm,
topMargin=1.8*cm, bottomMargin=1.8*cm,
title='SAH Grading Systems - Study Guide',
author='Orris Medical Education'
)
story = []
# ═══════════════════════════ COVER ════════════════════════════════════════
cover = Table([
[P('SAH GRADING SYSTEMS', st_title)],
[P('A Structured 1-Hour Study Guide | Junior Doctors & Residents', st_subtitle)],
[Spacer(1, 0.2*cm)],
[P('3 Sessions × 20 Minutes | Clinical + Radiological Grading | Prognostication & Management', st_subtitle)],
], colWidths=[17.4*cm])
cover.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,-1), NAVY),
('TOPPADDING', (0,0),(-1,-1), 14),
('BOTTOMPADDING',(0,0),(-1,-1), 12),
('LEFTPADDING', (0,0),(-1,-1), 14),
('RIGHTPADDING', (0,0),(-1,-1), 14),
]))
story.append(cover)
story.append(Spacer(1, 0.4*cm))
story.append(info_box(
'<b>Learning Objectives:</b> By the end of this guide you will be able to: '
'(1) Apply Hunt-Hess and WFNS clinical grading and interpret their prognostic significance; '
'(2) Classify SAH on CT using the Fisher and Modified Fisher scales; '
'(3) Correlate clinical and radiological grades to guide management; '
'(4) Recognise and avoid common grading pitfalls at the bedside.'
))
story.append(Spacer(1, 0.3*cm))
# Session overview
sess_data = [
['SESSION 1 – 20 min', 'SESSION 2 – 20 min', 'SESSION 3 – 20 min'],
['Why Grade SAH?\nHunt-Hess Scale\nWFNS Scale', 'Fisher CT Scale\nModified Fisher Scale\nCT Anatomy Review', 'Grade Correlation\nManagement Pearls\nQuick Quiz + Pitfalls'],
]
sess_tbl = Table(sess_data, colWidths=[5.6*cm, 5.6*cm, 5.6*cm])
sess_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), TEAL),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('BACKGROUND', (0,1),(-1,1), LTBLUE),
('FONTSIZE', (0,0),(-1,-1), 9),
('ALIGN', (0,0),(-1,-1), 'CENTER'),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('GRID', (0,0),(-1,-1), 0.5, WHITE),
('TOPPADDING', (0,0),(-1,-1), 6),
('BOTTOMPADDING',(0,0),(-1,-1), 6),
('LEADING', (0,1),(-1,1), 14),
]))
story.append(sess_tbl)
story.append(Spacer(1, 0.4*cm))
story.append(HRFlowable(width='100%', thickness=1.5, color=TEAL))
story.append(Spacer(1, 0.2*cm))
# ═══════════════════════ SESSION 1 ════════════════════════════════════════
story.append(KeepTogether([
banner('SESSION 1 | Clinical Grading Scales', NAVY),
Spacer(1, 0.15*cm),
two_col(time_chip('20 MINUTES'), P('Hunt-Hess Scale • WFNS Scale • Why grade at all?', st_h3)),
Spacer(1, 0.2*cm),
]))
story.append(P('1.1 Why Do We Grade SAH?', st_h2))
story.append(P(
'Grading SAH at presentation serves three purposes: '
'<b>(1) Communication</b> - a shared language across teams and research; '
'<b>(2) Prognostication</b> - predicting hospital mortality and neurological outcome; '
'<b>(3) Management decisions</b> - timing of surgery/coiling, ICU admission, and neuroprotection. '
'A grade change of even 1 point can signal a new complication and should trigger immediate reassessment.',
st_body
))
story.append(warn_box(
'A decline of 1 GCS point or new motor deficit demands immediate reassessment – '
'it may indicate rebleeding, acute hydrocephalus, or herniation.'
))
story.append(Spacer(1, 0.3*cm))
story.append(P('1.2 Hunt-Hess Scale (Hunt & Hess, 1968)', st_h2))
story.append(P(
'The oldest and most widely used clinical grading scale, originally designed to guide surgical risk stratification. '
'Grades 1-3 = "good grade" (suitable for early intervention); grades 4-5 = "poor grade" (requires stabilisation first). '
'Mortality data from 580 patients treated at Columbia University Medical Center.',
st_body
))
story.append(Spacer(1, 0.15*cm))
hh_tbl = data_table(
[P('<b>Grade</b>', st_small), P('<b>Clinical Findings</b>', st_small),
P('<b>Hospital Mortality</b>', st_small), P('<b>Key Feature</b>', st_small)],
[
[P('<b>0</b>', st_small), P('Unruptured aneurysm; asymptomatic', st_small), P('–', st_small), P('Incidental finding', st_small)],
[P('<b>1</b>', st_small), P('Asymptomatic or mild headache; slight nuchal rigidity', st_small), P('~3%', st_small), P('No deficit', st_small)],
[P('<b>2</b>', st_small), P('Moderate-severe headache; nuchal rigidity; possible CN palsy (III/IV)', st_small), P('~3%', st_small), P('CN palsy only', st_small)],
[P('<b>3</b>', st_small), P('Drowsy/confused; mild focal deficit (e.g. pronator drift)', st_small), P('~9%', st_small), P('Altered consciousness', st_small)],
[P('<b>4</b>', st_small), P('Stupor; moderate-severe hemiparesis; early decerebrate posturing', st_small), P('~24%', st_small), P('Stupor + motor deficit', st_small)],
[P('<b>5</b>', st_small), P('Deep coma; decerebrate rigidity; moribund', st_small), P('~70%', st_small), P('Coma', st_small)],
],
[1.2*cm, 7.0*cm, 3.0*cm, 4.2*cm]
)
story.append(hh_tbl)
story.append(P('Source: Goldman-Cecil Medicine; Columbia University Medical Center (n=580)', st_caption))
story.append(Spacer(1, 0.2*cm))
story.append(info_box(
'<b>Limitations of Hunt-Hess:</b> Subjective language – "vegetative disturbance" and "moribund appearance" are imprecise. '
'Hunt and Hess themselves wrote: <i>"It is recognized that such classifications are arbitrary and that '
'the margins between categories may be ill defined."</i> Inter-rater variability is a known limitation.'
))
story.append(Spacer(1, 0.3*cm))
story.append(P('1.3 WFNS Scale (World Federation of Neurological Surgeons, 1988)', st_h2))
story.append(P(
'A more objective scale based on Glasgow Coma Scale (GCS) score and presence of motor deficit. '
'Introduced to improve inter-rater reliability and international standardisation in clinical trials.',
st_body
))
story.append(Spacer(1, 0.15*cm))
wfns_tbl = data_table(
[P('<b>Grade</b>', st_small), P('<b>GCS Score</b>', st_small),
P('<b>Motor Deficit</b>', st_small), P('<b>Prognosis</b>', st_small)],
[
[P('<b>1</b>', st_small), P('15', st_small), P('Absent', st_small), P('Good', st_small)],
[P('<b>2</b>', st_small), P('13–14', st_small), P('Absent', st_small), P('Good-Fair', st_small)],
[P('<b>3</b>', st_small), P('13–14', st_small), P('Present', st_small), P('Fair', st_small)],
[P('<b>4</b>', st_small), P('7–12', st_small), P('Present or absent', st_small), P('Poor', st_small)],
[P('<b>5</b>', st_small), P('3–6', st_small), P('Present or absent', st_small), P('Very poor', st_small)],
],
[2.0*cm, 3.5*cm, 4.5*cm, 7.4*cm],
hdr_bg=TEAL
)
story.append(wfns_tbl)
story.append(Spacer(1, 0.2*cm))
story.append(P('1.4 Hunt-Hess vs WFNS – Side-by-Side', st_h2))
compare = [
[P('<b>Feature</b>', st_small), P('<b>Hunt-Hess</b>', st_small), P('<b>WFNS</b>', st_small)],
[P('Year introduced', st_small), P('1968', st_small), P('1988', st_small)],
[P('Grading basis', st_small), P('Clinical descriptors', st_small), P('GCS + motor deficit', st_small)],
[P('Objectivity', st_small), P('Lower', st_small), P('Higher', st_small)],
[P('Sedated patients', st_small), P('Invalid – document pre-sedation grade', st_small), P('Invalid – document pre-sedation grade', st_small)],
[P('"Good grade" cut-off', st_small), P('Grade 1-3', st_small), P('Grade 1-2', st_small)],
[P('Primary use', st_small), P('Surgical timing, prognostication', st_small), P('Universal grading, research', st_small)],
]
cmp_tbl = Table(compare, colWidths=[4.0*cm, 6.5*cm, 6.5*cm])
cmp_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), NAVY),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8.5),
('GRID', (0,0),(-1,-1), 0.4, MIDGREY),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING',(0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 5),
('BACKGROUND', (0,2),(-1,2), GREY),
('BACKGROUND', (0,4),(-1,4), GREY),
('BACKGROUND', (0,6),(-1,6), GREY),
]))
story.append(cmp_tbl)
story.append(Spacer(1, 0.3*cm))
story.append(P('1.5 Session 1 Self-Check', st_h2))
story.append(bullets([
'Thunderclap headache, GCS 15, neck stiffness, no focal deficit: what Hunt-Hess and WFNS grade? <i>[HH Grade 1-2; WFNS Grade 1]</i>',
'Patient is stuporous with right hemiplegia: what Hunt-Hess grade? <i>[HH Grade 4]</i>',
'Why might WFNS be preferred in a multi-centre trial? <i>[Objective, GCS-based, reproducible]</i>',
'At what Hunt-Hess grade would you consider delaying aneurysm surgery? <i>[Grade 4-5 – stabilise first]</i>',
]))
story.append(PageBreak())
# ═══════════════════════ SESSION 2 ════════════════════════════════════════
story.append(KeepTogether([
banner('SESSION 2 | Radiological Grading – CT-Based Scales', NAVY),
Spacer(1, 0.15*cm),
two_col(time_chip('20 MINUTES'), P('Fisher Scale • Modified Fisher Scale • CT Anatomy', st_h3)),
Spacer(1, 0.2*cm),
]))
story.append(P('2.1 What Does CT Tell Us About SAH?', st_h2))
story.append(P(
'Non-contrast CT (NCCT) is the first-line investigation for SAH. Beyond <i>diagnosing</i> the bleed, '
'CT findings predict the risk of <b>vasospasm</b> and <b>delayed cerebral ischaemia (DCI)</b> – '
'the leading cause of morbidity and mortality after the initial haemorrhage. '
'Subarachnoid blood appears as hyperdense (bright white) signal filling the cisterns and fissures.',
st_body
))
story.append(info_box(
'<b>CT Anatomy Reminder – Know These Structures:</b> '
'Basal cisterns (suprasellar, ambient, quadrigeminal, sylvian fissure), '
'interhemispheric fissure, cerebral ventricles (I-IV), subarachnoid space vs parenchyma. '
'Identify each cistern before applying any grading scale.'
))
story.append(Spacer(1, 0.3*cm))
story.append(P('2.2 Original Fisher Scale (Fisher et al., 1980)', st_h2))
story.append(P(
'Designed to predict the risk of <b>symptomatic vasospasm</b> from the amount and distribution '
'of subarachnoid blood on CT. Groups 3 and 4 carry the highest vasospasm risk.',
st_body
))
story.append(Spacer(1, 0.15*cm))
fisher_tbl = data_table(
[P('<b>Group</b>', st_small), P('<b>CT Finding</b>', st_small), P('<b>Vasospasm Risk</b>', st_small)],
[
[P('<b>1</b>', st_small), P('No blood detected on CT', st_small), P('Very low', st_small)],
[P('<b>2</b>', st_small), P('Diffuse or thin layer of subarachnoid blood; all vertical layers <1 mm thick (interhemispheric, insular, ambient cisterns)', st_small), P('Low-moderate', st_small)],
[P('<b>3</b>', st_small), P('Localised clots OR vertical layers of blood ≥1 mm thickness', st_small), P('<b>HIGH</b>', st_small)],
[P('<b>4</b>', st_small), P('Diffuse or no subarachnoid blood BUT intraparenchymal or intraventricular clots present', st_small), P('<b>HIGH</b>', st_small)],
],
[1.8*cm, 11.0*cm, 4.6*cm]
)
story.append(fisher_tbl)
story.append(P('Source: Bradley and Daroff\'s Neurology in Clinical Practice, Box 67.3', st_caption))
story.append(Spacer(1, 0.15*cm))
story.append(warn_box(
'Fisher Group 4 can paradoxically show LESS vasospasm than Group 3 in some series – '
'because it may have minimal cistern blood (just parenchymal/ventricular clots). '
'This non-monotonic relationship was the key driver for the Modified Fisher Scale.'
))
story.append(Spacer(1, 0.3*cm))
story.append(P('2.3 Modified Fisher Scale (Frontera et al., 2006)', st_h2))
story.append(P(
'Revised to address the original Fisher scale\'s failure to account for intraventricular haemorrhage (IVH) '
'as an independent risk factor for vasospasm. IVH is now a separate binary variable on a 0-4 scale. '
'<b>This scale applies to aneurysmal SAH only.</b>',
st_body
))
story.append(Spacer(1, 0.15*cm))
mf_tbl = data_table(
[P('<b>Grade</b>', st_small), P('<b>Subarachnoid Blood</b>', st_small),
P('<b>IVH</b>', st_small), P('<b>Symptomatic Vasospasm Risk</b>', st_small)],
[
[P('<b>0</b>', st_small), P('None', st_small), P('None', st_small), P('0%', st_small)],
[P('<b>1</b>', st_small), P('Focal or diffuse; thin', st_small), P('Absent', st_small), P('24%', st_small)],
[P('<b>2</b>', st_small), P('Focal thin or diffuse', st_small), P('<b>Present</b>', st_small), P('33%', st_small)],
[P('<b>3</b>', st_small), P('<b>Thick</b>', st_small), P('Absent', st_small), P('33%', st_small)],
[P('<b>4</b>', st_small), P('<b>Thick</b>', st_small), P('<b>Present</b>', st_small), P('<b>40% (highest)</b>', st_small)],
],
[1.8*cm, 5.8*cm, 2.8*cm, 7.0*cm],
hdr_bg=TEAL
)
story.append(mf_tbl)
story.append(P('Source: Frontera JA et al. Neurosurgery. 2006;59(1):21-27', st_caption))
story.append(Spacer(1, 0.2*cm))
story.append(P('2.4 Key Improvements of Modified Fisher Scale', st_h2))
story.append(P(
'Patients with IVH alone (no subarachnoid blood) now score Grade 2 (33% vasospasm risk). '
'On the original Fisher scale they would score Group 1 – dramatically underestimating their risk. '
'IVH as an independent variable makes this scale more clinically actionable.',
st_body
))
story.append(info_box(
'<b>Limitations of Modified Fisher:</b> '
'(1) Grades 2 and 3 have identical vasospasm incidence (33%) – insufficient separation between categories. '
'(2) Inter-rater reliability for distinguishing "thick vs thin" blood is only 72-78%. '
'(3) The Hijdra Sum Score (semi-quantitative, 0-30 across 10 cisterns) is more precise and may be '
'superior for research – though impractical for rapid bedside grading (Miller\'s Anesthesia, 10th Ed.).'
))
story.append(Spacer(1, 0.25*cm))
story.append(P('2.5 Session 2 Self-Check', st_h2))
story.append(bullets([
'CT: thick blood in basal cisterns, no IVH – Modified Fisher grade and vasospasm risk? <i>[Grade 3; 33%]</i>',
'CT: thin SAH + blood in both lateral ventricles – Modified Fisher grade? <i>[Grade 2; 33%]</i>',
'Which Fisher group can show HIGH vasospasm risk despite minimal subarachnoid blood? <i>[Group 4 – IVH/parenchymal clot]</i>',
'Why does a Modified Fisher Grade 2 patient need the same vasospasm vigilance as Grade 3? <i>[Both carry 33% risk]</i>',
]))
story.append(PageBreak())
# ═══════════════════════ SESSION 3 ════════════════════════════════════════
story.append(KeepTogether([
banner('SESSION 3 | Correlation, Management & Pitfalls', NAVY),
Spacer(1, 0.15*cm),
two_col(time_chip('20 MINUTES'), P('Clinical-Radiological Correlation • Management • Quick Quiz', st_h3)),
Spacer(1, 0.2*cm),
]))
story.append(P('3.1 Putting It Together – Clinical + CT Grade Correlation', st_h2))
story.append(P(
'Use both clinical and CT grades together. A good clinical grade with a high CT grade (or vice versa) '
'should heighten your vigilance and monitoring frequency.',
st_body
))
story.append(Spacer(1, 0.15*cm))
integration = [
[P('<b>Clinical Grade</b>', st_small), P('<b>Modified Fisher</b>', st_small), P('<b>Action</b>', st_small)],
[P('HH 1-2 / WFNS 1-2', st_small), P('0-1', st_small), P('Good grade + low vasospasm risk: early aneurysm treatment, standard monitoring', st_small)],
[P('HH 1-2 / WFNS 1-2', st_small), P('3-4', st_small), P('Good grade but HIGH vasospasm risk: early treatment + aggressive vasospasm surveillance', st_small)],
[P('HH 3 / WFNS 3', st_small), P('2-3', st_small), P('Moderate grade: ICU admission, hourly neuro obs, consider early intervention', st_small)],
[P('HH 4-5 / WFNS 4-5', st_small), P('3-4', st_small), P('Poor grade + heavy bleed: intubate, ICP monitoring, stabilise before intervention', st_small)],
]
int_tbl = Table(integration, colWidths=[4.2*cm, 2.8*cm, 10.4*cm])
int_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), NAVY),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8.5),
('GRID', (0,0),(-1,-1), 0.4, MIDGREY),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING',(0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 5),
('BACKGROUND', (0,2),(-1,2), GREY),
('BACKGROUND', (0,4),(-1,4), GREY),
]))
story.append(int_tbl)
story.append(Spacer(1, 0.3*cm))
story.append(P('3.2 Management Pearls by Grade', st_h2))
mgmt = [
[P('<b>Domain</b>', st_small), P('<b>Good Grade (HH 1-3)</b>', st_small), P('<b>Poor Grade (HH 4-5)</b>', st_small)],
[P('Airway', st_small), P('Self-maintaining; monitor', st_small), P('Intubate and ventilate', st_small)],
[P('Aneurysm Tx', st_small), P('Early coiling/clipping (24-72 h)', st_small), P('Stabilise first; delayed intervention', st_small)],
[P('Blood Pressure', st_small), P('SBP 120-160 mmHg until secured', st_small), P('Tight BP control; CPP 60-80 mmHg', st_small)],
[P('Nimodipine', st_small), P('60 mg q4h × 21 days (all grades)', st_small), P('60 mg q4h × 21 days (all grades)', st_small)],
[P('Vasospasm', st_small), P('TCD daily; clinical exam q4-6 h', st_small), P('TCD + continuous EEG; ICP monitoring', st_small)],
[P('Hydrocephalus', st_small), P('Monitor; LP or EVD if symptomatic', st_small), P('Early EVD if obstructive hydrocephalus', st_small)],
[P('Seizures', st_small), P('Levetiracetam if occurs; stop after aneurysm secured', st_small), P('Continue AED + cEEG monitoring', st_small)],
]
mg_tbl = Table(mgmt, colWidths=[3.5*cm, 6.6*cm, 6.6*cm])
mg_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), TEAL),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8.5),
('GRID', (0,0),(-1,-1), 0.4, MIDGREY),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING',(0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 5),
('BACKGROUND', (0,2),(-1,2), GREY),
('BACKGROUND', (0,4),(-1,4), GREY),
('BACKGROUND', (0,6),(-1,6), GREY),
]))
story.append(mg_tbl)
story.append(Spacer(1, 0.25*cm))
story.append(P('3.3 Common Pitfalls – Do Not Miss', st_h2))
story.append(bullets([
'<b>Grade on admission only:</b> SAH grade can change rapidly – reassess every 4-6 hours.',
'<b>Grading post-sedation:</b> Both HH and WFNS are invalid after sedation – always document the <i>pre-sedation</i> grade.',
'<b>Fisher Group 1 = "safe":</b> CT-negative SAH exists; correlate with LP if clinical suspicion remains high.',
'<b>Ignoring IVH on CT:</b> Bilateral IVH alone = Modified Fisher Grade 2 (33% vasospasm risk) – not low risk.',
'<b>Relying on the radiology report alone:</b> Learn cisternal anatomy – read the CT yourself.',
'<b>Poor grade = futility:</b> Hunt-Hess Grade 5 carries 70% mortality but 30% can survive – never withdraw care without senior discussion.',
]))
story.append(Spacer(1, 0.25*cm))
story.append(P('3.4 Quick Quiz – 5 Questions', st_h2))
story.append(info_box(
'<b>Q1.</b> Thunderclap headache, GCS 14, right CN III palsy, no motor deficit. CT: thin diffuse SAH, no IVH. '
'State Hunt-Hess grade, WFNS grade, Modified Fisher grade, and vasospasm risk.<br/><br/>'
'<b>Q2.</b> CT: thick blood in basal cisterns + blood in both lateral ventricles. '
'Modified Fisher grade and expected vasospasm incidence?<br/><br/>'
'<b>Q3.</b> Why is WFNS Grade 3 higher risk than Grade 2 despite an identical GCS range?<br/><br/>'
'<b>Q4.</b> When should nimodipine be started and for how long?<br/><br/>'
'<b>Q5.</b> A patient admitted as HH Grade 2 suddenly drops to Grade 3. List three causes.'
))
story.append(Spacer(1, 0.15*cm))
story.append(info_box(
'<b>Answers:</b><br/>'
'<b>Q1:</b> HH Grade 2 (severe headache + CN palsy, no motor); WFNS Grade 2 (GCS 13-14, no motor); '
'Modified Fisher Grade 1 (thin, no IVH) → 24% vasospasm risk.<br/>'
'<b>Q2:</b> Modified Fisher Grade 4 (thick SAH + IVH) → 40% vasospasm risk.<br/>'
'<b>Q3:</b> Grade 3 adds motor deficit to GCS 13-14, indicating greater cortical/subcortical injury.<br/>'
'<b>Q4:</b> Within 48 hours of haemorrhage; continued for 21 days (60 mg every 4 hours).<br/>'
'<b>Q5:</b> (1) Rebleeding; (2) Acute hydrocephalus; (3) Cerebral vasospasm/DCI.',
bg=LTAMBER, border=AMBER
))
story.append(Spacer(1, 0.3*cm))
story.append(P('3.5 Full Grading Summary – Quick Reference Card', st_h2))
summary = [
[P('<b>Scale</b>', st_small), P('<b>Type</b>', st_small), P('<b>Year</b>', st_small),
P('<b>What It Grades</b>', st_small), P('<b>Primary Use</b>', st_small)],
[P('Hunt-Hess', st_small), P('Clinical', st_small), P('1968', st_small),
P('Neurological status (0-5)', st_small), P('Surgical timing, prognosis', st_small)],
[P('WFNS', st_small), P('Clinical', st_small), P('1988', st_small),
P('GCS + motor deficit (1-5)', st_small), P('Universal grading, research', st_small)],
[P('Fisher', st_small), P('Radiological', st_small), P('1980', st_small),
P('CT blood volume/distribution (1-4)', st_small), P('Predicts vasospasm', st_small)],
[P('Modified Fisher', st_small), P('Radiological', st_small), P('2006', st_small),
P('CT blood + IVH (0-4)', st_small), P('More accurate vasospasm prediction', st_small)],
[P('Hijdra Sum Score', st_small), P('Radiological', st_small), P('1985', st_small),
P('Semi-quantitative cisternal blood (0-30)', st_small), P('Research; superior vasospasm prediction', st_small)],
]
sum_tbl = Table(summary, colWidths=[3.3*cm, 2.3*cm, 1.6*cm, 5.4*cm, 4.8*cm])
sum_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), NAVY),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8.5),
('GRID', (0,0),(-1,-1), 0.4, MIDGREY),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING',(0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 5),
('BACKGROUND', (0,2),(-1,2), GREY),
('BACKGROUND', (0,4),(-1,4), GREY),
]))
story.append(sum_tbl)
story.append(Spacer(1, 0.3*cm))
# Footer
footer = Table([[P(
'References: Bradley & Daroff\'s Neurology in Clinical Practice | Tintinalli\'s Emergency Medicine | '
'Schwartz\'s Principles of Surgery 11e | Goldman-Cecil Medicine | '
'Tietz Textbook of Laboratory Medicine 7e | Frontera JA et al. Neurosurgery 2006 | '
'Drake CG. J Neurosurg 1988',
st_caption
)]], colWidths=[17.4*cm])
footer.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,-1), NAVY),
('TOPPADDING', (0,0),(-1,-1), 5),
('BOTTOMPADDING',(0,0),(-1,-1), 5),
('LEFTPADDING', (0,0),(-1,-1), 8),
('TEXTCOLOR', (0,0),(-1,-1), LTBLUE),
]))
story.append(footer)
doc.build(story)
print("SAH_Grading_Study_Guide.pdf built successfully.")
~/sah-ecmo/build_sah_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/sah-ecmo/build_sah_pdf.py'
cat > /home/daytona/workspace/sah-ecmo/build_sah_pdf.py << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import cm from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, PageBreak, KeepTogether, ListFlowable, ListItem) from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY NAVY = colors.HexColor('#1a3a5c') TEAL = colors.HexColor('#2a7f8f') AMBER = colors.HexColor('#e07b23') LTBLUE = colors.HexColor('#ddeef6') LTAMBER = colors.HexColor('#fef3e2') WHITE = colors.white GREY = colors.HexColor('#f4f6f8') DKGREY = colors.HexColor('#444444') MIDGREY = colors.HexColor('#cccccc') _base = getSampleStyleSheet() def sty(nm,**kw): p=kw.pop('parent','Normal') return ParagraphStyle(nm,parent=_base[p],**kw) st_title = sty('st_title', fontSize=22,textColor=WHITE, alignment=TA_CENTER,fontName='Helvetica-Bold',spaceAfter=4) st_subtitle = sty('st_subtitle',fontSize=11,textColor=LTBLUE,alignment=TA_CENTER,fontName='Helvetica',spaceAfter=4) st_h1 = sty('st_h1', fontSize=14,textColor=WHITE, fontName='Helvetica-Bold',spaceAfter=2,spaceBefore=2) st_h2 = sty('st_h2', fontSize=12,textColor=NAVY, fontName='Helvetica-Bold',spaceAfter=3,spaceBefore=6) st_h3 = sty('st_h3', fontSize=10,textColor=TEAL, fontName='Helvetica-Bold',spaceAfter=2,spaceBefore=3) st_body = sty('st_body', fontSize=9, textColor=DKGREY,fontName='Helvetica',leading=13,spaceAfter=3,alignment=TA_JUSTIFY) st_small = sty('st_small', fontSize=8.5,textColor=DKGREY,fontName='Helvetica',leading=11) st_badge = sty('st_badge', fontSize=8, textColor=WHITE, fontName='Helvetica-Bold',alignment=TA_CENTER) st_caption = sty('st_caption',fontSize=7.5,textColor=colors.HexColor('#888888'),fontName='Helvetica-Oblique',alignment=TA_CENTER,spaceBefore=1) st_bullet = sty('st_bullet', fontSize=9, textColor=DKGREY,fontName='Helvetica',leading=13,leftIndent=8,spaceAfter=1) def P(txt,s=None): return Paragraph(txt,s or st_body) def banner(text,bg=NAVY,s=None,h=0.65*cm): tbl=Table([[P(text,s or st_h1)]],colWidths=[17.4*cm],rowHeights=[h]) tbl.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,-1),bg),('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('LEFTPADDING',(0,0),(-1,-1),10),('RIGHTPADDING',(0,0),(-1,-1),10), ('TOPPADDING',(0,0),(-1,-1),4),('BOTTOMPADDING',(0,0),(-1,-1),4)])) return tbl def ibox(text,bg=LTBLUE,border=TEAL): tbl=Table([[P(text,st_body)]],colWidths=[17.4*cm]) tbl.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,-1),bg),('BOX',(0,0),(-1,-1),1.2,border), ('LEFTPADDING',(0,0),(-1,-1),8),('RIGHTPADDING',(0,0),(-1,-1),8), ('TOPPADDING',(0,0),(-1,-1),6),('BOTTOMPADDING',(0,0),(-1,-1),6)])) return tbl def wbox(text): return ibox('<b>Key Point: </b>'+text,LTAMBER,AMBER) def chip(lbl,bg=TEAL): tbl=Table([[P(lbl,st_badge)]],colWidths=[3.4*cm],rowHeights=[0.5*cm]) tbl.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,-1),bg),('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('ALIGN',(0,0),(-1,-1),'CENTER'),('LEFTPADDING',(0,0),(-1,-1),4),('RIGHTPADDING',(0,0),(-1,-1),4)])) return tbl def twocol(l,r,lw=4.0*cm,rw=13.0*cm): tbl=Table([[l,r]],colWidths=[lw,rw]) tbl.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'MIDDLE'),('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 tbl def dtbl(hdrs,rows,cw,hbg=NAVY): data=[hdrs]+rows tbl=Table(data,colWidths=cw) ts=[('BACKGROUND',(0,0),(-1,0),hbg),('TEXTCOLOR',(0,0),(-1,0),WHITE), ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),8.5), ('LEADING',(0,0),(-1,-1),12),('ALIGN',(0,0),(-1,-1),'LEFT'),('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('GRID',(0,0),(-1,-1),0.4,MIDGREY),('LEFTPADDING',(0,0),(-1,-1),5), ('RIGHTPADDING',(0,0),(-1,-1),5),('TOPPADDING',(0,0),(-1,-1),4),('BOTTOMPADDING',(0,0),(-1,-1),4)] for i in range(1,len(rows)+1): if i%2==0: ts.append(('BACKGROUND',(0,i),(-1,i),GREY)) tbl.setStyle(TableStyle(ts)) return tbl def blist(items): return ListFlowable([ListItem(P(i,st_bullet),leftIndent=10,bulletColor=TEAL,value='bullet') for i in items], bulletType='bullet',leftIndent=6,bulletFontSize=8) doc=SimpleDocTemplate('/home/daytona/workspace/sah-ecmo/SAH_Grading_Study_Guide.pdf', pagesize=A4,leftMargin=1.8*cm,rightMargin=1.8*cm,topMargin=1.8*cm,bottomMargin=1.8*cm, title='SAH Grading Systems - Study Guide',author='Orris Medical Education') story=[] # COVER cov=Table([[P('SAH GRADING SYSTEMS',st_title)], [P('A Structured 1-Hour Study Guide | Junior Doctors & Residents',st_subtitle)], [Spacer(1,0.2*cm)], [P('3 Sessions x 20 Minutes | Clinical + Radiological Grading | Prognostication & Management',st_subtitle)]], colWidths=[17.4*cm]) cov.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,-1),NAVY), ('TOPPADDING',(0,0),(-1,-1),14),('BOTTOMPADDING',(0,0),(-1,-1),12), ('LEFTPADDING',(0,0),(-1,-1),14),('RIGHTPADDING',(0,0),(-1,-1),14)])) story.append(cov) story.append(Spacer(1,0.4*cm)) story.append(ibox('<b>Learning Objectives:</b> By the end of this guide you will: ' '(1) Apply Hunt-Hess and WFNS clinical grading and interpret their prognostic significance; ' '(2) Classify SAH on CT using Fisher and Modified Fisher scales; ' '(3) Correlate clinical and radiological grades to guide management; ' '(4) Recognise and avoid common grading pitfalls at the bedside.')) story.append(Spacer(1,0.3*cm)) sd=Table([['SESSION 1 - 20 min','SESSION 2 - 20 min','SESSION 3 - 20 min'], ['Why Grade SAH?\nHunt-Hess Scale\nWFNS Scale','Fisher CT Scale\nModified Fisher Scale\nCT Anatomy Review', 'Grade Correlation\nManagement Pearls\nQuick Quiz + Pitfalls']], colWidths=[5.6*cm,5.6*cm,5.6*cm]) sd.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,0),TEAL),('TEXTCOLOR',(0,0),(-1,0),WHITE), ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('BACKGROUND',(0,1),(-1,1),LTBLUE), ('FONTSIZE',(0,0),(-1,-1),9),('ALIGN',(0,0),(-1,-1),'CENTER'),('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('GRID',(0,0),(-1,-1),0.5,WHITE),('TOPPADDING',(0,0),(-1,-1),6),('BOTTOMPADDING',(0,0),(-1,-1),6)])) story.append(sd) story.append(Spacer(1,0.4*cm)) story.append(HRFlowable(width='100%',thickness=1.5,color=TEAL)) story.append(Spacer(1,0.2*cm)) # SESSION 1 story.append(KeepTogether([banner('SESSION 1 | Clinical Grading Scales'),Spacer(1,0.15*cm), twocol(chip('20 MINUTES'),P('Hunt-Hess Scale - WFNS Scale - Why grade at all?',st_h3)),Spacer(1,0.2*cm)])) story.append(P('1.1 Why Do We Grade SAH?',st_h2)) story.append(P('Grading SAH at presentation serves three purposes: <b>(1) Communication</b> - a shared language ' 'across teams and research; <b>(2) Prognostication</b> - predicting hospital mortality and neurological outcome; ' '<b>(3) Management decisions</b> - timing of surgery/coiling, ICU admission, and neuroprotection. ' 'A grade change of even 1 point can signal a new complication.',st_body)) story.append(wbox('A decline of 1 GCS point or new motor deficit demands immediate reassessment - ' 'it may indicate rebleeding, acute hydrocephalus, or herniation.')) story.append(Spacer(1,0.3*cm)) story.append(P('1.2 Hunt-Hess Scale (Hunt & Hess, 1968)',st_h2)) story.append(P('The oldest and most widely used clinical grading scale, originally designed to guide surgical risk. ' 'Grades 1-3 = "good grade" (early intervention); grades 4-5 = "poor grade" (stabilise first).',st_body)) story.append(Spacer(1,0.15*cm)) story.append(dtbl( [P('<b>Grade</b>',st_small),P('<b>Clinical Findings</b>',st_small),P('<b>Hospital Mortality</b>',st_small),P('<b>Key Feature</b>',st_small)], [[P('<b>0</b>',st_small),P('Unruptured aneurysm; asymptomatic',st_small),P('-',st_small),P('Incidental finding',st_small)], [P('<b>1</b>',st_small),P('Asymptomatic or mild headache; slight nuchal rigidity',st_small),P('~3%',st_small),P('No neurological deficit',st_small)], [P('<b>2</b>',st_small),P('Moderate-severe headache; nuchal rigidity; possible CN palsy (III/IV)',st_small),P('~3%',st_small),P('CN palsy only',st_small)], [P('<b>3</b>',st_small),P('Drowsy/confused; mild focal deficit (e.g. pronator drift)',st_small),P('~9%',st_small),P('Altered consciousness',st_small)], [P('<b>4</b>',st_small),P('Stupor; moderate-severe hemiparesis; early decerebrate posturing',st_small),P('~24%',st_small),P('Stupor + motor deficit',st_small)], [P('<b>5</b>',st_small),P('Deep coma; decerebrate rigidity; moribund',st_small),P('~70%',st_small),P('Coma',st_small)]], [1.2*cm,7.0*cm,3.0*cm,4.2*cm])) story.append(P('Source: Goldman-Cecil Medicine; Columbia University Medical Center (n=580)',st_caption)) story.append(Spacer(1,0.2*cm)) story.append(ibox('<b>Limitations of Hunt-Hess:</b> Subjective language. Hunt and Hess themselves wrote: ' '"It is recognized that such classifications are arbitrary and that the margins between categories may be ill defined." ' 'Inter-rater variability is a known limitation, particularly between grades 3 and 4.')) story.append(Spacer(1,0.3*cm)) story.append(P('1.3 WFNS Scale (World Federation of Neurological Surgeons, 1988)',st_h2)) story.append(P('A more objective scale based on Glasgow Coma Scale (GCS) + presence of motor deficit. ' 'Introduced to improve inter-rater reliability and international standardisation in clinical trials.',st_body)) story.append(Spacer(1,0.15*cm)) story.append(dtbl( [P('<b>Grade</b>',st_small),P('<b>GCS Score</b>',st_small),P('<b>Motor Deficit</b>',st_small),P('<b>Prognosis</b>',st_small)], [[P('<b>1</b>',st_small),P('15',st_small),P('Absent',st_small),P('Good',st_small)], [P('<b>2</b>',st_small),P('13-14',st_small),P('Absent',st_small),P('Good-Fair',st_small)], [P('<b>3</b>',st_small),P('13-14',st_small),P('Present',st_small),P('Fair',st_small)], [P('<b>4</b>',st_small),P('7-12',st_small),P('Present or absent',st_small),P('Poor',st_small)], [P('<b>5</b>',st_small),P('3-6',st_small),P('Present or absent',st_small),P('Very poor',st_small)]], [2.0*cm,3.5*cm,4.5*cm,7.4*cm],hbg=TEAL)) story.append(Spacer(1,0.2*cm)) story.append(P('1.4 Hunt-Hess vs WFNS Comparison',st_h2)) ct=Table([[P('<b>Feature</b>',st_small),P('<b>Hunt-Hess</b>',st_small),P('<b>WFNS</b>',st_small)], [P('Year',st_small),P('1968',st_small),P('1988',st_small)], [P('Grading basis',st_small),P('Clinical descriptors',st_small),P('GCS + motor deficit',st_small)], [P('Objectivity',st_small),P('Lower',st_small),P('Higher',st_small)], [P('Sedated patients',st_small),P('Invalid - document pre-sedation grade',st_small),P('Invalid - document pre-sedation grade',st_small)], [P('"Good grade" cut-off',st_small),P('Grade 1-3',st_small),P('Grade 1-2',st_small)], [P('Primary use',st_small),P('Surgical timing, prognostication',st_small),P('Universal grading, research',st_small)]], colWidths=[4.0*cm,6.5*cm,6.5*cm]) ct.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,0),NAVY),('TEXTCOLOR',(0,0),(-1,0),WHITE), ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),8.5), ('GRID',(0,0),(-1,-1),0.4,MIDGREY),('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('TOPPADDING',(0,0),(-1,-1),4),('BOTTOMPADDING',(0,0),(-1,-1),4),('LEFTPADDING',(0,0),(-1,-1),5), ('BACKGROUND',(0,2),(-1,2),GREY),('BACKGROUND',(0,4),(-1,4),GREY),('BACKGROUND',(0,6),(-1,6),GREY)])) story.append(ct) story.append(Spacer(1,0.3*cm)) story.append(P('1.5 Session 1 Self-Check',st_h2)) story.append(blist([ 'Thunderclap headache, GCS 15, neck stiffness, no focal deficit: HH and WFNS grade? <i>[HH Grade 1-2; WFNS Grade 1]</i>', 'Patient is stuporous with right hemiplegia: HH grade? <i>[HH Grade 4]</i>', 'Why might WFNS be preferred in a multi-centre trial? <i>[Objective, GCS-based, reproducible]</i>', 'At what HH grade would you consider delaying aneurysm surgery? <i>[Grade 4-5]</i>'])) story.append(PageBreak()) # SESSION 2 story.append(KeepTogether([banner('SESSION 2 | Radiological Grading - CT-Based Scales'),Spacer(1,0.15*cm), twocol(chip('20 MINUTES'),P('Fisher Scale - Modified Fisher Scale - CT Anatomy',st_h3)),Spacer(1,0.2*cm)])) story.append(P('2.1 What Does CT Tell Us About SAH?',st_h2)) story.append(P('Non-contrast CT (NCCT) is the first-line investigation for SAH. Beyond diagnosing the bleed, ' 'CT findings predict the risk of <b>vasospasm</b> and <b>delayed cerebral ischaemia (DCI)</b> - ' 'the leading cause of morbidity/mortality after the initial haemorrhage. ' 'Blood appears as hyperdense (bright white) signal filling the cisterns and fissures.',st_body)) story.append(ibox('<b>CT Anatomy Reminder:</b> Know the basal cisterns (suprasellar, ambient, quadrigeminal, ' 'sylvian fissure), interhemispheric fissure, and ventricles (I-IV). ' 'Identify each cistern before applying any grading scale.')) story.append(Spacer(1,0.3*cm)) story.append(P('2.2 Original Fisher Scale (Fisher et al., 1980)',st_h2)) story.append(P('Designed to predict the risk of <b>symptomatic vasospasm</b> from the amount and distribution ' 'of subarachnoid blood on CT. Groups 3 and 4 carry the highest vasospasm risk.',st_body)) story.append(Spacer(1,0.15*cm)) story.append(dtbl( [P('<b>Group</b>',st_small),P('<b>CT Finding</b>',st_small),P('<b>Vasospasm Risk</b>',st_small)], [[P('<b>1</b>',st_small),P('No blood detected on CT',st_small),P('Very low',st_small)], [P('<b>2</b>',st_small),P('Diffuse or thin layer of SAH; all vertical layers <1 mm thick',st_small),P('Low-moderate',st_small)], [P('<b>3</b>',st_small),P('Localised clots OR vertical layers of blood >=1 mm thickness',st_small),P('<b>HIGH</b>',st_small)], [P('<b>4</b>',st_small),P('Diffuse or no SAH blood BUT intraparenchymal or intraventricular clots present',st_small),P('<b>HIGH</b>',st_small)]], [1.8*cm,11.0*cm,4.6*cm])) story.append(P('Source: Bradley and Daroff\'s Neurology in Clinical Practice, Box 67.3',st_caption)) story.append(Spacer(1,0.15*cm)) story.append(wbox('Fisher Group 4 can paradoxically show LESS vasospasm than Group 3 in some series ' '(minimal cisternal blood despite IVH/parenchymal clot). ' 'This non-monotonic relationship was the key driver for developing the Modified Fisher Scale.')) story.append(Spacer(1,0.3*cm)) story.append(P('2.3 Modified Fisher Scale (Frontera et al., 2006)',st_h2)) story.append(P('Revised to address the original Fisher scale\'s failure to account for IVH as an independent risk factor. ' 'IVH is now a separate binary variable. <b>Applies to aneurysmal SAH only.</b>',st_body)) story.append(Spacer(1,0.15*cm)) story.append(dtbl( [P('<b>Grade</b>',st_small),P('<b>Subarachnoid Blood</b>',st_small),P('<b>IVH</b>',st_small),P('<b>Vasospasm Risk</b>',st_small)], [[P('<b>0</b>',st_small),P('None',st_small),P('None',st_small),P('0%',st_small)], [P('<b>1</b>',st_small),P('Focal or diffuse; thin',st_small),P('Absent',st_small),P('24%',st_small)], [P('<b>2</b>',st_small),P('Focal thin or diffuse',st_small),P('<b>Present</b>',st_small),P('33%',st_small)], [P('<b>3</b>',st_small),P('<b>Thick</b>',st_small),P('Absent',st_small),P('33%',st_small)], [P('<b>4</b>',st_small),P('<b>Thick</b>',st_small),P('<b>Present</b>',st_small),P('<b>40% (highest)</b>',st_small)]], [1.8*cm,5.8*cm,2.8*cm,7.0*cm],hbg=TEAL)) story.append(P('Source: Frontera JA et al. Prediction of symptomatic vasospasm after SAH. Neurosurgery. 2006;59(1):21-27',st_caption)) story.append(Spacer(1,0.2*cm)) story.append(ibox('<b>Key improvement:</b> A patient with IVH alone (no SAH blood) now scores Grade 2 (33% vasospasm risk). ' 'On the original Fisher scale they would score Group 1 - dramatically underestimating their risk.<br/>' '<b>Limitations:</b> (1) Grades 2 and 3 have identical vasospasm incidence (33%). ' '(2) Inter-rater reliability for thick vs thin blood is only 72-78%.')) story.append(Spacer(1,0.25*cm)) story.append(P('2.4 Session 2 Self-Check',st_h2)) story.append(blist([ 'CT: thick blood in basal cisterns, no IVH - Modified Fisher grade and vasospasm risk? <i>[Grade 3; 33%]</i>', 'CT: thin SAH + blood in both lateral ventricles - Modified Fisher grade? <i>[Grade 2; 33%]</i>', 'Which Fisher group shows HIGH vasospasm risk despite minimal subarachnoid blood? <i>[Group 4 - due to IVH/parenchymal clot]</i>', 'Why does Modified Fisher Grade 2 need the same vasospasm vigilance as Grade 3? <i>[Both carry 33% risk]</i>'])) story.append(PageBreak()) # SESSION 3 story.append(KeepTogether([banner('SESSION 3 | Correlation, Management & Pitfalls'),Spacer(1,0.15*cm), twocol(chip('20 MINUTES'),P('Clinical-Radiological Correlation - Management Pearls - Quick Quiz',st_h3)),Spacer(1,0.2*cm)])) story.append(P('3.1 Clinical + CT Grade Correlation',st_h2)) story.append(P('Use both clinical and CT grades together. A mismatch (good clinical grade + bad CT, or vice versa) ' 'should heighten vigilance and monitoring frequency.',st_body)) it=Table([[P('<b>Clinical Grade</b>',st_small),P('<b>Modified Fisher</b>',st_small),P('<b>Action</b>',st_small)], [P('HH 1-2 / WFNS 1-2',st_small),P('0-1',st_small),P('Early aneurysm treatment; standard monitoring',st_small)], [P('HH 1-2 / WFNS 1-2',st_small),P('3-4',st_small),P('Early treatment + aggressive vasospasm surveillance',st_small)], [P('HH 3 / WFNS 3',st_small),P('2-3',st_small),P('ICU admission; hourly neuro obs; consider early intervention',st_small)], [P('HH 4-5 / WFNS 4-5',st_small),P('3-4',st_small),P('Intubate; ICP monitoring; stabilise before intervention',st_small)]], colWidths=[4.2*cm,2.8*cm,10.4*cm]) it.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,0),NAVY),('TEXTCOLOR',(0,0),(-1,0),WHITE), ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),8.5), ('GRID',(0,0),(-1,-1),0.4,MIDGREY),('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('TOPPADDING',(0,0),(-1,-1),4),('BOTTOMPADDING',(0,0),(-1,-1),4),('LEFTPADDING',(0,0),(-1,-1),5), ('BACKGROUND',(0,2),(-1,2),GREY),('BACKGROUND',(0,4),(-1,4),GREY)])) story.append(Spacer(1,0.15*cm)) story.append(it) story.append(Spacer(1,0.25*cm)) story.append(P('3.2 Management Pearls by Grade',st_h2)) mt=Table([[P('<b>Domain</b>',st_small),P('<b>Good Grade (HH 1-3)</b>',st_small),P('<b>Poor Grade (HH 4-5)</b>',st_small)], [P('Airway',st_small),P('Self-maintaining; close monitoring',st_small),P('Intubate and ventilate',st_small)], [P('Aneurysm Tx',st_small),P('Early coiling/clipping (24-72 h)',st_small),P('Stabilise first; delayed if needed',st_small)], [P('Blood Pressure',st_small),P('SBP 120-160 mmHg until secured',st_small),P('Tight BP control; CPP 60-80 mmHg',st_small)], [P('Nimodipine',st_small),P('60 mg q4h x 21 days (ALL grades)',st_small),P('60 mg q4h x 21 days (ALL grades)',st_small)], [P('Vasospasm',st_small),P('TCD daily; neuro exam q4-6 h',st_small),P('TCD + continuous EEG; ICP monitoring',st_small)], [P('Hydrocephalus',st_small),P('Monitor; LP or EVD if symptomatic',st_small),P('Early EVD if obstructive HCP',st_small)], [P('Seizures',st_small),P('Levetiracetam if occurs; stop after aneurysm secured',st_small),P('Continue AED + cEEG',st_small)]], colWidths=[3.5*cm,6.6*cm,6.6*cm]) mt.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,0),TEAL),('TEXTCOLOR',(0,0),(-1,0),WHITE), ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),8.5), ('GRID',(0,0),(-1,-1),0.4,MIDGREY),('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('TOPPADDING',(0,0),(-1,-1),4),('BOTTOMPADDING',(0,0),(-1,-1),4),('LEFTPADDING',(0,0),(-1,-1),5), ('BACKGROUND',(0,2),(-1,2),GREY),('BACKGROUND',(0,4),(-1,4),GREY),('BACKGROUND',(0,6),(-1,6),GREY)])) story.append(mt) story.append(Spacer(1,0.25*cm)) story.append(P('3.3 Common Pitfalls - Do Not Miss',st_h2)) story.append(blist([ '<b>Grade on admission only:</b> SAH grade can change rapidly - reassess every 4-6 hours.', '<b>Grading post-sedation:</b> Both HH and WFNS are invalid after sedation. Always document the pre-sedation grade.', '<b>Fisher Group 1 = safe:</b> CT-negative SAH exists. Correlate with LP if clinical suspicion remains high.', '<b>Ignoring IVH on CT:</b> Bilateral IVH alone = Modified Fisher Grade 2 (33% vasospasm risk) - not low risk.', '<b>Relying solely on radiology report:</b> Learn cisternal anatomy. Read the CT yourself.', '<b>Poor grade = futility:</b> HH Grade 5 carries 70% mortality but 30% can survive. Never withdraw without senior discussion.'])) story.append(Spacer(1,0.25*cm)) story.append(P('3.4 Quick Quiz',st_h2)) story.append(ibox('<b>Q1.</b> Thunderclap headache, GCS 14, right CN III palsy, no motor deficit. ' 'CT: thin diffuse SAH, no IVH. State HH grade, WFNS grade, Modified Fisher grade, and vasospasm risk.<br/><br/>' '<b>Q2.</b> CT: thick blood in basal cisterns + blood in both lateral ventricles. ' 'Modified Fisher grade and vasospasm incidence?<br/><br/>' '<b>Q3.</b> Why is WFNS Grade 3 higher risk than Grade 2 despite an identical GCS range?<br/><br/>' '<b>Q4.</b> When should nimodipine be started and for how long?<br/><br/>' '<b>Q5.</b> A patient admitted HH Grade 2 suddenly drops to Grade 3. List three causes.')) story.append(Spacer(1,0.15*cm)) story.append(ibox('<b>Answers:</b><br/>' '<b>Q1:</b> HH Grade 2; WFNS Grade 2 (GCS 13-14, no motor); Modified Fisher Grade 1 (thin, no IVH) = 24% vasospasm risk.<br/>' '<b>Q2:</b> Modified Fisher Grade 4 (thick + IVH) = 40% vasospasm risk.<br/>' '<b>Q3:</b> Grade 3 adds motor deficit to GCS 13-14, indicating greater cortical/subcortical injury.<br/>' '<b>Q4:</b> Within 48 hours; continued for 21 days (60 mg every 4 hours).<br/>' '<b>Q5:</b> (1) Rebleeding; (2) Acute hydrocephalus; (3) Cerebral vasospasm/DCI.', bg=LTAMBER,border=AMBER)) story.append(Spacer(1,0.3*cm)) story.append(P('3.5 Full Grading Summary - Quick Reference Card',st_h2)) sr=Table([[P('<b>Scale</b>',st_small),P('<b>Type</b>',st_small),P('<b>Year</b>',st_small), P('<b>What It Grades</b>',st_small),P('<b>Primary Use</b>',st_small)], [P('Hunt-Hess',st_small),P('Clinical',st_small),P('1968',st_small), P('Neurological status (0-5)',st_small),P('Surgical timing, prognosis',st_small)], [P('WFNS',st_small),P('Clinical',st_small),P('1988',st_small), P('GCS + motor deficit (1-5)',st_small),P('Universal grading, research',st_small)], [P('Fisher',st_small),P('Radiological',st_small),P('1980',st_small), P('CT blood volume/distribution (1-4)',st_small),P('Predicts vasospasm',st_small)], [P('Modified Fisher',st_small),P('Radiological',st_small),P('2006',st_small), P('CT blood + IVH (0-4)',st_small),P('Accurate vasospasm prediction',st_small)], [P('Hijdra Sum Score',st_small),P('Radiological',st_small),P('1985',st_small), P('Semi-quantitative cisternal blood (0-30)',st_small),P('Research; superior prediction',st_small)]], colWidths=[3.3*cm,2.3*cm,1.6*cm,5.4*cm,4.8*cm]) sr.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,0),NAVY),('TEXTCOLOR',(0,0),(-1,0),WHITE), ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),8.5), ('GRID',(0,0),(-1,-1),0.4,MIDGREY),('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('TOPPADDING',(0,0),(-1,-1),4),('BOTTOMPADDING',(0,0),(-1,-1),4),('LEFTPADDING',(0,0),(-1,-1),5), ('BACKGROUND',(0,2),(-1,2),GREY),('BACKGROUND',(0,4),(-1,4),GREY)])) story.append(sr) story.append(Spacer(1,0.3*cm)) ft=Table([[P('References: Bradley & Daroff\'s Neurology | Tintinalli\'s Emergency Medicine | ' 'Schwartz\'s Principles of Surgery 11e | Goldman-Cecil Medicine | ' 'Tietz Textbook Lab Medicine 7e | Frontera JA et al. Neurosurgery 2006 | Drake CG. J Neurosurg 1988', st_caption)]],colWidths=[17.4*cm]) ft.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,-1),NAVY), ('TOPPADDING',(0,0),(-1,-1),5),('BOTTOMPADDING',(0,0),(-1,-1),5), ('LEFTPADDING',(0,0),(-1,-1),8),('TEXTCOLOR',(0,0),(-1,-1),LTBLUE)])) story.append(ft) doc.build(story) print("SUCCESS") PYEOF echo "Script written"
cd /home/daytona/workspace/sah-ecmo && python build_sah_pdf.py 2>&1