diff --git a/tofhir-common/src/main/scala/io/tofhir/common/util/CustomMappingFunctions.scala b/tofhir-common/src/main/scala/io/tofhir/common/util/CustomMappingFunctions.scala index da74e49e..812aabbc 100644 --- a/tofhir-common/src/main/scala/io/tofhir/common/util/CustomMappingFunctions.scala +++ b/tofhir-common/src/main/scala/io/tofhir/common/util/CustomMappingFunctions.scala @@ -1,6 +1,7 @@ package io.tofhir.common.util -import io.onfhir.path.annotation.FhirPathFunction +import io.onfhir.api.FHIR_DATA_TYPES +import io.onfhir.path.annotation.{FhirPathFunction, FhirPathFunctionDocumentation, FhirPathFunctionParameter, FhirPathFunctionReturn} import io.onfhir.path.grammar.FhirPathExprParser.ExpressionContext import io.onfhir.path.{AbstractFhirPathFunctionLibrary, FhirPathEnvironment, FhirPathException, FhirPathExpressionEvaluator, FhirPathResult, FhirPathString, IFhirPathFunctionLibraryFactory} @@ -23,8 +24,32 @@ class CustomMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPath * @param dataExpr String data such that byte representation of each 2 consecutive characters represents a number. * @return Space separated numbers concatenated in a string */ - @FhirPathFunction(documentation = "\uD83D\uDCDC Decodes the given data and converts it to an array of space separated numbers. Returns the space separated numbers concatenated in a string.\n\n\uD83D\uDCDD _@param_ **`dataExpr`** \nString data such that byte representation of each 2 consecutive characters represents a number.\n\n\uD83D\uDD19 _@return_ \n```\n'123 456 123'\n``` \n\uD83D\uDCA1 **E.g.** cst:createTimeSeriesData(%data)", - insertText = "cst:createTimeSeriesData()", detail = "cst", label = "cst:createTimeSeriesData", kind = "Method", returnType = Seq("string"), inputType = Seq("string")) + @FhirPathFunction( + documentation = FhirPathFunctionDocumentation( + detail = "Decodes the given data and converts it to an array of space separated numbers. Returns the space separated numbers concatenated in a string.", + usageWarnings = None, + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "dataExpr", + detail = "String data such that byte representation of each 2 consecutive characters represents a number.", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq("'123 456 123'") + ), + examples = Seq( + "cst:createTimeSeriesData(%data)" + ) + ), + insertText = "cst:createTimeSeriesData()", + detail = "cst", + label = "cst:createTimeSeriesData", + kind = "Method", + returnType = Seq(FHIR_DATA_TYPES.STRING), + inputType = Seq(FHIR_DATA_TYPES.STRING) + ) def createTimeSeriesData(dataExpr: ExpressionContext): Seq[FhirPathResult] = { val dataResult = new FhirPathExpressionEvaluator(context, current).visit(dataExpr) if (dataResult.length > 1 || !dataResult.head.isInstanceOf[FhirPathString]) { diff --git a/tofhir-engine/src/main/scala/io/tofhir/engine/mapping/fhirPath/FhirPathMappingFunctions.scala b/tofhir-engine/src/main/scala/io/tofhir/engine/mapping/fhirPath/FhirPathMappingFunctions.scala index ae137c7c..950bc147 100644 --- a/tofhir-engine/src/main/scala/io/tofhir/engine/mapping/fhirPath/FhirPathMappingFunctions.scala +++ b/tofhir-engine/src/main/scala/io/tofhir/engine/mapping/fhirPath/FhirPathMappingFunctions.scala @@ -1,8 +1,9 @@ package io.tofhir.engine.mapping.fhirPath +import io.onfhir.api.FHIR_DATA_TYPES import io.onfhir.api.util.FHIRUtil import io.onfhir.path._ -import io.onfhir.path.annotation.FhirPathFunction +import io.onfhir.path.annotation.{FhirPathFunction, FhirPathFunctionDocumentation, FhirPathFunctionParameter, FhirPathFunctionReturn} import io.onfhir.path.grammar.FhirPathExprParser.ExpressionContext import io.tofhir.engine.model.{ConceptMapContext, FhirMappingContext, UnitConversionContext} import io.tofhir.engine.util.FhirMappingUtility @@ -25,12 +26,36 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa * @return */ @FhirPathFunction( - documentation = "\uD83D\uDCDC Creates an ID using the hash of given string. Resource name should be quoted and ID should be string. It returns a string.\n\n\uD83D\uDCDD _@param_ **`resourceType`** \nHL7 FHIR resource type to generate hashed ID for.\n\n\uD83D\uDCDD _@param_ **`id`** \nA unique ID to generate a hash.\n\n\uD83D\uDD19 _@return_ \n```\n\"Patient/a363ff2b1833e3df408910f5b3d04334\"\n``` \n\uD83D\uDCA1 **E.g.** mpp:getHashedId('Patient', id.toString())", + documentation = FhirPathFunctionDocumentation( + detail = "Creates an ID using the hash of given string. Resource name should be quoted and ID should be string. It returns a string.", + usageWarnings = None, + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "resourceType", + detail = "HL7 FHIR resource type to generate hashed ID for.", + examples = None + ), + FhirPathFunctionParameter( + name = "id", + detail = "A unique ID to generate a hash.", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq( + "\"Patient/a363ff2b1833e3df408910f5b3d04334\"" + ) + ), + examples = Seq( + "mpp:getHashedId('Patient', id.toString())" + ) + ), insertText = "mpp:getHashedId(, )", detail = "mpp", label = "mpp:getHashedId", kind = "Function", - returnType = Seq("string"), + returnType = Seq(FHIR_DATA_TYPES.STRING), inputType = Seq() ) def getHashedId(resourceTypeExp:ExpressionContext, inputExpr:ExpressionContext):Seq[FhirPathResult] = { @@ -59,8 +84,39 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa * @param inputExpr Expression to return the value of referenced id * @return */ - @FhirPathFunction(documentation = "\uD83D\uDCDC Creates a FHIR Reference object with a given resource type and hash of the given ID. Resource name should be quoted and ID should be string. It returns a FHIR Reference object.\n\n\uD83D\uDCDD _@param_ **`resourceType`** \nHL7 FHIR resource type to generate hashed ID for.\n\n\uD83D\uDCDD _@param_ **`id`** \nA unique ID to generate a hash.\n\n\uD83D\uDD19 _@return_ \n```json\n{\n \"reference\": \"Patient/a363ff2b1833e3df408910f5b3d04334\"\n}\n``` \n\uD83D\uDCA1 **E.g.** mpp:createFhirReferenceWithHashedId('Patient', id.toString())", - insertText = "mpp:createFhirReferenceWithHashedId(, )",detail = "mpp", label = "mpp:createFhirReferenceWithHashedId", kind = "Function", returnType = Seq(), inputType = Seq()) + @FhirPathFunction( + documentation = FhirPathFunctionDocumentation( + detail = "Creates a FHIR Reference object with a given resource type and hash of the given ID. Resource name should be quoted and ID should be string. It returns a FHIR Reference object.", + usageWarnings = None, + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "resourceType", + detail = "HL7 FHIR resource type to generate hashed ID for.", + examples = None + ), + FhirPathFunctionParameter( + name = "id", + detail = "A unique ID to generate a hash.", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq( + """{"reference": "Patient/a363ff2b1833e3df408910f5b3d04334"}""" + ) + ), + examples = Seq( + "mpp:createFhirReferenceWithHashedId('Patient', id.toString())" + ) + ), + insertText = "mpp:createFhirReferenceWithHashedId(, )", + detail = "mpp", + label = "mpp:createFhirReferenceWithHashedId", + kind = "Function", + returnType = Seq(), + inputType = Seq() + ) def createFhirReferenceWithHashedId(resourceTypeExp:ExpressionContext, inputExpr:ExpressionContext):Seq[FhirPathResult] = { val resourceType = getStringValueOfExpr(resourceTypeExp, s"Invalid function call 'createFhirReferenceWithHashedId', given expression for keyExpr:${resourceTypeExp.getText} should return a string value!") val input = getStringValuesOfExpr(inputExpr, s"Invalid function call 'createFhirReferenceWithHashedId', given expression for keyExpr:${inputExpr.getText} should return string value(s)!") @@ -77,9 +133,44 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa * @param toExpr End index (inclusive) * @return the field names which have values i.e. the non-empty ones */ - @FhirPathFunction(documentation = "\uD83D\uDCDC Creates a sequence of indices between from-to integers and concatenates them and prefix string to generate looped field names (i.e. prefix+from,...,prefix+to). After that, for each field, it checks whether it has a value or not and returns the list of field names which have values i.e the non-empty ones.\n\n\uD83D\uDCDD _@param_ **`prefixExpr`** \nPrefix string to be used to generate field names.\n\n\uD83D\uDCDD _@param_ **`fromExpr`** \nThe start index (inclusive).\n\n\uD83D\uDCDD _@param_ **`toExpr`** \nThe end index (inclusive).\n\n\uD83D\uDD19 _@return_ \n```\n[\"child_1\", \"child_3\", \"child_4\"]\n``` \n\uD83D\uDCA1 **E.g.** mpp:nonEmptyLoopedFields('child_',1,5)", - insertText = "mpp:nonEmptyLoopedFields(, , )",detail = "mpp", label = "mpp:nonEmptyLoopedFields", kind = "Function", - returnType = Seq("String"), inputType = Seq()) + @FhirPathFunction( + documentation = FhirPathFunctionDocumentation( + detail = "Creates a sequence of indices between from-to integers and concatenates them with a prefix string to generate looped field names (i.e. prefix+from,...,prefix+to). After that, for each field, it checks whether it has a value or not and returns the list of field names which have values i.e. the non-empty ones.", + usageWarnings = None, + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "prefixExpr", + detail = "Prefix string to be used to generate field names.", + examples = None + ), + FhirPathFunctionParameter( + name = "fromExpr", + detail = "The start index (inclusive).", + examples = None + ), + FhirPathFunctionParameter( + name = "toExpr", + detail = "The end index (inclusive).", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq( + """["child_1", "child_3", "child_4"]""" + ) + ), + examples = Seq( + "mpp:nonEmptyLoopedFields('child_',1,5)" + ) + ), + insertText = "mpp:nonEmptyLoopedFields(, , )", + detail = "mpp", + label = "mpp:nonEmptyLoopedFields", + kind = "Function", + returnType = Seq(FHIR_DATA_TYPES.STRING), + inputType = Seq() + ) def nonEmptyLoopedFields(prefixExpr: ExpressionContext, fromExpr: ExpressionContext, toExpr: ExpressionContext): Seq[FhirPathResult] = { val prefix = new FhirPathExpressionEvaluator(context, current).visit(prefixExpr) if (prefix.length != 1 || !prefix.forall(_.isInstanceOf[FhirPathString])) @@ -124,8 +215,40 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa * @param keyExpr This is any expression that will provide the key value e.g. code * @return */ - @FhirPathFunction(documentation = "\uD83D\uDCDC Get corresponding concept from the given concept map with the given key. If there are more than one target column, it returns them as a complex JSON object. Otherwise, returns the value of it as list of string. \n⚠\uFE0F A mapping concept with specified reference **must** be registered to the mapping as a context to use this function.\n\n\uD83D\uDCDD _@param_ **`conceptMap`** \nA reference to the concept map context e.g. %obsConceptMap\n\n\uD83D\uDCDD _@param_ **`source_code`** \nSource code to perform lookup operation in the concept map e.g. code\n\n\uD83D\uDD19 _@return_ \n```json\n[\n {\n \"target_code\": \"AMB\",\n \"target_display\": \"ambulatory\"\n ...\n }\n]\n```\n\uD83D\uDD19 _@return_ \n```json\n[\"AMB\", ...] \n``` \n\uD83D\uDCA1 **E.g.** mpp:getConcept(%obsConceptMap, code)", - insertText = "mpp:getConcept(<%conceptMap>, )",detail = "mpp", label = "mpp:getConcept", kind = "Function", returnType = Seq(), inputType = Seq()) + @FhirPathFunction( + documentation = FhirPathFunctionDocumentation( + detail = "Get corresponding concept from the given concept map with the given key. If there are more than one target column, it returns them as a complex JSON object. Otherwise, returns the value of it as list of string.", + usageWarnings = Some(Seq("A mapping concept with specified reference **must** be registered to the mapping as a context to use this function.")), + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "conceptMap", + detail = "A reference to the concept map context.", + examples = Some(Seq("%obsConceptMap")) + ), + FhirPathFunctionParameter( + name = "source_code", + detail = "Source code to perform lookup operation in the concept map.", + examples = Some(Seq("code")) + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq( + """[{"target_code": "AMB", "target_display": "ambulatory", ...}]""", + """["AMB", ...]""" + ) + ), + examples = Seq( + "mpp:getConcept(%obsConceptMap, code)" + ) + ), + insertText = "mpp:getConcept(<%conceptMap>, )", + detail = "mpp", + label = "mpp:getConcept", + kind = "Function", + returnType = Seq(), + inputType = Seq() + ) def getConcept(conceptMap: ExpressionContext, keyExpr: ExpressionContext): Seq[FhirPathResult] = { val mapName = conceptMap.getText.substring(1) // skip the leading % character val conceptMapContext = getConceptMap(mapName) @@ -168,9 +291,42 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa * @param columnName This should be the FHIR Path string literal providing the name of the column * @return */ - @FhirPathFunction(documentation = "\uD83D\uDCDC Get corresponding value from the given concept map with the given key and column name. If there is no concept found with given key (code), return empty. It returns a list of string. \n⚠\uFE0F A mapping concept with specified reference **must** be registered to the mapping as a context to use this function.\n\n\uD83D\uDCDD _@param_ **`conceptMap`** \nA reference to the concept map context e.g. %obsConceptMap\n\n\uD83D\uDCDD _@param_ **`source_code`** \nSource code to perform lookup operation in the concept map e.g. code\n\n\uD83D\uDCDD _@param_ **`columnName`** \nFHIRPath string literal providing the name of the interested column from the concept map context.\n\n\uD83D\uDD19 _@return_ \n```json\n[\n \"target-column-value-1\",\n ...\n]\n``` \n\uD83D\uDCA1 **E.g.** mpp:getConcept(%obsConceptMap, code, 'target_code')", - insertText = "mpp:getConcept(<%conceptMap>, , )", detail = "mpp", label = "mpp:getConcept" - , kind = "Function", returnType = Seq("string"), inputType = Seq()) + @FhirPathFunction( + documentation = FhirPathFunctionDocumentation( + detail = "Get corresponding value from the given concept map with the given key and column name. If there is no concept found with given key (code), return empty. It returns a list of string.", + usageWarnings = Some(Seq("A mapping concept with specified reference **must** be registered to the mapping as a context to use this function.")), + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "conceptMap", + detail = "A reference to the concept map context.", + examples = Some(Seq("%obsConceptMap")) + ), + FhirPathFunctionParameter( + name = "source_code", + detail = "Source code to perform lookup operation in the concept map.", + examples = Some(Seq("code")) + ), + FhirPathFunctionParameter( + name = "columnName", + detail = "FHIRPath string literal providing the name of the interested column from the concept map context.", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq("""["target-column-value-1", ...]""") + ), + examples = Seq( + "mpp:getConcept(%obsConceptMap, code, 'target_code')" + ) + ), + insertText = "mpp:getConcept(<%conceptMap>, , )", + detail = "mpp", + label = "mpp:getConcept", + kind = "Function", + returnType = Seq(FHIR_DATA_TYPES.STRING), + inputType = Seq() + ) def getConcept(conceptMap: ExpressionContext, keyExpr: ExpressionContext, columnName: ExpressionContext): Seq[FhirPathResult] = { val mapName = conceptMap.getText.substring(1) // skip the leading % character val conceptMapContext = getConceptMap(mapName) @@ -211,8 +367,47 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa * @param unitExpr FHIR Path expression returning the unit in the source * @return */ - @FhirPathFunction(documentation = "\uD83D\uDCDC Convert the given value in given unit to the target unit specified in the context file with specified conversion function, return FHIR [Quantity](https://build.fhir.org/datatypes.html#Quantity). If there is no corresponding key (code) and unit in the unit conversion context, then return empty. \n⚠\uFE0F An unit conversion context with specified reference **must** be registered to the mapping as a context to use this function.\n\n\uD83D\uDCDD _@param_ **`unitConversion`** \nReference name to the unit conversion context used to register in the mapping. e.g. %labUnitConv\n\n\uD83D\uDCDD _@param_ **`source_code`** \nCode to perform lookup operation together with source_unit. e.g. labCode\n\n\uD83D\uDCDD _@param_ **`source_value`** \nThe value in the source unit. This value will be applied to the found conversion function to get the value in target unit. e.g. labResultValue\n\n\uD83D\uDCDD _@param_ **`source_unit`** \nThe unit to be converted. It is used to perform lookup operation together with source_code\n\n\uD83D\uDD19 _@return_ \n```json\n{\n \"value\": \"1.5\",\n \"code\": \"mg/L\",\n \"unit\": \"mg/L\",\n \"system\": \"http://unitsofmeasure.org\"\n}\n```\n\n\uD83D\uDCA1 **E.g.** mpp:convertAndReturnQuantity(%conversionFunctions, labCode, measuredValue, unit)", - insertText = "mpp:convertAndReturnQuantity(<%unitConversion>, , , )",detail = "mpp", label = "mpp:convertAndReturnQuantity", kind = "Function", returnType = Seq(), inputType = Seq()) + @FhirPathFunction( + documentation = FhirPathFunctionDocumentation( + detail = "Convert the given value in given unit to the target unit specified in the context file with specified conversion function, return FHIR [Quantity](https://build.fhir.org/datatypes.html#Quantity). If there is no corresponding key (code) and unit in the unit conversion context, then return empty.", + usageWarnings = Some(Seq("An unit conversion context with specified reference **must** be registered to the mapping as a context to use this function.")), + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "unitConversion", + detail = "Reference name to the unit conversion context used to register in the mapping.", + examples = Some(Seq("%labUnitConv")) + ), + FhirPathFunctionParameter( + name = "source_code", + detail = "Code to perform lookup operation together with source_unit.", + examples = Some(Seq("labCode")) + ), + FhirPathFunctionParameter( + name = "source_value", + detail = "The value in the source unit. This value will be applied to the found conversion function to get the value in target unit.", + examples = Some(Seq("labResultValue")) + ), + FhirPathFunctionParameter( + name = "source_unit", + detail = "The unit to be converted. It is used to perform lookup operation together with source_code", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq("""{"value": "1.5","code": "mg/L","unit": "mg/L","system": "http://unitsofmeasure.org"}""") + ), + examples = Seq( + "mpp:convertAndReturnQuantity(%conversionFunctions, labCode, measuredValue, unit)" + ) + ), + insertText = "mpp:convertAndReturnQuantity(<%unitConversion>, , , )", + detail = "mpp", + label = "mpp:convertAndReturnQuantity", + kind = "Function", + returnType = Seq(), + inputType = Seq() + ) def convertAndReturnQuantity(conversionFunctionsMap: ExpressionContext, keyExpr: ExpressionContext, valueExpr: ExpressionContext, unitExpr: ExpressionContext): Seq[FhirPathResult] = { val mapName = conversionFunctionsMap.getText.substring(1) // skip the leading % character val unitConversionContext = try { diff --git a/tofhir-rxnorm/src/main/scala/io/tofhir/rxnorm/RxNormApiFunctionLibrary.scala b/tofhir-rxnorm/src/main/scala/io/tofhir/rxnorm/RxNormApiFunctionLibrary.scala index b7ea91bb..58800eb8 100644 --- a/tofhir-rxnorm/src/main/scala/io/tofhir/rxnorm/RxNormApiFunctionLibrary.scala +++ b/tofhir-rxnorm/src/main/scala/io/tofhir/rxnorm/RxNormApiFunctionLibrary.scala @@ -1,7 +1,8 @@ package io.tofhir.rxnorm +import io.onfhir.api.FHIR_DATA_TYPES import io.onfhir.api.util.FHIRUtil -import io.onfhir.path.annotation.FhirPathFunction +import io.onfhir.path.annotation.{FhirPathFunction, FhirPathFunctionDocumentation, FhirPathFunctionParameter, FhirPathFunctionReturn} import io.onfhir.path.grammar.FhirPathExprParser.ExpressionContext import io.onfhir.path.{AbstractFhirPathFunctionLibrary, FhirPathComplex, FhirPathEnvironment, FhirPathException, FhirPathExpressionEvaluator, FhirPathNumber, FhirPathResult, FhirPathString, IFhirPathFunctionLibraryFactory} import org.json4s.JsonAST.{JArray, JDouble, JObject, JString} @@ -21,15 +22,34 @@ class RxNormApiFunctionLibrary(rxNormApiClient:RxNormApiClient, context: FhirPat * @param ndcExpr * @return */ - @FhirPathFunction( - documentation = "\uD83D\uDCDC Finds out RxNorm Concept Ids (rxcui) for the given NDC code.\n\n\uD83D\uDCDD _@param_ **`ndcExpr`** \nThe NDC code expression.\n\n\uD83D\uDD19 _@return_ \n```\n[\"104922\", \"105784\"]\n``` \n\uD83D\uDCA1 **E.g.** rxn:findRxConceptIdsByNdc('12345678901')", - insertText = "rxn:findRxConceptIdsByNdc()", - detail = "rxn", - label = "rxn:findRxConceptIdByNdc", - kind = "Function", - returnType = Seq("string"), - inputType = Seq("string") - ) + @FhirPathFunction( + documentation = FhirPathFunctionDocumentation( + detail = "Finds out RxNorm Concept Ids (rxcui) for the given NDC code.", + usageWarnings = None, + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "ndcExpr", + detail = "The NDC code expression.", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq( + """["104922", "105784"]""" + ) + ), + examples = Seq( + "rxn:findRxConceptIdsByNdc('12345678901')" + ) + ), + insertText = "rxn:findRxConceptIdsByNdc()", + detail = "rxn", + label = "rxn:findRxConceptIdByNdc", + kind = "Function", + returnType = Seq(FHIR_DATA_TYPES.STRING), + inputType = Seq(FHIR_DATA_TYPES.STRING) + ) def findRxConceptIdsByNdc(ndcExpr:ExpressionContext):Seq[FhirPathResult] = { val ndc: Option[String] = evaluator.visit(ndcExpr) match { @@ -53,13 +73,32 @@ class RxNormApiFunctionLibrary(rxNormApiClient:RxNormApiClient, context: FhirPat * @return */ @FhirPathFunction( - documentation = "\uD83D\uDCDC Finds out Ingredients and their properties for the given RxNorm Drug.\n\n\uD83D\uDCDD _@param_ **`rxcuiExpr`** \nThe RxNorm Concept Id expression for the drug.\n\n\uD83D\uDD19 _@return_ \n```json\n{\n \"ingredientAndStrength\": [\n {\n \"activeIngredientRxcui\": \"104922\",\n \"activeIngredientName\": \"Acetaminophen\",\n \"numeratorValue\": 500.0,\n \"numeratorUnit\": \"mg\",\n \"denominatorValue\": 1.0,\n \"denominatorUnit\": \"tablet\"\n },\n {\n \"activeIngredientRxcui\": \"105784\",\n \"activeIngredientName\": \"Ibuprofen\",\n \"numeratorValue\": 200.0,\n \"numeratorUnit\": \"mg\",\n \"denominatorValue\": 1.0,\n \"denominatorUnit\": \"tablet\"\n }\n ],\n \"doseFormConcept\": {\n \"rxcui\": \"106366\",\n \"doseFormName\": \"Tablet\"\n }\n}\n``` \n\uD83D\uDCA1 **E.g.** rxn:getMedicationDetails('12345')", + documentation = FhirPathFunctionDocumentation( + detail = "Finds out Ingredients and their properties for the given RxNorm Drug.", + usageWarnings = None, + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "rxcuiExpr", + detail = "The RxNorm Concept Id expression for the drug.", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq( + """{"ingredientAndStrength": [{"activeIngredientRxcui": "104922","activeIngredientName": "Acetaminophen","numeratorValue": 500.0,"numeratorUnit": "mg","denominatorValue": 1.0,"denominatorUnit": "tablet"},{"activeIngredientRxcui": "105784","activeIngredientName": "Ibuprofen","numeratorValue": 200.0,"numeratorUnit": "mg","denominatorValue": 1.0,"denominatorUnit": "tablet"}],"doseFormConcept": {"rxcui": "106366","doseFormName": "Tablet"}}""" + ) + ), + examples = Seq( + "rxn:getMedicationDetails('12345')" + ) + ), insertText = "rxn:getMedicationDetails()", detail = "rxn", label = "rxn:getMedicationDetails", kind = "Function", returnType = Seq("complex"), - inputType = Seq("string") + inputType = Seq(FHIR_DATA_TYPES.STRING) ) def getMedicationDetails(rxcuiExpr:ExpressionContext):Seq[FhirPathResult] = { val conceptIds = evaluator.visit(rxcuiExpr) @@ -130,13 +169,32 @@ class RxNormApiFunctionLibrary(rxNormApiClient:RxNormApiClient, context: FhirPat * @return */ @FhirPathFunction( - documentation = "\uD83D\uDCDC Finds out Ingredients and their properties for the given RxNorm Drug.\n\n\uD83D\uDCDD _@param_ **`rxcuiExpr`** \nThe RxNorm Concept Id expression for the drug.\n\n\uD83D\uDD19 _@return_ \n```json\n[\n {\n \"ingredient\": \"Acetaminophen\",\n \"strength\": \"500 mg\"\n },\n {\n \"ingredient\": \"Ibuprofen\",\n \"strength\": \"200 mg\"\n }\n]\n``` \n\uD83D\uDCA1 **E.g.** rxn:findIngredientsOfDrug('12345')", + documentation = FhirPathFunctionDocumentation( + detail = "Finds out Ingredients and their properties for the given RxNorm Drug.", + usageWarnings = None, + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "rxcuiExpr", + detail = "The RxNorm Concept Id expression for the drug.", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq( + """[{"ingredient": "Acetaminophen","strength": "500 mg"}, {"ingredient": "Ibuprofen","strength": "200 mg"}]""" + ) + ), + examples = Seq( + "rxn:findIngredientsOfDrug('12345')" + ) + ), insertText = "rxn:findIngredientsOfDrug()", detail = "rxn", label = "rxn:findIngredientsOfDrug", kind = "Function", returnType = Seq("complex"), - inputType = Seq("string") + inputType = Seq(FHIR_DATA_TYPES.STRING) ) def findIngredientsOfDrug(rxcuiExpr:ExpressionContext):Seq[FhirPathResult] = { val rxcui: Option[String] = @@ -157,13 +215,32 @@ class RxNormApiFunctionLibrary(rxNormApiClient:RxNormApiClient, context: FhirPat * @return */ @FhirPathFunction( - documentation = "\uD83D\uDCDC Finds the corresponding ATC code for the given RxNorm ingredient concept.\n\n\uD83D\uDCDD _@param_ **`rxcuiExpr`** \nThe RxNorm Concept Id expression for the drug.\n\n\uD83D\uDD19 _@return_ \n```\n\"N02BE01\"\n``` \n\uD83D\uDCA1 **E.g.** rxn:getATC('12345')", + documentation = FhirPathFunctionDocumentation( + detail = "Finds the corresponding ATC code for the given RxNorm ingredient concept.", + usageWarnings = None, + parameters = Some(Seq( + FhirPathFunctionParameter( + name = "rxcuiExpr", + detail = "The RxNorm Concept Id expression for the drug.", + examples = None + ) + )), + returnValue = FhirPathFunctionReturn( + detail = None, + examples = Seq( + "\"N02BE01\"" + ) + ), + examples = Seq( + "rxn:getATC('12345')" + ) + ), insertText = "rxn:getATC()", detail = "rxn", label = "rxn:getATC", kind = "Function", - returnType = Seq("string"), - inputType = Seq("string") + returnType = Seq(FHIR_DATA_TYPES.STRING), + inputType = Seq(FHIR_DATA_TYPES.STRING) ) def getATC(rxcuiExpr:ExpressionContext): Seq[FhirPathResult] = { val rxcui: String = diff --git a/tofhir-server/src/test/scala/io/tofhir/server/endpoint/SchemaEndpointTest.scala b/tofhir-server/src/test/scala/io/tofhir/server/endpoint/SchemaEndpointTest.scala index a80d0391..ecd87a14 100644 --- a/tofhir-server/src/test/scala/io/tofhir/server/endpoint/SchemaEndpointTest.scala +++ b/tofhir-server/src/test/scala/io/tofhir/server/endpoint/SchemaEndpointTest.scala @@ -4,7 +4,7 @@ import akka.actor.ActorSystem import akka.http.scaladsl.model.{ContentType, ContentTypes, HttpEntity, MediaTypes, Multipart, StatusCodes} import akka.http.scaladsl.testkit.RouteTestTimeout import io.onfhir.api.client.FhirBatchTransactionRequestBuilder -import io.onfhir.api.{FHIR_FOUNDATION_RESOURCES, Resource} +import io.onfhir.api.{FHIR_DATA_TYPES, FHIR_FOUNDATION_RESOURCES, Resource} import io.tofhir.OnFhirTestContainer import io.onfhir.definitions.common.model.{DataTypeWithProfiles, SchemaDefinition, SimpleStructureDefinition} import io.tofhir.engine.model._ @@ -52,12 +52,12 @@ class SchemaEndpointTest extends BaseEndpointTest with OnFhirTestContainer { val schema3: SchemaDefinition = SchemaDefinition(url = "https://example.com/fhir/StructureDefinition/schema3", version = SchemaDefinition.VERSION_LATEST, `type` = "Ty3", name = "name3", description = Some("description3"), rootDefinition = None, fieldDefinitions = Some( Seq( SimpleStructureDefinition(id = "element-with-definition", - path = "Ty3.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty3.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-definition"), definition = Some("element definition"), comment = None, elements = None), SimpleStructureDefinition(id = "element-with-no-definition", - path = "Ty3.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty3.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-no-definition"), definition = None, comment = None, elements = None) @@ -70,12 +70,12 @@ class SchemaEndpointTest extends BaseEndpointTest with OnFhirTestContainer { val schema4: SchemaDefinition = SchemaDefinition(url = "https://example.com/fhir/StructureDefinition/schema4", version = SchemaDefinition.VERSION_LATEST, `type` = "Ty4", name = "name4", description = Some("description4"), rootDefinition = None, fieldDefinitions = Some( Seq( SimpleStructureDefinition(id = "element-with-short", - path = "Ty4.element-with-short", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty4.element-with-short", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-short"), definition = None, comment = None, elements = None), SimpleStructureDefinition(id = "element-with-no-short", - path = "Ty4.element-with-no-short", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty4.element-with-no-short", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = None, definition = None, comment = None, elements = None) @@ -245,10 +245,10 @@ class SchemaEndpointTest extends BaseEndpointTest with OnFhirTestContainer { val schema: SchemaDefinition = JsonMethods.parse(responseAs[String]).extract[SchemaDefinition] val fieldDefinitions = schema.fieldDefinitions.get fieldDefinitions.size shouldEqual 4 - fieldDefinitions.head.dataTypes.get.head.dataType shouldEqual "integer" - fieldDefinitions(1).dataTypes.get.head.dataType shouldEqual "date" - fieldDefinitions(2).dataTypes.get.head.dataType shouldEqual "dateTime" - fieldDefinitions(3).dataTypes.get.head.dataType shouldEqual "string" + fieldDefinitions.head.dataTypes.get.head.dataType shouldEqual FHIR_DATA_TYPES.INTEGER + fieldDefinitions(1).dataTypes.get.head.dataType shouldEqual FHIR_DATA_TYPES.DATE + fieldDefinitions(2).dataTypes.get.head.dataType shouldEqual FHIR_DATA_TYPES.DATETIME + fieldDefinitions(3).dataTypes.get.head.dataType shouldEqual FHIR_DATA_TYPES.STRING } } @@ -626,12 +626,12 @@ class SchemaEndpointTest extends BaseEndpointTest with OnFhirTestContainer { val schemaUrl: String = "https://example.com/fhir/StructureDefinition/schema" val schema: SchemaDefinition = SchemaDefinition(url = schemaUrl, version = "1.4.2", `type` = "Ty", name = "name", description = Some("description"), rootDefinition = None, fieldDefinitions = Some(Seq( SimpleStructureDefinition(id = "element-with-definition", - path = "Ty.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-definition"), definition = Some("element definition"), comment = None, elements = None), SimpleStructureDefinition(id = "element-with-no-definition", - path = "Ty.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-no-definition"), definition = None, comment = None, elements = None)