Skip to main content

· 5 min read

Consider the solemn promise made in courtrooms worldwide: "Do you swear to tell the truth, the whole truth, and nothing but the truth?" This oath, while seemingly repetitive, meticulously encapsulates the essence of comprehensive honesty. It's a perfect springboard to explore the importance of precision in software development and how exceeding or not fully meeting requirements can lead to issues.

The Circle of Truth

Picture a circle representing "the truth." Anything within this boundary is true, while everything outside is not. This visual helps us understand the nuances of partial versus complete honesty.

Venn Diagram

When a witness commits to "tell the truth, and nothing but the truth," imagine they only share a segment of the entire circle (the truth), depicted as a smaller, contained circle. They're not lying, but they're omitting significant portions of the truth.

Venn Diagram

Conversely, if someone shares "the whole truth" but not exclusively, they might include extraneous, untrue details, or perhaps even lies. It's only by merging both commitments that we fully capture the essence of the truth, covering the entire circle without exceeding its bounds.

Venn Diagram Venn Diagram

Functionality, and Nothing But Functionality

Imagine you're tasked with developing a function to convert Celsius to Fahrenheit. A straightforward implementation might look like this:

// Example 1
function celsiusToFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}

This code directly fulfills the client's request. However, consider a different approach:

// Example 2
function celsiusToFahrenheit(celsius) {
if (celsius === 0) { return 32; }
if (celsius === 100) { return 212; }
throw new Error("Unable to convert");
}

This function technically converts Celsius to Fahrenheit but only for two specific cases. It's an example of providing "the functionality, and nothing but the functionality," yet it fails to deliver comprehensively, rendering the solution impractical.

Alternatively, you might come up with:

// Example 3
function celsiusToFahrenheit(celsius) {
Logger.log("Converting Celsius to Fahrenheit!");
Logger.log("Input value: " + celscius.toString());
Logger.log("Length of Celsius input: " + celscius.toString().length);

var fahrenheit = (celsius * 9 / 5) + 32;

Logger.log("Converted Celsius to Fahrenheit, now storing in DB");
dbClient.temperatures.push({ celsius, fahrenheit, date: new Date() })

return fahrenheit;
}

Although this function ultimately returns the correct value, it does a lot of unnecessary logging and seems to store values into a database. You have given your client "the whole functionality", but failed to give "nothing but the functionality". Although the function is usable, it introduces external dependencies, increases risk of failure, and uses additional resources than are necessary.

Stick to the Scope

Example 2 above is essentially flawed, providing incorrect outputs for most inputs, whereas Example 3, although functional, strays beyond the requested scope. This tendency to overdeliver can be tempting but deviates from the core principle of fulfilling precise requirements. Scenarios like Example 3 are frequently tolerated, and sometimes even supported! (After all, you're giving your client BONUS functionality!). But Example 3 is equally as invalid and incorrect as Example 2, just in different ways.

When writing code, we should be very careful to do what our client requires us to do. Nothing more, and nothing less. Don't introduce assumptions into the code without checking with the client first. It's possible (and common) for the initial requirements to be incorrect or incomplete. When this happens, it's important to make a note of this disparity to maintain a 1-to-1 correlation between expectations and implementation. This discipline ensures clarity, efficiency, and satisfaction in the deliverables.

Code Review and Quality Assurance

For developers, maintaining a strict correlation between requirements and implementation is key. For code reviewers and quality assurance professionals, vigilance against over-engineering and unintended functionalities is equally important. Together, these practices safeguard the integrity of the codebase and ensure that software not only meets expectations but does so with precision and reliability.

Code reviewers possess the unique opportunity to scrutinize the code from an internal perspective, identifying any deviations from the specified requirements. Their vigilance helps prevent the inclination towards over-engineering, ensuring that each feature remains purposeful and within the project's scope. By doing so, they contribute significantly to maintaining the integrity and manageability of the codebase.

Quality assurance professionals complement this process by testing the software from the user's viewpoint. Their focus extends beyond verifying that the software performs its intended functions; they also ensure that it does not exhibit behaviors or side effects outside of those functions. This dual focus on confirming expected behaviors and identifying unexpected ones is critical for delivering a product that not only functions correctly but also operates securely and efficiently.

Together, developers, code reviewers, and quality assurance professionals form a comprehensive frontline defense against scope creep, over-engineering, and unintended functionality. Their collaborative efforts ensure that software development projects achieve a delicate balance between fulfilling explicit requirements and maintaining simplicity and clarity. This balance is paramount, not just for meeting immediate project goals, but for ensuring the long-term sustainability and adaptability of the software product.

· 3 min read
Eric Morgan
  • Bulk Export is a way to download large amounts of data from a FHIR Server
  • There are different types of Bulk Export, depending on the kind of data you need
  • Not all EHRs implement Bulk Export the same way, but Plasma can help with the cross-platform compatibility

FHIR Bulk Export is a way to download massive amounts of data from a FHIR server that you can then use to initialize a database, perform an analysis, or train an AI model. In this article, we will give further details about Bulk Export, and demonstrate how it can be achieved.

Specifications

You can read more about the FHIR Bulk Export specification here: https://build.fhir.org/ig/HL7/bulk-data/export.html

There are three types of Bulk Export:

  1. Patient - Exports all data for a particular patient
  2. Group - Exports all data for a group of patients
  3. System - Exports all data in the entire system

Bulk Exports are asynchronous operations that could take multiple seconds, minutes, or even hours to complete. Once a Bulk Export is initialized, you can check the status of the Bulk Export periodically via a URL you will receive after kicking off the Bulk Export.

The status of the Bulk Export will usually provide a percentage complete as well as a number of seconds that you should wait before checking the progress again. It is a good idea to abide by the delay period, or else you may get blocked from checking on the progress.

Once the Bulk Export has completed, you will receive a list of files in ndjson format containining all of the data, which you can then download.

Demonstration

I have built a demo app to demonstrate the bulk export feature here: https://bulk-export.smart-on-fhir.com.

Plasma provides a convenient interface through which you can work with Bulk Exports. When calling the Bulk Export function via the Plasma SDK, you should provide callback functions that will be called during certain events throughout the lifecycle of the Bulk Export

  • onKickoff - Will be called once when the Bulk Export is initialized
  • onStatusChecked - Will be called each time the status is checked. Plasma will adhere to the recommended delay time given by the FHIR server
  • onComplete - Will be called once when the Bulk Export is completed
  • onError - Will be called once, if there is an error with the Bulk Export

Here is a code sample of how this would look, if you are just printing out information to the screen:

const onKickoff = async (kickoffResponse: IBulkExportTypes.IStartBulkRequestResponse) => {
console.log(`Bulk export has started. Status is available at: ${kickoffResponse.contentLocation}`);
};

const onStatusChecked = async (status: IBulkExportTypes.IGetBulkRequestStatusResponse) => {
if (status.bulkStatus === IBulkExportTypes.BulkExportStatus.Completed) { return; }
console.log(`Bulk export status is ${FHIRBulkUtils.getBulkExportStatusDisplay(status.bulkStatus)}`);
console.log(`Progress message: ${status.xProgress}`);
console.log(`Retry-After: ${status.retryAfter}`);
};

const onComplete = async (status: IBulkExportTypes.IGetBulkRequestStatusResponse) => {
console.log(`Bulk export is complete!`);)
};

const onError = (err: any) => {
console.log("Error on Bulk Export: ", err);
};

await plasma.doBulkGroupExport(
groupId, exportParams,
onKickoff, onStatusChecked, onComplete, onError
);

Please be aware that not all EHRs implement Bulk Export the same way. Using tools such as Plasma can help to achieve cross-platform compatibility with little effort.

Conclusion

Bulk Export can be a great asset if you have a need to download large amounts of data at once, as mentioned in the use-cases listed above. Plasma can help you accomplish this very easily.

For a video demonstration of this concept, please check it out here: https://www.youtube.com/watch?v=E8FTsiCEQhY

· 3 min read
Eric Morgan
  • SMART-on-FHIR scopes can be specified in different formats
  • The format depends on the EHR and FHIR Server being connected to
  • Plasma can format scopes in whichever way is necessary

When building a SMART-on-FHIR application, requesting scopes is yet another small, but crucial detail that may differ from EHR-to-EHR. When launching your app, you must pass a string representing the scopes that your app is requesting and it must be formatted precisely based on the EHR or FHIR Server you are connecting to.

Usually, when registering an app with an EHR, you are asked which scopes your app will be requesting. Here are a few examples of how some of the larger EHRs ask you which scopes your app is requesting

Epic Cerner Athena

However, after registering your apps, you must make sure to request those scopes using the proper format for that EHR.

Scope Format

The format of FHIR scopes can be found on the website.

There are three basic patterns for scopes.

  1. Resource Scopes Resource scopes have the pattern context/resource.actions. For example, patient/Observation.rs means an app can read and search for all observations about a patient.
  2. Launch Scopes Launch scopes have the pattern launch/context and indicate that the app will need some piece of information before launching. For example, launch/patient indicates that an app requires a patient context.
  3. Other Scopes Other scopes are simply keywords that specify some information or functionality that is needed. For example, fhirUser specifies that the current user should be available as a FHIR resource.

Scope Versions

There are also two types of versions for specifying scopes, called v1 and v2. v2 is the current version, but sometimes v1 scopes still need to be supported. You can read about the differences between v1 and v2 scopes at the link above.

When launching your app, you must be sure to pass scopes the way that the server you are connecting to expects them to be formatted.

Plasma Solution

With Plasma, we have defined a discrete way to represent scopes and we provide parsers and formatters that can generate the scope string. The formatters are configurable, and as always, we provide plugins that generate the correct format for all of the major EHRs.

Scope Parser/Formatter

Our scope module works like the code shown below. Here, we are parsing a string of scopes in the v2 format and then converting them back to a string in the v1 format.

// Parse the string into discrete scopes...
const fhirScopes = FHIRScopes.fromString("patient/Patient.rs launch launch/patient fhirUser");

// Define a formatter for how you want to format your scopes...
const formatter = new FHIRScopeFormatter({ version: "v1", expandResourceScopes: false });
// Or: const formatter = FHIRScopeFormatter.forEpic();
// Or: const formatter = FHIRScopeFormatter.forCerner();

// Format the discrete scopes as a string in the intended format...
const sScope = formatter.formatScopes(fhirScopes);

Scope Builder

We also provide a UI tool that lets you build a scope string and set whichever format you need.

v2 v1

Scope Management

In addition to formatting scopes properly, some EHRs require certain custom scopes to always be included in every launch.

Use Plasma to manage your SMART-on-FHIR applications to ensure that your app is always conforming to the latest requirements of your target platforms.

· 2 min read
Eric Morgan
  • The Plasma platform makes integrating with EHRs simple and easy
  • Applications can connect to EHRs with no changes to code
  • Our tooling can expedite the build and development process

What Is Plasma?

Plasma is a platform and set of tools that make integrating with an EHR as easy and seamless as possible.

Not in the Health IT industry

Plasma Platform

Plasma SDK

The Plasma SDK is a collection of tools and libraries to help facilitate the building of an integrated Health IT application

Formatters

FHIR resources can be cumbersome to work with, especially when there are multiple properties that can potentially hold the data we need or when there are multiple nested layers of properties, all of which can be null. Our API library can handle this, grabbing the data in a presentable format for you and keeping your codebase nice and clean.

UI Components

UI Component

We provide a library of fully cuztomizable UI components that bind directly to FHIR resources and can greatly improve the efficiency of building a SMART-on-FHIR application.

Rather than going through the tediousness of building components to display a FHIR resource, just use our components and style them however you'd like.

Unit Conversion

Often, data will be returned to applications in units of measurement that are not desirable (for example, we may not want to show the patient their height in centimeters). Our tools can quickly and conveniently convert units of measurement to whichever unit is needed.

De-Duplication and Merge

Merge

When data is pulled from multiple health systems, there are likely to be duplicates that are just slightly different.

Use our tools to identify these duplicates and merge them together to ensure data is neither lost nor duplicated.

· 5 min read
Eric Morgan
  • SSO with SMART-on-FHIR can prevent your users from logging in twice: Once for the EHR, and once for your application
  • This article discusses strategies for implementing this into your application
  • Plasma provides a mechanism to make this process quick and easy

Multiple Logins

Many people building SMART-on-FHIR applications have an existing app with an existing user management system. In these applications, users (typically clinicians) must first login in order to use the app.

