---
title: Natural Language Processing for Medical Report Analysis - Part 2
date: 2026-05-09T14:52:29Z
modified: 2026-05-25T06:23:28Z
permalink: "https://www.micheledpierri.com/2026/05/09/advanced-spacy-scispacy-clinical-nlp/"
type: post
status: publish
excerpt: ""
wpid: 2692
categories:
  - Health Informatics
  - Medicine
tags:
  - Health Informatics
  - Medicine
  - Data Science
  - NLP
  - Python
featured_image: "https://www.micheledpierri.com/wp-content/uploads/2026/05/NLP2.png"
featured_image_alt: A doctor translates text
timestamp: 2026-05-25T06:23:28Z
---

# Introduction to Natural Language Processing for Medical Report Analysis: Advanced Linguistic Analysis with spaCy and scispaCy for Medical Reports

---

_Article authored by Michele D. Pierri, MD_

_Cardiac Surgeon & Medical Technology Researcher_

_Last updated: May 2025_

_Reading time: 30 minutes_

---

## Abstract

Regular expressions give us a solid foundation for pulling structured data out of clinical documents. They struggle, though, with the semantic complexity that defines medical language. This article introduces two complementary libraries (spaCy and scispaCy) that bring genuine linguistic intelligence to clinical text. We work through detailed examples on a post-CABG discharge summary, covering tokenization, part-of-speech tagging, dependency parsing, and Named Entity Recognition (NER) with biomedical models. UMLS entity linking is also explored, since it ties extracted clinical terms to standardized medical ontologies and enables interoperability across health information systems. By the final section the reader will know not only how to implement these techniques in Python, but when to prefer them over regex; and when, in the daily reality of clinical NLP, the two should be combined.

**Article Series:**

