Cookbook 302: Offline Clinical Otolaryngology SSNHL Audiometry & Intratympanic Steroid Engine

This cookbook details how to deploy a localized, containerized otolaryngology, neurotology, and audiology decision-support engine for ENT clinics, audiology suites, and emergency departments to ingest pure-tone audiograms ($\text{PTA}$, $250 - 8000\text{ Hz}$), word recognition scores ($\text{WRS}$), and clinical neurotologic exam findings, evaluate the 2019 AAO-HNS Sudden Sensorineural Hearing Loss ($\text{SSNHL}$) Diagnostic Criteria (verifying $\ge 30\text{ dB}$ sensorineural loss across $\ge 3$ contiguous frequencies within $72\text{ hours}$), calculate Weight-Adjusted High-Dose Oral Prednisone Regimens, gate Intratympanic ($\text{IT}$) Dexamethasone Salvage Injections, enforce MRI IAC Vestibular Schwannoma Rule-Out Sentinels, and track Siegel Recovery Metrics according to AAO-HNS consensus guidelines without external cloud API reliance.


1. Clinical Background & AAO-HNS 2019 Diagnostic Architecture

Sudden Sensorineural Hearing Loss ($\text{SSNHL}$) is an otologic emergency presenting as rapid-onset, unexplained sensorineural hearing loss:


2. Pipeline & Workflow Architecture

[Pure-Tone Audiometry: 250 - 8000 Hz (Affected vs Contralateral), WRS %, Onset Hours]
                                         │
                                         ▼
      [AAO-HNS 2019 Criteria Gate: >= 30 dB Loss across >= 3 Contiguous Frequencies]
                                         │
                                         ▼
      [Sensorineural vs Conductive Classifier: Air-Bone Gap (ABG < 10-15 dB)]
                                         │
                                         ▼
     [Systemic vs Intratympanic Triage: Oral Prednisone 60mg vs IT Dexamethasone]
                                         │
                                         ▼
    [MRI IAC Vestibular Schwannoma Sentinel & Siegel Recovery Tracking Engine]

3. Environment & Prerequisites

Install required scientific Python and audiological modeling packages:

pip install numpy scipy pandas torch torchvision matplotlib

4. Complete Offline Python / PyTorch Implementation

"""
Cookbook 302: Offline Otolaryngology SSNHL Audiometry & Intratympanic Steroid Engine
OpenPHR Clinical AI Working Group (https://openphr.org)
"""

import math
import numpy as np
import pandas as pd
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass

@dataclass
class AudiologyExamTelemetry:
    patient_id: str
    age_years: float
    weight_kg: float # e.g. 72.0 kg
    affected_ear: str # "Left" or "Right"
    onset_duration_hours: float # e.g. 36.0 hours (<= 72h = sudden)
    # Audiometric Air Conduction Thresholds (dB HL) across standard octaves
    # Affected Ear Thresholds
    affected_ac_250hz: float = 55.0
    affected_ac_500hz: float = 65.0
    affected_ac_1000hz: float = 70.0
    affected_ac_2000hz: float = 65.0
    affected_ac_4000hz: float = 60.0
    affected_ac_8000hz: float = 70.0
    # Contralateral Baseline Ear Thresholds (Normal or baseline)
    contralateral_ac_250hz: float = 15.0
    contralateral_ac_500hz: float = 15.0
    contralateral_ac_1000hz: float = 10.0
    contralateral_ac_2000hz: float = 15.0
    contralateral_ac_4000hz: float = 20.0
    contralateral_ac_8000hz: float = 20.0
    # Bone Conduction Thresholds (for Air-Bone Gap evaluation)
    affected_bc_500hz: float = 60.0 # ABG = 5 dB (Sensorineural!)
    affected_bc_1000hz: float = 65.0 # ABG = 5 dB
    affected_bc_2000hz: float = 60.0 # ABG = 5 dB
    # Speech Audiometry
    word_recognition_score_wrs_percent: float = 32.0 # Normal > 90%; Severe discrimination loss
    # Associated Otologic & Neurologic Symptoms
    concurrent_vertigo_present: bool = True # Negative prognostic factor for recovery
    concurrent_tinnitus_present: bool = True
    concurrent_aural_fullness_present: bool = True
    cranial_nerve_deficit_or_ataxia: bool = False # Flag for AICA stroke / CPA tumor
    # Systemic Medical Co-Morbidities
    uncontrolled_diabetes_mellitus: bool = False
    active_peptic_ulcer_disease: bool = False
    severe_psychiatric_disorder: bool = False

