From 63498a6c2c7497b6e4b3716f7d407fc483fa6c94 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Sat, 18 Nov 2023 12:44:01 -0800 Subject: [PATCH 001/107] Allowed accessed to only active external patients, reinstating de-activated patients, if such found, not creating new ones --- patientsearch/api.py | 13 ++++++++----- patientsearch/models/sync.py | 29 +++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 16b9ea11..74fdb947 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -278,10 +278,12 @@ def post_resource(resource_type): resource = new_resource_hook(resource) method = request.method + params = request.args + # params["active"] = True audit_HAPI_change( user_info=current_user_info(token), method=method, - params=request.args, + params=params, resource=resource, resource_type=resource_type, ) @@ -289,7 +291,7 @@ def post_resource(resource_type): HAPI_request( token=token, method=method, - params=request.args, + params=params, resource_type=resource_type, resource=resource, ) @@ -500,7 +502,7 @@ def external_search(resource_type): # See if local match already exists patient = resource_from_args(resource_type, request.args) try: - internal_bundle = internal_patient_search(token, patient) + internal_bundle = internal_patient_search(token, patient, True) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) local_fhir_patient = None @@ -526,8 +528,9 @@ def external_search(resource_type): resource=patient, ) local_fhir_patient = HAPI_request( - token=token, method=method, resource_type="Patient", resource=patient - ) + token=token, method=method, resource_type="Patient", resource=patient, params={ + "active": True + }) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) audit_entry( diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 2d453458..4b1c9bd2 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -128,6 +128,8 @@ def external_request(token, resource_type, params): if "DEA" not in user: raise ValueError("DEA not found") search_params = dict(deepcopy(params)) # Necessary on ImmutableMultiDict + # Only working with active external patients + search_params["active"] = True search_params["DEA"] = user.get("DEA") url = current_app.config.get("EXTERNAL_FHIR_API") + resource_type resp = requests.get(url, auth=BearerAuth(token), params=search_params, timeout=30) @@ -179,6 +181,8 @@ def _merge_patient(src_patient, internal_patient, token): def different(src, dest): """returns true if details of interest found to be different""" + if not dest.get("active"): + return True if src == dest: return False if src.get("identifier") is None: @@ -198,6 +202,8 @@ def different(src, dest): else: internal_patient["identifier"] = src_patient["identifier"] params = patient_as_search_params(internal_patient) + # Ensure the internal patient is active now + params["active"] = True return HAPI_request( token=token, method="PUT", @@ -208,7 +214,7 @@ def different(src, dest): ) -def patient_as_search_params(patient): +def patient_as_search_params(patient, active_only = False): """Generate HAPI search params from patient resource""" # Use same parameters sent to external src looking for existing Patient @@ -221,6 +227,17 @@ def patient_as_search_params(patient): ("name[0].given[0]", "given", ""), ("birthDate", "birthdate", "eq"), ) + if active_only: + search_map = ( + ("name.family", "family", ""), + ("name[0].family", "family", ""), + ("name.given", "given", ""), + ("name.given[0]", "given", ""), + ("name[0].given[0]", "given", ""), + ("birthDate", "birthdate", "eq"), + ("active", "True", "eq"), + ) + search_params = {} for path, queryterm, compstr in search_map: @@ -230,9 +247,10 @@ def patient_as_search_params(patient): return search_params -def internal_patient_search(token, patient): +def internal_patient_search(token, patient, active_only = False): """Look up given patient from "internal" HAPI store, returns bundle""" - params = patient_as_search_params(patient) + params = patient_as_search_params(patient, active_only) + return HAPI_request( token=token, method="GET", resource_type="Patient", params=params ) @@ -273,6 +291,7 @@ def sync_patient(token, patient): ) internal_patient = internal_search["entry"][0]["resource"] + #TODO: ask whether they prefer to merge and update active value or to start a new Patient for each sync attempt toward a non-active patient. merged_patient = _merge_patient( src_patient=patient, internal_patient=internal_patient, token=token ) @@ -281,5 +300,7 @@ def sync_patient(token, patient): # No match, insert and return patient = new_resource_hook(resource=patient) return HAPI_request( - token=token, method="POST", resource_type="Patient", resource=patient + token=token, method="POST", resource_type="Patient", resource=patient, params={ + "active": True + } ) From 32946f5ed2dffe5d86ccb5eee2b0991c86324002 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Sat, 18 Nov 2023 13:04:26 -0800 Subject: [PATCH 002/107] Added a simple test case --- .../external_patient_search_not-active.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/test_sync/external_patient_search_not-active.json diff --git a/tests/test_sync/external_patient_search_not-active.json b/tests/test_sync/external_patient_search_not-active.json new file mode 100644 index 00000000..2d289945 --- /dev/null +++ b/tests/test_sync/external_patient_search_not-active.json @@ -0,0 +1,17 @@ +{ + "entry": [ + { + "active": false, + "birthDate": "1977-01-12", + "gender": "male", + "name": { + "family": "skywalker", + "given": ["luke"] + }, + "resourceType": "Patient" + } + ], + "resourceType": "Bundle", + "type": "searchset" + } + \ No newline at end of file From 15278e628daa177b6414af41f67995ad539f3744 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Sat, 18 Nov 2023 15:21:41 -0800 Subject: [PATCH 003/107] Added a simple test case --- patientsearch/models/sync.py | 20 +++++++++++++++---- .../external_patient_search_not-active.json | 17 ++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 tests/test_sync/external_patient_search_not-active.json diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 4b1c9bd2..58011840 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -181,8 +181,6 @@ def _merge_patient(src_patient, internal_patient, token): def different(src, dest): """returns true if details of interest found to be different""" - if not dest.get("active"): - return True if src == dest: return False if src.get("identifier") is None: @@ -198,11 +196,25 @@ def different(src, dest): return True if not different(src_patient, internal_patient): - return internal_patient + # If patient is active, proceed. If not, re-activate + if internal_patient.get("active") is True: + return internal_patient + + params = patient_as_search_params(internal_patient) + # Ensure it is active + params["active"] = True + return HAPI_request( + token=token, + method="PUT", + params=params, + resource_type="Patient", + resource=internal_patient, + resource_id=internal_patient["id"], + ) else: internal_patient["identifier"] = src_patient["identifier"] params = patient_as_search_params(internal_patient) - # Ensure the internal patient is active now + # Ensure it is active params["active"] = True return HAPI_request( token=token, diff --git a/tests/test_sync/external_patient_search_not-active.json b/tests/test_sync/external_patient_search_not-active.json new file mode 100644 index 00000000..2d289945 --- /dev/null +++ b/tests/test_sync/external_patient_search_not-active.json @@ -0,0 +1,17 @@ +{ + "entry": [ + { + "active": false, + "birthDate": "1977-01-12", + "gender": "male", + "name": { + "family": "skywalker", + "given": ["luke"] + }, + "resourceType": "Patient" + } + ], + "resourceType": "Bundle", + "type": "searchset" + } + \ No newline at end of file From a516388559070895625ca0d72cb6b5e4225cbcbc Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Wed, 29 Nov 2023 20:49:34 -0800 Subject: [PATCH 004/107] WIP: fixing the patient --- patientsearch/models/sync.py | 2 +- .../external_patient_search_not-active.json | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 tests/test_sync/external_patient_search_not-active.json diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 58011840..b1750272 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -197,7 +197,7 @@ def different(src, dest): if not different(src_patient, internal_patient): # If patient is active, proceed. If not, re-activate - if internal_patient.get("active") is True: + if internal_patient.get("active") is not False: return internal_patient params = patient_as_search_params(internal_patient) diff --git a/tests/test_sync/external_patient_search_not-active.json b/tests/test_sync/external_patient_search_not-active.json deleted file mode 100644 index 2d289945..00000000 --- a/tests/test_sync/external_patient_search_not-active.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "entry": [ - { - "active": false, - "birthDate": "1977-01-12", - "gender": "male", - "name": { - "family": "skywalker", - "given": ["luke"] - }, - "resourceType": "Patient" - } - ], - "resourceType": "Bundle", - "type": "searchset" - } - \ No newline at end of file From 5507ad1291741117a8594809cbfe03e97ade28b8 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Wed, 29 Nov 2023 20:53:23 -0800 Subject: [PATCH 005/107] add a test case for a deactivated patient search --- .../external_patient_search_not-active.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/test_sync/external_patient_search_not-active.json diff --git a/tests/test_sync/external_patient_search_not-active.json b/tests/test_sync/external_patient_search_not-active.json new file mode 100644 index 00000000..2d289945 --- /dev/null +++ b/tests/test_sync/external_patient_search_not-active.json @@ -0,0 +1,17 @@ +{ + "entry": [ + { + "active": false, + "birthDate": "1977-01-12", + "gender": "male", + "name": { + "family": "skywalker", + "given": ["luke"] + }, + "resourceType": "Patient" + } + ], + "resourceType": "Bundle", + "type": "searchset" + } + \ No newline at end of file From cac157128192fdd9eefc607afd2492253b7f04a0 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Wed, 29 Nov 2023 22:14:59 -0800 Subject: [PATCH 006/107] adding choice to restore local patient --- patientsearch/api.py | 10 ++++++++-- patientsearch/models/sync.py | 13 +++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 74fdb947..52fe9621 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -24,6 +24,7 @@ internal_patient_search, new_resource_hook, sync_bundle, + restore_patient ) from patientsearch.extensions import oidc from patientsearch.jsonify_abort import jsonify_abort @@ -463,8 +464,10 @@ def external_search(resource_type): as additional search criteria. i.e. /Patient?subject:Patient.name.given=luke&subject:Patient.birthdate=eq1977-01-12 - """ + """ token = validate_auth() + + reinstate_patient: bool = request.args[-1] # Tag any matching results with identifier naming source try: external_search_bundle = add_identifier_to_resource_type( @@ -502,12 +505,15 @@ def external_search(resource_type): # See if local match already exists patient = resource_from_args(resource_type, request.args) try: - internal_bundle = internal_patient_search(token, patient, True) + internal_bundle = internal_patient_search(token, patient, not reinstate_patient) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) local_fhir_patient = None if internal_bundle["total"] > 0: local_fhir_patient = internal_bundle["entry"][0]["resource"] + if reinstate_patient: + local_fhir_patient = restore_patient(token, local_fhir_patient) + if internal_bundle["total"] > 1: audit_entry( f"found multiple internal matches ({patient}), return first", diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index b1750272..07438a6c 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -316,3 +316,16 @@ def sync_patient(token, patient): "active": True } ) + + +def restore_patient(token, patient): + """Restore single internal patient resource""" + + return HAPI_request( + token=token, + method="PUT", + params={"active": True}, + resource_type="Patient", + resource=patient, + resource_id=patient["id"], + ) From 4ba6cbac90af8a670e4ed6d43790d6ab2b6d06c6 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Wed, 29 Nov 2023 22:26:26 -0800 Subject: [PATCH 007/107] adding choice to restore local patient --- patientsearch/api.py | 4 ++-- patientsearch/models/__init__.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 52fe9621..f3af2525 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -464,10 +464,10 @@ def external_search(resource_type): as additional search criteria. i.e. /Patient?subject:Patient.name.given=luke&subject:Patient.birthdate=eq1977-01-12 - """ + """ token = validate_auth() - reinstate_patient: bool = request.args[-1] + reinstate_patient = False # Tag any matching results with identifier naming source try: external_search_bundle = add_identifier_to_resource_type( diff --git a/patientsearch/models/__init__.py b/patientsearch/models/__init__.py index 35a59235..2a5577be 100644 --- a/patientsearch/models/__init__.py +++ b/patientsearch/models/__init__.py @@ -6,6 +6,7 @@ internal_patient_search, new_resource_hook, sync_bundle, + restore_patient, ) __all__ = [ @@ -16,4 +17,5 @@ "internal_patient_search", "new_resource_hook", "sync_bundle", + "restore_patient" ] From 56dd2c981f57e23d1b2268a2ef43c0e6852376f4 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:19:34 -0800 Subject: [PATCH 008/107] WIP: experimenting with tests --- tests/test_sync.py | 7 +++++++ .../external_patient_search_active.json | 17 +++++++++++++++++ ... => external_patient_search_not_active.json} | 0 3 files changed, 24 insertions(+) create mode 100644 tests/test_sync/external_patient_search_active.json rename tests/test_sync/{external_patient_search_not-active.json => external_patient_search_not_active.json} (100%) diff --git a/tests/test_sync.py b/tests/test_sync.py index e04f6e1f..6d142e20 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -32,6 +32,13 @@ def raise_for_status(self): def external_patient_search(datadir): return load_json(datadir, "external_patient_search.json") +@fixture +def external_patient_search_active(datadir): + return load_json(datadir, "external_patient_search_active.json") + +@fixture +def external_patient_search_non_active(datadir): + return load_json(datadir, "external_patient_search_non_active.json") @fixture def new_patient(datadir): diff --git a/tests/test_sync/external_patient_search_active.json b/tests/test_sync/external_patient_search_active.json new file mode 100644 index 00000000..77584bfe --- /dev/null +++ b/tests/test_sync/external_patient_search_active.json @@ -0,0 +1,17 @@ +{ + "entry": [ + { + "active": true, + "birthDate": "1977-01-12", + "gender": "male", + "name": { + "family": "skywalker", + "given": ["luke"] + }, + "resourceType": "Patient" + } + ], + "resourceType": "Bundle", + "type": "searchset" + } + \ No newline at end of file diff --git a/tests/test_sync/external_patient_search_not-active.json b/tests/test_sync/external_patient_search_not_active.json similarity index 100% rename from tests/test_sync/external_patient_search_not-active.json rename to tests/test_sync/external_patient_search_not_active.json From d33de4f1ec633d824b470c00ecc16d89978099ac Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:11:06 -0800 Subject: [PATCH 009/107] WIP: reinstating abilities --- patientsearch/api.py | 7 +++---- patientsearch/models/sync.py | 12 ++++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index f3af2525..279e969b 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -280,7 +280,7 @@ def post_resource(resource_type): resource = new_resource_hook(resource) method = request.method params = request.args - # params["active"] = True + audit_HAPI_change( user_info=current_user_info(token), method=method, @@ -533,10 +533,9 @@ def external_search(resource_type): resource_type=resource_type, resource=patient, ) + patient["active"] = True local_fhir_patient = HAPI_request( - token=token, method=method, resource_type="Patient", resource=patient, params={ - "active": True - }) + token=token, method=method, resource_type="Patient", resource=patient) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) audit_entry( diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 07438a6c..3ba87089 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -202,7 +202,7 @@ def different(src, dest): params = patient_as_search_params(internal_patient) # Ensure it is active - params["active"] = True + internal_patient["active"] = True return HAPI_request( token=token, method="PUT", @@ -215,7 +215,7 @@ def different(src, dest): internal_patient["identifier"] = src_patient["identifier"] params = patient_as_search_params(internal_patient) # Ensure it is active - params["active"] = True + internal_patient["active"] = True return HAPI_request( token=token, method="PUT", @@ -311,20 +311,20 @@ def sync_patient(token, patient): # No match, insert and return patient = new_resource_hook(resource=patient) + patient["active"] = True return HAPI_request( - token=token, method="POST", resource_type="Patient", resource=patient, params={ - "active": True - } + token=token, method="POST", resource_type="Patient", resource=patient, ) def restore_patient(token, patient): """Restore single internal patient resource""" + # Set patient to active + patient["active"] = True return HAPI_request( token=token, method="PUT", - params={"active": True}, resource_type="Patient", resource=patient, resource_id=patient["id"], From f4ada44f62d628c3d58b2c76eae5b83d286e1edc Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Mon, 11 Dec 2023 08:19:09 -0800 Subject: [PATCH 010/107] WIP: changing active from bool to str --- patientsearch/api.py | 2 +- patientsearch/models/sync.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 279e969b..b6e3d172 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -533,7 +533,7 @@ def external_search(resource_type): resource_type=resource_type, resource=patient, ) - patient["active"] = True + patient["active"] = "true" local_fhir_patient = HAPI_request( token=token, method=method, resource_type="Patient", resource=patient) except (RuntimeError, ValueError) as error: diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 3ba87089..aa1d38c1 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -129,7 +129,7 @@ def external_request(token, resource_type, params): raise ValueError("DEA not found") search_params = dict(deepcopy(params)) # Necessary on ImmutableMultiDict # Only working with active external patients - search_params["active"] = True + search_params["active"] = "true" search_params["DEA"] = user.get("DEA") url = current_app.config.get("EXTERNAL_FHIR_API") + resource_type resp = requests.get(url, auth=BearerAuth(token), params=search_params, timeout=30) @@ -197,12 +197,12 @@ def different(src, dest): if not different(src_patient, internal_patient): # If patient is active, proceed. If not, re-activate - if internal_patient.get("active") is not False: + if internal_patient.get("active") != "true": return internal_patient params = patient_as_search_params(internal_patient) # Ensure it is active - internal_patient["active"] = True + internal_patient["active"] = "true" return HAPI_request( token=token, method="PUT", @@ -215,7 +215,7 @@ def different(src, dest): internal_patient["identifier"] = src_patient["identifier"] params = patient_as_search_params(internal_patient) # Ensure it is active - internal_patient["active"] = True + internal_patient["active"] = "true" return HAPI_request( token=token, method="PUT", @@ -247,7 +247,7 @@ def patient_as_search_params(patient, active_only = False): ("name.given[0]", "given", ""), ("name[0].given[0]", "given", ""), ("birthDate", "birthdate", "eq"), - ("active", "True", "eq"), + ("active", "true", "eq"), ) search_params = {} @@ -311,7 +311,7 @@ def sync_patient(token, patient): # No match, insert and return patient = new_resource_hook(resource=patient) - patient["active"] = True + patient["active"] = "true" return HAPI_request( token=token, method="POST", resource_type="Patient", resource=patient, ) @@ -320,7 +320,7 @@ def sync_patient(token, patient): def restore_patient(token, patient): """Restore single internal patient resource""" # Set patient to active - patient["active"] = True + patient["active"] = "true" return HAPI_request( token=token, From 6738ba6ca3b17a3c63750a74e146d22d8592cf49 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:55:12 -0800 Subject: [PATCH 011/107] WIP: changing active from str back to bool --- patientsearch/models/sync.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index aa1d38c1..6a0a09a8 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -129,7 +129,7 @@ def external_request(token, resource_type, params): raise ValueError("DEA not found") search_params = dict(deepcopy(params)) # Necessary on ImmutableMultiDict # Only working with active external patients - search_params["active"] = "true" + search_params["active"] = True search_params["DEA"] = user.get("DEA") url = current_app.config.get("EXTERNAL_FHIR_API") + resource_type resp = requests.get(url, auth=BearerAuth(token), params=search_params, timeout=30) @@ -197,12 +197,12 @@ def different(src, dest): if not different(src_patient, internal_patient): # If patient is active, proceed. If not, re-activate - if internal_patient.get("active") != "true": + if internal_patient.get("active") != True: return internal_patient params = patient_as_search_params(internal_patient) # Ensure it is active - internal_patient["active"] = "true" + internal_patient["active"] = True return HAPI_request( token=token, method="PUT", @@ -215,7 +215,7 @@ def different(src, dest): internal_patient["identifier"] = src_patient["identifier"] params = patient_as_search_params(internal_patient) # Ensure it is active - internal_patient["active"] = "true" + internal_patient["active"] = True return HAPI_request( token=token, method="PUT", @@ -247,7 +247,7 @@ def patient_as_search_params(patient, active_only = False): ("name.given[0]", "given", ""), ("name[0].given[0]", "given", ""), ("birthDate", "birthdate", "eq"), - ("active", "true", "eq"), + ("active", True, "eq"), ) search_params = {} @@ -311,7 +311,7 @@ def sync_patient(token, patient): # No match, insert and return patient = new_resource_hook(resource=patient) - patient["active"] = "true" + patient["active"] = True return HAPI_request( token=token, method="POST", resource_type="Patient", resource=patient, ) @@ -320,7 +320,7 @@ def sync_patient(token, patient): def restore_patient(token, patient): """Restore single internal patient resource""" # Set patient to active - patient["active"] = "true" + patient["active"] = True return HAPI_request( token=token, From dd4a0c93e7a91ba8da9804403175b15519b6549a Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Tue, 12 Dec 2023 18:22:11 -0800 Subject: [PATCH 012/107] WIP: modifying active to be consistently boolean for json --- patientsearch/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index b6e3d172..279e969b 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -533,7 +533,7 @@ def external_search(resource_type): resource_type=resource_type, resource=patient, ) - patient["active"] = "true" + patient["active"] = True local_fhir_patient = HAPI_request( token=token, method=method, resource_type="Patient", resource=patient) except (RuntimeError, ValueError) as error: From cfb4d90bbab66135c6afbfb82a92a5dec0c02aad Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:24:38 -0800 Subject: [PATCH 013/107] Adding tests for sync function --- tests/test_sync.py | 99 ++++++++++++++++++- .../internal_patient_active_match.json | 50 ++++++++++ ...ternal_patient_duplicate_active_match.json | 83 ++++++++++++++++ ...patient_duplicate_inactive_match.json.json | 83 ++++++++++++++++ .../internal_patient_inactive_match.json | 50 ++++++++++ 5 files changed, 360 insertions(+), 5 deletions(-) create mode 100644 tests/test_sync/internal_patient_active_match.json create mode 100644 tests/test_sync/internal_patient_duplicate_active_match.json create mode 100644 tests/test_sync/internal_patient_duplicate_inactive_match.json.json create mode 100644 tests/test_sync/internal_patient_inactive_match.json diff --git a/tests/test_sync.py b/tests/test_sync.py index 6d142e20..68c087a7 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -32,13 +32,11 @@ def raise_for_status(self): def external_patient_search(datadir): return load_json(datadir, "external_patient_search.json") + @fixture def external_patient_search_active(datadir): return load_json(datadir, "external_patient_search_active.json") -@fixture -def external_patient_search_non_active(datadir): - return load_json(datadir, "external_patient_search_non_active.json") @fixture def new_patient(datadir): @@ -55,18 +53,39 @@ def internal_patient_match(datadir): return load_json(datadir, "internal_patient_match.json") +@fixture +def internal_patient_active_match(datadir): + return load_json(datadir, "internal_patient_active_match.json") + + +@fixture +def internal_patient_inactive_match(datadir): + return load_json(datadir, "internal_patient_inactive_match.json") + + @fixture def internal_patient_duplicate_match(datadir): return load_json(datadir, "internal_patient_duplicate_match.json") +@fixture +def internal_patient_duplicate_active_match(datadir): + return load_json(datadir, "internal_patient_duplicate_active_match.json") + + +@fixture +def internal_patient_duplicate_inactive_match(datadir): + return load_json(datadir, "internal_patient_duplicate_inactive_match.json") + def test_new_upsert( client, mocker, faux_token, external_patient_search, + external_patient_search_active, internal_patient_miss, new_patient, + internal_patient_inactive_match ): """Without finding a matching patient, should insert new and return""" @@ -86,6 +105,25 @@ def test_new_upsert( assert result == new_patient + """Finding inactive patient, user specified to not restore, should insert new and return""" + + # Mock HAPI search finding an inactive matching patient + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_inactive_match), + ) + + # Mock POST to generate new patient on HAPI + mocker.patch( + "patientsearch.models.sync.requests.post", + return_value=mock_response(new_patient), + ) + + result = sync_bundle(faux_token, external_patient_search_active) + assert result == new_patient + + + def test_adding_identifier(external_patient_search): """Test adding identifier to external search bundle""" ident = {"system": "https://examle.org/foobar", "value": 12} @@ -97,7 +135,14 @@ def test_adding_identifier(external_patient_search): def test_existing( - client, mocker, faux_token, external_patient_search, internal_patient_match + client, + mocker, + faux_token, + external_patient_search, + external_patient_search_active, + internal_patient_match, + internal_patient_active_match, + internal_patient_inactive_match ): """Finding a matching patient, return existing""" @@ -110,9 +155,38 @@ def test_existing( result = sync_bundle(faux_token, external_patient_search) assert result == internal_patient_match["entry"][0]["resource"] + """Finding a matching active patient from active external search, return existing""" + + # Mock HAPI search finding a matching active patient + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_active_match), + ) + + result = sync_bundle(faux_token, external_patient_search_active) + assert result == internal_patient_active_match["entry"][0]["resource"] + + """Finding a matching inactive patient from active external search, return existing restored/new""" + + # Mock HAPI search finding a matching inactive patient + # when the service is called to create to be restored + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_inactive_match), + ) + + result = sync_bundle(faux_token, external_patient_search_active) + assert result == internal_patient_inactive_match["entry"][0]["resource"] + + + def test_existing_modified( - client, mocker, faux_token, external_patient_search, internal_patient_match + client, + mocker, + faux_token, + external_patient_search, + internal_patient_match ): """Confirm modified external details get synchronized""" @@ -150,7 +224,10 @@ def test_duplicate( mocker, faux_token, external_patient_search, + external_patient_search_active, internal_patient_duplicate_match, + internal_patient_duplicate_active_match, + internal_patient_duplicate_inactive_match ): """Finding a matching patient with duplicates, handle well""" @@ -163,3 +240,15 @@ def test_duplicate( # Shouldn't kill the process, but return the first result = sync_bundle(faux_token, external_patient_search) assert result == internal_patient_duplicate_match["entry"][0]["resource"] + + # Shouldn't kill the process, but return the first + result = sync_bundle(faux_token, external_patient_search_active) + assert result == internal_patient_duplicate_active_match["entry"][0]["resource"] + + # Shouldn't kill the process, but return the first, restoring/initiating new patient + result = sync_bundle(faux_token, external_patient_search_active) + assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] + + # # TODO: test inactive/active configuration, should return active one? + # result = sync_bundle(faux_token, external_patient_search_active) + # assert result == internal_patient_duplicate_inactive_match["entry"][1]["resource"] diff --git a/tests/test_sync/internal_patient_active_match.json b/tests/test_sync/internal_patient_active_match.json new file mode 100644 index 00000000..9e93540d --- /dev/null +++ b/tests/test_sync/internal_patient_active_match.json @@ -0,0 +1,50 @@ +{ + "resourceType": "Bundle", + "id": "c865c9ee-2432-4f6d-87bd-3a6d5243bd77", + "meta": { + "lastUpdated": "2020-06-19T13:04:43.062+00:00" + }, + "type": "searchset", + "total": 1, + "link": [ + { + "relation": "self", + "url": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient?family=skywalker" + } + ], + "entry": [ + { + "fullUrl": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/1102", + "resource": { + "resourceType": "Patient", + "id": "1102", + "meta": { + "versionId": "1", + "lastUpdated": "2020-06-19T12:54:39.363+00:00" + }, + "text": { + "status": "generated", + "div": "
luke SKYWALKER
Date of birth12 January 1977
" + }, + "active": true, + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "gender": "male", + "birthDate": "1977-01-12" + }, + "search": { + "mode": "match" + }, + "response": { + "status": "201 Created", + "etag": "W/\"1\"" + } + } + ] +} diff --git a/tests/test_sync/internal_patient_duplicate_active_match.json b/tests/test_sync/internal_patient_duplicate_active_match.json new file mode 100644 index 00000000..fad886be --- /dev/null +++ b/tests/test_sync/internal_patient_duplicate_active_match.json @@ -0,0 +1,83 @@ +{ + "resourceType": "Bundle", + "id": "c865c9ee-2432-4f6d-87bd-3a6d5243bd77", + "meta": { + "lastUpdated": "2020-06-19T13:04:43.062+00:00" + }, + "type": "searchset", + "total": 2, + "link": [ + { + "relation": "self", + "url": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient?family=skywalker" + } + ], + "entry": [ + { + "fullUrl": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/1102", + "resource": { + "resourceType": "Patient", + "id": "1102", + "meta": { + "versionId": "1", + "lastUpdated": "2020-06-19T12:54:39.363+00:00" + }, + "text": { + "status": "generated", + "div": "
luke SKYWALKER
Date of birth12 January 1977
" + }, + "active": true, + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "gender": "male", + "birthDate": "1977-01-12" + }, + "search": { + "mode": "match" + }, + "response": { + "status": "201 Created", + "etag": "W/\"1\"" + } + }, + { + "fullUrl": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/1103", + "resource": { + "resourceType": "Patient", + "id": "1103", + "meta": { + "versionId": "1", + "lastUpdated": "2020-06-19T12:54:39.363+00:00" + }, + "text": { + "status": "generated", + "div": "
luke SKYWALKER
Date of birth12 January 1977
" + }, + "active": true, + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "gender": "male", + "birthDate": "1977-01-12" + }, + "search": { + "mode": "match" + }, + "response": { + "status": "201 Created", + "etag": "W/\"1\"" + } + } + ] +} diff --git a/tests/test_sync/internal_patient_duplicate_inactive_match.json.json b/tests/test_sync/internal_patient_duplicate_inactive_match.json.json new file mode 100644 index 00000000..593407ae --- /dev/null +++ b/tests/test_sync/internal_patient_duplicate_inactive_match.json.json @@ -0,0 +1,83 @@ +{ + "resourceType": "Bundle", + "id": "c865c9ee-2432-4f6d-87bd-3a6d5243bd77", + "meta": { + "lastUpdated": "2020-06-19T13:04:43.062+00:00" + }, + "type": "searchset", + "total": 2, + "link": [ + { + "relation": "self", + "url": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient?family=skywalker" + } + ], + "entry": [ + { + "fullUrl": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/1102", + "resource": { + "resourceType": "Patient", + "id": "1102", + "meta": { + "versionId": "1", + "lastUpdated": "2020-06-19T12:54:39.363+00:00" + }, + "text": { + "status": "generated", + "div": "
luke SKYWALKER
Date of birth12 January 1977
" + }, + "active": false, + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "gender": "male", + "birthDate": "1977-01-12" + }, + "search": { + "mode": "match" + }, + "response": { + "status": "201 Created", + "etag": "W/\"1\"" + } + }, + { + "fullUrl": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/1103", + "resource": { + "resourceType": "Patient", + "id": "1103", + "meta": { + "versionId": "1", + "lastUpdated": "2020-06-19T12:54:39.363+00:00" + }, + "text": { + "status": "generated", + "div": "
luke SKYWALKER
Date of birth12 January 1977
" + }, + "active": false, + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "gender": "male", + "birthDate": "1977-01-12" + }, + "search": { + "mode": "match" + }, + "response": { + "status": "201 Created", + "etag": "W/\"1\"" + } + } + ] +} diff --git a/tests/test_sync/internal_patient_inactive_match.json b/tests/test_sync/internal_patient_inactive_match.json new file mode 100644 index 00000000..664e2ee8 --- /dev/null +++ b/tests/test_sync/internal_patient_inactive_match.json @@ -0,0 +1,50 @@ +{ + "resourceType": "Bundle", + "id": "c865c9ee-2432-4f6d-87bd-3a6d5243bd77", + "meta": { + "lastUpdated": "2020-06-19T13:04:43.062+00:00" + }, + "type": "searchset", + "total": 1, + "link": [ + { + "relation": "self", + "url": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient?family=skywalker" + } + ], + "entry": [ + { + "fullUrl": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/1102", + "resource": { + "resourceType": "Patient", + "id": "1102", + "meta": { + "versionId": "1", + "lastUpdated": "2020-06-19T12:54:39.363+00:00" + }, + "text": { + "status": "generated", + "div": "
luke SKYWALKER
Date of birth12 January 1977
" + }, + "active": false, + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "gender": "male", + "birthDate": "1977-01-12" + }, + "search": { + "mode": "match" + }, + "response": { + "status": "201 Created", + "etag": "W/\"1\"" + } + } + ] +} From 95084de1b88431125a737b88c14473a01ec861a9 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:32:55 -0800 Subject: [PATCH 014/107] WIP: modifying new tests --- tests/test_sync.py | 18 ++++++-------- .../test_sync/new_patient_result_active.json | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 tests/test_sync/new_patient_result_active.json diff --git a/tests/test_sync.py b/tests/test_sync.py index 68c087a7..8bee21eb 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -42,6 +42,9 @@ def external_patient_search_active(datadir): def new_patient(datadir): return load_json(datadir, "new_patient_result.json") +@fixture +def new_patient_active(datadir): + return load_json(datadir, "new_patient_result_active.json") @fixture def internal_patient_miss(datadir): @@ -85,8 +88,8 @@ def test_new_upsert( external_patient_search_active, internal_patient_miss, new_patient, - internal_patient_inactive_match -): + new_patient_active + ): """Without finding a matching patient, should insert new and return""" # Mock HAPI search failing to find a matching patient @@ -107,21 +110,14 @@ def test_new_upsert( """Finding inactive patient, user specified to not restore, should insert new and return""" - # Mock HAPI search finding an inactive matching patient - mocker.patch( - "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_inactive_match), - ) - # Mock POST to generate new patient on HAPI mocker.patch( "patientsearch.models.sync.requests.post", - return_value=mock_response(new_patient), + return_value=mock_response(new_patient_active), ) result = sync_bundle(faux_token, external_patient_search_active) - assert result == new_patient - + assert result == new_patient_active def test_adding_identifier(external_patient_search): diff --git a/tests/test_sync/new_patient_result_active.json b/tests/test_sync/new_patient_result_active.json new file mode 100644 index 00000000..9d4aa58a --- /dev/null +++ b/tests/test_sync/new_patient_result_active.json @@ -0,0 +1,24 @@ +{ + "resourceType": "Patient", + "id": "1102", + "meta": { + "versionId": "1", + "lastUpdated": "2020-06-19T12:54:39.363+00:00" + }, + "text": { + "status": "generated", + "div": "
luke SKYWALKER
Date of birth12 January 1977
" + }, + "active": true, + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "gender": "male", + "birthDate": "1977-01-12" + } + \ No newline at end of file From e60fe9c3460102b748a8578ac675d0cb52cc4867 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:34:39 -0800 Subject: [PATCH 015/107] WIP: fixing tests --- ...h.json.json => internal_patient_duplicate_inactive_match.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/test_sync/{internal_patient_duplicate_inactive_match.json.json => internal_patient_duplicate_inactive_match.json} (100%) diff --git a/tests/test_sync/internal_patient_duplicate_inactive_match.json.json b/tests/test_sync/internal_patient_duplicate_inactive_match.json similarity index 100% rename from tests/test_sync/internal_patient_duplicate_inactive_match.json.json rename to tests/test_sync/internal_patient_duplicate_inactive_match.json From d1c0ed4399a46365f6113c2299790b539a917e5d Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Wed, 13 Dec 2023 16:55:38 -0800 Subject: [PATCH 016/107] Refactoring tests --- tests/test_sync.py | 144 +++++++++++++++--- .../internal_patient_duplicate_mismatch.json | 83 ++++++++++ 2 files changed, 209 insertions(+), 18 deletions(-) create mode 100644 tests/test_sync/internal_patient_duplicate_mismatch.json diff --git a/tests/test_sync.py b/tests/test_sync.py index 8bee21eb..47b4c0ad 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -76,6 +76,12 @@ def internal_patient_duplicate_active_match(datadir): return load_json(datadir, "internal_patient_duplicate_active_match.json") +@fixture +def internal_patient_duplicate_mismatch(datadir): + return load_json(datadir, "internal_patient_duplicate_mismatch.json") + + + @fixture def internal_patient_duplicate_inactive_match(datadir): return load_json(datadir, "internal_patient_duplicate_inactive_match.json") @@ -85,10 +91,8 @@ def test_new_upsert( mocker, faux_token, external_patient_search, - external_patient_search_active, internal_patient_miss, new_patient, - new_patient_active ): """Without finding a matching patient, should insert new and return""" @@ -108,7 +112,47 @@ def test_new_upsert( assert result == new_patient - """Finding inactive patient, user specified to not restore, should insert new and return""" +def test_new_upsert_active( + client, + mocker, + faux_token, + external_patient_search_active, + internal_patient_miss, + new_patient_active + ): + """Without finding a matching patient, should insert new and return""" + + # Mock HAPI search failing to find a matching patient + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_miss), + ) + + # Mock POST to generate new patient on HAPI + mocker.patch( + "patientsearch.models.sync.requests.post", + return_value=mock_response(new_patient_active), + ) + + result = sync_bundle(faux_token, external_patient_search_active) + assert result == new_patient_active + + +def test_upsert_inactive( + client, + mocker, + faux_token, + external_patient_search_active, + internal_patient_miss, + new_patient_active + ): + """Finding a matching inactive patient, user chose to generate new patient""" + + # Mock HAPI search finding a matching inactive + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_miss), + ) # Mock POST to generate new patient on HAPI mocker.patch( @@ -135,10 +179,7 @@ def test_existing( mocker, faux_token, external_patient_search, - external_patient_search_active, internal_patient_match, - internal_patient_active_match, - internal_patient_inactive_match ): """Finding a matching patient, return existing""" @@ -151,6 +192,14 @@ def test_existing( result = sync_bundle(faux_token, external_patient_search) assert result == internal_patient_match["entry"][0]["resource"] + +def test_existing_active( + client, + mocker, + faux_token, + external_patient_search_active, + internal_patient_active_match, +): """Finding a matching active patient from active external search, return existing""" # Mock HAPI search finding a matching active patient @@ -162,10 +211,18 @@ def test_existing( result = sync_bundle(faux_token, external_patient_search_active) assert result == internal_patient_active_match["entry"][0]["resource"] - """Finding a matching inactive patient from active external search, return existing restored/new""" + +def test_existing_inactive( + client, + mocker, + faux_token, + external_patient_search_active, + internal_patient_inactive_match +): + """Finding a matching inactive patient from active external search, return existing restored""" # Mock HAPI search finding a matching inactive patient - # when the service is called to create to be restored + # when the service is called for the patient to be restored mocker.patch( "patientsearch.models.sync.requests.get", return_value=mock_response(internal_patient_inactive_match), @@ -175,8 +232,6 @@ def test_existing( assert result == internal_patient_inactive_match["entry"][0]["resource"] - - def test_existing_modified( client, mocker, @@ -220,10 +275,7 @@ def test_duplicate( mocker, faux_token, external_patient_search, - external_patient_search_active, internal_patient_duplicate_match, - internal_patient_duplicate_active_match, - internal_patient_duplicate_inactive_match ): """Finding a matching patient with duplicates, handle well""" @@ -237,14 +289,70 @@ def test_duplicate( result = sync_bundle(faux_token, external_patient_search) assert result == internal_patient_duplicate_match["entry"][0]["resource"] + +def test_duplicate_active( + client, + mocker, + faux_token, + external_patient_search_active, + internal_patient_duplicate_active_match, +): + """Finding a matching active patient with duplicates, handle well""" + + # Mock HAPI search finding duplicate matching patients + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_duplicate_active_match), + ) + # Shouldn't kill the process, but return the first result = sync_bundle(faux_token, external_patient_search_active) assert result == internal_patient_duplicate_active_match["entry"][0]["resource"] - # Shouldn't kill the process, but return the first, restoring/initiating new patient + +def test_duplicate_inactive( + client, + mocker, + faux_token, + external_patient_search_active, + internal_patient_duplicate_inactive_match, +): + """Finding a matching inactive patient with duplicates, handle well""" + + # Mock HAPI search finding duplicate matching patients + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_duplicate_inactive_match), + ) + + # Shouldn't kill the process, but return the first result = sync_bundle(faux_token, external_patient_search_active) - assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] + assert result != internal_patient_duplicate_inactive_match["entry"][0]["resource"] - # # TODO: test inactive/active configuration, should return active one? - # result = sync_bundle(faux_token, external_patient_search_active) - # assert result == internal_patient_duplicate_inactive_match["entry"][1]["resource"] + +def test_duplicate_mismatch( + client, + mocker, + faux_token, + external_patient_search_active, + internal_patient_duplicate_mismatch, + internal_patient_duplicate_active_match, +): + """Finding mistmatching active/inactive patient with duplicates, handle well""" + + # Mock HAPI search finding duplicate matching patients + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_duplicate_mismatch), + ) + # TODO: add logic to handle if true is not first + + # Shouldn't kill the process, but return the first + result = sync_bundle(faux_token, external_patient_search_active) + # First duplicate is true, should be the same + assert result == internal_patient_duplicate_active_match["entry"][0]["resource"] + + # Shouldn't kill the process, but return the first + result = sync_bundle(faux_token, external_patient_search_active) + # Second duplicate is false, should not be the same + assert result != internal_patient_duplicate_active_match["entry"][1]["resource"] diff --git a/tests/test_sync/internal_patient_duplicate_mismatch.json b/tests/test_sync/internal_patient_duplicate_mismatch.json new file mode 100644 index 00000000..f0e129ef --- /dev/null +++ b/tests/test_sync/internal_patient_duplicate_mismatch.json @@ -0,0 +1,83 @@ +{ + "resourceType": "Bundle", + "id": "c865c9ee-2432-4f6d-87bd-3a6d5243bd77", + "meta": { + "lastUpdated": "2020-06-19T13:04:43.062+00:00" + }, + "type": "searchset", + "total": 2, + "link": [ + { + "relation": "self", + "url": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient?family=skywalker" + } + ], + "entry": [ + { + "fullUrl": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/1102", + "resource": { + "resourceType": "Patient", + "id": "1102", + "meta": { + "versionId": "1", + "lastUpdated": "2020-06-19T12:54:39.363+00:00" + }, + "text": { + "status": "generated", + "div": "
luke SKYWALKER
Date of birth12 January 1977
" + }, + "active": true, + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "gender": "male", + "birthDate": "1977-01-12" + }, + "search": { + "mode": "match" + }, + "response": { + "status": "201 Created", + "etag": "W/\"1\"" + } + }, + { + "fullUrl": "http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/1103", + "resource": { + "resourceType": "Patient", + "id": "1103", + "meta": { + "versionId": "1", + "lastUpdated": "2020-06-19T12:54:39.363+00:00" + }, + "text": { + "status": "generated", + "div": "
luke SKYWALKER
Date of birth12 January 1977
" + }, + "active": false, + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "gender": "male", + "birthDate": "1977-01-12" + }, + "search": { + "mode": "match" + }, + "response": { + "status": "201 Created", + "etag": "W/\"1\"" + } + } + ] +} From f50edeb6647752bfec361cfabc947291b9d29870 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 08:54:38 -0800 Subject: [PATCH 017/107] WIP: fixing tests --- tests/test_sync.py | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index 47b4c0ad..efa760d5 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -42,10 +42,12 @@ def external_patient_search_active(datadir): def new_patient(datadir): return load_json(datadir, "new_patient_result.json") + @fixture def new_patient_active(datadir): return load_json(datadir, "new_patient_result_active.json") + @fixture def internal_patient_miss(datadir): return load_json(datadir, "internal_patient_miss.json") @@ -81,7 +83,6 @@ def internal_patient_duplicate_mismatch(datadir): return load_json(datadir, "internal_patient_duplicate_mismatch.json") - @fixture def internal_patient_duplicate_inactive_match(datadir): return load_json(datadir, "internal_patient_duplicate_inactive_match.json") @@ -295,14 +296,14 @@ def test_duplicate_active( mocker, faux_token, external_patient_search_active, - internal_patient_duplicate_active_match, + internal_patient_duplicate_inactive_match, ): """Finding a matching active patient with duplicates, handle well""" # Mock HAPI search finding duplicate matching patients mocker.patch( "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_duplicate_active_match), + return_value=mock_response(internal_patient_duplicate_inactive_match), ) # Shouldn't kill the process, but return the first @@ -327,32 +328,5 @@ def test_duplicate_inactive( # Shouldn't kill the process, but return the first result = sync_bundle(faux_token, external_patient_search_active) - assert result != internal_patient_duplicate_inactive_match["entry"][0]["resource"] - - -def test_duplicate_mismatch( - client, - mocker, - faux_token, - external_patient_search_active, - internal_patient_duplicate_mismatch, - internal_patient_duplicate_active_match, -): - """Finding mistmatching active/inactive patient with duplicates, handle well""" - - # Mock HAPI search finding duplicate matching patients - mocker.patch( - "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_duplicate_mismatch), - ) - # TODO: add logic to handle if true is not first - - # Shouldn't kill the process, but return the first - result = sync_bundle(faux_token, external_patient_search_active) - # First duplicate is true, should be the same - assert result == internal_patient_duplicate_active_match["entry"][0]["resource"] + assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] - # Shouldn't kill the process, but return the first - result = sync_bundle(faux_token, external_patient_search_active) - # Second duplicate is false, should not be the same - assert result != internal_patient_duplicate_active_match["entry"][1]["resource"] From 75a092006ac39c09f33f4f87846b42e7f31e699e Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 08:56:54 -0800 Subject: [PATCH 018/107] WIP: fixing tests --- tests/test_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index efa760d5..dda1d20e 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -308,7 +308,7 @@ def test_duplicate_active( # Shouldn't kill the process, but return the first result = sync_bundle(faux_token, external_patient_search_active) - assert result == internal_patient_duplicate_active_match["entry"][0]["resource"] + assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] def test_duplicate_inactive( From 53b3d9f26d469f6f8577a39e051d4cecf9ab203b Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 09:02:47 -0800 Subject: [PATCH 019/107] WIP: assessing tests --- tests/test_sync.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index dda1d20e..26d1ec01 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -199,18 +199,18 @@ def test_existing_active( mocker, faux_token, external_patient_search_active, - internal_patient_active_match, + internal_patient_inactive_match, ): """Finding a matching active patient from active external search, return existing""" # Mock HAPI search finding a matching active patient mocker.patch( "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_active_match), + return_value=mock_response(internal_patient_inactive_match), ) result = sync_bundle(faux_token, external_patient_search_active) - assert result == internal_patient_active_match["entry"][0]["resource"] + assert result == internal_patient_inactive_match["entry"][0]["resource"] def test_existing_inactive( @@ -270,7 +270,6 @@ def test_existing_modified( assert result == identified_internal assert result["identifier"] == [found_identifier] - def test_duplicate( client, mocker, From 5207690f0818eeee5384f5a9af2f6b4df2f49770 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 09:06:36 -0800 Subject: [PATCH 020/107] WIP: adding tests --- tests/test_sync.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index 26d1ec01..49b1c434 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -199,18 +199,18 @@ def test_existing_active( mocker, faux_token, external_patient_search_active, - internal_patient_inactive_match, + internal_patient_active_match, ): """Finding a matching active patient from active external search, return existing""" # Mock HAPI search finding a matching active patient mocker.patch( "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_inactive_match), + return_value=mock_response(internal_patient_active_match), ) result = sync_bundle(faux_token, external_patient_search_active) - assert result == internal_patient_inactive_match["entry"][0]["resource"] + assert result != internal_patient_active_match["entry"][0]["resource"] def test_existing_inactive( From 44e22e23eda2d16f199b39e7b5fd22f6ed023d9f Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:16:06 -0800 Subject: [PATCH 021/107] Fixing black and flake8 --- patientsearch/api.py | 9 ++++--- patientsearch/models/__init__.py | 2 +- patientsearch/models/sync.py | 16 +++++++----- tests/test_sync.py | 45 +++++++++++++++----------------- 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 279e969b..30d6cab5 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -24,7 +24,7 @@ internal_patient_search, new_resource_hook, sync_bundle, - restore_patient + restore_patient, ) from patientsearch.extensions import oidc from patientsearch.jsonify_abort import jsonify_abort @@ -505,7 +505,9 @@ def external_search(resource_type): # See if local match already exists patient = resource_from_args(resource_type, request.args) try: - internal_bundle = internal_patient_search(token, patient, not reinstate_patient) + internal_bundle = internal_patient_search( + token, patient, not reinstate_patient + ) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) local_fhir_patient = None @@ -535,7 +537,8 @@ def external_search(resource_type): ) patient["active"] = True local_fhir_patient = HAPI_request( - token=token, method=method, resource_type="Patient", resource=patient) + token=token, method=method, resource_type="Patient", resource=patient + ) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) audit_entry( diff --git a/patientsearch/models/__init__.py b/patientsearch/models/__init__.py index 2a5577be..a6ac6fec 100644 --- a/patientsearch/models/__init__.py +++ b/patientsearch/models/__init__.py @@ -17,5 +17,5 @@ "internal_patient_search", "new_resource_hook", "sync_bundle", - "restore_patient" + "restore_patient", ] diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 6a0a09a8..72ce59ed 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -197,9 +197,9 @@ def different(src, dest): if not different(src_patient, internal_patient): # If patient is active, proceed. If not, re-activate - if internal_patient.get("active") != True: + if internal_patient.get("active") is not True: return internal_patient - + params = patient_as_search_params(internal_patient) # Ensure it is active internal_patient["active"] = True @@ -226,7 +226,7 @@ def different(src, dest): ) -def patient_as_search_params(patient, active_only = False): +def patient_as_search_params(patient, active_only=False): """Generate HAPI search params from patient resource""" # Use same parameters sent to external src looking for existing Patient @@ -249,7 +249,7 @@ def patient_as_search_params(patient, active_only = False): ("birthDate", "birthdate", "eq"), ("active", True, "eq"), ) - + search_params = {} for path, queryterm, compstr in search_map: @@ -259,12 +259,15 @@ def patient_as_search_params(patient, active_only = False): return search_params -def internal_patient_search(token, patient, active_only = False): +def internal_patient_search(token, patient, active_only=False): """Look up given patient from "internal" HAPI store, returns bundle""" params = patient_as_search_params(patient, active_only) return HAPI_request( - token=token, method="GET", resource_type="Patient", params=params + token=token, + method="GET", + resource_type="Patient", + params=params ) @@ -303,7 +306,6 @@ def sync_patient(token, patient): ) internal_patient = internal_search["entry"][0]["resource"] - #TODO: ask whether they prefer to merge and update active value or to start a new Patient for each sync attempt toward a non-active patient. merged_patient = _merge_patient( src_patient=patient, internal_patient=internal_patient, token=token ) diff --git a/tests/test_sync.py b/tests/test_sync.py index 49b1c434..f1b43fa0 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -87,6 +87,7 @@ def internal_patient_duplicate_mismatch(datadir): def internal_patient_duplicate_inactive_match(datadir): return load_json(datadir, "internal_patient_duplicate_inactive_match.json") + def test_new_upsert( client, mocker, @@ -94,7 +95,7 @@ def test_new_upsert( external_patient_search, internal_patient_miss, new_patient, - ): +): """Without finding a matching patient, should insert new and return""" # Mock HAPI search failing to find a matching patient @@ -119,8 +120,8 @@ def test_new_upsert_active( faux_token, external_patient_search_active, internal_patient_miss, - new_patient_active - ): + new_patient_active, +): """Without finding a matching patient, should insert new and return""" # Mock HAPI search failing to find a matching patient @@ -146,7 +147,7 @@ def test_upsert_inactive( external_patient_search_active, internal_patient_miss, new_patient_active - ): +): """Finding a matching inactive patient, user chose to generate new patient""" # Mock HAPI search finding a matching inactive @@ -176,11 +177,11 @@ def test_adding_identifier(external_patient_search): def test_existing( - client, - mocker, - faux_token, - external_patient_search, - internal_patient_match, + client, + mocker, + faux_token, + external_patient_search, + internal_patient_match, ): """Finding a matching patient, return existing""" @@ -195,11 +196,11 @@ def test_existing( def test_existing_active( - client, - mocker, - faux_token, + client, + mocker, + faux_token, external_patient_search_active, - internal_patient_active_match, + internal_patient_active_match, ): """Finding a matching active patient from active external search, return existing""" @@ -211,14 +212,14 @@ def test_existing_active( result = sync_bundle(faux_token, external_patient_search_active) assert result != internal_patient_active_match["entry"][0]["resource"] - + def test_existing_inactive( - client, - mocker, - faux_token, + client, + mocker, + faux_token, external_patient_search_active, - internal_patient_inactive_match + internal_patient_inactive_match, ): """Finding a matching inactive patient from active external search, return existing restored""" @@ -234,11 +235,7 @@ def test_existing_inactive( def test_existing_modified( - client, - mocker, - faux_token, - external_patient_search, - internal_patient_match + client, mocker, faux_token, external_patient_search, internal_patient_match ): """Confirm modified external details get synchronized""" @@ -270,6 +267,7 @@ def test_existing_modified( assert result == identified_internal assert result["identifier"] == [found_identifier] + def test_duplicate( client, mocker, @@ -328,4 +326,3 @@ def test_duplicate_inactive( # Shouldn't kill the process, but return the first result = sync_bundle(faux_token, external_patient_search_active) assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] - From ee370b6114cffeced5b9ba2a24833d32ad27d045 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:25:11 -0800 Subject: [PATCH 022/107] Fixed tests to follow black and flake8 --- patientsearch/models/sync.py | 10 +++++----- tests/test_sync.py | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 72ce59ed..561221c5 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -264,10 +264,7 @@ def internal_patient_search(token, patient, active_only=False): params = patient_as_search_params(patient, active_only) return HAPI_request( - token=token, - method="GET", - resource_type="Patient", - params=params + token=token, method="GET", resource_type="Patient", params=params ) @@ -315,7 +312,10 @@ def sync_patient(token, patient): patient = new_resource_hook(resource=patient) patient["active"] = True return HAPI_request( - token=token, method="POST", resource_type="Patient", resource=patient, + token=token, + method="POST", + resource_type="Patient", + resource=patient, ) diff --git a/tests/test_sync.py b/tests/test_sync.py index f1b43fa0..7fcb737f 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -146,7 +146,7 @@ def test_upsert_inactive( faux_token, external_patient_search_active, internal_patient_miss, - new_patient_active + new_patient_active, ): """Finding a matching inactive patient, user chose to generate new patient""" @@ -200,19 +200,19 @@ def test_existing_active( mocker, faux_token, external_patient_search_active, - internal_patient_active_match, + internal_patient_inactive_match, ): """Finding a matching active patient from active external search, return existing""" # Mock HAPI search finding a matching active patient mocker.patch( "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_active_match), + return_value=mock_response(internal_patient_inactive_match), ) result = sync_bundle(faux_token, external_patient_search_active) - assert result != internal_patient_active_match["entry"][0]["resource"] - + assert result["active"] != internal_patient_inactive_match["entry"][0]["resource"]["active"] + def test_existing_inactive( client, @@ -223,7 +223,7 @@ def test_existing_inactive( ): """Finding a matching inactive patient from active external search, return existing restored""" - # Mock HAPI search finding a matching inactive patient + # Mock HAPI search finding a matching inactive patient # when the service is called for the patient to be restored mocker.patch( "patientsearch.models.sync.requests.get", From b42bd3eaf95d353ae518aa83c7912419df91bd61 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:29:40 -0800 Subject: [PATCH 023/107] WIP: fixing tests --- tests/test_sync.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index 7fcb737f..270c7864 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -211,7 +211,10 @@ def test_existing_active( ) result = sync_bundle(faux_token, external_patient_search_active) - assert result["active"] != internal_patient_inactive_match["entry"][0]["resource"]["active"] + assert ( + result["active"] + == internal_patient_inactive_match["entry"][0]["resource"]["active"] + ) def test_existing_inactive( From 61e09cc0f8b5b13acaef7b34b443cccec5f3c77c Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:41:11 -0800 Subject: [PATCH 024/107] WIP: not displaying deactivated patients --- patientsearch/src/js/context/PatientListContextProvider.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index d02051ad..f9c05492 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -703,6 +703,7 @@ export default function PatientListContextProvider({ children }) { }, ], birthDate: rowData.birth_date, + active: true }); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() @@ -815,7 +816,7 @@ export default function PatientListContextProvider({ children }) { }, }); let patientResources = response.entry.filter( - (item) => item.resource && item.resource.resourceType === "Patient" + (item) => item.resource && item.resource.resourceType === "Patient" && item.resource.active == true ); let responseData = _formatData(patientResources) || []; const additionalParams = getAppSettingByKey( From f5e4f71befec8092d0ff115cc1d7a1ae9ba042ec Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:47:48 -0800 Subject: [PATCH 025/107] WIP: not displaying deactivated patients --- patientsearch/models/sync.py | 1 - patientsearch/src/js/context/PatientListContextProvider.js | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 561221c5..29729e2e 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -129,7 +129,6 @@ def external_request(token, resource_type, params): raise ValueError("DEA not found") search_params = dict(deepcopy(params)) # Necessary on ImmutableMultiDict # Only working with active external patients - search_params["active"] = True search_params["DEA"] = user.get("DEA") url = current_app.config.get("EXTERNAL_FHIR_API") + resource_type resp = requests.get(url, auth=BearerAuth(token), params=search_params, timeout=30) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index d02051ad..44a25b55 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -703,6 +703,7 @@ export default function PatientListContextProvider({ children }) { }, ], birthDate: rowData.birth_date, + active: true }); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() @@ -767,7 +768,7 @@ export default function PatientListContextProvider({ children }) { }); }; const getPatientList = (query) => { - console.log("patient list query object ", query); + console.log("patient list query objects ", query); const defaults = { data: [], page: 0, @@ -815,7 +816,7 @@ export default function PatientListContextProvider({ children }) { }, }); let patientResources = response.entry.filter( - (item) => item.resource && item.resource.resourceType === "Patient" + (item) => item.resource && item.resource.resourceType === "Patient" && item.resource.active == true ); let responseData = _formatData(patientResources) || []; const additionalParams = getAppSettingByKey( From 9cd1e4fff6b1ec6671cab9b5ca1ea66231caafcb Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:39:17 -0800 Subject: [PATCH 026/107] Adding enviroment variables --- patientsearch/api.py | 7 ++++--- patientsearch/config.py | 3 +++ patientsearch/src/js/context/PatientListContextProvider.js | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 30d6cab5..f0b9c2a4 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -467,7 +467,8 @@ def external_search(resource_type): """ token = validate_auth() - reinstate_patient = False + active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") + reactivate_patient = current_app.config.get("REACTIVATE_PATIENT") # Tag any matching results with identifier naming source try: external_search_bundle = add_identifier_to_resource_type( @@ -506,14 +507,14 @@ def external_search(resource_type): patient = resource_from_args(resource_type, request.args) try: internal_bundle = internal_patient_search( - token, patient, not reinstate_patient + token, patient, not reactivate_patient ) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) local_fhir_patient = None if internal_bundle["total"] > 0: local_fhir_patient = internal_bundle["entry"][0]["resource"] - if reinstate_patient: + if reactivate_patient: local_fhir_patient = restore_patient(token, local_fhir_patient) if internal_bundle["total"] > 1: diff --git a/patientsearch/config.py b/patientsearch/config.py index ce925abf..e37d5f5d 100644 --- a/patientsearch/config.py +++ b/patientsearch/config.py @@ -139,3 +139,6 @@ def load_json_config(potential_json_string): PROJECT_NAME = os.getenv("PROJECT_NAME", "COSRI") REQUIRED_ROLES = json.loads(os.getenv("REQUIRED_ROLES", "[]")) UDS_LAB_TYPES = json.loads(os.getenv("UDS_LAB_TYPES", "[]")) + +ACTIVE_PATIENT_FLAG = os.getenv("ACTIVE_PATIENT_FLAG") +REACTIVATE_PATIENT = os.getenv("REACTIVATE_PATIENT") \ No newline at end of file diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 44a25b55..22179318 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -703,7 +703,6 @@ export default function PatientListContextProvider({ children }) { }, ], birthDate: rowData.birth_date, - active: true }); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() From d96b22ff1377fb5fe0462371683c3534521de02c Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 10:59:08 -0800 Subject: [PATCH 027/107] WIP: testing front-end change being reflected in the list --- patientsearch/src/js/context/PatientListContextProvider.js | 1 + 1 file changed, 1 insertion(+) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 22179318..947345f3 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -703,6 +703,7 @@ export default function PatientListContextProvider({ children }) { }, ], birthDate: rowData.birth_date, + active: false }); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() From 43ba152cde0fddbdf164eb705cd882d94d2081ed Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:57:41 -0800 Subject: [PATCH 028/107] WIP: testing the search --- patientsearch/config.py | 5 ++--- patientsearch/src/js/context/PatientListContextProvider.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/patientsearch/config.py b/patientsearch/config.py index e37d5f5d..5d74823c 100644 --- a/patientsearch/config.py +++ b/patientsearch/config.py @@ -139,6 +139,5 @@ def load_json_config(potential_json_string): PROJECT_NAME = os.getenv("PROJECT_NAME", "COSRI") REQUIRED_ROLES = json.loads(os.getenv("REQUIRED_ROLES", "[]")) UDS_LAB_TYPES = json.loads(os.getenv("UDS_LAB_TYPES", "[]")) - -ACTIVE_PATIENT_FLAG = os.getenv("ACTIVE_PATIENT_FLAG") -REACTIVATE_PATIENT = os.getenv("REACTIVATE_PATIENT") \ No newline at end of file +ACTIVE_PATIENT_FLAG = os.getenv("ACTIVE_PATIENT_FLAG") +REACTIVATE_PATIENT = os.getenv("REACTIVATE_PATIENT") diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 947345f3..44a25b55 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -703,7 +703,7 @@ export default function PatientListContextProvider({ children }) { }, ], birthDate: rowData.birth_date, - active: false + active: true }); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() From 7a35a067d8a5f742e271ded78cb5337c55af4a10 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 12:18:55 -0800 Subject: [PATCH 029/107] WIP: testing active --- patientsearch/models/sync.py | 1 - patientsearch/src/js/context/PatientListContextProvider.js | 1 - 2 files changed, 2 deletions(-) diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 29729e2e..e92fad08 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -128,7 +128,6 @@ def external_request(token, resource_type, params): if "DEA" not in user: raise ValueError("DEA not found") search_params = dict(deepcopy(params)) # Necessary on ImmutableMultiDict - # Only working with active external patients search_params["DEA"] = user.get("DEA") url = current_app.config.get("EXTERNAL_FHIR_API") + resource_type resp = requests.get(url, auth=BearerAuth(token), params=search_params, timeout=30) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 44a25b55..22179318 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -703,7 +703,6 @@ export default function PatientListContextProvider({ children }) { }, ], birthDate: rowData.birth_date, - active: true }); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() From 8165b7e3d3459870858a5a7409f1d7d9f33f9df4 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 12:39:02 -0800 Subject: [PATCH 030/107] WIP: working on active --- patientsearch/src/js/context/PatientListContextProvider.js | 1 + 1 file changed, 1 insertion(+) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 22179318..44a25b55 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -703,6 +703,7 @@ export default function PatientListContextProvider({ children }) { }, ], birthDate: rowData.birth_date, + active: true }); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() From 9e24522f87ea56b7a2cd3da9a74e6c947d91253c Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:13:58 -0800 Subject: [PATCH 031/107] WIP: enabling ignoring the inactive/active to COSRI --- patientsearch/api.py | 10 +++++++--- patientsearch/models/sync.py | 37 ++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index f0b9c2a4..063a3083 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -469,6 +469,9 @@ def external_search(resource_type): active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") reactivate_patient = current_app.config.get("REACTIVATE_PATIENT") + if active_patient_flag: + # Add default behavior for when the active consideration is disabled + reactivate_patient = True # Tag any matching results with identifier naming source try: external_search_bundle = add_identifier_to_resource_type( @@ -497,7 +500,7 @@ def external_search(resource_type): if external_match_count: # Merge result details with internal resources try: - local_fhir_patient = sync_bundle(token, external_search_bundle) + local_fhir_patient = sync_bundle(token, external_search_bundle, active_patient_flag) except ValueError: return jsonify_abort(message="Error in local sync", status_code=400) if local_fhir_patient: @@ -514,7 +517,7 @@ def external_search(resource_type): local_fhir_patient = None if internal_bundle["total"] > 0: local_fhir_patient = internal_bundle["entry"][0]["resource"] - if reactivate_patient: + if active_patient_flag and reactivate_patient: local_fhir_patient = restore_patient(token, local_fhir_patient) if internal_bundle["total"] > 1: @@ -536,7 +539,8 @@ def external_search(resource_type): resource_type=resource_type, resource=patient, ) - patient["active"] = True + if active_patient_flag: + patient["active"] = True local_fhir_patient = HAPI_request( token=token, method=method, resource_type="Patient", resource=patient ) diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index e92fad08..66fe931c 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -146,7 +146,7 @@ def external_request(token, resource_type, params): return resp.json() -def sync_bundle(token, bundle): +def sync_bundle(token, bundle, consider_active = False): """Given FHIR bundle, insert or update all contained resources :param token: valid JWT token for use in auth calls @@ -167,13 +167,13 @@ def sync_bundle(token, bundle): if entry["resourceType"] != "Patient": raise ValueError(f"Can't sync resourceType {entry['resourceType']}") - patient = sync_patient(token, entry) + patient = sync_patient(token, entry, consider_active) # TODO handle multiple external matches (if it ever happens!) # currently returning first return patient -def _merge_patient(src_patient, internal_patient, token): +def _merge_patient(src_patient, internal_patient, token, consider_active = False): """Helper used to push details from src into internal patient""" # TODO consider additional patient attributes beyond identifiers @@ -195,7 +195,10 @@ def different(src, dest): if not different(src_patient, internal_patient): # If patient is active, proceed. If not, re-activate - if internal_patient.get("active") is not True: + if not consider_active: + return internal_patient + + if internal_patient.get("active", False) is not False: return internal_patient params = patient_as_search_params(internal_patient) @@ -212,8 +215,9 @@ def different(src, dest): else: internal_patient["identifier"] = src_patient["identifier"] params = patient_as_search_params(internal_patient) - # Ensure it is active - internal_patient["active"] = True + # Ensure it is active, skip if active field does not exis + if consider_active: + internal_patient["active"] = True return HAPI_request( token=token, method="PUT", @@ -229,14 +233,6 @@ def patient_as_search_params(patient, active_only=False): # Use same parameters sent to external src looking for existing Patient # Note FHIR uses list for 'name' and 'given', common parameter use defines just one - search_map = ( - ("name.family", "family", ""), - ("name[0].family", "family", ""), - ("name.given", "given", ""), - ("name.given[0]", "given", ""), - ("name[0].given[0]", "given", ""), - ("birthDate", "birthdate", "eq"), - ) if active_only: search_map = ( ("name.family", "family", ""), @@ -247,6 +243,15 @@ def patient_as_search_params(patient, active_only=False): ("birthDate", "birthdate", "eq"), ("active", True, "eq"), ) + else: + search_map = ( + ("name.family", "family", ""), + ("name[0].family", "family", ""), + ("name.given", "given", ""), + ("name.given[0]", "given", ""), + ("name[0].given[0]", "given", ""), + ("birthDate", "birthdate", "eq"), + ) search_params = {} @@ -287,7 +292,7 @@ def new_resource_hook(resource): return resource -def sync_patient(token, patient): +def sync_patient(token, patient, consider_active = False): """Sync single patient resource - insert or update as needed""" internal_search = internal_patient_search(token, patient) @@ -302,7 +307,7 @@ def sync_patient(token, patient): internal_patient = internal_search["entry"][0]["resource"] merged_patient = _merge_patient( - src_patient=patient, internal_patient=internal_patient, token=token + src_patient=patient, internal_patient=internal_patient, token=token, consider_active=consider_active, ) return merged_patient From 3e746e77f8381c9757b173c99f16be47cbda29ac Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:29:34 -0800 Subject: [PATCH 032/107] WIP: adding a confirmation pop-up --- patientsearch/src/js/context/PatientListContextProvider.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 44a25b55..bbc1ad55 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -714,6 +714,7 @@ export default function PatientListContextProvider({ children }) { ? constants.PDMP_SYSTEM_ERROR_MESSAGE : "Server error when looking up patient"; setOpenLoadingModal(true); + console.log("Modal set to Open"); fetchData( _getPatientSearchURL(rowData), { @@ -731,6 +732,7 @@ export default function PatientListContextProvider({ children }) { ) .then((result) => { setOpenLoadingModal(false); + console.log("Modal Opened"); let response = result; if (result && result.entry && result.entry[0]) { response = result.entry[0]; From 6116fe71ccda48e303f0a1cbaae411a3c90bf11f Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:06:53 -0800 Subject: [PATCH 033/107] WIP: adding modal --- patientsearch/src/js/components/TimeoutModal.js | 2 +- patientsearch/src/js/context/PatientListContextProvider.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/patientsearch/src/js/components/TimeoutModal.js b/patientsearch/src/js/components/TimeoutModal.js index cb4269d5..bfd7f352 100644 --- a/patientsearch/src/js/components/TimeoutModal.js +++ b/patientsearch/src/js/components/TimeoutModal.js @@ -45,7 +45,7 @@ export default function TimeoutModal() { const [modalStyle] = React.useState(getModalStyle); const [open, setOpen] = React.useState(false); const [disabled, setDisabled] = React.useState(false); - const trackInterval = 15000; + const trackInterval = 150; const appCtx = useSettingContext(); const appSettings = appCtx.appSettings; diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index bbc1ad55..44a25b55 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -714,7 +714,6 @@ export default function PatientListContextProvider({ children }) { ? constants.PDMP_SYSTEM_ERROR_MESSAGE : "Server error when looking up patient"; setOpenLoadingModal(true); - console.log("Modal set to Open"); fetchData( _getPatientSearchURL(rowData), { @@ -732,7 +731,6 @@ export default function PatientListContextProvider({ children }) { ) .then((result) => { setOpenLoadingModal(false); - console.log("Modal Opened"); let response = result; if (result && result.entry && result.entry[0]) { response = result.entry[0]; From 1f5a4c294643fffbc18bdf5715ec113cca277ac0 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:26:35 -0800 Subject: [PATCH 034/107] WIP: working on modal --- patientsearch/src/js/components/TimeoutModal.js | 2 +- patientsearch/src/js/context/PatientListContextProvider.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/patientsearch/src/js/components/TimeoutModal.js b/patientsearch/src/js/components/TimeoutModal.js index bfd7f352..cb4269d5 100644 --- a/patientsearch/src/js/components/TimeoutModal.js +++ b/patientsearch/src/js/components/TimeoutModal.js @@ -45,7 +45,7 @@ export default function TimeoutModal() { const [modalStyle] = React.useState(getModalStyle); const [open, setOpen] = React.useState(false); const [disabled, setDisabled] = React.useState(false); - const trackInterval = 150; + const trackInterval = 15000; const appCtx = useSettingContext(); const appSettings = appCtx.appSettings; diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 44a25b55..2d8bacd0 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -816,7 +816,7 @@ export default function PatientListContextProvider({ children }) { }, }); let patientResources = response.entry.filter( - (item) => item.resource && item.resource.resourceType === "Patient" && item.resource.active == true + (item) => item.resource && item.resource.resourceType === "Patient" && (item.resource.active === true || !item.resource.active) ); let responseData = _formatData(patientResources) || []; const additionalParams = getAppSettingByKey( From e5b19f435bf58d340ea238697cadb15f88dbaae3 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 16:43:29 -0800 Subject: [PATCH 035/107] WIP: fixing the tests --- patientsearch/api.py | 33 ++++++++++++++----- patientsearch/models/sync.py | 23 +++++++------ .../js/context/PatientListContextProvider.js | 1 + tests/test_sync.py | 12 +++---- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 063a3083..a405baf6 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -242,15 +242,30 @@ def resource_bundle(resource_type): """ token = validate_auth() + active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") + reactivate_patient = current_app.config.get("REACTIVATE_PATIENT") try: - return jsonify( - HAPI_request( - token=token, - method="GET", - resource_type=resource_type, - params=request.args, + if not active_patient_flag: + params=request.args + return jsonify( + HAPI_request( + token=token, + method="GET", + resource_type=resource_type, + params=params, + ) + ) + else: + params=request.args + params["active"] = "true" + return jsonify( + HAPI_request( + token=token, + method="GET", + resource_type=resource_type, + params=params, + ) ) - ) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) @@ -500,7 +515,9 @@ def external_search(resource_type): if external_match_count: # Merge result details with internal resources try: - local_fhir_patient = sync_bundle(token, external_search_bundle, active_patient_flag) + local_fhir_patient = sync_bundle( + token, external_search_bundle, active_patient_flag + ) except ValueError: return jsonify_abort(message="Error in local sync", status_code=400) if local_fhir_patient: diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 66fe931c..3844bed9 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -146,7 +146,7 @@ def external_request(token, resource_type, params): return resp.json() -def sync_bundle(token, bundle, consider_active = False): +def sync_bundle(token, bundle, consider_active=False): """Given FHIR bundle, insert or update all contained resources :param token: valid JWT token for use in auth calls @@ -173,7 +173,7 @@ def sync_bundle(token, bundle, consider_active = False): return patient -def _merge_patient(src_patient, internal_patient, token, consider_active = False): +def _merge_patient(src_patient, internal_patient, token, consider_active=False): """Helper used to push details from src into internal patient""" # TODO consider additional patient attributes beyond identifiers @@ -245,12 +245,12 @@ def patient_as_search_params(patient, active_only=False): ) else: search_map = ( - ("name.family", "family", ""), - ("name[0].family", "family", ""), - ("name.given", "given", ""), - ("name.given[0]", "given", ""), - ("name[0].given[0]", "given", ""), - ("birthDate", "birthdate", "eq"), + ("name.family", "family", ""), + ("name[0].family", "family", ""), + ("name.given", "given", ""), + ("name.given[0]", "given", ""), + ("name[0].given[0]", "given", ""), + ("birthDate", "birthdate", "eq"), ) search_params = {} @@ -292,7 +292,7 @@ def new_resource_hook(resource): return resource -def sync_patient(token, patient, consider_active = False): +def sync_patient(token, patient, consider_active=False): """Sync single patient resource - insert or update as needed""" internal_search = internal_patient_search(token, patient) @@ -307,7 +307,10 @@ def sync_patient(token, patient, consider_active = False): internal_patient = internal_search["entry"][0]["resource"] merged_patient = _merge_patient( - src_patient=patient, internal_patient=internal_patient, token=token, consider_active=consider_active, + src_patient=patient, + internal_patient=internal_patient, + token=token, + consider_active=consider_active, ) return merged_patient diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 2d8bacd0..67353688 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -714,6 +714,7 @@ export default function PatientListContextProvider({ children }) { ? constants.PDMP_SYSTEM_ERROR_MESSAGE : "Server error when looking up patient"; setOpenLoadingModal(true); + console.log(searchBody); fetchData( _getPatientSearchURL(rowData), { diff --git a/tests/test_sync.py b/tests/test_sync.py index 270c7864..ed7bca72 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -200,20 +200,20 @@ def test_existing_active( mocker, faux_token, external_patient_search_active, - internal_patient_inactive_match, + internal_patient_active_match, ): """Finding a matching active patient from active external search, return existing""" # Mock HAPI search finding a matching active patient mocker.patch( "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_inactive_match), + return_value=mock_response(internal_patient_active_match), ) result = sync_bundle(faux_token, external_patient_search_active) assert ( result["active"] - == internal_patient_inactive_match["entry"][0]["resource"]["active"] + == internal_patient_active_match["entry"][0]["resource"]["active"] ) @@ -296,19 +296,19 @@ def test_duplicate_active( mocker, faux_token, external_patient_search_active, - internal_patient_duplicate_inactive_match, + internal_patient_duplicate_active_match, ): """Finding a matching active patient with duplicates, handle well""" # Mock HAPI search finding duplicate matching patients mocker.patch( "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_duplicate_inactive_match), + return_value=mock_response(internal_patient_duplicate_active_match), ) # Shouldn't kill the process, but return the first result = sync_bundle(faux_token, external_patient_search_active) - assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] + assert result == internal_patient_duplicate_active_match["entry"][0]["resource"] def test_duplicate_inactive( From 75f03c5ee29da75f7753d9189253dc2e6815f0f3 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 16:59:54 -0800 Subject: [PATCH 036/107] WIP: fixing the tests --- patientsearch/api.py | 4 ++-- patientsearch/src/js/context/PatientListContextProvider.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index a405baf6..40555939 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -246,7 +246,7 @@ def resource_bundle(resource_type): reactivate_patient = current_app.config.get("REACTIVATE_PATIENT") try: if not active_patient_flag: - params=request.args + params = request.args return jsonify( HAPI_request( token=token, @@ -256,7 +256,7 @@ def resource_bundle(resource_type): ) ) else: - params=request.args + params = request.args params["active"] = "true" return jsonify( HAPI_request( diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 67353688..265fc83e 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -714,7 +714,6 @@ export default function PatientListContextProvider({ children }) { ? constants.PDMP_SYSTEM_ERROR_MESSAGE : "Server error when looking up patient"; setOpenLoadingModal(true); - console.log(searchBody); fetchData( _getPatientSearchURL(rowData), { @@ -770,6 +769,7 @@ export default function PatientListContextProvider({ children }) { }; const getPatientList = (query) => { console.log("patient list query objects ", query); + console.log("search body ", searchBody); const defaults = { data: [], page: 0, From 3aa2895193aab612be48bbcc475158e5d9f328e7 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:39:02 -0800 Subject: [PATCH 037/107] WIP: fixing the active search --- patientsearch/api.py | 52 +++++++++++++------ .../js/context/PatientListContextProvider.js | 2 +- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 40555939..80b106b4 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -242,11 +242,10 @@ def resource_bundle(resource_type): """ token = validate_auth() - active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") - reactivate_patient = current_app.config.get("REACTIVATE_PATIENT") - try: - if not active_patient_flag: - params = request.args + params = request.args + # If the resource is not a patient, proceed with the GET + if resource_type != "Patient": + try: return jsonify( HAPI_request( token=token, @@ -255,19 +254,38 @@ def resource_bundle(resource_type): params=params, ) ) - else: - params = request.args - params["active"] = "true" - return jsonify( - HAPI_request( - token=token, - method="GET", - resource_type=resource_type, - params=params, + except (RuntimeError, ValueError) as error: + return jsonify_abort(status_code=400, message=str(error)) + + if resource_type == "Patient": + total_length = len(params.get("subject:Patient.name.given", "")) + \ + len(params.get("subject:Patient.name.family", "")) + \ + len(params.get("subject:Patient.birthdate", "").split("eq")) + + # Check for the user's configurations + active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") + try: + if total_length != 4 or not active_patient_flag: + return jsonify( + HAPI_request( + token=token, + method="GET", + resource_type=resource_type, + params=params, + ) ) - ) - except (RuntimeError, ValueError) as error: - return jsonify_abort(status_code=400, message=str(error)) + else: + params["active"] = "true" + return jsonify( + HAPI_request( + token=token, + method="GET", + resource_type=resource_type, + params=params, + ) + ) + except (RuntimeError, ValueError) as error: + return jsonify_abort(status_code=400, message=str(error)) @api_blueprint.route("/fhir/", methods=["POST", "PUT"]) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 265fc83e..64803f72 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -817,7 +817,7 @@ export default function PatientListContextProvider({ children }) { }, }); let patientResources = response.entry.filter( - (item) => item.resource && item.resource.resourceType === "Patient" && (item.resource.active === true || !item.resource.active) + (item) => item.resource && item.resource.resourceType === "Patient" ); let responseData = _formatData(patientResources) || []; const additionalParams = getAppSettingByKey( From 6bfec1ef4f7ccea1baced1b09323f2a02414e72d Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:41:33 -0800 Subject: [PATCH 038/107] WIP: fixing active search --- patientsearch/src/js/context/PatientListContextProvider.js | 1 - 1 file changed, 1 deletion(-) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 64803f72..09436457 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -769,7 +769,6 @@ export default function PatientListContextProvider({ children }) { }; const getPatientList = (query) => { console.log("patient list query objects ", query); - console.log("search body ", searchBody); const defaults = { data: [], page: 0, From c08cb8c3996ca6b695e3e95e86cbb405de2009e1 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 18:02:48 -0800 Subject: [PATCH 039/107] WIP: fixing active search --- patientsearch/api.py | 23 ++++++++++--------- .../js/context/PatientListContextProvider.js | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 80b106b4..82173e37 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -256,34 +256,35 @@ def resource_bundle(resource_type): ) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) - - if resource_type == "Patient": - total_length = len(params.get("subject:Patient.name.given", "")) + \ - len(params.get("subject:Patient.name.family", "")) + \ - len(params.get("subject:Patient.birthdate", "").split("eq")) + if resource_type == "Patient": + total_length = ( + len(params.get("subject:Patient.name.given", "")) + + len(params.get("subject:Patient.name.family", "")) + + len(params.get("subject:Patient.birthdate", "").split("eq")) + ) # Check for the user's configurations active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") try: if total_length != 4 or not active_patient_flag: - return jsonify( - HAPI_request( + patient = HAPI_request( token=token, method="GET", resource_type=resource_type, params=params, ) - ) + + return jsonify(patient) else: params["active"] = "true" - return jsonify( - HAPI_request( + patient = HAPI_request( token=token, method="GET", resource_type=resource_type, params=params, ) - ) + + return jsonify(patient) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 09436457..72b733c6 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -768,7 +768,7 @@ export default function PatientListContextProvider({ children }) { }); }; const getPatientList = (query) => { - console.log("patient list query objects ", query); + console.log("patient list query object ", query); const defaults = { data: [], page: 0, From b3944339cf232bd9794b5fc2189e33d46072e0fc Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 18:07:07 -0800 Subject: [PATCH 040/107] WIP: fixing active --- patientsearch/api.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 82173e37..0d6ee41f 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -266,23 +266,23 @@ def resource_bundle(resource_type): # Check for the user's configurations active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") try: - if total_length != 4 or not active_patient_flag: + if total_length == 4 or not active_patient_flag: patient = HAPI_request( - token=token, - method="GET", - resource_type=resource_type, - params=params, - ) + token=token, + method="GET", + resource_type=resource_type, + params=params, + ) return jsonify(patient) else: params["active"] = "true" patient = HAPI_request( - token=token, - method="GET", - resource_type=resource_type, - params=params, - ) + token=token, + method="GET", + resource_type=resource_type, + params=params, + ) return jsonify(patient) except (RuntimeError, ValueError) as error: From 9e4007ce4de1e99df53ffbf7aba374e11943c523 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 18:50:46 -0800 Subject: [PATCH 041/107] WIP: fixing active search --- patientsearch/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 0d6ee41f..3c56a9d8 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -264,7 +264,7 @@ def resource_bundle(resource_type): + len(params.get("subject:Patient.birthdate", "").split("eq")) ) # Check for the user's configurations - active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") + active_patient_flag = True try: if total_length == 4 or not active_patient_flag: patient = HAPI_request( From 3f00ea8682e4fe38e80d6c8ce51165713d00eeb2 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 18:52:34 -0800 Subject: [PATCH 042/107] WIP: fixing activea --- patientsearch/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 3c56a9d8..bd61279d 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -276,7 +276,7 @@ def resource_bundle(resource_type): return jsonify(patient) else: - params["active"] = "true" + params["active"] = True patient = HAPI_request( token=token, method="GET", From e5cbcac996b7982c1539e4032c82866f03c462f8 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 18:53:15 -0800 Subject: [PATCH 043/107] WIP: fixing test --- patientsearch/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index bd61279d..90c6c88d 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -264,7 +264,7 @@ def resource_bundle(resource_type): + len(params.get("subject:Patient.birthdate", "").split("eq")) ) # Check for the user's configurations - active_patient_flag = True + active_patient_flag = False try: if total_length == 4 or not active_patient_flag: patient = HAPI_request( @@ -276,7 +276,7 @@ def resource_bundle(resource_type): return jsonify(patient) else: - params["active"] = True + params["active"] = "true" patient = HAPI_request( token=token, method="GET", From 0613f1574ea397ddc2cc2693ae9c583fb1f8737e Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 18:55:24 -0800 Subject: [PATCH 044/107] WIP: fixing active --- patientsearch/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 90c6c88d..0d6ee41f 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -264,7 +264,7 @@ def resource_bundle(resource_type): + len(params.get("subject:Patient.birthdate", "").split("eq")) ) # Check for the user's configurations - active_patient_flag = False + active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") try: if total_length == 4 or not active_patient_flag: patient = HAPI_request( From add0709c9711db53042cdd2fae820b91f67f3a04 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 19:58:08 -0800 Subject: [PATCH 045/107] WIP: fixing active --- patientsearch/api.py | 60 ++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 0d6ee41f..7552ee5b 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -258,36 +258,48 @@ def resource_bundle(resource_type): return jsonify_abort(status_code=400, message=str(error)) if resource_type == "Patient": - total_length = ( - len(params.get("subject:Patient.name.given", "")) - + len(params.get("subject:Patient.name.family", "")) - + len(params.get("subject:Patient.birthdate", "").split("eq")) - ) # Check for the user's configurations active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") - try: - if total_length == 4 or not active_patient_flag: - patient = HAPI_request( - token=token, - method="GET", - resource_type=resource_type, - params=params, - ) - return jsonify(patient) - else: - params["active"] = "true" - patient = HAPI_request( - token=token, - method="GET", - resource_type=resource_type, - params=params, - ) - - return jsonify(patient) + patient = resource_from_args(resource_type, request.args) + try: + internal_bundle = internal_patient_search( + token, patient, active_patient_flag + ) + patient = internal_bundle["entry"][0]["resource"] + return jsonify(patient) except (RuntimeError, ValueError) as error: return jsonify_abort(status_code=400, message=str(error)) + # full_sequence = all( + # params.get("subject:Patient.name.given") + # and params.get("subject:Patient.name.family") + # and len(params.get("subject:Patient.birthdate", "").split("eq")) > 1 + # ) + + # try: + # if full_sequence or not active_patient_flag: + # patient = HAPI_request( + # token=token, + # method="GET", + # resource_type=resource_type, + # params=params, + # ) + + # return jsonify(patient) + # else: + # params["active"] = "true" + # patient = HAPI_request( + # token=token, + # method="GET", + # resource_type=resource_type, + # params=params, + # ) + + # return jsonify(patient) + # except (RuntimeError, ValueError) as error: + # return jsonify_abort(status_code=400, message=str(error)) + @api_blueprint.route("/fhir/", methods=["POST", "PUT"]) def post_resource(resource_type): From e5963c5aaeb441cc0b3dfaf7f40a22d016e93de5 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 20:18:18 -0800 Subject: [PATCH 046/107] WIP: fixing active --- patientsearch/api.py | 70 ++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 7552ee5b..7d0f3aff 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -261,45 +261,45 @@ def resource_bundle(resource_type): # Check for the user's configurations active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") - patient = resource_from_args(resource_type, request.args) - try: - internal_bundle = internal_patient_search( - token, patient, active_patient_flag - ) - patient = internal_bundle["entry"][0]["resource"] - return jsonify(patient) - except (RuntimeError, ValueError) as error: - return jsonify_abort(status_code=400, message=str(error)) - - # full_sequence = all( - # params.get("subject:Patient.name.given") - # and params.get("subject:Patient.name.family") - # and len(params.get("subject:Patient.birthdate", "").split("eq")) > 1 - # ) - + # patient = resource_from_args(resource_type, request.args) # try: - # if full_sequence or not active_patient_flag: - # patient = HAPI_request( - # token=token, - # method="GET", - # resource_type=resource_type, - # params=params, - # ) - - # return jsonify(patient) - # else: - # params["active"] = "true" - # patient = HAPI_request( - # token=token, - # method="GET", - # resource_type=resource_type, - # params=params, - # ) - - # return jsonify(patient) + # internal_bundle = internal_patient_search( + # token, patient, active_patient_flag + # ) + # patient = internal_bundle["entry"][0]["resource"] + # return jsonify(patient) # except (RuntimeError, ValueError) as error: # return jsonify_abort(status_code=400, message=str(error)) + full_sequence = all( + params.get("subject:Patient.name.given") + and params.get("subject:Patient.name.family") + and len(params.get("subject:Patient.birthdate", "").split("eq")) > 1 + ) + + try: + if full_sequence or not active_patient_flag: + patient = HAPI_request( + token=token, + method="GET", + resource_type=resource_type, + params=params, + ) + + return jsonify(patient) + else: + params.append("&active=true") + patient = HAPI_request( + token=token, + method="GET", + resource_type=resource_type, + params=params, + ) + + return jsonify(patient) + except (RuntimeError, ValueError) as error: + return jsonify_abort(status_code=400, message=str(error)) + @api_blueprint.route("/fhir/", methods=["POST", "PUT"]) def post_resource(resource_type): From a98824bd5e83fac9f40babdc59e544c7f884acdb Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 20:40:51 -0800 Subject: [PATCH 047/107] WIP: fixing active --- patientsearch/api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 7d0f3aff..71504ae4 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -243,6 +243,10 @@ def resource_bundle(resource_type): """ token = validate_auth() params = request.args + raise ValueError( + "params updated " + f"{params}" + ) # If the resource is not a patient, proceed with the GET if resource_type != "Patient": try: @@ -277,6 +281,7 @@ def resource_bundle(resource_type): and len(params.get("subject:Patient.birthdate", "").split("eq")) > 1 ) + try: if full_sequence or not active_patient_flag: patient = HAPI_request( @@ -288,7 +293,7 @@ def resource_bundle(resource_type): return jsonify(patient) else: - params.append("&active=true") + # params.append("&active=true") patient = HAPI_request( token=token, method="GET", From 2907a070c6fd5da63f442835ce00f48a8fef24ee Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 20:45:43 -0800 Subject: [PATCH 048/107] WIP: fixing active --- patientsearch/api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 71504ae4..ebdfe2c8 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -243,10 +243,7 @@ def resource_bundle(resource_type): """ token = validate_auth() params = request.args - raise ValueError( - "params updated " - f"{params}" - ) + current_app.logger.debug(f"params updated: {params} and type is {type(params)}") # If the resource is not a patient, proceed with the GET if resource_type != "Patient": try: From d3fa5712976d526f7f255411d398ea1ebdb29cc4 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 20:59:37 -0800 Subject: [PATCH 049/107] WIP: fixing active --- patientsearch/api.py | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index ebdfe2c8..8c6847c4 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -14,6 +14,7 @@ import jwt import requests from werkzeug.exceptions import Unauthorized, Forbidden +from copy import deepcopy from patientsearch.audit import audit_entry, audit_HAPI_change from patientsearch.models import ( @@ -243,7 +244,8 @@ def resource_bundle(resource_type): """ token = validate_auth() params = request.args - current_app.logger.debug(f"params updated: {params} and type is {type(params)}") + search_params = dict(deepcopy(params)) # Necessary on ImmutableMultiDict + # If the resource is not a patient, proceed with the GET if resource_type != "Patient": try: @@ -261,22 +263,12 @@ def resource_bundle(resource_type): if resource_type == "Patient": # Check for the user's configurations active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") - - # patient = resource_from_args(resource_type, request.args) - # try: - # internal_bundle = internal_patient_search( - # token, patient, active_patient_flag - # ) - # patient = internal_bundle["entry"][0]["resource"] - # return jsonify(patient) - # except (RuntimeError, ValueError) as error: - # return jsonify_abort(status_code=400, message=str(error)) - - full_sequence = all( - params.get("subject:Patient.name.given") - and params.get("subject:Patient.name.family") - and len(params.get("subject:Patient.birthdate", "").split("eq")) > 1 - ) + + full_sequence = all([ + params.get("subject:Patient.name.given", False), + params.get("subject:Patient.name.family", False), + len(params.get("subject:Patient.birthdate", "").split("eq")) > 1 + ]) try: @@ -290,12 +282,12 @@ def resource_bundle(resource_type): return jsonify(patient) else: - # params.append("&active=true") + search_params["active"] = "true" patient = HAPI_request( token=token, method="GET", resource_type=resource_type, - params=params, + params=search_params, ) return jsonify(patient) From 6d72a321c39b014f87f95194cb1f7815a2d376ea Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 21:33:40 -0800 Subject: [PATCH 050/107] WIP: temporary debugging tools added --- patientsearch/api.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 8c6847c4..7d203ac4 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -263,13 +263,14 @@ def resource_bundle(resource_type): if resource_type == "Patient": # Check for the user's configurations active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") - - full_sequence = all([ - params.get("subject:Patient.name.given", False), - params.get("subject:Patient.name.family", False), - len(params.get("subject:Patient.birthdate", "").split("eq")) > 1 - ]) + full_sequence = all( + [ + params.get("subject:Patient.name.given", False), + params.get("subject:Patient.name.family", False), + len(params.get("subject:Patient.birthdate", "").split("eq")) > 1 + ] + ) try: if full_sequence or not active_patient_flag: @@ -307,6 +308,8 @@ def post_resource(resource_type): """ token = validate_auth() + current_app.logger.debug(f"POST/PUT, following args: {request.args}") + try: resource = request.get_json() if not resource: @@ -354,6 +357,7 @@ def update_resource_by_id(resource_type, resource_id): redirect. Client should watch for 401 and redirect appropriately. """ token = validate_auth() + current_app.logger.debug(f"PUT, following args: {request.args}") try: resource = request.get_json() From ca17c343b6790ddbd00a824bc99a7fe039b8f95a Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:01:52 -0800 Subject: [PATCH 051/107] WIP: adding reinstation/creating new one option --- patientsearch/api.py | 13 ++++++++----- patientsearch/models/sync.py | 16 ++++++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 7d203ac4..b7842910 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -308,7 +308,8 @@ def post_resource(resource_type): """ token = validate_auth() - current_app.logger.debug(f"POST/PUT, following args: {request.args}") + active_patient_flag = False + reactivate_patient = False try: resource = request.get_json() @@ -319,11 +320,14 @@ def post_resource(resource_type): "type mismatch - POSTed resource type " f"{resource['resourceType']} != {resource_type}" ) - - resource = new_resource_hook(resource) + if resource_type == "Patient" and active_patient_flag: + current_app.logger.debug(f"active, restore: {resource.get('active')} and type: {type(resource.get('active'))}") + resource = new_resource_hook(resource, active_patient_flag, reactivate_patient) + else: + resource = new_resource_hook(resource) method = request.method params = request.args - + audit_HAPI_change( user_info=current_user_info(token), method=method, @@ -357,7 +361,6 @@ def update_resource_by_id(resource_type, resource_id): redirect. Client should watch for 401 and redirect appropriately. """ token = validate_auth() - current_app.logger.debug(f"PUT, following args: {request.args}") try: resource = request.get_json() diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 3844bed9..510ab6b6 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -271,7 +271,7 @@ def internal_patient_search(token, patient, active_only=False): ) -def new_resource_hook(resource): +def new_resource_hook(resource, consider_active=False, reactivate_patient=False): """Return modified version of resourse as per new resource rules Products occasionally require customization of resources on creation. @@ -279,9 +279,17 @@ def new_resource_hook(resource): :returns: modified resource """ - if resource.get("id"): - # not a new resource, bail - return resource + if consider_active: + if reactivate_patient: + if resource.get("id"): + return resource + else: + if resource.get("id") and resource.get("active") is True: + return resource + else: + if resource.get("id"): + # not a new resource, bail + return resource if resource["resourceType"] == "Patient": np_extensions = current_app.config.get("NEW_PATIENT_EXTENSIONS") From 436e52fb24e50895b1187ff61cf400003d9d1750 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:25:14 -0800 Subject: [PATCH 052/107] WIP: testing active configuration --- patientsearch/api.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index b7842910..114c98b5 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -262,7 +262,7 @@ def resource_bundle(resource_type): if resource_type == "Patient": # Check for the user's configurations - active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") + active_patient_flag = True full_sequence = all( [ @@ -308,7 +308,7 @@ def post_resource(resource_type): """ token = validate_auth() - active_patient_flag = False + active_patient_flag = True reactivate_patient = False try: @@ -321,13 +321,15 @@ def post_resource(resource_type): f"{resource['resourceType']} != {resource_type}" ) if resource_type == "Patient" and active_patient_flag: - current_app.logger.debug(f"active, restore: {resource.get('active')} and type: {type(resource.get('active'))}") resource = new_resource_hook(resource, active_patient_flag, reactivate_patient) else: resource = new_resource_hook(resource) method = request.method params = request.args - + if active_patient_flag: + # Ensure it is active + resource["active"] = True + audit_HAPI_change( user_info=current_user_info(token), method=method, From 401ad85d0afbbe1ad48d8a9e73839f3f3fe3862e Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:59:40 -0800 Subject: [PATCH 053/107] Enabling reinstallation and new creation --- patientsearch/api.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 114c98b5..72246e0f 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -243,6 +243,8 @@ def resource_bundle(resource_type): """ token = validate_auth() + # Check for the user's configurations + active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") params = request.args search_params = dict(deepcopy(params)) # Necessary on ImmutableMultiDict @@ -261,9 +263,6 @@ def resource_bundle(resource_type): return jsonify_abort(status_code=400, message=str(error)) if resource_type == "Patient": - # Check for the user's configurations - active_patient_flag = True - full_sequence = all( [ params.get("subject:Patient.name.given", False), @@ -308,8 +307,8 @@ def post_resource(resource_type): """ token = validate_auth() - active_patient_flag = True - reactivate_patient = False + active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") + reactivate_patient = current_app.config.get("REACTIVATE_PATIENT") try: resource = request.get_json() @@ -327,7 +326,9 @@ def post_resource(resource_type): method = request.method params = request.args if active_patient_flag: + if not reactivate_patient and resource.get("active", True) is False: # Ensure it is active + method = "POST" resource["active"] = True audit_HAPI_change( From 6c2e93653543008f1fb7b41d2e18022b4b613f4f Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 21 Dec 2023 23:57:00 -0800 Subject: [PATCH 054/107] WIP: fixing creating new ones when user prefers --- patientsearch/src/js/context/PatientListContextProvider.js | 1 - 1 file changed, 1 deletion(-) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 72b733c6..d02051ad 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -703,7 +703,6 @@ export default function PatientListContextProvider({ children }) { }, ], birthDate: rowData.birth_date, - active: true }); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() From 9e86f10009a4fd0150535b957b92af5320d22976 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Fri, 22 Dec 2023 00:03:13 -0800 Subject: [PATCH 055/107] Fixing black --- patientsearch/api.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 72246e0f..c5aec6eb 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -267,7 +267,7 @@ def resource_bundle(resource_type): [ params.get("subject:Patient.name.given", False), params.get("subject:Patient.name.family", False), - len(params.get("subject:Patient.birthdate", "").split("eq")) > 1 + len(params.get("subject:Patient.birthdate", "").split("eq")) > 1, ] ) @@ -320,14 +320,16 @@ def post_resource(resource_type): f"{resource['resourceType']} != {resource_type}" ) if resource_type == "Patient" and active_patient_flag: - resource = new_resource_hook(resource, active_patient_flag, reactivate_patient) + resource = new_resource_hook( + resource, active_patient_flag, reactivate_patient + ) else: resource = new_resource_hook(resource) method = request.method params = request.args if active_patient_flag: if not reactivate_patient and resource.get("active", True) is False: - # Ensure it is active + # Ensure it is active method = "POST" resource["active"] = True From af535b4904fb9c6f87d23dc5ffe0003560a9c7db Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Fri, 22 Dec 2023 00:33:45 -0800 Subject: [PATCH 056/107] Allowing the user to create a new patient, even when the patient is not inactive. Should be fixed --- patientsearch/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index c5aec6eb..6aa2338f 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -328,7 +328,7 @@ def post_resource(resource_type): method = request.method params = request.args if active_patient_flag: - if not reactivate_patient and resource.get("active", True) is False: + if not reactivate_patient: # Ensure it is active method = "POST" resource["active"] = True From 10cf101c26d65409db492f7740fd7f42d6da8dcd Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 28 Dec 2023 19:35:15 -0800 Subject: [PATCH 057/107] WIP: starting working on the modal --- patientsearch/api.py | 34 ++++--- patientsearch/models/sync.py | 17 +--- .../src/js/components/ReactivatingModal.js | 91 +++++++++++++++++++ .../js/components/patientList/PatientList.js | 4 +- .../js/context/PatientListContextProvider.js | 6 ++ 5 files changed, 121 insertions(+), 31 deletions(-) create mode 100644 patientsearch/src/js/components/ReactivatingModal.js diff --git a/patientsearch/api.py b/patientsearch/api.py index 6aa2338f..52e1e4eb 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -248,6 +248,12 @@ def resource_bundle(resource_type): params = request.args search_params = dict(deepcopy(params)) # Necessary on ImmutableMultiDict + # Override if the search is specifically for inactive objects, only occurs + # when working on reactivating a patient + if request.args.get("inactive_search", False): + active_patient_flag = False + del search_params["inactive_search"] + # If the resource is not a patient, proceed with the GET if resource_type != "Patient": try: @@ -263,16 +269,9 @@ def resource_bundle(resource_type): return jsonify_abort(status_code=400, message=str(error)) if resource_type == "Patient": - full_sequence = all( - [ - params.get("subject:Patient.name.given", False), - params.get("subject:Patient.name.family", False), - len(params.get("subject:Patient.birthdate", "").split("eq")) > 1, - ] - ) - try: - if full_sequence or not active_patient_flag: + if active_patient_flag: + search_params["active"] = "true" patient = HAPI_request( token=token, method="GET", @@ -282,7 +281,6 @@ def resource_bundle(resource_type): return jsonify(patient) else: - search_params["active"] = "true" patient = HAPI_request( token=token, method="GET", @@ -309,6 +307,10 @@ def post_resource(resource_type): token = validate_auth() active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") reactivate_patient = current_app.config.get("REACTIVATE_PATIENT") + if active_patient_flag and reactivate_patient: + create_new_patient = request.args.get("create_new", False) + else: + create_new_patient = False try: resource = request.get_json() @@ -319,17 +321,13 @@ def post_resource(resource_type): "type mismatch - POSTed resource type " f"{resource['resourceType']} != {resource_type}" ) - if resource_type == "Patient" and active_patient_flag: - resource = new_resource_hook( - resource, active_patient_flag, reactivate_patient - ) - else: - resource = new_resource_hook(resource) + + resource = new_resource_hook(resource, create_new_patient) method = request.method params = request.args if active_patient_flag: - if not reactivate_patient: - # Ensure it is active + if create_new_patient: + # Ensure it is a new active patient method = "POST" resource["active"] = True diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 510ab6b6..b5046141 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -271,7 +271,7 @@ def internal_patient_search(token, patient, active_only=False): ) -def new_resource_hook(resource, consider_active=False, reactivate_patient=False): +def new_resource_hook(resource, create_new_patient = False): """Return modified version of resourse as per new resource rules Products occasionally require customization of resources on creation. @@ -279,17 +279,10 @@ def new_resource_hook(resource, consider_active=False, reactivate_patient=False) :returns: modified resource """ - if consider_active: - if reactivate_patient: - if resource.get("id"): - return resource - else: - if resource.get("id") and resource.get("active") is True: - return resource - else: - if resource.get("id"): - # not a new resource, bail - return resource + if resource.get("id") and not create_new_patient: + # not a new resource, bail + return resource + if resource["resourceType"] == "Patient": np_extensions = current_app.config.get("NEW_PATIENT_EXTENSIONS") diff --git a/patientsearch/src/js/components/ReactivatingModal.js b/patientsearch/src/js/components/ReactivatingModal.js new file mode 100644 index 00000000..45903748 --- /dev/null +++ b/patientsearch/src/js/components/ReactivatingModal.js @@ -0,0 +1,91 @@ +import React from "react"; +import PropTypes from "prop-types"; +import Modal from "@material-ui/core/Modal"; +import { makeStyles } from "@material-ui/core/styles"; +import Button from "@material-ui/core/Button"; + +const useStyles = makeStyles((theme) => ({ + flex: { + display: "flex", + alignItems: "center", + justifyContent: "center", + flexWrap: "wrap", + width: "400px", + backgroundColor: "#FFF", + position: "relative", + top: "40%", + border: `1px solid ${theme.palette.primary.main}`, + margin: "auto", + padding: theme.spacing(2), + fontSize: "1.1rem" + }, + loadingText: { + display: "block", + marginBottom: theme.spacing(2), + }, + buttonContainer: { + marginTop: theme.spacing(2), + }, + button: { + marginRight: theme.spacing(2), + }, +})); + +export default function ConfirmationModal(props) { + const classes = useStyles(); + const [open, setOpen] = React.useState(false); + + React.useEffect(() => { + setOpen(props.open); + }, [props.open]); + + const handleRestoreClick = () => { + onCreateNewClick(false); // Inform the parent that the user does not want to create a new object + onClose(); // Close the modal + }; + + const handleCreateNewClick = () => { + onCreateNewClick(true); // Inform the parent that the user wants to create a new object + onClose(); // Close the modal + }; + + return ( + +
+ + There is a deactivated patient record in the system that matches this name and birthdate. + Do you want to restore that record, or create a new one? + +
+ + +
+
+
+ ); +} + +ConfirmationModal.propTypes = { + open: PropTypes.bool, + onClose: PropTypes.func.isRequired, + onCreateNewClick: PropTypes.func.isRequired, +}; diff --git a/patientsearch/src/js/components/patientList/PatientList.js b/patientsearch/src/js/components/patientList/PatientList.js index 132c5984..0d116fdc 100644 --- a/patientsearch/src/js/components/patientList/PatientList.js +++ b/patientsearch/src/js/components/patientList/PatientList.js @@ -33,7 +33,9 @@ export default function PatientListTable() { //states errorMessage, openLoadingModal, - setOpenLoadingModal + setOpenLoadingModal, + openReactivatingModal, + setOpenReactivatingModal } = usePatientListContext(); const renderTitle = () => { diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index d02051ad..71f2eda0 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -90,6 +90,7 @@ export default function PatientListContextProvider({ children }) { : "" ); const [openLoadingModal, setOpenLoadingModal] = React.useState(false); + const [openReactivatingModal, setOpenReactivatingModal] = React.useState(false); const [openLaunchInfoModal, setOpenLaunchInfoModal] = React.useState(false); const [containNoPMPRow, setContainNoPMPRow] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState(false); @@ -713,6 +714,9 @@ export default function PatientListContextProvider({ children }) { ? constants.PDMP_SYSTEM_ERROR_MESSAGE : "Server error when looking up patient"; setOpenLoadingModal(true); + console.log("I am opening a modal!") + setOpenReactivatingModal(true); + console.log("I am finished with the modal!") fetchData( _getPatientSearchURL(rowData), { @@ -995,6 +999,8 @@ export default function PatientListContextProvider({ children }) { errorMessage, openLoadingModal, setOpenLoadingModal, + openReactivatingModal, + setOpenReactivatingModal, openLaunchInfoModal, pagination, paginationDispatch, From 384620e9767ae15680ea361a14b42e71c194d690 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 28 Dec 2023 19:42:54 -0800 Subject: [PATCH 058/107] WIP: eliminating unnecessary intermediate variable --- patientsearch/api.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 52e1e4eb..fd1d0149 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -245,14 +245,12 @@ def resource_bundle(resource_type): token = validate_auth() # Check for the user's configurations active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") - params = request.args - search_params = dict(deepcopy(params)) # Necessary on ImmutableMultiDict + params = dict(deepcopy(request.args)) # Necessary on ImmutableMultiDict # Override if the search is specifically for inactive objects, only occurs # when working on reactivating a patient if request.args.get("inactive_search", False): - active_patient_flag = False - del search_params["inactive_search"] + active_patient_flag = params.pop("inactive_search", False) # If the resource is not a patient, proceed with the GET if resource_type != "Patient": @@ -271,7 +269,7 @@ def resource_bundle(resource_type): if resource_type == "Patient": try: if active_patient_flag: - search_params["active"] = "true" + params["active"] = "true" patient = HAPI_request( token=token, method="GET", @@ -285,7 +283,7 @@ def resource_bundle(resource_type): token=token, method="GET", resource_type=resource_type, - params=search_params, + params=params, ) return jsonify(patient) @@ -307,8 +305,10 @@ def post_resource(resource_type): token = validate_auth() active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG") reactivate_patient = current_app.config.get("REACTIVATE_PATIENT") + params = dict(deepcopy(request.args)) # Necessary on ImmutableMultiDict + if active_patient_flag and reactivate_patient: - create_new_patient = request.args.get("create_new", False) + create_new_patient = params.pop("create_new", False) else: create_new_patient = False @@ -324,7 +324,6 @@ def post_resource(resource_type): resource = new_resource_hook(resource, create_new_patient) method = request.method - params = request.args if active_patient_flag: if create_new_patient: # Ensure it is a new active patient From 1c6419fba5bef7425475729ea30c08356fb13516 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 28 Dec 2023 21:13:00 -0800 Subject: [PATCH 059/107] WIP: adding debugging logs temporary --- patientsearch/models/sync.py | 1 - .../src/js/components/ReactivatingModal.js | 36 ++++++++++--------- .../js/context/PatientListContextProvider.js | 15 ++++++-- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index b5046141..8a861a26 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -282,7 +282,6 @@ def new_resource_hook(resource, create_new_patient = False): if resource.get("id") and not create_new_patient: # not a new resource, bail return resource - if resource["resourceType"] == "Patient": np_extensions = current_app.config.get("NEW_PATIENT_EXTENSIONS") diff --git a/patientsearch/src/js/components/ReactivatingModal.js b/patientsearch/src/js/components/ReactivatingModal.js index 45903748..3fe50414 100644 --- a/patientsearch/src/js/components/ReactivatingModal.js +++ b/patientsearch/src/js/components/ReactivatingModal.js @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import Modal from "@material-ui/core/Modal"; import { makeStyles } from "@material-ui/core/styles"; import Button from "@material-ui/core/Button"; +import { usePatientListContext } from "../../context/PatientListContextProvider"; const useStyles = makeStyles((theme) => ({ flex: { @@ -33,25 +34,26 @@ const useStyles = makeStyles((theme) => ({ export default function ConfirmationModal(props) { const classes = useStyles(); - const [open, setOpen] = React.useState(false); + let { + //consts + openReactivatingModal, + //methods + hasSoFClients = function () { + console.log("hasSoFClients is not defined. Unable to check."); + return false; + }, + onReactivatingModalClose = function () {}, + } = usePatientListContext(); - React.useEffect(() => { - setOpen(props.open); - }, [props.open]); - - const handleRestoreClick = () => { - onCreateNewClick(false); // Inform the parent that the user does not want to create a new object - onClose(); // Close the modal - }; - - const handleCreateNewClick = () => { - onCreateNewClick(true); // Inform the parent that the user wants to create a new object - onClose(); // Close the modal - }; + // const onRecreateClick = (createNew) => { + // onCreateNewClick(createNew); // Inform the parent that the user does not want to create a new object + // onClose(); // Close the modal + // }; return ( onReactivatingModalClose()} aria-labelledby="confirmation-modal" aria-describedby="confirmation-modal" disableAutoFocus @@ -67,14 +69,14 @@ export default function ConfirmationModal(props) { variant="contained" color="primary" className={classes.button} - onClick={handleRestoreClick} + onClick={() => onRecreateClick(false)} > Restore diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 71f2eda0..e9546510 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -210,6 +210,9 @@ export default function PatientListContextProvider({ children }) { const onLaunchDialogClose = () => { setOpenLaunchInfoModal(false); }; + const onReactivatingModalClose = (createNew) => { + setOpenReactivatingModal(false); + }; const onFiltersDidChange = (filters) => { clearTimeout(filterIntervalId); filterIntervalId = setTimeout(function () { @@ -620,6 +623,7 @@ export default function PatientListContextProvider({ children }) { .length > 0; // if last accessed field is present if (hasLastAccessedField) { + console.log("Updating the time"); // this will ensure that last accessed date, i.e. meta.lastUpdated, is being updated putPatientData( rowData.id, @@ -687,12 +691,15 @@ export default function PatientListContextProvider({ children }) { ), }, }); + console.log("I am not handling a search!"); const handleSearch = (rowData) => { + console.log("I am in a search!"); if (!rowData) { handleLaunchError("No patient data to proceed."); return false; } // search parameters + console.log("Before defining searchBody!"); const searchBody = rowData.resource ? JSON.stringify(rowData.resource) : JSON.stringify({ @@ -705,6 +712,7 @@ export default function PatientListContextProvider({ children }) { ], birthDate: rowData.birth_date, }); + console.log("Before defining searchBody!"); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() ? constants.NON_PDMP_RESULT_MESSAGE @@ -714,9 +722,9 @@ export default function PatientListContextProvider({ children }) { ? constants.PDMP_SYSTEM_ERROR_MESSAGE : "Server error when looking up patient"; setOpenLoadingModal(true); - console.log("I am opening a modal!") + console.log("I am opening a modal!"); setOpenReactivatingModal(true); - console.log("I am finished with the modal!") + console.log("I am finished with the modal!"); fetchData( _getPatientSearchURL(rowData), { @@ -733,6 +741,8 @@ export default function PatientListContextProvider({ children }) { (e) => handleErrorCallback(e) ) .then((result) => { + console.log("I have a result!"); + console.log(result) setOpenLoadingModal(false); let response = result; if (result && result.entry && result.entry[0]) { @@ -986,6 +996,7 @@ export default function PatientListContextProvider({ children }) { onDetailPanelClose, onFiltersDidChange, onLaunchDialogClose, + onReactivatingModalClose, onMyPatientsCheckboxChange, onTestPatientsCheckboxChange, shouldShowLegend, From 695151f92964a04d2ee77a71e55d5cbfcf3a06a6 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 28 Dec 2023 21:16:33 -0800 Subject: [PATCH 060/107] WIP: adding logs --- .../src/js/context/PatientListContextProvider.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index e9546510..d6d74f7c 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -623,7 +623,7 @@ export default function PatientListContextProvider({ children }) { .length > 0; // if last accessed field is present if (hasLastAccessedField) { - console.log("Updating the time"); + console.log("I am updating the time"); // this will ensure that last accessed date, i.e. meta.lastUpdated, is being updated putPatientData( rowData.id, @@ -699,7 +699,7 @@ export default function PatientListContextProvider({ children }) { return false; } // search parameters - console.log("Before defining searchBody!"); + console.log("I am before defining searchBody!"); const searchBody = rowData.resource ? JSON.stringify(rowData.resource) : JSON.stringify({ @@ -712,7 +712,7 @@ export default function PatientListContextProvider({ children }) { ], birthDate: rowData.birth_date, }); - console.log("Before defining searchBody!"); + console.log("I am after defining searchBody!"); // error message when no result returned const noResultErrorMessage = needExternalAPILookup() ? constants.NON_PDMP_RESULT_MESSAGE @@ -741,9 +741,9 @@ export default function PatientListContextProvider({ children }) { (e) => handleErrorCallback(e) ) .then((result) => { - console.log("I have a result!"); - console.log(result) + console.log("I have the result", result); setOpenLoadingModal(false); + setOpenReactivatingModal(false); let response = result; if (result && result.entry && result.entry[0]) { response = result.entry[0]; @@ -782,11 +782,13 @@ export default function PatientListContextProvider({ children }) { }; const getPatientList = (query) => { console.log("patient list query object ", query); + setOpenReactivatingModal(true); const defaults = { data: [], page: 0, totalCount: 0, }; + setOpenReactivatingModal(false); // return patient data return new Promise((resolve) => { fetchData( From 81b59694a0f88e7040afa0ff066fa71a4a78fdde Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 28 Dec 2023 21:44:43 -0800 Subject: [PATCH 061/107] Adding restoration unit tests --- tests/test_sync.py | 61 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index ed7bca72..84f5c419 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -3,7 +3,7 @@ from pytest import fixture import os -from patientsearch.models import add_identifier_to_resource_type, sync_bundle +from patientsearch.models import add_identifier_to_resource_type, sync_bundle, restore_patient def load_json(datadir, filename): @@ -329,3 +329,62 @@ def test_duplicate_inactive( # Shouldn't kill the process, but return the first result = sync_bundle(faux_token, external_patient_search_active) assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] + + +def test_reactivate_resource( + client, + mocker, + faux_token, + external_patient_search, + internal_patient_active_match, +): + """Confirm the patient gets restored""" + # Mock HAPI search finding a matching external active patient + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_active_match), + ) + + result = sync_bundle(faux_token, external_patient_search) + result = restore_patient(faux_token, result) + + assert result == internal_patient_active_match["entry"][0]["resource"] + + +def test_reactivate_inactive_resource( + client, + mocker, + faux_token, + internal_patient_active_match, + internal_patient_inactive_match, +): + """Confirm the patient gets restored""" + # Mock HAPI search finding a matching external inactive patient + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_inactive_match), + ) + + result = restore_patient(faux_token, internal_patient_inactive_match) + + assert result == internal_patient_active_match["entry"][0]["resource"] + + +def test_reactivate_active_sync_resource( + client, + mocker, + faux_token, + external_patient_search_active, + internal_patient_active_match, +): + """Confirm the patient gets restored""" + # Mock HAPI search finding a matching external active patient + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_active_match), + ) + + active_result = sync_bundle(faux_token, external_patient_search_active) + result = restore_patient(faux_token, active_result) + + assert result == internal_patient_active_match["entry"][0]["resource"] From cd3742417f9ee3a04b75f306ecc2b3632d3d5206 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 28 Dec 2023 21:49:23 -0800 Subject: [PATCH 062/107] Fixing formatting --- patientsearch/models/sync.py | 2 +- tests/test_sync.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index 8a861a26..c276a5d8 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -271,7 +271,7 @@ def internal_patient_search(token, patient, active_only=False): ) -def new_resource_hook(resource, create_new_patient = False): +def new_resource_hook(resource, create_new_patient=False): """Return modified version of resourse as per new resource rules Products occasionally require customization of resources on creation. diff --git a/tests/test_sync.py b/tests/test_sync.py index 84f5c419..7d0f260c 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -3,7 +3,11 @@ from pytest import fixture import os -from patientsearch.models import add_identifier_to_resource_type, sync_bundle, restore_patient +from patientsearch.models import ( + add_identifier_to_resource_type, + sync_bundle, + restore_patient, +) def load_json(datadir, filename): From 5c21449f1c6c491b7ef980a5d97a7e7d91a6cba4 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 28 Dec 2023 21:51:51 -0800 Subject: [PATCH 063/107] WIP: mock API call error --- tests/test_sync.py | 118 ++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index 7d0f260c..acfecdd6 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -4,8 +4,8 @@ import os from patientsearch.models import ( - add_identifier_to_resource_type, - sync_bundle, + add_identifier_to_resource_type, + sync_bundle, restore_patient, ) @@ -335,60 +335,60 @@ def test_duplicate_inactive( assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] -def test_reactivate_resource( - client, - mocker, - faux_token, - external_patient_search, - internal_patient_active_match, -): - """Confirm the patient gets restored""" - # Mock HAPI search finding a matching external active patient - mocker.patch( - "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_active_match), - ) - - result = sync_bundle(faux_token, external_patient_search) - result = restore_patient(faux_token, result) - - assert result == internal_patient_active_match["entry"][0]["resource"] - - -def test_reactivate_inactive_resource( - client, - mocker, - faux_token, - internal_patient_active_match, - internal_patient_inactive_match, -): - """Confirm the patient gets restored""" - # Mock HAPI search finding a matching external inactive patient - mocker.patch( - "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_inactive_match), - ) - - result = restore_patient(faux_token, internal_patient_inactive_match) - - assert result == internal_patient_active_match["entry"][0]["resource"] - - -def test_reactivate_active_sync_resource( - client, - mocker, - faux_token, - external_patient_search_active, - internal_patient_active_match, -): - """Confirm the patient gets restored""" - # Mock HAPI search finding a matching external active patient - mocker.patch( - "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_active_match), - ) - - active_result = sync_bundle(faux_token, external_patient_search_active) - result = restore_patient(faux_token, active_result) - - assert result == internal_patient_active_match["entry"][0]["resource"] +# def test_reactivate_resource( +# client, +# mocker, +# faux_token, +# external_patient_search, +# internal_patient_active_match, +# ): +# """Confirm the patient gets restored""" +# # Mock HAPI search finding a matching external active patient +# mocker.patch( +# "patientsearch.models.sync.requests.get", +# return_value=mock_response(internal_patient_active_match), +# ) + +# result = sync_bundle(faux_token, external_patient_search) +# result = restore_patient(faux_token, result) + +# assert result == internal_patient_active_match["entry"][0]["resource"] + + +# def test_reactivate_inactive_resource( +# client, +# mocker, +# faux_token, +# internal_patient_active_match, +# internal_patient_inactive_match, +# ): +# """Confirm the patient gets restored""" +# # Mock HAPI search finding a matching external inactive patient +# mocker.patch( +# "patientsearch.models.sync.requests.get", +# return_value=mock_response(internal_patient_inactive_match), +# ) + +# result = restore_patient(faux_token, internal_patient_inactive_match) + +# assert result == internal_patient_active_match["entry"][0]["resource"] + + +# def test_reactivate_active_sync_resource( +# client, +# mocker, +# faux_token, +# external_patient_search_active, +# internal_patient_active_match, +# ): +# """Confirm the patient gets restored""" +# # Mock HAPI search finding a matching external active patient +# mocker.patch( +# "patientsearch.models.sync.requests.get", +# return_value=mock_response(internal_patient_active_match), +# ) + +# active_result = sync_bundle(faux_token, external_patient_search_active) +# result = restore_patient(faux_token, active_result) + +# assert result == internal_patient_active_match["entry"][0]["resource"] From a47047b77c8055503f2e00e6bb0189915a2e85ba Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:30:41 -0800 Subject: [PATCH 064/107] Adding a test for new_resource_hook --- tests/test_sync.py | 90 ++++++++++++++++----------- tests/test_sync/patient_resource.json | 12 ++++ 2 files changed, 67 insertions(+), 35 deletions(-) create mode 100644 tests/test_sync/patient_resource.json diff --git a/tests/test_sync.py b/tests/test_sync.py index acfecdd6..a70b0141 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -7,6 +7,7 @@ add_identifier_to_resource_type, sync_bundle, restore_patient, + new_resource_hook, ) @@ -92,6 +93,11 @@ def internal_patient_duplicate_inactive_match(datadir): return load_json(datadir, "internal_patient_duplicate_inactive_match.json") +@fixture +def patient_resource(datadir): + return load_json(datadir, "patient_resource.json") + + def test_new_upsert( client, mocker, @@ -241,6 +247,26 @@ def test_existing_inactive( assert result == internal_patient_inactive_match["entry"][0]["resource"] +def test_existing_inactive( + client, + mocker, + faux_token, + external_patient_search_active, + internal_patient_inactive_match, +): + """Finding a matching inactive patient from active external search, return existing restored""" + + # Mock HAPI search finding a matching inactive patient + # when the service is called for the patient to be restored + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_inactive_match), + ) + + result = sync_bundle(faux_token, external_patient_search_active, True) + assert result == internal_patient_inactive_match["entry"][0]["resource"] + + def test_existing_modified( client, mocker, faux_token, external_patient_search, internal_patient_match ): @@ -335,43 +361,24 @@ def test_duplicate_inactive( assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] -# def test_reactivate_resource( -# client, -# mocker, -# faux_token, -# external_patient_search, -# internal_patient_active_match, -# ): -# """Confirm the patient gets restored""" -# # Mock HAPI search finding a matching external active patient -# mocker.patch( -# "patientsearch.models.sync.requests.get", -# return_value=mock_response(internal_patient_active_match), -# ) - -# result = sync_bundle(faux_token, external_patient_search) -# result = restore_patient(faux_token, result) - -# assert result == internal_patient_active_match["entry"][0]["resource"] - - -# def test_reactivate_inactive_resource( -# client, -# mocker, -# faux_token, -# internal_patient_active_match, -# internal_patient_inactive_match, -# ): -# """Confirm the patient gets restored""" -# # Mock HAPI search finding a matching external inactive patient -# mocker.patch( -# "patientsearch.models.sync.requests.get", -# return_value=mock_response(internal_patient_inactive_match), -# ) +def test_reactivate_resource( + client, + mocker, + faux_token, + external_patient_search, + internal_patient_active_match, +): + """Confirm the patient gets restored""" + # Mock HAPI search finding a matching external active patient + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_active_match), + ) -# result = restore_patient(faux_token, internal_patient_inactive_match) + result = sync_bundle(faux_token, external_patient_search) + result = restore_patient(faux_token, result) -# assert result == internal_patient_active_match["entry"][0]["resource"] + assert result == internal_patient_active_match["entry"][0]["resource"] # def test_reactivate_active_sync_resource( @@ -392,3 +399,16 @@ def test_duplicate_inactive( # result = restore_patient(faux_token, active_result) # assert result == internal_patient_active_match["entry"][0]["resource"] + + +def test_new_resource_hook( + client, + patient_resource, +): + """Given a resource, produce a new resource""" + + result_same = new_resource_hook(patient_resource) + assert result_same == patient_resource + + result_extended = new_resource_hook(patient_resource, True) + assert result_extended != patient_resource diff --git a/tests/test_sync/patient_resource.json b/tests/test_sync/patient_resource.json new file mode 100644 index 00000000..2be607cc --- /dev/null +++ b/tests/test_sync/patient_resource.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Patient", + "name": [ + { + "family": "skywalker", + "given": [ + "luke" + ] + } + ], + "birthDate": "2023-12-29" +} From 6dd5a736c0cd3241a7f25d0e805468f125dbe835 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:38:41 -0800 Subject: [PATCH 065/107] WIP: fixing Mock API call --- tests/test_sync.py | 64 +++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index a70b0141..63941585 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -247,24 +247,24 @@ def test_existing_inactive( assert result == internal_patient_inactive_match["entry"][0]["resource"] -def test_existing_inactive( - client, - mocker, - faux_token, - external_patient_search_active, - internal_patient_inactive_match, -): - """Finding a matching inactive patient from active external search, return existing restored""" +# def test_existing_inactive_alt( +# client, +# mocker, +# faux_token, +# external_patient_search_active, +# internal_patient_inactive_match, +# ): +# """Finding a matching inactive patient from active external search, return existing restored""" - # Mock HAPI search finding a matching inactive patient - # when the service is called for the patient to be restored - mocker.patch( - "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_inactive_match), - ) +# # Mock HAPI search finding a matching inactive patient +# # when the service is called for the patient to be restored +# mocker.patch( +# "patientsearch.models.sync.requests.get", +# return_value=mock_response(internal_patient_inactive_match), +# ) - result = sync_bundle(faux_token, external_patient_search_active, True) - assert result == internal_patient_inactive_match["entry"][0]["resource"] +# result = sync_bundle(faux_token, external_patient_search_active, True) +# assert result == internal_patient_inactive_match["entry"][0]["resource"] def test_existing_modified( @@ -361,24 +361,24 @@ def test_duplicate_inactive( assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] -def test_reactivate_resource( - client, - mocker, - faux_token, - external_patient_search, - internal_patient_active_match, -): - """Confirm the patient gets restored""" - # Mock HAPI search finding a matching external active patient - mocker.patch( - "patientsearch.models.sync.requests.get", - return_value=mock_response(internal_patient_active_match), - ) +# def test_reactivate_resource( +# client, +# mocker, +# faux_token, +# external_patient_search, +# internal_patient_active_match, +# ): +# """Confirm the patient gets restored""" +# # Mock HAPI search finding a matching external active patient +# mocker.patch( +# "patientsearch.models.sync.requests.get", +# return_value=mock_response(internal_patient_active_match), +# ) - result = sync_bundle(faux_token, external_patient_search) - result = restore_patient(faux_token, result) +# result = sync_bundle(faux_token, external_patient_search) +# result = restore_patient(faux_token, result) - assert result == internal_patient_active_match["entry"][0]["resource"] +# assert result == internal_patient_active_match["entry"][0]["resource"] # def test_reactivate_active_sync_resource( From 6b47dbf7caa0f50f08ea0bfe8976b7dc2b2f8e87 Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Fri, 29 Dec 2023 13:02:31 -0800 Subject: [PATCH 066/107] WIP: removing unnecessary test --- .../src/js/context/PatientListContextProvider.js | 1 - tests/test_sync.py | 8 ++------ tests/test_sync/patient_resource.json | 1 + 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index d6d74f7c..5772c35e 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -691,7 +691,6 @@ export default function PatientListContextProvider({ children }) { ), }, }); - console.log("I am not handling a search!"); const handleSearch = (rowData) => { console.log("I am in a search!"); if (!rowData) { diff --git a/tests/test_sync.py b/tests/test_sync.py index 63941585..e8daa391 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -400,15 +400,11 @@ def test_duplicate_inactive( # assert result == internal_patient_active_match["entry"][0]["resource"] - -def test_new_resource_hook( +def test_new_resource_hook_ext( client, patient_resource, ): - """Given a resource, produce a new resource""" - - result_same = new_resource_hook(patient_resource) - assert result_same == patient_resource + """Given a resource with create_new_patient flag set to True, produce a new resource""" result_extended = new_resource_hook(patient_resource, True) assert result_extended != patient_resource diff --git a/tests/test_sync/patient_resource.json b/tests/test_sync/patient_resource.json index 2be607cc..07b3d001 100644 --- a/tests/test_sync/patient_resource.json +++ b/tests/test_sync/patient_resource.json @@ -1,5 +1,6 @@ { "resourceType": "Patient", + "id": "1102", "name": [ { "family": "skywalker", From bd3b50e5a76a64131cfdae165e08a9ada3eac0ca Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Fri, 29 Dec 2023 13:16:18 -0800 Subject: [PATCH 067/107] Removing tests that are platform-specific --- tests/test_sync.py | 14 -------------- tests/test_sync/patient_resource.json | 13 ------------- 2 files changed, 27 deletions(-) delete mode 100644 tests/test_sync/patient_resource.json diff --git a/tests/test_sync.py b/tests/test_sync.py index e8daa391..a6f4006f 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -93,11 +93,6 @@ def internal_patient_duplicate_inactive_match(datadir): return load_json(datadir, "internal_patient_duplicate_inactive_match.json") -@fixture -def patient_resource(datadir): - return load_json(datadir, "patient_resource.json") - - def test_new_upsert( client, mocker, @@ -399,12 +394,3 @@ def test_duplicate_inactive( # result = restore_patient(faux_token, active_result) # assert result == internal_patient_active_match["entry"][0]["resource"] - -def test_new_resource_hook_ext( - client, - patient_resource, -): - """Given a resource with create_new_patient flag set to True, produce a new resource""" - - result_extended = new_resource_hook(patient_resource, True) - assert result_extended != patient_resource diff --git a/tests/test_sync/patient_resource.json b/tests/test_sync/patient_resource.json deleted file mode 100644 index 07b3d001..00000000 --- a/tests/test_sync/patient_resource.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "resourceType": "Patient", - "id": "1102", - "name": [ - { - "family": "skywalker", - "given": [ - "luke" - ] - } - ], - "birthDate": "2023-12-29" -} From 7c2b12158b0a4921f82433a28f588085694a6d2b Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Wed, 3 Jan 2024 11:10:09 -0800 Subject: [PATCH 068/107] add modal, bug fix --- patientsearch/api.py | 12 +- .../src/js/components/ReactivatingModal.js | 93 -------- .../js/components/patientList/FilterRow.js | 88 ++------ .../js/components/patientList/PatientList.js | 4 +- .../patientList/ReactivatingModal.js | 97 +++++++++ patientsearch/src/js/constants/consts.js | 10 +- .../js/context/PatientListContextProvider.js | 206 +++++++++--------- patientsearch/src/js/helpers/utility.js | 37 +++- patientsearch/src/js/models/RowData.js | 84 +++++++ patientsearch/src/styles/app.scss | 2 +- 10 files changed, 355 insertions(+), 278 deletions(-) delete mode 100644 patientsearch/src/js/components/ReactivatingModal.js create mode 100644 patientsearch/src/js/components/patientList/ReactivatingModal.js create mode 100644 patientsearch/src/js/models/RowData.js diff --git a/patientsearch/api.py b/patientsearch/api.py index fd1d0149..0da5bce7 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -249,8 +249,16 @@ def resource_bundle(resource_type): # Override if the search is specifically for inactive objects, only occurs # when working on reactivating a patient - if request.args.get("inactive_search", False): - active_patient_flag = params.pop("inactive_search", False) + inactive_search_param = request.args.get("inactive_search") + is_inactive_search = False + if inactive_search_param: + is_inactive_search = inactive_search_param.lower() in {"true", "1"} + if is_inactive_search: + active_patient_flag = False + # need to remove this param + # as unknown name/key error will be raised when passed to HAPI_request + if inactive_search_param is not None: + del params["inactive_search"] # If the resource is not a patient, proceed with the GET if resource_type != "Patient": diff --git a/patientsearch/src/js/components/ReactivatingModal.js b/patientsearch/src/js/components/ReactivatingModal.js deleted file mode 100644 index 3fe50414..00000000 --- a/patientsearch/src/js/components/ReactivatingModal.js +++ /dev/null @@ -1,93 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Modal from "@material-ui/core/Modal"; -import { makeStyles } from "@material-ui/core/styles"; -import Button from "@material-ui/core/Button"; -import { usePatientListContext } from "../../context/PatientListContextProvider"; - -const useStyles = makeStyles((theme) => ({ - flex: { - display: "flex", - alignItems: "center", - justifyContent: "center", - flexWrap: "wrap", - width: "400px", - backgroundColor: "#FFF", - position: "relative", - top: "40%", - border: `1px solid ${theme.palette.primary.main}`, - margin: "auto", - padding: theme.spacing(2), - fontSize: "1.1rem" - }, - loadingText: { - display: "block", - marginBottom: theme.spacing(2), - }, - buttonContainer: { - marginTop: theme.spacing(2), - }, - button: { - marginRight: theme.spacing(2), - }, -})); - -export default function ConfirmationModal(props) { - const classes = useStyles(); - let { - //consts - openReactivatingModal, - //methods - hasSoFClients = function () { - console.log("hasSoFClients is not defined. Unable to check."); - return false; - }, - onReactivatingModalClose = function () {}, - } = usePatientListContext(); - - // const onRecreateClick = (createNew) => { - // onCreateNewClick(createNew); // Inform the parent that the user does not want to create a new object - // onClose(); // Close the modal - // }; - - return ( - onReactivatingModalClose()} - aria-labelledby="confirmation-modal" - aria-describedby="confirmation-modal" - disableAutoFocus - disableEnforceFocus - > -
- - There is a deactivated patient record in the system that matches this name and birthdate. - Do you want to restore that record, or create a new one? - -
- - -
-
-
- ); -} - -ConfirmationModal.propTypes = { - open: PropTypes.bool, - onClose: PropTypes.func.isRequired, - onCreateNewClick: PropTypes.func.isRequired, -}; diff --git a/patientsearch/src/js/components/patientList/FilterRow.js b/patientsearch/src/js/components/patientList/FilterRow.js index 84196e2c..890d7e2c 100644 --- a/patientsearch/src/js/components/patientList/FilterRow.js +++ b/patientsearch/src/js/components/patientList/FilterRow.js @@ -14,6 +14,7 @@ import { KeyboardDatePicker, } from "@material-ui/pickers"; import { usePatientListContext } from "../../context/PatientListContextProvider"; +import RowData from "../../models/RowData"; const useStyles = makeStyles((theme) => ({ row: { @@ -68,41 +69,20 @@ export default function FilterRow() { const [lastName, setLastName] = React.useState(""); const [date, setDate] = React.useState(null); const [dateInput, setDateInput] = React.useState(null); + const getDateInput = () => (isValid(new Date(dateInput)) ? dateInput : ""); + const handleFilterChange = (firstName, lastName, birthDate) => { + const oData = RowData.create(firstName, lastName, birthDate); + onFiltersDidChange(oData.getFilters()); + }; const handleFirstNameChange = (event) => { let targetValue = event.target.value; setFirstName(targetValue); - onFiltersDidChange([ - { - field: "first_name", - value: targetValue, - }, - { - field: "last_name", - value: lastName, - }, - { - field: "birth_date", - value: isValid(new Date(dateInput)) ? dateInput : "", - }, - ]); + handleFilterChange(targetValue, lastName, getDateInput()); }; const handleLastNameChange = (event) => { let targetValue = event.target.value; setLastName(targetValue); - onFiltersDidChange([ - { - field: "last_name", - value: targetValue, - }, - { - field: "first_name", - value: firstName, - }, - { - field: "birth_date", - value: isValid(new Date(dateInput)) ? dateInput : "", - }, - ]); + handleFilterChange(firstName, targetValue, getDateInput()); }; const hasFilter = () => { return firstName || lastName || dateInput; @@ -115,29 +95,13 @@ export default function FilterRow() { return getCurrentFilters(); }; const getCurrentFilters = () => { - return { - first_name: firstName, - last_name: lastName, - birth_date: dateInput, - }; + const oData = RowData.create(firstName, lastName, getDateInput()); + return oData.getData(); }; const clearDate = () => { setDateInput(""); setDate(null); - onFiltersDidChange([ - { - field: "first_name", - value: firstName, - }, - { - field: "last_name", - value: lastName, - }, - { - field: "birth_date", - value: null, - }, - ]); + handleFilterChange(firstName, lastName, null); }; const handleClear = () => { clearFields(); @@ -244,37 +208,11 @@ export default function FilterRow() { if (!event || !isValid(event)) { if (event && String(dateInput).replace(/[-_]/g, "").length >= 8) setDate(event); - onFiltersDidChange([ - { - field: "first_name", - value: firstName, - }, - { - field: "last_name", - value: lastName, - }, - { - field: "birth_date", - value: null, - }, - ]); + handleFilterChange(firstName, lastName, null); return; } setDate(event); - onFiltersDidChange([ - { - field: "first_name", - value: firstName, - }, - { - field: "last_name", - value: lastName, - }, - { - field: "birth_date", - value: dateString, - }, - ]); + handleFilterChange(firstName, lastName, dateString); }} KeyboardButtonProps={{ color: "primary", title: "Date picker" }} /> diff --git a/patientsearch/src/js/components/patientList/PatientList.js b/patientsearch/src/js/components/patientList/PatientList.js index 0d116fdc..b9ca3651 100644 --- a/patientsearch/src/js/components/patientList/PatientList.js +++ b/patientsearch/src/js/components/patientList/PatientList.js @@ -14,6 +14,7 @@ import MyPatientsCheckbox from "./MyPatientsCheckbox"; import Pagination from "./Pagination"; import OverlayElement from "../OverlayElement"; import TestPatientsCheckbox from "./TestPatientsCheckbox"; +import ReactivatingModal from "./ReactivatingModal"; import * as constants from "../../constants/consts"; import { addMamotoTracking, hasFlagForCheckbox } from "../../helpers/utility"; @@ -34,8 +35,6 @@ export default function PatientListTable() { errorMessage, openLoadingModal, setOpenLoadingModal, - openReactivatingModal, - setOpenReactivatingModal } = usePatientListContext(); const renderTitle = () => { @@ -128,6 +127,7 @@ export default function PatientListTable() { + {renderDropdownMenu()} ); diff --git a/patientsearch/src/js/components/patientList/ReactivatingModal.js b/patientsearch/src/js/components/patientList/ReactivatingModal.js new file mode 100644 index 00000000..8168a8ae --- /dev/null +++ b/patientsearch/src/js/components/patientList/ReactivatingModal.js @@ -0,0 +1,97 @@ +import React from "react"; +//import PropTypes from "prop-types"; +import Modal from "@material-ui/core/Modal"; +import { makeStyles } from "@material-ui/core/styles"; +import Button from "@material-ui/core/Button"; +import Box from "@material-ui/core/Box"; +import { Alert } from '@material-ui/lab'; +import { usePatientListContext } from "../../context/PatientListContextProvider"; +import RowData from "../../models/RowData"; + +const useStyles = makeStyles((theme) => ({ + container: { + backgroundColor: "#FFF", + margin: "auto", + padding: theme.spacing(2), + position: "absolute", + top: "25%", + width: "480px", + left: "calc(50% - 240px)" + }, + buttonsContainer: { + padding: theme.spacing(2), + display: "flex", + justifyContent: "center", + alignItems: "center", + gap: theme.spacing(1) + } +})); + +export default function ReactivatingModal() { + const classes = useStyles(); + let { + //consts + openReactivatingModal, + currentRow, + handleSearch + } = usePatientListContext(); + + const [open, setOpen] = React.useState(openReactivatingModal); + + const onReactivate = () => { + handleSearch({ + ...getPatientDataFromFilters(), + active: true + }) + setOpen(false); + }; + const onClose = () => { + setOpen(false); + }; + const getPatientDataFromFilters = () => { + const oData = new RowData(currentRow); + return oData.data; + }; + const getPatientNameFromFilters = () => { + const oData = new RowData(currentRow); + if (!oData.lastName || !oData.lastName) return "patient"; + return [oData.lastName, oData.firstName].join(", "); + }; + + React.useEffect(() => { + setOpen(openReactivatingModal); + }, [openReactivatingModal]) + + return ( + + + + The account for {getPatientNameFromFilters()} was previously + deactivated. Do you want to re-activate it? + +
+ + +
+
+
+ ); +} diff --git a/patientsearch/src/js/constants/consts.js b/patientsearch/src/js/constants/consts.js index a426c2b4..445bcf95 100644 --- a/patientsearch/src/js/constants/consts.js +++ b/patientsearch/src/js/constants/consts.js @@ -10,6 +10,7 @@ import ArrowDownIcon from '@material-ui/icons/ArrowDropUp'; import ArrowUpIcon from '@material-ui/icons/ArrowDropDown'; import LastPage from "@material-ui/icons/LastPage"; import Search from "@material-ui/icons/Search"; +import RowData from "../models/RowData"; export const tableIcons = { Check: forwardRef((props, ref) => ( @@ -69,11 +70,10 @@ export const defaultPagination = { nextPageURL: "", prevPageURL: "", }; -export const defaultFilters = { - first_name: "", - last_name: "", - birth_date: "", -}; +export const defaultFilters = (() => { + const defaultData = RowData.create(); + return defaultData.getData(); +})(); export const fieldNameMaps = { first_name: "given", last_name: "family", diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 5772c35e..3149269c 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -12,6 +12,8 @@ import DetailPanel from "../components/patientList/DetailPanel"; import { fetchData, getAppLaunchURL, + getFirstResourceFromFhirBundle, + getErrorDiagnosticTextFromResponse, getLocalDateTimeString, getClientsByRequiredRoles, getTimeAgoDisplay, @@ -23,6 +25,7 @@ import { isEmptyArray, toTop, } from "../helpers/utility"; +import RowData from "../models/RowData"; const PatientListContext = React.createContext({}); /* * context provider component that allows patient list settings to be accessible to its children component(s) @@ -90,7 +93,8 @@ export default function PatientListContextProvider({ children }) { : "" ); const [openLoadingModal, setOpenLoadingModal] = React.useState(false); - const [openReactivatingModal, setOpenReactivatingModal] = React.useState(false); + const [openReactivatingModal, setOpenReactivatingModal] = + React.useState(false); const [openLaunchInfoModal, setOpenLaunchInfoModal] = React.useState(false); const [containNoPMPRow, setContainNoPMPRow] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState(false); @@ -178,11 +182,15 @@ export default function PatientListContextProvider({ children }) { const hasMultipleSoFClients = () => { return appClients && appClients.length > 1; }; + // if only one SoF client, use its launch params + // or param specified launching first client app + const canLanchApp = () => + hasSoFClients() && + (appClients.length === 1 || + getAppSettingByKey("LAUNCH_AFTER_PATIENT_CREATION")); const handleLaunchApp = (rowData, launchParams) => { if (!launchParams) { - // if only one SoF client, use its launch params - launchParams = - hasSoFClients() && appClients.length === 1 ? appClients[0] : null; + launchParams = canLanchApp() ? appClients[0] : null; } // if no launch params specifieid, need to handle multiple SoF clients that can be launched // open a dialog here so user can select which one to launch? @@ -210,9 +218,6 @@ export default function PatientListContextProvider({ children }) { const onLaunchDialogClose = () => { setOpenLaunchInfoModal(false); }; - const onReactivatingModalClose = (createNew) => { - setOpenReactivatingModal(false); - }; const onFiltersDidChange = (filters) => { clearTimeout(filterIntervalId); filterIntervalId = setTimeout(function () { @@ -371,22 +376,26 @@ export default function PatientListContextProvider({ children }) { launchParams = launchParams || {}; return getAppLaunchURL(patientId, { ...launchParams, ...appSettings }); }; - const _getPatientSearchURL = (data) => { + const _getPatientSearchURL = (data, searchInactive) => { + const oData = new RowData(data); + const fName = String(oData.firstName).trim(); + const lName = String(oData.lastName).trim(); + const birthDate = oData.birthDate; if (needExternalAPILookup()) { const dataURL = "/external_search/Patient"; // remove leading/trailing spaces from first/last name data sent to patient search API const params = [ - `subject:Patient.name.given=${String(data.first_name).trim()}`, - `subject:Patient.name.family=${String(data.last_name).trim()}`, - `subject:Patient.birthdate=eq${data.birth_date}`, + `subject:Patient.name.given=${fName}`, + `subject:Patient.name.family=${lName}`, + `subject:Patient.birthdate=eq${birthDate}`, ]; return `${dataURL}?${params.join("&")}`; } - return `/fhir/Patient?given=${String( - data.first_name - ).trim()}&family=${String(data.last_name).trim()}&birthdate=${ - data.birth_date - }`; + let url = `/fhir/Patient?given=${fName}&family=${lName}&birthdate=${birthDate}`; + if (searchInactive) { + url += `&inactive_search=true`; + } + return url; }; const _formatData = (data) => { if (!data) return false; @@ -422,7 +431,7 @@ export default function PatientListContextProvider({ children }) { } if (col.field === "next_message") { // TODO maybe a specific data type to handle not displaying past message? - value = isInPast(value) ? "--": getLocalDateTimeString(value); + value = isInPast(value) ? "--" : getLocalDateTimeString(value); } if (col.field) rowData[col.field] = value; }); @@ -692,102 +701,104 @@ export default function PatientListContextProvider({ children }) { }, }); const handleSearch = (rowData) => { - console.log("I am in a search!"); if (!rowData) { handleLaunchError("No patient data to proceed."); return false; } - // search parameters - console.log("I am before defining searchBody!"); - const searchBody = rowData.resource - ? JSON.stringify(rowData.resource) - : JSON.stringify({ - resourceType: "Patient", - name: [ - { - family: rowData.last_name.trim(), - given: [rowData.first_name.trim()], - }, - ], - birthDate: rowData.birth_date, - }); - console.log("I am after defining searchBody!"); - // error message when no result returned - const noResultErrorMessage = needExternalAPILookup() - ? constants.NON_PDMP_RESULT_MESSAGE - : "No matched patient found"; - // error message for API error - const fetchErrorMessage = needExternalAPILookup() - ? constants.PDMP_SYSTEM_ERROR_MESSAGE - : "Server error when looking up patient"; - setOpenLoadingModal(true); - console.log("I am opening a modal!"); - setOpenReactivatingModal(true); - console.log("I am finished with the modal!"); - fetchData( - _getPatientSearchURL(rowData), - { - ...{ - method: "PUT", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: searchBody, - }, - ...constants.noCacheParam, + setCurrentRow(rowData); + + const searchParams = { + headers: { + Accept: "application/json", + "Content-Type": "application/json", }, - (e) => handleErrorCallback(e) - ) - .then((result) => { - console.log("I have the result", result); - setOpenLoadingModal(false); - setOpenReactivatingModal(false); - let response = result; - if (result && result.entry && result.entry[0]) { - response = result.entry[0]; - } - if (!response || !response.id) { - handleLaunchError(noResultErrorMessage); - _handleRefresh(); - return false; + ...constants.noCacheParam, + }; + const getFHIRPatientData = async () => + fetchData( + _getPatientSearchURL(rowData, !!appSettings["REACTIVATE_PATIENT"]), + { + ...searchParams, + method: "GET", + }, + (e) => handleErrorCallback(e) + ); + + getFHIRPatientData().then((bundleResult) => { + const result = getFirstResourceFromFhirBundle(bundleResult); + const isInactive = typeof result.active !== "undefined" && !result.active; + if (result) { + if (!rowData.active && isInactive) { + setOpenReactivatingModal(true); + return; } - //add new table row where applicable - try { - _addDataRow(response); - } catch (e) { - console.log("Error occurred adding row to table ", e); + if (bundleResult.entry.length === 1) { + if (!isInactive && canLanchApp()) { + // found patient, not need to update/create it again + handleLaunchApp(_formatData(result)[0]); + return; + } } - const shouldLaunchApp = - appClients && - appClients.length > 0 && - (appClients.length === 1 || - (hasMultipleSoFClients() && - getAppSettingByKey("LAUNCH_AFTER_PATIENT_CREATION"))); + } + const oData = new RowData(rowData); + const payload = JSON.stringify(oData.getFhirData()); - if (shouldLaunchApp) { - // use config variable to determine whether to launch the first defined client application after account creation - handleLaunchApp(_formatData(response)[0], appClients[0]); - } else { - _handleRefresh(); - } - }) - .catch((e) => { - //log error to console - console.log(`Patient search error: ${e}`); - setOpenLoadingModal(false); - handleLaunchError(fetchErrorMessage + `

See console for detail.

`); - }); + // error message when no result returned + const noResultErrorMessage = needExternalAPILookup() + ? constants.NON_PDMP_RESULT_MESSAGE + : "Server error occurred. No result returned. See console for detail"; + // error message for API error + const fetchErrorMessage = needExternalAPILookup() + ? constants.PDMP_SYSTEM_ERROR_MESSAGE + : "Server error when looking up patient"; + setOpenLoadingModal(true); + fetchData( + _getPatientSearchURL(rowData), + { + ...searchParams, + body: payload, + method: "PUT", + }, + (e) => handleErrorCallback(e) + ) + .then((result) => { + setOpenLoadingModal(false); + let response = getFirstResourceFromFhirBundle(result); + console.log("Patient update result: ", response); + if (!response || !response.id) { + handleLaunchError( + getErrorDiagnosticTextFromResponse(response) || + noResultErrorMessage + ); + return false; + } + //add new table row where applicable + try { + _addDataRow(response); + } catch (e) { + console.log("Error occurred adding row to table ", e); + } + if (canLanchApp()) { + handleLaunchApp(_formatData(response)[0]); + } + }) + .catch((e) => { + //log error to console + console.log(`Patient search error: ${e}`); + setOpenLoadingModal(false); + handleLaunchError( + fetchErrorMessage + `

See console for detail.

` + ); + }); + }); }; const getPatientList = (query) => { console.log("patient list query object ", query); - setOpenReactivatingModal(true); const defaults = { data: [], page: 0, totalCount: 0, }; - setOpenReactivatingModal(false); // return patient data return new Promise((resolve) => { fetchData( @@ -997,7 +1008,6 @@ export default function PatientListContextProvider({ children }) { onDetailPanelClose, onFiltersDidChange, onLaunchDialogClose, - onReactivatingModalClose, onMyPatientsCheckboxChange, onTestPatientsCheckboxChange, shouldShowLegend, diff --git a/patientsearch/src/js/helpers/utility.js b/patientsearch/src/js/helpers/utility.js index 89480782..1d8cc813 100644 --- a/patientsearch/src/js/helpers/utility.js +++ b/patientsearch/src/js/helpers/utility.js @@ -457,7 +457,7 @@ export function addMamotoTracking(siteId, userId) { /* * @param dateString of type String - * @returns true if supplied dateString is determined to be less than today's date otherwise false + * @returns true if supplied dateString is determined to be less than today's date otherwise false */ export function isInPast(dateString) { if (!dateString) return false; @@ -571,9 +571,42 @@ export async function getPatientIdsByCareTeamParticipant(practitionerId) { * @param flagId, value for the query string, flags * @return boolean */ -export function hasFlagForCheckbox (flagId) { +export function hasFlagForCheckbox(flagId) { const flagQueryString = getUrlParameter("flags"); if (!flagQueryString) return false; if (!flagId) return false; return flagQueryString === flagId; } + +/* + * return first resource in a bundled FHIR resource data + * @param FHIR resource bundle + * @return FHIR object + */ +export function getFirstResourceFromFhirBundle(bundle) { + if (!bundle) return null; + if (!bundle.entry) { + if (Array.isArray(bundle)) return bundle[0]; + if (bundle.resource) return bundle.resource; + return bundle; + } + const firstEntry = Array.isArray(bundle.entry) + ? bundle.entry[0] + : bundle.entry; + if (firstEntry.resource) return firstEntry.resource; + return firstEntry; +} + +/* + * return error text from diagnostic element in a FHIR response data + * @param FHIR response data + * @return string + */ +export function getErrorDiagnosticTextFromResponse(response) { + const issue = + response && + response.issue && + Array.isArray(response.issue) && + response.issue.find((item) => item.severity === "error"); + return issue && issue.diagnostics ? issue.diagnostics : ""; +} diff --git a/patientsearch/src/js/models/RowData.js b/patientsearch/src/js/models/RowData.js new file mode 100644 index 00000000..7a8e0dc7 --- /dev/null +++ b/patientsearch/src/js/models/RowData.js @@ -0,0 +1,84 @@ +class RowData { + constructor(data) { + this.data = data; + } + get firstName() { + if (!this.data) return ""; + return this.data["first_name"]; + } + set firstName(value) { + this.data["first_name"] = value; + } + get lastName() { + if (!this.data) return ""; + return this.data["last_name"]; + } + set lastName(value) { + this.data["last_name"] = value; + } + get birthDate() { + if (!this.data) return null; + return this.data["birth_date"]; + } + set birthDate(value) { + this.data["birth_date"] = value; + } + + get activeFlag() { + if (!this.data) return null; + if ("active" in this.data) return this.data["active"]; + return null; + } + set activeFlag(value) { + if (!this.data) return null; + this.data["active"] = value; + } + getFhirData() { + if (!this.data) return null; + if (this.data.resource) return this.data.resource; + let fhirData = { + resourceType: "Patient", + name: [ + { + family: this.lastName ? this.lastName.trim() : "", + given: [this.firstName ? this.firstName.trim() : ""], + }, + ], + birthDate: this.birthDate, + }; + if (this.activeFlag) { + fhirData = { + ...fhirData, + active: this.activeFlag, + }; + } + return fhirData; + } + getData() { + return this.data; + } + getFilters() { + return [ + { + field: "first_name", + value: this.firstName, + }, + { + field: "last_name", + value: this.lastName, + }, + { + field: "birth_date", + value: this.birthDate, + }, + ]; + } + static create(firstName = "", lastName = "", birthDate = "") { + return new RowData({ + first_name: firstName, + last_name: lastName, + birth_date: birthDate, + }); + } +} +export default RowData; diff --git a/patientsearch/src/styles/app.scss b/patientsearch/src/styles/app.scss index 2fb317f1..ec651a89 100644 --- a/patientsearch/src/styles/app.scss +++ b/patientsearch/src/styles/app.scss @@ -280,7 +280,7 @@ button.disabled:focus, &:hover { filter: brightness(0.99); } - td:last-child { + td:has(.action-button) { min-width: 96px; } } From b71358871131fb857591a25fb00314e9a5cfa929 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Wed, 3 Jan 2024 11:14:54 -0800 Subject: [PATCH 069/107] lint fix --- patientsearch/api.py | 1 + .../patientList/ReactivatingModal.js | 52 +++++++++---------- patientsearch/src/js/models/RowData.js | 4 +- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 0da5bce7..21cc0870 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -255,6 +255,7 @@ def resource_bundle(resource_type): is_inactive_search = inactive_search_param.lower() in {"true", "1"} if is_inactive_search: active_patient_flag = False + # need to remove this param # as unknown name/key error will be raised when passed to HAPI_request if inactive_search_param is not None: diff --git a/patientsearch/src/js/components/patientList/ReactivatingModal.js b/patientsearch/src/js/components/patientList/ReactivatingModal.js index 8168a8ae..b9d45f9e 100644 --- a/patientsearch/src/js/components/patientList/ReactivatingModal.js +++ b/patientsearch/src/js/components/patientList/ReactivatingModal.js @@ -4,27 +4,27 @@ import Modal from "@material-ui/core/Modal"; import { makeStyles } from "@material-ui/core/styles"; import Button from "@material-ui/core/Button"; import Box from "@material-ui/core/Box"; -import { Alert } from '@material-ui/lab'; +import { Alert } from "@material-ui/lab"; import { usePatientListContext } from "../../context/PatientListContextProvider"; import RowData from "../../models/RowData"; const useStyles = makeStyles((theme) => ({ - container: { - backgroundColor: "#FFF", - margin: "auto", - padding: theme.spacing(2), - position: "absolute", - top: "25%", - width: "480px", - left: "calc(50% - 240px)" - }, - buttonsContainer: { - padding: theme.spacing(2), - display: "flex", - justifyContent: "center", - alignItems: "center", - gap: theme.spacing(1) - } + container: { + backgroundColor: "#FFF", + margin: "auto", + padding: theme.spacing(2), + position: "absolute", + top: "25%", + width: "480px", + left: "calc(50% - 240px)", + }, + buttonsContainer: { + padding: theme.spacing(2), + display: "flex", + justifyContent: "center", + alignItems: "center", + gap: theme.spacing(1), + }, })); export default function ReactivatingModal() { @@ -33,7 +33,7 @@ export default function ReactivatingModal() { //consts openReactivatingModal, currentRow, - handleSearch + handleSearch, } = usePatientListContext(); const [open, setOpen] = React.useState(openReactivatingModal); @@ -41,8 +41,8 @@ export default function ReactivatingModal() { const onReactivate = () => { handleSearch({ ...getPatientDataFromFilters(), - active: true - }) + active: true, + }); setOpen(false); }; const onClose = () => { @@ -60,7 +60,7 @@ export default function ReactivatingModal() { React.useEffect(() => { setOpen(openReactivatingModal); - }, [openReactivatingModal]) + }, [openReactivatingModal]); return ( - The account for {getPatientNameFromFilters()} was previously - deactivated. Do you want to re-activate it? + The account for {getPatientNameFromFilters()} was + previously deactivated. Do you want to re-activate it?
-
diff --git a/patientsearch/src/js/models/RowData.js b/patientsearch/src/js/models/RowData.js index 7a8e0dc7..0024ab84 100644 --- a/patientsearch/src/js/models/RowData.js +++ b/patientsearch/src/js/models/RowData.js @@ -30,8 +30,8 @@ class RowData { return null; } set activeFlag(value) { - if (!this.data) return null; - this.data["active"] = value; + if (this.data) + this.data["active"] = value; } getFhirData() { if (!this.data) return null; From 22b17f330fb863170ea87133494398ecc0177298 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Wed, 3 Jan 2024 11:17:38 -0800 Subject: [PATCH 070/107] black fix --- patientsearch/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 21cc0870..81ae59f7 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -256,8 +256,7 @@ def resource_bundle(resource_type): if is_inactive_search: active_patient_flag = False - # need to remove this param - # as unknown name/key error will be raised when passed to HAPI_request + # need to remove this param, name/key error is raised when passed to HAPI_request if inactive_search_param is not None: del params["inactive_search"] From c6d5dde73bfbe2e7a5a604ad407c35bb0bc862b0 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Wed, 3 Jan 2024 13:23:04 -0800 Subject: [PATCH 071/107] black fix --- patientsearch/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/patientsearch/api.py b/patientsearch/api.py index 81ae59f7..351b8dfb 100644 --- a/patientsearch/api.py +++ b/patientsearch/api.py @@ -255,7 +255,6 @@ def resource_bundle(resource_type): is_inactive_search = inactive_search_param.lower() in {"true", "1"} if is_inactive_search: active_patient_flag = False - # need to remove this param, name/key error is raised when passed to HAPI_request if inactive_search_param is not None: del params["inactive_search"] From 26046a4b641da8db7b3089c7b52e8a5de887ec9a Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Wed, 3 Jan 2024 15:16:02 -0800 Subject: [PATCH 072/107] fix modal text to match specs --- .../patientList/ReactivatingModal.js | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/patientsearch/src/js/components/patientList/ReactivatingModal.js b/patientsearch/src/js/components/patientList/ReactivatingModal.js index b9d45f9e..ad8739a4 100644 --- a/patientsearch/src/js/components/patientList/ReactivatingModal.js +++ b/patientsearch/src/js/components/patientList/ReactivatingModal.js @@ -1,5 +1,4 @@ import React from "react"; -//import PropTypes from "prop-types"; import Modal from "@material-ui/core/Modal"; import { makeStyles } from "@material-ui/core/styles"; import Button from "@material-ui/core/Button"; @@ -31,6 +30,7 @@ export default function ReactivatingModal() { const classes = useStyles(); let { //consts + getAppSettingByKey, openReactivatingModal, currentRow, handleSearch, @@ -40,22 +40,33 @@ export default function ReactivatingModal() { const onReactivate = () => { handleSearch({ - ...getPatientDataFromFilters(), + ...getSubjectDataFromFilters(), active: true, }); setOpen(false); }; - const onClose = () => { + const onClose = (event, reason) => { + if (reason && reason === "backdropClick") + return; setOpen(false); + setTimeout(() => window.location.reload(), 0); }; - const getPatientDataFromFilters = () => { + const getSubjectReferenceText = () => + String(getAppSettingByKey("MY_PATIENTS_FILTER_LABEL")) + .toLowerCase() + .includes("recipient") + ? "recipient" + : "patient"; + const getSubjectDataFromFilters = () => { const oData = new RowData(currentRow); return oData.data; }; - const getPatientNameFromFilters = () => { + const getSubjectInfoFromFilters = () => { const oData = new RowData(currentRow); if (!oData.lastName || !oData.lastName) return "patient"; - return [oData.lastName, oData.firstName].join(", "); + const name = [oData.lastName, oData.firstName].join(", "); + const dob = oData.birthDate ? oData.birthDate : ""; + return [name, dob].join(" "); }; React.useEffect(() => { @@ -71,9 +82,13 @@ export default function ReactivatingModal() { > - The account for {getPatientNameFromFilters()} was - previously deactivated. Do you want to re-activate it? + There is a deactivated {getSubjectReferenceText()} record in the + system that matches this name and birthdate ({" "} + {getSubjectInfoFromFilters()} ). Do you want to + restore that record? + {/* TODO: implement create new */} + {/* Note, need to consider implication where there are multiple patient records of the same name and dob, which one to reactivate if there are multiple inactive ones? */}
+ diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index acc12985..5c4dfbe3 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -376,7 +376,7 @@ export default function PatientListContextProvider({ children }) { launchParams = launchParams || {}; return getAppLaunchURL(patientId, { ...launchParams, ...appSettings }); }; - const _getPatientSearchURL = (data, searchInactive) => { + const _getPatientSearchURL = (data, params) => { const oData = new RowData(data); const fName = String(oData.firstName).trim(); const lName = String(oData.lastName).trim(); @@ -391,9 +391,19 @@ export default function PatientListContextProvider({ children }) { ]; return `${dataURL}?${params.join("&")}`; } + const searchInactive = params && params.searchInactive; + const useActiveFlag = params && params.useActiveFlag; + const isUpdate = params && params.isUpdate; + if (isUpdate && data.id) { + return `/fhir/Patient/${data.id}`; + } let url = `/fhir/Patient?given=${fName}&family=${lName}&birthdate=${birthDate}`; if (searchInactive) { url += `&inactive_search=true`; + } else { + if (useActiveFlag) { + url += `&active=true`; + } } return url; }; @@ -632,7 +642,6 @@ export default function PatientListContextProvider({ children }) { .length > 0; // if last accessed field is present if (hasLastAccessedField) { - console.log("I am updating the time"); // this will ensure that last accessed date, i.e. meta.lastUpdated, is being updated putPatientData( rowData.id, @@ -706,7 +715,8 @@ export default function PatientListContextProvider({ children }) { return false; } setCurrentRow(rowData); - + const isReactivate = params && params.reactivate; + const isCreateNew = params && params.createNew; const searchParams = { headers: { Accept: "application/json", @@ -716,7 +726,9 @@ export default function PatientListContextProvider({ children }) { }; const getFHIRPatientData = async () => fetchData( - _getPatientSearchURL(rowData, !!appSettings["REACTIVATE_PATIENT"]), + _getPatientSearchURL(rowData, { + searchInactive: !!appSettings["REACTIVATE_PATIENT"], + }), { ...searchParams, method: "GET", @@ -724,40 +736,50 @@ export default function PatientListContextProvider({ children }) { (e) => handleErrorCallback(e) ); - const isReactivate = params && params.reactivate; - getFHIRPatientData().then((bundleResult) => { const result = getFirstResourceFromFhirBundle(bundleResult); - const isInactive = typeof result.active !== "undefined" && !result.active; if (result) { - if (!isReactivate && isInactive) { - setOpenReactivatingModal(true); - return; - } - if (bundleResult.entry.length === 1) { - if (!isInactive && canLanchApp()) { - // found patient, not need to update/create it again - handleLaunchApp(_formatData(result)[0]); - return; - } else { - rowData.resource = { - ...result, - active: true, - }; - } - } else { - const activeEntries = bundleResult.entry.filter((item) => { + const activeEntries = bundleResult.entry + .filter((item) => { if (!item.resource) return false; - if (typeof item.active === "undefined") { + if (typeof item.resource.active === "undefined") { return true; } return String(item.resource.active).toLowerCase() === "true"; - }); + }) + .map((item) => item.resource); + const inactiveEntries = bundleResult.entry + .filter((item) => { + if (!item.resource) return false; + if (typeof item.resource.active === "undefined") { + return false; + } + return String(item.resource.active).toLowerCase() === "false"; + }) + .map((item) => item.resource); + const isInactive = inactiveEntries.length > 0; + if (!activeEntries.length) { + if (!isCreateNew && !isReactivate && isInactive) { + setOpenReactivatingModal(true); + return; + } + } else { if (activeEntries.length > 1) { handleErrorCallback("Multiple matched entries found."); return; } + if (!isCreateNew && canLanchApp()) { + // found patient, not need to update/create it again + handleLaunchApp(_formatData(activeEntries[0])[0]); + return; + } } + const entryToUse = activeEntries.length ? activeEntries[0] : result; + rowData.resource = { + ...entryToUse, + active: true, + }; + rowData.id = entryToUse.id; } const oData = new RowData(rowData); const payload = JSON.stringify(oData.getFhirData()); @@ -772,11 +794,14 @@ export default function PatientListContextProvider({ children }) { : "Server error when looking up patient"; setOpenLoadingModal(true); fetchData( - _getPatientSearchURL(rowData), + _getPatientSearchURL(rowData, { + useActiveFlag: !!getAppSettingByKey("ACTIVE_PATIENT_FLAG"), + isUpdate: !isCreateNew && !!rowData.id, + }), { ...searchParams, body: payload, - method: "PUT", + method: isCreateNew ? "POST" : "PUT", }, (e) => handleErrorCallback(e) ) @@ -812,7 +837,7 @@ export default function PatientListContextProvider({ children }) { }); }; const getPatientList = (query) => { - console.log("patient list query object ", query); + // console.log("patient list query object ", query); const defaults = { data: [], page: 0, From 4dd83edcb14c4e4399c010cb6f13b717db6e38fc Mon Sep 17 00:00:00 2001 From: Daniil <94884910+Filienko@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:32:22 -0800 Subject: [PATCH 076/107] Adding a test suit --- patientsearch/models/sync.py | 8 +- tests/test_sync.py | 196 +++++++++++------- ...on => external_patient_active_search.json} | 0 .../external_patient_search_not_active.json | 17 -- ...ve.json => new_patient_active_result.json} | 0 5 files changed, 121 insertions(+), 100 deletions(-) rename tests/test_sync/{external_patient_search_active.json => external_patient_active_search.json} (100%) delete mode 100644 tests/test_sync/external_patient_search_not_active.json rename tests/test_sync/{new_patient_result_active.json => new_patient_active_result.json} (100%) diff --git a/patientsearch/models/sync.py b/patientsearch/models/sync.py index c276a5d8..fc87c8b8 100644 --- a/patientsearch/models/sync.py +++ b/patientsearch/models/sync.py @@ -203,7 +203,8 @@ def different(src, dest): params = patient_as_search_params(internal_patient) # Ensure it is active - internal_patient["active"] = True + if consider_active: + internal_patient["active"] = True return HAPI_request( token=token, method="PUT", @@ -241,7 +242,7 @@ def patient_as_search_params(patient, active_only=False): ("name.given[0]", "given", ""), ("name[0].given[0]", "given", ""), ("birthDate", "birthdate", "eq"), - ("active", True, "eq"), + ("active", True, ""), ) else: search_map = ( @@ -316,7 +317,8 @@ def sync_patient(token, patient, consider_active=False): # No match, insert and return patient = new_resource_hook(resource=patient) - patient["active"] = True + if consider_active: + patient["active"] = True return HAPI_request( token=token, method="POST", diff --git a/tests/test_sync.py b/tests/test_sync.py index a6f4006f..3f0b7d49 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -39,8 +39,8 @@ def external_patient_search(datadir): @fixture -def external_patient_search_active(datadir): - return load_json(datadir, "external_patient_search_active.json") +def external_patient_active_search(datadir): + return load_json(datadir, "external_patient_active_search.json") @fixture @@ -49,8 +49,8 @@ def new_patient(datadir): @fixture -def new_patient_active(datadir): - return load_json(datadir, "new_patient_result_active.json") +def new_active_patient(datadir): + return load_json(datadir, "new_patient_active_result.json") @fixture @@ -119,13 +119,13 @@ def test_new_upsert( assert result == new_patient -def test_new_upsert_active( +def test_new_active_upsert( client, mocker, faux_token, - external_patient_search_active, + external_patient_active_search, internal_patient_miss, - new_patient_active, + new_active_patient, ): """Without finding a matching patient, should insert new and return""" @@ -138,20 +138,20 @@ def test_new_upsert_active( # Mock POST to generate new patient on HAPI mocker.patch( "patientsearch.models.sync.requests.post", - return_value=mock_response(new_patient_active), + return_value=mock_response(new_active_patient), ) - result = sync_bundle(faux_token, external_patient_search_active) - assert result == new_patient_active + result = sync_bundle(faux_token, external_patient_active_search, True) + assert result == new_active_patient -def test_upsert_inactive( +def test_new_nonactive_upsert( client, mocker, faux_token, - external_patient_search_active, + external_patient_active_search, internal_patient_miss, - new_patient_active, + new_patient, ): """Finding a matching inactive patient, user chose to generate new patient""" @@ -164,11 +164,11 @@ def test_upsert_inactive( # Mock POST to generate new patient on HAPI mocker.patch( "patientsearch.models.sync.requests.post", - return_value=mock_response(new_patient_active), + return_value=mock_response(new_patient), ) - result = sync_bundle(faux_token, external_patient_search_active) - assert result == new_patient_active + result = sync_bundle(faux_token, external_patient_active_search) + assert result == new_patient def test_adding_identifier(external_patient_search): @@ -200,11 +200,11 @@ def test_existing( assert result == internal_patient_match["entry"][0]["resource"] -def test_existing_active( +def test_active_existing( client, mocker, faux_token, - external_patient_search_active, + external_patient_active_search, internal_patient_active_match, ): """Finding a matching active patient from active external search, return existing""" @@ -215,18 +215,66 @@ def test_existing_active( return_value=mock_response(internal_patient_active_match), ) - result = sync_bundle(faux_token, external_patient_search_active) + result = sync_bundle(faux_token, external_patient_active_search, True) assert ( result["active"] == internal_patient_active_match["entry"][0]["resource"]["active"] ) -def test_existing_inactive( +def test_inactive_existing( + client, + mocker, + faux_token, + external_patient_active_search, + internal_patient_inactive_match, + internal_patient_active_match, +): + """Finding a matching inactive patient from active external search, return existing restored""" + + # Mock HAPI search finding a matching inactive patient + # when the service is called for the patient to be restored + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_inactive_match), + ) + + identified_internal = deepcopy(internal_patient_active_match["entry"][0]["resource"]) + # Mock POST to put active version of the same patient on HAPI + mocker.patch( + "patientsearch.models.sync.requests.put", + return_value=mock_response(identified_internal), + ) + + result = sync_bundle(faux_token, external_patient_active_search, True) + assert result == internal_patient_active_match["entry"][0]["resource"] + + +def test_active_nonactive_existing( + client, + mocker, + faux_token, + external_patient_active_search, + internal_patient_active_match, +): + """Finding a matching inactive patient from active external search, return existing restored""" + + # Mock HAPI search finding a matching inactive patient + # when the service is called for the patient to be restored + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_active_match), + ) + + result = sync_bundle(faux_token, external_patient_active_search, False) + assert result == internal_patient_active_match["entry"][0]["resource"] + + +def test_inactive_nonactive_existing( client, mocker, faux_token, - external_patient_search_active, + external_patient_search, internal_patient_inactive_match, ): """Finding a matching inactive patient from active external search, return existing restored""" @@ -238,28 +286,28 @@ def test_existing_inactive( return_value=mock_response(internal_patient_inactive_match), ) - result = sync_bundle(faux_token, external_patient_search_active) + result = sync_bundle(faux_token, external_patient_search, False) assert result == internal_patient_inactive_match["entry"][0]["resource"] -# def test_existing_inactive_alt( -# client, -# mocker, -# faux_token, -# external_patient_search_active, -# internal_patient_inactive_match, -# ): -# """Finding a matching inactive patient from active external search, return existing restored""" +def test_nonactive_existing( + client, + mocker, + faux_token, + external_patient_active_search, + internal_patient_inactive_match, +): + """Finding a matching inactive patient from active external search, return existing restored""" -# # Mock HAPI search finding a matching inactive patient -# # when the service is called for the patient to be restored -# mocker.patch( -# "patientsearch.models.sync.requests.get", -# return_value=mock_response(internal_patient_inactive_match), -# ) + # Mock HAPI search finding a matching inactive patient + # when the service is called for the patient to be restored + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_inactive_match), + ) -# result = sync_bundle(faux_token, external_patient_search_active, True) -# assert result == internal_patient_inactive_match["entry"][0]["resource"] + result = sync_bundle(faux_token, external_patient_active_search) + assert result == internal_patient_inactive_match["entry"][0]["resource"] def test_existing_modified( @@ -320,7 +368,7 @@ def test_duplicate_active( client, mocker, faux_token, - external_patient_search_active, + external_patient_active_search, internal_patient_duplicate_active_match, ): """Finding a matching active patient with duplicates, handle well""" @@ -332,7 +380,7 @@ def test_duplicate_active( ) # Shouldn't kill the process, but return the first - result = sync_bundle(faux_token, external_patient_search_active) + result = sync_bundle(faux_token, external_patient_active_search) assert result == internal_patient_duplicate_active_match["entry"][0]["resource"] @@ -340,7 +388,7 @@ def test_duplicate_inactive( client, mocker, faux_token, - external_patient_search_active, + external_patient_active_search, internal_patient_duplicate_inactive_match, ): """Finding a matching inactive patient with duplicates, handle well""" @@ -352,45 +400,33 @@ def test_duplicate_inactive( ) # Shouldn't kill the process, but return the first - result = sync_bundle(faux_token, external_patient_search_active) + result = sync_bundle(faux_token, external_patient_active_search) assert result == internal_patient_duplicate_inactive_match["entry"][0]["resource"] -# def test_reactivate_resource( -# client, -# mocker, -# faux_token, -# external_patient_search, -# internal_patient_active_match, -# ): -# """Confirm the patient gets restored""" -# # Mock HAPI search finding a matching external active patient -# mocker.patch( -# "patientsearch.models.sync.requests.get", -# return_value=mock_response(internal_patient_active_match), -# ) - -# result = sync_bundle(faux_token, external_patient_search) -# result = restore_patient(faux_token, result) - -# assert result == internal_patient_active_match["entry"][0]["resource"] - - -# def test_reactivate_active_sync_resource( -# client, -# mocker, -# faux_token, -# external_patient_search_active, -# internal_patient_active_match, -# ): -# """Confirm the patient gets restored""" -# # Mock HAPI search finding a matching external active patient -# mocker.patch( -# "patientsearch.models.sync.requests.get", -# return_value=mock_response(internal_patient_active_match), -# ) - -# active_result = sync_bundle(faux_token, external_patient_search_active) -# result = restore_patient(faux_token, active_result) - -# assert result == internal_patient_active_match["entry"][0]["resource"] +def test_restore_inactive_resource( + client, + mocker, + faux_token, + external_patient_active_search, + internal_patient_inactive_match, + internal_patient_active_match, +): + """Confirm the patient gets restored""" + # Mock HAPI search finding a matching external active patient + mocker.patch( + "patientsearch.models.sync.requests.get", + return_value=mock_response(internal_patient_inactive_match), + ) + + identified_internal = deepcopy(internal_patient_active_match["entry"][0]["resource"]) + # Mock POST to put active version of the same patient on HAPI + mocker.patch( + "patientsearch.models.sync.requests.put", + return_value=mock_response(identified_internal), + ) + + active_result = sync_bundle(faux_token, external_patient_active_search, True) + result = restore_patient(faux_token, active_result) + + assert result == internal_patient_active_match["entry"][0]["resource"] diff --git a/tests/test_sync/external_patient_search_active.json b/tests/test_sync/external_patient_active_search.json similarity index 100% rename from tests/test_sync/external_patient_search_active.json rename to tests/test_sync/external_patient_active_search.json diff --git a/tests/test_sync/external_patient_search_not_active.json b/tests/test_sync/external_patient_search_not_active.json deleted file mode 100644 index 2d289945..00000000 --- a/tests/test_sync/external_patient_search_not_active.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "entry": [ - { - "active": false, - "birthDate": "1977-01-12", - "gender": "male", - "name": { - "family": "skywalker", - "given": ["luke"] - }, - "resourceType": "Patient" - } - ], - "resourceType": "Bundle", - "type": "searchset" - } - \ No newline at end of file diff --git a/tests/test_sync/new_patient_result_active.json b/tests/test_sync/new_patient_active_result.json similarity index 100% rename from tests/test_sync/new_patient_result_active.json rename to tests/test_sync/new_patient_active_result.json From ca97eab822e28bfc376405e615790b496ef450e2 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 5 Jan 2024 09:23:03 -0800 Subject: [PATCH 077/107] js error fix, black and flake fixes --- .../src/js/context/PatientListContextProvider.js | 14 ++++++++++---- patientsearch/src/js/helpers/utility.js | 9 +++++++-- tests/test_sync.py | 9 ++++++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/patientsearch/src/js/context/PatientListContextProvider.js b/patientsearch/src/js/context/PatientListContextProvider.js index 5c4dfbe3..61354d64 100644 --- a/patientsearch/src/js/context/PatientListContextProvider.js +++ b/patientsearch/src/js/context/PatientListContextProvider.js @@ -737,8 +737,12 @@ export default function PatientListContextProvider({ children }) { ); getFHIRPatientData().then((bundleResult) => { - const result = getFirstResourceFromFhirBundle(bundleResult); - if (result) { + if ( + bundleResult && + bundleResult.entry && + Array.isArray(bundleResult.entry) && + bundleResult.entry.length + ) { const activeEntries = bundleResult.entry .filter((item) => { if (!item.resource) return false; @@ -774,7 +778,9 @@ export default function PatientListContextProvider({ children }) { return; } } - const entryToUse = activeEntries.length ? activeEntries[0] : result; + const entryToUse = activeEntries.length + ? activeEntries[0] + : getFirstResourceFromFhirBundle(bundleResult); rowData.resource = { ...entryToUse, active: true, @@ -837,7 +843,7 @@ export default function PatientListContextProvider({ children }) { }); }; const getPatientList = (query) => { - // console.log("patient list query object ", query); + // console.log("patient list query object ", query); const defaults = { data: [], page: 0, diff --git a/patientsearch/src/js/helpers/utility.js b/patientsearch/src/js/helpers/utility.js index 1d8cc813..24211078 100644 --- a/patientsearch/src/js/helpers/utility.js +++ b/patientsearch/src/js/helpers/utility.js @@ -586,12 +586,17 @@ export function hasFlagForCheckbox(flagId) { export function getFirstResourceFromFhirBundle(bundle) { if (!bundle) return null; if (!bundle.entry) { - if (Array.isArray(bundle)) return bundle[0]; + if (Array.isArray(bundle)) { + if (bundle.length) return bundle[0]; + else return null; + } if (bundle.resource) return bundle.resource; return bundle; } const firstEntry = Array.isArray(bundle.entry) - ? bundle.entry[0] + ? bundle.entry.length + ? bundle.entry[0] + : null : bundle.entry; if (firstEntry.resource) return firstEntry.resource; return firstEntry; diff --git a/tests/test_sync.py b/tests/test_sync.py index 3f0b7d49..c699ef7f 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -7,7 +7,6 @@ add_identifier_to_resource_type, sync_bundle, restore_patient, - new_resource_hook, ) @@ -239,7 +238,9 @@ def test_inactive_existing( return_value=mock_response(internal_patient_inactive_match), ) - identified_internal = deepcopy(internal_patient_active_match["entry"][0]["resource"]) + identified_internal = deepcopy( + internal_patient_active_match["entry"][0]["resource"] + ) # Mock POST to put active version of the same patient on HAPI mocker.patch( "patientsearch.models.sync.requests.put", @@ -419,7 +420,9 @@ def test_restore_inactive_resource( return_value=mock_response(internal_patient_inactive_match), ) - identified_internal = deepcopy(internal_patient_active_match["entry"][0]["resource"]) + identified_internal = deepcopy( + internal_patient_active_match["entry"][0]["resource"] + ) # Mock POST to put active version of the same patient on HAPI mocker.patch( "patientsearch.models.sync.requests.put", From 80a1bf24a6b0ecf9edbdeb124c28edd881938dbd Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 5 Jan 2024 10:39:51 -0800 Subject: [PATCH 078/107] minor code refactor --- .../patientList/ReactivatingModal.js | 2 - .../js/context/PatientListContextProvider.js | 38 +++++++++---------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/patientsearch/src/js/components/patientList/ReactivatingModal.js b/patientsearch/src/js/components/patientList/ReactivatingModal.js index 5071c1eb..17b1f1b6 100644 --- a/patientsearch/src/js/components/patientList/ReactivatingModal.js +++ b/patientsearch/src/js/components/patientList/ReactivatingModal.js @@ -91,8 +91,6 @@ export default function ReactivatingModal() { {getSubjectInfoFromFilters()} ). Do you want to restore that record or create a new one? - {/* TODO: implement create new */} - {/* Note, need to consider implication where there are multiple patient records of the same name and dob, which one to reactivate if there are multiple inactive ones? */}