88 AnyELMExpression ,
99 ELM ,
1010 ELMAliasedQuerySource ,
11+ ELMFunctionRef ,
1112 ELMIdentifier ,
1213 ELMLast ,
1314 ELMProperty ,
@@ -25,7 +26,7 @@ import {
2526 parseQueryInfo
2627} from './elm/QueryFilterParser' ;
2728import * as RetrievesHelper from './elm/RetrievesHelper' ;
28- import { uniqBy , isEqual } from 'lodash' ;
29+ import { uniqBy , isEqual , union } from 'lodash' ;
2930import { DateTime , Interval } from 'cql-execution' ;
3031import { parseTimeStringAsUTC } from '../execution/ValueSetHelper' ;
3132import * as MeasureBundleHelpers from './MeasureBundleHelpers' ;
@@ -104,16 +105,28 @@ export async function getDataRequirements(
104105 type : { coding : [ { code : 'module-definition' , system : 'http://terminology.hl7.org/CodeSystem/library-type' } ] } ,
105106 status : 'unknown'
106107 } ;
107- // TODO: combine must supports if there are data requirements from the same retrieve that have different mustSupports
108- // combine based on resourcetype/primary code path
109- // allRetrieves[0].templateId
110- // allRetrieves[0].dataType
111- // allRetrieves[0].path
112- // allRetrieves[0].code or allRetrieves[0].valueSet
113- // ^if these 4 things are the same, then it is the same data requirement and we can combine mustSupports
108+
109+ // Combine must supports if there are data requirements from the same retrieve that have different mustSupports
110+ // Combine based on this set defining uniqueness: templateId, dataType, path, and code/valueSet
111+ const retrievesHash = allRetrieves . reduce ( ( hash : Record < string , DataTypeQuery > , retrieve ) => {
112+ const hashKey = `${ retrieve . templateId } -${ retrieve . dataType } -${ retrieve . path } -${
113+ retrieve . code ?. code || retrieve . valueSet
114+ } `;
115+ if ( hash [ hashKey ] ) {
116+ if ( hash [ hashKey ] . mustSupport ) {
117+ // combine
118+ hash [ hashKey ] . mustSupport = union ( hash [ hashKey ] . mustSupport , retrieve . mustSupport ) ;
119+ } else {
120+ hash [ hashKey ] . mustSupport = retrieve . mustSupport ;
121+ }
122+ } else {
123+ hash [ hashKey ] = retrieve ;
124+ }
125+ return hash ;
126+ } , { } ) ;
114127
115128 results . dataRequirement = uniqBy (
116- allRetrieves . map ( retrieve => {
129+ Object . values ( retrievesHash ) . map ( retrieve => {
117130 const dr = generateDataRequirement ( retrieve ) ;
118131 addFiltersToDataRequirement ( retrieve , dr , withErrors ) ;
119132 addFhirQueryPatternToDataRequirements ( dr ) ;
@@ -129,7 +142,7 @@ export async function getDataRequirements(
129142 cql : cqls ,
130143 elm : elmJSONs ,
131144 gaps : {
132- retrieves : allRetrieves
145+ retrieves : Object . values ( retrievesHash )
133146 }
134147 } ,
135148 withErrors
@@ -625,15 +638,16 @@ export function findRetrieveMatches(prop: PropertyTracker, retrieves: DataTypeQu
625638 const { query, position, source } = scopedQuery ;
626639 // if the query is our stackMatch, stop here, otherwise continue with query source
627640 if ( position === 0 ) {
628- // confirm alias matches scope (i.e. the retrieve is somewhere within the source expression tree)
641+ // confirm alias matches scope (i.e. the retrieve is somewhere within the source expression tree) // TODO: this is not sufficient, multiple retrieves could be defined within the tree of this source, not just the right one
629642 return (
630643 source &&
631644 source . expression . localId &&
632645 ! ! retrieve . expressionStack ?. find ( st => st . localId === source . expression . localId )
633646 ) ;
634647 }
635648 if ( 'name' in source . expression && source . expression . name ) {
636- return checkStackFunctionDefs (
649+ // TODO: do we need any further checks here with the highest reference name?
650+ return ! ! checkStackFunctionDefs (
637651 prop . stack . slice ( matchIdx , matchIdx + position ) ,
638652 source . expression . name ,
639653 allELM
@@ -644,7 +658,34 @@ export function findRetrieveMatches(prop: PropertyTracker, retrieves: DataTypeQu
644658 // assume property.source (checked above)
645659 if ( prop . property . source && 'name' in prop . property . source && prop . property . source . name ) {
646660 // slice property stack from stackMatch id to end
647- return checkStackFunctionDefs ( prop . stack . slice ( matchIdx ) , prop . property . source . name , allELM ) ;
661+ const stackSlice = prop . stack . slice ( matchIdx ) ;
662+ const checkName = checkStackFunctionDefs ( stackSlice , prop . property . source . name , allELM ) ;
663+ if ( checkName ) {
664+ // get highest FunctionRef in the stack
665+ // check that the operand has an AliasRef type expression with a name equal to...
666+ // top alias in the retrieve stack (up to stackMatch) -> BipolarDiagnosis vs QualifyingEncounter
667+ // const funcRefStackEntry = stackSlice.find(se => se.type === 'FunctionRef');
668+ // if (!funcRefStackEntry) return true;
669+ // const aliasExp = (expressionFromStackEntry(funcRefStackEntry, allELM) as ELMFunctionRef).operand.find(
670+ // o => o.type === 'AliasRef'
671+ // );
672+
673+ const retMatchIdx = retrieve . expressionStack ?. findIndex (
674+ s => s . localId === stackMatch . localId && s . libraryName === stackMatch . libraryName
675+ ) ;
676+ const retSlice = retrieve . expressionStack ?. slice ( retMatchIdx ) ;
677+ const topAlias = retSlice
678+ ?. map ( se => {
679+ const exp = expressionFromStackEntry ( stackMatch , allELM ) ;
680+ return 'alias' in exp && exp . alias ? exp . alias : undefined ;
681+ } )
682+ . find ( a => ! ! a ) ;
683+
684+ if ( topAlias ) {
685+ return topAlias === checkName ;
686+ }
687+ return true ;
688+ }
648689 }
649690 return false ;
650691 }
@@ -662,7 +703,7 @@ export function findStackScopedQuery(stack: ExpressionStackEntry[], scope: strin
662703 // TODO: how do we handle this case (example AI&F query below localId 180 has no localId)
663704 continue ;
664705 }
665- const queryExpression = expressionFromStackEntry ( stack [ i ] , allELM ) ;
706+ const queryExpression = expressionFromStackEntry ( stack [ i ] , allELM ) as ELMQuery ;
666707 const source = queryExpression . source . find ( s => s . alias === scope ) ;
667708 if ( source ) {
668709 return { query : queryExpression , position : i , source : source } ;
@@ -686,7 +727,7 @@ export function expressionFromStackEntry(stackEntry: ExpressionStackEntry, allEL
686727 if ( ! lib ) {
687728 throw Error ( `Could not find library with identifier ${ stackEntry . libraryName } ` ) ;
688729 }
689- const expression = findClauseInLibrary ( lib , stackEntry . localId ) as ELMQuery ;
730+ const expression = findClauseInLibrary ( lib , stackEntry . localId ) ;
690731 if ( ! expression ) {
691732 throw Error (
692733 `Could not find ${ stackEntry . type } type expression in ${ stackEntry . libraryName } with localId ${ stackEntry . localId } `
@@ -696,12 +737,13 @@ export function expressionFromStackEntry(stackEntry: ExpressionStackEntry, allEL
696737}
697738
698739// traverse from end of the stack to check all function definitions use name in operand
699- // TODO: need to traverse from end in order to change approach partway up, or can we traverse from beginning?
740+ //check functiondef to functionref changeover and output last name/alias at the end (switches to an AliasRef instead of operand ref, then can use for comparison at the top level)
700741export function checkStackFunctionDefs ( stack : ExpressionStackEntry [ ] , name : string , allELM : ELM [ ] ) {
742+ let checkName = name ;
701743 // return false if no function defs
702744 if ( ! stack . find ( s => s . type === 'FunctionDef' ) ) return false ;
703745
704- for ( let i = stack . length - 1 ; i >= 0 ; i -- ) {
746+ for ( let i = stack . length - 1 ; i > 0 ; i -- ) {
705747 if ( stack [ i ] . type === 'FunctionDef' ) {
706748 const lib = allELM . find ( e => e . library . identifier . id === stack [ i ] . libraryName ) ;
707749 const functionStatement = lib ?. library . statements . def . find ( s => s . localId === stack [ i ] . localId ) ;
@@ -710,12 +752,21 @@ export function checkStackFunctionDefs(stack: ExpressionStackEntry[], name: stri
710752 `Unable to find function definition statement with localId ${ stack [ i ] . localId } in library ${ stack [ i ] . libraryName } `
711753 ) ;
712754 }
713- if ( ! checkFunctionDefMatch ( functionStatement , name ) ) {
714- return false ;
755+ if ( ! checkFunctionDefMatch ( functionStatement , checkName ) ) {
756+ // we've hit an operand missmatch, so it's a deadend that should be ignored
757+ return null ;
758+ }
759+ // get new name
760+ if ( stack [ i - 1 ] . type === 'FunctionRef' && stack [ i - 1 ] . localId !== 'unknown' ) {
761+ const functionRef = expressionFromStackEntry ( stack [ i - 1 ] , allELM ) as ELMFunctionRef ;
762+ // find first operand with a name. TODO: do we have any other differentiating factors for finding the right operand?
763+ const operand = functionRef . operand . find ( o => 'name' in o && o . name ) ;
764+ if ( operand && 'name' in operand && operand . name ) checkName = operand . name ;
715765 }
716766 }
717767 }
718- return true ;
768+ // final checkName can be used for further checks
769+ return checkName ;
719770}
720771
721772// return true if function def operand matches the passed source name
0 commit comments