NOTE: this page will move to an HL7 wiki when a location is found for it.
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
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?
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.
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.
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 | ||||||
---|---|---|---|---|---|---|---|
dateTime | Period | Timing | ChargeItem.occurrence[x] | 7 | |||
dateTime | Period | Timing | instant | Observation.effective[x] | 1 | ||
dateTime | Period | BiologicallyDerivedProduct.collection.collected[x] | 15 | ||||
date | Period | Claim.supportingInfo.timing[x] | 8 | ||||
date | Period | string | FamilyMemberHistory.born[x] | 1 | |||
dateTime | string | Immunization.occurrence[x] | 1 | ||||
dateTime | boolean | Patient.deceased[x] | 1 |
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?
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
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.
The following choice[x] patterns correspond to data elements that ideally express
Data types | Resource . location | ||||||
---|---|---|---|---|---|---|---|
Age | dateTime | Range | Period | string | AllergyIntolerance.onset[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 |
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:
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.
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 | |||||
---|---|---|---|---|---|---|
Duration | Period | dateTime | Timing | EvidenceVariable.characteristic.participantEffective[x] | 3 | |
Duration | Range | PlanDefinition.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 |
See previous entry above.
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 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Period | Timing | string | CarePlan.activity.detail.scheduled[x] | 1 |
| ||||||
Period | Timing | Range | dateTime | Duration | Age | ActivityDefinition.timing[x] | 3 |
| |||
Timing | date | Reference | TriggerDefinition.timing[x] | 1 |
| ||||||
date | CodeableConcept | Goal.start[x] | 1 |
Why does the 3rd choice list include Reference() and not the other two?
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.
The following Resource choice[x] lists are for elements that specify quantity data types.
Data types | Resource . location | ||||
---|---|---|---|---|---|
SimpleQuantity | Ratio | MedicationAdministration.dosage.rate[x] | 2 | ||
Quantity | string | SubstanceSpecification.moiety.amount[x] | 2 | ||
Quantity | Range | string | SubstanceAmount.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 |
The SimpleQuantity / Quantity / Range trio is similar to the point/interval of time requirement, and can be modelled with a single type.
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] | 3 |
|
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.
Possibly model as ‘consumption of a resource’, that incorporates a max $ value, units of <X> consumed and a code for kind of unit.
Data types | Resource . location | |||
---|---|---|---|---|
decimal | Range | RiskAssessment.prediction.probability[x] | 1 | |
integer | boolean | Patient.multipleBirth[x] | 1 |
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 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
CodeableConcept | Quantity | Range | Ratio | boolean | integer | time | Period | string | Observation.value[x] | 2 | |
Coding | boolean | integer | dateTime | string | CodeSystem.concept.property.value[x] | 1 | |||||
code | boolean | integer | dateTime | string | uri | ValueSet.expansion.parameter.value[x] | 1 | ||||
Coding | Quantity | boolean | integer | date | string | uri Attachment Reference | Contract.term.offer.answer.value[x] | 3 | |||
Coding | integer | date | string | Reference | Questionnaire.item.answerOption.value[x] | 1 | |||||
Coding | Quantity | boolean | integer | date | 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] | 2 | ||||||
Quantity | boolean | string | Reference Attachment | Claim.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] | 3 |
Data types | Resource . location | Notes | ||
---|---|---|---|---|
canonical | ConceptMap.source[x] | 3 | ||
Reference | 1 | |||
Identifier | 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 |
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 | ||||
---|---|---|---|---|---|---|
Reference Reference etc | Attachment | Consent.source[x] Contract.friendly.content[x] | 5 | The Reference() could be generalised to Reference(Document), where Document is established as an ancestor to various document types. | ||
Reference 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] | 2 | ||
Reference(Location) | Address | Claim.accident.location[x] | 2 |
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; } |
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 |
| ||||||
CodeableConcept | Duration | Specimen.collection.fastingStatus[x] | 1 |
| ||||||
CodeableConcept | canonical | DataRequirement | ResearchElementDefinition.characteristic.definition[x] | 1 | ||||||
CodeableConcept | canonical Reference | DataRequirement | EvidenceVariable.characteristic.definition[x] | 1 | ||||||
CodeableConcept | boolean | Dosage.asNeeded[x] | 3 | Should be modelled with either 2 data points, not a choice, or just coded, with any ‘any substitution’ code being available. |
Data types | Resource . location | |||
---|---|---|---|---|
CodeableConcept | Reference | DataRequirement.subject[x] | 41 | |
CodeableConcept | Reference | Address | Claim.item.location[x] | 4 |
Coding | uri | MessageDefinition.event[x] | 2 |
Data types | Resource . location | ||
---|---|---|---|
positiveInt | string | Immunization.protocolApplied.doseNumber[x] | 6 |