How to approach for exam of ent

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Reading File
Finding Sources
Reading File
Loading Image
I now have comprehensive content to give a thorough answer. Here is the complete guide to the ENT clinical examination approach, sourced from Cummings Otolaryngology Head and Neck Surgery.

Approach to the ENT (Otolaryngology) Examination


1. Before You Start

  • Establish rapport before touching the patient - many ENT maneuvers feel invasive and can cause discomfort if the patient is unprepared.
  • Wash hands before and after every examination.
  • Wear gloves for all mucosal examinations; add eye protection when dealing with secretions or possible sprays.
  • Ensure the room is quiet (important for hearing tests).

2. History Taking

A structured history is the cornerstone. Cover:
DomainKey Points
Chief complaintDuration, onset, progression
TobaccoPack-years, type (cigarettes/cigars/smokeless), quit date
AlcoholAmount, frequency, beverage type, duration
Drug/IV useCurrent and past
Sexual historyHPV risk (relevant to head and neck cancers), HIV, hepatitis C
Occupational exposureNoise, carcinogens, irritants, radiation history
Family historyHereditary hearing loss, congenital defects, atopy, cancer
Review of systemsRespiratory, cardiac, neurologic, endocrine, GI, urogenital, musculoskeletal, skin, psychiatric

3. General Appearance

Before the structured exam, observe:
  • Vital signs and level of alertness
  • Signs of distress - increased work of breathing, diaphoresis, rigors
  • Affect - depression, anxiety, intoxication
  • Personal hygiene, tar-stained nails/teeth (heavy tobacco use)
  • Gait and balance - disturbed gait may indicate vestibular or neurologic problems

4. Head and Facies

  • Inspect for overall shape, symmetry, and signs of trauma
  • Note areas of hair loss if relevant
  • Look for facial asymmetry - may indicate cranial nerve involvement or parotid mass
  • Check for facial skin lesions (basal cell, squamous cell carcinoma)

5. Ear Examination

External Ear

  • Inspect the auricle for deformities, skin lesions, pre-auricular sinuses/pits, keloids, Battle's sign (post-auricular ecchymosis in skull base fracture)
  • Palpate for tragal tenderness (otitis externa) and mastoid tenderness (mastoiditis)

Otoscopy

  • Straighten the ear canal by pulling the pinna upward and backward in adults, downward and backward in children
  • Clear cerumen and debris first
  • Systematically examine: ear canal (erythema, discharge, foreign body, exostoses) then tympanic membrane (TM)
Normal TM landmarks to identify:
  • Pars flaccida (superior)
  • Pars tensa
  • Lateral process of malleus
  • Malleus handle
  • Umbo (tip of malleus handle)
  • Cone of light (anterior-inferior)
Tympanic membrane landmarks - pars flaccida, posterior mallear fold, lateral process of malleus, malleus handle, umbo
Fig. 4.1 The tympanic membrane - Cummings Otolaryngology
TM findings to note:
  • Myringosclerosis - chalky white patches from prior trauma
  • Myringitis - thickened, erythematous TM, may have bullae
  • Atelectasis/adhesive otitis - thin membrane draped over middle ear structures
  • Perforation - note location (central vs. marginal), proximity to annulus, size (as % of drum)
  • Cholesteatoma - white mass with granulation tissue; congenital = white pearl behind intact TM in anterior-superior quadrant
  • Glomus tumor - vascular mass; Brown sign = blanching on pneumatic otoscopy

Pneumatic Otoscopy

  • Seal the canal with an appropriately sized speculum
  • Apply gentle pressure: the TM should move back and forth freely in a healthy, well-aerated middle ear
  • Immobility suggests perforation or middle ear effusion
  • Middle ear fluid: serous = amber with air-fluid levels; mucoid = dull gray-white, retracted drum

Hearing Assessment - Tuning Fork Tests (512 Hz standard)

TestHow to PerformNormalConductive HLSensorineural HL
WeberPlace vibrating fork on forehead/midlineMidline ("negative")Lateralizes to affected earLateralizes to better ear
RinneAir conduction (fork near canal) vs. bone conduction (mastoid)AC > BC (Rinne positive)BC > AC (Rinne negative)AC > BC but both reduced
  • A false-negative Rinne (BC > AC in sensorineural loss) can occur in severe sensorineural loss - always correlate with Weber.

6. Nose Examination

External Nose

  • Inspect for deviation, deformity, skin lesions, nasal valve collapse
  • Assess nasal tip support and columellar show

Anterior Rhinoscopy

  • Use a nasal speculum and light source (head mirror or headlight)
  • Open the speculum vertically (not horizontally) to avoid septal trauma
  • Examine: septum (deviation, perforation, spurs), inferior turbinate (hypertrophy, color), middle turbinate, nasal mucosa (pallor in allergy, erythema in infection), nasal polyps, discharge (clear, mucopurulent, bloody)

Nasal Endoscopy (Rigid or Flexible)

  • Preferred for complete assessment
  • Systematic passes:
    1. First pass - floor, inferior meatus, inferior turbinate, nasopharynx, Eustachian tube orifices
    2. Second pass - middle meatus, middle turbinate, uncinate process, ostiomeatal complex
    3. Third pass (if tolerated) - superior turbinate, sphenoethmoidal recess, sphenoid ostium
  • Note: mucosa character, sinus drainage pathways, polyps, anatomic variants, purulent discharge

7. Oral Cavity and Oropharynx

  • Use a tongue depressor and adequate light
  • Inspect: lips, buccal mucosa, gingiva, floor of mouth, hard palate, tongue (dorsum, lateral borders, ventral surface), soft palate, tonsillar pillars, tonsils, posterior pharyngeal wall
  • Look for: leukoplakia, erythroplakia, ulcerations, masses, tonsillar asymmetry, exudate
  • Assess uvula deviation (unilateral palatal weakness in cranial nerve X lesion)
  • Palpate the floor of mouth and tongue for submucosal masses

8. Larynx and Hypopharynx

Indirect Laryngoscopy (Mirror)

  • Patient sits upright, leans forward, tongue out
  • Warm the mirror to prevent fogging
  • Ask patient to phonate "eeeee" to assess vocal cord mobility
  • Inspect: epiglottis, aryepiglottic folds, arytenoids, true and false vocal cords, subglottis

Flexible Nasopharyngolaryngoscopy

  • Gold standard for dynamic laryngeal assessment
  • Passed transnasally after topical anesthesia/decongestant
  • Assess vocal cord mobility, mucosal lesions, pooling of secretions

9. Neck Examination

Inspection: Scars, asymmetry, masses, tracheal position, thyroid enlargement
Palpation - systematic levels:
LevelRegion
ISubmental + submandibular
IIUpper jugular (angle of mandible to hyoid)
IIIMid-jugular (hyoid to cricoid)
IVLower jugular (cricoid to clavicle)
VPosterior triangle
VICentral compartment (paratracheal, pretracheal)
  • Note any mass: size, consistency, mobility, tenderness, pulsatility, overlying skin changes
  • Thyroid: assess in swallowing (thyroid moves up with swallow); note nodules, tenderness
  • Salivary glands: parotid (pre-auricular/angle of mandible), submandibular, sublingual

10. Cranial Nerve Examination (ENT-Relevant)

CNTest
I (Olfactory)Identify common smells unilaterally
V (Trigeminal)Facial sensation in V1/V2/V3 distributions; corneal reflex
VII (Facial)Forehead, eye closure, smile, brow raise - note central vs. peripheral pattern
VIII (Vestibulocochlear)Tuning forks (hearing), Romberg, Unterberger test (vestibular)
IX/X (Glosso/Vagus)Gag reflex, palate elevation, phonation ("aah")
XI (Accessory)Sternocleidomastoid and trapezius strength
XII (Hypoglossal)Tongue protrusion - deviates toward weak side

11. Otoneurologic / Vestibular Tests

  • Romberg test - eyes closed stance, increased sway with vestibular/proprioceptive lesion
  • Unterberger (Fukuda) stepping test - steps in place eyes closed; rotation toward side of lesion
  • Dix-Hallpike - BPPV testing; look for nystagmus and symptom reproduction
  • Head impulse test (HIT) - assesses VOR; corrective saccade indicates peripheral vestibular lesion
  • Nystagmus assessment - direction, gaze-evoked vs. spontaneous, fixation suppression

