FHIR searches and bundles

Query FHIR servers with parameters, operators and sorting - the client pages through the result bundles for you.

You can query FHIR servers with search parameters, operators, sorting and paging - the client builds the query URL and unwraps the results for you. A server answers every search with a Bundle, a container resource that holds the matches, the total number of them and links to further pages.

The resources method of a connection client returns a lazy search object. Each call below refines the query and the server receives nothing until you fetch the results.

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class FindPatients(Service):
    name = 'demo.fhir.find-patients'

    def handle(self) -> 'None':

        client = self.fhir['FHIR.Sample']

        # Everything about patients starts here
        patients = client.resources('Patient')

        # Active patients named Chalmers, at most 10 of them, youngest first
        result = patients.search(name='Chalmers', active=True).sort('-birthdate').limit(10)

        # Only now is the server invoked
        for patient in result.fetch():
            self.logger.info('Found %s', patient['name'])

Invoking the service writes each match to the server log:

INFO - Found [{'use': 'official', 'family': 'Chalmers', 'given': ['Peter', 'James']}]

Search operators

Search parameters accept operators appended with a double underscore, mirroring what the FHIR specification allows in URLs:

# Born in the year 2000 or later - birthdate=ge2000-01-01 on the wire
patients.search(birthdate__ge='2000-01-01')

# Name containing a string
patients.search(name__contains='alm')

# Status being none of the listed values
observations = client.resources('Observation')
observations.search(status__not=['cancelled', 'entered-in-error'])

Python-style underscores in parameter names are translated to FHIR-style dashes automatically, e.g. general_practitioner becomes general-practitioner.

Fetch the results

There are several ways to obtain the results, depending on how much data you expect:

# One page of results, as configured with .limit
found = result.fetch()

# All the results - the Bundle's next links are followed until there are no more pages
found = result.fetch_all()

# The same, but lazily - each page is fetched only when the iteration reaches it
for patient in result:
    ...

# A single match - raises an error if there are none or more than one
patient = patients.search(_id='511a6231-361e-4b8e-8f9c-b183b7813f4d').get()

# The first match or None
patient = patients.search(name='Chalmers').first()

# Only the number of matches, without any resources
how_many = patients.search(active=True).count()

Read the Bundle itself

To read the Bundle's own fields, e.g. its total or its links, use fetch_raw:

bundle = result.fetch_raw()

self.logger.info('Total matches: %s', bundle.total)

for entry in bundle.entry:
    self.logger.info('Entry: %s', entry.resource)

A single search can return related resources in the same Bundle, e.g. each Encounter together with the Patient it refers to, saving you the extra round trips:

# Encounters plus the patients they point at ..
encounters = client.resources('Encounter').include('Encounter', 'subject')

# .. or, the reverse, patients plus the observations that point back at them.
patients = client.resources('Patient').revinclude('Observation', 'subject')

Select fields to transfer

To reduce the amount of data transferred, ask the server for specific elements of each resource:

# Each returned Patient will contain only its name, plus the id and resourceType fields
patients.elements('name').fetch()

Conditional operations

Searches can also drive writes - the search parameters then act as the condition:

# Create the patient only if no resource matches the search
patient = client.resource('Patient', identifier=[{'system': 'urn:mrn', 'value': '12345'}])
search = client.resources('Patient').search(identifier='urn:mrn|12345')
patient, created = search.get_or_create(patient)

self.logger.info('Newly created? %s', created)

See also

PageWhat it covers
ResourcesCreate, read, update and delete any FHIR resource
Path accessValues extracted from search results in single calls
ConnectionsThe Dashboard form and everything self.fhir accepts

Learn more


Schedule a meaningful demo

Book a demo with an expert who will help you build meaningful systems that match your ambitions

"We evaluated 12 integration platforms and Zato was the only one to score 100%."

Philip Zuñiga, Assistant Professor, University of the Philippines