Write about opd cases of ent as many as you now around 30 with prescription and the reason of medicine prescribed and make its pdf for practice and education purpose

Reading File
Running Command

mkdir -p /home/daytona/workspace/ent-opd && ls /home/daytona/workspace/ent-opd/

Writing File

~/ent-opd/ent_30_cases.py

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfgen import canvas
from datetime import date

OUTPUT = "/home/daytona/workspace/ent-opd/ENT_OPD_30_Cases.pdf"

# โ”€โ”€ colour palette โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
NAVY   = colors.HexColor("#1A3557")
TEAL   = colors.HexColor("#0E7C7B")
LBLUE  = colors.HexColor("#D6E8F5")
LGREEN = colors.HexColor("#E6F4EA")
LGREY  = colors.HexColor("#F5F5F5")
WHITE  = colors.white
RED    = colors.HexColor("#C0392B")
ORANGE = colors.HexColor("#E67E22")

# โ”€โ”€ page geometry โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
PAGE_W, PAGE_H = A4
MARGIN = 1.8 * cm

def build_styles():
    S = getSampleStyleSheet()

    S.add(ParagraphStyle("DocTitle",
        fontSize=22, textColor=WHITE, alignment=TA_CENTER,
        fontName="Helvetica-Bold", spaceAfter=4, leading=28))

    S.add(ParagraphStyle("DocSub",
        fontSize=11, textColor=LBLUE, alignment=TA_CENTER,
        fontName="Helvetica", spaceAfter=2, leading=15))

    S.add(ParagraphStyle("CaseTitle",
        fontSize=13, textColor=WHITE, alignment=TA_LEFT,
        fontName="Helvetica-Bold", leading=17))

    S.add(ParagraphStyle("SectionHead",
        fontSize=9, textColor=TEAL, fontName="Helvetica-Bold",
        spaceAfter=2, spaceBefore=4, leading=12))

    S.add(ParagraphStyle("Body",
        fontSize=9, textColor=colors.black, fontName="Helvetica",
        spaceAfter=2, leading=13, alignment=TA_JUSTIFY))

    S.add(ParagraphStyle("RxItem",
        fontSize=9, textColor=colors.black, fontName="Helvetica",
        spaceAfter=1, leading=13, leftIndent=6))

    S.add(ParagraphStyle("Rationale",
        fontSize=8.5, textColor=colors.HexColor("#2C3E50"),
        fontName="Helvetica-Oblique",
        spaceAfter=1, leading=12, leftIndent=8))

    S.add(ParagraphStyle("Footer",
        fontSize=7, textColor=colors.grey, alignment=TA_CENTER,
        fontName="Helvetica"))

    S.add(ParagraphStyle("TOCEntry",
        fontSize=10, textColor=NAVY, fontName="Helvetica",
        spaceAfter=3, leading=14))

    S.add(ParagraphStyle("TOCTitle",
        fontSize=16, textColor=NAVY, fontName="Helvetica-Bold",
        alignment=TA_CENTER, spaceAfter=10))
    return S


