Skip to content

Commit

Permalink
Applies to stratifiers (#307)
Browse files Browse the repository at this point in the history
* appliesTo stratifier functionality

* Remove DetailedResults changes, add comments

* Clean up comments

* Only add id and/or code on MeasureReport if they exist

* Additional unit tests

* Unit test for summary MeasureReport

* Remove unnecessary check
  • Loading branch information
elsaperelli authored Jul 19, 2024
1 parent 12e0048 commit 0b752a1
Show file tree
Hide file tree
Showing 4 changed files with 394 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/calculation/DetailedResultsBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export function createPatientPopulationValues(
const value = patientResults[strata.criteria?.expression];
const result = isStatementValueTruthy(value);
stratifierResults?.push({
strataCode: strata.code?.text ?? `strata-${strataIndex++}`,
strataCode: strata.code?.text ?? strata.id ?? `strata-${strataIndex++}`,
result,
...(strata.id ? { strataId: strata.id } : {})
});
Expand Down
42 changes: 33 additions & 9 deletions src/calculation/MeasureReportBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { UnexpectedProperty, UnsupportedProperty } from '../types/errors/CustomErrors';
import { isDetailedResult } from '../helpers/DetailedResultsHelpers';
import { AbstractMeasureReportBuilder } from './AbstractMeasureReportBuilder';
import { MeasureReportGroupStratifier } from 'fhir/r4';

export default class MeasureReportBuilder<T extends PopulationGroupResult> extends AbstractMeasureReportBuilder<T> {
report: fhir4.MeasureReport;
Expand Down Expand Up @@ -130,13 +131,32 @@ export default class MeasureReportBuilder<T extends PopulationGroupResult> exten
group.stratifier = [];
measureGroup.stratifier.forEach(s => {
const reportStratifier = <fhir4.MeasureReportGroupStratifier>{};
reportStratifier.code = s.code ? [s.code] : [];
if (s.code) {
reportStratifier.code = [s.code];
}
if (s.id) {
reportStratifier.id = s.id;
}
const strat = <fhir4.MeasureReportGroupStratifierStratum>{};
// use existing populations, but reduce count as appropriate
// Deep copy population with matching attributes but different interface
strat.population = <fhir4.MeasureReportGroupStratifierStratumPopulation[]>(
JSON.parse(JSON.stringify(group.population))
// if a stratifier has a cqfm-appliesTo extension, then we only want to
// include that population. If none is specified, the stratification applies
// to all populations in a group
const appliesToExtension = s.extension?.find(
e => e.url === 'http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-appliesTo'
);
if (appliesToExtension) {
const popCode = appliesToExtension.valueCodeableConcept?.coding?.[0].code;
const matchingPop = group.population?.find(p => p.code?.coding?.[0].code === popCode);
strat.population = <fhir4.MeasureReportGroupStratifierStratumPopulation[]>(
JSON.parse(JSON.stringify([matchingPop]))
);
} else {
strat.population = <fhir4.MeasureReportGroupStratifierStratumPopulation[]>(
JSON.parse(JSON.stringify(group.population))
);
}

reportStratifier.stratum = [strat];
group.stratifier?.push(reportStratifier);
Expand Down Expand Up @@ -217,7 +237,11 @@ export default class MeasureReportBuilder<T extends PopulationGroupResult> exten
er.stratifierResults?.forEach(stratResults => {
// only add to results if this episode is in the strata
if (stratResults.result) {
const strata = group.stratifier?.find(s => s.code && s.code[0].text === stratResults.strataCode);
// the strataCode has the potential to be a couple of things, either s.code[0].text (previous measures)
// or s.id (newer measures)
const strata: MeasureReportGroupStratifier | undefined =
group.stratifier?.find(s => s.code && s.code[0]?.text === stratResults.strataCode) ||
group.stratifier?.find(s => s.id === stratResults.strataCode);
const stratum = strata?.stratum?.[0];
if (stratum) {
er.populationResults?.forEach(pr => {
Expand Down Expand Up @@ -255,7 +279,11 @@ export default class MeasureReportBuilder<T extends PopulationGroupResult> exten
groupResults.stratifierResults?.forEach(stratResults => {
// only add to results if this patient is in the strata
if (stratResults.result) {
const strata = group.stratifier?.find(s => s.code && s.code[0].text === stratResults.strataCode);
// the strataCode has the potential to be a couple of things, either s.code[0].text (previous measures)
// or s.id (newer measures)
const strata: MeasureReportGroupStratifier | undefined =
group.stratifier?.find(s => s.code && s.code[0]?.text === stratResults.strataCode) ||
group.stratifier?.find(s => s.id === stratResults.strataCode);
const stratum = strata?.stratum?.[0];
if (stratum) {
groupResults.populationResults?.forEach(pr => {
Expand Down Expand Up @@ -318,10 +346,6 @@ export default class MeasureReportBuilder<T extends PopulationGroupResult> exten
// add to pop count creating it if not already created.
if (!pop.count) pop.count = 0;
pop.count += pr.result ? 1 : 0;
} else {
throw new UnexpectedProperty(
`Population ${pr.populationType} in stratum ${stratum.id} not found in measure report.`
);
}
}
}
Expand Down
168 changes: 167 additions & 1 deletion test/unit/MeasureReportBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
import { PatientSource } from 'cql-exec-fhir';
import MeasureReportBuilder from '../../src/calculation/MeasureReportBuilder';
import { getJSONFixture } from './helpers/testHelpers';
import { ExecutionResult, CalculationOptions, DetailedPopulationGroupResult } from '../../src/types/Calculator';
import {
ExecutionResult,
CalculationOptions,
DetailedPopulationGroupResult,
PopulationGroupResult
} from '../../src/types/Calculator';
import { PopulationType } from '../../src/types/Enums';

const patient1 = getJSONFixture(
Expand All @@ -20,6 +25,7 @@ const patient1Id = '3413754c-73f0-4559-9f67-df8e593ce7e1';
const patient2Id = '08fc9439-b7ff-4309-b409-4d143388594c';

const simpleMeasure = getJSONFixture('measure/simple-measure.json') as fhir4.Measure;
const propWithStratMeasure = getJSONFixture('measure/proportion-measure-with-stratifiers.json') as fhir4.Measure;
const ratioMeasure = getJSONFixture('measure/ratio-measure.json') as fhir4.Measure;
const cvMeasure = getJSONFixture('measure/cv-measure.json') as fhir4.Measure;
const cvMeasureScoringOnGroup = getJSONFixture('measure/group-score-cv-measure.json');
Expand All @@ -36,6 +42,7 @@ function buildTestMeasureBundle(measure: fhir4.Measure): fhir4.Bundle {
};
}
const simpleMeasureBundle = buildTestMeasureBundle(simpleMeasure);
const propWithStratMeasureBundle = buildTestMeasureBundle(propWithStratMeasure);
const ratioMeasureBundle = buildTestMeasureBundle(ratioMeasure);
const cvMeasureBundle = buildTestMeasureBundle(cvMeasure);

Expand Down Expand Up @@ -225,6 +232,63 @@ const cvExecutionResults: ExecutionResult<DetailedPopulationGroupResult>[] = [
}
];

const propWithStratExecutionResults: ExecutionResult<DetailedPopulationGroupResult>[] = [
{
patientId: patient1Id,
detailedResults: [
{
groupId: 'group-1',
statementResults: [],
populationResults: [
{
populationType: PopulationType.NUMER,
criteriaExpression: 'Numerator',
result: false
},
{
populationType: PopulationType.DENOM,
criteriaExpression: 'Denominator',
result: true
},
{
populationType: PopulationType.IPP,
criteriaExpression: 'Initial Population',
result: true
},
{
populationType: PopulationType.DENEX,
criteriaExpression: 'Denominator Exclusion',
result: false
}
],
stratifierResults: [
{
strataCode: '93f5f1c7-8638-40a4-a596-8b5831599209',
result: false,
strataId: '93f5f1c7-8638-40a4-a596-8b5831599209'
},
{
strataCode: '5baf37c7-8887-4576-837e-ea20a8938282',
result: false,
strataId: '5baf37c7-8887-4576-837e-ea20a8938282'
},
{
strataCode: '125b3d95-2d00-455f-8a6e-d53614a2a50e',
result: false,
strataId: '125b3d95-2d00-455f-8a6e-d53614a2a50e'
},
{
strataCode: 'c06647b9-e134-4189-858d-80cee23c0f8d',
result: false,
strataId: 'c06647b9-e134-4189-858d-80cee23c0f8d'
}
],
html: 'example-html'
}
]
}
];

const calculationOptions: CalculationOptions = {
measurementPeriodStart: '2021-01-01',
measurementPeriodEnd: '2021-12-31',
Expand Down Expand Up @@ -316,6 +380,108 @@ describe('MeasureReportBuilder Static', () => {
});
});

describe('Measure Report from Proportion Measure with stratifiers', () => {
let measureReports: fhir4.MeasureReport[];
beforeAll(() => {
measureReports = MeasureReportBuilder.buildMeasureReports(
propWithStratMeasureBundle,
propWithStratExecutionResults,
calculationOptions
);
});

test('should generate 1 result', () => {
expect(measureReports).toBeDefined();
expect(measureReports).toHaveLength(1);
});

test('should contain proper stratifierResults', () => {
const [mr] = measureReports;

expect(mr.group).toBeDefined();
expect(mr.group).toHaveLength(1);

const [group] = mr.group!;
const result = propWithStratExecutionResults[0].detailedResults?.[0];

expect(group.id).toEqual(result!.groupId);
expect(group.measureScore).toBeDefined();
expect(group.population).toBeDefined();

result!.populationResults!.forEach(pr => {
const populationResult = group.population?.find(p => p.code?.coding?.[0].code === pr.populationType);
expect(populationResult).toBeDefined();
expect(populationResult!.count).toEqual(pr.result === true ? 1 : 0);
});

result!.stratifierResults!.forEach(sr => {
const stratifierResult = group.stratifier?.find(s => s.id === sr.strataId);
expect(stratifierResult).toBeDefined();
expect(stratifierResult!.stratum?.[0].population?.length).toEqual(1);
expect(stratifierResult!.stratum?.[0].measureScore?.value).toEqual(0);
});
});
});

describe('Measure Report from Proportion Measure with stratifiers and two patient results', () => {
let builder: MeasureReportBuilder<PopulationGroupResult>;
beforeAll(() => {
builder = new MeasureReportBuilder(propWithStratMeasure, {
reportType: 'summary',
measurementPeriodStart: '2021-01-01',
measurementPeriodEnd: '2021-12-31'
});

builder.addPatientResults({
patientId: patient1Id,
detailedResults: [
{
groupId: 'group-1',
stratifierResults: [
{
strataCode: '93f5f1c7-8638-40a4-a596-8b5831599209',
result: false,
strataId: '93f5f1c7-8638-40a4-a596-8b5831599209'
},
{
strataCode: '5baf37c7-8887-4576-837e-ea20a8938282',
result: false,
strataId: '5baf37c7-8887-4576-837e-ea20a8938282'
}
]
}
]
});

builder.addPatientResults({
patientId: patient2Id,
detailedResults: [
{
groupId: 'group-1',
stratifierResults: [
{
strataCode: '125b3d95-2d00-455f-8a6e-d53614a2a50e',
result: false,
strataId: '125b3d95-2d00-455f-8a6e-d53614a2a50e'
},
{
strataCode: 'c06647b9-e134-4189-858d-80cee23c0f8d',
result: false,
strataId: 'c06647b9-e134-4189-858d-80cee23c0f8d'
}
]
}
]
});
});

test('should generate a summary MeasureReport whose stratifierResults only contain one population in the stratum', () => {
const { report } = builder;
expect(report).toBeDefined();
expect(report.group?.[0].stratifier?.[0].stratum?.[0].population?.length).toEqual(1);
});
});

describe('Ratio Measure Report', () => {
let measureReports: fhir4.MeasureReport[];
beforeAll(() => {
Expand Down
Loading

0 comments on commit 0b752a1

Please sign in to comment.