2022 CDC Clinical Practice Guideline for Prescribing Opioids Implementation Guide
2022.1.0 - CI Build

2022 CDC Clinical Practice Guideline for Prescribing Opioids Implementation Guide, published by Centers for Disease Control and Prevention (CDC). This guide is not an authorized publication; it is the continuous build for version 2022.1.0 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/cqframework/opioid-cds-r4/ and changes regularly. See the Directory of published versions

Library: Opioid Management Terminology Knowledge Logic

Official URL: http://fhir.org/guides/cdc/opioid-cds/Library/OMTKLogic Version: 2022.1.0
Active as of 2022-02-01 Realm: Unknown region code '840' Computable Name: OMTKLogic

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

Copyright/Legal: Copyright 2019+ Centers for Disease Control and Prevention (CDC)

This library provides functionality for calculating Morphine Milligram Equivalents (MME) for opioid medications, as described in the CDC Opioid Prescribing Guideline.

Id: OMTKLogic
Url: http://fhir.org/guides/cdc/opioid-cds/Library/OMTKLogic
Version: 3.0.0
Name: OMTKLogic
Title: Opioid Management Terminology Knowledge Logic
Status: active
Experimental: false
Type:

system: http://terminology.hl7.org/CodeSystem/library-type

code: logic-library

Date: 2022-02-01T20:35:24+00:00
Publisher: Alphora
Description: This library provides functionality for calculating Morphine Milligram Equivalents (MME) for opioid medications, as described in the CDC Opioid Prescribing Guideline.
Knowledge Capability: shareable computable publishable executable
Knowledge Representation Level: structured
Use Context:
codevaluedisplay
focus 182888003 Medication requested (situation)
focus 82423001 Chronic pain (finding)
Jurisdiction: 840
Usage: NOTE: Do not use the calculated dose in MMEs to determine dosage for converting one opioid to another—the new opioid should be lower to avoid unintentional overdose caused by incomplete cross-tolerance and individual differences in opioid pharmacokinetics. Consult the medication label.
Copyright: Copyright 2019+ Centers for Disease Control and Prevention (CDC)
Last Review Date: 2020-09-26
Related Artifacts:

Documentation

References

  • Nelson SJ, Zeng K, Kilbourne J, Powell T, Moore R. Normalized names for clinical drugs: RxNorm at 6 years.
    J Am Med Inform Assoc. 2011 Jul-Aug;18(4)441-8. doi: 10.1136/amiajnl-2011-000116.
    Epub 2011 Apr 21. PubMed PMID: 21515544; PubMed Central PMCID: PMC3128404.
    [Full Text](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3128404/)
    https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3128404/
  • Schadow G, McDonald CJ. The Unified Code for Units of Measure. Regenstrief Institute, Inc. and the UCUM Organization, 2017. Version 2.1, Revision 442. http://unitsofmeasure.org
    http://unitsofmeasure.org
  • Dowell D, Haegerich TM, Chou R. CDC Guideline for Prescribing Opioids for Subacute or Chronic Pain — United States, 2016. MMWR Recomm Rep 2016;65(No. RR-1):1–49. DOI: http://dx.doi.org/10.15585/mmwr.rr6501e1
    http://dx.doi.org/10.15585/mmwr.rr6501e1
  • National Center for Injury Prevention and Control. Calculating total daily dose of opioids for safer dosage.
    Atlanta, GA: Centers for Disease Control and Prevention; https://www.cdc.gov/drugoverdose/pdf/calculating_total_daily_dose-a.pdf, accessed November 19th, 2020
    https://www.cdc.gov/drugoverdose/pdf/calculating_total_daily_dose-a.pdf

Dependencies

  • http://fhir.org/guides/cdc/opioid-mme-r4/Library/OMTKData|3.0.0
  • http://fhir.org/guides/cdc/opioid-mme-r4/Library/ConversionFactors|3.0.0
  • http://www.nlm.nih.gov/research/umls/rxnorm
  • http://terminology.hl7.org/CodeSystem/usage-context-type
  • http://fhir.org/guides/cdc/opioid-mme-r4/CodeSystem/CDCMMEUsageContextCodes
Parameters:
NameTypeMinMaxIn/Out
ErrorLevelstring01in
ConversionFactorSupplementNamestring01in
Content: text/cql
library OMTKLogic version '3.0.0'

