diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java index a5200fb1b..836c8ef99 100755 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java @@ -3472,23 +3472,96 @@ else if (classType.getPrimaryCodePath() != null) { // Only expand a choice-valued code path if no comparator is specified // Otherwise, a code comparator will always choose a specific representation + boolean hasFHIRHelpers = libraryInfo.resolveLibraryName("FHIRHelpers") != null; if (property != null && property.getResultType() instanceof ChoiceType && codeComparator == null) { for (DataType propertyType : ((ChoiceType)property.getResultType()).getTypes()) { - Retrieve retrieve = buildRetrieve(ctx, useStrictRetrieveTyping, namedType, classType, codePath, - codeComparator, property, propertyType, propertyException, terminology); - retrieves.add(retrieve); - retrieve.setResultType(new ListType((DataType) namedType)); + if (hasFHIRHelpers && propertyType instanceof NamedType && ((NamedType)propertyType).getSimpleName().equals("Reference") && namedType.getSimpleName().equals("MedicationRequest")) { + // TODO: This is a model-specific special case to support QICore + // This functionality needs to be generalized to a retrieve mapping in the model info + // But that requires a model info change (to represent references, right now the model info only includes context relationships) + // The reference expands to [MedicationRequest] MR with [Medication] M such that M.id = Last(Split(MR.medication.reference, '/')) and M.code in + Retrieve mrRetrieve = buildRetrieve(ctx, useStrictRetrieveTyping, namedType, classType, null, null, null, null, null, null); + retrieves.add(mrRetrieve); + mrRetrieve.setResultType(new ListType((DataType) namedType)); + DataType mDataType = libraryBuilder.resolveTypeName(model, "Medication"); + ClassType mClassType = (ClassType)mDataType; + NamedType mNamedType = mClassType; + Retrieve mRetrieve = buildRetrieve(ctx, useStrictRetrieveTyping, mNamedType, mClassType, null, null, null, null, null, null); + retrieves.add(mRetrieve); + mRetrieve.setResultType(new ListType((DataType) namedType)); + Query q = of.createQuery(); + AliasedQuerySource aqs = of.createAliasedQuerySource().withExpression(mrRetrieve).withAlias("MR"); + track(aqs, ctx); + aqs.setResultType(aqs.getExpression().getResultType()); + q.getSource().add(aqs); + track(q, ctx); + q.setResultType(aqs.getResultType()); + With w = of.createWith().withExpression(mRetrieve).withAlias("M"); + track(w, ctx); + w.setResultType(w.getExpression().getResultType()); + q.getRelationship().add(w); + String idPath = "id"; + DataType idType = libraryBuilder.resolvePath(mDataType, idPath); + Property idProperty = libraryBuilder.buildProperty("M", idPath, false, idType); + String refPath = "medication.reference"; + DataType refType = libraryBuilder.resolvePath(dataType, refPath); + Property refProperty = libraryBuilder.buildProperty("MR", refPath, false, refType); + Split split = of.createSplit().withStringToSplit(refProperty).withSeparator(libraryBuilder.createLiteral("/")); + libraryBuilder.resolveCall("System", "Split", new SplitInvocation(split)); + Last last = of.createLast().withSource(split); + libraryBuilder.resolveCall("System", "Last", new LastInvocation(last)); + Equal e = of.createEqual().withOperand(idProperty, last); + libraryBuilder.resolveBinaryCall("System", "Equal", e); + + DataType mCodeType = libraryBuilder.resolvePath((DataType)mNamedType, "code"); + Property mProperty = of.createProperty().withPath("code"); + mProperty.setResultType(mCodeType); + String mCodeComparator = "~"; + if (terminology.getResultType() instanceof ListType) { + mCodeComparator = "in"; + + } + else if (libraryBuilder.isCompatibleWith("1.5")) { + mCodeComparator = terminology.getResultType().isSubTypeOf(libraryBuilder.resolveTypeName("System", "Vocabulary")) ? "in" : "~"; + } - if (result == null) { - result = retrieve; + Expression terminologyComparison = null; + if (mCodeComparator.equals("in")) { + terminologyComparison = libraryBuilder.resolveIn(mProperty, terminology); + } + else { + BinaryExpression equivalent = of.createEquivalent().withOperand(mProperty, terminology); + libraryBuilder.resolveBinaryCall("System", "Equivalent", equivalent); + terminologyComparison = equivalent; + } + And a = of.createAnd().withOperand(e, terminologyComparison); + libraryBuilder.resolveBinaryCall("System", "And", a); + w.withSuchThat(a); + + if (result == null) { + result = q; + } + else { + track(q, ctx); + result = libraryBuilder.resolveUnion(result, q); + } } else { - // Should only include the result if it resolved appropriately with the codeComparator - // Allowing it to go through for now - //if (retrieve.getCodeProperty() != null && retrieve.getCodeComparator() != null && retrieve.getCodes() != null) { + Retrieve retrieve = buildRetrieve(ctx, useStrictRetrieveTyping, namedType, classType, codePath, + codeComparator, property, propertyType, propertyException, terminology); + retrieves.add(retrieve); + retrieve.setResultType(new ListType((DataType) namedType)); + + if (result == null) { + result = retrieve; + } else { + // Should only include the result if it resolved appropriately with the codeComparator + // Allowing it to go through for now + //if (retrieve.getCodeProperty() != null && retrieve.getCodeComparator() != null && retrieve.getCodes() != null) { track(retrieve, ctx); result = libraryBuilder.resolveUnion(result, retrieve); - //} + //} + } } } } @@ -3520,7 +3593,7 @@ private Retrieve buildRetrieve(cqlParser.RetrieveContext ctx, boolean useStrictR retrieve.setContext(contextExpression); } - if (ctx.terminology() != null) { + if (terminology != null) { // Resolve the terminology target using an in or ~ operator try { if (codeComparator == null) { diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/qicore/v411/BaseTest.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/qicore/v411/BaseTest.java index 253b7b04f..6bb936588 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/qicore/v411/BaseTest.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/qicore/v411/BaseTest.java @@ -310,6 +310,44 @@ public void TestMedicationRequest() throws IOException { assertThat(r.getCodes(), instanceOf(ValueSetRef.class)); ValueSetRef vsr = (ValueSetRef)r.getCodes(); assertThat(vsr.getName(), is("Antithrombotic Therapy")); + + def = defs.get("Antithrombotic Therapy at Discharge (2)"); + assertThat(def, notNullValue()); + assertThat(def.getExpression(), instanceOf(Union.class)); + Union u = (Union)def.getExpression(); + assertThat(u.getOperand().size(), is(2)); + assertThat(u.getOperand().get(0), instanceOf(Retrieve.class)); + r = (Retrieve)u.getOperand().get(0); + assertThat(r.getTemplateId(), is("http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest")); + assertThat(r.getCodeProperty(), is("medication")); + assertThat(r.getCodeComparator(), is("in")); + assertThat(r.getCodes(), instanceOf(ValueSetRef.class)); + vsr = (ValueSetRef)r.getCodes(); + assertThat(vsr.getName(), is("Antithrombotic Therapy")); + + assertThat(u.getOperand().get(1), instanceOf(Query.class)); + q = (Query)u.getOperand().get(1); + assertThat(q.getSource().size(), is(1)); + assertThat(q.getSource().get(0).getExpression(), instanceOf(Retrieve.class)); + r = (Retrieve)q.getSource().get(0).getExpression(); + assertThat(r.getTemplateId(), is("http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest")); + assertThat(r.getCodeProperty() == null, is(true)); + assertThat(r.getCodes() == null, is(true)); + assertThat(q.getRelationship(), notNullValue()); + assertThat(q.getRelationship().size(), is(1)); + assertThat(q.getRelationship().get(0), instanceOf(With.class)); + With w = (With)q.getRelationship().get(0); + assertThat(w.getExpression(), instanceOf(Retrieve.class)); + r = (Retrieve)w.getExpression(); + assertThat(r.getTemplateId(), is("http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medication")); + assertThat(r.getCodeProperty() == null, is(true)); + assertThat(r.getCodes() == null, is(true)); + assertThat(w.getSuchThat(), instanceOf(And.class)); + And a = (And)w.getSuchThat(); + assertThat(a.getOperand().get(0), instanceOf(Equal.class)); + assertThat(a.getOperand().get(1), instanceOf(InValueSet.class)); + InValueSet ivs = (InValueSet)a.getOperand().get(1); + assertThat(ivs.getValueset().getName(), is("Antithrombotic Therapy")); } @Test diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/qicore/v411/TestMedicationRequest.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/qicore/v411/TestMedicationRequest.cql index 74667dc00..0b4a587da 100644 --- a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/qicore/v411/TestMedicationRequest.cql +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/qicore/v411/TestMedicationRequest.cql @@ -10,3 +10,6 @@ context Patient define "Antithrombotic Therapy at Discharge": ["MedicationRequest": medication in "Antithrombotic Therapy"] Antithrombotic + +define "Antithrombotic Therapy at Discharge (2)": + ["MedicationRequest": "Antithrombotic Therapy"] diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS645/CMS645-ModuleDefinitionLibrary.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS645/CMS645-ModuleDefinitionLibrary.json index 066627322..da827ef70 100644 --- a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS645/CMS645-ModuleDefinitionLibrary.json +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS645/CMS645-ModuleDefinitionLibrary.json @@ -36,5 +36,24 @@ "dataRequirement": [ { "type": "Patient", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ] + }, { + "extension": [ { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-relatedRequirement", + "extension": [ { + "url": "targetId", + "valueString": "G10002" + }, { + "url": "targetProperty", + "valueString": "medication" + } ] + } ], + "type": "Medication", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medication" ], + "mustSupport": [ "id" ] + }, { + "id": "G10002", + "type": "MedicationRequest", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest" ], + "mustSupport": [ "medication.reference" ] } ] } diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/FHIRReferencesRevisited.cql b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/FHIRReferencesRevisited.cql index 1056861af..46c8c6897 100644 --- a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/FHIRReferencesRevisited.cql +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/FHIRReferencesRevisited.cql @@ -203,3 +203,18 @@ define TestMedicationRequirement: define TestBoundDataRequirement: TestMedicationRequirement R where R.code in "Aspirin" + + +define TestChoiceMedicationRequest: + [MedicationRequest: "Aspirin"] + +// If the terminology target is a choice of a terminology-valued element and a reference + +define TestChoiceMedicationRequestExpanded: + [MedicationRequest: medication in "Aspirin"] + union ( + [MedicationRequest] MR + with [Medication] M + such that M.id = Last(Split(MR.medication.reference, '/')) + and M.code in "Aspirin" + ) \ No newline at end of file