Hi ima final year mbbs student
I have my ENT endoscopy and assessment tomorrow, uh, and my case is, uh, CSOM mucosal type and chronic tonsillitis. So can you give everything about those two topic, like important, uh, clinical things, what and all I have to remember in a flashcard thing? Like covering everything, all important CRIPS topics regarding to those two.
| Full name | Chronic Suppurative Otitis Media |
| Definition | Chronic inflammation of middle ear + mastoid with persistent tympanic membrane perforation + discharge for >6 weeks |
| Types | Mucosal (Tubotympanic / SAFE type) vs Squamous (Atticoantral / UNSAFE type) |
| Why "safe"? | Mucosal type - no cholesteatoma, no bone erosion typically |
| Perforation site | Central (pars tensa) - doesn't touch annulus |
| Factor | Details |
|---|---|
| Primary cause | Eustachian tube dysfunction |
| Sequence | ET dysfunction → persistent middle ear effusion → mucosal edema → granulation tissue → bacterial infection → chronic mucosal change |
| Key pathology | Mucosa develops submucosal glands → secretory mucosa → perpetuates effusion |
| TM changes | Collagen skeleton broken down by enzymes → weakening → retraction pockets → perforation |
| Organisms | Pseudomonas aeruginosa (most common in chronic), Staphylococcus aureus, Proteus, Gram-negatives, anaerobes |
| Symptom | Details |
|---|---|
| Otorrhea | Intermittent, mucoid/mucopurulent, NOT foul-smelling (no cholesteatoma) |
| Hearing loss | Conductive hearing loss (CHL) - most common presenting complaint |
| Otalgia | UNCOMMON - if present, suspect intracranial complication or malignancy |
| Vertigo | UNCOMMON - if present, suspect labyrinthine fistula |
| Tinnitus | May be present |
| Headache | If present → red flag for intracranial involvement |
EXAMINER TIP: Otalgia + headache in CSOM = suspect complications, NOT routine disease
| Finding | Mucosal Type |
|---|---|
| Perforation | Central, pars tensa - safe perforation |
| Position | Does NOT involve the annulus |
| Discharge | Mucoid/mucopurulent (not offensive) |
| Middle ear mucosa | Pale/edematous, may have granulation tissue |
| Cholesteatoma | ABSENT (key differentiator from squamosal type) |
| Ossicles | Usually intact (visible through perforation) |
| Polyps | May be present in EAC |
| Scutal erosion | Absent |
| Feature | Mucosal (Safe) | Squamosal (Unsafe) |
|---|---|---|
| Type | Tubotympanic | Atticoantral |
| Perforation | Central, pars tensa | Marginal / attic (pars flaccida) |
| Discharge | Mucoid, odourless | Scanty, FOUL-smelling |
| Cholesteatoma | ABSENT | PRESENT |
| Bone erosion | No | Yes |
| Complications | Less common | Common (facial palsy, meningitis) |
| Danger | SAFE | UNSAFE |
| Surgery | Tympanoplasty | Mastoidectomy |
| Investigation | Finding |
|---|---|
| Tuning fork (Rinne) | Rinne negative (BC > AC) |
| Tuning fork (Weber) | Lateralizes to affected (worse) ear |
| Pure Tone Audiogram | Conductive hearing loss; CHL >30 dB → suspect ossicular erosion |
| HRCT temporal bone | Gold standard for assessing extent; identifies cholesteatoma, ossicular erosion, complications |
| Ear swab C&S | Guides antibiotic therapy |
| SNHL | May coexist (5-33 dB range seen in studies) |
MNEMONIC for intracranial complications: "My Brain Loves Extra Oxygen" - Meningitis, Brain abscess, Lateral sinus thrombosis, Extradural abscess, Otitic hydrocephalus
| Tonsil ring | Waldeyer's ring = palatine tonsils + pharyngeal tonsil (adenoid) + lingual tonsil + tubal tonsils |
| Palatine tonsils | Between anterior (palatoglossal) and posterior (palatopharyngeal) pillars |
| Blood supply | Tonsillar branch of facial artery (main), also from lingual, ascending pharyngeal, ascending palatine, descending palatine |
| Dangerous pole | Upper pole - close to internal carotid artery |
| Lymph drainage | Jugulodigastric (tonsillar) node - Level II |
| Nerve supply | Glossopharyngeal nerve (IX) + lesser palatine nerve |
| Immunology | B-cell predominant organ; part of MALT (mucosa-associated lymphoid tissue) |
| Feature | Details |
|---|---|
| Definition | Persistent/recurrent tonsillar infection with chronic inflammation |
| Symptoms | Chronic throat discomfort, foreign body sensation, halitosis (bad breath), dysphagia |
| Key sign | Smelly white debris (caseous material) from tonsillar crypts = tonsilloliths |
| Tonsillar size | May be small (fibrosed) or enlarged |
| On examination | Dull, congested tonsils with scarred surface, crypts filled with debris; enlarged jugulodigastric nodes |
| Pus expression | Pus/debris can be expressed from crypts on pressure (Irwin Moore sign) |
| Recurrent attacks | History of recurrent acute tonsillitis episodes |
| Organism | Notes |
|---|---|
| GABHS | Group A Beta-Haemolytic Streptococcus - most important |
| S. aureus | Common |
| H. influenzae | Common |
| Anaerobes | Important in chronic/cryptic tonsillitis and peritonsillar abscess |
| Viruses | EBV, Adenovirus, Herpes simplex (trigger acute episodes) |
| Synergy | Viruses sensitize bacteria in tonsillar crypts |
| Grade | Description |
|---|---|
| 0 | Tonsils within tonsillar fossa |
| 1+ | <25% of oropharyngeal width |
| 2+ | 25-50% of oropharyngeal width |
| 3+ | 50-75% of oropharyngeal width |
| 4+ | >75% of oropharyngeal width ("kissing tonsils") |
| Feature | Details |
|---|---|
| Definition | Pus collection in peritonsillar space (between tonsil capsule and superior constrictor) |
| Site | UPPER pole of tonsil (supratonsillar space) |
| Classic symptoms | Unilateral severe throat pain, odynophagia, trismus (hallmark!), muffled "hot potato" voice, ipsilateral ear pain |
| Exam findings | Bulging soft palate, uvula deviated to opposite side, tonsil pushed medially |
| Treatment | Needle aspiration (first choice) or incision & drainage + IV antibiotics (high-dose penicillin/cephalosporin) |
| Interval tonsillectomy | Indicated after 2nd quinsy or background recurrent tonsillitis |
| "Hot" tonsillectomy | During acute phase - controversial, risk of aspiration |
| Aspect | Details |
|---|---|
| Technique | Dissection + snare (classical), coblation, cold steel, diathermy |
| Anaesthesia | General anaesthesia (GA) |
| Position | Rose position (neck extended, mouth open with Boyle-Davis gag) |
| Plane | Between tonsil capsule and superior constrictor muscle |
| Post-op bleed | Primary (<24h) vs Secondary (5-10 days, reactionary - from infection) |
| Danger | Internal carotid artery lateral to upper pole |
| Post-op diet | Soft diet, good hydration |
| Contraindications | Cleft palate (velopharyngeal insufficiency), bleeding disorders (relative), acute infection (relative) |
| Feature | Details |
|---|---|
| Organism | Epstein-Barr virus (EBV) |
| Presentation | Sore throat + massive tonsillar enlargement + cervical lymphadenopathy + fever + splenomegaly |
| Blood film | Atypical lymphocytes (Downey cells) |
| Test | Paul-Bunnell (Monospot) test - sensitivity 50% in children, 70-90% in adults |
| Key warning | NEVER give Ampicillin/Amoxicillin → causes maculopapular rash in >90% cases |
| Treatment | Supportive; steroids if airway compromise; avoid contact sports (splenic rupture) |
| Secondary infection | 30% get bacterial superinfection; use penicillin/cephalosporin |
| Acyclovir | Debatable; only for severe cases |
| Mnemonic | Meaning |
|---|---|
| CSOM Safe = Central | Mucosal type = Central perforation = Safe |
| CSOM Unsafe = Upper/Marginal | Squamosal type = Attic/Marginal = Unsafe = cholesteatoma |
| "Mucus = Mucosal type" | Mucopurulent, non-foul discharge |
| "FOUL = Squamosal" | Foul-smelling = Squamosal = Unsafe |
| "PARA" | Peritonsillar abscess → Aspiration first, then Recurrent = consider tonsillectomy |
| "No Amox in Mono" | Ampicillin/Amoxicillin + EBV = RASH |
Create a PDF version of these flashcards for quick revision
ls /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace/
~/ent-flashcards/create_flashcards.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import FrameBreak
import os
os.makedirs("/home/daytona/workspace/ent-flashcards", exist_ok=True)
# ── Colour palette ────────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1A3557")
TEAL = colors.HexColor("#0D7377")
AMBER = colors.HexColor("#F4A261")
LIGHT_BLUE = colors.HexColor("#E8F4FD")
LIGHT_TEAL = colors.HexColor("#E0F5F5")
LIGHT_AMBER= colors.HexColor("#FFF3E0")
LIGHT_RED = colors.HexColor("#FDECEA")
RED_DARK = colors.HexColor("#C0392B")
GREEN_DARK = colors.HexColor("#1E7B34")
LIGHT_GREEN= colors.HexColor("#E8F8EE")
WHITE = colors.white
GRAY_BG = colors.HexColor("#F5F7FA")
GRAY_LINE = colors.HexColor("#CDD3DA")
ORANGE_DARK= colors.HexColor("#D45F00")
# ── Document ──────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
"/home/daytona/workspace/ent-flashcards/ENT_Flashcards_CSOM_Tonsillitis.pdf",
pagesize=A4,
topMargin=14*mm, bottomMargin=14*mm,
leftMargin=14*mm, rightMargin=14*mm,
title="ENT Flashcards: CSOM & Chronic Tonsillitis",
author="Orris Medical AI"
)
# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
# Master title
TITLE_S = S("MasterTitle", fontName="Helvetica-Bold", fontSize=22,
textColor=WHITE, alignment=TA_CENTER, spaceAfter=2)
SUBTITLE_S = S("Subtitle", fontName="Helvetica", fontSize=11,
textColor=colors.HexColor("#B8D4F0"), alignment=TA_CENTER)
# Card header
CARD_HEADER_S = S("CardHeader", fontName="Helvetica-Bold", fontSize=13,
textColor=WHITE, alignment=TA_LEFT)
# Section header inside card
SEC_S = S("Section", fontName="Helvetica-Bold", fontSize=9,
textColor=NAVY, spaceAfter=1, spaceBefore=3)
# Body text
BODY_S = S("Body", fontName="Helvetica", fontSize=9,
textColor=colors.HexColor("#2C3E50"), leading=13, spaceAfter=1)
# Bold body
BOLD_S = S("Bold", fontName="Helvetica-Bold", fontSize=9,
textColor=NAVY, leading=13)
# Key point (highlighted)
KEY_S = S("Key", fontName="Helvetica-Bold", fontSize=9,
textColor=RED_DARK, leading=13)
# Warning box text
WARN_S = S("Warn", fontName="Helvetica-Bold", fontSize=9,
textColor=RED_DARK, leading=13, alignment=TA_CENTER)
# Table cell
CELL_S = S("Cell", fontName="Helvetica", fontSize=8.5,
textColor=colors.HexColor("#2C3E50"), leading=12)
CELL_B_S = S("CellB", fontName="Helvetica-Bold", fontSize=8.5,
textColor=NAVY, leading=12)
# Mnemonic
MNEM_S = S("Mnem", fontName="Helvetica-BoldOblique", fontSize=9,
textColor=ORANGE_DARK, leading=13)
# Tip
TIP_S = S("Tip", fontName="Helvetica-Oblique", fontSize=8.5,
textColor=colors.HexColor("#555"), leading=12)
# ── Helper builders ───────────────────────────────────────────────────────────
def c(text, style=CELL_S):
return Paragraph(text, style)
def cb(text):
return Paragraph(text, CELL_B_S)
def card_header(number, title, color=NAVY):
data = [[Paragraph(f"Card {number}", S("cn", fontName="Helvetica", fontSize=9,
textColor=colors.HexColor("#B8D4F0"))),
Paragraph(title, CARD_HEADER_S)]]
t = Table(data, colWidths=[18*mm, None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("ROUNDEDCORNERS", (0,0),(-1,-1), [4,4,0,0]),
]))
return t
def two_col_table(rows, col_widths=None, bg1=LIGHT_BLUE, bg2=WHITE):
"""Alternating-row 2-column table."""
if col_widths is None:
col_widths = [50*mm, None]
data = [[cb(r[0]), c(r[1])] for r in rows]
ts = TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 3),
("LINEBELOW", (0,0), (-1,-2), 0.3, GRAY_LINE),
("LINEBELOW", (0,-1),(-1,-1), 0.5, GRAY_LINE),
])
for i in range(len(data)):
bg = bg1 if i % 2 == 0 else bg2
ts.add("BACKGROUND", (0, i), (-1, i), bg)
t = Table(data, colWidths=col_widths)
t.setStyle(ts)
return t
def three_col_table(headers, rows, col_widths=None):
if col_widths is None:
col_widths = [45*mm, 60*mm, None]
hrow = [Paragraph(h, S("th", fontName="Helvetica-Bold", fontSize=8.5,
textColor=WHITE)) for h in headers]
data = [hrow]
for r in rows:
data.append([c(r[0]), c(r[1]), c(r[2])])
ts = TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 3),
("LINEBELOW", (0,0), (-1,-2), 0.3, GRAY_LINE),
("LINEBELOW", (0,-1),(-1,-1), 0.5, GRAY_LINE),
])
for i in range(1, len(data)):
bg = LIGHT_BLUE if i % 2 == 1 else WHITE
ts.add("BACKGROUND", (0, i), (-1, i), bg)
t = Table(data, colWidths=col_widths)
t.setStyle(ts)
return t
def warn_box(text, bg=LIGHT_RED, tc=RED_DARK):
p = Paragraph(text, S("wb", fontName="Helvetica-Bold", fontSize=8.5,
textColor=tc, leading=12))
t = Table([[p]], colWidths=[None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("LEFTPADDING", (0,0),(-1,-1), 7),
("RIGHTPADDING", (0,0),(-1,-1), 7),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LINEBELOW", (0,0),(-1,-1), 1.5, tc),
]))
return t
def mnem_box(text):
p = Paragraph(text, S("mb", fontName="Helvetica-BoldOblique", fontSize=9,
textColor=ORANGE_DARK, leading=13))
t = Table([[p]], colWidths=[None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), LIGHT_AMBER),
("LEFTPADDING", (0,0),(-1,-1), 7),
("RIGHTPADDING", (0,0),(-1,-1), 7),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LINEBELOW", (0,0),(-1,-1), 1.5, AMBER),
]))
return t
def tip_box(text):
p = Paragraph(text, S("tb", fontName="Helvetica-Oblique", fontSize=8.5,
textColor=colors.HexColor("#555"), leading=12))
t = Table([[p]], colWidths=[None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), LIGHT_GREEN),
("LEFTPADDING", (0,0),(-1,-1), 7),
("RIGHTPADDING", (0,0),(-1,-1), 7),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
("LINEBELOW", (0,0),(-1,-1), 1.5, GREEN_DARK),
]))
return t
def card_wrap(header_table, content_tables, bg=GRAY_BG):
"""Wrap content in a visually distinct card block."""
items = [header_table, Spacer(1, 2)]
for t in content_tables:
items.append(t)
items.append(Spacer(1, 2))
outer = Table([[item] for item in items], colWidths=[None])
outer.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("LEFTPADDING", (0,0),(-1,-1), 0),
("RIGHTPADDING", (0,0),(-1,-1), 0),
("TOPPADDING", (0,0),(-1,-1), 0),
("BOTTOMPADDING",(0,0),(-1,-1), 0),
("BOX", (0,0),(-1,-1), 1, GRAY_LINE),
("ROUNDEDCORNERS",(0,0),(-1,-1),[4,4,4,4]),
]))
return outer
# ── Cover page ────────────────────────────────────────────────────────────────
def cover_page():
cover_data = [[
Paragraph("ENT ENDOSCOPY REVISION", S("ct", fontName="Helvetica-Bold",
fontSize=26, textColor=WHITE, alignment=TA_CENTER)),
]]
cover = Table(cover_data, colWidths=[None])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), NAVY),
("TOPPADDING", (0,0),(-1,-1), 18),
("BOTTOMPADDING",(0,0),(-1,-1), 10),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 10),
]))
sub = Table([[Paragraph(
"CSOM Mucosal Type • Chronic Tonsillitis",
S("cs", fontName="Helvetica", fontSize=13, textColor=NAVY,
alignment=TA_CENTER))]], colWidths=[None])
sub.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), LIGHT_BLUE),
("TOPPADDING", (0,0),(-1,-1), 8),
("BOTTOMPADDING",(0,0),(-1,-1), 8),
]))
badge_data = [
[Paragraph("Final Year MBBS | ENT Quick Revision | 17 Flashcards",
S("bd", fontName="Helvetica", fontSize=9,
textColor=colors.HexColor("#888"), alignment=TA_CENTER))]
]
badge = Table(badge_data, colWidths=[None])
badge.setStyle(TableStyle([
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
]))
return [cover, sub, Spacer(1, 4*mm), badge]
# ── Section divider ───────────────────────────────────────────────────────────
def section_divider(title, subtitle="", color=NAVY):
data = [[
Paragraph(title, S("sd", fontName="Helvetica-Bold", fontSize=16,
textColor=WHITE, alignment=TA_CENTER)),
]]
if subtitle:
data.append([Paragraph(subtitle, S("ss", fontName="Helvetica", fontSize=10,
textColor=colors.HexColor("#B8D4F0"),
alignment=TA_CENTER))])
t = Table(data, colWidths=[None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), color),
("TOPPADDING", (0,0),(-1,-1), 12),
("BOTTOMPADDING",(0,0),(-1,-1), 12),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 10),
]))
return t
# ══════════════════════════════════════════════════════════════════════════════
# BUILD STORY
# ══════════════════════════════════════════════════════════════════════════════
story = []
# ── COVER ─────────────────────────────────────────────────────────────────────
story += cover_page()
story.append(Spacer(1, 6*mm))
# ══════════ SECTION 1: CSOM ══════════════════════════════════════════════════
story.append(section_divider("SECTION 1: CSOM — MUCOSAL TYPE",
"Tubotympanic / SAFE Type", NAVY))
story.append(Spacer(1, 3*mm))
# Card 1 — Definition & Classification
h = card_header("01", "Definition & Classification", NAVY)
rows = [
("Full Name", "Chronic Suppurative Otitis Media (CSOM)"),
("Definition", "Chronic inflammation of middle ear + mastoid with persistent TM perforation + discharge for >6 weeks"),
("Types", "Mucosal (Tubotympanic / SAFE) vs Squamous (Atticoantral / UNSAFE)"),
("Why 'SAFE'?", "No cholesteatoma, no bone erosion typically"),
("Perforation site", "CENTRAL — pars tensa; does NOT touch the annulus"),
("Other name", "Tubotympanic type / Benign CSOM"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None])]))
story.append(Spacer(1, 3*mm))
# Card 2 — Etiology & Pathogenesis
h = card_header("02", "Etiology & Pathogenesis", NAVY)
rows = [
("Primary cause", "Eustachian tube dysfunction (ETD)"),
("Sequence", "ETD → persistent MEE → mucosal oedema → granulation tissue → bacterial infection → chronic mucosal change"),
("Mucosal change", "Submucosal glands develop → secretory mucosa → perpetuates effusion"),
("TM changes", "Collagen skeleton degraded by enzymes → weakening → retraction pockets → perforation"),
("Organisms", "Pseudomonas aeruginosa (most common in chronic), S. aureus, Proteus, Gram-negatives, anaerobes"),
("Granulation tissue", "Initiated by bacterial toxins + inflammatory mediators → fibroblast recruitment + neovascularisation"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None])]))
story.append(Spacer(1, 3*mm))
# Card 3 — Clinical Features
h = card_header("03", "Clinical Features", TEAL)
rows = [
("Otorrhoea", "Intermittent, mucoid/mucopurulent — NOT foul-smelling (no cholesteatoma)"),
("Hearing loss", "Conductive hearing loss (CHL) — most common presenting complaint"),
("Otalgia", "UNCOMMON — if present → suspect intracranial complication or malignancy"),
("Vertigo", "UNCOMMON — if present → suspect labyrinthine fistula"),
("Tinnitus", "May be present"),
("Headache", "RED FLAG — suggests intracranial involvement"),
]
t = two_col_table(rows, [52*mm, None], bg1=LIGHT_TEAL, bg2=WHITE)
w = warn_box("⚠ Examiner Tip: Otalgia + Headache in CSOM = suspect COMPLICATIONS, NOT routine disease!")
story.append(KeepTogether([h, Spacer(1,1), t, Spacer(1,2), w]))
story.append(Spacer(1, 3*mm))
# Card 4 — Otoscopic Findings
h = card_header("04", "Otoscopic / Endoscopic Findings", TEAL)
rows = [
("Perforation", "CENTRAL — pars tensa (safe type)"),
("Annulus", "NOT involved"),
("Discharge", "Mucoid / mucopurulent — non-offensive odour"),
("Middle ear mucosa", "Pale, oedematous; may show granulation tissue"),
("Cholesteatoma", "ABSENT — key differentiator from squamosal type"),
("Ossicles", "Usually intact and visible through perforation"),
("EAC polyps", "May be present"),
("Scutal erosion", "ABSENT"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None], bg1=LIGHT_TEAL, bg2=WHITE)]))
story.append(Spacer(1, 3*mm))
# Card 5 — Comparison Table
h = card_header("05", "Comparison: Mucosal (SAFE) vs Squamosal (UNSAFE)", RED_DARK)
headers = ["Feature", "Mucosal / Safe", "Squamosal / Unsafe"]
rows = [
("Synonym", "Tubotympanic", "Atticoantral"),
("Perforation", "Central, pars tensa", "Marginal / attic (pars flaccida)"),
("Discharge", "Mucoid, odourless", "Scanty, FOUL-smelling"),
("Cholesteatoma", "ABSENT", "PRESENT"),
("Bone erosion", "No", "Yes"),
("Complications", "Less common", "Common (facial palsy, meningitis)"),
("Risk level", "SAFE", "UNSAFE / DANGEROUS"),
("Surgery", "Tympanoplasty", "Mastoidectomy"),
]
m = mnem_box("Mnemonic: FOUL = Squamosal (Foul smell → Unsafe). MUCOID = Mucosal (Mucopurulent → Safe). Central = Safe. Marginal = Murder (dangerous)!")
story.append(KeepTogether([h, Spacer(1,1), three_col_table(headers, rows, [42*mm, 60*mm, None]), Spacer(1,2), m]))
story.append(Spacer(1, 3*mm))
# Card 6 — Investigations
h = card_header("06", "Investigations", NAVY)
rows = [
("Rinne test", "Rinne NEGATIVE (BC > AC) — conductive hearing loss"),
("Weber test", "Lateralises to AFFECTED (worse) ear"),
("Pure Tone Audiogram","CHL most common; CHL >30 dB → suspect ossicular erosion"),
("HRCT temporal bone", "GOLD STANDARD — assesses extent, cholesteatoma, ossicular erosion, complications"),
("Ear swab C&S", "Guide antibiotic therapy"),
("SNHL", "May coexist (5–33 dB range); document preoperatively"),
("Tympanometry", "Type B flat curve — middle ear effusion"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None])]))
story.append(Spacer(1, 3*mm))
# Card 7 — Complications
h = card_header("07", "Complications", RED_DARK)
comp_data = [
[Paragraph("INTRATEMPORAL", S("ch", fontName="Helvetica-Bold", fontSize=8.5,
textColor=WHITE)),
Paragraph("INTRACRANIAL", S("ch", fontName="Helvetica-Bold", fontSize=8.5,
textColor=WHITE))],
[c("• Mastoiditis (most common)\n• Labyrinthitis → SNHL/vertigo\n• Facial nerve palsy\n• Tympanosclerosis\n• Ossicular erosion\n• Petrositis (Gradenigo's syndrome)"),
c("• Meningitis (most common IC complication)\n• Brain abscess (temporal lobe / cerebellar)\n• Lateral sinus thrombosis\n• Extradural / subdural abscess\n• Otitic hydrocephalus")],
]
comp_table = Table(comp_data, colWidths=[None, None])
comp_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED_DARK),
("BACKGROUND", (0,1), (0,1), LIGHT_RED),
("BACKGROUND", (1,1), (1,1), colors.HexColor("#FFF8F8")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LINEAFTER", (0,0), (0,-1), 0.5, GRAY_LINE),
]))
m = mnem_box("Gradenigo's Triad: Otorrhoea + Ipsilateral 6th nerve palsy (diplopia) + Facial pain (V nerve) = Petrous apex involvement")
story.append(KeepTogether([h, Spacer(1,1), comp_table, Spacer(1,2), m]))
story.append(Spacer(1, 3*mm))
# Card 8 — Treatment
h = card_header("08", "Treatment", TEAL)
med_rows = [
("Aural toilet", "Dry mopping — MOST IMPORTANT first step"),
("Topical antibiotics","Ciprofloxacin ear drops (preferred); AVOID aminoglycosides (ototoxic)"),
("Systemic antibiotics","Guided by C&S of ear swab"),
("Keep ear dry", "No swimming; cotton ball when washing hair"),
("Treat ETD/URTI", "Address underlying Eustachian tube dysfunction"),
]
med_t = two_col_table(med_rows, [55*mm, None], bg1=LIGHT_TEAL, bg2=WHITE)
surg_rows = [
("Failed medical Rx", "Multiple attempts at medical treatment failed"),
("Complications", "Vertigo, facial weakness, headache, intracranial signs"),
("Persistent perf", "After dry ear achieved — for hearing rehabilitation"),
("Cholesteatoma", "Nearly ABSOLUTE indication for surgery"),
("Myringoplasty", "TM repair alone (Type I tympanoplasty)"),
("Tympanoplasty", "TM repair + ossiculoplasty"),
("CWU mastoidectomy", "Canal wall up — better cosmesis; needs 2nd-look surgery"),
("CWD mastoidectomy", "Canal wall down — better access; open cavity; lifelong follow-up"),
]
surg_t = two_col_table(surg_rows, [55*mm, None])
med_hdr = Table([[Paragraph("MEDICAL TREATMENT", S("mh", fontName="Helvetica-Bold",
fontSize=8.5, textColor=TEAL))]], colWidths=[None])
med_hdr.setStyle(TableStyle([("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),2)]))
surg_hdr = Table([[Paragraph("SURGICAL INDICATIONS & OPTIONS", S("mh", fontName="Helvetica-Bold",
fontSize=8.5, textColor=NAVY))]], colWidths=[None])
surg_hdr.setStyle(TableStyle([("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),2)]))
story.append(KeepTogether([h, Spacer(1,1), med_hdr, med_t, Spacer(1,3), surg_hdr, surg_t]))
story.append(Spacer(1, 4*mm))
# ══════════ SECTION 2: CHRONIC TONSILLITIS ═══════════════════════════════════
story.append(PageBreak())
story.append(section_divider("SECTION 2: CHRONIC TONSILLITIS",
"& Related Conditions", TEAL))
story.append(Spacer(1, 3*mm))
# Card 9 — Anatomy
h = card_header("09", "Waldeyer's Ring & Tonsil Anatomy", TEAL)
rows = [
("Waldeyer's ring", "Palatine tonsils + Pharyngeal tonsil (adenoid) + Lingual tonsil + Tubal tonsils"),
("Position", "Between anterior (palatoglossal) and posterior (palatopharyngeal) pillars"),
("Blood supply", "Tonsillar branch of FACIAL artery (main); also lingual, ascending pharyngeal, ascending palatine, descending palatine"),
("Dangerous area", "Upper pole — close to INTERNAL CAROTID ARTERY"),
("Lymph drainage", "Jugulodigastric node (tonsillar node) — Level II"),
("Nerve supply", "Glossopharyngeal nerve (IX) + lesser palatine nerve"),
("Immunology", "B-cell predominant; part of MALT (mucosa-associated lymphoid tissue)"),
("Crypts", "Deep crypts lined by stratified squamous epithelium — harbour debris"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None], bg1=LIGHT_TEAL, bg2=WHITE)]))
story.append(Spacer(1, 3*mm))
# Card 10 — Chronic Tonsillitis Clinical Features
h = card_header("10", "Chronic Tonsillitis — Clinical Features", TEAL)
rows = [
("Definition", "Persistent/recurrent tonsillar infection with chronic mucosal inflammation"),
("Symptoms", "Chronic throat discomfort, foreign body sensation, halitosis, dysphagia, low-grade fever"),
("Key sign", "Smelly white debris (caseous material) from tonsillar crypts = tonsilloliths"),
("On examination", "Dull, congested tonsils; scarred surface; crypts filled with debris; enlarged jugulodigastric nodes"),
("Irwin Moore sign", "Pus/debris expressible from crypts on pressure with a spatula"),
("Tonsil size", "Variable — may be small (fibrosed) or enlarged"),
("History", "Recurrent acute tonsillitis episodes (key feature)"),
]
w = warn_box("Note: Tonsil size does NOT correlate well with degree of disease — small fibrosed tonsils can be severely diseased!")
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None], bg1=LIGHT_TEAL, bg2=WHITE), Spacer(1,2), w]))
story.append(Spacer(1, 3*mm))
# Card 11 — Bacteriology
h = card_header("11", "Bacteriology", NAVY)
rows = [
("GABHS", "Group A Beta-Haemolytic Streptococcus — MOST IMPORTANT; causes immunological complications"),
("S. aureus", "Common bacterial cause"),
("H. influenzae", "Common, especially in children"),
("Anaerobes", "Important in chronic/cryptic tonsillitis and peritonsillar abscess"),
("Viruses", "EBV, Adenovirus, Herpes simplex — trigger acute episodes"),
("Synergy", "Viruses sensitise bacteria in tonsillar crypts — act synergistically"),
("Latent viruses", "EBV, Adenoviruses, HSV — sensitise pathogenic bacteria in asymptomatic carriers"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None])]))
story.append(Spacer(1, 3*mm))
# Card 12 — Grading
h = card_header("12", "Tonsil Size Grading (Brodsky Scale)", NAVY)
rows = [
("Grade 0", "Tonsils within tonsillar fossa — not visible"),
("Grade 1+", "<25% of oropharyngeal width"),
("Grade 2+", "25–50% of oropharyngeal width — moderate enlargement"),
("Grade 3+", "50–75% of oropharyngeal width — significant enlargement"),
('Grade 4+', '>75% of oropharyngeal width — "kissing tonsils"; severe obstruction'),
]
m = mnem_box("Mnemonic: 0-1-2-3-4 → None, Quarter, Half, Three-quarter, Full obstruction | Grade 3-4 → consider tonsillectomy for OSA")
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [30*mm, None]), Spacer(1,2), m]))
story.append(Spacer(1, 3*mm))
# Card 13 — Indications for Tonsillectomy
h = card_header("13", "Indications for Tonsillectomy (Paradise Criteria)", RED_DARK)
rows = [
("Paradise criteria", "7+ episodes/year OR 5+/year × 2 consecutive years OR 3+/year × 3 consecutive years"),
("OSA / obstruction", "Obstructive sleep apnoea, upper airway obstruction, cor pulmonale"),
("Quinsy", "Peritonsillar abscess — after 2nd episode OR contralateral first episode"),
("Malignancy suspicion","Asymmetric, ulcerated, firm tonsil — biopsy / diagnostic tonsillectomy"),
("Febrile seizures", "Recurrent febrile seizures from tonsillitis episodes"),
("Carrier states", "Streptococcal carrier, diphtheria carrier"),
("Tonsillolith", "Symptomatic inspissated tonsillar calculi"),
("Haemorrhage", "Spontaneous tonsil bleeding unresponsive to conservative Rx"),
]
t = tip_box("Tip: Paradise criteria are a guide — clinical judgment + patient preference also matter. Scotland guidelines differ slightly (5/4/3 rule).")
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None], bg1=LIGHT_RED, bg2=WHITE), Spacer(1,2), t]))
story.append(Spacer(1, 3*mm))
# Card 14 — Peritonsillar Abscess (Quinsy)
h = card_header("14", "Peritonsillar Abscess (QUINSY) — High Yield!", RED_DARK)
rows = [
("Definition", "Pus collection in peritonsillar space — between tonsil capsule and superior constrictor muscle"),
("Site", "UPPER POLE of tonsil (supratonsillar space) — most common"),
("Symptoms", "Severe unilateral sore throat, odynophagia, TRISMUS (hallmark!), muffled 'hot potato' voice, ipsilateral ear pain"),
("Exam findings", "Bulging soft palate, uvula deviated to OPPOSITE side, tonsil pushed medially"),
("1st line Rx", "NEEDLE ASPIRATION (first choice) + IV antibiotics"),
("Antibiotics", "IV high-dose penicillin or cephalosporin + metronidazole for anaerobes"),
("Drainage", "Incision & drainage if aspiration fails"),
("Interval tonsillectomy","After 2nd quinsy OR background recurrent tonsillitis"),
("Hot tonsillectomy","Controversial — risk of aspiration in severely ill patients"),
]
m = mnem_box("QUINSY = Qualifies for Unilateral INfection of Superior pole space — uvula deviated awaY!")
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None], bg1=LIGHT_RED, bg2=WHITE), Spacer(1,2), m]))
story.append(Spacer(1, 3*mm))
# Card 15 — Complications
h = card_header("15", "Complications of Tonsillitis", RED_DARK)
comp_data = [
[Paragraph("LOCAL/REGIONAL", S("ch2", fontName="Helvetica-Bold", fontSize=8.5, textColor=WHITE)),
Paragraph("IMMUNOLOGICAL (GABHS)", S("ch2", fontName="Helvetica-Bold", fontSize=8.5, textColor=WHITE)),
Paragraph("RARE BUT DANGEROUS", S("ch2", fontName="Helvetica-Bold", fontSize=8.5, textColor=WHITE))],
[c("• Peritonsillar abscess\n• Parapharyngeal abscess\n• Retropharyngeal abscess (children <5y)\n• Otitis media\n• Sinusitis"),
c("• Acute Rheumatic Fever (ARF)\n• Acute Glomerulonephritis (AGN)\n• Reactive arthritis\n• Post-streptococcal vasculitis"),
c("• Lemierre's syndrome\n• Deep neck space infection\n• Mediastinitis\n• Septicaemia\n• Airway obstruction")],
]
comp_table = Table(comp_data, colWidths=[None, None, None])
comp_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED_DARK),
("BACKGROUND", (0,1), (0,1), LIGHT_RED),
("BACKGROUND", (1,1), (1,1), LIGHT_AMBER),
("BACKGROUND", (2,1), (2,1), colors.HexColor("#FFF0F0")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LINEAFTER", (0,0), (1,-1), 0.5, GRAY_LINE),
]))
lem = tip_box("Lemierre's Syndrome: Septic thrombophlebitis of IJV | Organism: Fusobacterium necrophorum | Treat: Penicillin + Metronidazole × 6 weeks | Suspect: Severe neck pain + septicaemia after sore throat")
story.append(KeepTogether([h, Spacer(1,1), comp_table, Spacer(1,2), lem]))
story.append(Spacer(1, 3*mm))
# Card 16 — Tonsillectomy Surgical Details
h = card_header("16", "Tonsillectomy — Surgical Details", NAVY)
rows = [
("Anaesthesia", "General anaesthesia (GA) — always"),
("Position", "Rose position: neck extended, mouth open with Boyle-Davis gag"),
("Plane", "Between tonsil capsule and superior constrictor muscle"),
("Technique", "Cold steel dissection + snare (classical); coblation, diathermy, laser"),
("Danger", "Internal carotid artery lateral to UPPER POLE — must not be injured"),
("Primary bleed", "<24 hours post-op — reactionary haemorrhage"),
("Secondary bleed","5–10 days post-op — from infection/slough separation"),
("Contraindications","Cleft palate (VPI risk), acute infection (relative), bleeding disorders (relative)"),
("Post-op diet", "Soft diet + good hydration; regular analgesia"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None])]))
story.append(Spacer(1, 3*mm))
# Card 17 — Infectious Mononucleosis
h = card_header("17", "Infectious Mononucleosis — Must-Know Differential!", RED_DARK)
rows = [
("Organism", "Epstein-Barr virus (EBV) — human herpesvirus 4"),
("Presentation", "Sore throat + massive tonsillar enlargement + cervical LAP + fever + splenomegaly"),
("Blood film", "Atypical lymphocytes (Downey cells) — classic finding"),
("Monospot test", "Paul-Bunnell test; sensitivity 50% in children, 70–90% in adults"),
("NEVER give", "Ampicillin or Amoxicillin → maculopapular rash in >90% (30% with Amoxicillin)"),
("Treatment", "Supportive; steroids if airway compromise or severe oropharyngeal oedema"),
("Bacterial superinfection", "30% get secondary bacterial infection → use Penicillin or Cephalosporin"),
("Acyclovir", "Debatable; consider only in severe/immunocompromised cases"),
("Avoid", "Contact sports — risk of splenic rupture"),
("Antigen testing","VCA IgM, EA antibodies — confirmatory"),
]
w = warn_box("⚠ NEVER give Ampicillin / Amoxicillin in suspected EBV tonsillitis — causes severe maculopapular RASH!")
story.append(KeepTogether([h, Spacer(1,1), two_col_table(rows, [52*mm, None], bg1=LIGHT_RED, bg2=WHITE), Spacer(1,2), w]))
story.append(Spacer(1, 4*mm))
# ══════════ RAPID FIRE SECTION ═══════════════════════════════════════════════
story.append(PageBreak())
story.append(section_divider("RAPID FIRE REVIEW", "Quick-recall points for your endoscopy", ORANGE_DARK))
story.append(Spacer(1, 3*mm))
# Mnemonics card
h = card_header("RF1", "Mnemonics & Memory Aids", ORANGE_DARK)
mnem_rows = [
("Safe vs Unsafe", "CENTRAL = SAFE | MARGINAL/ATTIC = MURDER (dangerous)"),
("Foul smell rule", "FOUL smell = SQUAMOSAL = Unsafe = Cholesteatoma"),
("Mucoid rule", "MUCOID + NON-FOUL = MUCOSAL = Safe type"),
("Gradenigo's triad","Otorrhoea + CN6 palsy (diplopia) + Trigeminal pain = Petrositis"),
("CSOM complications","MBLEAT: Meningitis, Brain abscess, Lateral sinus thrombosis, Extradural abscess, Abscess (subdural), Thrombosis (otitic hydrocephalus)"),
("Quinsy sign", "Uvula deviated AWAY from abscess side"),
("EBV + Ampicillin","= RASH (>90%) — NEVER prescribe together!"),
("Paradise 7/5/3", "7 episodes/yr × 1yr | 5 ep/yr × 2yrs | 3 ep/yr × 3yrs → tonsillectomy"),
("Lemierre's org", "Fusobacterium necrophorum → IJV thrombosis → septic emboli"),
("No Amox in Mono", "Amoxicillin + Mono = massive rash → always exclude EBV first"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col_table(mnem_rows, [55*mm, None], bg1=LIGHT_AMBER, bg2=WHITE)]))
story.append(Spacer(1, 3*mm))
# Examiner questions card
h = card_header("RF2", "Likely Examiner Questions — Viva Points", ORANGE_DARK)
q_data = [
[Paragraph("QUESTION", S("qh", fontName="Helvetica-Bold", fontSize=8.5, textColor=WHITE)),
Paragraph("IDEAL ANSWER", S("qh", fontName="Helvetica-Bold", fontSize=8.5, textColor=WHITE))],
[c("What type of perforation is this?"), c("Central, pars tensa — SAFE type (mucosal CSOM)")],
[c("Is this safe or unsafe CSOM?"), c("SAFE — central perforation, no cholesteatoma, no foul smell")],
[c("What is the discharge like in mucosal CSOM?"), c("Mucoid/mucopurulent, intermittent, NOT foul-smelling")],
[c("What hearing test findings?"), c("Rinne negative, Weber lateralises to affected ear; PTA shows CHL")],
[c("CHL >30 dB means?"), c("Suspect ossicular erosion (long process of incus most commonly affected)")],
[c("Best imaging for CSOM?"), c("HRCT temporal bone — gold standard")],
[c("First line treatment for mucosal CSOM?"), c("Aural toilet (dry mopping) + ciprofloxacin ear drops")],
[c("Surgery for TM perforation?"), c("Myringoplasty (Type I tympanoplasty)")],
[c("Indications for mastoidectomy in CSOM?"), c("Cholesteatoma, failed medical Rx, complications (vertigo, facial palsy)")],
[c("How do you diagnose chronic tonsillitis?"), c("Clinical — history of recurrent infections, debris in crypts, scarred tonsils")],
[c("Paradise criteria for tonsillectomy?"), c("7/yr × 1yr OR 5/yr × 2yrs OR 3/yr × 3yrs")],
[c("Trismus + bulging palate + uvula deviation?"), c("Peritonsillar abscess (quinsy) — aspirate + IV penicillin")],
[c("Why no amoxicillin in tonsillitis?"), c("Risk of EBV — Amox + EBV = severe maculopapular rash (>90%)")],
[c("Most common intracranial complication of CSOM?"), c("Meningitis")],
[c("Most common intratemporal complication of CSOM?"), c("Mastoiditis")],
[c("Gradenigo's syndrome components?"), c("Otorrhoea + CN6 palsy + Facial/trigeminal pain = petrous apex involvement")],
]
q_table = Table(q_data, colWidths=[85*mm, None])
q_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), ORANGE_DARK),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0), (-1,-1), 3),
("LINEBELOW", (0,0), (-1,-2), 0.3, GRAY_LINE),
]))
for i in range(1, len(q_data)):
bg = LIGHT_AMBER if i % 2 == 1 else WHITE
q_table.setStyle(TableStyle([("BACKGROUND", (0, i), (-1, i), bg)]))
story.append(KeepTogether([h, Spacer(1,1), q_table]))
story.append(Spacer(1, 4*mm))
# Footer note
footer = Table([[Paragraph(
"Sources: Shambaugh Surgery of the Ear | Scott-Brown's Otorhinolaryngology Head & Neck Surgery | Cummings Otolaryngology • Generated by Orris Medical AI",
S("ft", fontName="Helvetica-Oblique", fontSize=7.5,
textColor=colors.HexColor("#999"), alignment=TA_CENTER)
)]], colWidths=[None])
footer.setStyle(TableStyle([
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LINEABOVE", (0,0),(-1,-1), 0.5, GRAY_LINE),
]))
story.append(footer)
# ── Build ─────────────────────────────────────────────────────────────────────
doc.build(story)
print("PDF created successfully!")
cd /home/daytona/workspace/ent-flashcards && python create_flashcards.py 2>&1
ls -lh /home/daytona/workspace/ent-flashcards/ENT_Flashcards_CSOM_Tonsillitis.pd…
ENT Flashcards CSOM Tonsillitis
PDF Document · PDF
And one of my friend is having orthopedics clinical practical examination tomorrow, and his case is osteoarthritis. Can you please give everything related to osteoarthritis in a flashcard way and make it a PDF like before?
~/ent-flashcards/create_oa_flashcards.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
import os
os.makedirs("/home/daytona/workspace/ent-flashcards", exist_ok=True)
# ── Colour palette ─────────────────────────────────────────────────────────────
DARK_GREEN = colors.HexColor("#1B5E20")
MED_GREEN = colors.HexColor("#2E7D32")
LIGHT_GREEN = colors.HexColor("#E8F5E9")
TEAL = colors.HexColor("#00695C")
LIGHT_TEAL = colors.HexColor("#E0F2F1")
NAVY = colors.HexColor("#1A3557")
AMBER = colors.HexColor("#F57F17")
LIGHT_AMBER = colors.HexColor("#FFF8E1")
RED_DARK = colors.HexColor("#B71C1C")
LIGHT_RED = colors.HexColor("#FFEBEE")
PURPLE = colors.HexColor("#4A148C")
LIGHT_PURPLE= colors.HexColor("#F3E5F5")
GRAY_BG = colors.HexColor("#F5F7FA")
GRAY_LINE = colors.HexColor("#CDD3DA")
WHITE = colors.white
ORANGE_DARK = colors.HexColor("#E65100")
BLUE_DARK = colors.HexColor("#0D47A1")
LIGHT_BLUE = colors.HexColor("#E3F2FD")
# ── Document ───────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
"/home/daytona/workspace/ent-flashcards/Osteoarthritis_Flashcards.pdf",
pagesize=A4,
topMargin=13*mm, bottomMargin=13*mm,
leftMargin=13*mm, rightMargin=13*mm,
title="Osteoarthritis Flashcards — Orthopaedics Clinical Exam",
author="Orris Medical AI"
)
# ── Style helpers ──────────────────────────────────────────────────────────────
def S(name, **kw):
return ParagraphStyle(name, **kw)
def c(text, fs=8.5, bold=False, color=colors.HexColor("#2C3E50"), italic=False):
fn = "Helvetica-Bold" if bold else ("Helvetica-Oblique" if italic else "Helvetica")
return Paragraph(text, S("_c", fontName=fn, fontSize=fs,
textColor=color, leading=12.5))
def cb(text, fs=8.5):
return Paragraph(text, S("_cb", fontName="Helvetica-Bold", fontSize=fs,
textColor=NAVY, leading=12.5))
def card_header(num, title, color=MED_GREEN):
data = [[
Paragraph(f"Card {num}", S("cn", fontName="Helvetica", fontSize=8.5,
textColor=colors.HexColor("#C8E6C9"))),
Paragraph(title, S("ct", fontName="Helvetica-Bold", fontSize=12.5,
textColor=WHITE))
]]
t = Table(data, colWidths=[16*mm, None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), color),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 6),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
]))
return t
def two_col(rows, cw1=54*mm, bg1=LIGHT_GREEN, bg2=WHITE):
data = [[cb(r[0]), c(r[1])] for r in rows]
ts = TableStyle([
("VALIGN", (0,0),(-1,-1), "TOP"),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 5),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LINEBELOW", (0,0),(-1,-2), 0.3, GRAY_LINE),
("LINEBELOW", (0,-1),(-1,-1),0.5, GRAY_LINE),
])
for i,_ in enumerate(data):
ts.add("BACKGROUND",(0,i),(-1,i), bg1 if i%2==0 else bg2)
t = Table(data, colWidths=[cw1, None])
t.setStyle(ts)
return t
def three_col(headers, rows, cw=None):
if cw is None: cw = [42*mm, 65*mm, None]
hrow = [Paragraph(h, S("_h", fontName="Helvetica-Bold", fontSize=8.5,
textColor=WHITE)) for h in headers]
data = [hrow] + [[c(r[0]), c(r[1]), c(r[2])] for r in rows]
ts = TableStyle([
("BACKGROUND", (0,0),(-1,0), NAVY),
("VALIGN", (0,0),(-1,-1),"TOP"),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 5),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LINEBELOW", (0,0),(-1,-2), 0.3, GRAY_LINE),
("LINEBELOW", (0,-1),(-1,-1),0.5, GRAY_LINE),
])
for i in range(1,len(data)):
ts.add("BACKGROUND",(0,i),(-1,i), LIGHT_BLUE if i%2==1 else WHITE)
t = Table(data, colWidths=cw)
t.setStyle(ts)
return t
def warn_box(text, bg=LIGHT_RED, tc=RED_DARK):
t = Table([[Paragraph(text, S("_w", fontName="Helvetica-Bold", fontSize=8.5,
textColor=tc, leading=12))]], colWidths=[None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("LEFTPADDING", (0,0),(-1,-1), 7),
("RIGHTPADDING", (0,0),(-1,-1), 7),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LINEBELOW", (0,0),(-1,-1), 1.5, tc),
]))
return t
def mnem_box(text):
t = Table([[Paragraph(text, S("_m", fontName="Helvetica-BoldOblique", fontSize=9,
textColor=ORANGE_DARK, leading=13))]], colWidths=[None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), LIGHT_AMBER),
("LEFTPADDING", (0,0),(-1,-1), 7),
("RIGHTPADDING", (0,0),(-1,-1), 7),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LINEBELOW", (0,0),(-1,-1), 1.5, AMBER),
]))
return t
def tip_box(text, bg=LIGHT_TEAL, tc=TEAL):
t = Table([[Paragraph(text, S("_t", fontName="Helvetica-Oblique", fontSize=8.5,
textColor=tc, leading=12))]], colWidths=[None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("LEFTPADDING", (0,0),(-1,-1), 7),
("RIGHTPADDING", (0,0),(-1,-1), 7),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
("LINEBELOW", (0,0),(-1,-1), 1.5, tc),
]))
return t
def section_div(title, sub="", color=MED_GREEN):
rows = [[Paragraph(title, S("_sd", fontName="Helvetica-Bold", fontSize=16,
textColor=WHITE, alignment=TA_CENTER))]]
if sub:
rows.append([Paragraph(sub, S("_ss", fontName="Helvetica", fontSize=10,
textColor=colors.HexColor("#C8E6C9"),
alignment=TA_CENTER))])
t = Table(rows, colWidths=[None])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), color),
("TOPPADDING", (0,0),(-1,-1), 11),
("BOTTOMPADDING",(0,0),(-1,-1), 11),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 10),
]))
return t
# ══════════════════════════════════════════════════════════════════════════════
story = []
# ── COVER ──────────────────────────────────────────────────────────────────────
cover = Table([[
Paragraph("ORTHOPAEDICS CLINICAL EXAM", S("_cv", fontName="Helvetica-Bold",
fontSize=24, textColor=WHITE, alignment=TA_CENTER))
]], colWidths=[None])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), DARK_GREEN),
("TOPPADDING", (0,0),(-1,-1), 16),
("BOTTOMPADDING",(0,0),(-1,-1), 10),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 10),
]))
sub_cover = Table([[
Paragraph("OSTEOARTHRITIS — Complete Flashcard Review", S("_cs", fontName="Helvetica",
fontSize=13, textColor=DARK_GREEN, alignment=TA_CENTER))
]], colWidths=[None])
sub_cover.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), LIGHT_GREEN),
("TOPPADDING", (0,0),(-1,-1), 8),
("BOTTOMPADDING",(0,0),(-1,-1), 8),
]))
badge = Table([[
Paragraph("Final Year MBBS | 20 Flashcards | Pathology • Clinical • Radiology • Surgery",
S("_bd", fontName="Helvetica", fontSize=9,
textColor=colors.HexColor("#888"), alignment=TA_CENTER))
]], colWidths=[None])
badge.setStyle(TableStyle([("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5)]))
story += [cover, sub_cover, Spacer(1,4*mm), badge, Spacer(1,4*mm)]
# ══════════ SECTION 1: BASICS ═════════════════════════════════════════════════
story.append(section_div("SECTION 1: BASICS & PATHOLOGY",
"Definition • Classification • Pathogenesis • Morphology", DARK_GREEN))
story.append(Spacer(1,3*mm))
# Card 1 — Definition & Overview
h = card_header("01", "Definition & Overview", MED_GREEN)
rows = [
("Full name", "Osteoarthritis (OA) — also called Degenerative Joint Disease (DJD)"),
("Definition", "Degeneration of articular cartilage resulting in structural and functional failure of synovial joints"),
("Primary pathology", "Degenerative disorder of articular cartilage; inflammation is SECONDARY"),
("Most common", "Most common disease of joints / most common musculoskeletal disorder"),
("Epidemiology", "Prevalence increases exponentially after age 50; ~40% of people >70 years affected"),
("Gender", "Primary OA more common in WOMEN; post-menopausal increase in prevalence"),
("Pattern", "Oligoarticular (few joints) in primary OA; weight-bearing joints predominantly"),
("Key concept", "Despite the name 'osteoARTHRITIS', it is primarily DEGENERATIVE, not inflammatory"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col(rows)]))
story.append(Spacer(1,3*mm))
# Card 2 — Classification
h = card_header("02", "Classification: Primary vs Secondary", MED_GREEN)
rows3 = [
("Feature", "PRIMARY OA", "SECONDARY OA"),
("Synonym", "Idiopathic OA", "OA with underlying cause"),
("Cause", "No identifiable cause; aging phenomenon","Known predisposing condition"),
("Age", "Patients in their 50s+", "Any age, including younger individuals"),
("Proportion", "~95% of OA cases", "~5% of OA cases"),
("Distribution", "Oligoarticular, weight-bearing joints","May be single joint (trauma) or generalised"),
("Genetic link", "Strong genetic predisposition (GDF5, RUNX2, SMAD3, PTHLH loci)","Depends on cause"),
]
secondary = Table([[
Paragraph("Secondary OA Causes:", S("_sh", fontName="Helvetica-Bold", fontSize=8.5, textColor=NAVY)),
c("Trauma (acute/chronic repetitive) | Systemic metabolic: Haemochromatosis, Wilson's disease, Ochronosis | Endocrine: Acromegaly, Hypothyroidism, Hyperparathyroidism, Diabetes | Crystal deposition: CPPD, Gout | RA, Paget's disease, Bone/joint dysplasias | Ligamentous laxity (Ehlers-Danlos) | Congenital joint deformities", fs=8.5)
]], colWidths=[38*mm, None])
secondary.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), LIGHT_PURPLE),
("VALIGN", (0,0),(-1,-1),"TOP"),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING",(0,0),(-1,-1), 5),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1),4),
]))
story.append(KeepTogether([h, Spacer(1,1), three_col(["Feature","Primary OA","Secondary OA"], rows3, [38*mm,65*mm,None]),
Spacer(1,2), secondary]))
story.append(Spacer(1,3*mm))
# Card 3 — Pathogenesis
h = card_header("03", "Pathogenesis", TEAL)
rows = [
("Principal mechanism", "Biomechanical stress → degeneration of articular cartilage → disordered repair"),
("Initiating event", "Chondrocyte injury (mechanical + genetic predisposition)"),
("Matrix changes", "Chondrocytes release MMPs (matrix metalloproteinases), NO, PGE2, TNF, IL-1 → extracellular matrix breakdown"),
("Early OA", "Chondrocytes proliferate and attempt repair — but degradation exceeds repair"),
("Subchondral bone", "Osteoblast activity increases → subchondral sclerosis; subchondral cysts form as synovial fluid enters bone through damaged cartilage"),
("Osteophyte formation","BMP (bone morphogenetic protein) + TGF-β → peripheral new bone outgrowths at joint margins"),
("Role of inflammation","Secondary; inflammatory mediators (IL-1β, TNF-α) exacerbate cartilage damage"),
("ET dysfunction role", "Biomechanics: abnormal force transmission across joint → cartilage damage"),
("Key mediators", "MMPs, NO (nitric oxide), PGE2 (prostaglandin E2), TNF-α, BMP, TGF-β"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col(rows, bg1=LIGHT_TEAL, bg2=WHITE)]))
story.append(Spacer(1,3*mm))
# Card 4 — Morphology / Gross & Histology
h = card_header("04", "Morphology — Gross & Histological Features", TEAL)
rows = [
("Early changes", "Cartilage surface softening → fibrillation (surface irregularity) — earliest histological feature"),
("Fibrillation", "Characteristic histological finding — vertical clefts and fraying of articular cartilage surface"),
("Advanced changes", "Full-thickness cartilage loss → sloughing of dislodged cartilage pieces"),
("Loose bodies (joint mice)", "Dislodged fragments of cartilage and subchondral bone tumble into joint space"),
("Bone eburnation", "Exposed subchondral bone becomes new articular surface; friction burnishes it → polished ivory appearance"),
("Subchondral cysts", "Synovial fluid forced into subchondral bone through cartilage defects (ball-valve mechanism) → fibrous-walled cysts"),
("Osteophytes", "Outgrowths at margins of articular surface; capped by fibrocartilage → gradually ossify"),
("Synovium", "Mildly congested and fibrotic; scattered chronic inflammatory cells — much less than RA"),
("Subchondral sclerosis","Increased osteoblastic activity → sclerosis visible on X-ray"),
]
m = mnem_box("Histology mnemonic — 4 LOSS: Loss of cartilage | Osteophytes | Subchondral sclerosis | Subchondral cysts")
story.append(KeepTogether([h, Spacer(1,1), two_col(rows, bg1=LIGHT_TEAL, bg2=WHITE), Spacer(1,2), m]))
story.append(Spacer(1,3*mm))
# Card 5 — OA vs RA Comparison
h = card_header("05", "OA vs Rheumatoid Arthritis — KEY Comparison", RED_DARK)
headers = ["Feature", "Osteoarthritis", "Rheumatoid Arthritis"]
rows3 = [
("Pathogenesis", "Mechanical injury to articular cartilage", "Autoimmunity (T cells + antibodies)"),
("Inflammation", "SECONDARY — exacerbates cartilage damage", "PRIMARY — causes cartilage destruction"),
("Joints involved", "Weight-bearing (knees, hips, spine); DIP, CMC1","Small joints first (MCP, PIP); multiple joints"),
("Morning stiffness", "<30 minutes", ">1 hour (hallmark of RA)"),
("Pathology", "Cartilage degeneration, osteophytes, subchondral cysts; minimal synovitis","Inflammatory pannus, cartilage erosion, severe synovitis"),
("Serum markers", "NONE (seronegative)", "RF, ACPA (anti-CCP) — seropositive"),
("Joint fusion", "DOES NOT occur", "Ankylosis can occur"),
("Systemic features", "NO", "YES — lungs, heart, other organs"),
("Heberden nodes", "DIP joint osteophytes — YES", "NO (RA affects PIP — Bouchard nodes in OA)"),
("Surgery", "Joint replacement (TKR/THR)", "Synovectomy + DMARDs; joint replacement late"),
]
story.append(KeepTogether([h, Spacer(1,1), three_col(headers, rows3, [40*mm, 70*mm, None])]))
story.append(Spacer(1,4*mm))
# ══════════ SECTION 2: CLINICAL ══════════════════════════════════════════════
story.append(PageBreak())
story.append(section_div("SECTION 2: CLINICAL FEATURES",
"Symptoms • Signs • Joints Involved • Special Tests", MED_GREEN))
story.append(Spacer(1,3*mm))
# Card 6 — Symptoms
h = card_header("06", "Clinical Symptoms", MED_GREEN)
rows = [
("Pain", "Joint pain — WORSENS with use/activity (unlike RA where it is worse at rest); improves with rest"),
("Morning stiffness", "Present but BRIEF — <30 minutes (RA >1 hour). Also stiffness after periods of inactivity = 'gelling'"),
("Crepitus", "Grating/crackling sensation on movement — due to irregular joint surfaces"),
("Limitation of ROM", "Progressive restriction of range of motion"),
("Joint deformity", "With advanced disease — but NO joint fusion (ankylosis) unlike RA"),
("Functional impairment","Difficulty with ADLs — walking, stair climbing, gripping objects"),
("Spinal symptoms", "Osteophyte impingement on spinal foramina → cervical/lumbar nerve root compression → radicular pain, muscle spasm, neurological deficits"),
("No systemic features","No fever, no weight loss, no organ involvement — unlike RA"),
("Onset", "Insidious, gradual — usually 50s+ (primary OA)"),
]
w = warn_box("Pain WORSENS with activity and IMPROVES with rest — opposite of inflammatory arthritis (RA) where pain is worst in morning/at rest!")
story.append(KeepTogether([h, Spacer(1,1), two_col(rows), Spacer(1,2), w]))
story.append(Spacer(1,3*mm))
# Card 7 — Signs / Clinical Examination
h = card_header("07", "Clinical Signs on Examination", MED_GREEN)
rows = [
("Inspection", "Joint enlargement, deformity (varus/valgus at knee), muscle wasting, gait abnormality (antalgic gait)"),
("Heberden nodes", "Bony enlargements at DIP joints (distal interphalangeal) — more common in WOMEN"),
("Bouchard nodes", "Bony enlargements at PIP joints (proximal interphalangeal) — less common"),
("Squaring of thumb", "OA of 1st carpometacarpal joint → 'squaring' of the thumb base"),
("Genu varum", "Medial compartment OA → bow-leg deformity (most common knee deformity in OA)"),
("Genu valgum", "Lateral compartment OA → knock-knee deformity"),
("Crepitus on palpation","Palpable/audible crepitus on passive movement"),
("Joint tenderness", "Bony tenderness at joint margins, osteophyte sites"),
("Effusion", "Joint effusion (mild, cool — unlike hot effusion of RA/septic arthritis)"),
("Restricted ROM", "Loss of full range of motion; flexion contracture in advanced disease"),
("Muscle wasting", "Quadriceps wasting in knee OA; hip abductor wasting in hip OA"),
("No lymphadenopathy", "No regional lymphadenopathy — unlike inflammatory arthritides"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col(rows)]))
story.append(Spacer(1,3*mm))
# Card 8 — Joints Involved
h = card_header("08", "Joints Commonly Involved", TEAL)
rows = [
("KNEE", "Most commonly affected large joint — medial compartment (varus) most often"),
("HIP", "Superior joint space narrowing most common; pain in groin/thigh/knee; Trendelenburg gait"),
("LUMBAR SPINE", "Facet joint OA + intervertebral disc degeneration + osteophytes → spinal stenosis, radiculopathy"),
("CERVICAL SPINE", "Osteophytes impinge on foramina → cervical radiculopathy; 'spondylosis'"),
("DIP JOINTS (hands)", "Heberden nodes — bony osteophytes; most characteristic hand finding"),
("PIP JOINTS (hands)", "Bouchard nodes — less common than DIP involvement"),
("1st CMC joint", "Carpometacarpal OA → squaring of thumb, pain with pinch grip ('base of thumb arthritis')"),
("1st MTP joint (foot)","First metatarsophalangeal joint — hallux rigidus (stiff big toe)"),
("JOINTS SPARED", "Wrist, elbow, ankle, shoulder — SPARED in primary OA (involvement = suspect secondary OA)"),
("Remember", "MCP, wrist, ankle OA → think SECONDARY cause (trauma, gout, RA, haemochromatosis)"),
]
m = mnem_box("Joints involved: 'HIKE COLD' — Hip, IP joints (DIP>PIP), Knee, Every lumbar vertebra, Cervical spine, 1st CMC, 1st MTP Digit | Spared: Wrist, Elbow, Ankle, Shoulder (WEAS)")
story.append(KeepTogether([h, Spacer(1,1), two_col(rows, bg1=LIGHT_TEAL, bg2=WHITE), Spacer(1,2), m]))
story.append(Spacer(1,3*mm))
# Card 9 — Special Clinical Tests
h = card_header("09", "Special Clinical Tests for Knee & Hip OA", TEAL)
rows = [
("McMurray's test", "Knee — tests for MENISCAL pathology (not specific for OA but important in knee exam)"),
("Anterior/Posterior drawer","Knee — tests ACL/PCL integrity"),
("Valgus/varus stress test","Knee — tests MCL/LCL integrity + compartment loading"),
("Patellar tap / Ballottement","Detects knee effusion"),
("Bulge sign", "Detects small effusion in knee"),
("Thomas test", "Hip — detects fixed flexion deformity (FFD) of hip"),
("Trendelenburg test", "Hip — positive in hip OA with abductor weakness (gluteus medius)"),
("FABER test", "Hip — Flexion ABduction External Rotation → hip/SI joint pathology"),
("FADIR test", "Hip — Flexion ADduction Internal Rotation → impingement"),
("Leg length", "Measure for true vs apparent shortening in hip OA"),
("Gait assessment", "Antalgic gait (OA), Trendelenburg gait (hip abductor weakness)"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col(rows, bg1=LIGHT_TEAL, bg2=WHITE)]))
story.append(Spacer(1,4*mm))
# ══════════ SECTION 3: INVESTIGATIONS ════════════════════════════════════════
story.append(PageBreak())
story.append(section_div("SECTION 3: INVESTIGATIONS",
"X-ray • Lab • MRI • Kellgren-Lawrence Grading", TEAL))
story.append(Spacer(1,3*mm))
# Card 10 — X-ray Features (HIGH YIELD)
h = card_header("10", "Radiological Features of OA — X-ray (HIGH YIELD!)", TEAL)
rows = [
("1. Joint space narrowing","Asymmetric — affects area of maximal stress (e.g., medial knee compartment in varus, superior hip). Key distinguishing feature vs other arthropathies"),
("2. Osteophytes", "New bone at joint margins; also subchondral osteophytes. Most important DIAGNOSTIC feature of OA on X-ray"),
("3. Subchondral sclerosis","Increased bone density/whiteness adjacent to joint — due to osteoblast activity"),
("4. Subchondral cysts", "Radiolucent areas (dark) near joint — synovial fluid enters bone through damaged cartilage (ball-valve mechanism)"),
("5. Loose bodies (joint mice)","Radio-opaque fragments in joint space; may calcify and enlarge"),
("6. Deformity/subluxation","Bony collapse + ligamentous laxity → joint deformity; varus/valgus at knee; superior migration of femoral head at hip"),
("7. Loss of joint alignment","Progressive malignment — varus knee most common in OA"),
("Hip X-ray findings", "Superior joint space narrowing (most common); subchondral cysts (black arrows); osteophytes around femoral head/neck junction"),
("Correlation with pain", "POOR — radiographic severity does NOT correlate well with pain and disability (key exam point!)"),
("Weight-bearing views", "X-rays should be taken WEIGHT-BEARING to show true joint space narrowing"),
]
m = mnem_box("X-ray mnemonic: LOSS — Loss of joint space (asymmetric) | Osteophytes | Subchondral Sclerosis | Subchondral cysts (+ loose bodies + deformity)")
w = warn_box("⚠ Key exam fact: Radiographic severity DOES NOT correlate with pain and disability in OA!")
story.append(KeepTogether([h, Spacer(1,1), two_col(rows, bg1=LIGHT_TEAL, bg2=WHITE), Spacer(1,2), m, Spacer(1,2), w]))
story.append(Spacer(1,3*mm))
# Card 11 — Kellgren-Lawrence Grading
h = card_header("11", "Kellgren-Lawrence (K-L) Grading System", TEAL)
rows = [
("Grade 0", "Normal — no features of OA"),
("Grade 1", "Doubtful narrowing of joint space AND possible osteophytic lipping (marginal)"),
("Grade 2", "Definite osteophytes AND possible joint space narrowing"),
("Grade 3", "Moderate multiple osteophytes + DEFINITE narrowing of joint space + some sclerosis + possible deformity of bone ends"),
("Grade 4", "LARGE osteophytes + MARKED narrowing of joint space + SEVERE sclerosis + DEFINITE deformity of bone ends"),
("Used for", "Standardised radiological assessment/staging of OA; originally described for hip OA"),
("Surgical relevance", "Grade 3-4 typically indicates surgical intervention (osteotomy/arthroplasty) consideration"),
]
t2 = tip_box("Tip: K-L Grade 0 = Normal | Grade 1-2 = Mild-moderate | Grade 3-4 = Severe. Grade 4 = usually indicates joint replacement.")
story.append(KeepTogether([h, Spacer(1,1), two_col(rows, bg1=LIGHT_TEAL, bg2=WHITE), Spacer(1,2), t2]))
story.append(Spacer(1,3*mm))
# Card 12 — Laboratory & Other Investigations
h = card_header("12", "Laboratory & Other Investigations", MED_GREEN)
rows = [
("Blood tests", "NORMAL in primary OA — ESR, CRP, RF, ANA all NEGATIVE/NORMAL"),
("ESR/CRP", "Normal or mildly elevated (low-grade synovitis). Markedly elevated → suspect RA or septic arthritis"),
("Rheumatoid factor", "NEGATIVE in OA (positive in RA, SLE)"),
("Serum uric acid", "Check to exclude gout (important differential)"),
("Synovial fluid", "Non-inflammatory: WBC <2000 cells/μL, clear/viscous, good mucin clot; distinguishes from RA (inflammatory) and septic arthritis (purulent)"),
("MRI", "Best for early cartilage assessment; shows bone marrow oedema, meniscal tears, ligament damage; not routine but valuable"),
("CT scan", "Bony detail; used pre-operatively for complex cases"),
("Bone scan (scintigraphy)","Increased uptake in OA; useful when X-ray inconclusive"),
("Arthroscopy", "Gold standard for direct cartilage visualisation; also therapeutic (washout/debridement)"),
("Dual-energy X-ray absorptiometry (DEXA)","If osteoporosis suspected concurrently"),
]
w = warn_box("Remember: ALL blood tests are NORMAL in primary OA. If RF is positive or ESR/CRP is markedly elevated → reconsider diagnosis!")
story.append(KeepTogether([h, Spacer(1,1), two_col(rows), Spacer(1,2), w]))
story.append(Spacer(1,4*mm))
# ══════════ SECTION 4: MANAGEMENT ════════════════════════════════════════════
story.append(PageBreak())
story.append(section_div("SECTION 4: MANAGEMENT",
"Conservative • Pharmacological • Surgical", DARK_GREEN))
story.append(Spacer(1,3*mm))
# Card 13 — Non-pharmacological Management
h = card_header("13", "Non-Pharmacological Management (First Line!)", MED_GREEN)
rows = [
("Patient education", "Understanding disease, realistic expectations, self-management strategies"),
("Weight reduction", "MOST IMPORTANT modifiable risk factor — obesity mechanically overloads joints; BMI reduction reduces OA progression (Mendelian randomization evidence)"),
("Exercise therapy", "CORNERSTONE of management — low-impact aerobic exercise (swimming, cycling), quadriceps strengthening for knee OA, range of motion exercises"),
("Physical therapy", "Structured physiotherapy — strengthening, proprioception, gait training"),
("Occupational therapy","Splints, assistive devices, activity modification, joint protection techniques"),
("Walking aids", "Cane/walking stick in CONTRALATERAL hand reduces hip/knee loading by ~50%; crutches, walker"),
("Knee bracing", "Unloader braces for unicompartmental knee OA (medial compartment → lateral wedge insole)"),
("TENS / Heat/Cold", "Transcutaneous electrical nerve stimulation; heat for chronic pain; cold for acute flares"),
("Orthotic insoles", "Lateral wedge insoles for medial compartment knee OA (reduces varus load)"),
("Activity modification","Avoid high-impact activities; balance rest and exercise"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col(rows)]))
story.append(Spacer(1,3*mm))
# Card 14 — Pharmacological Management
h = card_header("14", "Pharmacological Management", MED_GREEN)
rows = [
("Paracetamol (acetaminophen)","First-line oral analgesic for mild-moderate OA pain; safe, well-tolerated"),
("NSAIDs (oral)", "Ibuprofen, diclofenac, naproxen — effective anti-inflammatory + analgesic; use lowest effective dose; GI protection with PPI in elderly"),
("Topical NSAIDs", "Topical diclofenac gel — preferred in elderly (fewer systemic side effects); first-line for knee/hand OA per NICE guidelines"),
("Topical capsaicin", "Applied to affected joints (knee/hand OA); depletes substance P; burning sensation initially"),
("Intra-articular corticosteroids","Short-term (weeks) relief for acute flares with effusion; NOT for long-term repeated use (cartilage damage with repeated injections)"),
("Intra-articular hyaluronic acid","Viscosupplementation (Synvisc, Hyalgan) — lubricates joint; modest benefit; use when steroids fail"),
("Duloxetine (SNRI)", "For chronic OA pain with central sensitisation; useful when NSAIDs contraindicated"),
("Opioids", "Low-dose opioids for severe pain unresponsive to other therapy; high caution in elderly (falls risk)"),
("Glucosamine/Chondroitin","Conflicting evidence; not recommended in current NICE/OARSI guidelines as disease-modifying agents"),
("No DMARDs", "No disease-modifying drugs (DMARDs) exist for OA (unlike RA)"),
]
m = mnem_box("Pharmacology stepladder: Paracetamol → Topical NSAIDs → Oral NSAIDs → Intra-articular steroids → IA hyaluronate → Duloxetine/Opioids → Surgery")
story.append(KeepTogether([h, Spacer(1,1), two_col(rows), Spacer(1,2), m]))
story.append(Spacer(1,3*mm))
# Card 15 — Surgical Management
h = card_header("15", "Surgical Management", RED_DARK)
rows = [
("Arthroscopic lavage/debridement","Washout of joint; cartilage shaving; removal of loose bodies; mechanical symptoms (locking). Controversial evidence for pain relief alone"),
("High Tibial Osteotomy (HTO)","For UNICOMPARTMENTAL knee OA with varus deformity in younger, active patients; 'unloads' medial compartment; ~80% satisfactory at 5 years"),
("Distal Femoral Osteotomy (DFO)","For valgus deformity (lateral compartment OA); redirects load to unaffected medial compartment"),
("Unicompartmental Knee Arthroplasty (UKA)","Single compartment replacement; unicompartmental OA; faster recovery; preserves bone; requires intact ACL"),
("Total Knee Replacement (TKR/TKA)","Gold standard for tricompartmental knee OA; >90% survive 15+ years; 90° flexion expected post-op"),
("Total Hip Replacement (THR/THA)","Gold standard for hip OA; 95-97% 10-year survival; restores near-normal function; approaches: posterior (most common), anterior, lateral (Hardinge)"),
("Hip resurfacing", "Preserves femoral head; younger active patients with hip OA; requires good bone quality"),
("Arthrodesis (joint fusion)","Last resort; eliminates pain but sacrifices motion; used in ankle, wrist, small joints when replacement not viable"),
("Indications for arthroplasty","Failed conservative + pharmacological management; pain interfering with ADLs; K-L Grade 3-4; patient preference"),
]
t2 = tip_box("Hip replacement approaches: Posterior (Southern) = most common, highest exposure but slightly higher dislocation rate; Anterior (Smith-Petersen) = muscle-sparing, rapid recovery but difficult femoral exposure; Lateral (Hardinge) = good exposure but abductor damage risk (limp).")
story.append(KeepTogether([h, Spacer(1,1), two_col(rows, bg1=LIGHT_RED, bg2=WHITE), Spacer(1,2), t2]))
story.append(Spacer(1,3*mm))
# Card 16 — Osteotomy Details
h = card_header("16", "Osteotomy — Indications & Contraindications", TEAL)
rows = [
("Indications", "Pain/disability from OA interfering with high-demand employment or recreation; Unicompartmental OA with corresponding varus/valgus; Isolated medial compartment pain in varus knee; Patient able to rehabilitate post-op"),
("Contraindications", "Lateral compartment cartilage narrowing; Tibial subluxation >1 cm; Flexion contracture >15°; Knee flexion <90°; Correction needed >20°; Inflammatory arthritis; Significant peripheral vascular disease; Bone loss >2-3 mm"),
("HTO technique", "Lateral closing wedge (Coventry) — longest track record; or medial opening wedge; dome; hemicallotasis"),
("Rationale", "Unload affected compartment by correcting malalignment → redistributes stress; fibrocartilaginous repair may occur"),
("Results", "~80% satisfactory at 5 years; ~60% at 10 years; results deteriorate over time"),
("Best candidates", "Young (<60), active, unicompartmental disease, minimal flexion deformity, good muscle strength"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col(rows, bg1=LIGHT_TEAL, bg2=WHITE)]))
story.append(Spacer(1,4*mm))
# ══════════ SECTION 5: HIGH YIELD ════════════════════════════════════════════
story.append(PageBreak())
story.append(section_div("SECTION 5: HIGH YIELD REVIEW",
"Rapid Fire • Viva Q&A • Mnemonics • Differentials", DARK_GREEN))
story.append(Spacer(1,3*mm))
# Card 17 — Risk Factors
h = card_header("17", "Risk Factors for OA", MED_GREEN)
rows = [
("Age", "Strongest non-modifiable risk factor — exponential increase after 50"),
("Sex", "Primary OA more common in women, especially post-menopausal"),
("Obesity / BMI", "MOST IMPORTANT modifiable risk factor; mechanical overload + metabolic effects (adipokines); causal link proven by Mendelian randomization"),
("Previous joint injury","Meniscal tear, ligament injury, fractures through joint → secondary OA"),
("Occupation", "Repetitive occupational loading (knee bending, heavy lifting) → knee/hip OA"),
("Sports history", "High-impact sports (running, football) → knee OA risk in predisposed individuals"),
("Genetics", "Heritability ~50-65%; GDF5 (chromosome 20), RUNX2, SMAD3 loci identified"),
("Joint deformity", "Developmental dysplasia of hip, varus/valgus knee → secondary OA"),
("Muscle weakness", "Quadriceps weakness → reduced knee stability → accelerates OA"),
("Low vitamin D", "Possible association with OA progression (controversial)"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col(rows)]))
story.append(Spacer(1,3*mm))
# Card 18 — Differentials
h = card_header("18", "Differential Diagnosis", RED_DARK)
headers = ["Condition", "Key Differentiating Feature"]
rows2 = [
("Rheumatoid Arthritis", "Morning stiffness >1 hour; small joints (MCP, PIP); systemic; RF/ACPA positive; bilateral symmetric"),
("Gout", "Acute attacks (podagra — 1st MTP); uric acid crystals; hyperuricaemia; negatively birefringent crystals on joint aspirate"),
("Pseudogout (CPPD)", "Calcium pyrophosphate crystals; chondrocalcinosis on X-ray; knee most common; weakly positive birefringent"),
("Psoriatic Arthritis", "Skin psoriasis; DIP involvement; sausage digits; 'pencil-in-cup' X-ray"),
("Ankylosing Spondylitis","Young male; low back pain; sacroiliac involvement; HLA-B27; bamboo spine X-ray"),
("Septic Arthritis", "Acute onset; hot, red, swollen joint; fever; raised WBC, CRP; joint aspirate purulent"),
("Avascular Necrosis", "Hip/shoulder; corticosteroid use/alcohol; subchondral collapse without initial joint space loss"),
("Reactive Arthritis", "Follows GI/GU infection; asymmetric large joints; young adults; associated conjunctivitis, urethritis"),
("Polymyalgia Rheumatica","Elderly; shoulder and hip girdle pain/stiffness; very high ESR; dramatic response to steroids"),
]
story.append(KeepTogether([h, Spacer(1,1),
three_col(["Condition", "Key Differentiating Feature",""], rows2, [55*mm, None, 0])
]))
story.append(Spacer(1,3*mm))
# Card 19 — Mnemonics rapid fire
h = card_header("19", "All Mnemonics & Memory Aids", AMBER)
rows = [
("X-ray: LOSS", "Loss of joint space (asymmetric) | Osteophytes | Subchondral Sclerosis | Subchondral cysts"),
("Histology: 4F's", "Fibrillation | Fragmentation | Full thickness loss | Free loose bodies (joint mice)"),
("Joints spared: WEAS","Wrist, Elbow, Ankle, Shoulder — involvement suggests SECONDARY OA"),
("OA vs RA stiffness", "OA = <30 min | RA = >1 hour"),
("Heberden = DIP", "Heberden has a D in it = DIP joint nodes"),
("Bouchard = PIP", "Bouchard = B = Better/higher up = PIP joint"),
("K-L Grade 4", "4 = FOUR features: Large osteophytes + Marked JSN + Severe sclerosis + Definite deformity"),
("Walking stick rule", "Stick in OPPOSITE hand = reduces joint load by ~50%"),
("HTO candidates", "Young, Unicompartmental, Varus, Active, No fixed flexion — 'YUVAN'"),
("No DMARDs in OA", "OA has NO disease-modifying drugs — only symptomatic treatment + surgery"),
("Lab tests in OA", "All NORMAL — RF neg, ANA neg, ESR/CRP normal/mildly elevated"),
("Varus = medial OA", "Varus deformity → medial compartment overloaded → medial OA"),
("Valgus = lateral OA","Valgus deformity → lateral compartment overloaded → lateral OA"),
]
story.append(KeepTogether([h, Spacer(1,1), two_col(rows, bg1=LIGHT_AMBER, bg2=WHITE)]))
story.append(Spacer(1,3*mm))
# Card 20 — Viva Q&A
h = card_header("20", "Likely Examiner Questions — Viva Points", AMBER)
q_data = [
[Paragraph("QUESTION", S("qh", fontName="Helvetica-Bold", fontSize=8.5, textColor=WHITE)),
Paragraph("IDEAL ANSWER", S("qh", fontName="Helvetica-Bold", fontSize=8.5, textColor=WHITE))],
[c("What is osteoarthritis?"), c("Degenerative joint disease; degeneration of articular cartilage → structural/functional failure of synovial joints; most common joint disease")],
[c("What are the X-ray findings?"), c("LOSS: Loss of joint space (asymmetric) + Osteophytes + Subchondral Sclerosis + Subchondral cysts; also deformity, loose bodies")],
[c("How does OA pain differ from RA?"), c("OA: pain worsens with activity, improves with rest, morning stiffness <30 min; RA: worst in morning/at rest, stiffness >1 hour")],
[c("What is Kellgren-Lawrence Grade 4?"), c("Large osteophytes + marked joint space narrowing + severe sclerosis + definite deformity of bone ends")],
[c("What are Heberden nodes?"), c("Bony osteophytes at DIP joints; characteristic of hand OA; more common in women")],
[c("All blood tests in OA?"), c("NORMAL — RF negative, ANA negative, ESR and CRP normal or mildly elevated; no serum antibodies")],
[c("First-line treatment for knee OA?"), c("Non-pharmacological: weight loss, physiotherapy, exercise; Pharmacological: paracetamol, topical NSAIDs")],
[c("Indication for High Tibial Osteotomy?"), c("Young patient, unicompartmental knee OA, varus deformity, high-demand activity, no fixed flexion contracture >15°")],
[c("Indication for Total Knee Replacement?"), c("Tricompartmental OA, failed conservative/pharmacological management, pain interfering with ADLs, K-L Grade 3-4")],
[c("Most common deformity in knee OA?"), c("Genu varum (varus) — medial compartment most commonly affected")],
[c("Hip OA on X-ray — what finding?"), c("Superior joint space narrowing (most common) + subchondral cysts + osteophytes at femoral head/neck junction")],
[c("Does X-ray severity correlate with pain?"), c("NO — poor correlation between radiographic severity and pain/disability in OA (key exam fact)")],
[c("Which walking stick hand?"), c("Contralateral hand to affected joint — reduces joint reaction force by ~50%")],
[c("What is joint mouse?"), c("Loose body in joint — fragment of cartilage/bone that dislodged; can cause mechanical locking")],
[c("Bone eburnation?"), c("Polished ivory appearance of exposed subchondral bone when overlying cartilage is completely lost; friction from opposing surface")],
]
q_table = Table(q_data, colWidths=[80*mm, None])
q_table.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), AMBER),
("VALIGN", (0,0),(-1,-1),"TOP"),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 5),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LINEBELOW", (0,0),(-1,-2), 0.3, GRAY_LINE),
]))
for i in range(1, len(q_data)):
bg = LIGHT_AMBER if i % 2 == 1 else WHITE
q_table.setStyle(TableStyle([("BACKGROUND",(0,i),(-1,i), bg)]))
story.append(KeepTogether([h, Spacer(1,1), q_table]))
story.append(Spacer(1,4*mm))
# Footer
footer = Table([[Paragraph(
"Sources: Robbins & Kumar Basic Pathology | Grainger & Allison's Diagnostic Radiology | Miller's Review of Orthopaedics | Campbell's Operative Orthopaedics | Firestein & Kelley's Rheumatology • Orris Medical AI",
S("ft", fontName="Helvetica-Oblique", fontSize=7.5,
textColor=colors.HexColor("#999"), alignment=TA_CENTER)
)]], colWidths=[None])
footer.setStyle(TableStyle([
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LINEABOVE", (0,0),(-1,-1), 0.5, GRAY_LINE),
]))
story.append(footer)
# ── Build ──────────────────────────────────────────────────────────────────────
doc.build(story)
print("OA Flashcards PDF created successfully!")
cd /home/daytona/workspace/ent-flashcards && python create_oa_flashcards.py 2>&1
~/ent-flashcards/create_oa_flashcards.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ent-flashcards/create_oa_flashcards.py'
cd /home/daytona/workspace/ent-flashcards && python create_oa_flashcards.py 2>&1
ls -lh /home/daytona/workspace/ent-flashcards/Osteoarthritis_Flashcards.pdf
Osteoarthritis Flashcards
PDF Document · PDF
I cannot able to download the PDF of osteoarthritis. Can you please check?
ls -lh /home/daytona/workspace/ent-flashcards/ && file /home/daytona/workspace/e…
cp /home/daytona/workspace/ent-flashcards/Osteoarthritis_Flashcards.pdf /home/da…
Osteoarthritis Flashcards
PDF Document · PDF
Osteoarthritis Flashcards
PDF Document · PDF
Thank you ❤️