HL7 FHIR choice[x] analysis

NOTE: this page will move to an HL7 wiki when a location is found for it.

Introduction

This page contains a reversed and re-arranged version of the FHIR page listing occurrences of the choice[x] construct in the FHIR DSTU4 resources. The original reversed form was created by Grahame Grieve. Here the rows are re-arranged and somewhat sorted in an attempt to find errors, patterns and opportunities for refactoring and simplification of FHIR, particularly a great reduction if not removal of the choice construct from the base resources.

The tables below group choice[x] usages by their apparent intended logical data type, such as a point or interval in time; reference or value etc. The rows in each table can be compared to each other to see if they represent the same logical data type, and if so, why are they specified differently. One assumption is that many of the differences are simply because different committees and/or individuals did the modelling, and did not manage to perform a complete analysis of the possibilities of the data type, and/or may have made errors (either would be unsurprising, with working groups no doubt focused on the larger picture of one or more whole resource(s)). Without any global review mechanism, the ad hoc lists remain as they are in the specification. Consequences of this in

Empirical basis

The specifications here are highly detailed, and some appear speculative rather than based on existing systems (e.g. the ones to do with plan definitions). Questions to consider:

  • what is the design intention of the data types: to represent existing systems data or ‘model’ in some way proposed functionality not currently available in existing systems? These are quite different points of view and will lead to different modelling.

  • do all the data type combinations described below really occur in source systems?

  • are all the data types in source systems represented in the specification?

  • are all possible data types that might occur e.g. in a new product or product version, represented?

Recurring general patterns

A number of patterns recur independent of particular data type, as follows:

  • value or status boolean: either a value is supplied or a boolean, indicating that the event / entity in question exists, had occurred etc; for these, the boolean value is marked in red;

    • these probably should be 2 data points in all cases, because the general case is a Boolean + a data structure if the Boolean is True.

  • structured value(s) or string: in many cases, it is assumed that the value will be supplied in one or more structured forms, OR a string (marked in grey);

    • this should be dealt with in a generic way across all the data types;

  • reference to X versus inline X versus attachment of X versus uri: various concrete representation alternatives; these are shown in some of the tables lower down;

    • generally a single type should be constructed to deal with this situation

  • coded versus value: where a coded category (e.g. ‘normal’) and/or a quantitative point or range (e.g. ‘4.5-12’ - Thyroxine T4) is available;

    • in many cases, both coded and quantitative data points should be used.

General data type questions

  • Why is Age a data type? It is an invariant on Quantity; it would be easier to just use Duration.

  • Why is instant any different from dateTime? They are both ISO 8601 date times. Instant is only different in that it is no-partial; the need for partial or non-partial is use-case/instance dependent, and can be handled using invariants based on a function isPartial(): Boolean.

When an event occurred (past) - point or interval in time

The following choice patterns correspond to the requirement to represent a point or interval in past time at/during which an event occurred, allowing for:

  • approximate information (partial date/time or period)

  • repeated occurrences according to some formula

  • narrative representation.

Data types

Resource . location

 

Data types

Resource . location

 

dateTime

Period

Timing







ChargeItem.occurrence[x]
Contract.term.action.occurrence[x]
DeviceRequest.occurrence[x]
DeviceUseStatement.timing[x]
ServiceRequest.occurrence[x]
SupplyDelivery.occurrence[x]
SupplyRequest.occurrence[x]]

7

dateTime

Period

Timing

instant

 

 

Observation.effective[x]

1

dateTime

Period









BiologicallyDerivedProduct.collection.collected[x]
BiologicallyDerivedProduct.processing.time[x]
BiologicallyDerivedProduct.manipulation.time[x]
ClinicalImpression.effective[x]
CommunicationRequest.occurrence[x]
DetectedIssue.identified[x], DiagnosticReport.effective[x]
Media.created[x]
MedicationAdministration.effective[x]
MedicationStatement.effective[x]
MedicinalProductAuthorization.procedure.date[x]
Provenance.occurred[x]
RiskAssessment.occurrence[x]
Specimen.collection.collected[x]
Specimen.processing.time[x]]

