Clinical Practice Guidelines
1.0.0 - ci-build International flag

Clinical Practice Guidelines, published by HL7 International - Clinical Decision Support WG. This is not an authorized publication; it is the continuous build for version 1.0.0). This version is based on the current content of and changes regularly. See the Directory of published versions

Library: Opioid CDS R4 Common Logic (Experimental)

Official URL: Version: 1.0.0
Active as of 2018-03-25 Computable Name: OpioidCDSR4Common

Usage:Clinical Focus: Medication requested (situation), Clinical Focus: Chronic pain (finding)

Copyright/Legal: © CDC 2016+.

Common Opioid Decision Support Logic for use in implementing CDC Opioid Prescribing Guidelines.

This library contains common logic across recommendations including MME calculations, conversions, and looking up codes in valuesets.


AuthorKensaku Kawamoto, MD, PhD, MHS
AuthorBryn Rhodes
AuthorFloyd Eisenberg, MD, MPH
AuthorRobert McClure, MD, MPH

Related Artifacts

documentationCDC guideline for prescribing opioids for chronic pain



library OpioidCDSR4Common version '0.1.0'

using FHIR version '4.0.0'

include FHIRHelpers version '4.0.0' called FHIRHelpers
include OMTKLogic version '0.0.0' called OMTKLogic

codesystem "SNOMED": ''
codesystem "Medication Request Category Codes": ''

valueset "Active Condition": ''
valueset "Benzodiazepines": ''
// TODO: Fix this name
valueset "End of Life Conditions": ''
// Harvested from VSAC - OID: 2.16.840.1.113762.1.4.1108.15
// NOTE: This harvest note is incorrect, none of the following 3 value sets contain any of the codes in the above referenced valueset
// Rob will construct an appropriate hospice value set aligned with current eCQM program usage and we will use that when available
valueset "Hospice Disposition": ''
valueset "Hospice Finding Codes": ''
valueset "Hospice Procedure Codes": ''
valueset "Illicit Drug Screening": ''
// Harvested from VSAC - OID: 2.16.840.1.113883.3.526.3.1259
valueset "Limited Life Expectancy Conditions": ''
valueset "Long Acting Opioids": ''
valueset "Naloxone": ''
valueset "Risk Assessment": ''
valueset "Opioid Drug Screening": ''
valueset "Ambulatory Abuse Potential Opioids": ''
valueset "Substance Abuse": ''

// TODO: Turn this into a valueset
code "Referral to Hospice": '306205009' from "SNOMED"
// TODO: Turn this into a valueset
code "Outpatient": 'outpatient' from "Medication Request Category Codes"

// TODO: Capture process decisions for long-term opioid use
define IsForChronicPain: true

define "Active Ambulatory Opioid Rx":
  [MedicationRequest: "Ambulatory Abuse Potential Opioids"] Rx
    where Rx.status = 'active'
      and ToCodes(Rx.category.coding) contains "Outpatient"

define "Active Ambulatory Benzodiazepine Rx":
  [MedicationRequest: "Benzodiazepines"] Rx
    where Rx.status = 'active'
      and ToCodes(Rx.category.coding) contains "Outpatient"

define "Active Ambulatory Naloxone Rx":
  [MedicationRequest: "Naloxone"] Rx
    where Rx.status = 'active'
      and ToCodes(Rx.category.coding) contains "Outpatient"

define "Ambulatory Opioid Rx":
  [MedicationRequest: "Ambulatory Abuse Potential Opioids"] Rx
      where ToCodes(Rx.category.coding) contains "Outpatient"

define "End of Life Assessment":
  // 1. Conditions indicating end of life or with limited life expectancy
  exists (
      [Condition: "End of Life Conditions"] C
        where C.clinicalStatus in "Active Condition"
      [Condition: code in "Limited Life Expectancy Conditions"] C
        where C.clinicalStatus in "Active Condition"
  // 2. Admitted/referred/discharged to hospice care
  or exists (
      [ServiceRequest: code in "Referral to Hospice"] RR
        where RR.status in { 'active', 'completed' }
      [ServiceRequest: code in "Hospice Procedure Codes"] P
        where P.status in { 'in-progress', 'completed' }
      [ServiceRequest: code in "Hospice Procedure Codes"] E
        where E.status in { 'planned', 'arrived', 'in-progress', 'finished', 'onleave' }
      [Observation: code in "Hospice Finding Codes"] O
        where not (O.status in { 'unknown', 'entered-in-error', 'cancelled' })
      [Encounter] E
            if E.hospitalization.dischargeDisposition.coding is null
                or not exists (E.hospitalization.dischargeDisposition.coding)
              then false
            else E.hospitalization.dischargeDisposition in "Hospice Disposition"
          and E.status in { 'planned', 'arrived', 'in-progress', 'finished', 'onleave' }

define function Prescriptions(Orders List<MedicationRequest>):
  Orders O
      // NOTE: Assuming medication is specified as a CodeableConcept with a single RxNorm code
      rxNormCode: ToCode(O.medication.coding[0]),
      medicationName: OMTKLogic.GetMedicationName(rxNormCode),
      // NOTE: Assuming a single dosage instruction element
      dosageInstruction: O.dosageInstruction[0],
      repeat: dosageInstruction.timing.repeat,
      frequency: Coalesce(repeat.frequencyMax.value, repeat.frequency.value),
      period: System.Quantity { value: repeat.period.value, unit: repeat.periodUnit.value },
          // There should be a conversion from FHIR.SimpleQuantity to System.Quantity
          if dosageInstruction.doseAndRate[0].dose is FHIR.Range
            then ToString(ToQuantity(dosageInstruction.doseAndRate[0].dose.low))
                          + '-' + ToString(ToQuantity(dosageInstruction.doseAndRate[0].dose.high))
                          + dosageInstruction.doseAndRate[0].dose.high.unit.value
            else ToString(ToQuantity(dosageInstruction.doseAndRate[0].dose)),
        ToString(dosageInstruction.timing.repeat.frequency.value) +
            '-' + ToString(dosageInstruction.timing.repeat.frequencyMax.value),
    return {
      rxNormCode: rxNormCode,
      isDraft: O.status.value = 'draft',
      // NOTE: Assuming asNeeded is expressed as a boolean
      isPRN: dosageInstruction.asNeeded.value,
        if dosageInstruction.text is not null then
          medicationName + ' ' + dosageInstruction.text.value
          // TODO: Shouldn't need the .value here on asNeededBoolean
          medicationName + ' ' + doseDescription + ' q' + frequencyDescription + (if dosageInstruction.asNeeded.value then ' PRN' else ''),
      // TODO: Shouldn't need the ToQuantity here...
      dose: if dosageInstruction.doseAndRate[0].dose is FHIR.Range
            then ToQuantity(dosageInstruction.doseAndRate[0].dose.high)
            else ToQuantity(dosageInstruction.doseAndRate[0].dose),
      dosesPerDay: Coalesce(OMTKLogic.ToDaily(frequency, period), 1.0)

define function MME(prescriptions List<MedicationRequest>):
  (Prescriptions(prescriptions)) P
    let mme: SingletonFrom(OMTKLogic.CalculateMMEs({ { rxNormCode: P.rxNormCode, doseQuantity: P.dose, dosesPerDay: P.dosesPerDay } }))
    return {
      rxNormCode: P.rxNormCode,
      isDraft: P.isDraft,
      isPRN: P.isPRN,
      prescription: P.prescription,
      dailyDose: mme.dailyDoseDescription,
      conversionFactor: mme.conversionFactor,
      mme: mme.mme

define function TotalMME(prescriptions List<MedicationRequest>):
  System.Quantity {
    value: Sum((MME(prescriptions)) M return M.mme.value),
    unit: 'mg/d'

define function ProbableDaysInRange(Orders List<MedicationRequest>, daysPast Integer, numDaysInDaysPast Integer):
  Orders orders
      frequency: orders.dosageInstruction[0].timing.repeat.frequency.value,
      period: orders.dosageInstruction[0].timing.repeat.period.value,
      periodDays: GetPeriodDays(orders.dosageInstruction[0].timing.repeat.periodUnit.value),
        if (frequency / (period * periodDays)) >= 1.0
        then 1.0
        else frequency / (period * periodDays),
      repeat: orders.dispenseRequest.numberOfRepeatsAllowed.value,
      supplyDuration: GetDurationInDays(orders.dispenseRequest.expectedSupplyDuration),
      validityPeriod: days between orders.dispenseRequest.validityPeriod."start".value and Today(),
        if orders.dispenseRequest.validityPeriod."end".value < Today()
        then days between orders.dispenseRequest.validityPeriod."end".value and Today()
        else 0
      if (repeat * supplyDuration) < numDaysInDaysPast then false
        (dosesPerDay * ((repeat * supplyDuration) / validityPeriod) * (daysPast - endDifference)) >= numDaysInDaysPast

define function GetPeriodDays(value System.String):
    when value = 'a' then 365.0
    when value = 'mo' then 30.0
    when value = 'h' then 1.0/24.0
    when value = 'min' then 1.0/24.0*60.0
    when value = 's' then 1.0/24.0*60.0*60.0
    when value = 'ms' then 1.0/24.0*60.0*60.0*1000.0
    else 1.0

  define function GetDurationInDays(value FHIR.Duration):
      when StartsWith(value.unit.value, 'a') then value.value.value * 365.0
      when StartsWith(value.unit.value, 'mo') then value.value.value * 30.0
      when StartsWith(value.unit.value, 'wk') then value.value.value * 7.0
      when StartsWith(value.unit.value, 'd') then value.value.value
      when StartsWith(value.unit.value, 'h') then value.value.value / 24.0
      when StartsWith(value.unit.value, 'min') then value.value.value / 60.0 / 24.0
      when StartsWith(value.unit.value, 's') then value.value.value / 60.0 / 60.0 / 24.0
      when StartsWith(value.unit.value, 'ms') then value.value.value / 60.0 / 60.0 / 24.0 / 1000.0
      else Message(1000, true, 'Undefined', 'Error', 'Unsupported duration unit')

define function GetIngredient(rxNormCode Code):

define function GetIngredients(rxNormCodes List<Code>):
  rxNormCodes rnc return GetIngredient(rnc)

define function GetMedicationNames(medications List<MedicationRequest>):
  medications M
    return OMTKLogic.GetIngredients(ToRxNormCode(M.medication.coding)).rxNormCode.display

*  Conversion Functions

define function ToCode(coding FHIR.Coding):
  System.Code {
    code: coding.code.value,
    system: coding.system.value,
    version: coding.version.value,
    display: coding.display.value

define function ToCodes(coding List<FHIR.Coding>):
  coding c return ToCode(c)

define function ToRxNormCode(coding List<FHIR.Coding>):
  singleton from (
    coding C where C.system = ''

define function ToQuantity(quantity FHIR.Quantity):
  System.Quantity { value: quantity.value.value, unit: quantity.unit.value }

No Content (application/elm+xml)