# โ”€โ”€ 30 ENT OPD cases data โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
CASES = [

# โ”€โ”€โ”€ EAR (1-10) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

{
"no": 1,
"title": "Acute Otitis Media (AOM)",
"category": "EAR",
"age_sex": "6-year-old Male",
"chief": "Right ear pain, fever (38.8 ยฐC), irritability for 2 days",
"history": "No prior ear infections. Preceded by URTI. Otoscopy: bulging, erythematous TM with absent light reflex.",
"diagnosis": "Acute Otitis Media - Right Ear",
"prescription": [
    ("Amoxicillin 500 mg cap", "1 cap TID ร— 7 days (weight-based: 80-90 mg/kg/day for children; adult equivalent shown)"),
    ("Paracetamol 500 mg tab", "1 tab TID PRN for pain/fever ร— 5 days"),
    ("Xylometazoline nasal drops 0.05%", "2 drops each nostril BD ร— 5 days"),
],
"rationale": [
    ("Amoxicillin", "First-line antibiotic per AAO-HNS guidelines. Covers S. pneumoniae, H. influenzae, M. catarrhalis - the three commonest AOM pathogens."),
    ("Paracetamol", "Analgesic-antipyretic; reduces otalgia and fever without NSAID GI risk."),
    ("Xylometazoline", "Alpha-1 adrenergic agonist; decongestant that relieves Eustachian tube oedema, improving middle ear ventilation."),
]
},

{
"no": 2,
"title": "Chronic Suppurative Otitis Media - Tubotympanic (CSOM-TT)",
"category": "EAR",
"age_sex": "28-year-old Female",
"chief": "Left ear discharge (mucopurulent, non-foul) ร— 3 months, mild hearing loss",
"history": "Recurrent AOM since childhood. Otoscopy: central perforation inferior quadrant, wet middle ear. Audiometry: 30 dB CHL.",
"diagnosis": "CSOM - Tubotympanic (Safe) Type",
"prescription": [
    ("Ciprofloxacin 0.3% + Dexamethasone 0.1% ear drops", "3 drops in left ear TID ร— 2 weeks after ear toilet"),
    ("Ciprofloxacin 500 mg tab", "1 tab BD ร— 7 days (for active infection)"),
    ("Betahistine 16 mg tab", "1 tab BD ร— 4 weeks"),
],
"rationale": [
    ("Ciprofloxacin ear drops", "Fluoroquinolone active against Pseudomonas aeruginosa and S. aureus (commonest CSOM pathogens). Dexamethasone reduces mucosal oedema. Only ototopical fluoroquinolone safe for use with perforated TM."),
    ("Oral Ciprofloxacin", "Systemic coverage for refractory infection; penetrates biofilm. Matches topical spectrum."),
    ("Betahistine", "Histamine H1 agonist / H3 antagonist; improves microcirculation in cochlea and labyrinth, reducing tinnitus and aural fullness."),
]
},

{
"no": 3,
"title": "Acute Otitis Externa (AOE / Swimmer's Ear)",
"category": "EAR",
"age_sex": "22-year-old Male",
"chief": "Right ear pain, itching, discharge ร— 4 days after swimming",
"history": "Regular swimmer. Ear canal: oedematous, red, scant purulent discharge. Tenderness on tragus pressure. TM not visible.",
"diagnosis": "Acute Diffuse Otitis Externa",
"prescription": [
    ("Ciprofloxacin 0.2% + Hydrocortisone 1% otic solution", "4 drops right ear QID ร— 7 days"),
    ("Ibuprofen 400 mg tab", "1 tab TID after food ร— 5 days"),
    ("Clotrimazole 1% ear drops", "To add if fungal component suspected: 3 drops TID ร— 2 weeks"),
],
"rationale": [
    ("Ciprofloxacin + Hydrocortisone drops", "Ciprofloxacin covers Pseudomonas (most common AOE pathogen) and S. aureus. Hydrocortisone reduces canal inflammation and itching."),
    ("Ibuprofen", "NSAID for analgesic and anti-inflammatory effect; reduces canal oedema pain."),
    ("Clotrimazole", "Azole antifungal; added if otomycosis (Aspergillus/Candida) is contributing - indicated by black/white fungal debris."),
]
},

{
"no": 4,
"title": "Otomycosis (Fungal Otitis Externa)",
"category": "EAR",
"age_sex": "45-year-old Male - Diabetic",
"chief": "Right ear severe itching, white-grey debris, muffled hearing ร— 2 weeks",
"history": "Type 2 DM on metformin. Prior steroid ear drops used 3 weeks. Otoscopy: white fluffy debris with black conidiophores (Aspergillus niger pattern).",
"diagnosis": "Otomycosis - Right Ear",
"prescription": [
    ("Clotrimazole 1% ear drops", "3 drops TID ร— 3 weeks after thorough aural toilet"),
    ("Fluconazole 150 mg cap", "1 cap weekly ร— 2 doses (systemic; especially in diabetic)"),
    ("Betamethasone 0.1% cream (external canal only)", "Apply sparingly OD ร— 1 week for itching"),
    ("Glycerin + Hydrogen peroxide (cerumenolytic) drops", "2 drops BD ร— 3 days to soften debris before suction clearance"),
],
"rationale": [
    ("Clotrimazole drops", "First-line topical azole; disrupts fungal ergosterol synthesis; effective against Aspergillus and Candida."),
    ("Fluconazole oral", "Systemic azole for immunocompromised/diabetic patient; prevents deep fungal extension."),
    ("Betamethasone cream", "Topical corticosteroid; reduces intense pruritus and canal inflammation."),
    ("H2O2 drops", "Effervescent action softens and lifts fungal debris, facilitating mechanical removal."),
]
},

{
"no": 5,
"title": "Benign Paroxysmal Positional Vertigo (BPPV)",
"category": "EAR",
"age_sex": "52-year-old Female",
"chief": "Sudden brief vertigo (< 1 min) on turning in bed or looking up ร— 1 week",
"history": "No hearing loss or tinnitus. Dix-Hallpike positive right side. No neurological signs.",
"diagnosis": "BPPV - Right Posterior Semicircular Canal",
"prescription": [
    ("Epley Manoeuvre", "Performed in OPD; patient taught home Brandt-Daroff exercises"),
    ("Betahistine 24 mg tab", "1 tab BD ร— 4 weeks"),
    ("Prochlorperazine 5 mg tab", "1 tab TID PRN (only for acute nausea/vomiting episodes)"),
],
"rationale": [
    ("Epley Manoeuvre", "Repositioning procedure that moves otoliths from posterior canal back to utricle. Success rate >85% after 1-3 sessions."),
    ("Betahistine", "Improves vestibular microcirculation; reduces endolymphatic pressure; decreases frequency of vertiginous episodes."),
    ("Prochlorperazine", "D2 receptor antagonist with vestibular suppressant properties; relieves nausea. Used short-term only as it can impair vestibular compensation."),
]
},

{
"no": 6,
"title": "Meniere's Disease",
"category": "EAR",
"age_sex": "38-year-old Female",
"chief": "Recurrent attacks of vertigo (20 min-2 hrs), left-sided roaring tinnitus, low-frequency hearing loss, aural fullness ร— 6 months",
"history": "PTA: low-frequency SNHL left ear. Electrocochleography: elevated SP/AP ratio. Salt-restricted diet not yet tried.",
"diagnosis": "Meniere's Disease - Left Ear",
"prescription": [
    ("Betahistine 24 mg tab", "1 tab TID ร— 3 months (maintenance)"),
    ("Hydrochlorothiazide 25 mg tab", "1 tab OD morning ร— 8 weeks"),
    ("Prochlorperazine 5 mg tab", "1 tab TID PRN during acute attack"),
    ("Lorazepam 1 mg tab", "0.5-1 mg sublingual during severe acute attack; max 3 days"),
],
"rationale": [
    ("Betahistine", "Main pharmacological treatment; reduces endolymphatic hydrops by improving strial blood flow and reducing vasopressin-mediated fluid accumulation."),
    ("Hydrochlorothiazide", "Thiazide diuretic; reduces endolymph volume by systemic fluid reduction; evidence supports decreased attack frequency."),
    ("Prochlorperazine", "Vestibular suppressant and antiemetic for acute attacks."),
    ("Lorazepam", "Benzodiazepine; GABA-A agonist; rapidly suppresses acute severe vertigo and anxiety. Short-term only."),
]
},

{
"no": 7,
"title": "Sudden Sensorineural Hearing Loss (SSNHL)",
"category": "EAR",
"age_sex": "44-year-old Male",
"chief": "Sudden painless right ear hearing loss noticed on waking this morning; tinnitus",
"history": "No trauma, no ototoxic drugs. PTA: >30 dB SNHL across 3 contiguous frequencies. MRI brain: normal. Idiopathic SSNHL.",
"diagnosis": "Idiopathic Sudden Sensorineural Hearing Loss - Right",
"prescription": [
    ("Prednisolone 1 mg/kg/day (60 mg) tab", "OD morning with breakfast ร— 10 days then taper over 5 days"),
    ("Pantoprazole 40 mg tab", "1 tab OD morning (gastric protection during steroid course)"),
    ("Acyclovir 400 mg tab", "1 tab 5 times/day ร— 7 days (if viral aetiology suspected)"),
    ("Betahistine 16 mg tab", "1 tab TID ร— 4 weeks"),
],
"rationale": [
    ("Prednisolone", "Systemic corticosteroid; first-line treatment. Reduces cochlear inflammation, autoimmune attack, and oedema. Improves recovery rate significantly when started within 72 hrs."),
    ("Pantoprazole", "PPI; protects gastric mucosa from steroid-induced ulceration."),
    ("Acyclovir", "Antiviral active against HSV and VZV; given empirically as viral labyrinthitis is a postulated cause."),
    ("Betahistine", "Cochlear blood flow enhancement; supports recovery."),
]
},

{
"no": 8,
"title": "Wax Impaction (Cerumen Impaction)",
"category": "EAR",
"age_sex": "35-year-old Male",
"chief": "Bilateral hearing loss, right ear fullness, mild tinnitus ร— 2 weeks; worse after shower",
"history": "Uses cotton buds regularly. Otoscopy: complete canal occlusion by dark hard wax bilaterally. TM not visible.",
"diagnosis": "Bilateral Cerumen Impaction",
"prescription": [
    ("Carbamide peroxide 6.5% ear drops (or Olive oil drops)", "2-3 drops TID for 3-5 days to soften wax prior to syringing/microsuction"),
    ("Syringing / Microsuction", "Perform after softening; avoid if TM perforation suspected"),
],
"rationale": [
    ("Carbamide peroxide", "Releases oxygen on contact with cerumen; mechanical effervescence and softening action breaks up impacted wax, making removal easier and safer."),
    ("Microsuction", "Preferred mechanical removal method; safe for perforated TM; no water used."),
]
},

{
"no": 9,
"title": "Tinnitus (Subjective, Non-pulsatile)",
"category": "EAR",
"age_sex": "50-year-old Male - Office worker",
"chief": "Bilateral high-pitched ringing in ears ร— 4 months, worse at night, affecting sleep; no vertigo",
"history": "Mild bilateral high-frequency SNHL on PTA (noise-induced). BP: 120/80. No medication history. MRI: normal.",
"diagnosis": "Subjective Tinnitus secondary to Noise-Induced SNHL",
"prescription": [
    ("Betahistine 16 mg tab", "1 tab TID ร— 3 months"),
    ("Amitriptyline 10 mg tab", "1 tab at bedtime ร— 6 weeks (low-dose)"),
    ("Vitamin B12 1000 mcg tab", "1 tab OD ร— 2 months"),
    ("Tinnitus Retraining Therapy (TRT) / Sound therapy", "Refer audiology; white noise generator"),
],
"rationale": [
    ("Betahistine", "Improves cochlear microcirculation; may reduce tinnitus loudness and distress."),
    ("Amitriptyline", "Tricyclic antidepressant; at low dose reduces tinnitus-related distress, improves sleep architecture, and has central auditory modulation effects."),
    ("Vitamin B12", "B12 deficiency linked to auditory nerve dysfunction and tinnitus; supplementation corrects deficiency."),
    ("TRT", "Gold-standard non-pharmacological approach; combines sound therapy with directive counselling to achieve habituation."),
]
},

{
"no": 10,
"title": "Otitis Media with Effusion (Glue Ear)",
"category": "EAR",
"age_sex": "5-year-old Male",
"chief": "Bilateral hearing difficulty, inattentiveness at school ร— 3 months; no pain, no fever",
"history": "Recurrent colds, large adenoids noted. Tympanometry: Type B (flat) bilaterally. PTA: 35 dB CHL.",
"diagnosis": "Otitis Media with Effusion (Bilateral) - Glue Ear",
"prescription": [
    ("Amoxicillin-Clavulanate 457 mg/5 mL susp", "7 mL BD ร— 10 days (treats any underlying infection)"),
    ("Xylometazoline 0.05% nasal drops", "2 drops each nostril BD ร— 5 days"),
    ("Intranasal Mometasone 50 mcg spray", "1 puff each nostril OD ร— 8 weeks"),
    ("Watchful waiting + Valsalva exercises", "If no improvement at 3 months: refer for grommet insertion + adenoidectomy"),
],
"rationale": [
    ("Amoxicillin-Clavulanate", "Beta-lactamase stable; covers H. influenzae and M. catarrhalis in biofilm-associated middle ear effusion."),
    ("Xylometazoline", "Decongestant; opens Eustachian tube orifice to allow effusion drainage."),
    ("Mometasone", "Intranasal corticosteroid; reduces adenoid hypertrophy and nasopharyngeal inflammation improving ET function. Evidence supports use in OME."),
]
},

# โ”€โ”€โ”€ NOSE (11-20) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

{
"no": 11,
"title": "Allergic Rhinitis (Perennial)",
"category": "NOSE",
"age_sex": "24-year-old Female",
"chief": "Bilateral watery nasal discharge, sneezing (>5 episodes/morning), nasal itching, eye itching ร— 1 year",
"history": "Dust mite allergy. No nasal polyps. Skin prick test: positive HDM. Nasal mucosa pale, boggy.",
"diagnosis": "Perennial Allergic Rhinitis - Moderate-Severe",
"prescription": [
    ("Mometasone furoate 50 mcg nasal spray", "2 puffs each nostril OD ร— 8 weeks (maintenance 1 puff OD)"),
    ("Cetirizine 10 mg tab", "1 tab OD at bedtime ร— 4 weeks"),
    ("Montelukast 10 mg tab", "1 tab OD at bedtime ร— 4 weeks"),
    ("Normal saline nasal irrigation (isotonic)", "Nasal wash BD ongoing"),
],
"rationale": [
    ("Mometasone", "Intranasal corticosteroid; most effective single agent for AR. Reduces mucosal oedema, discharge, and sneezing by suppressing IgE-mediated and eosinophilic inflammation."),
    ("Cetirizine", "Second-generation antihistamine (H1 blocker); minimal sedation; relieves sneezing, rhinorrhoea, and itching."),
    ("Montelukast", "Leukotriene receptor antagonist; blocks LTD4-mediated mucosal swelling and eosinophil recruitment; synergistic with antihistamine."),
    ("Saline irrigation", "Mechanically clears allergens and mucus; improves mucociliary clearance; enhances drug penetration."),
]
},

{
"no": 12,
"title": "Acute Rhinosinusitis (Bacterial)",
"category": "NOSE",
"age_sex": "32-year-old Male",
"chief": "Right-sided facial pain/pressure, nasal blockage, purulent discharge, fever (38.2 ยฐC) ร— 10 days (worsening after initial viral URTI)",
"history": "Purulent PND visible on posterior pharyngeal wall. Maxillary sinus opacification on X-ray. No allergy history.",
"diagnosis": "Acute Bacterial Rhinosinusitis - Right Maxillary",
"prescription": [
    ("Amoxicillin-Clavulanate 625 mg tab", "1 tab TID ร— 10-14 days"),
    ("Xylometazoline 0.1% nasal spray", "1-2 sprays each nostril TID ร— 5 days (max 5 days)"),
    ("Mometasone 50 mcg nasal spray", "2 puffs each nostril BD ร— 3 weeks"),
    ("Ibuprofen 400 mg tab", "1 tab TID after food ร— 5 days"),
    ("Steam inhalation", "Twice daily; menthol optional"),
],
"rationale": [
    ("Amoxicillin-Clavulanate", "Covers S. pneumoniae, H. influenzae, M. catarrhalis including beta-lactamase producers; first-line for acute bacterial sinusitis."),
    ("Xylometazoline", "Nasal decongestant; relieves ostial blockage to allow sinus drainage."),
    ("Mometasone", "Reduces mucosal inflammation and facilitates drainage; also prevents recurrence."),
    ("Ibuprofen", "NSAID; reduces facial pain, fever, and inflammation."),
]
},

{
"no": 13,
"title": "Chronic Rhinosinusitis with Nasal Polyps (CRSwNP)",
"category": "NOSE",
"age_sex": "40-year-old Male",
"chief": "Bilateral nasal obstruction, anosmia, thick mucus discharge ร— 1 year; aspirin sensitivity",
"history": "Grade 3 nasal polyps bilaterally on endoscopy. CT: pan-sinusitis. Samter's triad: asthma, aspirin sensitivity, nasal polyps.",
"diagnosis": "CRS with Nasal Polyps - Samter's Triad",
"prescription": [
    ("Prednisolone 40 mg tab", "OD ร— 7 days (polyp shrinkage course), then taper"),
    ("Mometasone 50 mcg nasal spray", "2 puffs each nostril BD ร— long-term"),
    ("Saline nasal douche (hypertonic)", "BD ongoing"),
    ("Refer for FESS", "If no response to 3 months medical therapy"),
],
"rationale": [
    ("Prednisolone (oral)", "Systemic corticosteroid significantly shrinks polyps; reduces eosinophilic inflammation; improves olfaction. Short course minimises side effects."),
    ("Mometasone spray", "Maintenance therapy; prevents polyp regrowth post-shrinkage or post-FESS; reduces recurrence rate."),
    ("Hypertonic saline", "Draws oedema fluid from polyp surface; improves mucociliary clearance."),
    ("FESS", "Functional Endoscopic Sinus Surgery - gold standard when medical therapy fails; removes polyps and opens sinus ostia."),
]
},

{
"no": 14,
"title": "Epistaxis (Anterior) - Kiesselbach's Plexus",
"category": "NOSE",
"age_sex": "18-year-old Male",
"chief": "Right nostril bleeding ร— 15 minutes, stopped spontaneously; 3rd episode this month",
"history": "Nose picking habit. Dry climate. Little's area: prominent vessels, crusting. BP: 118/76. No bleeding disorder.",
"diagnosis": "Recurrent Anterior Epistaxis - Right (Kiesselbach's Plexus)",
"prescription": [
    ("Silver nitrate cauterisation", "Applied to bleeding point under local anaesthesia in OPD"),
    ("Naseptin cream (Chlorhexidine 0.1% + Neomycin 0.5%)", "Apply inside nostrils TID ร— 2 weeks"),
    ("Petroleum jelly (Vaseline)", "Apply inside nostril BD ongoing for moisturisation"),
    ("Tranexamic acid 500 mg tab", "1 tab TID ร— 5 days (if active ongoing bleeding)"),
],
"rationale": [
    ("Silver nitrate cauterisation", "Chemical cautery obliterates fragile Kiesselbach's vessels; most effective OPD treatment."),
    ("Naseptin cream", "Antibacterial; prevents nasal vestibule infection and crust formation that precipitates bleeding."),
    ("Petroleum jelly", "Lubricates mucosa; prevents dryness and cracking of Little's area vessels."),
    ("Tranexamic acid", "Antifibrinolytic; prevents clot dissolution at bleeding point; useful for acute bleeding."),
]
},

{
"no": 15,
"title": "Deviated Nasal Septum (DNS) with Nasal Obstruction",
"category": "NOSE",
"age_sex": "29-year-old Male",
"chief": "Left nasal blockage (worse at night), mouth breathing, snoring ร— 3 years; one-sided nasal obstruction",
"history": "Anterior rhinoscopy: C-shaped DNS to left, compensatory hypertrophy right inferior turbinate. No polyps. CT: confirms DNS.",
"diagnosis": "Deviated Nasal Septum - Left",
"prescription": [
    ("Fluticasone 50 mcg nasal spray", "2 puffs each nostril OD ร— 6 weeks (diagnostic trial)"),
    ("Xylometazoline 0.1% spray", "1 spray left nostril at bedtime PRN"),
    ("Saline nasal rinse", "OD ongoing"),
    ("Septoplasty referral", "Definitive surgical correction under GA"),
],
"rationale": [
    ("Fluticasone", "Reduces inferior turbinate hypertrophy (compensatory mucosal swelling); may improve airflow sufficiently to defer surgery."),
    ("Xylometazoline", "Short-term decongestant for symptomatic relief at night; max 5-day course to avoid rhinitis medicamentosa."),
    ("Septoplasty", "Sub-mucous resection/repositioning of deviated cartilage and bone; definitive treatment."),
]
},

{
"no": 16,
"title": "Rhinitis Medicamentosa",
"category": "NOSE",
"age_sex": "36-year-old Female",
"chief": "Severe bilateral nasal obstruction, using oxymetazoline spray every 3-4 hours ร— 6 months; rebound congestion without spray",
"history": "Started spray for a cold. Now cannot sleep without it. Turbinates: beefy red, engorged on endoscopy.",
"diagnosis": "Rhinitis Medicamentosa (Rebound Rhinitis)",
"prescription": [
    ("Gradual oxymetazoline withdrawal", "Reduce to one nostril at a time; add nasal steroid before withdrawing"),
    ("Mometasone 50 mcg nasal spray", "2 puffs each nostril BD ร— 6-8 weeks"),
    ("Prednisolone 20 mg tab", "OD ร— 5 days (burst course to manage withdrawal inflammation)"),
    ("Saline rinse", "BD ongoing"),
],
"rationale": [
    ("Oxymetazoline withdrawal", "Alpha-2 agonist overuse causes alpha-receptor downregulation and rebound vasodilation; cessation is essential."),
    ("Mometasone", "Intranasal steroid counters rebound congestion and facilitates painless withdrawal; key to successful cessation."),
    ("Prednisolone burst", "Short systemic steroid course manages severe withdrawal nasal congestion and mucosal oedema."),
]
},

{
"no": 17,
"title": "Nasal Foreign Body",
"category": "NOSE",
"age_sex": "4-year-old Male",
"chief": "Unilateral (right) foul-smelling nasal discharge, nasal obstruction ร— 5 days",
"history": "Mother noticed child putting bead in nose. Anterior rhinoscopy under good light: small blue bead visible in right nasal floor.",
"diagnosis": "Nasal Foreign Body - Right Nostril (Bead)",
"prescription": [
    ("Foreign body removal", "Using Jobson-Horne probe or Tilley's forceps in OPD; parent-performed positive pressure (mother's kiss) technique attempted first"),
    ("Xylometazoline 0.05% drops", "2 drops right nostril 5 min before procedure to decongest"),
    ("Mupirocin 2% nasal ointment", "Apply TID ร— 5 days post-removal (treats secondary bacterial infection)"),
],
"rationale": [
    ("Xylometazoline pre-procedure", "Decongestant shrinks mucosa to improve visualisation and ease extraction."),
    ("Mother's kiss technique", "Non-invasive positive pressure method; parent occludes mouth over child's mouth while occluding opposite nostril; avoids GA."),
    ("Mupirocin", "Anti-staphylococcal nasal antibiotic; treats localised nasal vestibulitis/secondary infection around the FB."),
]
},

{
"no": 18,
"title": "Acute Viral Rhinitis (Common Cold)",
"category": "NOSE",
"age_sex": "27-year-old Female",
"chief": "Watery then thick nasal discharge, sneezing, nasal blockage, sore throat, malaise ร— 4 days",
"history": "Afebrile. No secondary bacterial features. Onset 4 days ago. Family members similarly affected.",
"diagnosis": "Acute Viral Rhinitis (Common Cold)",
"prescription": [
    ("Cetirizine 10 mg tab", "1 tab OD at bedtime ร— 5 days (for sneezing and watery rhinorrhoea)"),
    ("Xylometazoline 0.1% nasal spray", "1 spray each nostril TID ร— 3-5 days only"),
    ("Paracetamol 500 mg tab", "1-2 tabs TID PRN for discomfort/fever"),
    ("Honey + Warm lemon drink", "Soothe throat; increase fluids; rest"),
    ("Zinc gluconate lozenges 13.3 mg", "1 lozenge q2h while awake (within first 24 hrs of symptoms) ร— 5 days"),
],
"rationale": [
    ("Cetirizine", "H1 blocker reduces rhinorrhoea and sneezing mediated by histamine in early viral rhinitis."),
    ("Xylometazoline", "Sympathomimetic; temporary decongestion; max 5 days to prevent rhinitis medicamentosa."),
    ("Paracetamol", "Analgesic-antipyretic; no NSAID GI risk."),
    ("Zinc lozenges", "Meta-analyses show zinc reduces duration of common cold by ~33% when started within 24 hrs; inhibits viral capsid binding to nasal epithelium."),
]
},

{
"no": 19,
"title": "Anosmia post COVID-19",
"category": "NOSE",
"age_sex": "33-year-old Female",
"chief": "Complete loss of smell and distorted taste ร— 3 months following COVID-19 infection",
"history": "COVID confirmed 3 months ago. MRI olfactory bulbs: mild atrophy. No nasal polyps. No sinusitis.",
"diagnosis": "Post-COVID Anosmia / Parosmia",
"prescription": [
    ("Olfactory Training", "Smell 4 distinct odours (rose, eucalyptus, lemon, cloves) for 20 sec each, twice daily ร— minimum 4 months"),
    ("Alpha-lipoic acid 600 mg tab", "1 tab OD ร— 4 months"),
    ("Intranasal Vitamin A drops (Retinyl palmitate)", "Applied to olfactory epithelium OD ร— 4 months"),
    ("Omega-3 fatty acid 1000 mg cap", "1 cap BD ร— 3 months"),
],
"rationale": [
    ("Olfactory training", "Most evidence-based intervention; stimulates olfactory receptor neuron regeneration and neuroplasticity; RCTs show significant improvement."),
    ("Alpha-lipoic acid", "Antioxidant; reduces oxidative neuronal damage; small trials show benefit in post-viral anosmia."),
    ("Vitamin A (intranasal)", "Olfactory epithelium regeneration requires retinoids; topical delivery maximises local concentration."),
    ("Omega-3", "Anti-inflammatory; supports neural membrane integrity and olfactory nerve repair."),
]
},

{
"no": 20,
"title": "Septal Haematoma",
"category": "NOSE",
"age_sex": "19-year-old Male",
"chief": "Nasal trauma (football), progressive bilateral nasal obstruction, severe nasal pain 8 hours ago",
"history": "Anterior rhinoscopy: bilateral smooth, fluctuant, cherry-red swellings of nasal septum. Boggy, non-transilluminant.",
"diagnosis": "Nasal Septal Haematoma - Bilateral (ENT Emergency)",
"prescription": [
    ("Incision and Drainage", "URGENT - under local/GA; horizontal incision at inferior nasal septum; evacuation of clot; quilting suture or pack"),
    ("Nasal packing (anterior)", "Merocel pack bilateral for 48 hrs post-I&D"),
    ("Amoxicillin-Clavulanate 625 mg tab", "1 tab TID ร— 7 days"),
    ("Ibuprofen 400 mg tab", "1 tab TID ร— 5 days"),
],
"rationale": [
    ("I&D (urgent)", "Prevents avascular necrosis of septal cartilage (which has no intrinsic blood supply); prevents 'saddle nose' deformity and abscess formation."),
    ("Nasal packing", "Obliterates dead space; prevents haematoma re-accumulation."),
    ("Amoxicillin-Clavulanate", "Prophylactic antibiotic; septal cartilage is vulnerable to S. aureus infection post-trauma and I&D."),
]
},

# โ”€โ”€โ”€ THROAT / LARYNX (21-30) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

{
"no": 21,
"title": "Acute Tonsillitis (Bacterial - Group A Streptococcal)",
"category": "THROAT",
"age_sex": "16-year-old Female",
"chief": "Severe sore throat, fever (39.5 ยฐC), odynophagia, difficulty opening mouth ร— 3 days",
"history": "Centor score 4/4 (tonsillar exudates, fever, tender anterior cervical LN, no cough). Rapid antigen test: positive GAS.",
"diagnosis": "Acute Streptococcal Tonsillitis",
"prescription": [
    ("Benzathine Penicillin G 1.2 MU IM", "Single dose (or Phenoxymethylpenicillin 500 mg TID ร— 10 days orally)"),
    ("Ibuprofen 400 mg tab", "1 tab TID after food ร— 5 days"),
    ("Betamethasone 8 mg IM", "Single dose (for severe odynophagia and significant tonsillar oedema)"),
    ("Chlorhexidine 0.2% mouth gargle", "Gargle 15 mL for 30 sec, TID ร— 5 days"),
],
"rationale": [
    ("Penicillin / Benzathine Pen G", "Drug of choice for GAS tonsillitis; 10-day course essential to eradicate strep and prevent rheumatic fever. IM single dose ensures compliance."),
    ("Ibuprofen", "NSAID; superior to paracetamol for tonsillar pain; also reduces tonsillar oedema."),
    ("Betamethasone IM", "Corticosteroid reduces tonsillar swelling rapidly; significantly reduces time to pain relief and dysphagia."),
    ("Chlorhexidine gargle", "Antiseptic; reduces pharyngeal bacterial load; soothing for inflamed mucosa."),
]
},

{
"no": 22,
"title": "Peritonsillar Abscess (Quinsy)",
"category": "THROAT",
"age_sex": "23-year-old Male",
"chief": "Severe sore throat, high fever (39.8 ยฐC), muffled 'hot potato' voice, trismus, drooling ร— 4 days",
"history": "Deviated uvula to left. Right peritonsillar bulge. Unable to swallow. Preceding tonsillitis for 5 days.",
"diagnosis": "Right Peritonsillar Abscess (Quinsy)",
"prescription": [
    ("Needle aspiration / Incision & Drainage", "URGENT - under local anaesthesia; aspirate pus from peritonsillar space at point of maximum bulge"),
    ("Benzylpenicillin 2.4 g IV q6h + Metronidazole 500 mg IV TID", "IV antibiotics ร— 48 hrs then step down to oral"),
    ("Amoxicillin-Clavulanate 625 mg tab", "1 tab TID ร— 10 days (step-down)"),
    ("Dexamethasone 8 mg IV", "Single dose to reduce oedema and trismus"),
    ("Ibuprofen 400 mg tab", "1 tab TID ร— 7 days"),
],
"rationale": [
    ("I&D", "Evacuates pus from peritonsillar space; most effective treatment; relieves trismus immediately."),
    ("Penicillin + Metronidazole", "Penicillin covers streptococci; metronidazole covers Fusobacterium nucleatum and anaerobes which are key pathogens in PTA."),
    ("Dexamethasone IV", "Rapid reduction of surrounding oedema; improves trismus and dysphagia within hours."),
]
},

{
"no": 23,
"title": "Chronic Tonsillitis",
"category": "THROAT",
"age_sex": "14-year-old Male",
"chief": "Recurrent sore throats (>7 episodes/year ร— 2 years), cryptic tonsils with debris, halitosis",
"history": "Paradise criteria met (>7 episodes in 1 yr, >5 in each of 2 yrs, or >3 in each of 3 yrs). Enlarged grade III tonsils. Sleep-disordered breathing.",
"diagnosis": "Chronic Tonsillitis with Sleep-Disordered Breathing",
"prescription": [
    ("Tonsillectomy referral", "Recommended - meets Paradise criteria; also indicated for sleep-disordered breathing"),
    ("Amoxicillin 500 mg cap", "1 cap TID ร— 10 days (for current acute exacerbation)"),
    ("Ibuprofen 400 mg tab", "1 tab TID ร— 5 days PRN"),
    ("Warm saline gargle", "TID ongoing for symptomatic relief"),
],
"rationale": [
    ("Tonsillectomy", "Definitive treatment; eliminates focus of chronic infection and obstruction."),
    ("Amoxicillin", "Covers GAS for current exacerbation while awaiting surgery."),
]
},

{
"no": 24,
"title": "Adenoid Hypertrophy",
"category": "THROAT",
"age_sex": "7-year-old Male",
"chief": "Mouth breathing, nocturnal snoring, nasal voice, recurrent ear infections ร— 1 year",
"history": "Lateral soft tissue X-ray neck: adenoid-nasopharyngeal ratio > 0.8. Hyponasal speech. Sleep study: mild OSA (AHI 3.5).",
"diagnosis": "Adenoid Hypertrophy with OSA and OME",
"prescription": [
    ("Mometasone 50 mcg nasal spray", "1 puff each nostril OD ร— 3 months (medical trial before surgery)"),
    ("Montelukast 5 mg tab", "1 tab OD at bedtime ร— 3 months"),
    ("Xylometazoline 0.05% drops", "2 drops each nostril at bedtime ร— 5 days"),
    ("Adenoidectomy ยฑ tonsillectomy ยฑ grommets", "If no improvement at 3 months; OSA indication for early surgery"),
],
"rationale": [
    ("Mometasone", "Reduces lymphoid tissue volume by anti-inflammatory action; multiple RCTs show adenoid size reduction."),
    ("Montelukast", "Leukotriene receptor antagonist; reduces adenoid and turbinate tissue; paediatric evidence for adenoid hypertrophy."),
    ("Adenoidectomy", "Removes obstructing adenoid pad; first-line surgical treatment for symptomatic adenoid hypertrophy."),
]
},

{
"no": 25,
"title": "Acute Pharyngitis (Viral)",
"category": "THROAT",
"age_sex": "25-year-old Female",
"chief": "Sore throat, odynophagia, low-grade fever (37.8 ยฐC), hoarseness ร— 3 days; no exudates",
"history": "Concurrent rhinitis. Centor score 0. Rapid strep test: negative. Posterior pharyngeal wall: granular, red. No cervical LAP.",
"diagnosis": "Acute Viral Pharyngitis",
"prescription": [
    ("Paracetamol 500 mg tab", "1-2 tabs TID PRN ร— 5 days"),
    ("Benzydamine HCl 0.15% oral rinse", "Gargle 15 mL for 30 sec q3h ร— 7 days (undiluted)"),
    ("Lozenges (Amylmetacresol + Dichlorobenzyl alcohol)", "1 lozenge q3h max 8/day ร— 5 days"),
    ("Honey + warm water gargle", "PRN; coat pharyngeal mucosa"),
],
"rationale": [
    ("Paracetamol", "First-line analgesic-antipyretic; safe, no antibiotic needed for viral pharyngitis."),
    ("Benzydamine", "Topical NSAID; local anti-inflammatory and anaesthetic; significantly reduces pharyngeal pain."),
    ("Antiseptic lozenges", "Amylmetacresol/dichlorobenzyl alcohol have broad-spectrum antiseptic and mild topical anaesthetic properties."),
]
},

{
"no": 26,
"title": "Laryngopharyngeal Reflux (LPR)",
"category": "THROAT",
"age_sex": "42-year-old Male",
"chief": "Chronic throat clearing, globus pharyngeus, hoarseness (worse in morning), post-nasal drip feeling ร— 6 months; no heartburn",
"history": "RSI score > 13. RFS > 7 on flexible laryngoscopy: posterior commissure hypertrophy, arytenoid erythema, subglottic oedema.",
"diagnosis": "Laryngopharyngeal Reflux Disease",
"prescription": [
    ("Omeprazole 20 mg cap", "1 cap BD (30 min before breakfast and dinner) ร— 3 months"),
    ("Domperidone 10 mg tab", "1 tab TID before meals ร— 4 weeks (prokinetic)"),
    ("Alginate 10 mL (Gaviscon Advance)", "After each meal and at bedtime ร— 8 weeks"),
    ("Dietary and lifestyle modifications", "Avoid spicy/fatty food, coffee, carbonated drinks, alcohol; elevate head of bed 30 degrees; weight loss"),
],
"rationale": [
    ("Omeprazole BD", "Proton pump inhibitor; LPR requires BD (not OD) dosing as pharyngeal mucosa is more sensitive than oesophageal; reduces acid and pepsin reflux."),
    ("Domperidone", "Peripheral D2 antagonist; prokinetic; improves LES tone and gastric emptying; reduces reflux episodes."),
    ("Alginate", "Forms floating raft over gastric contents; physical barrier preventing post-prandial reflux reaching laryngopharynx."),
]
},

{
"no": 27,
"title": "Vocal Cord Nodules (Singer's Nodules)",
"category": "THROAT",
"age_sex": "30-year-old Female - School Teacher",
"chief": "Progressive hoarseness, voice fatigue, rough voice quality ร— 4 months; worsens with prolonged speaking",
"history": "Flexible laryngoscopy: bilateral symmetrical sessile nodules at anterior 1/3 and posterior 2/3 junction of vocal cords.",
"diagnosis": "Bilateral Vocal Cord Nodules",
"prescription": [
    ("Voice Rest", "Relative voice rest; no shouting/whispering ร— 2 weeks"),
    ("Voice Therapy referral", "Speech-language pathology: 6-8 weeks programme; reduce vocal abuse"),
    ("Omeprazole 20 mg cap", "1 cap BD ร— 8 weeks (treat concomitant LPR which may perpetuate nodules)"),
    ("Mucolytics (Carbocisteine 375 mg cap)", "1 cap TID ร— 4 weeks (to thin laryngeal secretions)"),
    ("Microlaryngoscopy", "If no resolution after 3 months of voice therapy"),
],
"rationale": [
    ("Voice rest + therapy", "Nodules caused by vocal misuse/overuse; removing cause is essential. Voice therapy resolves majority of nodules without surgery."),
    ("Omeprazole", "LPR co-exists in >40% of vocal nodule patients; acid reduces nodule resolution."),
    ("Carbocisteine", "Mucolytic; reduces viscid mucus layering on cords which exacerbates phonatory trauma."),
    ("Microlaryngoscopy", "Surgical removal under GA with cold instruments or KTP laser for persistent/large nodules."),
]
},

{
"no": 28,
"title": "Acute Epiglottitis",
"category": "THROAT",
"age_sex": "35-year-old Male",
"chief": "Rapidly progressive severe sore throat, high fever (40 ยฐC), muffled voice, drooling, stridor, tripod position ร— 6 hours",
"history": "EMERGENCY. Soft tissue lateral neck X-ray: 'thumb print' sign (enlarged epiglottis). DO NOT attempt oral examination without airway protection. SpO2: 94%.",
"diagnosis": "Acute Epiglottitis - EMERGENCY",
"prescription": [
    ("Secure airway FIRST", "Nasotracheal intubation in OT (anaesthesia + ENT standby) or tracheostomy if intubation fails"),
    ("Ceftriaxone 2 g IV OD", "ร— 7-10 days (H. influenzae type b coverage; also S. pneumoniae)"),
    ("Dexamethasone 8 mg IV q8h", "ร— 48 hrs (reduces supraglottic oedema; aids extubation)"),
    ("Adrenaline nebuliser 0.5 mL 1:1000 in 2.5 mL NS", "PRN for temporary airway relief while preparing for definitive airway"),
],
"rationale": [
    ("Airway first", "Airway compromise is the primary danger; mortality without airway management approaches 6%."),
    ("Ceftriaxone", "3rd generation cephalosporin; excellent activity against H. influenzae type b (classically) and other Gram-negatives; penetrates well."),
    ("Dexamethasone", "Reduces supraglottic oedema; reduces duration of intubation; decreases morbidity."),
    ("Adrenaline nebulisation", "Alpha/beta agonist; causes mucosal vasoconstriction and reduces glottic oedema transiently."),
]
},

{
"no": 29,
"title": "Laryngomalacia",
"category": "THROAT",
"age_sex": "3-month-old Male",
"chief": "Inspiratory stridor since birth, worse with feeding and crying, improves in prone position; poor weight gain",
"history": "Flexible laryngoscopy: omega-shaped epiglottis, short aryepiglottic folds, redundant arytenoid mucosa prolapsing on inspiration. Mild OSA on polysomnography.",
"diagnosis": "Laryngomalacia - Moderate (with feeding difficulty)",
"prescription": [
    ("Omeprazole 1 mg/kg/day (suspension)", "BD ร— 3 months (GERD is common co-morbidity worsening inflammation)"),
    ("Thickened feeds (AR formula)", "Reduce aspiration and GERD"),
    ("Supraglottoplasty", "If severe: surgical division of short aryepiglottic folds under microlaryngoscopy"),
    ("Parental reassurance", "Majority (>90%) resolve spontaneously by 12-18 months without surgery"),
],
"rationale": [
    ("Omeprazole", "PPI; acid reflux worsens supraglottic inflammation and oedema, aggravating laryngomalacia; treatment reduces severity."),
    ("Thickened feeds", "Reduces reflux and aspiration; improves weight gain."),
    ("Supraglottoplasty", "CO2 laser or cold instrument surgery; divides restrictive folds to prevent arytenoid prolapse."),
]
},

{
"no": 30,
"title": "Obstructive Sleep Apnoea (Adult) - ENT perspective",
"category": "THROAT",
"age_sex": "48-year-old Male - BMI 34",
"chief": "Loud snoring, witnessed apnoeas, excessive daytime sleepiness (ESS 16/24), morning headache ร— 2 years",
"history": "Polysomnography: AHI 28 (moderate-severe OSA). Flexible laryngoscopy: grade III tonsils, elongated soft palate, Muller's manoeuvre: grade 2 retropalatal and grade 1 retrolingual collapse.",
"diagnosis": "Moderate-Severe OSA with Retropalatal Collapse",
"prescription": [
    ("CPAP 8-12 cmH2O", "First-line treatment; nightly use; titrate with sleep physician"),
    ("Montelukast 10 mg tab", "1 tab OD at bedtime ร— 3 months (reduce mucosal oedema in mild cases)"),
    ("Weight reduction programme", "Target 10% weight loss; dietary referral + exercise"),
    ("Uvulopalatopharyngoplasty (UPPP) ยฑ tonsillectomy", "Surgical option if CPAP intolerant; best for retropalatal pattern"),
],
"rationale": [
    ("CPAP", "Gold standard for moderate-severe OSA; pneumatic splinting of airway; reverses hypoxaemia and eliminates apnoeas."),
    ("Montelukast", "Anti-inflammatory; reduces upper airway mucosal swelling; adjunct in mild OSA."),
    ("Weight loss", "10% weight reduction can reduce AHI by 26%; addresses root cause in obese patients."),
    ("UPPP", "Removes redundant retropalatal tissue; 40-50% success in selected patients; considered when CPAP is not tolerated."),
]
},

]  # end of CASES list

