Skip to content

Commit

Permalink
Coverage 3.14.0 (#314)
Browse files Browse the repository at this point in the history
* Update to handle coverage changes

Removes all localids that aren't in the annotation

* Add not null test and 3.15.0-translated fixtures

* Return string array from annotation search function

* Update comments and function name
  • Loading branch information
lmd59 authored Oct 22, 2024
1 parent 303107f commit 6811b6c
Show file tree
Hide file tree
Showing 18 changed files with 35,374 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Library for calculating Electronic Clinical Quality Measures (eCQMs) written in
- [Recipes](#recipes)
- [Displaying Highlighted HTML in a React App](#displaying-highlighted-html-in-a-react-app)
- [Usage Within a FHIR Server](#usage-within-a-fhir-server)
- [Special Testing](#special-testing)
- [Contributing](#contributing)
- [License](#license)

Expand Down
2 changes: 1 addition & 1 deletion src/calculation/ClauseResultsBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export function buildStatementAndClauseResults(

if (includeClauseResults) {
// create clause results for all localIds in this statement
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(elmLibrary, statementResult.statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(elmLibrary, statementResult.statementName);
for (const localId in localIds) {
const clause = localIds[localId];
const rawClauseResult =
Expand Down
38 changes: 34 additions & 4 deletions src/helpers/ClauseResultsHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { ELMFunctionRef } from '../types/ELMTypes';
import { ELM, ELMBinaryExpression, ELMStatement } from '../types/ELMTypes';

/**
* Finds all localIds in a statement by it's library and statement name.
* Finds all of the localIds that are needed for clause results for a single statement.
* @public
* @param {ELM} libraryElm - The library the statement belongs to.
* @param {string} statementName - The statement name to search for.
* @return {Hash} List of local ids in the statement.
* @return {Hash} List of result-relevant local ids in the statement.
*/
export function findAllLocalIdsInStatementByName(libraryElm: ELM, statementName: string): any {
export function findLocalIdsInStatementByName(libraryElm: ELM, statementName: string): any {
// create place for aliases and their usages to be placed to be filled in later. Aliases and their usages (aka scope)
// and returns do not have localIds in the elm but do in elm_annotations at a consistent calculable offset.
// BE WARY of this calculable offset.
Expand Down Expand Up @@ -49,7 +49,37 @@ export function findAllLocalIdsInStatementByName(libraryElm: ELM, statementName:
clause.isUnsupported = true;
}
}
return localIds;

// find all localids in the annotation
const allAnnotatedIds = findAnnotationLocalIds(statement?.annotation);
// filter out local ids that aren't in the annotation
const annotatedLocalIds: Record<string, any> = {};
for (const [key, value] of Object.entries(localIds)) {
if (allAnnotatedIds.includes(key)) {
annotatedLocalIds[key] = value;
}
}
return annotatedLocalIds;
}

/**
* Recursively finds localIds that are in an annotation structure by pulling out all "r:"-keyed values
* @public
* @param {object} annotation - all or a subset of the annotation structure to search
* @return {Array} List of local ids in the annotation.
*/
function findAnnotationLocalIds(annotation: any): string[] {
if (Array.isArray(annotation)) {
return annotation.flatMap(ent => findAnnotationLocalIds(ent));
} else if (typeof annotation === 'object') {
return Object.entries(annotation).flatMap(ent => {
// if key is r, return value, else recurse
if (ent[0] === 'r') return ent[1] as string;
return findAnnotationLocalIds(ent[1]);
});
}
// default empty
return [];
}

/**
Expand Down
58 changes: 36 additions & 22 deletions test/unit/ClauseResultsHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { ELM } from '../../src/types/ELMTypes';
import { getJSONFixture } from './helpers/testHelpers';

describe('ClauseResultsHelpers', () => {
describe('findAllLocalIdsInStatementByName', () => {
describe('findLocalIdsInStatementByName', () => {
test('finds localIds for an Equivalent comparison operator that is wrapped in a Not expression', () => {
// ELM from test/unit/fixtures/cql/NotEquivalent.cql
const libraryElm: ELM = getJSONFixture('elm/NotEquivalent.json');

const statementName = 'Not Equivalent Clause';
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// For the fixture loaded for this test it is known that the localId for the Equivalent statement
// is 23 and the localId for the Not expression is 24 but we want the Equivalent clause to take
// the result of the Not expression
expect(localIds[23]).toBeDefined();
expect(localIds[23]).toEqual({ localId: '23', sourceLocalId: '24' });
// is 23 and the localId for the Not expression is 24. We want 23 to not be included because 24 is
// the correct id from the annotation.
expect(localIds[23]).not.toBeDefined();
expect(localIds[24]).toBeDefined();
});

test('finds localIds for an Equal comparison operator that is wrapped in a Not expression', () => {
Expand All @@ -25,21 +25,35 @@ describe('ClauseResultsHelpers', () => {
const libraryElm: ELM = getJSONFixture('elm/NotEqual.json');

const statementName = 'Not Equal Clause';
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// For the fixture loaded for this test it is known that the localId for the Equal statement
// is 100 and the localId for the Not expression is 23 but we want the Equal clause to take
// the result of the Not expression
expect(localIds[100]).toBeDefined();
expect(localIds[100]).toEqual({ localId: '100', sourceLocalId: '23' });
// is 100, and the localId for the Not expression is 23. We want 100 to not be included because 23 is
// the correct id from the annotation.
expect(localIds[23]).toBeDefined();
expect(localIds[100]).not.toBeDefined();
});

test('finds localIds for a not null operator', () => {
// ELM from test/unit/fixtures/cql/NotNull.cql, translated with 3.15.0 translator
const libraryElm: ELM = getJSONFixture('elm/3.15.0/NotNull.json');

const statementName = 'Not Null Clause';
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// For the fixture loaded for this test it is known that the localId for the IsNull statement
// is 237, and the localId for the Not expression is 238. We want 237 to not be included because 238 is
// the correct id from the annotation.
expect(localIds[238]).toBeDefined();
expect(localIds[237]).not.toBeDefined();
});

test('finds localIds for an ELM Binary Expression with a comparison operator with a literal', () => {
// ELM from test/unit/fixtures/cql/comparisonWithLiteral.cql
const libraryElm: ELM = getJSONFixture('elm/ComparisonWithLiteral.json');

const statementName = 'ipop';
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// For the fixture loaded for this test it is known that the localId for the literal is 10 and
// the localId for the comparison expression itself is 11 so the sourceLocalId for the literal
Expand All @@ -53,7 +67,7 @@ describe('ClauseResultsHelpers', () => {
const libraryElm: ELM = getJSONFixture('elm/ComparisonWithoutLiteral.json');

const statementName = 'ipop';
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// For the fixture loaded for this test it is known that the ELM Binary Expression does not have a literal
// so the localId for the right side of the comparison should just be 12 and not have a sourceLocalId
Expand All @@ -64,7 +78,7 @@ describe('ClauseResultsHelpers', () => {
test('finds localIds for case statement items and properly finds sourceLocalId for them', () => {
// ELM from test/unit/elm/queries/CaseStatement.cql
const libraryElm: ELM = getJSONFixture('elm/queries/CaseStatement.json');
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, 'Case');
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, 'Case');

expect(localIds[11]).toEqual({ localId: '11', sourceLocalId: '9' });
expect(localIds[17]).toEqual({ localId: '17', sourceLocalId: '15' });
Expand All @@ -73,15 +87,15 @@ describe('ClauseResultsHelpers', () => {
test('finds localIds for null literals and properly sets isFalsyLiteral to true', () => {
// ELM from test/unit/elm/queries/CaseStatement.cql
const libraryElm: ELM = getJSONFixture('elm/queries/CaseStatement.json');
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, 'Case');
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, 'Case');

expect(localIds[18]).toEqual({ localId: '18', isFalsyLiteral: true });
});

test('finds localIds for false literals and properly sets isFalsyLiteral to true', () => {
// ELM from test/unit/elm/queries/CaseStatement.cql
const libraryElm: ELM = getJSONFixture('elm/queries/CaseStatement.json');
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, 'Case');
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, 'Case');

expect(localIds[16]).toEqual({ localId: '16', isFalsyLiteral: true });
});
Expand All @@ -94,7 +108,7 @@ describe('ClauseResultsHelpers', () => {

// Find the localid for the specific statement with the global function ref.
const statementName = 'Encounter with Principal Diagnosis and Age';
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// For the fixture loaded for this test it is known that the library reference is 49 and the functionRef itself is 55.
expect(localIds[49]).toBeDefined();
Expand All @@ -108,7 +122,7 @@ describe('ClauseResultsHelpers', () => {

// Find the localid for the specific statement with the global function ref.
const statementName = 'Initial Population';
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// For the fixture loaded for this test it is known that the library reference is 109 and the functionRef itself is 110.
expect(localIds[109]).toBeDefined();
Expand All @@ -122,7 +136,7 @@ describe('ClauseResultsHelpers', () => {

// Find the localid for the specific statement with the global function ref.
const statementName = 'Comfort Measures during Hospitalization';
const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// For the fixture loaded for this test it is known that the library reference is 109 and the functionRef itself is 110.
expect(localIds[42]).toBeDefined();
Expand All @@ -133,7 +147,7 @@ describe('ClauseResultsHelpers', () => {
const libraryElm: ELM = getJSONFixture('elm/SimpleAliasUsage.json');
const statementName = 'Some Encounter';

const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// '8' is in the annotations but not in the ELM. We use '9' as the localId from the actual expression and subtract one from it
expect(localIds['8']).toEqual({ localId: '8', sourceLocalId: '6' });
Expand All @@ -143,7 +157,7 @@ describe('ClauseResultsHelpers', () => {
const libraryElm: ELM = getJSONFixture('elm/SimpleAliasFunctionRef.json');
const statementName = 'Some Encounter';

const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// '8' is in the annotations but not in the ELM. We use '9' as the localId from the actual expression and subtract one from it
expect(localIds['8']).toEqual({ localId: '8', sourceLocalId: '6' });
Expand All @@ -153,7 +167,7 @@ describe('ClauseResultsHelpers', () => {
const libraryElm: ELM = getJSONFixture('elm/queries/QICoreQuery.json');
const statementName = 'Query';

const localIds = ClauseResultsHelpers.findAllLocalIdsInStatementByName(libraryElm, statementName);
const localIds = ClauseResultsHelpers.findLocalIdsInStatementByName(libraryElm, statementName);

// '7' is in the annotations but not in the ELM. We use '8' as the localId from the actual expression and subtract one from it
expect(localIds[7]).toBeDefined();
Expand Down
24 changes: 24 additions & 0 deletions test/unit/fixtures/cql/NotNull.cql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
library Test

using FHIR version '4.0.1'

include FHIRHelpers version '4.0.1'
include MATGlobalCommonFunctions version '5.0.000' called Global

codesystem "EXAMPLE": 'http://example.com'
codesystem "EXAMPLE-2": 'http://example.com/2'
codesystem "ConditionClinicalStatusCodes": 'http://terminology.hl7.org/CodeSystem/condition-clinical'

valueset "test-vs": 'http://example.com/test-vs'

code "Active": 'active' from "ConditionClinicalStatusCodes"
code "Recurrence": 'recurrence' from "ConditionClinicalStatusCodes"
code "Relapse": 'relapse' from "ConditionClinicalStatusCodes"

concept "Condition Active": { "Active", "Recurrence", "Relapse" } display 'Active'

context Patient

define "Not Null Clause":
[Condition: "test-vs"] C
where C.id is not null
Loading

0 comments on commit 6811b6c

Please sign in to comment.