diff --git a/src/code-attributes/propertyPaths.ts b/src/code-attributes/propertyPaths.ts new file mode 100644 index 00000000..9f78930c --- /dev/null +++ b/src/code-attributes/propertyPaths.ts @@ -0,0 +1,2618 @@ +// Source: https://github.com/projecttacoma/fqm-testify/blob/parse-properties/util/propertyPaths.ts + +export const parsedPropertyPaths: Record = { + Account: [ + 'identifier', + 'status', + 'type', + 'name', + 'subject', + 'servicePeriod', + 'coverage', + 'owner', + 'description', + 'guarantor', + 'partOf' + ], + ActivityDefinition: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'subtitle', + 'status', + 'experimental', + 'subject', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'usage', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'library', + 'kind', + 'profile', + 'code', + 'intent', + 'priority', + 'doNotPerform', + 'timing', + 'location', + 'participant', + 'product', + 'quantity', + 'dosage', + 'bodySite', + 'specimenRequirement', + 'observationRequirement', + 'observationResultRequirement', + 'transform', + 'dynamicValue' + ], + AdverseEvent: [ + 'identifier', + 'actuality', + 'category', + 'event', + 'subject', + 'encounter', + 'date', + 'detected', + 'recordedDate', + 'resultingCondition', + 'location', + 'seriousness', + 'severity', + 'outcome', + 'recorder', + 'contributor', + 'suspectEntity', + 'subjectMedicalHistory', + 'referenceDocument', + 'study' + ], + AllergyIntolerance: [ + 'identifier', + 'clinicalStatus', + 'verificationStatus', + 'type', + 'category', + 'criticality', + 'code', + 'patient', + 'encounter', + 'onset', + 'recordedDate', + 'recorder', + 'asserter', + 'lastOccurrence', + 'note', + 'reaction' + ], + Appointment: [ + 'identifier', + 'status', + 'cancelationReason', + 'serviceCategory', + 'serviceType', + 'specialty', + 'appointmentType', + 'reasonCode', + 'reasonReference', + 'priority', + 'description', + 'supportingInformation', + 'start', + 'end', + 'minutesDuration', + 'slot', + 'created', + 'comment', + 'patientInstruction', + 'basedOn', + 'participant', + 'requestedPeriod' + ], + AppointmentResponse: [ + 'identifier', + 'appointment', + 'start', + 'end', + 'participantType', + 'actor', + 'participantStatus', + 'comment' + ], + AuditEvent: [ + 'type', + 'subtype', + 'action', + 'period', + 'recorded', + 'outcome', + 'outcomeDesc', + 'purposeOfEvent', + 'agent', + 'source', + 'entity' + ], + Basic: ['identifier', 'code', 'subject', 'created', 'author'], + BiologicallyDerivedProduct: [ + 'identifier', + 'productCategory', + 'productCode', + 'status', + 'request', + 'quantity', + 'parent', + 'collection', + 'processing', + 'manipulation', + 'storage' + ], + BodyStructure: [ + 'identifier', + 'active', + 'morphology', + 'location', + 'locationQualifier', + 'description', + 'image', + 'patient' + ], + CapabilityStatement: [ + 'url', + 'version', + 'name', + 'title', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'copyright', + 'kind', + 'instantiates', + 'imports', + 'software', + 'implementation', + 'fhirVersion', + 'format', + 'patchFormat', + 'implementationGuide', + 'rest', + 'messaging', + 'document' + ], + CarePlan: [ + 'identifier', + 'instantiatesCanonical', + 'instantiatesUri', + 'basedOn', + 'replaces', + 'partOf', + 'status', + 'intent', + 'category', + 'title', + 'description', + 'subject', + 'encounter', + 'period', + 'created', + 'author', + 'contributor', + 'careTeam', + 'addresses', + 'supportingInfo', + 'goal', + 'activity', + 'note' + ], + CareTeam: [ + 'identifier', + 'status', + 'category', + 'name', + 'subject', + 'encounter', + 'period', + 'participant', + 'reasonCode', + 'reasonReference', + 'managingOrganization', + 'telecom', + 'note' + ], + CatalogEntry: [ + 'identifier', + 'type', + 'orderable', + 'referencedItem', + 'additionalIdentifier', + 'classification', + 'status', + 'validityPeriod', + 'validTo', + 'lastUpdated', + 'additionalCharacteristic', + 'additionalClassification', + 'relatedEntry' + ], + ChargeItem: [ + 'identifier', + 'definitionUri', + 'definitionCanonical', + 'status', + 'partOf', + 'code', + 'subject', + 'context', + 'occurrence', + 'performer', + 'performingOrganization', + 'requestingOrganization', + 'costCenter', + 'quantity', + 'bodysite', + 'factorOverride', + 'priceOverride', + 'overrideReason', + 'enterer', + 'enteredDate', + 'reason', + 'service', + 'product', + 'account', + 'note', + 'supportingInformation' + ], + ChargeItemDefinition: [ + 'url', + 'identifier', + 'version', + 'title', + 'derivedFromUri', + 'partOf', + 'replaces', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'code', + 'instance', + 'applicability', + 'propertyGroup' + ], + Claim: [ + 'identifier', + 'status', + 'type', + 'subType', + 'use', + 'patient', + 'billablePeriod', + 'created', + 'enterer', + 'insurer', + 'provider', + 'priority', + 'fundsReserve', + 'related', + 'prescription', + 'originalPrescription', + 'payee', + 'referral', + 'facility', + 'careTeam', + 'supportingInfo', + 'diagnosis', + 'procedure', + 'insurance', + 'accident', + 'item', + 'total' + ], + ClaimResponse: [ + 'identifier', + 'status', + 'type', + 'subType', + 'use', + 'patient', + 'created', + 'insurer', + 'requestor', + 'request', + 'outcome', + 'disposition', + 'preAuthRef', + 'preAuthPeriod', + 'payeeType', + 'item', + 'addItem', + 'adjudication', + 'total', + 'payment', + 'fundsReserve', + 'formCode', + 'form', + 'processNote', + 'communicationRequest', + 'insurance', + 'error' + ], + ClinicalImpression: [ + 'identifier', + 'status', + 'statusReason', + 'code', + 'description', + 'subject', + 'encounter', + 'effective', + 'date', + 'assessor', + 'previous', + 'problem', + 'investigation', + 'protocol', + 'summary', + 'finding', + 'prognosisCodeableConcept', + 'prognosisReference', + 'supportingInfo', + 'note' + ], + CodeSystem: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'copyright', + 'caseSensitive', + 'valueSet', + 'hierarchyMeaning', + 'compositional', + 'versionNeeded', + 'content', + 'supplements', + 'count', + 'filter', + 'property', + 'concept' + ], + Communication: [ + 'identifier', + 'instantiatesCanonical', + 'instantiatesUri', + 'basedOn', + 'partOf', + 'inResponseTo', + 'status', + 'statusReason', + 'category', + 'priority', + 'medium', + 'subject', + 'topic', + 'about', + 'encounter', + 'sent', + 'received', + 'recipient', + 'sender', + 'reasonCode', + 'reasonReference', + 'payload', + 'note' + ], + CommunicationRequest: [ + 'identifier', + 'basedOn', + 'replaces', + 'groupIdentifier', + 'status', + 'statusReason', + 'category', + 'priority', + 'doNotPerform', + 'medium', + 'subject', + 'about', + 'encounter', + 'payload', + 'occurrence', + 'authoredOn', + 'requester', + 'recipient', + 'sender', + 'reasonCode', + 'reasonReference', + 'note' + ], + CompartmentDefinition: [ + 'url', + 'version', + 'name', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'purpose', + 'code', + 'search', + 'resource' + ], + Composition: [ + 'identifier', + 'status', + 'type', + 'category', + 'subject', + 'encounter', + 'date', + 'author', + 'title', + 'confidentiality', + 'attester', + 'custodian', + 'relatesTo', + 'event', + 'section' + ], + ConceptMap: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'copyright', + 'source', + 'target', + 'group' + ], + Condition: [ + 'identifier', + 'clinicalStatus', + 'verificationStatus', + 'category', + 'severity', + 'code', + 'bodySite', + 'subject', + 'encounter', + 'onset', + 'abatement', + 'recordedDate', + 'recorder', + 'asserter', + 'stage', + 'evidence', + 'note' + ], + Consent: [ + 'identifier', + 'status', + 'scope', + 'category', + 'patient', + 'dateTime', + 'performer', + 'organization', + 'source', + 'policy', + 'policyRule', + 'verification', + 'provision' + ], + Contract: [ + 'identifier', + 'url', + 'version', + 'status', + 'legalState', + 'instantiatesCanonical', + 'instantiatesUri', + 'contentDerivative', + 'issued', + 'applies', + 'expirationType', + 'subject', + 'authority', + 'domain', + 'site', + 'name', + 'title', + 'subtitle', + 'alias', + 'author', + 'scope', + 'topic', + 'type', + 'subType', + 'contentDefinition', + 'term', + 'supportingInfo', + 'relevantHistory', + 'signer', + 'friendly', + 'legal', + 'rule', + 'legallyBinding' + ], + Coverage: [ + 'identifier', + 'status', + 'type', + 'policyHolder', + 'subscriber', + 'subscriberId', + 'beneficiary', + 'dependent', + 'relationship', + 'period', + 'payor', + 'class', + 'order', + 'network', + 'costToBeneficiary', + 'subrogation', + 'contract' + ], + CoverageEligibilityRequest: [ + 'identifier', + 'status', + 'priority', + 'purpose', + 'patient', + 'serviced', + 'created', + 'enterer', + 'provider', + 'insurer', + 'facility', + 'supportingInfo', + 'insurance', + 'item' + ], + CoverageEligibilityResponse: [ + 'identifier', + 'status', + 'purpose', + 'patient', + 'serviced', + 'created', + 'requestor', + 'request', + 'outcome', + 'disposition', + 'insurer', + 'insurance', + 'preAuthRef', + 'form', + 'error' + ], + DetectedIssue: [ + 'identifier', + 'status', + 'code', + 'severity', + 'patient', + 'identified', + 'author', + 'implicated', + 'evidence', + 'detail', + 'reference', + 'mitigation' + ], + Device: [ + 'identifier', + 'definition', + 'udiCarrier', + 'status', + 'statusReason', + 'distinctIdentifier', + 'manufacturer', + 'manufactureDate', + 'expirationDate', + 'lotNumber', + 'serialNumber', + 'deviceName', + 'modelNumber', + 'partNumber', + 'type', + 'specialization', + 'version', + 'property', + 'patient', + 'owner', + 'contact', + 'location', + 'url', + 'note', + 'safety', + 'parent' + ], + DeviceDefinition: [ + 'identifier', + 'udiDeviceIdentifier', + 'manufacturer', + 'deviceName', + 'modelNumber', + 'type', + 'specialization', + 'version', + 'safety', + 'shelfLifeStorage', + 'physicalCharacteristics', + 'languageCode', + 'capability', + 'property', + 'owner', + 'contact', + 'url', + 'onlineInformation', + 'note', + 'quantity', + 'parentDevice', + 'material' + ], + DeviceMetric: [ + 'identifier', + 'type', + 'unit', + 'source', + 'parent', + 'operationalStatus', + 'color', + 'category', + 'measurementPeriod', + 'calibration' + ], + DeviceRequest: [ + 'identifier', + 'instantiatesCanonical', + 'instantiatesUri', + 'basedOn', + 'priorRequest', + 'groupIdentifier', + 'status', + 'intent', + 'priority', + 'code', + 'parameter', + 'subject', + 'encounter', + 'occurrence', + 'authoredOn', + 'requester', + 'performerType', + 'performer', + 'reasonCode', + 'reasonReference', + 'insurance', + 'supportingInfo', + 'note', + 'relevantHistory' + ], + DeviceUseStatement: [ + 'identifier', + 'basedOn', + 'status', + 'subject', + 'derivedFrom', + 'timing', + 'recordedOn', + 'source', + 'device', + 'reasonCode', + 'reasonReference', + 'bodySite', + 'note' + ], + DiagnosticReport: [ + 'identifier', + 'basedOn', + 'status', + 'category', + 'code', + 'subject', + 'encounter', + 'effective', + 'issued', + 'performer', + 'resultsInterpreter', + 'specimen', + 'result', + 'imagingStudy', + 'media', + 'conclusion', + 'conclusionCode', + 'presentedForm' + ], + DocumentManifest: [ + 'masterIdentifier', + 'identifier', + 'status', + 'type', + 'subject', + 'created', + 'author', + 'recipient', + 'source', + 'description', + 'content', + 'related' + ], + DocumentReference: [ + 'masterIdentifier', + 'identifier', + 'status', + 'docStatus', + 'type', + 'category', + 'subject', + 'date', + 'author', + 'authenticator', + 'custodian', + 'relatesTo', + 'description', + 'securityLabel', + 'content', + 'context' + ], + EffectEvidenceSynthesis: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'status', + 'date', + 'publisher', + 'contact', + 'description', + 'note', + 'useContext', + 'jurisdiction', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'synthesisType', + 'studyType', + 'population', + 'exposure', + 'exposureAlternative', + 'outcome', + 'sampleSize', + 'resultsByExposure', + 'effectEstimate', + 'certainty' + ], + Encounter: [ + 'identifier', + 'status', + 'statusHistory', + 'class', + 'classHistory', + 'type', + 'serviceType', + 'priority', + 'subject', + 'episodeOfCare', + 'basedOn', + 'participant', + 'appointment', + 'period', + 'length', + 'reasonCode', + 'reasonReference', + 'diagnosis', + 'account', + 'hospitalization', + 'location', + 'serviceProvider', + 'partOf' + ], + Endpoint: [ + 'identifier', + 'status', + 'connectionType', + 'name', + 'managingOrganization', + 'contact', + 'period', + 'payloadType', + 'payloadMimeType', + 'address', + 'header' + ], + EnrollmentRequest: ['identifier', 'status', 'created', 'insurer', 'provider', 'candidate', 'coverage'], + EnrollmentResponse: [ + 'identifier', + 'status', + 'request', + 'outcome', + 'disposition', + 'created', + 'organization', + 'requestProvider' + ], + EpisodeOfCare: [ + 'identifier', + 'status', + 'statusHistory', + 'type', + 'diagnosis', + 'patient', + 'managingOrganization', + 'period', + 'referralRequest', + 'careManager', + 'team', + 'account' + ], + EventDefinition: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'subtitle', + 'status', + 'experimental', + 'subject', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'usage', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'trigger' + ], + Evidence: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'shortTitle', + 'subtitle', + 'status', + 'date', + 'publisher', + 'contact', + 'description', + 'note', + 'useContext', + 'jurisdiction', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'exposureBackground', + 'exposureVariant', + 'outcome' + ], + EvidenceVariable: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'shortTitle', + 'subtitle', + 'status', + 'date', + 'publisher', + 'contact', + 'description', + 'note', + 'useContext', + 'jurisdiction', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'type', + 'characteristic' + ], + ExampleScenario: [ + 'url', + 'identifier', + 'version', + 'name', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'useContext', + 'jurisdiction', + 'copyright', + 'purpose', + 'actor', + 'instance', + 'process', + 'workflow' + ], + ExplanationOfBenefit: [ + 'identifier', + 'status', + 'type', + 'subType', + 'use', + 'patient', + 'billablePeriod', + 'created', + 'enterer', + 'insurer', + 'provider', + 'priority', + 'fundsReserveRequested', + 'fundsReserve', + 'related', + 'prescription', + 'originalPrescription', + 'payee', + 'referral', + 'facility', + 'claim', + 'claimResponse', + 'outcome', + 'disposition', + 'preAuthRef', + 'preAuthRefPeriod', + 'careTeam', + 'supportingInfo', + 'diagnosis', + 'procedure', + 'precedence', + 'insurance', + 'accident', + 'item', + 'addItem', + 'adjudication', + 'total', + 'payment', + 'formCode', + 'form', + 'processNote', + 'benefitPeriod', + 'benefitBalance' + ], + FamilyMemberHistory: [ + 'identifier', + 'instantiatesCanonical', + 'instantiatesUri', + 'status', + 'dataAbsentReason', + 'patient', + 'date', + 'name', + 'relationship', + 'sex', + 'born', + 'age', + 'estimatedAge', + 'deceased', + 'reasonCode', + 'reasonReference', + 'note', + 'condition' + ], + Flag: ['identifier', 'status', 'category', 'code', 'subject', 'period', 'encounter', 'author'], + Goal: [ + 'identifier', + 'lifecycleStatus', + 'achievementStatus', + 'category', + 'priority', + 'description', + 'subject', + 'start', + 'target', + 'statusDate', + 'statusReason', + 'expressedBy', + 'addresses', + 'note', + 'outcomeCode', + 'outcomeReference' + ], + GraphDefinition: [ + 'url', + 'version', + 'name', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'start', + 'profile', + 'link' + ], + Group: [ + 'identifier', + 'active', + 'type', + 'actual', + 'code', + 'name', + 'quantity', + 'managingEntity', + 'characteristic', + 'member' + ], + GuidanceResponse: [ + 'requestIdentifier', + 'identifier', + 'module', + 'status', + 'subject', + 'encounter', + 'occurrenceDateTime', + 'performer', + 'reasonCode', + 'reasonReference', + 'note', + 'evaluationMessage', + 'outputParameters', + 'result', + 'dataRequirement' + ], + HealthcareService: [ + 'identifier', + 'active', + 'providedBy', + 'category', + 'type', + 'specialty', + 'location', + 'name', + 'comment', + 'extraDetails', + 'photo', + 'telecom', + 'coverageArea', + 'serviceProvisionCode', + 'eligibility', + 'program', + 'characteristic', + 'communication', + 'referralMethod', + 'appointmentRequired', + 'availableTime', + 'notAvailable', + 'availabilityExceptions', + 'endpoint' + ], + ImagingStudy: [ + 'identifier', + 'status', + 'modality', + 'subject', + 'encounter', + 'started', + 'basedOn', + 'referrer', + 'interpreter', + 'endpoint', + 'numberOfSeries', + 'numberOfInstances', + 'procedureReference', + 'procedureCode', + 'location', + 'reasonCode', + 'reasonReference', + 'note', + 'description', + 'series' + ], + Immunization: [ + 'identifier', + 'status', + 'statusReason', + 'vaccineCode', + 'patient', + 'encounter', + 'occurrence', + 'recorded', + 'primarySource', + 'reportOrigin', + 'location', + 'manufacturer', + 'lotNumber', + 'expirationDate', + 'site', + 'route', + 'doseQuantity', + 'performer', + 'note', + 'reasonCode', + 'reasonReference', + 'isSubpotent', + 'subpotentReason', + 'education', + 'programEligibility', + 'fundingSource', + 'reaction', + 'protocolApplied' + ], + ImmunizationEvaluation: [ + 'identifier', + 'status', + 'patient', + 'date', + 'authority', + 'targetDisease', + 'immunizationEvent', + 'doseStatus', + 'doseStatusReason', + 'description', + 'series', + 'doseNumber', + 'seriesDoses' + ], + ImmunizationRecommendation: ['identifier', 'patient', 'date', 'authority', 'recommendation'], + ImplementationGuide: [ + 'url', + 'version', + 'name', + 'title', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'copyright', + 'packageId', + 'license', + 'fhirVersion', + 'dependsOn', + 'global', + 'definition', + 'manifest' + ], + InsurancePlan: [ + 'identifier', + 'status', + 'type', + 'name', + 'alias', + 'period', + 'ownedBy', + 'administeredBy', + 'coverageArea', + 'contact', + 'endpoint', + 'network', + 'coverage', + 'plan' + ], + Invoice: [ + 'identifier', + 'status', + 'cancelledReason', + 'type', + 'subject', + 'recipient', + 'date', + 'participant', + 'issuer', + 'account', + 'lineItem', + 'totalPriceComponent', + 'totalNet', + 'totalGross', + 'paymentTerms', + 'note' + ], + Library: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'subtitle', + 'status', + 'experimental', + 'type', + 'subject', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'usage', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'parameter', + 'dataRequirement', + 'content' + ], + Linkage: ['active', 'author', 'item'], + List: [ + 'identifier', + 'status', + 'mode', + 'title', + 'code', + 'subject', + 'encounter', + 'date', + 'source', + 'orderedBy', + 'note', + 'entry', + 'emptyReason' + ], + Location: [ + 'identifier', + 'status', + 'operationalStatus', + 'name', + 'alias', + 'description', + 'mode', + 'type', + 'telecom', + 'address', + 'physicalType', + 'position', + 'managingOrganization', + 'partOf', + 'hoursOfOperation', + 'availabilityExceptions', + 'endpoint' + ], + Measure: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'subtitle', + 'status', + 'experimental', + 'subject', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'usage', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'library', + 'disclaimer', + 'scoring', + 'compositeScoring', + 'type', + 'riskAdjustment', + 'rateAggregation', + 'rationale', + 'clinicalRecommendationStatement', + 'improvementNotation', + 'definition', + 'guidance', + 'group', + 'supplementalData' + ], + MeasureReport: [ + 'identifier', + 'status', + 'type', + 'measure', + 'subject', + 'date', + 'reporter', + 'period', + 'improvementNotation', + 'group', + 'evaluatedResource' + ], + Media: [ + 'identifier', + 'basedOn', + 'partOf', + 'status', + 'type', + 'modality', + 'view', + 'subject', + 'encounter', + 'created', + 'issued', + 'operator', + 'reasonCode', + 'bodySite', + 'deviceName', + 'device', + 'height', + 'width', + 'frames', + 'duration', + 'content', + 'note' + ], + Medication: ['identifier', 'code', 'status', 'manufacturer', 'form', 'amount', 'ingredient', 'batch'], + MedicationAdministration: [ + 'identifier', + 'instantiates', + 'partOf', + 'status', + 'statusReason', + 'category', + 'medication', + 'subject', + 'context', + 'supportingInformation', + 'effective', + 'performer', + 'reasonCode', + 'reasonReference', + 'request', + 'device', + 'note', + 'dosage', + 'eventHistory' + ], + MedicationDispense: [ + 'identifier', + 'partOf', + 'status', + 'statusReason', + 'category', + 'medication', + 'subject', + 'context', + 'supportingInformation', + 'performer', + 'location', + 'authorizingPrescription', + 'type', + 'quantity', + 'daysSupply', + 'whenPrepared', + 'whenHandedOver', + 'destination', + 'receiver', + 'note', + 'dosageInstruction', + 'substitution', + 'detectedIssue', + 'eventHistory' + ], + MedicationKnowledge: [ + 'code', + 'status', + 'manufacturer', + 'doseForm', + 'amount', + 'synonym', + 'relatedMedicationKnowledge', + 'associatedMedication', + 'productType', + 'monograph', + 'ingredient', + 'preparationInstruction', + 'intendedRoute', + 'cost', + 'monitoringProgram', + 'administrationGuidelines', + 'medicineClassification', + 'packaging', + 'drugCharacteristic', + 'contraindication', + 'regulatory', + 'kinetics' + ], + MedicationRequest: [ + 'identifier', + 'status', + 'statusReason', + 'intent', + 'category', + 'priority', + 'doNotPerform', + 'reported', + 'medication', + 'subject', + 'encounter', + 'supportingInformation', + 'authoredOn', + 'requester', + 'performer', + 'performerType', + 'recorder', + 'reasonCode', + 'reasonReference', + 'instantiatesCanonical', + 'instantiatesUri', + 'basedOn', + 'groupIdentifier', + 'courseOfTherapyType', + 'insurance', + 'note', + 'dosageInstruction', + 'dispenseRequest', + 'substitution', + 'priorPrescription', + 'detectedIssue', + 'eventHistory' + ], + MedicationStatement: [ + 'identifier', + 'basedOn', + 'partOf', + 'status', + 'statusReason', + 'category', + 'medication', + 'subject', + 'context', + 'effective', + 'dateAsserted', + 'informationSource', + 'derivedFrom', + 'reasonCode', + 'reasonReference', + 'note', + 'dosage' + ], + MedicinalProduct: [ + 'identifier', + 'type', + 'domain', + 'combinedPharmaceuticalDoseForm', + 'legalStatusOfSupply', + 'additionalMonitoringIndicator', + 'specialMeasures', + 'paediatricUseIndicator', + 'productClassification', + 'marketingStatus', + 'pharmaceuticalProduct', + 'packagedMedicinalProduct', + 'attachedDocument', + 'masterFile', + 'contact', + 'clinicalTrial', + 'name', + 'crossReference', + 'manufacturingBusinessOperation', + 'specialDesignation' + ], + MedicinalProductAuthorization: [ + 'identifier', + 'subject', + 'country', + 'jurisdiction', + 'status', + 'statusDate', + 'restoreDate', + 'validityPeriod', + 'dataExclusivityPeriod', + 'dateOfFirstAuthorization', + 'internationalBirthDate', + 'legalBasis', + 'jurisdictionalAuthorization', + 'holder', + 'regulator', + 'procedure' + ], + MedicinalProductContraindication: [ + 'subject', + 'disease', + 'diseaseStatus', + 'comorbidity', + 'therapeuticIndication', + 'otherTherapy', + 'population' + ], + MedicinalProductIndication: [ + 'subject', + 'diseaseSymptomProcedure', + 'diseaseStatus', + 'comorbidity', + 'intendedEffect', + 'duration', + 'otherTherapy', + 'undesirableEffect', + 'population' + ], + MedicinalProductIngredient: [ + 'identifier', + 'role', + 'allergenicIndicator', + 'manufacturer', + 'specifiedSubstance', + 'substance' + ], + MedicinalProductInteraction: ['subject', 'description', 'interactant', 'type', 'effect', 'incidence', 'management'], + MedicinalProductManufactured: [ + 'manufacturedDoseForm', + 'unitOfPresentation', + 'quantity', + 'manufacturer', + 'ingredient', + 'physicalCharacteristics', + 'otherCharacteristics' + ], + MedicinalProductPackaged: [ + 'identifier', + 'subject', + 'description', + 'legalStatusOfSupply', + 'marketingStatus', + 'marketingAuthorization', + 'manufacturer', + 'batchIdentifier', + 'packageItem' + ], + MedicinalProductPharmaceutical: [ + 'identifier', + 'administrableDoseForm', + 'unitOfPresentation', + 'ingredient', + 'device', + 'characteristics', + 'routeOfAdministration' + ], + MedicinalProductUndesirableEffect: [ + 'subject', + 'symptomConditionEffect', + 'classification', + 'frequencyOfOccurrence', + 'population' + ], + MessageDefinition: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'replaces', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'copyright', + 'base', + 'parent', + 'event', + 'category', + 'focus', + 'responseRequired', + 'allowedResponse', + 'graph' + ], + MessageHeader: [ + 'event', + 'destination', + 'sender', + 'enterer', + 'author', + 'source', + 'responsible', + 'reason', + 'response', + 'focus', + 'definition' + ], + MolecularSequence: [ + 'identifier', + 'type', + 'coordinateSystem', + 'patient', + 'specimen', + 'device', + 'performer', + 'quantity', + 'referenceSeq', + 'variant', + 'observedSeq', + 'quality', + 'readCoverage', + 'repository', + 'pointer', + 'structureVariant' + ], + NamingSystem: [ + 'name', + 'status', + 'kind', + 'date', + 'publisher', + 'contact', + 'responsible', + 'type', + 'description', + 'useContext', + 'jurisdiction', + 'usage', + 'uniqueId' + ], + NutritionOrder: [ + 'identifier', + 'instantiatesCanonical', + 'instantiatesUri', + 'instantiates', + 'status', + 'intent', + 'patient', + 'encounter', + 'dateTime', + 'orderer', + 'allergyIntolerance', + 'foodPreferenceModifier', + 'excludeFoodModifier', + 'oralDiet', + 'supplement', + 'enteralFormula', + 'note' + ], + Observation: [ + 'identifier', + 'basedOn', + 'partOf', + 'status', + 'category', + 'code', + 'subject', + 'focus', + 'encounter', + 'effective', + 'issued', + 'performer', + 'value', + 'dataAbsentReason', + 'interpretation', + 'note', + 'bodySite', + 'method', + 'specimen', + 'device', + 'referenceRange', + 'hasMember', + 'derivedFrom', + 'component' + ], + ObservationDefinition: [ + 'category', + 'code', + 'identifier', + 'permittedDataType', + 'multipleResultsAllowed', + 'method', + 'preferredReportName', + 'quantitativeDetails', + 'qualifiedInterval', + 'validCodedValueSet', + 'normalCodedValueSet', + 'abnormalCodedValueSet', + 'criticalCodedValueSet' + ], + OperationDefinition: [ + 'url', + 'version', + 'name', + 'title', + 'status', + 'kind', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'affectsState', + 'code', + 'comment', + 'base', + 'resource', + 'system', + 'type', + 'instance', + 'inputProfile', + 'outputProfile', + 'parameter', + 'overload' + ], + OperationOutcome: ['issue'], + Organization: [ + 'identifier', + 'active', + 'type', + 'name', + 'alias', + 'telecom', + 'address', + 'partOf', + 'contact', + 'endpoint' + ], + OrganizationAffiliation: [ + 'identifier', + 'active', + 'period', + 'organization', + 'participatingOrganization', + 'network', + 'code', + 'specialty', + 'location', + 'healthcareService', + 'telecom', + 'endpoint' + ], + Patient: [ + 'identifier', + 'active', + 'name', + 'telecom', + 'gender', + 'birthDate', + 'deceased', + 'address', + 'maritalStatus', + 'multipleBirth', + 'photo', + 'contact', + 'communication', + 'generalPractitioner', + 'managingOrganization', + 'link' + ], + PaymentNotice: [ + 'identifier', + 'status', + 'request', + 'response', + 'created', + 'provider', + 'payment', + 'paymentDate', + 'payee', + 'recipient', + 'amount', + 'paymentStatus' + ], + PaymentReconciliation: [ + 'identifier', + 'status', + 'period', + 'created', + 'paymentIssuer', + 'request', + 'requestor', + 'outcome', + 'disposition', + 'paymentDate', + 'paymentAmount', + 'paymentIdentifier', + 'detail', + 'formCode', + 'processNote' + ], + Person: [ + 'identifier', + 'name', + 'telecom', + 'gender', + 'birthDate', + 'address', + 'photo', + 'managingOrganization', + 'active', + 'link' + ], + PlanDefinition: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'subtitle', + 'type', + 'status', + 'experimental', + 'subject', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'usage', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'library', + 'goal', + 'action' + ], + Practitioner: [ + 'identifier', + 'active', + 'name', + 'telecom', + 'address', + 'gender', + 'birthDate', + 'photo', + 'qualification', + 'communication' + ], + PractitionerRole: [ + 'identifier', + 'active', + 'period', + 'practitioner', + 'organization', + 'code', + 'specialty', + 'location', + 'healthcareService', + 'telecom', + 'availableTime', + 'notAvailable', + 'availabilityExceptions', + 'endpoint' + ], + Procedure: [ + 'identifier', + 'instantiatesCanonical', + 'instantiatesUri', + 'basedOn', + 'partOf', + 'status', + 'statusReason', + 'category', + 'code', + 'subject', + 'encounter', + 'performed', + 'recorder', + 'asserter', + 'performer', + 'location', + 'reasonCode', + 'reasonReference', + 'bodySite', + 'outcome', + 'report', + 'complication', + 'complicationDetail', + 'followUp', + 'note', + 'focalDevice', + 'usedReference', + 'usedCode' + ], + Provenance: [ + 'target', + 'occurred', + 'recorded', + 'policy', + 'location', + 'reason', + 'activity', + 'agent', + 'entity', + 'signature' + ], + Questionnaire: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'derivedFrom', + 'status', + 'experimental', + 'subjectType', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'code', + 'item' + ], + QuestionnaireResponse: [ + 'identifier', + 'basedOn', + 'partOf', + 'questionnaire', + 'status', + 'subject', + 'encounter', + 'authored', + 'author', + 'source', + 'item' + ], + RelatedPerson: [ + 'identifier', + 'active', + 'patient', + 'relationship', + 'name', + 'telecom', + 'gender', + 'birthDate', + 'address', + 'photo', + 'period', + 'communication' + ], + RequestGroup: [ + 'identifier', + 'instantiatesCanonical', + 'instantiatesUri', + 'basedOn', + 'replaces', + 'groupIdentifier', + 'status', + 'intent', + 'priority', + 'code', + 'subject', + 'encounter', + 'authoredOn', + 'author', + 'reasonCode', + 'reasonReference', + 'note', + 'action' + ], + ResearchDefinition: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'shortTitle', + 'subtitle', + 'status', + 'experimental', + 'subject', + 'date', + 'publisher', + 'contact', + 'description', + 'comment', + 'useContext', + 'jurisdiction', + 'purpose', + 'usage', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'library', + 'population', + 'exposure', + 'exposureAlternative', + 'outcome' + ], + ResearchElementDefinition: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'shortTitle', + 'subtitle', + 'status', + 'experimental', + 'subject', + 'date', + 'publisher', + 'contact', + 'description', + 'comment', + 'useContext', + 'jurisdiction', + 'purpose', + 'usage', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'library', + 'type', + 'variableType', + 'characteristic' + ], + ResearchStudy: [ + 'identifier', + 'title', + 'protocol', + 'partOf', + 'status', + 'primaryPurposeType', + 'phase', + 'category', + 'focus', + 'condition', + 'contact', + 'relatedArtifact', + 'keyword', + 'location', + 'description', + 'enrollment', + 'period', + 'sponsor', + 'principalInvestigator', + 'site', + 'reasonStopped', + 'note', + 'arm', + 'objective' + ], + ResearchSubject: ['identifier', 'status', 'period', 'study', 'individual', 'assignedArm', 'actualArm', 'consent'], + RiskAssessment: [ + 'identifier', + 'basedOn', + 'parent', + 'status', + 'method', + 'code', + 'subject', + 'encounter', + 'occurrence', + 'condition', + 'performer', + 'reasonCode', + 'reasonReference', + 'basis', + 'prediction', + 'mitigation', + 'note' + ], + RiskEvidenceSynthesis: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'status', + 'date', + 'publisher', + 'contact', + 'description', + 'note', + 'useContext', + 'jurisdiction', + 'copyright', + 'approvalDate', + 'lastReviewDate', + 'effectivePeriod', + 'topic', + 'author', + 'editor', + 'reviewer', + 'endorser', + 'relatedArtifact', + 'synthesisType', + 'studyType', + 'population', + 'exposure', + 'outcome', + 'sampleSize', + 'riskEstimate', + 'certainty' + ], + Schedule: [ + 'identifier', + 'active', + 'serviceCategory', + 'serviceType', + 'specialty', + 'actor', + 'planningHorizon', + 'comment' + ], + SearchParameter: [ + 'url', + 'version', + 'name', + 'derivedFrom', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'code', + 'base', + 'type', + 'expression', + 'xpath', + 'xpathUsage', + 'target', + 'multipleOr', + 'multipleAnd', + 'comparator', + 'modifier', + 'chain', + 'component' + ], + ServiceRequest: [ + 'identifier', + 'instantiatesCanonical', + 'instantiatesUri', + 'basedOn', + 'replaces', + 'requisition', + 'status', + 'intent', + 'category', + 'priority', + 'doNotPerform', + 'code', + 'orderDetail', + 'quantity', + 'subject', + 'encounter', + 'occurrence', + 'asNeeded', + 'authoredOn', + 'requester', + 'performerType', + 'performer', + 'locationCode', + 'locationReference', + 'reasonCode', + 'reasonReference', + 'insurance', + 'supportingInfo', + 'specimen', + 'bodySite', + 'note', + 'patientInstruction', + 'relevantHistory' + ], + Slot: [ + 'identifier', + 'serviceCategory', + 'serviceType', + 'specialty', + 'appointmentType', + 'schedule', + 'status', + 'start', + 'end', + 'overbooked', + 'comment' + ], + Specimen: [ + 'identifier', + 'accessionIdentifier', + 'status', + 'type', + 'subject', + 'receivedTime', + 'parent', + 'request', + 'collection', + 'processing', + 'container', + 'condition', + 'note' + ], + SpecimenDefinition: ['identifier', 'typeCollected', 'patientPreparation', 'timeAspect', 'collection', 'typeTested'], + StructureDefinition: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'copyright', + 'keyword', + 'fhirVersion', + 'mapping', + 'kind', + 'abstract', + 'context', + 'contextInvariant', + 'type', + 'baseDefinition', + 'derivation', + 'snapshot', + 'differential' + ], + StructureMap: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'copyright', + 'structure', + 'import', + 'group' + ], + Subscription: ['status', 'contact', 'end', 'reason', 'criteria', 'error', 'channel'], + Substance: ['identifier', 'status', 'category', 'code', 'description', 'instance', 'ingredient'], + SubstanceNucleicAcid: ['sequenceType', 'numberOfSubunits', 'areaOfHybridisation', 'oligoNucleotideType', 'subunit'], + SubstancePolymer: ['class', 'geometry', 'copolymerConnectivity', 'modification', 'monomerSet', 'repeat'], + SubstanceProtein: ['sequenceType', 'numberOfSubunits', 'disulfideLinkage', 'subunit'], + SubstanceReferenceInformation: ['comment', 'gene', 'geneElement', 'classification', 'target'], + SubstanceSourceMaterial: [ + 'sourceMaterialClass', + 'sourceMaterialType', + 'sourceMaterialState', + 'organismId', + 'organismName', + 'parentSubstanceId', + 'parentSubstanceName', + 'countryOfOrigin', + 'geographicalLocation', + 'developmentStage', + 'fractionDescription', + 'organism', + 'partDescription' + ], + SubstanceSpecification: [ + 'identifier', + 'type', + 'status', + 'domain', + 'description', + 'source', + 'comment', + 'moiety', + 'property', + 'referenceInformation', + 'structure', + 'code', + 'name', + 'molecularWeight', + 'relationship', + 'nucleicAcid', + 'polymer', + 'protein', + 'sourceMaterial' + ], + SupplyDelivery: [ + 'identifier', + 'basedOn', + 'partOf', + 'status', + 'patient', + 'type', + 'suppliedItem', + 'occurrence', + 'supplier', + 'destination', + 'receiver' + ], + SupplyRequest: [ + 'identifier', + 'status', + 'category', + 'priority', + 'item', + 'quantity', + 'parameter', + 'occurrence', + 'authoredOn', + 'requester', + 'supplier', + 'reasonCode', + 'reasonReference', + 'deliverFrom', + 'deliverTo' + ], + Task: [ + 'identifier', + 'instantiatesCanonical', + 'instantiatesUri', + 'basedOn', + 'groupIdentifier', + 'partOf', + 'status', + 'statusReason', + 'businessStatus', + 'intent', + 'priority', + 'code', + 'description', + 'focus', + 'for', + 'encounter', + 'executionPeriod', + 'authoredOn', + 'lastModified', + 'requester', + 'performerType', + 'owner', + 'location', + 'reasonCode', + 'reasonReference', + 'insurance', + 'note', + 'relevantHistory', + 'restriction', + 'input', + 'output' + ], + TerminologyCapabilities: [ + 'url', + 'version', + 'name', + 'title', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'copyright', + 'kind', + 'software', + 'implementation', + 'lockedDate', + 'codeSystem', + 'expansion', + 'codeSearch', + 'validateCode', + 'translation', + 'closure' + ], + TestReport: [ + 'identifier', + 'name', + 'status', + 'testScript', + 'result', + 'score', + 'tester', + 'issued', + 'participant', + 'setup', + 'test', + 'teardown' + ], + TestScript: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'purpose', + 'copyright', + 'origin', + 'destination', + 'metadata', + 'fixture', + 'profile', + 'variable', + 'setup', + 'test', + 'teardown' + ], + ValueSet: [ + 'url', + 'identifier', + 'version', + 'name', + 'title', + 'status', + 'experimental', + 'date', + 'publisher', + 'contact', + 'description', + 'useContext', + 'jurisdiction', + 'immutable', + 'purpose', + 'copyright', + 'compose', + 'expansion' + ], + VerificationResult: [ + 'target', + 'targetLocation', + 'need', + 'status', + 'statusDate', + 'validationType', + 'validationProcess', + 'frequency', + 'lastPerformed', + 'nextScheduled', + 'failureAction', + 'primarySource', + 'attestation', + 'validator' + ], + VisionPrescription: [ + 'identifier', + 'status', + 'created', + 'patient', + 'encounter', + 'dateWritten', + 'prescriber', + 'lensSpecification' + ] +}; diff --git a/src/helpers/DataRequirementHelpers.ts b/src/helpers/DataRequirementHelpers.ts index 01d2c7cc..8dbb9684 100644 --- a/src/helpers/DataRequirementHelpers.ts +++ b/src/helpers/DataRequirementHelpers.ts @@ -1,10 +1,18 @@ import { Extension } from 'fhir/r4'; -import { CalculationOptions, DataTypeQuery, DRCalculationOutput } from '../types/Calculator'; +import { CalculationOptions, DataTypeQuery, DRCalculationOutput, ExpressionStackEntry } from '../types/Calculator'; import { GracefulError } from '../types/errors/GracefulError'; import { EqualsFilter, InFilter, DuringFilter, codeFilterQuery, AttributeFilter } from '../types/QueryFilterTypes'; import { PatientParameters } from '../compartment-definition/PatientParameters'; import { SearchParameters } from '../compartment-definition/SearchParameters'; -import { ELM, ELMIdentifier } from '../types/ELMTypes'; +import { + AnyELMExpression, + ELM, + ELMFunctionRef, + ELMIdentifier, + ELMProperty, + ELMQuery, + ELMStatement +} from '../types/ELMTypes'; import { ExtractedLibrary } from '../types/CQLTypes'; import * as Execution from '../execution/Execution'; import { UnexpectedResource } from '../types/errors/CustomErrors'; @@ -16,10 +24,13 @@ import { parseQueryInfo } from './elm/QueryFilterParser'; import * as RetrievesHelper from './elm/RetrievesHelper'; -import { uniqBy } from 'lodash'; +import { uniqBy, isEqual, union } from 'lodash'; import { DateTime, Interval } from 'cql-execution'; import { parseTimeStringAsUTC } from '../execution/ValueSetHelper'; import * as MeasureBundleHelpers from './MeasureBundleHelpers'; +import { findLibraryReference } from './elm/ELMDependencyHelpers'; +import { findClauseInLibrary, findNamedClausesInExpression } from './elm/ELMHelpers'; +import { parsedPropertyPaths } from '../code-attributes/propertyPaths'; const FHIR_QUERY_PATTERN_URL = 'http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-fhirQueryPattern'; /** @@ -71,16 +82,54 @@ export async function getDataRequirements( await Promise.all(allRetrievesPromises); + // add main code path as a mustSupport + allRetrieves.forEach(retrieve => { + if (retrieve.path) { + if (retrieve.mustSupport) { + retrieve.mustSupport?.push(retrieve.path); + } else { + retrieve.mustSupport = [retrieve.path]; + } + } + }); + // add property must supports + rootLib.library.statements.def.forEach(statement => { + if (statement.expression && statement.name != 'Patient') { + addMustSupport(allRetrieves, statement, rootLib, elmJSONs); + } + }); + const results: fhir4.Library = { resourceType: 'Library', type: { coding: [{ code: 'module-definition', system: 'http://terminology.hl7.org/CodeSystem/library-type' }] }, status: 'unknown' }; + + // Combine must supports if there are data requirements from the same retrieve that have different mustSupports + // Combine based on this set defining uniqueness: templateId, dataType, path, and code/valueSet + const retrievesHash = allRetrieves.reduce((hash: Record, retrieve) => { + const hashKey = `${retrieve.templateId}-${retrieve.dataType}-${retrieve.path}-${ + retrieve.code?.code || retrieve.valueSet + }`; + if (hash[hashKey]) { + if (hash[hashKey].mustSupport) { + // combine + hash[hashKey].mustSupport = union(hash[hashKey].mustSupport, retrieve.mustSupport); + } else { + hash[hashKey].mustSupport = retrieve.mustSupport; + } + } else { + hash[hashKey] = retrieve; + } + return hash; + }, {}); + results.dataRequirement = uniqBy( - allRetrieves.map(retrieve => { + Object.values(retrievesHash).map(retrieve => { const dr = generateDataRequirement(retrieve); addFiltersToDataRequirement(retrieve, dr, withErrors); addFhirQueryPatternToDataRequirements(dr); + dr.mustSupport = retrieve.mustSupport; return dr; }), JSON.stringify @@ -92,7 +141,7 @@ export async function getDataRequirements( cql: cqls, elm: elmJSONs, gaps: { - retrieves: allRetrieves + retrieves: Object.values(retrievesHash) } }, withErrors @@ -411,3 +460,338 @@ function didEncounterDetailedValueFilterErrors(tbd: fhir4.Extension | GracefulEr return false; } } + +// addMustSupport: find any fields as part of this statement.expression, +// then search the allRetrieves for that field's context, and add the field to the correct retrieve's mustSupport +function addMustSupport(allRetrieves: DataTypeQuery[], statement: ELMStatement, rootLib: ELM, allELM: ELM[]) { + const propertyExpressions = findPropertyExpressions(statement, [], rootLib, allELM); + + propertyExpressions.forEach(prop => { + // find all matches for this property in allRetrieves + const retrieveMatches = findRetrieveMatches(prop, allRetrieves, allELM); + // add mustSupport for each match (if not already included) + retrieveMatches.forEach(match => { + // double check that the property is applicable for the retrieve type before adding is as a mustSupport + if (match.dataType in parsedPropertyPaths && parsedPropertyPaths[match.dataType].includes(prop.property.path)) { + if (match.mustSupport) { + if (!match.mustSupport.includes(prop.property.path)) { + match.mustSupport.push(prop.property.path); + } + } else { + match.mustSupport = [prop.property.path]; + } + } + }); + }); +} + +export interface PropertyTracker { + property: ELMProperty; + stack: ExpressionStackEntry[]; +} + +/** + * recurses across all key/values in an ELM tree structure + * finds values with type 'Property' and assumes they are ELMProperty type objects + * + * @param exp the current expression (top node) of the tree to search for Properties + * @param currentStack stack entries that led to this expression (not including this expression) + * @param lib library context for this expression + * @param allLib all elm libraries + * @returns array of all properties found in this expression's tree + */ +export function findPropertyExpressions( + exp: object, + currentStack: ExpressionStackEntry[], + lib: ELM, + allLib: ELM[] +): PropertyTracker[] { + if (typeof exp !== 'object') { + return []; + } + // ... only do this for objects TODO next + const thisStackEntry: ExpressionStackEntry = { + type: 'type' in exp && exp.type ? (exp.type as string) : 'unknown', + localId: 'localId' in exp && exp.localId ? (exp.localId as string) : 'unknown', + libraryName: lib.library.identifier.id + }; + + if ('type' in exp && exp.type && exp.type === 'Property') { + // base case found property expression + const prop = exp as ELMProperty; + if (prop.source) { + // add this expression to current stack before recursing on .source + return [ + { property: prop, stack: currentStack }, + ...findPropertyExpressions(prop.source, currentStack.concat([thisStackEntry]), lib, allLib) + ]; + } else { + return [{ property: prop, stack: currentStack }]; + } + } else if ( + 'type' in exp && + exp.type && + (exp.type === 'FunctionRef' || exp.type === 'ExpressionRef') && + 'name' in exp && + exp.name + ) { + // handle references that go to different libraries + + // TODO: do we have to worry about ParameterRef as well? + let operandProperties: PropertyTracker[] = []; + if ('operand' in exp && exp.operand) { + operandProperties = findPropertyExpressions(exp.operand, currentStack.concat([thisStackEntry]), lib, allLib); + if (operandProperties.length > 0) { + const idProperty = operandProperties.find(p => p.property.path === 'id'); + if (!idProperty) { + // if we find the property(s) in the operand, we can short-circuit the search without going to a different library + // unless it's an "id" property, in which case it may just be a reference finding operation + return operandProperties; + } + } + } + let newLib; + // find new lib if libraryName exists, otherwise use current lib + if ('libraryName' in exp && exp.libraryName) { + newLib = findLibraryReference(lib, allLib, exp.libraryName as string); + if (!newLib) { + throw new UnexpectedResource(`Cannot Find Referenced Library: ${exp.libraryName}`); + } + } else { + newLib = lib; + } + const newExp = findNameinLib(exp.name as string, newLib); + if (!newExp) { + // If we can't uniquely identify the reference, warn and explode immediate expression in current library context + console.warn( + `Issue with searching for properties within ${exp.name} in library ${lib.library.identifier.id}. Could not identify reference because it is overloaded or doesn't exist.` + ); + return findPropertyExpressions(Object.values(exp), currentStack.concat([thisStackEntry]), lib, allLib); + } + return operandProperties.concat( + findPropertyExpressions(newExp, currentStack.concat([thisStackEntry]), newLib, allLib) + ); + } else if (Array.isArray(exp)) { + return exp.flatMap(elem => findPropertyExpressions(elem, currentStack, lib, allLib)); + } else { + // non property object, recurse all children values + return findPropertyExpressions(Object.values(exp), currentStack.concat([thisStackEntry]), lib, allLib); + } +} + +// find the expression in this library that matches the passed name +// if there are issues uniquely identifying one, return null +export function findNameinLib(name: string, lib: ELM): AnyELMExpression | ELMStatement | null { + // search statements first and return expression + const namedStatement = lib.library.statements.def.filter(statement => statement.name === name); + if (namedStatement.length > 0) { + if (namedStatement.length === 1) { + return namedStatement[0]; + } else { + // if multiple come up, then it's overloaded (we can't handle) + return null; + } + } + + // search expressions in statements + const foundNames = lib.library.statements.def.flatMap(statement => + findNamedClausesInExpression(statement.expression, name) + ); + if (foundNames.length !== 1) { + // if multiple come up, then it's overloaded (we can't handle). If 0 come up, it's ill-formed. + return null; + } + // TODO: fix statement return (don't want to search annotations) + return foundNames[0]; +} + +// search retrieves for any that match this property's stack and alias context +export function findRetrieveMatches(prop: PropertyTracker, retrieves: DataTypeQuery[], allELM: ELM[]): DataTypeQuery[] { + // basic checks that the property is matchable + if (prop.property.source) { + // source must have operandref and name (TODO: check this is true) + if (prop.property.source.type !== 'OperandRef' || !('name' in prop.property.source) || !prop.property.source.name) + return []; + } else { + // must have either source or scope + if (!prop.property.scope) return []; + } + + return retrieves.filter(retrieve => { + const stackMatch = prop.stack.findLast(ps => { + // find the last property stack entry that matches any entry in the retrieve stack + return retrieve.expressionStack?.some( + rs => isEqual(ps, rs) //test object equality + ); + }); + + if (stackMatch) { + if (stackMatch?.type === 'Or' || stackMatch?.type === 'And' || stackMatch?.type === 'Exists') { + return false; + } + const matchIdx = prop.stack.findIndex( + s => s.localId === stackMatch.localId && s.libraryName === stackMatch.libraryName + ); + const retMatchIdx = retrieve.expressionStack?.findIndex( + s => s.localId === stackMatch.localId && s.libraryName === stackMatch.libraryName + ); + if (prop.property.scope) { + // travel the stack looking for nearest queries (limited by stackMatch) + const scopedQuery = findStackScopedQuery(prop.stack.slice(matchIdx), prop.property.scope, allELM); + + if (!scopedQuery) return false; + const { query, position, source } = scopedQuery; + // if the query is our stackMatch, stop here, otherwise continue with query source + if (position === 0 && 'alias' in source && source.alias && retrieve.expressionStack) { + //TODO: combine this and the if below??? or just get rid of position 0? + // confirm alias matches scope (i.e. follow the alias down the retrieve stack) + return checkRetrieveStackForAlias(retrieve.expressionStack.slice(retMatchIdx), source.alias, allELM); + } + if ('name' in source.expression && source.expression.name) { + // TODO: do we need any further checks here with the highest reference name? + return !!checkStackFunctionDefs( + prop.stack.slice(matchIdx, matchIdx + position), + source.expression.name, + allELM + ); + } + return false; + } else { + // assume property.source (checked above) + if (prop.property.source && 'name' in prop.property.source && prop.property.source.name) { + // slice property stack from stackMatch id to end + const stackSlice = prop.stack.slice(matchIdx); + const checkName = checkStackFunctionDefs(stackSlice, prop.property.source.name, allELM); + if (checkName) { + const retSlice = retrieve.expressionStack?.slice(retMatchIdx); + const topAlias = retSlice + ?.map(se => { + const exp = expressionFromStackEntry(stackMatch, allELM); + return 'alias' in exp && exp.alias ? exp.alias : undefined; + }) + .find(a => !!a); + + if (topAlias) { + return topAlias === checkName; + } + return true; + } + } + return false; + } + } + return false; + }); +} + +// traverse down a retrieve stack, checking query sources and traversing expression references all the way to the retrieve +// currently checks first query and whether a with/without invalidates a contained retrieve, TODO: may need further checks down the stack +export function checkRetrieveStackForAlias(stack: ExpressionStackEntry[], alias: string, allELM: ELM[]) { + for (let i = 0; i < stack.length - 1; i++) { + if (stack[i].type === 'Query') { + const query = expressionFromStackEntry(stack[i], allELM) as ELMQuery; + // search source or relationship next stack expression + const sourceMatch = query.source.find(s => s.expression.localId === stack[i + 1].localId); + const relationshipMatch = query.relationship?.find(r => r.localId === stack[i + 1].localId); + const expRefChange = + stack[i + 1].type === 'ExpressionRef' && stack.find(se => se.type === 'With' || se.type === 'Without'); + if (sourceMatch) { + return sourceMatch.alias === alias && !expRefChange; + } else if (relationshipMatch) { + return relationshipMatch.alias === alias && !expRefChange; + } + } + } + return true; +} + +// traverse from end of the stack to find the nearest query that has alias labeled with the passed scope +export function findStackScopedQuery(stack: ExpressionStackEntry[], scope: string, allELM: ELM[]) { + for (let i = stack.length - 1; i >= 0; i--) { + // ... should stop if it sees an expression ref + if (stack[i].type === 'Query') { + if (stack[i].localId === 'unknown' || stack[i].libraryName === 'unknown') { + // TODO: how do we handle this case (example AI&F query below localId 180 has no localId) + continue; + } + const queryExpression = expressionFromStackEntry(stack[i], allELM) as ELMQuery; + const source = queryExpression.source.find(s => s.alias === scope); + if (source) { + return { query: queryExpression, position: i, source: source }; + } + const letClause = queryExpression.let?.find(lc => lc.identifier === scope); + if (letClause) { + return { query: queryExpression, position: i, source: letClause }; + } + const relationshipClause = queryExpression.relationship?.find(r => r.alias === scope); + if (relationshipClause) { + return { query: queryExpression, position: i, source: relationshipClause }; + } + } + } + return null; +} + +// Pull actual expression from stack entry information. Assumes stack entry information exists in libraries (otherwise error) +export function expressionFromStackEntry(stackEntry: ExpressionStackEntry, allELM: ELM[]) { + const lib = allELM.find(e => e.library.identifier.id === stackEntry.libraryName); + if (!lib) { + throw Error(`Could not find library with identifier ${stackEntry.libraryName}`); + } + const expression = findClauseInLibrary(lib, stackEntry.localId); + if (!expression) { + throw Error( + `Could not find ${stackEntry.type} type expression in ${stackEntry.libraryName} with localId ${stackEntry.localId}` + ); + } + return expression; +} + +// traverse from end of the stack to check all function definitions use name in operand +//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) +export function checkStackFunctionDefs(stack: ExpressionStackEntry[], name: string, allELM: ELM[]) { + let checkName = name; + // return false if no function defs + if (!stack.find(s => s.type === 'FunctionDef')) return false; + + for (let i = stack.length - 1; i > 0; i--) { + if (stack[i].type === 'FunctionDef') { + const lib = allELM.find(e => e.library.identifier.id === stack[i].libraryName); + const functionStatement = lib?.library.statements.def.find(s => s.localId === stack[i].localId); + if (!functionStatement) { + throw Error( + `Unable to find function definition statement with localId ${stack[i].localId} in library ${stack[i].libraryName}` + ); + } + if (!checkFunctionDefMatch(functionStatement, checkName)) { + // we've hit an operand missmatch, so it's a deadend that should be ignored + return null; + } + // get new name + if (stack[i - 1].type === 'FunctionRef' && stack[i - 1].localId !== 'unknown') { + const functionRef = expressionFromStackEntry(stack[i - 1], allELM) as ELMFunctionRef; + // find first operand with a name. TODO: do we have any other differentiating factors for finding the right operand? + const operand = functionRef.operand.find(o => 'name' in o && o.name); + if (operand && 'name' in operand && operand.name) checkName = operand.name; + } + } + } + // final checkName can be used for further checks + return checkName; +} + +// return true if function def operand matches the passed source name +export function checkFunctionDefMatch(statement: ELMStatement, name: string) { + if (Array.isArray(statement.operand)) { + return statement.operand.find(o => 'name' in o && o.name && o.name === name); + } else { + return 'name' in statement.operand && statement.operand.name && statement.operand.name === name; + } +} + +// Special case TODO: function ref madness +// Special case TODO 2: expression ref layers (pair and debug cases with Hoss) +// Special case TODO 3: last of... means that matching up the source will require special handling + +// ... start making unit tests (cql to elm test data) +// think about what else needs to be tested (i.e. which create function to identify which retrieve is providing the results for an expression ref) diff --git a/src/helpers/elm/ELMHelpers.ts b/src/helpers/elm/ELMHelpers.ts index 737f60a3..378a87ca 100644 --- a/src/helpers/elm/ELMHelpers.ts +++ b/src/helpers/elm/ELMHelpers.ts @@ -36,9 +36,28 @@ export function findClauseInExpression(expression: any, localId: string): ELMExp } } return null; - } else if (expression.localId == localId) { + } else if (expression.localId === localId) { return expression as ELMExpression; } else { return findClauseInExpression(Object.values(expression), localId); } } + +/** + * Recursively search an ELM tree for all expression (clause) with a given name + * + * @param expression The expression tree to search for the clause in. + * @param name The name to look for. + * @returns The expression if found or null. + */ +export function findNamedClausesInExpression(expression: any, name: string): ELMExpression[] { + if (typeof expression === 'string' || typeof expression === 'number' || typeof expression === 'boolean') { + return []; + } else if (Array.isArray(expression)) { + return expression.flatMap(elem => findNamedClausesInExpression(elem, name)); + } else if (expression.name === name) { + return [expression as ELMExpression]; + } else { + return findNamedClausesInExpression(Object.values(expression), name); + } +} diff --git a/src/helpers/elm/QueryFilterParser.ts b/src/helpers/elm/QueryFilterParser.ts index e6e1664f..df32f5e7 100644 --- a/src/helpers/elm/QueryFilterParser.ts +++ b/src/helpers/elm/QueryFilterParser.ts @@ -233,7 +233,7 @@ function replaceAliasesInFilters(filter: AnyFilter, match: string, replace: stri */ function parseSources(query: ELMQuery): SourceInfo[] { const sources: SourceInfo[] = []; - const querySources = [...query.source, ...query.relationship]; + const querySources = [...query.source, ...(query.relationship || [])]; querySources.forEach(source => { if (source.expression.type == 'Retrieve') { diff --git a/src/helpers/elm/RetrievesHelper.ts b/src/helpers/elm/RetrievesHelper.ts index 2a3f0734..c65224c5 100644 --- a/src/helpers/elm/RetrievesHelper.ts +++ b/src/helpers/elm/RetrievesHelper.ts @@ -209,6 +209,13 @@ export function findRetrieves( } query.relationship?.forEach(relationshipClause => { + if (relationshipClause.localId) { + recursiveOpts.expressionStack.push({ + libraryName: elm.library.identifier.id, + localId: relationshipClause.localId, + type: relationshipClause.type + }); + } recurse(results, relationshipClause.expression, recursiveOpts); recurse(results, relationshipClause.suchThat, recursiveOpts); }); diff --git a/src/types/Calculator.ts b/src/types/Calculator.ts index 8b007950..51266dc4 100644 --- a/src/types/Calculator.ts +++ b/src/types/Calculator.ts @@ -297,6 +297,8 @@ export interface DataTypeQuery { queryInfo?: QueryInfo; /** specifies an optional template/profile for the objects that the retrieve returns to conform to */ templateId?: string; + /** array of fields that must be supported in association with this retrieve */ + mustSupport?: string[]; } export interface GapsDataTypeQuery extends DataTypeQuery { diff --git a/src/types/ELMTypes.ts b/src/types/ELMTypes.ts index 3fca2c6c..3ac82a2f 100644 --- a/src/types/ELMTypes.ts +++ b/src/types/ELMTypes.ts @@ -234,7 +234,7 @@ export interface ELMQuery extends ELMExpression { type: 'Query'; source: ELMAliasedQuerySource[]; let?: ELMLetClause[]; - relationship: ELMRelationshipClause[]; + relationship?: ELMRelationshipClause[]; where?: AnyELMExpression; return?: ELMReturnClause; sort?: any; @@ -253,6 +253,7 @@ export interface ELMAliasedQuerySource { export interface ELMRelationshipClause extends ELMAliasedQuerySource { suchThat: AnyELMExpression; + type: string; } export interface ELMLetClause { diff --git a/test/unit/DataRequirementHelpers.test.ts b/test/unit/DataRequirementHelpers.test.ts index 21c01ce4..b12f6642 100644 --- a/test/unit/DataRequirementHelpers.test.ts +++ b/test/unit/DataRequirementHelpers.test.ts @@ -4,6 +4,7 @@ import { CalculationOptions, DataTypeQuery } from '../../src/types/Calculator'; import { DataRequirement } from 'fhir/r4'; import { DateTime, Interval } from 'cql-execution'; import moment from 'moment'; +import { ELM, ELMAliasedQuerySource, ELMQuery } from '../../src'; describe('DataRequirementHelpers', () => { describe('generateDataRequirement', () => { @@ -308,4 +309,239 @@ describe('DataRequirementHelpers', () => { ); }); }); + describe('findPropertyExpressions', () => { + test('find properties in array', () => { + const expression = { + type: 'anyType', + localId: '0', + anyField: [ + { + localId: '1', + type: 'Property' + }, + { + localId: '2', + type: 'Other' + }, + { + localId: '3', + type: 'Property' + } + ] + }; + const elm: ELM = { + library: { + identifier: { + id: 'libraryId', + version: 'libraryVersion' + }, + schemaIdentifier: { + id: 'schemaId', + version: 'schemaVersion' + }, + usings: {}, + statements: { + def: [ + { + name: 'testStatement', + context: 'Patient', + expression: expression + } + ] + } + } + }; + const propertyExpressions = DataRequirementHelpers.findPropertyExpressions(expression, [], elm, [elm]); + const expectedPropertyExpressions = [ + { + property: { + localId: '1', + type: 'Property' + }, + stack: [ + { + type: 'anyType', + localId: '0', + libraryName: 'libraryId' + } + ] + }, + { + property: { + localId: '3', + type: 'Property' + }, + stack: [ + { + type: 'anyType', + localId: '0', + libraryName: 'libraryId' + } + ] + } + ]; + + expect(propertyExpressions).toEqual(expectedPropertyExpressions); + }); + + test('find properties in object', () => { + const expression = { + type: 'anyType', + localId: '0', + anyField1: { + localId: '1', + type: 'Property' + }, + anyField2: { + localId: '2', + type: 'Other' + }, + anyField3: { + localId: '3', + type: 'Property' + } + }; + const elm: ELM = { + library: { + identifier: { + id: 'libraryId', + version: 'libraryVersion' + }, + schemaIdentifier: { + id: 'schemaId', + version: 'schemaVersion' + }, + usings: {}, + statements: { + def: [ + { + name: 'testStatement', + context: 'Patient', + expression: expression + } + ] + } + } + }; + const propertyExpressions = DataRequirementHelpers.findPropertyExpressions(expression, [], elm, [elm]); + const expectedPropertyExpressions = [ + { + property: { + localId: '1', + type: 'Property' + }, + stack: [ + { + type: 'anyType', + localId: '0', + libraryName: 'libraryId' + } + ] + }, + { + property: { + localId: '3', + type: 'Property' + }, + stack: [ + { + type: 'anyType', + localId: '0', + libraryName: 'libraryId' + } + ] + } + ]; + + expect(propertyExpressions).toEqual(expectedPropertyExpressions); + }); + }); + + describe('findRetrieveMatches', () => { + test('simple retrieve match', () => { + const stack = [ + { + type: 'Query', + localId: '0', + libraryName: 'libraryId' + } + ]; + const retrieve: DataTypeQuery = { + dataType: 'fhir_type', + path: 'status', + templateId: 'http://hl7.org/fhir/StructureDefinition/fhir_type', + expressionStack: stack, + retrieveLocalId: '1' + }; + const property: DataRequirementHelpers.PropertyTracker = { + property: { + localId: '2', + type: 'Property', + path: 'status', + scope: 'TestScope' + }, + stack: stack + }; + const expression: ELMQuery = { + type: 'Query', + localId: '0', + source: [ + { + alias: 'TestScope', + expression: { + type: 'Retrieve', + localId: '1' + } + } + ], + relationship: [], + where: property.property + }; + const elm: ELM = { + library: { + identifier: { + id: 'libraryId', + version: 'libraryVersion' + }, + schemaIdentifier: { + id: 'schemaId', + version: 'schemaVersion' + }, + usings: {}, + statements: { + def: [ + { + name: 'testStatement', + context: 'Patient', + expression: expression + } + ] + } + } + }; + const expectedRetrieveMatches = [retrieve]; + const retrieveMatches = DataRequirementHelpers.findRetrieveMatches(property, [retrieve], [elm]); + expect(retrieveMatches).toEqual(expectedRetrieveMatches); + }); + }); + + describe('findSourceWithScope', () => { + test('simple source', () => { + const source: ELMAliasedQuerySource = { + expression: { + type: 'AnyType', + localId: '1' + }, + alias: 'testScope' + }; + const expression: ELMQuery = { + type: 'Query', + localId: '0', + relationship: [], + source: [source] + }; + + expect(DataRequirementHelpers.findSourcewithScope(expression, 'testScope')).toEqual(source); + }); + }); });