From 011847639ad506de1750ebe1e811b79d8bf0025d Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Mon, 15 Jul 2024 10:03:39 +0200 Subject: [PATCH 1/7] Update fhirpathpy version --- Pipfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index d702614..c5fa24f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ea42d2aa1440a3767ab7d896fc9a8faf2e7e04c991c1e38ab4e22c6554baa3cf" + "sha256": "9ab5733baab2fd42cc62994025d27ba226ba264e3123d628647bfbda04e3cbdb" }, "pipfile-spec": 6, "requires": { @@ -341,12 +341,12 @@ }, "fhirpathpy": { "hashes": [ - "sha256:38ca66ac7cfb058102ccc52166a7d09d8f60a75ebe4f2be5674cd41a4f18c44d", - "sha256:baf6f94f6677bb6965c72bc3cd393c96d9ef8c86ea90752100fc4a264fa92724" + "sha256:46149398002f9669bc4aa59d5f32dad7637dddc2c5b81ab8c0ddb09a1ceec9b9", + "sha256:d9247fc88234dc5dccdc4b1d814c1d7d7734001ae9825c7f444aade34bcfcd80" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.1.0" + "version": "==1.1.1" }, "fhirpy": { "hashes": [ From e66bac78eaf5908084a63e78a488b708aa61fc77 Mon Sep 17 00:00:00 2001 From: qscgyjqscgyj Date: Mon, 2 Sep 2024 15:54:19 +0200 Subject: [PATCH 2/7] Use aidbox $to-fromat for $assemble operations Ref #61 --- app/aidbox/operations.py | 28 ++++++++++++++++++++-------- app/converter/aidbox.py | 12 ++++++++++++ tests/sdc/test_assemble.py | 27 +++++++++++++-------------- 3 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 app/converter/aidbox.py diff --git a/app/aidbox/operations.py b/app/aidbox/operations.py index 04ac43f..cbc3420 100644 --- a/app/aidbox/operations.py +++ b/app/aidbox/operations.py @@ -2,7 +2,7 @@ from aiohttp import web -from app.converter import from_first_class_extension, to_first_class_extension +from app.converter.aidbox import from_first_class_extension, to_first_class_extension from ..sdc import ( assemble, @@ -30,7 +30,9 @@ async def assemble_op(request: AidboxSdcRequest): assembled_questionnaire_lazy = await assemble(request.fhir_client, questionnaire) assembled_questionnaire = json.loads(json.dumps(assembled_questionnaire_lazy, default=list)) if request.is_fhir: - assembled_questionnaire = from_first_class_extension(assembled_questionnaire) + assembled_questionnaire = await from_first_class_extension( + assembled_questionnaire, request.aidbox_client + ) return web.json_response(assembled_questionnaire) @@ -40,7 +42,9 @@ async def constraint_check_operation(request: AidboxSdcRequest): env = parameter_to_env(request.resource) questionnaire = ( - to_first_class_extension(env["Questionnaire"]) if request.is_fhir else env["Questionnaire"] + await to_first_class_extension(env["Questionnaire"], request.aidbox_client) + if request.is_fhir + else env["Questionnaire"] ) as_root = questionnaire.get("runOnBehalfOfRoot") client = client if as_root else get_user_sdk_client(request.request, request.client) @@ -54,7 +58,9 @@ async def get_questionnaire_context_operation(request: AidboxSdcRequest): env = parameter_to_env(request.resource) questionnaire = ( - to_first_class_extension(env["Questionnaire"]) if request.is_fhir else env["Questionnaire"] + await to_first_class_extension(env["Questionnaire"], request.aidbox_client) + if request.is_fhir + else env["Questionnaire"] ) as_root = questionnaire.get("runOnBehalfOfRoot") client = client if as_root else get_user_sdk_client(request.request, request.client) @@ -81,7 +87,7 @@ async def extract_questionnaire_operation(request: AidboxSdcRequest): elif resource["resourceType"] == "Parameters": env = parameter_to_env(request.resource) questionnaire = ( - to_first_class_extension(env["Questionnaire"]) + await to_first_class_extension(env["Questionnaire"], request.aidbox_client) if request.is_fhir else env["Questionnaire"] ) @@ -147,14 +153,18 @@ async def populate_questionnaire(request: AidboxSdcRequest): ) questionnaire = ( - to_first_class_extension(env["Questionnaire"]) if request.is_fhir else env["Questionnaire"] + await to_first_class_extension(env["Questionnaire"], request.aidbox_client) + if request.is_fhir + else env["Questionnaire"] ) as_root = questionnaire.get("runOnBehalfOfRoot") client = request.client if as_root else get_user_sdk_client(request.request, request.client) populated_resource = await populate(client, questionnaire, env) if request.is_fhir: - populated_resource = from_first_class_extension(populated_resource) + populated_resource = await from_first_class_extension( + populated_resource, request.aidbox_client + ) return web.json_response(populated_resource) @@ -172,7 +182,9 @@ async def populate_questionnaire_instance(request: AidboxSdcRequest): populated_resource = await populate(client, questionnaire, env) if request.is_fhir: - populated_resource = from_first_class_extension(populated_resource) + populated_resource = await from_first_class_extension( + populated_resource, request.aidbox_client + ) return web.json_response(populated_resource) diff --git a/app/converter/aidbox.py b/app/converter/aidbox.py new file mode 100644 index 0000000..f2c5258 --- /dev/null +++ b/app/converter/aidbox.py @@ -0,0 +1,12 @@ +async def from_first_class_extension(fce_resource, aidbox_client): + fhir_resource = await aidbox_client.execute( + "/$to-format/fhir", method="post", data=fce_resource, params=None + ) + return fhir_resource.get("resource") + + +async def to_first_class_extension(fhir_resource, aidbox_client): + fce_resource = await aidbox_client.execute( + "/$to-format/aidbox", method="post", data=fhir_resource, params=None + ) + return fce_resource.get("resource") diff --git a/tests/sdc/test_assemble.py b/tests/sdc/test_assemble.py index 3ee6127..f6f3320 100644 --- a/tests/sdc/test_assemble.py +++ b/tests/sdc/test_assemble.py @@ -551,7 +551,16 @@ async def test_assemble_sub_questionnaire_fhir(aidbox_client, safe_db): ], "status": "active", "resourceType": "Questionnaire", + "assembledFrom": q['id'], "extension": [ + { + "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-targetStructureMap", + "valueCanonical": "StructureMap/create-another-patient", + }, + { + "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-targetStructureMap", + "valueCanonical": "StructureMap/create-patient", + }, { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext", "extension": [ @@ -560,23 +569,13 @@ async def test_assemble_sub_questionnaire_fhir(aidbox_client, safe_db): "valueCoding": { "system": "http://hl7.org/fhir/uv/sdc/CodeSystem/launchContext", "code": "LaunchPatient", - "system": "http://hl7.org/fhir/uv/sdc/CodeSystem/launchContext", }, }, - {"url": "type", "valueCode": "Patient"}, + { + "url": "type", + "valueCode": "Patient", + }, ], }, - { - "url": "https://jira.hl7.org/browse/FHIR-22356#assembledFrom", - "valueCanonical": q.id, - }, - { - "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-targetStructureMap", - "valueCanonical": "StructureMap/create-another-patient", - }, - { - "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-targetStructureMap", - "valueCanonical": "StructureMap/create-patient", - }, ], } From 84e8ee300d9a74ad18a68456c23bd32413bb6b9a Mon Sep 17 00:00:00 2001 From: Ilya Beda Date: Tue, 3 Sep 2024 11:12:30 +1000 Subject: [PATCH 3/7] Fix test --- tests/sdc/test_assemble.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/sdc/test_assemble.py b/tests/sdc/test_assemble.py index f6f3320..701f1ca 100644 --- a/tests/sdc/test_assemble.py +++ b/tests/sdc/test_assemble.py @@ -551,7 +551,6 @@ async def test_assemble_sub_questionnaire_fhir(aidbox_client, safe_db): ], "status": "active", "resourceType": "Questionnaire", - "assembledFrom": q['id'], "extension": [ { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-targetStructureMap", @@ -577,5 +576,9 @@ async def test_assemble_sub_questionnaire_fhir(aidbox_client, safe_db): }, ], }, + { + "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-assembledFrom", + "valueCanonical": q['id'] + }, ], } From 75208a549079f1bbd2163fbd70c1460f78992083 Mon Sep 17 00:00:00 2001 From: Ilya Beda Date: Tue, 17 Sep 2024 17:46:33 +1000 Subject: [PATCH 4/7] Fix populate for reference --- app/sdc/populate.py | 17 ++++++++++++---- tests/sdc/test_populate.py | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/app/sdc/populate.py b/app/sdc/populate.py index 495da20..0c499c9 100644 --- a/app/sdc/populate.py +++ b/app/sdc/populate.py @@ -60,7 +60,10 @@ def init_item(): ) if data and len(data): type = get_type(item, data) - answers.extend([{"value": {type: d}} for d in data]) + if type == "Reference": + answers.extend([{"value": {"Reference": {"reference": d}}} for d in data]) + else: + answers.extend([{"value": {type: d}} for d in data]) if answers: root_item["answer"] = answers elif "initialExpression" in item: @@ -71,10 +74,16 @@ def init_item(): raise OperationOutcome(f'Error: "{item["initialExpression"]["expression"]}" - {str(e)}') if data and len(data): type = get_type(item, data) - if item.get("repeats") is True: - answers = [{"value": {type: d}} for d in data] + if type == "Reference": + if item.get("repeats") is True: + answers = [{"value": {"Reference": {"reference": d}}} for d in data] + else: + answers = [{"value": {"Reference": {"reference": data[0]}}}] else: - answers = [{"value": {type: data[0]}}] + if item.get("repeats") is True: + answers = [{"value": {type: d}} for d in data] + else: + answers = [{"value": {type: data[0]}}] if answers: root_item["answer"] = answers elif "initial" in item: diff --git a/tests/sdc/test_populate.py b/tests/sdc/test_populate.py index 098c133..e2ee575 100644 --- a/tests/sdc/test_populate.py +++ b/tests/sdc/test_populate.py @@ -3,6 +3,7 @@ import pytest from app.test.utils import create_parameters +from app.converter.aidbox import from_first_class_extension @pytest.mark.asyncio @@ -802,3 +803,42 @@ async def test_source_query_populate_fhir_from_api(aidbox_client, safe_db): "questionnaire": questionnaire.id, "resourceType": "QuestionnaireResponse", } + + +@pytest.mark.asyncio +async def test_reference_populate(aidbox_client, safe_db): + q = aidbox_client.resource( + "Questionnaire", + **{ + "status": "active", + "launchContext": [{"name": {"code": "LaunchPatient"}, "type": ["Patient"]}], + "item": [ + { + "type": "reference", + "linkId": "patientId", + "initialExpression": { + "language": "text/fhirpath", + "expression": "'Patient/' + %LaunchPatient.id", + }, + }, + ], + }, + ) + await q.save() + + assert q.id is not None + + launch_patient = {"resourceType": "Patient", "id": "patienit-id"} + + p = await from_first_class_extension(await q.execute("$populate", data=create_parameters(LaunchPatient=launch_patient)), aidbox_client) + + assert p == { + "resourceType": "QuestionnaireResponse", + "questionnaire": q.id, + "item": [ + { + "linkId": "patientId", + "answer": [{"valueReference": {"reference": f"Patient/{launch_patient['id']}"}}], + } + ], + } From fb2979847fe73dd57f3fefe9231e3b8c182e298b Mon Sep 17 00:00:00 2001 From: Ilya Beda Date: Wed, 18 Sep 2024 10:42:37 +1000 Subject: [PATCH 5/7] Revert "Fix populate for reference" This reverts commit 75208a549079f1bbd2163fbd70c1460f78992083. --- app/sdc/populate.py | 17 ++++------------ tests/sdc/test_populate.py | 40 -------------------------------------- 2 files changed, 4 insertions(+), 53 deletions(-) diff --git a/app/sdc/populate.py b/app/sdc/populate.py index 0c499c9..495da20 100644 --- a/app/sdc/populate.py +++ b/app/sdc/populate.py @@ -60,10 +60,7 @@ def init_item(): ) if data and len(data): type = get_type(item, data) - if type == "Reference": - answers.extend([{"value": {"Reference": {"reference": d}}} for d in data]) - else: - answers.extend([{"value": {type: d}} for d in data]) + answers.extend([{"value": {type: d}} for d in data]) if answers: root_item["answer"] = answers elif "initialExpression" in item: @@ -74,16 +71,10 @@ def init_item(): raise OperationOutcome(f'Error: "{item["initialExpression"]["expression"]}" - {str(e)}') if data and len(data): type = get_type(item, data) - if type == "Reference": - if item.get("repeats") is True: - answers = [{"value": {"Reference": {"reference": d}}} for d in data] - else: - answers = [{"value": {"Reference": {"reference": data[0]}}}] + if item.get("repeats") is True: + answers = [{"value": {type: d}} for d in data] else: - if item.get("repeats") is True: - answers = [{"value": {type: d}} for d in data] - else: - answers = [{"value": {type: data[0]}}] + answers = [{"value": {type: data[0]}}] if answers: root_item["answer"] = answers elif "initial" in item: diff --git a/tests/sdc/test_populate.py b/tests/sdc/test_populate.py index e2ee575..098c133 100644 --- a/tests/sdc/test_populate.py +++ b/tests/sdc/test_populate.py @@ -3,7 +3,6 @@ import pytest from app.test.utils import create_parameters -from app.converter.aidbox import from_first_class_extension @pytest.mark.asyncio @@ -803,42 +802,3 @@ async def test_source_query_populate_fhir_from_api(aidbox_client, safe_db): "questionnaire": questionnaire.id, "resourceType": "QuestionnaireResponse", } - - -@pytest.mark.asyncio -async def test_reference_populate(aidbox_client, safe_db): - q = aidbox_client.resource( - "Questionnaire", - **{ - "status": "active", - "launchContext": [{"name": {"code": "LaunchPatient"}, "type": ["Patient"]}], - "item": [ - { - "type": "reference", - "linkId": "patientId", - "initialExpression": { - "language": "text/fhirpath", - "expression": "'Patient/' + %LaunchPatient.id", - }, - }, - ], - }, - ) - await q.save() - - assert q.id is not None - - launch_patient = {"resourceType": "Patient", "id": "patienit-id"} - - p = await from_first_class_extension(await q.execute("$populate", data=create_parameters(LaunchPatient=launch_patient)), aidbox_client) - - assert p == { - "resourceType": "QuestionnaireResponse", - "questionnaire": q.id, - "item": [ - { - "linkId": "patientId", - "answer": [{"valueReference": {"reference": f"Patient/{launch_patient['id']}"}}], - } - ], - } From 26265241786c6d9bf55d8fe8f9348d0d74b62ddf Mon Sep 17 00:00:00 2001 From: Ilya Beda Date: Wed, 18 Sep 2024 19:20:12 +1000 Subject: [PATCH 6/7] Fix typo in populate_questionnaire_instance definition --- app/fhir_server/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/fhir_server/operations.py b/app/fhir_server/operations.py index a2eaba8..e1f9015 100644 --- a/app/fhir_server/operations.py +++ b/app/fhir_server/operations.py @@ -215,7 +215,7 @@ async def populate_questionnaire_handler(request: web.BaseRequest): @routes.post("/Questionnaire/{id}/$populate") -async def populate_questionnaire_instance(_operation, request: web.BaseRequest): +async def populate_questionnaire_instance(request: web.BaseRequest): client = request.app["client"] questionnaire = ( await client.resources("Questionnaire").search(_id=request.match_info["id"]).get() From d89267a255146e18caf1ff925aa7c2cff1600d6c Mon Sep 17 00:00:00 2001 From: Ilya Beda Date: Wed, 18 Sep 2024 19:22:46 +1000 Subject: [PATCH 7/7] Add healthcheck --- app/fhir_server/operations.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/fhir_server/operations.py b/app/fhir_server/operations.py index e1f9015..1df8025 100644 --- a/app/fhir_server/operations.py +++ b/app/fhir_server/operations.py @@ -232,3 +232,8 @@ async def populate_questionnaire_instance(request: web.BaseRequest): @routes.post("/Questionnaire/$resolve-expression") async def resolve_expression_operation_handler(request: web.BaseRequest): return web.json_response(resolve_expression(await request.json())) + + +@routes.get("/healthcheck") +async def healthcheck(request: web.BaseRequest): + return web.json_response({"status": "ok"})