12. Special Investigations (ENT Exam Supplement)

Hearing (Audiology):
  • Pure tone audiometry (air + bone conduction)
  • Speech reception threshold + speech discrimination
  • Tympanometry (Type A = normal; Type B = effusion/perforation; Type C = negative middle ear pressure)
  • Acoustic reflex testing
Imaging:
  • CT - sinonasal anatomy and disease, temporal bone, neck masses
  • MRI - soft tissue, skull base, perineural spread

Summary: Order of ENT Examination

  1. General appearance and vital signs
  2. Head and facies
  3. Ears (external → otoscopy → pneumatic otoscopy → tuning forks)
  4. Nose (external → anterior rhinoscopy → endoscopy)
  5. Oral cavity and oropharynx
  6. Larynx and hypopharynx (indirect mirror or flexible scope)
  7. Neck (inspection → palpation of all levels → thyroid → salivary glands)
  8. Cranial nerves (I, V, VII, VIII, IX, X, XI, XII)
  9. Vestibular/otoneurologic tests as indicated

Source: Cummings Otolaryngology Head and Neck Surgery (Cummings, Chapter 4 - History and Physical Examination).

Make a study guide PDF on ENT examination tips and mnemonics

Reading File
Running Command

mkdir -p /home/daytona/workspace/ent-study-guide && ls /home/daytona/workspace/e…

Writing File

~/ent-study-guide/generate_ent_guide.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, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import Flowable

# ── Colour palette ──────────────────────────────────────────────────────────
DARK_TEAL   = colors.HexColor("#006D7A")
MID_TEAL    = colors.HexColor("#009CAB")
LIGHT_TEAL  = colors.HexColor("#E0F5F7")
ORANGE      = colors.HexColor("#E87722")
LIGHT_ORANGE= colors.HexColor("#FFF3E8")
PURPLE      = colors.HexColor("#6A3D9A")
LIGHT_PURPLE= colors.HexColor("#F3EEFF")
GREEN       = colors.HexColor("#2E7D32")
LIGHT_GREEN = colors.HexColor("#E8F5E9")
RED         = colors.HexColor("#C62828")
LIGHT_RED   = colors.HexColor("#FFEBEE")
YELLOW_BG   = colors.HexColor("#FFFDE7")
GREY_BG     = colors.HexColor("#F5F5F5")
WHITE       = colors.white
BLACK       = colors.black

PAGE_W, PAGE_H = A4
MARGIN = 18 * mm

# ── Style helpers ────────────────────────────────────────────────────────────
base_styles = getSampleStyleSheet()

def S(name, **kw):
    return ParagraphStyle(name, **kw)

TITLE_STYLE = S("Title",
    fontName="Helvetica-Bold", fontSize=28,
    textColor=WHITE, alignment=TA_CENTER, leading=34, spaceAfter=4)

SUBTITLE_STYLE = S("Subtitle",
    fontName="Helvetica", fontSize=13,
    textColor=colors.HexColor("#CCF0F3"), alignment=TA_CENTER, leading=18)

H1 = S("H1",
    fontName="Helvetica-Bold", fontSize=14,
    textColor=WHITE, alignment=TA_LEFT, leading=18, spaceAfter=0)

H2 = S("H2",
    fontName="Helvetica-Bold", fontSize=11,
    textColor=DARK_TEAL, alignment=TA_LEFT, leading=15, spaceAfter=2, spaceBefore=4)

H3 = S("H3",
    fontName="Helvetica-Bold", fontSize=10,
    textColor=ORANGE, alignment=TA_LEFT, leading=13, spaceAfter=2, spaceBefore=3)

BODY = S("Body",
    fontName="Helvetica", fontSize=9,
    textColor=BLACK, alignment=TA_LEFT, leading=13, spaceAfter=2)

BODY_J = S("BodyJ",
    fontName="Helvetica", fontSize=9,
    textColor=BLACK, alignment=TA_JUSTIFY, leading=13, spaceAfter=2)

SMALL = S("Small",
    fontName="Helvetica", fontSize=8,
    textColor=colors.HexColor("#444444"), alignment=TA_LEFT, leading=11)

BOLD_BODY = S("BoldBody",
    fontName="Helvetica-Bold", fontSize=9,
    textColor=BLACK, alignment=TA_LEFT, leading=13)

MNEMONIC_TITLE = S("MnemonicTitle",
    fontName="Helvetica-Bold", fontSize=10,
    textColor=PURPLE, alignment=TA_LEFT, leading=14, spaceAfter=1)

MNEMONIC_BODY = S("MnemonicBody",
    fontName="Helvetica", fontSize=9,
    textColor=BLACK, alignment=TA_LEFT, leading=13, spaceAfter=1)

WARN = S("Warn",
    fontName="Helvetica-Bold", fontSize=9,
    textColor=RED, alignment=TA_LEFT, leading=13)

CENTER_SM = S("CenterSm",
    fontName="Helvetica", fontSize=8,
    textColor=colors.HexColor("#555555"), alignment=TA_CENTER, leading=11)

# ── Reusable building blocks ─────────────────────────────────────────────────
def section_header(title, color=DARK_TEAL):
    tbl = Table([[Paragraph(title, H1)]], colWidths=[PAGE_W - 2*MARGIN])
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), color),
        ("TOPPADDING",  (0,0), (-1,-1), 6),
        ("BOTTOMPADDING",(0,0), (-1,-1), 6),
        ("LEFTPADDING", (0,0), (-1,-1), 10),
        ("RIGHTPADDING",(0,0), (-1,-1), 10),
        ("ROUNDEDCORNERS", [4]),
    ]))
    return tbl

def tip_box(text, bg=LIGHT_TEAL, border=MID_TEAL):
    tbl = Table([[Paragraph(f"<b>Tip:</b> {text}", BODY)]], colWidths=[PAGE_W - 2*MARGIN])
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), bg),
        ("LINEABOVE",  (0,0), (-1, 0), 2, border),
        ("TOPPADDING",  (0,0), (-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("LEFTPADDING", (0,0), (-1,-1), 8),
        ("RIGHTPADDING",(0,0), (-1,-1), 8),
    ]))
    return tbl

def mnemonic_box(acronym, expansion_rows, bg=LIGHT_PURPLE, title_color=PURPLE):
    """
    expansion_rows: list of (letter, word, meaning) tuples
    """
    header = [Paragraph(f"<b>{acronym}</b>", S("Mn",
        fontName="Helvetica-Bold", fontSize=11, textColor=title_color, leading=15))]
    data = [header]
    for letter, word, meaning in expansion_rows:
        row = [Paragraph(
            f"<b><font color='#{title_color.hexval()[2:]}' size='10'>{letter}</font></b>  "
            f"<b>{word}</b> - <i>{meaning}</i>", BODY)]
        data.append(row)
    tbl = Table(data, colWidths=[PAGE_W - 2*MARGIN])
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), bg),
        ("LINEABOVE",  (0,0), (0, 0), 2, title_color),
        ("TOPPADDING",  (0,0), (-1,-1), 4),
        ("BOTTOMPADDING",(0,0),(-1,-1), 4),
        ("LEFTPADDING", (0,0), (-1,-1), 8),
        ("RIGHTPADDING",(0,0), (-1,-1), 8),
    ]))
    return tbl

