Definition etiology causes clinical features examination diagnosis and management of Epiphora for Ent pg exam
"Epiphora is the overflow of tears at the eyelid margin." — Kanski's Clinical Ophthalmology, 10th ed.

| Structure | Dimensions |
|---|---|
| Ampulla (vertical canaliculus) | 2 mm |
| Horizontal canaliculus | ~8 mm |
| Common canaliculus | joins lateral wall of lacrimal sac |
| Lacrimal sac | 10–12 mm; lies in lacrimal fossa |
| Nasolacrimal duct | 12–18 mm; opens into inferior meatus |
| Valve of Hasner | mucosal fold at NLD opening |
| Valve of Rosenmüller | at junction of common canaliculus & sac — prevents reflux |
| Level | Cause |
|---|---|
| Punctal | Stenosis (chronic blepharitis, herpes, radiation, cicatrising conjunctivitis, 5-FU, topical glaucoma drops); atresia (congenital) |
| Canalicular | Canaliculitis (Actinomyces israelii), trauma, herpes, cicatrising conditions |
| Lacrimal sac | Dacryocystitis, dacryolithiasis, tumour |
| NLD | PANDO (Primary Acquired NLD Obstruction) — commonest acquired cause in adults; congenital NLD obstruction (valve of Hasner failure to canalise); secondary — sinusitis, trauma, tumour |

