From 7d311a4658c6835854082d0e02eaf0a8d5ddbe24 Mon Sep 17 00:00:00 2001 From: Piotr Szul Date: Wed, 25 Oct 2023 08:26:53 +1000 Subject: [PATCH] Refactoring: Implementing FHIRPahts for boolean, Quantity and Coding literals. --- .../extract/ExtractAggregatePOCViewTest.java | 48 +++++- .../au/csiro/pathling/fhirpath/Temporal.java | 2 +- .../collection/DateTimeCollection.java | 5 +- .../collection/QuantityCollection.java | 14 +- .../fhirpath/collection/TimeCollection.java | 5 +- .../fhirpath/parser/LiteralTermVisitor.java | 50 ++----- .../pathling/fhirpath/path/Literals.java | 141 ++++++++++++++++++ .../csiro/pathling/fhirpath/path/Paths.java | 19 --- 8 files changed, 210 insertions(+), 74 deletions(-) create mode 100644 fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Literals.java diff --git a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractAggregatePOCViewTest.java b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractAggregatePOCViewTest.java index 4aff7eb5f2..1c0896eb7a 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractAggregatePOCViewTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/extract/ExtractAggregatePOCViewTest.java @@ -222,7 +222,52 @@ void testExtractView() { System.out.println(resultDataset.queryExecution().optimizedPlan()); } - + + + @Test + void testExtractFGPDView() { + final List expressions = List.of( + //"name.family", + //"name.given", + "id" + ); + + final List filters = List.of( + "reverseResolve(Observation.subject).where(code.subsumedBy(http://snomed.info/sct|424144002)).exists(valueQuantity >= 18 'a' and valueQuantity <= 75 'a')", + "reverseResolve(MedicationRequest.subject).medicationCodeableConcept.subsumedBy(http://fhir.de/CodeSystem/bfarm/atc|A10B|2022).anyTrue()", + "reverseResolve(MedicationRequest.subject).medicationCodeableConcept.subsumedBy(http://fhir.de/CodeSystem/bfarm/atc|A10A|2022).anyTrue()", + "reverseResolve(Condition.subject).code.subsumedBy(http://fhir.de/CodeSystem/bfarm/icd-10-gm|E11).anyTrue()", + "reverseResolve(Observation.subject).where(code.subsumedBy(http://loinc.org|4548-4)).exists(valueQuantity >= 6 '%' and valueQuantity <= 10 '%')", + "reverseResolve(Condition.subject).code.subsumedBy(http://fhir.de/CodeSystem/bfarm/icd-10-gm|E10).allFalse()", + "reverseResolve(Condition.subject).code.subsumedBy(http://fhir.de/CodeSystem/bfarm/icd-10-gm|N17).allFalse()" + ); + + System.out.println("### Expressions: ###"); + expressions.forEach(System.out::println); + System.out.println("### Filters: ###"); + filters.forEach(System.out::println); + + + final ExtractRequest extractRequest = ExtractRequest.fromUserInput( + ResourceType.PATIENT, + Optional.of(expressions), + Optional.of(filters), + Optional.empty() + ); + + final QueryParser queryParser = new QueryParser(new Parser()); + final ExtractView extractView = queryParser.toView(extractRequest); + System.out.println("## Extract view ##"); + extractView.printTree(); + final Dataset resultDataset = extractView.evaluate(newContext()); + resultDataset.show(false); + System.out.println(resultDataset.logicalPlan()); + System.out.println(resultDataset.queryExecution().executedPlan()); + + System.out.println(resultDataset.queryExecution().optimizedPlan()); + + } + @Test void testAggregation() { @@ -439,7 +484,6 @@ void mockResource(final ResourceType... resourceTypes) { } - @Test void testExtractViewWithReverseResolve() { diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java index 62fbe5673d..67a330dda6 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/Temporal.java @@ -84,7 +84,7 @@ static Function buildDateArithmeticOperation( final Column valueColumn = functions.callUDF(functionName, source.getColumn(), target.getColumn()); - return DateTimeCollection.build(valueColumn, Optional.empty(), true); + return DateTimeCollection.build(valueColumn, Optional.empty()); }; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java index 1b281a2d18..0dfe964246 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/DateTimeCollection.java @@ -66,12 +66,11 @@ protected DateTimeCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use - * @param singular Whether the collection is singular * @return A new instance of {@link DateTimeCollection} */ @Nonnull public static DateTimeCollection build(@Nonnull final Column column, - @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { return new DateTimeCollection(column, Optional.of(FhirPathType.DATETIME), Optional.of(FHIRDefinedType.DATETIME), definition); } @@ -87,7 +86,7 @@ public static DateTimeCollection build(@Nonnull final Column column, public static DateTimeCollection fromLiteral(@Nonnull final String fhirPath) throws ParseException { final String dateString = fhirPath.replaceFirst("^@", ""); - return DateTimeCollection.build(lit(dateString), Optional.empty(), true); + return DateTimeCollection.build(lit(dateString), Optional.empty()); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java index cc9f8a3dac..2c84cb0c0d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/QuantityCollection.java @@ -59,7 +59,7 @@ public class QuantityCollection extends Collection implements Comparable, Numeri public QuantityCollection(@Nonnull final Column column, @Nonnull final Optional type, @Nonnull final Optional fhirType, - @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { super(column, type, fhirType, definition); } @@ -68,14 +68,13 @@ public QuantityCollection(@Nonnull final Column column, * * @param column The column to use * @param definition The definition to use - * @param singular Whether the collection is singular * @return A new instance of {@link QuantityCollection} */ @Nonnull public static QuantityCollection build(@Nonnull final Column column, - @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { return new QuantityCollection(column, Optional.of(FhirPathType.QUANTITY), - Optional.of(FHIRDefinedType.QUANTITY), definition, singular); + Optional.of(FHIRDefinedType.QUANTITY), definition); } /** @@ -122,7 +121,7 @@ public static QuantityCollection fromUcumString(@Nonnull final String fhirPath, public static QuantityCollection fromCalendarDurationString(@Nonnull final String fhirPath) { final Column column = QuantityEncoding.encodeLiteral(parseCalendarDuration(fhirPath)); - return QuantityCollection.build(column, Optional.empty(), true); + return QuantityCollection.build(column, Optional.empty()); } @Nonnull @@ -206,8 +205,7 @@ private static QuantityCollection buildLiteralPath(@Nonnull final BigDecimal dec quantity.setCode(unit); display.ifPresent(quantity::setUnit); - return QuantityCollection.build(QuantityEncoding.encodeLiteral(quantity), Optional.empty(), - true); + return QuantityCollection.build(QuantityEncoding.encodeLiteral(quantity), Optional.empty()); } @Nonnull @@ -265,7 +263,7 @@ public Function getMathOperation(@Nonnull final MathOperati final Column resultQuantityColumn = when(sourceContext.isNull().or(targetContext.isNull()), null).otherwise(validResult); - return QuantityCollection.build(resultQuantityColumn, Optional.empty(), true); + return QuantityCollection.build(resultQuantityColumn, Optional.empty()); }; } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java index 68593c9d0a..2f5e7d5c75 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/collection/TimeCollection.java @@ -51,12 +51,11 @@ public TimeCollection(@Nonnull final Column column, @Nonnull final Optional definition, final boolean singular) { + @Nonnull final Optional definition) { return new TimeCollection(column, Optional.of(FhirPathType.TIME), Optional.of(FHIRDefinedType.TIME), definition); } @@ -70,7 +69,7 @@ public static TimeCollection build(@Nonnull final Column column, @Nonnull public static TimeCollection fromLiteral(@Nonnull final String literal) { final String timeString = literal.replaceFirst("^@T", ""); - return TimeCollection.build(lit(timeString), Optional.empty(), true); + return TimeCollection.build(lit(timeString), Optional.empty()); } @Nonnull diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java index 597fb73ab0..540cb00169 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/LiteralTermVisitor.java @@ -19,17 +19,13 @@ import static java.util.Objects.requireNonNull; -import au.csiro.pathling.encoders.terminology.ucum.Ucum; import au.csiro.pathling.errors.InvalidUserInputError; import au.csiro.pathling.fhirpath.FhirPath; -import au.csiro.pathling.fhirpath.collection.BooleanCollection; -import au.csiro.pathling.fhirpath.collection.CodingCollection; import au.csiro.pathling.fhirpath.collection.Collection; import au.csiro.pathling.fhirpath.collection.DateCollection; import au.csiro.pathling.fhirpath.collection.DateTimeCollection; import au.csiro.pathling.fhirpath.collection.DecimalCollection; import au.csiro.pathling.fhirpath.collection.IntegerCollection; -import au.csiro.pathling.fhirpath.collection.QuantityCollection; import au.csiro.pathling.fhirpath.collection.TimeCollection; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.BooleanLiteralContext; @@ -41,13 +37,16 @@ import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.QuantityLiteralContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.StringLiteralContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.TimeLiteralContext; +import au.csiro.pathling.fhirpath.path.Literals.BooleanLiteral; +import au.csiro.pathling.fhirpath.path.Literals.CalendarDurationLiteral; +import au.csiro.pathling.fhirpath.path.Literals.CodingLiteral; +import au.csiro.pathling.fhirpath.path.Literals.StringLiteral; +import au.csiro.pathling.fhirpath.path.Literals.UcumQuantityLiteral; +import au.csiro.pathling.fhirpath.path.PureFhirPath; import java.text.ParseException; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import au.csiro.pathling.fhirpath.path.Paths.StringLiteral; -import au.csiro.pathling.fhirpath.path.PureFhirPath; import org.antlr.v4.runtime.tree.TerminalNode; -import org.fhir.ucum.UcumException; /** * This class deals with terms that are literal expressions. @@ -130,11 +129,7 @@ public PureFhirPath visitNumberLiteral( @Nonnull public PureFhirPath visitBooleanLiteral( @Nullable final BooleanLiteralContext ctx) { - requireNonNull(ctx); - @Nullable final String fhirPath = ctx.getText(); - requireNonNull(fhirPath); - - return (input, context) -> BooleanCollection.fromLiteral(fhirPath); + return new BooleanLiteral(requireNonNull(requireNonNull(ctx).getText())); } @Override @@ -151,38 +146,17 @@ public PureFhirPath visitQuantityLiteral( @Nullable final String number = ctx.quantity().NUMBER().getText(); requireNonNull(number); @Nullable final TerminalNode ucumUnit = ctx.quantity().unit().STRING(); - - return (input, context) -> { - if (ucumUnit == null) { - // Create a calendar duration literal. - final String fhirPath = String.format("%s %s", number, ctx.quantity().unit().getText()); - return QuantityCollection.fromCalendarDurationString(fhirPath); - } else { - // Create a UCUM Quantity literal. - final String fhirPath = String.format("%s %s", number, ucumUnit.getText()); - try { - return QuantityCollection.fromUcumString(fhirPath, Ucum.service()); - } catch (final UcumException e) { - throw new RuntimeException(e); - } - } - }; + return (ucumUnit == null) + ? new CalendarDurationLiteral( + String.format("%s %s", number, ctx.quantity().unit().getText())) + : new UcumQuantityLiteral(String.format("%s %s", number, ucumUnit.getText())); } @Override @Nonnull public PureFhirPath visitCodingLiteral( @Nullable final CodingLiteralContext ctx) { - @Nullable final String fhirPath = requireNonNull(ctx).getText(); - requireNonNull(fhirPath); - - return (input, context) -> { - try { - return CodingCollection.fromLiteral(fhirPath); - } catch (final IllegalArgumentException e) { - throw new InvalidUserInputError(e.getMessage(), e); - } - }; + return new CodingLiteral(requireNonNull(requireNonNull(ctx).getText())); } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Literals.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Literals.java new file mode 100644 index 0000000000..6032c55ce4 --- /dev/null +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Literals.java @@ -0,0 +1,141 @@ +/* + * Copyright 2023 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package au.csiro.pathling.fhirpath.path; + +import au.csiro.pathling.encoders.terminology.ucum.Ucum; +import au.csiro.pathling.errors.InvalidUserInputError; +import au.csiro.pathling.fhirpath.EvaluationContext; +import au.csiro.pathling.fhirpath.collection.BooleanCollection; +import au.csiro.pathling.fhirpath.collection.CodingCollection; +import au.csiro.pathling.fhirpath.collection.Collection; +import au.csiro.pathling.fhirpath.collection.QuantityCollection; +import au.csiro.pathling.fhirpath.collection.StringCollection; +import lombok.Value; +import org.fhir.ucum.UcumException; +import javax.annotation.Nonnull; + +public final class Literals { + + private Literals() { + } + + @Value + public static class StringLiteral implements PureFhirPath { + + @Nonnull + String value; + + @Override + public StringCollection apply(@Nonnull final Collection input, + @Nonnull final EvaluationContext context) { + return StringCollection.fromLiteral(value); + } + + @Nonnull + @Override + public String toExpression() { + // TODO: use a better conversion + return "'" + value + "'"; + } + } + + @Value + public static class BooleanLiteral implements PureFhirPath { + + @Nonnull + String value; + + @Override + public BooleanCollection apply(@Nonnull final Collection input, + @Nonnull final EvaluationContext context) { + return BooleanCollection.fromLiteral(value); + } + + @Nonnull + @Override + public String toExpression() { + return value; + } + } + + @Value + public static class CodingLiteral implements PureFhirPath { + + @Nonnull + String value; + + @Override + public CodingCollection apply(@Nonnull final Collection input, + @Nonnull final EvaluationContext context) { + try { + return CodingCollection.fromLiteral(value); + } catch (final IllegalArgumentException e) { + throw new InvalidUserInputError(e.getMessage(), e); + } + } + + @Nonnull + @Override + public String toExpression() { + return value; + } + } + + @Value + public static class CalendarDurationLiteral implements PureFhirPath { + + @Nonnull + String value; + + @Override + public QuantityCollection apply(@Nonnull final Collection input, + @Nonnull final EvaluationContext context) { + return QuantityCollection.fromCalendarDurationString(value); + } + + @Nonnull + @Override + public String toExpression() { + return value; + } + } + + @Value + public static class UcumQuantityLiteral implements PureFhirPath { + + @Nonnull + String value; + + @Override + public QuantityCollection apply(@Nonnull final Collection input, + @Nonnull final EvaluationContext context) { + try { + return QuantityCollection.fromUcumString(value, Ucum.service()); + } catch (final UcumException e) { + throw new RuntimeException(e); + } + } + + @Nonnull + @Override + public String toExpression() { + return value; + } + } + +} diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Paths.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Paths.java index d0774dab95..ca31e2fd96 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Paths.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/path/Paths.java @@ -25,7 +25,6 @@ import au.csiro.pathling.fhirpath.FunctionInput; import au.csiro.pathling.fhirpath.TypeSpecifier; import au.csiro.pathling.fhirpath.collection.Collection; -import au.csiro.pathling.fhirpath.collection.StringCollection; import au.csiro.pathling.fhirpath.function.NamedFunction; import au.csiro.pathling.fhirpath.function.registry.FunctionRegistry.NoSuchFunctionException; import au.csiro.pathling.fhirpath.operator.BinaryOperator; @@ -161,22 +160,4 @@ public Collection apply(@Nonnull final Collection input, } } - @Value - public static class StringLiteral implements PureFhirPath { - - String value; - - @Override - public Collection apply(@Nonnull final Collection input, - @Nonnull final EvaluationContext context) { - return StringCollection.fromLiteral(value); - } - - @Nonnull - @Override - public String toExpression() { - // TODO: use a better conversion - return "'" + value + "'"; - } - } }