From 7e02e0b28041edfe29c89f37a8efd94e65c45b30 Mon Sep 17 00:00:00 2001 From: Chris Moesel Date: Tue, 25 Feb 2020 23:36:51 -0500 Subject: [PATCH 1/4] Fix typo in library name key --- src/utils/executeELM.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/executeELM.js b/src/utils/executeELM.js index 8816eb6a5..e2275f047 100644 --- a/src/utils/executeELM.js +++ b/src/utils/executeELM.js @@ -66,7 +66,7 @@ function getLibrary(release) { })); case 4: return new cql.Library(r4FactorsELM, new cql.Repository({ - CDS_Connect_Commons_for_FHIRv102: r4CommonsELM, + CDS_Connect_Commons_for_FHIRv400: r4CommonsELM, FHIRHelpers: r4HelpersELM })); default: From c30a5613a9902dbcb013db2a86f5e375411dcd87 Mon Sep 17 00:00:00 2001 From: Chris Moesel Date: Tue, 25 Feb 2020 23:48:01 -0500 Subject: [PATCH 2/4] Add script to update valueset-db.json file --- README.md | 15 +++--- package.json | 3 +- src/utils/fixVsDb.js | 37 --------------- src/utils/updateValueSetDB.js | 88 +++++++++++++++++++++++++++++++++++ yarn.lock | 59 +++++++++++++++++++++-- 5 files changed, 153 insertions(+), 49 deletions(-) delete mode 100644 src/utils/fixVsDb.js create mode 100644 src/utils/updateValueSetDB.js diff --git a/README.md b/README.md index 8d90e502e..e3476b7ea 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Pain Management Summary SMART on FHIR application was developed to support t The Pain Management Summary SMART on FHIR application was piloted during Summer 2018. Local modifications and development were needed to fully support this application in the pilot environment. For example, custom development was needed to expose pain assessments via the FHIR API. See the pilot reports for more information. -This application was originally piloted with support for FHIR DSTU2. The app has been updated since the pilot to also support FHIR R4, although pilot R4 support has not been piloted in a clinical setting. +This application was originally piloted with support for FHIR DSTU2. The app has been updated since the pilot to also support FHIR R4, although pilot R4 support has not been piloted in a clinical setting. In addition, value sets and standardized codes have been updated since the pilot. See the comments in the bundled CQL for details. This prototype application is part of the [CDS Connect](https://cds.ahrq.gov/cdsconnect) project, sponsored by the [Agency for Healthcare Research and Quality](https://www.ahrq.gov/) (AHRQ), and developed under contract with AHRQ by [MITRE's CAMH](https://www.mitre.org/centers/cms-alliances-to-modernize-healthcare/who-we-are) FFRDC. @@ -28,11 +28,6 @@ This CDS logic queries for several concepts that do not yet have standardized co | Code | System | Display | | --- | --- | --- | -| PEGASSESSMENT | http://cds.ahrq.gov/cdsconnect/pms | Pain Enjoyment General Activity (PEG) Assessment | -| PEGPAIN | http://cds.ahrq.gov/cdsconnect/pms | Pain | -| PEGENJOYMENT | http://cds.ahrq.gov/cdsconnect/pms | Enjoyment of life | -| PEGGENERALACTIVITY | http://cds.ahrq.gov/cdsconnect/pms | General activity | -| STARTBACK | http://cds.ahrq.gov/cdsconnect/pms | STarT Back Screening Tool | | SQETOHUSE | http://cds.ahrq.gov/cdsconnect/pms | Single question r/t ETOH use | | SQDRUGUSE | http://cds.ahrq.gov/cdsconnect/pms | Single question r/t drug use | | MME | http://cds.ahrq.gov/cdsconnect/pms | Morphine Milligram Equivalent (MME) | @@ -69,6 +64,14 @@ The Pain Management Summary can be deployed as static web resources on any HTTP Optionally to step 9, you can run the static build contents in a simple Node http-server via the command: `yarn start-static`. +### To update the valueset-db.json file + +The value set content used by the CQL is cached in a file named `valueset-db.json`. If the CQL has been modified to add or remove value sets, or if the value sets themselves have been updated, you may wish to update the valueset-db.json with the latest codes. To do this, you will need a [UMLS Terminology Services account](https://uts.nlm.nih.gov//license.html). + +To update the `valueset-db.json` file: + +1. Run `node src/utils/updateValueSetDB.js UMLS_USER_NAME UMLS_PASSWORD` _(replacing UMLS\_USER\_NAME and UMLS\_PASSWORD with your username and password)_ + ### To run the unit tests To execute the unit tests: diff --git a/package.json b/package.json index d91dd09b0..b4f8a0efb 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@rescripts/cli": "^0.0.13", "@rescripts/utilities": "^0.0.6", "cors": "^2.8.5", + "cql-exec-vsac": "^1.0.4", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", "express": "^4.17.1", @@ -38,6 +39,7 @@ "jest-enzyme": "^7.1.2", "mock-local-storage": "^1.1.11", "react-router-test-context": "^0.1.0", + "temp": "^0.9.1", "typescript": "^3.7.5" }, "resolutions": { @@ -66,7 +68,6 @@ "eject": "rescripts eject", "predeploy": "npm run build", "deploy": "gh-pages -d build", - "fix-vs-db": "node ./src/utils/fixVsDb", "upload-test-patients": "node ./src/utils/uploadTestPatients", "lint": "eslint ." } diff --git a/src/utils/fixVsDb.js b/src/utils/fixVsDb.js deleted file mode 100644 index d0dc149c5..000000000 --- a/src/utils/fixVsDb.js +++ /dev/null @@ -1,37 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const filePath = path.join(__dirname, '..', 'cql', 'valueset-db.json'); -const original = JSON.parse(fs.readFileSync(filePath, 'utf8')); - -// First fix the format -const fixed = {}; -for (const oid of Object.keys(original)) { - fixed[oid] = {}; - for (const version of Object.keys(original[oid])) { - if (Array.isArray(original[oid][version])) { - // Already in correct format! - fixed[oid][version] = original[oid][version] - } else { - fixed[oid][version] = original[oid][version]['codes']; - } - } -} - -// Then sort it -const sorted = {}; -const oidKeys = Object.keys(fixed).sort(); -for (const oid of oidKeys) { - sorted[oid] = {}; - const versionKeys = Object.keys(fixed[oid]); - for (const version of versionKeys) { - sorted[oid][version] = fixed[oid][version].sort((a, b) => { - if (a.code < b.code) return -1; - else if (a.code > b.code) return 1; - return 0; - }); - } -} - -fs.writeFileSync(`${filePath}.original`, JSON.stringify(original, null, 2), 'utf8'); -fs.writeFileSync(`${filePath}`, JSON.stringify(sorted, null, 2), 'utf8'); diff --git a/src/utils/updateValueSetDB.js b/src/utils/updateValueSetDB.js new file mode 100644 index 000000000..d462cb0f9 --- /dev/null +++ b/src/utils/updateValueSetDB.js @@ -0,0 +1,88 @@ +// This script updates the valueset-db.json file with any changes from the CQL +// library and/or changes in the value set definitions in VSAC. It should be +// called with the UMLS Username and Password as arguments. +const fs = require('fs'); +const path = require('path'); +const temp = require('temp'); +const { Library, Repository } = require('cql-execution'); +const { CodeService } = require('cql-exec-vsac'); +const dstu2FactorsELM = require('../cql/dstu2/Factors_to_Consider_in_Managing_Chronic_Pain.json'); +const dstu2CommonsELM = require('../cql/dstu2/CDS_Connect_Commons_for_FHIRv102.json'); +const dstu2HelpersELM = require('../cql/dstu2/FHIRHelpers.json'); +const r4FactorsELM = require('../cql/r4/Factors_to_Consider_in_Managing_Chronic_Pain_FHIRv400.json'); +const r4CommonsELM = require('../cql/r4/CDS_Connect_Commons_for_FHIRv400.json'); +const r4HelpersELM = require('../cql/r4/FHIRHelpers.json'); + +// First ensure a username and password are provided +const [user, password] = process.argv.slice(2); +if (user == null || password == null) { + console.error('The UMLS username and password must be passed in as arguments'); + process.exit(1); +} + +// Then initialize the cql-exec-vsac CodeService, pointing to a temporary +// folder to dump the valueset cache files. +temp.track(); // track temporary files and delete them when the process exits +const tempFolder = temp.mkdirSync('vsac-cache'); +const codeService = new CodeService(tempFolder); + +console.log(`Using temp folder: ${tempFolder}`); + +// Then setup the CQL libraries that we need to analyze to extract the +// valuesets from. In theory, the DSTU2 and R4 libraries should use the +// same valuesets, but in case they don't we go ahead and load both of +// them. +const dstu2Lib = new Library(dstu2FactorsELM, new Repository({ + CDS_Connect_Commons_for_FHIRv102: dstu2CommonsELM, + FHIRHelpers: dstu2HelpersELM +})); +const r4Lib = new Library(r4FactorsELM, new Repository({ + CDS_Connect_Commons_for_FHIRv400: r4CommonsELM, + FHIRHelpers: r4HelpersELM +})); + +// Then use the ensureValueSetsInLibrary function to analyze the Pain +// Management Summary CQL, request all the value sets from VSAC, and store +// their data in the temporary folder. The second argument (true) +// indicates to also look at dependency libraries. This has no affect +// for the current CQL, but may be helpful for people who extend it. +console.log(`Loading value sets from VSAC using account: ${user}`); +codeService.ensureValueSetsInLibrary(dstu2Lib, true, user, password) + .then(() => codeService.ensureValueSetsInLibrary(r4Lib, true, user, password)) + .then(() => { + // The valueset-db.json that the codeService produces isn't exactly the + // format that the Pain Management Summary wants, so now we must reformat + // it into the desired format. + const tempDBFile = path.join(tempFolder, 'valueset-db.json'); + const original = JSON.parse(fs.readFileSync(tempDBFile, 'utf8')); + let oidKeys = Object.keys(original).sort(); + console.log(`Loaded ${oidKeys.length} value sets`); + console.log('Translating JSON to expected format') + const fixed = {}; + for (const oid of oidKeys) { + fixed[oid] = {}; + for (const version of Object.keys(original[oid])) { + fixed[oid][version] = original[oid][version]['codes'].sort((a, b) => { + if (a.code < b.code) return -1; + else if (a.code > b.code) return 1; + return 0; + }); + } + } + + // And finally write the result to the real locations of the valueset-db.json. + const dbPath = path.join(__dirname, '..', 'cql', 'valueset-db.json'); + fs.writeFileSync(dbPath, JSON.stringify(fixed, null, 2), 'utf8'); + console.log('Updated:', dbPath); + }) + .catch((error) => { + let message = error.message; + if (error.statusCode === 401) { + // The default 401 message isn't helpful at all + message = 'invalid password or unauthorized access' + } + console.error('Error updating valueset-db.json:', message); + process.exit(1); + }); + + diff --git a/yarn.lock b/yarn.lock index 9e6414a03..91293ef31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3335,6 +3335,17 @@ cql-exec-fhir@^1.3.1: dependencies: xml2js "~0.4.19" +cql-exec-vsac@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cql-exec-vsac/-/cql-exec-vsac-1.0.4.tgz#3359f62c2eb96fa8dcf5cd28acc28de6a437d0d8" + integrity sha512-ZnHGqt7baLmw7abiAgK7Z9MCAi2ZGZPt+J6avVTshSBY1s3rOCQUDoppOQTvN7QlUSM+Y7mNI8oP7BzJ4NR8dA== + dependencies: + debug "^4.1.1" + mkdirp "^1.0.3" + request "^2.88.2" + request-promise-native "^1.0.8" + xml2js "^0.4.23" + cql-execution@^1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/cql-execution/-/cql-execution-1.3.7.tgz#bf302da698c6ed626fe37af62b93431a95ce64c2" @@ -5306,7 +5317,7 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0: +har-validator@~5.1.0, har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== @@ -7517,6 +7528,11 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" +mkdirp@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" + integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== + mock-local-storage@^1.1.11: version "1.1.11" resolved "https://registry.yarnpkg.com/mock-local-storage/-/mock-local-storage-1.1.11.tgz#2a36faeb30f76ef3c5005460b6bbf12f19555811" @@ -9977,7 +9993,7 @@ request-promise-core@1.1.3: dependencies: lodash "^4.17.15" -request-promise-native@^1.0.5: +request-promise-native@^1.0.5, request-promise-native@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== @@ -10012,6 +10028,32 @@ request@^2.87.0, request@^2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" +request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -10150,7 +10192,7 @@ rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1: dependencies: glob "^7.1.3" -rimraf@2.6.3: +rimraf@2.6.3, rimraf@~2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -11112,6 +11154,13 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.3" +temp@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.1.tgz#2d666114fafa26966cd4065996d7ceedd4dd4697" + integrity sha512-WMuOgiua1xb5R56lE0eH6ivpVmg/lq2OHm4+LtT/xtEtPQ+sz6N3bBM6WZ5FvO1lO4IKIOb43qnhoc4qxP5OeA== + dependencies: + rimraf "~2.6.2" + terser-webpack-plugin@2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.2.1.tgz#5569e6c7d8be79e5e43d6da23acc3b6ba77d22bd" @@ -11267,7 +11316,7 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0: +tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -12069,7 +12118,7 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xml2js@~0.4.19: +xml2js@^0.4.23, xml2js@~0.4.19: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== From f651063545e2d5948eae380fb87b73c82f091f85 Mon Sep 17 00:00:00 2001 From: Chris Moesel Date: Wed, 26 Feb 2020 09:13:30 -0500 Subject: [PATCH 3/4] Update .node-version The latest commits don't work w/ Node 8, so bump the minimum version to Node 10. --- .node-version | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.node-version b/.node-version index dba04c1e1..db24ab967 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -8.11.3 +10.13.0 diff --git a/README.md b/README.md index e3476b7ea..cdac55708 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Systems integrating the Pain Management Summary will need to expose the correspo ### To build and run in development: -1. Install [Node.js](https://nodejs.org/en/download/) (LTS edition, currently 8.x) +1. Install [Node.js](https://nodejs.org/en/download/) (LTS edition, currently 12.x) 2. Install [Yarn](https://yarnpkg.com/en/docs/install) (1.3.x or above) 3. Install dependencies by executing `yarn` from the project's root directory 4. If you have a SMART-on-FHIR client ID, edit `public/launch-context.json` to specify it @@ -48,7 +48,7 @@ Systems integrating the Pain Management Summary will need to expose the correspo The Pain Management Summary can be deployed as static web resources on any HTTP server. There are several customizations, however, that need to be made based on the site where it is deployed. -1. Install [Node.js](https://nodejs.org/en/download/) (LTS edition, currently 8.x) +1. Install [Node.js](https://nodejs.org/en/download/) (LTS edition, currently 12.x) 2. Install [Yarn](https://yarnpkg.com/en/docs/install) (1.3.x or above) 3. Install dependencies by executing `yarn` from the project's root directory 4. Modify the `homepage` value in `package.json` to reflect the path (after the hostname) at which it will be deployed From c9b9a5d7e01d72fe1797bbc16b4044ae18ce7305 Mon Sep 17 00:00:00 2001 From: Chris Moesel Date: Wed, 26 Feb 2020 09:14:59 -0500 Subject: [PATCH 4/4] v0.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b4f8a0efb..c5c5737b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pain-management-factors", - "version": "0.3.0", + "version": "0.3.1", "description": "Pain Management Factors SMART-on-FHIR App", "homepage": "https://ahrq-cds.github.io/AHRQ-CDS-Connect-PAIN-MANAGEMENT-SUMMARY", "license": "Apache-2.0",