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:
SELECT DISTINCT P.* FROM PATIENT
JOIN DIAGNOSIS DX
ON DX.CODE = X
JOIN RESULTS R
ON R.CODE = Y
WHERE P.DOB <= EighteenYearsAgo
AND P.GENDER = 'M'
AND R.VALUE > Z
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
If it were SQL, what you would want to say is something like this:
SELECT DISTINCT P.* FROM PATIENT
JOIN DIAGNOSIS DX
ON DX.CODE = X
JOIN RESULTS R
ON R.CODE = Y
WHERE P.DOB <= EighteenYearsAgo
AND P.GENDER = 'M'
AND R.VALUE > Z
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.
Keith
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.
Keith
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
ReplyDeleteKeith, 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
ReplyDeleteHello Keith, thank you for the write up. Please, can you explain more on what you mean by "...search parameter extentions..."? How did you create "...patientCondition-code, patientObservation-code and patientObservation-value..."? I'm new to FHIR and this blog is close to what I'm working on.
ReplyDeleteHello Keith, thank you for the write up. Please, can you explain more on what you mean by "...search parameter extentions..."? How did you create "...patientCondition-code, patientObservation-code and patientObservation-value..."? I'm new to FHIR and this blog is close to what I'm working on.
ReplyDeleteSee https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html
Deletethank you, very much, especially for the quick response!
ReplyDelete