Skip to content

Commit

Permalink
[QPPA-1310] Set metricType to multiplePerformanceRate on some QCDR me…
Browse files Browse the repository at this point in the history
…asures (#85)

This PR sets `metricType` to `multiPerformanceRate` for QCDR measures with `Number of performance rates to be included in the XML` greater than 1, AND (proportional=Y, continuous=N, ratio=N).

In order to include strata names and descriptions for multiPerformanceRate measures, we've manually modified the QCDR csv in these ways for each of the 27 affected measures (aka QCDR measures where the metricType is multiPerformanceRate):

- For column N, `Indicate an Overall Performance Rate if more than 1 performance rate is to be submitted`, we modify the values to strictly be one of: 
  - the number of the rate that is the overall rate (e.g. `3`) instead of `Rate 3` or `Performance Rate 3`, or 
  - `weighted average` or `sum numerators` (case-insensitive)
- For rows where column N is filled out, in column E, `Measure Description`, we modify the values to strictly be in the format:
  - `*summary* Rate 1: description Rate 2: description Rate 3: description`, instead of e.g. 
  - `*summary* A) description B) description C) description *more summary*`

We plan to ask policy to make the above modifications prior to future updates to the QCDR csv.

Testing: checked results manually and found 27 measures change from `singlePerformanceRate` to `multiPerformanceRate` which appears to be accurate. Added a unit test, tests pass.

(This is a followup to #81, and includes a generated file that should have been added as part of that PR, `measures-data-with-qcdrs.json`)

Jira: https://jira.cms.gov/browse/QPPA-1310

- [x] Include multiple strata name/descriptions and overallalgorithm field
- [x] Include other strata data once policy responds
- [x] Move strata names to a separate file

Reviewer: @marimiyachi
  • Loading branch information
kalvinwang authored Nov 10, 2017
1 parent 832c17d commit 2908724
Show file tree
Hide file tree
Showing 13 changed files with 31,704 additions and 5,355 deletions.
3,669 changes: 492 additions & 3,177 deletions measures/measures-data.json

Large diffs are not rendered by default.

2,769 changes: 629 additions & 2,140 deletions measures/measures-data.xml

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions measures/measures-schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ definitions:
type: boolean
default: false
overallAlgorithm:
description: Formula to determine the overall performance rate, given multiple strata of performance rates.
description: Formula to determine the overall performance rate, given multiple strata of performance rates. Only applicable to multiPerformanceRate measures.
enum: [simpleAverage, weightedAverage, sumNumerators, overallStratumOnly]
strata:
description: Population segments for which the measure requires reporting data. Only applicable to multiPerformanceRate measures.
Expand Down Expand Up @@ -142,7 +142,18 @@ definitions:
cpcPlusGroup:
description: CPC+ group which the measure belongs
type: string
required: [nationalQualityStrategyDomain, measureType, eMeasureId, nqfEMeasureId, nqfId, isHighPriority, isInverse, strata, primarySteward, measureSets, isRegistryMeasure]
oneOf: [
{
properties: {
metricType: { enum: [multiPerformanceRate] }
},
required: [overallAlgorithm, strata]
},{
properties: {
metricType: { enum: [singlePerformanceRate, nonProportion, cahps] }
}
}]
required: [nationalQualityStrategyDomain, measureType, eMeasureId, nqfEMeasureId, nqfId, isHighPriority, isInverse, primarySteward, measureSets, isRegistryMeasure]

performanceStrata:
type: object
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "qpp-measures-data",
"version": "1.0.0-alpha.15",
"version": "1.0.0-alpha.16",
"description": "Quality Payment Program Measures Data Repository",
"repository": {
"type": "git",
Expand Down
5 changes: 3 additions & 2 deletions scripts/measures/build-measures
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#!/usr/bin/env bash

qcdr_csv='../../util/measures/latest-QCDR-Measures-20170911.csv'
qcdr_csv='../../util/measures/QCDR-Measures-20171109.csv'
qcdr_strata_names='../../util/measures/qcdr-measures-strata-names.json'
staging_measures='../../staging/measures-data.json'
staging_measures_with_qcdrs='../../staging/measures-data-with-qcdrs.json'
final_measures='../../measures/measures-data.json'

# 0. Add QCDR measures to the 'staging/measures-data.json' file:
node scripts/measures/import-qcdr-measures.js \
$staging_measures $qcdr_csv $staging_measures_with_qcdrs
$staging_measures $qcdr_csv $qcdr_strata_names $staging_measures_with_qcdrs

# 1. Enrich `measures-data.json` file, run:
node scripts/measures/enrich-measures-data.js \
Expand Down
100 changes: 78 additions & 22 deletions scripts/measures/import-qcdr-measures.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ const config = {
eMeasureId: null,
nqfEMeasureId: null,
nqfId: null,
strata: [
{
name: 'overall'
}
],
measureSets: [],
isRegistryMeasure: true
},
Expand Down Expand Up @@ -86,15 +81,70 @@ const config = {
}
};

const addMultiPerformanceRateDetails = function(newMeasure, record, qcdrStrataNamesDataPath) {
// Parse the names for qcdr measures with multiple strata/performance rates
// { measureId: [name of 1st performance rate, name of 2nd performance rate, etc.] }
//
// In the strata names file, note that the order of the array values matter.
// Also, unlike the descriptions for each of the strata/performance rates,
// the names do not come from a source outside of this codebase. They were
// created by manually selecting distinct keywords from the associated
// performance rate description and are used when submitting to the API.
const strataNames = fs.readFileSync(path.join(__dirname, qcdrStrataNamesDataPath), 'utf8');
const qcdrStrataNames = JSON.parse(strataNames);

newMeasure['metricType'] = 'multiPerformanceRate';

const overallPerformanceRate = _.lowerCase(_.trim(record[12]));
const nthPerformanceRate = _.parseInt(overallPerformanceRate);
if (_.isInteger(nthPerformanceRate)) {
newMeasure['overallAlgorithm'] = 'overallStratumOnly';
} else if (overallPerformanceRate === 'sum numerators') {
newMeasure['overallAlgorithm'] = 'sumNumerators';
} else if (overallPerformanceRate === 'weighted average') {
newMeasure['overallAlgorithm'] = 'weightedAverage';
}

// Add the names and descriptions of strata
let strataName;
const measureId = _.trim(record[2]);
const measureDescription = _.trim(record[4]);

// Measure description column contains performance rate description
// Split '*summary* Rate 1: text Rate 2: text' into [text, text]
const strata = _.split(measureDescription, /\s*[Rr]ate [0-9]+:\s*/);
// Drop anything before 'Rate 1' (usually a description of the measure)
strata.shift();

newMeasure['strata'] = [];
_.each(strata, function(stratum, index) {
strataName = qcdrStrataNames[measureId][index];
// i + 1 because Rates in the csv are numbered starting from 1
if (_.lowerCase(strataName) === 'overall' &&
index + 1 !== nthPerformanceRate) {
throw TypeError('"Overall" strata for ' + measureId + ' in QCDR ' +
'CSV doesn\'t match the name in the strata details file');
}
newMeasure['strata'].push({
'name': strataName,
'description': strata[index]
});
});

return newMeasure;
};

/**
* [convertCsvToMeasures description]
* @param {array of arrays} records each array in the outer array represents a new measure, each inner array its attributes
* @param {object} config object defining how to build a new measure from this csv file, including mapping of measure fields to column indices
* @return {array} Returns an array of measures objects
*
* We trim all data sourced from CSVs because people sometimes unintentionally include spaces or linebreaks
* Notes:
* 1. The terms [performance rate] 'strata' and 'performance rates' are used interchangeably
* 2. We trim all data sourced from CSVs because people sometimes unintentionally include spaces or linebreaks
*/
const convertCsvToMeasures = function(records, config) {
const convertCsvToMeasures = function(records, config, qcdrStrataNamesDataPath) {
const sourcedFields = config.sourced_fields;
const constantFields = config.constant_fields;

Expand All @@ -120,15 +170,19 @@ const convertCsvToMeasures = function(records, config) {

// If the 'proportion' column (col 17) is Y and the other two columns
// (continuous and ratio, cols 18 and 19) are N, metricType should be
// 'singlePerformanceRate'. Otherwise it should be 'nonProportion'
//
// Note: if the 'proportion' column is Y *and* there are multiple
// strata, then the metricType should be 'multiPerformanceRate'
// TODO(kalvin): implement multiPerformanceRate;
if (record[17] === 'Y' &&
record[18] === 'N' &&
record[19] === 'N') {
newMeasure['metricType'] = 'singlePerformanceRate';
// 'singlePerformanceRate', or 'multiPerformanceRate' if there are multiple
// strata/performance rates. Otherwise it should be 'nonProportion'
const proportion = _.trim(record[17]);
const continuous = _.trim(record[18]);
const ratio = _.trim(record[19]);
if (proportion === 'Y' && continuous === 'N' && ratio === 'N') {
// returns an integer if passed string '3', NaN if passed 'N/A'
const numPerformanceRates = _.parseInt(_.trim(record[11]));
if (_.isInteger(numPerformanceRates) && numPerformanceRates > 1) {
addMultiPerformanceRateDetails(newMeasure, record, qcdrStrataNamesDataPath);
} else {
newMeasure['metricType'] = 'singlePerformanceRate';
}
} else {
newMeasure['metricType'] = 'nonProportion';
}
Expand Down Expand Up @@ -201,26 +255,28 @@ function addMissingRegistryFlags(measures) {
return measures;
}

function importMeasures(measuresDataPath, qcdrMeasuresDataPath, outputPath) {
function importMeasures(measuresDataPath, qcdrMeasuresDataPath, qcdrStrataNamesDataPath, outputPath) {
const qpp = fs.readFileSync(path.join(__dirname, measuresDataPath), 'utf8');
const allMeasures = JSON.parse(qpp);

const csv = fs.readFileSync(path.join(__dirname, qcdrMeasuresDataPath), 'utf8');
const records = parse(csv, 'utf8');
const qcdrCsv = parse(csv, 'utf8');
// remove header
records.shift();
qcdrCsv.shift();

// If there's more than one QCDR measure with the same measure, we can
// arbitrarily pick one and ignore the others (they should all be
// identical except for the QCDR Organization Name which we don't care about)
const qcdrMeasures = _.uniqBy(convertCsvToMeasures(records, config), 'measureId');
const qcdrMeasures = _.uniqBy(convertCsvToMeasures(qcdrCsv, config, qcdrStrataNamesDataPath), 'measureId');

const mergedMeasures = mergeMeasures(allMeasures, qcdrMeasures, outputPath);
return JSON.stringify(addMissingRegistryFlags(mergedMeasures), null, 2);
}

const measuresDataPath = process.argv[2];
const qcdrMeasuresDataPath = process.argv[3];
const outputPath = process.argv[4];
const qcdrStrataNamesDataPath = process.argv[4];
const outputPath = process.argv[5];

const newMeasures = importMeasures(measuresDataPath, qcdrMeasuresDataPath, outputPath);
const newMeasures = importMeasures(measuresDataPath, qcdrMeasuresDataPath, qcdrStrataNamesDataPath, outputPath);
fs.writeFileSync(path.join(__dirname, outputPath), newMeasures);
Loading

0 comments on commit 2908724

Please sign in to comment.