Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/deactivate account fix #204

Merged
merged 115 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
63498a6
Allowed accessed to only active external patients, reinstating de-act…
Filienko Nov 18, 2023
32946f5
Added a simple test case
Filienko Nov 18, 2023
15278e6
Added a simple test case
Filienko Nov 18, 2023
efa77e7
Merge branch 'feature/deactivate-account-fix' of https://github.com/u…
Filienko Nov 18, 2023
a516388
WIP: fixing the patient
Filienko Nov 30, 2023
5507ad1
add a test case for a deactivated patient search
Filienko Nov 30, 2023
cac1571
adding choice to restore local patient
Filienko Nov 30, 2023
4ba6cba
adding choice to restore local patient
Filienko Nov 30, 2023
56dd2c9
WIP: experimenting with tests
Filienko Nov 30, 2023
d33de4f
WIP: reinstating abilities
Filienko Dec 1, 2023
f4ada44
WIP: changing active from bool to str
Filienko Dec 11, 2023
6738ba6
WIP: changing active from str back to bool
Filienko Dec 13, 2023
dd4a0c9
WIP: modifying active to be consistently boolean for json
Filienko Dec 13, 2023
cfb4d90
Adding tests for sync function
Filienko Dec 13, 2023
95084de
WIP: modifying new tests
Filienko Dec 13, 2023
e60fe9c
WIP: fixing tests
Filienko Dec 13, 2023
d1c0ed4
Refactoring tests
Filienko Dec 14, 2023
f50edeb
WIP: fixing tests
Filienko Dec 14, 2023
75a0920
WIP: fixing tests
Filienko Dec 14, 2023
53b3d9f
WIP: assessing tests
Filienko Dec 14, 2023
5207690
WIP: adding tests
Filienko Dec 14, 2023
44e22e2
Fixing black and flake8
Filienko Dec 14, 2023
ee370b6
Fixed tests to follow black and flake8
Filienko Dec 14, 2023
b42bd3e
WIP: fixing tests
Filienko Dec 14, 2023
61e09cc
WIP: not displaying deactivated patients
Filienko Dec 14, 2023
f5e4f71
WIP: not displaying deactivated patients
Filienko Dec 14, 2023
6ee537a
Merge branch 'feature/deactivate-account-fix' of https://github.com/u…
Filienko Dec 14, 2023
9cd1e4f
Adding enviroment variables
Filienko Dec 15, 2023
d96b22f
WIP: testing front-end change being reflected in the list
Filienko Dec 21, 2023
43ba152
WIP: testing the search
Filienko Dec 21, 2023
7a35a06
WIP: testing active
Filienko Dec 21, 2023
8165b7e
WIP: working on active
Filienko Dec 21, 2023
9e24522
WIP: enabling ignoring the inactive/active to COSRI
Filienko Dec 21, 2023
3e746e7
WIP: adding a confirmation pop-up
Filienko Dec 21, 2023
6116fe7
WIP: adding modal
Filienko Dec 21, 2023
1f5a4c2
WIP: working on modal
Filienko Dec 21, 2023
e5b19f4
WIP: fixing the tests
Filienko Dec 22, 2023
75f03c5
WIP: fixing the tests
Filienko Dec 22, 2023
3aa2895
WIP: fixing the active search
Filienko Dec 22, 2023
6bfec1e
WIP: fixing active search
Filienko Dec 22, 2023
c08cb8c
WIP: fixing active search
Filienko Dec 22, 2023
b394433
WIP: fixing active
Filienko Dec 22, 2023
9e4007c
WIP: fixing active search
Filienko Dec 22, 2023
3f00ea8
WIP: fixing activea
Filienko Dec 22, 2023
e5cbcac
WIP: fixing test
Filienko Dec 22, 2023
0613f15
WIP: fixing active
Filienko Dec 22, 2023
add0709
WIP: fixing active
Filienko Dec 22, 2023
e5963c5
WIP: fixing active
Filienko Dec 22, 2023
a98824b
WIP: fixing active
Filienko Dec 22, 2023
2907a07
WIP: fixing active
Filienko Dec 22, 2023
d3fa571
WIP: fixing active
Filienko Dec 22, 2023
6d72a32
WIP: temporary debugging tools added
Filienko Dec 22, 2023
ca17c34
WIP: adding reinstation/creating new one option
Filienko Dec 22, 2023
436e52f
WIP: testing active configuration
Filienko Dec 22, 2023
401ad85
Enabling reinstallation and new creation
Filienko Dec 22, 2023
6c2e936
WIP: fixing creating new ones when user prefers
Filienko Dec 22, 2023
9e86f10
Fixing black
Filienko Dec 22, 2023
af535b4
Allowing the user to create a new patient, even when the patient is n…
Filienko Dec 22, 2023
10cf101
WIP: starting working on the modal
Filienko Dec 29, 2023
384620e
WIP: eliminating unnecessary intermediate variable
Filienko Dec 29, 2023
1c6419f
WIP: adding debugging logs temporary
Filienko Dec 29, 2023
695151f
WIP: adding logs
Filienko Dec 29, 2023
81b5969
Adding restoration unit tests
Filienko Dec 29, 2023
cd37424
Fixing formatting
Filienko Dec 29, 2023
5c21449
WIP: mock API call error
Filienko Dec 29, 2023
a47047b
Adding a test for new_resource_hook
Filienko Dec 29, 2023
6dd5a73
WIP: fixing Mock API call
Filienko Dec 29, 2023
6b47dbf
WIP: removing unnecessary test
Filienko Dec 29, 2023
bd3b50e
Removing tests that are platform-specific
Filienko Dec 29, 2023
7c2b121
add modal, bug fix
Jan 3, 2024
b713588
lint fix
Jan 3, 2024
22b17f3
black fix
Jan 3, 2024
c6d5dde
black fix
Jan 3, 2024
26046a4
fix modal text to match specs
Jan 3, 2024
d87c84d
search code fix
Jan 4, 2024
c3f42a8
add check for multiple matched entries
Jan 4, 2024
3189bf1
implment create new in modal
Jan 4, 2024
4dd83ed
Adding a test suit
Filienko Jan 5, 2024
71055f5
Merge branch 'feature/deactivate-account-fix' of https://github.com/u…
Filienko Jan 5, 2024
ca97eab
js error fix, black and flake fixes
Jan 5, 2024
80a1bf2
minor code refactor
Jan 5, 2024
1d57ab3
Setting the default configuration to not consider active/not reactiva…
Filienko Jan 5, 2024
08668fd
Merge branch 'feature/deactivate-account-fix' of https://github.com/u…
Filienko Jan 5, 2024
c19618f
Modifying the comments to reflect proposed test setup
Filienko Jan 5, 2024
48beb57
Removing unnecessary test
Filienko Jan 5, 2024
bf890d3
Moving active attribute change to api.py to correspond to how GET works;
Filienko Jan 7, 2024
4c3336c
WIP: making records without active attribute act as active
Filienko Jan 7, 2024
d872f8b
removing inproper use of the js object property
Filienko Jan 9, 2024
0f463f7
WIP: ensure active being true only when active is considered
Filienko Jan 10, 2024
ebcb485
Moving active clause to api backend
Filienko Jan 10, 2024
ac15fb8
Ensure that proper active configuration is kept both for PUT and POST
Filienko Jan 10, 2024
7b7097c
Removing duplicate code in a store GET api call
Filienko Jan 10, 2024
f65c594
addressing the comments
Filienko Jan 10, 2024
6129dd7
WIP: adding 500 exception error for the duplicate phone number condition
Filienko Jan 16, 2024
33ab302
display error from API
Jan 16, 2024
fec5223
Moving the phone check to PUT
Filienko Jan 16, 2024
7d647be
Merge branch 'feature/deactivate-account-fix' of https://github.com/u…
Filienko Jan 16, 2024
4ab27cc
fix error display
Jan 16, 2024
59e906c
lint, black fixes
Jan 16, 2024
536951d
Making PUT returning 500 error if active phone number already exists …
Filienko Jan 17, 2024
9a21985
Merge branch 'feature/deactivate-account-fix' of https://github.com/u…
Filienko Jan 17, 2024
81579be
Addressing formatting issues
Filienko Jan 17, 2024
77d4fcc
allow clicking create button after error
Jan 17, 2024
02173cd
Merge branch 'feature/deactivate-account-fix' of https://github.com/u…
Jan 17, 2024
3d3f433
black fix
Jan 17, 2024
40124cc
lint fix
Jan 17, 2024
341d039
Changing the error message to correspond to the isacc requirements
Filienko Jan 17, 2024
6e312a6
Fixing format
Filienko Jan 17, 2024
6dde9c7
Format fix
Filienko Jan 17, 2024
a1a8045
fix check for reactivating
Jan 17, 2024
06cb00e
Merge branch 'feature/deactivate-account-fix' of https://github.com/u…
Jan 17, 2024
fef553d
WIP: fixing an error
Filienko Jan 18, 2024
9dab9c7
Changed to proper restoration
Filienko Jan 18, 2024
f62ae67
Formatting
Filienko Jan 18, 2024
4e40df9
Removed flag change
Filienko Jan 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 90 additions & 5 deletions patientsearch/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -24,6 +25,7 @@
internal_patient_search,
new_resource_hook,
sync_bundle,
restore_patient,
)
from patientsearch.extensions import oidc
from patientsearch.jsonify_abort import jsonify_abort
Expand Down Expand Up @@ -228,6 +230,7 @@ def bundle_getpages():
return jsonify_abort(status_code=400, message=str(error))


