diff --git a/Fhir Operations.postman_collection.json b/Fhir Operations.postman_collection.json index b30776b..76d369e 100644 --- a/Fhir Operations.postman_collection.json +++ b/Fhir Operations.postman_collection.json @@ -1,9 +1,9 @@ { "info": { - "_postman_id": "93839e15-42f8-48d7-9487-733f99d42f16", + "_postman_id": "438a5820-f866-436b-8d77-21ced8d026c8", "name": "Fhir Operations", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "24615511" + "_exporter_id": "14557818" }, "item": [ { @@ -359,6 +359,145 @@ }, "response": [] }, + { + "name": "Questionnaire-Simple", + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text", + "disabled": true + }, + { + "key": "charset", + "value": "UTF-8", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"resourceType\": \"Questionnaire\",\r\n \"id\": \"client-registration-sample\",\r\n \"meta\": {\r\n \"versionId\": \"1\",\r\n \"lastUpdated\": \"2024-04-30T18:11:56.539+05:30\",\r\n \"source\": \"#Vqdr1Oe8Js5cFtLk\"\r\n },\r\n \"extension\": [\r\n {\r\n \"url\": \"http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-targetStructureMap\",\r\n \"valueCanonical\": \"https://fhir.labs.smartregister.org/StructureMap/PatientRegistration\"\r\n }\r\n ],\r\n \"status\": \"active\",\r\n \"subjectType\": [\r\n \"Patient\"\r\n ],\r\n \"date\": \"2020-11-18T07:24:47.111Z\",\r\n \"item\": [\r\n {\r\n \"linkId\": \"PR\",\r\n \"type\": \"group\",\r\n \"item\": [\r\n {\r\n \"linkId\": \"patient-0-birth-date\",\r\n \"text\": \"Date of Birth\",\r\n \"type\": \"date\"\r\n }\r\n ]\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/fhir/Questionnaire/client-registration-sample", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "fhir", + "Questionnaire", + "client-registration-sample" + ] + } + }, + "response": [] + }, + { + "name": "StructureMap-Simple", + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text", + "disabled": true + }, + { + "key": "charset", + "value": "UTF-8", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"resourceType\": \"StructureMap\",\r\n \"id\": \"PatientRegistration\",\r\n \"meta\": {\r\n \"versionId\": \"1\",\r\n \"lastUpdated\": \"2024-04-30T18:11:59.744+05:30\",\r\n \"source\": \"#rBggDJDCECBpeIsd\"\r\n },\r\n \"text\": {\r\n \"status\": \"additional\",\r\n \"div\": \"
map "http://hl7.org/fhir/StructureMap/PatientRegistration" = 'PatientRegistration'\\n\\n        uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireReponse" as source\\n        uses "http://hl7.org/fhir/StructureDefinition/Bundle" as target\\n        \\n        group PatientRegistration(source src : QuestionnaireResponse, target bundle: Bundle) {\\n            src -> bundle.id = uuid() "rule_c";\\n            src -> bundle.type = 'collection' "rule_b";\\n            src -> bundle.entry as entry, entry.resource = create('Patient') as patient then\\n                ExtractPatient(src, patient) "rule_z";\\n        }\\n        \\n        group ExtractPatient(source src : QuestionnaireResponse, target tgt : Patient) {\\n             src.item as item where(linkId = 'PR') then {\\n                 item.item as inner_item where (linkId = 'patient-0-birth-date') then {\\n                     inner_item.answer first as ans then { \\n                         ans.value as val -> tgt.birthDate = val "rule_a";\\n                     } ;\\n                 } ;\\n             };\\n        }
\"\r\n },\r\n \"url\": \"http://hl7.org/fhir/StructureMap/PatientRegistration\",\r\n \"name\": \"PatientRegistration\",\r\n \"structure\": [\r\n {\r\n \"url\": \"http://hl7.org/fhir/StructureDefinition/QuestionnaireReponse\",\r\n \"mode\": \"source\"\r\n },\r\n {\r\n \"url\": \"http://hl7.org/fhir/StructureDefinition/Bundle\",\r\n \"mode\": \"target\"\r\n }\r\n ],\r\n \"group\": [\r\n {\r\n \"name\": \"PatientRegistration\",\r\n \"typeMode\": \"none\",\r\n \"input\": [\r\n {\r\n \"name\": \"src\",\r\n \"type\": \"QuestionnaireResponse\",\r\n \"mode\": \"source\"\r\n },\r\n {\r\n \"name\": \"bundle\",\r\n \"type\": \"Bundle\",\r\n \"mode\": \"target\"\r\n }\r\n ],\r\n \"rule\": [\r\n {\r\n \"name\": \"rule_c\",\r\n \"source\": [\r\n {\r\n \"context\": \"src\"\r\n }\r\n ],\r\n \"target\": [\r\n {\r\n \"context\": \"bundle\",\r\n \"contextType\": \"variable\",\r\n \"element\": \"id\",\r\n \"transform\": \"uuid\"\r\n }\r\n ]\r\n },\r\n {\r\n \"name\": \"rule_b\",\r\n \"source\": [\r\n {\r\n \"context\": \"src\"\r\n }\r\n ],\r\n \"target\": [\r\n {\r\n \"context\": \"bundle\",\r\n \"contextType\": \"variable\",\r\n \"element\": \"type\",\r\n \"transform\": \"copy\",\r\n \"parameter\": [\r\n {\r\n \"valueString\": \"collection\"\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n {\r\n \"name\": \"rule_z\",\r\n \"source\": [\r\n {\r\n \"context\": \"src\"\r\n }\r\n ],\r\n \"target\": [\r\n {\r\n \"context\": \"bundle\",\r\n \"contextType\": \"variable\",\r\n \"element\": \"entry\",\r\n \"variable\": \"entry\"\r\n },\r\n {\r\n \"context\": \"entry\",\r\n \"contextType\": \"variable\",\r\n \"element\": \"resource\",\r\n \"variable\": \"patient\",\r\n \"transform\": \"create\",\r\n \"parameter\": [\r\n {\r\n \"valueString\": \"Patient\"\r\n }\r\n ]\r\n }\r\n ],\r\n \"dependent\": [\r\n {\r\n \"name\": \"ExtractPatient\",\r\n \"variable\": [\r\n \"src\",\r\n \"patient\"\r\n ]\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n {\r\n \"name\": \"ExtractPatient\",\r\n \"typeMode\": \"none\",\r\n \"input\": [\r\n {\r\n \"name\": \"src\",\r\n \"type\": \"QuestionnaireResponse\",\r\n \"mode\": \"source\"\r\n },\r\n {\r\n \"name\": \"tgt\",\r\n \"type\": \"Patient\",\r\n \"mode\": \"target\"\r\n }\r\n ],\r\n \"rule\": [\r\n {\r\n \"name\": \"item\",\r\n \"source\": [\r\n {\r\n \"context\": \"src\",\r\n \"element\": \"item\",\r\n \"variable\": \"item\",\r\n \"condition\": \"(linkId = 'PR')\"\r\n }\r\n ],\r\n \"rule\": [\r\n {\r\n \"name\": \"item\",\r\n \"source\": [\r\n {\r\n \"context\": \"item\",\r\n \"element\": \"item\",\r\n \"variable\": \"inner_item\",\r\n \"condition\": \"(linkId = 'patient-0-birth-date')\"\r\n }\r\n ],\r\n \"rule\": [\r\n {\r\n \"name\": \"answer\",\r\n \"source\": [\r\n {\r\n \"context\": \"inner_item\",\r\n \"element\": \"answer\",\r\n \"listMode\": \"first\",\r\n \"variable\": \"ans\"\r\n }\r\n ],\r\n \"rule\": [\r\n {\r\n \"name\": \"rule_a\",\r\n \"source\": [\r\n {\r\n \"context\": \"ans\",\r\n \"element\": \"value\",\r\n \"variable\": \"val\"\r\n }\r\n ],\r\n \"target\": [\r\n {\r\n \"context\": \"tgt\",\r\n \"contextType\": \"variable\",\r\n \"element\": \"birthDate\",\r\n \"transform\": \"copy\",\r\n \"parameter\": [\r\n {\r\n \"valueId\": \"val\"\r\n }\r\n ]\r\n }\r\n ]\r\n }\r\n ]\r\n }\r\n ]\r\n }\r\n ]\r\n }\r\n ]\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/fhir/StructureMap/PatientRegistration", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "fhir", + "StructureMap", + "PatientRegistration" + ] + } + }, + "response": [] + }, + { + "name": "$Extract-Simple", + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text", + "disabled": true + }, + { + "key": "charset", + "value": "UTF-8", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"resourceType\": \"QuestionnaireResponse\",\r\n \"questionnaire\": \"client-registration-sample\",\r\n \"item\": [\r\n {\r\n \"linkId\": \"PR\",\r\n \"item\": [\r\n {\r\n \"linkId\": \"patient-0-birth-date\",\r\n \"answer\": [\r\n {\r\n \"valueDate\": \"2016-02-11\"\r\n }\r\n ]\r\n }\r\n ]\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/fhir/QuestionnaireResponse/client-registration-sample/$extract", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "fhir", + "QuestionnaireResponse", + "client-registration-sample", + "$extract" + ] + } + }, + "response": [] + }, { "name": "Library", "protocolProfileBehavior": { diff --git a/configs/hapi_application.yaml b/configs/hapi_application.yaml index 5edc323..81b0cdd 100644 --- a/configs/hapi_application.yaml +++ b/configs/hapi_application.yaml @@ -46,7 +46,7 @@ hapi: implementationguides: # ### example from registry (packages.fhir.org) smart: - packageUrl: https://worldhealthorganization.github.io/smart-immunizations-measles/package.tgz + packageUrl: https://worldhealthorganization.github.io/smart-immunizations-measles/branches/testing/package.tgz name: smart.who.int.immunizations-measles version: 0.1.0 reloadExisting: false diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/customOperations/r4/ExtractOperationProvider.kt b/src/main/java/ca/uhn/fhir/jpa/starter/customOperations/r4/ExtractOperationProvider.kt index 57f5f83..4436eed 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/customOperations/r4/ExtractOperationProvider.kt +++ b/src/main/java/ca/uhn/fhir/jpa/starter/customOperations/r4/ExtractOperationProvider.kt @@ -1,11 +1,14 @@ package ca.uhn.fhir.jpa.starter.customOperations.r4 +import ca.uhn.fhir.context.FhirContext +import ca.uhn.fhir.jpa.starter.AppProperties import ca.uhn.fhir.jpa.starter.customOperations.r4.r4mapping.R4StructureMapExtractionContext import ca.uhn.fhir.jpa.starter.customOperations.r4.r4mapping.targetStructureMap import ca.uhn.fhir.jpa.starter.customOperations.services.HelperService import ca.uhn.fhir.rest.annotation.IdParam import ca.uhn.fhir.rest.annotation.Operation import ca.uhn.fhir.rest.annotation.OperationParam +import ca.uhn.fhir.rest.client.api.IGenericClient import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException import kotlinx.coroutines.runBlocking import org.hl7.fhir.r4.model.QuestionnaireResponse @@ -13,6 +16,7 @@ import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.StructureMap import org.hl7.fhir.r4.model.IdType import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.utils.StructureMapUtilities import org.hl7.fhir.utilities.npm.NpmPackage import org.slf4j.LoggerFactory @@ -21,7 +25,7 @@ import org.springframework.stereotype.Service import java.io.IOException @Service -class ExtractOperationProvider { +class ExtractOperationProvider @Autowired constructor(private val appProperties: AppProperties) { @Autowired private lateinit var helperService: HelperService @@ -31,6 +35,11 @@ class ExtractOperationProvider { private val logger = LoggerFactory.getLogger(ExtractOperationProvider::class.java) + private val context: FhirContext = FhirContext.forR4() + + // Create a FHIR client + private val client: IGenericClient = context.newRestfulGenericClient(appProperties.fhir_baseUrl) + @Operation(name = "\$extract", idempotent = true, global = true, type = QuestionnaireResponse::class) fun extract( @IdParam theId: IdType, @@ -43,14 +52,24 @@ class ExtractOperationProvider { val workerContext = r4FhirOperationHelper.loadWorkerContext(measlesOutbreakPackage, basePackage) val transformSupportServices = R4TransformSupportServicesLM(workerContext, mutableListOf()) // Fetch resources - val questionnaire = + var questionnaire = workerContext.fetchResource(Questionnaire::class.java, theQuestionnaireResponse.questionnaire) - ?: throw ResourceNotFoundException("Questionnaire Resource not found") - - val structureMap = - workerContext.fetchResource(StructureMap::class.java, questionnaire.targetStructureMap!!) + if(questionnaire == null) { + questionnaire = fetchResourceById(Questionnaire::class.java, theQuestionnaireResponse.questionnaire) + ?: throw ResourceNotFoundException("Questionnaire Resource not found") + } + val structureMapExtensionValue = questionnaire.targetStructureMap ?: throw ResourceNotFoundException("StructureMap Resource not found") + var structureMap = + workerContext.fetchResource(StructureMap::class.java, structureMapExtensionValue) + if(structureMap == null) { + val structureMapId = structureMapExtensionValue.split("/").lastOrNull() + ?: throw ResourceNotFoundException("StructureMap Resource not found") + structureMap = fetchResourceById(StructureMap::class.java, structureMapId) + ?: throw ResourceNotFoundException("StructureMap Resource not found") + } + r4FhirOperationHelper.extract( questionnaire, theQuestionnaireResponse, @@ -63,6 +82,7 @@ class ExtractOperationProvider { ) } } catch (e: Exception) { + e.printStackTrace() return handleException(e) } } @@ -81,6 +101,13 @@ class ExtractOperationProvider { return bundle } + private fun fetchResourceById(resourceType: Class, resourceId: String): T? { + return client.read() + .resource(resourceType) + .withId(resourceId) + .execute() + } + } \ No newline at end of file