Tuesday, June 7, 2016

Selecting Population Cohorts in FHIR using extension search parameters

The basic idea is pretty straightforward, right?  What you want to be able to do is identify a set of patients which meet a particular criteria.  Give me all 18 year-old or older men with a diagnosis of X, and a lab test Y where the result is > Z units.  The question is how to express it in FHIR.

If it were SQL, what you would want to say is something like this:

WHERE P.DOB <= EighteenYearsAgo 

Of course, I just made that database schema up, but the reality is that it probably in not all that far from what you already know.  However, we don't write FHIR queries in SQL, so how do we say this?

First of all, the resource we want to return is Patient, so the first thing we know is that we will start with:

GET [base]/Patient?

Because that will return patients.  If we had wanted observation, we'd start with
GET [base]/Observation?

Next, we can add the basic parameters on gender and age to the query.  These will be AND'd together, giving us:

GET [base]/Patient?gender=male&birthdate=ltEighteenYearsAgo

But the next bits are tricky.  In fact, not really possible in FHIR.  I can find those Conditions where the code is X, and I can find the results where the code is Y and the value is > Z, and I can even get the patients associated with each.
But I cannot get the patients AND'd with these two JOINs.

For example, finding the patients in addition to the conditions where condition.code = X:
GET [base]/Condition?code=X&_include=Condition:patient

And finding results where the code is Y and the value is > Z
GET [base]/Observation?code=Y&value-quantity=gtZ&_include=Observation:patient

But as I look at it, _include isn't a JOIN, instead it is a query with multiple results. Chaining doesn't cut it either.  It supports joins in the forward case, but I want the reverse.  I'm thinking about a syntax something like this:
GET [base]/Patient?gender=male&birthdate=ltEighteenYearsAgo&_join=Observation:patient,Condition:patient&Condition.code=X&Observation.code=Y&Observation.value=gtZ

In the above, the _join argument establishes the join critieria, Observation's patient search  path has to reference the found patient, and the same with Condition's patient search path. The reality is we could probably automatically figure out the join criteria in this case, since there's only one thing in Condition or Observation that would really point to a patient. So, Observation:patient, and Condition:patient are (or could be) implied.  I changed the search parameters a little bit and what I get is:
GET [base]/Patient?gender=male&birthdate=ltEighteenYearsAgo&patientCondition-code=X&patientObservation-code=Y&patientObservation-value=gtZ

Now I have three search parameter extentions: patientCondition-code, patientObservation-code and patientObservation-value which I can use in my profile for patient, and with these, I can do the necessary joins in my happy little server.

I might prefer a little less verbose syntax for these parameters (e.g., condition-code rather than patientCondition-code), but for the most part, the principles are still the same.



  1. Keith, you should connect up with the discussion about reverse chaining at chat.fhir.org: https://chat.fhir.org/#narrow/stream/implementers/subject/_has.20parameter.20proposal

  2. Keith, now multiple your process across thousands or tens of thousands of FHIR servers to search for the information you are looking for. Great in the small, but really funny to scale. Dean