If u had to write the most comprehensive and effective booklet notes (pdf)seperated into left and right on the symptom study pf dysphagia...I have attached my sample notes ...add ejatver requires for final year, bbs accordingly and send me
cd /home/daytona/workspace/attachments/aa169b0e-5e58-4ca0-bcac-c26d73b8d8ef && p…
cd /home/daytona/workspace/attachments/aa169b0e-5e58-4ca0-bcac-c26d73b8d8ef && p…
Not corenell style...i just want booklet with divided amd written in the form left and right per page
mkdir -p /home/daytona/workspace/dysphagia-booklet && ls /home/daytona/workspace…
~/dysphagia-booklet/build_booklet.py
"""
Dysphagia Symptom Study Booklet
Left | Right page layout (A4 landscape, divided down the middle)
Final Year BBS Level
"""
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
BaseDocTemplate, Frame, PageTemplate, Paragraph, Spacer,
Table, TableStyle, KeepTogether, HRFlowable
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus.flowables import Flowable
import reportlab.lib.colors as rcolors
# ── Dimensions ──────────────────────────────────────────────────────────────
PAGE_W, PAGE_H = landscape(A4) # 297mm x 210mm
MARGIN = 12 * mm
DIVIDER = 4 * mm # gap around centre line
COL_W = (PAGE_W - 2*MARGIN - DIVIDER) / 2 # each column width
BODY_H = PAGE_H - 2*MARGIN
# ── Colour palette ───────────────────────────────────────────────────────────
CREAM = colors.HexColor("#FDFBF2")
NAVY = colors.HexColor("#1B3A6B")
MIDBLUE = colors.HexColor("#2E6DA4")
LIGHTBLUE= colors.HexColor("#D6E9F8")
GOLD = colors.HexColor("#D4A017")
AMBER = colors.HexColor("#FFF3CD")
PINK = colors.HexColor("#FCE4EC")
GREEN = colors.HexColor("#E8F5E9")
ORANGE = colors.HexColor("#FFF0E6")
RED = colors.HexColor("#FFEBEE")
DIVLINE = colors.HexColor("#CCCCCC")
BLACK = colors.black
WHITE = colors.white
# ── Styles ───────────────────────────────────────────────────────────────────
SS = getSampleStyleSheet()
def style(name, **kw):
base = kw.pop("parent", "Normal")
s = ParagraphStyle(name, parent=SS[base], **kw)
return s
H1 = style("H1", fontSize=13, textColor=NAVY, fontName="Helvetica-Bold",
spaceAfter=3, spaceBefore=6, leading=16)
H2 = style("H2", fontSize=10.5, textColor=MIDBLUE, fontName="Helvetica-Bold",
spaceAfter=2, spaceBefore=4, leading=13, borderPad=1)
H3 = style("H3", fontSize=9.5, textColor=NAVY, fontName="Helvetica-BoldOblique",
spaceAfter=1, spaceBefore=3, leading=12)
BODY = style("BODY", fontSize=8.5, leading=12, spaceAfter=2,
fontName="Helvetica", alignment=TA_JUSTIFY)
BULLET = style("BULLET", fontSize=8.5, leading=11.5, spaceAfter=1.5,
fontName="Helvetica", leftIndent=10, bulletIndent=2,
firstLineIndent=0)
BOLD = style("BOLD", fontSize=8.5, leading=11.5, spaceAfter=1.5,
fontName="Helvetica-Bold")
SMALL = style("SMALL", fontSize=7.5, leading=10, spaceAfter=1,
fontName="Helvetica", textColor=colors.HexColor("#555555"))
BOX_HEAD = style("BOX_HEAD", fontSize=9, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_CENTER, leading=11)
BOX_BODY = style("BOX_BODY", fontSize=8.2, leading=11, fontName="Helvetica",
alignment=TA_LEFT)
KEY = style("KEY", fontSize=8.5, leading=11.5, fontName="Helvetica-Bold",
textColor=NAVY)
def P(text, s=BODY): return Paragraph(text, s)
def B(text): return Paragraph(f"• {text}", BULLET)
def H(text, lvl=1):
return Paragraph(text, [H1, H2, H3][lvl-1])
def SP(h=3): return Spacer(1, h*mm)
def HR(): return HRFlowable(width="100%", thickness=0.5, color=DIVLINE,
spaceAfter=2, spaceBefore=2)
def coloured_box(title, items, bg=LIGHTBLUE, head_bg=MIDBLUE, width=None):
"""Returns a Table acting as a coloured box with title and bullet items."""
if width is None:
width = COL_W - 4*mm
rows = [[Paragraph(title, BOX_HEAD)]]
for item in items:
rows.append([Paragraph(f"• {item}", BOX_BODY)])
ts = TableStyle([
("BACKGROUND", (0,0), (-1,0), head_bg),
("BACKGROUND", (0,1), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 0.8, NAVY),
("GRID", (0,0), (-1,-1), 0.3, DIVLINE),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
])
return Table(rows, colWidths=[width], style=ts)
def two_col_table(left_header, left_items, right_header, right_items,
bg_l=LIGHTBLUE, bg_r=AMBER, head_l=MIDBLUE, head_r=GOLD):
cw = (COL_W - 6*mm) / 2
rows = [
[Paragraph(left_header, BOX_HEAD), Paragraph(right_header, BOX_HEAD)],
]
max_len = max(len(left_items), len(right_items))
for i in range(max_len):
l = Paragraph(f"• {left_items[i]}", BOX_BODY) if i < len(left_items) else Paragraph("", BOX_BODY)
r = Paragraph(f"• {right_items[i]}", BOX_BODY) if i < len(right_items) else Paragraph("", BOX_BODY)
rows.append([l, r])
ts = TableStyle([
("BACKGROUND", (0,0), (0,0), head_l),
("BACKGROUND", (1,0), (1,0), head_r),
("BACKGROUND", (0,1), (0,-1), bg_l),
("BACKGROUND", (1,1), (1,-1), bg_r),
("BOX", (0,0), (-1,-1), 0.8, NAVY),
("GRID", (0,0), (-1,-1), 0.3, DIVLINE),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
])
return Table(rows, colWidths=[cw, cw], style=ts)
def highlight_box(text, bg=AMBER, border=GOLD):
t = Table([[Paragraph(text, BOX_BODY)]],
colWidths=[COL_W - 4*mm],
style=TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 1, border),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
return t
# ─── PAGE DRAWING FUNCTIONS ──────────────────────────────────────────────────
def draw_page_background(canvas, doc):
canvas.saveState()
# cream background
canvas.setFillColor(CREAM)
canvas.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
# top header bar
canvas.setFillColor(NAVY)
canvas.rect(0, PAGE_H - 16*mm, PAGE_W, 16*mm, fill=1, stroke=0)
# header text
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 13)
canvas.drawCentredString(PAGE_W/2, PAGE_H - 10*mm, "DYSPHAGIA — Symptom Study Notes")
canvas.setFont("Helvetica", 8)
canvas.drawRightString(PAGE_W - MARGIN, PAGE_H - 6*mm, "Final Year BBS")
canvas.drawString(MARGIN, PAGE_H - 6*mm, f"Page {doc.page}")
# centre divider line
mid = PAGE_W / 2
canvas.setStrokeColor(DIVLINE)
canvas.setLineWidth(1)
canvas.line(mid, MARGIN, mid, PAGE_H - 18*mm)
# left / right labels
canvas.setFont("Helvetica-Bold", 7)
canvas.setFillColor(colors.HexColor("#999999"))
canvas.drawCentredString(PAGE_W/4, PAGE_H - 19.5*mm, "◄ LEFT")
canvas.drawCentredString(3*PAGE_W/4, PAGE_H - 19.5*mm, "RIGHT ►")
# bottom bar
canvas.setFillColor(NAVY)
canvas.rect(0, 0, PAGE_W, 8*mm, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica", 7)
canvas.drawCentredString(PAGE_W/2, 2.5*mm,
"Sources: Harrison's 22E · Sleisenger & Fordtran · Yamada's Gastroenterology · Cummings ORL · Rosen's EM")
canvas.restoreState()
def draw_cover_background(canvas, doc):
canvas.saveState()
canvas.setFillColor(NAVY)
canvas.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
# decorative stripe
canvas.setFillColor(MIDBLUE)
canvas.rect(0, PAGE_H*0.35, PAGE_W, PAGE_H*0.3, fill=1, stroke=0)
canvas.setFillColor(GOLD)
canvas.rect(0, PAGE_H*0.33, PAGE_W, 3*mm, fill=1, stroke=0)
canvas.rect(0, PAGE_H*0.65, PAGE_W, 3*mm, fill=1, stroke=0)
# title
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 42)
canvas.drawCentredString(PAGE_W/2, PAGE_H*0.56, "DYSPHAGIA")
canvas.setFillColor(GOLD)
canvas.setFont("Helvetica-Bold", 20)
canvas.drawCentredString(PAGE_W/2, PAGE_H*0.47, "Symptom Study — Booklet Notes")
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica", 13)
canvas.drawCentredString(PAGE_W/2, PAGE_H*0.38, "Final Year BBS | Comprehensive Reference")
canvas.setFont("Helvetica", 10)
canvas.drawCentredString(PAGE_W/2, PAGE_H*0.27,
"Definition · Classification · Pathophysiology · Causes · Clinical Features")
canvas.drawCentredString(PAGE_W/2, PAGE_H*0.22,
"Investigations · Management · Key Differentials · Red Flags · OSCE Tips")
canvas.setFillColor(colors.HexColor("#AAAAAA"))
canvas.setFont("Helvetica", 8)
canvas.drawCentredString(PAGE_W/2, 12*mm,
"Sources: Harrison's 22E · Sleisenger & Fordtran · Yamada's Gastroenterology · Cummings ORL · Rosen's EM")
canvas.restoreState()
# ─── BUILD CONTENT ──────────────────────────────────────────────────────────
# Each page is a list: [left_column_flowables, right_column_flowables]
pages = []
# ═══════════════════════════════════════════════════════════════════════════
# PAGE 1 — Definition, Etymology, Epidemiology | Classification Overview
# ═══════════════════════════════════════════════════════════════════════════
left1 = [
H("DEFINITION & ETYMOLOGY", 1),
HR(),
P("<b>Dysphagia</b> = difficulty swallowing (Greek: <i>dys</i> = disordered + <i>phagia</i> = to eat)."),
P("Sensation that food or liquid is <b>impeded in passage</b> from mouth to stomach."),
SP(2),
highlight_box("⚠ Dysphagia ALWAYS indicates pathology — it is NEVER physiological in adults.", bg=RED, border=colors.HexColor("#C62828")),
SP(2),
H("DISTINGUISH FROM:", 2),
B("<b>Odynophagia</b> — painful swallowing (retrosternal pain on transit)"),
B("<b>Globus sensation</b> — constant lump in throat; NOT related to swallowing; NO food impaction"),
B("<b>Phagophobia</b> — fear of swallowing (psychiatric); swallowing mechanism normal"),
SP(2),
H("EPIDEMIOLOGY", 1),
HR(),
two_col_table(
"General Population", ["Prevalence ~8–22%", "More common >50 yrs", "M = F overall", "Leading cause of aspiration pneumonia"],
"Hospital/Care Settings", ["50% of stroke patients", "60–80% dementia patients", "30% elderly in care homes", "Major cause of morbidity/malnutrition"],
bg_l=GREEN, bg_r=LIGHTBLUE, head_l=colors.HexColor("#2E7D32"), head_r=MIDBLUE
),
SP(3),
H("NORMAL SWALLOWING — 4 PHASES", 1),
HR(),
Table([
[Paragraph("<b>Phase</b>", BOLD), Paragraph("<b>Location</b>", BOLD), Paragraph("<b>Control</b>", BOLD), Paragraph("<b>Key Action</b>", BOLD)],
[P("1. Oral prep"), P("Mouth"), P("Voluntary"), P("Chewing, bolus formation, salivation")],
[P("2. Oral"), P("Mouth → pharynx"), P("Voluntary"), P("Tongue propels bolus posteriorly")],
[P("3. Pharyngeal"), P("Pharynx → UES"), P("Involuntary reflex"), P("Larynx elevates, epiglottis folds, UES opens (<1 sec)")],
[P("4. Oesophageal"), P("UES → stomach"), P("Involuntary"), P("Primary peristalsis, LES relaxes (8–10 sec)")],
], colWidths=[(COL_W-4*mm)*f for f in [0.18,0.2,0.2,0.42]],
style=TableStyle([
("BACKGROUND",(0,0),(-1,0), NAVY), ("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,-1), 8),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHTBLUE]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),4),
])),
]
right1 = [
H("CLASSIFICATION OF DYSPHAGIA", 1),
HR(),
coloured_box("ANATOMICAL CLASSIFICATION",
["Oropharyngeal (Transfer) Dysphagia — problem in mouth/pharynx/UES",
"Oesophageal Dysphagia — problem in oesophageal body/LES/stomach"],
bg=LIGHTBLUE, head_bg=MIDBLUE),
SP(3),
coloured_box("MECHANISTIC CLASSIFICATION",
["Structural/Mechanical — physical narrowing or obstruction of lumen",
"Neuromuscular/Motility — disordered muscle contraction or innervation",
"Mixed — combination of both (e.g. post-radiotherapy stricture + fibrosis)"],
bg=AMBER, head_bg=GOLD),
SP(3),
H("OROPHARYNGEAL vs OESOPHAGEAL", 2),
Table([
[Paragraph("<b>Feature</b>", BOLD), Paragraph("<b>Oropharyngeal</b>", BOLD), Paragraph("<b>Oesophageal</b>", BOLD)],
[P("Onset timing"), P("Immediate (<1 sec post-swallow)"), P("Delayed (>1 sec post-swallow)")],
[P("Localisation"), P("Throat / neck / cervical"), P("Retrosternal / epigastric")],
[P("Associated Sx"), P("Coughing, choking, nasal regurgitation, drooling, dysarthria"), P("Heartburn, regurgitation, weight loss")],
[P("Bolus type"), P("Both solids AND liquids (equally)"), P("Solids first, then liquids (mechanical); or both (motility)")],
[P("Aspiration"), P("Common — laryngeal protection impaired"), P("Less common unless severe")],
[P("Main causes"), P("Neurological (stroke, PD, MND)"), P("Stricture, achalasia, cancer")],
], colWidths=[(COL_W-4*mm)*f for f in [0.22,0.40,0.38]],
style=TableStyle([
("BACKGROUND",(0,0),(-1,0), NAVY), ("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,-1), 7.5),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHTBLUE]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),4),
("VALIGN",(0,0),(-1,-1),"TOP"),
])),
SP(3),
highlight_box("<b>Key Clue:</b> Solids ONLY → mechanical obstruction. Solids + Liquids from onset → motility disorder. Liquids worse than solids → neurological.", bg=AMBER, border=GOLD),
]
pages.append((left1, right1))
# ═══════════════════════════════════════════════════════════════════════════
# PAGE 2 — Causes of Oropharyngeal Dysphagia | Causes of Oesophageal Dysphagia
# ═══════════════════════════════════════════════════════════════════════════
left2 = [
H("CAUSES — OROPHARYNGEAL DYSPHAGIA", 1),
HR(),
H("A. NEUROLOGICAL (~80% of cases)", 2),
two_col_table(
"Central", [
"Stroke (CVA) — most common",
"Parkinson's Disease",
"Multiple Sclerosis",
"Motor Neurone Disease (ALS)",
"Pseudobulbar palsy",
"Bulbar palsy",
"Brain tumours",
"Cerebral palsy",
],
"Peripheral / NMJ", [
"Myasthenia gravis",
"Guillain-Barré syndrome",
"Post-polio syndrome",
"Botulism",
"Diphtheria",
"Rabies",
"CN IX/X palsy",
"Diabetic neuropathy",
],
bg_l=RED, bg_r=PINK, head_l=colors.HexColor("#C62828"), head_r=colors.HexColor("#AD1457")
),
SP(3),
H("B. MUSCULAR", 2),
B("Polymyositis / Dermatomyositis"),
B("Myotonic dystrophy"),
B("Oculopharyngeal muscular dystrophy"),
B("Inclusion body myositis"),
SP(3),
H("C. STRUCTURAL", 2),
two_col_table(
"Intrinsic", [
"Zenker's diverticulum",
"Cricopharyngeal bar / achalasia",
"Pharyngeal web (Plummer-Vinson)",
"Post-cricoid carcinoma",
"Tonsillar hypertrophy",
"Pharyngeal abscess / tumour",
],
"Extrinsic", [
"Cervical osteophytes",
"Goitre / thyroid mass",
"Lymphadenopathy",
"Aortic aneurysm",
"Retropharyngeal mass",
],
bg_l=LIGHTBLUE, bg_r=AMBER
),
SP(3),
H("D. IATROGENIC & OTHER", 2),
B("Post-surgery (laryngectomy, neck dissection, thyroidectomy)"),
B("Post-radiotherapy (fibrosis, xerostomia)"),
B("Drug-induced: neuroleptics, anticholinergics, NSAIDs, bisphosphonates"),
B("Xerostomia (Sjögren's, medications, radiation)"),
B("Poor dentition / ill-fitting dentures"),
B("Infectious: Ludwig's angina, epiglottitis, peritonsillar abscess"),
]
right2 = [
H("CAUSES — OESOPHAGEAL DYSPHAGIA", 1),
HR(),
H("A. MECHANICAL / STRUCTURAL", 2),
coloured_box("Intrinsic Obstructions",
["Oesophageal carcinoma (SCC upper/mid; Adenocarcinoma lower) ← RED FLAG",
"Peptic stricture (GERD-related) — most common benign stricture",
"Schatzki's ring (B-ring at SCJ) — episodic dysphagia for solids",
"Oesophageal web (upper; associated with Plummer-Vinson syndrome)",
"Eosinophilic oesophagitis (EoE) — young males; linear furrows + rings",
"Corrosive stricture (caustic ingestion)",
"Post-radiotherapy stricture",
"Anastomotic stricture (post-surgery)",
"Foreign body impaction"],
bg=RED, head_bg=colors.HexColor("#C62828")),
SP(2),
coloured_box("Extrinsic Compression",
["Mediastinal lymphadenopathy",
"Bronchial carcinoma / lung mass",
"Aortic aneurysm",
"Vascular dysphagia (dysphagia lusoria — aberrant R subclavian)",
"Enlarged left atrium",
"Retrosternal goitre / thyroid mass",
"Posterior mediastinal mass"],
bg=PINK, head_bg=colors.HexColor("#AD1457")),
SP(3),
H("B. MOTILITY DISORDERS", 2),
Table([
[P("<b>Disorder</b>", BOLD), P("<b>Mechanism</b>", BOLD), P("<b>Key Clue</b>", BOLD)],
[P("Achalasia"), P("LES fails to relax; aperistalsis"), P("Solids + liquids; regurgitation undigested food; night Sx")],
[P("Diffuse Oesophageal Spasm (DES)"), P("Non-peristaltic contractions"), P("Intermittent; chest pain; 'corkscrew' on barium")],
[P("Nutcracker / Jackhammer"), P("High-amplitude peristalsis"), P("Chest pain > dysphagia")],
[P("Scleroderma"), P("Smooth muscle atrophy + GERD"), P("Raynaud's; heartburn; low LES pressure")],
[P("Absent contractility"), P("Aperistalsis, normal LES"), P("Systemic disease (scleroderma)")],
[P("POEM / EGJ outflow obstruction"), P("Incomplete LES relaxation"), P("Like achalasia but some peristalsis preserved")],
], colWidths=[(COL_W-4*mm)*f for f in [0.27,0.38,0.35]],
style=TableStyle([
("BACKGROUND",(0,0),(-1,0), NAVY), ("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,-1), 7.5),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHTBLUE]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
])),
]
pages.append((left2, right2))
# ═══════════════════════════════════════════════════════════════════════════
# PAGE 3 — History Taking & Clinical Features | Red Flags & Investigations
# ═══════════════════════════════════════════════════════════════════════════
left3 = [
H("HISTORY-TAKING IN DYSPHAGIA", 1),
HR(),
H("THE 3 KEY QUESTIONS (Oesophageal)", 2),
coloured_box("1. What food causes dysphagia?",
["Solids only → mechanical obstruction (stricture, ring, tumour)",
"Solids AND liquids from start → motility disorder",
"Liquids more than solids → neuromuscular (oropharyngeal)"],
bg=LIGHTBLUE, head_bg=MIDBLUE),
SP(2),
coloured_box("2. Is it intermittent or progressive?",
["Intermittent + solids → Schatzki's ring or DES",
"Progressive + solids then liquids → malignancy (ALARM FEATURE)",
"Progressive + solids + liquids → achalasia"],
bg=AMBER, head_bg=GOLD),
SP(2),
coloured_box("3. Does the patient have heartburn?",
["Heartburn + dysphagia → peptic stricture / GERD complication",
"No heartburn + progressive → malignancy until proven otherwise",
"Heartburn + Raynaud's → scleroderma"],
bg=GREEN, head_bg=colors.HexColor("#2E7D32")),
SP(3),
H("FULL SYMPTOM CHARACTERISATION", 2),
Table([
[P("<b>Feature</b>", BOLD), P("<b>Ask...</b>", BOLD), P("<b>Significance</b>", BOLD)],
[P("Site"), P("Where does food stick?"), P("Cervical = oropharyngeal; Retrosternal = oesophageal")],
[P("Onset / Duration"), P("Weeks vs months vs years?"), P("Rapid onset → malignancy; long-standing → benign")],
[P("Speed of progression"), P("Getting worse?"), P("Progressive = malignancy, achalasia, MND")],
[P("Bolus type"), P("Solids, liquids or both?"), P("See above — key differentiator")],
[P("Associated cough"), P("During or after swallowing?"), P("Silent aspiration — especially in stroke/MND")],
[P("Nasal regurgitation"), P("Fluid comes back through nose?"), P("UES dysfunction / oropharyngeal")],
[P("Regurgitation"), P("Undigested food?"), P("Achalasia / Zenker's diverticulum")],
[P("Weight loss"), P("How much, how fast?"), P("RED FLAG — malignancy, severe dysphagia")],
[P("Voice change"), P("Hoarse / nasal voice?"), P("RLN palsy (malignancy), bulbar palsy")],
[P("Pain"), P("Odynophagia?"), P("Malignancy, EoE, infective oesophagitis, caustic")],
[P("Night symptoms"), P("Woken by regurgitation?"), P("Achalasia — oesophageal overflow")],
], colWidths=[(COL_W-4*mm)*f for f in [0.18,0.35,0.47]],
style=TableStyle([
("BACKGROUND",(0,0),(-1,0), NAVY), ("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,-1), 7.5),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHTBLUE]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("TOPPADDING",(0,0),(-1,-1),2), ("BOTTOMPADDING",(0,0),(-1,-1),2),
("LEFTPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
])),
]
right3 = [
H("RED FLAGS IN DYSPHAGIA", 1),
HR(),
coloured_box("⚠ RED FLAGS — Require URGENT Investigation (2-Week Wait Referral)",
["Progressive dysphagia (solids → liquids)",
"Unintentional weight loss",
"Odynophagia (painful swallowing)",
"Haematemesis / melaena",
"Hoarseness / voice change",
"Anaemia (iron-deficiency)",
"Age >55 with new dysphagia",
"Neck mass / lymphadenopathy",
"Aspiration pneumonia (recurrent)",
"Nocturnal regurgitation of undigested food"],
bg=RED, head_bg=colors.HexColor("#B71C1C")),
SP(3),
H("INVESTIGATIONS", 1),
HR(),
H("First-Line", 2),
Table([
[P("<b>Investigation</b>", BOLD), P("<b>Indication</b>", BOLD), P("<b>Finding</b>", BOLD)],
[P("OGD (Upper Endoscopy)"), P("Suspected mechanical obstruction; RED FLAGS"), P("Strictures, rings, tumours — allows biopsy + dilatation")],
[P("Barium Swallow"), P("Motility disorder suspected; transfer dysphagia"), P("Peristalsis, 'bird-beak' achalasia, 'rat-tail' Ca, webs")],
[P("Modified Barium Swallow (MBSS)"), P("Oropharyngeal dysphagia assessment"), P("Video fluoroscopy — identifies aspiration, timing defects")],
[P("Videofluoroscopy (VFSS)"), P("Gold standard for oropharyngeal phase"), P("Real-time swallow assessment with SLT")],
[P("High-Resolution Manometry (HRM)"), P("Motility disorder after excluding structural"), P("Achalasia types (I/II/III), DES, nutcracker, EGJ outflow")],
[P("pH + Impedance"), P("GERD-related dysphagia"), P("Acid exposure time, reflux events")],
[P("Chest X-Ray"), P("Aspiration, mediastinal widening"), P("Air-fluid level (achalasia), mediastinal mass")],
[P("CT Chest/Neck"), P("Extrinsic compression, staging"), P("Lymphadenopathy, aortic arch, tumour extent")],
], colWidths=[(COL_W-4*mm)*f for f in [0.28,0.35,0.37]],
style=TableStyle([
("BACKGROUND",(0,0),(-1,0), NAVY), ("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,-1), 7.5),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHTBLUE]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("TOPPADDING",(0,0),(-1,-1),2), ("BOTTOMPADDING",(0,0),(-1,-1),2),
("LEFTPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
])),
SP(2),
H("Second-Line / Specialist", 2),
B("Endoscopic ultrasound (EUS) — tumour T-staging"),
B("PET-CT — metastatic staging of oesophageal Ca"),
B("Oesophageal transit scintigraphy — motility quantification"),
B("FNE (Flexible nasendoscopy) — pharyngeal lesions, vocal cords"),
B("Laryngoscopy — RLN palsy, post-cricoid lesions"),
B("MRI brain/spine — central neurological cause"),
B("EMG / nerve conduction — neuromuscular disease"),
B("Blood tests: FBC, CRP, LFTs, TFTs, ANA, anti-AChR Ab (MG), CK (myopathy)"),
]
pages.append((left3, right3))
# ═══════════════════════════════════════════════════════════════════════════
# PAGE 4 — Specific Conditions in Detail (Left) | Management (Right)
# ═══════════════════════════════════════════════════════════════════════════
left4 = [
H("KEY CONDITIONS IN DETAIL", 1),
HR(),
H("ACHALASIA", 2),
Table([
[P("<b>Definition</b>", BOLD), P("Primary oesophageal motility disorder: absent LES relaxation + aperistalsis")],
[P("<b>Pathophysiology</b>", BOLD), P("Loss of inhibitory myenteric (Auerbach's) plexus neurons (VIP + NO); LES fails to relax on swallowing")],
[P("<b>Types (Chicago)</b>", BOLD), P("Type I: absent contractility; Type II: pan-oesophageal pressurisation (BEST prognosis); Type III: spastic")],
[P("<b>Symptoms</b>", BOLD), P("Dysphagia solids + liquids; regurgitation undigested food; weight loss; nocturnal cough; chest pain")],
[P("<b>Barium</b>", BOLD), P("'Bird's beak' tapering at LES; dilated oesophagus; air-fluid level")],
[P("<b>Manometry</b>", BOLD), P("Elevated integrated relaxation pressure (IRP >15 mmHg); absent peristalsis (Types I/II)")],
[P("<b>Treatment</b>", BOLD), P("Pneumatic dilation (PD); POEM (per-oral endoscopic myotomy); laparoscopic Heller myotomy + fundoplication")],
], colWidths=[(COL_W-4*mm)*0.25, (COL_W-4*mm)*0.75],
style=TableStyle([
("BACKGROUND",(0,0),(0,-1), LIGHTBLUE),
("BACKGROUND",(1,0),(1,-1), WHITE),
("ROWBACKGROUNDS",(0,0),(-1,-1),[LIGHTBLUE, WHITE]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("FONTSIZE",(0,0),(-1,-1),7.5),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),4), ("VALIGN",(0,0),(-1,-1),"TOP"),
])),
SP(3),
H("EOSINOPHILIC OESOPHAGITIS (EoE)", 2),
B("Demographics: young males, atopic history (asthma, eczema, food allergy)"),
B("Pathology: ≥15 eosinophils/HPF on biopsy (proximal + distal)"),
B("Endoscopy: linear furrows, concentric rings ('trachealization'), white exudates, narrow lumen, stricture"),
B("Trigger: milk, wheat, eggs, soy, nuts, seafood (dietary antigens)"),
B("Symptom: recurrent food bolus impaction (typical presentation in young male)"),
B("Treatment: PPI trial (6-8 wks) → if no response → swallowed topical steroids (fluticasone/budesonide) → elimination diet → endoscopic dilation"),
SP(3),
H("ZENKER'S DIVERTICULUM", 2),
B("Pulsion diverticulum at Killian's dehiscence (between thyropharyngeus + cricopharyngeus)"),
B("Mechanism: UES dysfunction → ↑ hypopharyngeal pressure → mucosal herniation"),
B("Symptoms: dysphagia, regurgitation of old undigested food, halitosis, gurgling noise on swallowing, aspiration"),
B("Barium: posterior pharyngeal pouch filling with contrast"),
B("Treatment: endoscopic stapler-assisted diverticulotomy or cricopharyngeal myotomy"),
]
right4 = [
H("MANAGEMENT OF DYSPHAGIA", 1),
HR(),
H("GENERAL PRINCIPLES", 2),
coloured_box("Initial Assessment & Safety",
["Assess aspiration risk — chest signs, wet voice after swallow",
"Nil by mouth if high aspiration risk pending formal swallow assessment",
"Speech & Language Therapy (SLT) referral — all patients",
"SALT: bedside swallowing assessment → videofluoroscopy if needed",
"Nutritional assessment — dietitian referral",
"Consider NG tube or PEG if unsafe oral intake",
"Modify food/fluid consistency (IDDSI framework — Levels 0-7)"],
bg=GREEN, head_bg=colors.HexColor("#2E7D32")),
SP(3),
H("CONDITION-SPECIFIC MANAGEMENT", 2),
Table([
[P("<b>Condition</b>", BOLD), P("<b>First-Line Treatment</b>", BOLD), P("<b>Notes</b>", BOLD)],
[P("Oesophageal Ca"), P("MDT: surgery (Ivor-Lewis) + chemoradiotherapy"), P("Staging guides resectability; palliative stent if unresectable")],
[P("Peptic stricture"), P("PPI + endoscopic dilation"), P("Repeat dilation if recurrent; treat underlying GERD")],
[P("Achalasia"), P("POEM / pneumatic dilation / Heller myotomy"), P("POEM = preferred; Type II best prognosis")],
[P("Schatzki's ring"), P("Endoscopic dilation; PPI if GERD"), P("Single dilation often curative")],
[P("EoE"), P("PPI → topical steroids → dietary elimination"), P("Biopsies at follow-up OGD to confirm remission")],
[P("DES"), P("Calcium channel blockers / nitrates / PPI"), P("POEM increasingly used for refractory cases")],
[P("Zenker's"), P("Endoscopic diverticulotomy"), P("Surgical myotomy if endoscopic fails; elderly patients well tolerated")],
[P("Pharyngeal web"), P("Endoscopic dilation; treat iron deficiency"), P("Plummer-Vinson → risk of post-cricoid Ca")],
[P("Stroke/neurological"), P("SLT-led swallow rehab + MBSS"), P("Texture modification, compensatory strategies, PEG if needed")],
[P("MG"), P("Pyridostigmine + immunosuppression"), P("Plasmapheresis/IVIG in crisis")],
[P("Scleroderma"), P("Aggressive PPI + prokinetics"), P("No specific oesophageal Rx; prevent aspiration")],
], colWidths=[(COL_W-4*mm)*f for f in [0.22,0.40,0.38]],
style=TableStyle([
("BACKGROUND",(0,0),(-1,0), NAVY), ("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,-1), 7.5),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHTBLUE]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("TOPPADDING",(0,0),(-1,-1),2), ("BOTTOMPADDING",(0,0),(-1,-1),2),
("LEFTPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
])),
SP(3),
H("NUTRITIONAL SUPPORT IN DYSPHAGIA", 2),
Table([
[P("<b>Route</b>", BOLD), P("<b>Indication</b>", BOLD), P("<b>Duration</b>", BOLD)],
[P("Thickened fluids/modified diet"), P("Mild-moderate oropharyngeal dysphagia"), P("Ongoing")],
[P("Nasogastric (NG) tube"), P("Short-term; unsafe swallow; acute illness"), P("<4-6 weeks")],
[P("PEG tube"), P("Long-term; persistent unsafe swallow (stroke, MND)"), P(">4-6 weeks")],
[P("TPN"), P("GI tract non-functional / severe oesophageal disease"), P("Temporary")],
], colWidths=[(COL_W-4*mm)*f for f in [0.28,0.42,0.30]],
style=TableStyle([
("BACKGROUND",(0,0),(-1,0), NAVY), ("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,-1), 7.5),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, AMBER]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
])),
]
pages.append((left4, right4))
# ═══════════════════════════════════════════════════════════════════════════
# PAGE 5 — Plummer-Vinson + Scleroderma + Oesophageal Ca | OSCE / Clinical Pearls
# ═══════════════════════════════════════════════════════════════════════════
left5 = [
H("IMPORTANT ASSOCIATIONS & SYNDROMES", 1),
HR(),
H("PLUMMER-VINSON SYNDROME (Patterson-Kelly)", 2),
coloured_box("Triad",
["1. Dysphagia (post-cricoid web, upper oesophagus)",
"2. Iron-deficiency anaemia",
"3. Glossitis, angular cheilitis, koilonychia"],
bg=PINK, head_bg=colors.HexColor("#AD1457")),
B("Demographics: middle-aged women"),
B("⚠ Premalignant — risk of post-cricoid carcinoma (SCC)"),
B("Treatment: iron replacement + endoscopic dilation of web"),
SP(3),
H("SCLERODERMA (Systemic Sclerosis) & DYSPHAGIA", 2),
B("Fibrosis replaces smooth muscle in lower 2/3 of oesophagus"),
B("Result: absent peristalsis + low/absent LES pressure"),
B("Severe GERD → peptic stricture + Barrett's oesophagus"),
B("Associated: CREST syndrome (Calcinosis, Raynaud's, oEsophageal, Sclerodactyly, Telangiectasia)"),
B("Manometry: low-amplitude or absent contractions in distal oesophagus"),
B("Management: aggressive PPI, head of bed elevation, dietary modification"),
SP(3),
H("OESOPHAGEAL CARCINOMA", 2),
Table([
[P("<b>Feature</b>", BOLD), P("<b>SCC</b>", BOLD), P("<b>Adenocarcinoma</b>", BOLD)],
[P("Location"), P("Upper / mid oesophagus"), P("Lower oesophagus / GOJ")],
[P("Risk Factors"), P("Smoking, alcohol, hot drinks, achalasia, nitrosamines, Plummer-Vinson"), P("GERD, Barrett's, obesity, smoking, alcohol")],
[P("Background"), P("No Barrett's change"), P("Barrett's oesophagus (pre-malignant)")],
[P("Incidence trend"), P("Declining in West"), P("Rising rapidly in West")],
[P("Symptom"), P("Progressive dysphagia, weight loss, hoarseness"), P("Progressive dysphagia, weight loss, heartburn Hx")],
], colWidths=[(COL_W-4*mm)*f for f in [0.22,0.39,0.39]],
style=TableStyle([
("BACKGROUND",(0,0),(-1,0), NAVY), ("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,-1), 7.5),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHTBLUE]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),3), ("VALIGN",(0,0),(-1,-1),"TOP"),
])),
SP(2),
highlight_box("<b>Barrett's Oesophagus:</b> Metaplasia of squamous → columnar (intestinal type) epithelium at SCJ due to chronic GERD. Confirmed by biopsy (goblet cells). Risk of progression to adenocarcinoma ~0.5%/yr. Surveillance OGD every 2-5 years (Prague criteria C&M grading).", bg=AMBER, border=GOLD),
SP(2),
H("DIFFUSE OESOPHAGEAL SPASM (DES)", 2),
B("Hallmark: simultaneous non-peristaltic contractions (≥20% of swallows)"),
B("'Corkscrew' or 'rosary bead' oesophagus on barium"),
B("Chest pain mimicking cardiac angina (important differential)"),
B("Intermittent dysphagia — solids AND liquids"),
B("Manometry (HRM): DL <4.5 sec; IRP normal (<15 mmHg)"),
]
right5 = [
H("OSCE & CLINICAL PEARLS — FINAL YEAR BBS", 1),
HR(),
coloured_box("OSCE HISTORY FRAMEWORK FOR DYSPHAGIA",
["1. SITE: Where does it stick? (throat/chest/stomach)","2. ONSET: Sudden vs gradual; duration",
"3. CHARACTER: Solids, liquids or both? Which is worse?",
"4. PROGRESSION: Stable, improving, or worsening?",
"5. ASSOCIATED: weight loss, odynophagia, regurgitation, cough, voice change, heartburn, anaemia",
"6. TIMING: Intermittent vs constant; at start of meal vs any time",
"7. RELIEVING: Any manoeuvres that help (raising arms = motility)?",
"8. PMHx: GERD, autoimmune disease, previous cancer/radiotherapy",
"9. DH: NSAIDs, bisphosphonates, neuroleptics, anticholinergics",
"10. SHx: Smoking, alcohol, occupation (asbestos → mesothelioma)"],
bg=LIGHTBLUE, head_bg=MIDBLUE),
SP(3),
H("HIGH-YIELD EXAM MNEMONICS", 2),
coloured_box("DYSPHAGIA RED FLAGS = 'PHOWARD'",
["P — Progressive symptoms",
"H — Haematemesis / melaena",
"O — Old age (>55) + new dysphagia",
"W — Weight loss (unintentional)",
"A — Anaemia (iron deficiency)",
"R — Regurgitation (nocturnal) + hoarseness",
"D — Duration short (<3 months) + rapid progression"],
bg=RED, head_bg=colors.HexColor("#B71C1C")),
SP(3),
H("CLASSIC EXAM SCENARIOS", 2),
Table([
[P("<b>Scenario</b>", BOLD), P("<b>Likely Dx</b>", BOLD)],
[P("Young male, food bolus impaction, atopy, linear furrows on OGD"), P("Eosinophilic Oesophagitis (EoE)")],
[P("Elderly patient, gurgling swallow, regurgitates old food, halitosis"), P("Zenker's Diverticulum")],
[P("Middle-aged woman, iron-deficiency anaemia, upper dysphagia"), P("Plummer-Vinson Syndrome")],
[P("Dysphagia solids + liquids, regurgitates undigested food at night, 'bird-beak' barium"), P("Achalasia")],
[P("50yr smoker, progressive dysphagia solids → liquids, weight loss"), P("Oesophageal Carcinoma (SCC)")],
[P("Obese patient, chronic GERD, now dysphagia, endoscopy = columnar mucosa"), P("Barrett's + possible Adenocarcinoma")],
[P("Raynaud's + GERD + absent peristalsis on manometry"), P("Scleroderma / CREST")],
[P("Stroke patient, coughs on all fluids, wet voice"), P("Neurogenic dysphagia + aspiration risk")],
[P("Intermittent dysphagia solids at beginning of meal, self-resolves"), P("Schatzki's Ring")],
[P("Chest pain + intermittent dysphagia, 'corkscrew' barium"), P("Diffuse Oesophageal Spasm")],
], colWidths=[(COL_W-4*mm)*0.52, (COL_W-4*mm)*0.48],
style=TableStyle([
("BACKGROUND",(0,0),(-1,0), NAVY), ("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,-1), 7.5),
("ROWBACKGROUNDS",(0,1),(-1,-1),[WHITE, LIGHTBLUE]),
("BOX",(0,0),(-1,-1),0.8,NAVY), ("GRID",(0,0),(-1,-1),0.3,DIVLINE),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),3), ("VALIGN",(0,0),(-1,-1),"TOP"),
])),
SP(2),
highlight_box("<b>Remember:</b> Dysphagia to liquids > solids = neuromuscular/oropharyngeal. Ask about aspiration (pneumonia Hx). ALL patients with dysphagia need SLT assessment. Unexplained weight loss + dysphagia = malignancy until proven otherwise.", bg=AMBER, border=GOLD),
]
pages.append((left5, right5))
# ═══════════════════════════════════════════════════════════════════════════
# PDF CONSTRUCTION
# ═══════════════════════════════════════════════════════════════════════════
from reportlab.platypus import SimpleDocTemplate
OUTPUT = "/home/daytona/workspace/dysphagia-booklet/Dysphagia_Booklet_Notes.pdf"
class BookletDoc(BaseDocTemplate):
def __init__(self, filename, **kw):
super().__init__(filename, pagesize=landscape(A4), **kw)
self._current_page_type = "normal"
def handle_pageBegin(self):
super().handle_pageBegin()
def build_pages(self, pages_data):
"""Build the document with alternating left/right columns per spread."""
all_story = []
for page_idx, (left_flows, right_flows) in enumerate(pages_data):
# Wrap each column in a fixed-size frame rendered as a table
# We'll use a two-column table with a vertical line in the middle
left_content = left_flows
right_content = right_flows
all_story.append(_PageSpread(left_content, right_content, page_idx+1))
doc = SimpleDocTemplate(
OUTPUT,
pagesize=landscape(A4),
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=22*mm, bottomMargin=10*mm,
)
doc.build(all_story, onFirstPage=draw_cover_background,
onLaterPages=draw_page_background)
return doc
class _PageSpread(Flowable):
"""Renders left and right columns side by side on one page spread."""
def __init__(self, left, right, page_num):
super().__init__()
self.left = left
self.right = right
self.page_num = page_num
self.width = PAGE_W - 2*MARGIN
self.height = PAGE_H - 22*mm - 10*mm
def draw(self):
from reportlab.platypus import Frame
c = self.canv
col_h = self.height
mid = self.width/2 - DIVIDER/2
# Left frame
lf = Frame(0, 0, mid, col_h,
leftPadding=3, rightPadding=6, topPadding=4, bottomPadding=4,
showBoundary=0)
lf.addFromList(list(self.left), c)
# Right frame
rf = Frame(mid + DIVIDER, 0, mid, col_h,
leftPadding=6, rightPadding=3, topPadding=4, bottomPadding=4,
showBoundary=0)
rf.addFromList(list(self.right), c)
# Draw vertical dividing line
c.setStrokeColor(DIVLINE)
c.setLineWidth(0.8)
c.line(mid + DIVIDER/2, 0, mid + DIVIDER/2, col_h)
# Build cover page story
from reportlab.platypus import SimpleDocTemplate as SDT
# We'll build a multi-page doc manually via BaseDocTemplate + PageTemplate
class MyDoc(BaseDocTemplate):
def __init__(self, *a, **kw):
super().__init__(*a, **kw)
self.addPageTemplates([
PageTemplate(id="cover", frames=[Frame(0, 0, PAGE_W, PAGE_H, 0, 0, 0, 0)],
onPage=draw_cover_background),
PageTemplate(id="normal", frames=[
Frame(MARGIN, 10*mm, PAGE_W-2*MARGIN, PAGE_H-32*mm,
leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0)
], onPage=draw_page_background),
])
from reportlab.platypus import NextPageTemplate, PageBreak
story = []
story.append(NextPageTemplate("cover"))
story.append(Spacer(1, PAGE_H)) # blank cover — background drawn by onPage
story.append(NextPageTemplate("normal"))
for page_idx, (left_flows, right_flows) in enumerate(pages):
story.append(PageBreak())
story.append(_PageSpread(list(left_flows), list(right_flows), page_idx+1))
doc = MyDoc(OUTPUT, pagesize=landscape(A4),
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=22*mm, bottomMargin=10*mm)
doc.build(story)
print(f"PDF built: {OUTPUT}")
cd /home/daytona/workspace/dysphagia-booklet && python3 build_booklet.py 2>&1
ls -lh /home/daytona/workspace/dysphagia-booklet/
Dysphagia Booklet Notes
PDF Document · PDF
| Page | Left Side | Right Side |
|---|---|---|
| Cover | Full navy title cover | - |
| 1 | Definition, Etymology, Epidemiology, 4 Phases of Swallowing | Classification (anatomical + mechanistic), Oropharyngeal vs Oesophageal comparison table |
| 2 | Causes of Oropharyngeal Dysphagia (neurological, muscular, structural, iatrogenic) | Causes of Oesophageal Dysphagia (mechanical, motility disorders table) |
| 3 | History-taking framework (3 key questions + full symptom characterisation table) | Red Flags (URGENT referral list) + Full Investigations table |
| 4 | Achalasia in detail, EoE, Zenker's Diverticulum | Management (general principles, condition-specific table, nutritional support) |
| 5 | Plummer-Vinson, Scleroderma/CREST, Oesophageal Ca (SCC vs Adeno), Barrett's, DES | OSCE history framework, "PHOWARD" red flags mnemonic, Classic exam scenario table |
Any reference from, davidsoms 24th edition
| Textbook | Coverage |
|---|---|
| Harrison's Principles of Internal Medicine 22E (2025) | Most comparable to Davidson's — comprehensive internal medicine, dysphagia chapter |
| Sleisenger & Fordtran's GI & Liver Disease | Deep gastroenterology + oesophageal dysphagia |
| Yamada's Textbook of Gastroenterology 7e | Motility disorders, achalasia, oropharyngeal causes |
| Goldman-Cecil Medicine (2 vol) | Internal medicine reference |
| Bailey & Love's Surgery 28e | Surgical perspective |
| Cummings Otolaryngology + Scott-Brown's ORL | ENT/oropharyngeal dysphagia |
Under Motility disorder it looks blank, full symptom characterization and classic exam based all are blank
~/dysphagia-booklet/build_booklet_v2.py
"""
Dysphagia Symptom Study Booklet v2
Left | Right layout per page — content NEVER clipped.
Each page is A4 landscape with a centre dividing line.
Left column flows down, right column flows down independently.
If a section is too tall, it is split across a new page.
"""
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
BaseDocTemplate, Frame, PageTemplate,
Paragraph, Spacer, Table, TableStyle,
HRFlowable, NextPageTemplate, PageBreak, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus.flowables import Flowable
# ── Page geometry ────────────────────────────────────────────────────────────
PW, PH = landscape(A4) # 841.9 x 595.3 pt
MARGIN = 11 * mm
HEADER_H = 18 * mm
FOOTER_H = 9 * mm
GAP = 5 * mm # centre gap
COL_W = (PW - 2*MARGIN - GAP) / 2
COL_H = PH - MARGIN - HEADER_H - FOOTER_H
LX = MARGIN # left col x
RX = MARGIN + COL_W + GAP # right col x
CY = FOOTER_H # cols start y
# ── Colours ──────────────────────────────────────────────────────────────────
CREAM = colors.HexColor("#FDFBF2")
NAVY = colors.HexColor("#1B3A6B")
BLUE = colors.HexColor("#2E6DA4")
LBLUE = colors.HexColor("#D6E9F8")
GOLD = colors.HexColor("#C8960C")
AMBER = colors.HexColor("#FFF3CD")
PINK = colors.HexColor("#FCE4EC")
DKPINK = colors.HexColor("#AD1457")
GREEN = colors.HexColor("#E8F5E9")
DKGREEN = colors.HexColor("#2E7D32")
RED = colors.HexColor("#FFEBEE")
DKRED = colors.HexColor("#B71C1C")
MIDRED = colors.HexColor("#C62828")
DIVL = colors.HexColor("#BBBBBB")
WHITE = colors.white
# ── Paragraph styles ─────────────────────────────────────────────────────────
_SS = getSampleStyleSheet()
def ps(name, **kw):
kw.setdefault("parent", _SS["Normal"])
return ParagraphStyle(name, **kw)
H1s = ps("H1s", fontSize=10, fontName="Helvetica-Bold", textColor=NAVY,
spaceBefore=5, spaceAfter=2, leading=13)
H2s = ps("H2s", fontSize=9, fontName="Helvetica-Bold", textColor=BLUE,
spaceBefore=4, spaceAfter=1, leading=12)
H3s = ps("H3s", fontSize=8.5,fontName="Helvetica-BoldOblique", textColor=NAVY,
spaceBefore=3, spaceAfter=1, leading=11)
NOR = ps("NOR", fontSize=7.8,fontName="Helvetica", leading=11,
spaceAfter=1, alignment=TA_JUSTIFY)
BUL = ps("BUL", fontSize=7.8,fontName="Helvetica", leading=11,
spaceAfter=1, leftIndent=9, firstLineIndent=0)
BOD = ps("BOD", fontSize=7.5,fontName="Helvetica", leading=10.5,
spaceAfter=1)
BHD = ps("BHD", fontSize=8, fontName="Helvetica-Bold", leading=11,
textColor=WHITE, alignment=TA_CENTER)
SML = ps("SML", fontSize=7, fontName="Helvetica", leading=10,
textColor=colors.HexColor("#555555"))
def P(t, s=NOR): return Paragraph(t, s)
def B(t): return Paragraph(f"<bullet>•</bullet> {t}", BUL)
def H1(t): return Paragraph(t, H1s)
def H2(t): return Paragraph(t, H2s)
def H3(t): return Paragraph(t, H3s)
def SP(n=2): return Spacer(1, n*mm)
def HR(): return HRFlowable(width="100%", thickness=0.5,
color=DIVL, spaceAfter=2, spaceBefore=1)
# ── Helper: coloured section box (title + bullets) ───────────────────────────
def cbox(title, items, hbg=BLUE, bbg=LBLUE, w=None):
w = w or (COL_W - 3*mm)
data = [[Paragraph(title, BHD)]]
for it in items:
data.append([Paragraph(f"• {it}", BOD)])
ts = TableStyle([
("BACKGROUND", (0,0), (-1,0), hbg),
("BACKGROUND", (0,1), (-1,-1), bbg),
("BOX", (0,0),(-1,-1), 0.7, NAVY),
("GRID", (0,0),(-1,-1), 0.3, DIVL),
("TOPPADDING", (0,0),(-1,-1), 2),
("BOTTOMPADDING", (0,0),(-1,-1), 2),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 5),
])
return Table(data, colWidths=[w], style=ts)
# ── Helper: highlight note box ────────────────────────────────────────────────
def hbox(text, bg=AMBER, border=GOLD, w=None):
w = w or (COL_W - 3*mm)
ts = TableStyle([
("BACKGROUND",(0,0),(-1,-1), bg),
("BOX",(0,0),(-1,-1), 1, border),
("TOPPADDING",(0,0),(-1,-1),4),
("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),6),
("RIGHTPADDING",(0,0),(-1,-1),6),
])
return Table([[Paragraph(text, BOD)]], colWidths=[w], style=ts)
# ── Helper: simple two-col mini-table ─────────────────────────────────────────
def tbl(headers, rows, col_fracs, hbg=NAVY, even_bg=WHITE, odd_bg=LBLUE):
cws = [(COL_W - 3*mm) * f for f in col_fracs]
data = [[Paragraph(f"<b>{h}</b>", BOD) for h in headers]]
for i, row in enumerate(rows):
data.append([Paragraph(str(c), BOD) for c in row])
ts = TableStyle([
("BACKGROUND",(0,0),(-1,0), hbg),
("TEXTCOLOR",(0,0),(-1,0), WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1), 7.5),
("ROWBACKGROUNDS",(0,1),(-1,-1),[even_bg, odd_bg]),
("BOX",(0,0),(-1,-1),0.7,NAVY),
("GRID",(0,0),(-1,-1),0.3,DIVL),
("TOPPADDING",(0,0),(-1,-1),2),
("BOTTOMPADDING",(0,0),(-1,-1),2),
("LEFTPADDING",(0,0),(-1,-1),3),
("RIGHTPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
])
return Table(data, colWidths=cws, style=ts)
# ── Page callbacks ────────────────────────────────────────────────────────────
def _draw_normal(canvas, doc):
c = canvas
c.saveState()
# cream bg
c.setFillColor(CREAM); c.rect(0,0,PW,PH,fill=1,stroke=0)
# top bar
c.setFillColor(NAVY); c.rect(0, PH-HEADER_H, PW, HEADER_H, fill=1, stroke=0)
c.setFillColor(WHITE); c.setFont("Helvetica-Bold", 12)
c.drawCentredString(PW/2, PH-11*mm, "DYSPHAGIA — Symptom Study Notes")
c.setFont("Helvetica", 7.5)
c.drawString(MARGIN, PH-7*mm, f"Page {doc.page}")
c.drawRightString(PW-MARGIN, PH-7*mm, "Final Year BBS")
# centre divider
mx = MARGIN + COL_W + GAP/2
c.setStrokeColor(DIVL); c.setLineWidth(0.8)
c.line(mx, CY, mx, PH-HEADER_H-1)
# LEFT / RIGHT labels
c.setFont("Helvetica-Bold", 6.5); c.setFillColor(colors.HexColor("#AAAAAA"))
c.drawCentredString(LX + COL_W/2, PH-HEADER_H-5, "◄ LEFT")
c.drawCentredString(RX + COL_W/2, PH-HEADER_H-5, "RIGHT ►")
# bottom bar
c.setFillColor(NAVY); c.rect(0,0,PW,FOOTER_H,fill=1,stroke=0)
c.setFillColor(WHITE); c.setFont("Helvetica",6.5)
c.drawCentredString(PW/2, 3*mm,
"Sources: Harrison's 22E (2025) · Sleisenger & Fordtran · Yamada's Gastroenterology · Cummings ORL · Rosen's EM")
c.restoreState()
def _draw_cover(canvas, doc):
c = canvas
c.saveState()
c.setFillColor(NAVY); c.rect(0,0,PW,PH,fill=1,stroke=0)
c.setFillColor(BLUE); c.rect(0, PH*0.35, PW, PH*0.30, fill=1, stroke=0)
c.setFillColor(GOLD)
c.rect(0, PH*0.33, PW, 3*mm, fill=1, stroke=0)
c.rect(0, PH*0.65, PW, 3*mm, fill=1, stroke=0)
c.setFillColor(WHITE); c.setFont("Helvetica-Bold", 44)
c.drawCentredString(PW/2, PH*0.555, "DYSPHAGIA")
c.setFillColor(GOLD); c.setFont("Helvetica-Bold", 18)
c.drawCentredString(PW/2, PH*0.46, "Symptom Study — Booklet Notes")
c.setFillColor(WHITE); c.setFont("Helvetica", 13)
c.drawCentredString(PW/2, PH*0.375, "Final Year BBS | Comprehensive Reference")
c.setFont("Helvetica", 9.5)
c.drawCentredString(PW/2, PH*0.27,
"Definition · Classification · Pathophysiology · Causes · Clinical Features")
c.drawCentredString(PW/2, PH*0.215,
"Investigations · Management · Key Differentials · Red Flags · OSCE Tips")
c.setFillColor(colors.HexColor("#AAAAAA")); c.setFont("Helvetica", 7.5)
c.drawCentredString(PW/2, 12*mm,
"Sources: Harrison's 22E · Sleisenger & Fordtran · Yamada's Gastroenterology · Cummings ORL · Rosen's EM")
c.restoreState()
# ── Document setup ────────────────────────────────────────────────────────────
OUTPUT = "/home/daytona/workspace/dysphagia-booklet/Dysphagia_Booklet_Notes_v2.pdf"
lf = Frame(LX, CY, COL_W, COL_H, leftPadding=3, rightPadding=4,
topPadding=6, bottomPadding=3, id="left", showBoundary=0)
rf = Frame(RX, CY, COL_W, COL_H, leftPadding=4, rightPadding=3,
topPadding=6, bottomPadding=3, id="right", showBoundary=0)
cf = Frame(0, 0, PW, PH, leftPadding=0, rightPadding=0,
topPadding=0, bottomPadding=0, id="cover", showBoundary=0)
doc = BaseDocTemplate(
OUTPUT, pagesize=landscape(A4),
leftMargin=0, rightMargin=0, topMargin=0, bottomMargin=0
)
doc.addPageTemplates([
PageTemplate(id="cover", frames=[cf], onPage=_draw_cover),
PageTemplate(id="normal", frames=[lf, rf], onPage=_draw_normal),
])
# ═════════════════════════════════════════════════════════════════════════════
# STORY — content flows through left frame then right frame automatically
# Use FrameBreak to jump from left → right on same page
# Use PageBreak to go to next page (both frames)
# ═════════════════════════════════════════════════════════════════════════════
from reportlab.platypus import FrameBreak
story = []
# ── COVER ──────────────────────────────────────────────────────────────────
story += [NextPageTemplate("cover"), Spacer(1, PH)]
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 1 LEFT: Definition / Etymology / Epidemiology / 4 Phases
# RIGHT: Classification / Oro vs Oesophageal comparison
# ══════════════════════════════════════════════════════════════════════════════
story += [NextPageTemplate("normal"), PageBreak()]
# --- LEFT ---
story += [
H1("DEFINITION & ETYMOLOGY"),
HR(),
P("<b>Dysphagia</b> — Greek: <i>dys</i> (disordered) + <i>phagia</i> (to eat)."),
P("Sensation that food or liquid is <b>impeded in passage</b> from mouth to stomach."),
SP(1),
hbox("⚠ Dysphagia ALWAYS indicates pathology — NEVER physiological in adults.",
bg=RED, border=MIDRED),
SP(2),
H2("Distinguish from:"),
B("<b>Odynophagia</b> — painful swallowing (pain on transit, retrosternal)"),
B("<b>Globus sensation</b> — constant lump in throat; unrelated to swallowing; no impaction"),
B("<b>Phagophobia</b> — fear of swallowing (psychiatric); swallowing mechanism intact"),
SP(3),
H1("EPIDEMIOLOGY"),
HR(),
tbl(["General Population","Hospital / Care Settings"],
[["Prevalence ~8–22% adults","50% of stroke patients"],
["More common >50 yrs","60–80% of dementia patients"],
["M = F overall","30% of elderly in care homes"],
["Major cause of aspiration pneumonia","Leading cause of malnutrition in hospital"]],
[0.5, 0.5], even_bg=WHITE, odd_bg=GREEN),
SP(3),
H1("NORMAL SWALLOWING — 4 PHASES"),
HR(),
tbl(["Phase","Location","Control","Key Action"],
[["1. Oral Prep","Mouth","Voluntary","Chewing; bolus formation; salivation"],
["2. Oral","Mouth → Pharynx","Voluntary","Tongue propels bolus posteriorly"],
["3. Pharyngeal","Pharynx → UES","Involuntary reflex","Larynx elevates; epiglottis covers airway; UES opens (<1 sec)"],
["4. Oesophageal","UES → Stomach","Involuntary","Primary peristalsis; LES relaxes (8–10 sec total)"]],
[0.17, 0.20, 0.18, 0.45]),
]
story.append(FrameBreak())
# --- RIGHT ---
story += [
H1("CLASSIFICATION OF DYSPHAGIA"),
HR(),
cbox("ANATOMICAL",
["Oropharyngeal (Transfer) Dysphagia — problem in mouth / pharynx / UES",
"Oesophageal Dysphagia — problem in oesophageal body / LES / stomach"],
hbg=BLUE, bbg=LBLUE),
SP(2),
cbox("MECHANISTIC",
["Structural / Mechanical — physical narrowing or obstruction of lumen",
"Neuromuscular / Motility — disordered muscle contraction or innervation",
"Mixed — e.g. post-radiotherapy stricture + dysmotility"],
hbg=GOLD, bbg=AMBER),
SP(3),
H2("OROPHARYNGEAL vs OESOPHAGEAL — KEY DIFFERENCES"),
tbl(["Feature","Oropharyngeal","Oesophageal"],
[["Onset timing","Immediate (<1 sec post-swallow)","Delayed (>1 sec post-swallow)"],
["Localisation","Throat / neck / cervical","Retrosternal / epigastric"],
["Bolus type","Solids AND liquids equally; liquids often WORSE","Solids first → liquids later (mechanical); or both (motility)"],
["Aspiration","Common — laryngeal protection impaired","Less common unless severe / advanced"],
["Associated Sx","Cough, choke, nasal regurgitation, drooling, dysarthria","Heartburn, regurgitation, weight loss, odynophagia"],
["Main causes","Neurological (stroke, Parkinson's, MND)","Stricture, achalasia, malignancy, EoE"]],
[0.20, 0.40, 0.40]),
SP(2),
hbox("<b>Key Clue:</b> Solids ONLY → mechanical obstruction. Solids + Liquids from onset → motility disorder. Liquids WORSE than solids → neurological / oropharyngeal.",
bg=AMBER, border=GOLD),
]
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 2 LEFT: Causes — Oropharyngeal
# RIGHT: Causes — Oesophageal (structural + motility — FULL)
# ══════════════════════════════════════════════════════════════════════════════
story += [PageBreak()]
# --- LEFT ---
story += [
H1("CAUSES — OROPHARYNGEAL DYSPHAGIA"),
HR(),
H2("A. NEUROLOGICAL (~80% of cases)"),
tbl(["Central CNS","Peripheral / NMJ / Muscular"],
[["Stroke / CVA — most common overall","Myasthenia gravis"],
["Parkinson's Disease","Guillain-Barré syndrome"],
["Multiple Sclerosis","Post-polio syndrome"],
["Motor Neurone Disease (ALS/MND)","Botulism"],
["Pseudobulbar palsy","Diphtheria"],
["Bulbar palsy","CN IX / X / XII palsy"],
["Brain tumours / abscess","Diabetic neuropathy"],
["Cerebral palsy","Polymyositis / Dermatomyositis"],
["Wilson's disease","Myotonic dystrophy"],
["Huntington's disease","Oculopharyngeal muscular dystrophy"]],
[0.50, 0.50], even_bg=WHITE, odd_bg=PINK),
SP(2),
H2("B. STRUCTURAL — INTRINSIC"),
B("Zenker's diverticulum (Killian's dehiscence — posterior pouch)"),
B("Cricopharyngeal bar / UES achalasia (incomplete UES opening)"),
B("Pharyngeal web — Plummer-Vinson syndrome (upper oesophagus)"),
B("Post-cricoid carcinoma"),
B("Tonsillar / adenoidal hypertrophy"),
B("Pharyngeal abscess / tumour"),
SP(2),
H2("C. STRUCTURAL — EXTRINSIC"),
B("Cervical osteophytes (anterior cervical spondylosis)"),
B("Goitre / thyroid mass"),
B("Cervical lymphadenopathy"),
B("Retropharyngeal mass / abscess"),
B("Aortic aneurysm"),
SP(2),
H2("D. IATROGENIC / OTHER"),
B("Post-surgery: laryngectomy, neck dissection, thyroidectomy"),
B("Post-radiotherapy: fibrosis, xerostomia"),
B("Drugs: neuroleptics, anticholinergics, bisphosphonates, NSAIDs"),
B("Xerostomia: Sjögren's, medications, radiation"),
B("Poor dentition / ill-fitting dentures — poor bolus formation"),
B("Infectious: epiglottitis, Ludwig's angina, peritonsillar abscess"),
]
story.append(FrameBreak())
# --- RIGHT ---
story += [
H1("CAUSES — OESOPHAGEAL DYSPHAGIA"),
HR(),
H2("A. STRUCTURAL / MECHANICAL — INTRINSIC"),
cbox("Obstructing Lesions (Lumen Narrowing)",
["Oesophageal carcinoma — SCC (upper/mid) or Adenocarcinoma (lower/GOJ) ← RED FLAG",
"Peptic / acid stricture (GERD complication) — most common BENIGN stricture",
"Schatzki's ring (B-ring at squamocolumnar junction) — episodic, solids, start of meal",
"Oesophageal web (upper; Plummer-Vinson) — iron deficiency anaemia + web",
"Eosinophilic Oesophagitis (EoE) — young atopic males; rings + linear furrows on OGD",
"Corrosive / caustic stricture",
"Post-radiotherapy stricture",
"Anastomotic stricture (post-oesophagectomy)",
"Foreign body impaction"],
hbg=MIDRED, bbg=RED),
SP(2),
H2("B. STRUCTURAL / MECHANICAL — EXTRINSIC COMPRESSION"),
B("Mediastinal lymphadenopathy (TB, lymphoma, metastases)"),
B("Bronchial carcinoma / lung mass"),
B("Aortic aneurysm"),
B("Dysphagia lusoria — aberrant right subclavian artery"),
B("Enlarged left atrium (severe mitral stenosis)"),
B("Retrosternal goitre"),
B("Posterior mediastinal mass"),
SP(3),
H2("C. MOTILITY DISORDERS — OESOPHAGEAL"),
tbl(["Disorder","Mechanism","Classic Clue on Barium / Manometry"],
[["Achalasia","LES fails to relax + absent peristalsis (lost inhibitory neurons)","'Bird-beak' at LES; dilated oesophagus; Type II best prognosis"],
["Diffuse Oesophageal Spasm (DES)","Non-peristaltic simultaneous contractions (≥20% swallows)","'Corkscrew' / 'rosary bead'; chest pain mimics angina; intermittent"],
["Jackhammer / Nutcracker","High-amplitude peristaltic contractions","Chest pain > dysphagia; hypertensive peristalsis on HRM"],
["Scleroderma","Smooth muscle atrophy in lower 2/3 oesophagus","Absent peristalsis + low LES; severe GERD; Raynaud's; CREST"],
["EGJ Outflow Obstruction","Incomplete LES relaxation; some peristalsis preserved","Elevated IRP (>15 mmHg); less severe than achalasia"],
["Absent Contractility","Complete aperistalsis; normal IRP","Systemic disease (scleroderma, mixed CTD)"]],
[0.25, 0.38, 0.37]),
SP(2),
hbox("<b>Chicago Classification v4.0 (HRM):</b> Achalasia Type I = no contractility; Type II = pan-oesophageal pressurisation; Type III = spastic (premature). IRP >15 mmHg = impaired LES relaxation.",
bg=LBLUE, border=BLUE),
]
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 3 LEFT: History-Taking (FULL — 3 questions + all features)
# RIGHT: Red Flags + Investigations
# ══════════════════════════════════════════════════════════════════════════════
story += [PageBreak()]
# --- LEFT ---
story += [
H1("HISTORY-TAKING IN DYSPHAGIA"),
HR(),
H2("THE 3 KEY QUESTIONS (Oesophageal)"),
cbox("1. What type of food/liquid causes dysphagia?",
["Solids ONLY → mechanical obstruction (stricture, ring, tumour, web)",
"Solids AND Liquids from the START → motility disorder (achalasia, DES)",
"Liquids WORSE than solids → neuromuscular / oropharyngeal"],
hbg=BLUE, bbg=LBLUE),
SP(1),
cbox("2. Is dysphagia intermittent or progressive?",
["Intermittent + solids only → Schatzki's ring or DES",
"Progressive solids → then liquids → MALIGNANCY until proven otherwise",
"Progressive solids + liquids together → Achalasia",
"Long-standing (years) + intermittent → Benign (ring, web, motility)"],
hbg=GOLD, bbg=AMBER),
SP(1),
cbox("3. Does the patient have heartburn?",
["Heartburn + dysphagia → peptic stricture / GERD complication",
"No heartburn + progressive dysphagia → malignancy",
"Heartburn + Raynaud's + dysphagia → scleroderma / CREST",
"Heartburn + young male + food bolus impaction → EoE"],
hbg=DKGREEN, bbg=GREEN),
SP(3),
H2("FULL SYMPTOM CHARACTERISATION"),
tbl(["Feature to Ask","What to Ask","Clinical Significance"],
[["Site","Where does food stick — throat/chest/stomach?","Cervical = oropharyngeal; Retrosternal = oesophageal"],
["Onset / Duration","Days, weeks, months, years?","Rapid (<3 months) + progressive → malignancy"],
["Progression","Getting worse over time?","Progressive = malignancy, achalasia, MND"],
["Bolus type","Solids, liquids or both? Which is worse?","Key mechanistic differentiator (see above)"],
["Cough / choking","During or immediately after swallowing?","Silent aspiration especially in stroke / MND"],
["Nasal regurgitation","Fluid or food comes back through nose?","UES dysfunction / oropharyngeal cause"],
["Regurgitation","Undigested food? After hours / at night?","Achalasia (hours later); Zenker's (old food, halitosis)"],
["Weight loss","How much? Over how long?","RED FLAG — malignancy, severe dysphagia, nutritional compromise"],
["Voice change","Hoarse? Nasal? Breathy?","RLN palsy (malignancy); bulbar palsy; laryngeal disease"],
["Pain","Pain ON swallowing (odynophagia)?","Malignancy, EoE, infective / caustic oesophagitis"],
["Night symptoms","Woken by regurgitation? Cough at night?","Achalasia — oesophageal overflow; aspiration"],
["Relieving factors","Manoeuvres that help? Raise arms?","Raising arms relieves motility disorders (achalasia, DES)"],
["Heartburn","Reflux symptoms? GERD history?","Peptic stricture, Barrett's, scleroderma"],
["PMHx","Previous cancer, radiotherapy, autoimmune disease?","Post-RT stricture, scleroderma, MG, MND"],
["Drug history","NSAIDs, bisphosphonates, neuroleptics?","Drug-induced oesophagitis / dysmotility"],
["Social history","Smoking, alcohol, occupation?","Risk factors for oesophageal SCC and adenocarcinoma"]],
[0.20, 0.35, 0.45]),
]
story.append(FrameBreak())
# --- RIGHT ---
story += [
H1("RED FLAGS IN DYSPHAGIA"),
HR(),
cbox("⚠ URGENT RED FLAGS — 2-Week Wait Urgent Referral Required",
["Progressive dysphagia (solids progressing to liquids)",
"Unintentional weight loss (any amount over <3 months)",
"Odynophagia — painful swallowing",
"Haematemesis / melaena / iron-deficiency anaemia",
"Hoarseness / voice change / stridor",
"Age >55 with new onset dysphagia",
"Neck mass or supraclavicular lymphadenopathy",
"Recurrent aspiration pneumonia",
"Nocturnal regurgitation of undigested food",
"Short symptom duration with rapid deterioration"],
hbg=DKRED, bbg=RED),
SP(3),
H1("INVESTIGATIONS"),
HR(),
H2("First-Line Investigations"),
tbl(["Investigation","Indication","Key Finding / Purpose"],
[["Upper GI Endoscopy (OGD)","Suspected mechanical obstruction; ALL red flag dysphagia","Strictures, rings, tumours — allows biopsy + dilation"],
["Barium Swallow","Motility disorder suspected; post-endoscopy negative","'Bird-beak' achalasia; 'rat-tail' Ca; 'corkscrew' DES; webs; rings"],
["Modified Barium Swallow (MBSS)","Oropharyngeal dysphagia — SLT-led","Video fluoroscopy — identifies aspiration, phase timing defects"],
["Videofluoroscopy (VFSS)","Gold standard oropharyngeal phase assessment","Real-time swallow with SLT; different food consistencies tested"],
["High-Resolution Manometry (HRM)","Motility disorder after structural causes excluded","Chicago Classification — achalasia types, DES, jackhammer, EGJ outflow"],
["pH + Impedance Study","GERD-related dysphagia; peptic stricture suspected","Acid exposure time; reflux episodes; symptom correlation"],
["Chest X-Ray","Aspiration, mediastinal widening, achalasia","Air-fluid level (achalasia); mediastinal mass; aspiration changes"],
["CT Chest / Neck / Abdomen","Extrinsic compression; staging oesophageal Ca","Lymphadenopathy; aortic arch; tumour extent; metastases"]],
[0.25, 0.33, 0.42]),
SP(2),
H2("Second-Line / Specialist Investigations"),
B("<b>Endoscopic Ultrasound (EUS)</b> — T-staging oesophageal carcinoma"),
B("<b>PET-CT</b> — metastatic staging of oesophageal Ca (M-stage)"),
B("<b>Flexible Nasendoscopy (FNE)</b> — pharyngeal lesions, vocal cord movement, RLN palsy"),
B("<b>Laryngoscopy</b> — post-cricoid lesions, laryngeal malignancy"),
B("<b>MRI Brain / Spine</b> — central neurological cause (stroke, MS, tumour)"),
B("<b>EMG / Nerve Conduction Studies</b> — MG, MND, myopathy"),
B("<b>Oesophageal Scintigraphy</b> — quantifies transit time and motility"),
B("<b>Blood Tests:</b> FBC (anaemia), CRP, LFTs, TFTs, ANA, anti-AChR Ab (MG), CK (myopathy), anti-Scl-70 (scleroderma)"),
SP(2),
hbox("<b>Investigation Algorithm:</b> Red flags → OGD first. Suspected motility → barium swallow first, then HRM. Oropharyngeal → MBSS / videofluoroscopy first.",
bg=AMBER, border=GOLD),
]
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 4 LEFT: Achalasia + EoE + Zenker's in detail
# RIGHT: Management — general + condition-specific + nutrition
# ══════════════════════════════════════════════════════════════════════════════
story += [PageBreak()]
# --- LEFT ---
story += [
H1("KEY CONDITIONS IN DETAIL"),
HR(),
H2("ACHALASIA"),
tbl(["Feature","Details"],
[["Definition","Primary oesophageal motility disorder: absent LES relaxation + aperistalsis"],
["Pathophysiology","Loss of inhibitory myenteric (Auerbach's) plexus neurons (VIP + NO). Unoppressed excitatory cholinergic activity → sustained LES contraction"],
["Chicago Types","Type I: absent contractility (no pressurisation); Type II: pan-oesophageal pressurisation ← BEST prognosis; Type III: spastic (premature)"],
["Symptoms","Dysphagia solids + liquids equally; regurgitation of UNDIGESTED food (hours later); weight loss; nocturnal cough / aspiration; chest pain; hiccups"],
["Barium Swallow","'Bird's beak' / 'rat-tail' tapering at LES; dilated sigmoid oesophagus above; air-fluid level on CXR"],
["Manometry (HRM)","Elevated IRP >15 mmHg; absent peristalsis (Types I/II); premature contractions (Type III)"],
["OGD","Normal mucosa (unlike stricture); 'pop' on passing scope through LES; exclude pseudoachalasia (malignancy at cardia)"],
["Treatment","Pneumatic dilation (PD — 30/35 mm); POEM (per-oral endoscopic myotomy) — preferred; Laparoscopic Heller myotomy + partial fundoplication; CCB / nitrates (temporary / elderly)"]],
[0.22, 0.78]),
SP(2),
H2("EOSINOPHILIC OESOPHAGITIS (EoE)"),
B("Demographics: young males, atopic (asthma, eczema, food allergy)"),
B("Pathology: ≥15 eosinophils / high-power field (HPF) on oesophageal biopsy"),
B("Endoscopy: linear furrows, concentric rings ('trachealization'), white exudates, narrow lumen, stricture"),
B("Triggers: milk, wheat, eggs, soy, nuts, seafood"),
B("Classic presentation: young male, recurrent food bolus impaction"),
B("Rx: PPI trial 6–8 wks → if no response → swallowed topical steroids (fluticasone / budesonide) → 6-food elimination diet → endoscopic dilation"),
SP(2),
H2("ZENKER'S DIVERTICULUM"),
B("Pulsion diverticulum at Killian's dehiscence (between thyropharyngeus + cricopharyngeus)"),
B("Mechanism: UES dysfunction → ↑ hypopharyngeal pressure → mucosal herniation posteriorly"),
B("Symptoms: dysphagia, regurgitation of OLD undigested food, halitosis, gurgling swallow, aspiration, nocturnal cough"),
B("Barium: posterior pharyngeal pouch filling with contrast on lateral view"),
B("Rx: endoscopic stapler diverticulotomy / cricopharyngeal myotomy — well tolerated in elderly"),
SP(2),
H2("PLUMMER-VINSON SYNDROME"),
tbl(["Triad","Extra Features"],
[["1. Post-cricoid web (upper oesophagus)","Demographics: middle-aged women"],
["2. Iron-deficiency anaemia","Glossitis, angular cheilitis, koilonychia"],
["3. Dysphagia","⚠ PRE-MALIGNANT → post-cricoid SCC risk"],
["Treatment: iron replacement + dilation","Also called Patterson-Kelly syndrome"]],
[0.55, 0.45]),
]
story.append(FrameBreak())
# --- RIGHT ---
story += [
H1("MANAGEMENT OF DYSPHAGIA"),
HR(),
H2("IMMEDIATE INITIAL ASSESSMENT"),
cbox("Safety First — All Dysphagia Patients",
["Assess aspiration risk: wet/gurgly voice after swallow, cough, chest signs",
"Nil by mouth if HIGH aspiration risk — pending formal assessment",
"SALT (Speech & Language Therapy) referral — MANDATORY for all patients",
"Bedside swallowing assessment → MBSS / videofluoroscopy if oropharyngeal",
"Dietitian referral — assess nutritional status; MUST weight patient",
"Modify food/fluid consistency per IDDSI framework (Levels 0–7)",
"Consider NG tube or PEG if oral intake is unsafe"],
hbg=DKGREEN, bbg=GREEN),
SP(2),
H2("CONDITION-SPECIFIC MANAGEMENT"),
tbl(["Condition","First-Line Treatment","Key Notes"],
[["Oesophageal Ca","MDT: surgery (Ivor-Lewis) ± neoadjuvant chemoRT","Staging guides resectability; palliative stent if unresectable"],
["Peptic stricture","PPI (high-dose) + endoscopic dilation","Repeat dilation if recurrent; treat underlying GERD"],
["Achalasia","POEM / pneumatic dilation / Heller myotomy","POEM preferred; Type II best prognosis; CCB if unfit"],
["Schatzki's ring","Endoscopic dilation; PPI if GERD-related","Single dilation often curative; annual PPI maintenance"],
["EoE","PPI → topical steroids → elimination diet","Biopsies at follow-up OGD to confirm histological remission"],
["DES / Jackhammer","PPI + CCB (nifedipine) / nitrates","POEM increasingly used for refractory spasm"],
["Zenker's","Endoscopic stapler diverticulotomy","Surgical myotomy if endoscopic fails; elderly well tolerated"],
["Pharyngeal web","Endoscopic dilation + treat iron deficiency","Plummer-Vinson: risk of post-cricoid Ca; surveillance"],
["Stroke / neurological","SLT-led swallow rehab + MBSS","Texture modification, compensatory strategies (chin tuck), PEG"],
["Myasthenia gravis","Pyridostigmine + immunosuppression","IVIG / plasmapheresis in myasthenic crisis"],
["Scleroderma","Aggressive PPI + prokinetics (metoclopramide)","No specific oesophageal Rx; aspiration prevention key"],
["EoE — biologic","Dupilumab (IL-4/IL-13 blockade) — new option","FDA approved 2022 for EoE in adults"]],
[0.22, 0.40, 0.38]),
SP(2),
H2("NUTRITIONAL SUPPORT"),
tbl(["Route","Indication","Duration"],
[["Modified diet (IDDSI)","Mild-moderate oropharyngeal dysphagia","Ongoing — reassess regularly"],
["Nasogastric (NG) tube","Short-term; unsafe swallow; acute illness","<4–6 weeks"],
["PEG tube","Long-term unsafe swallow (stroke, MND, Ca)","≥4–6 weeks; MDT decision"],
["TPN","GI tract non-functional","Temporary bridging only"]],
[0.25, 0.48, 0.27]),
]
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 5 LEFT: Scleroderma + Barrett's + Oesophageal Ca + DES detail
# RIGHT: OSCE framework + Mnemonics + Classic exam scenarios (FULL)
# ══════════════════════════════════════════════════════════════════════════════
story += [PageBreak()]
# --- LEFT ---
story += [
H1("IMPORTANT ASSOCIATIONS & SYNDROMES"),
HR(),
H2("SCLERODERMA / SYSTEMIC SCLEROSIS & DYSPHAGIA"),
B("Fibrosis replaces smooth muscle in lower 2/3 of oesophagus"),
B("Results in: absent / low-amplitude peristalsis + low / absent LES pressure"),
B("Consequence: severe GERD → peptic stricture → Barrett's oesophagus"),
B("CREST: Calcinosis, Raynaud's, oEsophageal dysmotility, Sclerodactyly, Telangiectasia"),
B("Manometry: low-amplitude / absent contractions distally; low LES pressure"),
B("Management: aggressive high-dose PPI + head-of-bed elevation + prokinetics"),
SP(2),
H2("BARRETT'S OESOPHAGUS"),
cbox("Key Facts",
["Definition: metaplasia of squamous → columnar (intestinal type) epithelium at SCJ",
"Caused by chronic GERD — acid + bile exposure",
"Confirmed by: OGD biopsy showing goblet cells (intestinal metaplasia)",
"Grading: Prague criteria — C (circumferential) and M (maximum) extent in cm",
"Risk of adenocarcinoma: ~0.5% per year (short segment) to ~1% per year (long segment)",
"Surveillance: OGD every 2–5 years (no dysplasia); 6-monthly (low-grade dysplasia)",
"High-grade dysplasia / T1: endoscopic mucosal resection (EMR) or radiofrequency ablation (RFA)"],
hbg=MIDRED, bbg=RED),
SP(2),
H2("OESOPHAGEAL CARCINOMA — SCC vs ADENOCARCINOMA"),
tbl(["Feature","SCC","Adenocarcinoma"],
[["Location","Upper / mid oesophagus","Lower oesophagus / GOJ"],
["Risk factors","Smoking, alcohol, hot liquids, achalasia, nitrosamines, Plummer-Vinson","GERD, Barrett's, obesity, smoking, alcohol"],
["Background change","None (no Barrett's)","Barrett's oesophagus (pre-malignant)"],
["Incidence trend","Declining in Western countries","Rising rapidly in West"],
["Staging","CT + EUS + PET-CT (TNM staging)","CT + EUS + PET-CT (TNM staging)"],
["Surgery","Oesophagectomy (Ivor-Lewis / McKeown)","Oesophagogastrectomy"],
["Palliation","Self-expanding metal stent (SEMS); radiotherapy","SEMS; chemoradiotherapy"]],
[0.22, 0.39, 0.39]),
SP(2),
H2("DIFFUSE OESOPHAGEAL SPASM (DES)"),
B("Hallmark: ≥20% of swallows show simultaneous non-peristaltic contractions"),
B("Barium: 'corkscrew' or 'rosary bead' oesophagus"),
B("HRM (Chicago v4): DL <4.5 sec; normal IRP (<15 mmHg)"),
B("Chest pain can mimic cardiac angina — always exclude cardiac first"),
B("Management: PPI + nifedipine / nitrates; POEM for refractory cases"),
]
story.append(FrameBreak())
# --- RIGHT ---
story += [
H1("OSCE & CLINICAL PEARLS — FINAL YEAR BBS"),
HR(),
H2("OSCE HISTORY FRAMEWORK FOR DYSPHAGIA"),
cbox("Structured History — 10 Steps",
["1. SITE: Where does it stick? Throat / chest / stomach? Point to it.",
"2. ONSET: How long? Sudden vs gradual? Any triggering event?",
"3. CHARACTER: Solids, liquids or both? Which started first? Which is worse?",
"4. PROGRESSION: Stable, improving or worsening? Rate of change?",
"5. ASSOCIATED: Weight loss, odynophagia, regurgitation, cough, voice change, heartburn, anaemia symptoms",
"6. TIMING: Intermittent vs constant? At start of meal or any time? After which foods?",
"7. RELIEVING / AGGRAVATING: Manoeuvres that help (raising arms, Valsalva)?",
"8. PMHx: GERD, autoimmune disease, previous cancer, radiotherapy, previous dysphagia",
"9. DRUG Hx: NSAIDs, bisphosphonates (pill oesophagitis), neuroleptics, anticholinergics",
"10. SHx: Smoking (pack-years), alcohol (units/week), occupation, diet, swallowing history"],
hbg=BLUE, bbg=LBLUE),
SP(2),
H2("RED FLAGS MNEMONIC — 'PHOWARD'"),
cbox("PHOWARD = 2-Week Wait Referral",
["P — Progressive dysphagia (solids → liquids)",
"H — Haematemesis / melaena",
"O — Old age (>55 years) with NEW dysphagia",
"W — Weight loss (unintentional, any amount)",
"A — Anaemia (iron-deficiency)",
"R — Regurgitation (nocturnal / undigested) + hoarseness",
"D — Duration short (<3 months) with rapid progression"],
hbg=DKRED, bbg=RED),
SP(2),
H2("CLASSIC EXAM SCENARIOS — SPOT DIAGNOSIS"),
tbl(["Clinical Scenario","Likely Diagnosis"],
[["Young male, food bolus impaction, atopy Hx, OGD shows linear furrows + concentric rings","Eosinophilic Oesophagitis (EoE)"],
["Elderly patient, gurgling swallow, regurgitates OLD undigested food, halitosis","Zenker's Diverticulum"],
["Middle-aged woman, iron-deficiency anaemia, upper / post-cricoid dysphagia","Plummer-Vinson Syndrome"],
["Dysphagia solids + liquids equally, regurgitates undigested food at night, 'bird-beak' barium","Achalasia (Type II most likely)"],
["55-year-old smoker + heavy drinker, progressive dysphagia solids → liquids, weight loss","Oesophageal Carcinoma (SCC)"],
["Obese patient, longstanding GERD, now progressive dysphagia, OGD = columnar mucosa at SCJ","Barrett's + possible Adenocarcinoma"],
["Raynaud's + severe heartburn + absent peristalsis on manometry + low LES pressure","Scleroderma / CREST syndrome"],
["Stroke patient, coughs on ALL fluids, wet / gurgly voice after swallow","Neurogenic dysphagia + high aspiration risk"],
["Intermittent dysphagia for SOLIDS at the very START of a meal, self-resolves","Schatzki's Ring"],
["Severe chest pain + intermittent dysphagia solids + liquids, 'corkscrew' on barium","Diffuse Oesophageal Spasm"],
["Young male + atopy + dysphagia + OGD normal + biopsies show >15 eosinophils/HPF","EoE (PPI-non-responsive — start topical steroid)"],
["Post-laryngectomy patient, coughing during swallowing, nasal regurgitation","Oropharyngeal dysphagia — SLT + MBSS urgently"]],
[0.55, 0.45]),
SP(2),
hbox("<b>Remember:</b> ALL patients with dysphagia need SLT assessment. Unexplained weight loss + progressive dysphagia = malignancy until proven otherwise. Motility disorders cause dysphagia to BOTH solids and liquids from the start.",
bg=AMBER, border=GOLD),
]
# ── Build ─────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"Done: {OUTPUT}")
cd /home/daytona/workspace/dysphagia-booklet && python3 build_booklet_v2.py 2>&1
ls -lh /home/daytona/workspace/dysphagia-booklet/
Dysphagia Booklet Notes v2
PDF Document · PDF
Flowable with fixed-height frames - anything that didn't fit just got silently cut. v2 uses ReportLab's native Frame + FrameBreak system, so:What's the clinical significance of Regurgitation...like old food or immediately and what's the achalsis and zenkers got to do with
| Type | Definition |
|---|---|
| Regurgitation | Passive return of food/fluid from oesophagus or pharynx — NO nausea, NO retching, NO acid |
| Vomiting | Active, forceful expulsion from stomach — nausea + retching + acidic/bile taste |
| Feature | Achalasia | Zenker's Diverticulum |
|---|---|---|
| Site of hold-up | Oesophageal body (above LES) | Pharyngeal pouch (above UES) |
| Why food stays | LES won't open — food can't pass to stomach | Food falls into the pouch and gets trapped |
| Timing of regurgitation | Hours after eating (oesophageal reservoir) | Hours to days (pouch reservoir) |
| Food character | Undigested, not acidic, no bile | Undigested, not acidic, often foul-smelling |
| Halitosis | Moderate (fermenting in oesophagus) | Prominent (rotting in pouch) |
| Gurgling noise | Not typical | Yes — neck borborygmi (Boyce's sign) |
| Aspiration risk | High — nocturnal overflow | High — nocturnal pouch emptying |
| Neck compression | No effect | Can empty the pouch manually |
| Barium finding | Bird-beak at LES; dilated oesophagus | Posterior pouch on lateral view |
| Age group | Any age, younger adults too | Typically elderly (>60 yrs) |
As zenkers pouch a pharyngeal pouch shidtn it cause orpharyngeal dysphagia
| Framing | Why |
|---|---|
| Oropharyngeal dysphagia | The pouch is at the pharyngo-oesophageal junction, above the UES. The dysfunction is at the cricopharyngeus (UES). Symptoms are classic oropharyngeal — food sticks in the neck/throat region, not retrosternal. |
| Listed alongside oesophageal causes | Some GI textbooks group it here because it presents with regurgitation similar to achalasia, and gastroenterologists manage it endoscopically. It's a classification convenience, not anatomy. |
Mouth
↓
Pharynx
↓
Killian's Dehiscence ← pouch forms HERE (between thyropharyngeus
↓ and cricopharyngeus — both parts of inferior
Cricopharyngeus (UES) pharyngeal constrictor)
↓
Oesophagus
| Zenker's | Achalasia | |
|---|---|---|
| Level | Pharynx / above UES | Oesophagus / LES |
| Classification | Oropharyngeal | Oesophageal motility |
| Why old food | Pouch retains food | Dilated oesophagus retains food |
| Dysphagia felt | Neck / cervical | Retrosternal |
| Timing of dysphagia | Immediate on swallowing | Delayed |
Can u send me just one page a4 of the same pdf but more to then point last minute revision + once related main
+Osce related main points
~/dysphagia-booklet/build_quickref.py
"""
Dysphagia — 1-Page A4 Last Minute Revision + OSCE Cheat Sheet
Left | Right split, portrait A4
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
BaseDocTemplate, Frame, PageTemplate,
Paragraph, Spacer, Table, TableStyle,
HRFlowable, FrameBreak
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
PW, PH = A4 # 595 x 842 pt (portrait)
MARGIN = 8 * mm
HEADER_H = 16 * mm
FOOTER_H = 7 * mm
GAP = 4 * mm
COL_W = (PW - 2*MARGIN - GAP) / 2
COL_H = PH - MARGIN - HEADER_H - FOOTER_H
LX = MARGIN
RX = MARGIN + COL_W + GAP
CY = FOOTER_H
# ── Colours ──────────────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1B3A6B")
BLUE = colors.HexColor("#2E6DA4")
LBLUE = colors.HexColor("#D6E9F8")
GOLD = colors.HexColor("#C8960C")
AMBER = colors.HexColor("#FFF3CD")
GREEN = colors.HexColor("#E8F5E9")
DKGRN = colors.HexColor("#2E7D32")
RED = colors.HexColor("#FFEBEE")
DKRED = colors.HexColor("#B71C1C")
MIDRED = colors.HexColor("#C62828")
PINK = colors.HexColor("#FCE4EC")
DKPINK = colors.HexColor("#AD1457")
DIVL = colors.HexColor("#BBBBBB")
WHITE = colors.white
CREAM = colors.HexColor("#FDFBF2")
# ── Styles ────────────────────────────────────────────────────────────────────
_SS = getSampleStyleSheet()
def ps(name,**kw):
kw.setdefault("parent",_SS["Normal"])
return ParagraphStyle(name,**kw)
H1s = ps("H1s", fontSize=8, fontName="Helvetica-Bold", textColor=WHITE,
spaceBefore=3, spaceAfter=1, leading=10, alignment=TA_CENTER)
H2s = ps("H2s", fontSize=7.5, fontName="Helvetica-Bold", textColor=NAVY,
spaceBefore=2, spaceAfter=1, leading=9.5)
BOD = ps("BOD", fontSize=6.8, fontName="Helvetica", leading=9.2,
spaceAfter=0.5)
BUL = ps("BUL", fontSize=6.8, fontName="Helvetica", leading=9.2,
spaceAfter=0.5, leftIndent=7, firstLineIndent=0)
BHD = ps("BHD", fontSize=7, fontName="Helvetica-Bold", textColor=WHITE,
leading=9, alignment=TA_CENTER)
SML = ps("SML", fontSize=6.2, fontName="Helvetica", leading=8.5,
textColor=colors.HexColor("#444444"))
def P(t,s=BOD): return Paragraph(t,s)
def B(t): return Paragraph(f"• {t}", BUL)
def H1(t,bg=NAVY): return _hbar(t,bg)
def H2(t): return Paragraph(t, H2s)
def SP(n=1): return Spacer(1, n*mm)
def HR(): return HRFlowable(width="100%",thickness=0.4,
color=DIVL,spaceAfter=1,spaceBefore=1)
def _hbar(title, bg):
ts = TableStyle([
("BACKGROUND",(0,0),(-1,-1),bg),
("TOPPADDING",(0,0),(-1,-1),2),("BOTTOMPADDING",(0,0),(-1,-1),2),
("LEFTPADDING",(0,0),(-1,-1),4),("RIGHTPADDING",(0,0),(-1,-1),4),
])
return Table([[Paragraph(title,H1s)]],
colWidths=[COL_W-2*mm], style=ts)
def cbox(title, items, hbg=BLUE, bbg=LBLUE):
w = COL_W - 2*mm
data = [[Paragraph(title, BHD)]]
for it in items:
data.append([Paragraph(f"• {it}", BOD)])
ts = TableStyle([
("BACKGROUND",(0,0),(-1,0),hbg),
("BACKGROUND",(0,1),(-1,-1),bbg),
("BOX",(0,0),(-1,-1),0.6,NAVY),
("GRID",(0,0),(-1,-1),0.25,DIVL),
("TOPPADDING",(0,0),(-1,-1),1.5),("BOTTOMPADDING",(0,0),(-1,-1),1.5),
("LEFTPADDING",(0,0),(-1,-1),4),("RIGHTPADDING",(0,0),(-1,-1),4),
])
return Table(data, colWidths=[w], style=ts)
def tbl(headers, rows, fracs, hbg=NAVY, ebg=WHITE, obg=LBLUE):
cws = [(COL_W-2*mm)*f for f in fracs]
data = [[Paragraph(f"<b>{h}</b>",BOD) for h in headers]]
for i,row in enumerate(rows):
data.append([Paragraph(str(c),BOD) for c in row])
ts = TableStyle([
("BACKGROUND",(0,0),(-1,0),hbg),
("TEXTCOLOR",(0,0),(-1,0),WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),6.8),
("ROWBACKGROUNDS",(0,1),(-1,-1),[ebg,obg]),
("BOX",(0,0),(-1,-1),0.6,NAVY),
("GRID",(0,0),(-1,-1),0.25,DIVL),
("TOPPADDING",(0,0),(-1,-1),1.5),("BOTTOMPADDING",(0,0),(-1,-1),1.5),
("LEFTPADDING",(0,0),(-1,-1),3),("RIGHTPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
])
return Table(data, colWidths=cws, style=ts)
def hbox(text, bg=AMBER, border=GOLD):
ts = TableStyle([
("BACKGROUND",(0,0),(-1,-1),bg),
("BOX",(0,0),(-1,-1),0.8,border),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
])
return Table([[Paragraph(text,BOD)]],colWidths=[COL_W-2*mm],style=ts)
# ── Page background ───────────────────────────────────────────────────────────
def _draw(canvas, doc):
c = canvas
c.saveState()
c.setFillColor(CREAM); c.rect(0,0,PW,PH,fill=1,stroke=0)
# header
c.setFillColor(NAVY); c.rect(0,PH-HEADER_H,PW,HEADER_H,fill=1,stroke=0)
c.setFillColor(WHITE); c.setFont("Helvetica-Bold",11)
c.drawCentredString(PW/2, PH-9*mm, "DYSPHAGIA — Last Minute Revision + OSCE Cheat Sheet")
c.setFillColor(GOLD); c.setFont("Helvetica-Bold",7.5)
c.drawCentredString(PW/2, PH-14*mm, "Final Year BBS | Harrison's 22E · Sleisenger & Fordtran · Yamada's · Cummings · Rosen's EM")
# divider
mx = MARGIN + COL_W + GAP/2
c.setStrokeColor(DIVL); c.setLineWidth(0.7)
c.line(mx, CY, mx, PH-HEADER_H-1)
# LEFT / RIGHT
c.setFont("Helvetica-Bold",6); c.setFillColor(colors.HexColor("#AAAAAA"))
c.drawCentredString(LX+COL_W/2, PH-HEADER_H-4, "◄ LEFT")
c.drawCentredString(RX+COL_W/2, PH-HEADER_H-4, "RIGHT ►")
# footer
c.setFillColor(NAVY); c.rect(0,0,PW,FOOTER_H,fill=1,stroke=0)
c.setFillColor(WHITE); c.setFont("Helvetica",6)
c.drawCentredString(PW/2, 2*mm, "Sources: Harrison's 22E (2025) · Sleisenger & Fordtran · Yamada's Gastroenterology · Cummings ORL · Rosen's EM")
c.restoreState()
# ── Document ──────────────────────────────────────────────────────────────────
OUTPUT = "/home/daytona/workspace/dysphagia-booklet/Dysphagia_QuickRef_1Page.pdf"
lf = Frame(LX, CY, COL_W, COL_H, leftPadding=2,rightPadding=3,
topPadding=5, bottomPadding=2, id="left", showBoundary=0)
rf = Frame(RX, CY, COL_W, COL_H, leftPadding=3,rightPadding=2,
topPadding=5, bottomPadding=2, id="right", showBoundary=0)
doc = BaseDocTemplate(OUTPUT, pagesize=A4,
leftMargin=0,rightMargin=0,topMargin=0,bottomMargin=0)
doc.addPageTemplates([
PageTemplate(id="main", frames=[lf,rf], onPage=_draw),
])
# ═══════════════════════════════════════════════════════════════════════════
# LEFT COLUMN
# ═══════════════════════════════════════════════════════════════════════════
story = []
story += [
H1("DEFINITION & QUICK CLASSIFICATION", NAVY), SP(),
P("<b>Dysphagia</b> = difficulty swallowing. <b>Always pathological.</b>"),
P("≠ Globus (no impaction) ≠ Odynophagia (pain) ≠ Phagophobia (fear)"),
SP(),
tbl(["","Oropharyngeal","Oesophageal"],
[["Timing","<1 sec — immediate","Delayed >1 sec"],
["Site felt","Neck / cervical","Retrosternal"],
["Bolus","Liquids ≥ solids","Solids first (mechanical); both (motility)"],
["Aspiration","Common","Less common"],
["Key cause","Stroke / MND / Zenker's","Stricture / achalasia / Ca"]],
[0.18,0.41,0.41]),
SP(),
H1("CAUSES — MUST KNOW", BLUE), SP(),
tbl(["Oropharyngeal","Oesophageal Structural","Oesophageal Motility"],
[["Stroke (80% neuro)","Peptic stricture (GERD)","Achalasia"],
["Parkinson's / MND","Oesophageal Ca ← RF","DES (corkscrew)"],
["Bulbar / pseudobulbar","Schatzki's ring","Jackhammer"],
["Zenker's diverticulum*","EoE (young ♂ + atopy)","Scleroderma"],
["Plummer-Vinson","Post-RT / caustic stricture","EGJ outflow obstruct."],
["Post-RT / surgery","Extrinsic compression","Absent contractility"]],
[0.34,0.37,0.29], ebg=WHITE, obg=PINK),
P("*Zenker's = <b>oropharyngeal</b> (pharyngeal pouch above UES)", SML),
SP(),
H1("REGURGITATION — TIMING IS KEY", colors.HexColor("#5B2C8D")), SP(),
tbl(["Timing","Diagnosis","Why"],
[["Immediately (<1s)","Oropharyngeal obstruction / cricopharyngeal","Food hits a block before UES"],
["Hours later (undigested, no acid)","ACHALASIA","Food stored in dilated oesophagus above LES"],
["Hours–days (old food + halitosis + gurgle)","ZENKER'S DIVERTICULUM","Food trapped in pharyngeal pouch above UES"],
["Acidic / sour taste","GERD / peptic stricture","Gastric reflux — food reached stomach"]],
[0.22,0.32,0.46], ebg=WHITE, obg=LBLUE),
SP(),
H1("ACHALASIA vs ZENKER'S — COMPARE", NAVY), SP(),
tbl(["","Achalasia","Zenker's"],
[["Level","Oesophagus (LES)","Pharynx (above UES)"],
["Type","Oesophageal motility","Oropharyngeal structural"],
["Barium","Bird-beak at LES","Posterior pouch lateral view"],
["Regurg","Hours later, undigested","Hours–days, old food, halitosis"],
["Gurgle","No","Yes — Boyce's sign (neck)"],
["Risk","Aspiration pneumonia","Perforation on blind scope ⚠"],
["Rx","POEM / dilation / Heller","Endoscopic diverticulotomy"]],
[0.28,0.36,0.36]),
]
# ═══════════════════════════════════════════════════════════════════════════
# RIGHT COLUMN
# ═══════════════════════════════════════════════════════════════════════════
story.append(FrameBreak())
story += [
H1("OSCE HISTORY — 3 KEY QUESTIONS FIRST", NAVY), SP(),
cbox("Q1: What food? → Mechanism",
["Solids ONLY → mechanical obstruction (stricture, ring, Ca, web)",
"Solids + Liquids from START → motility disorder (achalasia, DES)",
"Liquids WORSE → neuromuscular / oropharyngeal"],
hbg=BLUE, bbg=LBLUE),
SP(0.5),
cbox("Q2: Progressive or intermittent? → Urgency",
["Progressive solids → liquids → MALIGNANCY — 2WW referral",
"Progressive solids + liquids → Achalasia",
"Intermittent solids at meal start → Schatzki's ring",
"Long-standing years + intermittent → benign (ring / web / motility)"],
hbg=GOLD, bbg=AMBER),
SP(0.5),
cbox("Q3: Heartburn? → Aetiology",
["Heartburn + dysphagia → peptic stricture / GERD",
"Heartburn + Raynaud's → scleroderma / CREST",
"No heartburn + rapid progression → malignancy"],
hbg=DKGRN, bbg=GREEN),
SP(),
H1("RED FLAGS — 2-WEEK WAIT (PHOWARD)", DKRED), SP(),
tbl(["P","H","O","W","A","R","D"],
[["Progressive\ndysphagia","Haematemesis\n/ melaena","Old age\n>55 yrs","Weight\nloss","Anaemia\n(IDA)","Regurgitation\n+ hoarseness","Duration\n<3 months"]],
[0.143,0.143,0.143,0.143,0.143,0.143,0.142],
ebg=RED, obg=PINK),
SP(),
H1("CLASSIC OSCE SPOT DIAGNOSES", BLUE), SP(),
tbl(["Scenario","Diagnosis"],
[["Young ♂ + atopy + food bolus impaction + OGD rings/furrows","EoE"],
["Elderly + gurgle + OLD food regurg + halitosis + neck mass","Zenker's diverticulum"],
["♀ + IDA + post-cricoid dysphagia + angular cheilitis","Plummer-Vinson syndrome"],
["Solids + liquids + nocturnal regurg undigested + bird-beak","Achalasia"],
["Smoker >55 + progressive solids → liquids + weight loss","Oesophageal SCC"],
["Obese + GERD Hx + progressive dysphagia + columnar OGD","Barrett's → Adenocarcinoma"],
["Raynaud's + severe GERD + absent peristalsis on manometry","Scleroderma / CREST"],
["Stroke pt + coughs on ALL fluids + wet voice post-swallow","Neurogenic dysphagia — aspiration risk"],
["Intermittent solids ONLY at START of meal — self-resolves","Schatzki's ring"],
["Chest pain + intermittent solids+liquids + corkscrew barium","Diffuse oesophageal spasm"]],
[0.62,0.38], ebg=WHITE, obg=LBLUE),
SP(),
H1("INVESTIGATIONS QUICK GUIDE", NAVY), SP(),
tbl(["Suspicion","First Test","Key Finding"],
[["Red flags / mechanical","OGD + biopsy","Stricture, tumour, EoE (eosinophils)"],
["Motility disorder","Barium swallow","Bird-beak / corkscrew / rat-tail"],
["Oropharyngeal / aspiration","MBSS (SLT-led)","Phase defect, aspiration, Zenker's"],
["Confirm motility type","HRM","Chicago class — achalasia type, DES"],
["Staging oesoph. Ca","CT + EUS + PET","TNM staging, resectability"]],
[0.30,0.32,0.38]),
SP(),
hbox("<b>OSCE MUST-SAY:</b> All dysphagia → SLT referral. Progressive + weight loss = 2WW. Zenker's = oropharyngeal. Regurgitation timing distinguishes achalasia (hours, oesophageal) from Zenker's (hours–days, pharyngeal pouch). Never scope Zenker's blindly.",
bg=AMBER, border=GOLD),
]
doc.build(story)
print(f"Done → {OUTPUT}")
cd /home/daytona/workspace/dysphagia-booklet && python3 build_quickref.py 2>&1
ls -lh /home/daytona/workspace/dysphagia-booklet/Dysphagia_QuickRef_1Page.pdf
Dysphagia QuickRef 1Page
PDF Document · PDF