Skip to content

Commit 8b682b4

Browse files
committed
feat: filter observations by patient identifier
1 parent e870031 commit 8b682b4

File tree

4 files changed

+52
-26
lines changed

4 files changed

+52
-26
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,11 +284,20 @@ code=4AWKhgaaomTSf9PfwxN4ExnXjdSEqh&grant_type=authorization_code&redirect_uri=h
284284
#### Observations
285285

286286
- The `FHIR Observation` endpoint returns a list of Observations as a FHIR Bundle
287-
- At least one of Study ID, passed as `patient._has:Group:member:_id` or Patient ID, passed as `patient` query parameters are required
287+
- At least one of Study ID, passed as `patient._has:Group:member:_id` or Patient ID, passed as `patient` or Patient Identifier passed as `patient.identifier=<system>|<value>` query parameters are required
288288
- `subject.reference` references a Patient ID
289289
- `device.reference` references a Data Source ID
290290
- `valueAttachment` is Base 64 Encoded Binary JSON
291291

292+
| Query Parameter | Example | Description |
293+
| ----------------------- | ----------------------------------------------------- | ------------------------------------------------------------ |
294+
| `_has:Group:member:_id` | `30001` | Filter by Patients that are in the Study with ID 30001 |
295+
| `patient` | `40001` | Filter by single Patient with ID 40001 |
296+
| `patient.identifier` | `http://ehr.example.com|abc123` | Filter by single Patient with Identifier System `http://ehr.example.com` and Value `abc123` |
297+
| `code` | `https://w3id.org/openmhealth|omh:blood-pressure:4.0` | Filter by Type/Scope with System `https://w3id.org/openmhealth` and Code `omh:blood-pressure:4.0` |
298+
299+
300+
292301
```json
293302
// GET /fhir/r5/Observation?patient._has:Group:member:_id=30001&patient=40001&code=https://w3id.org/openmhealth|omh:blood-pressure:4.0
294303
{

jhe/core/models.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,9 @@ def for_practitioner_organization_study(practitioner_user_id, organization_id=No
234234
if study_id:
235235
study_sql_where = "AND core_study.id={study_id}".format(study_id=int(study_id))
236236

237-
patient_sql_where = ''
237+
patient_id_sql_where = ''
238238
if patient_id:
239-
patient_sql_where = "AND core_patient.id={patient_id}".format(patient_id=int(patient_id))
239+
patient_id_sql_where = "AND core_patient.id={patient_id}".format(patient_id=int(patient_id))
240240

241241

242242
q = """
@@ -249,11 +249,11 @@ def for_practitioner_organization_study(practitioner_user_id, organization_id=No
249249
WHERE core_jheuserorganization.jhe_user_id=%(jhe_user_id)s
250250
{organization_sql_where}
251251
{study_sql_where}
252-
{patient_sql_where}
252+
{patient_id_sql_where}
253253
""".format(
254254
organization_sql_where=organization_sql_where,
255255
study_sql_where=study_sql_where,
256-
patient_sql_where=patient_sql_where,
256+
patient_id_sql_where=patient_id_sql_where,
257257
)
258258

259259
return Patient.objects.raw(q, {'jhe_user_id': practitioner_user_id})
@@ -657,9 +657,9 @@ def for_practitioner_organization_study_patient(practitioner_user_id, organizati
657657
if study_id:
658658
study_sql_where = "AND core_study.id={study_id}".format(study_id=int(study_id))
659659

660-
patient_sql_where = ''
660+
patient_id_sql_where = ''
661661
if patient_id:
662-
patient_sql_where = "AND core_patient.id={patient_id}".format(patient_id=int(patient_id))
662+
patient_id_sql_where = "AND core_patient.id={patient_id}".format(patient_id=int(patient_id))
663663

664664
observation_sql_where = ''
665665
if observation_id:
@@ -684,14 +684,14 @@ def for_practitioner_organization_study_patient(practitioner_user_id, organizati
684684
WHERE core_jheuserorganization.jhe_user_id=%(jhe_user_id)s
685685
{organization_sql_where}
686686
{study_sql_where}
687-
{patient_sql_where}
687+
{patient_id_sql_where}
688688
{observation_sql_where}
689689
ORDER BY core_observation.last_updated DESC
690690
LIMIT %(pageSize)s OFFSET %(offset)s;
691691
""".format(
692692
organization_sql_where=organization_sql_where,
693693
study_sql_where=study_sql_where,
694-
patient_sql_where=patient_sql_where,
694+
patient_id_sql_where=patient_id_sql_where,
695695
observation_sql_where=observation_sql_where,
696696
pageSize=pageSize,
697697
offset=offset,
@@ -710,25 +710,29 @@ def practitioner_authorized(practitioner_user_id, observation_id):
710710
return True
711711

712712
@staticmethod
713-
def fhir_search(practitioner_user_id, study_id=None, patient_id=None, coding_system=None, coding_code=None, observation_id=None, offset=None, page=None):
713+
def fhir_search(practitioner_user_id, study_id=None, patient_id=None, patient_identifier_system=None, patient_identifier_value=None, coding_system=None, coding_code=None, observation_id=None, offset=None, page=None):
714714
from core.serializers import FHIRObservationSerializer
715715

716716
# Explicitly cast to ints so no injection vulnerability
717717
study_sql_where = ''
718718
if study_id:
719719
study_sql_where = "AND core_study.id={study_id}".format(study_id=int(study_id))
720720

721-
patient_sql_where = ''
721+
patient_id_sql_where = ''
722722
if patient_id:
723-
patient_sql_where = "AND core_patient.id={patient_id}".format(patient_id=int(patient_id))
723+
patient_id_sql_where = "AND core_patient.id={patient_id}".format(patient_id=int(patient_id))
724+
725+
patient_identifier_value_sql_where = ''
726+
if patient_identifier_value:
727+
patient_identifier_value_sql_where = "AND core_patient.identifier=%(patient_identifier_value)s"
724728

725729
observation_sql_where = ''
726730
if observation_id:
727731
observation_sql_where = "AND core_observation.id={observation_id}".format(observation_id=int(observation_id))
728732

729733
# Set default values for pagination parameters
730734
offset = 0 if offset is None else int(offset)
731-
limit = 10 if page is None else int(page)
735+
limit = 1000 if page is None else int(page)
732736

733737
# TBD: Query optimization: https://stackoverflow.com/a/6037376
734738
# pagination: https://github.com/mattbuck85/django-paginator-rawqueryset
@@ -777,10 +781,11 @@ def fhir_search(practitioner_user_id, study_id=None, patient_id=None, coding_sys
777781
LEFT JOIN core_study ON core_study.id=core_studypatient.study_id
778782
JOIN core_organization ON core_organization.id=core_patient.organization_id
779783
JOIN core_jheuserorganization ON core_jheuserorganization.organization_id=core_organization.id
780-
WHERE core_jheuserorganization.jhe_user_id={jhe_user_id} AND
781-
core_codeableconcept.coding_system LIKE %(coding_system)s AND core_codeableconcept.coding_code LIKE %(coding_code)s
784+
WHERE core_jheuserorganization.jhe_user_id={jhe_user_id}
785+
AND core_codeableconcept.coding_system LIKE %(coding_system)s AND core_codeableconcept.coding_code LIKE %(coding_code)s
782786
{study_sql_where}
783-
{patient_sql_where}
787+
{patient_id_sql_where}
788+
{patient_identifier_value_sql_where}
784789
{observation_sql_where}
785790
GROUP BY core_observation.id, core_codeableconcept.coding_system, core_codeableconcept.coding_code
786791
ORDER BY core_observation.last_updated DESC
@@ -790,15 +795,17 @@ def fhir_search(practitioner_user_id, study_id=None, patient_id=None, coding_sys
790795
SITE_URL=settings.SITE_URL,
791796
jhe_user_id=practitioner_user_id,
792797
study_sql_where=study_sql_where,
793-
patient_sql_where=patient_sql_where,
798+
patient_id_sql_where=patient_id_sql_where,
799+
patient_identifier_value_sql_where=patient_identifier_value_sql_where,
794800
observation_sql_where=observation_sql_where,
795801
limit=limit,
796802
offset=offset
797803
)
798804

799805
records = Observation.objects.raw(q, {
800806
"coding_system": coding_system if coding_system else '%',
801-
"coding_code": coding_code if coding_code else '%'
807+
"coding_code": coding_code if coding_code else '%',
808+
"patient_identifier_value": patient_identifier_value
802809
})
803810

804811
for record in records:
@@ -832,9 +839,9 @@ def count_for_practitioner_organization_study_patient(practitioner_user_id, orga
832839
if study_id:
833840
study_sql_where = f"AND core_study.id={int(study_id)}"
834841

835-
patient_sql_where = ''
842+
patient_id_sql_where = ''
836843
if patient_id:
837-
patient_sql_where = f"AND core_patient.id={int(patient_id)}"
844+
patient_id_sql_where = f"AND core_patient.id={int(patient_id)}"
838845

839846
observation_sql_where = ''
840847
if observation_id:
@@ -859,7 +866,7 @@ class Meta:
859866
WHERE core_jheuserorganization.jhe_user_id = %(jhe_user_id)s
860867
{organization_sql_where}
861868
{study_sql_where}
862-
{patient_sql_where}
869+
{patient_id_sql_where}
863870
{observation_sql_where}
864871
"""
865872

jhe/core/views/observation.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ def get_queryset(self):
3333
study_id = self.request.GET.get('_has:_group:member:_id', None)
3434

3535
patient_id = self.request.GET.get('patient', None)
36+
patient_identifier_system_and_value = self.request.GET.get('patient.identifier', None)
3637
coding_system_and_value = self.request.GET.get('code', None)
3738

38-
if not (study_id or patient_id):
39-
raise BadRequest("Request parameter patient._has:Group:member:_id=<study_id> or patient=<patient_id> must be provided.")
39+
if not (study_id or patient_id or patient_identifier_system_and_value):
40+
raise BadRequest("Request parameter patient._has:Group:member:_id=<study_id> or patient=<patient_id> or patient.identifier=<system>|<value> must be provided.")
4041

4142
if study_id and (not Study.practitioner_authorized(self.request.user.id, study_id)):
4243
raise PermissionDenied("Current User does not have authorization to access this Study.")
@@ -47,14 +48,23 @@ def get_queryset(self):
4748
coding_system = None
4849
coding_value = None
4950
if coding_system_and_value:
50-
coding_split = coding_system_and_value.split('|')
51+
coding_split = coding_system_and_value.split('|') # TBD 400 for formatting error
5152
coding_system = coding_split[0]
5253
coding_value = coding_split[1]
5354

55+
patient_identifier_system = None
56+
patient_identifier_value = None
57+
if patient_identifier_system_and_value:
58+
patient_identifier_split = patient_identifier_system_and_value.split('|') # TBD 400 for formatting error
59+
patient_identifier_system = patient_identifier_split[0]
60+
patient_identifier_value = patient_identifier_split[1]
61+
5462
return Observation.fhir_search(
5563
self.request.user.id,
5664
study_id,
5765
patient_id,
66+
patient_identifier_system,
67+
patient_identifier_value,
5868
coding_system,
5969
coding_value
6070
)

jhe/jhe/settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
JHE_VERSION = 'v0.0.1'
1+
JHE_VERSION = 'v0.0.2'
22

33
"""
44
Django settings for jhe project.
@@ -70,7 +70,7 @@
7070
# uncomment if you need to revert to the default pagination class
7171
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
7272
#'DEFAULT_PAGINATION_CLASS': 'core.pagination.CustomPageNumberPagination',
73-
'PAGE_SIZE': 20,
73+
'PAGE_SIZE': 1000,
7474
'DEFAULT_AUTHENTICATION_CLASSES': (
7575
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
7676
),

0 commit comments

Comments
 (0)