# โ”€โ”€ PDF builder โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

def cover_page(canvas_obj, doc):
    """Draw cover background."""
    canvas_obj.saveState()
    canvas_obj.setFillColor(NAVY)
    canvas_obj.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
    # accent stripe
    canvas_obj.setFillColor(TEAL)
    canvas_obj.rect(0, PAGE_H * 0.38, PAGE_W, 8, fill=1, stroke=0)
    canvas_obj.restoreState()


def page_footer(canvas_obj, doc):
    canvas_obj.saveState()
    canvas_obj.setFont("Helvetica", 7)
    canvas_obj.setFillColor(colors.grey)
    canvas_obj.drawCentredString(PAGE_W / 2, 1.2 * cm,
        "ENT OPD Clinical Case Studies | For Educational & Practice Purposes Only | Not a substitute for clinical judgment")
    canvas_obj.drawRightString(PAGE_W - MARGIN, 1.2 * cm, f"Page {doc.page}")
    canvas_obj.restoreState()


class CoverTemplate:
    """First-page handler."""
    def __call__(self, canvas_obj, doc):
        cover_page(canvas_obj, doc)
        page_footer(canvas_obj, doc)


class NormalTemplate:
    def __call__(self, canvas_obj, doc):
        # subtle header bar
        canvas_obj.saveState()
        canvas_obj.setFillColor(LBLUE)
        canvas_obj.rect(0, PAGE_H - 1.5 * cm, PAGE_W, 1.5 * cm, fill=1, stroke=0)
        canvas_obj.setFont("Helvetica-Bold", 8)
        canvas_obj.setFillColor(NAVY)
        canvas_obj.drawString(MARGIN, PAGE_H - 0.9 * cm, "ENT OPD Clinical Cases - Prescriptions & Drug Rationale")
        canvas_obj.restoreState()
        page_footer(canvas_obj, doc)


