Build a Clinical Section Detector

Parse discharge summaries using regex to detect and extract clinical sections.

Introduction

30 Second Summary

When a patient leaves the hospital, their care team creates a discharge summary containing everything from vital signs to medication lists. Finding a specific piece of information in these multi-page documents means reading through the entire thing manually.

In this project, you will build a Python script that automatically detects and extracts named sections from a fake clinical discharge summary using regex patterns and line-by-line text scanning.

What You'll Build

A clinical section detector that scans a realistic fake discharge summary, identifies section headers using regex pattern matching, and returns a structured dictionary mapping each detected section to its content.

By the end of this project, you'll have:

  • A fake discharge summary modeled after real clinical document structure with sections like Chief Complaint, Hospital Course, and Discharge Medications.
  • A detect_sections() function that uses regex-based header detection and line-by-line scanning to parse any clinical document into labeled sections.
  • An extract_physical_exam() function that specifically targets physical exam sections and classifies them as admission or discharge exams.
  • Secret Mission: Build an enhanced classifier that distinguishes admission from discharge physical exams automatically using positional heuristics.

Do I need medical experience for this project?

No medical or NLP experience is needed. You only need basic Python knowledge (variables, strings, lists, loops, and functions). All clinical concepts are explained as you go.

Set Up Your Python Environment

Before you can build a section detector, you need a working Python environment and a project folder to hold your code.

In this step, you will verify your Python version, create a dedicated project folder, and start the script file that will become your clinical section detector.

In this step, get ready to:
  • Verify Python 3.12 or later is installed.
  • Create a project folder and script file.
  • Add the regex import to your script.
Verify Python is installed

This project requires Python version 3.12 or later.

  • Check your Python version by running this command in your terminal:
python --version

✔️ I see version 3.12 or higher

Your Python is ready to go. Move on to the next substep.

ⓧ I see an older version

You have Python installed, but your version is below 3.12. On macOS, the system Python is often an older version even if you have installed a newer one separately.

  • Try running python3 --version instead. If that shows 3.12 or higher, use python3 for all commands in this project.
  • If that also shows an older version, download the latest Python 3.12 installer from python.org/downloads and run it.
  • After installing, close and reopen your terminal, then run python --version again to confirm.

ⓧ Command not found

Python is not installed on your machine yet. On macOS or Linux, try python3 --version first. Some systems install Python under the python3 name only.

If that also returns "command not found", install Python:

  • Go to python.org/downloads and download the latest Python 3.12 installer for your operating system.
  • Run the installer. On Windows, check the box that says Add python.exe to PATH before clicking Install Now.
  • Close and reopen your terminal after installation.
  • Run python --version again to confirm you see 3.12 or higher.
Create your project folder and script file
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir clinical-section-detector
cd clinical-section-detector
  • Create your main script file by running this command:

macOS

touch section_detector.py

Windows

New-Item section_detector.py
  • Confirm the file exists by running:
ls

You should see section_detector.py in the output.

Don't see the file?

  • Make sure you ran cd clinical-section-detector before creating the file. If you skipped that step, the file was created one level up.
  • On Windows, make sure you used New-Item section_detector.py in PowerShell. The touch command does not exist on Windows.

Help me troubleshoot creating files in my terminal.

Add the regex import

Python's re module provides regular expression support. It is part of Python's standard library, so there is nothing extra to install.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop and select the clinical-section-detector folder you just created, then click Open.
  • In the Explorer sidebar on the left, click section_detector.py to open it.
  • Add the regex import to your empty file by pasting the following code:
import re

What does this line do?

The re module gives you regular expression tools for pattern matching in text. You will use it to identify section headers in clinical documents by matching patterns like ALL CAPS words followed by a colon.

  • Save section_detector.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).

✔️ Awesome, I've got everything!

Great. Make sure you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete section_detector.py file at this point:

import re

Your environment is ready. Next up, you will create a realistic fake discharge summary to use as test data for your section detector.

Create a Fake Discharge Summary

Before you can detect sections in a clinical document, you need a clinical document to work with. In real clinical NLP pipelines, developers use de-identified patient notes. For this project, you will create a realistic fake discharge summary.

A discharge summary is the document a hospital generates when a patient leaves. It captures everything about the hospital stay in a structured format. In this step, you will add one of these documents as test data in your Python script.

In this step, get ready to:
  • Define a multi-line fake discharge summary as a Python string variable.
  • Understand what each section of the document represents clinically.
  • Identify the visual patterns that make section headers detectable by code.
Add the discharge summary to your script

Your section detector needs a document to parse. You will define a multi-line string containing a realistic fake discharge summary with nine distinct sections.

  • In section_detector.py, add the following code below the import re line:
# =============================================================
# FAKE DISCHARGE SUMMARY (realistic clinical document)
# =============================================================

DISCHARGE_SUMMARY = """
ADMISSION DATE:
2026-05-10

DISCHARGE DATE:
2026-05-15

CHIEF COMPLAINT:
Shortness of breath and productive cough for 3 days.

HISTORY OF PRESENT ILLNESS:
Mr. John Smith is a 68-year-old male with a history of COPD and
hypertension who presents with worsening dyspnea and productive
cough with yellow-green sputum for 3 days. He reports fever at
home measured at 101.2F. He denies chest pain, hemoptysis, or
recent travel.

PHYSICAL EXAMINATION ON ADMISSION:
Vitals: T 101.4F, HR 102, BP 138/82, RR 24, SpO2 91% on RA.
General: Ill-appearing male in mild respiratory distress.
Lungs: Bilateral rhonchi, decreased breath sounds at right base,
dullness to percussion at right base.
Heart: Tachycardic, regular rhythm, no murmurs.
Extremities: No edema, no cyanosis.

HOSPITAL COURSE:
Patient was admitted to the medical floor with a diagnosis of
community-acquired pneumonia. He was started on IV ceftriaxone
and azithromycin. Blood cultures were obtained and showed no
growth. Chest X-ray confirmed right lower lobe consolidation.
By day 3, the patient was afebrile and oxygen requirements had
decreased. He was transitioned to oral antibiotics on day 4.

PHYSICAL EXAMINATION AT DISCHARGE:
Vitals: T 98.6F, HR 78, BP 132/76, RR 16, SpO2 96% on RA.
General: Alert, comfortable, in no distress.
Lungs: Clear to auscultation bilaterally, no rhonchi or wheezing.
Heart: Regular rate and rhythm, no murmurs.
Extremities: No edema.

DISCHARGE MEDICATIONS:
1. Amoxicillin-clavulanate 875mg PO BID x 5 days
2. Lisinopril 20mg PO daily (continued from home)
3. Tiotropium 18mcg INH daily (continued from home)
4. Albuterol inhaler PRN (continued from home)

DISCHARGE INSTRUCTIONS:
Follow up with your primary care physician within 7 days.
Return to the emergency department if you develop worsening
shortness of breath, fever above 101F, or chest pain.
Complete the full course of antibiotics.
"""

