CPG Opioid Prescribing Guideline Examples
1.0.0 - ci-build International flag

CPG Opioid Prescribing Guideline Examples, published by HL7 International - Clinical Decision Support WG. This guide is not an authorized publication; it is the continuous build for version 1.0.0 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/cqframework/cpg-example-opioids/ and changes regularly. See the Directory of published versions

Library: Opioid CDS R4 Common Logic (Experimental)

Official URL: http://hl7.org/fhir/uv/cpg/opiods/Library/OpioidCDSR4Common Version: 1.0.0
Active as of 2024-03-01 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.

Participants

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

Related Artifacts

documentationCDC guideline for prescribing opioids for chronic painhttps://www.cdc.gov/mmwr/volumes/65/rr/rr6501e1.htm?CDC_AA_refVal=https%3A%2F%2Fwww.cdc.gov%2Fmmwr%2Fvolumes%2F65%2Frr%2Frr6501e1er.htm
depends-onLibrary OMTKLogichttp://hl7.org/fhir/uv/cpg/opioids/Library/OMTKLogic|0.0.0
depends-onCode System SNOMEDSNOMED CT (all versions)
depends-onCode System Medication Request Category CodesMedicationRequest Category Codes
depends-onValue Set Active ConditionCPG Active Condition Value Set
depends-onValue Set Benzodiazepineshttp://fhir.org/guides/cdc/opioid-cds/ValueSet/benzodiazepines-medications
depends-onValue Set End of Life Conditionshttp://fhir.org/guides/cdc/opioid-cds/ValueSet/end-of-life-conditions
depends-onValue Set Hospice Dispositionhttp://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-disposition
depends-onValue Set Hospice Finding Codeshttp://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-finding
depends-onValue Set Hospice Procedure Codeshttp://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-procedure
depends-onValue Set Illicit Drug Screeninghttp://fhir.org/guides/cdc/opioid-cds/ValueSet/illicit-drug-urine-screening
depends-onValue Set Limited Life Expectancy Conditionshttp://fhir.org/guides/cdc/opioid-cds/ValueSet/limited-life-expectancy-conditions
depends-onValue Set Long Acting Opioidshttp://fhir.org/guides/cdc/opioid-cds/ValueSet/long-acting-opioids
depends-onValue Set Naloxonehttp://fhir.org/guides/cdc/opioid-cds/ValueSet/naloxone-medications
depends-onValue Set Risk Assessmenthttp://fhir.org/guides/cdc/opioid-cds/ValueSet/opioid-abuse-assessment
depends-onValue Set Opioid Drug Screeninghttp://fhir.org/guides/cdc/opioid-cds/ValueSet/opioid-urine-screening
depends-onValue Set Ambulatory Abuse Potential Opioidshttp://fhir.org/guides/cdc/opioid-cds/ValueSet/opioids-abused-in-ambulatory-care
depends-onValue Set Substance Abusehttp://fhir.org/guides/cdc/opioid-cds/ValueSet/substance-abuse

Parameters

IsForChronicPainout01boolean
Active Ambulatory Opioid Rxout0*MedicationRequest
Active Ambulatory Benzodiazepine Rxout0*MedicationRequest
Active Ambulatory Naloxone Rxout0*MedicationRequest
Ambulatory Opioid Rxout0*MedicationRequest
End of Life Assessmentout01boolean

Data Requirements

Type: MedicationRequest
FilterValue
medicationIn ValueSet http://fhir.org/guides/cdc/opioid-cds/ValueSet/opioids-abused-in-ambulatory-care
Type: MedicationRequest
Type: Medication
Type: MedicationRequest
FilterValue
medicationIn ValueSet http://fhir.org/guides/cdc/opioid-cds/ValueSet/benzodiazepines-medications
Type: MedicationRequest
Type: Medication
Type: MedicationRequest
FilterValue
medicationIn ValueSet http://fhir.org/guides/cdc/opioid-cds/ValueSet/naloxone-medications
Type: MedicationRequest
Type: Medication
Type: MedicationRequest
FilterValue
medicationIn ValueSet http://fhir.org/guides/cdc/opioid-cds/ValueSet/opioids-abused-in-ambulatory-care
Type: MedicationRequest
Type: Medication
Type: Condition
FilterValue
codeIn ValueSet http://fhir.org/guides/cdc/opioid-cds/ValueSet/end-of-life-conditions
Type: Condition
FilterValue
codeIn ValueSet http://fhir.org/guides/cdc/opioid-cds/ValueSet/limited-life-expectancy-conditions
Type: ServiceRequest
FilterValue
codeOne of these codes: SNOMED CT (all versions) 306205009: Referral to hospice
Type: ServiceRequest
FilterValue
codeIn ValueSet http://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-procedure
Type: ServiceRequest
FilterValue
codeIn ValueSet http://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-procedure
Type: Observation
FilterValue
codeIn ValueSet http://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-finding
Type: Encounter

Contents

text/cql

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": 'http://snomed.info/sct'
codesystem "Medication Request Category Codes": 'http://terminology.hl7.org/CodeSystem/medicationrequest-category'

valueset "Active Condition": 'http://hl7.org/fhir/uv/cpg/ValueSet/cpg-active-condition-vs'
valueset "Benzodiazepines": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/benzodiazepines-medications'
// TODO: Fix this name
valueset "End of Life Conditions": 'http://fhir.org/guides/cdc/opioid-cds/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": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-disposition'
valueset "Hospice Finding Codes": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-finding'
valueset "Hospice Procedure Codes": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/hospice-procedure'
valueset "Illicit Drug Screening": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/illicit-drug-urine-screening'
// Harvested from VSAC - OID: 2.16.840.1.113883.3.526.3.1259
valueset "Limited Life Expectancy Conditions": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/limited-life-expectancy-conditions'
valueset "Long Acting Opioids": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/long-acting-opioids'
valueset "Naloxone": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/naloxone-medications'
valueset "Risk Assessment": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/opioid-abuse-assessment'
valueset "Opioid Drug Screening": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/opioid-urine-screening'
valueset "Ambulatory Abuse Potential Opioids": 'http://fhir.org/guides/cdc/opioid-cds/ValueSet/opioids-abused-in-ambulatory-care'
valueset "Substance Abuse": 'http://fhir.org/guides/cdc/opioid-cds/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"
    )
    union
    (
      [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' }
    )
    union
    (
      [ServiceRequest: code in "Hospice Procedure Codes"] P
        where P.status in { 'in-progress', 'completed' }
    )
    union
    (
      [ServiceRequest: code in "Hospice Procedure Codes"] E
        where E.status in { 'planned', 'arrived', 'in-progress', 'finished', 'onleave' }
    )
    union
    (
      [Observation: code in "Hospice Finding Codes"] O
        where not (O.status in { 'unknown', 'entered-in-error', 'cancelled' })
    )
    union
    (
      [Encounter] E
        where
          (
            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
    let
      // 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 },
      doseDescription:
        Coalesce(
          // 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)),
            ''
        ),
      frequencyDescription:
        ToString(dosageInstruction.timing.repeat.frequency.value) +
          Coalesce(
            '-' + 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,
      prescription:
        if dosageInstruction.text is not null then
          medicationName + ' ' + dosageInstruction.text.value
        else
          // 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
    let
      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),
      dosesPerDay:
        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(),
      endDifference:
        if orders.dispenseRequest.validityPeriod."end".value < Today()
        then days between orders.dispenseRequest.validityPeriod."end".value and Today()
        else 0
    return
      if (repeat * supplyDuration) < numDaysInDaysPast then false
      else
        (dosesPerDay * ((repeat * supplyDuration) / validityPeriod) * (daysPast - endDifference)) >= numDaysInDaysPast

define function GetPeriodDays(value System.String):
  case
    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
  end

  define function GetDurationInDays(value FHIR.Duration):
    case
      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')
    end

define function GetIngredient(rxNormCode Code):
  OMTKLogic.GetIngredients(rxNormCode).ingredientName

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 = 'http://www.nlm.nih.gov/research/umls/rxnorm'
  )

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

Content not shown - (application/elm+xml, size = 266Kb)