ecqm-bundler
Advanced tools
Comparing version 0.2.0-beta.1 to 0.2.0
@@ -32,3 +32,3 @@ "use strict"; | ||
criteria: { | ||
language: 'text/cql', | ||
language: 'text/cql-identifier', | ||
expression: inf.criteriaExpression | ||
@@ -61,3 +61,3 @@ } | ||
criteria: { | ||
language: 'text/cql', | ||
language: 'text/cql-identifier', | ||
expression: info.criteriaExpression | ||
@@ -64,0 +64,0 @@ } |
@@ -19,3 +19,2 @@ #!/usr/bin/env node | ||
const commander_1 = require("commander"); | ||
const uuid_1 = require("uuid"); | ||
const translator_1 = require("./helpers/translator"); | ||
@@ -28,4 +27,7 @@ const fhir_1 = require("./helpers/fhir"); | ||
const ecqm_1 = require("./helpers/ecqm"); | ||
const interactive_1 = require("./cli/interactive"); | ||
const populations_1 = require("./cli/populations"); | ||
const combine_groups_1 = require("./cli/combine-groups"); | ||
const program = new commander_1.Command(); | ||
program.version('v0.2.0-beta.1'); | ||
program.version('v0.2.0'); | ||
program.command('generate', { isDefault: true }); | ||
@@ -36,46 +38,17 @@ program | ||
.option('--output <path>', 'Path to output file', './combined.json') | ||
.argument('<path-one>') | ||
.argument('<path-two>') | ||
.action((p1, p2, opts) => { | ||
var _a, _b, _c, _d; | ||
logger_1.default.info(`Combining measure groups of ${p1} and ${p2}`); | ||
const mb1 = JSON.parse(fs_1.default.readFileSync(p1, 'utf8')); | ||
const mb2 = JSON.parse(fs_1.default.readFileSync(p2, 'utf8')); | ||
if (!mb1.entry) { | ||
logger_1.default.error(`No .entry found on bundle ${p1}`); | ||
process.exit(1); | ||
.argument('<path...>') | ||
.action((paths, opts) => { | ||
logger_1.default.info(`Combining measure groups of ${paths}`); | ||
try { | ||
const bundles = paths.map(p => JSON.parse(fs_1.default.readFileSync(p, 'utf8'))); | ||
const newBundle = (0, combine_groups_1.combineGroups)(bundles); | ||
fs_1.default.writeFileSync(opts.output, JSON.stringify(newBundle, null, 2)); | ||
logger_1.default.info(`Wrote file to ${opts.output}`); | ||
} | ||
if (!mb2.entry) { | ||
logger_1.default.error(`No .entry found on bundle ${p2}`); | ||
process.exit(1); | ||
catch (e) { | ||
if (e instanceof Error) { | ||
logger_1.default.error(e.message); | ||
process.exit(1); | ||
} | ||
} | ||
if (mb1.entry.length != mb2.entry.length) { | ||
logger_1.default.error('Measure bundles must have the same number of resources'); | ||
process.exit(1); | ||
} | ||
logger_1.default.info(`Validated Measure bundles`); | ||
const resourceFrom1 = (_a = mb1.entry.find(e => { var _a; return ((_a = e.resource) === null || _a === void 0 ? void 0 : _a.resourceType) === 'Measure'; })) === null || _a === void 0 ? void 0 : _a.resource; | ||
const resourceFrom2 = (_b = mb2.entry.find(e => { var _a; return ((_a = e.resource) === null || _a === void 0 ? void 0 : _a.resourceType) === 'Measure'; })) === null || _b === void 0 ? void 0 : _b.resource; | ||
if (!resourceFrom1) { | ||
logger_1.default.error(`No Measure resource found in ${p1}`); | ||
process.exit(1); | ||
} | ||
if (!resourceFrom2) { | ||
logger_1.default.error(`No Measure resource found in ${p2}`); | ||
process.exit(1); | ||
} | ||
const measure1 = resourceFrom1; | ||
const measure2 = resourceFrom2; | ||
const newGroup = ((_c = measure1.group) !== null && _c !== void 0 ? _c : []).concat((_d = measure2.group) !== null && _d !== void 0 ? _d : []); | ||
const newMeasure = Object.assign(Object.assign({}, measure1), { group: newGroup }); | ||
const newBundleEntry = mb1.entry.map(e => { | ||
var _a; | ||
if (((_a = e.resource) === null || _a === void 0 ? void 0 : _a.resourceType) === 'Measure') { | ||
return { resource: newMeasure, request: e.request }; | ||
} | ||
return e; | ||
}); | ||
const newBundle = Object.assign(Object.assign({}, mb1), { entry: newBundleEntry }); | ||
fs_1.default.writeFileSync(opts.output, JSON.stringify(newBundle, null, 2)); | ||
logger_1.default.info(`Wrote file to ${opts.output}`); | ||
process.exit(0); | ||
@@ -90,3 +63,3 @@ }); | ||
.option('--deps-directory <path>', 'Directory containing all dependent CQL or ELM files') | ||
.option('--ipop <expr...>', 'Initial Population expression name(s) of measure (enter multiple values for a multiple ipp ratio measure)') | ||
.option('--ipop <expr...>', '"initial-Population" expression name(s) of measure (enter multiple values for a multiple ipp ratio measure)') | ||
.option('--numer <expr>', '"numerator" expression name of measure') | ||
@@ -148,2 +121,6 @@ .option('--numer-ipop-ref <expr>', 'expression name of the "initial-population" that the numerator draws from') | ||
} | ||
if (opts.interactive && opts.elmFile) { | ||
logger_1.default.error('Interactive mode is only supported with CQL files'); | ||
program.help(); | ||
} | ||
let deps = []; | ||
@@ -172,213 +149,23 @@ logger_1.default.info('Gathering dependencies'); | ||
logger_1.default.info(`Successfully gathered ${deps.length} dependencies`); | ||
const EXPR_SKIP_CHOICE = 'SKIP'; | ||
function makeSimplePopulationCriteria(popCode, criteriaExpression) { | ||
if (Array.isArray(criteriaExpression)) { | ||
return { | ||
[popCode]: criteriaExpression.map(ce => { | ||
const criteria = { | ||
id: (0, uuid_1.v4)(), | ||
criteriaExpression: ce | ||
}; | ||
return criteria; | ||
}) | ||
}; | ||
} | ||
const criteria = { | ||
id: (0, uuid_1.v4)(), | ||
criteriaExpression | ||
}; | ||
return { | ||
[popCode]: criteria | ||
}; | ||
} | ||
function main() { | ||
var _a, _b; | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { default: inquirer } = yield import('inquirer'); | ||
const allGroupInfo = []; | ||
let allGroupInfo = []; | ||
if (opts.interactive) { | ||
if (opts.elmFile) { | ||
logger_1.default.error('Interactive mode is only supported with CQL files'); | ||
program.help(); | ||
try { | ||
const mainCQLPath = path_1.default.resolve(opts.cqlFile); | ||
allGroupInfo = yield (0, interactive_1.collectInteractiveInput)(mainCQLPath); | ||
} | ||
const mainCQLPath = path_1.default.resolve(opts.cqlFile); | ||
const mainCQL = fs_1.default.readFileSync(mainCQLPath, 'utf8'); | ||
const expressionNames = (0, cql_1.extractDefinesFromCQL)(mainCQL); | ||
const { numberOfGroups } = yield inquirer.prompt({ | ||
name: 'numberOfGroups', | ||
type: 'number', | ||
message: 'Enter number of Groups in the Measure', | ||
default: 1 | ||
}); | ||
for (let i = 0; i < numberOfGroups; i++) { | ||
const selectedGroupExpressions = [...expressionNames]; | ||
const group = `Group ${i + 1}`; | ||
const { scoring, measureImprovementNotation, populationBasis, numMeasureObs } = yield inquirer.prompt([ | ||
{ | ||
name: 'scoring', | ||
type: 'list', | ||
message: `Enter ${group} scoring code`, | ||
choices: measure_1.scoringCodes | ||
}, | ||
{ | ||
name: 'measureImprovementNotation', | ||
type: 'list', | ||
message: `Enter ${group} improvement notation`, | ||
choices: measure_1.improvementNotation | ||
}, | ||
{ | ||
name: 'populationBasis', | ||
type: 'input', | ||
message: `Enter ${group} population basis (see https://build.fhir.org/ig/HL7/cqf-measures/StructureDefinition-cqfm-populationBasis.html for more info)`, | ||
default: 'boolean' | ||
}, | ||
{ | ||
name: 'numMeasureObs', | ||
type: 'number', | ||
message: `Enter ${group} number of "measure-observation"s`, | ||
default: 0 | ||
} | ||
]); | ||
const groupInfo = { | ||
scoring, | ||
improvementNotation: measureImprovementNotation, | ||
populationBasis, | ||
populationCriteria: {} | ||
}; | ||
let numIPPs = 1; | ||
if (scoring === 'ratio') { | ||
const { numIPPsChoice } = yield inquirer.prompt({ | ||
name: 'numIPPsChoice', | ||
type: 'number', | ||
message: `Enter ${group} number of "initial-population"s`, | ||
default: 1 | ||
}); | ||
numIPPs = numIPPsChoice; | ||
catch (e) { | ||
if (e instanceof Error) { | ||
logger_1.default.error(e.message); | ||
process.exit(1); | ||
} | ||
for (const popCode of measure_1.measurePopulations) { | ||
if (selectedGroupExpressions.length === 0) | ||
continue; | ||
if (popCode === 'initial-population') { | ||
for (let j = 0; j < numIPPs; j++) { | ||
const { criteriaExpression } = yield inquirer.prompt({ | ||
name: 'criteriaExpression', | ||
type: 'list', | ||
message: `${group} "${popCode}" ${j + 1} expression`, | ||
choices: selectedGroupExpressions | ||
}); | ||
if (groupInfo.populationCriteria['initial-population']) { | ||
groupInfo.populationCriteria['initial-population'].push({ | ||
id: (0, uuid_1.v4)(), | ||
criteriaExpression | ||
}); | ||
} | ||
else { | ||
groupInfo.populationCriteria['initial-population'] = [ | ||
{ | ||
id: (0, uuid_1.v4)(), | ||
criteriaExpression | ||
} | ||
]; | ||
} | ||
selectedGroupExpressions.splice(selectedGroupExpressions.indexOf(criteriaExpression), 1); | ||
} | ||
} | ||
else if (popCode === 'measure-observation') { | ||
for (let j = 0; j < numMeasureObs; j++) { | ||
const { measureObsCriteriaExpression } = yield inquirer.prompt({ | ||
name: 'measureObsCriteriaExpression', | ||
type: 'list', | ||
message: `${group} "${popCode}" expression`, | ||
choices: [EXPR_SKIP_CHOICE].concat(selectedGroupExpressions) | ||
}); | ||
if (measureObsCriteriaExpression !== EXPR_SKIP_CHOICE) { | ||
const { observingPopExpression } = yield inquirer.prompt({ | ||
name: 'observingPopExpression', | ||
type: 'list', | ||
message: `${group} "${popCode}" ${measureObsCriteriaExpression} observing population`, | ||
choices: expressionNames | ||
}); | ||
const observingPops = Object.values(groupInfo.populationCriteria).find(gi => { | ||
if (Array.isArray(gi)) { | ||
return gi.some(g => g.criteriaExpression === observingPopExpression); | ||
} | ||
return gi.criteriaExpression === observingPopExpression; | ||
}); | ||
if (!observingPops) { | ||
logger_1.default.error(`Could not find population ${observingPopExpression} in group`); | ||
process.exit(1); | ||
} | ||
const observingPop = Array.isArray(observingPops) | ||
? observingPops.find(op => op.criteriaExpression === observingPopExpression) | ||
: observingPops; | ||
if (!observingPop) { | ||
logger_1.default.error(`Could not find population ${observingPopExpression} in group`); | ||
process.exit(1); | ||
} | ||
if (groupInfo.populationCriteria['measure-observation']) { | ||
groupInfo.populationCriteria['measure-observation'].push({ | ||
id: (0, uuid_1.v4)(), | ||
criteriaExpression: measureObsCriteriaExpression, | ||
observingPopId: observingPop.id | ||
}); | ||
} | ||
else { | ||
groupInfo.populationCriteria['measure-observation'] = [ | ||
{ | ||
id: (0, uuid_1.v4)(), | ||
criteriaExpression: measureObsCriteriaExpression, | ||
observingPopId: observingPop.id | ||
} | ||
]; | ||
} | ||
selectedGroupExpressions.splice(selectedGroupExpressions.indexOf(measureObsCriteriaExpression), 1); | ||
} | ||
} | ||
} | ||
else { | ||
const { criteriaExpression } = yield inquirer.prompt({ | ||
name: 'criteriaExpression', | ||
type: 'list', | ||
message: `${group} "${popCode}" expression`, | ||
choices: [EXPR_SKIP_CHOICE].concat(selectedGroupExpressions) | ||
}); | ||
if (criteriaExpression !== EXPR_SKIP_CHOICE) { | ||
groupInfo.populationCriteria[popCode] = { id: (0, uuid_1.v4)(), criteriaExpression }; | ||
selectedGroupExpressions.splice(selectedGroupExpressions.indexOf(criteriaExpression), 1); | ||
} | ||
} | ||
if (scoring === 'ratio' && numIPPs > 1) { | ||
if (popCode === 'numerator' || popCode === 'denominator') { | ||
if (!groupInfo.populationCriteria['initial-population']) { | ||
logger_1.default.error(`Could not detect initial-population entries to draw from for ratio measure with multipe IPPs`); | ||
process.exit(1); | ||
} | ||
const { observingPopExpression } = yield inquirer.prompt({ | ||
name: 'observingPopExpression', | ||
type: 'list', | ||
message: `${group} initial-population that "${popCode}" draws from`, | ||
choices: groupInfo.populationCriteria['initial-population'].map(p => p.criteriaExpression) | ||
}); | ||
const observingPopId = (_a = groupInfo.populationCriteria['initial-population'].find(p => p.criteriaExpression === observingPopExpression)) === null || _a === void 0 ? void 0 : _a.id; | ||
if (!observingPopId) { | ||
logger_1.default.error(`Could not find population ${observingPopExpression} in group`); | ||
process.exit(1); | ||
} | ||
const gi = groupInfo.populationCriteria[popCode]; | ||
if (!gi) { | ||
logger_1.default.error(`Trying to set a criteria reference on ${popCode}, but no criteriaExpression was defined for it`); | ||
process.exit(1); | ||
} | ||
gi.observingPopId = observingPopId; | ||
} | ||
} | ||
} | ||
allGroupInfo.push(groupInfo); | ||
} | ||
} | ||
else { | ||
const popCriteria = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (opts.ipop && makeSimplePopulationCriteria('initial-population', opts.ipop))), (opts.numer && makeSimplePopulationCriteria('numerator', opts.numer))), (opts.numex && makeSimplePopulationCriteria('numerator-exclusion', opts.numex))), (opts.denom && makeSimplePopulationCriteria('denominator', opts.denom))), (opts.denex && makeSimplePopulationCriteria('denominator-exclusion', opts.denex))), (opts.denexcep && makeSimplePopulationCriteria('denominator-exception', opts.denexcep))), (opts.msrpopl && makeSimplePopulationCriteria('measure-population', opts.msrpopl))), (opts.msrpoplex && | ||
makeSimplePopulationCriteria('measure-population-exclusion', opts.msrpoplex))), (opts.msrobs && makeSimplePopulationCriteria('measure-observation', opts.msrobs))), (opts.detailedMsrobs && | ||
const popCriteria = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (opts.ipop && (0, populations_1.makeSimplePopulationCriteria)('initial-population', opts.ipop))), (opts.numer && (0, populations_1.makeSimplePopulationCriteria)('numerator', opts.numer))), (opts.numex && (0, populations_1.makeSimplePopulationCriteria)('numerator-exclusion', opts.numex))), (opts.denom && (0, populations_1.makeSimplePopulationCriteria)('denominator', opts.denom))), (opts.denex && (0, populations_1.makeSimplePopulationCriteria)('denominator-exclusion', opts.denex))), (opts.denexcep && (0, populations_1.makeSimplePopulationCriteria)('denominator-exception', opts.denexcep))), (opts.msrpopl && (0, populations_1.makeSimplePopulationCriteria)('measure-population', opts.msrpopl))), (opts.msrpoplex && | ||
(0, populations_1.makeSimplePopulationCriteria)('measure-population-exclusion', opts.msrpoplex))), (opts.msrobs && (0, populations_1.makeSimplePopulationCriteria)('measure-observation', opts.msrobs))), (opts.detailedMsrobs && | ||
opts.detailedMsrobs.length > 0 && | ||
makeSimplePopulationCriteria('measure-observation', opts.detailedMsrobs.map(obs => obs.expression)))); | ||
(0, populations_1.makeSimplePopulationCriteria)('measure-observation', opts.detailedMsrobs.map(obs => obs.expression)))); | ||
if (Object.keys(popCriteria).length === 0) { | ||
@@ -407,24 +194,10 @@ logger_1.default.error(`Must specify at least 1 population expression (e.g. --ipop "Initial Population")`); | ||
var _a; | ||
const matchingPopEntry = Object.values(popCriteria).find(populationInfo => { | ||
if (Array.isArray(populationInfo)) { | ||
return populationInfo.some(pi => pi.criteriaExpression === obs.observingPopulationExpression); | ||
} | ||
else { | ||
return populationInfo.criteriaExpression === obs.observingPopulationExpression; | ||
} | ||
}); | ||
if (!matchingPopEntry) { | ||
const observingPop = (0, populations_1.findReferencedPopulation)(obs.observingPopulationExpression, popCriteria); | ||
if (!observingPop) { | ||
logger_1.default.error(`Could not find population "${obs.observingPopulationExpression}" referenced by measure-observation "${obs.expression}"`); | ||
process.exit(1); | ||
} | ||
const observingPop = Array.isArray(matchingPopEntry) | ||
? matchingPopEntry.find(op => op.criteriaExpression === obs.observingPopulationExpression) | ||
: matchingPopEntry; | ||
if (!observingPop) { | ||
logger_1.default.error(`Could not find population "${obs.observingPopulationExpression}" in group referenced by "${obs.expression}"`); | ||
process.exit(1); | ||
} | ||
const msrObsEntry = (_a = popCriteria['measure-observation']) === null || _a === void 0 ? void 0 : _a.find(mo => mo.criteriaExpression === obs.expression); | ||
if (!msrObsEntry) { | ||
logger_1.default.error(`Could not find measure observation ${obs.expression} in group`); | ||
logger_1.default.error(`Could not find measure observation "${obs.expression}" in group`); | ||
process.exit(1); | ||
@@ -483,3 +256,3 @@ } | ||
console.error(e.stack); | ||
if ((_b = e.response) === null || _b === void 0 ? void 0 : _b.data) { | ||
if ((_a = e.response) === null || _a === void 0 ? void 0 : _a.data) { | ||
console.log(e.response.data); | ||
@@ -486,0 +259,0 @@ } |
{ | ||
"name": "ecqm-bundler", | ||
"version": "0.2.0-beta.1", | ||
"version": "0.2.0", | ||
"description": "CLI for bundling FHIR-based eCQMs", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
217
README.md
@@ -15,4 +15,8 @@ # ecqm-bundler | ||
For bundling with CQL files as input, you must have an instance of the [cql-translation-service](https://github.com/cqframework/cql-translation-service) running somewhere. | ||
For bundling with CQL files as input, you must have an instance of the [cql-translation-service](https://github.com/cqframework/cql-translation-service) running somewhere, e.g.: | ||
```bash | ||
docker run -d -p 8080:8080 cqframework/cql-translation-service:latest | ||
``` | ||
Bundling is also supported with JSON ELM content directly. See [Bundling from ELM Content](#bundling-from-elm-content) | ||
@@ -22,6 +26,38 @@ | ||
```bash | ||
ecqm-bundler -c /path/to/main/cql/file.cql -v /path/to/valueset/directory --ipop <ipp-cql-expression> --numer <numer-cql-expression> --denom <denom-cql-expression> | ||
``` | ||
**NOTE**: Based on the scoring code provided, the CLI will enforce the constraints listed in [Table 3-1 of the cqf measures IG](https://build.fhir.org/ig/HL7/cqf-measures/measure-conformance.html#criteria-names). This means that you _must_ specify the minimum | ||
valid population expressions for your Measure's scoring type. | ||
### Customizing Population Expressions | ||
The bundler will add [population group criteria](http://hl7.org/fhir/us/cqfmeasures/2021May/StructureDefinition-measure-cqfm-definitions.html#Measure.group) to the Measure resource, which references specific CQL/ELM expressions that identify | ||
the relevant eCQM population. These can be customized with the following CLI options: | ||
``` | ||
--ipop <expr> "initial-population" expression name of measure | ||
--numer <expr> "numerator" expression name of measure | ||
--numex <expr> "numerator-exclusion" expression name of measure | ||
--denom <expr> "denominator" expression name of measure | ||
--denex <expr> "denominator-exclusion" expression name of measure | ||
--denexcep <expr> "denominator-exception" expression name of measure | ||
--msrpopl <expr> "measure-population" expression name of measure | ||
--msrpoplex <expr> "measure-population-exclusion" expression name of measure | ||
--msrobs <expr> "measure-observation" expression name of measure | ||
``` | ||
```bash | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory -v /path/to/valueset/directory --numer "numer def" --denom "denom def" --ipop "ipop def" | ||
``` | ||
### Dependencies | ||
If your CQL depends on other cql (i.e. it uses an `include <otherlib> ...` statement, that CQL must be passed in to the CLI as well via either the `--deps` or `--deps-directory` arguments: | ||
### Individual Dependency List | ||
```bash | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps /path/to/dep1.cql /path/to/dep2.cql -v /path/to/valueset/directory | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps /path/to/dep1.cql /path/to/dep2.cql -v /path/to/valueset/directory --ipop <ipp-cql-expression> --numer <numer-cql-expression> --denom <denom-cql-expression> | ||
``` | ||
@@ -32,3 +68,3 @@ | ||
```bash | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory -v /path/to/valueset/directory | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory -v /path/to/valueset/directory --ipop <ipp-cql-expression> --numer <numer-cql-expression> --denom <denom-cql-expression> | ||
``` | ||
@@ -40,12 +76,2 @@ | ||
### Bundling from ELM Content | ||
```bash | ||
ecqm-bundler -e /path/to/main/elm/file.json --deps-directory /path/to/deps/directory -v /path/to/valueset/directory | ||
``` | ||
This will forego the CQL translation and bundle the libraries with the ELM content provided. | ||
## Advanced Usage/Features | ||
### Customizing Measure Properties | ||
@@ -57,3 +83,3 @@ | ||
```bash | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory -v /path/to/valueset/directory --scoring-code proportion --improvement-notation increase --basis boolean | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory -v /path/to/valueset/directory --scoring-code proportion --improvement-notation increase --basis boolean <...> | ||
``` | ||
@@ -67,44 +93,26 @@ | ||
```bash | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory --no-valuesets | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory --no-valuesets <...> | ||
``` | ||
### Customizing Population Expressions | ||
### Customizing Canonical URLs | ||
The bundler will add [population group criteria](http://hl7.org/fhir/us/cqfmeasures/2021May/StructureDefinition-measure-cqfm-definitions.html#Measure.group) to the Measure resource, which references specific CQL expressions that identify | ||
the relevant eCQM population. These can be customized with the following CLI options: | ||
By default, the bundler just uses an `example.com` URL as the base canonical URL for the resources (e.g. `http://example.com/Measure/measure-123`). This can be customized using the `--canonical-base` option: | ||
``` | ||
--ipop <expr> Initial Population expression name of measure | ||
--numer <expr> "numerator" expression name of measure | ||
--numex <expr> "numerator-exclusion" expression name of measure | ||
--denom <expr> "denominator" expression name of measure | ||
--denex <expr> "denominator-exclusion" expression name of measure | ||
--denexcep <expr> "denominator-exception" expression name of measure | ||
--msrpopl <expr> "measure-population" expression name of measure | ||
--msrpoplex <expr> "measure-population-exclusion" expression name of measure | ||
--msrobs <expr> "measure-observation" expression name of measure | ||
``` | ||
```bash | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory -v /path/to/valueset/directory --numer "numer def" --denom "denom def" --ipop "ipop def" | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory -v /path/to/valueset/directory --canonical-base "http://example.com/other/canonical/base" <...> | ||
``` | ||
**NOTE**: Based on the scoring code provided, the CLI will enforce the constraints listed in [Table 3-1 of the cqf measures IG](https://build.fhir.org/ig/HL7/cqf-measures/measure-conformance.html#criteria-names) | ||
### Bundling from ELM Content | ||
### Customizing Canonical URLs | ||
By default, the bundler just uses an `example.com` URL as the base canonical URL for the resources (e.g. `http://example.com/Measure/measure-123`). This can be customized using the `--canonical-base` option: | ||
```bash | ||
ecqm-bundler -c /path/to/main/cql/file.cql --deps-directory /path/to/deps/directory -v /path/to/valueset/directory --canonical-base "http://example.com/other/canonical/base" | ||
ecqm-bundler -e /path/to/main/elm/file.json --deps-directory /path/to/deps/directory -v /path/to/valueset/directory <...> | ||
``` | ||
### Debugging | ||
This will forego the CQL translation and bundle the libraries with the ELM content provided. | ||
Debug mode will write all of the ELM content to a file in the `./debug` directory, which it will create. This is useful for inspecting the contents of translated CQL before it gets | ||
base64 encoded onto a FHIR Library resource. | ||
## Advanced Usage/Features | ||
To enable, use `--debug` as an option in the CLI amongst the other options | ||
:warning: Highly experimental. Use only if you know exactly what you're doing and why :warning: | ||
### Advanced Usage | ||
### Interactive Mode | ||
@@ -116,3 +124,3 @@ **NOTE**: Currently only supported when using CQL as the input | ||
```bash | ||
ecqm-bundler -c /path/to/main/cql/file --deps-directory /path/to/deps/directory -v /path/to/valueset/directory --interactive | ||
ecqm-bundler <...> --interactive | ||
``` | ||
@@ -128,32 +136,107 @@ | ||
Interactive mode is highly recommended for constructing complex measures. If you are in an environment where keyboard input is not an option (e.g. in a script), continue to the below sections | ||
### Multiple Initial Populations | ||
**NOTE**: This is only allowed when using `--scoring-code ratio` | ||
The CLI supports multiple initial populations by passing in multiple values to the `--ipop` flag | ||
```bash | ||
ecqm-bundler <...> --scoring-code ratio --ipop ipp1 ipp2 <...> | ||
``` | ||
In the case of multiple initial populations, the numerator and denominator populations must specify which initial population they draw from. This can be done with the | ||
`--numer-ipop-ref` and `--denom-ipop-ref` options respectively. | ||
**IMPORTANT**: The values for these flags _must_ match one of the expressions used with the `--ipop` flag. Otherwise, the CLI will throw an error. | ||
```bash | ||
ecqm-bundler <...> --scoring-code ratio --ipop ipp1 ipp2 --numer numer --numer-ipop-ref ipp1 --denom denom --denom-ipop-ref ipp2 | ||
``` | ||
### Multiple Measure Observations | ||
In the case of multiple measure observations, use the `--detailed-msrobs` flag. The CLI accepts a string of the format `<observation-function-name>|<observing-population-expression>`. This allows for `n` many measure observations that reference any population that has already been provided. | ||
**IMPORTANT**: The values for `<observing-population-expression>` _must_ match one of the expressions provided with any of the other population expression CLI flags. | ||
```bash | ||
ecqm-bundler --ipop ipp --numer numer --denom denom --detailed-msrobs "obs1|numer" "obs2|denom" | ||
``` | ||
The above command would generate a measure group where there are two measure observations: `obs1`, which is a CQL function that observes results from the `numer` population, and `obs2`, which is a CQL function that observes results from the `denom` population | ||
### Multiple Measure Groups | ||
:warning: Only use this feature if [interactive mode](#interactive-mode) is not a viable solution due to limitations of the environment that `ecqm-bundler` is being used in :warning: | ||
:warning: Please be very careful if you use this feature :warning: | ||
Due to limitations of command line interfaces, generating a measure with `n` many groups in one invocation is not feasible. To solve this, `ecqm-bundler` also comes with a `combine-groups` command that takes the groups of previously created bundles and combines them into one | ||
:warning: This assumes that the only difference amongst the bundles is the measure group. Everything else (valuesets, library names, etc.) must be exactly the same for this feature to work properly :warning: | ||
```bash | ||
ecqm-bundler combine-bundles /path/to/bundle1.json /path/to/bundle2.json /path/to/bundle3.json ... | ||
``` | ||
This will generate a new bundle `combined.json` where the groups of the measure resources in `bundle1.json`, `bundle2.json`, and `bundle3.json` are combined into one measure group array. | ||
``` | ||
Usage: ecqm-bunder combine-groups [options] <path...> | ||
Combine the groups in the measure resources of two different bundles into one | ||
Options: | ||
--output <path> Path to output file (default: "./combined.json") | ||
-h, --help display help for command | ||
``` | ||
## Debugging | ||
Debug mode will write all of the ELM content to a file in the `./debug` directory, which it will create. This is useful for inspecting the contents of translated CQL before it gets | ||
base64 encoded onto a FHIR Library resource. | ||
To enable, use `--debug` as an option in the CLI amongst the other options | ||
## Full List of Options | ||
``` | ||
Usage: ecqm-bundler [options] | ||
Usage: ecqm-bundler [options] [command] | ||
Options: | ||
-i, --interactive Create Bundle in interactive mode (allows for complex values) (default: false) | ||
-V, --version output the version number | ||
-i, --interactive Create Bundle in interactive mode (allows for complex values) (default: false) | ||
-c, --cql-file <path> | ||
-e,--elm-file <path> | ||
--debug Enable debug mode to write contents to a ./debug directory (default: false) | ||
--deps <deps...> List of CQL or ELM dependency files of the main file (default: []) | ||
--deps-directory <path> Directory containing all dependent CQL or ELM files | ||
--ipop <expr> Initial Population expression name of measure | ||
--numer <expr> "numerator" expression name of measure | ||
--numex <expr> "numerator-exclusion" expression name of measure | ||
--denom <expr> "denominator" expression name of measure | ||
--denex <expr> "denominator-exclusion" expression name of measure | ||
--denexcep <expr> "denominator-exception" expression name of measure | ||
--msrpopl <expr> "measure-population" expression name of measure | ||
--msrpoplex <expr> "measure-population-exclusion" expression name of measure | ||
--msrobs <expr> "measure-observation" expression name of measure | ||
-o, --out <path> Path to output file (default: "./measure-bundle.json") | ||
-v, --valuesets <path> Path to directory containing necessary valueset resource | ||
--no-valuesets Disable valueset detection and bundling | ||
-u, --translator-url <url> URL of cql translation service to use (default: "http://localhost:8080/cql/translator") | ||
--canonical-base <url> Base URL to use for the canonical URLs of library and measure resources (default: "http://example.com") | ||
--improvement-notation <notation> Measure's improvement notation (choices: "increase", "decrease", default: "increase") | ||
-s, --scoring-code <scoring> Measure's scoring code (choices: "proportion", "ratio", "continuous-variable", "cohort", default: "proportion") | ||
-b, --basis <population-basis> Measure's population basis (default: "boolean") | ||
-h, --help display help for command | ||
--debug Enable debug mode to write contents to a ./debug directory (default: false) | ||
--deps <deps...> List of CQL or ELM dependency files of the main file (default: []) | ||
--deps-directory <path> Directory containing all dependent CQL or ELM files | ||
--ipop <expr...> "initial-Population" expression name(s) of measure (enter multiple values for a multiple ipp ratio measure) | ||
--numer <expr> "numerator" expression name of measure | ||
--numer-ipop-ref <expr> expression name of the "initial-population" that the numerator draws from | ||
--numex <expr> "numerator-exclusion" expression name of measure | ||
--denom <expr> "denominator" expression name of measure | ||
--denom-ipop-ref <expr> expression name of the "initial-population" that the denominator draws from | ||
--denex <expr> "denominator-exclusion" expression name of measure | ||
--denexcep <expr> "denominator-exception" expression name of measure | ||
--msrpopl <expr> "measure-population" expression name of measure | ||
--msrpoplex <expr> "measure-population-exclusion" expression name of measure | ||
--msrobs <expr...> "measure-observation" expression name of measure (enter multiple values for a measure with multiple observations) | ||
--detailed-msrobs <expr...> Specify measure-observation(s) that reference another population. Must be of the format "<observation-function-name>|<observing-population-expression>" (default: []) | ||
-o, --out <path> Path to output file (default: "./measure-bundle.json") | ||
-v, --valuesets <path> Path to directory containing necessary valueset resource | ||
--no-valuesets Disable valueset detection and bundling | ||
-u, --translator-url <url> URL of cql translation service to use (default: "http://localhost:8080/cql/translator") | ||
--canonical-base <url> Base URL to use for the canonical URLs of library and measure resources (default: "http://example.com") | ||
--improvement-notation <notation> Measure's improvement notation (choices: "increase", "decrease", default: "increase") | ||
-s, --scoring-code <scoring> Measure's scoring code (choices: "proportion", "ratio", "continuous-variable", "cohort", default: "proportion") | ||
-b, --basis <population-basis> Measure's population basis (default: "boolean") | ||
-h, --help display help for command | ||
Commands: | ||
generate | ||
combine-groups [options] <path...> Combine the groups in the measure resources of two different bundles into one | ||
help [command] display help for command | ||
``` | ||
@@ -166,3 +249,3 @@ | ||
```bash | ||
ecqm-bundler -c example/cql/MainLib.cql --deps-directory example/cql -v example/valuesets --ipop "Initial Population" --denom "Denominator" --numer "Numerator" | ||
ecqm-bundler -c example/cql/MainLib.cql --deps-directory example/cql -v example/valuesets --ipop "Initial Population" --denom "Denominator" --numer "Numerator" -o example/example-measure-bundle.json | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
75596
15
1029
243
4