/*
This library provides functionality for calculating Morphine Milligram
Equivalents (MME) for opioid medications, as described in the CDC Opioid
Prescribing Guideline.

The functionality in this library was developed based on the Java-based
implementation described [here](http://build.fhir.org/ig/cqframework/opioid-cds-r4/service-documentation.html#solution-component-3-core-logic-processing-java-class),
as well as the MME conversion calculation published as part of the CDC Opioid
Prescribing Guideline.

Note that the logic in this library (and specifically the conversion factors captured here)
are based on the 2016 version of the publication available from the CDC here:
https://www.cdc.gov/drugoverdose/modules/data-files.html

National Center for Injury Prevention and Control. CDC compilation of benzodiazepines,
muscle relaxants, stimulants, zolpidem, and opioid analgesics with oral morphine milligram
equivalent conversion factors, 2018 version. Atlanta, GA: Centers for Disease Control
and Prevention; 2018. Available at https://www.cdc.gov/drugoverdose/resources/data.html

This version of the OMTKLogic library uses the OMTKData library as the
source for drug ingredient and strength information, rather than the
OMTK data source. This library has no external dependencies and so
can run in an environment that supports pure CQL.

NOTE: For performance, all terminology comparisons in this library use
direct integer comparison of the RxNorm codes.

This product uses publicly available data courtesy of the U.S. National Library of Medicine (NLM),
National Institutes of Health, Department of Health and Human Services; NLM is not responsible for
the product and does not endorse or recommend this or any other product.

Nelson SJ, Zeng K, Kilbourne J, Powell T, Moore R. Normalized names for clinical drugs: RxNorm at 6 years.
J Am Med Inform Assoc. 2011 Jul-Aug;18(4)441-8. doi: 10.1136/amiajnl-2011-000116.
Epub 2011 Apr 21. PubMed PMID: 21515544; PubMed Central PMCID: PMC3128404.
[Full text](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3128404/)
*/

include OMTKData version '3.0.0' called OMTKData
include ConversionFactors version '3.0.0' called ConversionFactors

codesystem "RXNORM": 'http://www.nlm.nih.gov/research/umls/rxnorm'

parameter ErrorLevel String default 'Warning'

define function Msg(code String, errorLevel String, message String):
  {
    code: code,
    errorLevel: errorLevel,
    message: message
  }

/*
Normalizes the input units to UCUM units

The values listed here are the only ones currently present in the OMTK data

Based on the HL7 UCUM subset here:
http://download.hl7.de/documents/ucum/ucumdata.html
*/
define function ToUCUM(unit String):
  case unit
    when 'MG' then 'mg'
    when 'MG/ACTUAT' then 'mg/{actuat}'
    when 'MG/HR' then 'mg/h'
    when 'MG/ML' then 'mg/mL'
    else Message(null, true, 'OMTKLogic.ToUCUM.UnknownUnit', ErrorLevel, 'Unknown unit ' & unit)
  end

define function ToUCUM_Msg(unit String):
  unit U
    let result: ToUCUM(U)
    return {
      result: result,
      message:
        if result is null then
          Msg('OMTKLogic.ToUCUM.UnknownUnit', ErrorLevel, 'Unknown unit' & unit)
        else
          null
    }

/*
  Calculates daily frequency given frequency within a period
*/
define function ToDaily(frequency Integer, period Quantity):
  case period.unit
    when 'h' then frequency * (24.0 / period.value)
    when 'min' then frequency * (24.0 / period.value) * 60
    when 's' then frequency * (24.0 / period.value) * 60 * 60
    when 'd' then frequency * (24.0 / period.value) / 24
    when 'wk' then frequency * (24.0 / period.value) / (24 * 7)
    when 'mo' then frequency * (24.0 / period.value) / (24 * 30) /* assuming 30 days in month */
    when 'a' then frequency * (24.0 / period.value) / (24 * 365) /* assuming 365 days in year */
    when 'hour' then frequency * (24.0 / period.value)
    when 'minute' then frequency * (24.0 / period.value) * 60
    when 'second' then frequency * (24.0 / period.value) * 60 * 60
    when 'day' then frequency * (24.0 / period.value) / 24
    when 'week' then frequency * (24.0 / period.value) / (24 * 7)
    when 'month' then frequency * (24.0 / period.value) / (24 * 30) /* assuming 30 days in month */
    when 'year' then frequency * (24.0 / period.value) / (24 * 365) /* assuming 365 days in year */
    when 'hours' then frequency * (24.0 / period.value)
    when 'minutes' then frequency * (24.0 / period.value) * 60
    when 'seconds' then frequency * (24.0 / period.value) * 60 * 60
    when 'days' then frequency * (24.0 / period.value) / 24
    when 'weeks' then frequency * (24.0 / period.value) / (24 * 7)
    when 'months' then frequency * (24.0 / period.value) / (24 * 30) /* assuming 30 days in month */
    when 'years' then frequency * (24.0 / period.value) / (24 * 365) /* assuming 365 days in year */
    else Message(null, true, 'OMTKLogic.ToDaily.UnknownUnit', ErrorLevel, 'Unknown unit ' & period.unit)
  end

