Skip to content

FHIRPath Python API

This guide covers using the FHIRPath evaluator as a Python library. Whether you're building a FHIR server, clinical decision support system, or data extraction pipeline, this API lets you evaluate FHIRPath expressions programmatically.

Quick Start

from fhirkit import evaluate_fhirpath

# Load a FHIR resource (dict or JSON)
patient = {
    "resourceType": "Patient",
    "id": "example",
    "name": [{"family": "Smith", "given": ["John", "William"]}],
    "gender": "male",
    "birthDate": "1985-06-15"
}

# Evaluate FHIRPath expressions
result = evaluate_fhirpath("Patient.name.given", patient)
print(result)  # ['John', 'William']

result = evaluate_fhirpath("Patient.gender", patient)
print(result)  # ['male']

result = evaluate_fhirpath("Patient.birthDate", patient)
print(result)  # ['1985-06-15']

Core Functions

evaluate_fhirpath(expression, resource, variables=None)

The main function for evaluating FHIRPath expressions.

Parameters:

Parameter Type Description
expression str The FHIRPath expression to evaluate
resource dict The FHIR resource (as a Python dict)
variables dict Optional variables available in the expression

Returns: list - The result collection (FHIRPath always returns collections)

Example:

from fhirkit import evaluate_fhirpath

observation = {
    "resourceType": "Observation",
    "status": "final",
    "code": {
        "coding": [{"system": "http://loinc.org", "code": "85354-9"}]
    },
    "valueQuantity": {"value": 120, "unit": "mmHg"}
}

# Simple navigation
status = evaluate_fhirpath("Observation.status", observation)
# Returns: ['final']

# Nested navigation
code = evaluate_fhirpath("Observation.code.coding.code", observation)
# Returns: ['85354-9']

# With filtering
loinc = evaluate_fhirpath(
    "Observation.code.coding.where(system = 'http://loinc.org').code",
    observation
)
# Returns: ['85354-9']

parse_fhirpath(expression)

Parses a FHIRPath expression and returns the AST (Abstract Syntax Tree).

Parameters:

Parameter Type Description
expression str The FHIRPath expression to parse

Returns: The parsed AST

Example:

from fhirkit import parse_fhirpath

# Parse an expression
ast = parse_fhirpath("Patient.name.given.first()")

# The AST can be inspected or used for analysis
print(type(ast))  # <class 'FHIRPathParser.ExpressionContext'>

Working with Results

FHIRPath always returns collections (lists). Here are common patterns:

Getting Single Values

from fhirkit import evaluate_fhirpath

patient = {"resourceType": "Patient", "gender": "male"}

# Results are always lists
result = evaluate_fhirpath("Patient.gender", patient)
print(result)  # ['male']

# Get single value safely
gender = result[0] if result else None
print(gender)  # 'male'

# Or use FHIRPath's single() function
result = evaluate_fhirpath("Patient.gender.single()", patient)
# Returns: ['male'] - but would error if multiple values

Checking Existence

from fhirkit import evaluate_fhirpath

patient = {
    "resourceType": "Patient",
    "name": [{"family": "Smith"}]
}

# Check if element exists
has_name = evaluate_fhirpath("Patient.name.exists()", patient)
print(has_name)  # [True]

# Check if empty
no_telecom = evaluate_fhirpath("Patient.telecom.empty()", patient)
print(no_telecom)  # [True]

# Count elements
count = evaluate_fhirpath("Patient.name.count()", patient)
print(count)  # [1]

Boolean Results

from fhirkit import evaluate_fhirpath

patient = {
    "resourceType": "Patient",
    "gender": "male",
    "active": True
}

# Comparisons return boolean
is_male = evaluate_fhirpath("Patient.gender = 'male'", patient)
print(is_male)  # [True]

# Combined conditions
result = evaluate_fhirpath(
    "Patient.gender = 'male' and Patient.active = true",
    patient
)
print(result)  # [True]

# Helper to get boolean
def get_bool(result):
    return result[0] if result else False

if get_bool(evaluate_fhirpath("Patient.active", patient)):
    print("Patient is active")

Filtering and Selection

Using where()

from fhirkit import evaluate_fhirpath

patient = {
    "resourceType": "Patient",
    "telecom": [
        {"system": "phone", "value": "555-1234", "use": "home"},
        {"system": "phone", "value": "555-5678", "use": "mobile"},
        {"system": "email", "value": "john@example.com"}
    ]
}

# Filter by condition
phones = evaluate_fhirpath(
    "Patient.telecom.where(system = 'phone').value",
    patient
)
print(phones)  # ['555-1234', '555-5678']

# Multiple conditions
mobile = evaluate_fhirpath(
    "Patient.telecom.where(system = 'phone' and use = 'mobile').value",
    patient
)
print(mobile)  # ['555-5678']

Using select()

from fhirkit import evaluate_fhirpath

patient = {
    "resourceType": "Patient",
    "name": [
        {"family": "Smith", "given": ["John", "William"]},
        {"family": "Jones", "given": ["Johnny"]}
    ]
}

# Project specific fields
families = evaluate_fhirpath("Patient.name.select(family)", patient)
print(families)  # ['Smith', 'Jones']

# Complex projections
formatted = evaluate_fhirpath(
    "Patient.name.select(given.first() & ' ' & family)",
    patient
)
print(formatted)  # ['John Smith', 'Johnny Jones']

Working with Different Resource Types

Patient

from fhirkit import evaluate_fhirpath

patient = {
    "resourceType": "Patient",
    "id": "12345",
    "identifier": [
        {"system": "http://hospital.org", "value": "MRN12345"}
    ],
    "name": [{"family": "Smith", "given": ["John"]}],
    "birthDate": "1985-06-15",
    "address": [
        {"city": "Boston", "state": "MA", "postalCode": "02101"}
    ]
}

# Get MRN
mrn = evaluate_fhirpath(
    "Patient.identifier.where(system = 'http://hospital.org').value",
    patient
)

# Get full name
name = evaluate_fhirpath(
    "Patient.name.first().given.first() & ' ' & Patient.name.first().family",
    patient
)

# Get formatted address
address = evaluate_fhirpath(
    "Patient.address.first().city & ', ' & Patient.address.first().state",
    patient
)

Observation (Vitals)

from fhirkit import evaluate_fhirpath

bp_observation = {
    "resourceType": "Observation",
    "status": "final",
    "code": {
        "coding": [{"system": "http://loinc.org", "code": "85354-9"}]
    },
    "component": [
        {
            "code": {"coding": [{"code": "8480-6"}]},
            "valueQuantity": {"value": 120, "unit": "mmHg"}
        },
        {
            "code": {"coding": [{"code": "8462-4"}]},
            "valueQuantity": {"value": 80, "unit": "mmHg"}
        }
    ]
}

# Get systolic (LOINC 8480-6)
systolic = evaluate_fhirpath(
    "Observation.component.where(code.coding.code = '8480-6').valueQuantity.value",
    bp_observation
)
print(systolic)  # [120]

# Get diastolic (LOINC 8462-4)
diastolic = evaluate_fhirpath(
    "Observation.component.where(code.coding.code = '8462-4').valueQuantity.value",
    bp_observation
)
print(diastolic)  # [80]

# Check if elevated
elevated = evaluate_fhirpath(
    "Observation.component.where(code.coding.code = '8480-6').valueQuantity.value > 140",
    bp_observation
)
print(elevated)  # [False]

Bundle Processing

from fhirkit import evaluate_fhirpath

bundle = {
    "resourceType": "Bundle",
    "type": "searchset",
    "entry": [
        {"resource": {"resourceType": "Patient", "id": "1", "gender": "male"}},
        {"resource": {"resourceType": "Patient", "id": "2", "gender": "female"}},
        {"resource": {"resourceType": "Observation", "id": "3", "status": "final"}}
    ]
}

# Get all resources
resources = evaluate_fhirpath("Bundle.entry.resource", bundle)
print(len(resources))  # 3

# Get only Patient resources
patients = evaluate_fhirpath(
    "Bundle.entry.resource.where(resourceType = 'Patient')",
    bundle
)
print(len(patients))  # 2

# Using ofType() - type-safe filtering
patients = evaluate_fhirpath(
    "Bundle.entry.resource.ofType(Patient)",
    bundle
)

Using Variables

Pass external variables into expressions:

from fhirkit import evaluate_fhirpath

patient = {
    "resourceType": "Patient",
    "birthDate": "1985-06-15"
}

# Using %variable syntax
variables = {"cutoffDate": "1990-01-01"}

result = evaluate_fhirpath(
    "Patient.birthDate < %cutoffDate",
    patient,
    variables=variables
)
print(result)  # [True]

String Manipulation

from fhirkit import evaluate_fhirpath

patient = {
    "resourceType": "Patient",
    "name": [{"family": "Smith", "given": ["John"]}],
    "telecom": [{"value": "+1-555-123-4567"}]
}

# Uppercase
upper = evaluate_fhirpath("Patient.name.family.upper()", patient)
print(upper)  # ['SMITH']

# String contains
has_area = evaluate_fhirpath(
    "Patient.telecom.value.contains('555')",
    patient
)
print(has_area)  # [True]

# Join names
full_name = evaluate_fhirpath(
    "Patient.name.given.join(' ')",
    patient
)
print(full_name)  # ['John']

# Replace characters
cleaned = evaluate_fhirpath(
    "Patient.telecom.value.replace('-', '')",
    patient
)
print(cleaned)  # ['+15551234567']

# Substring
area_code = evaluate_fhirpath(
    "Patient.telecom.value.substring(3, 3)",
    patient
)
print(area_code)  # ['555']

Math Operations

from fhirkit import evaluate_fhirpath

observation = {
    "resourceType": "Observation",
    "valueQuantity": {"value": 98.6}
}

# Basic arithmetic
result = evaluate_fhirpath("1 + 2 * 3", {})
print(result)  # [7]

# Using observation values
value = evaluate_fhirpath("Observation.valueQuantity.value", observation)
print(value)  # [98.6]

# Math functions
rounded = evaluate_fhirpath(
    "Observation.valueQuantity.value.round(1)",
    observation
)
print(rounded)  # [98.6]

# Ceiling/floor
ceil = evaluate_fhirpath("(3.2).ceiling()", {})
print(ceil)  # [4]

floor = evaluate_fhirpath("(3.8).floor()", {})
print(floor)  # [3]

Type Conversion

from fhirkit import evaluate_fhirpath

# String to integer
result = evaluate_fhirpath("'42'.toInteger()", {})
print(result)  # [42]

# Integer to string
result = evaluate_fhirpath("(42).toString()", {})
print(result)  # ['42']

# String to boolean
result = evaluate_fhirpath("'true'.toBoolean()", {})
print(result)  # [True]

# Check if conversion is possible
result = evaluate_fhirpath("'hello'.convertsToInteger()", {})
print(result)  # [False]

result = evaluate_fhirpath("'42'.convertsToInteger()", {})
print(result)  # [True]

Collection Operations

from fhirkit import evaluate_fhirpath

# Union
result = evaluate_fhirpath("(1 | 2 | 3) | (3 | 4 | 5)", {})
print(result)  # [1, 2, 3, 4, 5]

# Distinct
result = evaluate_fhirpath("(1 | 2 | 2 | 3 | 3).distinct()", {})
print(result)  # [1, 2, 3]

# Intersect
result = evaluate_fhirpath("(1 | 2 | 3).intersect(2 | 3 | 4)", {})
print(result)  # [2, 3]

# Exclude
result = evaluate_fhirpath("(1 | 2 | 3 | 4).exclude(2 | 4)", {})
print(result)  # [1, 3]

# Membership
result = evaluate_fhirpath("2 in (1 | 2 | 3)", {})
print(result)  # [True]

result = evaluate_fhirpath("(1 | 2 | 3) contains 2", {})
print(result)  # [True]

Date/Time Operations

from fhirkit import evaluate_fhirpath

# Date literals
result = evaluate_fhirpath("@2024-06-15", {})
print(result)  # [Date(2024, 6, 15)]

# DateTime literals
result = evaluate_fhirpath("@2024-06-15T10:30:00", {})
print(result)  # [DateTime(2024, 6, 15, 10, 30, 0)]

# Date comparison
patient = {"resourceType": "Patient", "birthDate": "1985-06-15"}

result = evaluate_fhirpath(
    "Patient.birthDate < @1990-01-01",
    patient
)
print(result)  # [True]

# Current date/time
result = evaluate_fhirpath("today()", {})
print(result)  # [<current date>]

result = evaluate_fhirpath("now()", {})
print(result)  # [<current datetime>]

# Extract date/time components
result = evaluate_fhirpath("@2024-06-15.year()", {})
print(result)  # [2024]

result = evaluate_fhirpath("@2024-06-15.month()", {})
print(result)  # [6]

result = evaluate_fhirpath("@2024-06-15.day()", {})
print(result)  # [15]

# Extract time components
result = evaluate_fhirpath("@2024-06-15T10:30:45.hour()", {})
print(result)  # [10]

result = evaluate_fhirpath("@2024-06-15T10:30:45.minute()", {})
print(result)  # [30]

result = evaluate_fhirpath("@2024-06-15T10:30:45.second()", {})
print(result)  # [45]

# From resource dates
patient = {"resourceType": "Patient", "birthDate": "1985-06-15"}
result = evaluate_fhirpath("Patient.birthDate.year()", patient)
print(result)  # [1985]

Error Handling

from fhirkit import evaluate_fhirpath, parse_fhirpath

# Parse errors
try:
    parse_fhirpath("Patient.name.where(")  # Invalid syntax
except Exception as e:
    print(f"Parse error: {e}")

# Evaluation with missing data returns empty
patient = {"resourceType": "Patient"}

result = evaluate_fhirpath("Patient.name.family", patient)
print(result)  # [] (empty, no error)

# single() on multiple values raises error
patient = {
    "resourceType": "Patient",
    "name": [{"family": "Smith"}, {"family": "Jones"}]
}

try:
    result = evaluate_fhirpath("Patient.name.single()", patient)
except Exception as e:
    print(f"Error: {e}")  # single() requires exactly one element

Best Practices

1. Always Handle Empty Results

from fhirkit import evaluate_fhirpath

def get_patient_gender(patient):
    result = evaluate_fhirpath("Patient.gender", patient)
    return result[0] if result else "unknown"

2. Use exists() for Conditional Logic

from fhirkit import evaluate_fhirpath

def has_email(patient):
    result = evaluate_fhirpath(
        "Patient.telecom.exists(system = 'email')",
        patient
    )
    return result[0] if result else False

3. Validate Before Complex Operations

from fhirkit import evaluate_fhirpath

def get_blood_pressure(observation):
    # First check it's a BP observation
    is_bp = evaluate_fhirpath(
        "Observation.code.coding.exists(code = '85354-9')",
        observation
    )
    if not (is_bp and is_bp[0]):
        return None

    systolic = evaluate_fhirpath(
        "Observation.component.where(code.coding.code = '8480-6').valueQuantity.value",
        observation
    )
    diastolic = evaluate_fhirpath(
        "Observation.component.where(code.coding.code = '8462-4').valueQuantity.value",
        observation
    )

    return {
        "systolic": systolic[0] if systolic else None,
        "diastolic": diastolic[0] if diastolic else None
    }

4. Cache Parsed Expressions for Performance

from fhirkit import parse_fhirpath, evaluate_fhirpath

# If evaluating the same expression many times,
# consider caching the parsed AST
# (Implementation detail - evaluate_fhirpath handles this internally)

Complete Example: Patient Data Extraction

from fhirkit import evaluate_fhirpath
import json

def extract_patient_summary(patient: dict) -> dict:
    """Extract key patient information using FHIRPath."""

    def get_value(expr, default=None):
        result = evaluate_fhirpath(expr, patient)
        return result[0] if result else default

    def get_list(expr):
        return evaluate_fhirpath(expr, patient)

    return {
        "id": get_value("Patient.id"),
        "name": get_value(
            "Patient.name.where(use = 'official').select("
            "given.first() & ' ' & family).first()",
            "Unknown"
        ),
        "gender": get_value("Patient.gender", "unknown"),
        "birthDate": get_value("Patient.birthDate"),
        "active": get_value("Patient.active", False),
        "phones": get_list(
            "Patient.telecom.where(system = 'phone').value"
        ),
        "email": get_value(
            "Patient.telecom.where(system = 'email').value.first()"
        ),
        "address": get_value(
            "Patient.address.first().select("
            "line.join(', ') & ', ' & city & ', ' & state & ' ' & postalCode"
            ").first()"
        ),
        "mrn": get_value(
            "Patient.identifier.where("
            "type.coding.code = 'MR'"
            ").value.first()"
        )
    }

# Usage
patient_json = '''
{
    "resourceType": "Patient",
    "id": "12345",
    "identifier": [
        {"type": {"coding": [{"code": "MR"}]}, "value": "MRN12345"}
    ],
    "name": [{"use": "official", "family": "Smith", "given": ["John", "William"]}],
    "gender": "male",
    "birthDate": "1985-06-15",
    "active": true,
    "telecom": [
        {"system": "phone", "value": "555-1234"},
        {"system": "email", "value": "john@example.com"}
    ],
    "address": [{"line": ["123 Main St"], "city": "Boston", "state": "MA", "postalCode": "02101"}]
}
'''

patient = json.loads(patient_json)
summary = extract_patient_summary(patient)
print(json.dumps(summary, indent=2))

Output:

{
  "id": "12345",
  "name": "John Smith",
  "gender": "male",
  "birthDate": "1985-06-15",
  "active": true,
  "phones": ["555-1234"],
  "email": "john@example.com",
  "address": "123 Main St, Boston, MA 02101",
  "mrn": "MRN12345"
}

See Also