@api_blueprint.route("/fhir/<string:resource_type>", methods=["GET"])
@api_blueprint.route("/fhir/<string:resource_type>", methods=["GET"])
def resource_bundle(resource_type):
"""Query HAPI for resource_type and return as JSON FHIR Bundle
Expand All @@ -241,13 +244,23 @@ def resource_bundle(resource_type):
"""
token = validate_auth()
# Check for the store's configurations
active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG")
params = dict(deepcopy(request.args)) # Necessary on ImmutableMultiDict

# Override if the search is specifically for inactive objects
if request.args.get("inactive_search") in {"true", "1"}:
del params["inactive_search"]
Filienko marked this conversation as resolved.
Show resolved Hide resolved
elif active_patient_flag:
params["active"] = "true"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much better, thank you.

try:
return jsonify(
HAPI_request(
token=token,
method="GET",
resource_type=resource_type,
params=request.args,
params=params,
)
)
except (RuntimeError, ValueError) as error:
Expand All @@ -266,6 +279,9 @@ def post_resource(resource_type):
"""
token = validate_auth()
# Check for the store's configurations
active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG")

try:
resource = request.get_json()
if not resource:
Expand All @@ -278,6 +294,10 @@ def post_resource(resource_type):

resource = new_resource_hook(resource)
method = request.method
if active_patient_flag and resource_type == "Patient":
# Ensure it is an active patient
resource["active"] = True

audit_HAPI_change(
user_info=current_user_info(token),
method=method,
Expand Down Expand Up @@ -311,9 +331,52 @@ def update_resource_by_id(resource_type, resource_id):
redirect. Client should watch for 401 and redirect appropriately.
"""
token = validate_auth()
# Check for the store's configurations
active_patient_flag = current_app.config.get("ACTIVE_PATIENT_FLAG")
params = dict(deepcopy(request.args)) # Necessary on ImmutableMultiDict
resource = request.get_json()