def two_col_table(left_rows, right_rows, col_header_left="", col_header_right="",
                  bg_header=DARK_TEAL, bg_row=GREY_BG):
    max_rows = max(len(left_rows), len(right_rows))
    while len(left_rows) < max_rows: left_rows.append(("",""))
    while len(right_rows) < max_rows: right_rows.append(("",""))

    col_w = (PAGE_W - 2*MARGIN - 6*mm) / 2
    data = []
    if col_header_left or col_header_right:
        data.append([
            Paragraph(col_header_left, S("TH", fontName="Helvetica-Bold", fontSize=9,
                textColor=WHITE, leading=12)),
            Paragraph(col_header_right, S("TH2", fontName="Helvetica-Bold", fontSize=9,
                textColor=WHITE, leading=12)),
        ])
    for (k1, v1), (k2, v2) in zip(left_rows, right_rows):
        data.append([
            Paragraph(f"<b>{k1}</b>  {v1}", BODY) if k1 else Paragraph("", BODY),
            Paragraph(f"<b>{k2}</b>  {v2}", BODY) if k2 else Paragraph("", BODY),
        ])
    tbl = Table(data, colWidths=[col_w, col_w])
    style = [
        ("VALIGN", (0,0), (-1,-1), "TOP"),
        ("TOPPADDING",  (0,0), (-1,-1), 4),
        ("BOTTOMPADDING",(0,0),(-1,-1), 4),
        ("LEFTPADDING", (0,0), (-1,-1), 6),
        ("RIGHTPADDING",(0,0), (-1,-1), 6),
        ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CCCCCC")),
        ("ROWBACKGROUNDS", (0, 1 if (col_header_left or col_header_right) else 0),
         (-1,-1), [WHITE, bg_row]),
    ]
    if col_header_left or col_header_right:
        style += [
            ("BACKGROUND", (0,0), (-1,0), bg_header),
            ("TEXTCOLOR",  (0,0), (-1,0), WHITE),
        ]
    tbl.setStyle(TableStyle(style))
    return tbl

def simple_table(headers, rows, col_widths=None, header_bg=DARK_TEAL):
    col_w = col_widths or [(PAGE_W - 2*MARGIN) / len(headers)] * len(headers)
    data = [[Paragraph(h, S("TH3", fontName="Helvetica-Bold", fontSize=9,
                textColor=WHITE, leading=12)) for h in headers]]
    for row in rows:
        data.append([Paragraph(str(c), BODY) for c in row])
    tbl = Table(data, colWidths=col_w)
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,0), header_bg),
        ("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_BG]),
        ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CCCCCC")),
        ("VALIGN", (0,0), (-1,-1), "TOP"),
        ("TOPPADDING",  (0,0), (-1,-1), 4),
        ("BOTTOMPADDING",(0,0),(-1,-1), 4),
        ("LEFTPADDING", (0,0), (-1,-1), 6),
        ("RIGHTPADDING",(0,0), (-1,-1), 6),
    ]))
    return tbl

def bullet(text, indent=0):
    return Paragraph(f"{'&nbsp;'*indent*4}<b>•</b> {text}", BODY)

def subbullet(text):
    return bullet(text, indent=2)

def warn_box(text):
    tbl = Table([[Paragraph(f"⚠ {text}", WARN)]], colWidths=[PAGE_W - 2*MARGIN])
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), LIGHT_RED),
        ("LINEABOVE",  (0,0), (-1, 0), 2, RED),
        ("TOPPADDING",  (0,0), (-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("LEFTPADDING", (0,0), (-1,-1), 8),
        ("RIGHTPADDING",(0,0), (-1,-1), 8),
    ]))
    return tbl

def sp(n=4): return Spacer(1, n*mm)

# ── COVER PAGE ───────────────────────────────────────────────────────────────
def build_cover():
    elements = []

    # Title banner
    banner_data = [[
        Paragraph("ENT EXAMINATION", TITLE_STYLE),
    ],[
        Paragraph("Study Guide: Tips &amp; Mnemonics", SUBTITLE_STYLE),
    ],[
        Paragraph("Otolaryngology · Head &amp; Neck Surgery", S("Sub2",
            fontName="Helvetica", fontSize=11,
            textColor=colors.HexColor("#A8E6EC"), alignment=TA_CENTER, leading=16)),
    ]]
    banner = Table(banner_data, colWidths=[PAGE_W - 2*MARGIN])
    banner.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), DARK_TEAL),
        ("TOPPADDING",  (0,0), (-1,-1), 14),
        ("BOTTOMPADDING",(0,0),(-1,-1), 14),
        ("LEFTPADDING", (0,0), (-1,-1), 20),
        ("RIGHTPADDING",(0,0), (-1,-1), 20),
    ]))
    elements.append(banner)
    elements.append(sp(6))

    # Quick-reference summary boxes
    summary = [
        ("EAR", "Otoscopy · Tuning Forks\nAudiometry · Vestibular Tests", MID_TEAL),
        ("NOSE", "Rhinoscopy · Endoscopy\nSinus Assessment", ORANGE),
        ("THROAT", "Oral Cavity · Larynx\nNeck Palpation", PURPLE),
    ]
    cols = []
    for title, body, col in summary:
        cell = Table([
            [Paragraph(title, S("ST", fontName="Helvetica-Bold", fontSize=13,
                textColor=WHITE, alignment=TA_CENTER, leading=16))],
            [Paragraph(body, S("SB", fontName="Helvetica", fontSize=9,
                textColor=WHITE, alignment=TA_CENTER, leading=13))],
        ], colWidths=[(PAGE_W - 2*MARGIN - 8*mm)/3])
        cell.setStyle(TableStyle([
            ("BACKGROUND", (0,0), (-1,-1), col),
            ("TOPPADDING",  (0,0), (-1,-1), 10),
            ("BOTTOMPADDING",(0,0),(-1,-1), 10),
            ("LEFTPADDING", (0,0), (-1,-1), 6),
            ("RIGHTPADDING",(0,0), (-1,-1), 6),
        ]))
        cols.append(cell)
    summary_tbl = Table([cols], colWidths=[(PAGE_W - 2*MARGIN - 8*mm)/3]*3,
                        hAlign="CENTER")
    summary_tbl.setStyle(TableStyle([
        ("LEFTPADDING", (0,0), (-1,-1), 0),
        ("RIGHTPADDING",(0,0), (-1,-1), 0),
        ("INNERGRID", (0,0), (-1,-1), 3, WHITE),
    ]))
    elements.append(summary_tbl)
    elements.append(sp(5))

    # What's inside
    what_data = [
        [Paragraph("WHAT'S INSIDE", S("WI", fontName="Helvetica-Bold", fontSize=10,
            textColor=DARK_TEAL, alignment=TA_CENTER, leading=14))],
        [Paragraph(
            "History-Taking Framework  •  General Appearance  •  Ear Examination  •  "
            "Tuning Fork Tests  •  Nose &amp; Sinuses  •  Throat &amp; Oral Cavity  •  "
            "Larynx  •  Neck Levels  •  Cranial Nerve Quick-Check  •  Vestibular Tests  •  "
            "Mnemonics for Everything  •  High-Yield Exam Tips",
            S("WIB", fontName="Helvetica", fontSize=9,
                textColor=colors.HexColor("#333333"), alignment=TA_CENTER, leading=14))],
    ]
    what_tbl = Table(what_data, colWidths=[PAGE_W - 2*MARGIN])
    what_tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), YELLOW_BG),
        ("LINEABOVE", (0,0), (-1,0), 2, ORANGE),
        ("LINEBELOW", (0,-1), (-1,-1), 2, ORANGE),
        ("TOPPADDING",  (0,0), (-1,-1), 8),
        ("BOTTOMPADDING",(0,0),(-1,-1), 8),
        ("LEFTPADDING", (0,0), (-1,-1), 12),
        ("RIGHTPADDING",(0,0), (-1,-1), 12),
    ]))
    elements.append(what_tbl)
    elements.append(sp(4))

    elements.append(Paragraph("Based on Cummings Otolaryngology: Head and Neck Surgery",
        CENTER_SM))
    elements.append(sp(1))
    elements.append(Paragraph("For MBBS / MD / MRCS / Postgraduate ENT Examinations",
        CENTER_SM))

    elements.append(PageBreak())
    return elements

# ── SECTION 1 - HISTORY ──────────────────────────────────────────────────────
def build_history():
    e = []
    e.append(section_header("1. HISTORY TAKING IN ENT"))
    e.append(sp(3))

    e.append(Paragraph("Framework: The SOCRATES+ Approach", H2))
    e.append(simple_table(
        ["Domain", "Key Questions"],
        [
            ["Chief Complaint", "Site · Onset · Character · Radiation · Associated symptoms · Timing · Exacerbating/relieving factors · Severity"],
            ["Tobacco", "Pack-years, type (cigarette/cigar/smokeless), quit date"],
            ["Alcohol", "Amount, frequency, beverage type, duration"],
            ["Occupational Exposure", "Noise (work/recreational), carcinogens, irritants, radiation history"],
            ["Sexual History", "HPV risk for head & neck cancer, HIV, Hepatitis C status"],
            ["Family History", "Hereditary hearing loss, congenital defects, atopy, cancer"],
            ["Review of Systems", "Respiratory, cardiac, neurologic, endocrine, GI, urogenital, skin, psychiatric"],
        ],
        col_widths=[55*mm, PAGE_W - 2*MARGIN - 55*mm]
    ))
    e.append(sp(3))

    e.append(mnemonic_box("SOCRATES  (for any ENT symptom)",
        [
            ("S", "Site", "Ear, nose, throat, neck?"),
            ("O", "Onset", "Sudden / gradual?"),
            ("C", "Character", "Quality of symptom (sharp, dull, muffled, blocked)"),
            ("R", "Radiation", "Otalgia radiating from throat? Neck to ear?"),
            ("A", "Associated", "Tinnitus, vertigo, discharge, dysphagia, hoarseness"),
            ("T", "Timing", "Constant/intermittent, diurnal variation"),
            ("E", "Exacerbating", "Cold, allergies, posture, movement"),
            ("S", "Severity", "Effect on daily activities, sleep, work"),
        ]
    ))
    e.append(sp(3))

    e.append(tip_box("In head and neck cancer history always ask: tobacco + alcohol + HPV exposure "
                     "(sexual history). These are the 'Big 3' risk factors."))
    e.append(PageBreak())
    return e

# ── SECTION 2 - EAR EXAM ─────────────────────────────────────────────────────
def build_ear():
    e = []
    e.append(section_header("2. EAR EXAMINATION", color=MID_TEAL))
    e.append(sp(3))

    e.append(Paragraph("Step-by-Step Sequence", H2))
    steps = [
        ("1", "Inspect auricle", "Deformity, skin lesions, pre-auricular pits/sinuses, keloids, Battle's sign"),
        ("2", "Palpate", "Tragal tenderness (otitis externa) · Mastoid tenderness (mastoiditis)"),
        ("3", "Straighten canal", "Adults: pull pinna UP + BACK · Children: pull DOWN + BACK"),
        ("4", "Otoscopy", "Canal then tympanic membrane (TM) — see landmarks below"),
        ("5", "Pneumatic otoscopy", "Seal canal, apply gentle pressure; normal TM moves freely"),
        ("6", "Tuning fork tests", "Weber (512 Hz), Rinne (512 Hz)"),
        ("7", "Hearing screen", "Whisper test at 60 cm if audiometer unavailable"),
    ]
    step_data = [[
        Paragraph(f"<b>{s}</b>", S("StN", fontName="Helvetica-Bold", fontSize=11,
            textColor=WHITE, alignment=TA_CENTER, leading=14)),
        Paragraph(f"<b>{t}</b>", BOLD_BODY),
        Paragraph(d, BODY),
    ] for s, t, d in steps]
    step_tbl = Table(step_data, colWidths=[10*mm, 45*mm, PAGE_W-2*MARGIN-55*mm])
    step_tbl.setStyle(TableStyle([
        ("BACKGROUND",  (0,0), (0,-1), MID_TEAL),
        ("ROWBACKGROUNDS", (1,0), (-1,-1), [WHITE, LIGHT_TEAL]),
        ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CCCCCC")),
        ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
        ("TOPPADDING",  (0,0), (-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("LEFTPADDING", (0,0), (-1,-1), 6),
        ("RIGHTPADDING",(0,0), (-1,-1), 6),
        ("ALIGN", (0,0), (0,-1), "CENTER"),
    ]))
    e.append(step_tbl)
    e.append(sp(4))

    e.append(Paragraph("Tympanic Membrane Landmarks (MUST KNOW)", H2))
    e.append(mnemonic_box("PUMP-AC  (TM landmarks, superior to inferior)",
        [
            ("P", "Pars flaccida", "Superior, thin portion — site of acquired cholesteatoma"),
            ("U", "Umbo", "Tip of malleus handle — the central dimple"),
            ("M", "Malleus handle", "Runs from lateral process to umbo"),
            ("P", "Pars tensa", "Main bulk of TM; subdivided into 4 quadrants"),
            ("A", "Annulus", "Fibrocartilaginous ring attaching TM to bony canal"),
            ("C", "Cone of light", "Anterior-inferior bright reflection (5 o'clock right, 7 o'clock left)"),
        ],
        bg=LIGHT_TEAL, title_color=MID_TEAL
    ))
    e.append(sp(3))

    e.append(Paragraph("TM Findings and Their Significance", H2))
    e.append(simple_table(
        ["Finding", "Appearance", "Diagnosis"],
        [
            ["Myringosclerosis", "Chalky white patches", "Previous trauma/infection"],
            ["Myringitis", "Thickened, red TM ± bullae", "Acute otitis media / viral"],
            ["Atelectasis", "Thin TM draped over ossicles", "Adhesive otitis media"],
            ["Acquired cholesteatoma", "White mass + granulation tissue + perforation", "Posterior-superior marginal perforation"],
            ["Congenital cholesteatoma", "White pearl behind intact TM, ant-sup quadrant", "Keratin cyst (congenital)"],
            ["Serous effusion", "Amber fluid, air-fluid levels", "Otitis media with effusion"],
            ["Mucoid effusion", "Dull grey-white, retracted TM", "Glue ear (OME)"],
            ["Glomus tumor", "Vascular mass; Brown sign on pneumatic otoscopy", "Glomus tympanicum"],
            ["Perforations", "Central / marginal / attic - note size as % of TM", "Chronic suppurative otitis media"],
        ],
        col_widths=[42*mm, 55*mm, PAGE_W-2*MARGIN-97*mm]
    ))
    e.append(sp(4))

    e.append(Paragraph("Tuning Fork Tests", H2))
    e.append(simple_table(
        ["Test", "Method", "Normal", "Conductive HL", "Sensorineural HL"],
        [
            ["Weber\n(512 Hz)", "Place on forehead / midline teeth", "Midline (negative)", "Lateralises to AFFECTED ear", "Lateralises to BETTER ear"],
            ["Rinne\n(512 Hz)", "AC (fork near canal) vs BC (mastoid)", "AC > BC (Positive)", "BC > AC (Negative)", "AC > BC but both reduced"],
            ["Absolute Bone\nConduction", "Compare patient BC to examiner BC", "Equal to examiner", "Equal to examiner (normal cochlea)", "Reduced (cochlear loss)"],
        ],
        col_widths=[22*mm, 48*mm, 30*mm, 40*mm, PAGE_W-2*MARGIN-140*mm]
    ))
    e.append(sp(3))

    e.append(mnemonic_box("WEBER RULE:  'Worse Ear Wins for Conductive'",
        [
            ("W", "Weber to worse ear", "= Conductive hearing loss on that side"),
            ("B", "Better ear gets Weber", "= Sensorineural hearing loss on opposite side"),
            ("R", "Rinne Negative", "= BC > AC = Conductive loss"),
            ("P", "Positive Rinne", "= AC > BC = Normal or sensorineural"),
        ]
    ))
    e.append(sp(3))
    e.append(warn_box("False Negative Rinne: Occurs in severe unilateral sensorineural loss "
                      "- sound from mastoid crosses skull to better cochlea. "
                      "Always correlate Rinne with Weber!"))
    e.append(PageBreak())
    return e

# ── SECTION 3 - NOSE ─────────────────────────────────────────────────────────
def build_nose():
    e = []
    e.append(section_header("3. NOSE EXAMINATION", color=ORANGE))
    e.append(sp(3))

    e.append(Paragraph("Sequence", H2))
    sequence = [
        ("External Inspection", "Deviation, deformity, skin lesions, nasal tip support, columellar show, nasal valve collapse on inspiration"),
        ("Anterior Rhinoscopy", "Speculum opened VERTICALLY; inspect: septum, inferior turbinate, middle turbinate, mucosa colour, discharge, polyps"),
        ("Nasal Endoscopy", "Systematic 3-pass protocol (see below)"),
        ("Olfactory Test", "Ask patient to identify common smells unilaterally (CN I)"),
        ("Nasal Patency", "Ask patient to breathe through each nostril; or use cold spatula/mirror for misting"),
    ]
    for title, body in sequence:
        e.append(Paragraph(title, H3))
        e.append(Paragraph(body, BODY))
        e.append(sp(2))

    e.append(tip_box("Open the Thudichum speculum VERTICALLY (blades go up/down), NOT horizontally. "
                     "Horizontal opening crushes the septum and causes pain."))
    e.append(sp(3))

    e.append(Paragraph("Nasal Endoscopy: The 3-Pass Protocol", H2))
    e.append(simple_table(
        ["Pass", "Structures Visualised", "What to Look For"],
        [
            ["1st Pass", "Floor, inferior meatus, inferior turbinate, nasopharynx, Eustachian tube orifice, adenoids", "Inferior turbinate hypertrophy, post-nasal drip, adenoid enlargement, nasopharyngeal mass"],
            ["2nd Pass", "Middle meatus, middle turbinate, uncinate process, ostiomeatal complex", "Polyps, purulent discharge, fungal concretions, anatomic variants (concha bullosa)"],
            ["3rd Pass", "Superior turbinate, sphenoethmoidal recess, sphenoid ostium", "CSF leak, posterior polyps, sphenoid sinusitis"],
        ],
        col_widths=[14*mm, 75*mm, PAGE_W-2*MARGIN-89*mm]
    ))
    e.append(sp(3))

    e.append(Paragraph("Nasal Mucosa Colour Guide", H2))
    col_data = [
        [Paragraph("<b>Colour</b>", BOLD_BODY), Paragraph("<b>Significance</b>", BOLD_BODY)],
        [Paragraph("Pale / bluish-grey", BODY), Paragraph("Allergic rhinitis", BODY)],
        [Paragraph("Erythematous / red", BODY), Paragraph("Acute infection, vasomotor rhinitis", BODY)],
        [Paragraph("Pale + oedematous polyps", BODY), Paragraph("Chronic rhinosinusitis with polyps (CRSwNP)", BODY)],
        [Paragraph("Dry + crusting", BODY), Paragraph("Atrophic rhinitis (ozaena), Sjogren's, cocaine use", BODY)],
        [Paragraph("Perforation of septum", BODY), Paragraph("Cocaine, GPA (Wegener's), trauma, iatrogenic", BODY)],
    ]
    col_tbl = Table(col_data, colWidths=[(PAGE_W-2*MARGIN)/2]*2)
    col_tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,0), ORANGE),
        ("TEXTCOLOR",  (0,0), (-1,0), WHITE),
        ("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_ORANGE]),
        ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CCCCCC")),
        ("TOPPADDING",  (0,0), (-1,-1), 4),
        ("BOTTOMPADDING",(0,0),(-1,-1), 4),
        ("LEFTPADDING", (0,0), (-1,-1), 6),
        ("RIGHTPADDING",(0,0), (-1,-1), 6),
    ]))
    e.append(col_tbl)
    e.append(sp(3))

    e.append(mnemonic_box("SEPTUM (causes of septal perforation)",
        [
            ("S", "Surgery / Septal haematoma", "Post-operative, untreated haematoma"),
            ("E", "External trauma", "Nasal fractures"),
            ("P", "Perforation by cocaine", "Nasal cocaine use — classic cause"),
            ("T", "Tuberculosis / Syphilis", "Granulomatous infection"),
            ("U", "Ulceration (GPA/Wegener's)", "Granulomatosis with polyangiitis"),
            ("M", "Malignancy", "Lymphoma, squamous cell carcinoma"),
        ],
        bg=LIGHT_ORANGE, title_color=ORANGE
    ))
    e.append(PageBreak())
    return e

