Thoughts about updating Registries

In New Zealand we are lucky enough to have had a national patient registry for some years which serves as a unique identifier of patients in New Zealand – the National Health Index or NHI. The Registry is an instance of an EMPI (Enterprise Master Patient Index) and currently  exposes a SOAP interface that can be accessed by clients across a protected network.

In order to make it available for more users we are working in a project to expose a FHIR API that will offer similar functionality to the existing SOAP one, be simpler to use, available across the public Internet and so accessible to a wider range of clients in support of the healthcare ecosystem. We’re also looking at similar work for Provider registries.

This is what the architecture will look like (at a very high level – and not including any security or audit considerations):

NHI

The API will be exposed across two networks  – the existing private network that links providers in New Zealand and the public Internet. It has three main purposes:

  • To allow the client to retrieve a patient’s instance
  • To allow updating of (some of) the patient data
  • To create (register) new patients

We had a couple of options to expose the query interface. We could have used the standard restful query API allowing searching on a number of properties but we decided to only support the identifier query (for when NHI number is known) and to implement the $match operation for querying on other properties, as this is designed for querying an EMPI. It works by having the client create a sample patient instance with the elements containing the data to search on and sending that to the API. The server then uses those elements to query the EMPI, returning a list of possible matches with the score indicating how close the match is for each patient.

The server can also apply business logic to the query enforcing the validation rules such as requiring the date of birth and gender to be present in the query.

Here’s an example used for searching for male patients named Doe, born in 1970. Note that the query is contained in a Parameters resource – which is the standard method for passing parameters when invoking operations.

{
    "resourceType": "Parameters",
    "id": "example",
    "parameter": [{
        "name": "resource",
        "resource": {
            "resourceType": "Patient",
            "gender": "male",
            "birthDate": "1970",
            "name": [{"text": "Doe"}]
        }
    }]
}

And here’s what a response might look like (abbreviated)

{
    "resourceType": "Bundle",
    "id": "26419249-18b3-45de-b10e-dca0b2e72b",
    "type": "searchset",
    "total": 1,
    "entry": [{
        "fullUrl": "http://server/path/Patient/doe1",
        "resource": {
            "resourceType": "Patient",
            "id": "doe1",
            "name": [{
                "given": ["john"],
                "family": "Doe"
            }]
        },
        "search": {
            "mode": "match",
            "score": 0.9
        }
    }]
}

It’s up to the server to decide how to perform the query and how to score it. For example it could choose to include patients born in different years and with slightly different spellings of the name – perhaps phonetic matches.

When it comes to updating patient data the situation is a little more complicated.

  • Not all patient elements can be modified directly by the client. For example, the identifier is assigned by the system and the general practitioner is populated by a separate service (in our case the national enrolment service)
  • There are validation rules that need to be applied to all updates – and these are reasonably complex.
  • There are also security considerations: not all users can alter all the updatable elements (We’re not discussing access security here, though it’s obviously extremely important)
  • We need to support the creation of new patients via the API – but parts of the resource (eg the identifier) are created by the server, rather than being assigned by the client

There are a number of strategies we could use (assuming a real-time interface):

The restful update API allows the client to create an updated patient instance and submit that to the server which then replaces the current version. This would require us to examine the submitted instance to ensure that the data is valid, that it is a permissible update, that it doesn’t change what it shouldn’t and that the current user is allowed to make this update.

The Patch API allows the client to specify only the elements that are to be changed. For example it could specify a new phone number which would replace or add to the existing ones. The client creates a small document (which can be XML, JSON or FHIRPath) which specifies the changes to make against the indicated patient resource. Here’s an example of a JSON patch that changes the patient date of birth.

[{
  "op": "replace",
  "path": "/birthDate",
  "value": "1987-12-03"
}]

And here is one that removes the patients second address

[{
  "op": "remove",
  "path": "/address/1"
}]

 

These would both be applied against the resource instance (so you need to know the resource id), eg:

PATCH [host]/Patient/100

Finally, we could define specific operations for the updates that we support. For each update use case, we define the inputs that are required, any outputs and the business logic/validation that need to be applied. The operation itself is defined in an OperationDefinition resource. The client then uses the parameters resource to package the data and send it to the operation.

In considering these options, we decided that the restful update API is overly simplistic for our needs, but choosing between patch and operations is not so obvious.

Patch is straightforward to use but does have a number of drawbacks in our use case:

  • Altering extensions seems complex – for example if it’s not clear how to find a particular extension in the resource and replace it with another.
  • We still need to validate that the current user is authorized to make this change – which could be complicated given the range of patchs that could be received.
  • There is no obvious way to document permitted updates other than using text.
  • There is no obvious way to programmatically validate the patch before it is applied
  • Most significantly the syntax for altering repeating fields (like address) requires the client to specify the index of the element to change within the array. When the persistence layer is not actually storing a resource instance this means that the FHIR adapter must be able to map an element in a particular position to its representation in the database in a consistent fashion. This is not always straightforward.

There is also an architectural consideration. Both Patch and the restful update API place the control with the client. It is the client that states the exact nature of the change they want to make to the resource and the server follows that instruction (validation and security permitting) – though it can make changes to the resource. The operation however places the control with the server. The client specifies what they want to change (for example add an address) and the server makes the changes needed to meet that requirement.

