FHIR enabling an Immunization registry – part 3

So we’ve made a few proposals in the last couple of posts:

The next thing to think about is how to generate the ImmunizationRecommendation (IR) from the PlanDefinition.

This could be really complicated, depending on the circumstances around the generation. For example, if we just wanted the recommendation for a new born then it’s quite easy. If we wanted more ‘smarts’ – e.g. the recommendation for someone of a given age considering their immunization and clinical history, then it becomes rather more complicated. In this post we’ll keep it simple and not look at the patient’s history, focusing on the ‘mechanics’ of the generation.

As a background to the discussion, let’s think about how a client might use the registry API.

A likely sequence of events is:

  • The client asks for an ImmunizationRecommendation for a patient from the registry – specifying the paediatric protocol as the base. This could be saved by the client or retrieved periodically (which might be useful if the protocol changes or the child receives immunizations from elsewhere).
  • Based on the information in the ImmunizationRecommendation, the patient is scheduled to receive a vaccination.
  • The vaccination is given, and an Immunization resource created for each vaccine given and sent to the registry.

Immunization resource

Before we go into details however, it might be a good idea to think about what information we are expecting to get when a client submits an Immunization resource (recording a vaccine administration). After all, the information in that resource needs to come from the ImmunizationRecommendation, so there needs to be place in that resource for the required elements.

Each Immunization will contain at least:

  • The patient & administrator of the vaccine
  • Date of administration
  • Details of the administration – the code, dose, site of administration
  • A link of some sort back to the protocol.

It’s the last one that is the tricky one.

The reason it’s there is that we need to be able to say which of the entries in the protocol are ‘fulfilled’ by the administration, so we can determine if the person is up to date with regard to the programme. This is important for reporting as well.

If we take a look at the Immunization resource, we see that there is a ‘protocolApplied’ element, with a number of child elements. In particular we see:

  • Series – the name of the series
  • doseNumber – the dose within the series

So what are these?

Well, to protect against a disease we generally need a number of doses of a vaccine at a pre-determined set of intervals (In these days of COVID this is apparent to everyone!) So the series can be the vaccine we’re giving, and the dose number is the sequence within the sequence of administrations that we’re giving.

For example, the DTaP-IPV-HepB/Hib vaccine is given 3 times –  at 6 weeks (dose 1), 3 months (dose 2) and 5 months (dose 3).

So if we were administering the 6 weeks immunization, we would be giving dose number 1 of the DTaP-IPV-HepB/Hib vaccine series or:

  • Series = DTaP-IPV-HepB/Hib
  • doseNumber  = 1

Here’s what it might look like (using FSH):

Obviously this is an incomplete resource – we’re missing the vaccine administrator for example, and the batch details of the vaccine.


Now that we know what the Immunization is going to need, we can consider the ImmunizationRecommendation in a bit more detail.

The ImmunizationRecommendation is quite a simple resource that identifies the patient and has a set of recommendations, each of which contains one or more specific vaccines that the person should receive and when. However, we still need to decide how best to structure it for our purpose as there are one or more recommendation elements, each of which can contain 0 or more vaccines (not a single one).

For our implementation, as we’re tracking the vaccine administrations against the defined sequence in the plan, we’ll use a single recommendation per vaccine due. So, if the child was given the 6 week immunization, there would be 3 recommendations and 3 Immunization resources produced.

And there are other issues to consider.

For example the recommendation.targetDisease element is a single value only, while many vaccines protect against multiple diseases. Looking at the proposal for the next version of FHIR (R5) it is multiple, but we can’t use that yet.

Having the targetDisease there does seem like a useful thing to have so we’re going to need an extension to represent them (as many of the vaccines we use cover more than one disease). For simplicities sake, we won’t use the existing targetDisease at all – just our extension. We do generally try not to have extensions that have the same scope as an existing element but there doesn’t seem a better way to cope with this – requiring implementers to look in 2 places is just icky.

When we do move to R5 (in at least a couple of years) we can deprecate the extension in favour of the proper element – or duplicate the data for a time to allow clients to catch up.

Finally, there isn’t a place to reference the plan, which the Immunization is going to need. No problem, we’ll use the same extension as we’ll use in the Immunization for this.

Here’s an FSH example of an IR for a new-born (the first couple of vaccines anyway):

(By the way – if you look carefully, you’ll see that the extensions are represented in 2 different ways. The ‘plan definition’ extension specifies the url and value explicitly, while the ‘disease covered’ uses a named extension. The latter is preferable as it’s more understandable and shorter, but does require that the underlying profile and extension are defined in FSH. It can also be validated by Sushi during instance generation which is an additional benefit).

