From 7556acc430d7d4463de3392c838710907df573db Mon Sep 17 00:00:00 2001 From: sarahmcdougall Date: Mon, 16 Oct 2023 15:37:40 -0400 Subject: [PATCH] update inner query reassignment, addFiltersToDataRequirements, etc. --- src/gaps/GapsReportBuilder.ts | 57 ++++++++++++++++++---------------- src/gaps/QueryFilterParser.ts | 58 ++++++++++++----------------------- 2 files changed, 49 insertions(+), 66 deletions(-) diff --git a/src/gaps/GapsReportBuilder.ts b/src/gaps/GapsReportBuilder.ts index 930ed9fa..3697cac0 100644 --- a/src/gaps/GapsReportBuilder.ts +++ b/src/gaps/GapsReportBuilder.ts @@ -614,39 +614,42 @@ export function addFiltersToDataRequirement( withErrors: GracefulError[] ) { if (q.queryInfo) { - // TODO: compare q.dataType to the resource type in queryInfo.source + const relevantSource = q.queryInfo.sources.find(source => source.resourceType === q.dataType); // if a source cannot be found that matches, exit the function - const detailedFilters = flattenFilters(q.queryInfo.filter); + if (relevantSource) { + const detailedFilters = flattenFilters(q.queryInfo.filter); - detailedFilters.forEach(df => { - // TODO: check the alias before doing an explicit casting of the filters - if (df.type === 'equals' || df.type === 'in') { - const cf = generateDetailedCodeFilter(df as EqualsFilter | InFilter, q.dataType); - - if (cf !== null) { - dataRequirement.codeFilter?.push(cf); - } - } else if (df.type === 'during') { + detailedFilters.forEach(df => { // DuringFilter, etc. inherit from attribute filter (and have alias on them) - const dateFilter = generateDetailedDateFilter(df as DuringFilter); - if (dataRequirement.dateFilter) { - dataRequirement.dateFilter.push(dateFilter); - } else { - dataRequirement.dateFilter = [dateFilter]; - } - } else { - const valueFilter = generateDetailedValueFilter(df); - if (didEncounterDetailedValueFilterErrors(valueFilter)) { - withErrors.push(valueFilter); - } else if (valueFilter) { - if (dataRequirement.extension) { - dataRequirement.extension.push(valueFilter); + if (relevantSource.alias === (df as AttributeFilter).alias) { + if (df.type === 'equals' || df.type === 'in') { + const cf = generateDetailedCodeFilter(df as EqualsFilter | InFilter, q.dataType); + + if (cf !== null) { + dataRequirement.codeFilter?.push(cf); + } + } else if (df.type === 'during') { + const dateFilter = generateDetailedDateFilter(df as DuringFilter); + if (dataRequirement.dateFilter) { + dataRequirement.dateFilter.push(dateFilter); + } else { + dataRequirement.dateFilter = [dateFilter]; + } } else { - dataRequirement.extension = [valueFilter]; + const valueFilter = generateDetailedValueFilter(df); + if (didEncounterDetailedValueFilterErrors(valueFilter)) { + withErrors.push(valueFilter); + } else if (valueFilter) { + if (dataRequirement.extension) { + dataRequirement.extension.push(valueFilter); + } else { + dataRequirement.extension = [valueFilter]; + } + } } } - } - }); + }); + } } } diff --git a/src/gaps/QueryFilterParser.ts b/src/gaps/QueryFilterParser.ts index afa8ba61..7f141311 100644 --- a/src/gaps/QueryFilterParser.ts +++ b/src/gaps/QueryFilterParser.ts @@ -155,10 +155,8 @@ export async function parseQueryInfo( patient ); - // use sources from inner query - // TODO: should copy over everything from inner query info, but for anything after 1st elem - replace first one, and copy over rest - // query.sources at index 0 = inner query info sources at index 0 (then copy over the rest using splice to copy everything except first and push on) - queryInfo.sources = innerQueryInfo.sources; + // use first source from inner query (only replaces the first source) + queryInfo.sources = [...innerQueryInfo.sources.slice(0, 1), ...queryInfo.sources.slice(1)]; // replace the filters for this query to match the inner source replaceAliasesInFilters(queryInfo.filter, query.source[0].alias, innerQuery.source[0].alias); @@ -226,14 +224,16 @@ function replaceAliasesInFilters(filter: AnyFilter, match: string, replace: stri } /** - * Parse information about the sources in a given query. + * Parse information about the sources in a given query. Treat relationships as sources. * - * @param query The Query expression to parse. + * @param query The Query to parse. The query source can consist of aliased query sources or relationship clauses. * @returns Information about each source. This is usually an array of one. */ function parseSources(query: ELMQuery): SourceInfo[] { const sources: SourceInfo[] = []; - query.source.forEach(source => { + const querySources = [...query.source, ...query.relationship]; + + querySources.forEach(source => { if (source.expression.type == 'Retrieve') { const sourceInfo: SourceInfo = { sourceLocalId: source.localId, @@ -242,6 +242,7 @@ function parseSources(query: ELMQuery): SourceInfo[] { resourceType: parseDataType(source.expression as ELMRetrieve) }; sources.push(sourceInfo); + // use the resultTypeSpecifier as a fallback if the expression is not a Retrieve } else if (source.expression.resultTypeSpecifier) { const sourceInfo: SourceInfo = { sourceLocalId: source.localId, @@ -251,19 +252,7 @@ function parseSources(query: ELMQuery): SourceInfo[] { sources.push(sourceInfo); } }); - query.relationship.forEach(relationship => { - // TODO: add conditional for parsing on the resultTypeSpecifier and consolidate logic between - // relationship and source - if (relationship.expression.type === 'Retrieve') { - const sourceInfo: SourceInfo = { - sourceLocalId: relationship.localId, - retrieveLocalId: relationship.expression.localId, - alias: relationship.alias, - resourceType: parseDataType(relationship.expression as ELMRetrieve) - }; - sources.push(sourceInfo); - } - }); + return sources; } @@ -287,24 +276,15 @@ function parseDataType(retrieve: ELMRetrieve): string { */ function parseElementType(expression: ELMExpression): string { const resultTypeSpecifier = expression.resultTypeSpecifier; - // check if the type is ListTypeSpecifier - if (resultTypeSpecifier?.type === 'ListTypeSpecifier') { - const listTypeSpecifier = resultTypeSpecifier as ListTypeSpecifier; - // check if the elementType is of type NamedTypeSpecifier - if (listTypeSpecifier.elementType.type === 'NamedTypeSpecifier') { - const elementType = listTypeSpecifier.elementType as NamedTypeSpecifier; - return elementType.name.replace(/^(\{http:\/\/hl7.org\/fhir\})?/, ''); - } else { - // TODO: just don't create the source info if the resource type can't be found, remove this error - throw new Error('ListTypeSpecifier elementType must be of type NamedTypeSpecifier.'); - } - } else { - // TODO: don't throw an error, mark as a warning instead - // if something is being defined as alias either from source or relationship but cant figure out the data type, - // want to mark it some way - // short-circuit out instead - throw new Error('Expression resultTypeSpecifier must be of type ListTypeSpecifier.'); + if ( + resultTypeSpecifier?.type === 'ListTypeSpecifier' && + (resultTypeSpecifier as ListTypeSpecifier).elementType.type === 'NamedTypeSpecifier' + ) { + const elementType = (resultTypeSpecifier as ListTypeSpecifier).elementType as NamedTypeSpecifier; + return elementType.name.replace(/^(\{http:\/\/hl7.org\/fhir\})?/, ''); } + console.warn(`Resource type cannot be found for ELM Expression with localId ${expression.localId}`); + return ''; } /** @@ -773,7 +753,7 @@ export function interpretIncludedIn( parameters: any ): DuringFilter | UnknownFilter { let propRef: ELMProperty | GracefulError | null = null; - let withError: GracefulError = { message: 'An unknown error occured' }; + let withError: GracefulError = { message: 'An unknown error occurred' }; if (includedIn.operand[0].type == 'FunctionRef') { propRef = interpretFunctionRef(includedIn.operand[0] as ELMFunctionRef, library); } else if (includedIn.operand[0].type == 'Property') { @@ -839,7 +819,7 @@ export async function interpretIn( parameters: any ): Promise { let propRef: ELMProperty | GracefulError | null = null; - let withError: GracefulError = { message: 'An unknown error occured' }; + let withError: GracefulError = { message: 'An unknown error occurred' }; if (inExpr.operand[0].type == 'FunctionRef') { propRef = interpretFunctionRef(inExpr.operand[0] as ELMFunctionRef, library); } else if (inExpr.operand[0].type == 'Property') {