A 2 year 3months old child has been presented with fever and vomiting of 10days duration. O/E: GCS 10/15. He weighed 7kgs and height – 78cms. CNS examination revealed left 6th nerve palsy, a brisk knee reflex with extensor plantar reflex. Neck stiffness was present. a) What is the likely diagnosis? What are the points in favour of the diagnosis? b) How would you investigate this child? c) How would you manage this child?
tuberculous meningitis child management anti-TB drugs dosage dexamethasone 2025 guidelines
| Feature | Relevance |
|---|---|
| Subacute onset (10 days) | TBM characteristically presents over 5-30 days, unlike acute bacterial meningitis (hours to days). Average duration before diagnosis is ~12 days - this case fits exactly. |
| Fever | Universal in TBM. Typically low-grade and prolonged in TB. |
| Vomiting | Reflects raised intracranial pressure (ICP) - classic in TBM due to basilar exudate causing CSF flow obstruction. |
| GCS 10/15 (altered consciousness) | TBM causes encephalopathy. Patients present as lethargic, stuporous, or comatose; this child has significant depression of consciousness. |
| Left 6th (abducens) nerve palsy | TBM is predominantly a basal meningitis. The inflammatory exudate at the base of the brain preferentially involves cranial nerves III, IV, VI, and VII. The 6th nerve has the longest intracranial course and is most commonly affected. Cranial nerve palsies are a hallmark of TBM in children. The 6th nerve is also stretched in raised ICP as a "false localizing sign." |
| Brisk knee reflexes + extensor plantar response (upgoing Babinski) | These upper motor neuron (UMN) signs indicate corticospinal tract involvement - seen due to cerebral vasculitis, hydrocephalus, or direct parenchymal involvement. TBM causes arteritis of vessels at the base of the brain (internal capsule territory). |
| Neck stiffness (meningismus) | Direct evidence of meningeal irritation from subarachnoid space contamination. |
| Severe malnutrition | Both a risk factor for TB disease and for TBM specifically. TB is far more common in malnourished children with impaired cell-mediated immunity. |
| Age group (2 years) | TBM accounts for up to 10% of all TB cases in children, compared to ~1% in adults. Children under 5 are at highest risk of disseminated and CNS TB following primary infection. |
| Combination of basal signs + raised ICP + long duration | This triad (cranial nerve palsy + pyramidal signs + subacute course) is classical for TBM and should prompt immediate treatment even before microbiologic confirmation. |
| Parameter | Expected Finding |
|---|---|
| Opening pressure | Elevated |
| Appearance | Clear/xanthochromic, forms a cobweb clot on standing |
| WBC count | 10-500 cells/µL (usually 100-500) |
| Cell type | Predominantly lymphocytes (65-75%); early disease may show polymorphs |
| Protein | Elevated >100 mg/dL (very high protein >500 mg/dL is poor prognostic sign) |
| Glucose | Low (CSF:serum glucose ratio <0.5; often <45 mg/dL) - but rarely <20 mg/dL |
| Acid-fast bacilli smear | Positive in only 10-20% |
| Culture for M. tuberculosis | Positive in 2-50%; takes 4-8 weeks; gold standard |
| CSF PCR (MTB) | Rapid; sensitivity 25-80%, specificity >95% - best available rapid test |
| Adenosine deaminase (ADA) | Elevated (>10 IU/L) - supports diagnosis |
50% of TBM patients have abnormal CXR; look for miliary mottling (1-2 mm nodules throughout both lung fields - "snowstorm" pattern), hilar lymphadenopathy, primary complex, consolidation. Miliary TB is strongly associated with TBM.
| Drug | Dose | Route |
|---|---|---|
| Isoniazid (H) | 15-20 mg/kg/day (max 450 mg) | Oral |
| Rifampicin (R) | 22.5-30 mg/kg/day (max 900 mg) | Oral |
| Pyrazinamide (Z) | 35-45 mg/kg/day (max 2 g) | Oral |
| Ethionamide (Eto) | 17.5-22.5 mg/kg/day (max 1 g) | Oral (preferred 4th drug for CNS TB - better CNS penetration than ethambutol) |
Why ethionamide over ethambutol? Ethambutol has poor CSF penetration once meningeal inflammation resolves. Ethionamide penetrates the CNS well. Some centers use fluoroquinolones (levofloxacin) as the 4th drug - also excellent CNS penetration and preferred in MDR-TB. Per Medscape/ATS guidelines, the preferred pediatric regimen is INH + RIF + PZA + ethionamide (or aminoglycoside) intensively.
Current 2025 guidelines per ATS/CDC/ERS/IDSA 2025 guideline update support this extended duration for CNS disease. The SURE trial (ongoing, 2025) is evaluating whether 6 months of intensified treatment may be sufficient in children.
Create a summary guide on CSF interpretation in meningitis differentials
mkdir -p /tmp/workspace/csf-guide && ls /tmp/workspace/csf-guide/
~/csf-guide/build_csf_guide.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.colors import HexColor, white, black
# ── Colour palette ──────────────────────────────────────────────────────────
NAVY = HexColor("#1A2E4A")
BLUE = HexColor("#1F6FBF")
LIGHT_BLUE = HexColor("#D6E8F7")
TEAL = HexColor("#0D7C7C")
TEAL_LIGHT = HexColor("#D0F0F0")
RED = HexColor("#C0392B")
RED_LIGHT = HexColor("#FADBD8")
AMBER = HexColor("#D68910")
AMBER_LIGHT= HexColor("#FCF3CF")
GREEN = HexColor("#1E8449")
GREEN_LIGHT= HexColor("#D5F5E3")
PURPLE = HexColor("#6C3483")
PURPLE_LIGHT=HexColor("#E8DAEF")
GREY_LIGHT = HexColor("#F2F4F4")
GREY_MID = HexColor("#BFC9CA")
GREY_DARK = HexColor("#5D6D7E")
WHITE = white
BLACK = black
OUTPUT = "/tmp/workspace/csf-guide/CSF_Interpretation_Guide.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=1.8*cm, rightMargin=1.8*cm,
topMargin=2*cm, bottomMargin=2*cm,
title="CSF Interpretation in Meningitis Differentials",
author="Orris Medical Education",
)
W, H = A4
CONTENT_W = W - 3.6*cm # usable width
styles = getSampleStyleSheet()
# ── Custom paragraph styles ─────────────────────────────────────────────────
def S(name, **kw):
return ParagraphStyle(name, **kw)
sTitle = S("sTitle", fontName="Helvetica-Bold", fontSize=22,
textColor=WHITE, alignment=TA_CENTER, leading=28)
sSubtitle = S("sSubtitle", fontName="Helvetica", fontSize=12,
textColor=LIGHT_BLUE, alignment=TA_CENTER, leading=16)
sTag = S("sTag", fontName="Helvetica-Bold", fontSize=9,
textColor=AMBER, alignment=TA_CENTER)
sH1 = S("sH1", fontName="Helvetica-Bold", fontSize=14,
textColor=WHITE, leading=18)
sH2 = S("sH2", fontName="Helvetica-Bold", fontSize=12,
textColor=NAVY, leading=16, spaceAfter=4)
sH3 = S("sH3", fontName="Helvetica-Bold", fontSize=10,
textColor=TEAL, leading=14)
sBody = S("sBody", fontName="Helvetica", fontSize=9,
textColor=HexColor("#2C3E50"), leading=13, spaceAfter=3,
alignment=TA_JUSTIFY)
sBullet = S("sBullet", fontName="Helvetica", fontSize=9,
textColor=HexColor("#2C3E50"), leading=13,
leftIndent=12, firstLineIndent=-8, spaceAfter=2)
sNote = S("sNote", fontName="Helvetica-Oblique", fontSize=8,
textColor=GREY_DARK, leading=11, spaceAfter=2)
sTableH = S("sTableH", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_CENTER, leading=10)
sTableCell = S("sTableCell", fontName="Helvetica", fontSize=8,
textColor=HexColor("#1A2E4A"), alignment=TA_CENTER, leading=10)
sTableCellL = S("sTableCellL", fontName="Helvetica", fontSize=8,
textColor=HexColor("#1A2E4A"), alignment=TA_LEFT, leading=10)
sTableCellB = S("sTableCellB", fontName="Helvetica-Bold", fontSize=8,
textColor=HexColor("#1A2E4A"), alignment=TA_LEFT, leading=10)
sCellNote = S("sCellNote", fontName="Helvetica-Oblique", fontSize=7,
textColor=GREY_DARK, alignment=TA_CENTER, leading=9)
sCaption = S("sCaption", fontName="Helvetica-Oblique", fontSize=7.5,
textColor=GREY_DARK, alignment=TA_CENTER, leading=10)
sRef = S("sRef", fontName="Helvetica-Oblique", fontSize=7,
textColor=GREY_DARK, leading=10, spaceAfter=1)
# ── Helper flowables ─────────────────────────────────────────────────────────
def rule(color=BLUE, thickness=1.5, space=6):
return HRFlowable(width="100%", thickness=thickness, color=color,
spaceAfter=space, spaceBefore=space)
def section_header(text, color=BLUE):
tbl = Table([[Paragraph(text, sH1)]], colWidths=[CONTENT_W])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("ROUNDEDCORNERS", [5,5,5,5]),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
]))
return tbl
def info_box(text, bg=LIGHT_BLUE, border=BLUE):
tbl = Table([[Paragraph(text, sBody)]], colWidths=[CONTENT_W])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 1.2, border),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
]))
return tbl
def bullet(text):
return Paragraph(f"\u2022 {text}", sBullet)
story = []
# ════════════════════════════════════════════════════════════════════════════
# PAGE 1 – TITLE BANNER
# ════════════════════════════════════════════════════════════════════════════
banner = Table(
[[Paragraph("CSF Interpretation in Meningitis", sTitle)],
[Paragraph("A Differential Diagnosis Guide for Medical Students", sSubtitle)],
[Spacer(1, 6)],
[Paragraph("Orris Medical Education • August 2026", sTag)]],
colWidths=[CONTENT_W]
)
banner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 16),
("RIGHTPADDING", (0,0), (-1,-1), 16),
("ROUNDEDCORNERS", [8,8,8,8]),
]))
story += [banner, Spacer(1, 10)]
# ── INTRODUCTION BOX ───────────────────────────────────────────────────────
story += [
section_header("Why CSF Analysis Matters"),
Spacer(1, 6),
Paragraph(
"Cerebrospinal fluid (CSF) analysis via lumbar puncture is the cornerstone investigation "
"in any patient with suspected meningitis. The pattern of abnormalities — opening pressure, "
"cell count and differential, protein, and glucose — reliably distinguishes the four major "
"categories of meningitis and guides urgent empiric treatment, often before culture results "
"are available. This guide summarises normal CSF values, the classic profiles of each "
"meningitis type, and the key investigations and clinical pearls for each.",
sBody),
Spacer(1, 6),
]
# ── NORMAL CSF VALUES BOX ──────────────────────────────────────────────────
story += [
Paragraph("Normal CSF Reference Values (Adult)", sH2),
]
normal_data = [
[Paragraph("Parameter", sTableH),
Paragraph("Normal Range", sTableH),
Paragraph("Clinical Note", sTableH)],
[Paragraph("Opening Pressure", sTableCellL),
Paragraph("70–180 mm H₂O", sTableCell),
Paragraph("Measured with patient lateral decubitus; >200 = raised ICP", sTableCellL)],
[Paragraph("Appearance", sTableCellL),
Paragraph("Clear, colourless", sTableCell),
Paragraph("Xanthochromia → SAH or high protein; turbidity → infection", sTableCellL)],
[Paragraph("WBC count", sTableCellL),
Paragraph("0–5 cells/µL", sTableCell),
Paragraph("Predominantly lymphocytes in adults", sTableCellL)],
[Paragraph("RBC", sTableCellL),
Paragraph("0 (non-traumatic)", sTableCell),
Paragraph("Traumatic tap → correct WBC by 1 WBC per 700 RBC", sTableCellL)],
[Paragraph("Glucose", sTableCellL),
Paragraph("50–75 mg/dL (2.8–4.2 mmol/L)", sTableCell),
Paragraph("CSF:serum ratio >0.6 is normal; always send paired serum glucose", sTableCellL)],
[Paragraph("Protein", sTableCellL),
Paragraph("15–45 mg/dL (0.15–0.45 g/L)", sTableCell),
Paragraph("Elevated with BBB disruption, infection, or malignancy", sTableCellL)],
[Paragraph("Gram stain / Culture", sTableCellL),
Paragraph("Negative", sTableCell),
Paragraph("Any positive is abnormal", sTableCellL)],
]
col_w = [CONTENT_W*0.22, CONTENT_W*0.28, CONTENT_W*0.50]
t_normal = Table(normal_data, colWidths=col_w, repeatRows=1)
t_normal.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("BACKGROUND", (0,1), (-1,1), GREY_LIGHT),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), GREY_LIGHT),
("BACKGROUND", (0,4), (-1,4), WHITE),
("BACKGROUND", (0,5), (-1,5), GREY_LIGHT),
("BACKGROUND", (0,6), (-1,6), WHITE),
("BACKGROUND", (0,7), (-1,7), GREY_LIGHT),
("GRID", (0,0), (-1,-1), 0.5, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 7),
("RIGHTPADDING", (0,0), (-1,-1), 7),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story += [t_normal, Spacer(1, 4),
Paragraph("Note: Neonates and young infants have higher normal WBC and protein — always use age-adjusted reference ranges.", sNote),
Spacer(1, 10)]
# ════════════════════════════════════════════════════════════════════════════
# MASTER COMPARISON TABLE
# ════════════════════════════════════════════════════════════════════════════
story += [
section_header("Master CSF Profile Comparison", NAVY),
Spacer(1, 8),
]
# Column headers
hdrs = ["Parameter", "Normal", "Bacterial", "Viral (Aseptic)", "Tuberculous", "Cryptococcal/Fungal"]
hdr_row = [Paragraph(h, sTableH) for h in hdrs]
cw = [CONTENT_W*0.18, CONTENT_W*0.10, CONTENT_W*0.155, CONTENT_W*0.155, CONTENT_W*0.155, CONTENT_W*0.155]
rows = [
["Opening\nPressure", "< 180\nmm H₂O", "↑↑\n200–500", "↑ or normal\n<200", "↑\n200–400", "↑↑↑\nOften >400"],
["Appearance", "Clear", "Turbid/\nPurulent", "Clear", "Clear/ slight\nturbid", "Clear"],
["WBC (/µL)", "0–5", "100–5000\n(up to 10 000)", "10–1000", "50–300", "20–500"],
["Predominant\nCell Type", "Lymphocyte", "Neutrophils\n(PMN)", "Lymphocytes", "Lymphocytes\n(early: PMN)", "Lymphocytes"],
["Protein\n(mg/dL)", "15–45", "100–500", "50–100", "50–300", ">45\n(often 100–500)"],
["Glucose\n(mg/dL)", "50–75", "< 40\n(<45% serum)", "Normal\n(40–80)", "< 45\n(low)", "< 40\n(very low)"],
["CSF:Serum\nGlucose", "> 0.6", "< 0.4", "Normal\n≥ 0.6", "< 0.5", "< 0.4"],
["Gram Stain", "Negative", "Positive\n>60%", "Negative", "Negative", "Negative"],
["Key Special\nTest", "—", "Culture\n(>80%+)\nBlood culture", "Virus-specific\nPCR", "AFB smear/\nCulture/PCR\nADA", "India ink\nCryptococcal\nAntigen (CrAg)"],
["Cobweb Clot", "Absent", "Absent", "Absent", "Present\n(classic!)", "Absent"],
]
data = [hdr_row]
# row backgrounds alternating; special colour for critical cells
row_bgs = [GREY_LIGHT, WHITE] * 10
for i, row in enumerate(rows):
cells = [Paragraph(row[0], sTableCellB)]
for val in row[1:]:
cells.append(Paragraph(val, sTableCell))
data.append(cells)
master_tbl = Table(data, colWidths=cw, repeatRows=1)
ts = TableStyle([
# Header row
("BACKGROUND", (0,0), (-1,0), NAVY),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
# Alternating rows
*[("BACKGROUND", (0,i+1), (-1,i+1), row_bgs[i]) for i in range(len(rows))],
# Colour-code critical values
# Bacterial – glucose very low (row 5, col 2), PMN (row 3, col 2)
("BACKGROUND", (2,6), (2,6), RED_LIGHT), # glucose
("BACKGROUND", (2,4), (2,4), RED_LIGHT), # PMN
("BACKGROUND", (2,5), (2,5), RED_LIGHT), # protein high
# Viral – glucose normal (row 5, col 3)
("BACKGROUND", (3,6), (3,6), GREEN_LIGHT),
# TBM – cobweb (row 9, col 4)
("BACKGROUND", (4,10), (4,10), AMBER_LIGHT),
# Crypto – very high pressure
("BACKGROUND", (5,1), (5,1), PURPLE_LIGHT),
("BACKGROUND", (5,6), (5,6), PURPLE_LIGHT),
])
master_tbl.setStyle(ts)
story += [master_tbl, Spacer(1, 5),
Paragraph("Sources: Washington Manual of Medical Therapeutics; Harrison's Principles of Internal Medicine 22e; Harriet Lane Handbook 23e.", sCaption),
Spacer(1, 10)]
# ════════════════════════════════════════════════════════════════════════════
# PAGE 2 – INDIVIDUAL PROFILES
# ════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story += [section_header("Individual Profiles — Key Features & Investigations"), Spacer(1, 8)]
# ── helper: profile card ──────────────────────────────────────────────────
def profile_card(title, color, light, lines, investigations, pearls):
inner = []
inner.append(Paragraph(title, S("ph", fontName="Helvetica-Bold", fontSize=11,
textColor=color, leading=14, spaceAfter=3)))
inner.append(rule(color, 1))
for k, v in lines:
inner.append(Paragraph(f"<b>{k}:</b> {v}", sBody))
inner.append(Spacer(1, 4))
inner.append(Paragraph("Key Investigations", S("pi", fontName="Helvetica-Bold",
fontSize=9, textColor=GREY_DARK)))
for inv in investigations:
inner.append(bullet(inv))
inner.append(Spacer(1, 4))
inner.append(Paragraph("Clinical Pearls", S("pi2", fontName="Helvetica-Bold",
fontSize=9, textColor=GREY_DARK)))
for p in pearls:
inner.append(bullet(p))
card = Table([[inner]], colWidths=[CONTENT_W])
card.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), light),
("BOX", (0,0), (-1,-1), 1.5, color),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
]))
return card
# 1. Bacterial Meningitis
story.append(KeepTogether([
profile_card(
"1. Bacterial Meningitis",
RED, RED_LIGHT,
[
("Onset", "Acute — hours to 1–2 days"),
("Opening pressure", "↑↑ 200–500 mm H₂O"),
("WBC", "100–5,000 /µL, predominantly neutrophils (PMN >80%)"),
("Protein", "100–500 mg/dL"),
("Glucose", "<40 mg/dL; CSF:serum ratio <0.4 — hypoglycorrhachia"),
("Appearance", "Turbid or frankly purulent; may be haemorrhagic"),
],
[
"Gram stain of CSF (positive >60%) — do immediately",
"CSF culture and sensitivity (positive >80%) — gold standard",
"Blood cultures x2 before antibiotics",
"CRP, procalcitonin, FBC, U&E",
"CT brain before LP if papilledema, focal deficit, or GCS <15",
"Multiplex PCR panel (BioFire/FilmArray) for rapid pathogen ID",
"Lactate >3.5 mmol/L in CSF suggests bacterial aetiology",
],
[
"Common organisms: N. meningitidis (purpuric rash!), S. pneumoniae, H. influenzae, Group B Strep (neonates), Listeria (elderly/immunocompromised)",
"Do NOT delay antibiotics to await CT/LP if clinically deteriorating",
"Petechial/purpuric rash + fever = N. meningitidis until proven otherwise — give IV benzylpenicillin immediately",
"Empiric treatment: Ceftriaxone 2g IV q12h + Vancomycin; add Ampicillin if >50 yrs or immunocompromised (Listeria cover)",
"Adjunctive Dexamethasone 0.15 mg/kg q6h for 4 days reduces mortality and hearing loss",
]
),
Spacer(1, 8)
]))
# 2. Viral Meningitis
story.append(KeepTogether([
profile_card(
"2. Viral (Aseptic) Meningitis",
GREEN, GREEN_LIGHT,
[
("Onset", "Subacute — 1–7 days"),
("Opening pressure", "Normal or mildly elevated"),
("WBC", "10–1,000 /µL, predominantly lymphocytes"),
("Protein", "50–100 mg/dL (mildly elevated)"),
("Glucose", "Normal (40–80 mg/dL); CSF:serum ratio ≥0.6"),
("Appearance", "Clear"),
],
[
"CSF PCR panel: Enterovirus (most common), HSV-1 & 2, VZV, CMV, EBV",
"HSV PCR — sensitivity 96%, specificity 99% (after 72h of symptoms)",
"Serum and CSF serology for arbovirus (West Nile, Japanese encephalitis)",
"MRI brain with FLAIR/DWI if encephalitis suspected (HSV: temporal lobe changes)",
"EEG if seizures or encephalitis (HSV: periodic lateralised discharges)",
"Serum and throat/rectal swabs for enterovirus PCR",
],
[
"\"Lymphocytic pleocytosis with normal glucose\" is the viral meningitis fingerprint",
"Most common cause: Enteroviruses (Coxsackievirus, Echovirus)",
"HSV-2 is the most common cause of viral meningitis in adults; HSV-1 causes encephalitis",
"Early viral meningitis may show PMN predominance — repeat LP in 12–24h will show shift to lymphocytes",
"Important: low glucose with lymphocytic pleocytosis = THINK TB or Fungal, NOT viral",
"Management: usually supportive; Acyclovir 10 mg/kg IV q8h if HSV suspected",
]
),
Spacer(1, 8)
]))
# 3. Tuberculous Meningitis
story.append(KeepTogether([
profile_card(
"3. Tuberculous Meningitis (TBM)",
AMBER, AMBER_LIGHT,
[
("Onset", "Subacute to chronic — days to weeks (avg 12 days before diagnosis)"),
("Opening pressure", "↑ 200–400 mm H₂O"),
("WBC", "50–300 /µL, predominantly lymphocytes (early: PMN then shifts)"),
("Protein", "50–300 mg/dL; very high protein (>500) = poor prognosis"),
("Glucose", "<45 mg/dL; low but rarely <20 mg/dL; CSF:serum <0.5"),
("Appearance", "Clear; forms a cobweb/pellicle clot on standing — classic sign"),
("ADA", "Elevated (>10 IU/L) — supports diagnosis"),
],
[
"AFB smear (Ziehl-Neelsen stain): positive in only 10–20%; yield improves with large-volume or repeat LP",
"CSF culture for M. tuberculosis: gold standard but takes 4–8 weeks",
"CSF PCR (GeneXpert Ultra on CSF): rapid, sensitivity ~50–60%, specificity >95%",
"CSF ADA: elevation supports TBM",
"Chest X-ray: miliary shadowing, hilar lymphadenopathy, primary complex",
"CT/MRI brain: basal meningeal enhancement, hydrocephalus, vasculitic infarcts, tuberculomas",
"Mantoux/TST: may be negative (anergy) in up to 50% of TBM cases",
"Gastric aspirate x3 (children who cannot expectorate): AFB smear and culture",
],
[
"Location of inflammation: BASE of brain → cranial nerve palsies (III, VI most common)",
"Triad: subacute onset + cranial nerve palsies + lymphocytic CSF with low glucose = TBM",
"Cobweb clot and very high protein = poor prognostic markers",
"MRC Staging: Stage I (alert, no deficit) → Stage II (focal signs/altered consciousness) → Stage III (coma)",
"Treatment: 2 months HRZE + 7–10 months HR; Dexamethasone adjunct reduces mortality by 25%",
"Risk factors: malnutrition, HIV, young children (<5 yrs), immunocompromise",
]
),
Spacer(1, 8)
]))
# 4. Cryptococcal / Fungal Meningitis
story.append(KeepTogether([
profile_card(
"4. Cryptococcal / Fungal Meningitis",
PURPLE, PURPLE_LIGHT,
[
("Onset", "Subacute to chronic — days to weeks"),
("Opening pressure", "↑↑↑ markedly elevated (often >400 mm H₂O) — most distinctive feature"),
("WBC", "20–500 /µL, predominantly lymphocytes; may be very low in HIV"),
("Protein", ">45 mg/dL; often 100–500 mg/dL"),
("Glucose", "<40 mg/dL; very low; CSF:serum ratio <0.4"),
("Appearance", "Clear; India ink stain shows encapsulated yeasts"),
],
[
"India ink stain of CSF: yeast cells with wide polysaccharide capsule",
"Serum and CSF Cryptococcal antigen (CrAg): highly sensitive (>93%) and specific",
"Fungal culture of CSF: definitive (C. neoformans most common)",
"Blood cultures: often positive in HIV patients with cryptococcaemia",
"HIV test: cryptococcal meningitis is AIDS-defining (CD4 usually <100)",
"Urine LF-LAM (Lateral Flow Assay) in HIV patients",
"CT/MRI brain: dilated Virchow-Robin spaces, cryptococcomas",
],
[
"Very high opening pressure is the hallmark — therapeutic LP to drain CSF may be life-saving",
"In HIV+ patients, cell count may be paradoxically low (poor immune response)",
"Two-phase treatment: Amphotericin B + Flucytosine x2 weeks (induction) → Fluconazole (consolidation then maintenance)",
"Corticosteroids are NOT beneficial in cryptococcal meningitis (unlike bacterial and TBM)",
"Other fungi: Aspergillus, Candida (in immunocompromised, post-neurosurgery), Histoplasma (AIDS)",
]
),
Spacer(1, 8)
]))
# ════════════════════════════════════════════════════════════════════════════
# PAGE 3 – DECISION ALGORITHM + SPECIAL TESTS + PITFALLS
# ════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story += [section_header("Diagnostic Algorithm — Step-by-Step Approach", TEAL), Spacer(1, 8)]
algo_steps = [
("Step 1", "Clinically suspect meningitis",
"Fever + headache + neck stiffness (Kernig's/Brudzinski's) + photophobia. ± altered consciousness, cranial nerve palsies, seizures, rash."),
("Step 2", "Assess for LP safety",
"Check for: papilledema, focal neurological deficit, GCS <13, immunocompromise, new-onset seizures. If any present → CT brain FIRST before LP. Do not delay antibiotics."),
("Step 3", "Lumbar puncture — send CSF for:",
"Opening pressure • Appearance • Cell count + differential • Glucose (+ simultaneous serum glucose) • Protein • Gram stain & culture • PCR panel • Specialised tests (AFB, India ink, CrAg, ADA) as indicated."),
("Step 4", "Interpret the opening pressure",
"Normal (<180) → viral likely. Elevated (200–500) → bacterial or TBM. Markedly elevated (>400) → think Cryptococcal meningitis."),
("Step 5", "Look at the cell type",
"PMN (neutrophil) predominance → Bacterial (or very early viral/TBM). Lymphocyte predominance → Viral, TBM, or Fungal."),
("Step 6", "Interpret the glucose",
"Normal glucose + lymphocytes = Viral meningitis. Low glucose + lymphocytes = TBM or Cryptococcal. Low glucose + neutrophils = Bacterial meningitis."),
("Step 7", "Protein level",
"Mildly elevated (50–100) → viral. Moderately elevated (100–300) → TBM/Cryptococcal. Markedly elevated (>300–500) → Bacterial or severe TBM."),
("Step 8", "Targeted investigations",
"Gram stain → Bacterial. AFB smear/PCR/ADA → TBM. India ink/CrAg → Cryptococcal. Virus PCR → Viral."),
]
for step, heading, detail in algo_steps:
row = Table([[
Table([[Paragraph(step, S("as", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_CENTER))]],
colWidths=[1.5*cm]),
Table([[Paragraph(f"<b>{heading}</b>", sH3),
Paragraph(detail, sBody)]], colWidths=[CONTENT_W - 1.5*cm - 0.3*cm])
]], colWidths=[1.5*cm, CONTENT_W - 1.5*cm])
row.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), TEAL),
("BACKGROUND", (1,0), (1,0), TEAL_LIGHT),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (0,0), 4),
("LEFTPADDING", (1,0), (1,0), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 0.5, GREY_MID),
]))
story += [row, Spacer(1, 3)]
story += [Spacer(1, 10)]
# ── SPECIAL TESTS TABLE ──────────────────────────────────────────────────
story += [section_header("Special & Confirmatory CSF Tests", BLUE), Spacer(1, 6)]
spec_data = [
[Paragraph("Test", sTableH), Paragraph("Meningitis Type", sTableH),
Paragraph("Sensitivity", sTableH), Paragraph("Specificity", sTableH), Paragraph("Note", sTableH)],
[Paragraph("Gram stain", sTableCellL), Paragraph("Bacterial", sTableCell),
Paragraph(">60%", sTableCell), Paragraph("~97%", sTableCell),
Paragraph("Yield reduced after antibiotics", sTableCellL)],
[Paragraph("CSF culture", sTableCellL), Paragraph("Bacterial", sTableCell),
Paragraph(">80%", sTableCell), Paragraph("~100%", sTableCell),
Paragraph("Gold standard; antimicrobial sensitivities", sTableCellL)],
[Paragraph("Blood culture", sTableCellL), Paragraph("Bacterial", sTableCell),
Paragraph("~50%", sTableCell), Paragraph("High", sTableCell),
Paragraph("Always collect before antibiotics", sTableCellL)],
[Paragraph("Multiplex PCR\n(FilmArray/BioFire)", sTableCellL), Paragraph("Bacterial/Viral", sTableCell),
Paragraph("~90%+", sTableCell), Paragraph("~97%", sTableCell),
Paragraph("Rapid (1h); does not replace culture", sTableCellL)],
[Paragraph("HSV PCR", sTableCellL), Paragraph("Viral (Encephalitis)", sTableCell),
Paragraph("96%", sTableCell), Paragraph("99%", sTableCell),
Paragraph("Best after 72h of symptoms and <7d of treatment", sTableCellL)],
[Paragraph("Enterovirus PCR", sTableCellL), Paragraph("Viral", sTableCell),
Paragraph("~86%", sTableCell), Paragraph("~97%", sTableCell),
Paragraph("Most common cause of aseptic meningitis", sTableCellL)],
[Paragraph("AFB smear\n(ZN stain)", sTableCellL), Paragraph("TBM", sTableCell),
Paragraph("10–20%\n(↑60% with large vol)", sTableCell), Paragraph("High", sTableCell),
Paragraph("Yield increases with volume and repeat sampling", sTableCellL)],
[Paragraph("MTB culture", sTableCellL), Paragraph("TBM", sTableCell),
Paragraph("~45%", sTableCell), Paragraph("~100%", sTableCell),
Paragraph("Takes 4–8 weeks; gold standard for TBM", sTableCellL)],
[Paragraph("GeneXpert Ultra\n(MTB PCR)", sTableCellL), Paragraph("TBM", sTableCell),
Paragraph("~50–60%", sTableCell), Paragraph(">95%", sTableCell),
Paragraph("WHO-endorsed; rapid (2h); detects rifampicin resistance", sTableCellL)],
[Paragraph("ADA (adenosine\ndeaminase)", sTableCellL), Paragraph("TBM", sTableCell),
Paragraph("~79%", sTableCell), Paragraph("~91%", sTableCell),
Paragraph(">10 IU/L supports TBM; useful where PCR unavailable", sTableCellL)],
[Paragraph("India ink stain", sTableCellL), Paragraph("Cryptococcal", sTableCell),
Paragraph("~50%", sTableCell), Paragraph(">95%", sTableCell),
Paragraph("Encapsulated yeast with halo; faster than culture", sTableCellL)],
[Paragraph("CrAg (Cryptococcal\nAntigen)", sTableCellL), Paragraph("Cryptococcal", sTableCell),
Paragraph(">93%", sTableCell), Paragraph(">97%", sTableCell),
Paragraph("Serum CrAg highly sensitive; CSF CrAg confirms CNS disease", sTableCellL)],
[Paragraph("CSF Lactate", sTableCellL), Paragraph("Bacterial", sTableCell),
Paragraph("~93%", sTableCell), Paragraph("~96%", sTableCell),
Paragraph(">3.5 mmol/L strongly suggests bacterial aetiology", sTableCellL)],
[Paragraph("VDRL (CSF)", sTableCellL), Paragraph("Neurosyphilis", sTableCell),
Paragraph("~50–70%", sTableCell), Paragraph("~99%", sTableCell),
Paragraph("Insensitive but specific; send when syphilis suspected", sTableCellL)],
]
scw = [CONTENT_W*0.18, CONTENT_W*0.17, CONTENT_W*0.12, CONTENT_W*0.12, CONTENT_W*0.41]
spec_tbl = Table(spec_data, colWidths=scw, repeatRows=1)
spec_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [GREY_LIGHT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("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 += [spec_tbl, Spacer(1, 8)]
# ── PITFALLS BOX ─────────────────────────────────────────────────────────
story += [section_header("Common Pitfalls & Exam Tips", RED), Spacer(1, 6)]
pitfalls = [
("<b>Early viral meningitis may show PMN predominance</b> — repeat LP in 12–24h shows shift to lymphocytes. Do not label as bacterial without culture evidence.",),
("<b>Low glucose + lymphocytes ≠ viral meningitis</b> — this is TBM or Cryptococcal until proven otherwise. Viral meningitis has NORMAL glucose.",),
("<b>Negative Mantoux/TST does not exclude TBM</b> — up to 50% of TBM patients are anergic (especially malnourished children, HIV-positive patients).",),
("<b>Cryptococcal meningitis: very high opening pressure</b> — markedly elevated ICP is the most distinctive feature. Therapeutic LPs may be needed to prevent blindness.",),
("<b>Antibiotics before LP?</b> — if CT is needed first, or patient deteriorates: start IV antibiotics IMMEDIATELY. Do not allow waiting for LP to delay treatment.",),
("<b>Traumatic tap correction</b> — subtract 1 WBC for every 700 RBC. Do the same for protein (subtract 1 mg/dL per 1000 RBC).",),
("<b>Cobweb clot in CSF = TBM until proven otherwise</b> — due to very high fibrinogen and protein content.",),
("<b>Paediatric normal values differ</b> — neonates have higher baseline WBC (up to 30 cells) and protein. Always use age-adjusted ranges.",),
("<b>CSF:serum glucose ratio</b> is more reliable than absolute CSF glucose — always send paired serum glucose at time of LP.",),
("<b>CSF lactate >3.5 mmol/L</b> distinguishes bacterial from viral meningitis with >90% accuracy — especially useful when patient has received prior antibiotics.",),
]
pit_data = [[Paragraph(f"\u26a0 {p[0]}", sBullet)] for p in pitfalls]
pit_tbl = Table(pit_data, colWidths=[CONTENT_W])
pit_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), RED_LIGHT),
("BOX", (0,0), (-1,-1), 1.2, RED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("GRID", (0,0), (-1,-1), 0.3, HexColor("#F1948A")),
]))
story += [pit_tbl, Spacer(1, 8)]
# ════════════════════════════════════════════════════════════════════════════
# PAGE 4 – QUICK REFERENCE CARD + REFERENCES
# ════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story += [section_header("Quick Reference Pocket Card", NAVY), Spacer(1, 8)]
qr_headers = ["", "Bacterial", "Viral", "TBM", "Cryptococcal"]
qr_rows = [
["Onset", "Hours–1d", "1–7d", "Days–weeks", "Days–weeks"],
["Pressure", "↑↑", "N/↑", "↑", "↑↑↑"],
["Cells", "PMN 100–5000","Lymph 10–1000", "Lymph 50–300", "Lymph 20–500"],
["Protein", "100–500", "50–100", "50–300", ">45–500"],
["Glucose", "<40 ↓↓", "Normal", "<45 ↓", "<40 ↓↓"],
["Appearance","Turbid", "Clear", "Clear/cobweb", "Clear"],
["Key test", "Gram/Culture","Virus PCR","AFB/PCR/ADA","India ink/CrAg"],
["Rx", "Ceftriaxone\n+Vancomycin\n+Dexa","Supportive\n±Acyclovir","HRZE x2m\n+HR x7-10m\n+Dexa","AmphB+5FC\nthen Flucon"],
]
qr_col_colors = [NAVY, RED, GREEN, AMBER, PURPLE]
qr_light = [GREY_LIGHT, RED_LIGHT, GREEN_LIGHT, AMBER_LIGHT, PURPLE_LIGHT]
qr_header_row = [Paragraph(h, sTableH) for h in qr_headers]
qr_data = [qr_header_row]
for row in qr_rows:
cells = [Paragraph(row[0], sTableCellB)]
for v in row[1:]:
cells.append(Paragraph(v, sTableCell))
qr_data.append(cells)
qr_cw = [CONTENT_W*0.14] + [CONTENT_W*0.215]*4
qr_tbl = Table(qr_data, colWidths=qr_cw, repeatRows=1)
ts2 = TableStyle([
("BACKGROUND", (0,0), (0,0), NAVY),
("BACKGROUND", (1,0), (1,0), RED),
("BACKGROUND", (2,0), (2,0), GREEN),
("BACKGROUND", (3,0), (3,0), AMBER),
("BACKGROUND", (4,0), (4,0), PURPLE),
("GRID", (0,0), (-1,-1), 0.5, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
])
for r_idx in range(1, len(qr_rows)+1):
ts2.add("BACKGROUND", (1, r_idx), (1, r_idx), RED_LIGHT if r_idx%2 else HexColor("#FDFEFE"))
ts2.add("BACKGROUND", (2, r_idx), (2, r_idx), GREEN_LIGHT if r_idx%2 else HexColor("#FDFEFE"))
ts2.add("BACKGROUND", (3, r_idx), (3, r_idx), AMBER_LIGHT if r_idx%2 else HexColor("#FDFEFE"))
ts2.add("BACKGROUND", (4, r_idx), (4, r_idx), PURPLE_LIGHT if r_idx%2 else HexColor("#FDFEFE"))
ts2.add("BACKGROUND", (0, r_idx), (0, r_idx), GREY_LIGHT if r_idx%2 else WHITE)
qr_tbl.setStyle(ts2)
story += [qr_tbl, Spacer(1, 4),
Paragraph("Rx = first-line empiric treatment. H=Isoniazid, R=Rifampicin, Z=Pyrazinamide, E=Ethambutol. AmphB=Amphotericin B, 5FC=Flucytosine, Flucon=Fluconazole. Dexa=Dexamethasone.", sCaption),
Spacer(1, 12)]
# ── REFERENCES ────────────────────────────────────────────────────────────
story += [rule(GREY_MID), Paragraph("References", sH2)]
refs = [
"Washington Manual of Medical Therapeutics — Typical CSF Findings in Meningitis Table",
"Harrison's Principles of Internal Medicine 22e (2025, McGraw Hill) — Table 143-2: CSF Abnormalities in Bacterial Meningitis",
"Harriet Lane Handbook 23e (Johns Hopkins) — Table 17.8: CSF Analysis in Suspected Meningitis",
"Murray & Nadel's Textbook of Respiratory Medicine — Central Nervous System Tuberculosis, pp.1199–1200",
"Plum and Posner's Diagnosis and Treatment of Stupor and Coma — Tuberculous Meningitis, p.421",
"ROSEN's Emergency Medicine — Tuberculous & Cryptococcal Meningitis",
"Frameworks for Internal Medicine — Meningitis Chapter",
"Tintinalli's Emergency Medicine — Laboratory Testing in Meningitis",
"WHO 2024/2025 Consolidated Guidelines on Tuberculosis — Treatment and Care (Module 4)",
"MSF Medical Guidelines — Adjunctive Corticosteroids in TB (dexamethasone dosing)",
]
for r in refs:
story.append(Paragraph(f"\u2022 {r}", sRef))
story += [Spacer(1, 6), rule(GREY_MID),
Paragraph("Produced by Orris Medical Education • August 2026 • For educational use only. Not a substitute for clinical judgement.", sCaption)]
doc.build(story)
print("PDF generated:", OUTPUT)
cd /tmp/workspace/csf-guide && python build_csf_guide.py 2>&1
ls -lh /tmp/workspace/csf-guide/CSF_Interpretation_Guide.pdf
CSF Interpretation Guide
PDF Document · PDF