define function ToDaily_Msg(frequency Integer, period Quantity):
  frequency F
    let result: ToDaily(frequency, period)
    return {
      result: result,
      message:
        if result is null then
          Msg('OMTKLogic.ToDaily.UnknownUnit', ErrorLevel, 'Unknown unit ' & period.unit)
        else
          null
    }

/*
Returns the opioid ingredients and their strengths that
make up the drug identified by the given rxNormCode as a list of tuples:

List<Tuple {
  rxNormCode Code,
  doseFormCode Code,
  doseFormName String,
  ingredientCode Code,
  ingredientName String,
  strength Quantity
}>
*/

/*
GetIngredients:
  List<{
    rxNormCode Code,
    doseFormCode Code,
    ingredientCode code,
    strength Quantity
  }>
*/
define function GetIngredients(rxNormCode Code):
  OMTKData.DrugIngredients DI
    where DI.drugCode = ToInteger(rxNormCode.code)
    return {
      rxNormCode: Code { code: ToString(DI.drugCode), system: 'http://www.nlm.nih.gov/research/umls/rxnorm', display: DI.drugName },
      doseFormCode: Code { code: ToString(DI.doseFormCode), system: 'http://www.nlm.nih.gov/research/umls/rxnorm', display: DI.doseFormName },
      ingredientCode: Code { code: ToString(DI.ingredientCode), system: 'http://www.nlm.nih.gov/research/umls/rxnorm', display: DI.ingredientName },
      strength: Quantity {
        value: DI.strengthValue,
        unit: ToUCUM(DI.strengthUnit)
      }
    }

/*
Returns the first RxNorm code in the given concept that matches a drug code
specified in OMTKData.
*/
define function GetMedicationCode(concept Concept):
  First(
    ((concept.codes) C
      where C.system = 'http://www.nlm.nih.gov/research/umls/rxnorm'
        return singleton from (
          OMTKData.DrugIngredients DI
            where DI.drugCode = ToInteger(C.code)
            return Code {
              code: ToString(DI.drugCode),
              system: 'http://www.nlm.nih.gov/research/umls/rxnorm',
              display: DI.drugName
            }
        )
    ) X
      where X is not null
  )

/*
Returns the display of the given concept, if present, otherwise, looks up medication
names for any RxNormCodes within the concept using the GetMedicationName function
*/
define function GetMedicationConceptName(concept Concept):
  if concept.display is null then
    First(
      (concept.codes) C
        where C.system = 'http://www.nlm.nih.gov/research/umls/rxnorm'
        return GetMedicationName(C)
    )
  else
    concept.display

/*
Returns the display of the given code, if present, otherwise looks it up from the OMTK data
*/
define function GetMedicationName(rxNormCode Code):
  if rxNormCode.display is null then
    singleton from (
      OMTKData.DrugIngredients DI
        where DI.drugCode = ToInteger(rxNormCode.code)
        return DI.drugName
    )
    else rxNormCode.display

/*
Returns the display of the given ingredient, if present, otherwise looks it up from the OMTK data
*/
define function GetIngredientName(ingredientCode Code):
  if ingredientCode.display is null then
    singleton from (
      OMTKData.DrugIngredients DI
        where DI.ingredientCode = ToInteger(ingredientCode.code)
        return DI.ingredientName
    )
    else ingredientCode.display

/*
Returns the display of the given dose form, if present, otherwise looks it up from the OMTK data
*/
define function GetDoseFormName(doseFormCode Code):
  if doseFormCode.display is null then
    singleton from (
      OMTKData.DrugIngredients DI
        where DI.doseFormCode = ToInteger(doseFormCode.code)
        return DI.doseFormName
    )
    else doseFormCode.display

/*
  Removes the last per argument from a unit
  NOTE: Rewrote to not use LastPositionOf, since that function is not implemented in the JS engine
  https://github.com/cqframework/cql-execution/issues/147
*/
define function StripPer(unit String):
  unit X
    let split: Split(unit, '/'),
      splitCount: Count(split)
    return
      if splitCount > 1 then
        Substring(unit, 0, Length(unit) - Length(split[splitCount - 1]) - 1)
      else
        unit

/*
define function StripPer(unit String):
  if LastPositionOf('/', unit) >= 0
    then Substring(unit, 0, LastPositionOf('/', unit))
    else unit
*/

