clinFHIR profile viewer
June 26, 2017 2 Comments
Over the years I’ve made a number of attempts to build a profile viewer – to a mixed amount of success. The issue is becoming more urgent though, as profiles (as part of Implementation Guides) start to become published, and vendors such as Orion Health need to think about how we are to support them.
The issue is even more important for vendors in the international space, as our solutions are going to have to support different profiles in different countries, and we cannot assume that the profiles will be in alignment even for the same concept.
We’re not going to solve that issue right now (though it does highlight that the developers of profiles need to be aware of it and ideally working to avoid it as much as possible), but the ability to view profiles from different jurisdictions and analyse them in a common way is going to be important.
So this post documents the latest attempt in clinFHIR to develop a Profile Viewer that will work with any compliant profile.
We tend to use the term ‘Profile’ a bit loosely, so let’s be a bit more specific about what we need the app to do. We want to:
- Be able to view the restrictions, additions and changes on a single resource type
- View the extension definitions that are included
- Understand the ValueSets used – whether a new one on a ‘core’ element, or as part of a coded extension
- Review other ‘conformance’ resources such as ConceptMap and NamingSystem that might also be needed
- Show the differences between profiles that are adapting the same core resource type
Given that it is common that these profiles are published as part of an Implementation Guide, it makes sense that we use the ImplementationGuide resources to group all the resources in a single UI rather than as discrete resources.
To build the app, I started with the ‘CareConnect’ conformance artifacts that are being built by the interOpen project in the UK. They are being created using the Forge profiling tool, and stored in a GitHub repository. So I cloned the repo onto my computer, then created a small nodejs application that iterated through the files, uploaded them to a FHIR server (an instance of HAPI 2) and created an ImplementationGuide resource as it went. This means that the Profile Viewer can download the ImplementationGuide resource, and use that to display all the different parts that comprise the careConnect ‘profile’ (which we should really call an Implementation Guide I guess).
To see this in operation, start clinFHIR and set the servers to ‘snapp 2’ (which is where I uploaded the resources to). On the left pane, you’ll see that there are now 2 tabs. Select the ‘experimental’ tab, and then the profile Browser from there.
As you can see, when it loads it reads all the ImplementationGuide resources it finds on the conformance server and displays them in the dropdown. Select ‘careConnect’ and then load.
The app examines the ImplementationGuide and generates a display with the resources in the guide in an accordion to the left (divided into profiles, extension definitions and terminology resources).
Here’s a screen shot of the resultant page:
The app groups all the resources into 3 main groups:
- Profiles on individual resource types
- Extension Definitions used by those profiles
- Terminology artifacts (like ValueSets)
Select a profile – say the Patient profile, and details of that profile are then displayed in the right pane.
There are a number of different views that are available.
The Logical tab (shown above) shows a simple tree view of the profile. Extensions are retrieved and a human readable name displayed (from the ‘name’ element in the profile). It is intended to give a high level overview of the profile – suitable for a clinician or business analyst who just wants to know what information is being represented, rather than the details of the profile. Extensions are represented in purple, required elements are red, and an asterisk (*) indicates that the element can repeat.
Note that the tree view will not necessarily correspond directly to the core resource paths. For example, in the Patient profile there is both an nhsNumber and a localIdentifier. These are both at the Patient.identifier path in the profile.
The Table tab is intended for more in-depth review. Each row in the table represents a ‘row’ from the profile (strictly speaking an ‘ElementDefinition element from the profile StructureDefinition.snapshot.element) – with some un-needed ones removed to reduce the ‘clutter’.
Here’s an example:
There are some thicker horizontal lines that group together the ‘expanded’ datatype children for elements with complex datatypes (if they are expanded in the profile). This is because the children of complex datatypes can be profiled as well as the elements – for example you can specify that an identifier must have a system – and the actual value of the system as well – as we will see shortly.
There are 4 columns.
The first column is the path in the profile. If it’s an extension then the path is replaced by the name of the extension (from the extension definition) and the word ‘Ext’ is displayed in front of it.
Next is the multiplicity of the element. If required (min = 1) then the whole row is colored red.
Then the datatypes are shown. (There are multiple possible types in some cases of course). If the datatype is a reference (either to a profiled resource in the ImplementationGuide or a ‘core’ resource type) then the datatype is a hyperlink that, if clicked, will load that profile and display it. (btw – at the moment the accordion display to the left is not updated to indicate a new profile is being displayed – I’m working on that 🙂 )
The fourth column serves a number of purposes.
If the row is an extension, then:
the url for the extension is displayed (as a hyper link)
If the extension is to a coded datatype, then the url of the ValueSet is displayed. Clicking on the hyperlink will show the ValueSet browser in a modal dialog so you can see the concepts within it.
If the extension is a complex extension (ie has its own children) then a internal table is shown with a row for each child indicating the name, datatype, multiplicity, ValueSet and binding strength (with a hyperlink for any ValueSets).
If the row is an existing element from the base, then the valueset url and binding strength is displayed.
One aspect that I‘m not sure is rendered nicely yet are ‘sliced’ elements.
Slicing is a technique whereby an element that is multiple in the spec is ‘sliced’ into sub-elements with specific properties. The example from the spec is of Observation.component where the profile uses slicing to specify the Observation.component.code values that are expected in the profile (in this case a Blood Pressure with a systolic and a diastolic value).
When you define a slice on an element in the profile, you indicate the element property that is used to distinguish between slices – in this case it would be Observation.component.code – and this is a ‘row’ in the profile. In the profile viewer, the word: ‘Sliced Element:’ appears – followed by the distinguishing element (termed the ‘discriminator’). After this are the details of the sliced elements (which can get quite detailed).
To see this in operation, take a look at the CareConnect-Patient-1 profile.
You’ll see that there are quite a few rows starting with ‘identifier’ and that they are in 3 ‘groups’ (as defined by the thicker horizontal lines).
The first group represents the discriminator element – it’s the ‘system’ element. In other words, the individual ‘slices’ of Identifier will each have a different value for ‘system’. This group also includes all the child elements for an Identifier datatype. This contains the ‘expected’ values for these fields – for example that Identifier.type and identifier.use elements are coded, and giving the ValueSets and bindings.
The second group is a slice that represents the NHS number, and specifies that the system is required with a fixed value. There is also an extension on this slice to indicate the verification status which is drawn from a specific ValueSet. There can only one of these types of identifier in an instance, which is optional.
The third group is multiple and the system is fixed as well. (I’m not quite clear why this system is fixed – it seems to specify a ‘local’ identifier, but the value of which still needs to be unique within that system).
Looking at the table, you can also see that the name element is also sliced. Slicing can become really complicated – and creating a generic display for sliced elements is not going not be straightforward
The other tabs are really more intended for techies – and for helping to develop the App. ‘Required Elements’ and ‘ValueSets used’ is self explanatory, while the ‘Paths’ tab is a 1:1 rendition of the StructureDefinition.snapshot.element array. Json is, of course, the raw Json for the profile.
The other parts of the app are self-explanatory, so I won’t spend time on them here.
It’s early days for the app, so expect it to change over the coming months. In particular I’m keen to have some form of ‘differencing’ function – to be able to compare 2 profiles to show where they differ.
At the moment, if you want to view the Argonaut ImplementationGuide – select the SNAPP R3 server (it’s is a STU-2 ImplementationGuide, but seems to be represented in R3) – I expect that will be corrected in time.
As always, any comments are welcome!
Pingback: ‘Extending’ a ValueSet in a profile | Hay on FHIR
Pingback: Implementation Guide viewer | Hay on FHIR