# ── SECTION 4 - THROAT ───────────────────────────────────────────────────────
def build_throat():
    e = []
    e.append(section_header("4. THROAT / ORAL CAVITY EXAMINATION", color=PURPLE))
    e.append(sp(3))

    e.append(Paragraph("Systematic Oral Cavity Inspection", H2))
    oral = [
        ("Lips", "Ulcers, crusting, angular cheilitis, carcinoma"),
        ("Buccal mucosa", "Leukoplakia, erythroplakia, lichen planus, Stensen's duct orifice"),
        ("Upper and lower alveolus", "Gingival swellings, extraction sockets, loose teeth"),
        ("Hard palate", "Torus palatinus, perforations, mucosal lesions"),
        ("Floor of mouth", "Ranula, Wharton's duct, submandibular calculi — PALPATE bimanually"),
        ("Tongue", "Dorsum (colour, texture), lateral borders (carcinoma), ventral surface, mobility, fasciculations"),
        ("Soft palate", "Uvula deviation, palatal petechiae, submucous cleft"),
        ("Tonsils", "Size (grade 1-4), crypts, exudate, ulceration, asymmetry"),
        ("Posterior pharyngeal wall", "Mucosal irregularity, post-nasal drip, lymphoid hyperplasia"),
    ]
    e.append(simple_table(
        ["Structure", "Key Findings"],
        oral,
        col_widths=[45*mm, PAGE_W-2*MARGIN-45*mm]
    ))
    e.append(sp(3))

    e.append(mnemonic_box("LOTS  (causes of unilateral tonsillar enlargement)",
        [
            ("L", "Lymphoma", "Most feared - irregular, hard, non-tender"),
            ("O", "Other malignancy", "Squamous cell carcinoma (often ulcerated)"),
            ("T", "Tonsillar abscess (peritonsillar)", "Trismus, uvula deviation, muffled voice"),
            ("S", "Simple hypertrophy", "Smooth, uninfected enlargement"),
        ],
        bg=LIGHT_PURPLE, title_color=PURPLE
    ))
    e.append(sp(3))

    e.append(warn_box("Unilateral tonsillar enlargement = malignancy until proven otherwise. "
                      "Refer for biopsy if no obvious infective cause."))
    e.append(sp(3))

    e.append(Paragraph("Tonsil Grading (Brodsky Scale)", H2))
    e.append(simple_table(
        ["Grade", "Description"],
        [
            ["1+", "Tonsils within tonsillar pillars"],
            ["2+", "Tonsils extend to pillars"],
            ["3+", "Tonsils extend beyond pillars but do not meet midline"],
            ["4+", "Tonsils meet midline ('kissing tonsils')"],
        ],
        col_widths=[20*mm, PAGE_W-2*MARGIN-20*mm]
    ))
    e.append(sp(3))

    e.append(Paragraph("Laryngeal Examination", H2))
    larynx_data = [
        ("Indirect laryngoscopy", "Patient leans forward, tongue protruded on gauze; warm mirror to prevent fogging; patient says 'eeee'"),
        ("Flexible nasopharyngolaryngoscopy", "Gold standard; passed transnasally after decongestion + topical anaesthesia"),
        ("What to assess", "Epiglottis, aryepiglottic folds, arytenoids, false cords, true cords, subglottis, cord mobility (abduct on inspiration, adduct on phonation)"),
        ("Voice changes", "Hoarseness: mucosal lesion or cord palsy; Muffled: supraglottic; Stridor: subglottic / tracheal"),
    ]
    for title, body in larynx_data:
        e.append(Paragraph(title, H3))
        e.append(Paragraph(body, BODY))
        e.append(sp(2))

    e.append(mnemonic_box("HASTE  (causes of hoarseness)",
        [
            ("H", "Hypothyroidism", "Myxoedema voice - low, husky"),
            ("A", "Allergy / Laryngitis", "Acute or chronic inflammatory"),
            ("S", "Squamous cell carcinoma", "Persistent hoarseness > 3 weeks = urgent referral"),
            ("T", "Trauma / Thyroid surgery", "Recurrent laryngeal nerve palsy"),
            ("E", "Ectopic lesion (mediastinal)", "Aortic aneurysm, lung cancer compressing RLN"),
        ]
    ))
    e.append(PageBreak())
    return e

