I want to give case study presentation on otitis media so prepare a otitis media case study presentation pdf

Reading File
Reading File
Finding Sources
Finding Sources
Searching PubMed

"otitis media" management children

Reading File
Reading File
Reading File
Reading File
Running Command

mkdir -p /tmp/workspace/otitis-media-case && ls /tmp/workspace/

Writing File

~/otitis-media-case/build_pdf.py

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, PageBreak, KeepTogether
)
from reportlab.platypus.flowables import Flowable
from reportlab.pdfgen import canvas
from reportlab.graphics.shapes import Drawing, Rect, String, Line
from reportlab.graphics import renderPDF
import os

OUTPUT = "/tmp/workspace/otitis-media-case/Otitis_Media_Case_Study.pdf"

# ─── Colour palette ────────────────────────────────────────────────────────────
NAVY       = colors.HexColor("#1a3557")
BLUE       = colors.HexColor("#2563a8")
LIGHT_BLUE = colors.HexColor("#dbeafe")
TEAL       = colors.HexColor("#0e7490")
TEAL_LIGHT = colors.HexColor("#cffafe")
RED        = colors.HexColor("#be123c")
RED_LIGHT  = colors.HexColor("#ffe4e6")
AMBER      = colors.HexColor("#b45309")
AMBER_LIGHT= colors.HexColor("#fef3c7")
GREEN      = colors.HexColor("#166534")
GREEN_LIGHT= colors.HexColor("#dcfce7")
PURPLE     = colors.HexColor("#6d28d9")
PURPLE_LIGHT=colors.HexColor("#ede9fe")
GREY_DARK  = colors.HexColor("#374151")
GREY_MID   = colors.HexColor("#6b7280")
GREY_LIGHT = colors.HexColor("#f3f4f6")
WHITE      = colors.white

# ─── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()

def S(name, **kw):
    base = styles.get(name) if name in styles else styles["Normal"]
    return ParagraphStyle(name + str(id(kw)), parent=base, **kw)

TITLE_STYLE   = S("Normal", fontSize=28, textColor=WHITE,  fontName="Helvetica-Bold",
                   alignment=TA_CENTER, leading=34, spaceAfter=4)
SUB_STYLE     = S("Normal", fontSize=13, textColor=colors.HexColor("#bfdbfe"),
                   fontName="Helvetica", alignment=TA_CENTER, leading=18)
H1            = S("Normal", fontSize=17, textColor=NAVY,    fontName="Helvetica-Bold",
                   spaceAfter=6, spaceBefore=10, leading=22)
H2            = S("Normal", fontSize=13, textColor=BLUE,    fontName="Helvetica-Bold",
                   spaceAfter=4, spaceBefore=8, leading=17)
H3            = S("Normal", fontSize=11, textColor=TEAL,    fontName="Helvetica-Bold",
                   spaceAfter=3, spaceBefore=6, leading=15)
BODY          = S("Normal", fontSize=10, textColor=GREY_DARK, fontName="Helvetica",
                   spaceAfter=4, leading=14, alignment=TA_JUSTIFY)
BODY_SM       = S("Normal", fontSize=9,  textColor=GREY_DARK, fontName="Helvetica",
                   spaceAfter=3, leading=13)
BULLET        = S("Normal", fontSize=10, textColor=GREY_DARK, fontName="Helvetica",
                   spaceAfter=3, leading=14, leftIndent=14, firstLineIndent=-10)
LABEL         = S("Normal", fontSize=9,  textColor=GREY_MID,  fontName="Helvetica-Bold",
                   spaceAfter=2, leading=12)
CAPTION       = S("Normal", fontSize=8,  textColor=GREY_MID,  fontName="Helvetica-Oblique",
                   alignment=TA_CENTER, leading=11)
ALERT_TXT     = S("Normal", fontSize=10, textColor=RED,      fontName="Helvetica-Bold",
                   leading=14)
GREEN_TXT     = S("Normal", fontSize=10, textColor=GREEN,    fontName="Helvetica-Bold",
                   leading=14)
TH_STYLE      = S("Normal", fontSize=9,  textColor=WHITE,    fontName="Helvetica-Bold",
                   alignment=TA_CENTER, leading=13)
TD_STYLE      = S("Normal", fontSize=9,  textColor=GREY_DARK,fontName="Helvetica",
                   alignment=TA_CENTER, leading=13)
TD_LEFT       = S("Normal", fontSize=9,  textColor=GREY_DARK,fontName="Helvetica",
                   alignment=TA_LEFT, leading=13)

# ─── Page number canvas ────────────────────────────────────────────────────────
class NumberedCanvas(canvas.Canvas):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._saved_page_states = []

    def showPage(self):
        self._saved_page_states.append(dict(self.__dict__))
        self._startPage()

    def save(self):
        num_pages = len(self._saved_page_states)
        for state in self._saved_page_states:
            self.__dict__.update(state)
            self.draw_page_number(num_pages)
            canvas.Canvas.showPage(self)
        canvas.Canvas.save(self)

    def draw_page_number(self, page_count):
        page_num = self._saved_page_states.index(dict(self.__dict__)) + 1
        # skip cover page
        if page_num == 1:
            return
        self.setFont("Helvetica", 8)
        self.setFillColor(GREY_MID)
        self.drawRightString(A4[0] - 1.5*cm, 1.2*cm,
                             f"Otitis Media – Case Study Presentation  |  Page {page_num} of {page_count}")
        self.setStrokeColor(colors.HexColor("#e5e7eb"))
        self.setLineWidth(0.5)
        self.line(1.5*cm, 1.5*cm, A4[0]-1.5*cm, 1.5*cm)


# ─── Cover page background ─────────────────────────────────────────────────────
def cover_background(canvas_obj, doc):
    w, h = A4
    canvas_obj.saveState()
    # deep navy background
    canvas_obj.setFillColor(NAVY)
    canvas_obj.rect(0, 0, w, h, fill=1, stroke=0)
    # blue accent band top
    canvas_obj.setFillColor(BLUE)
    canvas_obj.rect(0, h-3.5*cm, w, 3.5*cm, fill=1, stroke=0)
    # teal accent band bottom
    canvas_obj.setFillColor(TEAL)
    canvas_obj.rect(0, 0, w, 2*cm, fill=1, stroke=0)
    # decorative circle top-right
    canvas_obj.setFillColor(colors.HexColor("#1e4d8c"))
    canvas_obj.circle(w-1*cm, h+0.5*cm, 5*cm, fill=1, stroke=0)
    # decorative circle bottom-left
    canvas_obj.setFillColor(colors.HexColor("#0c3060"))
    canvas_obj.circle(0.5*cm, -0.5*cm, 4*cm, fill=1, stroke=0)
    canvas_obj.restoreState()


def normal_background(canvas_obj, doc):
    w, h = A4
    canvas_obj.saveState()
    # thin top bar
    canvas_obj.setFillColor(NAVY)
    canvas_obj.rect(0, h-1.2*cm, w, 1.2*cm, fill=1, stroke=0)
    # header text
    canvas_obj.setFont("Helvetica-Bold", 9)
    canvas_obj.setFillColor(WHITE)
    canvas_obj.drawString(1.5*cm, h-0.85*cm, "OTITIS MEDIA")
    canvas_obj.setFont("Helvetica", 9)
    canvas_obj.setFillColor(colors.HexColor("#93c5fd"))
    canvas_obj.drawRightString(w-1.5*cm, h-0.85*cm, "Case Study Presentation")
    canvas_obj.restoreState()


