""" Clinical Acronym and Abbreviation Expansion Engine. Provides high-coverage clinical abbreviation normalization, context-sensitive disambiguation, and query augmentation across SNOMED CT, LOINC, and RxNorm domains. """ import re from typing import Dict, List, Optional, Tuple # Core clinical abbreviation mappings: Acronym -> Canonical Expansion CLINICAL_ACRONYMS: Dict[str, str] = { # Cardiovascular "HTN": "Hypertension", "CAD": "Coronary artery disease", "CABG": "Coronary artery bypass graft", "CHF": "Congestive heart failure", "HF": "Heart failure", "HFpEF": "Heart failure with preserved ejection fraction", "HFrEF": "Heart failure with reduced ejection fraction", "AF": "Atrial fibrillation", "AFIB": "Atrial fibrillation", "A-FIB": "Atrial fibrillation", "AFL": "Atrial flutter", "STEMI": "ST elevation myocardial infarction", "NSTEMI": "Non-ST elevation myocardial infarction", "SVT": "Supraventricular tachycardia", "VT": "Ventricular tachycardia", "VF": "Ventricular fibrillation", "DVT": "Deep vein thrombosis", "PAD": "Peripheral artery disease", "PVD": "Peripheral vascular disease", "AAA": "Abdominal aortic aneurysm", "AS": "Aortic valve stenosis", "AR": "Aortic regurgitation", "MR": "Mitral regurgitation", # Respiratory "COPD": "Chronic obstructive pulmonary disease", "SOB": "Shortness of breath", "DOE": "Dyspnea on exertion", "URI": "Upper respiratory infection", "OSA": "Obstructive sleep apnea", "ARDS": "Acute respiratory distress syndrome", "PNA": "Pneumonia", "CAP": "Community acquired pneumonia", "HAP": "Hospital acquired pneumonia", "CF": "Cystic fibrosis", "ILD": "Interstitial lung disease", "IPF": "Idiopathic pulmonary fibrosis", # Endocrine & Metabolic "DM": "Diabetes mellitus", "DM1": "Type 1 diabetes mellitus", "T1DM": "Type 1 diabetes mellitus", "DM2": "Type 2 diabetes mellitus", "T2DM": "Type 2 diabetes mellitus", "DKA": "Diabetic ketoacidosis", "HHS": "Hyperosmolar hyperglycemic state", "HTG": "Hypertriglyceridemia", "HLD": "Hyperlipidemia", "PCOS": "Polycystic ovary syndrome", # Nephrology & Urology "AKI": "Acute kidney injury", "CKD": "Chronic kidney disease", "ESRD": "End stage renal disease", "UTI": "Urinary tract infection", "BPH": "Benign prostatic hyperplasia", "ATN": "Acute tubular necrosis", "GN": "Glomerulonephritis", # Neurological "CVA": "Cerebrovascular accident", "TIA": "Transient ischemic attack", "ICH": "Intracerebral hemorrhage", "SAH": "Subarachnoid hemorrhage", "SDH": "Subdural hematoma", "ALS": "Amyotrophic lateral sclerosis", "PD": "Parkinson's disease", "AD": "Alzheimer's disease", "ICP": "Intracranial pressure", # Gastrointestinal "GERD": "Gastroesophageal reflux disease", "IBD": "Inflammatory bowel disease", "IBS": "Irritable bowel syndrome", "PUD": "Peptic ulcer disease", "UGIB": "Upper gastrointestinal bleeding", "LGIB": "Lower gastrointestinal bleeding", "SBO": "Small bowel obstruction", "LBO": "Large bowel obstruction", "NASH": "Nonalcoholic steatohepatitis", "NAFLD": "Nonalcoholic fatty liver disease", # Rheumatology & Infectious Disease "RA": "Rheumatoid arthritis", "OA": "Osteoarthritis", "SLE": "Systemic lupus erythematosus", "HIV": "Human immunodeficiency virus infection", "AIDS": "Acquired immunodeficiency syndrome", "TB": "Tuberculosis", "MRSA": "Methicillin resistant Staphylococcus aureus", "MSSA": "Methicillin susceptible Staphylococcus aureus", "C. DIFF": "Clostridioides difficile infection", "CDIFF": "Clostridioides difficile infection", # Common Labs (LOINC domain) "CBC": "Complete blood count", "BMP": "Basic metabolic panel", "CMP": "Comprehensive metabolic panel", "LFT": "Liver function tests", "HGB": "Hemoglobin", "HB": "Hemoglobin", "HCT": "Hematocrit", "WBC": "White blood cell count", "RBC": "Red blood cell count", "PLT": "Platelet count", "INR": "International normalized ratio", "PT": "Prothrombin time", "PTT": "Partial thromboplastin time", "CR": "Creatinine", "BUN": "Blood urea nitrogen", "HBA1C": "Hemoglobin A1c", "A1C": "Hemoglobin A1c", "TSH": "Thyroid stimulating hormone", "ESR": "Erythrocyte sedimentation rate", "CRP": "C-reactive protein", "BNP": "B-type natriuretic peptide", "UA": "Urinalysis", # Procedures & Diagnostics "EGD": "Esophagogastroduodenoscopy", "CXR": "Chest X-ray", "CT": "Computed tomography", "CTA": "Computed tomography angiography", "MRI": "Magnetic resonance imaging", "MRA": "Magnetic resonance angiography", "ECHO": "Echocardiogram", "EKG": "Electrocardiogram", "ECG": "Electrocardiogram", "US": "Ultrasound", "LP": "Lumbar puncture", } # Context-dependent disambiguation rules: # Acronym -> List of (ContextKeywords, DisambiguatedExpansion) CONTEXT_DISAMBIGUATION_RULES: Dict[str, List[Tuple[List[str], str]]] = { "MS": [ (["valve", "mitral", "murmur", "stenosis", "rheumatic", "cardiac", "heart"], "Mitral valve stenosis"), (["sclerosis", "demyelinating", "relapse", "flare", "neurologic", "mri", "brain", "spine", "optic"], "Multiple sclerosis"), (["mental", "status", "alert", "oriented", "confused"], "Mental status"), ], "MI": [ (["chest", "pain", "troponin", "stemi", "nstemi", "cardiac", "coronary", "ecg", "ekg"], "Myocardial infarction"), (["mitral", "insufficiency", "valve", "regurgitation", "murmur"], "Mitral valve insufficiency"), ], "PE": [ (["chest", "pain", "shortness", "breath", "d-dimer", "cta", "thrombus", "embolism", "clot", "dvt", "lung"], "Pulmonary embolism"), (["exam", "physical", "vital", "abdomen", "extremities", "heent", "neuro", "pupils", "clear"], "Physical examination"), (["effusion", "pleural", "thoracentesis", "fluid"], "Pleural effusion"), ], "MR": [ (["valve", "mitral", "regurgitation", "systolic", "murmur", "cardiac"], "Mitral regurgitation"), (["retardation", "mental", "intellectual"], "Mental retardation"), (["resonance", "magnetic", "scan", "imaging"], "Magnetic resonance"), ], "AS": [ (["valve", "aortic", "stenosis", "systolic", "murmur", "cardiac", "tavr", "echo"], "Aortic valve stenosis"), (["ankylosing", "spondylitis", "hla-b27", "back", "sacroiliac"], "Ankylosing spondylitis"), ], "RA": [ (["arthritis", "rheumatoid", "joint", "swelling", "rf", "anti-ccp", "methotrexate"], "Rheumatoid arthritis"), (["atrium", "right", "atrial", "cardiac", "enlargement"], "Right atrium"), ], "PT": [ (["inr", "prothrombin", "coagulation", "warfarin", "bleeding"], "Prothrombin time"), (["physical", "therapy", "rehab", "ambulation", "gait", "exercises"], "Physical therapy"), (["patient"], "Patient"), ], } class ClinicalAcronymExpander: """ Normalizes clinical shorthand and expands domain acronyms into fully qualified medical terminology. """ def __init__(self, custom_dict: Optional[Dict[str, str]] = None): self.lexicon = dict(CLINICAL_ACRONYMS) if custom_dict: self.lexicon.update(custom_dict) self.context_rules = dict(CONTEXT_DISAMBIGUATION_RULES) def disambiguate(self, acronym: str, context: Optional[str] = None) -> Optional[str]: """ Disambiguates an acronym using contextual note keywords if ambiguous. """ acr_upper = acronym.strip().upper() if acr_upper in self.context_rules: if context: context_lower = context.lower() best_expansion = None max_matches = 0 for keywords, expansion in self.context_rules[acr_upper]: matches = sum(1 for kw in keywords if re.search(rf"\b{re.escape(kw)}\b", context_lower)) if matches > max_matches: max_matches = matches best_expansion = expansion if best_expansion and max_matches > 0: return best_expansion # Default to first expansion if no context matched return self.context_rules[acr_upper][0][1] return self.lexicon.get(acr_upper, None) def expand_mention(self, mention: str, context: Optional[str] = None) -> str: """ Expands a standalone clinical mention string if it is an acronym, or returns the original mention if not recognized. """ cleaned = mention.strip() cleaned_upper = cleaned.upper() expansion = self.disambiguate(cleaned_upper, context) if expansion: return expansion return cleaned def get_search_queries(self, mention: str, context: Optional[str] = None) -> List[str]: """ Generates enriched search query representations for retrieval: e.g. ['HTN', 'Hypertension', 'HTN (Hypertension)'] """ cleaned = mention.strip() expansion = self.disambiguate(cleaned, context) queries = [cleaned] if expansion and expansion.lower() != cleaned.lower(): queries.append(expansion) queries.append(f"{cleaned} ({expansion})") return list(dict.fromkeys(queries)) def normalize_text_acronyms(self, text: str) -> str: """ Scans a clinical narrative for known unambiguous uppercase acronyms and attaches their expansions inline: e.g. 'c/o SOB' -> 'c/o SOB (Shortness of breath)' """ def replace_fn(match): word = match.group(0) word_upper = word.upper() if word_upper in self.lexicon and word_upper not in self.context_rules: exp = self.lexicon[word_upper] return f"{word} ({exp})" return word # Only target standalone uppercase words of length 2 to 5 pattern = r"\b[A-Z0-9\-\.]{2,6}\b" return re.sub(pattern, replace_fn, text)