# This portion of code is only invoked when restoring a patient
# and returns 500 error if patient's phone number is already in use
if resource_type == "Patient" and active_patient_flag:
try:
# Get our patient in order to access his phone number
patient = HAPI_request(
method="GET",
resource_type=resource_type,
token=token,
resource_id=resource_id,
)
telecom = patient.get("telecom")
if telecom:
# Assuming there is one telecom, looking for phone number among active patients
telecom_entry = telecom[0]
telecom_value = telecom_entry.get("value")
params = {
"telecom": telecom_value,
"active": "true",
}

duplicates = HAPI_request(
token=token,
method="GET",
resource_type=resource_type,
params=params,
)

# Raise a 500 error if active patients with the same phone number have been found
if duplicates["total"] > 0:
first = duplicates["entry"][0]["resource"]["name"][0]["given"][0]
last = duplicates["entry"][0]["resource"]["name"][0]["family"]
error_message = f"""The account can't be restored because
it's phone number, {telecom_value} is now used by another
account {first} {last}"""
raise RuntimeError(error_message)

except (RuntimeError, ValueError) as error:
return jsonify_abort(status_code=500, message=str(error))

try:
resource = request.get_json()
if not resource:
return jsonify_abort(
status_code=400,
Expand All @@ -337,6 +400,9 @@ def update_resource_by_id(resource_type, resource_id):
else:
ignorable_id_increment_audit = True
resource["identifier"] = identifiers
if active_patient_flag:
# Ensure it is an active patient
resource["active"] = True

method = "PUT"
if not ignorable_id_increment_audit:
Expand All @@ -356,6 +422,7 @@ def update_resource_by_id(resource_type, resource_id):
token=token,
)
)

