Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ecqm-bundler

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ecqm-bundler - npm Package Compare versions

Comparing version 0.2.0-beta.1 to 0.2.0

dist/cli/combine-groups.js

4

dist/helpers/fhir.js

@@ -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",

@@ -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
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc