Unstructured Text is the Final Boss: Parsing Doctor's Notes with LLMs πŸ₯
wellally.techΒ·2dΒ·
Discuss: DEV
πŸ“šFactor
Preview
Report Post

Hey devs! πŸ‘‹

Let’s be honest. We all live in a bubble where we think data looks like this:

{
  "patient_id": 1024,
  "symptoms": ["headache", "nausea"],
  "severity": "moderate"

Hey devs! πŸ‘‹

Let’s be honest. We all live in a bubble where we think data looks like this:

{
  "patient_id": 1024,
  "symptoms": ["headache", "nausea"],
  "severity": "moderate",
  "is_critical": false
}

It’s beautiful. It’s parsable. It’s type-safe. 😍

But if you’ve ever worked in HealthTech (or scraped any legacy enterprise system), you know the reality is usually a terrifying block of free text written by a tired human at 3 AM.

I’ve been deep in the trenches lately trying to standardize clinical notes, and let me tell you: dealing with doctor's notes makes parsing HTML with Regex look like a vacation.

Here is why it's so hard, and how we can use RAG (Retrieval-Augmented Generation) and Fine-tuning to tame the chaos without letting the AI hallucinate a diagnosis. 🧠

The Reality Check: β€œPt c/o…”

Doctors don't write JSON. They write in a secret code of abbreviations, typos, and shorthand.

The "Data" actually looks like this:

"Pt 45yo m, c/o SOB x 2d. Denies CP. Hx of HTN, on lisinopril. Exam: wheezing b/l. Plan: nebs + steroids."

If you run a standard keyword search for "High Blood Pressure," you might miss this record entirely because the doctor wrote "HTN" (Hypertension).

If you search for "Pain," you might get a false positive because the note says "Denies CP" (Chest Pain).

Traditional NLP (Natural Language Processing) struggles here because context is everything. "SOB" means "Shortness of Breath" in a hospital, but something very different in a Reddit comment section. πŸ˜‚

The Hallucination Trap πŸ‘»

So, the modern solution is: "Just throw it into ChatGPT/LLM, right?"

Well... yes and no.

If you ask a generic LLM to "Summarize this patient's status," it does a great job until it doesn't. The biggest risk in medical AI is Hallucination.

I saw a model once take a note about a patient having a "family history of diabetes" and output a structured JSON saying the patient currently has diabetes.

Big yikes. In healthcare, that kind of error is unacceptable.

The Fix: The RAG + Fine-Tuning Sandwich πŸ₯ͺ

To make this queryable (i.e., "Show me all patients with respiratory issues") without the AI lying to us, we need a strict pipeline.

Here is the architecture that actually works:

  1. Fine-Tuning (Teaching the Language)

You can't rely on gpt-3.5-turbo out of the box for niche specialties. You often need to fine-tune a smaller model (like Llama 3 or Mistral) on medical texts.

This teaches the model that bid means "twice a day" (bis in die), not an auction offer.

  1. Structured Extraction (The Translator)

Don't ask the LLM to "chat." Ask it to extract. We force the LLM to output a specific Schema using tools like Pydantic or Instructor.

Here is a Python snippet using the instructor library (which patches OpenAI) to force structure out of chaos:

import instructor
from pydantic import BaseModel, Field
from openai import OpenAI

# Define the structure we WANT (The Dream) class ClinicalNote(BaseModel): patient_age: int symptoms: list[str] = Field(description=β€œList of physical complaints”) medications: list[str] diagnosis_confirmed: bool = Field(description=β€œIs the diagnosis final or just suspected?”)

client = instructor.patch(OpenAI())

text_blob = β€œPt 45yo m, c/o SOB x 2d. Denies CP. Hx of HTN, on lisinopril.”

resp = client.chat.completions.create( model=β€œgpt-4”, response_model=ClinicalNote, messages=[ {β€œrole”: β€œsystem”, β€œcontent”: β€œYou are a medical scribe. Extract data accurately.”}, {β€œrole”: β€œuser”, β€œcontent”: text_blob}, ], )

print(resp.model_dump_json(indent=2))

The Output:

{
  "patient_age": 45,
  "symptoms": ["Shortness of Breath"],
  "medications": ["lisinopril"],
  "diagnosis_confirmed": false
}

Boom. Now we have SQL-queryable data! πŸš€

  1. RAG for Verification (The Guardrail)

Even with extraction, how do we trust it?
We use RAG. We embed the doctor's notes into a Vector Database (like Pinecone or Weaviate).

When a user asks, "Does this patient have heart issues?", the system:

  1. Retrieves the specific chunk of text mentioning "Denies CP" and "Hx of HTN".
  2. Feeds only that chunk to the LLM.
  3. Cites the source.

If the AI can't find a source chunk in the vector DB, it is programmed to say "I don't know" rather than guessing.

Conclusion

Standardizing free text is painful, but it's the only way to unlock the value in medical records. We have to move away from "magic black box" AI and toward Structured AI Pipelinesβ€”where we validate inputs, enforce JSON schemas, and ground everything in retrieved context.

It’s messy work, but someone’s gotta do it! πŸ’» ✨

Want to go deeper?

I’ve been writing a lot more about system architecture, dealing with messy data, and the weird side of software engineering.

πŸ‘‰ Check out my personal blog for the deep dives: wellally.tech/blog

Similar Posts

Loading similar posts...