SMART on FHIR® for Eye Health Research Workshop

National Eye Institute, Office of Data Science and Health Informatics

May 30, 2024


FHIR® is the registered trademark of Health Level Seven International (HL7). Use of the FHIR trademark does not constitute an HL7 endorsement of this workshop.

© 2024 The MITRE Corporation / Approved for Public Release / Case #24-0169

Workshop Overview

  • Today’s use case: creating a novel clinical decision support (CDS) tool for ophthalmology that integrates into an electronic health record (EHR) workflow

  • We will identify and work hands-on with the parts of the FHIR ecosystem that support this use case

  • Workshop content is organized from least-to-most technical

    • No prior FHIR experience or other technical skills* are needed

    • *Except for at the very end of the workshop: we will be working hands-on with Python

Logistics

Agenda

Part 1: Overview of…

  1. FHIR
  2. SMART on FHIR
  3. Example clinical decision support system architecture

Part 2: Deep dive into…

  1. Example clinical decision support system
  2. SMART on FHIR log-in and permissions (authentication and authorization) workflow
  3. Reading the FHIR specification and FHIR IGs
  4. Synthetic data

Part 3: Hands-on with…

  1. FHIR API
  2. FHIRPath
  3. Python code to access FHIR data

1.1 FHIR Overview

What is FHIR?

FHIR stands for:

  • Fast (to design & implement)
  • Healthcare
  • Interoperability
  • Resources (aka building blocks)

FHIR® is a standard for exchanging health information electronically.

Standards establish a common language and process for all health information technology (IT) systems to communicate, allowing information to be shared seamlessly and efficiently.

Real world examples of FHIR

What characteristics of FHIR support its broad implementation?

  • Free: It is an open, free-to-use standard.
  • Modular: “Resources” are used to build solutions from compatible components.
  • Customizable: Developers and implementers can use extensions and profiling to meet specific needs.

What do you get with FHIR?

FHIR system architecture

Diagram of typical FHIR system architecture.

FHIR’s RESTful API

FHIR’s RESTful API, continued

FHIR’s RESTful API, continued

  • Why is the FHIR API important for our ophthalmology CDS use case?
    The same interfaces work across “all” FHIR servers, allowing for software portability.

1.2 SMART on FHIR Overview

What is SMART on FHIR?

The goal of the original SMART on FHIR API is audacious and can be expressed concisely: an innovative app developer can write an app once and expect that it will run anywhere in the health care system.

SMART provides a full stack of open specifications that enable a medical apps platform.

https://smarthealthit.org/smart-on-fhir-api/

What is SMART on FHIR?

How can SMART on FHIR help research?

SMART on FHIR lets you:

  • Integrate with an EHR (e.g., add an AI-driven CDS app)
  • Add patient-generated data to an EHR workflow
  • Create an app that can be used across institutions and EHR products (“write once, use everywhere”)
  • Access Bulk Data

SMART on FHIR standards

It ties together existing common web standards and HL7 specifications to enable secure EHR integration:

  • OAuth2 for authorizing a third-party app (“permissions”)
  • OpenID Connect for authenticating a patient or provider (“logging in”)
  • HL7 FHIR for data modeling and API
  • JSON for the data format
  • HL7.FHIR.UV.SMART-APP-LAUNCH standard for launching from EHR
  • HL7 CDS Hooks for triggering based on EHR actions (see next slide)

Aside: CDS Hooks

  • CDS Hooks is an HL7 standard that can support SMART on FHIR application integration with EHRs
  • They allow an action in an EHR to trigger an action in a third-party application
  • For example, a patient-view hook is triggered when the patient record is opened, which could then call natural language processing software

Technical considerations

SMART apps have multiple authorization patterns

  • SMART App Launch
    • EHR Launch: user launches an application from within an EHR (ex: a CDS app)
    • Standalone Launch: user launches the application directly (ex: iPhone Health app)
  • SMART Backend Service: support applications that run autonomously (ex: data pipeline)

Technical considerations, continued

Security

  • Use reputable open-source software libraries to save development time and avoid common security pitfalls. SMART Health IT lists SMART-on-FHIR software libraries.

Privacy

  • FHIR servers will likely return sensitive healthcare data. PHI rules will likely apply. You must also comply with your institution’s IRB and privacy rules.

