A 65-year-old woman is evaluated for worsening urinary urgency and incontinence. The patient has had no abdominal pain, dysuria, or changes in urinary frequency. On several occasions, she has urinated involuntarily, which she attributes to an inability to walk quickly to the bathroom. The patient feels unsteady while ambulating and walks slowly with small steps. Her family also reports that she has become forgetful and has difficulty planning and executing various activities that she used to perform regularly. Medical history is notable for hypertension. The patient does not use tobacco, alcohol, or illicit drugs. Vital signs are within normal limits. Cardiopulmonary, abdominal, and genitourinary examinations show no abnormalities. Neurological examination shows impaired attention and short-term memory. MRI of the brain reveals chronic white matter changes with no infarction, hemorrhage, or mass lesions. There is diffuse ventriculomegaly with no sulcal enlargement. Which of the following is the most likely underlying cause of this patient's urinary incontinence? his patient with impaired memory and cognition, unsteady gait, and urinary incontinence has ventriculomegaly without sulcal enlargement on MRI, which is characteristic of normal pressure hydrocephalus (NPH). NPH can occur due to decreased absorption of cerebrospinal fluid (CSF) by the arachnoid granulations (eg, fibrosis from previous cerebral inflammation or bleeding) with gradual accumulation of CSF in the ventricular system. The lateral ventricles expand, resulting in normal intracranial pressure and stretching the descending cortical fibers (corona radiata). Interruption of these pathways, along with impairment of cortical and subcortical function, leads to the classic triad of NPH symptoms (dementia, gait disturbance, urinary incontinence). The cerebral cortex (through the pontine micturition center) regulates the micturition reflex, which is an autonomic spinal reflex mediated by the neurons in the sacral spinal cord. In NPH, the lack of cortical inhibition results in detrusor muscle hyperactivity. This leads to urinary urgency, which can be worsened by gait abnormalities that limit a patient's ability to quickly get to a bathroom (urge incontinence). Progressive frontal lobe impairment leads to the inability to recognize urinary urge, uncontrolled urination, and a lack of concern as the disease progresses. (Choice A) Cerebellar dysfunction can cause an ataxic gait, dysarthria, and intention tremors. Memory dysfunction and urinary incontinence are more indicative of cerebral impairment. NPH typically spares cerebellar function. (Choice B) Compression of the reticular formation can occur with brainstem herniation, leading to loss of consciousness and coma. (Choice C) Impaired basal ganglia signaling is characteristic of Parkinson disease, which can result in ataxia with a slow gait. However, cogwheel rigidity and resting tremor would be prominent and dementia and urinary incontinence would not be expected until later in the disease course. (Choice D) Spinal cord damage is a common cause of incontinence with gait impairment. However, spinal cord injury also frequently causes lower extremity weakness and is not associated with cognitive impairment. Educational objective: Normal pressure hydrocephalus occurs due to decreased cerebrospinal fluid absorption, resulting in ventriculomegaly with normal intracranial pressure. This causes stretching of the descending cortical fibers (corona radiata) from the frontal lobe and contributes to the classic triad of symptoms (ie, dementia, gait abnormalities, urinary incontinence). Loss of cortical inhibition of the sacral micturition center leads to urge incontinence.
normal pressure hydrocephalus ventriculomegaly MRI corona radiata stretching
| Feature | This Patient |
|---|---|
| Gait disturbance | Unsteady, slow, small steps ("magnetic gait" / apraxic gait) |
| Cognitive impairment | Impaired attention, memory, difficulty planning |
| Urinary incontinence | Urgency-type, unable to reach bathroom in time |

| Choice | Condition | Why Incorrect |
|---|---|---|
| Cerebellar dysfunction | Ataxic gait, dysarthria, intention tremor | Does not cause memory dysfunction or urinary incontinence; NPH typically spares the cerebellum |
| Reticular formation compression | Brainstem herniation → coma | This patient is alert and ambulatory; no herniation syndrome |
| Basal ganglia impairment | Parkinson disease | Would show cogwheel rigidity and resting tremor; dementia and incontinence are late and less prominent features |
| Spinal cord damage | Myelopathy | Causes lower extremity weakness, hyperreflexia, Babinski signs; does NOT cause cognitive impairment |
"A slowly progressive gait disorder is usually the earliest feature, followed by impairment of mental function... and, later, sphincter incontinence."
hey biostatistics usmle step 1 based from foundation to mastering questions how many days tro complete totatl biostats with uworld questions
| Phase | Resource | Why |
|---|---|---|
| Foundation | Boards and Beyond (BnB) — Biostatistics module | Best video-based foundation; Dr. Ryan explains everything from scratch, ~4–5 hours total video |
| Reference | First Aid 2024 — Biostatistics chapter | The "bible" for Step 1; use it alongside every topic as your notes anchor |
| Practice | UWorld Biostatistics + Epidemiology block | ~120–140 questions; do after foundation is solid |
| Reinforcement | Amboss Biostats (optional, if time allows) | Harder questions, great explanations |
| Day | Topics | What to Do |
|---|---|---|
| 1 | Study design basics | Observational vs. experimental; cohort, case-control, cross-sectional, RCT, case reports. BnB video + First Aid. Make a comparison table. |
| 2 | Bias & confounding | Selection bias, recall bias, lead-time bias, length-time bias, confounding, effect modification. These are very high yield on Step 1. |
| 3 | Statistical tests | t-test, ANOVA, chi-square, Fisher exact — know which test for which data type. Parametric vs. non-parametric. |
| 4 | 2×2 table mastery | This is the single most important day. Sensitivity, specificity, PPV, NPV, LR+, LR−. Draw 50+ practice 2×2 tables by hand until it is automatic. |
| 5 | Disease frequency & risk | Incidence, prevalence, relative risk (RR), odds ratio (OR), attributable risk (AR), number needed to treat (NNT), number needed to harm (NNH). |
| 6 | Statistical concepts | P-value, type I error (α), type II error (β), power (1−β), confidence intervals, null hypothesis, precision vs. accuracy. |
| Day | Activity |
|---|---|
| 7 | Full First Aid biostatistics chapter read-through. Annotate everything. Build a master formula sheet (1–2 pages max). |
| 8 | Do your first UWorld block (40 questions, timed). Review every single explanation — correct AND incorrect. Note which topics you got wrong. |
| Day | Activity |
|---|---|
| 9 | UWorld block 2 (40 questions). Focus: study design + bias questions. Rewatch BnB video for any topic you miss repeatedly. |
| 10 | UWorld block 3 (40 questions). Focus: 2×2 table, sensitivity/specificity, PPV/NPV. Draw the table for every question even if you know the answer. |
| 11 | Review all marked/incorrect UWorld questions from days 8–10. Redo them without looking at explanations. |
| 12 | Final UWorld block (remaining questions or a random mixed block). Aim for ≥70% by this point. |
| Day | Activity |
|---|---|
| 13 | Do Amboss biostats questions (20–30) OR redo your weakest UWorld questions. These are harder and sharpen edge cases. |
| 14 | Review your master formula sheet. Timed self-quiz: cover the formula, recall it, write it out. Rest. |
Sensitivity = TP / (TP + FN) → "SnNout" (high Sn rules OUT)
Specificity = TN / (TN + FP) → "SpPin" (high Sp rules IN)
PPV = TP / (TP + FP) → affected by prevalence ↑ → PPV ↑
NPV = TN / (TN + FN) → affected by prevalence ↑ → NPV ↓
RR (cohort) = [a/(a+b)] / [c/(c+d)]
OR (case-ctrl)= (a×d) / (b×c)
AR = Risk(exposed) − Risk(unexposed)
NNT = 1 / ARR
Power = 1 − β (increased by ↑ sample size, ↑ effect size)
P < 0.05 = reject null hypothesis = statistically significant
| Phase | Days | Hours/Day | Total Hours |
|---|---|---|---|
| Foundation (BnB + FA) | 6 | 3–4 | ~21 hrs |
| Consolidation + first UWorld | 2 | 3–4 | ~7 hrs |
| UWorld deep dive | 4 | 3–4 | ~14 hrs |
| Mastery/review | 2 | 3–4 | ~7 hrs |
| Total | 14 days | 3–4 hrs | ~49 hrs |
make a pdf of above will start after completing neuro
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace is empty or does not exist"
mkdir -p /home/daytona/workspace/biostats-plan
~/biostats-plan/generate_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, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import PageBreak
OUTPUT = "/home/daytona/workspace/biostats-plan/USMLE_Step1_Biostatistics_StudyPlan.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm,
leftMargin=2*cm,
topMargin=2.2*cm,
bottomMargin=2.2*cm,
)
W = A4[0] - 4*cm # usable width
# ── Colour palette ───────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1A3557")
TEAL = colors.HexColor("#1B7F8E")
GOLD = colors.HexColor("#E8A020")
LGREY = colors.HexColor("#F4F6F9")
MGREY = colors.HexColor("#D0D7E2")
WHITE = colors.white
RED = colors.HexColor("#C0392B")
GREEN = colors.HexColor("#1E8449")
styles = getSampleStyleSheet()
# Custom styles
def S(name, **kw):
base = kw.pop("parent", "Normal")
s = ParagraphStyle(name, parent=styles[base], **kw)
return s
sTitle = S("sTitle", fontSize=22, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica-Bold", leading=28, spaceAfter=4)
sSubtitle = S("sSubtitle", fontSize=12, textColor=MGREY, alignment=TA_CENTER,
fontName="Helvetica", leading=16)
sH1 = S("sH1", fontSize=14, textColor=WHITE, fontName="Helvetica-Bold",
leading=18, spaceBefore=10, spaceAfter=4)
sH2 = S("sH2", fontSize=11, textColor=NAVY, fontName="Helvetica-Bold",
leading=14, spaceBefore=8, spaceAfter=3, borderPad=3)
sBody = S("sBody", fontSize=9.5, textColor=colors.HexColor("#2C2C2C"),
fontName="Helvetica", leading=14, spaceAfter=3, alignment=TA_JUSTIFY)
sBullet = S("sBullet", fontSize=9.5, textColor=colors.HexColor("#2C2C2C"),
fontName="Helvetica", leading=13, leftIndent=14, firstLineIndent=-10,
spaceAfter=2)
sCode = S("sCode", fontSize=8.5, textColor=colors.HexColor("#1A1A2E"),
fontName="Courier", leading=13, backColor=LGREY,
leftIndent=10, rightIndent=10, spaceAfter=2)
sNote = S("sNote", fontSize=8.5, textColor=TEAL, fontName="Helvetica-Oblique",
leading=12, leftIndent=12, spaceAfter=3)
sSmall = S("sSmall", fontSize=8, textColor=colors.grey,
fontName="Helvetica", leading=10, alignment=TA_CENTER)
def section_header(text, color=NAVY):
"""Coloured band with white text as a section header."""
data = [[Paragraph(text, sH1)]]
t = Table(data, colWidths=[W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("ROWPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 10),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("ROUNDEDCORNERS", [4]),
]))
return t
def h2(text):
return Paragraph(f"<font color='#{TEAL.hexval()[2:]}'>▌</font> {text}", sH2)
def bullet(text, symbol="•"):
return Paragraph(f"<b>{symbol}</b> {text}", sBullet)
def body(text):
return Paragraph(text, sBody)
def note(text):
return Paragraph(f"⚑ {text}", sNote)
def hr():
return HRFlowable(width="100%", thickness=0.5, color=MGREY, spaceAfter=4, spaceBefore=4)
def table(data, col_widths, header_bg=NAVY, stripe=True):
t = Table(data, colWidths=col_widths, repeatRows=1)
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,0), 9),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 8.5),
("ROWPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 7),
("RIGHTPADDING", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, MGREY),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("WORDWRAP", (0,0), (-1,-1), True),
]
if stripe:
for i in range(1, len(data)):
bg = LGREY if i % 2 == 0 else WHITE
style.append(("BACKGROUND", (0,i), (-1,i), bg))
t.setStyle(TableStyle(style))
return t
# ════════════════════════════════════════════════════════════════════════════
# BUILD STORY
# ════════════════════════════════════════════════════════════════════════════
story = []
# ── COVER ────────────────────────────────────────────────────────────────────
cover_data = [[
Paragraph("USMLE Step 1", sTitle),
Paragraph("Biostatistics & Epidemiology", sTitle),
Paragraph("Complete Study Plan", sSubtitle),
Spacer(1, 0.3*cm),
Paragraph("Complete Beginner → UWorld Mastery | 3–4 hrs/day | 14 Days", sSubtitle),
]]
cover_table = Table([[
Paragraph("USMLE Step 1", sTitle),
]], colWidths=[W])
# Actually build cover as a big coloured block
cover = Table([
[Paragraph("USMLE Step 1", sTitle)],
[Paragraph("Biostatistics & Epidemiology", sTitle)],
[Spacer(1,0.2*cm)],
[Paragraph("Complete Study Plan", S("cov2", fontSize=15, textColor=GOLD,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=20))],
[Spacer(1,0.3*cm)],
[Paragraph("Complete Beginner → UWorld Mastery", sSubtitle)],
[Paragraph("3–4 hours/day • 14 Days • Start after Neuro", sSubtitle)],
[Spacer(1,0.4*cm)],
[Paragraph("Generated: April 28, 2026", sSmall)],
], colWidths=[W])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 14),
("BOTTOMPADDING", (0,0), (-1,-1), 14),
("LEFTPADDING", (0,0), (-1,-1), 20),
("RIGHTPADDING", (0,0), (-1,-1), 20),
("ROUNDEDCORNERS", [6]),
]))
story.append(cover)
story.append(Spacer(1, 0.6*cm))
# Quick summary boxes
summary_data = [
["📅 14 Days", "⏱ 3–4 hrs/day", "📊 ~120–140 UWorld Qs", "🎯 Target: ≥70% UWorld"],
]
summary_t = Table(summary_data, colWidths=[W/4]*4)
summary_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("TEXTCOLOR", (0,0), (-1,-1), WHITE),
("FONTNAME", (0,0), (-1,-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWPADDING", (0,0), (-1,-1), 10),
("GRID", (0,0), (-1,-1), 1, WHITE),
("ROUNDEDCORNERS", [4]),
]))
story.append(summary_t)
story.append(Spacer(1, 0.5*cm))
# ── SECTION 1: RECOMMENDED RESOURCES ────────────────────────────────────────
story.append(section_header("📚 Recommended Resources", NAVY))
story.append(Spacer(1, 0.3*cm))
res_data = [
["Phase", "Resource", "Purpose", "Time Est."],
["Foundation", "Boards and Beyond (BnB)\nBiostatistics Module",
"Best video-based intro from scratch; Dr. Ryan explains every concept clearly", "~4–5 hrs video"],
["Reference", "First Aid 2024\nBiostatistics Chapter",
"The Step 1 bible — use alongside every topic; anchor all notes here", "Throughout"],
["Practice", "UWorld\nBiostatistics + Epi Block",
"~120–140 questions; do AFTER foundation is solid", "~8–10 hrs"],
["Reinforcement\n(optional)", "Amboss Biostats",
"Harder questions, excellent explanations; sharpen edge cases", "~2–3 hrs"],
]
story.append(table(res_data, [2.2*cm, 4*cm, 7.2*cm, 2.2*cm], header_bg=TEAL))
story.append(Spacer(1, 0.2*cm))
story.append(note("Start with BnB videos for each topic, then immediately read the corresponding First Aid section."))
story.append(Spacer(1, 0.4*cm))
# ── SECTION 2: DAY-BY-DAY PLAN ──────────────────────────────────────────────
story.append(section_header("📅 Day-by-Day Study Plan", NAVY))
story.append(Spacer(1, 0.3*cm))
# Phase 1
story.append(h2("Phase 1 — Foundations (Days 1–6)"))
story.append(Spacer(1, 0.15*cm))
phase1_data = [
["Day", "Topics", "Activity"],
["Day 1", "Study Design Basics",
"Observational vs. experimental; cohort, case-control, cross-sectional, RCT, case reports, case series.\nBnB video + First Aid. Make a comparison table."],
["Day 2", "Bias & Confounding",
"Selection bias, recall bias, lead-time bias, length-time bias, observer bias, confounding, effect modification.\nVERY high yield — these appear repeatedly on Step 1."],
["Day 3", "Statistical Tests",
"t-test, ANOVA, chi-square, Fisher exact, Pearson/Spearman correlation.\nKnow which test for which data type. Parametric vs. non-parametric."],
["Day 4", "2×2 Table Mastery ⭐",
"Sensitivity, specificity, PPV, NPV, LR+, LR−.\nDraw 50+ practice 2×2 tables by hand until completely automatic. This is the single most important day."],
["Day 5", "Disease Frequency & Risk",
"Incidence, prevalence, relative risk (RR), odds ratio (OR), attributable risk (AR), NNT, NNH.\nPractice calculating each from given data."],
["Day 6", "Statistical Concepts",
"P-value, type I error (α), type II error (β), power (1−β), confidence intervals, null hypothesis, precision vs. accuracy, mean/median/mode."],
]
story.append(table(phase1_data, [1.5*cm, 4*cm, 10.1*cm], header_bg=NAVY))
story.append(Spacer(1, 0.4*cm))
# Phase 2
story.append(h2("Phase 2 — Consolidation (Days 7–8)"))
story.append(Spacer(1, 0.15*cm))
phase2_data = [
["Day", "Activity"],
["Day 7", "Full First Aid biostats chapter read-through. Annotate thoroughly. Build a master formula sheet (1–2 pages max covering all key equations)."],
["Day 8", "First UWorld block (40 questions, tutor mode). Review EVERY explanation — correct and incorrect. Flag weak topics."],
]
story.append(table(phase2_data, [1.5*cm, 14.1*cm], header_bg=TEAL))
story.append(Spacer(1, 0.4*cm))
# Phase 3
story.append(h2("Phase 3 — UWorld Deep Dive (Days 9–12)"))
story.append(Spacer(1, 0.15*cm))
phase3_data = [
["Day", "Focus", "Activity"],
["Day 9", "Study Design + Bias",
"UWorld block 2 (40 Qs). Re-watch BnB for any topic missed repeatedly. Tutor mode."],
["Day 10", "2×2 Table, Sens/Spec, PPV/NPV",
"UWorld block 3 (40 Qs). Draw the 2×2 table for EVERY question, even when you know the answer."],
["Day 11", "Weak Topic Review",
"Review ALL marked/incorrect UWorld questions from Days 8–10. Redo without looking at explanations first."],
["Day 12", "Mixed Timed Block",
"Final UWorld block (remaining Qs or random mixed). Switch to TIMED mode. Target ≥70%."],
]
story.append(table(phase3_data, [1.5*cm, 4*cm, 10.1*cm], header_bg=NAVY))
story.append(Spacer(1, 0.4*cm))
# Phase 4
story.append(h2("Phase 4 — Mastery & Wrap-Up (Days 13–14)"))
story.append(Spacer(1, 0.15*cm))
phase4_data = [
["Day", "Activity"],
["Day 13", "Amboss biostats (20–30 Qs) OR redo weakest UWorld questions. Amboss questions are harder and sharpen edge cases."],
["Day 14", "Review master formula sheet. Timed self-quiz: cover each formula, recall it, write it out from memory. Rest."],
]
story.append(table(phase4_data, [1.5*cm, 14.1*cm], header_bg=TEAL))
story.append(Spacer(1, 0.4*cm))
# ── SECTION 3: TIME SUMMARY ──────────────────────────────────────────────────
story.append(section_header("⏱ Time Estimate Summary", NAVY))
story.append(Spacer(1, 0.3*cm))
time_data = [
["Phase", "Days", "Hrs/Day", "Total Hours"],
["Phase 1 — Foundation (BnB + First Aid)", "6", "3–4", "~21 hrs"],
["Phase 2 — Consolidation + First UWorld", "2", "3–4", "~7 hrs"],
["Phase 3 — UWorld Deep Dive", "4", "3–4", "~14 hrs"],
["Phase 4 — Mastery / Review", "2", "3–4", "~7 hrs"],
["TOTAL", "14", "3–4", "~49 hrs"],
]
t = Table(time_data, colWidths=[9*cm, 2*cm, 2.5*cm, 3.1*cm])
style = [
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 9),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 8.5),
("BACKGROUND", (0,-1), (-1,-1), GOLD),
("TEXTCOLOR", (0,-1), (-1,-1), NAVY),
("FONTNAME", (0,-1), (-1,-1), "Helvetica-Bold"),
("FONTSIZE", (0,-1), (-1,-1), 9),
("ROWPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("GRID", (0,0), (-1,-1), 0.4, MGREY),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (1,0), (-1,-1), "CENTER"),
]
for i in range(1, len(time_data)-1):
bg = LGREY if i % 2 == 0 else WHITE
style.append(("BACKGROUND", (0,i), (-1,i), bg))
t.setStyle(TableStyle(style))
story.append(t)
story.append(Spacer(1, 0.4*cm))
# ── SECTION 4: MASTER FORMULA SHEET ─────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("🧮 Master Formula Sheet", TEAL))
story.append(Spacer(1, 0.3*cm))
story.append(body("Memorise these <b>cold</b> — they appear on almost every Step 1 biostats question."))
story.append(Spacer(1, 0.2*cm))
formula_data = [
["Formula", "Expression", "Key Mnemonic / Note"],
["Sensitivity", "TP / (TP + FN)", "SnNout — high Sensitivity rules OUT disease"],
["Specificity", "TN / (TN + FP)", "SpPin — high Specificity rules IN disease"],
["PPV", "TP / (TP + FP)", "Prevalence ↑ → PPV ↑"],
["NPV", "TN / (TN + FN)", "Prevalence ↑ → NPV ↓"],
["Relative Risk", "[a/(a+b)] / [c/(c+d)]", "Used in cohort studies"],
["Odds Ratio", "(a × d) / (b × c)", "Used in case-control studies; approximates RR when disease is rare"],
["Attributable Risk (AR)", "Risk(exposed) − Risk(unexposed)", "Also called Risk Difference"],
["NNT", "1 / ARR", "ARR = Absolute Risk Reduction = AR in control − AR in treatment"],
["NNH", "1 / ARI", "ARI = Absolute Risk Increase"],
["Power", "1 − β", "↑ sample size → ↑ power; ↑ effect size → ↑ power"],
["P-value < 0.05", "Reject null hypothesis", "= statistically significant; NOT the same as clinically significant"],
["95% CI", "Does not cross 1 (RR/OR) or 0 (difference)", "If CI crosses null → not significant"],
]
story.append(table(formula_data, [3.5*cm, 4.5*cm, 7.6*cm], header_bg=TEAL))
story.append(Spacer(1, 0.3*cm))
story.append(h2("The 2×2 Table — Draw This for Every Question"))
story.append(Spacer(1, 0.15*cm))
grid_data = [
["", "Disease +", "Disease −", "Total"],
["Test +", "TP (a)", "FP (b)", "a + b"],
["Test −", "FN (c)", "TN (d)", "c + d"],
["Total", "a + c", "b + d", "a+b+c+d"],
]
gt = Table(grid_data, colWidths=[3*cm, 3*cm, 3*cm, 2.6*cm])
gt.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("BACKGROUND", (0,0), (0,-1), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("TEXTCOLOR", (0,1), (0,-1), WHITE),
("FONTNAME", (0,0), (-1,-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWPADDING", (0,0), (-1,-1), 9),
("GRID", (0,0), (-1,-1), 1, MGREY),
("BACKGROUND", (1,1), (1,1), colors.HexColor("#D5F5E3")),
("BACKGROUND", (2,2), (2,2), colors.HexColor("#D5F5E3")),
("BACKGROUND", (1,2), (1,2), colors.HexColor("#FADBD8")),
("BACKGROUND", (2,1), (2,1), colors.HexColor("#FADBD8")),
]))
story.append(gt)
story.append(Spacer(1, 0.15*cm))
story.append(note("Green = correct results (TP, TN). Red = errors (FP, FN). Always fill in this table before calculating anything."))
story.append(Spacer(1, 0.4*cm))
# ── SECTION 5: HIGH YIELD TOPICS ─────────────────────────────────────────────
story.append(section_header("⭐ Highest-Yield Topics for Step 1", NAVY))
story.append(Spacer(1, 0.3*cm))
hy_data = [
["Rank", "Topic", "Why It Matters"],
["1", "2×2 table calculations",
"Appears on almost every exam. Non-negotiable. Sensitivity, specificity, PPV, NPV must be automatic."],
["2", "Sensitivity vs. Specificity",
"Especially: which is used for screening vs. confirmatory tests (SnNout / SpPin)."],
["3", "Study design identification",
"'Which study design is described?' — cohort, case-control, cross-sectional, RCT, etc."],
["4", "Bias recognition",
"Lead-time bias, recall bias, selection bias, and observer bias appear frequently in clinical vignettes."],
["5", "NNT / NNH",
"Always in clinical scenarios. Must be able to calculate quickly from a table."],
["6", "Power & sample size",
"Type I vs. Type II error; what increases power. Frequently tested conceptually."],
["7", "RR vs. OR",
"When each is used, how to calculate, and when OR approximates RR (rare disease assumption)."],
]
story.append(table(hy_data, [1*cm, 4.5*cm, 10.1*cm], header_bg=NAVY))
story.append(Spacer(1, 0.4*cm))
# ── SECTION 6: KEY TIPS ───────────────────────────────────────────────────────
story.append(section_header("💡 Key Tips for Success", TEAL))
story.append(Spacer(1, 0.3*cm))
tips = [
("Draw the 2×2 table for every question",
"Even when you know the answer. It becomes muscle memory and prevents careless errors."),
("Tutor mode first, then timed",
"Do UWorld in tutor mode in Phase 3, then switch to timed mode in Day 12 to simulate exam pressure."),
("Biostats = reliable points",
"Unlike clinical sciences, the formulas never change. Time invested here directly translates to score."),
("Prevalence affects PPV/NPV — not Sensitivity/Specificity",
"This distinction is tested constantly. Sensitivity and specificity are intrinsic properties of the test."),
("Lead-time bias traps",
"Survival appears longer after early screening even if outcome is unchanged — recognise this scenario."),
("P < 0.05 ≠ clinically significant",
"Statistical significance does not equal clinical importance. Step 1 loves testing this distinction."),
("Extra time? Focus on bias and study design",
"Most conceptually tricky and most commonly missed. These are 'free points' once you understand them."),
]
for title, desc in tips:
tip_row = Table([[
Paragraph(f"<b>{title}</b>", S("tipT", fontSize=9, textColor=NAVY,
fontName="Helvetica-Bold", leading=13)),
Paragraph(desc, S("tipD", fontSize=9, textColor=colors.HexColor("#2C2C2C"),
fontName="Helvetica", leading=13)),
]], colWidths=[5.5*cm, 10.1*cm])
tip_row.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (0,-1), 10),
("BACKGROUND", (0,0), (-1,-1), LGREY),
("LINEBELOW", (0,0), (-1,-1), 0.5, MGREY),
("ROUNDEDCORNERS", [3]),
]))
story.append(tip_row)
story.append(Spacer(1, 0.08*cm))
story.append(Spacer(1, 0.5*cm))
# ── FOOTER NOTE ──────────────────────────────────────────────────────────────
footer_data = [[
Paragraph(
"Start this plan <b>after completing Neurology</b>. "
"Keep this sheet handy during every UWorld review session. Good luck! 🎯",
S("footer", fontSize=9.5, textColor=WHITE, fontName="Helvetica",
leading=14, alignment=TA_CENTER)
)
]]
ft = Table(footer_data, colWidths=[W])
ft.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("ROWPADDING", (0,0), (-1,-1), 12),
("LEFTPADDING", (0,0), (-1,-1), 16),
("RIGHTPADDING", (0,0), (-1,-1), 16),
("ROUNDEDCORNERS", [5]),
]))
story.append(ft)
# ── BUILD PDF ──────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")
python /home/daytona/workspace/biostats-plan/generate_pdf.py
ls -lh /home/daytona/workspace/biostats-plan/USMLE_Step1_Biostatistics_StudyPlan.pdf
USMLE Step1 Biostatistics StudyPlan
PDF Document · PDF
so there are almost 5 questions on multiple sclerosis checking differently i want to add all together as a pdf and you can make HY superconsice notes asusal okay ?
~/biostats-plan/ms_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
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
OUTPUT = "/home/daytona/workspace/biostats-plan/MS_USMLE_Step1_HY_Notes.pdf"
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
rightMargin=1.8*cm, leftMargin=1.8*cm,
topMargin=2*cm, bottomMargin=2*cm,
)
W = A4[0] - 3.6*cm
# ── Palette ──────────────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1A3557")
TEAL = colors.HexColor("#1B7F8E")
GOLD = colors.HexColor("#E8A020")
RED = colors.HexColor("#C0392B")
GREEN = colors.HexColor("#1E8449")
PURPLE = colors.HexColor("#6C3483")
ORANGE = colors.HexColor("#CA6F1E")
LGREY = colors.HexColor("#F4F6F9")
MGREY = colors.HexColor("#D0D7E2")
WHITE = colors.white
DKTEXT = colors.HexColor("#1C1C1C")
def S(name, **kw):
s = ParagraphStyle(name, parent=getSampleStyleSheet()["Normal"], **kw)
return s
sH1 = S("H1", fontSize=13, textColor=WHITE, fontName="Helvetica-Bold",
leading=18, spaceBefore=0, spaceAfter=0)
sH2 = S("H2", fontSize=10.5, textColor=NAVY, fontName="Helvetica-Bold",
leading=14, spaceBefore=6, spaceAfter=2)
sBody = S("Body", fontSize=9, textColor=DKTEXT, fontName="Helvetica",
leading=13, spaceAfter=2, alignment=TA_JUSTIFY)
sBull = S("Bull", fontSize=9, textColor=DKTEXT, fontName="Helvetica",
leading=13, leftIndent=12, firstLineIndent=-10, spaceAfter=1)
sQ = S("Q", fontSize=9.5, textColor=NAVY, fontName="Helvetica-Bold",
leading=13, spaceAfter=2)
sA = S("A", fontSize=9, textColor=colors.HexColor("#1A5276"), fontName="Helvetica",
leading=13, leftIndent=10, spaceAfter=2)
sExp = S("Exp", fontSize=8.5, textColor=colors.HexColor("#424242"), fontName="Helvetica-Oblique",
leading=12, leftIndent=10, spaceAfter=4)
sSmall= S("Small", fontSize=7.5, textColor=colors.grey, fontName="Helvetica",
leading=10, alignment=TA_CENTER)
sCode = S("Code", fontSize=8.5, textColor=DKTEXT, fontName="Courier",
leading=12, leftIndent=8, spaceAfter=2, backColor=LGREY)
sTip = S("Tip", fontSize=8.5, textColor=GREEN, fontName="Helvetica-Bold",
leading=12, spaceBefore=2)
def section_bar(text, color=NAVY, emoji=""):
full = f"{emoji} {text}" if emoji else text
tbl = Table([[Paragraph(full, sH1)]], colWidths=[W])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), color),
("LEFTPADDING", (0,0),(-1,-1), 10),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
]))
return tbl
def h2(text): return Paragraph(text, sH2)
def body(text): return Paragraph(text, sBody)
def bull(text): return Paragraph(f"• {text}", sBull)
def redbull(text): return Paragraph(f"<font color='#C0392B'>▶</font> {text}", sBull)
def greenbull(text): return Paragraph(f"<font color='#1E8449'>✓</font> {text}", sBull)
def tip(text): return Paragraph(f"⭐ {text}", sTip)
def sp(n=0.2): return Spacer(1, n*cm)
def hr(): return HRFlowable(width="100%", thickness=0.4, color=MGREY, spaceAfter=3, spaceBefore=3)
def qa_block(num, question, answer, explanation, q_color=NAVY):
rows = [
[Paragraph(f"Q{num}", S(f"qnum{num}", fontSize=9, textColor=WHITE,
fontName="Helvetica-Bold", leading=12, alignment=TA_CENTER)),
Paragraph(question, sQ)],
["", Paragraph(f"<b>Answer:</b> {answer}", sA)],
["", Paragraph(explanation, sExp)],
]
t = Table(rows, colWidths=[0.8*cm, W - 0.8*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(0,0), q_color),
("BACKGROUND", (0,1),(0,-1), LGREY),
("VALIGN", (0,0),(-1,-1), "TOP"),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 6),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING", (0,0),(-1,-1), 5),
("GRID", (0,0),(-1,-1), 0.3, MGREY),
("ROWSPAN", (0,0),(0,2)),
]))
return t
def hy_table(data, col_widths, hdr_color=NAVY):
t = Table(data, colWidths=col_widths, repeatRows=1)
style = [
("BACKGROUND", (0,0),(-1,0), hdr_color),
("TEXTCOLOR", (0,0),(-1,0), WHITE),
("FONTNAME", (0,0),(-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0),(-1,0), 8.5),
("FONTNAME", (0,1),(-1,-1), "Helvetica"),
("FONTSIZE", (0,1),(-1,-1), 8.5),
("ROWPADDING", (0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 6),
("GRID", (0,0),(-1,-1), 0.4, MGREY),
("VALIGN", (0,0),(-1,-1), "TOP"),
]
for i in range(1, len(data)):
style.append(("BACKGROUND",(0,i),(-1,i), LGREY if i%2==0 else WHITE))
t.setStyle(TableStyle(style))
return t
# ════════════════════════════════════════════════════════════════════════════
story = []
# ── COVER ────────────────────────────────────────────────────────────────────
cover = Table([
[Paragraph("USMLE Step 1", S("ct1", fontSize=20, textColor=GOLD,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=26))],
[Paragraph("Multiple Sclerosis", S("ct2", fontSize=24, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=30))],
[sp(0.3)],
[Paragraph("High-Yield Concise Notes + 5-Angle Vignette Review", S("ct3", fontSize=12,
textColor=MGREY, fontName="Helvetica", alignment=TA_CENTER, leading=16))],
[sp(0.2)],
[Paragraph("Pathology • Clinical Features • Diagnosis • Pharmacology • Special Signs",
S("ct4", fontSize=9.5, textColor=GOLD, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=14))],
[sp(0.4)],
[Paragraph("Generated: April 28, 2026", S("ctd", fontSize=8, textColor=MGREY,
fontName="Helvetica", alignment=TA_CENTER))],
], colWidths=[W])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), NAVY),
("TOPPADDING", (0,0),(-1,-1), 16),
("BOTTOMPADDING", (0,0),(-1,-1), 16),
("LEFTPADDING", (0,0),(-1,-1), 24),
("RIGHTPADDING", (0,0),(-1,-1), 24),
]))
story.append(cover)
story.append(sp(0.5))
# ── QUICK STATS BAR ──────────────────────────────────────────────────────────
stats = Table([["Young ♀ 20–40 yrs", "White matter demyelination", "Relapsing-Remitting (85%)", "McDonald Criteria 2017"]],
colWidths=[W/4]*4)
stats.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), TEAL),
("TEXTCOLOR", (0,0),(-1,-1), WHITE),
("FONTNAME", (0,0),(-1,-1), "Helvetica-Bold"),
("FONTSIZE", (0,0),(-1,-1), 8.5),
("ALIGN", (0,0),(-1,-1), "CENTER"),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("ROWPADDING", (0,0),(-1,-1), 8),
("GRID", (0,0),(-1,-1), 1, WHITE),
]))
story.append(stats)
story.append(sp(0.5))
# ════════════════════════════════════════════════════════════════════════════
# SECTION 1 — RAPID HY OVERVIEW
# ════════════════════════════════════════════════════════════════════════════
story.append(section_bar("SECTION 1 — High-Yield Rapid Overview", NAVY, "⚡"))
story.append(sp(0.3))
story.append(h2("Basics"))
for b in [
"<b>Type:</b> CNS demyelinating disease — autoimmune T-cell attack on oligodendrocytes → myelin loss",
"<b>Location:</b> Periventricular white matter, optic nerves, brainstem, spinal cord (cervical > thoracic)",
"<b>Demographics:</b> Women > Men (2:1); onset age 20–40; Northern latitudes; HLA-DR2 association",
"<b>Pathology:</b> Plaques = areas of demyelination + axonal loss + gliosis (sclerosis). Relative sparing of axons early. Periventricular lesions classically perpendicular to ventricles = <b>'Dawson fingers'</b> on MRI",
"<b>CSF:</b> Mild lymphocytic pleocytosis; ↑ IgG index; <b>oligoclonal bands</b> in CSF but NOT serum (intrathecal synthesis) — present in >95%",
]:
story.append(bull(b))
story.append(sp(0.2))
story.append(h2("Clinical Presentation — Disseminated in Space and Time"))
ons_data = [
["System", "Symptom", "Mechanism"],
["Visual", "Optic neuritis — painful unilateral vision loss; RAPD (Marcus-Gunn pupil); temporal pallor of disc later",
"Demyelination of optic nerve"],
["Brainstem", "INO (internuclear ophthalmoplegia) — impaired adduction ipsilateral eye + abducting nystagmus contralateral eye",
"MLF lesion; bilateral INO = pathognomonic of MS"],
["Motor", "Spastic weakness, hyperreflexia, Babinski sign — legs > arms",
"Corticospinal tract lesion"],
["Sensory", "Paresthesias, numbness, dysesthesias; loss of vibration/proprioception",
"Posterior column / dorsal spinal lesion"],
["Cerebellum", "Ataxia, intention tremor, nystagmus, scanning speech (DANISH mnemonic)",
"Cerebellar / spinocerebellar tract lesion"],
["Bladder", "Urgency, frequency, incontinence (detrusor overactivity)",
"Frontal/spinal tract lesion"],
["Fatigue", "Most common overall symptom; worsens with heat",
"Diffuse CNS dysfunction"],
]
story.append(hy_table(ons_data, [2.2*cm, 6.5*cm, 5*cm], NAVY))
story.append(sp(0.2))
story.append(h2("Classic Signs — Step 1 Loves These"))
signs_data = [
["Sign", "What It Is", "Mechanism"],
["Lhermitte's Sign", "Electric shock down spine on neck flexion", "Demyelination of cervical posterior columns"],
["Uhthoff's Phenomenon", "Symptom worsening with heat / exercise / fever", "↑ temp → ↓ conduction in demyelinated axons"],
["INO", "Bilateral INO = pathognomonic of MS", "MLF (medial longitudinal fasciculus) lesion"],
["Trigeminal Neuralgia", "Young patient with facial pain — think MS", "CN V nerve root demyelination"],
["Marcus-Gunn Pupil", "RAPD — affected eye dilates to direct light (swinging flashlight test)", "Optic nerve demyelination"],
]
story.append(hy_table(signs_data, [3*cm, 5.5*cm, 5.2*cm], TEAL))
story.append(sp(0.3))
# ── Disease Course ────────────────────────────────────────────────────────────
story.append(h2("Disease Course — Know All 4 Types"))
course_data = [
["Type", "Pattern", "% Cases", "Key Fact"],
["Relapsing-Remitting (RRMS)", "Attacks + complete/partial recovery", "85%", "Most common; most DMTs target this"],
["Secondary Progressive (SPMS)", "RRMS → gradual progression ± relapses", "—", "Evolves from RRMS over years"],
["Primary Progressive (PPMS)", "Gradual worsening from onset, no relapses", "10–15%", "Ocrelizumab = only FDA-approved DMT"],
["Progressive-Relapsing (PRMS)", "Steady progression + superimposed relapses", "Rare", "Worst prognosis"],
]
story.append(hy_table(course_data, [4*cm, 4.5*cm, 1.8*cm, 3.4*cm], PURPLE))
story.append(sp(0.3))
# ── DIAGNOSIS ────────────────────────────────────────────────────────────────
story.append(h2("Diagnosis — McDonald Criteria 2017 (Simplified)"))
for b in [
"Requires <b>Dissemination in Space (DIS)</b> + <b>Dissemination in Time (DIT)</b>",
"<b>DIS:</b> ≥2 lesions in ≥2 CNS regions: periventricular, cortical/juxtacortical, infratentorial, spinal cord",
"<b>DIT:</b> New T2 or Gd-enhancing lesion on follow-up MRI; OR simultaneous Gd+ and Gd− lesions; OR 2nd clinical attack",
"<b>MRI:</b> T2/FLAIR hyperintense periventricular lesions; Dawson fingers; Gd-enhancement = active lesion",
"<b>CSF:</b> Oligoclonal bands (CSF only, not serum) — present >95%; ↑ IgG index; mild lymphocytosis",
"<b>VEPs:</b> Prolonged P100 latency in optic neuritis (demyelination slows conduction)",
]:
story.append(bull(b))
story.append(sp(0.1))
story.append(tip("Key trap: Oligoclonal bands in BOTH CSF and serum = NOT MS (suggests systemic inflammation). MS = CSF bands only."))
story.append(sp(0.3))
# ════════════════════════════════════════════════════════════════════════════
# SECTION 2 — PHARMACOLOGY
# ════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(section_bar("SECTION 2 — Pharmacology (Disease-Modifying Therapy)", TEAL, "💊"))
story.append(sp(0.3))
story.append(h2("Acute Relapse Treatment"))
for b in [
"<b>IV methylprednisolone (high-dose corticosteroids)</b> — shortens duration of relapse; does NOT change long-term outcome",
"Oral prednisone NOT preferred for optic neuritis (may ↑ recurrence risk)",
]:
story.append(bull(b))
story.append(sp(0.25))
story.append(h2("Disease-Modifying Therapies (DMTs) — Step 1 HY Drug Table"))
dmt_data = [
["Drug", "Class / MOA", "Route", "Key SE / Pearls"],
["Interferon β-1a\nInterferon β-1b",
"Immunomodulator; ↓ T-cell activation, ↓ BBB permeability",
"SC / IM",
"Flu-like symptoms, injection site reactions, ↑LFTs, depression. NO ↑PML risk."],
["Glatiramer acetate\n(Copaxone)",
"Synthetic myelin-like polypeptide; decoy for T-cell attack",
"SC",
"Postinjection reaction: flushing, chest pain, anxiety (self-limiting). No systemic SE."],
["Natalizumab\n(Tysabri)",
"Anti-α4 integrin mAb → blocks leukocyte transmigration across BBB",
"IV",
"⚠ PML (JC virus reactivation) — check JC antibody status. Only as monotherapy."],
["Fingolimod\n(Gilenya)",
"S1P receptor modulator → sequesters lymphocytes in lymph nodes",
"Oral",
"⚠ First-dose bradycardia (monitor 6 hrs); macular edema; ↑PML risk; teratogenic."],
["Dimethyl fumarate\n(Tecfidera)",
"Nrf2 activator → antioxidant pathway; ↓ lymphocyte activity",
"Oral",
"Flushing, GI upset (take with food). ↑ PML risk. ↑ LFTs."],
["Ocrelizumab\n(Ocrevus)",
"Anti-CD20 mAb → B-cell depletion (similar to rituximab)",
"IV",
"Only DMT for PRIMARY PROGRESSIVE MS. Infusion reactions, ↑ infection risk."],
["Alemtuzumab\n(Lemtrada)",
"Anti-CD52 mAb → depletes T/B cells, NK cells, monocytes",
"IV",
"Reserved for severe/refractory cases. ⚠ Autoimmune disorders (ITP, Goodpasture), stroke, malignancy."],
["Teriflunomide\n(Aubagio)",
"Pyrimidine synthesis inhibitor → ↓ lymphocyte proliferation",
"Oral",
"Teratogenic (Category X). ↑ LFTs. Alopecia."],
["Cladribine\n(Mavenclad)",
"Purine analogue antimetabolite → cytotoxic to B/T lymphocytes",
"Oral",
"Teratogenic; ↑ infection risk; potential malignancy."],
["Dalfampridine\n(Ampyra)",
"K+ channel blocker → ↑ conduction in demyelinated axons",
"Oral",
"Symptomatic: improves walking speed. ⚠ Seizure risk (dose-dependent). NOT a DMT."],
]
story.append(hy_table(dmt_data, [3.2*cm, 4.8*cm, 1.5*cm, 4.2*cm], TEAL))
story.append(sp(0.2))
story.append(tip("PML mnemonic: Natalizumab, Fingolimod, Dimethyl fumarate, Ocrelizumab all carry PML risk. Interferons and glatiramer do NOT."))
story.append(sp(0.3))
story.append(h2("Symptomatic Treatment"))
symp_data = [
["Symptom", "Drug"],
["Spasticity", "Baclofen (↑ GABA-B) or tizanidine; NEVER stop baclofen abruptly (withdrawal seizures)"],
["Fatigue", "Amantadine, modafinil"],
["Bladder urgency", "Anticholinergics: oxybutynin, tolterodine; or mirabegron (β3 agonist)"],
["Neuropathic pain", "Gabapentin, pregabalin, TCAs (amitriptyline)"],
["Walking speed", "Dalfampridine (K+ channel blocker)"],
["Acute optic neuritis", "IV methylprednisolone"],
["Depression", "SSRIs (fluoxetine, sertraline); avoid paroxetine"],
["Trigeminal neuralgia", "Carbamazepine, oxcarbazepine, baclofen"],
]
story.append(hy_table(symp_data, [4*cm, 9.7*cm], PURPLE))
story.append(sp(0.3))
# ════════════════════════════════════════════════════════════════════════════
# SECTION 3 — 5 VIGNETTE Q&A
# ════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(section_bar("SECTION 3 — 5-Angle USMLE Vignette Review", NAVY, "🩺"))
story.append(sp(0.3))
story.append(body("Each vignette targets a different way Step 1 tests MS. Read the question, answer, and explanation for each."))
story.append(sp(0.25))
# Q1 — Pathology / mechanism
story.append(KeepTogether([
section_bar("Angle 1 — Pathology & Mechanism", PURPLE, "🔬"),
sp(0.2),
qa_block(1,
"A 28-year-old woman presents with a 3-week history of blurred vision in her right eye and pain with eye movement. Examination reveals a right relative afferent pupillary defect. She had an episode of right leg weakness 18 months ago that resolved spontaneously. MRI shows periventricular T2-hyperintense lesions oriented perpendicular to the ventricles and a gadolinium-enhancing lesion in the right optic nerve. Which cell type is primarily damaged in this disease?",
"Oligodendrocytes",
"MS is an autoimmune demyelinating disease in which CD4+ T cells (Th1) attack oligodendrocytes — the CNS myelin-producing cells. The result is demyelination → slowed/blocked conduction. Schwann cells produce PNS myelin (attacked in Guillain-Barré). The perpendicular periventricular lesions = 'Dawson fingers' on FLAIR MRI. Gadolinium-enhancing = active/acute lesion (BBB breakdown). Non-enhancing = chronic/old lesion."
),
sp(0.15),
]))
# Q2 — Clinical diagnosis / signs
story.append(KeepTogether([
section_bar("Angle 2 — Clinical Features & Classic Signs", TEAL, "👁"),
sp(0.2),
qa_block(2,
"A 32-year-old man notes that his vision blurs and his legs feel weaker after a hot shower. Two years ago he had an episode of tingling in both hands that resolved. Neurological exam shows bilateral impaired adduction of both eyes on lateral gaze with nystagmus of the abducting eye. Which finding on exam is most specific for MS, and what sign describes worsening symptoms with heat?",
"Bilateral INO (bilateral MLF lesion) = pathognomonic for MS. Heat worsening = Uhthoff's phenomenon.",
"INO = lesion in the medial longitudinal fasciculus (MLF). Unilateral INO: think stroke or tumor. Bilateral INO in a young patient = MS until proven otherwise. Uhthoff's phenomenon: even a 0.5°C rise in body temperature slows conduction in already demyelinated axons — hence symptom worsening with exercise, fever, or hot baths. This is NOT a sign of disease progression; symptoms return to baseline on cooling. Lhermitte's sign = electric shock down spine on neck flexion = posterior column cervical MS plaque."
),
sp(0.15),
]))
# Q3 — CSF / Diagnosis
story.append(KeepTogether([
section_bar("Angle 3 — CSF Findings & Diagnosis", GOLD, "🔭"),
sp(0.2),
qa_block(3,
"A 26-year-old woman has had two episodes of neurological dysfunction: one involving right leg weakness (6 months ago) and one involving left eye pain with vision loss (current). MRI shows ≥4 periventricular T2 lesions. Lumbar puncture is performed. Which CSF finding is most expected, and what does it indicate?",
"Oligoclonal IgG bands present in CSF but ABSENT in serum → intrathecal IgG synthesis",
"Oligoclonal bands (OCBs) are present in >95% of MS patients. The key: bands must be in CSF only — if identical bands appear in both CSF AND serum, this suggests systemic inflammation (e.g., sarcoidosis, Lyme, SLE) NOT MS. The IgG index = (CSF IgG / serum IgG) ÷ (CSF albumin / serum albumin) > 0.7 in MS. CSF may also show mild lymphocytosis (<50 cells), mildly elevated protein, normal glucose. VEPs show prolonged P100 latency (demyelination slows optic nerve conduction)."
),
sp(0.15),
]))
# Q4 — Pharmacology
story.append(PageBreak())
story.append(section_bar("Angle 4 — Pharmacology & Drug Side Effects", RED, "💉"))
story.append(sp(0.2))
story.append(qa_block(4,
"A 30-year-old woman with relapsing-remitting MS is started on natalizumab after failing interferon beta therapy. Three months later, she develops progressive cognitive decline, motor weakness, and MRI shows a large non-enhancing white matter lesion in the right hemisphere. What is the most likely complication, and which drug class does NOT carry this risk?",
"Progressive multifocal leukoencephalopathy (PML) due to JC virus reactivation. Interferons and glatiramer do NOT cause PML.",
"Natalizumab blocks α4-integrin, preventing lymphocyte trafficking across the BBB — but this also prevents immune surveillance in the CNS, allowing JC virus (John Cunningham virus) to reactivate → PML, a rapidly progressive and potentially fatal demyelinating disease. PML risk is also seen with fingolimod, dimethyl fumarate, and ocrelizumab. Interferons (β-1a, β-1b) and glatiramer acetate do NOT increase PML risk. Fingolimod also causes first-dose bradycardia (observe 6 hrs) and macular edema. Ocrelizumab (anti-CD20) is the ONLY DMT approved for PRIMARY PROGRESSIVE MS."
))
story.append(sp(0.25))
# Q5 — Differentials / distinguishing MS
story.append(section_bar("Angle 5 — Differentials: MS vs. Mimics", ORANGE, "⚖"))
story.append(sp(0.2))
story.append(qa_block(5,
"A 35-year-old woman presents with bilateral sequential optic neuritis and acute transverse myelitis affecting the entire spinal cord (complete cord syndrome). MRI spine shows a long lesion spanning ≥3 vertebral segments. Brain MRI shows no periventricular lesions. CSF shows no oligoclonal bands. Which diagnosis should be considered instead of MS, and what antibody confirms it?",
"Neuromyelitis optica spectrum disorder (NMOSD) — confirmed by anti-AQP4 antibody (aquaporin-4 IgG)",
"NMOSD (Devic disease) mimics MS but has key differences: (1) long spinal cord lesions ≥3 segments (MS: short <2 segments), (2) bilateral or sequential optic neuritis (MS: typically unilateral), (3) area postrema involvement → intractable hiccups/nausea, (4) NO periventricular Dawson fingers, (5) OCBs usually ABSENT, (6) anti-AQP4 (NMO-IgG) antibody positive. MOG antibody disease (MOGAD) is another mimic. Key treatment difference: natalizumab can WORSEN NMOSD — avoid it. NMOSD treated with azathioprine, mycophenolate, rituximab, eculizumab, or inebilizumab."
))
story.append(sp(0.3))
# ════════════════════════════════════════════════════════════════════════════
# SECTION 4 — MUST-KNOW COMPARISON TABLE
# ════════════════════════════════════════════════════════════════════════════
story.append(section_bar("SECTION 4 — MS vs. Mimics: Must-Know Comparison", NAVY, "⚖"))
story.append(sp(0.3))
comp_data = [
["Feature", "MS", "NMOSD", "GBS", "ADEM"],
["Age", "20–40 F", "Young F; AAs > whites", "Any age post-infection", "Children, post-viral"],
["Lesion type", "Short, periventricular\nDawson fingers",
"Long ≥3 seg cord\nArea postrema", "PNS demyelination\n(ascending paralysis)", "Bilateral, diffuse cortical\n+ subcortical"],
["OCBs", ">95% positive", "Usually absent", "Absent", "Usually absent"],
["Key antibody", "None specific", "Anti-AQP4; anti-MOG", "Anti-GQ1b (Miller-Fisher)","None specific"],
["Course", "Relapsing-remitting", "Severe attacks; no remission", "Monophasic; GBS", "Monophasic"],
["MRI brain", "Periventricular\nwhite matter T2 lesions", "Usually normal", "Normal", "Diffuse white matter edema"],
["Treatment", "DMTs + steroids", "Steroids; rituximab\n(avoid natalizumab!)", "IVIG or plasma exchange", "High-dose IV steroids"],
]
story.append(hy_table(comp_data, [2.5*cm, 3.5*cm, 3.5*cm, 3*cm, 2.2*cm], NAVY))
story.append(sp(0.3))
# ════════════════════════════════════════════════════════════════════════════
# SECTION 5 — RAPID FIRE MNEMONICS
# ════════════════════════════════════════════════════════════════════════════
story.append(section_bar("SECTION 5 — Rapid-Fire Mnemonics & Memory Hooks", TEAL, "🧠"))
story.append(sp(0.3))
mnems = [
("DANISH (cerebellar signs)",
"Dysdiadochokinesia, Ataxia, Nystagmus, Intention tremor, Scanning speech, Hypotonia"),
("SnNout / SpPin for diagnosis",
"OCBs: highly SENSITIVE (>95%) — absent OCBs argue against MS. MRI is very SENSITIVE for lesions."),
("Dawson Fingers",
"Periventricular MS plaques perpendicular to ventricles on sagittal FLAIR — look like fingers pointing away from ventricles"),
("PML drugs: NFDO",
"Natalizumab, Fingolimod, Dimethyl fumarate, Ocrelizumab — all carry PML risk. Interferons and glatiramer are SAFE."),
("INO = MLF = MS",
"Bilateral INO in a young adult = MS until proven otherwise. MLF = medial longitudinal fasciculus."),
("Uhthoff = Unwanted heat",
"Heat → worsens demyelinated conduction → symptoms flare. Not disease progression — reverses on cooling."),
("Lhermitte = Lightning on bending",
"Neck flexion → electric shock sensation down the spine = posterior column cervical lesion."),
("Only PPMS drug",
"Ocrelizumab (anti-CD20) = ONLY FDA-approved drug for Primary Progressive MS."),
("Baclofen rule",
"Never stop baclofen abruptly → withdrawal can cause seizures and autonomic instability."),
("Optic neuritis aphorism",
"'Patient sees nothing, doctor sees nothing' — inflammation is retrobulbar (behind disc) → normal fundus exam initially."),
]
mnem_data = [["Mnemonic / Hook", "Meaning"]] + [[Paragraph(f"<b>{m[0]}</b>", S(f"m{i}", fontSize=8.5, textColor=NAVY, fontName="Helvetica-Bold", leading=12)), Paragraph(m[1], sBody)] for i, m in enumerate(mnems)]
mt = Table(mnem_data, colWidths=[4.8*cm, 8.9*cm])
ms_style = [
("BACKGROUND", (0,0),(-1,0), TEAL),
("TEXTCOLOR", (0,0),(-1,0), WHITE),
("FONTNAME", (0,0),(-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0),(-1,0), 8.5),
("FONTNAME", (0,1),(-1,-1), "Helvetica"),
("FONTSIZE", (0,1),(-1,-1), 8.5),
("ROWPADDING", (0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 7),
("GRID", (0,0),(-1,-1), 0.4, MGREY),
("VALIGN", (0,0),(-1,-1), "TOP"),
]
for i in range(1, len(mnem_data)):
ms_style.append(("BACKGROUND",(0,i),(-1,i), LGREY if i%2==0 else WHITE))
mt.setStyle(TableStyle(ms_style))
story.append(mt)
story.append(sp(0.4))
# ── FOOTER ────────────────────────────────────────────────────────────────────
footer = Table([[
Paragraph(
"Review this sheet before UWorld MS questions. "
"Pair with First Aid Neurology chapter and Boards & Beyond MS video. Good luck! 🎯",
S("ft", fontSize=9, textColor=WHITE, fontName="Helvetica",
leading=13, alignment=TA_CENTER)
)
]], colWidths=[W])
footer.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), NAVY),
("ROWPADDING", (0,0),(-1,-1), 12),
("LEFTPADDING", (0,0),(-1,-1), 16),
("RIGHTPADDING", (0,0),(-1,-1), 16),
]))
story.append(footer)
doc.build(story)
print(f"PDF created: {OUTPUT}")