From 6402536d0b7d7e871098805dfffd0ce8d148dce4 Mon Sep 17 00:00:00 2001 From: cason Date: Tue, 29 Oct 2024 12:36:24 -0400 Subject: [PATCH 1/4] feat: add fhir parser module --- docs/process_for_adding_feature.md | 2 +- src/recordlinker/hl7/__init__.py | 0 src/recordlinker/hl7/fhir.py | 87 ++++++++++++++++ src/recordlinker/linking/link.py | 80 -------------- src/recordlinker/routes/link_router.py | 62 ++++++++++- tests/unit/hl7/test_fhir.py | 138 +++++++++++++++++++++++++ tests/unit/linking/test_link.py | 130 +---------------------- tests/unit/routes/test_link_router.py | 130 ++++++++++++++++++++++- 8 files changed, 415 insertions(+), 214 deletions(-) create mode 100644 src/recordlinker/hl7/__init__.py create mode 100644 src/recordlinker/hl7/fhir.py create mode 100644 tests/unit/hl7/test_fhir.py diff --git a/docs/process_for_adding_feature.md b/docs/process_for_adding_feature.md index 736b2f4b..fedb6dd3 100644 --- a/docs/process_for_adding_feature.md +++ b/docs/process_for_adding_feature.md @@ -11,7 +11,7 @@ - Update the [PIIRecord.feature_iter](https://github.com/CDCgov/RecordLinker/blob/a672d2b6409cbd1a08f729d94fba5692f57f6fc6/src/recordlinker/schemas/pii.py#L246) method to return the value of the new feature when it's used for comparison. ### Extract the FHIR Field in `fhir_record_to_pii_record` -- In [src/recordlinker/linking/link.py](https://github.com/CDCgov/RecordLinker/blob/a672d2b6409cbd1a08f729d94fba5692f57f6fc6/src/recordlinker/linking/link.py), update the [fhir_record_to_pii_record](https://github.com/CDCgov/RecordLinker/blob/a672d2b6409cbd1a08f729d94fba5692f57f6fc6/src/recordlinker/linking/link.py#L26) function to map the relevant FHIR field to the new feature in [PIIRecord](https://github.com/CDCgov/RecordLinker/blob/c85f555e5da91d54eb8c51e3bdf0789d1e204b2f/src/recordlinker/schemas/pii.py#L97). +- In [src/recordlinker/linking/link.py](https://github.com/CDCgov/RecordLinker/blob/e8a64407b6e8564595cad6380d5291e9f5c959e3/src/recordlinker/parsers/fhir.py), update the [fhir_record_to_pii_record](https://github.com/CDCgov/RecordLinker/blob/e8a64407b6e8564595cad6380d5291e9f5c959e3/src/recordlinker/parsers/fhir.py#L12) function to map the relevant FHIR field to the new feature in [PIIRecord](https://github.com/CDCgov/RecordLinker/blob/e8a64407b6e8564595cad6380d5291e9f5c959e3/src/recordlinker/schemas/pii.py#L141). ### Update the Tests - Add or modify unit tests to verify that the new feature is properly extracted, mapped, and compared. \ No newline at end of file diff --git a/src/recordlinker/hl7/__init__.py b/src/recordlinker/hl7/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/recordlinker/hl7/fhir.py b/src/recordlinker/hl7/fhir.py new file mode 100644 index 00000000..f7df1824 --- /dev/null +++ b/src/recordlinker/hl7/fhir.py @@ -0,0 +1,87 @@ +""" +recordlinker.parsers.fhir +~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module is used to handle fhir parsing +""" + +import typing + +import pydantic + +from recordlinker import schemas + + +def fhir_record_to_pii_record(fhir_record: dict) -> schemas.PIIRecord: + """ + Parse the FHIR record into a PIIRecord object + """ + val = { + "external_id": fhir_record.get("id"), + "name": fhir_record.get("name", []), + "birthDate": fhir_record.get("birthDate"), + "sex": fhir_record.get("gender"), + "address": fhir_record.get("address", []), + "mrn": None, + "ssn": None, + "race": None, + "gender": None, + "telecom": fhir_record.get("telecom", []), + } + for identifier in fhir_record.get("identifier", []): + for coding in identifier.get("type", {}).get("coding", []): + if coding.get("code") == "MR": + val["mrn"] = identifier.get("value") + elif coding.get("code") == "SS": + val["ssn"] = identifier.get("value") + for address in val["address"]: + address["county"] = address.get("district", "") + for extension in address.get("extension", []): + if extension.get("url") == "http://hl7.org/fhir/StructureDefinition/geolocation": + for coord in extension.get("extension", []): + if coord.get("url") == "latitude": + address["latitude"] = coord.get("valueDecimal") + elif coord.get("url") == "longitude": + address["longitude"] = coord.get("valueDecimal") + for extension in fhir_record.get("extension", []): + if extension.get("url") == "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race": + for ext in extension.get("extension", []): + if ext.get("url") == "ombCategory": + val["race"] = ext.get("valueCoding", {}).get("display") + if extension.get("url") == "http://hl7.org/fhir/StructureDefinition/individual-genderIdentity": + for ext in extension.get("extension", []): + if ext.get("url") == "value": + for coding in ext.get("valueCodeableConcept", {}).get("coding", []): + val["gender"] = coding.get("display") + + return schemas.PIIRecord(**val) + +def add_person_resource( + person_id: str, + patient_id: typing.Optional[str] = "", + bundle: dict = pydantic.Field(description="A FHIR bundle"), +) -> dict: + """ + Adds a simplified person resource to a bundle if the patient resource in the bundle + matches an existing record in the Master Patient Index. Returns the bundle with + the newly added person resource. + + :param person_id: _description_ + :param patient_id: _description_ + :param bundle: _description_, defaults to Field(description="A FHIR bundle") + :return: _description_ + """ + person_resource = { + "fullUrl": f"urn:uuid:{person_id}", + "resource": { + "resourceType": "Person", + "id": f"{person_id}", + "link": [{"target": {"reference": f"Patient/{patient_id}"}}], + }, + "request": { + "method": "PUT", + "url": f"Person/{person_id}", + }, + } + bundle.get("entry", []).append(person_resource) + return bundle \ No newline at end of file diff --git a/src/recordlinker/linking/link.py b/src/recordlinker/linking/link.py index e3e4206a..0cd5a6c4 100644 --- a/src/recordlinker/linking/link.py +++ b/src/recordlinker/linking/link.py @@ -9,7 +9,6 @@ import typing import uuid -import pydantic from sqlalchemy import orm from recordlinker import models @@ -29,85 +28,6 @@ TRACER = MockTracer() - -# TODO: This is a FHIR specific function, should be moved to a FHIR module -def fhir_record_to_pii_record(fhir_record: dict) -> schemas.PIIRecord: - """ - Parse the FHIR record into a PIIRecord object - """ - val = { - "external_id": fhir_record.get("id"), - "name": fhir_record.get("name", []), - "birthDate": fhir_record.get("birthDate"), - "sex": fhir_record.get("gender"), - "address": fhir_record.get("address", []), - "mrn": None, - "ssn": None, - "race": None, - "gender": None, - "telecom": fhir_record.get("telecom", []), - } - for identifier in fhir_record.get("identifier", []): - for coding in identifier.get("type", {}).get("coding", []): - if coding.get("code") == "MR": - val["mrn"] = identifier.get("value") - elif coding.get("code") == "SS": - val["ssn"] = identifier.get("value") - for address in val["address"]: - address["county"] = address.get("district", "") - for extension in address.get("extension", []): - if extension.get("url") == "http://hl7.org/fhir/StructureDefinition/geolocation": - for coord in extension.get("extension", []): - if coord.get("url") == "latitude": - address["latitude"] = coord.get("valueDecimal") - elif coord.get("url") == "longitude": - address["longitude"] = coord.get("valueDecimal") - for extension in fhir_record.get("extension", []): - if extension.get("url") == "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race": - for ext in extension.get("extension", []): - if ext.get("url") == "ombCategory": - val["race"] = ext.get("valueCoding", {}).get("display") - if extension.get("url") == "http://hl7.org/fhir/StructureDefinition/individual-genderIdentity": - for ext in extension.get("extension", []): - if ext.get("url") == "value": - for coding in ext.get("valueCodeableConcept", {}).get("coding", []): - val["gender"] = coding.get("display") - - return schemas.PIIRecord(**val) - - -# TODO: This is a FHIR specific function, should be moved to a FHIR module -def add_person_resource( - person_id: str, - patient_id: typing.Optional[str] = "", - bundle: dict = pydantic.Field(description="A FHIR bundle"), -) -> dict: - """ - Adds a simplified person resource to a bundle if the patient resource in the bundle - matches an existing record in the Master Patient Index. Returns the bundle with - the newly added person resource. - - :param person_id: _description_ - :param patient_id: _description_ - :param bundle: _description_, defaults to Field(description="A FHIR bundle") - :return: _description_ - """ - person_resource = { - "fullUrl": f"urn:uuid:{person_id}", - "resource": { - "resourceType": "Person", - "id": f"{person_id}", - "link": [{"target": {"reference": f"Patient/{patient_id}"}}], - }, - "request": { - "method": "PUT", - "url": f"Person/{person_id}", - }, - } - bundle.get("entry", []).append(person_resource) - return bundle - - def compare( record: schemas.PIIRecord, patient: models.Patient, algorithm_pass: models.AlgorithmPass ) -> bool: diff --git a/src/recordlinker/routes/link_router.py b/src/recordlinker/routes/link_router.py index a42b5626..4ff56397 100644 --- a/src/recordlinker/routes/link_router.py +++ b/src/recordlinker/routes/link_router.py @@ -15,6 +15,7 @@ from recordlinker.database import get_session from recordlinker.linking import algorithm_service from recordlinker.linking import link +from recordlinker.hl7 import fhir router = fastapi.APIRouter() @@ -108,7 +109,7 @@ async def link_dibbs( ) # convert record to PII - pii_record: schemas.PIIRecord = link.fhir_record_to_pii_record(record_to_link) + pii_record: schemas.PIIRecord = fhir.fhir_record_to_pii_record(record_to_link) # Now link the record try: @@ -118,7 +119,7 @@ async def link_dibbs( algorithm=algorithm, external_person_id=external_id, ) - updated_bundle = link.add_person_resource( + updated_bundle = fhir.add_person_resource( str(new_person_id), pii_record.external_id, input_bundle ) return schemas.LinkFhirResponse(found_match=found_match, updated_bundle=updated_bundle) @@ -130,3 +131,60 @@ async def link_dibbs( updated_bundle=input_bundle, message=f"Could not connect to database: {err}", ) + +@router.post("/fhir", summary="Link FHIR") +async def link_fhir( + request: fastapi.Request, + input: typing.Annotated[schemas.LinkFhirInput, fastapi.Body()], + response: fastapi.Response, + db_session: orm.Session = fastapi.Depends(get_session), +) -> schemas.LinkResponse: + """ + Compare a FHIR bundle with records in the Master Patient Index (MPI) to + check for matches with existing patient records If matches are found, + returns the patient and person reference id's + """ + input_bundle = input.bundle + external_id = input.external_person_id + + if input.algorithm: + algorithm = algorithm_service.get_algorithm(db_session, input.algorithm) + else: + algorithm = algorithm_service.default_algorithm(db_session) + + if not algorithm: + response.status_code = fastapi.status.HTTP_422_UNPROCESSABLE_ENTITY + raise fastapi.HTTPException(status_code=422, detail="Error: Invalid algorithm specified") + + # Now extract the patient record we want to link + try: + record_to_link = [ + entry.get("resource") + for entry in input_bundle.get("entry", []) + if entry.get("resource", {}).get("resourceType", "") == "Patient" + ][0] + except IndexError: + response.status_code = fastapi.status.HTTP_400_BAD_REQUEST + raise fastapi.HTTPException(status_code=400, detail="Error: Supplied bundle contains no Patient resource to link on.") + + # convert record to PII + pii_record: schemas.PIIRecord = fhir.fhir_record_to_pii_record(record_to_link) + + # link the record + try: + # Make a copy of pii_record so we don't modify the original + (found_match, new_person_id, patient_reference_id) = link.link_record_against_mpi( + record=pii_record, + session=db_session, + algorithm=algorithm, + external_person_id=external_id, + ) + return schemas.LinkResponse( + is_match=found_match, + patient_reference_id=patient_reference_id, + person_reference_id=new_person_id, + ) + + except ValueError: + response.status_code = fastapi.status.HTTP_400_BAD_REQUEST + raise fastapi.HTTPException(status_code=400, detail="Error: Bad request") \ No newline at end of file diff --git a/tests/unit/hl7/test_fhir.py b/tests/unit/hl7/test_fhir.py new file mode 100644 index 00000000..a5622ed6 --- /dev/null +++ b/tests/unit/hl7/test_fhir.py @@ -0,0 +1,138 @@ +""" +unit.parsers.test_fhir.py +~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains the unit tests for the recordlinker.parsers.fhir module +""" + +import copy + +from conftest import load_json_asset + +from recordlinker.hl7 import fhir + + +def test_fhir_record_to_pii_record(): + fhir_record = { + "resourceType": "Patient", + "id": "f6a16ff7-4a31-11eb-be7b-8344edc8f36b", + "identifier": [ + { + "value": "1234567890", + "type": { + "coding": [{ + "code": "MR", + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "display": "Medical record number" + }] + }, + }, + { + "system" : "http://hl7.org/fhir/sid/us-ssn", + "value" : "111223333", + "type" : { + "coding" : [{ + "system" : "http://terminology.hl7.org/CodeSystem/v2-0203", + "code" : "SS" + }] + }, + } + ], + "name": [ + { + "family": "Shepard", + "given": [ + "John" + ], + "use": "official" + } + ], + "birthDate": "2053-11-07", + "gender": "male", + "address": [ + { + "line": [ + "1234 Silversun Strip" + ], + "buildingNumber": "1234", + "city": "Boston", + "state": "Massachusetts", + "postalCode": "99999", + "district": "county", + "use": "home" + } + ], + "telecom": [ + { + "use": "home", + "system": "phone", + "value": "123-456-7890" + } + ], + "extension" : [ + { + "url" : "http://hl7.org/fhir/StructureDefinition/individual-genderIdentity", + "extension" : [{ + "url" : "value", + "valueCodeableConcept" : { + "coding" : [{ + "system" : "http://snomed.info/sct", + "code" : "446141000124107", + "display" : "Identifies as female gender (finding)" + }] + } + }] + }, + { + "url" : "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", + "extension": [ + { + "url" : "ombCategory", + "valueCoding" : { + "system" : "urn:oid:2.16.840.1.113883.6.238", + "code" : "2106-3", + "display" : "White" + } + } + ] + } + ] + } + + pii_record = fhir.fhir_record_to_pii_record(fhir_record) + + assert pii_record.external_id == "f6a16ff7-4a31-11eb-be7b-8344edc8f36b" + assert pii_record.name[0].family == "Shepard" + assert pii_record.name[0].given == ["John"] + assert str(pii_record.birth_date) == "2053-11-07" + assert str(pii_record.sex) == "M" + assert pii_record.address[0].line == ["1234 Silversun Strip"] + assert pii_record.address[0].city == "Boston" + assert pii_record.address[0].state == "Massachusetts" + assert pii_record.address[0].postal_code == "99999" + assert pii_record.address[0].county == "county" + assert pii_record.mrn == "1234567890" + assert pii_record.ssn == "111-22-3333" + assert pii_record.telecom[0].value == "123-456-7890" + assert pii_record.telecom[0].system == "phone" + assert str(pii_record.race) == "WHITE" + assert str(pii_record.gender) == "FEMALE" + + +def test_add_person_resource(): + bundle = load_json_asset("patient_bundle.json") + raw_bundle = copy.deepcopy(bundle) + patient_id = "TEST_PATIENT_ID" + person_id = "TEST_PERSON_ID" + + returned_bundle = fhir.add_person_resource( + person_id=person_id, patient_id=patient_id, bundle=raw_bundle + ) + + # Assert returned_bundle has added element in "entry" + assert len(returned_bundle.get("entry")) == len(bundle.get("entry")) + 1 + + # Assert the added element is the person_resource bundle + assert returned_bundle.get("entry")[-1].get("resource").get("resourceType") == "Person" + assert returned_bundle.get("entry")[-1].get("request").get("url") == "Person/TEST_PERSON_ID" + diff --git a/tests/unit/linking/test_link.py b/tests/unit/linking/test_link.py index dd923c59..ad7e72da 100644 --- a/tests/unit/linking/test_link.py +++ b/tests/unit/linking/test_link.py @@ -14,28 +14,10 @@ from recordlinker import models from recordlinker import schemas +from recordlinker.hl7 import fhir from recordlinker.linking import link -class TestAddPersonResource: - def test_add_person_resource(self): - bundle = load_json_asset("patient_bundle.json") - raw_bundle = copy.deepcopy(bundle) - patient_id = "TEST_PATIENT_ID" - person_id = "TEST_PERSON_ID" - - returned_bundle = link.add_person_resource( - person_id=person_id, patient_id=patient_id, bundle=raw_bundle - ) - - # Assert returned_bundle has added element in "entry" - assert len(returned_bundle.get("entry")) == len(bundle.get("entry")) + 1 - - # Assert the added element is the person_resource bundle - assert returned_bundle.get("entry")[-1].get("resource").get("resourceType") == "Person" - assert returned_bundle.get("entry")[-1].get("request").get("url") == "Person/TEST_PERSON_ID" - - class TestCompare: def test_compare_match(self): rec = schemas.PIIRecord( @@ -128,7 +110,7 @@ def patients(self): patients: list[schemas.PIIRecord] = [] for entry in bundle["entry"]: if entry.get("resource", {}).get("resourceType", {}) == "Patient": - patients.append(link.fhir_record_to_pii_record(entry["resource"])) + patients.append(fhir.fhir_record_to_pii_record(entry["resource"])) return patients def test_basic_match_one(self, session, basic_algorithm, patients): @@ -188,110 +170,4 @@ def test_enhanced_match_three(self, session, enhanced_algorithm, patients: list[ # in second pass name blocks on different cluster and address matches it, # finds greatest strength match and correctly assigns to larger cluster assert matches == [False, True, False, True, False, False, True] - assert sorted(list(mapped_patients.values())) == [1, 1, 1, 4] - -def test_fhir_record_to_pii_record(): - fhir_record = { - "resourceType": "Patient", - "id": "f6a16ff7-4a31-11eb-be7b-8344edc8f36b", - "identifier": [ - { - "value": "1234567890", - "type": { - "coding": [{ - "code": "MR", - "system": "http://terminology.hl7.org/CodeSystem/v2-0203", - "display": "Medical record number" - }] - }, - }, - { - "system" : "http://hl7.org/fhir/sid/us-ssn", - "value" : "111223333", - "type" : { - "coding" : [{ - "system" : "http://terminology.hl7.org/CodeSystem/v2-0203", - "code" : "SS" - }] - }, - } - ], - "name": [ - { - "family": "Shepard", - "given": [ - "John" - ], - "use": "official" - } - ], - "birthDate": "2053-11-07", - "gender": "male", - "address": [ - { - "line": [ - "1234 Silversun Strip" - ], - "buildingNumber": "1234", - "city": "Boston", - "state": "Massachusetts", - "postalCode": "99999", - "district": "county", - "use": "home" - } - ], - "telecom": [ - { - "use": "home", - "system": "phone", - "value": "123-456-7890" - } - ], - "extension" : [ - { - "url" : "http://hl7.org/fhir/StructureDefinition/individual-genderIdentity", - "extension" : [{ - "url" : "value", - "valueCodeableConcept" : { - "coding" : [{ - "system" : "http://snomed.info/sct", - "code" : "446141000124107", - "display" : "Identifies as female gender (finding)" - }] - } - }] - }, - { - "url" : "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", - "extension": [ - { - "url" : "ombCategory", - "valueCoding" : { - "system" : "urn:oid:2.16.840.1.113883.6.238", - "code" : "2106-3", - "display" : "White" - } - } - ] - } - ] - } - - pii_record = link.fhir_record_to_pii_record(fhir_record) - - assert pii_record.external_id == "f6a16ff7-4a31-11eb-be7b-8344edc8f36b" - assert pii_record.name[0].family == "Shepard" - assert pii_record.name[0].given == ["John"] - assert str(pii_record.birth_date) == "2053-11-07" - assert str(pii_record.sex) == "M" - assert pii_record.address[0].line == ["1234 Silversun Strip"] - assert pii_record.address[0].city == "Boston" - assert pii_record.address[0].state == "Massachusetts" - assert pii_record.address[0].postal_code == "99999" - assert pii_record.address[0].county == "county" - assert pii_record.mrn == "1234567890" - assert pii_record.ssn == "111-22-3333" - assert pii_record.telecom[0].value == "123-456-7890" - assert pii_record.telecom[0].system == "phone" - assert str(pii_record.race) == "WHITE" - assert str(pii_record.gender) == "FEMALE" + assert sorted(list(mapped_patients.values())) == [1, 1, 1, 4] \ No newline at end of file diff --git a/tests/unit/routes/test_link_router.py b/tests/unit/routes/test_link_router.py index 837b0f80..f976398e 100644 --- a/tests/unit/routes/test_link_router.py +++ b/tests/unit/routes/test_link_router.py @@ -14,10 +14,9 @@ from fastapi import status from recordlinker import schemas -from recordlinker.linking import link +from recordlinker.hl7 import fhir - -class TestLinkFHIR: +class TestLinkDIBBS: @mock.patch("recordlinker.linking.algorithm_service.default_algorithm") def test_bundle_with_no_patient(self, patched_subprocess, basic_algorithm, client): patched_subprocess.return_value = basic_algorithm @@ -174,7 +173,7 @@ def patients(self): patients: list[schemas.PIIRecord] = [] for entry in bundle["entry"]: if entry.get("resource", {}).get("resourceType", {}) == "Patient": - patients.append(link.fhir_record_to_pii_record(entry["resource"])) + patients.append(fhir.fhir_record_to_pii_record(entry["resource"])) return patients @mock.patch("recordlinker.linking.algorithm_service.default_algorithm") @@ -296,3 +295,126 @@ def test_link_invalid_algorithm_param(self, patched_subprocess, patients, client assert actual_response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert actual_response.json()["detail"] == "Error: Invalid algorithm specified" + +class TestLinkFHIR: + @mock.patch("recordlinker.linking.algorithm_service.default_algorithm") + def test_linkrecord_bundle_with_no_patient(self, patched_subprocess, basic_algorithm, client): + patched_subprocess.return_value = basic_algorithm + bad_bundle = {"entry": []} + actual_response = client.post( + "/link/fhir", + json={"bundle": bad_bundle}, + ) + + assert actual_response.status_code == status.HTTP_400_BAD_REQUEST + assert actual_response.json()["detail"] == "Error: Supplied bundle contains no Patient resource to link on." + + @mock.patch("recordlinker.linking.algorithm_service.default_algorithm") + def test_link_success(self, patched_subprocess, basic_algorithm, client): + patched_subprocess.return_value = basic_algorithm + test_bundle = load_json_asset("patient_bundle_to_link_with_mpi.json") + entry_list = copy.deepcopy(test_bundle["entry"]) + + bundle_1 = test_bundle + bundle_1["entry"] = [entry_list[0]] + response_1 = client.post("/link/fhir", json={"bundle": bundle_1}) + person_1 = response_1.json()["person_reference_id"] + assert not response_1.json()["is_match"] + + bundle_2 = test_bundle + bundle_2["entry"] = [entry_list[1]] + response_2 = client.post("/link/fhir", json={"bundle": bundle_2}) + person_2 = response_2.json()["person_reference_id"] + assert response_2.json()["is_match"] + assert person_2 == person_1 + + bundle_3 = test_bundle + bundle_3["entry"] = [entry_list[2]] + response_3 = client.post("/link/fhir", json={"bundle": bundle_3}) + assert not response_3.json()["is_match"] + + # Cluster membership success--justified match + bundle_4 = test_bundle + bundle_4["entry"] = [entry_list[3]] + response_4 = client.post("/link/fhir", json={"bundle": bundle_4}) + person_4 = response_4.json()["person_reference_id"] + assert response_4.json()["is_match"] + assert person_4 == person_1 + + bundle_5 = test_bundle + bundle_5["entry"] = [entry_list[4]] + response_5 = client.post("/link/fhir", json={"bundle": bundle_5}) + assert not response_5.json()["is_match"] + + bundle_6 = test_bundle + bundle_6["entry"] = [entry_list[5]] + response_6 = client.post("/link/fhir", json={"bundle": bundle_6}) + assert not response_6.json()["is_match"] + + @mock.patch("recordlinker.linking.algorithm_service.get_algorithm") + def test_link_enhanced_algorithm( + self, patched_subprocess, enhanced_algorithm, client + ): + patched_subprocess.return_value = enhanced_algorithm + test_bundle = load_json_asset("patient_bundle_to_link_with_mpi.json") + entry_list = copy.deepcopy(test_bundle["entry"]) + + bundle_1 = test_bundle + bundle_1["entry"] = [entry_list[0]] + response_1 = client.post( + "/link/fhir", json={"bundle": bundle_1, "algorithm": "dibbs-enhanced"} + ) + person_1 = response_1.json()["person_reference_id"] + assert not response_1.json()["is_match"] + + bundle_2 = test_bundle + bundle_2["entry"] = [entry_list[1]] + response_2 = client.post( + "/link/fhir", json={"bundle": bundle_2, "algorithm": "dibbs-enhanced"} + ) + person_2 = response_2.json()["person_reference_id"] + assert response_2.json()["is_match"] + assert person_2 == person_1 + + bundle_3 = test_bundle + bundle_3["entry"] = [entry_list[2]] + response_3 = client.post( + "/link/fhir", json={"bundle": bundle_3, "algorithm": "dibbs-enhanced"} + ) + assert not response_3.json()["is_match"] + + # Cluster membership success--justified match + bundle_4 = test_bundle + bundle_4["entry"] = [entry_list[3]] + response_4 = client.post( + "/link/fhir", json={"bundle": bundle_4, "algorithm": "dibbs-enhanced"} + ) + person_4 = response_4.json()["person_reference_id"] + assert response_4.json()["is_match"] + assert person_4 == person_1 + + bundle_5 = test_bundle + bundle_5["entry"] = [entry_list[4]] + response_5 = client.post( + "/link/fhir", json={"bundle": bundle_5, "algorithm": "dibbs-enhanced"} + ) + assert not response_5.json()["is_match"] + + bundle_6 = test_bundle + bundle_6["entry"] = [entry_list[5]] + response_6 = client.post( + "/link/fhir", json={"bundle": bundle_6, "algorithm": "dibbs-enhanced"} + ) + assert not response_6.json()["is_match"] + + @mock.patch("recordlinker.linking.algorithm_service.get_algorithm") + def test_linkrecord_invalid_algorithm_param(self, patched_subprocess, client): + patched_subprocess.return_value = None + test_bundle = load_json_asset("patient_bundle_to_link_with_mpi.json") + + actual_response = client.post( + "/link/fhir", json={"bundle": test_bundle, "algorithm": "INVALID"} + ) + + assert actual_response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert actual_response.json()["detail"] == "Error: Invalid algorithm specified" \ No newline at end of file From 36dfa56c90f85b761ca58d0b10c476e2a5c3ac43 Mon Sep 17 00:00:00 2001 From: cason Date: Wed, 30 Oct 2024 14:45:38 -0400 Subject: [PATCH 2/4] feat: linting --- src/recordlinker/routes/link_router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recordlinker/routes/link_router.py b/src/recordlinker/routes/link_router.py index 4ff56397..7db4e27c 100644 --- a/src/recordlinker/routes/link_router.py +++ b/src/recordlinker/routes/link_router.py @@ -13,9 +13,9 @@ from recordlinker import schemas from recordlinker.database import get_session +from recordlinker.hl7 import fhir from recordlinker.linking import algorithm_service from recordlinker.linking import link -from recordlinker.hl7 import fhir router = fastapi.APIRouter() From 489259eb9a60337550fe3dcd1831a9b20e778372 Mon Sep 17 00:00:00 2001 From: cason Date: Wed, 30 Oct 2024 14:47:06 -0400 Subject: [PATCH 3/4] feat: more linting --- tests/unit/routes/test_link_router.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/routes/test_link_router.py b/tests/unit/routes/test_link_router.py index f976398e..366c31be 100644 --- a/tests/unit/routes/test_link_router.py +++ b/tests/unit/routes/test_link_router.py @@ -16,6 +16,7 @@ from recordlinker import schemas from recordlinker.hl7 import fhir + class TestLinkDIBBS: @mock.patch("recordlinker.linking.algorithm_service.default_algorithm") def test_bundle_with_no_patient(self, patched_subprocess, basic_algorithm, client): From a869162ac2df8eaa238c69d087b9b98f039514dc Mon Sep 17 00:00:00 2001 From: Eric Buckley Date: Wed, 30 Oct 2024 12:21:44 -0700 Subject: [PATCH 4/4] update docstring --- src/recordlinker/hl7/fhir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recordlinker/hl7/fhir.py b/src/recordlinker/hl7/fhir.py index f7df1824..1a1a61c9 100644 --- a/src/recordlinker/hl7/fhir.py +++ b/src/recordlinker/hl7/fhir.py @@ -1,5 +1,5 @@ """ -recordlinker.parsers.fhir +recordlinker.hl7.fhir ~~~~~~~~~~~~~~~~~~~~~~~~~ This module is used to handle fhir parsing