SMART on FHIR: Part 1

With the 7th connectathon coming up, we’ve looked at the first scenario (Patient access) in a couple of posts and how we can use a couple of the libraries (.net and java) to make this almost trivial to achieve. (btw you don’t have to use these libraries of course – FHIR by itself uses standard technologies so there are a ton of different ways to do this if you already have the technology to do so, or use a different language).

In this post we’re going to take a look at the 3rd connectathon scenario – SMART on FHIR. There’s a lot of information about what this is trying to achieve (the connectathon site has links) so we won’t repeat that here – the ‘elevator pitch’ is that it establishes standards that enable the development of independent applications that can securely access data in any server supporting those standards – this could be an EHR, EMR, Portal or HIE.

We’ll start by implementing a basic server, and substantially follow the path described in the quickstart guide, so you might want to review that as well if you are going to do the same. In particular we’ll first create an unprotected server (just to get all the bits working) and then add security to it.

So the scenario is that we are an EMR system, and want to allow our users to be able to use the cool growth chart application hosted by the SMART team. (There’s a whole separate discussion on building and deploying SMART applications that we’ll talk about at some future date).

What will happen is that on our EMR interface (which is web based  – though could also be a desktop client) we’ll add a button that says “View Growth Charts”. When the user clicks on that:

  1. We’ll add an iFrame to the page, and point it at the launch page for the Growth Chart application. The URL we use will contain parameters for the patient ID, and also the location where we expose the data needed by the application (the FHIR endpoints for Patient and Observation).
  2. The launch page will load in our iFrame, and then make a couple of ajax calls to our FHIR endpoints to retrieve the data it needs. (This is the part we need to secure of course).
  3. Using that data, it can then perform whatever functionality it needs – which could, potentially, involve writing data back to our server if we support that.

So for this post we will need to build:

  • An HTML page that emulates the EMR (with the ‘Launch Button’)
  • A FHIR Patient endpoint that returns demographics about the patient
  • A FHIR Observation endpoint that returns the observations (height, weight & BMI).

The HTML page is easy – here’s the relevant HTML code (using jquery with some hard coded values).


  $(document).ready(function(){
        //the url to launch the app (hardcoded patientID, and local server)
        var url = "https://fhir.smartplatforms.org/apps/growth-chart/launch.html?;
        url += "fhirServiceUrl=http://localhost:8080/fhir";
        url += "&patientId=100&";
        //var iframe = ""; // this line should contain the html for an iframe, if wordpress would allow it...
        //the jQuery handler to create an iFrame and launch the app in it
        $("#launchApp").on('click',function(){
          //$('#launchFrameDiv').append(iframe);
          $("#launchIframe").attr("src",url);
        })
  });

(In case you’re wondering why the code above isn’t dynamically inserting an iframe as I said it would, it’s because my stupid blogging platform keeps on removing the line every time I save the page! So, for this to work you’ll either need to have an iframe in the page with an ID of “launchIframe”, or insert it dynamically. grrrr….)

The server is going to take a bit more work. We’ll use the HAPI library (which provides a lot of the plumbing for exposing a FHIR server) and deploy into a Tomcat servlet container. We’ll also use the IntelliJ IDEA IDE. Installing this stuff takes a little while, but there are a ton of tutorials on the net – took me about an hour to set it all up. Incidentally, this is why I bought the full IDEA IDE rather than the free community version as it has good integration with tomcat – I can build and deploy into tomcat with a single click.

To build a server in HAPI, you define a resource provider for each resource you want to expose, and then surface them in a server class (There’s lots more you can do of course – it’s all documented).

So here’s our Patient Provider:

public class PatientResourceProvider implements IResourceProvider {
    //return a single Patient by ID
    @Read()
    public Patient getResourceById(@IdParam IdDt theId) {
        Patient patient = new Patient();
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.YEAR, -18);         //18 years old
        patient.setBirthDate(new DateTimeDt(cal.getTime()));
        patient.addName().addFamily("Power");
        patient.getName().get(0).addGiven("Cold");
        patient.setGender(AdministrativeGenderCodesEnum.M);
        return patient;
    }
}

As you can see, all the returned data is hard coded, but it’s easy to see where the real lookup into back end data sources would go.

And here’s the Observation Provider:

public class ObservationResourceProvider implements IResourceProvider {
    @Search()
    public List<Observation> getObservationBySubject(@RequiredParam(name = Observation.SP_SUBJECT) StringDt theSubject, @RequiredParam(name = Observation.SP_NAME) TokenOrListParam theObsNames) {
        List<Observation> lstObservations = new ArrayList<Observation>();

        //emulates calling an existing non-FHIR REST service and getting a JSON object back...
        //we'd probably pass across the theObsNames to only return the list that we want
        InputStream is = null;
        try {
            URL url = new URL("http://localhost:4001/dataplatform/obs/"+theSubject);
            URLConnection conn = url.openConnection();
            conn.connect();
            is = conn.getInputStream();
            JsonReader rdr = Json.createReader(is);
            JsonObject obj = rdr.readObject();
            //assume that the json structure is {data[{id:,unit:,code:,value:,date: }]}

            JsonArray results = obj.getJsonArray("data");
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
            for (JsonObject result : results.getValuesAs(JsonObject.class)) {
                //get the basic data for the Observation
                String id = result.getJsonString("id").getString();
                String unit = result.getJsonString("unit").getString();
                String code = result.getJsonString("code").getString();
                String display = result.getJsonString("display").getString();
                double value =  result.getJsonNumber("value").doubleValue();
                Date date = format.parse(result.getJsonString("date").getString());

                //create an Observation resource
                Observation obs = new Observation();
                obs.setId(id);
                obs.setApplies(new DateTimeDt(date));
                obs.setName(new CodeableConceptDt("http://loinc.org", code));
                QuantityDt quantityDt = new QuantityDt(value).setUnits(unit).setSystem("http://unitsofmeasure.org").setCode(unit);
                obs.setValue(quantityDt);
                obs.setStatus(ObservationStatusEnum.FINAL);
                obs.setReliability(ObservationReliabilityEnum.OK);

                //the text - should include proper text here...
                obs.getText().setDiv(result.getJsonString("");
                obs.getText().setStatus(NarrativeStatusEnum.GENERATED);

                //and add to the list...
                lstObservations.add(obs);
            }
        } catch (Exception ex) {
            System.out.println("Error during REST call " + ex.toString());
        }
        finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (Exception ex){               //was going to throw an exception - but that seems to need to be in a try/catch loop, which seems to defeat the purpose...
                System.out.println("Exception closing stream "+ ex.getMessage());
            }
        }
        return lstObservations;

    }
}

This provider is written as if it were a ‘proxy’ to an existing REST server that can return the data we want – just not in the FHIR format. This is likely to be a common design pattern as vendors add FHIR functionality to existing products.

And finally here’s the server servlet:

@WebServlet(urlPatterns= {"/fhir/*"}, displayName="FHIR Server")
public class OrionRestfulServlet extends RestfulServer {
    public OrionRestfulServlet() {
        List<IResourceProvider>; resourceProviders = new ArrayList<IResourceProvider>();
        resourceProviders.add(new ConditionResourceProvider(_myMongo));
        resourceProviders.add(new ObservationResourceProvider(_myMongo));
        resourceProviders.add(new PatientResourceProvider(_myMongo));
        setResourceProviders(resourceProviders);
    }
}

So all you need to do is to deploy that into the servlet container (via a WAR file if you’re doing this manually) and you’re good to go. Point a browser at the EMR page, click the button and voila – an elegant growth chart application!

Lets just stop for a minute and reflect on what we’ve achieved here. We’re in the role of an EHR/EMR, but we’ve enabled an external application to access our data to provide quite advanced functionality (and it is a lovely interface as well).

This is all part of supporting the ecosystem that we’ve mentioned in some of the OAuth2 related posts recently. An ecosystem that promotes the development of these specialized applications, which are able to do cool things with our data. You can easily imagine a vendor focusing on developing these client applications, able to run against many different back end systems.

Disruptive technology indeed.

This example has been a browser based application, but there’s no reason why it couldn’t be a mobile device. If it was web based then it would work as is (you’d use a mobile framework of course) – a native app would require a little more work.

And speaking of OAuth2, our next job is to secure this access, so that will be the topic of the next post.

 

 

About David Hay
I'm an independent contractor working with a number of Organizations in the health IT space. I'm an HL7 Fellow, Chair Emeritus of HL7 New Zealand and a co-chair of the FHIR Management Group. I have a keen interest in health IT, especially health interoperability with HL7 and the FHIR standard. I'm the author of a FHIR training and design tool - clinFHIR - which is sponsored by InterSystems Ltd.

3 Responses to SMART on FHIR: Part 1

  1. Pingback: SMART on FHIR – adding OAuth2 | Hay on FHIR

  2. Bob says:

    Hi David,
    nice tutorial. I tried myself, but am wondering why launching the growth chart app does never stop loading for some reason. The link in your text “cool growth chart application” seems to struggle at the same problem. Do you know why?

    Best regards,
    Bob

    • David Hay says:

      Hi Bob – thanks for the comment. Looking at the link, it appears that there is an issue with CORS from the server, so the app doesn’t load leaving the icon spinning. SMART (like FHIR) is a fast moving target, and unfortunately posts can become outdated quite quickly! You might want to pay a visit to the official SMART site (http://docs.smarthealthit.org/ ) that will have the more up to date state and examples. Sorry about that…

      (Another site to get involved is tha FHIR chat at https://chat.fhir.org/ – there’s a SMART stream there as well as a whole lot of FHIR stuff)

      cheers…

Leave a Reply

%d bloggers like this: