make me details note on GERD for Residency, FCPS, MRCP exam as pdf with diagram and image
"gastroesophageal reflux disease"[MeSH Terms] AND management
"gastroesophageal reflux"[MeSH Terms] AND "proton pump inhibitors"[MeSH Terms]
GERD pathophysiology diagnosis management guidelines 2024 2025 MRCP residency exam
https://pmc.ncbi.nlm.nih.gov/articles/PMC8754510
mkdir -p /home/daytona/workspace/gerd-notes && ls /home/daytona/workspace/gerd-n…
~/gerd-notes/generate_gerd_pdf.py
#!/usr/bin/env python3
"""
Comprehensive GERD Notes PDF Generator
For Residency / FCPS / MRCP Exam Preparation
"""
import urllib.request
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, Image, KeepTogether
)
from reportlab.platypus.flowables import Flowable
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Circle, Polygon
from reportlab.graphics import renderPDF
from reportlab.graphics.charts.barcharts import VerticalBarChart
from io import BytesIO
import os
# ── Color Palette ────────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1A3A5C")
MID_BLUE = colors.HexColor("#2E6DA4")
LIGHT_BLUE = colors.HexColor("#D6E8FA")
ACCENT_RED = colors.HexColor("#C0392B")
ACCENT_GREEN= colors.HexColor("#1A7A4A")
ACCENT_ORANGE= colors.HexColor("#D35400")
YELLOW_BG = colors.HexColor("#FFF9C4")
GREY_BG = colors.HexColor("#F5F5F5")
WHITE = colors.white
BLACK = colors.black
TEAL = colors.HexColor("#117A8B")
PURPLE = colors.HexColor("#6C3483")
OUTPUT_PATH = "/home/daytona/workspace/gerd-notes/GERD_Exam_Notes.pdf"
# ── Custom Flowables ─────────────────────────────────────────────────────────
class ColoredBox(Flowable):
"""A colored box with text for key points."""
def __init__(self, text, bg_color, border_color, width=None, left_bar=True):
super().__init__()
self.text = text
self.bg_color = bg_color
self.border_color = border_color
self.box_width = width or (A4[0] - 1.8*inch)
self.left_bar = left_bar
def wrap(self, avail_w, avail_h):
self.box_width = avail_w
return (avail_w, 0.55*inch)
def draw(self):
c = self.canv
w = self.box_width
h = 0.45*inch
if self.left_bar:
c.setFillColor(self.bg_color)
c.roundRect(0, 0, w, h, 4, fill=1, stroke=0)
c.setFillColor(self.border_color)
c.rect(0, 0, 4, h, fill=1, stroke=0)
else:
c.setFillColor(self.bg_color)
c.roundRect(0, 0, w, h, 4, fill=1, stroke=0)
c.setStrokeColor(self.border_color)
c.setLineWidth(1.2)
c.roundRect(0, 0, w, h, 4, fill=0, stroke=1)
c.setFillColor(BLACK)
c.setFont("Helvetica", 9)
c.drawString(12, h/2 - 4, self.text)
class SectionDivider(Flowable):
"""A fancy section divider with number."""
def __init__(self, number, title, color=DARK_BLUE):
super().__init__()
self.number = number
self.title = title
self.color = color
def wrap(self, avail_w, avail_h):
self.width = avail_w
return (avail_w, 0.5*inch)
def draw(self):
c = self.canv
w = self.width
# Background bar
c.setFillColor(self.color)
c.roundRect(0, 2, w, 0.42*inch, 5, fill=1, stroke=0)
# Circle with number
c.setFillColor(WHITE)
c.circle(0.28*inch, 0.23*inch, 0.18*inch, fill=1, stroke=0)
c.setFillColor(self.color)
c.setFont("Helvetica-Bold", 13)
c.drawCentredString(0.28*inch, 0.165*inch, str(self.number))
# Title
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 14)
c.drawString(0.6*inch, 0.16*inch, self.title.upper())
class PathwayArrow(Flowable):
"""Simple horizontal pathway diagram."""
def __init__(self, steps, colors_list, width=None):
super().__init__()
self.steps = steps
self.colors_list = colors_list
self.box_width = width
def wrap(self, avail_w, avail_h):
self.box_width = avail_w
return (avail_w, 0.85*inch)
def draw(self):
c = self.canv
n = len(self.steps)
box_w = (self.box_width - 0.1*inch) / n - 0.08*inch
box_h = 0.55*inch
y = 0.12*inch
for i, (step, col) in enumerate(zip(self.steps, self.colors_list)):
x = i * (box_w + 0.1*inch + 0.08*inch)
c.setFillColor(col)
c.roundRect(x, y, box_w, box_h, 5, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 7.5)
words = step.split('\n')
if len(words) == 1:
c.drawCentredString(x + box_w/2, y + box_h/2 - 4, step)
else:
for j, word in enumerate(words):
c.drawCentredString(x + box_w/2, y + box_h/2 + (len(words)/2 - j - 0.5)*10, word)
# Arrow
if i < n - 1:
ax = x + box_w + 2
ay = y + box_h/2
c.setFillColor(colors.HexColor("#555555"))
c.setStrokeColor(colors.HexColor("#555555"))
c.setLineWidth(1.5)
c.line(ax, ay, ax + 8, ay)
c.polygon([ax+8, ay+4, ax+8, ay-4, ax+14, ay], fill=1, stroke=0)
# ── Styles ───────────────────────────────────────────────────────────────────
def make_styles():
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(
'MainTitle',
parent=styles['Title'],
fontSize=26, fontName='Helvetica-Bold',
textColor=WHITE, alignment=TA_CENTER,
spaceAfter=4
))
styles.add(ParagraphStyle(
'Subtitle',
fontSize=13, fontName='Helvetica',
textColor=LIGHT_BLUE, alignment=TA_CENTER,
spaceAfter=6
))
styles.add(ParagraphStyle(
'H2', fontSize=13, fontName='Helvetica-Bold',
textColor=DARK_BLUE, spaceBefore=10, spaceAfter=5,
borderPad=2
))
styles.add(ParagraphStyle(
'H3', fontSize=11, fontName='Helvetica-Bold',
textColor=MID_BLUE, spaceBefore=7, spaceAfter=3
))
styles.add(ParagraphStyle(
'H4', fontSize=10, fontName='Helvetica-BoldOblique',
textColor=TEAL, spaceBefore=5, spaceAfter=2
))
styles.add(ParagraphStyle(
'Body', fontSize=9.5, fontName='Helvetica',
textColor=colors.HexColor("#222222"),
leading=14, spaceAfter=4, alignment=TA_JUSTIFY
))
styles.add(ParagraphStyle(
'Bullet', fontSize=9.5, fontName='Helvetica',
textColor=colors.HexColor("#222222"),
leading=13, spaceAfter=2,
leftIndent=12, bulletIndent=0,
alignment=TA_LEFT
))
styles.add(ParagraphStyle(
'SubBullet', fontSize=9, fontName='Helvetica',
textColor=colors.HexColor("#333333"),
leading=12, spaceAfter=2,
leftIndent=24, bulletIndent=12
))
styles.add(ParagraphStyle(
'KeyPoint', fontSize=9.5, fontName='Helvetica-Bold',
textColor=ACCENT_RED, leading=13, spaceAfter=2,
leftIndent=8
))
styles.add(ParagraphStyle(
'TableHeader', fontSize=9, fontName='Helvetica-Bold',
textColor=WHITE, alignment=TA_CENTER
))
styles.add(ParagraphStyle(
'TableCell', fontSize=8.5, fontName='Helvetica',
textColor=BLACK, leading=12
))
styles.add(ParagraphStyle(
'Caption', fontSize=8, fontName='Helvetica-Oblique',
textColor=colors.HexColor("#555555"), alignment=TA_CENTER,
spaceAfter=6
))
styles.add(ParagraphStyle(
'SmallNote', fontSize=8, fontName='Helvetica',
textColor=colors.HexColor("#666666"), leading=11, spaceAfter=2
))
styles.add(ParagraphStyle(
'PageFooter', fontSize=8, fontName='Helvetica',
textColor=colors.HexColor("#888888"), alignment=TA_CENTER
))
return styles
S = make_styles()
def b(text): return f"<b>{text}</b>"
def i(text): return f"<i>{text}</i>"
def color_text(text, hex_color): return f'<font color="{hex_color}">{text}</font>'
def bullet(text, style='Bullet'):
return Paragraph(f"• {text}", S[style])
def subbullet(text):
return Paragraph(f"◦ {text}", S['SubBullet'])
def sp(n=6):
return Spacer(1, n)
def hr(color=MID_BLUE, thickness=0.5):
return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=4, spaceBefore=4)
# ── Cover Page ───────────────────────────────────────────────────────────────
def cover_page():
elems = []
# Header background handled via custom flowable
class CoverHeader(Flowable):
def wrap(self, aw, ah):
self.width = aw
return (aw, 2.4*inch)
def draw(self):
c = self.canv
w = self.width
# Gradient-like background
c.setFillColor(DARK_BLUE)
c.roundRect(0, 0, w, 2.3*inch, 10, fill=1, stroke=0)
c.setFillColor(MID_BLUE)
c.roundRect(0, 0.05*inch, w, 0.6*inch, 10, fill=1, stroke=0)
# GERD big title
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 38)
c.drawCentredString(w/2, 1.55*inch, "GERD")
c.setFont("Helvetica-Bold", 15)
c.setFillColor(LIGHT_BLUE)
c.drawCentredString(w/2, 1.22*inch, "Gastroesophageal Reflux Disease")
c.setFont("Helvetica", 11)
c.setFillColor(WHITE)
c.drawCentredString(w/2, 0.95*inch, "Comprehensive Exam Notes")
# Badge row
c.setFillColor(ACCENT_RED)
bw = 1.0*inch
bh = 0.28*inch
badges = ["RESIDENCY", "FCPS", "MRCP"]
total = len(badges) * bw + (len(badges)-1)*0.15*inch
start_x = (w - total)/2
for idx, badge in enumerate(badges):
bx = start_x + idx*(bw + 0.15*inch)
c.setFillColor([ACCENT_RED, ACCENT_GREEN, ACCENT_ORANGE][idx])
c.roundRect(bx, 0.12*inch, bw, bh, 4, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 8.5)
c.drawCentredString(bx + bw/2, 0.22*inch, badge)
elems.append(CoverHeader())
elems.append(sp(16))
# Info boxes
info_data = [
[Paragraph(b("Specialty"), S['TableHeader']),
Paragraph(b("Target Exams"), S['TableHeader']),
Paragraph(b("Evidence Base"), S['TableHeader']),
Paragraph(b("Year"), S['TableHeader'])],
[Paragraph("Gastroenterology\nGeneral Medicine", S['TableCell']),
Paragraph("FCPS-II, MRCP Part 1/2\nResidency / USMLE", S['TableCell']),
Paragraph("Goldman-Cecil, Yamada's\nACG/AGA Guidelines 2022-25", S['TableCell']),
Paragraph("2025-26\nUpdated", S['TableCell'])]
]
info_tbl = Table(info_data, colWidths=[1.4*inch, 1.6*inch, 2.2*inch, 1.0*inch])
info_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('BACKGROUND', (0,1), (-1,1), LIGHT_BLUE),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('ALIGN', (0,0), (-1,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('GRID', (0,0), (-1,-1), 0.5, colors.white),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BLUE]),
('ROUNDEDCORNERS', [5,5,5,5]),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
]))
elems.append(info_tbl)
elems.append(sp(14))
# Topics covered
elems.append(Paragraph(b("Topics Covered in This Document"), S['H2']))
elems.append(hr())
topics = [
("1", "Definition & Epidemiology", MID_BLUE),
("2", "Pathophysiology & Mechanisms", TEAL),
("3", "Clinical Features (Typical & Atypical)", ACCENT_GREEN),
("4", "Diagnosis & Investigations", ACCENT_ORANGE),
("5", "Los Angeles Classification", ACCENT_RED),
("6", "Complications (Barrett's, Stricture, Adenocarcinoma)", PURPLE),
("7", "Management (Stepwise Approach)", DARK_BLUE),
("8", "Surgery & Endoscopic Therapy", MID_BLUE),
("9", "Special Situations & High-Yield Points", TEAL),
("10", "Exam High-Yield Summary", ACCENT_RED),
]
tdata = []
row = []
for idx, (num, topic, col) in enumerate(topics):
cell_para = Paragraph(
f'<font color="{col.hexval()}"><b>{num}.</b></font> {topic}',
S['Bullet']
)
row.append(cell_para)
if len(row) == 2:
tdata.append(row)
row = []
if row:
row.append(Paragraph("", S['Body']))
tdata.append(row)
tbl = Table(tdata, colWidths=[3.1*inch, 3.1*inch])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), GREY_BG),
('GRID', (0,0), (-1,-1), 0.5, colors.white),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
('LEFTPADDING', (0,0), (-1,-1), 8),
]))
elems.append(tbl)
elems.append(sp(14))
# Source note
elems.append(Paragraph(
f'<i>Sources: Goldman-Cecil Medicine (26e) · Yamada\'s Textbook of Gastroenterology (7e) · '
f'Robbins & Cotran Pathologic Basis of Disease · Current Surgical Therapy (14e) · '
f'ACG Clinical Guidelines 2022 · AGA Guidelines 2022 · ASGE Guidelines 2025</i>',
S['SmallNote']
))
elems.append(PageBreak())
return elems
# ── Section 1: Definition & Epidemiology ────────────────────────────────────
def section1():
elems = []
elems.append(SectionDivider(1, "Definition & Epidemiology", DARK_BLUE))
elems.append(sp(8))
elems.append(Paragraph(b("Definition (Montreal Consensus)"), S['H3']))
elems.append(Paragraph(
"GERD is defined as a condition that develops when the reflux of stomach contents into the "
"esophagus causes <b>troublesome symptoms</b> or <b>complications</b>. Physiologic reflux "
"occurs in everyone; GERD is diagnosed when it becomes clinically significant.",
S['Body']
))
elems.append(sp(6))
# Epidemiology table
elems.append(Paragraph(b("Epidemiology"), S['H3']))
epi_data = [
[Paragraph(b("Parameter"), S['TableHeader']),
Paragraph(b("Data"), S['TableHeader'])],
[Paragraph("Global Prevalence", S['TableCell']),
Paragraph("10-20% in Western world; <5% in Asia", S['TableCell'])],
[Paragraph("USA Prevalence", S['TableCell']),
Paragraph("~44% ever had symptoms; ~30% in prior week", S['TableCell'])],
[Paragraph("Gender", S['TableCell']),
Paragraph("Equal M=F for GERD; M>F for Barrett's esophagus", S['TableCell'])],
[Paragraph("Age", S['TableCell']),
Paragraph("Increases with age; plateau at 7th-9th decade for Barrett's", S['TableCell'])],
[Paragraph("Highest in", S['TableCell']),
Paragraph("North America > Europe; Northern Europe > Southern Europe", S['TableCell'])],
]
epi_tbl = Table(epi_data, colWidths=[2.0*inch, 4.2*inch])
epi_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('BACKGROUND', (0,1), (-1,1), LIGHT_BLUE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BLUE, WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 8),
]))
elems.append(epi_tbl)
elems.append(sp(8))
elems.append(Paragraph(b("Risk Factors"), S['H3']))
rf_data = [
[Paragraph(b("Modifiable"), S['TableHeader']),
Paragraph(b("Non-modifiable"), S['TableHeader'])],
[Paragraph(
"• Obesity / central adiposity\n• High-fat diet, spicy foods, chocolate, caffeine\n"
"• Alcohol & smoking\n• Late-night meals, large meals\n• Certain drugs (CCBs, nitrates,\n"
" anticholinergics, bisphosphonates)\n• Pregnancy",
S['TableCell']),
Paragraph(
"• Male sex (for Barrett's esophagus)\n• Older age\n• Positive family history\n"
"• Monozygotic twin concordance\n• White/European ethnicity\n• Hiatal hernia",
S['TableCell'])]
]
rf_tbl = Table(rf_data, colWidths=[3.1*inch, 3.1*inch])
rf_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), TEAL),
('BACKGROUND', (0,1), (0,1), colors.HexColor("#E8F5E9")),
('BACKGROUND', (1,1), (1,1), colors.HexColor("#FFF3E0")),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
('LEFTPADDING', (0,0), (-1,-1), 8),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(rf_tbl)
elems.append(sp(8))
return elems
# ── Section 2: Pathophysiology ───────────────────────────────────────────────
def section2():
elems = []
elems.append(SectionDivider(2, "Pathophysiology", TEAL))
elems.append(sp(8))
elems.append(Paragraph(b("Anti-Reflux Barrier (Normal Defense)"), S['H3']))
elems.append(Paragraph(
"The esophagus is protected by four overlapping mechanisms. Failure of any of these "
"leads to pathologic reflux:", S['Body']
))
defense_data = [
[Paragraph(b("Defense Mechanism"), S['TableHeader']),
Paragraph(b("Components"), S['TableHeader']),
Paragraph(b("Role"), S['TableHeader'])],
[Paragraph("1. Anti-reflux Barrier", S['TableCell']),
Paragraph("LES + crural diaphragm + phrenoesophageal\nligament + angle of His", S['TableCell']),
Paragraph("Primary mechanical barrier", S['TableCell'])],
[Paragraph("2. Esophageal Clearance", S['TableCell']),
Paragraph("Peristalsis (volume) + swallowed saliva\n(acid neutralization)", S['TableCell']),
Paragraph("Clears refluxed material", S['TableCell'])],
[Paragraph("3. Epithelial Defense", S['TableCell']),
Paragraph("Mucous layer + tight junctions +\nbicarbonate secretion", S['TableCell']),
Paragraph("Resists mucosal injury", S['TableCell'])],
[Paragraph("4. Gastric Emptying", S['TableCell']),
Paragraph("Normal gastric motility", S['TableCell']),
Paragraph("Reduces gastric volume available to reflux", S['TableCell'])],
]
def_tbl = Table(defense_data, colWidths=[1.6*inch, 2.6*inch, 1.9*inch])
def_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), TEAL),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BLUE, WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(def_tbl)
elems.append(sp(8))
# Pathophysiology diagram
elems.append(Paragraph(b("Mechanisms of GERD (Stepwise Pathway)"), S['H3']))
elems.append(PathwayArrow(
["tLESR\n(most common)", "Low LES\nPressure", "Hiatal\nHernia", "Impaired\nClearance", "Mucosal\nDamage"],
[MID_BLUE, TEAL, ACCENT_ORANGE, PURPLE, ACCENT_RED]
))
elems.append(sp(4))
elems.append(Paragraph(b("Key Pathophysiologic Mechanisms"), S['H3']))
mechs = [
("Transient LES Relaxations (tLESRs) - MOST COMMON",
"Vagally-mediated reflex triggered by gastric distension. LES relaxes WITHOUT a swallow. "
"This is the dominant mechanism in GERD patients. GABA-B receptor agonists (baclofen) and "
"mGluR5 antagonists can reduce tLESRs."),
("Low Resting LES Pressure",
"Normal LES pressure: 10-35 mmHg. A pressure <6 mmHg = free reflux. "
"Associated with: scleroderma, certain drugs (CCBs, nitrates, theophylline, "
"anticholinergics), foods (fat, chocolate, peppermint, alcohol)."),
("Hiatal Hernia",
"Displaces LES above the diaphragm, separating LES from crural diaphragm. "
"Creates a 'trap' (hernia sac) for gastric contents that can reflux during LES relaxation. "
"Displaces the acid pocket (unbuffered acid in gastric cardia) into the hernia, "
"worsening postprandial reflux. The larger the hernia, the worse the GERD."),
("Obesity",
"Central fat increases intra-abdominal pressure → increases gastroesophageal pressure gradient "
"→ promotes tLESRs. Also increases hiatal hernia incidence and disturbs GEJ anatomy."),
("Impaired Esophageal Clearance",
"Reduced peristaltic amplitude or frequency fails to clear refluxed material. "
"Saliva production reduced during sleep, explaining nocturnal GERD severity."),
("Delayed Gastric Emptying",
"Increased gastric volume → more material available to reflux. Relevant in diabetic gastroparesis."),
("Acid Pocket",
"Unbuffered acid accumulates just below GEJ post-prandially. In GERD patients, this pocket is "
"larger and more proximal, and may extend into a hiatal hernia sac."),
]
for title, text in mechs:
elems.append(Paragraph(f"<b>{title}</b>", S['H4']))
elems.append(Paragraph(text, S['Body']))
elems.append(sp(8))
# Injurious agents table
elems.append(Paragraph(b("Injurious Agents in Refluxate"), S['H3']))
agents_data = [
[Paragraph(b("Agent"), S['TableHeader']),
Paragraph(b("Damage"), S['TableHeader']),
Paragraph(b("Clinical Significance"), S['TableHeader'])],
[Paragraph("Hydrochloric Acid", S['TableCell']),
Paragraph("Denatures proteins, disrupts tight junctions", S['TableCell']),
Paragraph("Primary injurious agent; pH <4 most harmful", S['TableCell'])],
[Paragraph("Pepsin", S['TableCell']),
Paragraph("Proteolytic; active at pH <4", S['TableCell']),
Paragraph("Potentiates acid injury", S['TableCell'])],
[Paragraph("Bile Salts", S['TableCell']),
Paragraph("Detergent effect on mucosa", S['TableCell']),
Paragraph("Important in Barrett's development", S['TableCell'])],
[Paragraph("Trypsin/Pancreatic enzymes", S['TableCell']),
Paragraph("Mucosal injury at alkaline pH", S['TableCell']),
Paragraph("Relevant in post-gastrectomy, duodenogastric reflux", S['TableCell'])],
]
agents_tbl = Table(agents_data, colWidths=[1.5*inch, 2.0*inch, 2.65*inch])
agents_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), PURPLE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.HexColor("#F3E5F5"), WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(agents_tbl)
elems.append(sp(8))
return elems
# ── Section 3: Clinical Features ────────────────────────────────────────────
def section3():
elems = []
elems.append(SectionDivider(3, "Clinical Features", ACCENT_GREEN))
elems.append(sp(8))
# GERD Syndrome Classification
elems.append(Paragraph(b("Montreal Classification of GERD Syndromes"), S['H3']))
mc_data = [
[Paragraph(b("Esophageal Syndromes"), S['TableHeader']),
Paragraph(b("Extra-Esophageal Syndromes"), S['TableHeader'])],
[Paragraph(
"<b>Symptomatic Syndromes:</b>\n"
"• Typical reflux syndrome (heartburn + regurgitation)\n"
"• Reflux chest pain syndrome\n\n"
"<b>Syndromes with esophageal injury:</b>\n"
"• Reflux esophagitis\n• Reflux stricture\n• Barrett's esophagus\n"
"• Esophageal adenocarcinoma",
S['TableCell']),
Paragraph(
"<b>Established associations:</b>\n"
"• Reflux cough syndrome\n• Reflux laryngitis syndrome\n"
"• Reflux asthma syndrome\n• Reflux dental erosion syndrome\n\n"
"<b>Proposed associations:</b>\n"
"• Sinusitis, pharyngitis, recurrent otitis media\n"
"• Idiopathic pulmonary fibrosis\n• Recurrent aspiration pneumonia",
S['TableCell'])]
]
mc_tbl = Table(mc_data, colWidths=[3.1*inch, 3.1*inch])
mc_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), ACCENT_GREEN),
('BACKGROUND', (0,1), (0,1), colors.HexColor("#E8F5E9")),
('BACKGROUND', (1,1), (1,1), colors.HexColor("#E3F2FD")),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
('LEFTPADDING', (0,0), (-1,-1), 8),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(mc_tbl)
elems.append(sp(8))
elems.append(Paragraph(b("Typical Symptoms"), S['H3']))
typical = [
("Heartburn", "Retrosternal burning sensation radiating upward from epigastrium to throat. "
"Worsens after meals, when bending, or lying down. Relieved by antacids. "
"<b>High specificity but LOW sensitivity</b> for GERD."),
("Regurgitation", "Effortless return of gastric contents to oropharynx/mouth with sour/bitter taste. "
"NOT preceded by nausea. Bending/belching provokes it. "
"<b>Distinguish from vomiting</b> (which requires effort and nausea) and "
"<b>rumination</b> (effortless, subconscious, during meals, no heartburn)."),
("Water Brash", "Excessive salivation (mouth fills with saliva) - vagal reflex from esophageal acidification."),
("Globus", "Sensation of a lump or fullness in the throat, irrespective of swallowing."),
("Dysphagia", "Reported by >30% of GERD patients. Causes: peptic stricture, Schatzki ring, "
"impaired peristalsis, or mucosal inflammation. <b>Alarm symptom</b> - needs endoscopy."),
("Odynophagia", "Pain on swallowing - suggests esophageal ulcer or deep erosion. <b>Alarm symptom.</b>"),
]
for name, desc in typical:
elems.append(Paragraph(f"<b>{name}:</b> {desc}", S['Bullet']))
elems.append(sp(3))
elems.append(sp(6))
elems.append(Paragraph(b("Alarm Symptoms (Red Flags) - Mandate Urgent Endoscopy"), S['H3']))
class RedFlagBox(Flowable):
def wrap(self, aw, ah):
self.width = aw
return (aw, 1.1*inch)
def draw(self):
c = self.canv
w = self.width
c.setFillColor(colors.HexColor("#FFEBEE"))
c.roundRect(0, 0, w, 1.0*inch, 6, fill=1, stroke=0)
c.setStrokeColor(ACCENT_RED)
c.setLineWidth(2)
c.roundRect(0, 0, w, 1.0*inch, 6, fill=0, stroke=1)
c.setFillColor(ACCENT_RED)
c.setFont("Helvetica-Bold", 10)
c.drawString(10, 0.8*inch, "RED FLAGS - REFER FOR URGENT ENDOSCOPY")
c.setFont("Helvetica", 8.5)
c.setFillColor(BLACK)
flags = [
"• Dysphagia • Odynophagia • Unintentional weight loss",
"• Anemia / hematemesis / melena • Palpable abdominal mass",
"• Progressive symptoms despite adequate PPI therapy • Age >55 with new-onset symptoms",
]
for j, flag in enumerate(flags):
c.drawString(10, 0.58*inch - j*0.16*inch, flag)
elems.append(RedFlagBox())
elems.append(sp(8))
elems.append(Paragraph(b("Atypical (Extra-Esophageal) Symptoms"), S['H3']))
atypical_data = [
[Paragraph(b("System"), S['TableHeader']),
Paragraph(b("Manifestation"), S['TableHeader']),
Paragraph(b("Notes"), S['TableHeader'])],
[Paragraph("Pulmonary", S['TableCell']),
Paragraph("Chronic cough, asthma, aspiration pneumonia,\nidiopathic pulmonary fibrosis", S['TableCell']),
Paragraph("GERD is 3rd most common cause of chronic cough\n(after rhinosinusitis, asthma). "
"50-75% have NO typical GERD symptoms.", S['TableCell'])],
[Paragraph("ENT", S['TableCell']),
Paragraph("Laryngitis, hoarseness, globus, chronic\nthroat-clearing, subglottic stenosis", S['TableCell']),
Paragraph("Laryngopharyngeal reflux (LPR); "
"Reinke's edema, posterior laryngitis (cobblestoning)", S['TableCell'])],
[Paragraph("Dental", S['TableCell']),
Paragraph("Dental erosions (enamel erosion)", S['TableCell']),
Paragraph("Prevalence 17-68% in GERD patients;\npalatal surface most affected", S['TableCell'])],
[Paragraph("Cardiac", S['TableCell']),
Paragraph("Non-cardiac chest pain (NCCP)", S['TableCell']),
Paragraph("GERD is MOST COMMON esophageal cause of NCCP\n(more than spastic motor disorders). "
"Abnormal pH in 21-48% of NCCP.", S['TableCell'])],
[Paragraph("Sleep", S['TableCell']),
Paragraph("Nocturnal symptoms, sleep apnea", S['TableCell']),
Paragraph("Head-of-bed elevation recommended", S['TableCell'])],
]
atyp_tbl = Table(atypical_data, colWidths=[1.1*inch, 2.0*inch, 3.05*inch])
atyp_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), ACCENT_GREEN),
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.HexColor("#E8F5E9"), WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(atyp_tbl)
elems.append(sp(8))
return elems
# ── Section 4: Diagnosis ─────────────────────────────────────────────────────
def section4():
elems = []
elems.append(SectionDivider(4, "Diagnosis & Investigations", ACCENT_ORANGE))
elems.append(sp(8))
# Diagnostic algorithm
elems.append(Paragraph(b("Diagnostic Algorithm (ACG/AGA 2022 Guidelines)"), S['H3']))
algo_data = [
[Paragraph(b("Step"), S['TableHeader']),
Paragraph(b("Action"), S['TableHeader']),
Paragraph(b("Recommendation"), S['TableHeader'])],
[Paragraph("Step 1", S['TableCell']),
Paragraph("Classic symptoms (heartburn + regurgitation)\nNO alarm features", S['TableCell']),
Paragraph("Empiric 8-week PPI trial (once daily before meal)\n"
"[Strong recommendation, moderate evidence - ACG 2022]", S['TableCell'])],
[Paragraph("Step 2a", S['TableCell']),
Paragraph("Symptoms respond to PPI trial", S['TableCell']),
Paragraph("Attempt PPI discontinuation. If recurrence → long-term PPI\n"
"(on-demand or continuous)", S['TableCell'])],
[Paragraph("Step 2b", S['TableCell']),
Paragraph("Symptoms do NOT respond adequately\nOR symptoms recur on stopping PPI", S['TableCell']),
Paragraph("Upper endoscopy (after stopping PPI for 2-4 weeks ideally)\n"
"[Strong recommendation]", S['TableCell'])],
[Paragraph("Step 3a", S['TableCell']),
Paragraph("Endoscopy shows LA Grade C/D esophagitis,\nlong-segment Barrett's, or peptic stricture", S['TableCell']),
Paragraph("GERD confirmed. No need for reflux monitoring off therapy.\n"
"Initiate aggressive management.", S['TableCell'])],
[Paragraph("Step 3b", S['TableCell']),
Paragraph("Endoscopy normal / LA Grade A or B only", S['TableCell']),
Paragraph("Reflux monitoring OFF therapy (pH or impedance-pH)\n"
"to confirm diagnosis [Strong recommendation]", S['TableCell'])],
[Paragraph("Step 4", S['TableCell']),
Paragraph("Extraesophageal symptoms WITHOUT\ntypical GERD symptoms", S['TableCell']),
Paragraph("Reflux testing before PPI therapy (to establish GERD)\n"
"then treat concurrent esophageal GERD syndrome", S['TableCell'])],
]
algo_tbl = Table(algo_data, colWidths=[0.85*inch, 2.3*inch, 3.0*inch])
algo_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), ACCENT_ORANGE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.HexColor("#FFF3E0"), WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(algo_tbl)
elems.append(sp(8))
elems.append(Paragraph(b("Investigations in Detail"), S['H3']))
inv_data = [
[Paragraph(b("Investigation"), S['TableHeader']),
Paragraph(b("Indication / Use"), S['TableHeader']),
Paragraph(b("Key Facts"), S['TableHeader'])],
[Paragraph("Upper GI Endoscopy\n(OGD/EGD)", S['TableCell']),
Paragraph("• Alarm symptoms\n• Failure of empiric PPI\n• Confirm GERD\n"
"• Surveillance (Barrett's)", S['TableCell']),
Paragraph("LA Grade C/D or Barrett's = definitive GERD.\n"
"Ideally off PPI 2-4 weeks before. Biopsy for "
"Barrett's/dysplasia.", S['TableCell'])],
[Paragraph("Ambulatory 24-hr\npH Monitoring", S['TableCell']),
Paragraph("• Gold standard for diagnosing GERD\n• Must be OFF PPI (7 days)\n"
"• Correlating symptoms with reflux", S['TableCell']),
Paragraph("DeMeester score >14.72 OR\n"
"Acid exposure time (AET) >6% = pathologic.\n"
"Wireless capsule: 48-96 hrs, more comfortable.", S['TableCell'])],
[Paragraph("Impedance-pH\nMonitoring", S['TableCell']),
Paragraph("• Detects non-acid reflux\n• Persistent symptoms on PPI\n"
"• Preferred for extraesophageal GERD", S['TableCell']),
Paragraph("Can detect weakly acid and non-acid reflux.\n"
"Symptom Index (SI) >50% significant.\n"
"Symptom Association Probability (SAP) >95% significant.", S['TableCell'])],
[Paragraph("Esophageal\nManometry (HRM)", S['TableCell']),
Paragraph("• Pre-surgical workup\n• Rule out achalasia\n"
"• Determine type of fundoplication\n• NOT for primary GERD diagnosis", S['TableCell']),
Paragraph("High-resolution manometry not recommended as\ndiagnostic test for GERD alone. "
"Chicago Classification v4.0 for motility disorders.", S['TableCell'])],
[Paragraph("Barium Esophagram", S['TableCell']),
Paragraph("• Detect hiatal hernia\n• Evaluate dysphagia\n"
"• Assess strictures pre-dilation", S['TableCell']),
Paragraph("Higher sensitivity than endoscopy for subtle\nstrictures (use solid challenge - barium pill). "
"Not sensitive for mild GERD.", S['TableCell'])],
[Paragraph("Mucosal Impedance\n(new)", S['TableCell']),
Paragraph("• Adjunct to endoscopy\n• Differentiate GERD from EoE", S['TableCell']),
Paragraph("Catheter-based balloon sensor during endoscopy.\n"
"Promising - detects mucosal integrity changes. ASGE 2025.", S['TableCell'])],
]
inv_tbl = Table(inv_data, colWidths=[1.4*inch, 2.0*inch, 2.75*inch])
inv_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BLUE, WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(inv_tbl)
elems.append(sp(8))
return elems
# ── Section 5: Los Angeles Classification (with image) ──────────────────────
def section5():
elems = []
elems.append(SectionDivider(5, "Los Angeles Classification of Esophagitis", ACCENT_RED))
elems.append(sp(8))
la_data = [
[Paragraph(b("Grade"), S['TableHeader']),
Paragraph(b("Endoscopic Finding"), S['TableHeader']),
Paragraph(b("Clinical Significance"), S['TableHeader'])],
[Paragraph("A", S['TableCell']),
Paragraph("One or more mucosal breaks <5 mm long,\nnot extending between tops of 2 mucosal folds", S['TableCell']),
Paragraph("Mild esophagitis; PPI for 4-8 weeks. May not confirm GERD diagnosis alone.", S['TableCell'])],
[Paragraph("B", S['TableCell']),
Paragraph("One or more mucosal breaks >5 mm long,\nnot extending between tops of 2 mucosal folds", S['TableCell']),
Paragraph("Moderate; 8 weeks PPI, re-endoscope to confirm healing.", S['TableCell'])],
[Paragraph("C", S['TableCell']),
Paragraph("Mucosal break continuous between tops of\n≥2 folds but <75% of circumference", S['TableCell']),
Paragraph("Severe; CONFIRMS GERD diagnosis. 8-12 weeks high-dose PPI.\n"
"No need for pH monitoring to confirm diagnosis.", S['TableCell'])],
[Paragraph("D", S['TableCell']),
Paragraph("Mucosal break involving ≥75% of\nesophageal circumference", S['TableCell']),
Paragraph("Very severe; CONFIRMS GERD. Consider early surgery referral.\n"
"High risk for stricture / Barrett's.", S['TableCell'])],
]
la_tbl = Table(la_data, colWidths=[0.6*inch, 2.5*inch, 3.05*inch])
la_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), ACCENT_RED),
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.HexColor("#FFEBEE"), WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
('LEFTPADDING', (0,0), (-1,-1), 8),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(la_tbl)
elems.append(sp(8))
# Try to include the LA classification image
img_url = "https://cdn.orris.care/cdss_images/f94e730e08c384d7bb094a78cdb91d45929bbb7a711a437b276f749072dc6c07.png"
img_path = "/home/daytona/workspace/gerd-notes/la_classification.png"
try:
urllib.request.urlretrieve(img_url, img_path)
if os.path.exists(img_path) and os.path.getsize(img_path) > 1000:
elems.append(Paragraph(b("Endoscopic Images - Los Angeles Classification"), S['H3']))
img = Image(img_path, width=5.5*inch, height=2.8*inch)
img.hAlign = 'CENTER'
elems.append(img)
elems.append(Paragraph(
"Fig. 1: Los Angeles Classification of reflux esophagitis (Grades A-D). "
"Grade A: small isolated mucosal break. Grade B: larger break. "
"Grade C: confluent breaks <75% circumference. Grade D: circumferential involvement. "
"(Source: Current Surgical Therapy 14e / Nayar & Vaezi, 2004)",
S['Caption']
))
except Exception as e:
elems.append(Paragraph(f"[Image: LA Classification diagram - {str(e)[:50]}]", S['Caption']))
elems.append(sp(8))
elems.append(Paragraph(b("Key Exam Point on LA Classification"), S['H3']))
elems.append(Paragraph(
"LA Grade C or D = definitive confirmation of GERD diagnosis (no need for pH monitoring). "
"LA Grade A or B = insufficient to confirm GERD (may be seen in healthy subjects); "
"if diagnosis uncertain, proceed to reflux monitoring off PPI therapy. "
"Note: up to 30% of LA Grade A lesions may be normal variants.",
S['Body']
))
elems.append(sp(8))
return elems
# ── Section 6: Complications ─────────────────────────────────────────────────
def section6():
elems = []
elems.append(SectionDivider(6, "Complications of GERD", PURPLE))
elems.append(sp(8))
# Complications overview
elems.append(PathwayArrow(
["Esophagitis\n(erosive)", "Peptic\nUlcer", "Stricture\n(peptic)", "Barrett's\nEsophagus", "Adeno-\ncarcinoma"],
[MID_BLUE, TEAL, ACCENT_ORANGE, PURPLE, ACCENT_RED]
))
elems.append(sp(4))
elems.append(Paragraph(b("1. Erosive Esophagitis"), S['H3']))
elems.append(Paragraph(
"Mucosal breaks (erosions/ulcerations) in the distal esophagus. Classified by LA system. "
"Heals with 8-12 weeks PPI therapy. Recurrence common if PPI stopped. "
"Complications: bleeding (hematemesis/anemia), stricture.",
S['Body']
))
elems.append(Paragraph(b("2. Peptic Stricture"), S['H3']))
elems.append(Paragraph(
"Fibrous narrowing from chronic acid injury and healing. Presents as <b>progressive "
"dysphagia to solids</b>. Diagnosed on barium esophagram (higher sensitivity) or endoscopy. "
"Treatment: endoscopic dilation (gradual, to ≥13 mm) + long-term PPI. "
"For recalcitrant strictures: intralesional triamcinolone injection.",
S['Body']
))
elems.append(Paragraph(b("3. Barrett's Esophagus"), S['H3']))
elems.append(Paragraph(
"Intestinal metaplasia replaces normal squamous epithelium of distal esophagus. "
"The most important complication due to its association with esophageal adenocarcinoma.",
S['Body']
))
be_data = [
[Paragraph(b("Feature"), S['TableHeader']),
Paragraph(b("Details"), S['TableHeader'])],
[Paragraph("Prevalence", S['TableCell']),
Paragraph("5-15% of endoscopy patients with GERD symptoms; 1.3-1.6% general population", S['TableCell'])],
[Paragraph("Demographics", S['TableCell']),
Paragraph("Middle-aged White men; ~25% are women or <50 yrs; 45% have NO reflux symptoms", S['TableCell'])],
[Paragraph("Risk Factors", S['TableCell']),
Paragraph("Long-standing reflux, smoking, male sex, older age, central obesity, White ethnicity", S['TableCell'])],
[Paragraph("Endoscopic Appearance", S['TableCell']),
Paragraph("Tongues of red, velvety mucosa extending upward from GEJ\n"
"(salmon-colored mucosa above Z-line)", S['TableCell'])],
[Paragraph("Classification", S['TableCell']),
Paragraph("Long-segment: ≥3 cm | Short-segment: <3 cm (lower cancer risk)", S['TableCell'])],
[Paragraph("Histology (MUST KNOW)", S['TableCell']),
Paragraph("Intestinal-type metaplasia with GOBLET CELLS (wine goblet shape, alcian-blue positive).\n"
"Goblet cells are DIAGNOSTIC of Barrett's.", S['TableCell'])],
[Paragraph("Cancer Risk", S['TableCell']),
Paragraph("Annual cancer risk ~0.1-0.3% per year overall; higher with dysplasia and long-segment.\n"
"CDX2 transcription factor promotes columnar metaplasia.", S['TableCell'])],
[Paragraph("Dysplasia Grades", S['TableCell']),
Paragraph("No dysplasia → Low-grade dysplasia (LGD) → High-grade dysplasia (HGD) → "
"Adenocarcinoma\nAtypical mitoses, nuclear hyperchromasia, increased N:C ratio", S['TableCell'])],
]
be_tbl = Table(be_data, colWidths=[1.6*inch, 4.6*inch])
be_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), PURPLE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.HexColor("#F3E5F5"), WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(be_tbl)
elems.append(sp(8))
# Barrett's image
img_url2 = "https://cdn.orris.care/cdss_images/854a9069e076aef6407662efaf7ced76b5083d57a1275c30a53c50192f11de0c.png"
img_path2 = "/home/daytona/workspace/gerd-notes/barretts.png"
# Barrett's surveillance table
elems.append(Paragraph(b("Barrett's Esophagus - Surveillance & Management"), S['H3']))
surv_data = [
[Paragraph(b("Histology"), S['TableHeader']),
Paragraph(b("Surveillance Interval"), S['TableHeader']),
Paragraph(b("Management"), S['TableHeader'])],
[Paragraph("No dysplasia", S['TableCell']),
Paragraph("Every 3-5 years", S['TableCell']),
Paragraph("Continue PPI. Lifestyle modification.", S['TableCell'])],
[Paragraph("Indefinite for dysplasia", S['TableCell']),
Paragraph("Repeat endoscopy in 3-6 months", S['TableCell']),
Paragraph("Optimize PPI therapy.", S['TableCell'])],
[Paragraph("Low-grade dysplasia (LGD)", S['TableCell']),
Paragraph("Every 6 months × 2, then annually", S['TableCell']),
Paragraph("Endoscopic eradication (RFA, EMR) preferred.\nCan survey if patient declines ablation.", S['TableCell'])],
[Paragraph("High-grade dysplasia (HGD)", S['TableCell']),
Paragraph("N/A - treat immediately", S['TableCell']),
Paragraph("Endoscopic eradication therapy (RFA or EMR).\nEsophagectomy if endoscopic therapy unavailable.", S['TableCell'])],
[Paragraph("Intramucosal adenocarcinoma\n(T1a)", S['TableCell']),
Paragraph("N/A - treat immediately", S['TableCell']),
Paragraph("EMR + RFA if no lymphovascular invasion.", S['TableCell'])],
]
surv_tbl = Table(surv_data, colWidths=[1.6*inch, 1.6*inch, 3.0*inch])
surv_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), PURPLE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.HexColor("#F3E5F5"), WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(surv_tbl)
elems.append(sp(8))
return elems
# ── Section 7: Management ────────────────────────────────────────────────────
def section7():
elems = []
elems.append(SectionDivider(7, "Management of GERD", DARK_BLUE))
elems.append(sp(8))
elems.append(Paragraph(b("Step 1: Lifestyle Modifications (All Patients)"), S['H3']))
lifestyle_data = [
[Paragraph(b("Recommendation"), S['TableHeader']),
Paragraph(b("Evidence"), S['TableHeader']),
Paragraph(b("Notes"), S['TableHeader'])],
[Paragraph("Weight loss (if overweight/obese)", S['TableCell']),
Paragraph("Strong (RCT evidence)", S['TableCell']),
Paragraph("Most effective single lifestyle intervention; reduces tLESRs", S['TableCell'])],
[Paragraph("Head-of-bed elevation (15-20 cm)", S['TableCell']),
Paragraph("Good (RCTs)", S['TableCell']),
Paragraph("For nocturnal symptoms; use bed wedge, not extra pillows", S['TableCell'])],
[Paragraph("Avoid meals 2-3 hrs before bedtime", S['TableCell']),
Paragraph("Conditional", S['TableCell']),
Paragraph("Reduces supine reflux risk", S['TableCell'])],
[Paragraph("Avoid trigger foods", S['TableCell']),
Paragraph("Conditional (limited data)", S['TableCell']),
Paragraph("Fat, chocolate, peppermint, spicy foods, citrus, tomatoes, caffeine, alcohol", S['TableCell'])],
[Paragraph("Stop smoking", S['TableCell']),
Paragraph("Conditional", S['TableCell']),
Paragraph("Smoking reduces LES pressure", S['TableCell'])],
[Paragraph("Avoid tight clothing", S['TableCell']),
Paragraph("Expert opinion", S['TableCell']),
Paragraph("Reduces intra-abdominal pressure", S['TableCell'])],
[Paragraph("Left lateral decubitus sleeping position", S['TableCell']),
Paragraph("Good", S['TableCell']),
Paragraph("Reduces nocturnal acid exposure", S['TableCell'])],
]
ls_tbl = Table(lifestyle_data, colWidths=[1.8*inch, 1.3*inch, 3.1*inch])
ls_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), MID_BLUE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BLUE, WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(ls_tbl)
elems.append(sp(8))
elems.append(Paragraph(b("Step 2: Pharmacological Management"), S['H3']))
elems.append(Paragraph(b("A. Antacids & Alginates"), S['H4']))
elems.append(bullet("Magnesium/aluminum hydroxide - rapid symptom relief, short-acting"))
elems.append(bullet("Sodium alginate forms a viscous 'raft' on top of gastric contents, reducing reflux"))
elems.append(bullet("Suitable for mild, intermittent symptoms. No healing effect on esophagitis."))
elems.append(sp(4))
elems.append(Paragraph(b("B. H2-Receptor Antagonists (H2RAs)"), S['H4']))
elems.append(bullet("Ranitidine (withdrawn - NDMA contamination), Famotidine, Cimetidine, Nizatidine"))
elems.append(bullet("Competitive reversible histamine H2 receptor blockade on parietal cells"))
elems.append(bullet("Effective for mild-moderate GERD; tolerance (tachyphylaxis) develops within 2 weeks"))
elems.append(bullet("Useful as add-on nighttime dose with PPI for nocturnal acid breakthrough"))
elems.append(sp(4))
elems.append(Paragraph(b("C. Proton Pump Inhibitors (PPIs) - FIRST-LINE TREATMENT"), S['H4']))
ppi_data = [
[Paragraph(b("PPI"), S['TableHeader']),
Paragraph(b("Dose"), S['TableHeader']),
Paragraph(b("Notes"), S['TableHeader'])],
[Paragraph("Omeprazole", S['TableCell']),
Paragraph("20-40 mg OD/BD", S['TableCell']),
Paragraph("First PPI; best studied; substrate of CYP2C19 (genetic polymorphism affects efficacy)", S['TableCell'])],
[Paragraph("Lansoprazole", S['TableCell']),
Paragraph("15-30 mg OD", S['TableCell']),
Paragraph("Once daily adequate for GERD maintenance", S['TableCell'])],
[Paragraph("Pantoprazole", S['TableCell']),
Paragraph("20-40 mg OD", S['TableCell']),
Paragraph("Fewest drug interactions; preferred in polypharmacy patients", S['TableCell'])],
[Paragraph("Rabeprazole", S['TableCell']),
Paragraph("10-20 mg OD", S['TableCell']),
Paragraph("Least dependent on CYP2C19; more consistent suppression", S['TableCell'])],
[Paragraph("Esomeprazole", S['TableCell']),
Paragraph("20-40 mg OD", S['TableCell']),
Paragraph("S-isomer of omeprazole; marginally superior healing rates in LA Grade C/D", S['TableCell'])],
[Paragraph("Dexlansoprazole", S['TableCell']),
Paragraph("30-60 mg OD", S['TableCell']),
Paragraph("Dual delayed-release; can be taken without regard to meals (MRCP point)", S['TableCell'])],
]
ppi_tbl = Table(ppi_data, colWidths=[1.3*inch, 1.3*inch, 3.6*inch])
ppi_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BLUE, WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(ppi_tbl)
elems.append(sp(6))
# PPI key points
ppi_keys = [
("TIMING", "Take 30-60 minutes BEFORE the first meal of the day (activates the H+/K+-ATPase)"),
("MECHANISM", "Irreversibly block H+/K+-ATPase (proton pump) on parietal cell canalicular membrane. "
"Bind covalently as active sulfenamide after acid activation."),
("EFFICACY", "Heal 80-95% of erosive esophagitis in 8 weeks. Superior to H2RAs."),
("ADVERSE EFFECTS", "Short-term: headache, diarrhea, nausea. Long-term: hypomagnesemia, "
"C. difficile infection, community-acquired pneumonia, bone fractures (prolonged), "
"vitamin B12 deficiency, renal interstitial nephritis, SIBO"),
("REBOUND ACID", "Hypersecretion on stopping PPI; taper gradually in long-term users"),
("CYP2C19", "Omeprazole/esomeprazole inhibit CYP2C19 → may reduce clopidogrel efficacy "
"(IMPORTANT drug interaction for cardiology MRCP questions)"),
]
for key, val in ppi_keys:
elems.append(Paragraph(f'<font color="{MID_BLUE.hexval()}"><b>{key}:</b></font> {val}', S['Bullet']))
elems.append(sp(2))
elems.append(sp(6))
elems.append(Paragraph(b("D. Potassium-Competitive Acid Blockers (P-CABs) - Vonoprazan"), S['H4']))
elems.append(Paragraph(
"Vonoprazan reversibly blocks K+ binding site on H+/K+-ATPase. "
"Unlike PPIs, does NOT require acid activation → works faster, more consistent suppression. "
"Meta-analysis (Simadibrata 2024, PMID 38263507) shows superior efficacy vs PPIs in "
"PPI-resistant GERD. Network meta-analysis (Zhuang 2024, PMID 38345252) shows P-CABs "
"superior to PPIs for Grade C/D esophagitis healing. Approved in Japan/USA; gaining "
"adoption in UK/South Asia.",
S['Body']
))
elems.append(sp(6))
elems.append(Paragraph(b("E. Other Agents"), S['H4']))
elems.append(bullet("Sucralfate: cytoprotective; useful in pregnancy (limited systemic absorption)"))
elems.append(bullet("Prokinetics (metoclopramide, domperidone): limited evidence; used for gastroparesis-related GERD"))
elems.append(bullet("Baclofen (GABA-B agonist): reduces tLESRs; used in refractory GERD; "
"side effects limit use (sedation, confusion) - NOT recommended without objective GERD evidence (ACG)"))
elems.append(bullet("TCAs / SSRIs: for functional heartburn / visceral hypersensitivity component"))
elems.append(sp(8))
return elems
# ── Section 8: Surgery & Endoscopic Therapy ──────────────────────────────────
def section8():
elems = []
elems.append(SectionDivider(8, "Surgery & Endoscopic Therapy", MID_BLUE))
elems.append(sp(8))
elems.append(Paragraph(b("Indications for Surgical/Endoscopic Referral"), S['H3']))
ind_items = [
"Medically refractory GERD (failure of twice-daily PPI for ≥8 weeks)",
"Patient preference / unwilling to take lifelong medication",
"Significant PPI side effects or intolerance",
"Large hiatal hernia with mechanical symptoms",
"Objective GERD (pH-proven) with persistent symptoms",
"High-grade dysplasia / early adenocarcinoma in Barrett's esophagus",
"Esophageal injury (esophagitis, Barrett's) despite adequate medical therapy",
]
for item in ind_items:
elems.append(bullet(item))
elems.append(sp(8))
elems.append(Paragraph(b("Surgical Options"), S['H3']))
surg_data = [
[Paragraph(b("Procedure"), S['TableHeader']),
Paragraph(b("Description"), S['TableHeader']),
Paragraph(b("Indication / Notes"), S['TableHeader'])],
[Paragraph("Laparoscopic Nissen\nFundoplication (LNF)\n360° wrap", S['TableCell']),
Paragraph("Complete 360° posterior wrap of gastric fundus\naround lower esophagus", S['TableCell']),
Paragraph("Gold standard for normal esophageal motility.\n"
"Meta-analysis (Li 2023, PMID 36998190): Nissen vs Toupet - Nissen has lower "
"GERD recurrence; Toupet has lower dysphagia rate.", S['TableCell'])],
[Paragraph("Laparoscopic Toupet\nFundoplication\n270° wrap", S['TableCell']),
Paragraph("Partial 270° posterior wrap", S['TableCell']),
Paragraph("Preferred in ineffective esophageal motility (Chicago\nClassification v4.0) "
"to prevent post-op dysphagia.", S['TableCell'])],
[Paragraph("Laparoscopic Dor\n180° anterior wrap", S['TableCell']),
Paragraph("Anterior partial wrap", S['TableCell']),
Paragraph("After Heller myotomy for achalasia.\nSometimes used in poor motility.", S['TableCell'])],
[Paragraph("Magnetic Sphincter\nAugmentation (MSA)\nLINX device", S['TableCell']),
Paragraph("Ring of magnetic titanium beads placed\naround LES laparoscopically", S['TableCell']),
Paragraph("Minimally invasive alternative to fundoplication.\nPreserves ability to belch/vomit. "
"Requires normal esophageal motility.\nContraindicated: metal allergy, MRI needs.", S['TableCell'])],
[Paragraph("Roux-en-Y gastric bypass", S['TableCell']),
Paragraph("Bariatric surgery creating Roux limb", S['TableCell']),
Paragraph("Excellent anti-reflux effect; preferred in morbid\nobesity with GERD.", S['TableCell'])],
]
surg_tbl = Table(surg_data, colWidths=[1.4*inch, 2.1*inch, 2.65*inch])
surg_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), MID_BLUE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BLUE, WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(surg_tbl)
elems.append(sp(8))
elems.append(Paragraph(b("Endoscopic Therapies for Barrett's Esophagus"), S['H3']))
endo_data = [
[Paragraph(b("Technique"), S['TableHeader']),
Paragraph(b("Mechanism"), S['TableHeader']),
Paragraph(b("Use"), S['TableHeader'])],
[Paragraph("RFA (Radiofrequency\nAblation)", S['TableCell']),
Paragraph("Thermal energy delivered via balloon/focal\ncatheter ablates metaplastic mucosa", S['TableCell']),
Paragraph("First-line for flat Barrett's + dysplasia.\n~80-90% complete eradication of dysplasia.", S['TableCell'])],
[Paragraph("EMR (Endoscopic\nMucosal Resection)", S['TableCell']),
Paragraph("Injection + snare resection of visible nodules\nor dysplastic areas", S['TableCell']),
Paragraph("Staging + treatment of HGD/early T1a adenocarcinoma.\nShould precede RFA for nodular disease.", S['TableCell'])],
[Paragraph("ESD (Endoscopic\nSubmucosal Dissection)", S['TableCell']),
Paragraph("En-bloc submucosal dissection using\nspecialized knives", S['TableCell']),
Paragraph("For larger lesions or those needing en-bloc\nresection; more technically demanding.", S['TableCell'])],
[Paragraph("Cryotherapy", S['TableCell']),
Paragraph("Liquid nitrogen or CO2 freezes tissue", S['TableCell']),
Paragraph("Alternative when RFA fails or not available.", S['TableCell'])],
]
endo_tbl = Table(endo_data, colWidths=[1.4*inch, 2.1*inch, 2.65*inch])
endo_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), PURPLE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.HexColor("#F3E5F5"), WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(endo_tbl)
elems.append(sp(8))
return elems
# ── Section 9: Special Situations ────────────────────────────────────────────
def section9():
elems = []
elems.append(SectionDivider(9, "Special Situations", TEAL))
elems.append(sp(8))
special = [
("GERD in Pregnancy",
"Very common (30-80%); due to progesterone-mediated LES relaxation + uterine pressure. "
"Treatment ladder: lifestyle → antacids/alginates → H2RAs (famotidine - Category B) → "
"PPIs (Category B: all PPIs except omeprazole Category C in some guidelines). "
"Sucralfate: safe (not absorbed). Resolve post-partum in most."),
("GERD in Scleroderma",
"Severely impaired LES + absent/weak peristalsis. Aggressive PPI therapy required. "
"Surgery CONTRAINDICATED (absent peristalsis - fundoplication causes dysphagia). "
"High Barrett's risk."),
("Nocturnal GERD",
"Most acid suppression occurs at night. Add H2RA at bedtime ('nocturnal acid breakthrough') "
"to PPI therapy. Head-of-bed elevation and left lateral position. Avoid food within 3 hrs."),
("Laryngopharyngeal Reflux (LPR)",
"Posterior laryngitis, hoarseness, chronic cough, throat clearing. "
"Only 40% have typical GERD symptoms. Diagnosis: laryngoscopy (cobblestoning, erythema, "
"pseudosulcus). Requires TWICE-daily PPI for minimum 3 months. "
"Poor symptom-acid correlation; do not start PPI without objective confirmation."),
("GERD and Asthma",
"Bidirectional relationship: GERD triggers asthma (micro-aspiration, vagal reflex); "
"asthma drugs (beta-agonists, theophylline) worsen GERD (reduce LES pressure). "
"If asthma poorly controlled + typical GERD symptoms: treat GERD. "
"If asthma + NO typical GERD: pH/impedance testing before PPI."),
("PPI-Refractory GERD",
"Definition: failure of twice-daily PPI therapy. Differential: non-compliance, "
"incorrect timing, functional heartburn, weakly acidic reflux, esophageal hypersensitivity, "
"EoE, or bile reflux. Investigations: impedance-pH ON therapy. "
"Treatment options: optimize PPI timing, switch PPI, vonoprazan, baclofen, TCA/SSRI, "
"surgical referral."),
]
for title, text in special:
elems.append(Paragraph(f"<b>{title}</b>", S['H3']))
elems.append(Paragraph(text, S['Body']))
elems.append(sp(4))
return elems
# ── Section 10: High-Yield Summary ───────────────────────────────────────────
def section10():
elems = []
elems.append(SectionDivider(10, "High-Yield Exam Summary", ACCENT_RED))
elems.append(sp(8))
elems.append(Paragraph(b("Must-Know Facts for FCPS / MRCP / Residency"), S['H3']))
class HighYieldBox(Flowable):
def __init__(self, items, col_header, bg, border):
super().__init__()
self.items = items
self.col_header = col_header
self.bg = bg
self.border = border
def wrap(self, aw, ah):
self.width = aw
h = 0.35*inch + len(self.items) * 0.18*inch
self.height = h
return (aw, h)
def draw(self):
c = self.canv
w = self.width
h = self.height
c.setFillColor(self.bg)
c.roundRect(0, 0, w, h, 5, fill=1, stroke=0)
c.setStrokeColor(self.border)
c.setLineWidth(1.5)
c.roundRect(0, 0, w, h, 5, fill=0, stroke=1)
c.setFillColor(self.border)
c.setFont("Helvetica-Bold", 9.5)
c.drawString(8, h - 0.25*inch, self.col_header)
c.setFillColor(BLACK)
c.setFont("Helvetica", 8.5)
for j, item in enumerate(self.items):
c.drawString(14, h - 0.38*inch - j*0.18*inch, f"• {item}")
hy_groups = [
("DEFINITION & EPIDEMIOLOGY", DARK_BLUE, colors.HexColor("#E3F2FD"), [
"Montreal definition: troublesome symptoms OR complications from esophageal reflux",
"Prevalence: 10-20% Western; <5% Asia; USA up to 44%",
"Risk factors: obesity (most modifiable), hiatal hernia, smoking, genetics",
"Physiologic reflux occurs normally - GERD = pathologic amount",
]),
("PATHOPHYSIOLOGY KEY POINTS", TEAL, colors.HexColor("#E0F2F1"), [
"Most common mechanism = Transient LES Relaxations (tLESRs)",
"Vagally mediated, triggered by gastric distension",
"Hiatal hernia: separates LES from crural diaphragm, creates acid pocket",
"Acid pocket: unbuffered acid below GEJ, source of postprandial reflux",
"Obesity: increases intra-abdominal pressure → more tLESRs",
]),
("CLINICAL FEATURES", ACCENT_GREEN, colors.HexColor("#E8F5E9"), [
"Heartburn + regurgitation: HIGH specificity, LOW sensitivity for GERD",
"GERD is MOST COMMON esophageal cause of non-cardiac chest pain",
"Chronic cough: GERD is 3rd commonest cause; 50-75% no typical GERD symptoms",
"Rumination ≠ regurgitation (rumination: subconscious, no heartburn, post-meal)",
"Alarm symptoms: dysphagia, weight loss, anemia, hematemesis, odynophagia",
]),
("INVESTIGATIONS", ACCENT_ORANGE, colors.HexColor("#FFF3E0"), [
"Classic symptoms + no alarms → empiric 8-week PPI (no endoscopy needed first)",
"Gold standard for GERD: 24-hr pH monitoring (DeMeester score >14.72, AET >6%)",
"LA Grade C/D = confirms GERD; no pH monitoring needed",
"LA Grade A/B alone = does NOT confirm GERD diagnosis",
"Manometry: NOT for GERD diagnosis; use for pre-surgical workup only",
"pH monitoring: must be OFF PPI for 7 days",
]),
("MANAGEMENT", MID_BLUE, colors.HexColor("#E3F2FD"), [
"PPIs: take 30-60 min BEFORE first meal (not at bedtime)",
"Dexlansoprazole: only PPI taken WITHOUT regard to meals",
"Omeprazole/esomeprazole inhibit CYP2C19 → reduce clopidogrel efficacy",
"Vonoprazan (P-CAB): superior to PPI in resistant GERD and Grade C/D esophagitis",
"Nissen fundoplication: 360° wrap; gold standard for surgical GERD",
"Toupet (270°): preferred in poor esophageal motility",
"Surgery contraindicated in scleroderma (no peristalsis)",
]),
("BARRETT'S ESOPHAGUS", PURPLE, colors.HexColor("#F3E5F5"), [
"Goblet cells (intestinal metaplasia) = DIAGNOSTIC of Barrett's",
"Only 10% of symptomatic GERD → Barrett's; 45% Barrett's have NO reflux symptoms",
"Cancer risk ~0.1-0.3%/yr; higher with LGD, HGD, long-segment, male, obesity",
"HGD/early adenocarcinoma → EMR then RFA",
"Long-segment Barrett's = ≥3 cm; short-segment <3 cm",
"CDX2 transcription factor promotes columnar metaplasia",
"Surveillance: no dysplasia → 3-5 years; LGD → 6 months; HGD → treat immediately",
]),
("LA CLASSIFICATION (MUST MEMORIZE)", ACCENT_RED, colors.HexColor("#FFEBEE"), [
"Grade A: mucosal break <5 mm, not crossing fold tops",
"Grade B: mucosal break >5 mm, not crossing fold tops",
"Grade C: mucosal break crossing fold tops, <75% circumference",
"Grade D: mucosal break ≥75% circumference (circumferential)",
"Only LA Grade C/D confirms GERD diagnosis on endoscopy",
]),
]
for header, border_col, bg_col, items in hy_groups:
elems.append(KeepTogether([
HighYieldBox(items, header, bg_col, border_col),
sp(8)
]))
# Differentials table
elems.append(Paragraph(b("GERD - Key Differentials"), S['H3']))
diff_data = [
[Paragraph(b("Condition"), S['TableHeader']),
Paragraph(b("Distinguishing Features"), S['TableHeader'])],
[Paragraph("Functional Heartburn", S['TableCell']),
Paragraph("Normal pH/impedance testing; no acid-symptom correlation; "
"responds to neuromodulators (TCAs/SSRIs), not PPIs", S['TableCell'])],
[Paragraph("Eosinophilic Esophagitis (EoE)", S['TableCell']),
Paragraph("Young atopic males; dysphagia/food impaction dominant; "
"eosinophils >15/HPF on biopsy; furrows/rings on endoscopy; "
"responds to PPI or swallowed budesonide", S['TableCell'])],
[Paragraph("Achalasia", S['TableCell']),
Paragraph("Dysphagia to solids AND liquids; regurgitation of undigested food; "
"bird-beak on barium; absent peristalsis + aperistalsis on manometry", S['TableCell'])],
[Paragraph("Rumination Syndrome", S['TableCell']),
Paragraph("Effortless regurgitation during meals; no heartburn; "
"subconscious; resolves with behavioral therapy/diaphragmatic breathing", S['TableCell'])],
[Paragraph("Peptic Ulcer Disease", S['TableCell']),
Paragraph("Epigastric pain; relieved by food (DU) or worsened by food (GU); "
"H. pylori testing; endoscopy shows ulcer", S['TableCell'])],
[Paragraph("Cardiac Chest Pain", S['TableCell']),
Paragraph("Exertional, radiation to arm/jaw, diaphoresis, ECG changes; "
"MUST EXCLUDE before esophageal workup", S['TableCell'])],
]
diff_tbl = Table(diff_data, colWidths=[1.6*inch, 4.6*inch])
diff_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BLUE, WHITE]),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
elems.append(diff_tbl)
elems.append(sp(8))
# Recent evidence
elems.append(Paragraph(b("Recent Evidence (2023-2025)"), S['H3']))
recent_ev = [
("PMID 38263507 (J Gastroenterol Hepatol 2024)",
"Vonoprazan (P-CAB) superior to PPIs for PPI-resistant GERD - systematic review & meta-analysis"),
("PMID 38345252 (Am J Gastroenterol 2024)",
"P-CABs superior to PPIs for Grade C/D esophagitis healing - network meta-analysis"),
("PMID 36998190 (Surg Innov 2023)",
"Nissen vs Toupet fundoplication: Nissen = lower GERD recurrence; Toupet = lower dysphagia"),
("ASGE 2025",
"New mucosal impedance testing (catheter-based balloon) during endoscopy - differentiates GERD from EoE"),
("ACG 2022",
"Empiric 8-week PPI recommended for classic symptoms without alarms (before endoscopy)"),
]
for ref, text in recent_ev:
elems.append(Paragraph(
f'<font color="{MID_BLUE.hexval()}"><b>{ref}:</b></font> {text}',
S['Bullet']
))
elems.append(sp(2))
elems.append(sp(8))
# Final footer
elems.append(hr(DARK_BLUE, 1))
elems.append(Paragraph(
"Prepared for Residency / FCPS / MRCP Examination Preparation | May 2026 | "
"Sources: Goldman-Cecil Medicine (26e), Yamada's Gastroenterology (7e), Robbins Pathology, "
"Current Surgical Therapy (14e), ACG/AGA/ASGE Guidelines",
S['Caption']
))
return elems
# ── Page Template (Header + Footer) ─────────────────────────────────────────
def add_header_footer(canvas, doc):
canvas.saveState()
page_width = A4[0]
# Header bar
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, A4[1] - 0.45*inch, page_width, 0.45*inch, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 10)
canvas.drawString(0.4*inch, A4[1] - 0.3*inch, "GERD - Comprehensive Exam Notes")
canvas.setFont("Helvetica", 8)
canvas.drawRightString(page_width - 0.4*inch, A4[1] - 0.3*inch, "Residency | FCPS | MRCP")
# Footer
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, 0, page_width, 0.35*inch, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica", 8)
canvas.drawCentredString(page_width/2, 0.12*inch,
f"Page {doc.page} | Goldman-Cecil · Yamada's · ACG/AGA Guidelines 2022-25")
canvas.restoreState()
def first_page(canvas, doc):
canvas.saveState()
# Full dark blue background for cover
canvas.setFillColor(DARK_BLUE)
canvas.rect(0, A4[1]-0.4*inch, A4[0], 0.4*inch, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica", 7)
canvas.drawCentredString(A4[0]/2, A4[1]-0.27*inch, "GASTROENTEROLOGY | EXAM PREPARATION")
canvas.restoreState()
# ── Main build ───────────────────────────────────────────────────────────────
def build_pdf():
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
leftMargin=0.9*inch,
rightMargin=0.9*inch,
topMargin=0.65*inch,
bottomMargin=0.55*inch,
title="GERD Comprehensive Exam Notes",
author="Medical AI Assistant",
subject="GERD - Residency/FCPS/MRCP"
)
story = []
story += cover_page()
story += section1()
story.append(PageBreak())
story += section2()
story.append(PageBreak())
story += section3()
story.append(PageBreak())
story += section4()
story.append(PageBreak())
story += section5()
story.append(PageBreak())
story += section6()
story.append(PageBreak())
story += section7()
story.append(PageBreak())
story += section8()
story.append(PageBreak())
story += section9()
story.append(PageBreak())
story += section10()
doc.build(
story,
onFirstPage=first_page,
onLaterPages=add_header_footer
)
print(f"PDF generated: {OUTPUT_PATH}")
import os
size = os.path.getsize(OUTPUT_PATH)
print(f"File size: {size/1024:.1f} KB")
if __name__ == "__main__":
build_pdf()
cd /home/daytona/workspace/gerd-notes && python generate_gerd_pdf.py 2>&1
~/gerd-notes/generate_gerd_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/gerd-notes/generate_gerd_pdf.py'
cd /home/daytona/workspace/gerd-notes && sed -i "s/style='Bullet'/style='BulletI…
cd /home/daytona/workspace/gerd-notes && python -c " content = open('generate_ge…
cd /home/daytona/workspace/gerd-notes && python -c " content = open('generate_ge…