except (RuntimeError, ValueError) as error:
return jsonify_abort(status_code=400, message=str(error))

Expand Down Expand Up @@ -463,10 +530,18 @@ def external_search(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 resource_type == "Patient":
# Only consider active external patients
params["active"] = "true"

# Tag any matching results with identifier naming source
try:
external_search_bundle = add_identifier_to_resource_type(
bundle=external_request(token, resource_type, request.args),
bundle=external_request(token, resource_type, params),
resource_type=resource_type,
identifier={
"system": "https://github.com/uwcirg/script-fhir-facade",
Expand All @@ -491,7 +566,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)
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:
Expand All @@ -500,12 +577,18 @@ 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, not reactivate_patient
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using keyword arguments will prevent getting them out of order, and make this easier to read/review. i had to look up internal_patient_search to see what that third parameter is. i.e. active_only=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"]
active = local_fhir_patient.get("active", True)
if reactivate_patient and not active:
local_fhir_patient = restore_patient(token, local_fhir_patient)

if internal_bundle["total"] > 1:
audit_entry(
f"found multiple internal matches ({patient}), return first",
Expand All @@ -525,6 +608,8 @@ def external_search(resource_type):
resource_type=resource_type,
resource=patient,
)
if active_patient_flag:
patient["active"] = True
local_fhir_patient = HAPI_request(
token=token, method=method, resource_type="Patient", resource=patient
)
Expand Down
2 changes: 2 additions & 0 deletions patientsearch/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +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", "false").lower() == "true"
REACTIVATE_PATIENT = os.getenv("REACTIVATE_PATIENT", "false").lower() == "true"
2 changes: 2 additions & 0 deletions patientsearch/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
internal_patient_search,
new_resource_hook,
sync_bundle,
restore_patient,
)

__all__ = [
Expand All @@ -16,4 +17,5 @@
"internal_patient_search",
"new_resource_hook",
"sync_bundle",
"restore_patient",
]
66 changes: 56 additions & 10 deletions patientsearch/models/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -194,10 +194,27 @@ 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 not consider_active or internal_patient.get("active", False):
return internal_patient

params = patient_as_search_params(internal_patient)
# Ensure it is active
internal_patient["active"] = True
Filienko marked this conversation as resolved.
Show resolved Hide resolved
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 it is active, skip if active parameter is not considered
if consider_active:
internal_patient["active"] = True
return HAPI_request(
token=token,
method="PUT",
Expand All @@ -208,7 +225,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
Expand All @@ -221,6 +238,10 @@ def patient_as_search_params(patient):
("name[0].given[0]", "given", ""),
("birthDate", "birthdate", "eq"),
)
if active_only:
Filienko marked this conversation as resolved.
Show resolved Hide resolved
# Change the search params if we are considering only active patients in search
search_map = search_map + (("active", True, ""),)

search_params = {}

for path, queryterm, compstr in search_map:
Expand All @@ -230,9 +251,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
)
Expand All @@ -259,7 +281,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)
Expand All @@ -274,12 +296,36 @@ 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

# No match, insert and return
patient = new_resource_hook(resource=patient)
if consider_active:
patient["active"] = True
return HAPI_request(
token=token,
method="POST",
resource_type="Patient",
resource=patient,
)


def restore_patient(token, patient):
"""Restore single internal patient resource"""
# If the patient is already active, bail
if patient.get("active", False):
return patient
patient["active"] = True

return HAPI_request(
token=token, method="POST", resource_type="Patient", resource=patient
token=token,
method="PUT",
resource_type="Patient",
resource=patient,
resource_id=patient["id"],
)
Loading
Loading