40.8.1FHIR REST API

 

The Smile CDR JavaScript execution environment has two objects that can be used to interact with the REST API of a FHIR server.

The Fhir object can be used to connect to a local FHIR Storage module that is declared as a module dependency. This connection is internal and does not require authorization. For example, the following example shows an internal FHIR create operation.

Fhir.create(patient);

The FhirClientFactory object can be used to create a client with the ability to perform HTTP REST connections to external FHIR servers. An equivalent call to an external FHIR server might look like the following:

var client = FhirClientFactory.newClient('http://example.com/fhirBaseUrl');
client.create(patient);

Both of these APIs provide access to operations against a FHIR Persistence module (e.g. search, transaction, etc.). Note that most examples below show the API being used to access a local FHIR server using the Fhir API. Unless specified otherwise, it is possible to invoke all of the same functions on a client obtained from the FhirClientFactory.

40.8.2Method: create(resource)

 

Create a new resource.

Inputs:

  • resource – the resource to create. Note that when the method returns the resource will be updated to have the ID which was assigned during the create process.

Outputs:

  • This method does not return an output.

Example:

var patient = ResourceBuilder.build('Patient');
patient.name.family = 'Smith';
patient.name.given = 'John';
Fhir.create(patient);

40.8.3Method: read(url)

 

Read a resource at a particular url.

Inputs:

  • url – the location of the desired Resource.

Outputs:

  • Returns a Fhir Resource or null if the url is invalid or the resource is not found.

Example:

var myPatient = Fhir.read('Patient/123');

40.8.4Method: search().forResource(resourceType)

 

Fhir.search().forResource(resourceType) creates a Fhir search object which can be built up and eventually executed.

Inputs:

  • resourceType – the resource type to eventually perform the search on

Outputs:

  • Returns an IFhirTypedSearch object with the following methods:

40.8.4.1where(paramName, paramValue)

Each .where call adds an And condition to your search. To use an Or list, separate values with a comma like you would in a Fhir search (example below).

Inputs:

  • paramName – the code of your desired Hapi Fhir SearchParameter
  • paramValue – the value to search for. Typically identical to how you would search Hapi Fhir in your browser, with a couple noted exceptions below.

Outputs:

  • Returns the same IFhirTypedSearch object this was applied to.
var conditionList = Fhir
    .search()
    .forResource('Observation')
    .where('subject', 'Patient/123')
    .asList();

40.8.4.2where(resourceType, paramName, paramValue)

Only to be used on searches of type reference. Same as the 2-parameter version, but with optional resourceType declaration for more specific / efficient reference type searches.

var conditionList = Fhir
    .search()
    .forResource('Observation')
    .where('Encounter', 'context', 'Encounter/234')
    .asList();

40.8.4.3where(tokenParam, 'system|code')

For comma separated Token searches, there is no need to repeat the same system multiple times. Instead, each comma-separated code will take on the most recently declared System.

As a result, if you want to include any codes with a wildcard system, you must list those first.

Example:

The following would return all Conditions for all of the 3 listed Patients where the Condition code is ABCDE in any code system, 123, 456, or 789 in Snomed, or 12345 in Loinc.

var conditionList = Fhir
    .search()
    .forResource('Condition')
    .where('subject', 'Patient/1386,Patient/1387,Patient/1388')
    .where('code', 'ABCDE,http://snomed.info/sct|123,456,789,http://loinc.org|12345')
    .asList();

40.8.4.3.1Creating Token Parameter Values

You may also use the utility method whereToken(paramName, system, value) to generate a token.

Example:

var conditionList = Fhir
    .search()
    .forResource('Condition')
    .whereToken('code', 'http://snomed.info/sct', '425363002')
    .asList();

40.8.4.4sort(sortParam)

Results from the search will be sorted according to the sort parameters specified. Your sort chain can be specified in a single comma separated sort call or in multiple calls to sort. Prepend your param with a dash - to sort in descending order.

Example usage: Sort such that the most recent entry is first and break ties by id ascending.

var conditionList = Fhir
    .search()
    .forResource('Observation')
    .sort('-date,_id')
    .asList();

40.8.4.5count(resourceLimit)

Limit the maximum possible number of results. If this parameter is not specified, your search will return all matching results up to a maximum of 1000.

Example usage: Retrieve only the most recent Observation for a patient.

var conditionList = Fhir
    .search()
    .forResource('Observation')
    .where('subject', 'Patient/1386')
    .sort('-date')
    .count(1)
    .asList();

if (conditionList.length > 0) {
   Log.info(conditionList[0].toJson());
}

40.8.4.6Chaining

The JavaScript environment supports chaining for reference parameters. To create a chained query, place a dot . between each chain.

When chaining, you can optionally add a resource type parameter for the expected resource type in the first chain. In the example below, by specifying Encounter, we ensure that Condition.context only matches resources of type Encounter before continuing the chain.

Example:

var conditionList = Fhir
    .search()
    .forResource('Condition')
    .where('subject.given', 'Ron')
    .where('Patient', 'subject.family', 'Howard')
    .where('Encounter', 'context.service-provider.type', 'prov')
    .asList();

40.8.4.7Dates

To search by dates, use the same syntax as you would when making a Hapi Fhir query.

var problemList = Fhir
    .search()
    .forResource('Condition')
    .where('asserted-date', 'gt2018-06-01')
    .asList();

If you want to do a search relative to the current date, you have two options.

  1. Determine your desired search query outside of the JSEE and pass it in as a parameter to whichever module you are using.
  2. Use JavaScript's native Date object.
var oneYearAgo = new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);

var problemList = Fhir
    .search()
    .forResource('Condition')
    .where('asserted-date', 'ge' + oneYearAgo.toISOString().slice(0,10))
    .asList();

40.8.4.8asList()

Outputs:

  • Performs the search and returns an array of resources.

Note: no search is performed until the .asList() method is called. Everything before that just builds the query.

40.8.4.8.1Usage

var conditionList = Fhir
    .search()
    .forResource('Condition')
    .where('subject', 'Patient/1386')
    .where('context', 'Encounter/1390')
    .asList();
    
for (var i = 0; i < conditionList.length; i++) {
  Log.info(conditionList[i].toJson());
}

40.8.5Method: transaction(IBundleBuilder theTransaction)

 

This method is used to persist the resources in a bundle built with the TransactionBuilder API.

var transaction = TransactionBuilder.newTransactionBuilder();

/* populate transaction as per the TransactionBuilder API */

// persist the resources in the transaction bundle
Fhir.transaction(transaction);

40.8.6Method: translate()

 

This method facilitates terminology mapping by translating a code from one CodeSystem to another. It makes use of an underlying implementation of the $translate operation for ConceptMap. Presently, only the three invocations detailed below are supported; however, more will be made available in future releases of Smile CDR.

Note that for these translations to occur, mappings must first be stored on the server in ConceptMap resources. These mappings can be imported using either a FHIR endpoint or available command-line tools.

40.8.6.1Method Chain: translate()...andReturnCodeAsString()

Inputs:

  • srcSystem – The source CodeSystem.
  • srcCode – The code to be translated.
  • tgtSystem – The target CodeSystem.

Outputs:

  • Returns the mapped code in the target CodeSystem as a string.

Example:

var tgtCode = Fhir
   .translate()
   .fromSystemAndCode(srcSystem, srcCode)
   .toCodeSystem(tgtSystem)
   .andReturnCodeAsString();

40.8.6.2Method Chain: translate()...andReturnCoding()

Inputs:

  • srcSystem – The source CodeSystem.
  • srcCode – The code to be translated.
  • tgtSystem – The target CodeSystem.

Outputs:

  • Returns the mapped code in the target CodeSystem as a Coding.

Example:

var tgtCoding = Fhir
   .translate()
   .fromSystemAndCode(srcSystem, srcCode)
   .toCodeSystem(tgtSystem)
   .andReturnCoding();

The various fields within a Coding can be accessed as follows:

var tgtSystem = tgtCoding.system;
var tgtCode = tgtCoding.code;
var tgtDisplay = tgtCoding.display;

Note if multiple matches found in the case of the two methods above. The method will only take the first match with equal equivalence, or if none exists, just take the first match. For getting all the matched results, see the methods below.

40.8.6.3Method Chain: translate()...andReturnMultipleCodesAsString()

Inputs:

  • srcSystem – The source CodeSystem.
  • srcCode – The code to be translated.
  • tgtSystem – The target CodeSystem.

Outputs:

  • Returns the mapped codes in the target CodeSystem as a string of list.

Example:

var tgtCodes = Fhir
   .translate()
   .fromSystemAndCode(srcSystem, srcCode)
   .toCodeSystem(tgtSystem)
   .andReturnMultipleCodesAsString();

40.8.6.4Method Chain: translate()...andReturnMultipleCodings()

Inputs:

  • srcSystem – The source CodeSystem.
  • srcCode – The code to be translated.
  • tgtSystem – The target CodeSystem.

Outputs:

  • Returns the mapped codes in the target CodeSystem as a list of Coding.

Example:

var tgtCodings = Fhir
   .translate()
   .fromSystemAndCode(srcSystem, srcCode)
   .toCodeSystem(tgtSystem)
   .andReturnMultipleCodings();

The various fields within a list of Codings can be accessed as follows:

var tgtSystem = tgtCoding[0].system;
var tgtCode = tgtCoding[0].code;
var tgtDisplay = tgtCoding[0].display;

40.8.7Method: update(resource)

 

Update an existing resource (can also be used to create a resource with a client assigned ID.

Inputs:

  • resource – the resource to update. Note that when the method returns the resource will be updated to have the ID which was assigned during the update process.

Outputs:

This method does not return an output.

Example:

var patient = ResourceBuilder.build('Patient');
patient.id = 'Patient/A123';
patient.name.family = 'Smith';
patient.name.given = 'John';
Fhir.update(patient);

40.8.8Method: patch(url, parameters)

 

Update an existing resource using the FHIR Patch operation.

Inputs:

  • url – the location of the Resource to be patched.
  • parameters – the parameters defining the patch operation.

Outputs:

This method does not return an output.

Example:

   let parameters = ResourceBuilder.build('Parameters');
   parameters.parameter[0].name = 'operation';
   parameters.parameter[0].part[0].name = 'type';
   parameters.parameter[0].part[0].valueString = 'add';
   parameters.parameter[0].part[1].name = 'path';
   parameters.parameter[0].part[1].valueString= 'Patient.name';
   parameters.parameter[0].part[2].name = 'name';
   parameters.parameter[0].part[2].valueString = 'family';
   parameters.parameter[0].part[3].name = 'value';
   parameters.parameter[0].part[3].valueString = 'Peterson';
   Fhir.patch('Patient/P1234', parameters);

40.8.9Authentication

 

When using the FhirClientFactory to access a remote HTTP server, the following methods may be used to add authentication headers to requests. Note that the Environment API may be useful in order to securely store credentials without needing to hard-code them in your JavaScript code.

40.8.9.1HTTP Basic Auth

Use the withHttpBasicAuth(theUsername, thePassword) method to add HTTP Basic Auth credentials to your request. For example:

var client = FhirClientFactory.newClient('http://example.com/baseFhirUrl');
client = client.withHttpBasicAuth('username', 'password');
var patient = client.read('Patient/1');

Use the withBearerTokenAuth(theBearerToken) method to add a Bearer token to your request. For example:

var client = FhirClientFactory.newClient('http://example.com/baseFhirUrl');
client = client.withBearerTokenAuth('w88eoiewifsdg');
var patient = client.read('Patient/1');

40.8.10Tenant Selection

 

When using the Fhir object for direct access to a FHIR Storage module that is using Request Tenant Selection Mode, you can request that the FHIR object perform operations against a specific tenant by invoking the withTenantName(..) method directly against the Fhir object. This method returns a new Fhir instance that will work against the given tenant.

For example:

Fhir.withTenantName('some-tenant').create(patient);
Fhir.withTenantName('some-tenant').update(patient);
Fhir.withTenantName('some-tenant').transaction(bundle);

40.8.11Concurrency Retry

 

When using data ingestion modules that involve highly concurrent data writes (for example, when using the ETL Import module) it is sometimes hard to avoid multiple rows resulting in the creation of the same resource.

For example, suppose you are ingesting an ETL file where each row represents a single Observation resource, and then using a FHIR Conditional Create to create the relevant subject link in the Observation resources. In this case it is possible for two rows to try creating the same Patient resource at the exact same time.

By default, Smile CDR will abort processing if it detects such a collision. However, it is also possible to direct Smile CDR to simply retry an operation if a collision is detected. This is done by invoking the withMaxConcurrencyRetry(count) function on the Fhir object. The argument to this function specifies a maximum number of retry attempts that should be made if a concurrency issue is detected.

For example:

Fhir.withMaxConcurrencyRetry(10).create(patient);
Fhir.withMaxConcurrencyRetry(10).update(patient);
Fhir.withMaxConcurrencyRetry(10).transaction(bundle);

40.8.12Configuration Requirements for ONC (g)(10) Certification

 

The following Smile CDR configurations were used during testing of ONC (g)(10). ONC (g)(10) certification impacts three components of the Smile CDR platform:

  • FHIR REST API
  • SMART on FHIR
  • Bulk Export

The following modules require a specific configuration for ONC (g)(10) certification. Module names, paths, ports, threads and urls are flexible in this setup, but provided for completeness.

40.8.12.1FHIR API

################################################################################
fhir_endpoint
################################################################################
module.fhir_endpoint.type=ENDPOINT_FHIR_REST_R4
module.fhir_endpoint.requires.SECURITY_IN_UP=local_security
module.fhir_endpoint.requires.PERSISTENCE_R4=persistence
module.fhir_endpoint.requires.SECURITY_IN_OIC=smart_auth
module.fhir_endpoint.config.context_path=/fhir-request
module.fhir_endpoint.config.port=18000
module.fhir_endpoint.config.threadpool.min=2
module.fhir_endpoint.config.custom_interceptor_can_replace_authorization_interceptor=false
module.fhir_endpoint.config.base_url.fixed=https://cms.smilecdr.com/fhir-request
module.fhir_endpoint.config.fhir_endpoint_security.reject_insufficient_permissions_with_401=true
module.fhir_endpoint.config.respect_forward_headers=true
module.fhir_endpoint.config.anonymous.access.enabled=true
module.fhir_endpoint.config.security.http.basic.enabled=true
module.fhir_endpoint.config.security.oic.enabled=true
module.fhir_endpoint.config.cors.enable=true

40.8.12.2SMART Outbound Security Module

################################################################################
smart_auth
################################################################################
module.smart_auth.type=SECURITY_OUT_SMART
module.smart_auth.requires.SECURITY_USER_SELF_REGISTRATION=local_security
module.smart_auth.requires.SECURITY_IN_UP=local_security
module.smart_auth.config.port=19200
module.smart_auth.config.openid.signing.jwks_file=classpath:/smilecdr-demo.jwks
module.smart_auth.config.post_authorize_script.text=function onTokenGenerating(theUserSession, theAuthorizationRequestDetails, theClientDetails) {\
if (theUserSession) {\
theUserSession.addLaunchContextParameter('need_patient_banner', 'false');\
theUserSession.addLaunchContextParameter('smart_style_url','https://cms.smilecdr.com/static/smart_v1.json');\
}\
\
if (theUserSession) {\
var patientLaunchContext = theUserSession.getOrCreateDefaultLaunchContext('patient');\
\
if (patientLaunchContext.resourceId) {\
theUserSession.setFhirUserUrl("http://cms.smilecdr.com/fhir-request/Patient/" + patientLaunchContext.resourceId); \
}\
}\
\
}\
\
module.smart_auth.config.issuer.url=https://cms.smilecdr.com
module.smart_auth.config.smart_capabilities_list=launch-ehr\
client-public\
client-confidential-symmetric\
context-ehr-patient\
context-standalone-patient\
sso-openid-connect\
permission-patient\
launch-standalone\
permission-offline\
permission-user\
context-ehr-encounter
module.smart_auth.config.respect_forward_headers=true
module.smart_auth.config.rotate_refresh_token_after_use=true
module.smart_auth.config.cors.enable=true

40.8.12.3Bulk Export

################################################################################
persistence
################################################################################
module.persistence.type=PERSISTENCE_R4
module.persistence.config.db.hibernate_search.directory=./database/lucene_fhir_persistence
module.persistence.config.db.connectionpool.maxtotal=8
module.persistence.config.indexing.index_missing_search_params=ENABLED
module.persistence.config.dao_config.default_total_mode=NONE
module.persistence.config.db.driver=POSTGRES_9_4
module.persistence.config.searchcache.infinispan.protocol_version=PROTOCOL_VERSION_10
module.persistence.config.db.username=cdr
module.persistence.config.db.password=[REMOVED]
module.persistence.config.search.nickname_enabled=false
module.persistence.config.db.hibernate_search.elasticsearch.url=http://localhost:9090
module.persistence.config.db.url=jdbc:postgresql://localhost/cdrnew?sslmode=disable
module.persistence.config.bulk_export.enabled=true

40.8.12.4User Setup for testing

Using the Anonymous user (provided with Smile CDR base installation), include access to the capability statement (/metadata operation)

{
   "pid":4,
   "nodeId":"Master",
   "moduleId":"local_security",
   "username":"ANONYMOUS",
   "familyName":"Anonymous",
   "givenName":"Anonymous",
   "lastActive":"2022-09-07T02:15:44.208-04:00",
   "accountLocked":false,
   "systemUser":true,
   "authorities":[
      {
         "permission":"ROLE_ANONYMOUS"
      },
      {
         "permission":"ROLE_FHIR_CLIENT"
      },
      {
         "permission":"FHIR_CAPABILITIES"
      }
   ],
   "accountDisabled":false,
   "failedLoginAttempts":0,
   "external":false,
   "serviceAccount":false,
   "twoFactorAuthStatus":"NO_KEY_DEFINED"
}