Hi ima final year mbbs student

Hey! Welcome. Final year MBBS is intense but exciting - you're almost there!
What can I help you with today? Whether it's clinical topics, exam prep, case discussions, pharmacology, or anything else - just ask!

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.

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
I now have enough material from authoritative ENT textbooks (Shambaugh Surgery of the Ear + Scott-Brown's) to create the comprehensive flashcards. Let me compile everything now.

🎯 ENT ENDOSCOPY PREP - FLASHCARD MASTER NOTES

TOPIC 1: CSOM - MUCOSAL TYPE (Tubotympanic / Safe Type)


🃏 CARD 1 - DEFINITION & CLASSIFICATION

Full nameChronic Suppurative Otitis Media
DefinitionChronic inflammation of middle ear + mastoid with persistent tympanic membrane perforation + discharge for >6 weeks
TypesMucosal (Tubotympanic / SAFE type) vs Squamous (Atticoantral / UNSAFE type)
Why "safe"?Mucosal type - no cholesteatoma, no bone erosion typically
Perforation siteCentral (pars tensa) - doesn't touch annulus

🃏 CARD 2 - ETIOLOGY & PATHOGENESIS

FactorDetails
Primary causeEustachian tube dysfunction
SequenceET dysfunction → persistent middle ear effusion → mucosal edema → granulation tissue → bacterial infection → chronic mucosal change
Key pathologyMucosa develops submucosal glands → secretory mucosa → perpetuates effusion
TM changesCollagen skeleton broken down by enzymes → weakening → retraction pockets → perforation
OrganismsPseudomonas aeruginosa (most common in chronic), Staphylococcus aureus, Proteus, Gram-negatives, anaerobes

🃏 CARD 3 - CLINICAL FEATURES (High Yield!)

SymptomDetails
OtorrheaIntermittent, mucoid/mucopurulent, NOT foul-smelling (no cholesteatoma)
Hearing lossConductive hearing loss (CHL) - most common presenting complaint
OtalgiaUNCOMMON - if present, suspect intracranial complication or malignancy
VertigoUNCOMMON - if present, suspect labyrinthine fistula
TinnitusMay be present
HeadacheIf present → red flag for intracranial involvement
EXAMINER TIP: Otalgia + headache in CSOM = suspect complications, NOT routine disease

🃏 CARD 4 - OTOSCOPIC FINDINGS (Critical for Endoscopy!)

FindingMucosal Type
PerforationCentral, pars tensa - safe perforation
PositionDoes NOT involve the annulus
DischargeMucoid/mucopurulent (not offensive)
Middle ear mucosaPale/edematous, may have granulation tissue
CholesteatomaABSENT (key differentiator from squamosal type)
OssiclesUsually intact (visible through perforation)
PolypsMay be present in EAC
Scutal erosionAbsent

🃏 CARD 5 - COMPARISON: MUCOSAL vs SQUAMOSAL TYPE

FeatureMucosal (Safe)Squamosal (Unsafe)
TypeTubotympanicAtticoantral
PerforationCentral, pars tensaMarginal / attic (pars flaccida)
DischargeMucoid, odourlessScanty, FOUL-smelling
CholesteatomaABSENTPRESENT
Bone erosionNoYes
ComplicationsLess commonCommon (facial palsy, meningitis)
DangerSAFEUNSAFE
SurgeryTympanoplastyMastoidectomy

🃏 CARD 6 - INVESTIGATIONS

InvestigationFinding
Tuning fork (Rinne)Rinne negative (BC > AC)
Tuning fork (Weber)Lateralizes to affected (worse) ear
Pure Tone AudiogramConductive hearing loss; CHL >30 dB → suspect ossicular erosion
HRCT temporal boneGold standard for assessing extent; identifies cholesteatoma, ossicular erosion, complications
Ear swab C&SGuides antibiotic therapy
SNHLMay coexist (5-33 dB range seen in studies)

🃏 CARD 7 - COMPLICATIONS

Intratemporal:
  • Mastoiditis (most common)
  • Labyrinthitis → SNHL/vertigo
  • Facial nerve palsy
  • Tympanosclerosis
  • Ossicular erosion
  • Petrositis (Gradenigo's syndrome: trigeminal pain + 6th nerve palsy + otorrhea)
Intracranial:
  • Meningitis (most common intracranial complication)
  • Brain abscess (temporal lobe or cerebellar)
  • Lateral sinus thrombosis
  • Extradural/subdural abscess
  • Otitic hydrocephalus
MNEMONIC for intracranial complications: "My Brain Loves Extra Oxygen" - Meningitis, Brain abscess, Lateral sinus thrombosis, Extradural abscess, Otitic hydrocephalus

🃏 CARD 8 - TREATMENT

Medical (first line for mucosal type):
  • Aural toilet (dry mopping) - most important step
  • Ototopical antibiotics (ciprofloxacin ear drops - preferred; avoid aminoglycosides - ototoxic)
  • Systemic antibiotics guided by C&S
  • Treat underlying ET dysfunction / upper respiratory infections
  • Keep ear dry
Surgical Indications:
  • Failed medical treatment (multiple attempts)
  • Symptoms suspicious of complications (vertigo, facial weakness, headache)
  • Persistent perforation after dry ear achieved
  • Significant hearing loss
  • Cholesteatoma (nearly absolute indication)
Surgical Options:
  • Myringoplasty - repair of TM perforation alone (Type I tympanoplasty)
  • Tympanoplasty - TM repair + ossiculoplasty
  • Cortical mastoidectomy - for mastoid disease without cholesteatoma
  • Modified radical mastoidectomy - for cholesteatoma with intact ossicles
  • Radical mastoidectomy - for extensive cholesteatoma
CWU vs CWD mastoidectomy:
  • Canal Wall Up (CWU): preserves posterior EAC wall; better cosmesis; requires second-look surgery
  • Canal Wall Down (CWD): removes posterior EAC wall; better access; open cavity needs lifelong follow-up but lower recurrence


TOPIC 2: CHRONIC TONSILLITIS


🃏 CARD 9 - DEFINITION & ANATOMY

Tonsil ringWaldeyer's ring = palatine tonsils + pharyngeal tonsil (adenoid) + lingual tonsil + tubal tonsils
Palatine tonsilsBetween anterior (palatoglossal) and posterior (palatopharyngeal) pillars
Blood supplyTonsillar branch of facial artery (main), also from lingual, ascending pharyngeal, ascending palatine, descending palatine
Dangerous poleUpper pole - close to internal carotid artery
Lymph drainageJugulodigastric (tonsillar) node - Level II
Nerve supplyGlossopharyngeal nerve (IX) + lesser palatine nerve
ImmunologyB-cell predominant organ; part of MALT (mucosa-associated lymphoid tissue)

🃏 CARD 10 - CHRONIC TONSILLITIS - CLINICAL FEATURES

FeatureDetails
DefinitionPersistent/recurrent tonsillar infection with chronic inflammation
SymptomsChronic throat discomfort, foreign body sensation, halitosis (bad breath), dysphagia
Key signSmelly white debris (caseous material) from tonsillar crypts = tonsilloliths
Tonsillar sizeMay be small (fibrosed) or enlarged
On examinationDull, congested tonsils with scarred surface, crypts filled with debris; enlarged jugulodigastric nodes
Pus expressionPus/debris can be expressed from crypts on pressure (Irwin Moore sign)
Recurrent attacksHistory of recurrent acute tonsillitis episodes

🃏 CARD 11 - BACTERIOLOGY

OrganismNotes
GABHSGroup A Beta-Haemolytic Streptococcus - most important
S. aureusCommon
H. influenzaeCommon
AnaerobesImportant in chronic/cryptic tonsillitis and peritonsillar abscess
VirusesEBV, Adenovirus, Herpes simplex (trigger acute episodes)
SynergyViruses sensitize bacteria in tonsillar crypts

🃏 CARD 12 - GRADING OF TONSIL SIZE (Brodsky Scale)

GradeDescription
0Tonsils 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")

🃏 CARD 13 - INDICATIONS FOR TONSILLECTOMY (Paradise Criteria - most used)

Recurrent tonsillitis criteria:
  • 7+ episodes in 1 year, OR
  • 5+ episodes/year for 2 consecutive years, OR
  • 3+ episodes/year for 3 consecutive years
Other indications:
  • Obstructive sleep apnoea / upper airway obstruction
  • Peritonsillar abscess (quinsy) - after 2nd episode or contralateral first episode
  • Suspicion of malignancy (asymmetric, ulcerated tonsil)
  • Febrile seizures from recurrent tonsillitis
  • Streptococcal carrier state
  • Cor pulmonale from airway obstruction
  • Diphtheria carrier state
  • Tonsillolith formation with symptoms
  • Psoriasis (debatable)

🃏 CARD 14 - PERITONSILLAR ABSCESS (QUINSY) - High Yield Complication

FeatureDetails
DefinitionPus collection in peritonsillar space (between tonsil capsule and superior constrictor)
SiteUPPER pole of tonsil (supratonsillar space)
Classic symptomsUnilateral severe throat pain, odynophagia, trismus (hallmark!), muffled "hot potato" voice, ipsilateral ear pain
Exam findingsBulging soft palate, uvula deviated to opposite side, tonsil pushed medially
TreatmentNeedle aspiration (first choice) or incision & drainage + IV antibiotics (high-dose penicillin/cephalosporin)
Interval tonsillectomyIndicated after 2nd quinsy or background recurrent tonsillitis
"Hot" tonsillectomyDuring acute phase - controversial, risk of aspiration

🃏 CARD 15 - COMPLICATIONS OF CHRONIC/ACUTE TONSILLITIS

Local/Regional:
  • Peritonsillar abscess (quinsy) - most common
  • Parapharyngeal abscess
  • Retropharyngeal abscess (mainly children <5 years)
  • Otitis media (via Eustachian tube)
  • Sinusitis
Systemic (Immunological - from GABHS):
  • Acute Rheumatic Fever (ARF)
  • Acute Glomerulonephritis (AGN)
  • Reactive arthritis
Rare but dangerous:
  • Lemierre's Syndrome - septic thrombophlebitis of internal jugular vein; organism = Fusobacterium necrophorum (fusiform bacillus); treat with penicillin + metronidazole x 6 weeks
  • Deep neck space infections → mediastinitis

🃏 CARD 16 - TONSILLECTOMY SURGICAL DETAILS

AspectDetails
TechniqueDissection + snare (classical), coblation, cold steel, diathermy
AnaesthesiaGeneral anaesthesia (GA)
PositionRose position (neck extended, mouth open with Boyle-Davis gag)
PlaneBetween tonsil capsule and superior constrictor muscle
Post-op bleedPrimary (<24h) vs Secondary (5-10 days, reactionary - from infection)
DangerInternal carotid artery lateral to upper pole
Post-op dietSoft diet, good hydration
ContraindicationsCleft palate (velopharyngeal insufficiency), bleeding disorders (relative), acute infection (relative)

🃏 CARD 17 - INFECTIOUS MONONUCLEOSIS (Must Know Differential!)

FeatureDetails
OrganismEpstein-Barr virus (EBV)
PresentationSore throat + massive tonsillar enlargement + cervical lymphadenopathy + fever + splenomegaly
Blood filmAtypical lymphocytes (Downey cells)
TestPaul-Bunnell (Monospot) test - sensitivity 50% in children, 70-90% in adults
Key warningNEVER give Ampicillin/Amoxicillin → causes maculopapular rash in >90% cases
TreatmentSupportive; steroids if airway compromise; avoid contact sports (splenic rupture)
Secondary infection30% get bacterial superinfection; use penicillin/cephalosporin
AcyclovirDebatable; only for severe cases

🔥 RAPID FIRE MNEMONICS

MnemonicMeaning
CSOM Safe = CentralMucosal type = Central perforation = Safe
CSOM Unsafe = Upper/MarginalSquamosal 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

⚡ LIKELY ENDOSCOPY QUESTIONS FROM YOUR EXAMINER

For CSOM:
  1. Where is the perforation? → Central, pars tensa
  2. Is it safe or unsafe? → Safe (mucosal/tubotympanic)
  3. Why no cholesteatoma? → Central perforation, no involvement of pars flaccida/attic
  4. What is the discharge like? → Mucoid, non-foul smelling
  5. What investigation will you do? → PTA (audiometry), HRCT temporal bone, C&S of discharge
  6. What hearing loss? → Conductive; CHL >30 dB → suspect ossicular erosion
  7. How will you treat? → Aural toilet + ototopical ciprofloxacin drops; surgery if fails
  8. Surgery for perforation? → Myringoplasty / Tympanoplasty
For Chronic Tonsillitis:
  1. What are you seeing on examination? → Congested tonsils, cryptic debris, enlarged jugulodigastric nodes
  2. How do you confirm chronic tonsillitis? → History + clinical exam (no single gold standard test)
  3. When will you operate? → Paradise criteria (7/5/3 rule) + obstruction + quinsy
  4. What complication are you most worried about from GABHS? → Rheumatic fever, glomerulonephritis
  5. Patient has trismus + bulging palate → Peritonsillar abscess; management?
  6. Why not give Amoxicillin in suspected EBV tonsillitis? → Rash in >90%

All the best for tomorrow! You've got this. Sources: Shambaugh Surgery of the Ear and Scott-Brown's Otorhinolaryngology Head & Neck Surgery (via Orris medical library).

Create a PDF version of these flashcards for quick revision

Reading File
Running Command

ls /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace/

Writing File

~/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!")
Running Command

cd /home/daytona/workspace/ent-flashcards && python create_flashcards.py 2>&1

Running Command

ls -lh /home/daytona/workspace/ent-flashcards/ENT_Flashcards_CSOM_Tonsillitis.pd…

Your PDF is ready! Download it here:

ENT Flashcards CSOM Tonsillitis

PDF Document · PDF

Here's what's inside the PDF:
Section 1 - CSOM Mucosal Type (Cards 1-8)
  • Definition & Classification
  • Etiology & Pathogenesis
  • Clinical Features (with red flag warning box)
  • Otoscopic/Endoscopic Findings
  • SAFE vs UNSAFE comparison table (colour-coded)
  • Investigations
  • Complications (intratemporal vs intracranial split table)
  • Treatment (medical + surgical)
Section 2 - Chronic Tonsillitis (Cards 9-17)
  • Waldeyer's ring & tonsil anatomy
  • Chronic tonsillitis clinical features
  • Bacteriology
  • Brodsky grading scale
  • Tonsillectomy indications (Paradise criteria)
  • Peritonsillar abscess/quinsy
  • Complications (3-column layout)
  • Tonsillectomy surgical details
  • Infectious mononucleosis (with the "no amoxicillin" warning box)
Rapid Fire Review Section
  • All mnemonics collected in one place
  • 16-question viva Q&A table with ideal answers
Good luck tomorrow - you've got this!

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?

Reading File
Finding Sources
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Finding Sources
Reading File
Finding Sources
Finding Sources
Finding Sources
Reading File
Finding Sources
Writing File

~/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!")
Running Command

cd /home/daytona/workspace/ent-flashcards && python create_oa_flashcards.py 2>&1

Editing File

~/ent-flashcards/create_oa_flashcards.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ent-flashcards/create_oa_flashcards.py'
Running Command

cd /home/daytona/workspace/ent-flashcards && python create_oa_flashcards.py 2>&1

Running Command

ls -lh /home/daytona/workspace/ent-flashcards/Osteoarthritis_Flashcards.pdf

Your friend's PDF is ready! Download it here:

Osteoarthritis Flashcards

PDF Document · PDF

Here's what's packed inside (20 flashcards across 5 sections):
Section 1 - Basics & Pathology (Cards 1-5)
  • Definition & overview (most common joint disease, degenerative NOT inflammatory)
  • Primary vs Secondary OA classification with secondary causes table
  • Pathogenesis (biomechanical stress → chondrocyte injury → MMP release → cartilage loss)
  • Morphology - fibrillation, eburnation, joint mice, osteophytes, subchondral cysts
  • OA vs RA full comparison table
Section 2 - Clinical Features (Cards 6-9)
  • Symptoms (pain worse with activity/better with rest - opposite of RA!)
  • Signs (Heberden/Bouchard nodes, genu varum, crepitus, effusion)
  • Joints involved + spared (WEAS = Wrist, Elbow, Ankle, Shoulder = spared)
  • Special clinical tests (Thomas, Trendelenburg, FABER, FADIR, McMurray)
Section 3 - Investigations (Cards 10-12)
  • X-ray LOSS mnemonic with full radiological features
  • Kellgren-Lawrence grading (Grade 0-4 in detail)
  • Lab tests (all NORMAL in primary OA - key exam trap!)
Section 4 - Management (Cards 13-16)
  • Non-pharmacological (weight loss = most important modifiable factor)
  • Pharmacological stepladder
  • Surgical options (HTO, UKA, TKR, THR with approaches)
  • Osteotomy indications/contraindications from Campbell's
Section 5 - High Yield (Cards 17-20)
  • Risk factors, Differentials, All mnemonics, 15-question Viva Q&A
Sources: Robbins Pathology, Grainger & Allison's Radiology, Miller's Review of Orthopaedics, Campbell's Operative Orthopaedics. Tell your friend good luck tomorrow!

I cannot able to download the PDF of osteoarthritis. Can you please check?

Running Command

ls -lh /home/daytona/workspace/ent-flashcards/ && file /home/daytona/workspace/e…

Running Command

cp /home/daytona/workspace/ent-flashcards/Osteoarthritis_Flashcards.pdf /home/da…

The file is confirmed valid (35KB, 11 pages). Try this direct link:

Osteoarthritis Flashcards

PDF Document · PDF

If that still doesn't work, also try the original path:

Osteoarthritis Flashcards

PDF Document · PDF

If neither link is clickable on your end, it may be a browser/client issue rather than the file itself. In that case, let me know and I can:
  1. Convert it to a different format
  2. Recreate it with a different approach (HTML → PDF, etc.)

Thank you ❤️

You're welcome! Best of luck to both of you tomorrow! 🩺
You've got CSOM and Tonsillitis covered, your friend has Osteoarthritis - you're both well prepared. Go get some rest tonight, that's just as important as revision at this point.
If anything comes up before the exam - a concept you want to double-check, a drug dose, a clinical scenario - just ask. You've got this! 💪
This is a shared conversation. Sign in to Orris to start your own chat.