# ── SECTION 5 - NECK ─────────────────────────────────────────────────────────
def build_neck():
    e = []
    e.append(section_header("5. NECK EXAMINATION", color=GREEN))
    e.append(sp(3))

    e.append(Paragraph("Inspection", H2))
    for item in [
        "Scars (previous surgery, thyroidectomy)",
        "Asymmetry or obvious masses",
        "Tracheal deviation (mediastinal/thyroid mass)",
        "Thyroid enlargement (goitre) - ask patient to swallow",
        "Skin changes over mass (redness, tethering, punctum)",
    ]:
        e.append(bullet(item))
    e.append(sp(3))

    e.append(Paragraph("Neck Levels (Robbins Classification) - MUST KNOW", H2))
    e.append(simple_table(
        ["Level", "Region", "Boundaries", "Drainage From"],
        [
            ["Ia", "Submental", "Between anterior digastric bellies", "Floor of mouth, anterior tongue, lower lip"],
            ["Ib", "Submandibular", "Submandibular triangle", "Oral cavity, submandibular gland"],
            ["II", "Upper jugular", "Skull base to hyoid (angle of mandible)", "Nasopharynx, oropharynx, parotid, oral cavity"],
            ["III", "Mid jugular", "Hyoid to cricoid", "Oral cavity, oropharynx, hypopharynx"],
            ["IV", "Lower jugular", "Cricoid to clavicle", "Hypopharynx, larynx, thyroid, oesophagus"],
            ["V", "Posterior triangle", "Between SCM, trapezius, clavicle", "Scalp, nasopharynx, thyroid"],
            ["VI", "Central compartment", "Between carotid sheaths, hyoid to sternal notch", "Thyroid, larynx, trachea, oesophagus"],
        ],
        col_widths=[12*mm, 35*mm, 55*mm, PAGE_W-2*MARGIN-102*mm]
    ))
    e.append(sp(3))

    e.append(mnemonic_box("SIX  (neck levels in order)",
        [
            ("I", "Sub-chin + Sub-mand", "Level I = innermost (submental + submandibular)"),
            ("II", "Upper Jugular", "II = above hyoid"),
            ("III", "Mid Jugular", "III = hyoid to cricoid"),
            ("IV", "Lower Jugular", "IV = cricoid to clavicle"),
            ("V", "Posterior Triangle", "V = behind SCM"),
            ("VI", "Central", "VI = between carotid sheaths (thyroid territory)"),
        ],
        bg=LIGHT_GREEN, title_color=GREEN
    ))
    e.append(sp(3))

    e.append(Paragraph("Palpation of a Neck Mass - What to Note", H2))
    mass_chars = [
        ("Site / Level", "Which neck level? Midline vs. lateral?"),
        ("Size", "In cm - two dimensions"),
        ("Shape", "Rounded, irregular, lobulated"),
        ("Consistency", "Soft, firm, hard, rubbery (lymphoma = rubbery)"),
        ("Mobility", "Freely mobile, tethered to skin, fixed to deep structures"),
        ("Tenderness", "Tender = usually inflammatory; non-tender = ?malignant"),
        ("Pulsatility", "Carotid body tumour - transmitted pulsation, compressible"),
        ("Transilluminability", "Cystic hygroma / branchial cyst - transilluminate"),
        ("Tracheal deviation", "Push or pull effect from large mass"),
        ("Moves on swallowing", "Thyroid / thyroglossal cyst = moves up with swallow"),
        ("Moves on tongue protrusion", "Thyroglossal cyst ONLY = moves on tongue protrusion"),
    ]
    e.append(simple_table(
        ["Characteristic", "Significance"],
        mass_chars,
        col_widths=[50*mm, PAGE_W-2*MARGIN-50*mm]
    ))
    e.append(sp(3))

    e.append(tip_box("Thyroglossal cyst: moves on BOTH swallowing AND tongue protrusion "
                     "(attached to foramen caecum via thyroglossal tract). "
                     "This distinguishes it from all other midline neck masses."))
    e.append(PageBreak())
    return e

# ── SECTION 6 - CRANIAL NERVES ───────────────────────────────────────────────
def build_cranial_nerves():
    e = []
    e.append(section_header("6. CRANIAL NERVE QUICK-CHECK (ENT-Relevant)"))
    e.append(sp(3))

    e.append(simple_table(
        ["CN", "Name", "ENT Test", "Finding if Affected"],
        [
            ["I", "Olfactory", "Identify common smells unilaterally (coffee, vanilla)", "Anosmia: head injury, cribriform plate fracture, ethmoid tumour"],
            ["V", "Trigeminal", "Facial sensation V1/V2/V3; corneal reflex (V1 afferent)", "Reduced sensation: skull base tumour, trigeminal neuralgia"],
            ["VII", "Facial", "Raise eyebrows, close eyes tightly, show teeth, puff cheeks", "UMN: lower face only. LMN (Bell's palsy): ALL muscles ipsilateral"],
            ["VIII", "Vestibulo-cochlear", "Tuning forks + audiometry + Romberg + Unterberger", "Hearing loss (cochlear or retrocochlear); vestibular imbalance"],
            ["IX", "Glossopharyngeal", "Gag reflex (sensory limb), taste posterior 1/3 tongue", "Loss = unilateral palatal palsy, gag loss"],
            ["X", "Vagus", "Palate elevation on saying 'aah' - uvula deviates away from lesion", "Palate drags to normal side; vocal cord palsy (hoarseness)"],
            ["XI", "Accessory", "Shrug shoulders (trapezius), turn head against resistance (SCM)", "Shoulder drop; weakness turning head contralateral"],
            ["XII", "Hypoglossal", "Protrude tongue - deviates toward weak side (LMN lesion)", "Tongue deviation + wasting = LMN lesion (e.g., skull base metastasis)"],
        ],
        col_widths=[10*mm, 30*mm, 65*mm, PAGE_W-2*MARGIN-105*mm]
    ))
    e.append(sp(3))

    e.append(mnemonic_box("BELL'S PALSY vs STROKE  (LMN vs UMN VII palsy)",
        [
            ("B", "Bell's = Both upper AND lower face", "LMN: forehead involved (cannot raise eyebrow)"),
            ("S", "Stroke = Spares forehead", "UMN: forehead SPARED (bilateral cortical supply to upper face)"),
            ("E", "Eye closure affected in Bell's", "Risk of exposure keratopathy - tape eye shut at night"),
            ("L", "LMN Bell's = taste loss (chorda tympani)", "Abnormal taste, hyperacusis (stapedius branch)"),
        ],
        bg=LIGHT_RED, title_color=RED
    ))
    e.append(sp(3))

    e.append(Paragraph("Vestibular Assessment", H2))
    e.append(simple_table(
        ["Test", "Method", "Positive Finding", "Interpretation"],
        [
            ["Romberg", "Stand feet together, arms crossed, close eyes", "Increased sway / falls", "Peripheral vestibular or proprioceptive lesion"],
            ["Unterberger\n(Fukuda)", "March 50 steps on the spot, eyes closed", "Rotation > 45° toward one side", "Rotates toward side of vestibular lesion"],
            ["Dix-Hallpike", "Rapidly move from sitting to head-hanging position, 30-45° to one side", "Upbeat-torsional nystagmus (5-20s latency, fatigable)", "BPPV of posterior semicircular canal"],
            ["Head Impulse\n(HIT/HINTS)", "Rapid head thrust to one side; patient fixates on nose", "Catch-up saccade after thrust", "Peripheral vestibular lesion (ipsilateral)"],
            ["Hallpike-Dix\n(Negative)", "No nystagmus after Dix-Hallpike", "No response but severe vertigo", "Central cause - urgent MRI"],
        ],
        col_widths=[25*mm, 50*mm, 45*mm, PAGE_W-2*MARGIN-120*mm]
    ))
    e.append(sp(3))
    e.append(tip_box("HINTS exam in acute vestibular syndrome: "
                     "Head Impulse (abnormal) + Nystagmus (unidirectional horizontal) + "
                     "Test of Skew (no skew deviation) = peripheral (benign). "
                     "A NORMAL head impulse test in acute vertigo suggests STROKE."))
    e.append(PageBreak())
    return e