# ─── Helper: coloured info box ─────────────────────────────────────────────────
def info_box(title, lines, bg_color, title_color, bullet_char="•"):
    rows = [[Paragraph(f"<b>{title}</b>", S("Normal", fontSize=10,
              textColor=title_color, fontName="Helvetica-Bold", leading=14))]]
    for line in lines:
        rows.append([Paragraph(f"{bullet_char}  {line}", BODY_SM)])
    t = Table(rows, colWidths=[16.5*cm])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,0), bg_color),
        ("BACKGROUND", (0,1), (-1,-1), colors.HexColor("#f9fafb")),
        ("BOX",        (0,0), (-1,-1), 0.8, title_color),
        ("LINEBELOW",  (0,0), (-1,0),  0.8, title_color),
        ("LEFTPADDING",(0,0), (-1,-1), 10),
        ("RIGHTPADDING",(0,0),(-1,-1), 10),
        ("TOPPADDING", (0,0), (-1,-1), 6),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.HexColor("#f9fafb"), WHITE]),
    ]))
    return t


def two_col_box(left_title, left_items, right_title, right_items,
                lbg, ltc, rbg, rtc):
    def col_block(title, items, bg, tc):
        rows = [[Paragraph(f"<b>{title}</b>",
                  S("Normal", fontSize=10, textColor=tc,
                    fontName="Helvetica-Bold", leading=14))]]
        for it in items:
            rows.append([Paragraph(f"•  {it}", BODY_SM)])
        t = Table(rows, colWidths=[7.8*cm])
        t.setStyle(TableStyle([
            ("BACKGROUND", (0,0), (-1,0), bg),
            ("BACKGROUND", (0,1), (-1,-1), colors.HexColor("#f9fafb")),
            ("BOX",        (0,0), (-1,-1), 0.8, tc),
            ("LINEBELOW",  (0,0), (-1,0),  0.8, tc),
            ("LEFTPADDING",(0,0), (-1,-1), 8),
            ("RIGHTPADDING",(0,0),(-1,-1), 8),
            ("TOPPADDING", (0,0), (-1,-1), 5),
            ("BOTTOMPADDING",(0,0),(-1,-1), 4),
        ]))
        return t
    outer = Table(
        [[col_block(left_title, left_items, lbg, ltc),
          col_block(right_title, right_items, rbg, rtc)]],
        colWidths=[8.0*cm, 8.5*cm], hAlign="LEFT"
    )
    outer.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),
    ]))
    return outer


def section_header_bar(text, bg=BLUE, fg=WHITE):
    t = Table([[Paragraph(text, S("Normal", fontSize=13, textColor=fg,
               fontName="Helvetica-Bold", leading=17))]],
              colWidths=[16.5*cm])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,-1), bg),
        ("LEFTPADDING",(0,0),(-1,-1), 12),
        ("TOPPADDING", (0,0),(-1,-1), 7),
        ("BOTTOMPADDING",(0,0),(-1,-1), 7),
        ("RIGHTPADDING",(0,0),(-1,-1), 12),
    ]))
    return t