def build_pdf():
    S = build_styles()
    doc = SimpleDocTemplate(OUTPUT, pagesize=A4,
                            leftMargin=MARGIN, rightMargin=MARGIN,
                            topMargin=2.2 * cm, bottomMargin=2.0 * cm)

    story = []

    # โ”€โ”€ COVER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    story.append(Spacer(1, 3.5 * cm))
    story.append(Paragraph("ENT OPD", S["DocTitle"]))
    story.append(Spacer(1, 0.3 * cm))
    story.append(Paragraph("Clinical Case Studies with Prescriptions", S["DocTitle"]))
    story.append(Spacer(1, 0.5 * cm))
    story.append(Paragraph("Drug Rationale &amp; Management Guide", S["DocSub"]))
    story.append(Spacer(1, 0.4 * cm))
    story.append(Paragraph("30 Comprehensive Cases across Ear, Nose and Throat", S["DocSub"]))
    story.append(Spacer(1, 1.5 * cm))

    info_data = [
        ["Speciality", "Otorhinolaryngology (ENT)"],
        ["Scope", "Ear (10 cases)  |  Nose (10 cases)  |  Throat/Larynx (10 cases)"],
        ["Purpose", "OPD Practice, Medical Education, Postgraduate Revision"],
        ["Date", str(date.today().strftime("%B %Y"))],
        ["Disclaimer", "For educational use only. Always apply clinical judgment."],
    ]
    info_table = Table(info_data, colWidths=[4 * cm, 12 * cm])
    info_table.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (0, -1), TEAL),
        ("TEXTCOLOR", (0, 0), (0, -1), WHITE),
        ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, -1), 9),
        ("BACKGROUND", (1, 0), (1, -1), colors.HexColor("#1E4674")),
        ("TEXTCOLOR", (1, 0), (1, -1), WHITE),
        ("FONTNAME", (1, 0), (1, -1), "Helvetica"),
        ("ROWBACKGROUNDS", (1, 0), (1, -1), [colors.HexColor("#1E4674"), colors.HexColor("#22527E")]),
        ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#0D5E5D")),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("TOPPADDING", (0, 0), (-1, -1), 6),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
        ("LEFTPADDING", (0, 0), (-1, -1), 10),
    ]))
    story.append(info_table)
    story.append(PageBreak())

    # โ”€โ”€ TABLE OF CONTENTS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    story.append(Spacer(1, 0.5 * cm))
    story.append(Paragraph("Table of Contents", S["TOCTitle"]))
    story.append(HRFlowable(width="100%", thickness=2, color=TEAL, spaceAfter=10))

    categories = ["EAR", "NOSE", "THROAT"]
    cat_labels = {"EAR": "EAR (Cases 1-10)", "NOSE": "NOSE (Cases 11-20)", "THROAT": "THROAT / LARYNX (Cases 21-30)"}
    cat_colors = {"EAR": colors.HexColor("#1A6B8A"), "NOSE": colors.HexColor("#2E7D32"), "THROAT": colors.HexColor("#6A1E55")}

    for cat in categories:
        story.append(Spacer(1, 4))
        cat_p = Paragraph(f"<b><font color='#{cat_colors[cat].hexval()[2:]}'>{cat_labels[cat]}</font></b>", S["TOCEntry"])
        story.append(cat_p)
        for c in CASES:
            if c["category"] == cat:
                story.append(Paragraph(
                    f"&nbsp;&nbsp;&nbsp;&nbsp;Case {c['no']:02d}: {c['title']} <font color='grey'>- {c['age_sex']}</font>",
                    S["TOCEntry"]))

    story.append(PageBreak())

    # โ”€โ”€ CASES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    category_colors_bg = {
        "EAR":    colors.HexColor("#1A6B8A"),
        "NOSE":   colors.HexColor("#2E7D32"),
        "THROAT": colors.HexColor("#6A1E55"),
    }
    cat_section_headers = {
        "EAR": ("EAR CASES", "Cases 1-10"),
        "NOSE": ("NOSE CASES", "Cases 11-20"),
        "THROAT": ("THROAT & LARYNX CASES", "Cases 21-30"),
    }
    added_sections = set()
    prev_cat = None

    for c in CASES:
        cat = c["category"]
        bg = category_colors_bg[cat]

        # Section divider
        if cat not in added_sections:
            added_sections.add(cat)
            sec_title, sec_sub = cat_section_headers[cat]
            sec_table = Table([[Paragraph(f"<b>{sec_title}</b>", ParagraphStyle(
                "ST", fontSize=18, textColor=WHITE, fontName="Helvetica-Bold", leading=22)),
                Paragraph(sec_sub, ParagraphStyle("SS", fontSize=11, textColor=LBLUE, fontName="Helvetica", leading=14))]],
                colWidths=[12 * cm, 4 * cm])
            sec_table.setStyle(TableStyle([
                ("BACKGROUND", (0, 0), (-1, -1), bg),
                ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
                ("TOPPADDING", (0, 0), (-1, -1), 14),
                ("BOTTOMPADDING", (0, 0), (-1, -1), 14),
                ("LEFTPADDING", (0, 0), (-1, -1), 16),
                ("ALIGN", (1, 0), (1, 0), "RIGHT"),
                ("RIGHTPADDING", (1, 0), (1, 0), 16),
            ]))
            story.append(sec_table)
            story.append(Spacer(1, 0.4 * cm))

        # Case header
        header_data = [[
            Paragraph(f"Case {c['no']:02d}", ParagraphStyle("CN", fontSize=10, textColor=LBLUE, fontName="Helvetica-Bold")),
            Paragraph(c['title'], S["CaseTitle"]),
            Paragraph(c['age_sex'], ParagraphStyle("AS", fontSize=9, textColor=LBLUE, fontName="Helvetica", alignment=2)),
        ]]
        header_table = Table(header_data, colWidths=[1.8 * cm, 10.5 * cm, 4 * cm])
        header_table.setStyle(TableStyle([
            ("BACKGROUND", (0, 0), (-1, -1), bg),
            ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
            ("TOPPADDING", (0, 0), (-1, -1), 8),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
            ("LEFTPADDING", (0, 0), (0, -1), 10),
            ("LEFTPADDING", (1, 0), (1, -1), 6),
            ("RIGHTPADDING", (-1, 0), (-1, -1), 10),
        ]))

        # Details table
        detail_rows = [
            [Paragraph("<b>Chief Complaint:</b>", S["SectionHead"]),
             Paragraph(c["chief"], S["Body"])],
            [Paragraph("<b>History / Findings:</b>", S["SectionHead"]),
             Paragraph(c["history"], S["Body"])],
            [Paragraph("<b>Diagnosis:</b>", S["SectionHead"]),
             Paragraph(f"<b><font color='#C0392B'>{c['diagnosis']}</font></b>", S["Body"])],
        ]
        details_table = Table(detail_rows, colWidths=[3.5 * cm, 12.7 * cm])
        details_table.setStyle(TableStyle([
            ("BACKGROUND", (0, 0), (-1, -1), LGREY),
            ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ("TOPPADDING", (0, 0), (-1, -1), 4),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
            ("LEFTPADDING", (0, 0), (-1, -1), 8),
            ("GRID", (0, 0), (-1, -1), 0.3, colors.HexColor("#CCCCCC")),
        ]))

        # Prescription table
        rx_header = [[
            Paragraph("Rx PRESCRIPTION", ParagraphStyle("RH", fontSize=9, textColor=WHITE, fontName="Helvetica-Bold")),
            Paragraph("DOSE / FREQUENCY / DURATION", ParagraphStyle("RH2", fontSize=9, textColor=WHITE, fontName="Helvetica-Bold")),
        ]]
        rx_rows = []
        for i, (med, dose) in enumerate(c["prescription"]):
            bg_rx = LGREEN if i % 2 == 0 else WHITE
            rx_rows.append([
                Paragraph(f"<b>{i+1}. {med}</b>", S["RxItem"]),
                Paragraph(dose, S["Body"]),
            ])
        rx_table_data = rx_header + rx_rows
        rx_table = Table(rx_table_data, colWidths=[7 * cm, 9.2 * cm])
        rx_table.setStyle(TableStyle([
            ("BACKGROUND", (0, 0), (-1, 0), TEAL),
            ("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
            ("ROWBACKGROUNDS", (0, 1), (-1, -1), [LGREEN, WHITE]),
            ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ("TOPPADDING", (0, 0), (-1, -1), 4),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
            ("LEFTPADDING", (0, 0), (-1, -1), 8),
            ("GRID", (0, 0), (-1, -1), 0.3, colors.HexColor("#AAAAAA")),
        ]))

        # Rationale table
        rat_header = [[
            Paragraph("DRUG", ParagraphStyle("DH", fontSize=9, textColor=WHITE, fontName="Helvetica-Bold")),
            Paragraph("RATIONALE / MECHANISM", ParagraphStyle("DH2", fontSize=9, textColor=WHITE, fontName="Helvetica-Bold")),
        ]]
        rat_rows = []
        for i, (drug, reason) in enumerate(c["rationale"]):
            rat_rows.append([
                Paragraph(f"<b>{drug}</b>", ParagraphStyle("DR", fontSize=8.5, textColor=NAVY, fontName="Helvetica-Bold",
                    spaceAfter=1, leading=12)),
                Paragraph(reason, S["Rationale"]),
            ])
        rat_table_data = rat_header + rat_rows
        rat_table = Table(rat_table_data, colWidths=[3.8 * cm, 12.4 * cm])
        rat_table.setStyle(TableStyle([
            ("BACKGROUND", (0, 0), (-1, 0), NAVY),
            ("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
            ("ROWBACKGROUNDS", (0, 1), (-1, -1), [LBLUE, WHITE]),
            ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ("TOPPADDING", (0, 0), (-1, -1), 4),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
            ("LEFTPADDING", (0, 0), (-1, -1), 8),
            ("GRID", (0, 0), (-1, -1), 0.3, colors.HexColor("#BBBBBB")),
        ]))

        block = KeepTogether([
            header_table,
            Spacer(1, 2),
            details_table,
            Spacer(1, 4),
            rx_table,
            Spacer(1, 4),
            rat_table,
            Spacer(1, 14),
        ])
        story.append(block)

    # โ”€โ”€ APPENDIX: Quick Reference โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    story.append(PageBreak())
    story.append(Paragraph("Quick Reference: ENT Drug Classes", S["TOCTitle"]))
    story.append(HRFlowable(width="100%", thickness=2, color=TEAL, spaceAfter=10))

    drug_ref = [
        ["Drug Class", "Key Drugs", "Primary ENT Use"],
        ["Penicillins", "Amoxicillin, Amoxicillin-Clavulanate, Benzylpenicillin", "AOM, sinusitis, tonsillitis, peritonsillar abscess"],
        ["Fluoroquinolones", "Ciprofloxacin (oral + otic), Ofloxacin otic", "CSOM (especially Pseudomonas), AOE"],
        ["Cephalosporins", "Ceftriaxone, Cefuroxime", "Epiglottitis, severe sinusitis, mastoiditis"],
        ["Corticosteroids (systemic)", "Prednisolone, Dexamethasone, Betamethasone IM", "SSNHL, tonsillitis (oedema), epiglottitis, polyps"],
        ["Corticosteroids (intranasal)", "Mometasone, Fluticasone, Budesonide", "Allergic rhinitis, CRSwNP, OME, adenoid hypertrophy"],
        ["Antihistamines (2nd gen)", "Cetirizine, Loratadine, Fexofenadine", "Allergic rhinitis, otitis media with effusion"],
        ["Leukotriene Antagonists", "Montelukast", "Allergic rhinitis, adenoid hypertrophy, mild OSA"],
        ["Decongestants (topical)", "Xylometazoline, Oxymetazoline", "Nasal congestion, sinus drainage, ET dysfunction"],
        ["Vestibular agents", "Betahistine, Prochlorperazine, Lorazepam", "Meniere's, BPPV, vestibular neuritis"],
        ["PPIs", "Omeprazole, Pantoprazole, Rabeprazole", "LPR, GERD-related ENT symptoms, steroid GI protection"],
        ["Antifungals", "Clotrimazole otic, Fluconazole oral", "Otomycosis (Aspergillus, Candida)"],
        ["Mucolytics", "Carbocisteine, Bromhexine, N-Acetylcysteine", "Post-nasal drip, vocal cord nodules, thick secretions"],
        ["Topical NSAIDs", "Benzydamine 0.15%", "Viral pharyngitis, post-tonsillectomy pain"],
        ["Antifibrinolytics", "Tranexamic acid", "Epistaxis (anterior), post-tonsillectomy bleeding"],
        ["Ototopical combinations", "Cipro + Dexamethasone, Sofradex, Otosporin", "CSOM, AOE (note: avoid aminoglycosides with perforated TM)"],
    ]

    ref_table = Table(drug_ref, colWidths=[4.5 * cm, 6.5 * cm, 5.2 * cm])
    ref_table.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), NAVY),
        ("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, -1), 8),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [LGREY, WHITE]),
        ("GRID", (0, 0), (-1, -1), 0.3, colors.HexColor("#CCCCCC")),
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("TOPPADDING", (0, 0), (-1, -1), 5),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
        ("LEFTPADDING", (0, 0), (-1, -1), 6),
    ]))
    story.append(ref_table)

    story.append(Spacer(1, 0.8 * cm))
    story.append(HRFlowable(width="100%", thickness=1, color=colors.grey))
    story.append(Spacer(1, 0.3 * cm))
    story.append(Paragraph(
        "<b>Disclaimer:</b> This document is prepared for educational and practice purposes in ENT. "
        "Drug doses, frequencies, and treatment protocols are general guidelines and may vary with patient "
        "weight, renal/hepatic function, allergies, local antibiotic resistance patterns, and clinical context. "
        "Always verify with current formularies and apply individual clinical judgment.",
        S["Body"]))

    # build with per-page callbacks
    cover_handler = CoverTemplate()
    normal_handler = NormalTemplate()

    def on_page(canvas_obj, doc_obj):
        if doc_obj.page == 1:
            cover_handler(canvas_obj, doc_obj)
        else:
            normal_handler(canvas_obj, doc_obj)

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


if __name__ == "__main__":
    build_pdf()
Running Command

cd /home/daytona/workspace/ent-opd && python ent_30_cases.py 2>&1

Running Command

ls -lh /home/daytona/workspace/ent-opd/ENT_OPD_30_Cases.pdf

Your PDF is ready! Download it here:

ENT OPD 30 Cases

PDF Document ยท PDF


What's inside the PDF

The document is organized across 3 sections with a cover page, table of contents, 30 fully detailed cases, and a quick-reference drug class appendix.

EAR - Cases 1-10

#ConditionKey Drugs & Rationale
1Acute Otitis MediaAmoxicillin (covers S. pneumoniae/H. influenzae), Paracetamol, Xylometazoline (ET decongestant)
2CSOM - TubotympanicCiprofloxacin+Dexa otic (Pseudomonas coverage, safe with TM perforation), oral Ciprofloxacin
3Acute Otitis ExternaCipro+Hydrocortisone drops (Pseudomonas + inflammation), Ibuprofen
4Otomycosis (Diabetic)Clotrimazole drops (ergosterol disruption), oral Fluconazole (systemic in immunocompromised)
5BPPVEpley Manoeuvre, Betahistine (vestibular microcirculation), Prochlorperazine PRN
6Meniere's DiseaseBetahistine TID (reduces endolymphatic hydrops), HCTZ diuretic, Lorazepam for acute attack
7Sudden SNHLPrednisolone 1 mg/kg (anti-inflammatory, started within 72 hrs), Pantoprazole (GI cover), Acyclovir
8Wax ImpactionCarbamide peroxide drops (effervescent softening), microsuction
9TinnitusBetahistine, low-dose Amitriptyline (sleep + central modulation), Vit B12, TRT referral
10Glue Ear (OME)Amoxicillin-Clavulanate, Mometasone intranasal (adenoid volume reduction, ET function)

NOSE - Cases 11-20

#ConditionKey Drugs & Rationale
11Allergic RhinitisMometasone (best single agent), Cetirizine (H1 blocker), Montelukast (anti-leukotriene)
12Acute Bacterial SinusitisAmox-Clav (beta-lactamase stable), Xylometazoline (ostial drainage), Mometasone
13CRS with Nasal PolypsOral Prednisolone (polyp shrinkage), maintenance Mometasone, FESS if refractory
14Epistaxis (anterior)Silver nitrate cautery, Naseptin cream, Tranexamic acid (antifibrinolytic)
15Deviated Nasal SeptumFluticasone (turbinate reduction), Septoplasty referral
16Rhinitis MedicamentosaOxymetazoline withdrawal + Mometasone (counters rebound congestion)
17Nasal Foreign BodyXylometazoline pre-procedure, Mother's kiss technique, Mupirocin post-removal
18Acute Viral RhinitisCetirizine, Xylometazoline (max 5 days), Zinc lozenges (reduces cold duration ~33%)
19Post-COVID AnosmiaOlfactory training (most evidence-based), Alpha-lipoic acid, intranasal Vitamin A
20Septal HaematomaURGENT I&D (prevents cartilage necrosis/saddle nose), Amox-Clav prophylaxis

THROAT / LARYNX - Cases 21-30

#ConditionKey Drugs & Rationale
21Acute Streptococcal TonsillitisBenzathine Pen G (single IM, prevents rheumatic fever), Betamethasone IM (reduces oedema)
22Peritonsillar Abscess (Quinsy)URGENT I&D, Benzylpenicillin + Metronidazole IV (strep + Fusobacterium/anaerobes)
23Chronic TonsillitisTonsillectomy (Paradise criteria met), Amoxicillin for current exacerbation
24Adenoid HypertrophyMometasone + Montelukast (lymphoid reduction), Adenoidectomy if no response
25Acute Viral PharyngitisBenzydamine gargle (topical NSAID), no antibiotics needed
26Laryngopharyngeal RefluxOmeprazole BD (LPR requires BD not OD), Domperidone (prokinetic), Alginate raft
27Vocal Cord NodulesVoice therapy (resolves majority), Omeprazole (LPR co-exists in 40%), Carbocisteine
28Acute EpiglottitisAIRWAY FIRST, Ceftriaxone IV (H. influenzae), Dexamethasone IV, Adrenaline nebuliser
29LaryngomalaciaOmeprazole suspension (GERD worsens inflammation), thickened feeds, Supraglottoplasty if severe
30Adult OSACPAP (gold standard), Montelukast (mucosal oedema), UPPP if CPAP intolerant

The PDF also includes a Quick Reference Drug Class Table covering all major ENT drug classes (penicillins, fluoroquinolones, corticosteroids, antihistamines, vestibular agents, PPIs, antifungals, mucolytics, and more) with their primary ENT indications. Purely for educational and practice use.

Create a quick prescriptions-only cheat sheet as a separate PDF

Writing File

~/ent-opd/ent_cheatsheet.py

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT

OUTPUT = "/home/daytona/workspace/ent-opd/ENT_OPD_Prescription_Cheatsheet.pdf"

PAGE_W, PAGE_H = A4
MARGIN = 1.2 * cm

# Colours
NAVY   = colors.HexColor("#1A3557")
TEAL   = colors.HexColor("#0E7C7B")
EAR_C  = colors.HexColor("#1A6B8A")
NOSE_C = colors.HexColor("#2E7D32")
THR_C  = colors.HexColor("#6A1E55")
LBLUE  = colors.HexColor("#D6E8F5")
LGREEN = colors.HexColor("#E6F4EA")
LPURP  = colors.HexColor("#F3E5F5")
LGREY  = colors.HexColor("#F5F5F5")
WHITE  = colors.white
RED    = colors.HexColor("#C0392B")

TITLE  = ParagraphStyle("T",  fontSize=8,  fontName="Helvetica-Bold", textColor=WHITE, leading=10)
DRUG   = ParagraphStyle("D",  fontSize=7.5,fontName="Helvetica-Bold", textColor=NAVY,  leading=10)
DOSE   = ParagraphStyle("Ds", fontSize=7,  fontName="Helvetica",      textColor=colors.black, leading=9)
DX     = ParagraphStyle("Dx", fontSize=7,  fontName="Helvetica-BoldOblique", textColor=RED, leading=9)
HDR    = ParagraphStyle("H",  fontSize=9,  fontName="Helvetica-Bold", textColor=WHITE, leading=11, alignment=TA_CENTER)
SECTLBL= ParagraphStyle("SL", fontSize=14, fontName="Helvetica-Bold", textColor=WHITE, leading=18, alignment=TA_CENTER)
FOOTER = ParagraphStyle("F",  fontSize=6.5,fontName="Helvetica",      textColor=colors.grey, alignment=TA_CENTER)
MAINTITLE = ParagraphStyle("MT", fontSize=20, fontName="Helvetica-Bold", textColor=WHITE,
                             alignment=TA_CENTER, leading=24)
SUBTITLE  = ParagraphStyle("ST2", fontSize=10, fontName="Helvetica",  textColor=LBLUE,
                             alignment=TA_CENTER, leading=13)

# โ”€โ”€โ”€ data: (case_no, diagnosis, dx_label, [(drug, dose_freq_duration), ...]) โ”€
CASES = [
# EAR
(1,  "Acute Otitis Media",              "EAR",
 [("Amoxicillin 500 mg",        "1 cap TID ร— 7 days"),
  ("Paracetamol 500 mg",        "1 tab TID PRN ร— 5 days"),
  ("Xylometazoline 0.05% drops","2 drops each nostril BD ร— 5 days")]),

(2,  "CSOM - Tubotympanic (Safe)",      "EAR",
 [("Ciprofloxacin 0.3%+Dexa otic","3 drops TID ร— 2 wks (after ear toilet)"),
  ("Ciprofloxacin 500 mg tab",    "1 tab BD ร— 7 days"),
  ("Betahistine 16 mg",           "1 tab BD ร— 4 weeks")]),

(3,  "Acute Otitis Externa",            "EAR",
 [("Cipro 0.2%+Hydrocort 1% otic","4 drops QID ร— 7 days"),
  ("Ibuprofen 400 mg",            "1 tab TID after food ร— 5 days"),
  ("Clotrimazole 1% ear drops",   "3 drops TID ร— 2 wks (if fungal)")]),

(4,  "Otomycosis (Diabetic)",           "EAR",
 [("Clotrimazole 1% ear drops",   "3 drops TID ร— 3 wks (after aural toilet)"),
  ("Fluconazole 150 mg cap",      "1 cap weekly ร— 2 doses"),
  ("Betamethasone 0.1% cream",    "Thin layer OD ร— 1 wk (external canal)"),
  ("Hโ‚‚Oโ‚‚ cerumenolytic drops",    "2 drops BD ร— 3 days (pre-suction)")]),

(5,  "BPPV - Posterior Canal",          "EAR",
 [("Epley Manoeuvre",             "Performed OPD; home Brandt-Daroff exercises"),
  ("Betahistine 24 mg",           "1 tab BD ร— 4 weeks"),
  ("Prochlorperazine 5 mg",       "1 tab TID PRN (acute nausea only)")]),

(6,  "Meniere's Disease",               "EAR",
 [("Betahistine 24 mg",           "1 tab TID ร— 3 months"),
  ("Hydrochlorothiazide 25 mg",   "1 tab OD (morning) ร— 8 weeks"),
  ("Prochlorperazine 5 mg",       "1 tab TID PRN during acute attack"),
  ("Lorazepam 1 mg SL",           "0.5-1 mg SL PRN severe attack; max 3 days")]),

(7,  "Sudden SNHL (Idiopathic)",        "EAR",
 [("Prednisolone 60 mg (1mg/kg)", "OD morning ร— 10 days then taper ร— 5 days"),
  ("Pantoprazole 40 mg",          "1 tab OD morning (GI cover)"),
  ("Acyclovir 400 mg",            "5 times/day ร— 7 days"),
  ("Betahistine 16 mg",           "1 tab TID ร— 4 weeks")]),

(8,  "Wax Impaction",                   "EAR",
 [("Carbamide peroxide 6.5% drops","2-3 drops TID ร— 3-5 days (pre-syringing)"),
  ("Microsuction / Syringing",    "After softening; avoid if TM perforation")]),

(9,  "Tinnitus (Noise-induced SNHL)",   "EAR",
 [("Betahistine 16 mg",           "1 tab TID ร— 3 months"),
  ("Amitriptyline 10 mg",         "1 tab at bedtime ร— 6 weeks"),
  ("Vitamin B12 1000 mcg",        "1 tab OD ร— 2 months"),
  ("TRT / White noise generator", "Refer audiology")]),

(10, "Otitis Media with Effusion",      "EAR",
 [("Amoxicillin-Clavulanate 457 mg susp","7 mL BD ร— 10 days"),
  ("Xylometazoline 0.05% drops",  "2 drops each nostril BD ร— 5 days"),
  ("Mometasone 50 mcg spray",     "1 puff each nostril OD ร— 8 weeks"),
  ("Grommets + Adenoidectomy",    "If no improvement at 3 months")]),

# NOSE
(11, "Allergic Rhinitis (Perennial)",   "NOSE",
 [("Mometasone 50 mcg nasal spray","2 puffs each nostril OD ร— 8 wks"),
  ("Cetirizine 10 mg",            "1 tab OD at bedtime ร— 4 weeks"),
  ("Montelukast 10 mg",           "1 tab OD at bedtime ร— 4 weeks"),
  ("Saline nasal irrigation",     "BD ongoing")]),

(12, "Acute Bacterial Rhinosinusitis",  "NOSE",
 [("Amoxicillin-Clavulanate 625 mg","1 tab TID ร— 10-14 days"),
  ("Xylometazoline 0.1% spray",   "1-2 sprays each nostril TID ร— 5 days"),
  ("Mometasone 50 mcg spray",     "2 puffs each nostril BD ร— 3 weeks"),
  ("Ibuprofen 400 mg",            "1 tab TID after food ร— 5 days")]),

(13, "CRS with Nasal Polyps (Samter's)","NOSE",
 [("Prednisolone 40 mg tab",      "OD ร— 7 days then taper"),
  ("Mometasone 50 mcg spray",     "2 puffs each nostril BD (long-term)"),
  ("Hypertonic saline douche",    "BD ongoing"),
  ("FESS referral",               "If no response to 3 months medical Rx")]),

(14, "Anterior Epistaxis (Kiesselbach)","NOSE",
 [("Silver nitrate cauterisation","OPD procedure under LA"),
  ("Naseptin cream",              "Apply inside nostrils TID ร— 2 weeks"),
  ("Petroleum jelly (Vaseline)",  "Apply BD ongoing (moisturisation)"),
  ("Tranexamic acid 500 mg",      "1 tab TID ร— 5 days (if active bleeding)")]),

(15, "Deviated Nasal Septum",          "NOSE",
 [("Fluticasone 50 mcg spray",    "2 puffs each nostril OD ร— 6 weeks"),
  ("Xylometazoline 0.1% spray",   "1 spray at bedtime PRN"),
  ("Saline nasal rinse",          "OD ongoing"),
  ("Septoplasty referral",        "Definitive surgical correction")]),

(16, "Rhinitis Medicamentosa",         "NOSE",
 [("Oxymetazoline withdrawal",    "Gradual; one nostril at a time"),
  ("Mometasone 50 mcg spray",     "2 puffs each nostril BD ร— 6-8 weeks"),
  ("Prednisolone 20 mg",          "OD ร— 5 days (burst for withdrawal oedema)"),
  ("Saline rinse",                "BD ongoing")]),

(17, "Nasal Foreign Body (Child)",     "NOSE",
 [("Xylometazoline 0.05% drops",  "2 drops 5 min before removal procedure"),
  ("FB removal",                  "Jobson-Horne probe / Mother's kiss technique"),
  ("Mupirocin 2% nasal ointment", "Apply TID ร— 5 days (post-removal)")]),

(18, "Acute Viral Rhinitis",           "NOSE",
 [("Cetirizine 10 mg",            "1 tab OD at bedtime ร— 5 days"),
  ("Xylometazoline 0.1% spray",   "1 spray each nostril TID ร— 3-5 days"),
  ("Paracetamol 500 mg",          "1-2 tabs TID PRN"),
  ("Zinc gluconate 13.3 mg loz",  "1 lozenge q2h awake ร— 5 days (within 24 hrs onset)")]),

(19, "Post-COVID Anosmia / Parosmia",  "NOSE",
 [("Olfactory training",          "4 odours ร— 20 sec BD ร— โ‰ฅ4 months"),
  ("Alpha-lipoic acid 600 mg",    "1 tab OD ร— 4 months"),
  ("Intranasal Vitamin A drops",  "Apply to olfactory epithelium OD ร— 4 months"),
  ("Omega-3 1000 mg cap",         "1 cap BD ร— 3 months")]),

(20, "Nasal Septal Haematoma",         "NOSE",
 [("I&D - URGENT",                "Horizontal incision; evacuate clot; quilt suture"),
  ("Anterior nasal packing",      "Merocel bilateral ร— 48 hrs post-I&D"),
  ("Amoxicillin-Clavulanate 625 mg","1 tab TID ร— 7 days"),
  ("Ibuprofen 400 mg",            "1 tab TID ร— 5 days")]),

# THROAT
(21, "Acute GAS Tonsillitis",          "THROAT",
 [("Benzathine Penicillin G 1.2 MU IM","Single dose IM  (or Pen V 500 mg TID ร— 10 days)"),
  ("Ibuprofen 400 mg",            "1 tab TID after food ร— 5 days"),
  ("Betamethasone 8 mg IM",       "Single dose (severe odynophagia/oedema)"),
  ("Chlorhexidine 0.2% gargle",   "15 mL ร— 30 sec TID ร— 5 days")]),

(22, "Peritonsillar Abscess (Quinsy)", "THROAT",
 [("I&D - URGENT",                "Aspiration/incision at point of max bulge under LA"),
  ("Benzylpenicillin 2.4 g IV q6h","+ Metronidazole 500 mg IV TID ร— 48 hrs"),
  ("Amox-Clav 625 mg tab",        "1 tab TID ร— 10 days (step-down)"),
  ("Dexamethasone 8 mg IV",       "Single dose (reduces trismus/oedema)")]),

(23, "Chronic Tonsillitis",            "THROAT",
 [("Tonsillectomy referral",      "Paradise criteria met (>7/yr or >5/yr ร— 2 yrs)"),
  ("Amoxicillin 500 mg",          "1 cap TID ร— 10 days (current exacerbation)"),
  ("Ibuprofen 400 mg",            "1 tab TID ร— 5 days PRN"),
  ("Warm saline gargle",          "TID ongoing")]),

(24, "Adenoid Hypertrophy + OSA",      "THROAT",
 [("Mometasone 50 mcg spray",     "1 puff each nostril OD ร— 3 months"),
  ("Montelukast 5 mg tab",        "1 tab OD at bedtime ร— 3 months"),
  ("Xylometazoline 0.05% drops",  "2 drops at bedtime ร— 5 days"),
  ("Adenoidectomy ยฑ Grommets",    "If no improvement at 3 months")]),

(25, "Acute Viral Pharyngitis",        "THROAT",
 [("Paracetamol 500 mg",          "1-2 tabs TID PRN ร— 5 days"),
  ("Benzydamine 0.15% oral rinse","15 mL gargle ร— 30 sec q3h ร— 7 days"),
  ("Antiseptic lozenges (AMC)",   "1 lozenge q3h; max 8/day ร— 5 days"),
  ("Honey + warm water gargle",   "PRN; coat pharyngeal mucosa")]),

(26, "Laryngopharyngeal Reflux (LPR)", "THROAT",
 [("Omeprazole 20 mg cap",        "1 cap BD (30 min pre-meals) ร— 3 months"),
  ("Domperidone 10 mg",           "1 tab TID before meals ร— 4 weeks"),
  ("Gaviscon Advance 10 mL",      "After each meal + at bedtime ร— 8 weeks"),
  ("Lifestyle modifications",     "Avoid spicy/fatty/coffee; elevate HOB 30ยฐ")]),

(27, "Vocal Cord Nodules",             "THROAT",
 [("Voice rest + Voice therapy",  "Relative rest; SLP referral ร— 6-8 weeks"),
  ("Omeprazole 20 mg",            "1 cap BD ร— 8 weeks (treat co-existing LPR)"),
  ("Carbocisteine 375 mg",        "1 cap TID ร— 4 weeks (mucolytic)"),
  ("Microlaryngoscopy",           "If no resolution after 3 months therapy")]),

(28, "Acute Epiglottitis - EMERGENCY", "THROAT",
 [("AIRWAY FIRST",                "Nasotracheal intubation in OT / Emergency tracheostomy"),
  ("Ceftriaxone 2 g IV OD",       "ร— 7-10 days"),
  ("Dexamethasone 8 mg IV q8h",   "ร— 48 hrs (reduces supraglottic oedema)"),
  ("Adrenaline neb 0.5 mL 1:1000","in 2.5 mL NS; PRN while preparing airway")]),

(29, "Laryngomalacia (Infant)",        "THROAT",
 [("Omeprazole 1 mg/kg susp",     "BD ร— 3 months (treat GERD co-morbidity)"),
  ("Thickened feeds / AR formula","Reduce aspiration and GERD"),
  ("Supraglottoplasty",           "If severe: divide short aryepiglottic folds"),
  ("Parental reassurance",        ">90% resolve spontaneously by 12-18 months")]),

(30, "Obstructive Sleep Apnoea (OSA)", "THROAT",
 [("CPAP 8-12 cmHโ‚‚O",            "First-line; nightly; titrate with sleep physician"),
  ("Montelukast 10 mg",           "1 tab OD at bedtime ร— 3 months (adjunct)"),
  ("Weight reduction programme",  "Target 10% loss; diet referral + exercise"),
  ("UPPP ยฑ Tonsillectomy",        "Surgical option if CPAP intolerant")]),
]

CAT_COLOR = {"EAR": EAR_C, "NOSE": NOSE_C, "THROAT": THR_C}
CAT_LIGHT  = {"EAR": LBLUE, "NOSE": LGREEN, "THROAT": LPURP}

def page_deco(canvas_obj, doc):
    canvas_obj.saveState()
    # top bar
    canvas_obj.setFillColor(NAVY)
    canvas_obj.rect(0, PAGE_H - 1.2*cm, PAGE_W, 1.2*cm, fill=1, stroke=0)
    canvas_obj.setFont("Helvetica-Bold", 8)
    canvas_obj.setFillColor(WHITE)
    canvas_obj.drawString(MARGIN, PAGE_H - 0.75*cm, "ENT OPD PRESCRIPTION CHEAT SHEET  |  30 Cases")
    canvas_obj.setFont("Helvetica", 8)
    canvas_obj.drawRightString(PAGE_W - MARGIN, PAGE_H - 0.75*cm, f"Page {doc.page}")
    # bottom bar
    canvas_obj.setFillColor(NAVY)
    canvas_obj.rect(0, 0, PAGE_W, 1.0*cm, fill=1, stroke=0)
    canvas_obj.setFont("Helvetica", 6.5)
    canvas_obj.setFillColor(colors.HexColor("#AAAAAA"))
    canvas_obj.drawCentredString(PAGE_W/2, 0.35*cm,
        "For Educational & Practice Use Only  |  Verify doses against current formulary  |  Apply clinical judgment")
    canvas_obj.restoreState()


def cover_deco(canvas_obj, doc):
    canvas_obj.saveState()
    canvas_obj.setFillColor(NAVY)
    canvas_obj.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
    canvas_obj.setFillColor(TEAL)
    canvas_obj.rect(0, PAGE_H*0.42, PAGE_W, 6, fill=1, stroke=0)
    canvas_obj.setFillColor(EAR_C)
    canvas_obj.rect(0, 1.0*cm, PAGE_W*0.33, 0.5*cm, fill=1, stroke=0)
    canvas_obj.setFillColor(NOSE_C)
    canvas_obj.rect(PAGE_W*0.33, 1.0*cm, PAGE_W*0.33, 0.5*cm, fill=1, stroke=0)
    canvas_obj.setFillColor(THR_C)
    canvas_obj.rect(PAGE_W*0.66, 1.0*cm, PAGE_W*0.34, 0.5*cm, fill=1, stroke=0)
    canvas_obj.setFont("Helvetica-Bold", 7)
    canvas_obj.setFillColor(WHITE)
    canvas_obj.drawCentredString(PAGE_W*0.165, 1.22*cm, "EAR  (1-10)")
    canvas_obj.drawCentredString(PAGE_W*0.495, 1.22*cm, "NOSE  (11-20)")
    canvas_obj.drawCentredString(PAGE_W*0.83, 1.22*cm, "THROAT  (21-30)")
    canvas_obj.restoreState()


def build():
    doc = SimpleDocTemplate(OUTPUT, pagesize=A4,
                            leftMargin=MARGIN, rightMargin=MARGIN,
                            topMargin=1.5*cm, bottomMargin=1.4*cm)
    story = []

    # โ”€โ”€ COVER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    story.append(Spacer(1, 3.2*cm))
    story.append(Paragraph("ENT OPD", MAINTITLE))
    story.append(Spacer(1, 0.3*cm))
    story.append(Paragraph("PRESCRIPTION CHEAT SHEET", MAINTITLE))
    story.append(Spacer(1, 0.5*cm))
    story.append(Paragraph("30 Cases  |  Ear ยท Nose ยท Throat", SUBTITLE))
    story.append(Spacer(1, 0.3*cm))
    story.append(Paragraph("Quick-reference prescriptions for OPD practice &amp; revision", SUBTITLE))
    story.append(Spacer(1, 2.0*cm))

    legend = [
        [Paragraph("<b>EAR</b>", ParagraphStyle("L", fontSize=10, textColor=WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER)),
         Paragraph("<b>NOSE</b>", ParagraphStyle("L2", fontSize=10, textColor=WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER)),
         Paragraph("<b>THROAT</b>", ParagraphStyle("L3", fontSize=10, textColor=WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER))],
        [Paragraph("Cases 1-10", ParagraphStyle("LS", fontSize=8, textColor=LBLUE, fontName="Helvetica", alignment=TA_CENTER)),
         Paragraph("Cases 11-20", ParagraphStyle("LS2", fontSize=8, textColor=LGREEN, fontName="Helvetica", alignment=TA_CENTER)),
         Paragraph("Cases 21-30", ParagraphStyle("LS3", fontSize=8, textColor=LPURP, fontName="Helvetica", alignment=TA_CENTER))],
    ]
    leg_table = Table(legend, colWidths=[PAGE_W/3 - 2.4*cm]*3)
    leg_table.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(0,-1), EAR_C),
        ("BACKGROUND", (1,0),(1,-1), NOSE_C),
        ("BACKGROUND", (2,0),(2,-1), THR_C),
        ("TOPPADDING", (0,0),(-1,-1), 10),
        ("BOTTOMPADDING", (0,0),(-1,-1), 10),
        ("ALIGN", (0,0),(-1,-1), "CENTER"),
    ]))
    story.append(leg_table)
    story.append(PageBreak())

    # โ”€โ”€ CHEAT SHEET PAGES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    # Layout: 2 columns, cases stacked vertically
    # We'll build each case as a mini-table and arrange 2 per row

    col_w = (PAGE_W - 2*MARGIN - 0.4*cm) / 2  # two columns with gap

    def make_case_block(case):
        no, diag, cat, rxs = case
        bg    = CAT_COLOR[cat]
        light = CAT_LIGHT[cat]

        # Header row
        hdr_data = [[
            Paragraph(f"<b>#{no:02d}</b>",
                ParagraphStyle("CN", fontSize=8, textColor=light, fontName="Helvetica-Bold", leading=10)),
            Paragraph(f"<b>{diag}</b>",
                ParagraphStyle("DG", fontSize=7.5, textColor=WHITE, fontName="Helvetica-Bold", leading=10)),
            Paragraph(cat,
                ParagraphStyle("CA", fontSize=7, textColor=light, fontName="Helvetica-Bold",
                                leading=10, alignment=2)),
        ]]
        hdr_table = Table(hdr_data, colWidths=[0.9*cm, col_w - 2.1*cm, 1.2*cm])
        hdr_table.setStyle(TableStyle([
            ("BACKGROUND", (0,0),(-1,-1), bg),
            ("VALIGN", (0,0),(-1,-1), "MIDDLE"),
            ("TOPPADDING", (0,0),(-1,-1), 4),
            ("BOTTOMPADDING", (0,0),(-1,-1), 4),
            ("LEFTPADDING", (0,0),(0,-1), 5),
            ("LEFTPADDING", (1,0),(1,-1), 4),
            ("RIGHTPADDING", (-1,0),(-1,-1), 5),
        ]))

        # Rx rows
        rx_rows = []
        for i, (drug, dose) in enumerate(rxs):
            row_bg = light if i % 2 == 0 else WHITE
            rx_rows.append([
                Paragraph(f"โ€ข <b>{drug}</b>", DRUG),
                Paragraph(dose, DOSE),
            ])

        rx_table = Table(rx_rows, colWidths=[col_w*0.42, col_w*0.58])
        rx_table.setStyle(TableStyle([
            ("ROWBACKGROUNDS", (0,0),(-1,-1), [light, WHITE]),
            ("VALIGN", (0,0),(-1,-1), "TOP"),
            ("TOPPADDING", (0,0),(-1,-1), 2),
            ("BOTTOMPADDING", (0,0),(-1,-1), 2),
            ("LEFTPADDING", (0,0),(-1,-1), 5),
            ("GRID", (0,0),(-1,-1), 0.2, colors.HexColor("#CCCCCC")),
        ]))

        # Wrap both in an outer table so they share the same width
        outer = Table([[hdr_table], [rx_table]], colWidths=[col_w])
        outer.setStyle(TableStyle([
            ("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), 0.5, bg),
        ]))
        return outer

    # Group into pairs for 2-column layout
    pairs = []
    for i in range(0, len(CASES), 2):
        left  = make_case_block(CASES[i])
        right = make_case_block(CASES[i+1]) if i+1 < len(CASES) else Spacer(col_w, 1)
        pairs.append((left, right))

    # Section banners inserted before first case of each section
    section_inserts = {
        0:  ("EAR CASES",           "Cases 1 - 10",  EAR_C),
        10: ("NOSE CASES",          "Cases 11 - 20", NOSE_C),
        20: ("THROAT & LARYNX",     "Cases 21 - 30", THR_C),
    }

    for pair_idx, (left, right) in enumerate(pairs):
        case_idx = pair_idx * 2  # index of first case in this pair

        if case_idx in section_inserts:
            sec_title, sec_sub, sec_bg = section_inserts[case_idx]
            sec_data = [[
                Paragraph(f"<b>{sec_title}</b>",
                    ParagraphStyle("SBT", fontSize=12, textColor=WHITE, fontName="Helvetica-Bold", leading=15)),
                Paragraph(sec_sub,
                    ParagraphStyle("SBS", fontSize=9, textColor=colors.HexColor("#DDDDDD"),
                                   fontName="Helvetica", leading=12, alignment=2)),
            ]]
            sec_table = Table(sec_data, colWidths=[PAGE_W - 2*MARGIN - col_w*0.3, col_w*0.3])
            sec_table.setStyle(TableStyle([
                ("BACKGROUND", (0,0),(-1,-1), sec_bg),
                ("TOPPADDING", (0,0),(-1,-1), 7),
                ("BOTTOMPADDING", (0,0),(-1,-1), 7),
                ("LEFTPADDING", (0,0),(0,-1), 12),
                ("RIGHTPADDING", (-1,0),(-1,-1), 12),
                ("VALIGN", (0,0),(-1,-1), "MIDDLE"),
            ]))
            story.append(sec_table)
            story.append(Spacer(1, 4))

        row_table = Table([[left, Spacer(0.4*cm, 1), right]], colWidths=[col_w, 0.4*cm, col_w])
        row_table.setStyle(TableStyle([
            ("VALIGN", (0,0),(-1,-1), "TOP"),
            ("LEFTPADDING", (0,0),(-1,-1), 0),
            ("RIGHTPADDING", (0,0),(-1,-1), 0),
            ("TOPPADDING", (0,0),(-1,-1), 0),
            ("BOTTOMPADDING", (0,0),(-1,-1), 0),
        ]))
        story.append(row_table)
        story.append(Spacer(1, 5))

    # โ”€โ”€ QUICK-REFERENCE BANNER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    story.append(PageBreak())
    story.append(Spacer(1, 0.3*cm))

    qr_title = ParagraphStyle("QT", fontSize=13, fontName="Helvetica-Bold",
                               textColor=NAVY, alignment=TA_CENTER, spaceAfter=6)
    story.append(Paragraph("FIRST-LINE DRUG QUICK REFERENCE", qr_title))
    story.append(HRFlowable(width="100%", thickness=2, color=TEAL, spaceAfter=8))

    qr_data = [
        [Paragraph("<b>Condition</b>", HDR),
         Paragraph("<b>Drug of Choice</b>", HDR),
         Paragraph("<b>Alternative / Add-on</b>", HDR)],
        ["Acute Otitis Media", "Amoxicillin 80-90 mg/kg/day", "Amox-Clav (beta-lactamase)"],
        ["CSOM (Pseudomonas)", "Ciprofloxacin otic", "Oral Ciprofloxacin"],
        ["Otitis Externa", "Cipro 0.2% + Hydrocort otic", "Clotrimazole (if fungal)"],
        ["Otomycosis", "Clotrimazole 1% otic", "Fluconazole oral (DM/immuno)"],
        ["BPPV", "Epley Manoeuvre", "Betahistine (chronic)"],
        ["Meniere's Disease", "Betahistine 24 mg TID", "HCTZ diuretic"],
        ["Sudden SNHL", "Prednisolone 1 mg/kg OD", "Intratympanic steroid"],
        ["Tinnitus", "Betahistine + TRT", "Amitriptyline 10 mg nocte"],
        ["Allergic Rhinitis", "Intranasal Mometasone", "Cetirizine + Montelukast"],
        ["Acute Bacterial Sinusitis", "Amoxicillin-Clavulanate", "Doxycycline (pen allergy)"],
        ["CRS with Polyps", "Oral Prednisolone (short) + INS", "FESS"],
        ["Epistaxis (anterior)", "Silver nitrate cautery", "Tranexamic acid"],
        ["Rhinitis Medicamentosa", "Withdraw decongestant + INS", "Steroid burst"],
        ["Post-COVID Anosmia", "Olfactory training", "Alpha-lipoic acid + Vit A"],
        ["GAS Tonsillitis", "Benzathine Pen G IM", "Amoxicillin 10 days"],
        ["Peritonsillar Abscess", "I&D + Pen G + Metronidazole", "Amox-Clav step-down"],
        ["LPR", "Omeprazole 20 mg BD", "Alginate + Domperidone"],
        ["Vocal Cord Nodules", "Voice therapy + Voice rest", "Omeprazole (co-LPR)"],
        ["Acute Epiglottitis", "SECURE AIRWAY + Ceftriaxone IV", "Dexamethasone IV"],
        ["OSA (moderate-severe)", "CPAP", "UPPP (if CPAP intolerant)"],
    ]

    qr_col_w = [(PAGE_W - 2*MARGIN) * x for x in [0.33, 0.35, 0.32]]
    qr_table = Table(qr_data, colWidths=qr_col_w)
    qr_table.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), NAVY),
        ("TEXTCOLOR", (0,0),(-1,0), WHITE),
        ("FONTNAME", (0,0),(-1,0), "Helvetica-Bold"),
        ("FONTSIZE", (0,0),(-1,-1), 7.5),
        ("ROWBACKGROUNDS", (0,1),(-1,-1), [LGREY, WHITE]),
        ("GRID", (0,0),(-1,-1), 0.3, colors.HexColor("#BBBBBB")),
        ("TOPPADDING", (0,0),(-1,-1), 4),
        ("BOTTOMPADDING", (0,0),(-1,-1), 4),
        ("LEFTPADDING", (0,0),(-1,-1), 6),
        ("VALIGN", (0,0),(-1,-1), "TOP"),
    ]))
    story.append(qr_table)

    story.append(Spacer(1, 0.5*cm))
    story.append(HRFlowable(width="100%", thickness=0.5, color=colors.grey))
    story.append(Spacer(1, 0.2*cm))
    disc = ParagraphStyle("Disc", fontSize=6.5, fontName="Helvetica-Oblique",
                          textColor=colors.grey, alignment=TA_CENTER)
    story.append(Paragraph(
        "Disclaimer: Educational use only. Doses shown are standard adult doses unless stated. "
        "Always check patient weight, allergy status, renal/hepatic function, and local antibiotic guidelines before prescribing.",
        disc))

    def on_page(cv, d):
        if d.page == 1:
            cover_deco(cv, d)
        else:
            page_deco(cv, d)

    doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
    print(f"Saved: {OUTPUT}")


if __name__ == "__main__":
    build()
Running Command

cd /home/daytona/workspace/ent-opd && python ent_cheatsheet.py 2>&1

Running Command

ls -lh /home/daytona/workspace/ent-opd/ENT_OPD_Prescription_Cheatsheet.pdf

Done! Download the cheat sheet here:

ENT OPD Prescription Cheatsheet

PDF Document ยท PDF


What's in this cheat sheet

Format: Compact, 2-column layout - all 30 cases fit in just a few pages for quick OPD reference.
Each case card shows:
  • Case number + diagnosis (colour-coded by section)
  • Drug name in bold
  • Dose / frequency / duration on the right
Colour coding:
  • ๐Ÿ”ต Blue cards - EAR (Cases 1-10)
  • ๐ŸŸข Green cards - NOSE (Cases 11-20)
  • ๐ŸŸฃ Purple cards - THROAT / LARYNX (Cases 21-30)
Bonus last page: A "First-Line Drug Quick Reference" table listing the drug of choice + alternative/add-on for every condition at a glance - useful for exams and busy OPD rounds.

Not a signal pdf is working

Running Command

ls -lh /home/daytona/workspace/ent-opd/*.pdf 2>&1

Running Command

python3 -c " from pypdf import PdfReader for f in [ '/home/daytona/workspace/ent-opd/ENT_OPD_30_Cases.pdf', '/home/daytona/workspace/ent-opd/ENT_OPD_Prescription_Cheatsheet.pdf' ]: r = PdfReader(f) print(f'{f.split(\"/\")[-1]}: {len(r.pages)} pages, valid={not r.is_encrypted}') "

Both PDFs are valid and complete (32 pages and 5 pages). The files themselves are not broken. The download links should work - try clicking them again:
  • ENT OPD 30 Cases

    PDF Document ยท PDF

    - 32 pages, 83 KB
  • ENT OPD Prescription Cheatsheet

    PDF Document ยท PDF

    - 5 pages, 18 KB
If the links still don't open, can you tell me more about what happens when you click them? For example:
  • Does the page show a blank/error?
  • Does it open but show garbled content?
  • Does nothing happen at all?
This will help me figure out if it's a browser issue, a rendering issue, or something else I can fix on my end.
This is a shared conversation. Sign in to Orris to start your own chat.