# ── SECTION 7 - MEGA MNEMONICS ───────────────────────────────────────────────
def build_mnemonics():
    e = []
    e.append(section_header("7. HIGH-YIELD MNEMONICS COLLECTION"))
    e.append(sp(3))

    # Row 1
    e.append(Paragraph("Ear Mnemonics", H2))

    e.append(mnemonic_box("LEMON  (risk factors for difficult airway - pre-op ENT)",
        [
            ("L", "Look externally", "Beard, obesity, trauma, small mouth"),
            ("E", "Evaluate 3-3-2 rule", "Mouth opening 3F, thyromental 3F, thyroid-to-floor 2F"),
            ("M", "Mallampati class", "Class I-IV (higher = harder)"),
            ("O", "Obstruction", "Epiglottitis, peritonsillar abscess, tracheal deviation"),
            ("N", "Neck mobility", "Reduced in ankylosing spondylitis, cervical spine injury"),
        ]
    ))
    e.append(sp(2))

    e.append(mnemonic_box("SCOLD  (features of chronic suppurative otitis media - safe type)",
        [
            ("S", "Safe / mucosal type", "Central perforation, not dangerous"),
            ("C", "Central perforation", "Does NOT involve the annulus"),
            ("O", "Otorrhoea", "Mucopurulent, non-offensive discharge"),
            ("L", "Low risk of complications", "Intracranial complications rare"),
            ("D", "Dry ear possible", "Heals between episodes"),
        ],
        bg=LIGHT_TEAL, title_color=MID_TEAL
    ))
    e.append(sp(3))

    e.append(Paragraph("Nose / Sinus Mnemonics", H2))
    e.append(mnemonic_box("FADES  (contents of the ostiomeatal complex)",
        [
            ("F", "Frontal sinus ostium", "Drains into frontal recess / hiatus semilunaris"),
            ("A", "Anterior ethmoid sinuses", "Multiple cells draining into middle meatus"),
            ("D", "Drainage pathway", "All converge in the ostiomeatal complex (OMC)"),
            ("E", "Ethmoid infundibulum", "Groove in lateral nasal wall accepting maxillary ostium"),
            ("S", "Sphenoid does NOT", "Sphenoid drains into sphenoethmoidal recess (NOT OMC)"),
        ],
        bg=LIGHT_ORANGE, title_color=ORANGE
    ))
    e.append(sp(2))

    e.append(mnemonic_box("SAMTER  (Samter's triad = aspirin-exacerbated respiratory disease)",
        [
            ("S", "Sinusitis (CRS with polyps)", "Chronic severe nasal polyps"),
            ("A", "Aspirin sensitivity", "NSAID-induced bronchospasm / rhinitis"),
            ("M/T", "asThMa", "Often severe / steroid-dependent"),
            ("E", "Eosinophilia", "High tissue and blood eosinophils"),
            ("R", "Recurrence after surgery", "Polyps regrow without NSAID avoidance"),
        ],
        bg=LIGHT_ORANGE, title_color=ORANGE
    ))
    e.append(sp(3))

    e.append(Paragraph("Throat / Neck Mnemonics", H2))
    e.append(mnemonic_box("MOVED  (causes of unilateral recurrent laryngeal nerve palsy)",
        [
            ("M", "Malignancy", "Lung (most common), thyroid, oesophageal, lymphoma"),
            ("O", "Operation", "Thyroidectomy, parathyroidectomy, cardiac surgery"),
            ("V", "Vascular", "Aortic aneurysm (left side), subclavian aneurysm (right)"),
            ("E", "Enlarged lymph nodes", "Lymphoma, metastatic nodes"),
            ("D", "Demyelination / idiopathic", "Multiple sclerosis, viral neuritis"),
        ],
        bg=LIGHT_PURPLE, title_color=PURPLE
    ))
    e.append(sp(2))

    e.append(mnemonic_box("CATCH  (features of neck mass suggesting malignancy)",
        [
            ("C", "Consistency hard", "Rock hard = metastatic carcinoma"),
            ("A", "Age > 40", "New persistent neck mass in adult > 40 = cancer until proven otherwise"),
            ("T", "Tobacco + alcohol history", "Major risk factors for head and neck SCC"),
            ("C", "Can't move it", "Fixed to skin or deep structures = invasion"),
            ("H", "HPV history + tonsil asymmetry", "Oropharyngeal SCC (HPV16 p16+)"),
        ]
    ))
    e.append(PageBreak())
    return e

# ── SECTION 8 - EXAM TIPS ────────────────────────────────────────────────────
def build_exam_tips():
    e = []
    e.append(section_header("8. HIGH-YIELD EXAM TIPS"))
    e.append(sp(3))

    sections = [
        ("Ear Tips", MID_TEAL, [
            "Always describe TM in 4 quadrants: antero-superior, antero-inferior, postero-superior, postero-inferior",
            "Cholesteatoma is ALWAYS in the postero-superior quadrant or pars flaccida (attic) = 'unsafe' CSOM",
            "Kernicterus and aminoglycosides (streptomycin, gentamicin) target hair cells of basal turn (high freq loss first)",
            "Rinne false negative: severe sensorineural loss. Weber and audiogram will clarify",
            "In BPPV: Dix-Hallpike positive side = the AFFECTED ear (nystagmus beats toward ground = geotropic = posterior canal BPPV)",
            "Glomus jugulare: pulsatile tinnitus + conductive hearing loss + jugular foramen syndrome (CN IX, X, XI)",
            "Presbycusis = bilateral symmetrical high-frequency sensorineural hearing loss in the elderly",
        ]),
        ("Nose Tips", ORANGE, [
            "Anterior epistaxis: Kiesselbach's (Little's) plexus - 4 arteries meet here (sphenopalatine, greater palatine, anterior ethmoidal, superior labial)",
            "Posterior epistaxis: sphenopalatine artery (branch of maxillary artery) - harder to control, may need surgery",
            "CSF rhinorrhoea: halo test / beta-2 transferrin. High-res CT head + MRI for localisation",
            "Nasal polyps in a child = cystic fibrosis until proven otherwise (sweat chloride test)",
            "Inverted papilloma: unilateral, bleeds on touch, men > 50, risk of malignant transformation (~10%)",
            "Wegener's (GPA): saddle nose deformity + nasal crusting + septal perforation + renal disease (c-ANCA positive)",
        ]),
        ("Throat Tips", PURPLE, [
            "Ludwig's angina: bilateral submandibular space infection, airway emergency (floor of mouth elevation)",
            "Quinsy (peritonsillar abscess): uvula deviates AWAY from the affected side; trismus present",
            "Voice quality clues: hoarse = glottic pathology; muffled 'hot potato' voice = supraglottic; stridor = subglottic/tracheal",
            "3-week rule: hoarseness > 3 weeks in a smoker/drinker = urgent laryngoscopy",
            "In dysphagia: high dysphagia (level of throat/neck) suggests pharyngeal or cricopharyngeal pathology; progressive = ?malignancy",
            "Plummer-Vinson syndrome: post-cricoid dysphagia + iron-deficiency anaemia + post-cricoid web (predisposing to carcinoma)",
        ]),
        ("Neck Tips", GREEN, [
            "Rule of 80s for parotid masses: 80% parotid tumours are benign; 80% benign = pleomorphic adenoma; 80% pleomorphic = superficial lobe",
            "Pleomorphic adenoma: slow-growing, painless, firm, lobulated; risk of malignant transformation if left untreated",
            "Warthin's tumour (papillary cystadenoma lymphomatosum): older males, smokers, bilateral parotid, hot on Tc-99m scan",
            "Sialadenitis vs sialolithiasis: pain + swelling on eating (calculus) vs constant swelling + pus from duct (infection)",
            "Branchial cyst: anterior to upper 1/3 SCM, young adult, soft, transilluminable, contains cholesterol crystals",
            "Cystic hygroma: multilocular, posterior triangle, infant, trans-illuminates brilliantly",
        ]),
    ]

    for title, color, tips in sections:
        e.append(Paragraph(title, H2))
        for tip in tips:
            e.append(bullet(tip))
        e.append(sp(3))

    e.append(PageBreak())
    return e