Technical considerations, continued

Design

1.3 Example CDS System

Example CDS system architecture

Diagram of example CDS system architecture used for this workshop.

Demo

➡️ End of Part 1 / Break


2.1 Example CDS System Deep Dive

Example CDS system architecture

Diagram of example CDS system architecture used for this workshop.

Our version of this defined by compose.yaml, and that GitHub repository also contains code for the example front-end UI, CDS back-end server, and PACS mock-up.

2.2 SMART on FHIR Deep Dive

SMART App Launch workflow

SMART on FHIR example sequence diagram

Sequence diagram of SMART on FHIR log-in/permissions workflow as implemented in the example for this workshop.

Example CDS structure

example_ui/
     |
     +--- launch.html
     |
     +--- index.html

Available on GitHub: https://github.com/NIH-NEI/fhir-for-research-smart-example/tree/main/example_ui

SMART on FHIR walkthrough

Step 1. EHR Launch Simulator triggers a SMART App Launch

Your browser (a client) gets redirected by the EHR to your app’s http://localhost:3000/launch.html with the following parameters:

  • iss: Identifies the EHR’s endpoint for the app
  • launch: An opaque identifier for this specific app launch and EHR context, required for security purposes
    • JavaScript library automatically passes this back to EHR with authorization request (Step 2)

SMART on FHIR walkthrough

Step 2. The example app’s launch.html executes an authorization request with select parameters

  • We are using the SMART on FHIR JavaScript Library from SMART Health IT
  • This library handles the OAuth2 workflow and making authenticated requests to the FHIR server

SMART on FHIR walkthrough

Step 2. The example app’s launch.html executes an authorization request with select parameters

<script>
    FHIR.oauth2.authorize({

      // The client_id that you should have obtained after registering a client at
      // the EHR.
      //
      // Note that this can be an arbitrary string when testing with
      // http://launch.smarthealthit.org.
      clientId: "my_web_app",

      // The scopes that you request from the EHR. In this case we want to:
      // launch            - Get the launch context
      // openid & fhirUser - Get the current user
      // patient/*.read    - Read patient data
      scope: "launch openid fhirUser patient/*.read",

      // Typically, if your redirectUri points to the root of the current directory
      // (where the launchUri is), you can omit this option because the default value is
      // ".". However, some servers do not support directory indexes so "." and "./"
      // will not automatically map to the "index.html" file in that directory.
      redirectUri: "index.html"
    });
</script>

SMART on FHIR walkthrough

The clientId parameter is a specific string obtained after registering the app in the EHR manually. You would replace "my_web_app" with a specific app identifier for a production implementation.

<script>
    FHIR.oauth2.authorize({

      // The client_id that you should have obtained after registering a client at
      // the EHR.
      //
      // Note that this can be an arbitrary string when testing with
      // http://launch.smarthealthit.org.
      clientId: "my_web_app",

      // The scopes that you request from the EHR. In this case we want to:
      // launch            - Get the launch context
      // openid & fhirUser - Get the current user
      // patient/*.read    - Read patient data
      scope: "launch openid fhirUser patient/*.read",

      // Typically, if your redirectUri points to the root of the current directory
      // (where the launchUri is), you can omit this option because the default value is
      // ".". However, some servers do not support directory indexes so "." and "./"
      // will not automatically map to the "index.html" file in that directory.
      redirectUri: "index.html"
    });
</script>

SMART on FHIR walkthrough

The scope parameter specifies what kinds of data the app needs access to. See SMART on FHIR scope and lunch context for more data access options.

<script>
    FHIR.oauth2.authorize({

      // The client_id that you should have obtained after registering a client at
      // the EHR.
      //
      // Note that this can be an arbitrary string when testing with
      // http://launch.smarthealthit.org.
      clientId: "my_web_app",

      // The scopes that you request from the EHR. In this case we want to:
      // launch            - Get the launch context
      // openid & fhirUser - Get the current user
      // patient/*.read    - Read patient data
      scope: "launch openid fhirUser patient/*.read",

      // Typically, if your redirectUri points to the root of the current directory
      // (where the launchUri is), you can omit this option because the default value is
      // ".". However, some servers do not support directory indexes so "." and "./"
      // will not automatically map to the "index.html" file in that directory.
      redirectUri: "index.html"
    });
