From cec45b06ad4fffcc438d84c607fa80d526a5ca6b Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 22 Nov 2024 14:30:31 -0800 Subject: [PATCH 1/5] fix naloxone alert for active opioid meds only (#217) Co-authored-by: Amy Chen --- src/config/summary_config.json | 32 ++++- ...ider_in_Managing_Chronic_Pain_FHIRv400.cql | 7 +- ...der_in_Managing_Chronic_Pain_FHIRv400.json | 111 +++++++++++------- src/helpers/flagit.js | 7 +- 4 files changed, 108 insertions(+), 49 deletions(-) diff --git a/src/config/summary_config.json b/src/config/summary_config.json index bbdc6a39e..6ca2f4ff1 100644 --- a/src/config/summary_config.json +++ b/src/config/summary_config.json @@ -360,10 +360,34 @@ }, { "flag": { - "ifOneOrMore": { - "table": "MedicationRequestsForNaloxoneConsideration", - "source": "RiskConsiderations" - } + "ifAnd": [ + { + "ifEqualTo": { + "header": "Class", + "targetValue": "opioid" + } + }, + { + "ifGreaterThanOrEqualTo": { + "header": "MME", + "targetValue": 50 + } + }, + { + "ifEqualTo": { + "header": "isActive", + "type": "boolean", + "targetValue": true + } + }, + { + "ifEqualTo": { + "header": "isBup", + "type": "boolean", + "targetValue": false + } + } + ] }, "flagText": "Current MME 50 or more, consider prescribing Naloxone", "flagClass": "info" diff --git a/src/cql/r4/Factors_to_Consider_in_Managing_Chronic_Pain_FHIRv400.cql b/src/cql/r4/Factors_to_Consider_in_Managing_Chronic_Pain_FHIRv400.cql index 11688a5da..ad769473b 100644 --- a/src/cql/r4/Factors_to_Consider_in_Managing_Chronic_Pain_FHIRv400.cql +++ b/src/cql/r4/Factors_to_Consider_in_Managing_Chronic_Pain_FHIRv400.cql @@ -726,7 +726,8 @@ define ReportPDMPMedicationRequests: CalculatedEnd: GetMedicationRequestEndDate(O), dispenseRequest: O.dispenseRequest, MME_Object: MMECalculator.MME(O), - c: (O.medication as FHIR.CodeableConcept) + c: (O.medication as FHIR.CodeableConcept), + RxnormCode: Coalesce((c.coding) c2 where c2.system.value ~ 'http://www.nlm.nih.gov/research/umls/rxnorm' return c2.code.value) return { Type: 'Request', Name: ConceptText(c), @@ -744,7 +745,9 @@ define ReportPDMPMedicationRequests: Prescriber: O.requester.display.value, Pharmacy: GetMedicationRequestPharmacy(O), NDC_Code: Coalesce((c.coding) c2 where c2.system.value ~ 'http://hl7.org/fhir/sid/ndc' return c2.code.value), - RXNorm_Code: Coalesce((c.coding) c2 where c2.system.value ~ 'http://www.nlm.nih.gov/research/umls/rxnorm' return c2.code.value), + RXNorm_Code: RxnormCode, + isBup: RxnormCode in BuprenorphineRxCUIs, + isActive: CalculatedEnd same or after Today(), Status: O.status.value, Class: getMedicationRequestDrugClass(O), AuthoredOn: O.authoredOn diff --git a/src/cql/r4/Factors_to_Consider_in_Managing_Chronic_Pain_FHIRv400.json b/src/cql/r4/Factors_to_Consider_in_Managing_Chronic_Pain_FHIRv400.json index 116625e43..b67a3c24e 100644 --- a/src/cql/r4/Factors_to_Consider_in_Managing_Chronic_Pain_FHIRv400.json +++ b/src/cql/r4/Factors_to_Consider_in_Managing_Chronic_Pain_FHIRv400.json @@ -4607,6 +4607,53 @@ "type" : "NamedTypeSpecifier" } } + }, { + "identifier" : "RxnormCode", + "expression" : { + "type" : "Coalesce", + "operand" : [ { + "type" : "Query", + "source" : [ { + "alias" : "c2", + "expression" : { + "path" : "coding", + "type" : "Property", + "source" : { + "name" : "c", + "type" : "QueryLetRef" + } + } + } ], + "relationship" : [ ], + "where" : { + "type" : "Equivalent", + "operand" : [ { + "path" : "value", + "type" : "Property", + "source" : { + "path" : "system", + "scope" : "c2", + "type" : "Property" + } + }, { + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "http://www.nlm.nih.gov/research/umls/rxnorm", + "type" : "Literal" + } ] + }, + "return" : { + "expression" : { + "path" : "value", + "type" : "Property", + "source" : { + "path" : "code", + "scope" : "c2", + "type" : "Property" + } + } + } + } ] + } } ], "relationship" : [ ], "return" : { @@ -4885,48 +4932,30 @@ }, { "name" : "RXNorm_Code", "value" : { - "type" : "Coalesce", + "name" : "RxnormCode", + "type" : "QueryLetRef" + } + }, { + "name" : "isBup", + "value" : { + "type" : "In", "operand" : [ { - "type" : "Query", - "source" : [ { - "alias" : "c2", - "expression" : { - "path" : "coding", - "type" : "Property", - "source" : { - "name" : "c", - "type" : "QueryLetRef" - } - } - } ], - "relationship" : [ ], - "where" : { - "type" : "Equivalent", - "operand" : [ { - "path" : "value", - "type" : "Property", - "source" : { - "path" : "system", - "scope" : "c2", - "type" : "Property" - } - }, { - "valueType" : "{urn:hl7-org:elm-types:r1}String", - "value" : "http://www.nlm.nih.gov/research/umls/rxnorm", - "type" : "Literal" - } ] - }, - "return" : { - "expression" : { - "path" : "value", - "type" : "Property", - "source" : { - "path" : "code", - "scope" : "c2", - "type" : "Property" - } - } - } + "name" : "RxnormCode", + "type" : "QueryLetRef" + }, { + "name" : "BuprenorphineRxCUIs", + "type" : "ExpressionRef" + } ] + } + }, { + "name" : "isActive", + "value" : { + "type" : "SameOrAfter", + "operand" : [ { + "name" : "CalculatedEnd", + "type" : "QueryLetRef" + }, { + "type" : "Today" } ] } }, { diff --git a/src/helpers/flagit.js b/src/helpers/flagit.js index 9d2718a8b..d5a97801b 100644 --- a/src/helpers/flagit.js +++ b/src/helpers/flagit.js @@ -59,7 +59,6 @@ export default function flagit(entry, subSection, summary) { function ifAnd(flagRulesArray, entry, subSection, summary) { for (let i = 0; i < flagRulesArray.length; ++i) { const flagRule = flagRulesArray[i]; - let match; if (typeof flagRule === 'string') { match = functions[flagRule](entry, entry, subSection, summary); @@ -120,7 +119,8 @@ function ifGreaterThanOrEqualTo(value, entry, subSection, summary) { if (Array.isArray(targetEntry) && targetEntry.length) { targetEntry = targetEntry[0] } - return parseInt(targetEntry[value.header], 10) >= value.value; + const valueToCompare = value.targetValue != null ? value.targetValue : value.value; + return parseInt(targetEntry[value.header], 10) >= valueToCompare; } /* * return true if an entry's value for a field matches the specified target value @@ -128,6 +128,9 @@ function ifGreaterThanOrEqualTo(value, entry, subSection, summary) { function ifEqualTo(value, entry, subSection, summary) { if (!entry) return false; if (Array.isArray(entry[value.header])) return entry[value.header].indexOf(value.targetValue) !== -1; + if (value.type === "boolean") { + return Boolean(entry[value.header]) === Boolean(value.targetValue); + } return entry[value.header] === value.targetValue; } /* From 2137cd2c6b65d286f82b7a2866a186199ad030be Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 22 Nov 2024 15:22:08 -0800 Subject: [PATCH 2/5] add text to software dev detail per feedback (#218) Co-authored-by: Amy Chen --- src/components/Disclaimer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Disclaimer.js b/src/components/Disclaimer.js index c63b8a24c..7372c6bf4 100644 --- a/src/components/Disclaimer.js +++ b/src/components/Disclaimer.js @@ -38,7 +38,7 @@ export default class Disclaimer extends Component {
COSRI development and open source software details
-
COSRI incorporates the Clinical Pain Management Summary application, released as open-source software by CDS Connect project at the Agency for Healthcare Research and Quality (AHRQ). We have extended ARHQ's work to provide enhanced security, improved decision support, integration with state Prescription Drug Monitoring Program databases, standalone operation, and other features. For a description of our open source release, contact info@cosri.app. Support for the development of COSRI was provided by the Washington State Department of Health and the Washington State Health Care Authority through the CMS Support Act.
+
COSRI incorporates the Clinical Pain Management Summary application, released as open-source software by CDS Connect project at the Agency for Healthcare Research and Quality (AHRQ). We have extended ARHQ's work to provide enhanced security, improved decision support, integration with state Prescription Drug Monitoring Program databases, standalone operation, and other features. For a description of our open source release, contact info@cosri.app. Support for the development of COSRI was provided by the Washington State Department of Health and the Washington State Health Care Authority through the CMS Support Act. Additional COSRI implementation and evaluation funding provided by the Centers for Disease Control and Prevention (CDC) and the Assistant Secretary for Technology Policy (ASTP) through contract # GS-35F-0034W/75P00122F80168, awarded to Security Risk Solutions, Inc.
); From b75f3fcd2d64956707bc6930ac37501380e80230 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Tue, 3 Dec 2024 15:49:01 -0800 Subject: [PATCH 3/5] fix check for enabling report tab (#219) Co-authored-by: Amy Chen --- src/helpers/utility.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/utility.js b/src/helpers/utility.js index 4cfd5a8ad..402afb00e 100644 --- a/src/helpers/utility.js +++ b/src/helpers/utility.js @@ -678,7 +678,7 @@ export function getSiteId() { export function isReportEnabled() { const siteId = getSiteId(); - return String(siteId).toLowerCase() === "uwmc"; + return ["uwmc", "demo"].indexOf(String(siteId).toLowerCase()) !== -1; } export function getEnvDashboardURL() { From 5360d12e7cc96487550ce80f778053bc4d759c15 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Wed, 4 Dec 2024 13:02:25 -0800 Subject: [PATCH 4/5] add matomo tracking (#148) * add matomo tracking * fix based on feedback * use user info from access token * add comment * fix typo per feedback * fix typo per feedback --------- Co-authored-by: Amy Chen Co-authored-by: Amy Chen --- public/index.html | 19 ++++++++------- src/components/Landing/index.js | 3 +++ src/helpers/utility.js | 43 +++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/public/index.html b/public/index.html index c425b5fcf..fb64b1bd2 100644 --- a/public/index.html +++ b/public/index.html @@ -1,19 +1,20 @@ - + - - - - - + + + + + Washington Opioid Clinical Assessment - +
diff --git a/src/components/Landing/index.js b/src/components/Landing/index.js index 75ce6e6d1..64d184904 100644 --- a/src/components/Landing/index.js +++ b/src/components/Landing/index.js @@ -6,6 +6,7 @@ import executeElm from "../../utils/executeELM"; import * as landingUtils from "./utility"; import { datishFormat } from "../../helpers/formatit"; import { + addMatomoTracking, getEnvSystemType, getEPICPatientIdFromSource, getPatientNameFromSource, @@ -98,6 +99,8 @@ export default class Landing extends Component { }); return; } + // add PIWIK tracking + addMatomoTracking(); writeToLog("application loaded", "info", this.getPatientLogParams()); //set FHIR results let result = {}; diff --git a/src/helpers/utility.js b/src/helpers/utility.js index 402afb00e..5258675dd 100644 --- a/src/helpers/utility.js +++ b/src/helpers/utility.js @@ -2,6 +2,7 @@ import moment from "moment"; import { toBlob, toJpeg } from "html-to-image"; import { getEnv, ENV_VAR_PREFIX } from "../utils/envConfig"; import reportSummarySections from "../config/report_config"; +import { getTokenInfoFromStorage } from "./timeout"; /* * return number of days between two dates @@ -707,3 +708,45 @@ export function dedupArrObjects(arr, key) { return acc; }, []); } + +export function getMatomoTrackingSiteId() { + return getEnv(`${ENV_VAR_PREFIX}_MATOMO_SITE_ID`); +} + +export function getUserIdFromAccessToken() { + const accessToken = getTokenInfoFromStorage(); + if (!accessToken) return null; + if (accessToken.profile) return accessToken.profile; + if (accessToken.fhirUser) return accessToken.fhirUser; + return accessToken["preferred_username"]; +} + +export function addMatomoTracking() { + // already generated script, return + if (document.querySelector("#matomoScript")) return; + const userId = getUserIdFromAccessToken(); + // no user Id return + if (!userId) return; + const siteId = getMatomoTrackingSiteId(); + // no site Id return + if (!siteId) return; + // init global piwik tracking object + window._paq = []; + window._paq.push(["trackPageView"]); + window._paq.push(["enableLinkTracking"]); + window._paq.push(["setSiteId", siteId]); + window._paq.push(["setUserId", userId]); + + let u = "https://piwik.cirg.washington.edu/"; + window._paq.push(["setTrackerUrl", u + "matomo.php"]); + let d = document, + g = d.createElement("script"), + headElement = document.querySelector("head"); + g.type = "text/javascript"; + g.async = true; + g.defer = true; + g.setAttribute("src", u + "matomo.js"); + g.setAttribute("id", "matomoScript"); + headElement.appendChild(g); +} + From 2874f6261afedb43df6df9b982d0940585909110 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Mon, 16 Dec 2024 09:58:38 -0800 Subject: [PATCH 5/5] handle error when survey has not associated ELM lib (#220) * handle error when survey has not associated ELM lib * fix try catch code * fix typo per feedback --------- Co-authored-by: Amy Chen --- src/components/Summary.js | 3 +- src/config/report_config.js | 2 + src/styles/components/_Summary.scss | 3 +- src/utils/executeELM.js | 73 ++++++++++++++++------------- 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/components/Summary.js b/src/components/Summary.js index 5e3c4784d..d04200525 100644 --- a/src/components/Summary.js +++ b/src/components/Summary.js @@ -13,6 +13,7 @@ import { getErrorMessageString, isEmptyArray, isReportEnabled, + isNumber } from "../helpers/utility"; import ChartIcon from "../icons/ChartIcon"; @@ -353,7 +354,7 @@ export default class Summary extends Component { ...formatterArguments ); } - return value + return value || isNumber(value) ? value : headerKey.default ? headerKey.default diff --git a/src/config/report_config.js b/src/config/report_config.js index 251f2ef8f..4409a1c89 100644 --- a/src/config/report_config.js +++ b/src/config/report_config.js @@ -60,6 +60,7 @@ const reportConfig = [ { id: "CIRG-PainTracker-GE", key: GE_DATA_KEY, + useDefaultELMLib: true }, ], //status: "inactive", @@ -380,6 +381,7 @@ const reportConfig = [ { id: "CIRG-PainTracker-TRT", key: TRT_DATA_KEY, + useDefaultELMLib: true }, ], icon: (props) => ( diff --git a/src/styles/components/_Summary.scss b/src/styles/components/_Summary.scss index 23d076d16..9f2be193c 100644 --- a/src/styles/components/_Summary.scss +++ b/src/styles/components/_Summary.scss @@ -403,9 +403,10 @@ height: 100%; width: 100%; display: flex; - align-items: center; + align-items: flex-start; justify-content: center; font-size: 1.1em; + line-height: 1.35; } } @media (min-width: 992px) { diff --git a/src/utils/executeELM.js b/src/utils/executeELM.js index 6bfc052c8..1aacb6a1f 100644 --- a/src/utils/executeELM.js +++ b/src/utils/executeELM.js @@ -152,33 +152,35 @@ async function executeELM(collector, oResourceTypes) { if (!evalResults[PATIENT_SUMMARY_KEY]) { evalResults[PATIENT_SUMMARY_KEY] = {}; } - Promise.allSettled([ - executeELMForReport(patientBundle), - ...elmLibs, - ]).then( - (results) => { - evalResults[PATIENT_SUMMARY_KEY]["ReportSummary"] = - results[0].status !== "rejected" ? results[0].value : null; - const evaluatedSurveyResults = executeELMForInstruments( - results.slice(1), - patientBundle - ); - evalResults[PATIENT_SUMMARY_KEY]["SurveySummary"] = - evaluatedSurveyResults; - //debug - console.log( - "final evaluated CQL results including surveys ", - evalResults - ); - resolve(evalResults); - }, - (e) => { - console.log(e); - reject( - "Error occurred executing report library logics. See console for detail" - ); - } - ); + Promise.allSettled([executeELMForReport(patientBundle), ...elmLibs]) + .then( + (results) => { + evalResults[PATIENT_SUMMARY_KEY]["ReportSummary"] = + results[0].status !== "rejected" ? results[0].value : null; + const evaluatedSurveyResults = executeELMForInstruments( + results.slice(1), + patientBundle + ); + evalResults[PATIENT_SUMMARY_KEY]["SurveySummary"] = + evaluatedSurveyResults; + //debug + console.log( + "final evaluated CQL results including surveys ", + evalResults + ); + resolve(evalResults); + }, + (e) => { + console.log(e); + reject( + "Error occurred executing report library logics. See console for detail" + ); + } + ) + .catch((e) => { + console.log("Error processing instrument ELM: ", e); + reject("error processing instrument ELM. See console for details."); + }); }); }); resolve(finalResults); @@ -193,12 +195,15 @@ async function executeELMForReport(bundle) { console.log("Issue occurred loading ELM lib for reoirt", e); r4ReportCommonELM = null; }); - + if (!r4ReportCommonELM) return null; - let reportLib = new cql.Library(r4ReportCommonELM, new cql.Repository({ - FHIRHelpers: r4HelpersELM, - })); + let reportLib = new cql.Library( + r4ReportCommonELM, + new cql.Repository({ + FHIRHelpers: r4HelpersELM, + }) + ); const reportExecutor = new cql.Executor( reportLib, new cql.CodeService(valueSetDB) @@ -274,8 +279,11 @@ function getLibraryForInstruments() { return INSTRUMENT_LIST.map((item) => (async () => { let elmJson = null; + const libPrefix = item.useDefaultELMLib + ? "Default" + : item.key.toUpperCase(); elmJson = await import( - `../cql/r4/survey_resources/${item.key.toUpperCase()}_LogicLibrary.json` + `../cql/r4/survey_resources/${libPrefix}_LogicLibrary.json` ) .then((module) => module.default) .catch((e) => { @@ -287,6 +295,7 @@ function getLibraryForInstruments() { ); elmJson = null; }); + if (!elmJson) { elmJson = await import( `../cql/r4/survey_resources/Default_LogicLibrary.json`