15

date

Period









Claim.supportingInfo.timing[x]
Claim.item.serviced[x]
ClaimResponse.addItem.serviced[x]
CoverageEligibilityRequest.serviced[x]
CoverageEligibilityResponse.serviced[x]
ExplanationOfBenefit.supportingInfo.timing[x]
ExplanationOfBenefit.item.serviced[x]
ExplanationOfBenefit.addItem.serviced[x]

8

date

Period

 

 

string

 

FamilyMemberHistory.born[x]

1

dateTime

 

 

 

string

 

Immunization.occurrence[x]

1

dateTime

 

 

 

 

boolean

Patient.deceased[x]

1

Notes

  • Why is the boolean presence flag is use for Patient.deceased[x] but not for Immunization.occurrence[x] and potentially other occurrence[x] elements?

  • Why does string only apply for FamilyMemberHistory.born[x] and Immunization.occurrence[x]?

  • Why does instant only apply in one case, and not for example to the groups of 15 or 8?

Towards a coherent model

A better approach than the above variant and probably changing choice[x] pseudo-typing would be to create one or more types representing the notion of ‘occurrence’, that accounts for the variance between time-points and intervals and also approximation (aka accuracy). Whether narrative representation occurs is a separate matter, as is a boolean presence flag; these would need to be modelled e.g. in a wrapping class.

Concretely, there are two types of formal representation of the idea of occurrences in the past:

  • a list of time-points and/or intervals when the event occurred or state was true;

    • this corresponds to the date/dateTime and Period types

  • a specification of possible times, optionally within some bounding interval;

    • this corresponds to the Timing type

UML

The following model allows for two ways of specifying events in the past:

  • as a series of concrete occurrences, each with a start time and optional duration

  • as a pattern description, bounded by an overall time interval.

The Occurrence_pattern class corresponds to a simplified view of FHIR’s Timing class.

This model replaces all the combinations in the table above, except for String, which needs to be dealt with in a generic way for all data types.

Age at which an event occurred (past) - absolute point / interval in time | relative duration

The following choice[x] patterns correspond to data elements that ideally express

Data types

Resource . location

 

Data types

Resource . location

 

Age

dateTime

Range

Period

string

 

AllergyIntolerance.onset[x]
Condition.onset[x]
Condition.abatement[x]
Procedure.performed[x]

4

 

 

Range

Period

 

 

RiskAssessment.prediction.when[x]

1

Age

 

Range

Period

string

 

FamilyMemberHistory.condition.onset[x]

1

Age

 

Range

 

string

 

FamilyMemberHistory.age[x]

1

Age

date

Range

 

string

boolean

FamilyMemberHistory.deceased[x]

1

Towards a coherent model

The main challenge here is to deal with the usual problem of wanting to enter an age or a date at which something happened. This can be solved with a model like the following (note: as above, the exact HL7 data types are not used, as I don’t have a UML model of FHIR; thus for e.g. Iso8601_duration, assume Duration etc):

Age is specified either:

  • as a date (Age_specifier.date), which may be approximate, because Iso8601_date allows partial dates;

  • as an age (Age_specifier.age), which may be approximate.

In the first case, effective age can be computed given a DOB argument.

Absolute point / interval in time | relative duration

The following Resource choice[x] lists are for elements that specify logical date/time ranges or durations. Durations in FHIR are just Quantities with in invariant requiring certain time units.

Data types

Resource . location

 

Data types

Resource . location

 

Duration

Period

dateTime

Timing



EvidenceVariable.characteristic.participantEffective[x]
ResearchElementDefinition.characteristic.studyEffective[x]
ResearchElementDefinition.characteristic.participantEffective[x]

3

Duration







Range

PlanDefinition.action.relatedAction.offset[x]
RequestGroup.action.relatedAction.offset[x]

2

Duration

 

date

 

 

Goal.target.due[x]

1

Duration

Period

dateTime

 

 

DataRequirement.dateFilter.value[x]

1

Duration

Period

 

 

Range

Timing.repeat.bounds[x]

1

Towards a coherent model

See previous entry above.

Scheduled (future) time

The following Resource choice[x] lists are for elements that specify timing in the future for various activities or events.

Date types

Resource . location

 

Notes

Date types

Resource . location

 

Notes

Period

Timing

 

 

 

 

 

string

 

CarePlan.activity.detail.scheduled[x]

1

  • Q: What use is the string here?

Period

Timing

Range

dateTime

Duration

Age

 

 

 

ActivityDefinition.timing[x]
PlanDefinition.action.timing[x]
RequestGroup.action.timing[x]

3

  • Q: Are there really any systems containing Ranges (2 x SimpleQuantity) representing timing?

  • Q: how does Duration make sense here?

 

Timing

 

date
dateTime

 

 

 

 

Reference

TriggerDefinition.timing[x]

1

  • Q: Why no Period?

 

 

 

date

 

 

CodeableConcept

 

 

Goal.start[x]

1

 

Notes

  • Why does the 3rd choice list include Reference() and not the other two?

Towards a coherent model

A better approach than the above variant and probably changing choice[x] pseudo-typing would be to create one or more types representing the notion of ‘TimingSpecification’, meaning ‘specified future timing’. This would need to account for the variance between time-points and intervals and also coded ways of specifying time.

Concretely, there are two ways of representing time in the future:

  • with a list of specific date-times/intervals of date-times;

  • with a specification, i.e. like Timing.

Quantity patterns

The following Resource choice[x] lists are for elements that specify quantity data types.

Data types

Resource . location

 

Data types

Resource . location

 

SimpleQuantity

 

Ratio

 

MedicationAdministration.dosage.rate[x]
NutritionOrder.enteralFormula.administration.rate[x]

2

Quantity

 

 

string

SubstanceSpecification.moiety.amount[x]
SubstanceSpecification.property.amount[x]

2

Quantity

Range

 

string

SubstanceAmount.amount[x]
SubstanceReferenceInformation.target.amount[x]

2

SimpleQuantity

Range

 

 

Dosage.doseAndRate.dose[x]

1

SimpleQuantity

Range

Ratio

 

Dosage.doseAndRate.rate[x]

1

Quantity

Range

Ratio

 

ServiceRequest.quantity[x]

1

Quantity

Range

Ratio

string

SubstanceSpecification.relationship.amount[x]

1

SimpleQuantity

 

 

string

SpecimenDefinition.typeTested.container.minimumVolume[x]

1

Proposal

The SimpleQuantity / Quantity / Range trio is similar to the point/interval of time requirement, and can be modelled with a single type.

Money

Data types

Resource . location

 

 

Data types

Resource . location

 

 

Money

unsignedInt

 

 

ExplanationOfBenefit.benefitBalance.financial.used[x]

1

 

Money

 

SimpleQuantity

 

Coverage.costToBeneficiary.value[x]

1

 

Money

unsignedInt



string

CoverageEligibilityResponse.insurance.item.benefit.allowed[x]
CoverageEligibilityResponse.insurance.item.benefit.used[x]
ExplanationOfBenefit.benefitBalance.financial.allowed[x]

3

  • PK: Why is string here?

Notes

  • It is unclear why unsignedInt is allowed in 2 places but not the 3rd, and why SimpleQuantity applies; or if it does, why not in the other two cases.

Other proposals

Possibly model as ‘consumption of a resource’, that incorporates a max $ value, units of <X> consumed and a code for kind of unit.

Numeric: integer | real | Range<T>

Data types

Resource . location

 

Data types

Resource . location

 

decimal

Range

 

RiskAssessment.prediction.probability[x]

1

integer

 

boolean

Patient.multipleBirth[x]

1

'Any' value patterns

The following choices are for data elements that logically can have any or a broad set of data values.

It is likely that at least some of them are over-specified, and in any case, listing types corresponding only to use cases known today is not a reliable way to determine the possible types in all cases for any such data point.

A general solution is to define a parent type of data types, e.g. called DataType or DataValue. This can either be done by changing the inheritance structure of FHIR as it stands now (DSTU4) or by a retro-fitting approach as described in this post.

Data types

Resource . location

 

Data types

Resource . location

 

CodeableConcept

Quantity

Range

Ratio

boolean

integer

time
dateTime

Period
SampledData

string

 

Observation.value[x]
Observation.component.value[x]

2

Coding
code

 

 

 

boolean

integer
decimal

dateTime



string

 

CodeSystem.concept.property.value[x]

1

code







boolean

integer
decimal

dateTime

 

string

uri

ValueSet.expansion.parameter.value[x]

1

Coding

Quantity





boolean

integer
decimal

date
time
dateTime



string

uri

Attachment

Reference

Contract.term.offer.answer.value[x]
Questionnaire.item.initial.value[x]
QuestionnaireResponse.item.answer.value[x]

3

Coding









integer

date
time



string

Reference

Questionnaire.item.answerOption.value[x]

1

Coding

Quantity





boolean

integer
decimal

date
time
dateTime



string

Reference

Questionnaire.item.enableWhen.answer[x]

1

CodeableConcept

Quantity

Range

Ratio

boolean

integer

 

 

string

 

Goal.target.detail[x]

1

CodeableConcept

Quantity

Range

 

boolean

 

 

 

 

Reference

Group.characteristic.value[x]

1

CodeableConcept

Quantity

Range

 

boolean

 

 

 

 

 

DeviceRequest.parameter.value[x]
SupplyRequest.parameter.value[x]

2



Quantity





boolean







string

Reference

Attachment

Claim.supportingInfo.value[x]
ExplanationOfBenefit.supportingInfo.value[x]

2









boolean

integer

decimal





string

id

StructureMap.group.rule.target.parameter.value[x]

1

















Base64Binary

string

 

AuditEvent.entity.detail.value[x]

1

CodeableConcept

Quantity

Range

 

 

 

 

 

 

 

PlanDefinition.goal.target.detail[x]

1

CodeableConcept

Quantity

Range

 

 

 

 

 

 

Reference

 

 

*

















 

StructureMap.group.rule.source.defaultValue[x]
Task.input.value[x]
Task.output.value[x]

3


References

Data types

Resource . location

 

Notes

Data types

Resource . location

 

Notes

canonical
uri

 

ConceptMap.source[x]
ConceptMap.target[x]
PlanDefinition.action.definition[x]

3

 

Reference
url

 

ImplementationGuide.definition.page.name[x]

1

 

Identifier
Reference

 

Composition.relatesTo.target[x]

1

 

canonical

boolean

ImplementationGuide.definition.resource.example[x]

ImplementationGuide.manifest.resource.example[x]

2

This looks like bad modelling. It’s not clear why the boolean is there.

Reference

boolean

MedicationRequest.reported[x]

1

 

Reference or value

The following type choices try to cover variant forms of representing data by reference or in an inline / attached form.

Data types

Resource . location

 

Notes

Data types

Resource . location

 

Notes

Reference
(Consent | DocumentReference | Contract | QuestionnaireResponse)

Reference
(Composition | DocumentReference | QuestionnaireResponse)

etc

Attachment

 



Consent.source[x]

Contract.friendly.content[x]
Contract.legal.content[x]
Contract.rule.content[x]
Contract.legallyBinding[x]

5

The Reference() could be generalised to Reference(Document), where Document is established as an ancestor to various document types.

Reference
(Practitioner | Patient | RelatedPerson | Organization)

Reference(Organization)



string



Annotation.author[x]


DeviceDefinition.manufacturer[x]

2

The Reference() could be generalised to Reference(Party), where Party is established as an ancestor to various party types.

Reference(Any)

Attachment

string



Communication.payload.content[x]
CommunicationRequest.payload.content[x]

2

 

Reference(Location)





Address

Claim.accident.location[x]
ExplanationOfBenefit.accident.location[x]

2

 

Towards a coherent model

A modelled approach to the above types could use the general approach of modelling a document+reference or party+reference as a single type, e.g.

class PartyRef { reference: Reference (Party); inline: string; }