</script>

SMART on FHIR walkthrough

redirectUri is where the EHR will redirect the web browser (client) to after authorization. In this case it is the app’s index.html.

<script>
    FHIR.oauth2.authorize({

      // The client_id that you should have obtained after registering a client at
      // the EHR.
      //
      // Note that this can be an arbitrary string when testing with
      // http://launch.smarthealthit.org.
      clientId: "my_web_app",

      // The scopes that you request from the EHR. In this case we want to:
      // launch            - Get the launch context
      // openid & fhirUser - Get the current user
      // patient/*.read    - Read patient data
      scope: "launch openid fhirUser patient/*.read",

      // Typically, if your redirectUri points to the root of the current directory
      // (where the launchUri is), you can omit this option because the default value is
      // ".". However, some servers do not support directory indexes so "." and "./"
      // will not automatically map to the "index.html" file in that directory.
      redirectUri: "index.html"
    });
</script>

SMART on FHIR walkthrough

Step 3. The EHR securely authorizes (or rejects) your log-in request.


The EHR launch simulator has us select an encounter, which may be skipped in a production implementation.

SMART on FHIR walkthrough

Step 4. Your web browser gets redirected to the app’s index.html

As specified earlier in the redirectUri parameter.

SMART on FHIR walkthrough

Step 5. The app obtains an access token via FHIR.oauth2.ready()

This access token gets embedded in a client object to log in (authenticate) and use the established permissions (authorization) future FHIR queries.

<script type="text/javascript">
    ...
    var client = await FHIR.oauth2.ready();
    var patient = await client.patient.read();
    ...
</script>

The demo, once more:

2.3 Reading the FHIR Specification & IGs

The FHIR Specification

FHIR Implementation Guides (IGs)

2.4 Synthetic data

Ophthalmology Synthetic data

➡️ End of Part 2 / Break


5 minute break

+ 10 minutes of setup support

https://github.com/NIH-NEI/fhir-for-research-workshop/




3.1 The FHIR API

FHIR API basics

  • Generally speaking the pattern for a RESTful GET query appended to a URL will take the form of:

    VERB [base]/[Resource] {?param=[value]}

  • Spec: https://hl7.org/fhir/http.html

Aside: utility of open endpoint + synthetic data

FHIR API - getting more data

FHIR API - getting more data

FHIR API - chaining

  • MedicationRequest.subject has a reference back to Patient, allowing us to retrieve instances if we know the patient’s ID
  • What if you only know the patient’s last name?
    • We could do two queries: one to get the ID with GET [base]/Patient?name=peter, and then a second to get the MedicationRequests for patients with that ID
    • The FHIR API supports just one query: GET [base]/MedicationRequest?subject.name=peter
    • Note that MedicationRequest.subject can be either a Patient or Group, so this is better: GET [base]/MedicationRequest?subject:Patient.name=peter

FHIR API - reverse chaining

What about “patients diagnosed with a given condition”?

  • The Condition resource references a Patient (or Group) in Condition.subject
  • The _has parameter supports retrieving Patients based on a value from a Condition
    • : separates fields
    • Sub-parameters:
      • The resource type to search for references back from (Condition)
      • The field on that resource which would link back to the current resource (subject)
      • A field on that resource to filter by (code, which Condition uses to identify the condition)
  • Example: GET [base]/Patient?_has:Condition:subject:code=195662009

FHIR API - chaining documentation

https://hl7.org/fhir/search.html#chaining

FHIR API - searching multiple values

  • Logical AND to find john smith: GET [base]/Patient?given=john&family=smith
  • Logical OR to find john smith or jenny smith: GET [base]/Patient?given=john,jenny&family=smith
  • Lots more in the spec: https://hl7.org/fhir/search.html#combining

3.2 FHIRPath

FHIRPath

FHIRPath examples

Try in the sandbox: https://hl7.github.io/fhirpath.js/

  • Get the value from Patient.gender: Patient.gender

  • Get a patient’s legal last name: Patient.name.where(use='official').family

  • Get a patient’s MRN:

    Patient.identifier.where(type.coding.system = 'http://hl7.org/fhir/v2/0203' and type.coding.code = 'MR').value

3.3 Hands-on with Python

https://github.com/NIH-NEI/fhir-for-research-workshop/

Wrap-up