A Reusable Search Control for FHIR

This post has been republished via RSS; it originally appeared at: Microsoft Tech Community - Latest Blogs - .

 

Searching a FHIR resource is simple.  But what if you want to create a highly reusable search form?  In this article we walk through a highly reusable search control.

FHIR Search Scenario

Let’s say we have a list of patients.  Our app displays patients in a table (first 3 rows displayed below):

ID

Name

Date of Birth

0001

Joe Patient

01/13/81

0002

Jane Patient

02/13/80

 

If we have 1000 patients, we might want to allow users to search by ID and Name.

First Attempt

In the past we were tempted to create custom Blazor forms like:

SameerDoshi_0-1639695945263.png

 

Then on submit of this form we could assemble a search criteria.

 

 

 public async Task<IList<Patient>> SearchPatient(string givenName, string familyName, string identifier )
        {
            Bundle bundle;

            if (!string.IsNullOrEmpty(identifier))
            {
                bundle = await _fhirClient.SearchByIdAsync<Patient>(identifier);

                if (bundle != null)
                    return bundle.Entry.Select(p => (Patient)p.Resource).ToList();
            }

            if (!string.IsNullOrEmpty(familyName))
            {
                bundle = await _fhirClient.SearchAsync<Patient>(criteria: new[] { $"family:contains={familyName}" });

                if (bundle != null)
                    return bundle.Entry.Select(p => (Patient)p.Resource).ToList();
            }

            return await GetPatientsAsync();           
        }

 

 

 

Great!  But there’s trouble ahead as soon as we start adding more search criteria.

A Growing Form

Now let’s say we want to support searching by birth date.  Our first thought might be to add another field and end up with a form like this.

SameerDoshi_1-1639695945267.png

 

Our form is quickly growing.   And so is our submit code.

 

 

 public async Task<IList<Patient>> SearchPatient(string givenName, string familyName, string identifier, string birthdate )
        {
            Bundle bundle;

            if (!string.IsNullOrEmpty(identifier))
            {
                bundle = await _fhirClient.SearchByIdAsync<Patient>(identifier);

                if (bundle != null)
                    return bundle.Entry.Select(p => (Patient)p.Resource).ToList();
            }

            if (!string.IsNullOrEmpty(familyName))
            {
                bundle = await _fhirClient.SearchAsync<Patient>(criteria: new[] { $"family:contains={familyName}dateofbirth:contains{birthdate}" });

                if (bundle != null)
                    return bundle.Entry.Select(p => (Patient)p.Resource).ToList();
            }

            return await GetPatientsAsync();           
        }

 

 

Now what happens if we want to add search by Telephone, address, or by anything in the Patient object?   Our form is going to get longer and longer.  Pretty soon it’s going to be gigantic!  Even worse we can’t reuse this component for searching other FHIR resources.  For example, our search for Questionnaires will look 100% different!   After all Questionnaires don’t have Family Names, or Birthdates.

A better approach

Instead, we started to use a much simpler form:

SameerDoshi_2-1639695945267.png

 

The code for this component is similarly simple:

 

 

private async Task<IList<Patient>> SearchPatient(IDictionary<string, string> searchParameters)
        {
            var searchResults = new List<Patient>();
            IList<string> filterStrings = new List<string>();
            foreach (var parameter in searchParameters)
            {
                if (!string.IsNullOrEmpty(parameter.Value))
                {
                    filterStrings.Add($"{parameter.Key}:contains={parameter.Value}");
                }
            }
            Bundle bundle = await _fhirService.SearchAsync<Patient>(criteria: filterStrings.ToArray<string>());

            if (bundle != null)
            {
                searchResults = bundle.Entry.Select(p => (Patient)p.Resource).ToList();
            }
            return searchResults;
        }

 

 

But the magic is that each Resource that uses the component uses a method like this to build search criteria:

 

So now using that simple component our users can search for id like this:
Id:0001

Search for Birthdate like this:
birthdate:01/26/82

Or do a combined search:
Id:0001 birthdate:01/26/82

 

And for Questionnaires we can use the same component and search for a Questionnaire with complex search terms like:

Name:health description:nutrition

 

See it in Action

View our implementation of this component in FHIR Blaze- our sample FHIR + Blazor app.

Caveats

Ux: Though we have a simplified form- it’s no longer clear what available search fields.  How is your user supposed to know that “family:” is how one searches for last name?   To resolve this, we recommend visual hints or auto complete. For example, below the text box you could include simple text explaining the most likely search terms as well as linking to a document with all the terms defined.

Performance: Since our search routine uses “contains” our search uses much more Rus than if we included an exact search term.  Consider including keyword to allow exact searches. 

Spaces:  Our search routine does a simple split.  This means we can’t search for anything with a space (ex: a family name of “De Luis”.   Consider modifying our sample code to more intelligently split search terms.

Cancelling search:  Our code assumes submitting the search with no terms is to cancel the search.  This isn’t apparent and might be confusing.

REMEMBER: these articles are REPUBLISHED. Your best bet to get a reply is to follow the link at the top of the post to the ORIGINAL post! BUT you're more than welcome to start discussions here:

This site uses Akismet to reduce spam. Learn how your comment data is processed.