Situational Awareness for Novel Epidemic Response, published by HL7 International / Public Health. This guide is not an authorized publication; it is the continuous build for version 1.0.1 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/HL7/fhir-saner/ and changes regularly. See the Directory of published versions
This section of the IG explains possible ways to record the expressions used to automate a Measure. Essentially it is a phrase book from English to the Expression elements used in the PublicHealthMeasure resource to describe the automation. Due to different workflows and national requirements, these phrases may require translation to different FHIR models to represent the concept being tested.
Preferred models are marked with an asterisk.
The examples below are informative, and show how measures could be developed to support different constraints using existing vocabularies and value sets supported by HL7 FHIR.
In the text below, examples are based on measures analyzed from the CDC Patient Impact and Hospital Capacity module used for reporting in the first half of 2020 and shown below.
Encounters represent interactions between a patient and a healthcare provider in inpatient, outpatient or other settings. Many measures for situational awareness start with a patient encounter as the context for the measurement.
The HOSPITAL INPATIENT BED OCCUPANCY measure can be evaluated through encounters. The initial set of encounters can be retrieved using the Encounters?status=in-progress
FHIR query, or represented as
Encounter.where(status='in-progress')
in FHIRPath.
Both Encounter.period.start and Encounter.period.end can be tested for occurrence on a specific day, or within a given date range, allowing for tests of Admit/Discharge/Transfer/Death by date.
If looking for encounters started yesterday, and today is September 1, 2020, the appropriate FHIR query is: Encounter?date=sa2020-08-30T23:59:59&date=le2020-09-01
. This query will find encounters that start after the last second of August 30, and which were present before September 1. These encounters must have been present Yesterday, and started after the day before yesterday, thus, must have started yesterday. The same query expressed as a filter in FHIRPath is Encounter.where(period.start.toDate() = @2020-08-31)
. FHIRPath provides finer grained access than FHIR queries, enabling direct access to the start component of the encounter period. The start component needs to be converted to a date to ensure that precision matches for the equals operator.
The first and second examples above can be combined to ensure that the encounters in question are still in-progress, and filter out other encounters (e.g., those created in error). For the FHIR query, this would be Encounters?status=in-progress&date=sa2020-08-30T23:59:59&date=le2020-09-01
. In FHIRPath, this would be Encounter.where(period.start.toDate() = @2020-08-31 and status = 'in-progress')
An admission generally starts an encounter that lasts more than a single day, although might also be used for encounters lasting only a single day (e.g., emergency department encounters which could last more than a day, but often are completed with a single day).
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Encounter.status | Active encounters | Encounter?status=in-progress | Encounter.where(status='in-progress') | [Encounter] E where E.status = 'in-progress' |
Encounter.period.start | Encounters starting on a given date or within a particular period | Encounter?date=sa2020-09-07&date=lt2020-09-09 Because the date search parameter is compared to a period data type, the upper and lower bounds must be set in a way that ensures inclusion of the target period. Using a high precision time value for the sa component is less likely to run into implementation errors even though sa2020-09-07 **should** be equivalent. |
Encounter.where(period.start >= @2020-09-08 and period.start < @2020-09-09) It is generally preferable for comparisons in for a time period to use an the inclusive lower bound with greater than or equal to (>=) and an exclusive upper bound with less-than (<). This is less likely to run into implementation errors affecting date comparisions. |
parameter MeasurementPeriod default Interval[@2020-09-08, @2020-09-09) [Encounter] E where E.period starts during MeasurementPeriod |
The base FHIR specification does not support query by discharge disposition. Some FHIR Servers may be configurable to support this search. See the disposition search parameter for an example of a resource that can be used to support this capability. When present, discharge disposition codes are often populated according to requirements established for payment (e.g., US Medicare payment requirements, rather than treatment.
*The date of the disposition (discharge/transfer or death) may be determined from Encounter.period.end.
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Encounter.status | Encounters that have been completed | Encounter?status=finished | Encounter.where(status='finished') | [Encounter] E where E.status = 'finished' |
Encounter.period.end | Encounters ending on a given date or within a particular period. | Encounter?date=ge2020-09-07T23:59:59&date=eb2020-09-09 Because the date search parameter is compared to a period data type, the upper and lower bounds must be set. |
Encounter.where(period.end >= @2020-09-08 and period.end < @2020-09-09) It is generally preferable for comparisons in for a time period to use an the inclusive lower bound with greater than or equal to (>=) and an exclusive upper bound with less-than (<). This is less likely to run into implementation errors affecting date comparisons. |
parameter MeasurementPeriod default Interval[@2020-09-08, @2020-09-09) [Encounter] E where E.period ends during MeasurementPeriod |
To distinguish between discharge to home vs. a transfer to another facility vs. death, the value of *Encounter.hospitalization.disposionCode must be examined. Usually discharge means “to home” or other non-healthcare setting (e.g., another family member’s home). These cases are shown in more detail below.
A discharge is represented by an Encounter that has been completed in some way, either Encounter.status is “finished” to indicate normal completion, or in some cases, the Encounter.status may be marked as “cancelled” for special cases. Encounter.hospitalization.dispositionCode should be present, and where the the patient was discharged to.
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Encounter.hospitalization.dispositionCode | Transitions to home or similar settings | Encounter?disposition=http://terminology.hl7.org/CodeSystem/discharge-disposition|home, http://terminology.hl7.org/CodeSystem/discharge-disposition|alt-home, http://terminology.hl7.org/CodeSystem/discharge-disposition|aadvice</i>, http://terminology.hl7.org/CodeSystem/discharge-disposition|oth</i> |
Encounter.where( hospitalization.dispositionCode.where(system='http://terminology.hl7.org/CodeSystem/discharge-disposition' and code = ('home'|'alt-home'|'aadvice'|'oth') ) ) |
valueset HomeEnvironment http://example.com/valueset/HomeEvironment [Encounter] E where E.hospitalization.dispositionCode in HomeEnvironment |
A transfer to another facility (inter-facility transfer) is like a discharge, except that the Encounter.hospitalization.dispositionCode should be present and indicates a transfer to a different healthcare setting (e.g., rehabilitation, hospice, long-term care).
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Encounter.hospitalization.dispositionCode | Transitions to other healthcare settings other than home or death | Encounter?disposition=http://terminology.hl7.org/CodeSystem/discharge-disposition|other-hcf, http://terminology.hl7.org/CodeSystem/discharge-disposition|hosp, http://terminology.hl7.org/CodeSystem/discharge-disposition|long, http://terminology.hl7.org/CodeSystem/discharge-disposition|psy, http://terminology.hl7.org/CodeSystem/discharge-disposition|rehab, http://terminology.hl7.org/CodeSystem/discharge-disposition|snf |
Encounter.where( hospitalization.dispositionCode.where(system='http://terminology.hl7.org/CodeSystem/discharge-disposition' and code = ('other-hcf'|'hosp'|'long'|'psy'|'rehab'|'snf') ) ) |
valueset TransferEnvironment http://example.com/valueset/TransferEnvironment [Encounter] E where E.hospitalization.dispositionCode in TransferEnvironment |
Measure developers should provide clarity around the distinctions between discharge and transfer. Is discharge to home-health a “discharge” or a “transfer”? If “long-term care” is the same as “home” for the patient, how would different hospital workflows vary with regard to coding these values?
Not every discharge is a good outcome. Discharge due to death requires special handling because of different hospital workflows used to track the death of a patient.
When testing for death during an encounter using date of death (numbers 3 and 4 above), take care to verify that death occurred during the encounter (i.e., date of death is >= Encounter.period.start and <= Encounter.period.end).
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Encounter.hospitalization.dispositionCode | Transitions due to Death | Encounter?disposition=http://terminology.hl7.org/CodeSystem/discharge-disposition|exp | Encounter.where( hospitalization.dispositionCode.where(system='http://terminology.hl7.org/CodeSystem/discharge-disposition' and code = 'exp') ) ) |
valueset PatientExpired http://example.com/valueset/PatientExpired [Encounter] E where E.hospitalization.dispositionCode in PatientExpired |
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Patient.deceasedDateTime | Patient date of death | Patient?death-date=2020-09-09 | Patient.where(deceasedDateTime = @2020-09-09) | [Patient] P where P.deceasedDateTime = @2020-09-09 |
Patient.deceased[x] | Patient has died | Patient?deceased=true | // Patient has died, or there is a date of death Patient.where(deceasedBoolean = true | deceasedDateTime.exists()) |
[Patient] P where P.deceasedBoolean = true or P.deceasedDateTime is not null |
Patient has died within this encounter | // NOTE: This query uses chained search, and will NOT test that the patient died during the encounter // It will return both encounters and patients so that further analysis can // be performed by the client. This sort of issue should be documented in // Expression.description in the measure definition. Encounter?patient.deceased=true&_include=Encounter:subject |
Encounter.where( // Patient has died during the encounter (resolve(patient).deceasedDateTime >= $this.period.start and resolve(patient).deceasedDateTime <= $this.period.end) or // Encounter is in-progress and patient has died ($this.status = 'in-progress' and // Patient has died, or there is a date of death resolve(patient).where(deceasedDateTime.exists() or deceasedBoolean = true) ) ) |
Context Encounter [Patient] P where P.id = E.patient and // Patient has died during the encounter (P.deceasedDateTime in E.period or // Encounter is in-progress and patient has died (E.status = 'in-progress' and (P.deceasedBoolean = true or P.deceasedDateTime is not null) ) ) |
The type of healthcare service may be determined in a couple of different ways depending on hospital workflow:
Many HL7 standards use the HL7 Version 3 HealthcareServiceLocation vocabulary to describe locations. Based on the U.S. HSLOC Coding System is used to record the type of healthcare service for the HL7 Healthcare Acquired Infections Implementation Guides in both CDA and FHIR. This coding system supports development of value set that can be used in the expression to identify a location supporting a specific type of service.
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Encounter.class | Inpatient (Acute) Encounters | Encounter?class=http://terminology.hl7.org/CodeSystem/v3-ActCode|ACUTE | Encounter.where(status.where(system='http://terminology.hl7.org/CodeSystem/v3-ActCode' and code='ACUTE')) | valueset AcuteEncounter http://example.com/valueset/AcuteEncounter [Encounter] E where E.class in AcuteEncounter |
Encounter.serviceType | Encounters providing a specific kind of service | // NOTE: Requires custom search parameter for service-type Encounter?serviceType=http://terminology.hl7.org/CodeSystem/service-type|237 | Encounter.where(serviceType.where(system='http://terminology.hl7.org/CodeSystem/service-type' and code='237') | valueset EncounterServiceType http://example.com/valueset/EncounterServiceType [Encounter] E where E.serviceType in EncounterServiceType |
Location.type | Encounters in a specific type of location | // NOTE: Used chained search for Encounters in a hospital unit Encounter?location.type=http://terminology.hl7.org/ValueSet/v3-ServiceDeliveryLocationRoleType|HU | Encounter.where(location.physicalType.where(system='http://terminology.hl7.org/ValueSet/v3-ServiceDeliveryLocationRoleType' and code='HU')) | valueset LocationType http://example.com/valueset/LocationType context Encounter [Location] L where L.id = Encounter.location L.type in LocationType |
Some facilities, especially under stress will house patients in temporary locations, known as surge or overflow locations. NHSN defined these thus:
Overflow locations include any physical locations created to accommodate patients including but not > limited to 24-hour observation units, hallways, parking lots, or tents.
There is no common workflow or model used to represent this sort of situation. This IG recommends the use of a special code within Encounter.location.physicalType to identify a location that is a temporary location. This value should also appear within Location.physicalType in the location resource referenced by Encounter.location.location.
Patient conditions or symptoms may appear in several places, with different degrees of confidence in the patient having the condition (e.g., admission, preliminary, differential, possible, or confirmed diagnosis).
In the context of a given encounter, the condition may appear in:
NOTE: In all cases where Condition is used, consider also the values of Condition.verificationStatus
and Condition.clinicalStatus
during evaluation. The verificationStatus
indicates whether the condition is unconfirmed, provisional, confirmed or even refuted or entered-in-error. Note that the last two cases the patient does NOT have the condition. The clinicalStatus
may describe clinical status of the condition of interest, using the value active, inactive or resolved. Again, note that resolved indicates the condition is no longer active.
The Condition.onset[x]
and Condition.abatement[x]
fields identity the time frame over which the condition was active. The Condition.recordedDate
indicates when the provider recorded the condition in the system.
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Encounter.reasonCode | Encounter for a specific disease by code | Encounter?reason-code=http://snomed.info/sct|186747009,http://snomed.info/sct|713084008,http://snomed.info/sct|840539006,http://snomed.info/sct|840544004 | Encounter.where(reasonCode.where(system='http://snomed.info/sct' and code=('186747009','713084008','840539006','840544004'))) | valueset AcuteEncounter http://example.com/valueset/AcuteEncounter [Encounter] E where E.class in AcuteEncounter |
Encounter.reasonReference | Encounters referencing a condition resource | // Chained search Encounter.reasonReference.code=http://snomed.info/sct|186747009, http://snomed.info/sct|713084008, http://snomed.info/sct|840539006, http://snomed.info/sct|840544004 |
Encounter.where(reasonReference.resolve().code.where(system='http://snomed.info/sct' and code=('186747009','713084008','840539006','840544004')) |
valueset COVID19Conditions http://example.com/valueset/COVID19Conditions [Condition] C where C.id = Encounter.reasonReference and C.code in COVID19Conditions |
Condition.code | Condition recorded for a given encounter | Condition?encounter=encounterId& code=http://snomed.info/sct|186747009,http://snomed.info/sct|713084008,http://snomed.info/sct|840539006,http://snomed.info/sct|840544004 |
Condition.where(system='http://snomed.info/sct' and code=('186747009','713084008','840539006','840544004')) and encounter=encounterId |
valueset COVID19Conditions http://example.com/valueset/COVID19Conditions context Encounter [Condition] C where C.encounter = Encounter.id C.code in COVID19Conditions |
Several different kinds of measures can be based on codes describing a diagnostic tests, a procedure, or other activity having been performed, and in the case of diagnostic tests, combinations including both the test code and result value.
Electronic laboratory reporting is used to track both the kinds of tested performed as well as the results. A commonly reported measure for COVID-19 is the number of COVID-19 diagnostic tests performed, regardless of outcome, where the results are then stratified by outcome. This can be counted by looking for the existence of an Observation or Procedure. Reporting of certain kinds of observations (e.g., Fraction of Inhaled Oxygen or Positive End Expiratory Pressure) are commonly reported for patients who are on a ventilator. Existence of these observations indicate that a patient is on a ventilator.
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Observation.code | Diagnostic Result from a COVID-19 test | Observation?code=http://loinc.org|94307-6,http://loinc.org|94308-4,http://loinc.org|94309-2,http://loinc.org|94310-0, http://loinc.org|94314-2,http://loinc.org|94315-9,http://loinc.org|94316-7,http://loinc.org|94500-6, http://loinc.org|94533-7,http://loinc.org|94534-5,http://loinc.org|94558-4,http://loinc.org|94559-2 |
Observation.where(code.where(system = 'http://loinc.org' and code = ('19994-3','19995-0','19996-8','94310-0','94314-2','94315-9','94316-7','94500-6','94533-7','94534-5','94558-4','94559-2') ) ) |
valueset SarsCoV2Labs http://example.com/valueset/SarsCoV2Labs [Observation] O where O.code in SarsCoV2Labs |
Observation.code | Patients with observations indicating that they are on a ventilator | Observation?code=http://loinc.org|19835-8,http://loinc.org|19994-3,http://loinc.org|20077-4,http://loinc.org|20079-0,http://loinc.org|20103-8, http://loinc.org|20112-9,http://loinc.org|20115-2,http://loinc.org|33438-3,http://loinc.org|57655-3,http://loinc.org|76530-5,http://loinc.org|19839-0 |
Observation.where(code.where(system = 'http://loinc.org' and code = ('19835-8','19994-3','20077-4','20079-0','20103-8','20112-9','20115-2','33438-3','57655-3','76530-5','19839-0') ) ) |
valueset PatientsOnVentilator http://example.com/valueset/PatientsOnVentilator [Observation] O where O.code in PatientsOnVentilator |
The handling of Procedure or Immunization resources is similar. For Procedure, change Observation above to Procedure. For Immunization, change Observation to Immunization, and code to vaccine-code for FHIR Query, or to vaccineCode for FHIRPath and CQL.
To test for a specific coded result, add the following clauses to the above expressions:
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Observation.valueCodeableConcept | Coded Result matching a particular value | &value-concept=http://snomed.info/sct|260385009 | Observation.where( ... and valueCodeableConcept.where(system='http://snomed.info/sct' and value='260385009')) | valueset NegativeResults http://example.com/valueset/NegativeResults [Observation] O where O.valueCodeableConcept in NegativeResults |
Observation.interpretation | A result interpretation of a specified value | // NOTE: Requires custom search parameter for interpretation &interpretation=http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation|NEG | Observation.where( ... and interpretation.where(system='http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation' and value='NEG')) | valueset NegativeInterpretations http://example.com/valueset/NegativeInterpretations [Observation] O where O.interpreation in NegativeInterpretations |
Field | Description | FHIR Query | FHIR Path | CQL |
---|---|---|---|---|
Observation.valueQuantity | Quantity above/below/with a range | &value-quantity=gt5.4|http://unitsofmeasure.org|mg &value-quantity=lt5.4|http://unitsofmeasure.org|mg &value-quantity=gt5.4|http://unitsofmeasure.org|mg&value-quantity=lt8.0|http://unitsofmeasure.org|mg |
Observation.where( ... and valueQuantity > 5.4 'mg') Observation.where( ... and valueQuantity < 5.4 'mg') Observation.where( ... and valueQuantity > 5.4 'mg' and valueQuantity < 8.0 'mg') |
[Observation] O where O.valueQuantity > 5.4 'mg' [Observation] O where O.valueQuantity < 5.4 'mg' [Observation] O where O.valueQuantity > 5.4 'mg' and O.valueQuantity < 8.0 'mg' </tr> </tbody> </table> ### Handling Temporal Relationships In the example below, NHSN defined HOSPITAL ONSET for COVID-19 as shown below: > HOSPITAL ONSET: Patients currently hospitalized in an inpatient bed with onset of suspected or confirmed COVID-19 fourteen or more days after hospital admission due to a condition other than COVID-19 This kind of query cannot be handled directly using a FHIR Search query, as it requires computing a relationship between to related resources. In FHIRPath, assuming both the encounter and condition are available in the current set of results, one would write: ``` Condition.where(resolve(encounter).period.start + 14 'days' < onsetDateTime) ``` This expression will select Condition resources for which the associated Condition.encounter.period.start date plus 14 days is less than the time of onset of the condition. A similar expression can be written for CQL: ``` [Condition] C where C.encounter = Encounter.id and C.code in COVID19Conditions and C.onset - 14 days > Encounter.period.start ``` |