Updating the PlanDefinition

There’s one last thing we need to do before we can actually think about generating the ImmunizationRecommendation from the plan.

We noted above that both ImmunizationRecommendation and Immunization are going to need a ‘series’ and a ‘doseNumber’ value, but there’s nowhere in the plan where this information could be placed. No worries, we’ll create an extension on action to hold this. Here’s an example of an updated plan showing the first administration of DTaP-IPV-HepB/Hib

Without going into too many details of the extension, note that the extension url is http://clinfhir.com/StructureDefinition/vacc-sequence, and it actually has 2 values – one for the series name and another for the position of the vaccine within the sequence. As noted above, we can use the more compact version of the extension once the profiling resources are created.

Generating the ImmunizationRecommendation from the Plan

And now we finally talk about generating the ImmunizationRecommendation from the Plan! We’ll start with the simple example of generating an ImmunizationRecommendation for a person of a given age without considering any personal aspects like their immunization history or allergies, so we don’t need to consider ‘catch up’ vaccinations – just the information in the plan.

The specification actually talks about applying a PlanDefinition to produce outputs as a CarePlan under a section called Applying a PlanDefinition, and defines a $apply operation to use for this. This is especially interesting as the ImmunizationRecommendation as actually regarded as a specialized form of the CarePlan.

Here’s the image of the process:

So, applying that process to our use case, we can imagine the following pseudocode to generate the ImmunizationRecommendation from the PlanDefinition:

  • A call is made by a client to $apply passing across the Patient resource (and other parameters as required). The patient must at least have the birthdate, as we need to know the patients age. The gender may be needed as well.
  • Create an ImmunizationRecommendation for the patient setting the extension value to the url of the PlanDefinition
  • Process the top level actions in the PlanDefinition. For each top level action (representing an age due):
    • Is the age due of the action (timingAge) greater than the patients current age? If it is then:
      • Interact through the child actions (representing the individual vaccines in the plan). For each child action:
        • Generate a recommendation in the ImmunizationRecommendation.recommendation element.
        • Populate the dateCriterion by calculating the dateCriterion.value (which is the date due) from the difference between the patients age and the date due. dateCriterion.code is set to the loinc code 30980-7 (date imm due) or a SNOMED equivalent
        • Locate the ActivityDefinition it references and place the vaccine code (ActivityDefinition.productCodeableConcept) in the recommendation.vaccineCode element
        • Copy the disease covered extensions from child action to recommendation
        • Also populate the series and doseNumber elements from the extension on the parent action.

This has been a rather technical post so let’s summarize what we’ve discussed.

  • The ImmunizationRecommendation resource represents a specific patients recommended vaccinations, and is generated from a PlanDefinition that describes the overall immunization protocol. Potentially it could look at personal clinical data when creating the recommendation.
  • We are using the vaccine name and sequence of administration to correlate between recording an administration of a vaccine (via an Immunization resource) and the plan of vaccination
  • We defined a number of extension definitions to support the flow of information from PlanDefinition through ImmunizationRecommendation to Immunization. These were:
    • planDefinitionUrl. Used by ImmunizationRecommendation and Immunization to refer to the Plan that the resources are associated with.
    • diseaseCovered. Used by ImmunizationRecommendation.recommendation as the current (R4) resource supports only a single disease code. (The Immunization doesn’t need it as disease covered is already multiple).
    • vaccineSequence. A complex extension used by PlanDefinition to record the series and sequence of a specific vaccination due at a given age.

And finally, I’ve started to wrap these ideas into an ImplementationGuide (which helped immensely in validating the design and producing the examples).

Amongst other things, there’s a javascript script that generates a sample ImmunizationRecommendation from the PlanDefinition to populate an example page.

I can’t guarantee that this IG will always be there – contact me if it disappears and you are interested.

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.

6 Responses to FHIR enabling an Immunization registry – part 3

  1. Pingback: Dew Drop – March 30, 2021 (#3412) – Morning Dew by Alvin Ashcraft

  2. Peter Jordan says:

    What does that Vaccine Code look like in a Resource – i.e. Code System and Description?

  3. Will Rosenfeld says:

    Thanks for a great blog series on this topic!

  4. Craig Newman says:

    The HL7 Public Health WG has a project for an immunization CDS FHIR IG (http://build.fhir.org/ig/HL7/ImmunizationFHIRDS/index.html). It actually assumes that the CDS engine has implemented the forecasting and evaluation rules and so it bypasses some of what you’ve done here. It essentially takes the patient and their immunization as inputs with a recommendation as the output.

Leave a Reply

%d bloggers like this: