Using Terminology Services

FHIR defines several operations related to terminologies. These operations let healthcare applications make use of codes and value sets without having to become experts in the fine details of code system, value set and concept map resources, and the underlying code systems and terminological principles.

We have added support in the Firely .NET SDK to use these operations, both locally, as calling an external “Terminology Server” that have implemented these operations and return a result based on the input parameters. Both LocalTerminologyService as ExternalTerminologyService implement ITerminologyService.

ITerminologyService

ITerminologyService is an interface that describes all functions of a terminology service. ITerminologyService can for example be added to a FHIR validator to handle everything terminology related. All functions represent a FHIR Terminology operation and have a Parameters resource as input that represents the input parameters of the FHIR operation. There is a specific helper class for each operation to easily create this Parameters resource. Each function returns the output of that specific operation, this can be a Parameters resource including all output parameters, or just a single Resource which was the result of your request.

The following functions are defined in the interface are:

  • Task<Parameters> ValueSetValidateCode(Parameters parameters, string id = null, bool useGet = false)

Represents the $validate-code operation on the ValueSet resource (https://www.hl7.org/fhir/valueset-operation-validate-code.html) and will validate that a coded value is in the set of codes allowed by a value set. The input parameters can be easily created using the ValidateCodeParameters class.

  • Task<Parameters> CodeSystemValidateCode(Parameters parameters, string id = null, bool useGet = false)

Represents the $validate-code operation on the CodeSystem resource (http://hl7.org/fhir/codesystem-operation-validate-code.html) and will validate that a coded value is in the code system. The input parameters can be easily created using the ValidateCodeParameters class.

  • Task<Resource> Expand(Parameters parameters, string id = null, bool useGet = false)

Represents the $expand operation (http://hl7.org/fhir/valueset-operation-expand.html), which uses the definition of a value set to create a simple collection of codes suitable for use for data entry or validation. The input parameters can be easily created using the ExpandParameters class.

  • Task<Parameters> Lookup(Parameters parameters, bool useGet = false)

Represents the $lookup operation (http://hl7.org/fhir/codesystem-operation-lookup.html), which given a given a code, system, or a Coding, get additional details about the concept, including definition, status, designations, and properties. The input parameters can be easily created using the LookupParameters class.

  • Task<Parameters> Translate(Parameters parameters, string id = null, bool useGet = false)

Represents the translate operation (”http://hl7.org/fhir/conceptmap-operation-translate.html”), which translates a code from one value set to another, based on the existing value set and concept maps resources, and/or other additional knowledge of the processor. The input parameters can be easily created using the TranslateParameters class.

  • Task<Parameters> Subsumes(Parameters parameters, string id = null, bool useGet = false)

Represents the subsumes operation (”http://hl7.org/fhir/codesystem-operation-subsumes.html”) and tests the subsumption relationship between code/Coding A and code/Coding B given the semantics of subsumption in the underlying code system. The input parameters can be easily created using the SubsumesParameters class.

  • Task<Resource> Closure(Parameters parameters, bool useGet = false)

Represents the closure operation (”http://hl7.org/fhir/conceptmap-operation-closure.html”) and provides support for ongoing maintenance of a client-side transitive closure table based on server-side terminological logic. The input parameters can be easily created using the ClosureParameters class.

LocalTerminologyService

The Firely SDK support some terminology logic itself using the LocalTerminologyService meaning it doesn’t call a third party to handle it, but does as much as it can internally.

Note

The LocalTerminologyService doesn’t support all terminology features. To fully support all features a lot of terminology expertise is needed. So to make use of all advanced terminology features, use the ExternalTerminologyServer to let a dedicated terminology server handle your requests.

The LocalTerminologyService requires an IResourceResolver to resolve all the FHIR resources needed to perform it’s terminology logic.

The following function is currently supported by the LocalTerminologyService:

  • Task<Parameters> ValueSetValidateCode(Parameters parameters, string id = null, bool useGet = false)

This function validates that a coded value is in the set of codes allowed by a value set. The ValueSet is searched using the provided IResourceResolver, for example an package_source containing all FHIR artifacts from a given package. Once the ValueSet is found, the provides code will be validates against that ValueSet.

In some cases ValueSets are implicitly defined using the valueSet element in a CodeSystem resource, which implicitly defines a ValueSet containing all codes from that CodeSystem. If the IResourceResolver also implements IConformanceSource, the LocalTerminologyService can still validate your code against such an implicitly defined ValueSet.

ExternalTerminologyService

ExternalTerminologyService implements all functions of ITerminologyService and uses a FhirClient to connect to an external terminology server that will handle all operations.

All functions in the ExternalTerminologyService have a bool useGet = false parameter, which can be used to make the FHIR client use the GET rest operation, instead of POST when sending the request.

The example below first creates a FhirClient based on the FHIR endpoint of your favorite terminology server. The ExternalTerminologyServer will use that client to connect to the terminology server and send a request to the server to expand a particular ValueSet using the $expand operation. After processing your request, the terminology server returns the expanded ValueSet based on your input (or an OperationOutcome if something went wrong).

var client = new FhirClient("https://someterminologyserver.org/fhir");
var svc = new ExternalTerminologyService(client);

var parameters = new ExpandParameters()
    .WithValueSet(url: "http://snomed.info/sct?fhir_vs=refset/142321000036106")
    .WithFilter("met")
    .WithPaging(count: 10);

var result = await svc.Expand(parameters) as ValueSet;

CustomValueSetTerminologyService

CustomValueSetTerminologyService is an abstract implementation of ITerminologyService that allows you to specify a custom ValueSet to validate codes against. The base class implements most of the functionality, but if you wish to define your own terminology service, you will need to implement the ValidateCodeType function yourself. You should also populate some fields (required by the constructor). Some examples from the Mime type terminology service:

  • ValidateCodeType: This function should validate a string against the custom ValueSet. It should return true if the code is valid, and false if it is not.

  • terminologyType: String representation of the code type which is being checked. Exclusively used for error messages.

  • codeSystem: Name of the specification defining the members of the value set.

  • codeValueSets: uri’s of the definitions of the code system. This can be multiple, if a FHIR version has changed this at some point.

Two terminology services which use a custom ValueSet are already implemented:

  • MimeTypeTerminologyService: Can be used to verify that a code is a valid mime type.

  • LanguageTerminologyService: Can be used to verify that a code is a valid language code.

An example of a custom terminology service (LanguageTerminologyService) is shown below:

public class LanguageTerminologyService : CustomValueSetTerminologyService
{
    private const string LANGUAGE_SYSTEM = "urn:ietf:bcp:47";
    public const string LANGUAGE_VALUESET = "http://hl7.org/fhir/ValueSet/all-languages";

    public LanguageTerminologyService() : base("language", LANGUAGE_SYSTEM, [LANGUAGE_VALUESET])
    {
    }

    override protected bool ValidateCodeType(string code)
    {
        var regex = new Regex("^[a-z]{2}(-[A-Z]{2})?$"); // matches either two lowercase letters OR 2 lowercase letters followed by a dash and two uppercase letters
        return regex.IsMatch(code);
    }
}

MultiTerminologyService

MultiTerminologyService allows you to combine multiple terminology services. This is useful when you have terminology service that are specialized in certain ValueSet or if you want to first check if you can process codes locally before consulting an external terminology service. The order of the terminology services added to the constructor decides which terminology service is consulted first. If a terminology service comes back with a result (true of false), the fallback services are not consulted anymore.

var local = new LocalTerminologyService(ZipSource.CreateValidationSource());

var client = new FhirClient("https://someterminologyserver.org/fhir");
var multi = new ExternalTerminologyService(client);

var multiTermService = new MultiTerminologyService(local, multi);

In this example above, the local terminology service is always consulted first, but if the local service is indecisive (a FhirOperationException is thrown), the external service is consulted.

Routing

Sometimes, you already know that certain ValueSets should be handled by a specific terminology service. For example, if you have a LocalTerminologyService with all your custom ValueSets, you already know that all other services will not be able to validate codes from those ValueSets. That’s when you want to introduce routing in your MultiTerminologyService.

You can add routing information to the MultiTerminologyService by adding a TerminologyServiceRoutingSettings to the constructor.

var local = new LocalTerminologyService(ZipSource.CreateValidationSource());
var localRouting = new TerminologyServiceRoutingSettings(local)
{
    PreferredValueSets = new string[]{"http://fire.ly/ValueSet/*"}
};

var client = new FhirClient("https://someterminologyserver.org/fhir");
var multi = new ExternalTerminologyService(client);
var multiRouting = new TerminologyServiceRoutingSettings(multi);
{
    PreferredValueSets = new string[]{"http://hl7.fhir.org/ValueSet/*"}
}

var multiTermService = new MultiTerminologyService(localRouting, multiRouting);

Note

You can use a ‘*’ to specify wildcards in the routing mechanism.

The example above will route all ValueSets starting with “http://fire.ly/ValueSet/” to the local terminology service first, and the ValueSets starting with “http://hl7.fhir.org/ValueSet/” to the external service. All other incoming requests will be handles by the order the services have been passed to the constructor, in this case, first local, then external.