# ─── Build document ────────────────────────────────────────────────────────────
def build():
    doc = SimpleDocTemplate(
        OUTPUT, pagesize=A4,
        leftMargin=1.5*cm, rightMargin=1.5*cm,
        topMargin=2*cm,   bottomMargin=2*cm,
    )

    story = []

    # ═══════════════════════════════════════════════════════════════════════════
    # COVER PAGE
    # ═══════════════════════════════════════════════════════════════════════════
    # (background drawn by onFirstPage)
    story.append(Spacer(1, 3*cm))
    story.append(Paragraph("OTITIS MEDIA", TITLE_STYLE))
    story.append(Spacer(1, 0.4*cm))
    story.append(Paragraph("Case Study Presentation", SUB_STYLE))
    story.append(Spacer(1, 0.5*cm))
    story.append(HRFlowable(width="80%", thickness=1.5, color=TEAL, hAlign="CENTER"))
    story.append(Spacer(1, 0.5*cm))

    cover_info = [
        ["Patient:", "Master Arjun Sharma, 18 months old"],
        ["Setting:", "Pediatric Outpatient Clinic"],
        ["Chief Complaint:", "Ear pain, fever, irritability × 2 days"],
        ["Speciality:", "Pediatrics / Otolaryngology"],
        ["Prepared by:", "Clinical Case Study Team"],
        ["Date:", "July 2026"],
    ]
    ci_table = Table(cover_info, colWidths=[4*cm, 10*cm])
    ci_table.setStyle(TableStyle([
        ("FONTNAME",  (0,0),(-1,-1), "Helvetica"),
        ("FONTNAME",  (0,0),(0,-1),  "Helvetica-Bold"),
        ("FONTSIZE",  (0,0),(-1,-1), 11),
        ("TEXTCOLOR", (0,0),(0,-1),  colors.HexColor("#93c5fd")),
        ("TEXTCOLOR", (1,0),(1,-1),  WHITE),
        ("TOPPADDING",(0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("LEFTPADDING",(0,0),(-1,-1), 4),
        ("ALIGN",     (0,0),(0,-1),  "RIGHT"),
    ]))
    story.append(ci_table)
    story.append(Spacer(1, 1.5*cm))

    # Ear anatomy note box on cover
    ear_note = Table(
        [[Paragraph("<b>🦻  Middle Ear Anatomy at a Glance</b>",
                    S("Normal", fontSize=10, textColor=TEAL,
                      fontName="Helvetica-Bold", leading=14)),
          Paragraph(
              "The middle ear is an air-filled cavity behind the tympanic membrane, "
              "connected to the nasopharynx via the Eustachian tube. "
              "It contains the ossicles (malleus, incus, stapes) that transmit sound vibrations. "
              "Eustachian tube dysfunction is the primary pathway for middle-ear infection.",
              BODY_SM)]],
        colWidths=[4.5*cm, 11.5*cm]
    )
    ear_note.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,-1), colors.HexColor("#0c3060")),
        ("TEXTCOLOR",  (0,0),(-1,-1), WHITE),
        ("BOX",        (0,0),(-1,-1), 1, TEAL),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
        ("LEFTPADDING",(0,0),(-1,-1), 10),
        ("RIGHTPADDING",(0,0),(-1,-1), 10),
        ("TOPPADDING", (0,0),(-1,-1), 8),
        ("BOTTOMPADDING",(0,0),(-1,-1), 8),
    ]))
    story.append(ear_note)
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════════════════
    # PAGE 2 – CASE PRESENTATION
    # ═══════════════════════════════════════════════════════════════════════════
    story.append(section_header_bar("📋  Section 1: Case Presentation"))
    story.append(Spacer(1, 0.4*cm))

    story.append(two_col_box(
        "Patient Demographics",
        ["Name: Master Arjun Sharma",
         "Age: 18 months",
         "Gender: Male",
         "Weight: 10.5 kg",
         "Setting: Pediatric OPD",
         "Referred by: General Practitioner"],
        "Chief Complaint",
        ["Right ear pain × 2 days",
         "Tugging/pulling right ear repeatedly",
         "Fever up to 38.9°C (102°F)",
         "Restless, irritable, poor sleep",
         "Decreased appetite",
         "Mild runny nose × 5 days"],
        LIGHT_BLUE, BLUE, AMBER_LIGHT, AMBER
    ))
    story.append(Spacer(1, 0.5*cm))

    story.append(H2("History of Present Illness"))
    story.append(Paragraph(
        "Master Arjun Sharma, an 18-month-old boy, was brought to the pediatric OPD by his mother "
        "with a 2-day history of right-sided ear pain. His mother reports he has been repeatedly "
        "tugging and holding his right ear and has been unusually irritable since the previous night. "
        "He developed a fever of 38.9°C yesterday. He attends a daycare centre 5 days per week. "
        "A mild upper respiratory tract infection (rhinorrhoea and nasal congestion) had been present "
        "for the past 5 days. There has been no otorrhea, no vomiting, and no neck stiffness. "
        "He has had two prior episodes of ear infections in the last 6 months, both treated with amoxicillin.",
        BODY))
    story.append(Spacer(1, 0.3*cm))

    story.append(two_col_box(
        "Past Medical History",
        ["2 prior AOM episodes (last 6 months)",
         "Completed amoxicillin both times",
         "No known drug allergies",
         "Vaccinations: up to date (including PCV13)",
         "No surgical history",
         "Full-term birth, no NICU admission"],
        "Social & Family History",
        ["Attends daycare (5 days/week)",
         "Lives with both parents – 1 sibling",
         "Father: non-smoker; Mother: non-smoker",
         "No family history of hearing loss",
         "Breastfed until 10 months",
         "Formula-bottle fed (supine at times)"],
        GREEN_LIGHT, GREEN, PURPLE_LIGHT, PURPLE
    ))
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Review of Systems"))
    ros_data = [
        [Paragraph("<b>System</b>", TH_STYLE), Paragraph("<b>Findings</b>", TH_STYLE)],
        [Paragraph("ENT", TD_LEFT), Paragraph("Right otalgia, ear tugging, nasal congestion, mild rhinorrhoea", TD_LEFT)],
        [Paragraph("General", TD_LEFT), Paragraph("Fever 38.9°C, irritability, decreased appetite, poor sleep", TD_LEFT)],
        [Paragraph("Respiratory", TD_LEFT), Paragraph("No cough, no difficulty breathing", TD_LEFT)],
        [Paragraph("GI", TD_LEFT), Paragraph("No vomiting, no diarrhoea, normal feeds", TD_LEFT)],
        [Paragraph("Neurological", TD_LEFT), Paragraph("No neck stiffness, no photophobia, active movements", TD_LEFT)],
        [Paragraph("Eyes", TD_LEFT), Paragraph("No redness, no discharge", TD_LEFT)],
    ]
    ros_t = Table(ros_data, colWidths=[4*cm, 12.5*cm])
    ros_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), NAVY),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[GREY_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, GREY_MID),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#d1d5db")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("RIGHTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(ros_t)
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════════════════
    # PAGE 3 – PHYSICAL EXAMINATION
    # ═══════════════════════════════════════════════════════════════════════════
    story.append(section_header_bar("🔬  Section 2: Physical Examination & Investigations"))
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Vital Signs on Presentation"))
    vitals = [
        [Paragraph("<b>Parameter</b>", TH_STYLE),
         Paragraph("<b>Value</b>", TH_STYLE),
         Paragraph("<b>Normal Range</b>", TH_STYLE),
         Paragraph("<b>Interpretation</b>", TH_STYLE)],
        [Paragraph("Temperature", TD_LEFT), Paragraph("38.9°C (102°F)", TD_LEFT),
         Paragraph("36.5–37.5°C", TD_LEFT), Paragraph("⚠ Fever", S("Normal", fontSize=9, textColor=RED, fontName="Helvetica-Bold", leading=13))],
        [Paragraph("Heart Rate", TD_LEFT), Paragraph("130 bpm", TD_LEFT),
         Paragraph("80–140 bpm", TD_LEFT), Paragraph("Normal (upper limit)", TD_LEFT)],
        [Paragraph("Respiratory Rate", TD_LEFT), Paragraph("28 breaths/min", TD_LEFT),
         Paragraph("20–30 breaths/min", TD_LEFT), Paragraph("Normal", TD_LEFT)],
        [Paragraph("SpO2", TD_LEFT), Paragraph("99% (room air)", TD_LEFT),
         Paragraph(">95%", TD_LEFT), Paragraph("Normal", TD_LEFT)],
        [Paragraph("Weight", TD_LEFT), Paragraph("10.5 kg", TD_LEFT),
         Paragraph("9–13 kg (18 mo)", TD_LEFT), Paragraph("Normal", TD_LEFT)],
    ]
    vt = Table(vitals, colWidths=[3.5*cm, 3.5*cm, 4.5*cm, 5*cm])
    vt.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), TEAL),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[GREY_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, TEAL),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#d1d5db")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(vt)
    story.append(Spacer(1, 0.5*cm))

    story.append(H2("Otoscopic Examination (Key Finding)"))
    oto_data = [
        [Paragraph("<b>Finding</b>", TH_STYLE), Paragraph("<b>Right Ear</b>", TH_STYLE), Paragraph("<b>Left Ear</b>", TH_STYLE)],
        [Paragraph("Tympanic Membrane", TD_LEFT), Paragraph("Bulging, opaque, erythematous", S("Normal", fontSize=9, textColor=RED, fontName="Helvetica-Bold", leading=13)), Paragraph("Mildly injected, no bulging", TD_LEFT)],
        [Paragraph("Tympanic Membrane Mobility", TD_LEFT), Paragraph("Absent on pneumatic otoscopy", S("Normal", fontSize=9, textColor=RED, fontName="Helvetica-Bold", leading=13)), Paragraph("Slightly reduced", TD_LEFT)],
        [Paragraph("Air-fluid Level", TD_LEFT), Paragraph("Present behind TM", S("Normal", fontSize=9, textColor=RED, fontName="Helvetica-Bold", leading=13)), Paragraph("Not visible", TD_LEFT)],
        [Paragraph("Perforation / Discharge", TD_LEFT), Paragraph("None", TD_LEFT), Paragraph("None", TD_LEFT)],
        [Paragraph("External Canal", TD_LEFT), Paragraph("Clear, no erythema", TD_LEFT), Paragraph("Clear", TD_LEFT)],
    ]
    ot = Table(oto_data, colWidths=[5*cm, 5.75*cm, 5.75*cm])
    ot.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), RED),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[RED_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, RED),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#fca5a5")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(ot)
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Other Systemic Examination"))
    story.append(info_box("Systemic Findings",
        ["Oropharynx: mild pharyngeal erythema, no exudates, no tonsillar enlargement",
         "Nose: bilateral nasal congestion, clear discharge",
         "Neck: no lymphadenopathy, no neck stiffness, full range of motion",
         "Chest: clear to auscultation bilaterally, no wheeze, no crepitations",
         "Abdomen: soft, non-tender, no organomegaly",
         "CNS: alert, appropriate for age, no focal neurological deficits",
         "Skin: no rash, no petechiae"],
        GREEN_LIGHT, GREEN))
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Investigations"))
    inv_data = [
        [Paragraph("<b>Investigation</b>", TH_STYLE), Paragraph("<b>Result</b>", TH_STYLE), Paragraph("<b>Significance</b>", TH_STYLE)],
        [Paragraph("CBC – WBC", TD_LEFT), Paragraph("14,200/µL (neutrophilia)", S("Normal", fontSize=9, textColor=AMBER, fontName="Helvetica-Bold", leading=13)), Paragraph("Suggests bacterial infection", TD_LEFT)],
        [Paragraph("CBC – Haemoglobin", TD_LEFT), Paragraph("11.8 g/dL", TD_LEFT), Paragraph("Normal for age", TD_LEFT)],
        [Paragraph("CRP", TD_LEFT), Paragraph("32 mg/L (elevated)", S("Normal", fontSize=9, textColor=AMBER, fontName="Helvetica-Bold", leading=13)), Paragraph("Active inflammatory process", TD_LEFT)],
        [Paragraph("Tympanometry", TD_LEFT), Paragraph("Right: Type B (flat curve)", S("Normal", fontSize=9, textColor=RED, fontName="Helvetica-Bold", leading=13)), Paragraph("Middle ear effusion confirmed", TD_LEFT)],
        [Paragraph("Tympanometry – Left", TD_LEFT), Paragraph("Type C (negative pressure)", S("Normal", fontSize=9, textColor=AMBER, fontName="Helvetica-Bold", leading=13)), Paragraph("Eustachian tube dysfunction", TD_LEFT)],
        [Paragraph("Middle Ear Culture", TD_LEFT), Paragraph("Not performed (no perforation)", TD_LEFT), Paragraph("Empirical therapy initiated", TD_LEFT)],
    ]
    inv_t = Table(inv_data, colWidths=[5*cm, 5.5*cm, 6*cm])
    inv_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), NAVY),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[GREY_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, GREY_MID),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#d1d5db")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(inv_t)
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════════════════
    # PAGE 4 – PATHOPHYSIOLOGY & MICROBIOLOGY
    # ═══════════════════════════════════════════════════════════════════════════
    story.append(section_header_bar("🧬  Section 3: Pathophysiology & Microbiology"))
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Definition & Classification"))
    def_data = [
        [Paragraph("<b>Type</b>", TH_STYLE), Paragraph("<b>Key Features</b>", TH_STYLE), Paragraph("<b>This Case</b>", TH_STYLE)],
        [Paragraph("Acute Otitis Media (AOM)", TD_LEFT),
         Paragraph("Rapid onset, otalgia, fever, bulging TM, MEE", TD_LEFT),
         Paragraph("✔ YES", S("Normal", fontSize=9, textColor=GREEN, fontName="Helvetica-Bold", leading=13))],
        [Paragraph("Otitis Media with Effusion (OME)", TD_LEFT),
         Paragraph("MEE without acute signs or symptoms of infection (glue ear)", TD_LEFT),
         Paragraph("Not primary diagnosis", TD_LEFT)],
        [Paragraph("Recurrent AOM (rAOM)", TD_LEFT),
         Paragraph("≥3 episodes in 6 months or ≥4 in 12 months", TD_LEFT),
         Paragraph("At risk (2 prior episodes)", S("Normal", fontSize=9, textColor=AMBER, fontName="Helvetica-Bold", leading=13))],
        [Paragraph("Chronic Suppurative OM", TD_LEFT),
         Paragraph("Persistent discharge through TM perforation >6 weeks", TD_LEFT),
         Paragraph("Not present", TD_LEFT)],
    ]
    def_t = Table(def_data, colWidths=[5*cm, 7.5*cm, 4*cm])
    def_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), BLUE),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[LIGHT_BLUE, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, BLUE),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#bfdbfe")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(def_t)
    story.append(Spacer(1, 0.5*cm))

    story.append(H2("Pathophysiology: Step-by-Step"))
    path_steps = [
        ("1. Viral URTI", "A preceding viral URTI (rhinovirus, RSV, influenza) triggers nasopharyngeal mucosal inflammation. This is the most common precipitating event for AOM."),
        ("2. Eustachian Tube Dysfunction", "Mucosal oedema and secretion impair Eustachian tube (ET) function. In infants, the ET is shorter, more horizontal, and floppier — making dysfunction more likely. Abnormal tubal compliance + delayed tensor veli palatini innervation → ET collapse."),
        ("3. Negative Middle Ear Pressure", "ET obstruction prevents equalisation of middle-ear pressure. Negative pressure develops, drawing nasopharyngeal secretions (with bacteria/virus) into the middle ear space."),
        ("4. Middle Ear Effusion (MEE)", "Fluid accumulates in the middle ear. Bacteria colonise and proliferate, causing mucosal inflammation, oedema, and purulent effusion. The TM becomes bulging, erythematous, and opacified."),
        ("5. Acute Inflammatory Response", "Immune response: neutrophil infiltration, cytokine release (IL-1β, IL-6, TNF-α), complement activation. This drives otalgia, fever, and hearing loss."),
        ("6. Resolution or Complication", "Most cases resolve with/without antibiotics within 2–3 days. Complications arise if untreated: mastoiditis, meningitis, labyrinthitis, facial nerve palsy."),
    ]
    for title, desc in path_steps:
        prow = Table(
            [[Paragraph(f"<b>{title}</b>", S("Normal", fontSize=10, textColor=WHITE,
                fontName="Helvetica-Bold", leading=14, alignment=TA_CENTER)),
              Paragraph(desc, BODY_SM)]],
            colWidths=[3.5*cm, 13*cm]
        )
        prow.setStyle(TableStyle([
            ("BACKGROUND", (0,0),(0,-1), TEAL),
            ("BACKGROUND", (1,0),(1,-1), TEAL_LIGHT),
            ("LEFTPADDING",(0,0),(-1,-1), 8),
            ("RIGHTPADDING",(0,0),(-1,-1), 8),
            ("TOPPADDING", (0,0),(-1,-1), 6),
            ("BOTTOMPADDING",(0,0),(-1,-1), 6),
            ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
            ("BOX",        (0,0),(-1,-1), 0.5, TEAL),
            ("LINEBELOW",  (0,0),(-1,-1), 0.3, colors.HexColor("#99f6e4")),
        ]))
        story.append(prow)
        story.append(Spacer(1, 0.1*cm))

    story.append(Spacer(1, 0.4*cm))
    story.append(H2("Microbiology"))
    micro_data = [
        [Paragraph("<b>Organism</b>", TH_STYLE), Paragraph("<b>Frequency</b>", TH_STYLE), Paragraph("<b>Key Feature</b>", TH_STYLE)],
        [Paragraph("Streptococcus pneumoniae", TD_LEFT), Paragraph("25–40%", S("Normal", fontSize=9, textColor=RED, fontName="Helvetica-Bold", leading=13)), Paragraph("Most virulent; increasing penicillin resistance", TD_LEFT)],
        [Paragraph("Haemophilus influenzae", TD_LEFT), Paragraph("10–30%", S("Normal", fontSize=9, textColor=AMBER, fontName="Helvetica-Bold", leading=13)), Paragraph("Non-typeable strains; β-lactamase producing", TD_LEFT)],
        [Paragraph("Moraxella catarrhalis", TD_LEFT), Paragraph("2–15%", TD_LEFT), Paragraph("Almost universally β-lactamase producing", TD_LEFT)],
        [Paragraph("Viral (RSV, Rhinovirus, Influenza)", TD_LEFT), Paragraph("Up to 40%", TD_LEFT), Paragraph("Often co-infection with bacteria", TD_LEFT)],
        [Paragraph("Streptococcus pyogenes", TD_LEFT), Paragraph("< 5%", TD_LEFT), Paragraph("Group A Streptococcus; rare but more severe", TD_LEFT)],
    ]
    micro_t = Table(micro_data, colWidths=[6*cm, 3.5*cm, 7*cm])
    micro_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), RED),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[RED_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, RED),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#fca5a5")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(micro_t)
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════════════════
    # PAGE 5 – DIAGNOSIS
    # ═══════════════════════════════════════════════════════════════════════════
    story.append(section_header_bar("🩺  Section 4: Diagnosis & Risk Stratification"))
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Diagnostic Criteria for AOM (AAP Guidelines)"))
    story.append(Paragraph(
        "Diagnosis of AOM requires ALL of the following:", BODY))
    dx_criteria = [
        "Moderate-to-severe bulging of the tympanic membrane, OR new-onset otorrhoea not caused by otitis externa",
        "Middle ear effusion (MEE) confirmed by pneumatic otoscopy, tympanometry, or direct visualization of air-fluid level",
        "Signs/symptoms of middle ear inflammation: acute onset otalgia, marked erythema of TM, or intense earache"
    ]
    for c in dx_criteria:
        story.append(Paragraph(f"✔  {c}", BULLET))
    story.append(Spacer(1, 0.3*cm))

    # Criteria met table for this case
    met_data = [
        [Paragraph("<b>Diagnostic Criterion</b>", TH_STYLE), Paragraph("<b>Present in This Case?</b>", TH_STYLE)],
        [Paragraph("Acute onset of otalgia", TD_LEFT), Paragraph("✔ YES – ear tugging, irritability, 2 days", S("Normal", fontSize=9, textColor=GREEN, fontName="Helvetica-Bold", leading=13))],
        [Paragraph("Bulging tympanic membrane", TD_LEFT), Paragraph("✔ YES – Right TM bulging and erythematous", S("Normal", fontSize=9, textColor=GREEN, fontName="Helvetica-Bold", leading=13))],
        [Paragraph("Middle ear effusion", TD_LEFT), Paragraph("✔ YES – Absent TM mobility + Type B tympanogram", S("Normal", fontSize=9, textColor=GREEN, fontName="Helvetica-Bold", leading=13))],
        [Paragraph("Fever", TD_LEFT), Paragraph("✔ YES – 38.9°C (moderate; defines severity)", S("Normal", fontSize=9, textColor=GREEN, fontName="Helvetica-Bold", leading=13))],
        [Paragraph("No otorrhoea", TD_LEFT), Paragraph("Confirmed – TM intact", TD_LEFT)],
    ]
    met_t = Table(met_data, colWidths=[8*cm, 8.5*cm])
    met_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), GREEN),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[GREEN_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, GREEN),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#86efac")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(met_t)
    story.append(Spacer(1, 0.5*cm))

    story.append(H2("Severity Stratification"))
    sev_data = [
        [Paragraph("<b>Feature</b>", TH_STYLE), Paragraph("<b>Mild–Moderate</b>", TH_STYLE), Paragraph("<b>Severe</b>", TH_STYLE), Paragraph("<b>This Case</b>", TH_STYLE)],
        [Paragraph("Otalgia", TD_LEFT), Paragraph("Mild–moderate", TD_LEFT), Paragraph("Moderate–severe", TD_LEFT), Paragraph("Moderate", TD_LEFT)],
        [Paragraph("Fever", TD_LEFT), Paragraph("< 39°C", TD_LEFT), Paragraph("≥ 39°C (102.2°F)", TD_LEFT), Paragraph("38.9°C (borderline)", S("Normal", fontSize=9, textColor=AMBER, fontName="Helvetica-Bold", leading=13))],
        [Paragraph("Laterality", TD_LEFT), Paragraph("Unilateral", TD_LEFT), Paragraph("Bilateral", TD_LEFT), Paragraph("Unilateral (R) + contralateral MEE", S("Normal", fontSize=9, textColor=AMBER, fontName="Helvetica-Bold", leading=13))],
        [Paragraph("Antibiotic Decision", TD_LEFT), Paragraph("Observation option if >2 yr", TD_LEFT), Paragraph("Antibiotics mandatory", TD_LEFT), Paragraph("Antibiotics indicated (age <2 yr)", S("Normal", fontSize=9, textColor=RED, fontName="Helvetica-Bold", leading=13))],
    ]
    sev_t = Table(sev_data, colWidths=[3.5*cm, 4.5*cm, 4.5*cm, 4*cm])
    sev_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), NAVY),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[GREY_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, NAVY),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#d1d5db")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(sev_t)
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Differential Diagnosis"))
    dd_data = [
        [Paragraph("<b>Condition</b>", TH_STYLE), Paragraph("<b>Distinguishing Features</b>", TH_STYLE), Paragraph("<b>Ruled Out By</b>", TH_STYLE)],
        [Paragraph("Otitis Externa", TD_LEFT), Paragraph("Tragus tenderness, erythematous canal, no MEE", TD_LEFT), Paragraph("Normal external canal; MEE confirmed", TD_LEFT)],
        [Paragraph("OME (Glue Ear)", TD_LEFT), Paragraph("No acute symptoms, no fever, no otalgia", TD_LEFT), Paragraph("Acute onset + fever + otalgia present", TD_LEFT)],
        [Paragraph("Mastoiditis", TD_LEFT), Paragraph("Post-auricular swelling/tenderness, protruding ear", TD_LEFT), Paragraph("No post-auricular signs; early disease", TD_LEFT)],
        [Paragraph("Referred Otalgia", TD_LEFT), Paragraph("Normal TM; pain from teeth, TMJ, pharynx", TD_LEFT), Paragraph("Abnormal TM on otoscopy", TD_LEFT)],
        [Paragraph("Foreign Body Ear", TD_LEFT), Paragraph("Visible on otoscopy; no systemic signs", TD_LEFT), Paragraph("Bulging TM; systemic fever present", TD_LEFT)],
    ]
    dd_t = Table(dd_data, colWidths=[5*cm, 5.75*cm, 5.75*cm])
    dd_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), PURPLE),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[PURPLE_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, PURPLE),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#c4b5fd")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(dd_t)
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════════════════
    # PAGE 6 – MANAGEMENT
    # ═══════════════════════════════════════════════════════════════════════════
    story.append(section_header_bar("💊  Section 5: Management"))
    story.append(Spacer(1, 0.4*cm))

    # Final diagnosis box
    fd = Table(
        [[Paragraph("<b>FINAL DIAGNOSIS</b>",
                    S("Normal", fontSize=12, textColor=WHITE, fontName="Helvetica-Bold",
                      alignment=TA_CENTER, leading=16)),
          Paragraph("Acute Otitis Media (AOM) – Right Ear, Unilateral, Non-severe to Moderate "
                    "in an 18-month-old HIGH-RISK patient (age <2 yr, daycare, recurrent episodes) "
                    "→ ANTIBIOTIC THERAPY INDICATED",
                    S("Normal", fontSize=10, textColor=WHITE, fontName="Helvetica",
                      leading=14))]],
        colWidths=[4.5*cm, 12*cm]
    )
    fd.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(0,-1), RED),
        ("BACKGROUND", (1,0),(1,-1), colors.HexColor("#9b1c1c")),
        ("LEFTPADDING",(0,0),(-1,-1), 10),
        ("RIGHTPADDING",(0,0),(-1,-1), 10),
        ("TOPPADDING", (0,0),(-1,-1), 10),
        ("BOTTOMPADDING",(0,0),(-1,-1), 10),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(fd)
    story.append(Spacer(1, 0.5*cm))

    story.append(H2("Antibiotic Treatment"))
    ab_data = [
        [Paragraph("<b>Scenario</b>", TH_STYLE), Paragraph("<b>Drug</b>", TH_STYLE), Paragraph("<b>Dose & Duration</b>", TH_STYLE)],
        [Paragraph("High-risk patient (age <2 yr, daycare, prior antibiotics, fever)", TD_LEFT),
         Paragraph("Amoxicillin (First Line)", S("Normal", fontSize=9, textColor=GREEN, fontName="Helvetica-Bold", leading=13)),
         Paragraph("80–90 mg/kg/day in divided doses for 10 days", TD_LEFT)],
        [Paragraph("Treatment failure (symptoms persist >3 days)", TD_LEFT),
         Paragraph("Amoxicillin-clavulanate (Augmentin)", TD_LEFT),
         Paragraph("80–90 mg/kg/day for 7–10 days", TD_LEFT)],
        [Paragraph("Treatment failure (alternative)", TD_LEFT),
         Paragraph("Cefuroxime axetil (Ceftin)", TD_LEFT),
         Paragraph("20–30 mg/kg/day BID for 7–10 days", TD_LEFT)],
        [Paragraph("Treatment failure (parenteral)", TD_LEFT),
         Paragraph("Ceftriaxone (Rocephin)", TD_LEFT),
         Paragraph("50 mg/kg IM × 1–3 days (max 1 g/dose in <12 yr)", TD_LEFT)],
        [Paragraph("Penicillin allergy (non-severe)", TD_LEFT),
         Paragraph("Cefpodoxime or Cefuroxime", TD_LEFT),
         Paragraph("Cefpodoxime: 5 mg/kg/day ÷ q12h × 5–10 days", TD_LEFT)],
        [Paragraph("Penicillin allergy (severe / anaphylaxis)", TD_LEFT),
         Paragraph("Clindamycin ± azithromycin", TD_LEFT),
         Paragraph("As per local sensitivity; 5–7 days", TD_LEFT)],
    ]
    ab_t = Table(ab_data, colWidths=[5.5*cm, 4.5*cm, 6.5*cm])
    ab_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), GREEN),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[GREEN_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, GREEN),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#86efac")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(ab_t)
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Symptomatic & Supportive Management"))
    story.append(two_col_box(
        "Pain & Fever Management",
        ["Paracetamol (15 mg/kg/dose q4–6h) for otalgia and fever",
         "Ibuprofen (10 mg/kg/dose q6–8h, if >6 months)",
         "Topical analgesic eardrops (benzocaine/natamycin) – adjunct only",
         "Antibiotics do NOT provide analgesia in first 24 hrs",
         "Warm compress over affected ear for comfort",
         "Oral hydration and nutritional support"],
        "General Supportive Care",
        ["Nasal saline drops/spray for congestion",
         "Elevate head of bed slightly",
         "Avoid bottle feeding in supine position",
         "Smoke-free environment (parental counselling)",
         "Breastfeeding promotion in future",
         "Rest; avoid air travel if symptomatic"],
        AMBER_LIGHT, AMBER, LIGHT_BLUE, BLUE
    ))
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Observation vs. Immediate Antibiotic – Decision Framework"))
    story.append(Paragraph(
        "In this case, immediate antibiotics are indicated because the patient is <b>18 months old</b> (age <2 years) "
        "AND has <b>bilateral MEE</b> (right AOM + left contralateral effusion) AND has <b>prior episodes</b>. "
        "Observation (watchful waiting) is only an option for children >2 years with mild, unilateral AOM "
        "and reliable caregiver follow-up.", BODY))

    story.append(Spacer(1, 0.3*cm))
    obs_data = [
        [Paragraph("<b>Antibiotic Indicated</b>", TH_STYLE), Paragraph("<b>Observation Acceptable</b>", TH_STYLE)],
        [Paragraph("Age < 6 months → always prescribe", TD_LEFT), Paragraph("Age 2–12 yr, mild unilateral AOM, no otorrhoea", TD_LEFT)],
        [Paragraph("Age 6 mo – 2 yr with bilateral AOM", TD_LEFT), Paragraph("Fever < 39°C, mild otalgia", TD_LEFT)],
        [Paragraph("Any age with otorrhoea", TD_LEFT), Paragraph("Reliable caregiver access to follow-up / rescue Rx", TD_LEFT)],
        [Paragraph("Severe symptoms (fever ≥ 39°C, severe otalgia)", TD_LEFT), Paragraph("No prior AOM in past 3 months", TD_LEFT)],
        [Paragraph("Immunocompromised or craniofacial anomaly", TD_LEFT), Paragraph("No immunocompromise or structural abnormality", TD_LEFT)],
    ]
    obs_t = Table(obs_data, colWidths=[8.25*cm, 8.25*cm])
    obs_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), NAVY),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[RED_LIGHT, GREEN_LIGHT]),
        ("BOX",        (0,0),(-1,-1), 0.5, NAVY),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#d1d5db")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
    ]))
    story.append(obs_t)
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════════════════
    # PAGE 7 – COMPLICATIONS & FOLLOW-UP
    # ═══════════════════════════════════════════════════════════════════════════
    story.append(section_header_bar("⚠  Section 6: Complications, Follow-Up & Prevention"))
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Complications of Untreated / Inadequately Treated AOM"))
    comp_data = [
        [Paragraph("<b>Complication</b>", TH_STYLE), Paragraph("<b>Mechanism</b>", TH_STYLE), Paragraph("<b>Key Signs</b>", TH_STYLE), Paragraph("<b>Management</b>", TH_STYLE)],
        [Paragraph("Mastoiditis", TD_LEFT), Paragraph("Direct spread to mastoid air cells", TD_LEFT), Paragraph("Post-auricular pain/swelling, ear protrusion", TD_LEFT), Paragraph("IV antibiotics ± mastoidectomy", TD_LEFT)],
        [Paragraph("Meningitis", TD_LEFT), Paragraph("Haematogenous / direct spread via labyrinth", TD_LEFT), Paragraph("Neck stiffness, photophobia, altered consciousness", TD_LEFT), Paragraph("IV ceftriaxone + dexamethasone", TD_LEFT)],
        [Paragraph("Labyrinthitis", TD_LEFT), Paragraph("Spread to inner ear via round window", TD_LEFT), Paragraph("Sudden sensorineural hearing loss, vertigo", TD_LEFT), Paragraph("Urgent ENT; IV antibiotics; steroids", TD_LEFT)],
        [Paragraph("Facial Nerve Palsy", TD_LEFT), Paragraph("Inflammation near facial nerve canal", TD_LEFT), Paragraph("Unilateral facial weakness", TD_LEFT), Paragraph("IV antibiotics ± decompression", TD_LEFT)],
        [Paragraph("TM Perforation", TD_LEFT), Paragraph("Pressure necrosis of TM", TD_LEFT), Paragraph("Sudden ear discharge, pain relief", TD_LEFT), Paragraph("Topical antibiotics; most heal spontaneously", TD_LEFT)],
        [Paragraph("Conductive Hearing Loss", TD_LEFT), Paragraph("Persistent MEE, ossicular damage", TD_LEFT), Paragraph("Speech delay, inattentiveness", TD_LEFT), Paragraph("Monitor; hearing aids; grommet insertion", TD_LEFT)],
        [Paragraph("Cholesteatoma", TD_LEFT), Paragraph("Retraction pocket from chronic ET dysfunction", TD_LEFT), Paragraph("Keratinous mass, painless otorrhoea", TD_LEFT), Paragraph("Surgical excision", TD_LEFT)],
    ]
    comp_t = Table(comp_data, colWidths=[3.5*cm, 4.5*cm, 4*cm, 4.5*cm])
    comp_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), RED),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[RED_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, RED),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#fca5a5")),
        ("LEFTPADDING",(0,0),(-1,-1), 7),
        ("TOPPADDING", (0,0),(-1,-1), 4),
        ("BOTTOMPADDING",(0,0),(-1,-1), 4),
        ("VALIGN",     (0,0),(-1,-1), "TOP"),
        ("FONTSIZE",   (0,1),(-1,-1), 8),
    ]))
    story.append(comp_t)
    story.append(Spacer(1, 0.5*cm))

    story.append(H2("Follow-Up Plan for This Patient"))
    story.append(info_box("Follow-Up Schedule & Goals",
        ["24–48 hrs: Phone review by nurse – fever resolution, pain control, feeding",
         "Day 3: In-person review if symptoms not improving → escalate antibiotics",
         "Day 10 (end of antibiotic course): Clinical reassessment, otoscopy",
         "4–6 weeks post-AOM: Tympanometry to check MEE resolution (monitor for OME)",
         "Hearing assessment: if MEE persists >3 months, refer to audiology",
         "ENT referral: if ≥3 AOM episodes in 6 months or ≥4 in 12 months (rAOM) → discuss tympanostomy tubes"],
        LIGHT_BLUE, BLUE))
    story.append(Spacer(1, 0.5*cm))

    story.append(H2("Surgical Management: Tympanostomy Tubes (Grommets)"))
    story.append(Paragraph(
        "Insertion of tympanostomy tubes (pressure equalization tubes / grommets) is the most common "
        "surgical procedure in children. Indications include recurrent AOM (≥3 in 6 months or ≥4 in 12 months) "
        "and persistent bilateral OME >3 months with hearing loss. This patient is approaching the threshold "
        "for referral given 2 prior episodes in 6 months.", BODY))
    story.append(Spacer(1, 0.3*cm))
    story.append(two_col_box(
        "Indications for Grommets",
        ["rAOM: ≥3 episodes/6 months or ≥4/year",
         "Persistent bilateral OME >3 months + hearing loss",
         "OME with speech/language delay",
         "Failed medical management",
         "Structural complication risk"],
        "Benefits of Tympanostomy Tubes",
        ["Equalise middle-ear pressure",
         "Reduce frequency of AOM recurrence",
         "Improve hearing in OME",
         "Allow topical antibiotic delivery",
         "Self-extruding within 6–12 months"],
        AMBER_LIGHT, AMBER, GREEN_LIGHT, GREEN
    ))
    story.append(Spacer(1, 0.5*cm))

    story.append(H2("Prevention Strategies"))
    story.append(two_col_box(
        "Modifiable Risk Factors",
        ["Cease bottle feeding in supine position",
         "Eliminate secondhand smoke exposure",
         "Encourage breastfeeding (protective)",
         "Limit pacifier use (especially at sleep)",
         "Reduce daycare exposure if recurrent",
         "Treat allergic rhinitis if present"],
        "Vaccination (Key Preventive Measure)",
        ["PCV13 (pneumococcal conjugate): 35% ↓ in AOM",
         "Influenza vaccine: annual; prevents viral URTI trigger",
         "Hib vaccine: protective against H. influenzae",
         "PCV23 not routinely used in children",
         "Catch-up vaccination if not completed"],
        PURPLE_LIGHT, PURPLE, TEAL_LIGHT, TEAL
    ))
    story.append(PageBreak())

    # ═══════════════════════════════════════════════════════════════════════════
    # PAGE 8 – CLINICAL OUTCOME & LEARNING POINTS
    # ═══════════════════════════════════════════════════════════════════════════
    story.append(section_header_bar("📈  Section 7: Clinical Outcome & Learning Points"))
    story.append(Spacer(1, 0.4*cm))

    story.append(H2("Hospital Course & Response to Treatment"))
    timeline_data = [
        [Paragraph("<b>Day</b>", TH_STYLE), Paragraph("<b>Event / Status</b>", TH_STYLE)],
        [Paragraph("Day 0", TD_LEFT), Paragraph("Presentation to OPD. Diagnosed AOM. Started Amoxicillin 90 mg/kg/day in 2 divided doses × 10 days. Paracetamol for pain/fever. Parent education provided.", TD_LEFT)],
        [Paragraph("Day 2", TD_LEFT), Paragraph("Phone review: Fever resolved. Irritability improved. Still some ear tugging but much reduced. Feeding improving. Continue antibiotics.", TD_LEFT)],
        [Paragraph("Day 3", TD_LEFT), Paragraph("In-clinic review: Afebrile. TM still slightly erythematous but less bulging. Responding well. Continue full course.", TD_LEFT)],
        [Paragraph("Day 10", TD_LEFT), Paragraph("End of antibiotic course. Asymptomatic. Otoscopy: TM less erythematous, effusion resolving. Child back to normal activity and appetite.", TD_LEFT)],
        [Paragraph("6 Weeks", TD_LEFT), Paragraph("Follow-up: Tympanometry Type A bilaterally. No residual effusion. Hearing assessment normal. No ENT referral needed at this stage.", TD_LEFT)],
    ]
    tl_t = Table(timeline_data, colWidths=[2.5*cm, 14*cm])
    tl_t.setStyle(TableStyle([
        ("BACKGROUND", (0,0),(-1,0), TEAL),
        ("TEXTCOLOR",  (0,0),(-1,0), WHITE),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[TEAL_LIGHT, WHITE]),
        ("BOX",        (0,0),(-1,-1), 0.5, TEAL),
        ("GRID",       (0,0),(-1,-1), 0.3, colors.HexColor("#99f6e4")),
        ("LEFTPADDING",(0,0),(-1,-1), 8),
        ("TOPPADDING", (0,0),(-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1), 5),
        ("VALIGN",     (0,0),(-1,-1), "TOP"),
        ("ALIGN",      (0,0),(0,-1),  "CENTER"),
    ]))
    story.append(tl_t)
    story.append(Spacer(1, 0.5*cm))

    story.append(H2("Key Learning Points"))
    learning = [
        ("Epidemiology", "AOM is the most common reason children are seen in a physician's office, with 93% of children experiencing at least one episode by age 7. The peak incidence is at 6–24 months."),
        ("Pathogenesis", "Eustachian tube dysfunction (shorter, floppier, more horizontal in infants) is the primary mechanism. Viral URTI precedes most episodes. Key bacteria: S. pneumoniae, H. influenzae, M. catarrhalis."),
        ("Diagnosis", "Diagnosis requires: acute onset otalgia + MEE + signs of TM inflammation (bulging/erythema). Pneumatic otoscopy is the gold standard. Tympanometry is a useful objective adjunct."),
        ("Antibiotic Threshold", "Age < 2 years: antibiotics always recommended. Age 2–12 yr with mild unilateral disease: watchful waiting acceptable. Amoxicillin 80–90 mg/kg/day × 10 days for high-risk patients."),
        ("Treatment Failure", "If symptoms persist >3 days on amoxicillin, escalate to amoxicillin-clavulanate (to cover β-lactamase producers). Ceftriaxone IM is used for severe failure or vomiting."),
        ("Complications", "Mastoiditis is the most common suppurative complication. Although rare in the antibiotic era, always assess for post-auricular signs. Sensorineural hearing loss and cholesteatoma are long-term risks."),
        ("Prevention", "PCV13 vaccination reduces AOM incidence by ~35%. Influenza vaccine and breastfeeding are also protective. Eliminating smoke exposure and avoiding supine bottle feeding are modifiable risk factors."),
        ("Recurrence", "Two or more episodes in 6 months or 3+ in 12 months warrants ENT referral. Tympanostomy tube insertion significantly reduces recurrence and is the most commonly performed paediatric surgery."),
    ]
    for title, desc in learning:
        lrow = Table(
            [[Paragraph(f"<b>{title}</b>", S("Normal", fontSize=10, textColor=NAVY,
                fontName="Helvetica-Bold", leading=14, alignment=TA_CENTER)),
              Paragraph(desc, BODY_SM)]],
            colWidths=[3.5*cm, 13*cm]
        )
        lrow.setStyle(TableStyle([
            ("BACKGROUND", (0,0),(0,-1), LIGHT_BLUE),
            ("BACKGROUND", (1,0),(1,-1), WHITE),
            ("BOX",        (0,0),(-1,-1), 0.5, BLUE),
            ("LINEBELOW",  (0,0),(-1,-1), 0.3, colors.HexColor("#bfdbfe")),
            ("LEFTPADDING",(0,0),(-1,-1), 8),
            ("RIGHTPADDING",(0,0),(-1,-1), 8),
            ("TOPPADDING", (0,0),(-1,-1), 5),
            ("BOTTOMPADDING",(0,0),(-1,-1), 5),
            ("VALIGN",     (0,0),(-1,-1), "MIDDLE"),
        ]))
        story.append(lrow)
        story.append(Spacer(1, 0.1*cm))

    story.append(Spacer(1, 0.5*cm))
    story.append(H2("Discussion Questions for Presentation"))
    dq = [
        "Why is AOM more common in children under 2 years compared to adults? (Eustachian tube anatomy)",
        "What are the criteria that allow 'watchful waiting' instead of immediate antibiotics in AOM?",
        "How does S. pneumoniae resistance to penicillin affect empirical antibiotic selection?",
        "When would you consider tympanostomy tube insertion in a child with recurrent AOM?",
        "What is the significance of a Type B tympanogram, and how does it differ from Type A and C?",
        "What are the red flag signs suggesting complications of AOM requiring urgent intervention?",
        "How does the PCV13 vaccine reduce the incidence of AOM?",
    ]
    for i, q in enumerate(dq, 1):
        story.append(Paragraph(f"<b>Q{i}.</b>  {q}", BULLET))

    story.append(Spacer(1, 0.5*cm))
    story.append(HRFlowable(width="100%", thickness=0.8, color=GREY_MID))
    story.append(Spacer(1, 0.3*cm))

    refs = [
        "Lieberthal AS et al. (2013). The Diagnosis and Management of Acute Otitis Media. Pediatrics.",
        "Venekamp RP et al. (2023). Antibiotics for acute otitis media in children. Cochrane Database Syst Rev. PMID: 37965923",
        "Castelli Gattinara G et al. (2025). Antibiotic treatment of acute and recurrent otitis media in children. Ital J Pediatr. PMID: 39980042",
        "Textbook of Family Medicine, 9th edition. Rakel RE (Ed). Chapter 18: Otitis Media.",
        "Rosen's Emergency Medicine: Concepts and Clinical Practice, 9th edition. Chapter 10: Ear Emergencies.",
        "Cummings Otolaryngology Head and Neck Surgery, 7th edition. Otitis Media section.",
        "Scott-Brown's Otorhinolaryngology Head & Neck Surgery, Vol 2.",
    ]
    story.append(Paragraph("<b>References</b>", H2))
    for i, ref in enumerate(refs, 1):
        story.append(Paragraph(f"{i}.  {ref}", BODY_SM))

    # ═══════════════════════════════════════════════════════════════════════════
    # Build
    # ═══════════════════════════════════════════════════════════════════════════
    page_counter = [0]

    def on_first_page(c, d):
        cover_background(c, d)

    def on_later_pages(c, d):
        normal_background(c, d)

    doc.build(story, onFirstPage=on_first_page, onLaterPages=on_later_pages,
              canvasmaker=NumberedCanvas)
    print(f"PDF saved to {OUTPUT}")