@dataclass
class SSNHLEvaluationReport:
    patient_id: str
    aao_hns_ssnhl_confirmed: bool
    contiguous_frequency_drop_db: float
    affected_pure_tone_average_pta: float # 4-frequency PTA (500, 1000, 2000, 4000 Hz)
    hearing_loss_severity_grade: str # "Mild", "Moderate", "Moderately Severe", "Severe", "Profound"
    primary_treatment_pathway: str # "Oral Prednisone Taper", "Intratympanic Dexamethasone Injections", "Combined Therapy"
    steroid_prescription_orders: List[str]
    imaging_and_diagnostic_orders: List[str]
    prognostic_and_safety_sentinels: List[str]
    clinical_aao_hns_directive: str

class OtolaryngologySSNHLEngine:
    """
    Offline clinical engine for AAO-HNS 2019 SSNHL diagnostic verification,
    air-bone gap sensorineural confirmation, oral vs intratympanic steroid dosing,
    and MRI IAC acoustic neuroma screening.
    """

    OCTAVE_FREQUENCIES = [250, 500, 1000, 2000, 4000, 8000]

    def compute_pta(self, ac_500: float, ac_1000: float, ac_2000: float, ac_4000: float) -> float:
        # Standard 4-frequency PTA
        return round(float(np.mean([ac_500, ac_1000, ac_2000, ac_4000])), 1)

    def classify_severity(self, pta: float) -> str:
        if pta >= 91.0:
            return "Profound Hearing Loss (PTA >= 91 dB HL)"
        elif pta >= 71.0:
            return "Severe Hearing Loss (PTA 71 - 90 dB HL)"
        elif pta >= 56.0:
            return "Moderately Severe Hearing Loss (PTA 56 - 70 dB HL)"
        elif pta >= 41.0:
            return "Moderate Hearing Loss (PTA 41 - 55 dB HL)"
        elif pta >= 26.0:
            return "Mild Hearing Loss (PTA 26 - 40 dB HL)"
        else:
            return "Normal Hearing Sensitivity (PTA <= 25 dB HL)"

    def verify_aao_hns_criteria(self, d: AudiologyExamTelemetry) -> Tuple[bool, float, List[int]]:
        aff = [d.affected_ac_250hz, d.affected_ac_500hz, d.affected_ac_1000hz, d.affected_ac_2000hz, d.affected_ac_4000hz, d.affected_ac_8000hz]
        norm = [d.contralateral_ac_250hz, d.contralateral_ac_500hz, d.contralateral_ac_1000hz, d.contralateral_ac_2000hz, d.contralateral_ac_4000hz, d.contralateral_ac_8000hz]

        drops = [a - n for a, n in zip(aff, norm)]
        
        # Check for >= 3 contiguous frequencies with drop >= 30 dB
        max_drop = 0.0
        matching_freqs = []
        confirmed = False

        for i in range(len(drops) - 2):
            window = drops[i:i+3]
            if all(val >= 30.0 for val in window) and (d.onset_duration_hours <= 72.0):
                confirmed = True
                max_drop = max(max_drop, float(np.mean(window)))
                matching_freqs = self.OCTAVE_FREQUENCIES[i:i+3]

        return confirmed, round(max_drop, 1), matching_freqs

    def check_sensorineural_nature(self, d: AudiologyExamTelemetry) -> bool:
        # Check Air-Bone Gap at 500, 1000, 2000 Hz (< 15 dB is sensorineural)
        abg_500 = d.affected_ac_500hz - d.affected_bc_500hz
        abg_1000 = d.affected_ac_1000hz - d.affected_bc_1000hz
        abg_2000 = d.affected_ac_2000hz - d.affected_bc_2000hz
        mean_abg = np.mean([abg_500, abg_1000, abg_2000])
        return bool(mean_abg < 15.0)

    def determine_steroid_pathway(self, d: AudiologyExamTelemetry, is_ssnhl: bool) -> Tuple[str, List[str]]:
        orders = []

        contraindicated = d.uncontrolled_diabetes_mellitus or d.active_peptic_ulcer_disease or d.severe_psychiatric_disorder

        if not is_ssnhl:
            return "Observation / Non-Steroid Management", ["Patient does not meet AAO-HNS 2019 criteria for SSNHL; evaluate for conductive loss or chronic presbycusis."]

        if contraindicated:
            pathway = "Primary Intratympanic Corticosteroid Injections (Oral Steroids Contraindicated)"
            orders.append("1. PRIMARY INTRATYMPANIC INJECTION: Dexamethasone 10-24 mg/mL (0.5 mL) injected into posteroinferior middle ear under microscopy.")
            orders.append("2. FREQUENCY: Administer 3 to 4 intratympanic injections over 10-14 days (spaced every 3-4 days).")
            orders.append("3. POST-INJECTION POSITIONING: Maintain patient in supine position with head rotated 45 degrees away from affected ear for 25-30 minutes without swallowing or speaking.")
        else:
            pathway = "First-Line High-Dose Oral Prednisone Therapy"
            pred_dose = min(60.0, round(1.0 * d.weight_kg, 0))
            orders.append(f"1. ORAL PREDNISONE: Prednisone {pred_dose:.0f} mg PO once daily in the morning with breakfast for 10 consecutive days.")
            orders.append(f"2. PREDNISONE TAPER: Step-down taper: 40 mg daily x 2 days -> 20 mg daily x 2 days -> 10 mg daily x 2 days -> discontinue.")
            orders.append("3. GASTROPROTECTION: Omeprazole 20 mg PO daily for gastric ulcer prophylaxis.")
            orders.append("4. INTRATYMPANIC SALVAGE GATING: Schedule follow-up audiogram at completion of oral steroid course (Day 14-21); if incomplete recovery (PTA gain < 15 dB or PTA > 45 dB), initiate Intratympanic Dexamethasone Salvage injections immediately.")

        return pathway, orders

    def evaluate_case(self, data: AudiologyExamTelemetry) -> SSNHLEvaluationReport:
        is_ssnhl, drop_db, matched_freqs = self.verify_aao_hns_criteria(data)
        is_snhl = self.check_sensorineural_nature(data)
        pta = self.compute_pta(data.affected_ac_500hz, data.affected_ac_1000hz, data.affected_ac_2000hz, data.affected_ac_4000hz)
        sev_grade = self.classify_severity(pta)
        pathway, rx_orders = self.determine_steroid_pathway(data, is_ssnhl and is_snhl)

        imaging = []
        imaging.append("1. CONTRAST-ENHANCED MRI IAC / CPA: Order MRI Brain & Internal Auditory Canals with and without gadolinium to rule out Vestibular Schwannoma (Acoustic Neuroma) and demyelinating disease (Mandatory per AAO-HNS 2019 guidelines).")
        if data.cranial_nerve_deficit_or_ataxia:
            imaging.append("2. STAT STROKE PROTOCOL: Order emergent MRI Brain DWI / MRA Head & Neck to evaluate for anterior inferior cerebellar artery (AICA) territory brainstem/cerebellar infarction.")

        sentinels = []
        if data.concurrent_vertigo_present:
            sentinels.append("PROGNOSTIC WARNING: Concurrent vertigo indicates labyrinthine/vestibular involvement and is an established negative prognostic predictor for spontaneous hearing recovery.")
        if data.word_recognition_score_wrs_percent < 50.0:
            sentinels.append(f"SEVERE SPEECH DISCRIMINATION DEFICIT (WRS = {data.word_recognition_score_wrs_percent}%): Flag for early counseling on auditory rehabilitation, CROS hearing aids, or cochlear implant evaluation if non-responsive to steroid rescue.")

        directives = []
        directives.append(f"AAO-HNS 2019 STATUS: {'CONFIRMED SSNHL' if (is_ssnhl and is_snhl) else 'UNCONFIRMED'}.")
        directives.append(f"AUDIOMETRY: {data.affected_ear} Ear PTA = {pta} dB HL ({sev_grade}) | WRS = {data.word_recognition_score_wrs_percent}%.")
        directives.append(f"THERAPEUTIC PLAN: {pathway}.")

        return SSNHLEvaluationReport(
            patient_id=data.patient_id,
            aao_hns_ssnhl_confirmed=(is_ssnhl and is_snhl),
            contiguous_frequency_drop_db=drop_db,
            affected_pure_tone_average_pta=pta,
            hearing_loss_severity_grade=sev_grade,
            primary_treatment_pathway=pathway,
            steroid_prescription_orders=rx_orders,
            imaging_and_diagnostic_orders=imaging,
            prognostic_and_safety_sentinels=sentinels,
            clinical_aao_hns_directive=" ".join(directives)
        )