# ── SECTION 9 - QUICK REFERENCE CARD ────────────────────────────────────────
def build_quick_ref():
    e = []
    e.append(section_header("9. QUICK REFERENCE SUMMARY CARD"))
    e.append(sp(3))

    e.append(Paragraph("ENT Examination Sequence at a Glance", H2))
    order_data = [
        ["Step", "Action", "Key Points"],
        ["1", "General Appearance", "Vitals, gait, alertness, tobacco signs"],
        ["2", "Head & Facies", "Shape, symmetry, facial nerve function, skin lesions"],
        ["3", "External Ear", "Auricle, tragus, mastoid tenderness"],
        ["4", "Otoscopy", "Canal → TM landmarks → TM pathology"],
        ["5", "Pneumatic Otoscopy", "TM mobility; effusion or perforation = immobile"],
        ["6", "Tuning Forks", "Weber 512 Hz → Rinne 512 Hz → ABC if needed"],
        ["7", "External Nose", "Deviation, skin, nasal valve"],
        ["8", "Anterior Rhinoscopy", "Septum, inferior turbinate, mucosa, discharge"],
        ["9", "Nasal Endoscopy", "3-pass protocol; OMC, polyps, nasopharynx"],
        ["10", "Oral Cavity", "Lips → mucosa → floor → tongue → palate → tonsils → pharynx"],
        ["11", "Larynx", "Indirect mirror or flexible scope; cord mobility"],
        ["12", "Neck Inspection", "Scars, trachea, thyroid; ask to swallow"],
        ["13", "Neck Palpation", "Levels I-VI; thyroid; parotid; submandibular"],
        ["14", "Cranial Nerves", "I, V, VII, VIII, IX, X, XI, XII"],
        ["15", "Vestibular Tests", "Romberg, Unterberger, Dix-Hallpike, HIT as indicated"],
    ]
    step_tbl2 = Table(order_data, colWidths=[12*mm, 50*mm, PAGE_W-2*MARGIN-62*mm])
    step_tbl2.setStyle(TableStyle([
        ("BACKGROUND",  (0,0), (-1,0), DARK_TEAL),
        ("TEXTCOLOR",   (0,0), (-1,0), WHITE),
        ("FONTNAME",    (0,0), (-1,0), "Helvetica-Bold"),
        ("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_TEAL]),
        ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CCCCCC")),
        ("FONTSIZE", (0,0), (-1,-1), 8.5),
        ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
        ("TOPPADDING",  (0,0), (-1,-1), 4),
        ("BOTTOMPADDING",(0,0),(-1,-1), 4),
        ("LEFTPADDING", (0,0), (-1,-1), 6),
        ("RIGHTPADDING",(0,0), (-1,-1), 6),
        ("ALIGN", (0,0), (0,-1), "CENTER"),
        ("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
        ("TEXTCOLOR",   (0,1), (0,-1), DARK_TEAL),
    ]))
    e.append(step_tbl2)
    e.append(sp(4))

    # Final tip strip
    final_data = [[
        Paragraph(
            "<b>Remember:</b> The ENT examination flows HEAD to TOE within the head - "
            "always Ear first, then Nose, then Throat, then Neck, then Cranial Nerves. "
            "This systematic approach prevents missing pathology and impresses examiners.",
            S("FT", fontName="Helvetica", fontSize=9.5,
                textColor=DARK_TEAL, alignment=TA_CENTER, leading=14))
    ]]
    final_tbl = Table(final_data, colWidths=[PAGE_W - 2*MARGIN])
    final_tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), LIGHT_TEAL),
        ("LINEABOVE",  (0,0), (-1, 0), 3, DARK_TEAL),
        ("LINEBELOW",  (0,-1), (-1,-1), 3, DARK_TEAL),
        ("TOPPADDING",  (0,0), (-1,-1), 10),
        ("BOTTOMPADDING",(0,0),(-1,-1), 10),
        ("LEFTPADDING", (0,0), (-1,-1), 14),
        ("RIGHTPADDING",(0,0), (-1,-1), 14),
    ]))
    e.append(final_tbl)
    e.append(sp(3))
    e.append(Paragraph("Source: Cummings Otolaryngology: Head and Neck Surgery (Cummings et al.)",
        CENTER_SM))
    return e

# ── PAGE TEMPLATE ─────────────────────────────────────────────────────────────
def on_page(canvas, doc):
    canvas.saveState()
    w, h = A4
    # Footer
    canvas.setFillColor(colors.HexColor("#EEEEEE"))
    canvas.rect(0, 0, w, 14*mm, fill=1, stroke=0)
    canvas.setFillColor(DARK_TEAL)
    canvas.setFont("Helvetica-Bold", 8)
    canvas.drawCentredString(w/2, 5*mm, "ENT Examination Study Guide  •  Based on Cummings Otolaryngology")
    canvas.setFillColor(colors.HexColor("#888888"))
    canvas.setFont("Helvetica", 7)
    canvas.drawRightString(w - MARGIN, 5*mm, f"Page {doc.page}")
    # Top rule
    canvas.setStrokeColor(DARK_TEAL)
    canvas.setLineWidth(1.5)
    canvas.line(MARGIN, h - 12*mm, w - MARGIN, h - 12*mm)
    canvas.restoreState()

# ── BUILD ─────────────────────────────────────────────────────────────────────
def build_pdf(out_path):
    doc = SimpleDocTemplate(
        out_path,
        pagesize=A4,
        leftMargin=MARGIN,
        rightMargin=MARGIN,
        topMargin=16*mm,
        bottomMargin=18*mm,
        title="ENT Examination Study Guide",
        author="Orris Medical AI",
        subject="Otolaryngology Exam Preparation",
    )

    story = []
    story += build_cover()
    story += build_history()
    story += build_ear()
    story += build_nose()
    story += build_throat()
    story += build_neck()
    story += build_cranial_nerves()
    story += build_mnemonics()
    story += build_exam_tips()
    story += build_quick_ref()

    doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
    print(f"PDF generated: {out_path}")

if __name__ == "__main__":
    build_pdf("/home/daytona/workspace/ent-study-guide/ENT_Examination_Study_Guide.pdf")
Running Command

cd /home/daytona/workspace/ent-study-guide && python generate_ent_guide.py 2>&1

Running Command

ls -lh /home/daytona/workspace/ent-study-guide/ENT_Examination_Study_Guide.pdf

Your ENT Study Guide PDF is ready!

ENT Examination Study Guide

PDF Document · PDF


Here's what's packed inside the guide:
SectionContents
Cover PageColor-coded overview with Ear / Nose / Throat summary panels
1. History TakingSOCRATES+ framework, risk factor domains, history mnemonic
2. Ear ExaminationStep-by-step otoscopy, TM landmarks, TM findings table, tuning fork test table, WEBER RULE mnemonic, false-negative Rinne warning
3. Nose Examination3-pass endoscopy protocol, rhinoscopy tips, mucosa colour guide, SEPTUM mnemonic (causes of septal perforation)
4. Throat ExaminationOral cavity structures checklist, Brodsky tonsil grading scale, LOTS mnemonic (tonsillar enlargement), laryngeal assessment guide, HASTE mnemonic (hoarseness)
5. Neck ExaminationNeck mass characteristics, Robbins Levels I-VI table, thyroglossal vs branchial cyst tips, CATCH mnemonic (malignancy features)
6. Cranial Nerve Quick-CheckCN I, V, VII, VIII, IX, X, XI, XII in one table; BELL'S PALSY vs STROKE mnemonic; vestibular tests (Dix-Hallpike, HIT/HINTS)
7. Mnemonics CollectionLEMON (airway), SCOLD (safe CSOM), FADES (OMC), SAMTER, MOVED (RLN palsy)
8. High-Yield Exam Tips~28 clinical pearls across Ear, Nose, Throat, Neck
9. Quick Reference Card15-step examination sequence at a glance
This is a shared conversation. Sign in to Orris to start your own chat.