What does this code do?

This defines a single variable called DISCHARGE_SUMMARY that holds an entire fake clinical document as a multi-line Python string (using triple quotes).

The document contains nine named sections, each starting with an ALL CAPS header followed by a colon. This is the test data your section detector will parse in the next step.

  • Save section_detector.py.
  • Confirm there are no syntax errors by running this command in your terminal:
python section_detector.py
  • You should see no output and no errors. The command prompt returns cleanly, which confirms the string syntax is valid.

Seeing a SyntaxError?

Make sure the triple quotes (""") at the start and end of the string match exactly. A missing or extra quote will break the string.

Check that the closing """ is on its own line with no extra characters after it.

Help me debug a SyntaxError in my multi-line Python string.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete section_detector.py file so far:

import re


# =============================================================
# FAKE DISCHARGE SUMMARY (realistic clinical document)
# =============================================================

DISCHARGE_SUMMARY = """
ADMISSION DATE:
2026-05-10

DISCHARGE DATE:
2026-05-15

CHIEF COMPLAINT:
Shortness of breath and productive cough for 3 days.

HISTORY OF PRESENT ILLNESS:
Mr. John Smith is a 68-year-old male with a history of COPD and
hypertension who presents with worsening dyspnea and productive
cough with yellow-green sputum for 3 days. He reports fever at
home measured at 101.2F. He denies chest pain, hemoptysis, or
recent travel.

PHYSICAL EXAMINATION ON ADMISSION:
Vitals: T 101.4F, HR 102, BP 138/82, RR 24, SpO2 91% on RA.
General: Ill-appearing male in mild respiratory distress.
Lungs: Bilateral rhonchi, decreased breath sounds at right base,
dullness to percussion at right base.
Heart: Tachycardic, regular rhythm, no murmurs.
Extremities: No edema, no cyanosis.

HOSPITAL COURSE:
Patient was admitted to the medical floor with a diagnosis of
community-acquired pneumonia. He was started on IV ceftriaxone
and azithromycin. Blood cultures were obtained and showed no
growth. Chest X-ray confirmed right lower lobe consolidation.
By day 3, the patient was afebrile and oxygen requirements had
decreased. He was transitioned to oral antibiotics on day 4.

PHYSICAL EXAMINATION AT DISCHARGE:
Vitals: T 98.6F, HR 78, BP 132/76, RR 16, SpO2 96% on RA.
General: Alert, comfortable, in no distress.
Lungs: Clear to auscultation bilaterally, no rhonchi or wheezing.
Heart: Regular rate and rhythm, no murmurs.
Extremities: No edema.

DISCHARGE MEDICATIONS:
1. Amoxicillin-clavulanate 875mg PO BID x 5 days
2. Lisinopril 20mg PO daily (continued from home)
3. Tiotropium 18mcg INH daily (continued from home)
4. Albuterol inhaler PRN (continued from home)

DISCHARGE INSTRUCTIONS:
Follow up with your primary care physician within 7 days.
Return to the emergency department if you develop worsening
shortness of breath, fever above 101F, or chest pain.
Complete the full course of antibiotics.
"""
Understand what each section represents

Discharge summaries are required by CMS (Centers for Medicare & Medicaid Services) for inpatient stays. They serve as the handoff document between hospital and outpatient care.

What do the sections mean?

Each section in your fake document serves a specific clinical purpose:

  • CHIEF COMPLAINT and HISTORY OF PRESENT ILLNESS capture why the patient came to the hospital and the clinical story leading up to admission.
  • PHYSICAL EXAMINATION ON ADMISSION records the patient's baseline condition at entry. PHYSICAL EXAMINATION AT DISCHARGE documents how they improved after treatment.
  • HOSPITAL COURSE summarizes what happened during the stay (treatments, tests, response).
  • DISCHARGE MEDICATIONS and DISCHARGE INSTRUCTIONS tell the patient and their next doctor what to do going forward.

Who reads a discharge summary?

Discharge summaries serve three audiences. Clinicians use the hospital course to continue care. Patients read the instructions to know what to do at home. Reviewers (billing, quality, malpractice) use the full document for justification and compliance.

Spot the patterns your regex will target

Look at the section headers in your document. Every header follows the same visual pattern that makes them detectable by a program.

  • ALL CAPS text: Every header uses uppercase letters only (e.g., CHIEF COMPLAINT, HOSPITAL COURSE).
  • Colon delimiter: Every header ends with a colon character (:) that separates the header name from its content.
  • Start of line: Headers always appear at the beginning of a new line with no content before them.

Why do these patterns matter?

These three properties (uppercase, colon, start of line) form the exact signature that a regex pattern can match. In the next step, you will write a regex that targets this combination to automatically find where each section starts.

Your test document is ready and you know exactly what patterns to look for. Next up, you will write the regex-powered scanner that reads this document line by line and automatically splits it into named sections.

Build the Line-by-Line Scanner

Your discharge summary is ready, but right now it is just a big wall of text. To extract useful information from it, you need a way to identify where each section starts and ends.

In this step, you will build a function that scans the document line by line, uses a regex pattern to detect section headers, and applies a stop condition to determine where one section ends and the next begins.

In this step, get ready to:
  • Write a detect_sections() function with regex-based header detection.
  • Add a verbose trace that shows how each line is classified.
  • Run the detector on your discharge summary and observe the output.
Write the detect_sections() function

The core idea behind section detection is simple. Read the document one line at a time, check if the line looks like a header, and if it does, start a new section.

The "stop condition" is what tells you when a section ends. In this case, the next header you encounter marks the end of the previous section.

  • In section_detector.py, scroll to the bottom of the file (below the closing """ of your DISCHARGE_SUMMARY string).
  • Add the following function by copying this code below the DISCHARGE_SUMMARY variable:
# =============================================================
# SECTION DETECTOR: Line-by-line scanning with regex headers
# =============================================================

def detect_sections(document, verbose=False):
    """
    Detect sections in a clinical document using regex-based
    header detection and line-by-line scanning.

    Headers are identified as lines that:
    - Start with one or more uppercase words
    - End with a colon
    - May have optional leading whitespace

    Stop condition: a section ends when the next header is found.

    Args:
        document: multi-line string of clinical text
        verbose: if True, print a trace of each line's classification

    Returns:
        dict mapping section header names to their content (str)
    """
    header_pattern = re.compile(r'^\s*([A-Z][A-Z \t]+):\s*(.*)')

    sections = {}
    current_section = None
    current_lines = []

    lines = document.strip().split('\n')

    for i, line in enumerate(lines):
        match = header_pattern.match(line)

        if match:
            # Stop condition: save the previous section
            if current_section is not None:
                sections[current_section] = '\n'.join(current_lines).strip()

            # Start a new section
            current_section = match.group(1).strip()
            inline_content = match.group(2).strip()
            current_lines = [inline_content] if inline_content else []

            if verbose:
                print(f"Line {i+1:3d}: [HEADER] '{current_section}'")
        else:
            # Accumulate content into the current section
            current_lines.append(line)

            if verbose:
                section_label = current_section if current_section else "(preamble)"
                print(f"Line {i+1:3d}: [CONTENT] -> {section_label}")

    # Save the last section
    if current_section is not None:
        sections[current_section] = '\n'.join(current_lines).strip()

    return sections

What does this function do?

This function processes the document in three stages:

  • header_pattern is a compiled regex that matches lines starting with ALL CAPS words followed by a colon. The (.*) at the end captures any inline content after the colon.
  • The loop reads each line and checks if it matches the header pattern. If it does, the previous section gets saved (stop condition) and a new section starts.
  • If a line does NOT match the pattern, it gets appended to current_lines as body content for the current section.
  • After the loop ends, the last section is saved because there is no "next header" to trigger the stop condition.

Why regex and not something fancier?

A systematic review of 39 studies on section identification in clinical records found that 59% used rule-based methods (like regex), 22% used machine learning, and 19% used a hybrid approach.

Regex remains dominant because it requires no training data, produces interpretable results, and works well on documents with explicit headers like discharge summaries.

Understand the verbose trace

The verbose parameter is your window into how the algorithm thinks. When set to True, the function prints a trace for every line it processes.

What does the trace show?

Each trace line reports three things:

  • The line number in the document.
  • Whether it matched the header pattern ([HEADER]) or was classified as content ([CONTENT]).
  • Which section the line belongs to. This makes the stop condition visible because you can see exactly where one section ends and another begins.
Run the detector and observe the output

Now you will add a main block that calls your function and displays the results. This lets you run the script directly and see the detection trace in action.

  • Add the following code at the very bottom of section_detector.py, below the detect_sections() function:
if __name__ == '__main__':
    print("=" * 60)
    print("CLINICAL SECTION DETECTOR")
    print("=" * 60)
    print()

    print("--- Detection Trace (first 20 lines) ---")
    print()
    sections = detect_sections(DISCHARGE_SUMMARY, verbose=True)

    print()
    print("--- Detected Sections ---")
    print()
    for header in sections:
        content_preview = sections[header][:60].replace('\n', ' ')
        print(f"  [{header}]: {content_preview}...")

What does this code do?

The if __name__ == '__main__': block only runs when you execute the script directly (not when importing it as a module). It calls detect_sections() with verbose=True so you can see the full trace.

After the trace, it prints a summary of all detected section headers with a preview of the first 60 characters of their content.

  • Save section_detector.py.
  • Run the script by entering this command in your terminal:
python section_detector.py

You should see a detection trace showing each line classified as either [HEADER] or [CONTENT], followed by a summary listing all 9 detected sections with content previews.

What should I see?

Look at the trace output. You will see lines like:

Line   1: [HEADER] 'ADMISSION DATE'
Line   2: [CONTENT] -> ADMISSION DATE
Line   3: [CONTENT] -> ADMISSION DATE
Line   4: [HEADER] 'DISCHARGE DATE'

Notice how line 4 triggers both the stop condition (saving ADMISSION DATE's content) and the start of a new section (DISCHARGE DATE). This is the core mechanism of the scanner.

Seeing a SyntaxError or no output?

  • Check that your if __name__ == '__main__': block is at the top level of the file (no indentation), not inside the function.
  • Make sure there is a blank line between the end of the detect_sections() function and the if __name__ block.
  • Confirm you are running the command from inside the clinical-section-detector folder.

Help me debug my section detector script.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete section_detector.py file as it should look at the end of this step:

import re


# =============================================================
# FAKE DISCHARGE SUMMARY (realistic clinical document)
# =============================================================

DISCHARGE_SUMMARY = """
ADMISSION DATE:
2026-05-10

DISCHARGE DATE:
2026-05-15

CHIEF COMPLAINT:
Shortness of breath and productive cough for 3 days.

HISTORY OF PRESENT ILLNESS:
Mr. John Smith is a 68-year-old male with a history of COPD and
hypertension who presents with worsening dyspnea and productive
cough with yellow-green sputum for 3 days. He reports fever at
home measured at 101.2F. He denies chest pain, hemoptysis, or
recent travel.

PHYSICAL EXAMINATION ON ADMISSION:
Vitals: T 101.4F, HR 102, BP 138/82, RR 24, SpO2 91% on RA.
General: Ill-appearing male in mild respiratory distress.
Lungs: Bilateral rhonchi, decreased breath sounds at right base,
dullness to percussion at right base.
Heart: Tachycardic, regular rhythm, no murmurs.
Extremities: No edema, no cyanosis.

HOSPITAL COURSE:
Patient was admitted to the medical floor with a diagnosis of
community-acquired pneumonia. He was started on IV ceftriaxone
and azithromycin. Blood cultures were obtained and showed no
growth. Chest X-ray confirmed right lower lobe consolidation.
By day 3, the patient was afebrile and oxygen requirements had
decreased. He was transitioned to oral antibiotics on day 4.

PHYSICAL EXAMINATION AT DISCHARGE:
Vitals: T 98.6F, HR 78, BP 132/76, RR 16, SpO2 96% on RA.
General: Alert, comfortable, in no distress.
Lungs: Clear to auscultation bilaterally, no rhonchi or wheezing.
Heart: Regular rate and rhythm, no murmurs.
Extremities: No edema.

DISCHARGE MEDICATIONS:
1. Amoxicillin-clavulanate 875mg PO BID x 5 days
2. Lisinopril 20mg PO daily (continued from home)
3. Tiotropium 18mcg INH daily (continued from home)
4. Albuterol inhaler PRN (continued from home)

DISCHARGE INSTRUCTIONS:
Follow up with your primary care physician within 7 days.
Return to the emergency department if you develop worsening
shortness of breath, fever above 101F, or chest pain.
Complete the full course of antibiotics.
"""


# =============================================================
# SECTION DETECTOR: Line-by-line scanning with regex headers
# =============================================================

def detect_sections(document, verbose=False):
    """
    Detect sections in a clinical document using regex-based
    header detection and line-by-line scanning.

    Headers are identified as lines that:
    - Start with one or more uppercase words
    - End with a colon
    - May have optional leading whitespace

    Stop condition: a section ends when the next header is found.

    Args:
        document: multi-line string of clinical text
        verbose: if True, print a trace of each line's classification

    Returns:
        dict mapping section header names to their content (str)
    """
    header_pattern = re.compile(r'^\s*([A-Z][A-Z \t]+):\s*(.*)')

    sections = {}
    current_section = None
    current_lines = []

    lines = document.strip().split('\n')

    for i, line in enumerate(lines):
        match = header_pattern.match(line)

        if match:
            # Stop condition: save the previous section
            if current_section is not None:
                sections[current_section] = '\n'.join(current_lines).strip()

            # Start a new section
            current_section = match.group(1).strip()
            inline_content = match.group(2).strip()
            current_lines = [inline_content] if inline_content else []

            if verbose:
                print(f"Line {i+1:3d}: [HEADER] '{current_section}'")
        else:
            # Accumulate content into the current section
            current_lines.append(line)

            if verbose:
                section_label = current_section if current_section else "(preamble)"
                print(f"Line {i+1:3d}: [CONTENT] -> {section_label}")

    # Save the last section
    if current_section is not None:
        sections[current_section] = '\n'.join(current_lines).strip()

    return sections


if __name__ == '__main__':
    print("=" * 60)
    print("CLINICAL SECTION DETECTOR")
    print("=" * 60)
    print()

    print("--- Detection Trace (first 20 lines) ---")
    print()
    sections = detect_sections(DISCHARGE_SUMMARY, verbose=True)

    print()
    print("--- Detected Sections ---")
    print()
    for header in sections:
        content_preview = sections[header][:60].replace('\n', ' ')
        print(f"  [{header}]: {content_preview}...")

Your scanner is working. It can split any clinical document into named sections automatically. Next up, you will use this to extract something clinically meaningful: the physical exam.

Extract the Physical Exam Section

In the previous step, you built a scanner that detects every section in the discharge summary. Now you have a dictionary of sections, but you need to target a specific one: the physical exam.

A discharge summary often contains two physical exams. The admission exam records what the patient looked like when they arrived. The discharge exam documents their condition when they leave. In this step, you will write a function that finds these exams and classifies them correctly.

In this step, get ready to:
  • Write an extract_physical_exam() function that uses flexible substring matching to find physical exam sections.
  • Classify exams as admission or discharge using keyword detection in header text.
  • Print both exams side by side and explain why this distinction matters clinically.
Write the physical exam extractor function

Real clinical notes use many different header variations for the physical exam. Some say "PHYSICAL EXAMINATION", others use "PE" or "PHYSICAL EXAM". Your function needs to handle this variety by checking if the header contains the substring PHYSICAL EXAM rather than requiring an exact match.

Since your detect_sections() function already captures headers in uppercase (the regex only matches uppercase letters), a simple substring check on the header key handles all the common variations.

  • In section_detector.py, add the following function below the detect_sections() function (before the if __name__ == '__main__': block):
# =============================================================
# PHYSICAL EXAM EXTRACTOR
# =============================================================

def extract_physical_exam(sections):
    """
    Extract physical exam sections from detected sections.

    Uses flexible matching to find headers containing
    'PHYSICAL EXAM' and classifies them as admission or
    discharge exams based on keywords in the header.

    Args:
        sections: dict from detect_sections()

    Returns:
        dict with keys 'admission_exam' and 'discharge_exam',
        values are the section content or None if not found
    """
    result = {
        'admission_exam': None,
        'discharge_exam': None,
    }

    admission_keywords = ['ADMISSION', 'ADMIT', 'INITIAL', 'PRESENTING']
    discharge_keywords = ['DISCHARGE', 'DISMISSAL', 'FINAL']

    for header, content in sections.items():
        if 'PHYSICAL EXAM' in header or header == 'PE':
            # Check if header indicates admission or discharge
            if any(kw in header for kw in admission_keywords):
                result['admission_exam'] = content
            elif any(kw in header for kw in discharge_keywords):
                result['discharge_exam'] = content
            else:
                # Ambiguous single exam section - store as admission
                # (Secret Mission improves on this logic)
                result['admission_exam'] = content

    return result

What does this code do?

This function takes the sections dictionary from detect_sections() and searches for physical exam entries specifically.

  • 'PHYSICAL EXAM' in header uses flexible substring matching. This catches "PHYSICAL EXAMINATION ON ADMISSION", "PHYSICAL EXAMINATION AT DISCHARGE", and any other variation containing those words.
  • header == 'PE' handles the common abbreviation some clinicians use.
  • admission_keywords and discharge_keywords classify each matching header. If a header contains "ADMISSION", "ADMIT", "INITIAL", or "PRESENTING", it is the admission exam. If it contains "DISCHARGE", "DISMISSAL", or "FINAL", it is the discharge exam.
  • The fallback case stores an ambiguous exam as the admission exam. The Secret Mission later improves this logic with positional heuristics.

Why use flexible matching instead of exact header names?

Real clinical notes have no enforced formatting standard. One hospital might write "PHYSICAL EXAMINATION ON ADMISSION" while another uses "PE" or "PHYSICAL EXAM". A rigid exact-match approach breaks the moment you encounter a document from a different system.

Substring matching trades a small risk of false positives for much better coverage across document styles.

  • Save section_detector.py.

Seeing an indentation error?

Make sure the function is at the top level of the file (no indentation before def). It should sit between the end of detect_sections() and the if __name__ block.

help me fix an indentation error in my extract_physical_exam function

Print both exams side by side with clinical significance

The admission exam records the patient's baseline at hospital entry. The discharge exam documents their condition after treatment. Comparing these two proves whether the treatment worked.

  • In section_detector.py, replace the entire if __name__ == '__main__': block with the following updated version:
# =============================================================
# MAIN: Run the detector and display results
# =============================================================

if __name__ == '__main__':
    print("=" * 60)
    print("CLINICAL SECTION DETECTOR")
    print("=" * 60)
    print()

    # Step 1: Detect all sections with verbose trace
    print("--- Detection Trace (first 20 lines) ---")
    print()
    sections = detect_sections(DISCHARGE_SUMMARY, verbose=True)

    print()
    print("--- Detected Sections ---")
    print()
    for header in sections:
        content_preview = sections[header][:60].replace('\n', ' ')
        print(f"  [{header}]: {content_preview}...")

    print()
    print("--- Physical Exam Extraction ---")
    print()

    exams = extract_physical_exam(sections)

    print("ADMISSION EXAM:")
    print(exams['admission_exam'] if exams['admission_exam'] else "  (not found)")
    print()
    print("DISCHARGE EXAM:")
    print(exams['discharge_exam'] if exams['discharge_exam'] else "  (not found)")
    print()

    # Clinical significance
    print("--- Clinical Significance ---")
    print()
    print("Why does distinguishing admission vs discharge exam matter?")
    print()
    print("  ADMISSION EXAM (baseline):")
    print("    Records the patient's condition at hospital entry.")
    print("    Example: 'Bilateral rhonchi, decreased breath sounds'")
    print()
    print("  DISCHARGE EXAM (outcome):")
    print("    Documents improvement (or change) after treatment.")
    print("    Example: 'Clear to auscultation bilaterally'")
    print()
    print("  Comparing them proves treatment effectiveness,")
    print("  justifies the hospital stay for billing, and guides")
    print("  the follow-up provider on what to monitor.")

What does this code do?

  • It calls extract_physical_exam(sections) to pull out just the physical exam sections from the full sections dictionary.
  • It prints the admission exam and discharge exam content separately so you can compare them.
  • The clinical significance block explains why this comparison matters: the admission exam is the baseline, the discharge exam is the outcome, and comparing them proves treatment worked.
  • Save section_detector.py.
  • Run your script by executing this command:
python section_detector.py
  • You should see the detection trace, the list of detected sections, then a "Physical Exam Extraction" section showing the admission exam vitals (T 101.4F, HR 102) and the discharge exam vitals (T 98.6F, HR 78) printed separately.

Why does the admission vs. discharge distinction matter clinically?

The admission exam for our fake patient shows bilateral rhonchi and decreased breath sounds. The discharge exam shows lungs clear to auscultation bilaterally. This change proves the pneumonia treatment worked.

In practice, this comparison justifies the hospital stay for billing, satisfies quality metrics, and tells the follow-up provider what to monitor. A study of 537 admissions found approximately 19% diagnostic mismatch between admission and discharge, correlating with higher ICU transfers and in-hospital mortality.

Seeing "(not found)" for both exams?

Check that your extract_physical_exam() function receives the sections variable (the return value of detect_sections()). Make sure the line reads exams = extract_physical_exam(sections) and that sections was assigned earlier in the same block.

help me debug why extract_physical_exam returns not found

✔️ Awesome, I've got everything!

Great. Double check you have saved your file.

ⓧ I'd like to double check the full code

Here is the complete section_detector.py at this point:

import re


# =============================================================
# FAKE DISCHARGE SUMMARY (realistic clinical document)
# =============================================================

DISCHARGE_SUMMARY = """
ADMISSION DATE:
2026-05-10

DISCHARGE DATE:
2026-05-15

CHIEF COMPLAINT:
Shortness of breath and productive cough for 3 days.

HISTORY OF PRESENT ILLNESS:
Mr. John Smith is a 68-year-old male with a history of COPD and
hypertension who presents with worsening dyspnea and productive
cough with yellow-green sputum for 3 days. He reports fever at
home measured at 101.2F. He denies chest pain, hemoptysis, or
recent travel.

PHYSICAL EXAMINATION ON ADMISSION:
Vitals: T 101.4F, HR 102, BP 138/82, RR 24, SpO2 91% on RA.
General: Ill-appearing male in mild respiratory distress.
Lungs: Bilateral rhonchi, decreased breath sounds at right base,
dullness to percussion at right base.
Heart: Tachycardic, regular rhythm, no murmurs.
Extremities: No edema, no cyanosis.

HOSPITAL COURSE:
Patient was admitted to the medical floor with a diagnosis of
community-acquired pneumonia. He was started on IV ceftriaxone
and azithromycin. Blood cultures were obtained and showed no
growth. Chest X-ray confirmed right lower lobe consolidation.
By day 3, the patient was afebrile and oxygen requirements had
decreased. He was transitioned to oral antibiotics on day 4.

PHYSICAL EXAMINATION AT DISCHARGE:
Vitals: T 98.6F, HR 78, BP 132/76, RR 16, SpO2 96% on RA.
General: Alert, comfortable, in no distress.
Lungs: Clear to auscultation bilaterally, no rhonchi or wheezing.
Heart: Regular rate and rhythm, no murmurs.
Extremities: No edema.

DISCHARGE MEDICATIONS:
1. Amoxicillin-clavulanate 875mg PO BID x 5 days
2. Lisinopril 20mg PO daily (continued from home)
3. Tiotropium 18mcg INH daily (continued from home)
4. Albuterol inhaler PRN (continued from home)

DISCHARGE INSTRUCTIONS:
Follow up with your primary care physician within 7 days.
Return to the emergency department if you develop worsening
shortness of breath, fever above 101F, or chest pain.
Complete the full course of antibiotics.
"""


# =============================================================
# SECTION DETECTOR: Line-by-line scanning with regex headers
# =============================================================

def detect_sections(document, verbose=False):
    """
    Detect sections in a clinical document using regex-based
    header detection and line-by-line scanning.

    Headers are identified as lines that:
    - Start with one or more uppercase words
    - End with a colon
    - May have optional leading whitespace

    Stop condition: a section ends when the next header is found.

    Args:
        document: multi-line string of clinical text
        verbose: if True, print a trace of each line's classification

    Returns:
        dict mapping section header names to their content (str)
    """
    header_pattern = re.compile(r'^\s*([A-Z][A-Z \t]+):\s*(.*)')

    sections = {}
    current_section = None
    current_lines = []

    lines = document.strip().split('\n')

    for i, line in enumerate(lines):
        match = header_pattern.match(line)

        if match:
            # Stop condition: save the previous section
            if current_section is not None:
                sections[current_section] = '\n'.join(current_lines).strip()

            # Start a new section
            current_section = match.group(1).strip()
            inline_content = match.group(2).strip()
            current_lines = [inline_content] if inline_content else []

            if verbose:
                print(f"Line {i+1:3d}: [HEADER] '{current_section}'")
        else:
            # Accumulate content into the current section
            current_lines.append(line)

            if verbose:
                section_label = current_section if current_section else "(preamble)"
                print(f"Line {i+1:3d}: [CONTENT] -> {section_label}")

    # Save the last section
    if current_section is not None:
        sections[current_section] = '\n'.join(current_lines).strip()

    return sections


# =============================================================
# PHYSICAL EXAM EXTRACTOR
# =============================================================

def extract_physical_exam(sections):
    """
    Extract physical exam sections from detected sections.

    Uses flexible matching to find headers containing
    'PHYSICAL EXAM' and classifies them as admission or
    discharge exams based on keywords in the header.

    Args:
        sections: dict from detect_sections()

    Returns:
        dict with keys 'admission_exam' and 'discharge_exam',
        values are the section content or None if not found
    """
    result = {
        'admission_exam': None,
        'discharge_exam': None,
    }

    admission_keywords = ['ADMISSION', 'ADMIT', 'INITIAL', 'PRESENTING']
    discharge_keywords = ['DISCHARGE', 'DISMISSAL', 'FINAL']

    for header, content in sections.items():
        if 'PHYSICAL EXAM' in header or header == 'PE':
            # Check if header indicates admission or discharge
            if any(kw in header for kw in admission_keywords):
                result['admission_exam'] = content
            elif any(kw in header for kw in discharge_keywords):
                result['discharge_exam'] = content
            else:
                # Ambiguous single exam section - store as admission
                # (Secret Mission improves on this logic)
                result['admission_exam'] = content

    return result


# =============================================================
# MAIN: Run the detector and display results
# =============================================================

if __name__ == '__main__':
    print("=" * 60)
    print("CLINICAL SECTION DETECTOR")
    print("=" * 60)
    print()

    # Step 1: Detect all sections with verbose trace
    print("--- Detection Trace (first 20 lines) ---")
    print()
    sections = detect_sections(DISCHARGE_SUMMARY, verbose=True)

    print()
    print("--- Detected Sections ---")
    print()
    for header in sections:
        content_preview = sections[header][:60].replace('\n', ' ')
        print(f"  [{header}]: {content_preview}...")

    print()
    print("--- Physical Exam Extraction ---")
    print()

    exams = extract_physical_exam(sections)

    print("ADMISSION EXAM:")
    print(exams['admission_exam'] if exams['admission_exam'] else "  (not found)")
    print()
    print("DISCHARGE EXAM:")
    print(exams['discharge_exam'] if exams['discharge_exam'] else "  (not found)")
    print()

    # Clinical significance
    print("--- Clinical Significance ---")
    print()
    print("Why does distinguishing admission vs discharge exam matter?")
    print()
    print("  ADMISSION EXAM (baseline):")
    print("    Records the patient's condition at hospital entry.")
    print("    Example: 'Bilateral rhonchi, decreased breath sounds'")
    print()
    print("  DISCHARGE EXAM (outcome):")
    print("    Documents improvement (or change) after treatment.")
    print("    Example: 'Clear to auscultation bilaterally'")
    print()
    print("  Comparing them proves treatment effectiveness,")
    print("  justifies the hospital stay for billing, and guides")
    print("  the follow-up provider on what to monitor.")

Your script now detects sections and extracts the physical exams separately. Next up, you will stress-test the detector with tricky edge cases and improve the regex to handle them.

Handle Stop Conditions and Edge Cases

Your section detector works perfectly on the well-structured discharge summary. Every header follows the same clean pattern: ALL CAPS, a colon, then content on the next line.

Real clinical notes are messier. Some headers have content on the same line as the colon. Some sections combine multiple findings under a single header. In this step, you will stress-test your detector with a trickier document and see where rule-based detection hits its limits.

In this step, get ready to:
  • Add a tricky test document that challenges the section detector.
  • Observe how the detector handles inline content after a colon.
  • Understand why regex remains the dominant approach in clinical NLP.
Add a tricky test document

The first discharge summary had perfectly formatted headers. Each one sat alone on its own line with content starting on the next line. Many real clinical notes pack content right after the colon on the same line.

  • In section_detector.py, find the closing """ of DISCHARGE_SUMMARY (the triple-quote after the discharge instructions).
  • Add the following tricky test document directly below it:
# A trickier document for testing edge cases
TRICKY_SUMMARY = """
CHIEF COMPLAINT: Chest pain radiating to left arm.

HISTORY OF PRESENT ILLNESS:
A 55-year-old female presents with acute onset chest pain.

PHYSICAL EXAMINATION:
On admission: BP 160/95, HR 110, diaphoretic, anxious.
Cardiac: S1, S2 present, no S3 or S4, no murmurs.
Lungs: Clear bilaterally.
On discharge: BP 128/78, HR 72, comfortable at rest.
Cardiac: Regular rate and rhythm, no murmurs.
Lungs: Clear bilaterally.

HOSPITAL COURSE:
Troponin negative x3. Stress test negative. Discharged with
diagnosis of musculoskeletal chest pain.

MEDICATIONS: Ibuprofen 400mg PO TID PRN
"""

What makes this document tricky?

This document has two challenges your detector needs to handle:

  • Inline content after the colon: CHIEF COMPLAINT: Chest pain radiating to left arm. puts the complaint on the same line as the header. Same with MEDICATIONS: Ibuprofen 400mg PO TID PRN.
  • Combined physical exam: A single PHYSICAL EXAMINATION header contains both admission and discharge findings mixed together, separated only by informal sub-markers like "On admission:" and "On discharge:".
  • Save section_detector.py.
  • Test the detector on your new document by running this command:
python -c "
from section_detector import detect_sections, extract_physical_exam, TRICKY_SUMMARY
sections = detect_sections(TRICKY_SUMMARY, verbose=True)
print()
print('--- Sections found ---')
for h in sections:
    print(f'  [{h}]: {sections[h][:50]}...')
print()
exams = extract_physical_exam(sections)
print('Admission exam:', 'Found' if exams['admission_exam'] else 'Not found')
print('Discharge exam:', 'Found' if exams['discharge_exam'] else 'Not found')
"

What should I see?

The verbose trace shows each line being classified. You should see headers detected for CHIEF COMPLAINT, HISTORY OF PRESENT ILLNESS, PHYSICAL EXAMINATION, HOSPITAL COURSE, and MEDICATIONS.

The inline content ("Chest pain radiating to left arm." and "Ibuprofen 400mg PO TID PRN") is captured as part of each section's content. The physical exam extractor reports the admission exam as "Found" but the discharge exam as "Not found". That is the limitation: both exams are lumped into one section.

Getting a ModuleNotFoundError?

Make sure you are running the command from inside your clinical-section-detector folder. The python -c command imports from section_detector.py, which must be in the current directory.

help me fix a ModuleNotFoundError when importing section_detector

Confirm the pattern handles inline content

You might have expected inline content to break the detector. It did not. The reason is in the regex pattern your detect_sections() function already uses.

  • In section_detector.py, scroll to the detect_sections() function and find this line:
header_pattern = re.compile(r'^\s*([A-Z][A-Z \t]+):\s*(.*)')

Why does inline content already work?

The (.*) at the end of the pattern is the key. It captures everything after the colon on the same line. When the code runs match.group(2), it gets the inline content ("Chest pain radiating to left arm.") and stores it as the first line of that section's content.

If the pattern ended with $ instead of (.*), it would only match headers on lines with nothing after the colon. That would miss CHIEF COMPLAINT: Chest pain radiating to left arm. entirely.

  • In the docstring of detect_sections(), find this line:
    - End with a colon
  • Update it to document the inline content capability:
    - End with a colon (possibly followed by inline content)
  • Save section_detector.py.

What the detector still cannot handle

The combined physical exam section is the real limitation here. The header PHYSICAL EXAMINATION: has no keyword like "ADMISSION" or "DISCHARGE" in it. Your extract_physical_exam() function defaults to storing it as the admission exam, losing the discharge findings entirely.

A header without any colon (like bare "ASSESSMENT") would also be invisible to the regex. These are inherent limitations of rule-based detection on non-standard documents.

Understand why regex remains the dominant approach

You have now seen the limitations of regex-based section detection. Missing headers and combined sections are real problems. Despite these issues, rule-based methods remain the most common approach in clinical NLP.

Why do most clinical NLP systems still use regex?

A systematic review of 39 studies on section identification in electronic health records found that 59% used rule-based methods, 22% used machine learning, and 19% used hybrid approaches.

Rule-based methods dominate for three reasons. They require no training data (you can deploy them on day one). They produce interpretable results (you can trace exactly why a header was or was not detected). They work well on documents with explicit headers, which is the majority of structured discharge summaries.

The trade-off is clear: regex handles the 80% of well-formatted documents reliably, but struggles with the 20% that use non-standard formatting or implicit sections.

Where does this technique appear in production systems?

The pattern r'^\s*([A-Z\s]+):' was used in the i2b2 challenge medication extraction system to collect section headings from clinical training data. Your detector uses essentially the same technique that real clinical NLP pipelines use.

✔️ Awesome, I've got everything!

Great. Double check you have saved section_detector.py with both the new TRICKY_SUMMARY variable and the updated docstring.

ⓧ I'd like to double check the full code

import re


# =============================================================
# FAKE DISCHARGE SUMMARY (realistic clinical document)
# =============================================================

DISCHARGE_SUMMARY = """
ADMISSION DATE:
2026-05-10

DISCHARGE DATE:
2026-05-15

CHIEF COMPLAINT:
Shortness of breath and productive cough for 3 days.

HISTORY OF PRESENT ILLNESS:
Mr. John Smith is a 68-year-old male with a history of COPD and
hypertension who presents with worsening dyspnea and productive
cough with yellow-green sputum for 3 days. He reports fever at
home measured at 101.2F. He denies chest pain, hemoptysis, or
recent travel.

PHYSICAL EXAMINATION ON ADMISSION:
Vitals: T 101.4F, HR 102, BP 138/82, RR 24, SpO2 91% on RA.
General: Ill-appearing male in mild respiratory distress.
Lungs: Bilateral rhonchi, decreased breath sounds at right base,
dullness to percussion at right base.
Heart: Tachycardic, regular rhythm, no murmurs.
Extremities: No edema, no cyanosis.

HOSPITAL COURSE:
Patient was admitted to the medical floor with a diagnosis of
community-acquired pneumonia. He was started on IV ceftriaxone
and azithromycin. Blood cultures were obtained and showed no
growth. Chest X-ray confirmed right lower lobe consolidation.
By day 3, the patient was afebrile and oxygen requirements had
decreased. He was transitioned to oral antibiotics on day 4.

PHYSICAL EXAMINATION AT DISCHARGE:
Vitals: T 98.6F, HR 78, BP 132/76, RR 16, SpO2 96% on RA.
General: Alert, comfortable, in no distress.
Lungs: Clear to auscultation bilaterally, no rhonchi or wheezing.
Heart: Regular rate and rhythm, no murmurs.
Extremities: No edema.

DISCHARGE MEDICATIONS:
1. Amoxicillin-clavulanate 875mg PO BID x 5 days
2. Lisinopril 20mg PO daily (continued from home)
3. Tiotropium 18mcg INH daily (continued from home)
4. Albuterol inhaler PRN (continued from home)

DISCHARGE INSTRUCTIONS:
Follow up with your primary care physician within 7 days.
Return to the emergency department if you develop worsening
shortness of breath, fever above 101F, or chest pain.
Complete the full course of antibiotics.
"""

# A trickier document for testing edge cases
TRICKY_SUMMARY = """
CHIEF COMPLAINT: Chest pain radiating to left arm.

HISTORY OF PRESENT ILLNESS:
A 55-year-old female presents with acute onset chest pain.

PHYSICAL EXAMINATION:
On admission: BP 160/95, HR 110, diaphoretic, anxious.
Cardiac: S1, S2 present, no S3 or S4, no murmurs.
Lungs: Clear bilaterally.
On discharge: BP 128/78, HR 72, comfortable at rest.
Cardiac: Regular rate and rhythm, no murmurs.
Lungs: Clear bilaterally.

HOSPITAL COURSE:
Troponin negative x3. Stress test negative. Discharged with
diagnosis of musculoskeletal chest pain.

MEDICATIONS: Ibuprofen 400mg PO TID PRN
"""


# =============================================================
# SECTION DETECTOR: Line-by-line scanning with regex headers
# =============================================================

def detect_sections(document, verbose=False):
    """
    Detect sections in a clinical document using regex-based
    header detection and line-by-line scanning.

    Headers are identified as lines that:
    - Start with one or more uppercase words
    - End with a colon (possibly followed by inline content)
    - May have optional leading whitespace

    Stop condition: a section ends when the next header is found.

    Args:
        document: multi-line string of clinical text
        verbose: if True, print a trace of each line's classification

    Returns:
        dict mapping section header names to their content (str)
    """
    header_pattern = re.compile(r'^\s*([A-Z][A-Z \t]+):\s*(.*)')

    sections = {}
    current_section = None
    current_lines = []

    lines = document.strip().split('\n')

    for i, line in enumerate(lines):
        match = header_pattern.match(line)

        if match:
            # Stop condition: save the previous section
            if current_section is not None:
                sections[current_section] = '\n'.join(current_lines).strip()

            # Start a new section
            current_section = match.group(1).strip()
            inline_content = match.group(2).strip()
            current_lines = [inline_content] if inline_content else []

            if verbose:
                print(f"Line {i+1:3d}: [HEADER] '{current_section}'")
        else:
            # Accumulate content into the current section
            current_lines.append(line)

            if verbose:
                section_label = current_section if current_section else "(preamble)"
                print(f"Line {i+1:3d}: [CONTENT] -> {section_label}")

    # Save the last section
    if current_section is not None:
        sections[current_section] = '\n'.join(current_lines).strip()

    return sections


# =============================================================
# PHYSICAL EXAM EXTRACTOR
# =============================================================

def extract_physical_exam(sections):
    """
    Extract physical exam sections from detected sections.

    Uses flexible matching to find headers containing
    'PHYSICAL EXAM' and classifies them as admission or
    discharge exams based on keywords in the header.

    Args:
        sections: dict from detect_sections()

    Returns:
        dict with keys 'admission_exam' and 'discharge_exam',
        values are the section content or None if not found
    """
    result = {
        'admission_exam': None,
        'discharge_exam': None,
    }

    admission_keywords = ['ADMISSION', 'ADMIT', 'INITIAL', 'PRESENTING']
    discharge_keywords = ['DISCHARGE', 'DISMISSAL', 'FINAL']

    for header, content in sections.items():
        if 'PHYSICAL EXAM' in header or header == 'PE':
            # Check if header indicates admission or discharge
            if any(kw in header for kw in admission_keywords):
                result['admission_exam'] = content
            elif any(kw in header for kw in discharge_keywords):
                result['discharge_exam'] = content
            else:
                # Ambiguous single exam section - store as admission
                # (Secret Mission improves on this logic)
                result['admission_exam'] = content

    return result


# =============================================================
# MAIN: Run the detector and display results
# =============================================================

if __name__ == '__main__':
    print("=" * 60)
    print("CLINICAL SECTION DETECTOR")
    print("=" * 60)
    print()

    # Step 1: Detect all sections with verbose trace
    print("--- Detection Trace (first 20 lines) ---")
    print()
    sections = detect_sections(DISCHARGE_SUMMARY, verbose=True)

    print()
    print("--- Detected Sections ---")
    print()
    for header in sections:
        content_preview = sections[header][:60].replace('\n', ' ')
        print(f"  [{header}]: {content_preview}...")

    print()
    print("--- Physical Exam Extraction ---")
    print()

    exams = extract_physical_exam(sections)

    print("ADMISSION EXAM:")
    print(exams['admission_exam'] if exams['admission_exam'] else "  (not found)")
    print()
    print("DISCHARGE EXAM:")
    print(exams['discharge_exam'] if exams['discharge_exam'] else "  (not found)")
    print()

    # Clinical significance
    print("--- Clinical Significance ---")
    print()
    print("Why does distinguishing admission vs discharge exam matter?")
    print()
    print("  ADMISSION EXAM (baseline):")
    print("    Records the patient's condition at hospital entry.")
    print("    Example: 'Bilateral rhonchi, decreased breath sounds'")
    print()
    print("  DISCHARGE EXAM (outcome):")
    print("    Documents improvement (or change) after treatment.")
    print("    Example: 'Clear to auscultation bilaterally'")
    print()
    print("  Comparing them proves treatment effectiveness,")
    print("  justifies the hospital stay for billing, and guides")
    print("  the follow-up provider on what to monitor.")

Secret mission

Distinguish Admission vs. Discharge Exams Automatically

Your current extract_physical_exam() function only works when exam sections are explicitly labeled in the header. What happens when a document has a single PHYSICAL EXAMINATION section containing both admission and discharge findings? Can you build a smarter classifier that handles ambiguous cases?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

This project runs entirely locally using Python's built-in re module, so there are no ongoing costs.

Resources you used:

  • The clinical-section-detector project folder on your Desktop containing section_detector.py.

Keep everything running

No action needed. Choose this if you're still actively building or want to experiment with the section detector further.

  • Your clinical-section-detector folder stays on your Desktop.
  • Python remains installed on your machine for future projects.

Pause - I'll come back to this later

Since this project is a single Python file with no running processes, there is nothing to pause. Your files will be waiting for you when you come back.

  • Your clinical-section-detector folder stays on your Desktop with no resource usage.

Delete - I don't want to use this again

Remove the project folder entirely to start fresh.

  • Delete the project folder by running this command in your terminal (macOS/Linux):
rm -rf ~/Desktop/clinical-section-detector
  • On Windows, run this in PowerShell instead:
Remove-Item -Recurse -Force ~\Desktop\clinical-section-detector
  • Confirm the folder is gone by running ls ~/Desktop (macOS/Linux) or ls ~\Desktop (Windows). You should no longer see clinical-section-detector in the output.

Nice Work!

Nice Work!

Nice work! You just built a clinical section detector that parses discharge summaries using the same foundational technique that real clinical NLP pipelines rely on.

You've learned how to:

  • Build a line-by-line scanner using Python's re module to detect section headers via regex pattern matching.
  • Apply stop conditions to determine where one section ends and the next begins in a structured clinical document.
  • Extract targeted physical exam sections from a discharge summary and understand why distinguishing admission from discharge exams matters for patient care, billing, and follow-up.
  • Secret Mission: Built an enhanced classify_exam_sections() function that distinguishes admission from discharge exams using explicit labels, positional heuristics, and internal sub-header splitting.

Ready to quiz yourself?