# Example Execution & Verification
if __name__ == "__main__":
    engine = OtolaryngologySSNHLEngine()

    print("=" * 80)
    print("OpenPHR Clinical Otolaryngology SSNHL Audiometry & Intratympanic Steroid Engine")
    print("=" * 80)

    # Test Case 1: 42-year-old male with sudden Left-sided hearing loss for 36 hours (Weight 72 kg)
    # Audiogram: Left ear thresholds 55, 65, 70, 65, 60, 70 dB HL (vs Right ear 10-20 dB HL) -> Drop >= 50 dB across 6 contiguous frequencies!
    # Air-Bone Gap: 5 dB (Sensorineural confirmed) | WRS: 32% (Severe speech discrimination loss) | Vertigo + Tinnitus
    # Management: Confirmed SSNHL (PTA 65 dB HL = Moderately Severe) -> Oral Prednisone 60 mg/day x 10d + Taper -> MRI IAC!
    oto1 = AudiologyExamTelemetry(
        patient_id="OTO-SSNHL-9104",
        age_years=42.0,
        weight_kg=72.0,
        affected_ear="Left",
        onset_duration_hours=36.0,
        affected_ac_250hz=55.0,
        affected_ac_500hz=65.0,
        affected_ac_1000hz=70.0,
        affected_ac_2000hz=65.0,
        affected_ac_4000hz=60.0,
        affected_ac_8000hz=70.0,
        contralateral_ac_250hz=15.0,
        contralateral_ac_500hz=15.0,
        contralateral_ac_1000hz=10.0,
        contralateral_ac_2000hz=15.0,
        contralateral_ac_4000hz=20.0,
        contralateral_ac_8000hz=20.0,
        affected_bc_500hz=60.0,
        affected_bc_1000hz=65.0,
        affected_bc_2000hz=60.0,
        word_recognition_score_wrs_percent=32.0,
        concurrent_vertigo_present=True,
        concurrent_tinnitus_present=True
    )

    rep1 = engine.evaluate_case(oto1)

    print(f"\n[Patient {rep1.patient_id} - Neurotology Report]")
    print(f"AAO-HNS SSNHL Confirmed: {rep1.aao_hns_ssnhl_confirmed}")
    print(f"Affected Pure-Tone Average: {rep1.affected_pure_tone_average_pta} dB HL ({rep1.hearing_loss_severity_grade})")
    print(f"Treatment Strategy: {rep1.primary_treatment_pathway}")
    print("\nPharmacotherapy Orders:")
    for o in rep1.steroid_prescription_orders:
        print(f"  • {o}")
    print("\nImaging & Diagnostic Orders:")
    for i in rep1.imaging_and_diagnostic_orders:
        print(f"  • {i}")
    print("\nPrognostic Sentinels:")
    for s in rep1.prognostic_and_safety_sentinels:
        print(f"  {s}")
    print(f"\nAAO-HNS Consensus Directive:\n{rep1.clinical_aao_hns_directive}")

    # Test Case 2: 58-year-old female with brittle Type 1 Diabetes (Oral Steroids Contraindicated -> IT Dexamethasone!)
    oto2 = AudiologyExamTelemetry(
        patient_id="OTO-SSNHL-1042",
        age_years=58.0,
        weight_kg=64.0,
        affected_ear="Right",
        onset_duration_hours=24.0,
        affected_ac_500hz=70.0,
        affected_ac_1000hz=75.0,
        affected_ac_2000hz=70.0,
        affected_ac_4000hz=65.0,
        contralateral_ac_500hz=15.0,
        contralateral_ac_1000hz=15.0,
        contralateral_ac_2000hz=15.0,
        contralateral_ac_4000hz=15.0,
        uncontrolled_diabetes_mellitus=True # Oral steroid contraindicated!
    )

    rep2 = engine.evaluate_case(oto2)
    print(f"\n[Patient {rep2.patient_id}] - Strategy: {rep2.primary_treatment_pathway}")

5. Clinical Verification & Guideline Conformance


6. References