1. [Introduction to NLP and Regex for Medical Reports](https://www.micheledpierri.com/wp-content/uploads/wp-mfa-exports/post/natural-language-processing-for-medical-report.md)
2. **Advanced Linguistic Analysis with spaCy and scispaCy**
3. [Machine Learning and Large Language Models for Clinical Text Analysis](https://www.micheledpierri.com/wp-content/uploads/wp-mfa-exports/post/machine-learning-and-large-language-models-for-clinical-text-analysis.md)

---

## 1. Beyond Pattern Matching: Why Linguistic Analysis?

### 1.1 The Limits of Regular Expressions

The first article of this series built a regex parser able to extract patient demographics, surgical parameters, medications, and complications from a post-CABG discharge summary. That implementation worked because our sample document followed predictable formatting conventions. Real-world clinical documentation, in our experience reading hundreds of operative reports across centers, is far less disciplined.

Consider these semantically equivalent phrases that all describe the same complication:

"Postoperative atrial fibrillation was noted on POD 3"
"Patient developed new-onset AF in the immediate post-surgical period"
"Rhythm monitoring revealed paroxysmal atrial fibrillation 72 hours after bypass"
"POD 3: AF, cardioverted successfully"
```
<span class="line"><span style="color: #50FA7B">"Postoperative atrial fibrillation was noted on POD 3"</span></span>
<span class="line"><span style="color: #50FA7B">"Patient developed new-onset AF in the immediate post-surgical period"</span></span>
<span class="line"><span style="color: #50FA7B">"Rhythm monitoring revealed paroxysmal atrial fibrillation 72 hours after bypass"</span></span>
<span class="line"><span style="color: #50FA7B">"POD 3: AF, cardioverted successfully"</span></span>
<span class="line"></span>
```

A regex pattern designed to catch the first phrasing will miss the others. We could write extra patterns for each variant, but this becomes an arms race against the variability of natural language. Regex also cannot:

- Determine that “no wound infections” and “wound infection noted” have opposite clinical meanings (negation handling)
- Understand that “CABG,” “bypass surgery,” and “coronary revascularization” refer to the same procedure
- Extract the _relationship_ between a drug and its indication (“amiodarone for atrial fibrillation”)
- Handle abbreviation disambiguation (“AF” could mean atrial fibrillation or aortic flow depending on context)

These limitations are what push us toward NLP tools built on computational linguistics, the scientific study of language structure.

### 1.2 The Linguistic Approach

Natural language processing at the linguistic level rests on a simple premise: language has internal structure, and that structure can be computationally modeled. Rather than matching character sequences, linguistic NLP:

1. **Tokenizes** text into meaningful units (words, punctuation, medical abbreviations)
2. **Tags** each token with its grammatical role (noun, verb, adjective)
3. **Parses** the syntactic relationships between tokens (subject, object, modifier)
4. **Recognizes** named entities, that is, spans of text referring to real-world concepts (diseases, drugs, procedures)
5. **Links** recognized entities to external knowledge bases (UMLS, SNOMED-CT, RxNorm)

The pipeline turns raw text not into matched strings, but into a structured representation of linguistic meaning.

### 1.3 The spaCy Ecosystem for Biomedical Text

spaCy is an industrial-strength NLP library for Python, designed for production use rather than academic experimentation. It delivers fast, accurate linguistic analysis through pre-trained statistical models built on large corpora. For general English text, the `en_core_web_sm/md/lg` models are the standard starting point.

Medical language presents a domain-specific challenge. General-purpose NLP models are trained predominantly on news articles, books, and web content, corpora that share little with clinical documentation. Medical text is characterized by:

- High density of Latin and Greek terminology
- Extensive use of abbreviations and acronyms (POD, CABG, LVEF, LIMA)
- Domain-specific syntactic patterns
- Specialized named entity types (diseases, procedures, anatomical structures, medications)
- Negation patterns with clinical significance (“no evidence of”, “without complications”)

This is where **scispaCy** earns its place. Developed by the Allen Institute for AI, scispaCy provides spaCy-compatible models trained on biomedical scientific literature. The performance gap on clinical text, compared to general-purpose models, is substantial.

---

## 2. spaCy Fundamentals for Medical Text

### 2.1 Installation and Setup

Before proceeding, ensure you have the required libraries installed. spaCy and scispaCy require careful version management:

# Install spaCy
pip install spacy

# Install scispaCy
pip install scispacy

# Install a scispaCy biomedical model
# en_core_sci_md offers a good balance of accuracy and performance
pip install <https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_core_sci_md-0.5.4.tar.gz>

# For UMLS entity linking (requires additional setup)
pip install scispacy[linker]
```
<span class="line"><span style="color: #6272A4"># Install spaCy</span></span>
<span class="line"><span style="color: #F8F8F2">pip install spacy</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Install scispaCy</span></span>
<span class="line"><span style="color: #F8F8F2">pip install scispacy</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Install a scispaCy biomedical model</span></span>
<span class="line"><span style="color: #6272A4"># en_core_sci_md offers a good balance of accuracy and performance</span></span>
<span class="line"><span style="color: #F8F8F2">pip install </span><span style="color: #FF79C6"><</span><span style="color: #F8F8F2">https:</span><span style="color: #FF79C6">//</span><span style="color: #F8F8F2">s3</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">us</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">west</span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">2.</span><span style="color: #F8F8F2">amazonaws.com</span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2">ai2</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">s2</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">scispacy</span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2">releases</span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2">v0.5.4</span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2">en_core_sci_md</span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">0.5</span><span style="color: #F8F8F2">.4.tar.gz</span><span style="color: #FF79C6">></span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># For UMLS entity linking (requires additional setup)</span></span>
<span class="line"><span style="color: #F8F8F2">pip install scispacy[linker]</span></span>
<span class="line"></span>
```

> **Note on model selection**: scispaCy provides several pre-trained models. `en_core_sci_sm` is the smallest and fastest; `en_core_sci_md` includes word vectors for better similarity comparisons; `en_core_sci_lg` offers maximum accuracy. For production medical applications, `en_core_sci_md` or `en_core_sci_lg` is recommended. For research requiring UMLS linking specifically, `en_core_sci_lg` combined with the `UmlsEntityLinker` is the gold standard.

### 2.2 The spaCy Document Object Model

When spaCy processes a text string, it returns a `Doc` object: a rich data structure that exposes all linguistic annotations. Understanding this object model is essential before doing anything serious with the library.

import spacy

# Load the general English model first to understand the basics
# (we will switch to scispaCy models for medical text shortly)
nlp = spacy.load("en_core_web_sm")

sample_text = """
The patient was extubated on POD 1 without complications.
Postoperative atrial fibrillation was noted on POD 3,
successfully cardioverted with amiodarone loading followed by maintenance dose.
"""

doc = nlp(sample_text)

# The Doc object contains Token objects
print(f"Number of tokens:{len(doc)}")

# Each token exposes multiple attributes
for token in doc[:10]:  # First 10 tokens
    print(f"  Token:{token.text!r:20} | POS:{token.pos_:10} | Lemma:{token.lemma_:20} | Stop:{token.is_stop}")
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> spacy</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Load the general English model first to understand the basics</span></span>
<span class="line"><span style="color: #6272A4"># (we will switch to scispaCy models for medical text shortly)</span></span>
<span class="line"><span style="color: #F8F8F2">nlp </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> spacy.load(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">en_core_web_sm</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">sample_text </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"""</span></span>
<span class="line"><span style="color: #F1FA8C">The patient was extubated on POD 1 without complications.</span></span>
<span class="line"><span style="color: #F1FA8C">Postoperative atrial fibrillation was noted on POD 3,</span></span>
<span class="line"><span style="color: #F1FA8C">successfully cardioverted with amiodarone loading followed by maintenance dose.</span></span>
<span class="line"><span style="color: #E9F284">"""</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">doc </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp(sample_text)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># The Doc object contains Token objects</span></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"Number of tokens:</span><span style="color: #BD93F9">{</span><span style="color: #8BE9FD">len</span><span style="color: #F8F8F2">(doc)</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Each token exposes multiple attributes</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> token </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> doc[</span><span style="color: #FF79C6">:</span><span style="color: #BD93F9">10</span><span style="color: #F8F8F2">]:  </span><span style="color: #6272A4"># First 10 tokens</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  Token:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">token.text</span><span style="color: #FF79C6">!r:20</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C"> | POS:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">token.pos_</span><span style="color: #FF79C6">:10</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C"> | Lemma:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">token.lemma_</span><span style="color: #FF79C6">:20</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C"> | Stop:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">token.is_stop</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
```

Output:

Number of tokens: 40
 Token: ‘The’ | POS: DET | Lemma: ‘the’ | Stop: True
 Token: ‘patient’ | POS: NOUN | Lemma: ‘patient’ | Stop: False
 Token: ‘was’ | POS: AUX | Lemma: ‘be’ | Stop: True
 Token: ‘extubated’ | POS: VERB | Lemma: ‘extubate’ | Stop: False
 Token: ‘on’ | POS: ADP | Lemma: ‘on’ | Stop: True
 Token: ‘POD’ | POS: PROPN | Lemma: ‘POD’ | Stop: False
 Token: ‘1’ | POS: NUM | Lemma: ‘1’ | Stop: False

### 2.3 Tokenization in Medical Text

Tokenization (splitting text into individual tokens) sounds trivial, but in clinical NLP it is anything but. Take “Amiodarone 200mg”: should this be one token or two? What about “POD 3”, or “LVEF 40%”? Medical abbreviations punctuated with periods (“M.D.”, “i.v.”) add another layer of trouble.

spaCy’s tokenization rules are based on the Penn Treebank standard but can be customized. Let us look at how tokenization handles our discharge summary:

import spacy
from typing import List, Dict

def analyze_tokenization(text: str, nlp) -> List[Dict]:
    """
    Analyze how spaCy tokenizes medical text.

    This function reveals tokenization decisions that are clinically
    significant - for example, whether "100mg" is treated as one token
    or split into "100" and "mg".

    Args:
        text: Clinical text to tokenize
        nlp: Loaded spaCy model

    Returns:
        List of token dictionaries with linguistic attributes
    """
    doc = nlp(text)
    tokens = []

    for token in doc:
        tokens.append({
            'text': token.text,
            'lemma': token.lemma_,
            'pos': token.pos_,          # Coarse-grained POS (NOUN, VERB, etc.)
            'tag': token.tag_,           # Fine-grained POS (NNS, VBD, etc.)
            'dep': token.dep_,           # Syntactic dependency role
            'is_alpha': token.is_alpha,
            'is_digit': token.is_digit,
            'is_stop': token.is_stop,    # Common words with little semantic value
            'is_punct': token.is_punct,
            'shape': token.shape_,       # Character shape: "100mg" → "dddxx"
        })

    return tokens

# Test on the medication section of our discharge summary
medication_text = """
DISCHARGE MEDICATIONS:
1. Aspirin 100mg daily
2. Clopidogrel 75mg daily (for 12 months)
3. Atorvastatin 80mg daily
4. Metoprolol 50mg twice daily
5. Ramipril 5mg daily
6. Amiodarone 200mg daily (for 6 weeks)
"""

# We'll use scispaCy for actual medical processing
nlp_sci = spacy.load("en_core_sci_md")
tokens = analyze_tokenization(medication_text, nlp_sci)

# Print clinically relevant tokens (non-stop, non-punctuation)
print("Clinically Relevant Tokens:")
print(f"{'Text':<20}{'Lemma':<20}{'POS':<10}{'Shape':<15}")
print("-" * 70)
for t in tokens:
    if not t['is_stop'] and not t['is_punct'] and t['text'].strip():
        print(f"{t['text']:<20}{t['lemma']:<20}{t['pos']:<10}{t['shape']:<15}")
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> spacy</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> typing </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> List, Dict</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">analyze_tokenization</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">text</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">nlp</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> List[Dict]:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Analyze how spaCy tokenizes medical text.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    This function reveals tokenization decisions that are clinically</span></span>
<span class="line"><span style="color: #6272A4">    significant - for example, whether "100mg" is treated as one token</span></span>
<span class="line"><span style="color: #6272A4">    or split into "100" and "mg".</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Args:</span></span>
<span class="line"><span style="color: #6272A4">        text: Clinical text to tokenize</span></span>
<span class="line"><span style="color: #6272A4">        nlp: Loaded spaCy model</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        List of token dictionaries with linguistic attributes</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    doc </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp(text)</span></span>
<span class="line"><span style="color: #F8F8F2">    tokens </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> []</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> token </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> doc:</span></span>
<span class="line"><span style="color: #F8F8F2">        tokens.append({</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.text,</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">lemma</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.lemma_,</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">pos</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.pos_,          </span><span style="color: #6272A4"># Coarse-grained POS (NOUN, VERB, etc.)</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">tag</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.tag_,           </span><span style="color: #6272A4"># Fine-grained POS (NNS, VBD, etc.)</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">dep</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.dep_,           </span><span style="color: #6272A4"># Syntactic dependency role</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">is_alpha</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.is_alpha,</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">is_digit</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.is_digit,</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">is_stop</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.is_stop,    </span><span style="color: #6272A4"># Common words with little semantic value</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">is_punct</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.is_punct,</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">shape</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.shape_,       </span><span style="color: #6272A4"># Character shape: "100mg" → "dddxx"</span></span>
<span class="line"><span style="color: #F8F8F2">        })</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> tokens</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Test on the medication section of our discharge summary</span></span>
<span class="line"><span style="color: #F8F8F2">medication_text </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"""</span></span>
<span class="line"><span style="color: #F1FA8C">DISCHARGE MEDICATIONS:</span></span>
<span class="line"><span style="color: #F1FA8C">1. Aspirin 100mg daily</span></span>
<span class="line"><span style="color: #F1FA8C">2. Clopidogrel 75mg daily (for 12 months)</span></span>
<span class="line"><span style="color: #F1FA8C">3. Atorvastatin 80mg daily</span></span>
<span class="line"><span style="color: #F1FA8C">4. Metoprolol 50mg twice daily</span></span>
<span class="line"><span style="color: #F1FA8C">5. Ramipril 5mg daily</span></span>
<span class="line"><span style="color: #F1FA8C">6. Amiodarone 200mg daily (for 6 weeks)</span></span>
<span class="line"><span style="color: #E9F284">"""</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># We'll use scispaCy for actual medical processing</span></span>
<span class="line"><span style="color: #F8F8F2">nlp_sci </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> spacy.load(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">en_core_sci_md</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">tokens </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> analyze_tokenization(medication_text, nlp_sci)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Print clinically relevant tokens (non-stop, non-punctuation)</span></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Clinically Relevant Tokens:</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"</span><span style="color: #BD93F9">{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Text</span><span style="color: #E9F284">'</span><span style="color: #FF79C6">:<20</span><span style="color: #BD93F9">}{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Lemma</span><span style="color: #E9F284">'</span><span style="color: #FF79C6">:<20</span><span style="color: #BD93F9">}{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">POS</span><span style="color: #E9F284">'</span><span style="color: #FF79C6">:<10</span><span style="color: #BD93F9">}{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Shape</span><span style="color: #E9F284">'</span><span style="color: #FF79C6">:<15</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">-</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">70</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> t </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> tokens:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">not</span><span style="color: #F8F8F2"> t[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">is_stop</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">and</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">not</span><span style="color: #F8F8F2"> t[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">is_punct</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">and</span><span style="color: #F8F8F2"> t[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">].strip():</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">t[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #FF79C6">:<20</span><span style="color: #BD93F9">}{</span><span style="color: #F8F8F2">t[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">lemma</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #FF79C6">:<20</span><span style="color: #BD93F9">}{</span><span style="color: #F8F8F2">t[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">pos</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #FF79C6">:<10</span><span style="color: #BD93F9">}{</span><span style="color: #F8F8F2">t[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">shape</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #FF79C6">:<15</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
```

**Clinical Insight on Tokenization**: scispaCy handles medical abbreviations more gracefully than general models, although the picture is not uniform. “100mg” may be split or kept together depending on the model and configuration. For downstream extraction tasks, custom tokenization rules for domain-specific patterns are often beneficial:

from spacy.lang.char_classes import ALPHA, ALPHA_LOWER, ALPHA_UPPER
from spacy.lang.en import English

def add_medical_tokenization_rules(nlp):
    """
    Add custom tokenization rules for medical text.

    Medical documents contain patterns that standard tokenizers
    handle poorly: drug-dose combinations, medical acronyms,
    and clinical measurement notations.
    """
    # Prevent splitting on medical dose patterns like "100mg", "5mg"
    # These should remain as single tokens for accurate extraction
    infixes = nlp.Defaults.infixes

    # Add special cases for common medical abbreviations
    special_cases = {
        "POD": [{"ORTH": "POD"}],           # Postoperative Day
        "CPB": [{"ORTH": "CPB"}],           # Cardiopulmonary Bypass
        "CABG": [{"ORTH": "CABG"}],         # Coronary Artery Bypass Grafting
        "LIMA": [{"ORTH": "LIMA"}],         # Left Internal Mammary Artery
        "SVG": [{"ORTH": "SVG"}],           # Saphenous Vein Graft
        "LVEF": [{"ORTH": "LVEF"}],         # Left Ventricular Ejection Fraction
        "LAD": [{"ORTH": "LAD"}],           # Left Anterior Descending
        "RCA": [{"ORTH": "RCA"}],           # Right Coronary Artery
    }

    for text, case in special_cases.items():
        nlp.tokenizer.add_special_case(text, case)

    return nlp
```
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> spacy.lang.char_classes </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">ALPHA</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">ALPHA_LOWER</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">ALPHA_UPPER</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> spacy.lang.en </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> English</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">add_medical_tokenization_rules</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">nlp</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Add custom tokenization rules for medical text.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Medical documents contain patterns that standard tokenizers</span></span>
<span class="line"><span style="color: #6272A4">    handle poorly: drug-dose combinations, medical acronyms,</span></span>
<span class="line"><span style="color: #6272A4">    and clinical measurement notations.</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Prevent splitting on medical dose patterns like "100mg", "5mg"</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># These should remain as single tokens for accurate extraction</span></span>
<span class="line"><span style="color: #F8F8F2">    infixes </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp.Defaults.infixes</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Add special cases for common medical abbreviations</span></span>
<span class="line"><span style="color: #F8F8F2">    special_cases </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">POD</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: [{</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">ORTH</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">POD</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">}],           </span><span style="color: #6272A4"># Postoperative Day</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">CPB</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: [{</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">ORTH</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">CPB</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">}],           </span><span style="color: #6272A4"># Cardiopulmonary Bypass</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">CABG</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: [{</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">ORTH</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">CABG</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">}],         </span><span style="color: #6272A4"># Coronary Artery Bypass Grafting</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">LIMA</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: [{</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">ORTH</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">LIMA</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">}],         </span><span style="color: #6272A4"># Left Internal Mammary Artery</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">SVG</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: [{</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">ORTH</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">SVG</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">}],           </span><span style="color: #6272A4"># Saphenous Vein Graft</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">LVEF</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: [{</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">ORTH</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">LVEF</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">}],         </span><span style="color: #6272A4"># Left Ventricular Ejection Fraction</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">LAD</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: [{</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">ORTH</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">LAD</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">}],           </span><span style="color: #6272A4"># Left Anterior Descending</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">RCA</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: [{</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">ORTH</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">RCA</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">}],           </span><span style="color: #6272A4"># Right Coronary Artery</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> text, case </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> special_cases.items():</span></span>
<span class="line"><span style="color: #F8F8F2">        nlp.tokenizer.add_special_case(text, case)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> nlp</span></span>
<span class="line"></span>
```

### 2.4 Part-of-Speech Tagging

Part-of-speech (POS) tagging assigns each token its grammatical role. In medical NLP, POS tags are useful for identifying:

- **Nouns** (NOUN, PROPN): Disease names, anatomical structures, drugs
- **Verbs** (VERB): Procedures performed, clinical events (“extubated,” “cardioverted”)
- **Adjectives** (ADJ): Qualitative descriptions (“satisfactory,” “stable,” “reduced”)
- **Numbers** (NUM): Dosages, durations, laboratory values

def extract_clinical_verbs(text: str, nlp) -> List[Dict]:
    """
    Extract action verbs from clinical text.

    In cardiac surgery reports, verbs often encode critical procedural
    and clinical events: "performed," "extubated," "cardioverted,"
    "noted," "removed." This function identifies these for downstream
    event extraction.

    Args:
        text: Clinical narrative text
        nlp: scispaCy model

    Returns:
        List of clinical verbs with their lemmas and surrounding context
    """
    doc = nlp(text)
    clinical_verbs = []

    for token in doc:
        if token.pos_ == "VERB" and not token.is_stop:
            # Get 3-word context window around verb
            start = max(0, token.i - 3)
            end = min(len(doc), token.i + 4)
            context = doc[start:end].text

            clinical_verbs.append({
                'verb': token.text,
                'lemma': token.lemma_,
                'tense': token.tag_,    # VBD = past tense, VBN = past participle
                'context': context,
                'negated': _check_negation(token, doc)  # See negation section
            })

    return clinical_verbs

def _check_negation(token, doc) -> bool:
    """
    Simple negation detection using dependency parsing.

    Checks if a token has a negation modifier in its dependency tree.
    More sophisticated negation handling (like NegEx algorithm) is
    covered in the discussion section.
    """
    for child in token.children:
        if child.dep_ == "neg":
            return True
    return False

# Apply to the postoperative course section
postop_text = """
The patient was extubated on POD 1 without complications.
Chest tubes were removed on POD 2.
Postoperative atrial fibrillation was noted on POD 3,
successfully cardioverted with amiodarone loading.
No wound infections or sternal instability noted.
"""

nlp_sci = spacy.load("en_core_sci_md")
verbs = extract_clinical_verbs(postop_text, nlp_sci)

print("Clinical Verbs Detected:")
print(f"{'Verb':<20}{'Lemma':<20}{'Tense':<8}{'Negated':<10}{'Context'}")
print("-" * 90)
for v in verbs:
    print(f"{v['verb']:<20}{v['lemma']:<20}{v['tense']:<8}{str(v['negated']):<10}{v['context']}")
```
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">extract_clinical_verbs</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">text</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">nlp</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> List[Dict]:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Extract action verbs from clinical text.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    In cardiac surgery reports, verbs often encode critical procedural</span></span>
<span class="line"><span style="color: #6272A4">    and clinical events: "performed," "extubated," "cardioverted,"</span></span>
<span class="line"><span style="color: #6272A4">    "noted," "removed." This function identifies these for downstream</span></span>
<span class="line"><span style="color: #6272A4">    event extraction.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Args:</span></span>
<span class="line"><span style="color: #6272A4">        text: Clinical narrative text</span></span>
<span class="line"><span style="color: #6272A4">        nlp: scispaCy model</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        List of clinical verbs with their lemmas and surrounding context</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    doc </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp(text)</span></span>
<span class="line"><span style="color: #F8F8F2">    clinical_verbs </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> []</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> token </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> doc:</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> token.pos_ </span><span style="color: #FF79C6">==</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">VERB</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">and</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">not</span><span style="color: #F8F8F2"> token.is_stop:</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #6272A4"># Get 3-word context window around verb</span></span>
<span class="line"><span style="color: #F8F8F2">            start </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">max</span><span style="color: #F8F8F2">(</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">, token.i </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">3</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">            end </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">min</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD">len</span><span style="color: #F8F8F2">(doc), token.i </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">4</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">            context </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> doc[start</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">end].text</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">            clinical_verbs.append({</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">verb</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.text,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">lemma</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.lemma_,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">tense</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.tag_,    </span><span style="color: #6272A4"># VBD = past tense, VBN = past participle</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">context</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: context,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">negated</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: _check_negation(token, doc)  </span><span style="color: #6272A4"># See negation section</span></span>
<span class="line"><span style="color: #F8F8F2">            })</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> clinical_verbs</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">_check_negation</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">token</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">doc</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">bool</span><span style="color: #F8F8F2">:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Simple negation detection using dependency parsing.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Checks if a token has a negation modifier in its dependency tree.</span></span>
<span class="line"><span style="color: #6272A4">    More sophisticated negation handling (like NegEx algorithm) is</span></span>
<span class="line"><span style="color: #6272A4">    covered in the discussion section.</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> child </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> token.children:</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> child.dep_ </span><span style="color: #FF79C6">==</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">neg</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">:</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">True</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">False</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Apply to the postoperative course section</span></span>
<span class="line"><span style="color: #F8F8F2">postop_text </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"""</span></span>
<span class="line"><span style="color: #F1FA8C">The patient was extubated on POD 1 without complications.</span></span>
<span class="line"><span style="color: #F1FA8C">Chest tubes were removed on POD 2.</span></span>
<span class="line"><span style="color: #F1FA8C">Postoperative atrial fibrillation was noted on POD 3,</span></span>
<span class="line"><span style="color: #F1FA8C">successfully cardioverted with amiodarone loading.</span></span>
<span class="line"><span style="color: #F1FA8C">No wound infections or sternal instability noted.</span></span>
<span class="line"><span style="color: #E9F284">"""</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">nlp_sci </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> spacy.load(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">en_core_sci_md</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">verbs </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> extract_clinical_verbs(postop_text, nlp_sci)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Clinical Verbs Detected:</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"</span><span style="color: #BD93F9">{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Verb</span><span style="color: #E9F284">'</span><span style="color: #FF79C6">:<20</span><span style="color: #BD93F9">}{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Lemma</span><span style="color: #E9F284">'</span><span style="color: #FF79C6">:<20</span><span style="color: #BD93F9">}{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Tense</span><span style="color: #E9F284">'</span><span style="color: #FF79C6">:<8</span><span style="color: #BD93F9">}{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Negated</span><span style="color: #E9F284">'</span><span style="color: #FF79C6">:<10</span><span style="color: #BD93F9">}{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Context</span><span style="color: #E9F284">'</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">-</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">90</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> v </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> verbs:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">v[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">verb</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #FF79C6">:<20</span><span style="color: #BD93F9">}{</span><span style="color: #F8F8F2">v[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">lemma</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #FF79C6">:<20</span><span style="color: #BD93F9">}{</span><span style="color: #F8F8F2">v[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">tense</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #FF79C6">:<8</span><span style="color: #BD93F9">}{</span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">(v[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">negated</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">])</span><span style="color: #FF79C6">:<10</span><span style="color: #BD93F9">}{</span><span style="color: #F8F8F2">v[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">context</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
```

### 2.5 Dependency Parsing: Understanding Relationships

Dependency parsing is where linguistic analysis starts to look like genuine clinical intelligence. A dependency parser builds a tree showing how words in a sentence relate to each other grammatically. In medical text, this opens the door to extracting structured relationships:

- _Subject_ → _Verb_ → _Object_: “amiodarone cardioverted atrial fibrillation”
- _Modifier_ → _Head noun_: “postoperative atrial fibrillation” (adjective modifying noun)
- _Prepositional_ relationships: “cardioverted _with_ amiodarone” (instrument of action)

def extract_drug_event_relations(text: str, nlp) -> List[Dict]:
    """
    Extract drug-event relationships using dependency parsing.

    Dependency parsing allows us to identify sentences like:
    "successfully cardioverted with amiodarone" →
      drug: amiodarone, action: cardioverted, outcome: successful

    This is a level of extraction impossible with regex alone,
    as it requires understanding sentence structure, not just
    character patterns.

    Args:
        text: Clinical text containing drug and event mentions
        nlp: Loaded spaCy/scispaCy model

    Returns:
        List of extracted drug-event relationships
    """
    doc = nlp(text)
    relations = []

    # Medical procedure verbs relevant to cardiac surgery
    PROCEDURE_VERBS = {
        'cardiovert', 'extubate', 'remove', 'perform',
        'complete', 'administer', 'initiate', 'discontinue'
    }

    # Common cardiac drug keywords
    CARDIAC_DRUGS = {
        'amiodarone', 'aspirin', 'clopidogrel', 'atorvastatin',
        'metoprolol', 'ramipril', 'furosemide', 'metformin'
    }

    for token in doc:
        # Find procedure verbs
        if token.lemma_.lower() in PROCEDURE_VERBS:
            relation = {
                'verb': token.text,
                'verb_lemma': token.lemma_,
                'drug': None,
                'patient_subject': False,
                'outcome_modifier': None,
                'sentence': token.sent.text.strip()
            }

            # Traverse dependency tree for related tokens
            for child in token.children:
                # Check for drug as instrument (prepositional: "with amiodarone")
                if child.dep_ == "prep":
                    for grandchild in child.children:
                        if grandchild.lemma_.lower() in CARDIAC_DRUGS:
                            relation['drug'] = grandchild.text

                # Check for patient as subject ("patient was extubated")
                if child.dep_ in ("nsubj", "nsubjpass"):
                    if child.lemma_.lower() in ("patient",):
                        relation['patient_subject'] = True

                # Check for outcome adverbs ("successfully cardioverted")
                if child.dep_ == "advmod":
                    relation['outcome_modifier'] = child.text

            # Also check the token's own head for drugs
            for sibling in token.head.children:
                if sibling.lemma_.lower() in CARDIAC_DRUGS and sibling != token:
                    if not relation['drug']:
                        relation['drug'] = sibling.text

            if relation['verb']:
                relations.append(relation)

    return relations

# Test on the postoperative section
postop_text = """
The patient was extubated on POD 1 without complications.
Chest tubes were removed on POD 2.
Postoperative atrial fibrillation was noted on POD 3,
successfully cardioverted with amiodarone loading followed by maintenance dose.
"""

relations = extract_drug_event_relations(postop_text, nlp_sci)

print("Drug-Event Relationships Extracted:")
for r in relations:
    print(f"\\n  Verb:{r['verb']}")
    print(f"  Drug:{r['drug'] or 'N/A'}")
    print(f"  Patient subject:{r['patient_subject']}")
    print(f"  Outcome modifier:{r['outcome_modifier'] or 'N/A'}")
    print(f"  Sentence:{r['sentence'][:80]}...")
```
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">extract_drug_event_relations</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">text</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">nlp</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> List[Dict]:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Extract drug-event relationships using dependency parsing.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Dependency parsing allows us to identify sentences like:</span></span>
<span class="line"><span style="color: #6272A4">    "successfully cardioverted with amiodarone" →</span></span>
<span class="line"><span style="color: #6272A4">      drug: amiodarone, action: cardioverted, outcome: successful</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    This is a level of extraction impossible with regex alone,</span></span>
<span class="line"><span style="color: #6272A4">    as it requires understanding sentence structure, not just</span></span>
<span class="line"><span style="color: #6272A4">    character patterns.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Args:</span></span>
<span class="line"><span style="color: #6272A4">        text: Clinical text containing drug and event mentions</span></span>
<span class="line"><span style="color: #6272A4">        nlp: Loaded spaCy/scispaCy model</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        List of extracted drug-event relationships</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    doc </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp(text)</span></span>
<span class="line"><span style="color: #F8F8F2">    relations </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> []</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Medical procedure verbs relevant to cardiac surgery</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #BD93F9">PROCEDURE_VERBS</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">cardiovert</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">extubate</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">remove</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">perform</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">complete</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">administer</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">initiate</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">discontinue</span><span style="color: #E9F284">'</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Common cardiac drug keywords</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #BD93F9">CARDIAC_DRUGS</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">amiodarone</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">aspirin</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">clopidogrel</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">atorvastatin</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">metoprolol</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ramipril</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">furosemide</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">metformin</span><span style="color: #E9F284">'</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> token </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> doc:</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #6272A4"># Find procedure verbs</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> token.lemma_.lower() </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">PROCEDURE_VERBS</span><span style="color: #F8F8F2">:</span></span>
<span class="line"><span style="color: #F8F8F2">            relation </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">verb</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.text,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">verb_lemma</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.lemma_,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">drug</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">None</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">patient_subject</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">False</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">outcome_modifier</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">None</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">sentence</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: token.sent.text.strip()</span></span>
<span class="line"><span style="color: #F8F8F2">            }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #6272A4"># Traverse dependency tree for related tokens</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> child </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> token.children:</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #6272A4"># Check for drug as instrument (prepositional: "with amiodarone")</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> child.dep_ </span><span style="color: #FF79C6">==</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">prep</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">:</span></span>
<span class="line"><span style="color: #F8F8F2">                    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> grandchild </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> child.children:</span></span>
<span class="line"><span style="color: #F8F8F2">                        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> grandchild.lemma_.lower() </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">CARDIAC_DRUGS</span><span style="color: #F8F8F2">:</span></span>
<span class="line"><span style="color: #F8F8F2">                            relation[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">drug</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> grandchild.text</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #6272A4"># Check for patient as subject ("patient was extubated")</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> child.dep_ </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> (</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">nsubj</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">nsubjpass</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">                    </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> child.lemma_.lower() </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> (</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">patient</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,):</span></span>
<span class="line"><span style="color: #F8F8F2">                        relation[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">patient_subject</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">True</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #6272A4"># Check for outcome adverbs ("successfully cardioverted")</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> child.dep_ </span><span style="color: #FF79C6">==</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">advmod</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">:</span></span>
<span class="line"><span style="color: #F8F8F2">                    relation[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">outcome_modifier</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> child.text</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #6272A4"># Also check the token's own head for drugs</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> sibling </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> token.head.children:</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> sibling.lemma_.lower() </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">CARDIAC_DRUGS</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">and</span><span style="color: #F8F8F2"> sibling </span><span style="color: #FF79C6">!=</span><span style="color: #F8F8F2"> token:</span></span>
<span class="line"><span style="color: #F8F8F2">                    </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">not</span><span style="color: #F8F8F2"> relation[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">drug</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]:</span></span>
<span class="line"><span style="color: #F8F8F2">                        relation[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">drug</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> sibling.text</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> relation[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">verb</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]:</span></span>
<span class="line"><span style="color: #F8F8F2">                relations.append(relation)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> relations</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Test on the postoperative section</span></span>
<span class="line"><span style="color: #F8F8F2">postop_text </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"""</span></span>
<span class="line"><span style="color: #F1FA8C">The patient was extubated on POD 1 without complications.</span></span>
<span class="line"><span style="color: #F1FA8C">Chest tubes were removed on POD 2.</span></span>
<span class="line"><span style="color: #F1FA8C">Postoperative atrial fibrillation was noted on POD 3,</span></span>
<span class="line"><span style="color: #F1FA8C">successfully cardioverted with amiodarone loading followed by maintenance dose.</span></span>
<span class="line"><span style="color: #E9F284">"""</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">relations </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> extract_drug_event_relations(postop_text, nlp_sci)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Drug-Event Relationships Extracted:</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> r </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> relations:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"</span><span style="color: #FF79C6">\\</span><span style="color: #F1FA8C">n  Verb:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">r[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">verb</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  Drug:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">r[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">drug</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">or</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">N/A</span><span style="color: #E9F284">'</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  Patient subject:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">r[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">patient_subject</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  Outcome modifier:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">r[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">outcome_modifier</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">or</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">N/A</span><span style="color: #E9F284">'</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  Sentence:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">r[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">sentence</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">][:</span><span style="color: #BD93F9">80</span><span style="color: #F8F8F2">]</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">..."</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
```

The dependency parser thus enables extraction of _structured relationships_: not just entity mentions, but how entities interact within clinical events. This capability has direct applications in pharmacovigilance, complication tracking, and automated population of surgical registries.

---

## 3. scispaCy: Biomedical NLP

### 3.1 Why Standard Models Fail for Medical Text

To appreciate scispaCy’s contribution, it helps to compare how a general-purpose spaCy model and a scispaCy model handle the same clinical sentence. Take: _“LIMA to LAD anastomosis was completed with satisfactory flow measurements.”_

**General spaCy (en\_core\_web\_sm):**

- “LIMA” → labeled as `ORG` (organization), incorrect
- “LAD” → labeled as `ORG`, incorrect
- “anastomosis” → not recognized as a named entity at all

**scispaCy (en\_core\_sci\_md):**

- “LIMA” → recognized with `ENTITY` label (biomedical entity)
- “LAD” → recognized as biomedical entity
- “anastomosis” → recognized as biomedical entity

The difference is training data. scispaCy models are trained on the CRAFT corpus (Colorado Richly Annotated Full-Text Corpus) and PubMed abstracts, scientific biomedical text that includes the vocabulary and entity patterns of clinical medicine. The NER annotations in these corpora rely on the BC5CDR schema for chemical and disease entities, plus broader entity annotations from ontologies like UMLS.

### 3.2 Named Entity Recognition with scispaCy

Named Entity Recognition (NER) is the automatic identification and classification of named entities in text; in our case, medical concepts such as diseases, drugs, procedures, and anatomical structures.

import spacy
from typing import List, Dict, Tuple
import json

# Load scispaCy model
nlp_sci = spacy.load("en_core_sci_md")

# Full discharge summary for NER analysis
DISCHARGE_SUMMARY = """
CARDIAC SURGERY DISCHARGE SUMMARY

Patient: John Smith
MRN: 12345678
DOB: 15/03/1958
Admission Date: 10/01/2024
Discharge Date: 18/01/2024

DIAGNOSIS:
1. Triple vessel coronary artery disease
2. Reduced left ventricular function (LVEF 35%)
3. Hypertension
4. Type 2 Diabetes Mellitus

PROCEDURE PERFORMED:
Coronary Artery Bypass Grafting x3 (CABG)
- LIMA to LAD
- SVG to OM1
- SVG to RCA
Date of surgery: 11/01/2024

OPERATIVE DETAILS:
The procedure was performed via median sternotomy under general anesthesia.
Cardiopulmonary bypass time: 98 minutes
Aortic cross-clamp time: 67 minutes
All anastomoses were completed with satisfactory flow measurements.

POSTOPERATIVE COURSE:
The patient was extubated on POD 1 without complications. Chest tubes were removed on POD 2.
Postoperative atrial fibrillation was noted on POD 3, successfully cardioverted with amiodarone
loading followed by maintenance dose. Patient remained in sinus rhythm thereafter.

No wound infections or sternal instability noted. Echocardiography on POD 5 showed LVEF 40%
with good biventricular function and no pericardial effusion.

DISCHARGE MEDICATIONS:
1. Aspirin 100mg daily
2. Clopidogrel 75mg daily (for 12 months)
3. Atorvastatin 80mg daily
4. Metoprolol 50mg twice daily
5. Ramipril 5mg daily
6. Amiodarone 200mg daily (for 6 weeks)
7. Metformin 1000mg twice daily
8. Furosemide 40mg daily (for 2 weeks)

FOLLOW-UP:
- Outpatient cardiology clinic in 2 weeks
- Cardiac rehabilitation program enrollment
- INR monitoring not required (patient on dual antiplatelet therapy)

The patient was discharged home in stable condition with appropriate wound care instructions.

Dr. Sarah Johnson, MD
Cardiac Surgery Department
"""

def extract_biomedical_entities(text: str, nlp) -> Dict[str, List[str]]:
    """
    Extract all biomedical entities from clinical text using scispaCy NER.

    scispaCy's NER model identifies spans of text corresponding to
    biomedical concepts: diseases, chemicals, genes, cell types,
    species, and more. The entity label set depends on the model used:

    - en_core_sci_*: Uses a single 'ENTITY' label for all biomedical concepts
    - en_ner_bc5cdr_md: Distinguishes DISEASE and CHEMICAL entities
    - en_ner_jnlpba_md: Tags GENE, PROTEIN, RNA, DNA, CELL_TYPE, CELL_LINE

    For general clinical use, en_core_sci_md with UMLS linking provides
    the most complete semantic annotation.

    Args:
        text: Clinical text
        nlp: Loaded scispaCy model

    Returns:
        Dictionary grouping entities by their labels
    """
    doc = nlp(text)

    entities_by_label: Dict[str, List[str]] = {}

    for ent in doc.ents:
        label = ent.label_
        if label not in entities_by_label:
            entities_by_label[label] = []

        # Deduplicate while preserving order
        entity_text = ent.text.strip()
        if entity_text and entity_text not in entities_by_label[label]:
            entities_by_label[label].append(entity_text)

    return entities_by_label

# Extract entities from the discharge summary
entities = extract_biomedical_entities(DISCHARGE_SUMMARY, nlp_sci)

print("Biomedical Entities Extracted by scispaCy:")
print("=" * 60)
for label, ents in sorted(entities.items()):
    print(f"\\n[{label}] ({len(ents)} entities):")
    for entity in ents[:15]:  # Show first 15 per category
        print(f"  -{entity}")
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> spacy</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> typing </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> List, Dict, Tuple</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> json</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Load scispaCy model</span></span>
<span class="line"><span style="color: #F8F8F2">nlp_sci </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> spacy.load(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">en_core_sci_md</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Full discharge summary for NER analysis</span></span>
<span class="line"><span style="color: #BD93F9">DISCHARGE_SUMMARY</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"""</span></span>
<span class="line"><span style="color: #F1FA8C">CARDIAC SURGERY DISCHARGE SUMMARY</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">Patient: John Smith</span></span>
<span class="line"><span style="color: #F1FA8C">MRN: 12345678</span></span>
<span class="line"><span style="color: #F1FA8C">DOB: 15/03/1958</span></span>
<span class="line"><span style="color: #F1FA8C">Admission Date: 10/01/2024</span></span>
<span class="line"><span style="color: #F1FA8C">Discharge Date: 18/01/2024</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">DIAGNOSIS:</span></span>
<span class="line"><span style="color: #F1FA8C">1. Triple vessel coronary artery disease</span></span>
<span class="line"><span style="color: #F1FA8C">2. Reduced left ventricular function (LVEF 35%)</span></span>
<span class="line"><span style="color: #F1FA8C">3. Hypertension</span></span>
<span class="line"><span style="color: #F1FA8C">4. Type 2 Diabetes Mellitus</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">PROCEDURE PERFORMED:</span></span>
<span class="line"><span style="color: #F1FA8C">Coronary Artery Bypass Grafting x3 (CABG)</span></span>
<span class="line"><span style="color: #F1FA8C">- LIMA to LAD</span></span>
<span class="line"><span style="color: #F1FA8C">- SVG to OM1</span></span>
<span class="line"><span style="color: #F1FA8C">- SVG to RCA</span></span>
<span class="line"><span style="color: #F1FA8C">Date of surgery: 11/01/2024</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">OPERATIVE DETAILS:</span></span>
<span class="line"><span style="color: #F1FA8C">The procedure was performed via median sternotomy under general anesthesia.</span></span>
<span class="line"><span style="color: #F1FA8C">Cardiopulmonary bypass time: 98 minutes</span></span>
<span class="line"><span style="color: #F1FA8C">Aortic cross-clamp time: 67 minutes</span></span>
<span class="line"><span style="color: #F1FA8C">All anastomoses were completed with satisfactory flow measurements.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">POSTOPERATIVE COURSE:</span></span>
<span class="line"><span style="color: #F1FA8C">The patient was extubated on POD 1 without complications. Chest tubes were removed on POD 2.</span></span>
<span class="line"><span style="color: #F1FA8C">Postoperative atrial fibrillation was noted on POD 3, successfully cardioverted with amiodarone</span></span>
<span class="line"><span style="color: #F1FA8C">loading followed by maintenance dose. Patient remained in sinus rhythm thereafter.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">No wound infections or sternal instability noted. Echocardiography on POD 5 showed LVEF 40%</span></span>
<span class="line"><span style="color: #F1FA8C">with good biventricular function and no pericardial effusion.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">DISCHARGE MEDICATIONS:</span></span>
<span class="line"><span style="color: #F1FA8C">1. Aspirin 100mg daily</span></span>
<span class="line"><span style="color: #F1FA8C">2. Clopidogrel 75mg daily (for 12 months)</span></span>
<span class="line"><span style="color: #F1FA8C">3. Atorvastatin 80mg daily</span></span>
<span class="line"><span style="color: #F1FA8C">4. Metoprolol 50mg twice daily</span></span>
<span class="line"><span style="color: #F1FA8C">5. Ramipril 5mg daily</span></span>
<span class="line"><span style="color: #F1FA8C">6. Amiodarone 200mg daily (for 6 weeks)</span></span>
<span class="line"><span style="color: #F1FA8C">7. Metformin 1000mg twice daily</span></span>
<span class="line"><span style="color: #F1FA8C">8. Furosemide 40mg daily (for 2 weeks)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">FOLLOW-UP:</span></span>
<span class="line"><span style="color: #F1FA8C">- Outpatient cardiology clinic in 2 weeks</span></span>
<span class="line"><span style="color: #F1FA8C">- Cardiac rehabilitation program enrollment</span></span>
<span class="line"><span style="color: #F1FA8C">- INR monitoring not required (patient on dual antiplatelet therapy)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">The patient was discharged home in stable condition with appropriate wound care instructions.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">Dr. Sarah Johnson, MD</span></span>
<span class="line"><span style="color: #F1FA8C">Cardiac Surgery Department</span></span>
<span class="line"><span style="color: #E9F284">"""</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">extract_biomedical_entities</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">text</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">nlp</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> Dict[</span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, List[</span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">]]:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Extract all biomedical entities from clinical text using scispaCy NER.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    scispaCy's NER model identifies spans of text corresponding to</span></span>
<span class="line"><span style="color: #6272A4">    biomedical concepts: diseases, chemicals, genes, cell types,</span></span>
<span class="line"><span style="color: #6272A4">    species, and more. The entity label set depends on the model used:</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    - en_core_sci_*: Uses a single 'ENTITY' label for all biomedical concepts</span></span>
<span class="line"><span style="color: #6272A4">    - en_ner_bc5cdr_md: Distinguishes DISEASE and CHEMICAL entities</span></span>
<span class="line"><span style="color: #6272A4">    - en_ner_jnlpba_md: Tags GENE, PROTEIN, RNA, DNA, CELL_TYPE, CELL_LINE</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    For general clinical use, en_core_sci_md with UMLS linking provides</span></span>
<span class="line"><span style="color: #6272A4">    the most complete semantic annotation.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Args:</span></span>
<span class="line"><span style="color: #6272A4">        text: Clinical text</span></span>
<span class="line"><span style="color: #6272A4">        nlp: Loaded scispaCy model</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        Dictionary grouping entities by their labels</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    doc </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp(text)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    entities_by_label: Dict[</span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, List[</span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">]] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> ent </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> doc.ents:</span></span>
<span class="line"><span style="color: #F8F8F2">        label </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> ent.label_</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> label </span><span style="color: #FF79C6">not</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> entities_by_label:</span></span>
<span class="line"><span style="color: #F8F8F2">            entities_by_label[label] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> []</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #6272A4"># Deduplicate while preserving order</span></span>
<span class="line"><span style="color: #F8F8F2">        entity_text </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> ent.text.strip()</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> entity_text </span><span style="color: #FF79C6">and</span><span style="color: #F8F8F2"> entity_text </span><span style="color: #FF79C6">not</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> entities_by_label[label]:</span></span>
<span class="line"><span style="color: #F8F8F2">            entities_by_label[label].append(entity_text)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> entities_by_label</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Extract entities from the discharge summary</span></span>
<span class="line"><span style="color: #F8F8F2">entities </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> extract_biomedical_entities(</span><span style="color: #BD93F9">DISCHARGE_SUMMARY</span><span style="color: #F8F8F2">, nlp_sci)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Biomedical Entities Extracted by scispaCy:</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">=</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">60</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> label, ents </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">sorted</span><span style="color: #F8F8F2">(entities.items()):</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"</span><span style="color: #FF79C6">\\</span><span style="color: #F1FA8C">n[</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">label</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">] (</span><span style="color: #BD93F9">{</span><span style="color: #8BE9FD">len</span><span style="color: #F8F8F2">(ents)</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C"> entities):"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> entity </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> ents[</span><span style="color: #FF79C6">:</span><span style="color: #BD93F9">15</span><span style="color: #F8F8F2">]:  </span><span style="color: #6272A4"># Show first 15 per category</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  -</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">entity</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
```

### 3.3 Specialized NER Models: BC5CDR for Diseases and Chemicals

For clinical NLP requiring a precise distinction between disease entities and chemical/drug entities, the `en_ner_bc5cdr_md` scispaCy model trained on the BioCreative V CDR corpus is the appropriate choice. The model was trained to distinguish:

- **DISEASE**: Pathological conditions (“atrial fibrillation,” “coronary artery disease,” “hypertension”)
- **CHEMICAL**: Drugs and chemical compounds (“amiodarone,” “aspirin,” “atorvastatin”)

# Note: requires installation of en_ner_bc5cdr_md
# pip install <https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_ner_bc5cdr_md-0.5.4.tar.gz>

def extract_diseases_and_chemicals(text: str) -> Dict[str, List[str]]:
    """
    Use the BC5CDR model to separately identify diseases and drugs.

    The BC5CDR model was trained on 1,500 PubMed articles with
    expert annotations of disease and chemical mentions. It achieves
    F1 scores >85% on the BC5CDR test set, making it suitable for
    clinical applications where chemical-disease distinction matters.

    Clinical application: Automatically extracting the complication
    profile and pharmacological treatment from discharge summaries
    for pharmacovigilance databases.

    Args:
        text: Clinical document text

    Returns:
        Dictionary with 'DISEASE' and 'CHEMICAL' entity lists
    """
    try:
        nlp_cdr = spacy.load("en_ner_bc5cdr_md")
    except OSError:
        print("Model en_ner_bc5cdr_md not installed. Using en_core_sci_md instead.")
        return extract_biomedical_entities(text, spacy.load("en_core_sci_md"))

    doc = nlp_cdr(text)

    result = {"DISEASE": [], "CHEMICAL": []}

    for ent in doc.ents:
        entity_text = ent.text.strip().lower()
        label = ent.label_
        if label in result and entity_text not in result[label]:
            result[label].append(ent.text.strip())

    return result

# Test on our discharge summary
entities_bc5cdr = extract_diseases_and_chemicals(DISCHARGE_SUMMARY)

print("Diseases detected:")
for disease in entities_bc5cdr.get("DISEASE", []):
    print(f"  ✓{disease}")

print("\\nChemicals/Drugs detected:")
for chemical in entities_bc5cdr.get("CHEMICAL", []):
    print(f"  ✓{chemical}")
```
<span class="line"><span style="color: #6272A4"># Note: requires installation of en_ner_bc5cdr_md</span></span>
<span class="line"><span style="color: #6272A4"># pip install <https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_ner_bc5cdr_md-0.5.4.tar.gz></span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">extract_diseases_and_chemicals</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">text</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> Dict[</span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, List[</span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">]]:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Use the BC5CDR model to separately identify diseases and drugs.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    The BC5CDR model was trained on 1,500 PubMed articles with</span></span>
<span class="line"><span style="color: #6272A4">    expert annotations of disease and chemical mentions. It achieves</span></span>
<span class="line"><span style="color: #6272A4">    F1 scores >85% on the BC5CDR test set, making it suitable for</span></span>
<span class="line"><span style="color: #6272A4">    clinical applications where chemical-disease distinction matters.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Clinical application: Automatically extracting the complication</span></span>
<span class="line"><span style="color: #6272A4">    profile and pharmacological treatment from discharge summaries</span></span>
<span class="line"><span style="color: #6272A4">    for pharmacovigilance databases.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Args:</span></span>
<span class="line"><span style="color: #6272A4">        text: Clinical document text</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        Dictionary with 'DISEASE' and 'CHEMICAL' entity lists</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">try</span><span style="color: #F8F8F2">:</span></span>
<span class="line"><span style="color: #F8F8F2">        nlp_cdr </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> spacy.load(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">en_ner_bc5cdr_md</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">except</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">OSError</span><span style="color: #F8F8F2">:</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Model en_ner_bc5cdr_md not installed. Using en_core_sci_md instead.</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> extract_biomedical_entities(text, spacy.load(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">en_core_sci_md</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">))</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    doc </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp_cdr(text)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    result </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">DISEASE</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: [], </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">CHEMICAL</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: []}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> ent </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> doc.ents:</span></span>
<span class="line"><span style="color: #F8F8F2">        entity_text </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> ent.text.strip().lower()</span></span>
<span class="line"><span style="color: #F8F8F2">        label </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> ent.label_</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> label </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> result </span><span style="color: #FF79C6">and</span><span style="color: #F8F8F2"> entity_text </span><span style="color: #FF79C6">not</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> result[label]:</span></span>
<span class="line"><span style="color: #F8F8F2">            result[label].append(ent.text.strip())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> result</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Test on our discharge summary</span></span>
<span class="line"><span style="color: #F8F8F2">entities_bc5cdr </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> extract_diseases_and_chemicals(</span><span style="color: #BD93F9">DISCHARGE_SUMMARY</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Diseases detected:</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> disease </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> entities_bc5cdr.get(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">DISEASE</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">, []):</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  ✓</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">disease</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #FF79C6">\\</span><span style="color: #F1FA8C">nChemicals/Drugs detected:</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> chemical </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> entities_bc5cdr.get(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">CHEMICAL</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">, []):</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  ✓</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">chemical</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
```

### 3.4 Abbreviation Detection and Expansion

Medical text is dense with abbreviations, a major source of ambiguity for NLP systems. “AF” in a cardiology report almost certainly means atrial fibrillation; in a different context, it might mean acid-fast, amniotic fluid, or audio frequency. scispaCy provides an `AbbreviationDetector` component that identifies abbreviation-definition pairs within the same document, enabling in-context disambiguation.

import spacy
import scispacy
from scispacy.abbreviation import AbbreviationDetector

def setup_abbreviation_detector(model_name: str = "en_core_sci_md") -> spacy.language.Language:
    """
    Configure scispaCy pipeline with abbreviation detection.

    The AbbreviationDetector implements the algorithm from Schwartz & Hearst (2003),
    which identifies abbreviation-definition pairs based on character matching
    heuristics. When a document defines "CABG" as "Coronary Artery Bypass Grafting",
    the detector enables automatic expansion of all subsequent CABG mentions.

    Returns:
        spaCy language model with abbreviation detection configured
    """
    nlp = spacy.load(model_name)

    # Add abbreviation detector to the pipeline
    nlp.add_pipe("abbreviation_detector")

    return nlp

def extract_abbreviations(text: str, nlp) -> Dict[str, str]:
    """
    Extract and map abbreviations found in clinical text.

    Medical abbreviations are a significant challenge for NLP systems:
    the same abbreviation can have different meanings in different
    specialties or even different sections of the same document.

    This function identifies abbreviation-definition pairs where
    both the abbreviation and its expansion appear in the same document.

    Args:
        text: Clinical document text
        nlp: scispaCy model with AbbreviationDetector

    Returns:
        Dictionary mapping abbreviation → full form
    """
    doc = nlp(text)
    abbreviation_map = {}

    for abbreviation in doc._.abbreviations:
        abbr_text = abbreviation.text
        # The long form is the identified full expansion
        long_form = abbreviation._.long_form.text if abbreviation._.long_form else "Unknown"
        abbreviation_map[abbr_text] = long_form

    return abbreviation_map

# Note: Our discharge summary uses abbreviations but may not always define them inline
# Let's create a version with explicit definitions to demonstrate the capability
annotated_summary = """
The patient underwent Coronary Artery Bypass Grafting (CABG) using Left Internal
Mammary Artery (LIMA) to Left Anterior Descending (LAD) and Saphenous Vein Graft (SVG)
to Obtuse Marginal (OM1) and Right Coronary Artery (RCA).
Cardiopulmonary Bypass (CPB) time was 98 minutes.
Postoperative atrial fibrillation (AF) was noted and treated with amiodarone.
Left Ventricular Ejection Fraction (LVEF) improved from 35% to 40%.
"""

nlp_with_abbrev = setup_abbreviation_detector()
abbreviations = extract_abbreviations(annotated_summary, nlp_with_abbrev)

print("Abbreviations detected and expanded:")
for abbr, expansion in sorted(abbreviations.items()):
    print(f"{abbr} →{expansion}")
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> spacy</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> scispacy</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> scispacy.abbreviation </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> AbbreviationDetector</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">setup_abbreviation_detector</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">model_name</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">en_core_sci_md</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> spacy.language.Language:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Configure scispaCy pipeline with abbreviation detection.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    The AbbreviationDetector implements the algorithm from Schwartz & Hearst (2003),</span></span>
<span class="line"><span style="color: #6272A4">    which identifies abbreviation-definition pairs based on character matching</span></span>
<span class="line"><span style="color: #6272A4">    heuristics. When a document defines "CABG" as "Coronary Artery Bypass Grafting",</span></span>
<span class="line"><span style="color: #6272A4">    the detector enables automatic expansion of all subsequent CABG mentions.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        spaCy language model with abbreviation detection configured</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    nlp </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> spacy.load(model_name)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Add abbreviation detector to the pipeline</span></span>
<span class="line"><span style="color: #F8F8F2">    nlp.add_pipe(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">abbreviation_detector</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> nlp</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">extract_abbreviations</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">text</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">nlp</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> Dict[</span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">]:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Extract and map abbreviations found in clinical text.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Medical abbreviations are a significant challenge for NLP systems:</span></span>
<span class="line"><span style="color: #6272A4">    the same abbreviation can have different meanings in different</span></span>
<span class="line"><span style="color: #6272A4">    specialties or even different sections of the same document.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    This function identifies abbreviation-definition pairs where</span></span>
<span class="line"><span style="color: #6272A4">    both the abbreviation and its expansion appear in the same document.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Args:</span></span>
<span class="line"><span style="color: #6272A4">        text: Clinical document text</span></span>
<span class="line"><span style="color: #6272A4">        nlp: scispaCy model with AbbreviationDetector</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        Dictionary mapping abbreviation → full form</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    doc </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp(text)</span></span>
<span class="line"><span style="color: #F8F8F2">    abbreviation_map </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> abbreviation </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> doc._.abbreviations:</span></span>
<span class="line"><span style="color: #F8F8F2">        abbr_text </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> abbreviation.text</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #6272A4"># The long form is the identified full expansion</span></span>
<span class="line"><span style="color: #F8F8F2">        long_form </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> abbreviation._.long_form.text </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> abbreviation._.long_form </span><span style="color: #FF79C6">else</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Unknown</span><span style="color: #E9F284">"</span></span>
<span class="line"><span style="color: #F8F8F2">        abbreviation_map[abbr_text] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> long_form</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> abbreviation_map</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Note: Our discharge summary uses abbreviations but may not always define them inline</span></span>
<span class="line"><span style="color: #6272A4"># Let's create a version with explicit definitions to demonstrate the capability</span></span>
<span class="line"><span style="color: #F8F8F2">annotated_summary </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"""</span></span>
<span class="line"><span style="color: #F1FA8C">The patient underwent Coronary Artery Bypass Grafting (CABG) using Left Internal</span></span>
<span class="line"><span style="color: #F1FA8C">Mammary Artery (LIMA) to Left Anterior Descending (LAD) and Saphenous Vein Graft (SVG)</span></span>
<span class="line"><span style="color: #F1FA8C">to Obtuse Marginal (OM1) and Right Coronary Artery (RCA).</span></span>
<span class="line"><span style="color: #F1FA8C">Cardiopulmonary Bypass (CPB) time was 98 minutes.</span></span>
<span class="line"><span style="color: #F1FA8C">Postoperative atrial fibrillation (AF) was noted and treated with amiodarone.</span></span>
<span class="line"><span style="color: #F1FA8C">Left Ventricular Ejection Fraction (LVEF) improved from 35% to 40%.</span></span>
<span class="line"><span style="color: #E9F284">"""</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">nlp_with_abbrev </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> setup_abbreviation_detector()</span></span>
<span class="line"><span style="color: #F8F8F2">abbreviations </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> extract_abbreviations(annotated_summary, nlp_with_abbrev)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Abbreviations detected and expanded:</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> abbr, expansion </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">sorted</span><span style="color: #F8F8F2">(abbreviations.items()):</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">abbr</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C"> →</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">expansion</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
```

**Expected output:**


```
Abbreviations detected and expanded:
  AF → atrial fibrillation
  CABG → Coronary Artery Bypass Grafting
  CPB → Cardiopulmonary Bypass
  LAD → Left Anterior Descending
  LIMA → Left Internal Mammary Artery
  LVEF → Left Ventricular Ejection Fraction
  OM1 → Obtuse Marginal
  RCA → Right Coronary Artery
  SVG → Saphenous Vein Graft
```

A caveat from clinical practice: the Schwartz & Hearst algorithm only catches abbreviations that are _explicitly defined inside the same document_. Most operative reports we read in our daily work assume the reader already knows what CABG, LIMA or LVEF mean and never spell them out. For real deployment, this detector almost always needs to be paired with a curated cardiac-surgery glossary as a fallback.

---

## 4. UMLS Entity Linking: Connecting to Medical Ontologies

### 4.1 The Importance of Standardized Medical Vocabulary

Named entity recognition tells us _that_ a text span refers to a medical concept; UMLS linking tells us _which_ standardized concept it refers to. The distinction matters for interoperability.

Consider: “coronary artery disease,” “CAD,” “ischemic heart disease,” and “arteriosclerotic heart disease” are all strings pointing to the same underlying condition. In the Unified Medical Language System (UMLS), this concept has a single Concept Unique Identifier: **C0010068**. By linking extracted entities to UMLS CUIs we get:

1. **Semantic normalization**: All equivalent surface forms map to the same concept
2. **Cross-database interoperability**: UMLS bridges SNOMED-CT, ICD-10, MeSH, RxNorm, and thousands of other vocabularies
3. **Knowledge graph integration**: CUIs can be used to query external medical knowledge bases
4. **Research reproducibility**: Studies using CUI-based cohort definitions are unambiguous

The UMLS Metathesaurus is maintained by the U.S. National Library of Medicine and covers over 3.5 million concepts across more than 200 biomedical vocabularies. Key semantic type hierarchies relevant to cardiac surgery include:

- **T047**, Disease or Syndrome (coronary artery disease, atrial fibrillation)
- **T121**, Pharmacologic Substance (amiodarone, aspirin)
- **T061**, Therapeutic or Preventive Procedure (CABG, cardioversion)
- **T023**, Body Part, Organ, or Organ Component (left ventricle, coronary artery)

### 4.2 Implementing UMLS Entity Linking with scispaCy

import spacy
import scispacy
from scispacy.linking import EntityLinker

def setup_umls_pipeline(model_name: str = "en_core_sci_md") -> spacy.language.Language:
    """
    Configure scispaCy pipeline with UMLS entity linking.

    The EntityLinker component compares extracted entity spans against
    a local copy of the UMLS knowledge base using approximate string
    matching and concept embeddings. It returns ranked candidate CUIs
    with confidence scores.

    IMPORTANT: The UMLS linker requires ~2GB RAM for the knowledge base
    and has a one-time download of ~1.5GB. Performance is substantially
    better with en_core_sci_lg than with the smaller models.

    Configuration parameters:
    - resolve_abbreviations: Use AbbreviationDetector before linking
    - linker_name: "umls" uses full UMLS; "mesh" uses MeSH only (smaller)
    - threshold: Minimum similarity score (0.7-0.85 recommended for clinical use)
    - max_entities_per_mention: Candidates per entity span

    Returns:
        Configured spaCy pipeline with UMLS linking
    """
    nlp = spacy.load(model_name)

    # Add abbreviation detection (must come before linker)
    nlp.add_pipe("abbreviation_detector")

    # Add UMLS entity linker
    nlp.add_pipe(
        "scispacy_linker",
        config={
            "resolve_abbreviations": True,       # Expand abbreviations before linking
            "linker_name": "umls",               # Full UMLS knowledge base
            "threshold": 0.80,                   # Minimum similarity threshold
            "max_entities_per_mention": 3,       # Top-k candidates
        }
    )

    return nlp

def extract_umls_entities(text: str, nlp) -> List[Dict]:
    """
    Extract entities with full UMLS annotations.

    For each recognized biomedical entity, this function retrieves:
    - The entity text as it appears in the document
    - The UMLS Concept Unique Identifier (CUI)
    - The canonical (preferred) name for the concept
    - The UMLS semantic type (T047 = Disease, T121 = Drug, etc.)
    - The similarity score (confidence of the linking)
    - The definition from UMLS (if available)

    Args:
        text: Clinical document text
        nlp: Pipeline with UMLS linker configured

    Returns:
        List of entities with complete UMLS annotations
    """
    doc = nlp(text)

    # Access the linker component for knowledge base queries
    linker = nlp.get_pipe("scispacy_linker")

    annotated_entities = []

    for ent in doc.ents:
        entity_info = {
            'text': ent.text,
            'start_char': ent.start_char,
            'end_char': ent.end_char,
            'label': ent.label_,
            'umls_candidates': []
        }

        # Get UMLS linking results
        for umls_entity in ent._.kb_ents:
            cui = umls_entity[0]         # UMLS Concept Unique Identifier
            score = umls_entity[1]       # Similarity score (0-1)

            # Query the knowledge base for this CUI
            if cui in linker.kb.cui_to_entity:
                kb_entity = linker.kb.cui_to_entity[cui]
                candidate = {
                    'cui': cui,
                    'canonical_name': kb_entity.canonical_name,
                    'aliases': list(kb_entity.aliases[:5]),        # First 5 aliases
                    'types': list(kb_entity.types),                # Semantic type codes
                    'definition': kb_entity.definition or "No definition available",
                    'similarity_score': round(score, 4)
                }
                entity_info['umls_candidates'].append(candidate)

        if entity_info['umls_candidates']:
            annotated_entities.append(entity_info)

    return annotated_entities

def format_umls_report(entities: List[Dict]) -> str:
    """
    Format UMLS entity linking results as a readable clinical report.

    Args:
        entities: Output from extract_umls_entities()

    Returns:
        Formatted string report
    """
    lines = ["UMLS Entity Linking Report", "=" * 60]

    # Semantic type descriptions for readability
    SEMANTIC_TYPES = {
        "T047": "Disease or Syndrome",
        "T121": "Pharmacologic Substance",
        "T061": "Therapeutic/Preventive Procedure",
        "T023": "Body Part, Organ, Component",
        "T116": "Amino Acid/Peptide/Protein",
        "T048": "Mental/Behavioral Dysfunction",
        "T033": "Finding",
        "T184": "Sign or Symptom",
        "T060": "Diagnostic Procedure",
    }

    for entity in entities:
        lines.append(f"\\nEntity: '{entity['text']}'")
        lines.append(f"  Character span: [{entity['start_char']}:{entity['end_char']}]")

        if entity['umls_candidates']:
            best = entity['umls_candidates'][0]  # Top candidate
            lines.append(f"  Best UMLS match:")
            lines.append(f"    CUI:{best['cui']}")
            lines.append(f"    Canonical name:{best['canonical_name']}")
            lines.append(f"    Similarity:{best['similarity_score']:.2%}")

            # Describe semantic types
            type_descriptions = [
                SEMANTIC_TYPES.get(t, t) for t in best['types']
            ]
            lines.append(f"    Semantic types:{', '.join(type_descriptions)}")

            if best['aliases']:
                lines.append(f"    Known aliases:{', '.join(best['aliases'][:3])}")

    return "\\n".join(lines)

# Example usage
# Note: requires ~2GB RAM for UMLS knowledge base
# nlp_umls = setup_umls_pipeline()
# entities = extract_umls_entities(DISCHARGE_SUMMARY, nlp_umls)
# print(format_umls_report(entities))
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> spacy</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> scispacy</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> scispacy.linking </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> EntityLinker</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">setup_umls_pipeline</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">model_name</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">en_core_sci_md</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> spacy.language.Language:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Configure scispaCy pipeline with UMLS entity linking.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    The EntityLinker component compares extracted entity spans against</span></span>
<span class="line"><span style="color: #6272A4">    a local copy of the UMLS knowledge base using approximate string</span></span>
<span class="line"><span style="color: #6272A4">    matching and concept embeddings. It returns ranked candidate CUIs</span></span>
<span class="line"><span style="color: #6272A4">    with confidence scores.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    IMPORTANT: The UMLS linker requires ~2GB RAM for the knowledge base</span></span>
<span class="line"><span style="color: #6272A4">    and has a one-time download of ~1.5GB. Performance is substantially</span></span>
<span class="line"><span style="color: #6272A4">    better with en_core_sci_lg than with the smaller models.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Configuration parameters:</span></span>
<span class="line"><span style="color: #6272A4">    - resolve_abbreviations: Use AbbreviationDetector before linking</span></span>
<span class="line"><span style="color: #6272A4">    - linker_name: "umls" uses full UMLS; "mesh" uses MeSH only (smaller)</span></span>
<span class="line"><span style="color: #6272A4">    - threshold: Minimum similarity score (0.7-0.85 recommended for clinical use)</span></span>
<span class="line"><span style="color: #6272A4">    - max_entities_per_mention: Candidates per entity span</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        Configured spaCy pipeline with UMLS linking</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    nlp </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> spacy.load(model_name)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Add abbreviation detection (must come before linker)</span></span>
<span class="line"><span style="color: #F8F8F2">    nlp.add_pipe(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">abbreviation_detector</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Add UMLS entity linker</span></span>
<span class="line"><span style="color: #F8F8F2">    nlp.add_pipe(</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">scispacy_linker</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FFB86C; font-style: italic">config</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">{</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">resolve_abbreviations</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">,       </span><span style="color: #6272A4"># Expand abbreviations before linking</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">linker_name</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">umls</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,               </span><span style="color: #6272A4"># Full UMLS knowledge base</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">threshold</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">0.80</span><span style="color: #F8F8F2">,                   </span><span style="color: #6272A4"># Minimum similarity threshold</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">max_entities_per_mention</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">3</span><span style="color: #F8F8F2">,       </span><span style="color: #6272A4"># Top-k candidates</span></span>
<span class="line"><span style="color: #F8F8F2">        }</span></span>
<span class="line"><span style="color: #F8F8F2">    )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> nlp</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">extract_umls_entities</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">text</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">nlp</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> List[Dict]:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Extract entities with full UMLS annotations.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    For each recognized biomedical entity, this function retrieves:</span></span>
<span class="line"><span style="color: #6272A4">    - The entity text as it appears in the document</span></span>
<span class="line"><span style="color: #6272A4">    - The UMLS Concept Unique Identifier (CUI)</span></span>
<span class="line"><span style="color: #6272A4">    - The canonical (preferred) name for the concept</span></span>
<span class="line"><span style="color: #6272A4">    - The UMLS semantic type (T047 = Disease, T121 = Drug, etc.)</span></span>
<span class="line"><span style="color: #6272A4">    - The similarity score (confidence of the linking)</span></span>
<span class="line"><span style="color: #6272A4">    - The definition from UMLS (if available)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Args:</span></span>
<span class="line"><span style="color: #6272A4">        text: Clinical document text</span></span>
<span class="line"><span style="color: #6272A4">        nlp: Pipeline with UMLS linker configured</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        List of entities with complete UMLS annotations</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    doc </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp(text)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Access the linker component for knowledge base queries</span></span>
<span class="line"><span style="color: #F8F8F2">    linker </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> nlp.get_pipe(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">scispacy_linker</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    annotated_entities </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> []</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> ent </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> doc.ents:</span></span>
<span class="line"><span style="color: #F8F8F2">        entity_info </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: ent.text,</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">start_char</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: ent.start_char,</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">end_char</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: ent.end_char,</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">label</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: ent.label_,</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">umls_candidates</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: []</span></span>
<span class="line"><span style="color: #F8F8F2">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #6272A4"># Get UMLS linking results</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> umls_entity </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> ent._.kb_ents:</span></span>
<span class="line"><span style="color: #F8F8F2">            cui </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> umls_entity[</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">]         </span><span style="color: #6272A4"># UMLS Concept Unique Identifier</span></span>
<span class="line"><span style="color: #F8F8F2">            score </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> umls_entity[</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">]       </span><span style="color: #6272A4"># Similarity score (0-1)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #6272A4"># Query the knowledge base for this CUI</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> cui </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> linker.kb.cui_to_entity:</span></span>
<span class="line"><span style="color: #F8F8F2">                kb_entity </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> linker.kb.cui_to_entity[cui]</span></span>
<span class="line"><span style="color: #F8F8F2">                candidate </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">                    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">cui</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: cui,</span></span>
<span class="line"><span style="color: #F8F8F2">                    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">canonical_name</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: kb_entity.canonical_name,</span></span>
<span class="line"><span style="color: #F8F8F2">                    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">aliases</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #8BE9FD; font-style: italic">list</span><span style="color: #F8F8F2">(kb_entity.aliases[</span><span style="color: #FF79C6">:</span><span style="color: #BD93F9">5</span><span style="color: #F8F8F2">]),        </span><span style="color: #6272A4"># First 5 aliases</span></span>
<span class="line"><span style="color: #F8F8F2">                    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">types</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #8BE9FD; font-style: italic">list</span><span style="color: #F8F8F2">(kb_entity.types),                </span><span style="color: #6272A4"># Semantic type codes</span></span>
<span class="line"><span style="color: #F8F8F2">                    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">definition</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: kb_entity.definition </span><span style="color: #FF79C6">or</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">No definition available</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">                    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">similarity_score</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #8BE9FD">round</span><span style="color: #F8F8F2">(score, </span><span style="color: #BD93F9">4</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">                }</span></span>
<span class="line"><span style="color: #F8F8F2">                entity_info[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">umls_candidates</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">].append(candidate)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> entity_info[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">umls_candidates</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]:</span></span>
<span class="line"><span style="color: #F8F8F2">            annotated_entities.append(entity_info)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> annotated_entities</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">format_umls_report</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">entities</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> List[Dict]) </span><span style="color: #FF79C6">-></span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">str</span><span style="color: #F8F8F2">:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""</span></span>
<span class="line"><span style="color: #6272A4">    Format UMLS entity linking results as a readable clinical report.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Args:</span></span>
<span class="line"><span style="color: #6272A4">        entities: Output from extract_umls_entities()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">    Returns:</span></span>
<span class="line"><span style="color: #6272A4">        Formatted string report</span></span>
<span class="line"><span style="color: #6272A4">    """</span></span>
<span class="line"><span style="color: #F8F8F2">    lines </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> [</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">UMLS Entity Linking Report</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">=</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">60</span><span style="color: #F8F8F2">]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Semantic type descriptions for readability</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #BD93F9">SEMANTIC_TYPES</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">T047</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Disease or Syndrome</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">T121</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Pharmacologic Substance</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">T061</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Therapeutic/Preventive Procedure</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">T023</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Body Part, Organ, Component</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">T116</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Amino Acid/Peptide/Protein</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">T048</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Mental/Behavioral Dysfunction</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">T033</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Finding</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">T184</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Sign or Symptom</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">T060</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Diagnostic Procedure</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> entity </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> entities:</span></span>
<span class="line"><span style="color: #F8F8F2">        lines.append(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"</span><span style="color: #FF79C6">\\</span><span style="color: #F1FA8C">nEntity: '</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">entity[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">'"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">        lines.append(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  Character span: [</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">entity[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">start_char</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">entity[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">end_char</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">]"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> entity[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">umls_candidates</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]:</span></span>
<span class="line"><span style="color: #F8F8F2">            best </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> entity[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">umls_candidates</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">][</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">]  </span><span style="color: #6272A4"># Top candidate</span></span>
<span class="line"><span style="color: #F8F8F2">            lines.append(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"  Best UMLS match:"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">            lines.append(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"    CUI:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">best[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">cui</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">            lines.append(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"    Canonical name:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">best[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">canonical_name</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">            lines.append(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"    Similarity:</span><span style="color: #BD93F9">{</span><span style="color: #F8F8F2">best[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">similarity_score</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span><span style="color: #FF79C6">:.2%</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #6272A4"># Describe semantic types</span></span>
<span class="line"><span style="color: #F8F8F2">            type_descriptions </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> [</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #BD93F9">SEMANTIC_TYPES</span><span style="color: #F8F8F2">.get(t, t) </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> t </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> best[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">types</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">            ]</span></span>
<span class="line"><span style="color: #F8F8F2">            lines.append(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"    Semantic types:</span><span style="color: #BD93F9">{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">, </span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">.join(type_descriptions)</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> best[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">aliases</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]:</span></span>
<span class="line"><span style="color: #F8F8F2">                lines.append(</span><span style="color: #FF79C6">f</span><span style="color: #F1FA8C">"    Known aliases:</span><span style="color: #BD93F9">{</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">, </span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">.join(best[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">aliases</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">][:</span><span style="color: #BD93F9">3</span><span style="color: #F8F8F2">])</span><span style="color: #BD93F9">}</span><span style="color: #F1FA8C">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #FF79C6">\\</span><span style="color: #F1FA8C">n</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">.join(lines)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Example usage</span></span>
<span class="line"><span style="color: #6272A4"># Note: requires ~2GB RAM for UMLS knowledge base</span></span>
<span class="line"><span style="color: #6272A4"># nlp_umls = setup_umls_pipeline()</span></span>
<span class="line"><span style="color: #6272A4"># entities = extract_umls_entities(DISCHARGE_SUMMARY, nlp_umls)</span></span>
<span class="line"><span style="color: #6272A4"># print(format_umls_report(entities))</span></span>
<span class="line"></span>
```

**Expected output for key entities** (illustrative, actual CUIs from UMLS 2024AB):

UMLS Entity Linking Report

Entity: ‘coronary artery disease’
Character span: \[150:172\]
Best UMLS match:
CUI: C0010068
Canonical name: Coronary Arteriosclerosis
Similarity: 96.80%
Semantic types: Disease or Syndrome
Known aliases: CAD, Ischemic Heart Disease, Coronary Artery Disease

Entity: ‘atrial fibrillation’
Character span: \[478:496\]
Best UMLS match:
CUI: C0004238
Canonical name: Atrial Fibrillation
Similarity: 99.10%
Semantic types: Disease or Syndrome, Finding
Known aliases: AF, A-fib, Auricular Fibrillation

Entity: ‘amiodarone’
Character span: \[521:531\]
Best UMLS match:
CUI: C0002598
Canonical name: Amiodarone
Similarity: 99.90%
Semantic types: Pharmacologic Substance
Known aliases: Cordarone, Nexterone, Pacerone

Entity: ‘median sternotomy’
Best UMLS match:
CUI: C0185792
Canonical name: Median Sternotomy
Similarity: 98.50%
Semantic types: Therapeutic/Preventive Procedure

---

## 5. Temporal Information Extraction

### 5.1 The Clinical Importance of Temporal Relations

In cardiac surgery, the timing of events carries weight comparable to the events themselves. “Atrial fibrillation on POD 3” and “atrial fibrillation on POD 14” may imply different etiologies, different risks, and different management approaches. A complete NLP pipeline therefore needs to capture not only _what_ happened, but _when_.

spaCy’s dependency parser is well suited to extracting temporal relationships:

def extract_temporal_clinical_events(text: str, nlp) -> List[Dict]:
    """
    Extract clinical events with their temporal anchors.

    Uses dependency parsing to identify relationships between
    clinical events and their associated postoperative day (POD),
    time expressions, or date references.

    Temporal patterns in cardiac surgery discharge summaries:
    - Explicit POD references: "extubated on POD 1"
    - Implicit timing: "chest tubes removed subsequently"
    - Duration: "amiodarone for 6 weeks"
    - Relative timing: "48 hours after surgery"

    Args:
        text: Clinical narrative text
        nlp: scispaCy model

    Returns:
        List of events with temporal information
    """
    doc = nlp(text)

    # Pattern for "POD N", postoperative day
    pod_pattern = re.compile(r'POD\\s*(\\d+)', re.IGNORECASE)

    events_with_timing = []

    for sent in doc.sents:
        sent_text = sent.text

        # Find POD references in this sentence
        pod_matches = pod_pattern.findall(sent_text)
        pod_days = [int(d) for d in pod_matches]

        # Find clinical events (non-stop verbs and nouns)
        clinical_tokens = []
        for token in sent:
            if (token.pos_ in ("VERB", "NOUN", "PROPN") and
                not token.is_stop and
                len(token.text) > 2):
                clinical_tokens.append(token.lemma_)

        if pod_days and clinical_tokens:
            events_with_timing.append({
                'sentence': sent_text.strip(),
                'pod_days': pod_days,
                'earliest_pod': min(pod_days),
                'clinical_tokens': clinical_tokens[:8],  # Top 8 content words
            })

    # Sort by earliest POD
    events_with_timing.sort(key=lambda x: x['earliest_pod'])

    return events_with_timing

# Build clinical timeline from the postoperative course
postop_text = """
The patient was extubated on POD 1 without complications. Chest tubes were removed on POD 2.
Postoperative atrial fibrillation was noted on POD 3, successfully cardioverted with amiodarone
loading followed by maintenance dose. Patient remained in sinus rhythm thereafter.
Echocardiography on POD 5 showed LVEF 40% with good biventricular function.
"""

timeline = extract_temporal_clinical_events(postop_text, nlp_sci)

print("Clinical Timeline Reconstruction:")
print("=" * 60)
for event in timeline:
    print(f"\\n  POD{event['earliest_pod']}:")
    print(f"{event['sentence']}")
```
<span class="line"><span style="color: #50FA7B">def</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">extract_temporal_clinical_events</span><span style="color: #F8F8F2">(</span><span style="color: #50FA7B">text:</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">str,</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">nlp</span><span style="color: #F8F8F2">) -</span><span style="color: #FF79C6">></span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">List[Dict]:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #50FA7B">"""</span></span>
<span class="line"><span style="color: #50FA7B">    Extract clinical events with their temporal anchors.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #50FA7B">    Uses dependency parsing to identify relationships between</span></span>
<span class="line"><span style="color: #50FA7B">    clinical events and their associated postoperative day (POD),</span></span>
<span class="line"><span style="color: #50FA7B">    time expressions, or date references.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #50FA7B">    Temporal patterns in cardiac surgery discharge summaries:</span></span>
<span class="line"><span style="color: #50FA7B">    - Explicit POD references: "</span><span style="color: #50FA7B">extubated</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">on</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">POD</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #E9F284">"</span></span>
<span class="line"><span style="color: #F1FA8C">    - Implicit timing: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">chest</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">tubes</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">removed</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">subsequently</span><span style="color: #E9F284">"</span></span>
<span class="line"><span style="color: #F1FA8C">    - Duration: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">amiodarone</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">for</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">6</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">weeks</span><span style="color: #E9F284">"</span></span>
<span class="line"><span style="color: #F1FA8C">    - Relative timing: </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">48</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">hours</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">after</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">surgery</span><span style="color: #E9F284">"</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">    Args:</span></span>
<span class="line"><span style="color: #F1FA8C">        text: Clinical narrative text</span></span>
<span class="line"><span style="color: #F1FA8C">        nlp: scispaCy model</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F1FA8C">    Returns:</span></span>
<span class="line"><span style="color: #F1FA8C">        List of events with temporal information</span></span>
<span class="line"><span style="color: #F1FA8C">    </span><span style="color: #E9F284">"""</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #50FA7B">doc</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">=</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">nlp</span><span style="color: #F8F8F2">(</span><span style="color: #50FA7B">text</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Pattern for "POD N", postoperative day</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #50FA7B">pod_pattern</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">=</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">re.compile</span><span style="color: #F8F8F2">(</span><span style="color: #50FA7B">r</span><span style="color: #50FA7B">'POD\\s*(\\d+)'</span><span style="color: #50FA7B">,</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">re.IGNORECASE</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #50FA7B">events_with_timing</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">=</span><span style="color: #F8F8F2"> []</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">sent</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> doc.sents:</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #50FA7B">sent_text</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">=</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">sent.text</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #6272A4"># Find POD references in this sentence</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #50FA7B">pod_matches</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">=</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">pod_pattern.findall</span><span style="color: #F8F8F2">(</span><span style="color: #50FA7B">sent_text</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #50FA7B">pod_days</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">=</span><span style="color: #F8F8F2"> [int(d) </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">d</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> pod_matches]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #6272A4"># Find clinical events (non-stop verbs and nouns)</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #50FA7B">clinical_tokens</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">=</span><span style="color: #F8F8F2"> []</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">token</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> sent:</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> (</span><span style="color: #50FA7B">token.pos_</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">in</span><span style="color: #F8F8F2"> (</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">VERB</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">NOUN</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">,</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">PROPN</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">) and</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #50FA7B">not</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">token.is_stop</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">and</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #50FA7B">len(token.text</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">></span><span style="color: #F8F8F2"> 2):</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #50FA7B">clinical_tokens.append(token.lemma_</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">pod_days</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">and</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">clinical_tokens:</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #50FA7B">events_with_timing.append(</span><span style="color: #F8F8F2">{</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #50FA7B">'sentence'</span><span style="color: #8BE9FD">:</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">sent_text.strip</span><span style="color: #F8F8F2">()</span><span style="color: #F1FA8C">,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #50FA7B">'pod_days'</span><span style="color: #8BE9FD">:</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">pod_days,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #50FA7B">'earliest_pod'</span><span style="color: #8BE9FD">:</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">min</span><span style="color: #F8F8F2">(</span><span style="color: #50FA7B">pod_days</span><span style="color: #F8F8F2">)</span><span style="color: #F1FA8C">,</span></span>
<span class="line"><span style="color: #F8F8F2">                </span><span style="color: #50FA7B">'clinical_tokens'</span><span style="color: #8BE9FD">:</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">clinical_tokens[:8],</span><span style="color: #F8F8F2">  </span><span style="color: #6272A4"># Top 8 content words</span></span>
<span class="line"><span style="color: #F8F8F2">            })</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Sort by earliest POD</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #50FA7B">events_with_timing.sort(key</span><span style="color: #F8F8F2">=lambda </span><span style="color: #F1FA8C">x:</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">x[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">earliest_pod</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">]</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">events_with_timing</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Build clinical timeline from the postoperative course</span></span>
<span class="line"><span style="color: #50FA7B">postop_text</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">"""</span></span>
<span class="line"><span style="color: #F1FA8C">The patient was extubated on POD 1 without complications. Chest tubes were removed on POD 2.</span></span>
<span class="line"><span style="color: #F1FA8C">Postoperative atrial fibrillation was noted on POD 3, successfully cardioverted with amiodarone</span></span>
<span class="line"><span style="color: #F1FA8C">loading followed by maintenance dose. Patient remained in sinus rhythm thereafter.</span></span>
<span class="line"><span style="color: #F1FA8C">Echocardiography on POD 5 showed LVEF 40% with good biventricular function.</span></span>
<span class="line"><span style="color: #E9F284">"""</span></span>
<span class="line"></span>
<span class="line"><span style="color: #50FA7B">timeline</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">=</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">extract_temporal_clinical_events</span><span style="color: #F8F8F2">(</span><span style="color: #50FA7B">postop_text,</span><span style="color: #F8F8F2"> </span><span style="color: #F1FA8C">nlp_sci</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #50FA7B">"Clinical Timeline Reconstruction:"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(</span><span style="color: #50FA7B">"="</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9; font-style: italic">*</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">60</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">event</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> timeline:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(f</span><span style="color: #50FA7B">"</span><span style="color: #FF79C6">\\</span><span style="color: #50FA7B">n  POD{event['earliest_pod']}:"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #8BE9FD">print</span><span style="color: #F8F8F2">(f</span><span style="color: #50FA7B">"{event['sentence']}"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
```

**Output:**


```
Clinical Timeline Reconstruction:
============================================================

  POD 1:
  The patient was extubated on POD 1 without complications.

  POD 2:
  Chest tubes were removed on POD 2.

  POD 3:
  Postoperative atrial fibrillation was noted on POD 3, successfully cardioverted with amiodarone loading.

  POD 5:
  Echocardiography on POD 5 showed LVEF 40% with good biventricular function.
```

This kind of structured temporal reconstruction has direct applications in automated quality metrics reporting, particularly for surgical safety indicators like time-to-extubation and time-to-complication onset.

---

## 6. Comparing spaCy/scispaCy with Regex: A Decision Framework

### 6.1 Quantitative Performance Considerations

Neither regex nor linguistic NLP is universally better. The optimal choice depends on the specific extraction task, the consistency of the source documents, and the computational resources you actually have at your disposal. The table below summarizes the practical considerations:



| Criterion | Regex | spaCy/scispaCy |
| --- | --- | --- |
| **Setup complexity** | Minimal | Moderate (model installation) |
| **Processing speed** | Very fast (~1ms/doc) | Moderate (100ms–5s/doc) |
| **Memory requirements** | Negligible | 500MB–2GB |
| **Accuracy for structured fields** | High (when patterns are known) | Moderate |
| **Accuracy for free-text narrative** | Low | High |
| **Negation handling** | Manual, brittle | Built-in via dependency parsing |
| **Semantic normalization** | None | UMLS linking |
| **Maintenance burden** | High (pattern updates) | Low (model updates) |
| **Explainability** | Complete | Partial (neural models) |
| **Regulatory compliance** | Straightforward | Requires validation |

### 6.2 When to Use Regex

Regex is still the preferred approach for:

- **Highly structured fields** with predictable formats: dates, MRN numbers, medication dosages in formatted lists
- **Real-time applications** where processing latency is critical
- **Resource-constrained environments** (edge computing, embedded systems)
- **Deterministic requirements** where output must be fully explainable and reproducible
- **Simple extraction rules** with few variations (e.g., “extract all numbers followed by ‘mg’”)

### 6.3 When to Use spaCy/scispaCy

Linguistic NLP earns its place when you need:

- **Free-text narratives** with high syntactic variability (postoperative course sections)
- **Entity recognition** where surface forms vary (“AF,” “atrial fib,” “atrial fibrillation”)
- **Relationship extraction** between medical concepts (drug → indication, procedure → complication)
- **Negation detection** at scale (well above what rule-based approaches can deliver)
- **Interoperability requirements** where UMLS/SNOMED-CT standardization is needed
- **Research applications** requiring semantic search or concept-based cohort selection

### 6.4 The Hybrid Pipeline (Preview)

In practice, the most robust systems combine both. A hybrid pipeline for cardiac surgery discharge summaries might follow this logic:

1. **Regex layer**: Extract structured fields (dates, numeric values, section headers) with high precision
2. **scispaCy NER layer**: Identify medical entities in free-text sections
3. **UMLS linking layer**: Normalize entities to standard vocabularies
4. **Validation layer**: Cross-validate regex and NLP results; flag discrepancies

The architecture is explored in depth in Article 3, where we layer in machine learning classifiers for complication severity scoring and LLM-based reasoning for the harder inference tasks.

---

## 7. Conclusion: From Syntax to Semantics

This article has shown how spaCy and scispaCy push medical text analysis past character-level pattern matching, into something closer to genuine linguistic intelligence. The key advances over regex-based extraction are:

**Semantic awareness**: scispaCy NER recognizes medical concepts regardless of their surface form, handling the terminological variability inherent to clinical language. “Triple vessel coronary artery disease,” “3-vessel CAD,” and “severe multivessel coronary disease” all refer to the same clinical condition.

**Relational extraction**: Dependency parsing exposes the grammatical relationships between tokens, enabling extraction of drug-event associations, procedural outcomes, and temporal sequences that regex simply cannot reach.

**Ontological grounding**: UMLS entity linking standardizes extracted concepts to universal identifiers, supporting interoperability across electronic health record systems, clinical registries, and research databases. Once a CUI is assigned to a concept, that annotation is unambiguous across institutions and across time.

**Negation sensitivity**: Medical NLP that cannot distinguish affirmed from negated findings is, plainly, clinically dangerous. The dependency-based negation detection illustrated here, while not exhaustive, handles the majority of common patterns in discharge documentation.

### Limitations of the Linguistic Approach

scispaCy, biomedical training notwithstanding, is not without limitations that need to be acknowledged before any clinical use:

- **Out-of-vocabulary terms**: Novel drug names, rare procedures, or institution-specific abbreviations may not be recognized
- **Domain shift**: Models trained on PubMed abstracts may underperform on clinical notes, which have different linguistic characteristics
- **Negation complexity**: Advanced negation patterns (“unlikely to represent,” “cannot exclude”) require specialized components (MedSpaCy, NegEx) beyond standard dependency parsing
- **Numerical relationship extraction**: With “LVEF 35%”, scispaCy recognizes LVEF as an entity but does not inherently link the numerical value; that gap is best filled by regex or a post-processing layer
- **Validation requirements**: Clinical deployment of NLP systems requires rigorous validation on institution-specific documents, with performance metrics (precision, recall, F1) computed on expert-annotated gold standards

## 8. Looking Ahead: Machine Learning and LLMs

The third and final article in this series will tackle what neither regex nor linguistic NLP can do: learn from data, handle unrestricted text variation, and perform complex clinical inference.

Machine learning classifiers trained on annotated discharge summaries can pick up complication risk from subtle linguistic patterns. Large Language Models (LLMs), the Claude API among them, can extract arbitrarily complex information through natural language querying, compare findings against clinical guidelines, and generate structured summaries from narrative text. The article will also address the non-negotiable topics: GDPR/HIPAA compliance, de-identification pipelines, and deployment considerations for production medical AI systems.

**Preview**: scispaCy might recognize “atrial fibrillation” as a disease entity. A properly prompted LLM, by contrast, can determine whether the documented management (amiodarone loading, cardioversion) was consistent with current ACC/AHA guidelines, a level of clinical reasoning that pattern-based NLP systems cannot reach.

## References

1. Neumann M, et al. “ScispaCy: Fast and Robust Models for Biomedical Natural Language Processing.” _Proceedings of the 18th BioNLP Workshop and Shared Task_, 2019:319–327.
2. Honnibal M, Montani I. “spaCy 2: Natural language understanding with Bloom embeddings, convolutional neural networks and incremental parsing.” _Unpublished_, 2017.
3. Bodenreider O. “The Unified Medical Language System (UMLS): integrating biomedical terminology.” _Nucleic Acids Research_ 2004;32(suppl\_1):D267–D270.
4. Schwartz AS, Hearst MA. “A simple algorithm for identifying abbreviation definitions in biomedical text.” _Proceedings of the Pacific Symposium on Biocomputing_, 2003:451–462.
5. Li J, et al. “BioCreative V CDR task corpus: a resource for chemical disease relation extraction.” _Database_ 2016:baw068.
6. Lample G, et al. “Neural Architectures for Named Entity Recognition.” _Proceedings of NAACL-HLT 2016_, 2016:260–270.
7. Chapman WW, et al. “A simple algorithm for identifying negated findings and diseases in discharge summaries.” _Journal of Biomedical Informatics_ 2001;34(5):301–310. \[NegEx algorithm\]
8. Soldaini L, Goharian N. “QuickUMLS: a fast, unsupervised approach for medical concept extraction.” _MedIR Workshop, SIGIR_, 2016.
9. Peng Y, et al. “Transfer Learning in Biomedical Natural Language Processing.” _Proceedings of the 18th BioNLP Workshop_, 2019.
10. Savova GK, et al. “Mayo clinical Text Analysis and Knowledge Extraction System (cTAKES): architecture, component evaluation and applications.” _JAMIA_ 2010;17(5):507–513.
11. Uzuner O, et al. “2010 i2b2/VA challenge on concepts, assertions, and relations in clinical text.” _JAMIA_ 2011;18(5):552–556.
12. Stenetorp P, et al. “BRAT: a Web-based Tool for NLP-Assisted Text Annotation.” _Proceedings of EACL 2012_, 2012:102–107.