/*
  Calculates daily dose for a specific ingredient based on the ingredient strength,
  dose form, dose quantity, and daily frequency.
  In addition, returns a textual description of the daily dose.
  Tuple { result: Quantity, description: String }
*/
define function GetDailyDose(ingredientCode Code, strength Quantity, doseFormCode Code, doseQuantity Quantity, dosesPerDay Decimal):
  case
    when dosesPerDay is null or doseQuantity is null or strength is null or strength.value is null or strength.unit is null then
      {
        result: null as Quantity,
        description: 'Missing doses per day, dose quantity, and/or strength'
      }
	  /* if patch --> daily dose = dose value (e.g, number patches with doseQuantity unit = "patch") * per-hour strength */
    when ToInteger(doseFormCode.code) = 316987 then
      /* buprenorphine or fentanyl patch */
      if ToInteger(ingredientCode.code) in { 1819, 4337 } then
        (Quantity { value: dosesPerDay * doseQuantity.value * strength.value, unit: strength.unit }) dailyDose
          return {
            result: dailyDose,
            description: GetIngredientName(ingredientCode) & ' patch: ' & ToString(doseQuantity.value) & ' * ' & ToString(dosesPerDay) & '/d * ' & ToString(strength) & ' = ' + ToString(dailyDose)
          }
      else
        {
          result: null as Quantity,
          description: 'Unknown patch ingredient: ' & ingredientCode.code & ':' & ingredientCode.display
        }

    /* if dose unit in actual mass units (mg or ug -- when it's a single med) --> daily dose = numTimesPerDay * dose */
    when doseQuantity.unit in { 'mg', 'ug' } then
      (Quantity { value: dosesPerDay * doseQuantity.value, unit: doseQuantity.unit }) dailyDose
        return {
          result: dailyDose,
          description: GetIngredientName(ingredientCode) + ' ' + GetDoseFormName(doseFormCode) + ': ' + ToString(dosesPerDay) + '/d * ' + ToString(doseQuantity) + ' = ' + ToString(dailyDose)
        }

    /* if doseQuantity is in actual volume units (mL) --> daily dose = numTimesPerDay * dose * strength */
    when doseQuantity.unit = 'mL' and (PositionOf('/mL', strength.unit) = Length(strength.unit) - 3) then
      (Quantity { value: dosesPerDay * doseQuantity.value * strength.value, unit: StripPer(strength.unit) }) dailyDose
        return {
          result: dailyDose,
          description: GetIngredientName(ingredientCode) + ' ' + GetDoseFormName(doseFormCode) + ': ' + ToString(dosesPerDay) + '/d * ' + ToString(doseQuantity) + ' * ' & ToString(strength) & ' = ' + ToString(dailyDose)
        }

		/* if doseQuantity is not in actual units (e.g., 1 tab, 1 spray -- when it's a combo med with a unit of tablet, or it's mg/actuat) -->  daily dose = numTimesPerDay * dose value * strength value */
    else
      (Quantity { value: dosesPerDay * doseQuantity.value * strength.value, unit: StripPer(strength.unit) }) dailyDose
        return {
          result: dailyDose,
          description: GetIngredientName(ingredientCode) + ' ' + GetDoseFormName(doseFormCode) + ': ' + ToString(dosesPerDay) + '/d * ' + ToString(doseQuantity) + ' * ' + ToString(strength) + ' = ' + ToString(dailyDose)
        }
  end

/*
  Calculates MMEs for the given input prescription information and returns it
  as a list of tuples:

  List<Tuple {
    rxNormCode Code,
    doseFormCode Code,
    doseQuantity Quantity,
    dosesPerDay Decimal,
    ingredientCode Code,
    strength Quantity,
    dailyDose Quantity,
    dailyDoseDescription String,
    conversionFactor Decimal,
    mme Quantity
  }>
*/
define function CalculateMMEs(medications List<Tuple { rxNormCode Code, doseQuantity Quantity, dosesPerDay Decimal }>):
  Flatten(
    medications M
      let Ingredients: GetIngredients(M.rxNormCode)
      return
        Ingredients I
          let
            adjustedDoseQuantity: M.doseQuantity,
            dailyDose: GetDailyDose(I.ingredientCode, I.strength, I.doseFormCode, adjustedDoseQuantity, M.dosesPerDay),
            factor: ConversionFactors.GetConversionFactor(I.ingredientCode, dailyDose.result, I.doseFormCode, M.dosesPerDay)
          return {
            rxNormCode: M.rxNormCode,
            doseFormCode: I.doseFormCode,
            doseQuantity: adjustedDoseQuantity,
            dosesPerDay: M.dosesPerDay,
            ingredientCode: I.ingredientCode,
            strength: I.strength,
            dailyDose: dailyDose.result,
            dailyDoseDescription: dailyDose.description & (' * factor: ' + Coalesce(ToString(factor), 'No conversion factor available')),
            conversionFactor: factor,
            mme: Quantity(
              Round(dailyDose.result.value * factor, 1),
              '{MME}/d'
            )
          }
  )

define function Quantity(value Decimal, unit String):
  if value is not null then
    Quantity { value: value, unit: unit }
  else
    null
Content: application/elm+xml
Encoded data (391084 characters)
Content: application/elm+json
Encoded data (2041164 characters)