build()
Running Command

cd /tmp/workspace/otitis-media-case && python build_pdf.py 2>&1

Editing File

~/otitis-media-case/build_pdf.py

Successfully replaced 1 instance(s) of the string in '/tmp/workspace/otitis-media-case/build_pdf.py'
Running Command

cd /tmp/workspace/otitis-media-case && python build_pdf.py 2>&1

Searching Content

"Paragraph(" in ~/otitis-media-case/build_pdf.py

Editing File

~/otitis-media-case/build_pdf.py

Successfully replaced 1 instance(s) of the string in '/tmp/workspace/otitis-media-case/build_pdf.py'
Running Command

cd /tmp/workspace/otitis-media-case && python build_pdf.py 2>&1

Editing File

~/otitis-media-case/build_pdf.py

Successfully replaced 1 instance(s) of the string in '/tmp/workspace/otitis-media-case/build_pdf.py'
Running Command

cd /tmp/workspace/otitis-media-case && python build_pdf.py 2>&1 && ls -lh Otitis_Media_Case_Study.pdf

Your Otitis Media Case Study Presentation PDF is ready for download:

Otitis Media Case Study

PDF Document · PDF


What's Inside (14 pages)

The PDF is structured as a full clinical case study presentation with a professional navy/teal colour scheme, section headers, colour-coded tables, and page numbers.
SectionContent
Cover PagePatient summary, middle ear anatomy overview
Section 1 – Case PresentationPatient demographics, chief complaint, HPI, past medical & social history, ROS
Section 2 – Physical ExaminationVital signs, otoscopic findings (right vs. left ear), systemic exam, investigations (CBC, CRP, tympanometry)
Section 3 – Pathophysiology & MicrobiologyAOM classification table, 6-step pathophysiology flow, microbiology frequencies (S. pneumoniae 25-40%, H. influenzae 10-30%, M. catarrhalis)
Section 4 – DiagnosisAAP diagnostic criteria, criteria-met table for this case, severity stratification, differential diagnosis
Section 5 – ManagementFinal diagnosis box, antibiotic table (amoxicillin high-dose, treatment failure escalation, penicillin allergy alternatives), symptomatic care, observation vs. antibiotic framework
Section 6 – Complications, Follow-Up & Prevention7 complications with mechanisms and management, follow-up schedule, tympanostomy tube indications, prevention strategies
Section 7 – Clinical Outcome & Learning Points6-week treatment timeline, 8 key learning points, 7 discussion questions, full references
Sources used: Textbook of Family Medicine 9e, Rosen's Emergency Medicine 9e, Cummings Otolaryngology, Scott-Brown's ENT, Cochrane 2023 systematic review (PMID 37965923), and Italian Intersociety Consensus 2025 (PMID 39980042).
This is a shared conversation. Sign in to Orris to start your own chat.