When converting an app like this to SMART-on-FHIR, the user will also be required to login to the EHR in order to have access to the EHR's clinical data. Requiring users to login twice to use your app can be a source of frustration and increase the number of clicks it takes for your users to accomplish their goals.

The ideal solution is to have your users login a single time, through the EHR, in order to both use your application and interact with the EHR.

SMART-on-FHIR Standards

SMART-on-FHIR provides a way to receive an id_token which can be used to gather identity data about the currently logged in user. This data can then be incorporated into your own user management system to link existing accounts, or to initialize new user accounts.

This will work for both a Clinician-facing and a Patient-facing application. The user information will come back in the form of a FHIR resource, whichever is appropriate to the situation (a Patient resource or a Practitioner resource).

You can read more about the SMART-on-FHIR standards at the following URL: https://build.fhir.org/ig/HL7/smart-app-launch/scopes-and-launch-context.html

Case Study 1: Brand New App

In this case study, you are still building an application and you do not have any existing users. This is the simplest case because you will not have any legacy data to contend with.

One option you have in this case is to use the existing user management system of the EHR and just copy and customize the data necessary for your application.

We recommend placing a login button for whichever EHRs your application needs to support and direct the user to the authentication page for that specific EHR. The image below shows a sample implementation for an app that can support multiple EHRs. You may choose to provide individual buttons for quick access to the relevant EHR, or provide a drop-down list for the user to choose from.

SSO Demo

Once the user has logged in to their EHR, you can retrieve the identity information and populate your user management system accordingly. The user is now authenticated and can be granted access to your application's features.

Case Study 2: Existing App

In this case study, suppose you have an existing clinician-facing application with many users who are accustomed to logging into your app using their email and password.

We want to link each user's account to their EHR account and have them login via the EHR rather than with their email and password.

We recommend having the user keep their existing login and then "linking" their account to the EHR account. You can provide a "Link Acocunt" button that will direct the user to the EHR's login page. Once the user has logged in, you can retrieve identity information and store that linked information into your user management system.

Depending on your implementation, you may want to save refresh tokens so that you can refresh the user's access periodically and avoid frequent logins.

Case Study 3: Multiple EHR Accounts

In this case study, suppose you have a patient-facing app and suppose that some of your users (patients) have accounts registered with multiple EHRs. This can be quite common if the patient receives care from two different health systems in their community. Another example may be that the patient has changed insurance providers and was required to change health providers.

In this situation, we recommend maintaining a one-to-many relationship between your users and their health system connections. You can still retrieve identity information for each health system connection and you can decide in what capacity you should use it. We recommend using the patient's current health system data when pulling any kind of demongraphic identity data.

SSO with Plasma

With Plasma, we provide a seamless integration to an EHR's SMART-on-FHIR authentication service. Our platform can get your application and users connected to an EHR with the push of a button.

We provide an endpoint for you for every EHR system that you need to support. You can direct your users to this endpoint to have them login and connect to the EHR.

Once your users have connected to the EHR, we provide a convenient endpoint that will automatically retrieve the logged-in user's information. This can be done in a single step by calling the .../plamsa/sof/whoami?readFhirUser=1 endpoint.

A sample response from this API can be seen below.

SSO Data

Demonstration

To demonstrate this functionality, we have built a very basic app using the Plasma Platform. This app connects to both the Epic and the SMART Health IT sandbox environments. Feel free to try it out here: https://sso.smart-on-fhir.com/

Get Started

Check out https://medblocks.org/plasma to get started today!

· One min read
Eric Morgan

When working with a new FHIR server, there are certain characteristics about the server that may be helpful to know.

For example:

  • What operations are allowed on each resource?
  • What search options are available for each resource?
  • What extensions are defined on this server?

Metadata

This data is all available in the FHIR server's /metadata endpoint.

For example, take a look at the SMART Health IT server's /metadata: https://launch.smarthealthit.org/v/r4/fhir/metadata

Let's examine some of the properties of the AllergyIntolerance resource. To do this, search for "type": "AllergyIntolerance".

We can see that it allows for several searchParams, including date, severity, and code.

And it defines one extension: http://hl7api.sourceforge.net/hapi-fhir/res/extdefs.html#resourceCount

Plasma Dev Portal

The Plasma Dev Portal makes it extremely easy to access this information. We provide an intuitive UI for easily visualizing the details of a given FHIR server.

Just navigate to the [https://toolkit.plasmahealth.net/toolkit/fhir-server-explorer][FHIR Server Explorer] and paste the FHIR server that you're interested in. Data about that server will be gathered and presented to you.

FHIR Server Explorer

· 2 min read
Eric Morgan

For Patient-Facing apps, users (patients) are first asked for consent before the application is given access to their personal health data. How should your app handle the case when a user declines permission to a particular resource?

Fortunately, this is a common pattern that we have all encountered at one point on our smart phones.

Permission to Use Camera}

And thus, we can duplicate the patterns that these apps use to handle cases when a user declines access.

Always Have a Fallback

Never assume that every user will grant your app access to the resources you are requesting. Your app must provide a fallback in the event that consent is not granted.

If this resource is crucial to the functioning of the application, then it is acceptable to leave a warning message telling the user that they cannot proceed if they do not provide access to that resource.

For example, an app that displays a patient's allergies and associated information, must have access to a patient's AllergyIntolerance or else the app will not be able to perform its designated function.

On the other hand, if the resource is only supplementary to the application, but the application can still function without it, consider just hiding that feature and carrying on.

Testing is a Must

The most important thing to do is to make sure you test this case. Don't always test in the "best case" scenario. Make sure your testing also involves checking what happens when a user denies permission to certain resources.

· 6 min read
Eric Morgan

When working with FHIR data from within an application, we must decide on how to write updates back to the FHIR server. Generally, there are two strategies for doing this:

  1. Inline Updates: Everytime a change is made, immediately save that change back to the server
  2. Bulk Updates: Track changes and save them all in bulk based on a certain event, such as a user pressing Save, or the application being closed

Inline Updates

Inline updates mean that any time a change is made, that change is immediately saved back to the server. In this case, the application is nearly always synchronized with the server and limits some risks, such as the application crashing and all changes being lost, or the data becoming stale.

This method also tends to be simpler as it doesn't require any additional code for tracking changes.

Example Code (with Plasma)

Take, for example, an application that allows users to manipulate a patient's allergies. Any time an action is performed (user adds a new allergy, user changes something about an allergy, etc.), the new data will be saved back to the FHIR server. Below is some sample Plasma code that may work for this case.

async function refreshData() {
this.data = await this.plasma.readAllergyIntolerance(patientId);
}

async function createAllergy(allergy: r4.AllergyIntolerance) {
await this.plasma.createResource<r4.AllergyIntolerance>(allergy);
await this.refreshData();
}

async function deleteAllergy(allergy: r4.AllergyIntolerance) {
await this.plasma.deleteResource(allergy);
await this.refreshData();
}

async function updateAllergy(allergy: r4.AllergyIntolerance) {
await this.plasma.updateResource(allergy);
await this.refreshData();
}

As you can see, the code is very simple. We have a function, refreshData that loads data directly from the FHIR server and assigns it to this.data. Any time any CRUD operations are performed, we simply call the appropriate function and then refresh the data back from the server.

Pros/Cons

Pros:

  • Simple to implement
  • Protect against unexpected crashes

Cons:

  • User workflows are slower due to waiting for loading
  • Server updates may not appear immediately

The second item in the Cons list, server updates may not appear immediately, is a major problem with this approach. Very often, you may submit a change to a FHIR server, and that change may not appear for several seconds or several minutes.

So, for example, in the code above, we may create an allergy and then call refreshData() to overwrite this.data with whatever is on the server. However, that new allergy may not appear on the first call to refreshData(). This means that the allergy that was just created won't appear to the user and will result in a confusing user experience. Furthermore, it's not clear when the new allergy actually will show up, meaning our application will not know when it should call refreshData().

For this reason alone, we recommend using the Bulk Updates method for writing back FHIR data.

Bulk Updates

Bulk updates mean that changes are tracked locally within the application and then saved all at once after a triggering event. That event could be when the user presses a "Save" button. Or it could be right as the user closes the application.

This method results in a more responsive user experience compared with the Inline approach since there will be no network calls in between actions. However, this approach is more complex as it requires keeping track of some type of changes data structure.

FHIR provides a method to execute numerous actions as a transaction, which is great for this use-case.

One additional complexity with this approach is that there is a greater likelihood that the underlying FHIR data has been modified by another user and there may be data conflicts when attempting to save.

Example Code (with Plasma)

Take again, for example, an application that allows users to manipulate a patient's allergies. In this case, we will track changes made to the data throughout the course of the application, and then save those changes in bulk when the save() function is called. Below is some sample Plasma code that may work for this case.

async function init() {
this.data = await this.plasma.readAllergyIntolerance(patientId);
this.deletedIds: string[] = [];
this.modifiedIds: string[] = [];
this.createdAllergies: r4.AllergyIntolerance[] = [];
}

function createAllergy(allergy: r4.AllergyIntolerance) {
this.createdAllergies.push(allergy);
}

function deleteAllergy(allergy: r4.AllergyIntolerance) {
this.deletedIds.push(allergy.id);
}

function updateAllergy(allergy: r4.AllergyIntolerance) {
this.modifiedIds.push(allergy.id);
}

async function save() {
let bundle = plasma.startBundle();

// Add all newly created allergies...
this.createdAllergies.forEach((allergy) => {
bundle = plasma.addWriteToBundle(bundle, "create", allergy);
});

// Add all deleted allergies...
this.deletedIds.forEach((id) => {
const allergy = this.data.find(x => x.id === id);
bundle = plasma.addWriteToBundle(bundle, "delete", allergy);
});

// Add all modified allergies...
this.modifiedIds.forEach((id) => {
const allergy = this.data.find(x => x.id === id);
bundle = plasma.addWriteToBundle(bundle, "update", allergy);
});

await plasma.executeBundle(bundle);
}

Notes:

  • We track created, updated, and deleted records separately since they have different associated actions for each
  • For updated and deleted records, we only need to track the ID since the IDs are unique. Newly created records do not yet have IDs, so we track the entire object itself
  • We use a Bundle to perform all the actions at once as a transaction

Dealing with Conflicts

If data has been modified by another user in between the time that your application loaded data from the server and the time that your application's user wishes to save the data, you must account for any possible conflicting data.

Similar to how source code is merged, there may be some cases in which conflicts can be resolved programmatically. However, there may be some conflicts which must be resolved by a human being.

This can be a complicated process, but the Plasma Health framework has some guidelines and tools to assist. This will be the subject of a future blog post.

Conclusion

  • To provide the best user experience, we recommend tracking all changes and submitting them at once when ready to save
  • We recommend using a Bundle to submit all changes as an atomic transaction
  • Be cautious about data that may have changed as attempting to save data could result in a data conflict

· 2 min read
Eric Morgan

If every EHR implements their own unique standard, or, even their own unique flavor of a common standard (such as FHIR), it makes intuitive sense to invent a universal standard to consolidate them all, right? Well, there's an old joke about that.

Standards

Should I Create a New Data Model?

You may be tempted to create a new data model or to consider services that create one for you. A universal data model simplifies things. It allows you to use the same code for every platform or situation. It certainly has its benefits, but before investing in it, you should also consider the costs.

When using a service that creates a universal data model, you are locking your app into that company's data model. You're trusting that they will continue to support it, that it will be stable, and that it will have longevity. It will be difficult to eject your app should you decide to take another direction, because your entire application will be dependent on this data model.

Additionally, services that consolidate different data models into one are adding an additional layer between your app and the data. This additional layer adds potential risks and may have some performance impact.

What Should I Do?

If your application has an existing data model, you should map to that. It's going to be extra work for the developers, but with good coding practices, it doesn't have to be painful. Map everything to your data structure and then you own the data. You are free to modify it as you wish. You are not trapped beneath another service's decisions.

FHIR Standard

At Plasma Health, we stick to the FHIR standards and supply a utility framework to make working with it easier. Is FHIR a perfect data model? No. Does FHIR exist? Yes.

We strive to make interacting with EHRs as easy as possible and we want your app to be able to develop and innovate quickly. Our framework facilitates that, but at the end of the day, it's still using FHIR. Support for FHIR across EHRs is growing rapidly and Plasma Health can help bridge the gap between an EHR's FHIR service and your application. And if you ever decide to eject from the Plasma Framework, your app was still written to the FHIR specification rather than a proprietary data model.