| Investigation | Purpose |
|---|---|
| Fluorescein disappearance test | Screening; highly specific for congenital NLD obstruction |
| Jones I & II tests | Localise level of obstruction |
| Lacrimal syringing | Confirm and localise obstruction; gold standard for functional test |
| Probing | Diagnose + treat congenital NLD obstruction |
| Dacryocystography (DCG) | Radio-opaque contrast injected into canaliculi; shows anatomy, site of block, diverticula, filling defects (stones, tumours); digital subtraction DCG is most detailed |
| Nuclear lacrimal scintigraphy | Physiological assessment with radiolabelled tears; identifies partial or functional blocks; less anatomical detail than DCG |
| CT/MRI | Paranasal sinus disease, lacrimal sac tumour |
| Nasal endoscopy | Polyps, deviated septum, intranasal pathology |
| Microbiological swab | Discharge in canaliculitis/dacryocystitis |
| Fact | Detail |
|---|---|
| Commonest cause of watering eye | Paradoxical dry eye (hypersecretion) |
| Commonest acquired NLD obstruction in adults | PANDO (Primary Acquired NLD Obstruction) |
| Congenital NLD obstruction spontaneous resolution | ~90% by age 1 year |
| Site of congenital obstruction | Valve of Hasner (lower end NLD) |
| First-line treatment congenital NLD obstruction | Crigler massage |
| Definitive Rx congenital NLD obstruction | Probing |
| Definitive Rx acquired NLD obstruction / PANDO | DCR (Dacryocystorhinostomy) |
| Jones II positive (fluorescein-stained saline) | Partial NLD obstruction — fluorescein entered sac |
| Jones II negative (unstained saline from nose) | Upper lacrimal (punctal/canalicular) block or pump failure |
| Hard stop on syringing | Canalicular system patent to sac |
| Soft stop on syringing | Canalicular obstruction |
| Causative organism in canaliculitis | Actinomyces israelii |
| NLD drains into | Inferior nasal meatus (below inferior turbinate) |
| DCR: anastomosis between | Lacrimal sac and nasal mucosa |
| Congenital dacryocoele is NOT confused with | Encephalocoele (which is pulsatile, above medial canthal ligament) |
Make the whole thing into PDF
ls -la /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace
mkdir -p /home/daytona/workspace/epiphora-pdf
~/epiphora-pdf/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, Image as RLImage
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import PageBreak
from reportlab.lib.colors import HexColor
import urllib.request
import os
import io
# ── colour palette ──────────────────────────────────────────────────────────
DARK_BLUE = HexColor('#1a3a5c')
MID_BLUE = HexColor('#2563a8')
LIGHT_BLUE = HexColor('#dbeafe')
ACCENT = HexColor('#0ea5e9')
TABLE_HEAD = HexColor('#1e3a5f')
TABLE_ALT = HexColor('#f0f7ff')
WHITE = colors.white
BLACK = colors.black
GREY_TEXT = HexColor('#374151')
BORDER = HexColor('#93c5fd')
OUTPUT = '/home/daytona/workspace/epiphora-pdf/Epiphora_PG_Exam_Notes.pdf'
# ── document ─────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2.5*cm, bottomMargin=2.5*cm,
title='Epiphora — PG Exam Notes',
author='Orris Medical'
)
W = A4[0] - 4*cm # usable width
styles = getSampleStyleSheet()
# custom styles
def S(name, **kw):
base = kw.pop('parent', 'Normal')
s = ParagraphStyle(name, parent=styles[base], **kw)
return s
title_style = S('MyTitle', fontSize=22, textColor=WHITE,
alignment=TA_CENTER, fontName='Helvetica-Bold',
spaceAfter=4, leading=28)
subtitle_style= S('MySub', fontSize=11, textColor=LIGHT_BLUE,
alignment=TA_CENTER, fontName='Helvetica', spaceAfter=2)
date_style = S('MyDate', fontSize=9, textColor=LIGHT_BLUE,
alignment=TA_CENTER, fontName='Helvetica-Oblique')
h1_style = S('H1', fontSize=14, textColor=WHITE,
fontName='Helvetica-Bold', spaceAfter=4, spaceBefore=14,
leading=18)
h2_style = S('H2', fontSize=11, textColor=DARK_BLUE,
fontName='Helvetica-Bold', spaceAfter=3, spaceBefore=10,
leading=15)
h3_style = S('H3', fontSize=10, textColor=MID_BLUE,
fontName='Helvetica-Bold', spaceAfter=2, spaceBefore=6,
leading=14)
body_style = S('Body', fontSize=9.5, textColor=GREY_TEXT,
fontName='Helvetica', spaceAfter=4, leading=14,
alignment=TA_JUSTIFY)
bullet_style = S('Bullet', fontSize=9.5, textColor=GREY_TEXT,
fontName='Helvetica', spaceAfter=3, leading=13,
leftIndent=14, bulletIndent=4)
bullet2_style = S('Bullet2', fontSize=9, textColor=GREY_TEXT,
fontName='Helvetica', spaceAfter=2, leading=12,
leftIndent=28, bulletIndent=18)
tip_style = S('Tip', fontSize=9, textColor=HexColor('#1e40af'),
fontName='Helvetica-Oblique', spaceAfter=4, leading=13,
leftIndent=10)
source_style = S('Source', fontSize=8, textColor=HexColor('#6b7280'),
fontName='Helvetica-Oblique', alignment=TA_CENTER,
spaceAfter=6, spaceBefore=2)
key_style = S('Key', fontSize=9, textColor=HexColor('#166534'),
fontName='Helvetica-Bold', spaceAfter=2, leading=13,
leftIndent=14)
# ── helpers ──────────────────────────────────────────────────────────────────
def section_header(text, number):
"""Blue banner heading."""
tbl = Table([[Paragraph(f'{number}. {text}', h1_style)]], colWidths=[W])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), DARK_BLUE),
('TOPPADDING', (0,0), (-1,-1), 7),
('BOTTOMPADDING', (0,0), (-1,-1), 7),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
('ROWBACKGROUNDS',(0,0), (-1,-1), [DARK_BLUE]),
]))
return tbl
def sub_header(text):
return Paragraph(text, h2_style)
def sub_sub_header(text):
return Paragraph(text, h3_style)
def body(text):
return Paragraph(text, body_style)
def bullet(text, level=1):
prefix = '• ' if level == 1 else '◦ '
st = bullet_style if level == 1 else bullet2_style
return Paragraph(prefix + text, st)
def tip(text):
tbl = Table([[Paragraph('💡 TIP: ' + text, tip_style)]], colWidths=[W])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), HexColor('#eff6ff')),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
('BOX', (0,0), (-1,-1), 0.5, HexColor('#bfdbfe')),
('ROUNDEDCORNERS',(0,0), (-1,-1), 3),
]))
return tbl
def sp(h=4):
return Spacer(1, h)
def hr():
return HRFlowable(width='100%', thickness=0.5, color=BORDER, spaceAfter=4, spaceBefore=4)
def make_table(headers, rows, col_widths=None):
if col_widths is None:
col_widths = [W/len(headers)]*len(headers)
hdr_style = ParagraphStyle('th', fontSize=9, textColor=WHITE,
fontName='Helvetica-Bold', leading=12)
cell_style= ParagraphStyle('td', fontSize=8.5, textColor=GREY_TEXT,
fontName='Helvetica', leading=12)
data = [[Paragraph(h, hdr_style) for h in headers]]
for i, row in enumerate(rows):
data.append([Paragraph(str(c), cell_style) for c in row])
tbl = Table(data, colWidths=col_widths, repeatRows=1)
cmd = [
('BACKGROUND', (0,0), (-1,0), TABLE_HEAD),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 7),
('RIGHTPADDING', (0,0), (-1,-1), 7),
('GRID', (0,0), (-1,-1), 0.4, BORDER),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]
for i in range(1, len(data)):
bg = TABLE_ALT if i % 2 == 0 else WHITE
cmd.append(('BACKGROUND', (0,i), (-1,i), bg))
tbl.setStyle(TableStyle(cmd))
return tbl
def download_image(url, filename):
path = f'/home/daytona/workspace/epiphora-pdf/{filename}'
if not os.path.exists(path):
try:
urllib.request.urlretrieve(url, path)
except Exception as e:
print(f'Image download failed: {e}')
return None
return path
# ── download images ───────────────────────────────────────────────────────────
img1_path = download_image(
'https://cdn.orris.care/cdss_images/3a8a23ad5c9d6f2155842d46f4f6c5e369ee22d9348a7b6f6fcee983280f86ac.png',
'anatomy.png')
img2_path = download_image(
'https://cdn.orris.care/cdss_images/bb7f941f6f8bbcd929daf997a6d4ced1623339a125df7b0bef1d163fd4c3d321.png',
'epiphora_clinical.png')
# ── title page flowable ───────────────────────────────────────────────────────
def title_block():
tbl = Table([
[Paragraph('EPIPHORA', title_style)],
[Paragraph('Complete PG Exam Notes — ENT / Ophthalmology', subtitle_style)],
[Paragraph('Source: Kanski\'s Clinical Ophthalmology 10th Ed. | KJ Lee\'s Otolaryngology | Goldman-Cecil Medicine', date_style)],
], colWidths=[W])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), DARK_BLUE),
('TOPPADDING', (0,0), (0,0), 20),
('BOTTOMPADDING', (0,-1),(-1,-1), 20),
('TOPPADDING', (0,1), (-1,-2), 4),
('BOTTOMPADDING', (0,0), (-1,-2), 4),
('LEFTPADDING', (0,0), (-1,-1), 16),
('RIGHTPADDING', (0,0), (-1,-1), 16),
]))
return tbl
# ── BUILD STORY ───────────────────────────────────────────────────────────────
story = []
# Title
story.append(title_block())
story.append(sp(12))
# ═══════════════════════════════════════════════════════════════════════════════
# 1. DEFINITION
# ═══════════════════════════════════════════════════════════════════════════════
story.append(section_header('DEFINITION', 1))
story.append(sp(6))
story.append(body(
'<b>Epiphora</b> is the overflow of tears onto the cheek/face due to either '
'<b>hypersecretion</b> of tears beyond normal drainage capacity, or '
'<b>defective drainage</b> of tears through the lacrimal drainage system.'
))
story.append(sp(4))
story.append(tip(
'It is common for a watering eye to be caused by reflex hypersecretion of tears '
'secondary to a dry ocular surface ("paradoxical watering").'
))
# ═══════════════════════════════════════════════════════════════════════════════
# 2. ANATOMY
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('RELEVANT ANATOMY', 2))
story.append(sp(6))
if img1_path:
img = RLImage(img1_path, width=10*cm, height=8*cm, kind='proportional')
story.append(img)
story.append(Paragraph(
'Fig. 3.1 — Anatomy of the lacrimal drainage system (Kanski\'s Clinical Ophthalmology)',
source_style))
story.append(sp(6))
anat_rows = [
['Ampulla (vertical canaliculus)', '2 mm; part of tarsal plate'],
['Horizontal canaliculus', '~8 mm; surrounded by Horner muscle (palpebral orbicularis)'],
['Common canaliculus', 'Superior + inferior canaliculi unite (>90%); opens into lateral wall of lacrimal sac'],
['Valve of Rosenmüller', 'Mucosal flap at junction of common canaliculus and sac; prevents reflux'],
['Lacrimal sac', '10–12 mm; lies in lacrimal fossa between anterior & posterior lacrimal crests'],
['Nasolacrimal duct (NLD)', '12–18 mm; opens into inferior nasal meatus below inferior turbinate'],
['Valve of Hasner', 'Mucosal fold at lower end of NLD; last part to canalise at birth'],
]
story.append(make_table(['Structure', 'Key Details'], anat_rows, [6.5*cm, 11.5*cm]))
story.append(sp(8))
story.append(sub_header('Physiology of Tear Drainage'))
for t in [
'Tears flow along marginal strips → lacus lacrimalis → enter canaliculi by capillarity + suction.',
'On <b>blinking</b>: orbicularis compresses ampullae, shortens canaliculi, closes puncta medially → forces tears down NLD.',
'On <b>eye opening</b>: canaliculi and sac expand, creating negative pressure that draws tears from canaliculi into sac.',
]:
story.append(bullet(t))
# ═══════════════════════════════════════════════════════════════════════════════
# 3. ETIOLOGY
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('ETIOLOGY / CAUSES', 3))
story.append(sp(6))
story.append(sub_header('A. Hypersecretion (Lacrimation) — Reflex Overproduction'))
for t in [
'<b>Dry eye</b> — "paradoxical watering" (commonest cause overall)',
'Conjunctivitis, keratitis, uveitis, scleritis',
'Entropion / trichiasis / aberrant eyelashes / corneal foreign body',
'Congenital hereditary endothelial dystrophy',
'Sclerocornea (neonates)',
'Congenital glaucoma (rare)',
]:
story.append(bullet(t))
story.append(sp(6))
story.append(sub_header('B. Defective Drainage'))
story.append(sub_sub_header('I. Punctal / Lid Malposition'))
for t in [
'<b>Ectropion</b> (commonest lid cause) — punctum everts away from globe',
'<b>Centurion syndrome</b> — medial lid displaced anteriorly by prominent nasal bridge',
'Lower lid laxity / involutional changes',
]:
story.append(bullet(t))
story.append(sp(4))
story.append(sub_sub_header('II. Obstruction at Any Level'))
obs_rows = [
['Punctal', 'Stenosis (chronic blepharitis, herpes, radiation, cicatrising conjunctivitis, 5-FU, topical glaucoma drops); congenital atresia'],
['Canalicular', 'Canaliculitis (Actinomyces israelii), trauma, herpes simplex, cicatrising conditions'],
['Lacrimal sac', 'Dacryocystitis, dacryolithiasis, tumour, mucocele'],
['NLD', 'PANDO (Primary Acquired NLD Obstruction) — commonest acquired adult cause; congenital NLD obstruction (Hasner valve); secondary — sinusitis, trauma, tumour, post-surgical'],
]
story.append(make_table(['Level', 'Causes'], obs_rows, [4*cm, 14*cm]))
story.append(sp(4))
story.append(sub_sub_header('III. Lacrimal Pump Failure'))
for t in [
'<b>Facial nerve palsy</b> (orbicularis weakness)',
'Lower lid laxity / involutional change',
'<b>Conjunctivochalasis</b> — redundant conjunctival fold blocking punctum',
]:
story.append(bullet(t))
# ═══════════════════════════════════════════════════════════════════════════════
# 4. CLINICAL FEATURES
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('CLINICAL FEATURES', 4))
story.append(sp(6))
story.append(sub_header('Symptoms'))
for t in [
'<b>Tears overflowing onto the cheek</b> — cardinal symptom',
'Worse in cold, windy weather; better in warm, dry room (→ drainage failure)',
'Mucopurulent discharge (especially in NLD obstruction)',
'Crusting of lashes on waking',
'Skin maceration / excoriation at medial canthus',
'Swelling at medial canthus (dacryocystitis, mucocele, dacryocoele)',
]:
story.append(bullet(t))
story.append(sp(6))
story.append(sub_header('In Neonates / Congenital NLD Obstruction'))
for t in [
'Epiphora in ~20% of normal babies at birth',
'Spontaneous resolution in ~<b>90% by age 1 year</b>',
'Mucopurulent sticky discharge',
'<b>Congenital dacryocoele (amniotocoele)</b>: bluish cystic swelling at/below medial canthus at birth; must not be confused with encephalocoele (pulsatile, above medial canthal ligament)',
]:
story.append(bullet(t))
story.append(sp(6))
if img2_path:
img = RLImage(img2_path, width=12*cm, height=9*cm, kind='proportional')
story.append(img)
story.append(Paragraph(
'Fig. 3.18 — (A) Child with watering eye; (B) probing of NLD; (C) congenital dacryocele '
'(Kanski\'s Clinical Ophthalmology)',
source_style))
# ═══════════════════════════════════════════════════════════════════════════════
# 5. EXAMINATION
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('EXAMINATION', 5))
story.append(sp(6))
exam_steps = [
('Step 1 — Slit-lamp Examination (Before any manipulation)',
[
'<b>Marginal tear strip (meniscus)</b>: Normal = 0.2–0.4 mm; Elevated ≥0.6 mm = abnormal',
'Examine <b>puncta BEFORE cannulation</b> (irrigation dilates punctum, masking stenosis)',
'Punctal stenosis — commonest cause of drainage failure',
'Ectropion / eversion of punctum',
'Pouting punctum → canaliculitis',
'Conjunctivochalasis occluding punctum',
'Eyelash in ampulla; large caruncle displacing punctum',
]),
('Step 2 — Lacrimal Sac Palpation',
[
'<b>Mucocele</b>: Regurgitation of mucopurulent material on pressure → NLD obstruction with patent canaliculi',
'<b>Acute dacryocystitis</b>: Tender swelling — do NOT press (do NOT irrigate in acute phase)',
'Hard mass on palpation → stone or tumour',
]),
('Step 3 — Fluorescein Disappearance Test (FDT)',
[
'Instil fluorescein 1–2% drops into both conjunctival fornices',
'Normal: little/no dye remains at 5–10 min under blue light in darkened room',
'<b>Prolonged retention</b> = inadequate lacrimal drainage',
]),
('Step 4 — Jones Dye Tests',
[
'<b>Jones I (Primary Dye Test)</b>: Fluorescein instilled; nasal pledget checked at 5 min.',
'Positive (normal): fluorescein recovered from nose → drainage intact',
'Negative: proceed to Jones II',
'<b>Jones II (Secondary Dye Test)</b>: Irrigate canaliculus with saline after prior fluorescein:',
'Fluorescein-stained saline from nose → partial NLD obstruction (fluorescein entered sac)',
'Unstained saline from nose → upper lacrimal (punctal/canalicular) dysfunction or pump failure',
]),
('Step 5 — Lacrimal Syringing (Irrigation)',
[
'Dilate punctum → insert blunt 26/27G lacrimal cannula into lower punctum',
'<b>Hard stop</b> (cannula hits medial lacrimal bone) = canalicular system patent to sac level',
'<b>Soft stop</b> (soft resistance before hard stop) = canalicular obstruction',
'Free flow to nasopharynx → NLD patent',
'Regurgitation from <i>same</i> punctum → complete canalicular block',
'Regurgitation from <i>opposite</i> punctum → NLD obstruction (common canaliculus patent)',
'Partial flow with resistance → partial NLD obstruction / functional block',
]),
]
for (header, bullets) in exam_steps:
story.append(KeepTogether([
sub_header(header),
*[bullet(t) for t in bullets],
sp(4),
]))
# ═══════════════════════════════════════════════════════════════════════════════
# 6. INVESTIGATIONS / DIAGNOSIS
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('INVESTIGATIONS / DIAGNOSIS', 6))
story.append(sp(6))
inv_rows = [
['Fluorescein Disappearance Test', 'Screening; highly specific for congenital NLD obstruction'],
['Jones I & II Tests', 'Localise level of obstruction (upper vs lower drainage system)'],
['Lacrimal Syringing', 'Confirm and localise obstruction; gold standard functional test'],
['Probing', 'Diagnose AND treat congenital NLD obstruction'],
['Dacryocystography (DCG)', 'Radio-opaque contrast injected into canaliculi; shows anatomy, site of block, diverticula, filling defects (stones, tumours); digital subtraction DCG most detailed'],
['Nuclear Lacrimal Scintigraphy', 'Physiological assessment with radiolabelled tears; identifies partial or functional blocks; less anatomical detail than DCG'],
['CT / MRI', 'Paranasal sinus disease, lacrimal sac tumour, complex anatomy'],
['Nasal Endoscopy', 'Polyps, deviated septum, intranasal pathology'],
['Microbiological swab', 'Discharge in canaliculitis (Actinomyces) or dacryocystitis'],
]
story.append(make_table(['Investigation', 'Purpose'], inv_rows, [6*cm, 12*cm]))
# ═══════════════════════════════════════════════════════════════════════════════
# 7. MANAGEMENT
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('MANAGEMENT', 7))
story.append(sp(6))
story.append(sub_header('A. Treat Hypersecretion (Medical)'))
for t in [
'Artificial tears / lubricants for dry eye paradoxical watering',
'Anti-inflammatory drops (ciclosporin, topical steroids) for blepharitis / dry eye',
'Treat underlying conjunctivitis, trichiasis, entropion',
]:
story.append(bullet(t))
story.append(sp(6))
story.append(sub_header('B. Congenital NLD Obstruction'))
steps = [
('<b>Conservative (first-line, <12 months)</b>',
'Crigler massage — index finger over common canaliculus, rolled downwards over sac to create hydrostatic pressure rupturing Hasner membrane'),
('<b>Probing (definitive treatment)</b>',
'Fine wire via canalicular system → NLD → disrupts obstructing membrane at valve of Hasner; '
'delayed until 12–18 months (or 24 months if mild symptoms); under GA; can be repeated; '
'follow with irrigation to confirm patency'),
('<b>If probing fails</b>',
'Silicone tube intubation ± balloon dilatation of NLD; endoscopic procedures; dacryocystorhinostomy (DCR)'),
('<b>Congenital dacryocoele</b>',
'Conservative first; probing if conservative management fails'),
]
for (bold, text) in steps:
story.append(bullet(f'{bold}: {text}'))
story.append(sp(6))
story.append(sub_header('C. Acquired Punctal / Canalicular Obstruction'))
for t in [
'<b>Punctal stenosis</b>: Punctal dilatation ± mini-Monoka stent (removed at 1 month); <b>Punctoplasty</b> (one-, two- or three-snip procedure) if dilation fails',
'<b>Ectropion</b>: Lid tightening procedures (lateral tarsal strip, medial canthoplasty)',
'<b>Conjunctivochalasis</b>: Topical lubricants / anti-inflammatories; surgical excision or conjunctival suturing',
'<b>Canaliculitis (Actinomyces)</b>: Canaliculotomy + curettage + topical penicillin / cefuroxime',
]:
story.append(bullet(t))
story.append(sp(6))
story.append(sub_header('D. Acquired NLD Obstruction / PANDO — Dacryocystorhinostomy (DCR)'))
story.append(body(
'DCR creates an anastomosis between the lacrimal sac and nasal mucosa, bypassing NLD obstruction. '
'Success rate ~90%.'
))
dcr_rows = [
['External DCR (standard)',
'Vertical skin incision 10 mm medial to inner canthus; '
'anterior lacrimal crest and lacrimal fossa bone removed; '
'sac opened with H-shaped incision (anterior + posterior flaps); '
'nasal mucosa opened; posterior then anterior flaps sutured; '
'silicone tube often inserted (removed at 3–6 months)'],
['Endoscopic (Endonasal) DCR',
'No skin incision; comparable success rates; preferred for functional failures or redo cases'],
]
story.append(make_table(['Procedure', 'Key Steps / Notes'], dcr_rows, [5.5*cm, 12.5*cm]))
story.append(sp(6))
story.append(sub_header('E. Dacryocystitis'))
for t in [
'<b>Acute</b>: Systemic antibiotics (IV amoxicillin-clavulanate or cephalosporins in severe cases); warm compresses; DCR after infection resolves',
'<b>Chronic</b>: DCR is the definitive treatment',
'<b>Mucocele</b>: Pressure test confirms; DCR required',
]:
story.append(bullet(t))
story.append(sp(6))
story.append(sub_header('F. Lacrimal Pump Failure (Facial Nerve Palsy)'))
for t in [
'Treat underlying cause',
'Lubricating eye drops / ointment to protect cornea',
'Lateral tarsorrhaphy or lower lid tightening if lid laxity contributes',
]:
story.append(bullet(t))
# ═══════════════════════════════════════════════════════════════════════════════
# 8. MNEMONIC
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('MNEMONIC — Causes of Epiphora', 8))
story.append(sp(6))
mnem_rows = [
['P', 'Punctal stenosis / malposition (Ectropion)'],
['E', 'Evaporation / Dry Eye (paradoxical hypersecretion) — COMMONEST'],
['C', 'Canalicular obstruction (trauma, Canaliculitis — Actinomyces)'],
['K', 'Keratoconjunctivitis / ocular surface disease'],
['D', 'Dacryocystitis / NLD obstruction (PANDO)'],
['F', 'Facial nerve palsy (pump failure)'],
['L', 'Lid laxity / conjunctivochalasis'],
]
style_mnem = TableStyle([
('BACKGROUND', (0,0), (0,-1), MID_BLUE),
('TEXTCOLOR', (0,0), (0,-1), WHITE),
('FONTNAME', (0,0), (0,-1), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 11),
('FONTNAME', (1,0), (1,-1), 'Helvetica'),
('TEXTCOLOR', (1,0), (1,-1), GREY_TEXT),
('FONTSIZE', (1,0), (1,-1), 9.5),
('TOPPADDING', (0,0), (-1,-1), 7),
('BOTTOMPADDING', (0,0), (-1,-1), 7),
('LEFTPADDING', (0,0), (-1,-1), 12),
('RIGHTPADDING', (0,0), (-1,-1), 12),
('GRID', (0,0), (-1,-1), 0.4, BORDER),
])
for i in range(len(mnem_rows)):
bg = TABLE_ALT if i % 2 == 0 else WHITE
style_mnem.add('BACKGROUND', (1,i), (1,i), bg)
mnem_cell = ParagraphStyle('mc', fontSize=9.5, textColor=GREY_TEXT,
fontName='Helvetica', leading=13)
mnem_letter = ParagraphStyle('ml', fontSize=14, textColor=WHITE,
fontName='Helvetica-Bold', leading=18,
alignment=TA_CENTER)
mnem_data = [[Paragraph(r[0], mnem_letter),
Paragraph(r[1], mnem_cell)] for r in mnem_rows]
mnem_tbl = Table(mnem_data, colWidths=[2*cm, 16*cm])
mnem_tbl.setStyle(style_mnem)
story.append(mnem_tbl)
# ═══════════════════════════════════════════════════════════════════════════════
# 9. KEY EXAM POINTS
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header('KEY EXAM POINTS', 9))
story.append(sp(6))
key_rows = [
['Commonest cause of watering eye', 'Paradoxical dry eye (hypersecretion)'],
['Commonest acquired NLD obstruction (adults)', 'PANDO (Primary Acquired NLD Obstruction)'],
['Congenital NLD obstruction — spontaneous resolution', '~90% by age 1 year'],
['Site of congenital obstruction', 'Valve of Hasner (lower end of NLD)'],
['First-line Rx — congenital NLD obstruction', 'Crigler massage'],
['Definitive Rx — congenital NLD obstruction', 'Probing (under GA at 12–18 months)'],
['Definitive Rx — acquired NLD obstruction / PANDO', 'Dacryocystorhinostomy (DCR)'],
['Jones II positive (fluorescein-stained saline from nose)', 'Partial NLD obstruction — fluorescein entered sac'],
['Jones II negative (unstained saline from nose)', 'Upper lacrimal block (punctal/canalicular) or pump failure'],
['Hard stop on syringing', 'Canalicular system patent to sac level'],
['Soft stop on syringing', 'Canalicular obstruction'],
['Causative organism — canaliculitis', 'Actinomyces israelii (Gram-positive anaerobe)'],
['NLD drains into', 'Inferior nasal meatus (below inferior turbinate)'],
['DCR — anastomosis between', 'Lacrimal sac and nasal mucosa'],
['Congenital dacryocoele ≠', 'Encephalocoele (pulsatile, ABOVE medial canthal ligament)'],
['Normal marginal tear strip', '0.2–0.4 mm; ≥0.6 mm = abnormal (epiphora)'],
['DCR success rate', '~90%'],
]
story.append(make_table(['Point', 'Answer'], key_rows, [8*cm, 10*cm]))
# ═══════════════════════════════════════════════════════════════════════════════
# Footer / Sources
# ═══════════════════════════════════════════════════════════════════════════════
story.append(sp(14))
story.append(hr())
story.append(Paragraph(
'<b>Sources:</b> Kanski\'s Clinical Ophthalmology: A Systematic Approach, 10th Ed. (ISBN 9780443110993) '
'| K.J. Lee\'s Essential Otolaryngology | Goldman-Cecil Medicine',
source_style))
# ── BUILD PDF ─────────────────────────────────────────────────────────────────
doc.build(story)
print(f'PDF created: {OUTPUT}')
cd /home/daytona/workspace/epiphora-pdf && python generate_pdf.py
ls -lh /home/daytona/workspace/epiphora-pdf/Epiphora_PG_Exam_Notes.pdf
Epiphora PG Exam Notes
PDF Document · PDF