Coded | value | Reference

Data types

Resource . location

 

Notes

Data types

Resource . location

 

Notes

CodeableConcept











canonical

uri



GuidanceResponse.module[x]

1

 

CodeableConcept

SimpleQuantity

 

 

 

 

 

 

MedicationKnowledge.administrationGuidelines.patientCharacteristics.characteristic[x]

1

 

CodeableConcept

SimpleQuantity

 

 

 

base64Binary

string

 

 

MedicationKnowledge.drugCharacteristic.value[x]

1

 

CodeableConcept

 

 

Range

 

 

 

 

Population.age[x]

1

  • why complicate things with a coded age range? If it exists, convert it to a Range

CodeableConcept

 

Duration

 

 

 

 

 

Specimen.collection.fastingStatus[x]

1

  • Probably should not be a choice[x] but a required code and an optional Duration

CodeableConcept











canonical

DataRequirement
Expression

ResearchElementDefinition.characteristic.definition[x]

1

 

CodeableConcept











canonical

Reference

DataRequirement
Expression
TriggerDefinition

EvidenceVariable.characteristic.definition[x]

1

 

CodeableConcept







boolean







Dosage.asNeeded[x]
MedicationRequest.substitution.allowed[x]
ServiceRequest.asNeeded[x]

3

Should be modelled with either 2 data points, not a choice, or just coded, with any ‘any substitution’ code being available.

Coded or reference

Data types

Resource . location

 

Data types

Resource . location

 

CodeableConcept

Reference



DataRequirement.subject[x]
ActivityDefinition.subject[x]
ActivityDefinition.product[x]
CarePlan.activity.detail.product[x]
ChargeItem.product[x]
Claim.diagnosis.diagnosis[x]
Claim.procedure.procedure[x]
Contract.topic[x]
Contract.term.topic[x]
Contract.term.asset.valuedItem.entity[x]
CoverageEligibilityRequest.item.diagnosis.diagnosis[x]
DeviceRequest.code[x]
EventDefinition.subject[x]
ExplanationOfBenefit.diagnosis.diagnosis[x]
ExplanationOfBenefit.procedure.procedure[x]
Invoice.lineItem.chargeItem[x]
Library.subject[x]
Measure.subject[x]
Medication.ingredient.item[x]
MedicationAdministration.medication[x]
MedicationDispense.statusReason[x]
MedicationDispense.medication[x]
MedicationKnowledge.ingredient.item[x]
MedicationKnowledge.administrationGuidelines.indication[x]
MedicationRequest.medication[x]
MedicationStatement.medication[x]
MedicinalProduct.specialDesignation.indication[x]
MedicinalProductContraindication.otherTherapy.medication[x]
MedicinalProductIndication.otherTherapy.medication[x]
MedicinalProductInteraction.interactant.item[x]
PlanDefinition.subject[x]
PlanDefinition.action.subject[x]
ResearchDefinition.subject[x]
ResearchElementDefinition.subject[x]
Specimen.container.additive[x]
SpecimenDefinition.typeTested.container.additive.additive[x]
Substance.ingredient.substance[x]
SubstanceSpecification.property.definingSubstance[x]
SubstanceSpecification.relationship.substance[x]
SupplyDelivery.suppliedItem.item[x]
SupplyRequest.item[x]

41

CodeableConcept

Reference

Address

Claim.item.location[x]
ClaimResponse.addItem.location[x]
ExplanationOfBenefit.item.location[x]
ExplanationOfBenefit.addItem.location[x]

4

Coding

uri



MessageDefinition.event[x]
MessageHeader.event[x]

2

Unclassified

Data types

Resource . location

 

Data types

Resource . location

 

positiveInt

string

Immunization.protocolApplied.doseNumber[x]
Immunization.protocolApplied.seriesDoses[x]
ImmunizationEvaluation.doseNumber[x]
ImmunizationEvaluation.seriesDoses[x]
ImmunizationRecommendation.recommendation.doseNumber[x]
ImmunizationRecommendation.recommendation.seriesDoses[x]

6