Symptoms and signs first we see and the signs differentiate Parkinson’s from atypical Parkinson , multisystem atrophy , PSP and secondary parkinson
| Cardinal Feature | Description |
|---|---|
| Bradykinesia | Slowness of movement; patient may describe "weakness" despite normal power testing |
| Rigidity | Resistance throughout range of movement; often cogwheel (ratchety) quality |
| Tremor | Typically resting; "pill-rolling" in idiopathic PD |
| Postural instability | Impaired balance and righting reflexes |
Up to 20-25% of patients initially diagnosed with IPD ultimately have an alternative diagnosis. - Textbook of Family Medicine 9e
| Feature | Detail |
|---|---|
| Autonomic failure | Prominent and early - orthostatic hypotension, urinary incontinence, erectile dysfunction (formerly Shy-Drager syndrome) |
| Parkinsonism | Symmetric, bradykinesia + rigidity predominant |
| Cerebellar signs | Ataxia, dysarthria (MSA-C subtype, formerly OPCA) |
| Pyramidal signs | Possible in MSA-P (formerly striatonigral degeneration) |
| Tremor | Less prominent than in IPD |
| Levodopa response | Poor (even if dopaminergic transmission enhanced, striatal output neurons are also lost) |
| Posture | Camptocormia (severe forward truncal flexion) characteristic |
| Key differentiator | Severe dysautonomia + parkinsonism + cerebellar features in any combination |
| Feature | Detail |
|---|---|
| Vertical gaze palsy | Pathognomonic - especially downward gaze affected first (cannot look down to see feet on stairs). Supranuclear - oculocephalic reflexes preserved early |
| Falls | Early, unexplained, backward falls within first year - due to axial rigidity and loss of righting reflexes |
| Axial rigidity | Predominantly axial and proximal (neck, trunk) - not appendicular as in IPD |
| Retrocollis | Neck extended backward (contrast with IPD where neck is flexed forward/stooped) |
| Wide-eyed stare | Characteristic "astonished" expression with lid retraction and reduced blink |
| Pseudobulbar palsy | Dysarthria, dysphagia, emotional incontinence (laughing/crying without trigger) |
| Frontal lobe signs | Early dementia, personality change, apathy |
| Levodopa response | Minimal |
| Tremor | Rare or absent |
| Feature | Detail |
|---|---|
| Asymmetric parkinsonism | Like IPD - but unresponsive to levodopa |
| Alien limb phenomenon | Patient feels limb has its own will ("my hand has a mind of its own") |
| Apraxia | Ideomotor apraxia - cannot perform learned movements despite intact motor/sensory function |
| Dystonia | Unilateral, often focal limb dystonia |
| Myoclonus | Focal, action-induced or stimulus-sensitive cortical myoclonus |
| Cortical sensory loss | Astereognosis, agraphesthesia |
| Dementia | Progressive |
| Levodopa response | Poor |
| Key differentiator | Asymmetric parkinsonism + alien limb + cortical signs (apraxia, myoclonus, cortical sensory loss) |
| Feature | Detail |
|---|---|
| Parkinsonism | Rigidity more prominent than bradykinesia or tremor |
| Dementia | Early - often before or coincident with motor symptoms (contrast IPD where dementia is late) |
| Visual hallucinations | Prominent and early - well-formed, recurrent; often with episodic exacerbations |
| Fluctuating cognition | Day-to-day variation in alertness and attention |
| Levodopa response | Some response possible |
| Neuroleptic sensitivity | Severe adverse reactions to antipsychotics |
| Pathology | α-synuclein Lewy bodies in neocortex initially (not primarily substantia nigra) |
| Feature | Detail |
|---|---|
| Onset pattern | Stepwise progression (not insidious gradual onset) |
| Distribution | Lower body predominant - "lower body parkinsonism"; bradykinesia and rigidity worse in legs |
| Gait | Broad-based, wide-stance gait (not narrow shuffling as in IPD) |
| Tremor | Resting tremor is uncommon |
| Associated features | Dementia, spasticity, weakness, pseudobulbar signs, signs of prior stroke |
| Imaging | MRI shows periventricular white matter changes, lacunar infarcts in basal ganglia |
| Levodopa response | Poor |
| Key differentiator | Stepwise onset + lower-body predominance + vascular risk factors + MRI findings |
| Feature | Detail |
|---|---|
| Important caveat | Usually reversible - must always identify this cause |
| Distribution | Typically symmetric from the outset |
| Causative agents | Dopamine-blocking: neuroleptics, atypical antipsychotics, metoclopramide, prochlorperazine; Dopamine-depleting: reserpine, methyldopa; Others: amiodarone, valproic acid, lithium |
| Timing | Develops weeks-months after starting offending drug |
| Management | Stop drug; symptoms typically resolve over weeks to months |
| Key differentiator | Symmetric, clear drug history, reversible |
| Feature | IPD | MSA | PSP | CBD | DLB | Vascular PD | Drug-induced |
|---|---|---|---|---|---|---|---|
| Onset | Asymmetric | Symmetric | Symmetric | Asymmetric | Symmetric | Lower body | Symmetric |
| Tremor | Prominent (resting) | Less prominent | Rare | Rare | Less prominent | Rare | Variable |
| Levodopa | Excellent response | Poor | Minimal | Poor | Partial | Poor | - (remove drug) |
| Key feature 1 | Anosmia early | Severe autonomic failure | Vertical downgaze palsy | Alien limb | Early dementia | Stepwise onset | Drug history |
| Key feature 2 | Asymmetric rest tremor | Cerebellar signs (MSA-C) | Backward falls early | Apraxia + myoclonus | Visual hallucinations early | MRI white matter changes | Symmetric, reversible |
| Key feature 3 | Masked facies | Poor levodopa despite dopamine | Retrocollis + wide-eyed stare | Cortical sensory loss | Fluctuating cognition | Broad-based gait | Stops with drug removal |
| Dementia | Late | Mild, late | Early (frontal) | Progressive | Early, prominent | Late | No |
| Progression | Slow (5-15 yrs) | Faster | Faster | Faster | Variable | Stepwise | Reversible |
Create a diagnostic flowchart PDF for rapid clinical differentiation of these syndromes
ls /home/daytona/workspace/ 2>/dev/null && mkdir -p /home/daytona/workspace/parkinson-flowchart
~/parkinson-flowchart/generate_flowchart.py
"""
Diagnostic Flowchart PDF: Rapid Clinical Differentiation of Parkinsonian Syndromes
Generated with ReportLab
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfgen import canvas
from reportlab.platypus.flowables import Flowable
import os
OUTPUT_PATH = "/home/daytona/workspace/parkinson-flowchart/Parkinsonism_Diagnostic_Flowchart.pdf"
# ── Colour palette ────────────────────────────────────────────────────────────
C_DARK_NAVY = colors.HexColor("#1B2A4A")
C_TEAL = colors.HexColor("#0D7377")
C_LIGHT_TEAL = colors.HexColor("#E6F4F4")
C_AMBER = colors.HexColor("#E07B39")
C_AMBER_LIGHT = colors.HexColor("#FDF3EC")
C_GREEN = colors.HexColor("#2E7D32")
C_GREEN_LIGHT = colors.HexColor("#E8F5E9")
C_RED = colors.HexColor("#B71C1C")
C_RED_LIGHT = colors.HexColor("#FFEBEE")
C_PURPLE = colors.HexColor("#4A148C")
C_PURPLE_LIGHT= colors.HexColor("#F3E5F5")
C_BLUE = colors.HexColor("#0D47A1")
C_BLUE_LIGHT = colors.HexColor("#E3F2FD")
C_ORANGE = colors.HexColor("#E65100")
C_ORANGE_LIGHT= colors.HexColor("#FFF3E0")
C_GREY_LIGHT = colors.HexColor("#F5F5F5")
C_GREY_MID = colors.HexColor("#9E9E9E")
C_GREY_DARK = colors.HexColor("#424242")
C_WHITE = colors.white
W, H = A4 # 595 x 842 pts
# ── Custom flowable: rounded-rect box ─────────────────────────────────────────
class RoundedBox(Flowable):
"""A coloured box with rounded corners and inner text."""
def __init__(self, width, height, bg, border, text, text_color=C_WHITE,
font="Helvetica-Bold", font_size=11, radius=6):
Flowable.__init__(self)
self.box_w = width
self.box_h = height
self.bg = bg
self.border = border
self.text = text
self.text_color = text_color
self.font = font
self.font_size = font_size
self.radius = radius
self.width = width
self.height = height
def draw(self):
c = self.canv
c.setFillColor(self.bg)
c.setStrokeColor(self.border)
c.setLineWidth(1.5)
c.roundRect(0, 0, self.box_w, self.box_h, self.radius, fill=1, stroke=1)
c.setFillColor(self.text_color)
c.setFont(self.font, self.font_size)
# centre text vertically & horizontally
tw = c.stringWidth(self.text, self.font, self.font_size)
x = (self.box_w - tw) / 2
y = (self.box_h - self.font_size) / 2 + 1
c.drawString(x, y, self.text)
class Arrow(Flowable):
"""A downward arrow."""
def __init__(self, width=40, height=20):
Flowable.__init__(self)
self.width = width
self.height = height
def draw(self):
c = self.canv
c.setStrokeColor(C_GREY_DARK)
c.setFillColor(C_GREY_DARK)
c.setLineWidth(1.5)
cx = self.width / 2
c.line(cx, self.height, cx, 4)
# arrowhead
c.setLineWidth(1)
path = c.beginPath()
path.moveTo(cx, 0)
path.lineTo(cx - 5, 8)
path.lineTo(cx + 5, 8)
path.close()
c.drawPath(path, fill=1, stroke=0)
# ── Page number callback ──────────────────────────────────────────────────────
def add_page_number(canvas_obj, doc):
canvas_obj.saveState()
canvas_obj.setFont("Helvetica", 8)
canvas_obj.setFillColor(C_GREY_MID)
page_text = f"Page {doc.page}"
canvas_obj.drawCentredString(W / 2, 18, page_text)
# footer line
canvas_obj.setStrokeColor(C_GREY_MID)
canvas_obj.setLineWidth(0.5)
canvas_obj.line(30, 25, W - 30, 25)
canvas_obj.restoreState()
# ── Style helpers ─────────────────────────────────────────────────────────────
def styles():
base = getSampleStyleSheet()
def s(name, parent="Normal", **kw):
return ParagraphStyle(name, parent=base[parent], **kw)
return {
"title": s("title", "Title",
fontSize=20, textColor=C_WHITE,
alignment=TA_CENTER, spaceAfter=4,
fontName="Helvetica-Bold"),
"subtitle": s("subtitle", fontSize=11, textColor=C_LIGHT_TEAL,
alignment=TA_CENTER, spaceAfter=6,
fontName="Helvetica"),
"h1": s("h1", fontSize=13, textColor=C_DARK_NAVY,
spaceBefore=10, spaceAfter=4,
fontName="Helvetica-Bold"),
"h2": s("h2", fontSize=11, textColor=C_TEAL,
spaceBefore=6, spaceAfter=3,
fontName="Helvetica-Bold"),
"body": s("body", fontSize=9, textColor=C_GREY_DARK,
leading=13, spaceAfter=3),
"body_small": s("body_small", fontSize=8, textColor=C_GREY_DARK,
leading=11),
"bold": s("bold", fontSize=9, textColor=C_GREY_DARK,
fontName="Helvetica-Bold", leading=13),
"white_bold": s("white_bold", fontSize=9, textColor=C_WHITE,
fontName="Helvetica-Bold", leading=12),
"cell": s("cell", fontSize=8, textColor=C_GREY_DARK,
leading=11, wordWrap="CJK"),
"cell_bold": s("cell_bold", fontSize=8, textColor=C_DARK_NAVY,
fontName="Helvetica-Bold", leading=11),
"warn": s("warn", fontSize=8, textColor=C_RED,
fontName="Helvetica-Bold", leading=11),
"note": s("note", fontSize=7.5, textColor=C_GREY_MID,
leading=10, spaceAfter=2),
}
ST = styles()
USABLE_W = W - 60 # 30mm margin each side
# ═══════════════════════════════════════════════════════════════════════════════
# PAGE 1 – Cover / Overview
# ═══════════════════════════════════════════════════════════════════════════════
def build_cover():
story = []
# ── Title banner ──────────────────────────────────────────────────────────
banner_data = [[
Paragraph("PARKINSONIAN SYNDROMES", ST["title"]),
]]
banner = Table(banner_data, colWidths=[USABLE_W])
banner.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), C_DARK_NAVY),
("ROUNDEDCORNERS", [8]),
("TOPPADDING", (0, 0), (-1, -1), 14),
("BOTTOMPADDING",(0, 0), (-1, -1), 8),
("LEFTPADDING", (0, 0), (-1, -1), 10),
]))
story.append(banner)
sub_data = [[Paragraph("Rapid Clinical Differentiation · Diagnostic Flowchart", ST["subtitle"])]]
sub = Table(sub_data, colWidths=[USABLE_W])
sub.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), C_TEAL),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING",(0,0), (-1, -1), 6),
]))
story.append(sub)
story.append(Spacer(1, 10))
# ── Intro paragraph ───────────────────────────────────────────────────────
intro = (
"All parkinsonian syndromes share the cardinal triad of <b>bradykinesia, "
"rigidity, and tremor</b>. Up to 25% of patients initially diagnosed with "
"idiopathic Parkinson's disease (IPD) ultimately have an alternative diagnosis. "
"This flowchart guides rapid bedside differentiation using key clinical signs "
"and red flags."
)
story.append(Paragraph(intro, ST["body"]))
story.append(Spacer(1, 8))
# ── The four cardinal features ────────────────────────────────────────────
story.append(Paragraph("THE CARDINAL TRIAD — Seen in ALL Parkinsonian Syndromes", ST["h1"]))
cardinal_data = [
["BRADYKINESIA", "RIGIDITY", "TREMOR (usually resting)", "POSTURAL INSTABILITY"],
[
Paragraph("Slowness of movement; patient often reports 'weakness' but power is normal", ST["cell"]),
Paragraph("Resistance throughout ROM; cogwheel (ratchety) quality when tremor superimposed", ST["cell"]),
Paragraph("4–6 Hz rest tremor ('pill-rolling'); suppressed by action; re-emerges at rest", ST["cell"]),
Paragraph("Impaired righting reflexes; late in IPD, early in atypical syndromes", ST["cell"]),
]
]
cardinal_tbl = Table(cardinal_data, colWidths=[USABLE_W / 4] * 4)
cardinal_tbl.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), C_DARK_NAVY),
("TEXTCOLOR", (0, 0), (-1, 0), C_WHITE),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 9),
("ALIGN", (0, 0), (-1, 0), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("BACKGROUND", (0, 1), (-1, -1), C_LIGHT_TEAL),
("GRID", (0, 0), (-1, -1), 0.5, C_TEAL),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 5),
]))
story.append(cardinal_tbl)
story.append(Spacer(1, 12))
# ── RED FLAGS box ─────────────────────────────────────────────────────────
story.append(Paragraph("RED FLAGS — Point AWAY from Idiopathic Parkinson's Disease", ST["h1"]))
flags = [
("Early falls / postural instability", "Within first year of symptom onset"),
("Symmetric onset", "IPD is nearly always asymmetric at onset"),
("Rapid progression", "IPD progresses over 5–15 years"),
("Failure to respond to levodopa", "IPD shows marked improvement"),
("Early dementia", "Dementia before or within 1 year of motor onset → DLB"),
("Early prominent hallucinations", "Before levodopa; suggests DLB"),
("Vertical gaze palsy (esp. downward)", "Hallmark of PSP"),
("Cerebellar signs", "Suggests MSA-C"),
("Severe autonomic failure", "Prominent early → MSA"),
("Stepwise progression", "Suggests vascular parkinsonism"),
("Alien limb / apraxia / myoclonus", "Suggests CBD"),
("Offending drug in history", "Drug-induced parkinsonism — reversible!"),
]
flag_rows = [[
Paragraph("<b>Red Flag</b>", ST["cell_bold"]),
Paragraph("<b>Implication</b>", ST["cell_bold"])
]]
for flag, impl in flags:
flag_rows.append([
Paragraph(f"⚠ {flag}", ST["warn"]),
Paragraph(impl, ST["cell"]),
])
flag_tbl = Table(flag_rows, colWidths=[USABLE_W * 0.45, USABLE_W * 0.55])
flag_tbl.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), C_AMBER),
("TEXTCOLOR", (0, 0), (-1, 0), C_WHITE),
("BACKGROUND", (0, 1), (-1, -1), C_AMBER_LIGHT),
("GRID", (0, 0), (-1, -1), 0.4, C_AMBER),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [C_AMBER_LIGHT, C_WHITE]),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING",(0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("VALIGN", (0, 0), (-1, -1), "TOP"),
]))
story.append(flag_tbl)
story.append(Spacer(1, 10))
story.append(Paragraph(
"Source: Neuroanatomy through Clinical Cases 3e; Textbook of Family Medicine 9e; "
"Bradley and Daroff's Neurology in Clinical Practice; Localization in Clinical Neurology 8e",
ST["note"]))
return story
# ═══════════════════════════════════════════════════════════════════════════════
# PAGE 2 – Step-by-step Diagnostic Flowchart
# ═══════════════════════════════════════════════════════════════════════════════
def q_box(text, color=C_DARK_NAVY, bg=C_LIGHT_TEAL, width=None):
"""Diamond-like question box."""
w = width or USABLE_W
data = [[Paragraph(f"<b>? {text}</b>", ST["body"])]]
t = Table(data, colWidths=[w])
t.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), bg),
("LEFTBORDER", (0, 0), (0, -1), 4, color),
("LINEAFTER", (-1, 0), (-1, -1), 0.5, color),
("LINEBELOW", (0, -1), (-1, -1), 0.5, color),
("LINEABOVE", (0, 0), (-1, 0), 0.5, color),
("BOX", (0, 0), (-1, -1), 2, color),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING",(0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 10),
]))
return t
def dx_box(label, color, bg, features, width=None):
"""Diagnosis result box."""
w = width or USABLE_W
feat_text = "<br/>".join(f"• {f}" for f in features)
data = [
[Paragraph(f"<b>{label}</b>", ParagraphStyle(
"dxlabel", fontSize=10, textColor=C_WHITE, fontName="Helvetica-Bold"))],
[Paragraph(feat_text, ParagraphStyle(
"dxfeat", fontSize=8, textColor=color, leading=12))]
]
t = Table(data, colWidths=[w])
t.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), color),
("BACKGROUND", (0, 1), (-1, -1), bg),
("BOX", (0, 0), (-1, -1), 1.5, color),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 8),
]))
return t
def build_flowchart():
story = []
story.append(Paragraph("STEP-BY-STEP DIAGNOSTIC FLOWCHART", ST["h1"]))
story.append(HRFlowable(width=USABLE_W, thickness=2, color=C_DARK_NAVY))
story.append(Spacer(1, 8))
# ── START ─────────────────────────────────────────────────────────────────
start_data = [[Paragraph(
"<b>START: Patient presents with bradykinesia + rigidity ± tremor ± postural instability</b>",
ParagraphStyle("start", fontSize=10, textColor=C_WHITE, fontName="Helvetica-Bold",
alignment=TA_CENTER))]]
start_t = Table(start_data, colWidths=[USABLE_W])
start_t.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), C_DARK_NAVY),
("TOPPADDING", (0, 0), (-1, -1), 8),
("BOTTOMPADDING",(0, 0), (-1, -1), 8),
("ALIGN", (0, 0), (-1, -1), "CENTER"),
]))
story.append(start_t)
story.append(Spacer(1, 4))
# ── STEP 1: Drug history ──────────────────────────────────────────────────
story.append(q_box("STEP 1: Is there a history of dopamine-blocking or depleting drugs?",
color=C_AMBER, bg=C_AMBER_LIGHT))
story.append(Spacer(1, 3))
yes_no_data = [[
Paragraph("<b>YES →</b>", ST["bold"]),
dx_box("DRUG-INDUCED PARKINSONISM",
C_AMBER, C_AMBER_LIGHT,
["Symmetric onset; no resting tremor or variable",
"Drugs: neuroleptics, metoclopramide, prochlorperazine,",
" amiodarone, valproic acid, lithium, reserpine, methyldopa",
"REVERSIBLE — stop offending drug",
"Symptoms resolve over weeks to months"],
width=USABLE_W * 0.75),
Paragraph("<b>NO ↓</b>", ST["bold"]),
]]
yn = Table(yes_no_data, colWidths=[USABLE_W * 0.08, USABLE_W * 0.75, USABLE_W * 0.17])
yn.setStyle(TableStyle([
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 3),
]))
story.append(yn)
story.append(Spacer(1, 6))
# ── STEP 2: Levodopa trial ────────────────────────────────────────────────
story.append(q_box("STEP 2: Assess levodopa response (trial or history). Excellent improvement?",
color=C_GREEN, bg=C_GREEN_LIGHT))
story.append(Spacer(1, 3))
# YES → IPD branch
ipd_branch = dx_box(
"LIKELY IDIOPATHIC PARKINSON'S DISEASE (IPD)",
C_GREEN, C_GREEN_LIGHT,
["Asymmetric onset (nearly always)",
"Resting 'pill-rolling' tremor (4–6 Hz) — presents in ~70%",
"Bradykinesia + cogwheel rigidity",
"Postural instability is LATE (not within first year)",
"Non-motor: anosmia (often earliest), REM sleep behaviour disorder,",
" depression, constipation, seborrhoea",
"Masked facies, micrographia, hypophonia, festinating gait",
"Levodopa response: MARKED and SUSTAINED",
"Pathology: Lewy bodies (α-synuclein) in substantia nigra"])
story.append(Paragraph("<b>YES (marked response) →</b>", ST["bold"]))
story.append(ipd_branch)
story.append(Spacer(1, 3))
story.append(Paragraph(
"<b>POOR/ABSENT response → Continue to STEP 3</b>",
ParagraphStyle("arrow", fontSize=9, textColor=C_RED, fontName="Helvetica-Bold")))
story.append(Spacer(1, 6))
# ── STEP 3: Vertical gaze ──────────────────────────────────────────────────
story.append(q_box("STEP 3: Is there VERTICAL GAZE PALSY (especially downward)?",
color=C_PURPLE, bg=C_PURPLE_LIGHT))
story.append(Spacer(1, 3))
psp_box = dx_box(
"PROGRESSIVE SUPRANUCLEAR PALSY (PSP)",
C_PURPLE, C_PURPLE_LIGHT,
["Vertical (especially downward) supranuclear gaze palsy — HALLMARK",
"Early unexplained backward falls within first year",
"Axial and proximal rigidity (neck/trunk) — retrocollis (neck extended back)",
"Wide-eyed stare (lid retraction + reduced blink)",
"Pseudobulbar palsy: dysarthria, dysphagia, emotional incontinence",
"Frontal lobe dementia: apathy, personality change",
"Tremor: rare or absent",
"Levodopa: minimal response",
"PSP-RS (classic) vs PSP-P (Parkinson-like variant)",
"Avg. survival: ~8 years from diagnosis"])
story.append(Paragraph("<b>YES →</b>", ST["bold"]))
story.append(psp_box)
story.append(Spacer(1, 3))
story.append(Paragraph("<b>NO ↓</b>", ST["bold"]))
story.append(Spacer(1, 6))
# ── STEP 4: Autonomic ─────────────────────────────────────────────────────
story.append(q_box("STEP 4: Is there prominent early autonomic failure and/or cerebellar signs?",
color=C_BLUE, bg=C_BLUE_LIGHT))
story.append(Spacer(1, 3))
msa_box = dx_box(
"MULTIPLE SYSTEM ATROPHY (MSA)",
C_BLUE, C_BLUE_LIGHT,
["Prominent & early autonomic failure: orthostatic hypotension, urinary",
" incontinence, erectile dysfunction, impaired sweating",
"MSA-P (parkinsonism predominant, formerly striatonigral degeneration):",
" symmetric akinetic-rigid syndrome, poor levodopa response",
"MSA-C (cerebellar predominant, formerly OPCA):",
" parkinsonism + ataxia + dysarthria",
"Camptocormia (severe forward truncal flexion) characteristic",
"Tremor: less prominent than IPD",
"Levodopa: poor response (striatal neurons also lost)",
"Pathology: glial cytoplasmic inclusions (α-synuclein)"])
story.append(Paragraph("<b>YES →</b>", ST["bold"]))
story.append(msa_box)
story.append(Spacer(1, 3))
story.append(Paragraph("<b>NO ↓</b>", ST["bold"]))
story.append(Spacer(1, 6))
return story
# ═══════════════════════════════════════════════════════════════════════════════
# PAGE 3 – Flowchart continued + comparison table
# ═══════════════════════════════════════════════════════════════════════════════
def build_flowchart_p2():
story = []
story.append(Paragraph("STEP-BY-STEP DIAGNOSTIC FLOWCHART (continued)", ST["h1"]))
story.append(HRFlowable(width=USABLE_W, thickness=2, color=C_DARK_NAVY))
story.append(Spacer(1, 8))
# ── STEP 5: Alien limb ────────────────────────────────────────────────────
story.append(q_box("STEP 5: Alien limb, ideomotor apraxia, cortical myoclonus, or asymmetric dystonia?",
color=C_ORANGE, bg=C_ORANGE_LIGHT))
story.append(Spacer(1, 3))
cbd_box = dx_box(
"CORTICOBASAL DEGENERATION (CBD)",
C_ORANGE, C_ORANGE_LIGHT,
["Asymmetric parkinsonism (like IPD, unlike other atypical) — but levodopa-unresponsive",
"Alien limb phenomenon: 'my hand has a mind of its own'",
"Ideomotor apraxia: cannot perform learned motor tasks",
"Focal limb dystonia (unilateral)",
"Stimulus-sensitive / action-induced cortical myoclonus",
"Cortical sensory loss: astereognosis, agraphesthesia",
"Progressive dementia",
"Levodopa: poor response"])
story.append(Paragraph("<b>YES →</b>", ST["bold"]))
story.append(cbd_box)
story.append(Spacer(1, 3))
story.append(Paragraph("<b>NO ↓</b>", ST["bold"]))
story.append(Spacer(1, 6))
# ── STEP 6: Early dementia / hallucinations ───────────────────────────────
story.append(q_box(
"STEP 6: Early fluctuating dementia and/or well-formed visual hallucinations before/with motor onset?",
color=C_PURPLE, bg=C_PURPLE_LIGHT))
story.append(Spacer(1, 3))
dlb_box = dx_box(
"DEMENTIA WITH LEWY BODIES (DLB)",
C_PURPLE, C_PURPLE_LIGHT,
["Dementia onset BEFORE or within 1 year of parkinsonism (1-year rule)",
"Fluctuating cognition: day-to-day variation in alertness and attention",
"Well-formed, recurrent visual hallucinations — often early, prominent",
"Rigidity > bradykinesia > tremor (tremor less prominent)",
"REM sleep behaviour disorder",
"Extreme sensitivity to antipsychotics (avoid typical neuroleptics!)",
"Partial levodopa response possible",
"Pathology: Lewy bodies in neocortex (not primarily substantia nigra)"])
story.append(Paragraph("<b>YES →</b>", ST["bold"]))
story.append(dlb_box)
story.append(Spacer(1, 3))
story.append(Paragraph("<b>NO ↓</b>", ST["bold"]))
story.append(Spacer(1, 6))
# ── STEP 7: Vascular ─────────────────────────────────────────────────────
story.append(q_box(
"STEP 7: Stepwise progression? Lower-body predominant? Vascular risk factors? MRI changes?",
color=C_RED, bg=C_RED_LIGHT))
story.append(Spacer(1, 3))
vasc_box = dx_box(
"VASCULAR PARKINSONISM",
C_RED, C_RED_LIGHT,
["Stepwise rather than gradual onset (clue: 'it got worse after a hospital stay')",
"Lower body predominant: legs more affected than arms",
"Gait: broad-based (not narrow shuffling); 'magnetic gait'",
"Resting tremor uncommon",
"Associated: dementia, spasticity, weakness, pseudobulbar signs",
"MRI: lacunar infarcts in basal ganglia, periventricular white matter disease",
"Levodopa: poor response"])
story.append(Paragraph("<b>YES →</b>", ST["bold"]))
story.append(vasc_box)
story.append(Spacer(1, 3))
# ── STEP 8: Other secondary causes ────────────────────────────────────────
other_data = [[
Paragraph("<b>NO / Uncertain →</b>", ST["bold"]),
dx_box("CONSIDER OTHER SECONDARY CAUSES", C_GREY_DARK, C_GREY_LIGHT,
["Wilson's disease: young patient, KF rings, liver disease, low ceruloplasmin",
"Normal pressure hydrocephalus: gait apraxia + incontinence + dementia triad",
"MPTP toxicity / CO poisoning: exposure history",
"Hypothyroidism / severe depression: check TFTs",
"Encephalitis / structural lesion: MRI brain"],
width=USABLE_W * 0.80),
]]
other_t = Table(other_data, colWidths=[USABLE_W * 0.20, USABLE_W * 0.80])
other_t.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "MIDDLE")]))
story.append(other_t)
return story
# ═══════════════════════════════════════════════════════════════════════════════
# PAGE 4 – Side-by-side comparison table
# ═══════════════════════════════════════════════════════════════════════════════
def build_comparison():
story = []
story.append(Paragraph("SIDE-BY-SIDE COMPARISON TABLE", ST["h1"]))
story.append(HRFlowable(width=USABLE_W, thickness=2, color=C_DARK_NAVY))
story.append(Spacer(1, 8))
def p(text, style=None, color=None):
if style is None:
style = ST["cell"]
if color:
style = ParagraphStyle("tmp", parent=style, textColor=color)
return Paragraph(text, style)
hdrs = ["Feature", "IPD", "MSA", "PSP", "CBD", "DLB", "Vascular PD", "Drug-induced"]
rows = [
["Onset",
"Asymmetric", "Symmetric", "Symmetric", "Asymmetric", "Symmetric", "Lower body", "Symmetric"],
["Tremor",
"Prominent resting", "Less prominent", "Rare/absent", "Rare/absent", "Less prominent", "Rare", "Variable"],
["Levodopa response",
"Excellent", "Poor", "Minimal", "Poor", "Partial", "Poor", "Remove drug"],
["Pathognomonic sign",
"Asymmetric rest tremor + L-DOPA response",
"Autonomic failure + MSA-C ataxia",
"Downward gaze palsy",
"Alien limb + apraxia",
"Early visual hallucinations + fluctuating cognition",
"Stepwise + lower body",
"Drug history; symmetric; reversible"],
["Postural instability",
"Late (>3 yrs)", "Early", "Very early (falls in yr 1)", "Variable", "Variable", "Early", "Variable"],
["Dementia",
"Late (15–40%)", "Mild/late", "Early frontal", "Progressive", "Early, prominent", "Late", "No"],
["Autonomic failure",
"Mild/late", "Severe & early", "Mild", "Mild", "Moderate", "Absent/mild", "Absent"],
["Cerebellar signs",
"No", "MSA-C subtype", "No", "No", "No", "No", "No"],
["Eye movement",
"Slow saccades; broken pursuit", "Normal or slow saccades", "Vertical gaze palsy esp. down", "Variable", "Normal or slow", "Normal", "Normal"],
["Gait",
"Narrow shuffling festinating", "Ataxic or festinating", "Wide-based, frequent falls", "Asymmetric dystonic", "Shuffling", "Broad-based magnetic", "Variable"],
["Progression",
"Slow (5–15 yrs)", "Faster", "Faster (~8 yrs)", "Faster", "Variable", "Stepwise", "Reversible"],
["Pathology",
"Lewy bodies (SN)", "GCIs; SN + striatum loss", "NFTs; midbrain-diencephalic", "Tau; cortex + BG", "Lewy bodies (neocortex)", "Lacunar infarcts", "None (functional)"],
]
col_w = USABLE_W / len(hdrs)
col_widths = [col_w * 1.3] + [col_w * 0.957] * (len(hdrs) - 1)
def cell_p(text, bold=False, color=C_GREY_DARK):
style = ParagraphStyle("tc", fontSize=7, textColor=color,
fontName="Helvetica-Bold" if bold else "Helvetica",
leading=9, wordWrap="CJK")
return Paragraph(text, style)
tbl_data = [[cell_p(h, bold=True, color=C_WHITE) for h in hdrs]]
row_colors = [C_GREY_LIGHT, C_WHITE]
for i, row in enumerate(rows):
feat = row[0]
vals = row[1:]
tbl_data.append(
[cell_p(feat, bold=True)] + [cell_p(v) for v in vals]
)
comp_tbl = Table(tbl_data, colWidths=col_widths, repeatRows=1)
style_cmds = [
("BACKGROUND", (0, 0), (-1, 0), C_DARK_NAVY),
("GRID", (0, 0), (-1, -1), 0.3, C_GREY_MID),
("TOPPADDING", (0, 0), (-1, -1), 3),
("BOTTOMPADDING",(0, 0), (-1, -1), 3),
("LEFTPADDING", (0, 0), (-1, -1), 4),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("BACKGROUND", (0, 1), (0, -1), C_LIGHT_TEAL),
]
# Alternating row backgrounds
for i in range(1, len(tbl_data)):
bg = C_GREY_LIGHT if i % 2 == 0 else C_WHITE
style_cmds.append(("BACKGROUND", (1, i), (-1, i), bg))
comp_tbl.setStyle(TableStyle(style_cmds))
story.append(comp_tbl)
story.append(Spacer(1, 10))
# ── Key single differentiator callout ─────────────────────────────────────
story.append(Paragraph("KEY SINGLE DIFFERENTIATING SIGN — Rapid Bedside Checklist", ST["h1"]))
diff_data = [
["Diagnosis", "The ONE sign that clinches it"],
["Idiopathic PD", "Asymmetric resting tremor + EXCELLENT levodopa response + no red flags"],
["PSP", "Supranuclear VERTICAL (esp. downward) gaze palsy + backward falls + retrocollis"],
["MSA", "Severe early orthostatic hypotension / urinary incontinence + poor L-DOPA"],
["CBD", "Alien limb phenomenon + ideomotor apraxia (levodopa-unresponsive)"],
["DLB", "Well-formed visual hallucinations + fluctuating cognition BEFORE/WITH parkinsonism"],
["Vascular PD", "Stepwise onset + lower-body predominance + MRI lacunar/WM changes"],
["Drug-induced PD", "Causative drug in history → symmetric → REVERSIBLE on drug withdrawal"],
]
dx_colors_list = [C_DARK_NAVY, C_GREEN, C_PURPLE, C_BLUE, C_ORANGE, C_PURPLE, C_RED, C_AMBER]
diff_rows = []
for idx, (dx, sign) in enumerate(diff_data):
if idx == 0:
diff_rows.append([
cell_p(dx, bold=True, color=C_WHITE),
cell_p(sign, bold=True, color=C_WHITE)
])
else:
diff_rows.append([
cell_p(dx, bold=True, color=C_WHITE),
cell_p(sign)
])
diff_tbl = Table(diff_rows, colWidths=[USABLE_W * 0.22, USABLE_W * 0.78])
diff_style = [
("GRID", (0, 0), (-1, -1), 0.5, C_GREY_MID),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 7),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("BACKGROUND", (0, 0), (-1, 0), C_DARK_NAVY),
]
for idx in range(1, len(diff_rows)):
diff_style.append(("BACKGROUND", (0, idx), (0, idx), dx_colors_list[idx]))
diff_style.append(("BACKGROUND", (1, idx), (1, idx),
C_GREY_LIGHT if idx % 2 == 0 else C_WHITE))
diff_tbl.setStyle(TableStyle(diff_style))
story.append(diff_tbl)
story.append(Spacer(1, 8))
story.append(Paragraph(
"<i>Sources: Neuroanatomy through Clinical Cases 3e (Table 16.5); Textbook of Family Medicine 9e; "
"Bradley and Daroff's Neurology in Clinical Practice; Localization in Clinical Neurology 8e; "
"Adams and Victor's Principles of Neurology 12e</i>",
ST["note"]))
return story
# ═══════════════════════════════════════════════════════════════════════════════
# BUILD PDF
# ═══════════════════════════════════════════════════════════════════════════════
def main():
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
rightMargin=30, leftMargin=30,
topMargin=30, bottomMargin=35,
title="Parkinsonian Syndromes – Diagnostic Flowchart",
author="Orris Clinical Reference",
subject="Rapid Clinical Differentiation of Parkinsonian Syndromes",
)
story = []
story += build_cover()
story += build_flowchart()
story += build_flowchart_p2()
story += build_comparison()
doc.build(story, onFirstPage=add_page_number, onLaterPages=add_page_number)
print(f"PDF written to: {OUTPUT_PATH}")
if __name__ == "__main__":
main()
cd /home/daytona/workspace/parkinson-flowchart && python generate_flowchart.py 2>&1
ls -lh /home/daytona/workspace/parkinson-flowchart/Parkinsonism_Diagnostic_Flowchart.pdf
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/parkinson-flowchart/Parkinsonism_Diagnostic_Flowchart.pdf') print(f'Pages: {len(r.pages)}') for i, pg in enumerate(r.pages): txt = pg.extract_text() print(f'--- Page {i+1} ({len(txt)} chars) ---') print(txt[:300]) "
Parkinsonism Diagnostic Flowchart
PDF Document · PDF
What atropine causes initial bradycardia followed by tachycardia
Think of it as: blocking the "less-release" signal → paradoxically more ACh → more vagal tone → slower heart.
DOSE DOMINANT EFFECT HEART RATE
─────────────────────────────────────────────────────
< 0.4 mg Block presynaptic M1 ↓ Bradycardia
autoreceptors → more ACh (paradoxical)
released → M2 stimulation
≥ 0.5 mg Block postsynaptic M2 ↑ Tachycardia
at SA/AV node → (intended)
vagal blockade dominates
| Dose | Effect |
|---|---|
| < 0.4 mg | Paradoxical bradycardia (avoid sub-therapeutic doses) |
| 0.4–0.6 mg | Transition zone |
| 0.5–1.0 mg | Therapeutic tachycardia; antisialogogue |
| 1.0–2.0 mg | Full vagal blockade; complete tachycardia |
| > 2 mg | Near-complete peripheral muscarinic block |