Skip to content

Commit

Permalink
Stratified report fix (#315)
Browse files Browse the repository at this point in the history
* potential one line fix

* Update episode strataCode to have id as backup value

* Add tests

* create MeasureReportBuilder for each result

* Update test descriptions and README

---------

Co-authored-by: Chris Hossenlopp <[email protected]>
  • Loading branch information
lmd59 and hossenlopp authored Oct 15, 2024
1 parent d2345a1 commit 303107f
Show file tree
Hide file tree
Showing 6 changed files with 440 additions and 7 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ fqm-execution reports -m /path/to/measure/bundle.json -p /path/to/patient1/bundl

## Stratification

The results for each stratifier on a Measure (if they exist) are reported on the DetailedResults array for each group. The StratifierResult object contains two result attributes: `result` and `appliesResult`. `result` is simply the raw result of the stratifier and `appliesResult` is the same unless that stratifier contains a [cqfm-appliesTo extension](https://hl7.org/fhir/us/cqfmeasures/STU4/StructureDefinition-cqfm-appliesTo.html). In the case that a stratifier applies to a specified population, the `appliesResult` is the result of the stratifier result AND the result of the specified population. The following is an example of what the DetailedResults would look like for a Measure whose single stratifier has a result of `true` but appliesTo the numerator population that has a result of `false`.
The results for each stratifier on a Measure (if they exist) are reported on the DetailedResults array for each group. For episode-based measures, stratifier results may also be found within each of the `episodeResults`. The StratifierResult object contains two result attributes: `result` and `appliesResult`. `result` is simply the raw result of the stratifier and `appliesResult` is the same unless that stratifier contains a [cqfm-appliesTo extension](https://hl7.org/fhir/us/cqfmeasures/STU4/StructureDefinition-cqfm-appliesTo.html). In the case that a stratifier applies to a specified population, the `appliesResult` is the result of the stratifier result AND the result of the specified population. The following is an example of what the DetailedResults would look like for a Measure whose single stratifier has a result of `true` but appliesTo the numerator population that has a result of `false`.

```typescript
[
Expand Down
4 changes: 2 additions & 2 deletions src/calculation/DetailedResultsBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ export function createEpisodePopulationValues(
// loop over stratifications and collect episode results for the strata
let strataIndex = 1;
populationGroup.stratifier?.forEach(strata => {
const strataCode = strata.code?.text ?? `strata-${strataIndex++}`;
const strataCode = strata.code?.text ?? strata.id ?? `strata-${strataIndex++}`;
if (strata.criteria?.expression) {
const rawEpisodeResults = patientResults[strata.criteria?.expression];
createOrSetValueOfEpisodes(
Expand Down Expand Up @@ -563,7 +563,7 @@ function createOrSetValueOfEpisodes(
newEpisodeResults.stratifierResults = [];
let strataIndex = 1;
populationGroup.stratifier?.forEach(strata => {
const newStrataCode = strata.code?.text ?? `strata-${strataIndex++}`;
const newStrataCode = strata.code?.text ?? strata.id ?? `strata-${strataIndex++}`;
newEpisodeResults.stratifierResults?.push({
...(strataId ? { strataId } : {}),
strataCode: newStrataCode,
Expand Down
4 changes: 2 additions & 2 deletions src/calculation/MeasureReportBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export default class MeasureReportBuilder<T extends PopulationGroupResult> exten
// 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);
group.stratifier?.find(s => s.id === stratResults.strataCode); // strataCode may have an id value if code did not originally exist
const stratum = strata?.stratum?.[0];
if (stratum) {
er.populationResults?.forEach(pr => {
Expand Down Expand Up @@ -647,8 +647,8 @@ export default class MeasureReportBuilder<T extends PopulationGroupResult> exten
options: CalculationOptions
): fhir4.MeasureReport[] {
const reports: fhir4.MeasureReport[] = [];
const measure = extractMeasureFromBundle(measureBundle);
executionResults.forEach(result => {
const measure = extractMeasureFromBundle(measureBundle);
const builder = new MeasureReportBuilder(measure, options);
builder.addPatientResults(result);
reports.push(builder.getReport());
Expand Down
74 changes: 74 additions & 0 deletions test/unit/DetailedResultsBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,80 @@ describe('DetailedResultsBuilder', () => {
])
);
});

test('it should add stratificationIds to stratification results of an individual episode for episode-based measure', () => {
const episodeMeasureStrat: fhir4.Measure = {
resourceType: 'Measure',
status: 'unknown',
extension: [
{
url: 'http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis',
valueCode: 'Encounter'
}
],
group: [
{
population: [
{
id: 'example-population-id',
code: {
coding: [
{
system: 'http://terminology.hl7.org/CodeSystem/measure-population',
code: 'initial-population'
}
]
},
criteria: {
expression: 'ipp',
language: 'text/cql'
}
}
],
stratifier: [
{
id: 'example-stratifier-id',
criteria: {
language: 'text/cql-identifier',
expression: 'Strat1'
}
}
]
}
]
};

const group = (episodeMeasureStrat.group as [fhir4.MeasureGroup])[0];

const statementResults: StatementResults = {
ipp: [
{
id: {
value: 'example-encounter'
}
}
]
};

const { episodeResults } = DetailedResultsBuilder.createPopulationValues(
episodeMeasureStrat,
group,
statementResults
);

expect(episodeResults).toBeDefined();
expect(episodeResults).toHaveLength(1);

const episodeStratifierResults = (episodeResults as EpisodeResults[])[0].stratifierResults;

expect(episodeStratifierResults).toEqual(
expect.arrayContaining([
expect.objectContaining({
strataCode: 'example-stratifier-id'
})
])
);
});
});

describe('ELM JSON Function', () => {
Expand Down
163 changes: 161 additions & 2 deletions test/unit/MeasureReportBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ 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 episodeWithStratMeasure = getJSONFixture('measure/episode-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 @@ -43,6 +44,7 @@ function buildTestMeasureBundle(measure: fhir4.Measure): fhir4.Bundle {
}
const simpleMeasureBundle = buildTestMeasureBundle(simpleMeasure);
const propWithStratMeasureBundle = buildTestMeasureBundle(propWithStratMeasure);
const episodeWithStratMeasureBundle = buildTestMeasureBundle(episodeWithStratMeasure);
const ratioMeasureBundle = buildTestMeasureBundle(ratioMeasure);
const cvMeasureBundle = buildTestMeasureBundle(cvMeasure);

Expand Down Expand Up @@ -293,6 +295,120 @@ const propWithStratExecutionResults: ExecutionResult<DetailedPopulationGroupResu
}
];

const episodeWithStratExecutionResults: 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,
appliesResult: false,
strataId: '93f5f1c7-8638-40a4-a596-8b5831599209'
},
{
strataCode: '5baf37c7-8887-4576-837e-ea20a8938282',
result: false,
appliesResult: false,
strataId: '5baf37c7-8887-4576-837e-ea20a8938282'
},
{
strataCode: '125b3d95-2d00-455f-8a6e-d53614a2a50e',
result: false,
appliesResult: false,
strataId: '125b3d95-2d00-455f-8a6e-d53614a2a50e'
},
{
strataCode: 'c06647b9-e134-4189-858d-80cee23c0f8d',
result: false,
appliesResult: false,
strataId: 'c06647b9-e134-4189-858d-80cee23c0f8d'
}
],
episodeResults: [
{
episodeId: '5ca62964b8484628b8de1f2b',
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,
appliesResult: false,
strataId: '93f5f1c7-8638-40a4-a596-8b5831599209'
},
{
strataCode: '5baf37c7-8887-4576-837e-ea20a8938282',
result: false,
appliesResult: false,
strataId: '5baf37c7-8887-4576-837e-ea20a8938282'
},
{
strataCode: '125b3d95-2d00-455f-8a6e-d53614a2a50e',
result: false,
appliesResult: false,
strataId: '125b3d95-2d00-455f-8a6e-d53614a2a50e'
},
{
strataCode: 'c06647b9-e134-4189-858d-80cee23c0f8d',
result: false,
appliesResult: false,
strataId: 'c06647b9-e134-4189-858d-80cee23c0f8d'
}
]
}
],
html: 'example-html'
}
]
}
];

const calculationOptions: CalculationOptions = {
measurementPeriodStart: '2021-01-01',
measurementPeriodEnd: '2021-12-31',
Expand All @@ -311,7 +427,7 @@ describe('MeasureReportBuilder Static', () => {
);
});

test('should generate 1 result', () => {
test('should generate one measure report', () => {
expect(measureReports).toBeDefined();
expect(measureReports).toHaveLength(1);
});
Expand Down Expand Up @@ -394,7 +510,7 @@ describe('MeasureReportBuilder Static', () => {
);
});

test('should generate 1 result', () => {
test('should generate one measure report', () => {
expect(measureReports).toBeDefined();
expect(measureReports).toHaveLength(1);
});
Expand Down Expand Up @@ -490,6 +606,49 @@ describe('MeasureReportBuilder Static', () => {
});
});

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

test('should generate one measure report', () => {
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 = episodeWithStratExecutionResults[0].detailedResults?.[0];

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

result!.episodeResults![0].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!.episodeResults![0].stratifierResults!.forEach(sr => {
const stratifierResult = group.stratifier?.find(s => s.id === sr.strataId);
expect(stratifierResult).toBeDefined();
expect(stratifierResult!.stratum?.[0].population?.length).toEqual(4);
expect(stratifierResult!.stratum?.[0].measureScore?.value).toEqual(0);
});
});
});

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

0 comments on commit 303107f

Please sign in to comment.