For these reasons we are proposing to use operations as the primary mechanism for updating the patient resource. We’re creating specific operations scoped to each use case which can then be mapped to the usual roles permitted to invoke them.

To manage updates to repeating fields (like address) we added an id field to each array element in the patient instance that we expose. This was straightforward as all datatypes support an id field, as does the EMPI (and this is the way that the current SOAP interface works.)

Here’s the OperationDefinition for the updateAddress operation  (abbreviated).

{
  "resourceType":"OperationDefinition",
    "url":"http://hl7.org.nz/fhir/OperationDefinition/Patient-update-address",
    "status":"draft",
    "kind":"operation",
    "id":"Patient-update-address",
    "system":false,
    "type":true,
    "instance":false,
    "parameter": [
        {
            "name": "nhi",
            "use": "in",
            "min": 1,
            "max": "1",
            "type": "Identifier",
            "documentation":"The current (active) identifier for the patient that the address is to be added to"
        },
        {
            "name": "addressIdToRemove",
            "use": "in",
            "min": 0,
            "max": "*",
            "type": "string",
           "documentation":"The id of the address/s to be removed."
        },
        {
            "extension" : [
                {"url":"http://hl7.org/fhir/StructureDefinition/operationdefinition-profile",
                "valueUri":"http://hl7.org.nz/StructureDefinition/NHIAddress profile"}          
            ],
            "name": "addressToAdd",
            "use": "in",
            "min": 0,
            "max": "*",
            "type": "Address",
           "documentation":"The address/s to add. This must be a valid Address datatype conformant to the http://hl7.org.nz/StructureDefinition/NHIAddress profile"
        } ,
        {
            "name": "return",
            "use": "out",
            "min": 0,
            "max": "1",
            "type": "Resource",
           "documentation":"The updated Patient resource."
        }
    ]
  }

So we’re stating that in this operation you can:

  • Use the NHI (the identifier) to identify the patient – this will be known by the client, so a prior lookup is not needed (though advisable, if you’re making changes…)
  • Remove 0 or more existing addresses, based on the value of the id field in the address datatype (all descendants of Element have these)
  • Add 0 or more Addresses, each of which should be conformant to the profile we will create for NHI address (they have a number of extensions)
  • The response will be the updated Patient resource (directly – not in a Parameters resource).

And it’s an easy matter validate the request, and to map them to user roles for security.

BTW – we could have (and still could) apply the operation to a patient instance (as with patch) so the nhi parameter would be unneeded. But it does seem clearer this way – and all the implementers are familiar with the NHI (or should be 🙂 )

Here’s what an actual request might look like (adding a new address) to the patient with the NHI ABC1234

{
    "resourceType": "Parameters",
    "parameter": [
        {
            "name":"nhi",
            "valueIdentifier" : {
                "system":"https://standards.digital.health.nz/id/nhi",
                "value":"ABC1234"
            }
        },
        {
            "name": "addressToAdd",
            "valueAddress" : {
                "text":"23 thule st"
            }
        }
    ]
}

or, we could replace an address with a new one (a delete and an add):

{
    "resourceType": "Parameters",
    "parameter": [
        {
            "name":"nhi",
            "valueIdentifier" : {
                "system":"https://standards.digital.health.nz/id/nhi",
                "value":"ABC1234"
            }
        },
        {
            "name": "addressToAdd",
            "valueAddress" : {
                "text":"23 thule st"
            }
        },
        {
            "name": "addressIdToDelete",
            "valueString":"xxx"
        }
    ]
}

Obviously, you could (and should) include all the detailed components of the address.

If there are errors with the request – either incorrect data (like an invalid address), or a permissions mismatch then the statusCode will indicate that the operation failed, and there may be an OperationOutcome with more information about the error.

We’ve not talked in the project team about the best way to create a new Patient. As we discussed earlier, not all elements can be supplied by the client (eg identifier, generalPractitioner) and some of these will be added by the server during the create process. We could either use a normal POST operation, or another operation – the POST seems the easiest to me (there’s nothing in the spec that prevents a server altering the resource when it receives it).

Finally, here’s a screen dump of some of the operations in our project site (because each one is defined by an OperationDefinition resource it’s a straightforward matter to render them in a webpage – and to indicate valid datatypes, profiles and terminology bindings with lookup). Of course, they will also appear in the formal Implementation Guide that we will produce, along with all the other FHIR artifacts and documentation.

Screen Shot 2019-07-08 at 10.09.39 AM

Do note that the operations are still under development and will definitely change before the Project is complete. Apart from anything else, we want implementer feedback into these.

Our belief is that this will be straightforward for implementers – something we’ll be testing at a connectathon of course!

 

 

 

About David Hay
I'm an independent contractor working with companies like Orion Health and Rhapsody, 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 new FHIR standard.

3 Responses to Thoughts about updating Registries

  1. Peter Jordan says:

    I would also categorise the NZ NHI as a data repository as it contains actual patient demographic data, in addition to the registry which contains pointers to that data. Given that the underlying data schema is a relatively simple one, I’m really surprised that you are proposing to create custom operations to perform basic CRUD tasks. Using ‘vanilla’ RESTful commands would certain facilitate an API that’s simple than the existing SOAP interface, I rather fear that what you’re proposing here will make it more complex.

  2. Pingback: Updating a resource using patch | Hay on FHIR

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: