Stop Digging Through PDFs: Build a FHIR-Standard EHR Knowledge Base with RAG A developer built a Retrieval-Augmented Generation (RAG) pipeline to convert unstructured medical PDFs into a structured, queryable Electronic Health Record (EHR) knowledge base using FHIR standards. The system leverages Unstructured.io for document parsing, Milvus for vector storage, and DuckDB for structured data, enabling natural language queries and trend analysis. The approach addresses common challenges in healthcare data engineering, such as messy formats and siloed information. We’ve all been there: staring at a stack of printed lab results or a folder full of cryptic report final v2 NEW.pdf files, trying to remember if our cholesterol was higher or lower two years ago. For developers, this isn't just a filing problem—it's a data engineering challenge. In the world of healthcare, data is messy, siloed, and often locked in "unstructured" formats. To build a truly personal Electronic Health Record EHR system, we need more than just a folder; we need a RAG Retrieval-Augmented Generation pipeline that can parse PDFs, map them to the FHIR Fast Healthcare Interoperability Resources standard, and provide natural language insights. In this guide, we’ll leverage Unstructured.io , Milvus , and DuckDB to turn chaotic medical PDFs into a queryable, structured knowledge base. Before we dive into the code, let’s look at how the data flows from a messy lab report to a structured answer. php graph TD A Unstructured PDF Reports -- B Unstructured.io Partitioning B -- C{Data Split} C -- |Textual Context| D Milvus Vector DB C -- |Tabular Data| E DuckDB Structured Storage D -- F LangChain RAG Engine E -- F G User Query: Is my glucose trending up? -- F F -- H FHIR-Formatted Response Make sure you have your environment ready: pip install langchain milvus unstructured pdf duckdb openai Medical PDFs often contain complex tables. Standard PDF parsers usually fail here. We’ll use unstructured to partition the document into logical elements. python from unstructured.partition.pdf import partition pdf Extract elements from a medical lab report elements = partition pdf filename="lab report 2023.pdf", infer table structure=True, chunking strategy="by title", max characters=1000, new after n chars=800, Separate tables from narrative text tables = el for el in elements if el.category == "Table" texts = el for el in elements if el.category == "NarrativeText" print f"Detected {len tables } tables and {len texts } text blocks." To perform a semantic search e.g., "Find all reports related to cardiovascular health" , we need to store the text chunks in Milvus . python from langchain community.vectorstores import Milvus from langchain openai import OpenAIEmbeddings embeddings = OpenAIEmbeddings Initialize Milvus with our extracted text vector db = Milvus.from documents documents=texts, embedding=embeddings, connection args={"host": "127.0.0.1", "port": "19530"}, collection name="personal ehr knowledge" Test a similarity search docs = vector db.similarity search "How was my blood sugar in 2022?" Vector search is great for context, but for trends like tracking glucose over 5 years , we need structured data. We will map our extracted tables into a simplified FHIR "Observation" schema and store it in DuckDB . python import duckdb import pandas as pd Mocking the mapping of a table element to a FHIR-like DataFrame In a real scenario, use an LLM to parse the table.text into these columns data = { "resourceType": "Observation", "code": "4548-4", "display": "Hba1c", "value": 5.7, "unit": "%", "effectiveDateTime": "2023-10-15" } df = pd.DataFrame data Connect to DuckDB and create a structured health table con = duckdb.connect "health records.db" con.execute "CREATE TABLE IF NOT EXISTS observations AS SELECT FROM df" Query trends instantly trend = con.execute "SELECT AVG value FROM observations WHERE display='Hba1c'" .fetchone print f"Average HbA1c: {trend 0 }%" Building a basic RAG is easy, but building a production-ready healthcare agent is hard. You need to handle HIPAA compliance, complex data lineage, and advanced prompt engineering to ensure the LLM doesn't hallucinate medical advice. For deeper insights into building robust data pipelines and production-grade AI systems, I highly recommend checking out the WellAlly Tech Blog . They have some incredible deep dives on advanced RAG patterns and handling highly sensitive unstructured data that helped me refine this architecture 🥑 Finally, we wrap everything into a LangChain retrieval sequence that uses both the Vector DB for context and DuckDB for stats . python from langchain.chains import ConversationalRetrievalChain from langchain openai import ChatOpenAI llm = ChatOpenAI model name="gpt-4o", temperature=0 The RAG sequence qa chain = ConversationalRetrievalChain.from llm llm=llm, retriever=vector db.as retriever , return source documents=True query = "Compare my last two blood tests. Are there any concerning trends?" result = qa chain.invoke {"question": query, "chat history": } print f"Response: {result 'answer' }" By combining Unstructured.io for ingestion, Milvus for semantic memory, and DuckDB for analytical precision, we've moved beyond simple PDF storage. This system doesn't just "read" your records; it "understands" them within the context of the FHIR standard . Next Steps: What are you building with RAG lately? Have you tried parsing medical data? Let's discuss in the comments 👇