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 8d90e502e..cdac55708 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) | @@ -41,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 @@ -53,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 @@ -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..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", @@ -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/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: 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==